iMind Developers Blog

iMind開発者ブログ

Tensorflow2.0 alphaのBEGINNER TUTORIALSを読む - その2

概要

Tensorflow2.0のalpha版が出ていたのでBIGGINER TUTORIALSを読む。

バージョン情報

  • tensorflow-gpu==2.0.0a0
  • Python 3.7.3

読んだページ

Get started with TensorFlow 2.0 for beginnersのクイックスタート服の分類問題回帰モデルの保存とリストアあたり。

おさらい

その1では環境構築、手書き数字分類、服の分類問題をやった。

今回は回帰とモデルの保存/リストア周りをやる。

回帰

チュートリアルでは自動車の燃費に関する回帰をやっているけどあまり興味がない数字なので、個人的に興味がある不動産方面のデータ、ボストンの住宅価格で回帰をしてみる。

まずはデータの読み込み。

import tensorflow as tf
(x_train, y_train), (x_test, y_test) = tf.keras.datasets.boston_housing.load_data()

trainデータのshapeを見ると、404, 13になっている。

x_train.shape
    #=> (404, 13)

カラムの意味は下記を参照。

https://towardsdatascience.com/machine-learning-project-predicting-boston-house-prices-with-regression-b4e47493633d

不正確を覚悟で一言でカラムの意味を表すとこんな感じ。

カラム名 意味
CRIM 犯罪率
ZN 広い住居率
INDUS 工業率
CHAS チャールズ川周辺
NOX 窒素酸化物濃度
RM 部屋数平均
AGE 1940年以前築率
DIS ハロワの距離
RAD 高速のアクセス
TAX 財産の税率
PTRATIO 生徒と教師の割合
B 黒人率
LSTAT 低所得者率
MEDV 家の価格中央値

最後のMEDVがyに入っているので、CRIM〜LSTATまでの13個の要素を使ってMEDVを予測をするモデルを作る、ということになる。

pandasに入れてそれぞれの相関を出してみる。

import pandas as pd
train_df = pd.DataFrame(x_train, columns=['crim', 'zn', 'indus', 'shas', 'nox', 'rm', 'age', 'dis', 'rad', 'tax', 'ptratio', 'b', 'lstat'])
index crim zn indus shas nox rm age dis rad tax ptratio b lstat
0 1.23247 0.0 8.14 0.0 0.538 6.142 91.7 3.9769 4.0 307.0 21.0 396.90 18.72
1 0.02177 82.5 2.03 0.0 0.415 7.610 15.7 6.2700 2.0 348.0 14.7 395.38 3.11
2 4.89822 0.0 18.10 0.0 0.631 4.970 100.0 1.3325 24.0 666.0 20.2 375.52 3.26
3 0.03961 0.0 5.19 0.0 0.515 6.037 34.5 5.9853 5.0 224.0 20.2 396.90 8.01
4 3.69311 0.0 18.10 0.0 0.713 6.376 88.4 2.5671 24.0 666.0 20.2 391.43 14.65

medvとの相関を見てみる。

train_df['medv'] = y_train
train_df.corr().medv
column corr
crim -0.378498
zn 0.380299
indus -0.476743
shas 0.168661
nox -0.438328
rm 0.681483
age -0.364173
dis 0.253900
rad -0.375515
tax -0.448737
ptratio -0.493990
b 0.343953
lstat -0.730793
medv 1.000000

犯罪率(crim)とか工業地(indus)、窒素酸化物(nox)はマイナスの相関。そして低所得者率(lstat)のマイナス相関はとても強い。生徒と教師の割合(ptratio)もけっこう強く相関があると出ている。

逆に正の相関があるのは部屋数平均(rm)や広い住居率(zn)。

散布図で見てみる。

from matplotlib import pylab as plt
fig, ax_list = plt.subplots(5, 3, figsize=(8, 18))
for idx, col in enumerate(train_df.columns[0:-1]):
    ax = ax_list[int(idx / 3)][idx % 3]
    ax.set_xticklabels([])
    ax.set_yticklabels([])
    train_df.plot(ax=ax, kind='scatter', x=col, y='medv')

f:id:mwsoft:20190427182719p:plain

確かに低所得者率(lstat)と部屋数平均(rm)は相関がわかりやすく出ている。

データの内容はなんとなくわかったので、学習処理に移る。

モデルの記述。optimizerにRMSpropを使い、metrixはmaeとmse。

import tensorflow.keras.layers as layers
model = tf.keras.Sequential([
    layers.Dense(64, activation='relu', input_shape=[13]),
    layers.Dense(64, activation='relu'),
    layers.Dense(1)
])

optimizer = tf.keras.optimizers.RMSprop(0.001)
model.compile(loss='mse',
            optimizer=optimizer,
            metrics=['mae', 'mse'])

今回はepochを1000にするので毎週loss等を出力せず、callbackの処理を独自に記述して100回に1度だけloss等を表示する。

またvalidationデータも利用している。

class PrintDot(tf.keras.callbacks.Callback):
    def on_epoch_end(self, epoch, logs):
        if epoch % 100 == 0:
            print('{epoch}, loss={loss}, mae={mae}, val_mae={val_mae}'.format(
                epoch=epoch, **logs))

history = model.fit(
    x_train, y_train,
    epochs=1000,
    validation_split= 0.2,
    verbose=0,
    callbacks=[PrintDot()])

実行結果

0, loss=2967.542478136234, mae=34.36623764038086, val_mae=7.333405494689941
100, loss=67.28121773451106, mae=6.795538425445557, val_mae=4.576648235321045
200, loss=52.51011726804562, mae=5.538043975830078, val_mae=5.8965349197387695
300, loss=24.916718001705206, mae=3.6023008823394775, val_mae=6.093343257904053
400, loss=16.960618491512335, mae=2.9141218662261963, val_mae=4.904761791229248
500, loss=13.69447741493721, mae=2.71707820892334, val_mae=3.5946550369262695
600, loss=12.948669528075415, mae=2.5645904541015625, val_mae=3.6738786697387695
700, loss=11.052188329652367, mae=2.3186943531036377, val_mae=3.414222240447998
800, loss=14.082505194395319, mae=2.7073588371276855, val_mae=3.2434496879577637
900, loss=7.9465778516173, mae=2.054175853729248, val_mae=3.2760510444641113

mae(mean absolute error)のhistory。

plt.plot(history.history['mae'], alpha=0.5)
plt.plot(history.history['val_mae'], alpha=0.5)

f:id:mwsoft:20190427195255p:plain

mse(mean squared error)のhistory。初期のmseは3000くらいまでいってしまっているけどグラフのylimは500で切る。

plt.plot(history.history['mse'], alpha=0.5)
plt.plot(history.history['val_mse'], alpha=0.5)
plt.ylim([0, 500])

f:id:mwsoft:20190427195703p:plain

テストデータでの評価。

model.evaluate(x_test, y_test)
102/102 [==============================] - 0s 41us/sample - loss: 28.7893 - mae: 3.4685 - mse: 28.7893

X軸に実際の価格、Y軸に予測価格をscatterする。

pred = model.predict(x_test)
plt.scatter(x=y_test, y=pred)

けっこう外れている結果も見られる。

f:id:mwsoft:20190427200831p:plain

EarlyStopping

EarlyStopping(を設定して改善がなくなってきたところで自動で学習を止める。

early_stop = tf.keras.callbacks.EarlyStopping(monitor='val_loss', patience=100)

history = model.fit(
    x_train, y_train,
    epochs=1000,
    validation_split= 0.2,
    verbose=0,
    callbacks=[early_stop, PrintDot()])

epochは1000を指定しているけど700行かずに終了した。

0, loss=2618.3400986615347, mae=38.54704284667969, val_mae=11.25062370300293
100, loss=45.294436664404145, mae=5.251753330230713, val_mae=11.265941619873047
200, loss=20.658276230189085, mae=3.334211826324463, val_mae=9.753406524658203
300, loss=19.401370901810495, mae=3.280524492263794, val_mae=4.465625762939453
400, loss=11.281997360312163, mae=2.3918709754943848, val_mae=3.2295753955841064
500, loss=12.884619414621831, mae=2.493798017501831, val_mae=5.0809550285339355
600, loss=9.389874838454066, mae=2.2560911178588867, val_mae=3.4492201805114746

historyを見ると693回で終了している。

len(history.history['loss'])
    #=> 693

止める基準として指定しているvalidation lossの状況。

plt.plot(history.history['loss'], alpha=0.5)
plt.plot(history.history['val_loss'], alpha=0.5)
plt.ylim([0, 500])

f:id:mwsoft:20190427202530p:plain

確かに600過ぎたあたりで改善は見られなくなっている。

過学習

今回のデータだとそれほど出なかったけど、チュートリアルのデータでは学習が進むとスコアが悪化する現象が出ている。

正則化とドロップアウトを加えて変化を確認する。

まずはL2正則化を加えた場合のコード。

model = tf.keras.Sequential([
    layers.Dense(64, activation='relu', input_shape=[13],
                 kernel_regularizer=tf.keras.regularizers.l2(0.001)),
    layers.Dense(64, activation='relu',
                 kernel_regularizer=tf.keras.regularizers.l2(0.001)),
    layers.Dense(1)
])

Dropoutに0.5を指定した場合のコード。

model = tf.keras.Sequential([
    layers.Dense(64, activation='relu', input_shape=[13]),
    layers.Dropout(0.1),
    layers.Dense(64, activation='relu'),
    layers.Dropout(0.3),
    layers.Dense(1)
])

それぞれephochに1万を指定して回した結果。

条件 test loss test mae
何もしない 29.21 3.69
L2正則化 20.23 3.28
Dropout 19.44 2.72
何もしない L2正則化 Dropout
f:id:mwsoft:20190427204739p:plain:w200 f:id:mwsoft:20190427205318p:plain:w200 f:id:mwsoft:20190428182725p:plain:w200

正則化等を加えない場合は過学習気味なvalidate lossの上昇が見られるのに対して、正則化、Dropoutでは改善しているのがわかる。

モデルのsave/load

生成したモデルのsave/loadをしてみる。

モデルの作成コードは適当にこんな感じで。

model = tf.keras.Sequential([
    layers.Dense(64, activation='relu', input_shape=[13]),
    layers.Dropout(0.1),
    layers.Dense(64, activation='relu'),
    layers.Dropout(0.3),
    layers.Dense(1)
])

optimizer = tf.keras.optimizers.RMSprop(0.001)
model.compile(loss='mse',
            optimizer=optimizer,
            metrics=['mae', 'mse'])

history = model.fit(
    x_train, y_train,
    epochs=100,
    validation_split= 0.2,
    verbose=0)

model.evaluate(x_test, y_test)
    #=> 55.442268521178, 5.1921906

100回しか回してないのでloss=55.44。

こうしてできたモデルを保存、読み込み。

# 保存
model.save('handwritten_digits.h5')

# 読込み
model2 = tf.keras.models.load_model('handwritten_digits.h5')

読み込んだモデルに対して再度100回学習。

history = model2.fit(
    x_train, y_train,
    epochs=100,
    validation_split= 0.2,
    verbose=0)

model2.evaluate(x_test, y_test)
    #=> 30.69029639748966, 3.8236444

モデルはh5形式なのでh5pyを使って中身を確認することもできる。

import h5py
f = h5py.File('handwritten_digits_model', 'r')
list(f.keys())
    #=> ['model_weights', 'optimizer_weights']

チェックポイントの生成

学習中に定期的にチェックポイントを作成する。下記は20回に1度チェックポイントを作っている。

checkpoint_path = "training_1/cp.ckpt"
cp_callback = tf.keras.callbacks.ModelCheckpoint(
        checkpoint_path, verbose=1, period=20)

optimizer = tf.keras.optimizers.RMSprop(0.001)
model.compile(loss='mse',
            optimizer=optimizer,
            metrics=['mae', 'mse'])

model.fit(
    x_train, y_train,
    epochs=100,
    verbose=0,
    callbacks=[cp_callback])

生成したチェックポイントはモデルと同じようにロードできる。

model3 = tf.keras.models.load_model(checkpoint_path)

毎回モデルごと生成していると重いので、weightsだけを保存することもできる。

checkpoint_path = "training_2/cp.ckpt"
cp_callback = tf.keras.callbacks.ModelCheckpoint(
    checkpoint_path, save_weights_only=True, verbose=1, period=20)

この場合はmodels.load_modelではロードできない。

model4 = tf.keras.models.load_model(checkpoint_path)
    #=> OSError: Unable to open file (unable to open file: name = 'training_2/cp.ckpt', errno = 2, error message = 'No such file or directory', flags = 0, o_flags = 0)

compile済のモデルに対してload_weightsすると復元できる。

model = tf.keras.Sequential([
    layers.Dense(64, activation='relu', input_shape=[13]),
    layers.Dropout(0.1),
    layers.Dense(64, activation='relu'),
    layers.Dropout(0.3),
    layers.Dense(1)
])

optimizer = tf.keras.optimizers.RMSprop(0.001)
model.compile(loss='mse',
            optimizer=optimizer,
            metrics=['mae', 'mse'])

# checkpointから復元
model.load_weights(checkpoint_path)

固定パスでチェックポイントの出力先を指定した場合は、そのパスにファイルが毎回上書きされる。

出力するたびにパスを変えたい場合は、パスに下記のように {epoch:04d} を埋め込む。

# パスに{epoch}を入れる
checkpoint_path = "training_3/cp-{epoch:04d}.ckpt"
cp_callback = tf.keras.callbacks.ModelCheckpoint(checkpoint_path, verbose=1, period=20)

optimizer = tf.keras.optimizers.RMSprop(0.001)
model.compile(loss='mse',
            optimizer=optimizer,
            metrics=['mae', 'mse'])

model.save_weights(checkpoint_path.format(epoch=0)) # ディレクトリ作成も込みで必要
model.fit(
    x_train, y_train,
    epochs=100,
    verbose=0,
    callbacks=[cp_callback])

出力したディレクトリを確認すると、cp-0000〜0100のチェックポイントが生成されている。

ls training_3

checkpoint  cp-0000.ckpt.data-00000-of-00001  cp-0000.ckpt.index  cp-0020.ckpt  cp-0040.ckpt  cp-0060.ckpt  cp-0080.ckpt  cp-0100.ckpt

改定履歴

Author: Masato Watanabe, Date: 2019-05-06 記事投稿