/var/log/messages

Oct 24, 2018 - 7 minute read - Comments - machinelearning

How to Build a Simple Text Classifier With Tf-Hub

以下なドキュメントを機械翻訳したので以下に控えを。

Optional prerequisites

Preparing the environment

# Install the latest Tensorflow version.
!pip install --quiet "tensorflow>=1.7"
# Install TF-Hub.
!pip install -q tensorflow-hub
!pip install -q seaborn

More detailed information about installing Tensorflow can be found at https://www.tensorflow.org/install/.

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

Getting Started

Data

We will try to solve the Large Movie Review Dataset v1.0 task from Mass et al. The dataset consists of IMDB movie reviews labeled by positivity from 1 to 10. The task is to label the reviews as negative or positive.

我々は、MassらのLarge Movie Review Dataset v1.0タスクを解決しようとします。 このデータセットは、1から10までの陽性でラベル付けされたIMDBムービーレビューで構成されています。このタスクは、レビューにマイナスまたはプラスのラベルを付けることです。

# Load all files from a directory in a DataFrame.
def load_directory_data(directory):
  data = {}
  data["sentence"] = []
  data["sentiment"] = []
  for file_path in os.listdir(directory):
    with tf.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)

# Merge positive and negative examples, add a polarity column and shuffle.
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)

# Download and process the dataset files.
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

# Reduce logging output.
tf.logging.set_verbosity(tf.logging.ERROR)

train_df, test_df = download_and_load_datasets()
train_df.head()
Downloading data from http://ai.stanford.edu/~amaas/data/sentiment/aclImdb_v1.tar.gz
84131840/84125825 [==============================] - 9s 0us/step

Model

Input functions

Estimator framework provides input functions that wrap Pandas dataframes.

# Training input on the whole training set with no limit on training epochs.
train_input_fn = tf.estimator.inputs.pandas_input_fn(
    train_df, train_df["polarity"], num_epochs=None, shuffle=True)

# Prediction on the whole training set.
predict_train_input_fn = tf.estimator.inputs.pandas_input_fn(
    train_df, train_df["polarity"], shuffle=False)
# Prediction on the test set.
predict_test_input_fn = tf.estimator.inputs.pandas_input_fn(
    test_df, test_df["polarity"], shuffle=False)

Feature columns

TF-Hub provides a feature column that applies a module on the given text feature and passes further the outputs of the module. In this tutorial we will be using the nnlm-en-dim128 module. For the purpose of this tutorial, the most important facts are:

  • The module takes a batch of sentences in a 1-D tensor of strings as input.
  • The module is responsible for preprocessing of sentences (e.g. removal of punctuation and splitting on spaces).
  • The module works with any input (e.g. nnlm-en-dim128 hashes words not present in vocabulary into ~20.000 buckets).

TF-Hubは、指定されたテキストフィーチャーにモジュールを適用し、さらにモジュールの出力を渡すフィーチャー列を提供します。 このチュートリアルでは、nnlm-en-dim128モジュールを使用します。 このチュートリアルでは、最も重要な事実は次のとおりです。

  • このモジュールは、文字列の1次元テンソル内の文のバッチを入力として受け取ります。
  • モジュールは、文章の前処理(例えば、句読点の削除やスペースの分割など)を担当します。
  • このモジュールは、任意の入力(たとえば、nnlm-en-dim128ハッシュの語彙には〜20.000バケットに含まれていないハッシュ)で動作します。
embedded_text_feature_column = hub.text_embedding_column(
    key="sentence", 
    module_spec="https://tfhub.dev/google/nnlm-en-dim128/1")

Estimator

For classification we can use a DNN Classifier (note further remarks about different modelling of the label function at the end of the tutorial).

分類のためにDNNクラシファイアを使用することができます(チュートリアルの最後のラベル機能のさまざまなモデリングについてさらに注意してください)。

estimator = tf.estimator.DNNClassifier(
    hidden_units=[500, 100],
    feature_columns=[embedded_text_feature_column],
    n_classes=2,
    optimizer=tf.train.AdagradOptimizer(learning_rate=0.003))

Training

Train the estimator for a reasonable amount of steps.

妥当な量のステップについて見積もり者を訓練する。

# Training for 1,000 steps means 128,000 training examples with the default
# batch size. This is roughly equivalent to 5 epochs since the training dataset
# contains 25,000 examples.
estimator.train(input_fn=train_input_fn, steps=1000);

Prediction

Run predictions for both training and test set.

トレーニングセットとテストセットの両方の予測を実行します。

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))
Training set accuracy: 0.8025599718093872
Test set accuracy: 0.7925599813461304

Confusion matrix

We can visually check the confusion matrix to undestand the distribution of misclassifications.

私たちは、誤分類の分布を理解するために混乱マトリックスを視覚的に確認することができます。

def get_predictions(estimator, input_fn):
  return [x["class_ids"][0] for x in estimator.predict(input_fn=input_fn)]

LABELS = [
    "negative", "positive"
]

# Create a confusion matrix on training data.
with tf.Graph().as_default():
  cm = tf.confusion_matrix(train_df["polarity"], 
                           get_predictions(estimator, predict_train_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");

Further improvements

  1. Regression on sentiment: we used a classifier to assign each example into a polarity class. But we actually have another categorical feature at our disposal - sentiment. Here classes actually represent a scale and the underlying value (positive/negative) could be well mapped into a continuous range. We could make use of this property by computing a regression (DNN Regressor) instead of a classification (DNN Classifier).

  2. Larger module: for the purposes of this tutorial we used a small module to restrict the memory use. There are modules with larger vocabularies and larger embedding space that could give additional accuracy points.

  3. Parameter tuning: we can improve the accuracy by tuning the meta-parameters like the learning rate or the number of steps, especially if we use a different module. A validation set is very important if we want to get any reasonable results, because it is very easy to set-up a model that learns to predict the training data without generalizing well to the test set.

  4. More complex model: we used a module that computes a sentence embedding by embedding each individual word and then combining them with average. One could also use a sequential module (e.g. Universal Sentence Encoder module) to better capture the nature of sentences. Or an ensemble of two or more TF-Hub modules.

  5. Regularization: to prevent overfitting, we could try to use an optimizer that does some sort of regularization, for example Proximal Adagrad Optimizer.

  6. 感情回帰:分類子を使用して各例を極性クラスに割り当てました。しかし、私たちは実際には別のカテゴリー的な特徴を持っています。クラスは実際にスケールを表し、基礎となる値(正/負)は連続した範囲にうまくマッピングできます。分類(DNNクラシファイア)の代わりに回帰(DNN Regressor)を計算することで、この特性を利用することができました。

  7. より大きいモジュール:このチュートリアルでは、メモリ使用を制限するために小さなモジュールを使用しました。大きなボキャブラリと大きな埋め込みスペースを持つモジュールがあり、追加の精度ポイントを与えることができます。

  8. パラメータ調整:特に異なるモジュールを使用する場合は、学習率やステップ数などのメタパラメータを調整することで精度を向上させることができます。テストセットを一般化することなくトレーニングデータを予測することを学ぶモデルをセットアップすることは非常に簡単であるため、妥当な結果を得たい場合は、検証セットが非常に重要です。

  9. より複雑なモデル:個々の単語を埋め込んで平均を組み合わせることで、文の埋め込みを計算するモジュールを使用しました。また、文の性質をよりよく捕捉するために、順次モジュール(例えば、ユニバーサルセンテンスエンコーダモジュール)を使用することもできる。または2つ以上のTF-Hubモジュールのアンサンブル。

  10. 正規化:オーバーフィットを防ぐために、Proximal Adagrad Optimizerなど、ある種の正規化を行うオプティマイザを使用することができます。

Advanced: Transfer learning analysis

Transfer learning makes it possible to save training resources and to achieve good model generalization even when training on a small dataset. In this part, we will demonstrate this by training with two different TF-Hub modules:

  • nnlm-en-dim128 - pretrained text embedding module,
  • random-nnlm-en-dim128 - text embedding module that has same vocabulary and network as nnlm-en-dim128, but the weights were just randomly initialized and never trained on real data.

And by training in two modes:

  • training only the classifier (i.e. freezing the module), and
  • training the classifier together with the module.

Let’s run a couple of trainings and evaluations to see how using a various modules can affect the accuracy.

転送学習を使用すると、小さなデータセットでトレーニングする場合でも、トレーニングリソースを節約し、優れたモデルの一般化を実現できます。 このパートでは、2つの異なるTF-Hubモジュールを使用してトレーニングを行います。

  • nnlm-en-dim128 - 事前訓練されたテキスト埋め込みモジュール、
  • random-nnlm-en-dim128 - nnlm-en-dim128と同じボキャブラリとネットワークを持つテキスト埋め込みモジュールですが、ウェイトはランダムに初期化され、実際のデータでは訓練されませんでした。

そして、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.estimator.DNNClassifier(
      hidden_units=[500, 100],
      feature_columns=[embedded_text_feature_column],
      n_classes=2,
      optimizer=tf.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)

Let’s look at the results.

pd.DataFrame.from_dict(results, orient="index")

We can already see some patterns, but first we should establish the baseline accuracy of the test set - the lower bound that can be achieved by outputting only the label of the most represented class:

すでにいくつかのパターンを見ることができますが、最初にテストセットのベースライン精度を設定する必要があります。これは、最も表現されたクラスのラベルのみを出力することで達成できます。

estimator.evaluate(input_fn=predict_test_input_fn)["accuracy_baseline"]
0.5

Assigning the most represented class will give us accuracy of 50%. There are a couple of things to notice here:

  1. Maybe surprisingly, a model can still be learned on top of fixed, random embeddings. The reason is that even if every word in the dictionary is mapped to a random vector, the estimator can separate the space purely using its fully connected layers.
  2. Allowing training of the module with random embeddings increases both training and test accuracy as oposed to training just the classifier.
  3. Training of the module with pre-trained embeddings also increases both accuracies. Note however the overfitting on the training set. Training a pre-trained module can be dangerous even with regularization in the sense that the embedding weights no longer represent the language model trained on diverse data, instead they converge to the ideal representation of the new dataset.

最も代表的なクラスを割り当てることで、正確さは50%になります。 ここで注意すべきことがいくつかあります:

  1. 驚くべきことに、固定されたランダム埋め込みの上でモデルを学習することは可能かもしれません。 その理由は、辞書内のすべての単語がランダムなベクトルにマッピングされていても、推定器は完全に接続されたレイヤを使用してスペースを分けることができるからです。
  2. ランダム埋め込みを伴うモジュールの訓練を可能にすることは、訓練と試験の正確さの両方を高め、分類器だけを訓練することに過ぎない。
  3. 事前に訓練された埋め込みを有するモジュールの訓練はまた、両方の精度を高める。 ただし、トレーニングセットのオーバーフィットに注意してください。 事前に訓練されたモジュールをトレーニングすることは、正則化しても、埋め込みウェイトが、新しいデータセットの理想的な表現に収束するのではなく、多様なデータで訓練された言語モデルを表さなくなるという意味で危険です。

KifuDepot 積ん読

comments powered by Disqus