どん底から這い上がるまでの記録

どん底から這い上がりたいけど這い上がれない人がいろいろ書くブログ(主にプログラミング)

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()