TensorFlowを使ってテキスト分類をしてみる。

前回はIrisデータセットを使ってTensorFlowのTutorialをやってみました。

www.pytry3g.com

今回もTensorFlowのTutorialを参考にしながら、テキスト分類をしてみます。

今回やるテキスト分類の目的は与えられたTweetサッカー日本代表に関するものなのか、正しく分類することです。データは自分で集めました。

モデルの生成と学習にはTensorFlowの高レベルAPIのtf.kerasを使います。

 

環境

MeCabWindowsにインストールするには

https://www.pytry3g.com/entry/2018/04/24/143934#Windows

  • JapaneseTextEncoder

テキストの前処理のところでJapaneseTextEncoderを使います。

詳細はこちら

関連リンク

Classify movie reviews: binary classification

ロシアW杯 日本代表について雑談ができる対話エージェントを作る(データ収集編)①

日本語のテキストコーパスから辞書を作るライブラリを作りたい

データの用意

まずデータを集めます。データの集め方はこの記事と同じ方法で集めます。

今回は与えられたTweetサッカー日本代表についてのものなのかを分類することが目的なので、データを集める際にサッカー日本代表に関するキーワードを含むツイートを(positive)に、キーワードを含まないツイートを(negative)に設定します。

集めたデータは以下のようにサッカー日本代表に関するものはpositive.txtに、そうではないものはnegative.txtに改行区切りで書き込んでいます。

positive.txt

このメンツなら原口だな。
コロンビアってサッカーつよいんですか
そうなんか今日日本と試合するんしょ?

negative.txt

串カツよこせえええええええ
今は串カツとかより地震怖い😢
さらさら金髪マン裏山。

datasets.py

集めたデータは全部で742です。

この集めたデータから学習用データとテストデータを作り、JapaneseTextEncoderを使って辞書を作ります。

import codecs
from sklearn.model_selection import train_test_split
from text_encoder import JapaneseTextEncoder

def load_data():
    dataset = []
    with codecs.open("positive.txt", "r", "utf-8") as f:
        positive = f.read().splitlines()

    with codecs.open("negative.txt", "r", "utf-8") as f:
        negative = f.read().splitlines()

    for sentence in positive:
        dataset.append((sentence, 1))

    for sentence in negative:
        dataset.append((sentence, 0))

    sentences, labels = list(zip(*dataset))
    sentences, labels = list(sentences), list(labels)
    encoder = JapaneseTextEncoder(
        sentences,
        min_occurrences=1,
        append_eos=False
    )
    dataset = [encoder.encode(sentence) for sentence in sentences]
    train_x, test_x, train_t, test_t = train_test_split(dataset, labels, test_size=0.1)
    train_data, test_data = (train_x, train_t), (test_x, test_t)
    return train_data, test_data, encoder

データの中身

>>> train_x[0]
[3624, 62, 14, 97, 83, 52, 3625, 98, 844, 844, 3626, 2775, 13, 14, 253, 3101, 102, 14, 1236, 32, 836, 523, 5, 3627, 3345, 74, 75, 102, 3628, 423, 105, 2403, 108, 50, 14, 872, 1092, 13, 855, 3628, 423, 105, 63, 14, 223, 3347, 12, 2247, 236, 294, 140, 14]

JapaneseTextEncoderのdecodeを使えば、テキストに変換できます。

>>> encoder.decode(train_x[0])
帰らな。分かってるけど立ち上がれない・・😭💦帰ります。これ書いたら。けーじくんと林先生を見たらほんの少しだけ落ち着きました。ありがとうございます✨ほんの少しだけって。本当に落ち着いて💦大丈夫やから。

ベクトル変換

用意したデータは整数が集まったリストになっています。ニューラルネットワークにはtensor(ベクトル?)を渡す必要があるので、データをベクトルに変換します。

やり方としては主に2つの方法があります。

  1. データをone hot vectorに変換する方法。これはベクトルの値を0もしくは1で表現するものです。例えば、[2, 3]というリストがあった場合、n次元のベクトルの要素はインデックス2と3がの値となり、それ以外はすべての値となります(※nはデータセットの単語の数)。このやり方だと、データセットの単語の数が増えれば増えるほど次元が大きくなり、学習に時間がかかりメモリに高負荷がかかってしまいます。
  2. もう1つのやり方はpaddingです。この方法を使うと1の方法と比べて次元を小さくすることができます。

今回はTutorialに従って、方法2でベクトル変換します。

import datasets
import numpy as np
import tensorflow as tf
from tensorflow import keras
from reserved_tokens import PADDING_INDEX

(train_x, train_t), (test_x, test_t), encoder = datasets.load_data()
train_x = keras.preprocessing.sequence.pad_sequences(train_x,
                                                     value=PADDING_INDEX,
                                                     padding="post",
                                                     maxlen=128)
test_x = keras.preprocessing.sequence.pad_sequences(test_x,
                                                    value=PADDING_INDEX,
                                                    padding="post",
                                                    maxlen=128)

このプログラムはまず必要なライブラリをインポートします。(※reserved_tokensについてはこちら。)

学習

モデルを作る

n_vocab = len(encoder.vocab)
model = keras.Sequential()
model.add(keras.layers.Embedding(n_vocab, 16))
model.add(keras.layers.GlobalAveragePooling1D())
model.add(keras.layers.Dense(16, activation=tf.nn.relu))
model.add(keras.layers.Dense(1, activation=tf.nn.sigmoid))

学習の設定

model.compile(optimizer=tf.train.AdamOptimizer(),
              loss="binary_crossentropy",
              metrics=["accuracy"])

学習する

x_val, y_val = train_x[1000:], train_t[1000:]
partial_x_train, partial_y_train = train_x[:1000], train_t[:1000]

history = model.fit(
    partial_x_train,
    partial_y_train,
    epochs=40,
    batch_size=50,
    validation_data=(x_val, y_val),
    verbose=1
)

 

結果

results = model.evaluate(test_x, test_t)
print(results)

結果はこんな感じ。accuracyは73%となりました。

[0.5745982763751242, 0.7315436277613544]

考察

学習結果をみるとうまくいきませんでした。

うまくいかなかった原因として考えられるのは

  1. 学習データが少ない
  2. もう少し前処理をする、出てくる単語を全て辞書に登録していますが、あまり出てこない単語は取り除くとか、、、
  3. 意味のなさそうな単語を取り除く、例えば、助詞や助動詞

ソースコード

import datasets
import numpy as np
import tensorflow as tf
from tensorflow import keras
from reserved_tokens import PADDING_INDEX

(train_x, train_t), (test_x, test_t), encoder = datasets.load_data()
train_x = keras.preprocessing.sequence.pad_sequences(train_x,
                                                     value=PADDING_INDEX,
                                                     padding="post",
                                                     maxlen=128)
test_x = keras.preprocessing.sequence.pad_sequences(test_x,
                                                    value=PADDING_INDEX,
                                                    padding="post",
                                                    maxlen=128)

n_vocab = len(encoder.vocab)
model = keras.Sequential()
model.add(keras.layers.Embedding(n_vocab, 16))
model.add(keras.layers.GlobalAveragePooling1D())
model.add(keras.layers.Dense(16, activation=tf.nn.relu))
model.add(keras.layers.Dense(1, activation=tf.nn.sigmoid))


model.compile(optimizer=tf.train.AdamOptimizer(),
              loss="binary_crossentropy",
              metrics=["accuracy"])
              
x_val, y_val = train_x[1000:], train_t[1000:]
partial_x_train, partial_y_train = train_x[:1000], train_t[:1000]

history = model.fit(
    partial_x_train,
    partial_y_train,
    epochs=40,
    batch_size=50,
    validation_data=(x_val, y_val),
    verbose=1
)

results = model.evaluate(test_x, test_t)
print(results)

history_dict = history.history

import matplotlib.pyplot as plt

acc = history.history['acc']
val_acc = history.history['val_acc']
loss = history.history['loss']
val_loss = history.history['val_loss']

epochs = range(1, len(acc) + 1)

# "bo" is for "blue dot"
plt.plot(epochs, loss, 'bo', label='Training loss')
# b is for "solid blue line"
plt.plot(epochs, val_loss, 'b', label='Validation loss')
plt.title('Training and validation loss')
plt.xlabel('Epochs')
plt.ylabel('Loss')
plt.legend()

plt.show()

plt.clf()   # clear figure
acc_values = history_dict['acc']
val_acc_values = history_dict['val_acc']

plt.plot(epochs, acc, 'bo', label='Training acc')
plt.plot(epochs, val_acc, 'b', label='Validation acc')
plt.title('Training and validation accuracy')
plt.xlabel('Epochs')
plt.ylabel('Accuracy')
plt.legend()

plt.show()