自然言語処理で、レビューの識別やってみた!その2【図解速習DeepLearning】#013
こんにちは!こーたろーです。
【図解速習DEEP LEARNING】の自然言語処理の第2弾です。
本日は、映画情報サイトにあるレビューを識別する(その2)を行っていきます。
昨日は、Embedding層とMLPで構成されたものを取り扱いました。
自然言語処理で、レビューの識別やってみた!その1【図解速習DeepLearning】#012 - 福岡の社会人データサイエンティストの部屋
今回は、TF_Hubで提供されているEmbeddingモデルを使って、転移学習を行っていきます。
1.必要なライブラリーのインポート
import tensorflow as tf import tensorflow_hub as hub import matplotlib.pyplot as plt import numpy as np import os import pandas as pd import re import seaborn as sns
2.データセットの準備
今回は、ポジティブ度合いを10段階でラベル付けされたIMDB映画レビューで行っていきます。
呼び出すデータセットは、既にデータ整形されたものとなっているため、前回のように前処理は不要となっています。
def load_directory_data(directory): data = {} data["sentence"] = [] data["sentiment"] = [] for file_path in os.listdir(directory): with tf.io.gfile.GFile(os.path.join(directory, file_path), "r") as f: data["sentence"].append(f.read()) data["sentiment"].append(re.match("\d+_(\d+)\.txt", file_path).group(1)) return pd.DataFrame.from_dict(data) def load_dataset(directory): pos_df = load_directory_data(os.path.join(directory, "pos")) neg_df = load_directory_data(os.path.join(directory, "neg")) pos_df["polarity"] = 1 neg_df["polarity"] = 0 return pd.concat([pos_df, neg_df]).sample(frac=1).reset_index(drop=True) def download_and_load_datasets(force_download=False): dataset = tf.keras.utils.get_file( fname="aclImdb.tar.gz", origin="http://ai.stanford.edu/~amaas/data/sentiment/aclImdb_v1.tar.gz", extract=True) train_df = load_dataset(os.path.join(os.path.dirname(dataset), "aclImdb", "train")) test_df = load_dataset(os.path.join(os.path.dirname(dataset), "aclImdb", "test")) return train_df, test_df tf.compat.v1.logging.set_verbosity(tf.compat.v1.logging.ERROR) train_df, test_df = download_and_load_datasets() train_df.head()
3.モデルを作成
【入力関数】
TensorflowのEstimatorフレームワークを活用していきます。
こちらは、Pandasのデータフレームをラッピングする入力関数を提供しているため、データフレームのまま入力できるため、非常に便利です。
train_input_fn = tf.compat.v1.estimator.inputs.pandas_input_fn( train_df, train_df["polarity"], num_epochs=None, shuffle=True) predict_train_input_fn = tf.compat.v1.estimator.inputs.pandas_input_fn( train_df, train_df["polarity"], shuffle=False) predict_test_input_fn = tf.compat.v1.estimator.inputs.pandas_input_fn( test_df, test_df["polarity"], shuffle=False)
【特徴カラム】
TF_Hubでは、与えられたテキストにモジュールを適用させて、その出力を渡す【特徴カラム】というものがあるようです。
今回は、「nnlm-en-dim128」というモジュールを使用しています。
embedded_text_feature_column = hub.text_embedding_column( key="sentence", module_spec="https://tfhub.dev/google/nnlm-en-dim128/1")
【推論器】
分類器には、DNN_Classifierを使用しています。
estimator = tf.compat.v1.estimator.DNNClassifier( hidden_units=[500, 100], feature_columns=[embedded_text_feature_column], n_classes=2, optimizer=tf.compat.v1.train.AdagradOptimizer(learning_rate=0.003))
4.モデルの学習
estimator.train(input_fn=train_input_fn, steps=1000);
5.モデルの評価
学習データセットとテストデータセットの両方に対して予測を実行します。
train_eval_result = estimator.evaluate(input_fn=predict_train_input_fn) test_eval_result = estimator.evaluate(input_fn=predict_test_input_fn) print("Training set accuracy: {accuracy}".format(**train_eval_result)) print("Test set accuracy: {accuracy}".format(**test_eval_result))
誤分類の分布を表示して、理解を深めてみましょう。
混同行列を見て確認していきます。
def get_predictions(estimator, input_fn): return [x["class_ids"][0] for x in estimator.predict(input_fn=input_fn)] LABELS = [ "negative", "positive"] with tf.Graph().as_default(): cm = tf.math.confusion_matrix(train_df["polarity"], get_predictions(estimator, predict_train_input_fn)) with tf.compat.v1.Session() as session: cm_out = session.run(cm) 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");
上記では、2項分類で行った結果を示しています。
予測と正解を比べてみると、ネガティブが80%以上、ポジティブは70%以上の正解率で識別できているのが分かります。
6.発展:転移学習
今回使用したデータは、10段階評価をラベリングされているため、分類ではなく、回帰に変更すると、0から10のスケールを利用できるようになります。
- nnlm-en-dim128: 事前学習済みのテキストembeddingモジュール
- random-nnlm-en-dim128: nnlm-en-dim128:と同じ語彙やネットワークを持つテキストembeddingモジュール
これら2つのモードで学習させます。
- ケース1:分類器のみを学習させる
- ケース2:モジュールと分類器を共に学習させる
def train_and_evaluate_with_module(hub_module, train_module=False): embedded_text_feature_column = hub.text_embedding_column( key="sentence", module_spec=hub_module, trainable=train_module) estimator = tf.compat.v1.estimator.DNNClassifier( hidden_units=[500, 100], feature_columns=[embedded_text_feature_column], n_classes=2, optimizer=tf.compat.v1.train.AdagradOptimizer(learning_rate=0.003)) estimator.train(input_fn=train_input_fn, steps=1000) train_eval_result = estimator.evaluate(input_fn=predict_train_input_fn) test_eval_result = estimator.evaluate(input_fn=predict_test_input_fn) training_set_accuracy = train_eval_result["accuracy"] test_set_accuracy = test_eval_result["accuracy"] return { "Training accuracy": training_set_accuracy, "Test accuracy": test_set_accuracy } results = {} results["nnlm-en-dim128"] = train_and_evaluate_with_module( "https://tfhub.dev/google/nnlm-en-dim128/1") results["nnlm-en-dim128-with-module-training"] = train_and_evaluate_with_module( "https://tfhub.dev/google/nnlm-en-dim128/1", True) results["random-nnlm-en-dim128"] = train_and_evaluate_with_module( "https://tfhub.dev/google/random-nnlm-en-dim128/1") results["random-nnlm-en-dim128-with-module-training"] = train_and_evaluate_with_module( "https://tfhub.dev/google/random-nnlm-en-dim128/1", True)
結果を比較してみましょう。
pd.DataFrame.from_dict(results, orient="index")
結果をみた感じでは、モジュールと分類器はどちらも学習させる方が、汎化性能が高いような感じですね。
他のモデルでもそうなのかもしれませんが、転移学習の際は、転移元のデータ(重み)の更新までやる方が効率が高いようです。
以前のモデルもそうでした。
今回はここまでです。
次回もまたお楽しみに!
ではでは。。