zuminote

個人的な勉強記録

PythonとKerasによるディープラーニング 第3章(1)二値分類

IMDbデータセットを使用した二値分類を行う。

ここでの学び:

  • データ前処理(one-hot化)

  • model.fitの戻り値historyオブジェクトによるスコア確認

github.com

データ読み込み・前処理

from tensorflow.keras.datasets import imdb
(train_data, train_labels), (test_data, test_labels) = imdb.load_data(
    num_words=10000)

num_words=10000は、訓練データにおいて出現頻度が最も高い10000個の単語だけを残しておき、出現頻度が低い単語は捨てるということ。

train_dataはレビュー文に含まれる単語のIDのリスト、train_labelは0(否定的)/1(肯定的)のラベルとなっており、どちらもndarrayにデータが格納されている。

print(train_data[0])

出力:
[1, 14, 22, ...(省略), 19, 178, 32]
train_labels[0]

出力:
1

ちなみに、IDと単語の対応を格納した辞書もkerasで配信されているので、このようにIDから単語にデコードすることも可能。

# IDと単語の対応を格納した辞書
word_index = imdb.get_word_index()

# key, valueを逆転させ、(ID, 単語)の順のリストを作る
reverse_word_index = dict(
    [(value, key) for (key, value) in word_index.items()])

# IDリストを単語リストに変換し、最初のレビュー文を表示する。
# 0, 1, 2は予約語(パディング、シーケンス開始、不明)なので、iから3を引いた数
#(これがよくわからなかった、word_indexの中身ではなく、レビューデータ自体のID表記のことを言っている?)
# IDが存在しなかった場合は"?"を表示する。
decoded_review = " ".join(
    [reverse_word_index.get(i - 3, "?") for i in train_data[0]])
print(decoded_review)
出力:
? this film was just brilliant casting location scenery story direction everyone's really suited the part they played and you could just imagine being there …(省略)

ニューラルネットワークに入力するため、データをone-hot化する。 train_dataに含まれるリスト内のIDを読み取っていき、(シーケンス数, 出現する単語数(ここでは10000))のテンソルの、該当する要素を1にしていく。

import numpy as np

def vectorize_sequences(sequences, dimension=10000):
    # (シーケンス数, 次元数(=含まれる全単語の種類))のベクトル
    results = np.zeros((len(sequences), dimension))
    # 各シーケンスの単語IDをone-hot表現にする
    for i, sequence in enumerate(sequences):
        for j in sequence:
            results[i, j] = 1.
    return results

x_train = vectorize_sequences(train_data)
x_test = vectorize_sequences(test_data)

また、ラベルもベクトル形式で入力する必要がある。 本書ではこのように、元々整数でndarrayに格納されているラベルをfloatで格納し直していたが、試しにやってみたら整数のままでも入力できたので、意味はよく分からない…。

y_train = np.asarray(train_labels).astype("float32")
y_test = np.asarray(test_labels).astype("float32")

モデル構築

from tensorflow import keras
from tensorflow.keras import layers

model = keras.Sequential([
    layers.Dense(16, activation="relu"),
    layers.Dense(16, activation="relu"),
    layers.Dense(1, activation="sigmoid")
])

入力データがベクトルで、ラベルは0/1のスカラーという単純な問題なので、ここでは単純な全結合層の組み合わせと、ReLU活性化関数を用いている。 各Dense層に渡される引数は、その層の隠れユニットの数である。

ReLUは、dot(W, input) + b(Wは重み、bはバイアス)の結果を受け取り、その値が負であれば0,0以上であればその値を返す。

なぜ活性化関数を使うのかというと、線形変換をいくら積み重ねても線形変換にしかならずに表現の幅が広がらないので、活性化関数を使うことで、単なる線形変換では表現できないデータを表現できるというわけである。

コンパイル

model.compile(optimizer="rmsprop",
              loss="binary_crossentropy",
              metrics=["accuracy"])

ここでのタスクは二値分類問題で、ネットワークの出力は確率であるため、損失関数はbinary_crossentropyを使う。

学習

trainの前半10000データをvalidとし、それ以降15000データをtrainとして学習を行う。

x_val = x_train[:10000]
partial_x_train = x_train[10000:]
y_val = y_train[:10000]
partial_y_train = y_train[10000:]

history = model.fit(partial_x_train,
                    partial_y_train,
                    epochs=20,
                    batch_size=512,
                    validation_data=(x_val, y_val))

ここで、model.fitの戻り値を受け取って変数historyに格納している。

このhistoryHistoryオブジェクトであり、History.history属性で、学習過程の損失値と評価指標を記録している。

参考:Modelクラス (functional API) - Keras Documentation

history_dict = history.history
history_dict.keys()

出力:
dict_keys(['loss', 'accuracy', 'val_loss', 'val_accuracy'])

中身はこんな感じで、各エポックのスコアが格納されている。これを学習曲線として可視化することで、過学習が起きていないかなどを判断することができる。

{'accuracy': [0.7894666790962219,
  0.9011333584785461,
…(略)…
  0.9998000264167786,
  0.9998666644096375],
 'loss': [0.505029022693634,
  …(略)…
  0.003954361658543348],
 'val_accuracy': [0.8683000206947327,
 …(略)…
  0.863099992275238],
 'val_loss': [0.38592329621315,
 …(略)…
  0.7386608123779297]}