渋谷駅前で働くデータサイエンティストのブログ

元祖「六本木で働くデータサイエンティスト」です / 道玄坂→銀座→東京→六本木→渋谷駅前

生TensorFlow七転八倒記(9):TF-Hub embeddingを利用して感情分析してみる


これまた小ネタです。大したことはしていないので、興味のない方は読み飛ばしてくださって結構です。今回のお題は、感情分析(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");

f:id:TJO:20190125131822p:plain


概ね上手くいったように見えます。何となく0のラベル(つまりnegative)に寄っているようにも見えますが、学習データが完全な均衡データなのでこのようになった理由はちょっと分かりません。ともあれ、TF-Hubで感情分析を行うことは(ラベリング済みの整ったデータさえあれば)出来そうだということがこれで分かりました。


ただ、このシリーズの過去記事と比較すると分かりますが、明らかにACCが低いです。

例えばYouTubeのスパムコメントデータセットでやった場合はACC 0.90ぐらい行っていたわけで、0.72というのは正直に言うと二値分類ではダメとは言わないにしても低い方です*2。この辺に感情分析ならではの難しさがあるのかなとも思いましたが、この記事の範囲では分からないことなので一旦脇に置いておきます。


最後に


間抜けな話ですが、実はテストデータの前処理をミスっていて何回やってもACC 0.5に張り付いてしまって困ったなぁと思ったら、こちらの方が助け舟を出してくださったのでした。

ということで、前処理部分はこちらのコードを参照させていただきました。この場を借りて厚く御礼申し上げます。


追記


IDコールされたので、その他のSentiment140の分類例を以下にリンクしておきます。


*1:これが何かは分からなかった

*2:以前二値分類の真の正答率が50%の時の理論的なACCを計算したら0.72ぐらいだったことを思い出した