ステップ 3: データを準備する

データをモデルにフィードする前に、モデルが理解できる形式に変換する必要があります。

第一に、収集したデータサンプルは特定の順序である可能性があります。サンプルの順序付けに関連する情報がテキストとラベルの関係に影響を与えることは望ましくありません。たとえば、データセットがクラス別に並べ替えられ、その後トレーニング セットと検証セットに分割された場合、これらのセットはデータの全体的な分布を表すものではありません。

モデルがデータ順序の影響を受けないようにするための簡単なベスト プラクティスは、他の処理を実行する前に常にデータをシャッフルすることです。データがすでにトレーニング セットと検証セットに分割されている場合は、トレーニング データの変換と同じ方法で検証データを変換してください。トレーニング セットと検証セットがまだない場合は、シャッフル後にサンプルを分割できます。通常、サンプルの 80% をトレーニングに、20% を検証に使用します。

第二に、ML アルゴリズムは入力として数値を受け取ります。つまり、テキストを数値ベクトルに変換する必要があります。このプロセスには 2 つのステップがあります。

  1. トークン化: テキストを単語または小さなサブテキストに分割します。これにより、テキストとラベルの関係を適切に一般化できます。これにより、データセットの「語彙」(データ内に存在する一意のトークンのセット)が決まります。

  2. ベクトル化: これらのテキストの特徴付けに適切な数値尺度を定義します。

N グラム ベクトルとシーケンス ベクトルの両方に対してこの 2 つのステップを実行する方法と、特徴選択と正規化の手法を使用してベクトル表現を最適化する方法を見てみましょう。

N グラム ベクトル [オプション A]

以降の段落では、N グラムモデルのトークン化とベクトル化の方法について説明します。また、特徴選択と正規化の手法を使用して N-gram 表現を最適化する方法についても説明します。

N グラム ベクトルでは、テキストは一意の N グラム、つまり n 個の隣接するトークン(通常は単語)のグループとして表現されます。テキスト The mouse ran up the clock について考えてみましょう。こちらをご覧ください。

  • 単語のユニグラム(n = 1)は ['the', 'mouse', 'ran', 'up', 'clock'] です。
  • 単語のバイグラム(n = 2)は ['the mouse', 'mouse ran', 'ran up', 'up the', 'the clock'] である
  • といった質問に答えることができます。

トークン化

単語のユニグラムとバイグラムにトークン化すると、計算時間を減らしながら高い精度を実現できることがわかりました。

ベクトル化

テキスト サンプルを N グラムに分割したら、これらの N グラムを ML モデルが処理できる数値ベクトルに変換する必要があります。以下の例は、2 つのテキストに対して生成されたユニグラムとバイグラムに割り当てられるインデックスを示しています。

Texts: 'The mouse ran up the clock' and 'The mouse ran down'
Index assigned for every token: {'the': 7, 'mouse': 2, 'ran': 4, 'up': 10,
  'clock': 0, 'the mouse': 9, 'mouse ran': 3, 'ran up': 6, 'up the': 11, 'the
clock': 8, 'down': 1, 'ran down': 5}

インデックスを N グラムに割り当てると、通常は次のいずれかのオプションを使用してベクトル化されます。

ワンホット エンコーディング: すべてのサンプル テキストは、テキスト内にトークンが存在するかどうかを示すベクトルとして表されます。

'The mouse ran up the clock' = [1, 0, 1, 1, 1, 0, 1, 1, 1, 1, 1, 1]

カウント エンコード: すべてのサンプル テキストは、テキスト内のトークンの数を示すベクトルとして表されます。ユニグラム「the」に対応する要素は、2 と表現されています。これは、「the」という単語がテキスト内に 2 回出現するためです。

'The mouse ran up the clock' = [1, 0, 1, 1, 1, 0, 1, 2, 1, 1, 1, 1]

Tf-idf エンコード: 上記の 2 つのアプローチの問題は、すべてのドキュメントで同様の頻度で出現する一般的な単語(つまり、データセット内のテキスト サンプルに対して特に一意でない単語)にペナルティが課されないことです。たとえば “a”のような単語は すべてのテキストで非常に頻繁に出現しますそのため、「the」のトークン数を他のより意味のある単語よりも 多くしてもあまり役に立ちません

'The mouse ran up the clock' = [0.33, 0, 0.23, 0.23, 0.23, 0, 0.33, 0.47, 0.33, 0.23, 0.33, 0.33]

Scikit-learn TfidfTransformer をご覧ください)。

他にも多くのベクトル表現がありますが、上記の 3 つが最も一般的に使用されます。

tf-idf エンコードは、精度が他の 2 つよりもわずかに優れている(平均で 0.25 ~ 15% 高い)ことが確認されています。N グラムをベクトル化する場合は、この方法を使用することをおすすめします。ただし、特に大規模なデータセットの場合、浮動小数点表現を使用するため、より多くのメモリを占有し、計算に時間がかかります(場合によっては、2 倍の時間がかかります)。

特徴選択

データセット内のすべてのテキストを単語の Uni+ Bigram トークンに変換すると、数万個のトークンになる可能性があります。これらのトークンや特徴量のすべてがラベル予測に寄与するわけではありません。データセット全体で極めてまれにしか 出現しないものなど 特定のトークンをまた、特徴の重要度(各トークンがラベル予測にどの程度貢献しているか)を測定し、最も有益なトークンのみを含めることもできます。

特徴とそれに対応するラベルを使用して特徴の重要度スコアを出力する統計関数は数多くあります。よく使用される関数は f_classifchi2 の 2 つです。Google でのテストによると、この両方の関数のパフォーマンスは同等です。

さらに重要なことに、多くのデータセットで精度のピークが約 20,000 点に達しています(図 6 を参照)。このしきい値を超えて特徴を追加しても、影響はほとんど生じません。場合によっては、過学習が発生し、パフォーマンスが低下することもあります。

Top-K と精度

図 6: トップ K 機能と精度の比較。データセット全体で、上位 20, 000 個の特徴量で精度が頭打ちになっている。

正規化

正規化により、すべての特徴値やサンプル値が小さな類似の値に変換されます。これにより、学習アルゴリズムにおける勾配降下法の収束が簡素化されます。これまでに見てきた結果、データの前処理中に正規化しても、テキスト分類の問題に大きな価値は見られません。このステップはスキップすることをおすすめします。

次のコードは、上記のすべてのステップをまとめたものです。

  • テキストのサンプルを単語のユニグラムとバイグラムにトークン化する
  • tf-idf エンコードを使用してベクトル化する。
  • トークンのベクトルから上位 20,000 個の特徴のみを選択します。そのためには、出現回数が 2 回未満のトークンを破棄し、f_classif を使用して特徴の重要度を計算します。
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.feature_selection import SelectKBest
from sklearn.feature_selection import f_classif

# Vectorization parameters
# Range (inclusive) of n-gram sizes for tokenizing text.
NGRAM_RANGE = (1, 2)

# Limit on the number of features. We use the top 20K features.
TOP_K = 20000

# Whether text should be split into word or character n-grams.
# One of 'word', 'char'.
TOKEN_MODE = 'word'

# Minimum document/corpus frequency below which a token will be discarded.
MIN_DOCUMENT_FREQUENCY = 2

def ngram_vectorize(train_texts, train_labels, val_texts):
    """Vectorizes texts as n-gram vectors.

    1 text = 1 tf-idf vector the length of vocabulary of unigrams + bigrams.

    # Arguments
        train_texts: list, training text strings.
        train_labels: np.ndarray, training labels.
        val_texts: list, validation text strings.

    # Returns
        x_train, x_val: vectorized training and validation texts
    """
    # Create keyword arguments to pass to the 'tf-idf' vectorizer.
    kwargs = {
            'ngram_range': NGRAM_RANGE,  # Use 1-grams + 2-grams.
            'dtype': 'int32',
            'strip_accents': 'unicode',
            'decode_error': 'replace',
            'analyzer': TOKEN_MODE,  # Split text into word tokens.
            'min_df': MIN_DOCUMENT_FREQUENCY,
    }
    vectorizer = TfidfVectorizer(**kwargs)

    # Learn vocabulary from training texts and vectorize training texts.
    x_train = vectorizer.fit_transform(train_texts)

    # Vectorize validation texts.
    x_val = vectorizer.transform(val_texts)

    # Select top 'k' of the vectorized features.
    selector = SelectKBest(f_classif, k=min(TOP_K, x_train.shape[1]))
    selector.fit(x_train, train_labels)
    x_train = selector.transform(x_train).astype('float32')
    x_val = selector.transform(x_val).astype('float32')
    return x_train, x_val

n グラム ベクトル表現では、単語の順序と文法に関する多くの情報が破棄されます(せいぜい、n > 1 であれば部分的な順序情報は維持できます)。これは「一言で言わざるを得ない手法」と呼ばれます。この表現は、ロジスティック回帰、多層パーセプトロン、勾配ブースティング マシン、サポート ベクトル マシンなど、順序を考慮しないモデルと組み合わせて使用します。

シーケンス ベクトル [オプション B]

以降の段落では、シーケンス モデルのトークン化とベクトル化の方法について説明します。また、特徴選択と正規化の手法を使用してシーケンス表現を最適化する方法についても説明します。

一部のテキスト サンプルでは、単語の順序がテキストの意味に重要となります。たとえば、「以前は通勤が嫌いでした。「新しいバイクが変わってしまいました」 というのは順番に読んだ場合にのみ理解できますCNN/RNN などのモデルは、サンプル内の単語の順序から意味を推測できます。これらのモデルでは、順序を維持したまま、テキストを一連のトークンとして表現します。

トークン化

テキストは、文字のシーケンスまたは単語のシーケンスとして表すことができます。単語レベルの表現を使用すると、文字トークンよりもパフォーマンスが向上することがわかっています。これは一般的な標準でもあり これに続いて業界が続きます文字トークンの使用は、テキストに多くの入力ミスがある場合にのみ有効ですが、通常はそのようなことはありません。

ベクトル化

テキスト サンプルを単語のシーケンスに変換したら、そのシーケンスを数値ベクトルに変換する必要があります。以下の例は、2 つのテキストに対して生成されたユニグラムに割り当てられたインデックスと、最初のテキストが変換されるトークン インデックスのシーケンスを示しています。

Texts: 'The mouse ran up the clock' and 'The mouse ran down'

すべてのトークンに割り当てられるインデックス:

{'clock': 5, 'ran': 3, 'up': 4, 'down': 6, 'the': 1, 'mouse': 2}

注: 「the」という単語が最も頻繁に出現するため、インデックス値 1 が割り当てられます。一部のライブラリでは、この例のように不明なトークンのインデックス 0 が予約されています。

トークン インデックスのシーケンス:

'The mouse ran up the clock' = [1, 2, 3, 4, 1, 5]

トークン シーケンスをベクトル化するには、次の 2 つの方法があります。

ワンホット エンコーディング: シーケンスは、n 次元空間内の単語ベクトルを使用して表現されます。ここで、n は語彙のサイズです。この表現は、文字としてトークン化する場合に効果的です。そのため、語彙が少なくなります。単語としてトークン化する場合、語彙には通常、数万のトークンが含まれるため、ワンホット ベクトルが非常にスパースで非効率的になります。例:

'The mouse ran up the clock' = [
  [0, 1, 0, 0, 0, 0, 0],
  [0, 0, 1, 0, 0, 0, 0],
  [0, 0, 0, 1, 0, 0, 0],
  [0, 0, 0, 0, 1, 0, 0],
  [0, 1, 0, 0, 0, 0, 0],
  [0, 0, 0, 0, 0, 1, 0]
]

単語のエンベディング: 単語には意味が関連付けられています。その結果、単語トークンを密なベクトル空間(約数百の実数)で表現できます。ここで、単語間の位置と距離は意味的にどの程度類似しているかを示します(図 7 を参照)。この表現は、単語エンベディングと呼ばれます。

単語のエンベディング

図 7: 単語のエンベディング

シーケンス モデルには多くの場合、最初のレイヤとしてこのようなエンベディング レイヤがあります。このレイヤは、トレーニング プロセス中に単語インデックス シーケンスを単語エンベディング ベクトルに変換することを学習します。これにより、各単語インデックスは、セマンティック空間におけるその単語の位置を表す実数値の密ベクトルにマッピングされます(図 8 を参照)。

エンベディング レイヤ

図 8: エンベディング レイヤ

特徴選択

データ内のすべての単語がラベル予測に貢献するわけではありません。語彙からあまり使われない単語や無関係な単語を破棄することで、学習プロセスを最適化できます。実際には、最も頻度の高い 20,000 個の特徴量を使用するだけで十分であることがわかりました。これは、N グラムモデルにも当てはまります(図 6 を参照)。

シーケンス ベクトル化に上記のすべてのステップをまとめましょう。次のコードは、これらのタスクを実行します。

  • テキストを単語にトークン化します
  • 上位 20,000 個のトークンを使用して語彙を作成する
  • トークンをシーケンス ベクトルに変換する
  • シーケンスを固定シーケンス長までパディングする
from tensorflow.python.keras.preprocessing import sequence
from tensorflow.python.keras.preprocessing import text

# Vectorization parameters
# Limit on the number of features. We use the top 20K features.
TOP_K = 20000

# Limit on the length of text sequences. Sequences longer than this
# will be truncated.
MAX_SEQUENCE_LENGTH = 500

def sequence_vectorize(train_texts, val_texts):
    """Vectorizes texts as sequence vectors.

    1 text = 1 sequence vector with fixed length.

    # Arguments
        train_texts: list, training text strings.
        val_texts: list, validation text strings.

    # Returns
        x_train, x_val, word_index: vectorized training and validation
            texts and word index dictionary.
    """
    # Create vocabulary with training texts.
    tokenizer = text.Tokenizer(num_words=TOP_K)
    tokenizer.fit_on_texts(train_texts)

    # Vectorize training and validation texts.
    x_train = tokenizer.texts_to_sequences(train_texts)
    x_val = tokenizer.texts_to_sequences(val_texts)

    # Get max sequence length.
    max_length = len(max(x_train, key=len))
    if max_length > MAX_SEQUENCE_LENGTH:
        max_length = MAX_SEQUENCE_LENGTH

    # Fix sequence length to max value. Sequences shorter than the length are
    # padded in the beginning and sequences longer are truncated
    # at the beginning.
    x_train = sequence.pad_sequences(x_train, maxlen=max_length)
    x_val = sequence.pad_sequences(x_val, maxlen=max_length)
    return x_train, x_val, tokenizer.word_index

ラベルのベクトル化

サンプル テキストデータを数値ベクトルに変換する方法を学びました。同様のプロセスをラベルに適用する必要があります。ラベルは [0, num_classes - 1] の範囲の値に変換するだけで済みます。たとえば 3 つのクラスがある場合は 値 0、1、2 で表すことができます内部的には、ネットワークはワンホット ベクトルを使用してこれらの値を表します(ラベル間の誤った関係を推測しないように)。この表現は、損失関数と、ニューラル ネットワークで使用する最終レイヤの活性化関数に依存します。これらについては次のセクションで 詳しく説明します