概要
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)
カラムの意味は下記を参照。
不正確を覚悟で一言でカラムの意味を表すとこんな感じ。
カラム名 | 意味 |
---|---|
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')
確かに低所得者率(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)
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])
テストデータでの評価。
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)
けっこう外れている結果も見られる。
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])
確かに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 |
---|---|---|
![]() |
![]() |
![]() |
正則化等を加えない場合は過学習気味な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 記事投稿