Going Faraway

渡辺遼遠の雑記帳

【Deep Learning with Python】 IMDBのレビュー分類

フランソワ・ショレ氏の「Deep Learning with Python」のサンプルコードの2つ目。

この問題は、映画のレビューの文章を入力して、そのレビューが肯定的なものか否定的なものかを判定するもの。問題の種類としては二値分類 (binary classification) に属する。データセットは、IMDB (Internet Movie Database) というサイトに投稿された映画のレビュー5万件であり、肯定的レビューと否定的レビューが半数ずつ含まれている。

3.4. 映画レビューの分類

前回のMNISTデータセットと同様、対象のデータセットに対するインターフェースはKerasに付属しているので、それを使ってロードする。こちらも初回実行時はデータをダウンロードするので多少時間を要する。

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

load_data() 時に指定している引数「num_words」は、レビューの文章中に出現する単語の種類を制限するためのものである。ここでは10000を指定しているので、頻度順の上位10000語のみが使用され、頻度の低い語は「?」(unknown)として表記される。対象データは、単語を数値でエンコードしたリストである。それぞれの数値が、単語の出現頻度における順位を表す。

以下の関数で、エンコードされたレビューを英語の文章へとデコードすることができる。なお、単語のインデックスは3から始まっている。これは、0, 1, 2がそれぞれ、「パディング」、「開始位置」、「不明語」を表すため。

def decode_review(num):
    word_index = imdb.get_word_index()
    reversed_word_index = dict(
        [value, key] for (key, value) in word_index.items())

    decoded_review = ' '.join([reversed_word_index.get(i-3, '?') for i in train_data[num]])
    
    return decoded_review

試しに1件レビューを表示してみる。"one of the worst films…" みたいな表現があるので、当然これは否定的なもの。

? this has to be one of the worst films of the 1990s when my friends i were watching this film being the target audience
it was aimed at we just sat watched the first half an hour with our jaws touching the floor at how bad it really was the rest of the time everyone else in the theatre just started talking to each other leaving or generally crying into their popcorn that they actually paid money they had ? working to watch this feeble excuse for a film it must have looked like a great idea on paper but on film it looks like no one in the film has a clue what is going on crap acting crap costumes i can't get across how ? this is to watch save yourself an hour a bit of your life

前回と同様、入力リストをベクトル化する。1万の要素を持つリストが文章中の単語の個数並んでいるもので、個々のリストは単語の出現頻度中の順位を表す。

# 入力データのベクトル化を行う関数
def vectorized_sequences(sequences, dimension=10000):
    results = np.zeros((len(sequences), dimension))
    for i, seq in enumerate(sequences):
        results[i, seq] = 1.
    return results

# 訓練データのワンホットベクトル化
x_train = vectorized_sequences(train_data)
x_test  = vectorized_sequences(test_data)

# ラベルの変換
y_train = np.asarray(train_labels).astype('float32')
y_test = np.asarray(test_labels).astype('float32')

# 訓練中の正解率を訓練データ以外のデータで評価するため、
# 1万件を訓練データから取り分けておく。
x_val = x_train[:10000]
partial_x_train = x_train[10000:]
y_val = y_train[:10000]
partial_y_train = y_train[10000:]

次に、ニューラルネットのモデル定義を行う。ここでは、入力は10000要素のベクトル、隠れ層が2層。それぞれの隠れ層のノードは16個、出力層は0から1の値を出力する1ノードのニューラルネットとなる。各層間の結合は全て密結合で、訓練は20回行う。modelのcompile関数に、先に分離した訓練データ1万件を与える。

from keras import losses, optimizers
from keras import metrics

model.compile(optimizer=optimizers.RMSprop(lr=0.001),
              loss=losses.binary_crossentropy,
              metrics=['accuracy'])

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

訓練を実行し、検証データに対する損失関数の値と予測正解率のモニタリングを行う。ささいなミスではあるけど、書籍に掲載されたコードには誤植があるようだ。p.74で、訓練/検証時の損失関数の値をプロットしているが、x軸の範囲を求める際に、未定義の変数 accを使ってしまっている。

epochs = range(1, len(acc) + 1)
# => 正しくは len(loss_values) もしくは len(val_loss_values)

訓練時の正答率と損失関数の値をプロットしたものがこちら。2回目か3回目以降、悪化している (過学習に陥っている) ことが分かる。

f:id:liaoyuan:20180123225826p:plainf:id:liaoyuan:20180123225833p:plain

さて、訓練済みのモデルを使って、新しいデータに対する予測を行う。ここではmodelのpredict関数を使う。

model.predict(x_test)
array([[ 0.98006207]
       [ 0.99758697]
       [ 0.99975556]
       ...,
       [ 0.82167041]
       [ 0.02885115]
       [ 0.65371346]], dtype=float32)

元のデータセットにおいて、「1」が肯定的意見を、「0」が否定的意見を表す。出力も、1あるいは0に近ければ近いほど確信度の高さを表し、中間の0.5に近い値はどちらか判断できないことを表す。

更なる実験

モデルのレイヤー数、ノード数を変えてみたり、損失関数や活性化関数を変えて、結果の変化を確認してみましょう、という演習がある。ここでは、レイヤー数を増減させて学習し、テストデータに対する予測正解率がどう変化するかを見てみた。

上記のサンプルでは隠れ層が2層のモデルを使用したので、隠れ層が1層と3層のモデルを用いてみる。コードは省略するが結果は以下の通り。

f:id:liaoyuan:20180123225045p:plain

レイヤー数を増やすほど精度は向上する傾向があるわけではなく、むしろ1層のモデルの方が正答率は高い。深けりゃ良いってものでもなく、ナイーブな方法でパラメータを増やせば過学習が起こりやすくなるのだろうと思う。

今回は以上。ちなみに、次のサンプルプログラムである「ニュース記事のカテゴリ分類」は飛ばして、ボストンの住宅価格予測の回帰問題を取り上げる予定。