ManningのMEAP(出版前の本のできた部分から読めるサービス)でTensorFlowとTheanoの両方に対応したラッパーツールkerasの作者Francois Chollet氏の「Deep Learning with Python」が発売されました。
3章では、機械学習でよく使われる以下の手法をニューラルネットワークを使って実現する方法を紹介しています。
ここでは、最初の二値分類の例題をSageのjupyterノートブックで試しながら、Deep Learningへの理解を深めたいと思います。
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
%matplotlib inline
二値分類では、Sequential, Dense, to_categoricalを使用するため、最初にインポートしておきます。
from keras.models import Sequential
from keras.layers import Dense
from keras.utils.np_utils import to_categorical
Using TensorFlow backend.
kerasにいくつかのデータセットが組み込まれています。この内、二値分類ではIMDBのデータセットを使用します。
IMDBは、映画に対する感想を肯定的と否定的にラベル付けされたデータセットです。50,000のIMDB映画レビューのデータセット(単語をインデックス変換して加工済み)をkeras.datasets.imdbパッケージのload_data関数を使って取り込みます。25,000が学習用(train_data)、残りの25,000がテスト用(test_data)として使用します。
nb_wordsで使用する単語数を良く出てくる上位10000語に限定して、分析を行います。
from keras.datasets import imdb
(train_data, train_labels), (test_data, test_labels) = imdb.load_data(nb_words=10000)
train_dataの最初のデータを見てみましょう。型はリストで、長さが218、リストの内容は単語のインデックスが整数値で入っています。
print len(train_data), len(test_data)
print type(train_data[0]), len(train_data[0])
np.array(train_data[0]).flatten()
25000 25000 <type 'list'> 218
array([ 1, 14, 22, 16, 43, 530, 973, 1622, 1385, 65, 458, 4468, 66, 3941, 4, 173, 36, 256, 5, 25, 100, 43, 838, 112, 50, 670, 2, 9, 35, 480, 284, 5, 150, 4, 172, 112, 167, 2, 336, 385, 39, 4, 172, 4536, 1111, 17, 546, 38, 13, 447, 4, 192, 50, 16, 6, 147, 2025, 19, 14, 22, 4, 1920, 4613, 469, 4, 22, 71, 87, 12, 16, 43, 530, 38, 76, 15, 13, 1247, 4, 22, 17, 515, 17, 12, 16, 626, 18, 2, 5, 62, 386, 12, 8, 316, 8, 106, 5, 4, 2223, 5244, 16, 480, 66, 3785, 33, 4, 130, 12, 16, 38, 619, 5, 25, 124, 51, 36, 135, 48, 25, 1415, 33, 6, 22, 12, 215, 28, 77, 52, 5, 14, 407, 16, 82, 2, 8, 4, 107, 117, 5952, 15, 256, 4, 2, 7, 3766, 5, 723, 36, 71, 43, 530, 476, 26, 400, 317, 46, 7, 4, 2, 1029, 13, 104, 88, 4, 381, 15, 297, 98, 32, 2071, 56, 26, 141, 6, 194, 7486, 18, 4, 226, 22, 21, 134, 476, 26, 480, 5, 144, 30, 5535, 18, 51, 36, 28, 224, 92, 25, 104, 4, 226, 65, 16, 38, 1334, 88, 12, 16, 283, 5, 16, 4472, 113, 103, 32, 15, 16, 5345, 19, 178, 32])
ラベルには、肯定の場合1、否定の場合0がセットされています。またインデックスの最大値が9999ですから10000個の単語用のインデックスが入っていることも確認できました。
print train_labels[0:10]
[1 0 0 1 0 0 1 0 1 0]
max([max(sequence) for sequence in train_data])
9999
どのような単語が映画のレビューに含まれていたかを確認するために、単語インデックスもロードしておきます。
# word_index is a dictionary mapping words to an integer indice
word_index = imdb.get_word_index()
# we reverse it, mapping integer indices to words
reverse_word_index = dict([(value, key) for (key, value) in word_index.items()])
# we decode the review; note that our indices were offset by 3
# because 0, 1 and 2 are reserved indices for "padding", "start of sequence", and "unknown".
decoded_review = ' '.join([reverse_word_index.get(i - 3, '?') for i in train_data[0]])
最初のレビューの単語を表示みると、以下のようになりました。
print [reverse_word_index[idx] for idx in train_data[0]]
['the', 'as', 'you', 'with', 'out', 'themselves', 'powerful', 'lets', 'loves', 'their', 'becomes', 'reaching', 'had', 'journalist', 'of', 'lot', 'from', 'anyone', 'to', 'have', 'after', 'out', 'atmosphere', 'never', 'more', 'room', 'and', 'it', 'so', 'heart', 'shows', 'to', 'years', 'of', 'every', 'never', 'going', 'and', 'help', 'moments', 'or', 'of', 'every', 'chest', 'visual', 'movie', 'except', 'her', 'was', 'several', 'of', 'enough', 'more', 'with', 'is', 'now', 'current', 'film', 'as', 'you', 'of', 'mine', 'potentially', 'unfortunately', 'of', 'you', 'than', 'him', 'that', 'with', 'out', 'themselves', 'her', 'get', 'for', 'was', 'camp', 'of', 'you', 'movie', 'sometimes', 'movie', 'that', 'with', 'scary', 'but', 'and', 'to', 'story', 'wonderful', 'that', 'in', 'seeing', 'in', 'character', 'to', 'of', '70s', 'musicians', 'with', 'heart', 'had', 'shadows', 'they', 'of', 'here', 'that', 'with', 'her', 'serious', 'to', 'have', 'does', 'when', 'from', 'why', 'what', 'have', 'critics', 'they', 'is', 'you', 'that', "isn't", 'one', 'will', 'very', 'to', 'as', 'itself', 'with', 'other', 'and', 'in', 'of', 'seen', 'over', 'landed', 'for', 'anyone', 'of', 'and', 'br', "show's", 'to', 'whether', 'from', 'than', 'out', 'themselves', 'history', 'he', 'name', 'half', 'some', 'br', 'of', 'and', 'odd', 'was', 'two', 'most', 'of', 'mean', 'for', '1', 'any', 'an', 'boat', 'she', 'he', 'should', 'is', 'thought', 'frog', 'but', 'of', 'script', 'you', 'not', 'while', 'history', 'he', 'heart', 'to', 'real', 'at', 'barrel', 'but', 'when', 'from', 'one', 'bit', 'then', 'have', 'two', 'of', 'script', 'their', 'with', 'her', 'nobody', 'most', 'that', 'with', "wasn't", 'to', 'with', 'armed', 'acting', 'watch', 'an', 'for', 'with', 'heartfelt', 'film', 'want', 'an']
レビューに含まれている単語のインデックスを、インデックスが含まれている場合1、それ以外は0の長さ10000のベクトルに変えます。 それをすべてのレビューに適応してレビュー数x10000のマトリックスを作ります。
kerasへの入力データは、astype関数でfloat32の実数に変換します。
def vectorize_sequences(sequences, dimension=10000):
results = np.zeros((len(sequences), dimension))
# create an all-zero matrix of shape (len(sequences), dimension)
for i, sequence in enumerate(sequences):
results[i, sequence] = 1. # set specific indices of results[i] to 1s
return results
# our vectorized training data
x_train = vectorize_sequences(train_data)
# our vectorized test data
x_test = vectorize_sequences(test_data)
# our vectorized labels:
y_train = np.asarray(train_labels).astype('float32')
y_test = np.asarray(test_labels).astype('float32')
モデルは3層から成り、最初の2層に16個の隠しユニットを設け、活性化関数をReLuとし、最後に二値化します。
model = Sequential()
model.add(Dense(16, activation='relu', input_dim=10000))
model.add(Dense(16, activation='relu'))
model.add(Dense(1, activation='sigmoid'))
model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['accuracy'])
モデルができたので、25,000のデータの内10,000を検証用に使用します。
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, nb_epoch=20, batch_size=512, validation_data=(x_val, y_val))
Train on 15000 samples, validate on 10000 samples Epoch 1/20 15000/15000 [==============================] - 124s - loss: 0.5038 - acc: 0.7877 - val_loss: 0.3864 - val_acc: 0.8678 Epoch 2/20 15000/15000 [==============================] - 2s - loss: 0.3013 - acc: 0.9051 - val_loss: 0.3278 - val_acc: 0.8730 Epoch 3/20 15000/15000 [==============================] - 2s - loss: 0.2232 - acc: 0.9259 - val_loss: 0.2768 - val_acc: 0.8940 Epoch 4/20 15000/15000 [==============================] - 2s - loss: 0.1754 - acc: 0.9455 - val_loss: 0.2729 - val_acc: 0.8920 Epoch 5/20 15000/15000 [==============================] - 2s - loss: 0.1450 - acc: 0.9543 - val_loss: 0.2801 - val_acc: 0.8890 Epoch 6/20 15000/15000 [==============================] - 2s - loss: 0.1166 - acc: 0.9648 - val_loss: 0.3092 - val_acc: 0.8833 Epoch 7/20 15000/15000 [==============================] - 2s - loss: 0.1018 - acc: 0.9691 - val_loss: 0.3046 - val_acc: 0.8843 Epoch 8/20 15000/15000 [==============================] - 2s - loss: 0.0829 - acc: 0.9768 - val_loss: 0.3360 - val_acc: 0.8758 Epoch 9/20 15000/15000 [==============================] - 2s - loss: 0.0724 - acc: 0.9799 - val_loss: 0.3573 - val_acc: 0.8804 Epoch 10/20 15000/15000 [==============================] - 2s - loss: 0.0579 - acc: 0.9861 - val_loss: 0.3692 - val_acc: 0.8799 Epoch 11/20 15000/15000 [==============================] - 2s - loss: 0.0500 - acc: 0.9878 - val_loss: 0.3944 - val_acc: 0.8772 Epoch 12/20 15000/15000 [==============================] - 2s - loss: 0.0402 - acc: 0.9919 - val_loss: 0.4238 - val_acc: 0.8761 Epoch 13/20 15000/15000 [==============================] - 2s - loss: 0.0325 - acc: 0.9943 - val_loss: 0.4497 - val_acc: 0.8749 Epoch 14/20 15000/15000 [==============================] - 2s - loss: 0.0272 - acc: 0.9955 - val_loss: 0.4645 - val_acc: 0.8719 Epoch 15/20 15000/15000 [==============================] - 2s - loss: 0.0231 - acc: 0.9964 - val_loss: 0.5504 - val_acc: 0.8667 Epoch 16/20 15000/15000 [==============================] - 2s - loss: 0.0151 - acc: 0.9985 - val_loss: 0.5236 - val_acc: 0.8720 Epoch 17/20 15000/15000 [==============================] - 2s - loss: 0.0176 - acc: 0.9969 - val_loss: 0.5571 - val_acc: 0.8702 Epoch 18/20 15000/15000 [==============================] - 2s - loss: 0.0092 - acc: 0.9995 - val_loss: 0.6059 - val_acc: 0.8588 Epoch 19/20 15000/15000 [==============================] - 2s - loss: 0.0110 - acc: 0.9984 - val_loss: 0.6006 - val_acc: 0.8679 Epoch 20/20 15000/15000 [==============================] - 2s - loss: 0.0067 - acc: 0.9997 - val_loss: 0.6302 - val_acc: 0.8680
history_dict = history.history
history_dict.keys()
['acc', 'loss', 'val_acc', 'val_loss']
例題ではmatplotlibを使って収束の様子を可視化していますが、ここではpandasのグラフ機能を使って学習用データの損失関数の値lossと検証用データの損失関数の値val_lossをプロットしてみます。
グラフをカッコよく表示するために、seabornをインポートしています。
import seaborn
# history_dictをpandasnのデータフレームに変換
d = pd.DataFrame(history_dict)
# loss, val_lossをプロット
d[['loss', 'val_loss']].plot()
plt.show()
同様に精度(acc, val_acc)についてもプロットしてみます。
d[['acc', 'val_acc']].plot()
plt.show()
loss, val_lossのグラフからエポック値が4を超えると検証用データの損失関数の値が大きくなり、過学習の様相を呈してきます。 acc, val_accからはエポック値が4でも88%程度の精度が確保できそうだと期待できます。
テスト用のデータでモデルを性能を検証するために、nb_epoch=4で計算したモデルを準備し、evaluateメソッドを使ってテスト用データについて肯定/否定の評価を予想してみましょう。
model = Sequential()
model.add(Dense(16, activation='relu', input_dim=10000))
model.add(Dense(16, activation='relu'))
model.add(Dense(1, activation='sigmoid'))
model.compile(optimizer='rmsprop', loss='binary_crossentropy', metrics=['accuracy'])
model.fit(x_train, y_train, nb_epoch=4, batch_size=512)
results = model.evaluate(x_test, y_test)
Epoch 1/4 25000/25000 [==============================] - 107s - loss: 0.4709 - acc: 0.8180 Epoch 2/4 25000/25000 [==============================] - 17s - loss: 0.2707 - acc: 0.9079 Epoch 3/4 25000/25000 [==============================] - 3s - loss: 0.2057 - acc: 0.9286 Epoch 4/4 25000/25000 [==============================] - 3s - loss: 0.1722 - acc: 0.9393 25000/25000 [==============================] - 78s
results
[0.28535971707344054, 0.88688]
芸術的な域では精度は95%になることから、88%の精度は、簡単な例としてはまあまあの値だと思います。
model.predict(x_test)
array([[ 0.90993786], [ 0.90398455], [ 0.9996897 ], ..., [ 0.52131325], [ 0.00504617], [ 0.75605333]], dtype=float32)
同じ処理をScikit-Learnのロジスティック回帰(LogisticRegression)で計算してみましょう。
最初に、sklearn.linear_modelからLogisticRegressionをモデル(Model)としてインポートします。 次に、modelを生成して、学習用データで訓練します。計算はkerasの場合よりも長く掛かります。
from sklearn.linear_model import LogisticRegression as Model
model = Model()
model.fit(x_train, y_train)
LogisticRegression(C=1.0, class_weight=None, dual=False, fit_intercept=True, intercept_scaling=1, max_iter=100, multi_class='ovr', n_jobs=1, penalty='l2', random_state=None, solver='liblinear', tol=0.0001, verbose=0, warm_start=False)
計算の尺度は、metricsパッケージを使って求めます。
精度は、86%とニューラルネットと同等の結果となりました。
import sklearn.metrics as metrics
predicted = model.predict(x_test)
print metrics.confusion_matrix(y_test, predicted)
print metrics.accuracy_score(y_test, predicted)
[[10812 1688] [ 1761 10739]] 0.86204