これまた小ネタです。大したことはしていないので、興味のない方は読み飛ばしてくださって結構です。今回のお題は、感情分析(sentiment analysis)です。題材として選んだのは、上記のオープンデータセットです。
一般に、感情分析自体はNLPが苦手な僕から見ると鬼門も良いところで、例えば以下のQiitaの記事などを拝見していると「こういう細かく丁寧な分析は自分には難しいなぁ」と思ったりしてました。
ところが、NLPそのものは既にこのシリーズ記事でも見てきたようにある程度TF-HubのNNLMを使えばスキップすることが可能で、しかもよくよく見たらTF-Hubのtutorial自体がそもそも感情分析のデモなんですね。
なので、先に感情スコアのラベルがついた学習データ&テストデータがあれば、簡単に実践できるのではないかなと思ったのでした。
ということで、これまで通りTF-HubのNNLMを使って適当にやってみることとします。いつもながらですが、誤っている部分などあればご指摘いただけると幸いですm(_ _)m
データ自体を眺めてみる
Sentiment140自体はTwitterから収集したツイートのデータです。基礎集計のコードは省略しますが、学習データは16万行あります。感情の極性ラベルとして0 (negative)と4 (positive)とが8万行ずつ割り振られています。またツイートのユニークID、タイムスタンプ、クエリ*1、アカウント名、ツイート本文(英語)の計6変数が収められています。一方、テストデータは498行しかなく、感情の極性ラベルとして0 (negative)が177行、2 (neutral)が139行、4 (positive)が182行割り振られています。2のラベルは不要なので、前処理の際に削除することとします。
TF-Hubでざっくりやってみる
これ自体は極めて簡単で、以下のようにただPythonで書いて回すだけです。
import tensorflow as tf import tensorflow_hub as hub import numpy as np import pandas as pd from sklearn.utils import shuffle import seaborn as sns import matplotlib.pyplot as plt import csv if __name__ == "__main__": df_train = pd.read_csv("training.1600000.processed.noemoticon.csv", sep=',', header=None, error_bad_lines=False, encoding='latin1', quoting=csv.QUOTE_ALL) df_train = df_train[[0, 5]] df_train.columns = ["label", "text"] df_train["label"] = df_train["label"] == 0 df_train = shuffle(df_train) train_input_fn = tf.estimator.inputs.pandas_input_fn( df_train, df_train["label"], num_epochs=None, shuffle=True) df_test = pd.read_csv("testdata.manual.2009.06.14.csv", sep=',', header=None, error_bad_lines=False, encoding='latin1', quoting=csv.QUOTE_ALL) df_test = df_test[[0, 5]] df_test.columns = ["label", "text"] df_test = df_test[df_test["label"] != 2] df_test["label"] = df_test["label"] == 0 df_test = shuffle(df_test) predict_test_input_fn = tf.estimator.inputs.pandas_input_fn( df_test, df_test["label"], shuffle=False) embedded_text_feature_column = hub.text_embedding_column( key="text", module_spec="https://tfhub.dev/google/nnlm-en-dim128/1") estimator = tf.estimator.DNNClassifier( hidden_units=[512, 128], feature_columns=[embedded_text_feature_column], n_classes=2, optimizer=tf.train.AdamOptimizer(learning_rate=0.003) ) estimator.train(input_fn=train_input_fn, steps=1000); test_eval_result = estimator.evaluate(input_fn=predict_test_input_fn) print("Test set accuracy: {accuracy}".format(**test_eval_result))
Test set accuracy: 0.721448481083
ACC 0.72と、素晴らしく高いというわけではありませんが及第点と言って良いでしょう。ついでにconfusion matrixも書いておきます。
def get_predictions(estimator, input_fn): return [x["class_ids"][0] for x in estimator.predict(input_fn=input_fn)] LABELS = [ 0, 1 ] # Create a confusion matrix on training data. with tf.Graph().as_default(): cm = tf.confusion_matrix(df_test["label"], get_predictions(estimator, predict_test_input_fn)) with tf.Session() as session: cm_out = session.run(cm) # Normalize the confusion matrix so that each row sums to 1. cm_out = cm_out.astype(float) / cm_out.sum(axis=1)[:, np.newaxis] sns.heatmap(cm_out, annot=True, xticklabels=LABELS, yticklabels=LABELS); plt.xlabel("Predicted"); plt.ylabel("True");
概ね上手くいったように見えます。何となく0のラベル(つまりnegative)に寄っているようにも見えますが、学習データが完全な均衡データなのでこのようになった理由はちょっと分かりません。ともあれ、TF-Hubで感情分析を行うことは(ラベリング済みの整ったデータさえあれば)出来そうだということがこれで分かりました。
ただ、このシリーズの過去記事と比較すると分かりますが、明らかにACCが低いです。
例えばYouTubeのスパムコメントデータセットでやった場合はACC 0.90ぐらい行っていたわけで、0.72というのは正直に言うと二値分類ではダメとは言わないにしても低い方です*2。この辺に感情分析ならではの難しさがあるのかなとも思いましたが、この記事の範囲では分からないことなので一旦脇に置いておきます。
最後に
間抜けな話ですが、実はテストデータの前処理をミスっていて何回やってもACC 0.5に張り付いてしまって困ったなぁと思ったら、こちらの方が助け舟を出してくださったのでした。
ということで、前処理部分はこちらのコードを参照させていただきました。この場を借りて厚く御礼申し上げます。