前書き
昨年度までは諸事情によりCaffeを利用させていただいていましたが、
今年度はなるべくTensorFlowで開発を実施したいなぁと考えております。
kerasを使ったことはありますが、ここ最近のTensorFlowのアップデートを見ると
どうもEstimatorというAPIがキテいる模様・・・
ということもありますので、以前紹介させていただきました、時系列データを使用して
Estimatorを使ったDeepLearningを行ないます。
下準備
私は以下環境で開発しました。
-
python (3.5.2)
-
tensorflow (1.7.0)
-
pandas (0.22.0)
-
評価データ (UCR Time Series Classification Archive)
- リンク先上部のarchiveからダウンロードできます。
足りないものあれば適宜pip等でインストールしていただければ幸いです。
TensorFlowのバージョンが低いとEstimatorなどが"contrib"の方にいるかもしれないので、
なるべく最新の安定板が良いんじゃないかなぁと思います。
また時系列データが対象なので、GPUを積んでないようなマシンでも十分動作します。
(私もノートPCで開発しておりますし・・・)
ソースコード
本当はGitHubを利用すれば良いのでしょうが、ほとんど使ったことなく
導入に時間がかかりそうなため、今回はベタ張りでディレクトリ構成およびソースコードを張ります。
一応ソースにはコメントをつけておりますが、公式ドキュメントが充実しておりますので、
そちらを参照してもらった方が良いかもしれません。
ディレクトリ構成
評価データをdataフォルダに格納します。
サンプルだと”Adiac”のみを使用しているので、自分にあった使いたい
データセットを格納してください。
estimator_test
├── data
│ └── UCR_TS_Archive_2015
│ ├── Adiac
│ ...
│ └── yoga
│
└── src
└── main.py
main.pyの中身
下記の論文およびコードを参考にしています。
Time Series Classification from Scratch with Deep Neural Networks: A Strong Baseline
↑論文で提案されているMLPを再現しています。
TensorFlow/models/samples/core/get_started/
#!/usr/bin/env python
# -*- coding: utf-8 -*-
import argparse
import pandas as pd
import numpy as np
import tensorflow as tf
label_name = "label"
def data_norm_label(data_y):
# 分類クラスの番号を正規化します
# ラベルを0スタートにしないとDatasetの作成部分で上手くいきませんでした
nb_classes = data_y.nunique()
data_y = (data_y - data_y.min()) / (data_y.max() - data_y.min()) * (nb_classes - 1)
data_y = data_y.astype("int64")
return data_y
def data_norm_feature(data_x, train_mean=None, train_std=None):
# 学習データの正規化(平均0分散1)
# 正規化しないとうまく学習が進みません
# また、テストデータは学習データと同様の範囲で正規化します
if train_mean == None:
train_mean = np.mean(data_x.values.flatten())
train_std = np.std(data_x.values.flatten())
# 以下だと列ごとに正規化する
# train_mean = data_x.mean()
# train_std = data_x.std()
data_x = (data_x - train_mean) / (train_std)
return data_x, train_mean, train_std
def column_rename(df_data):
# ヘッダーの指定なしに読み込んでいるので、
# 列名には行数がそのまま割り振られています
# 列名が数値だとDatasetでエラーが出たため、ラベル名を作成してます
rename_list = [label_name]
feature_list = []
for index in range(len(df_data.columns)-1):
feature_list.append("feature" + str(index))
rename_list = rename_list + feature_list
df_data.columns = rename_list
return df_data
def load_data(dir_name):
# ファイルより学習データ、テストデータを読み込む
# 読み込み時に正規化等の前処理を行う
train_path = ".." + "/" + "data" + "/" + "UCR_TS_Archive_2015" + "/" + dir_name + "/" + dir_name + "_TRAIN"
test_path = ".." + "/" + "data" + "/" + "UCR_TS_Archive_2015" + "/" + dir_name + "/" + dir_name + "_TEST"
train = pd.read_csv(train_path, header=None)
train = column_rename(train)
train_x, train_y = train, train.pop(label_name)
train_x, train_mean, train_std = data_norm_feature(train_x)
train_y = data_norm_label(train_y)
print(train_mean)
print(train_std)
test = pd.read_csv(test_path, header=None)
test = column_rename(test)
test_x, test_y = test, test.pop(label_name)
test_x, train_mean, train_std = data_norm_feature(test_x, train_mean=train_mean, train_std=train_std)
test_y = data_norm_label(test_y)
return (train_x, train_y), (test_x, test_y)
def my_model(features, labels, mode, params):
# 学習モデルの構成
net = tf.feature_column.input_layer(features, params['feature_columns'])
net = tf.layers.dropout(net, rate=0.1)
net = tf.layers.dense(net, units=500, activation=tf.nn.relu)
net = tf.layers.dropout(net, rate=0.2)
net = tf.layers.dense(net, units=500, activation=tf.nn.relu)
net = tf.layers.dropout(net, rate=0.2)
net = tf.layers.dense(net, units=500, activation=tf.nn.relu)
net = tf.layers.dropout(net, rate=0.3)
logits = tf.layers.dense(net, params['n_classes'], activation=None)
predicted_classes = tf.argmax(logits, 1)
if mode == tf.estimator.ModeKeys.PREDICT:
predictions = {
'class_ids': predicted_classes[:, tf.newaxis],
'probabilities': tf.nn.softmax(logits),
'logits': logits,
}
return tf.estimator.EstimatorSpec(mode, predictions=predictions)
loss = tf.losses.sparse_softmax_cross_entropy(labels=labels, logits=logits)
accuracy = tf.metrics.accuracy(labels=labels,
predictions=predicted_classes,
name='acc_op')
metrics = {'accuracy': accuracy}
tf.summary.scalar('accuracy', accuracy[1])
if mode == tf.estimator.ModeKeys.EVAL:
return tf.estimator.EstimatorSpec(
mode, loss=loss, eval_metric_ops=metrics)
assert mode == tf.estimator.ModeKeys.TRAIN
optimizer = tf.train.AdamOptimizer(learning_rate=0.001)
train_op = optimizer.minimize(loss, global_step=tf.train.get_global_step())
return tf.estimator.EstimatorSpec(mode, loss=loss, train_op=train_op)
def train_input_fn(features, labels, batch_size):
dataset = tf.data.Dataset.from_tensor_slices((dict(features), labels))
dataset = dataset.shuffle(1000).repeat().batch(batch_size)
return dataset
def eval_input_fn(features, labels, batch_size):
features=dict(features)
if labels is None:
inputs = features
else:
inputs = (features, labels)
dataset = tf.data.Dataset.from_tensor_slices(inputs)
assert batch_size is not None, "batch_size must not be None"
dataset = dataset.batch(batch_size)
return dataset
parser = argparse.ArgumentParser()
parser.add_argument('--batch_size', default=100, type=int, help='batch size')
parser.add_argument('--train_steps', default=1000, type=int,
help='number of training steps')
#flist = ['Adiac', 'Beef', 'CBF', 'ChlorineConcentration', 'CinC_ECG_torso', 'Coffee', 'Cricket_X', 'Cricket_Y', 'Cricket_Z',
#'DiatomSizeReduction', 'ECGFiveDays', 'FaceAll', 'FaceFour', 'FacesUCR', '50words', 'FISH', 'Gun_Point', 'Haptics',
#'InlineSkate', 'ItalyPowerDemand', 'Lighting2', 'Lighting7', 'MALLAT', 'MedicalImages', 'MoteStrain', 'NonInvasiveFatalECG_Thorax1',
#'NonInvasiveFatalECG_Thorax2', 'OliveOil', 'OSULeaf', 'SonyAIBORobotSurface', 'SonyAIBORobotSurfaceII', 'StarLightCurves', 'SwedishLeaf', 'Symbols',
#'synthetic_control', 'Trace', 'TwoLeadECG', 'Two_Patterns', 'uWaveGestureLibrary_X', 'uWaveGestureLibrary_Y', 'uWaveGestureLibrary_Z', 'wafer', 'WordsSynonyms', 'yoga']
flist = ['Adiac']
def main(argv):
args = parser.parse_args(argv[1:])
for file_name in flist:
(train_x, train_y), (test_x, test_y) = load_data(file_name)
nb_classes = train_y.nunique()
my_feature_columns = []
for key in train_x.keys():
my_feature_columns.append(tf.feature_column.numeric_column(key=key))
classifier = tf.estimator.Estimator(
model_fn=my_model,
params={
'feature_columns': my_feature_columns,
'n_classes': nb_classes,
})
classifier.train(
input_fn=lambda:train_input_fn(train_x, train_y,
args.batch_size),
steps=args.train_steps)
eval_result = classifier.evaluate(
input_fn=lambda:eval_input_fn(test_x, test_y,
args.batch_size))
print('\nTest set accuracy: {accuracy:0.3f}\n'.format(**eval_result))
if __name__ == '__main__':
tf.logging.set_verbosity(tf.logging.INFO)
tf.app.run(main)
##実行結果
学習ステップに応じて、lossが低下していることがわかります。
最終的なテストデータに対する正解率は63.4%、論文のErrRateは0.248(正解率:75.2%)
...(゚∀゚)??
論文のGitHubを見ると、fitのValidationにテストデータを指定しているので、
テストデータ寄りに学習が進んでるのかもしれません。
(kerasの中身を知らないので私のカンではありますが・・・)
また、batchsizeや、epochsizeも異なるので、その辺りも影響しているのだと思います。
サンプル等々のツギハギソースで、とりあえず上手く動いて、
それなりに分類できていることがわかりました。
NFO:tensorflow:global_step/sec: 55.9142
INFO:tensorflow:loss = 1.8498216, step = 101 (1.790 sec)
INFO:tensorflow:global_step/sec: 69.5335
INFO:tensorflow:loss = 0.93660194, step = 201 (1.438 sec)
INFO:tensorflow:global_step/sec: 69.4014
INFO:tensorflow:loss = 1.1435629, step = 301 (1.441 sec)
INFO:tensorflow:global_step/sec: 70.3544
INFO:tensorflow:loss = 0.49161476, step = 401 (1.421 sec)
INFO:tensorflow:global_step/sec: 69.8491
INFO:tensorflow:loss = 0.4364499, step = 501 (1.432 sec)
INFO:tensorflow:global_step/sec: 70.5117
INFO:tensorflow:loss = 0.48663452, step = 601 (1.418 sec)
INFO:tensorflow:global_step/sec: 70.9824
INFO:tensorflow:loss = 0.3646434, step = 701 (1.409 sec)
INFO:tensorflow:global_step/sec: 70.5241
INFO:tensorflow:loss = 0.5106917, step = 801 (1.418 sec)
INFO:tensorflow:global_step/sec: 70.6443
INFO:tensorflow:loss = 0.25705165, step = 901 (1.416 sec)
INFO:tensorflow:Saving checkpoints for 1000 into /tmp/tmpl8zt72wq/model.ckpt.
INFO:tensorflow:Loss for final step: 0.25710252.
INFO:tensorflow:Calling model_fn.
INFO:tensorflow:Done calling model_fn.
INFO:tensorflow:Starting evaluation at 2018-04-29-09:15:37
INFO:tensorflow:Graph was finalized.
INFO:tensorflow:Restoring parameters from /tmp/tmpl8zt72wq/model.ckpt-1000
INFO:tensorflow:Running local_init_op.
INFO:tensorflow:Done running local_init_op.
INFO:tensorflow:Finished evaluation at 2018-04-29-09:15:37
INFO:tensorflow:Saving dict for global step 1000: accuracy = 0.6342711, global_step = 1000, loss = 2.2607203
Test set accuracy: 0.634
所感
以下作ってみて思ったことを・・・
- 本題であるEstimatorの記述は直感的で使いやすいと思った
- ただし、他の高位APIや他OSSと同様に入力データの編集部分で手間
- ↑に関連してDataSetの作りがキモな気がする
- 入力データの生成とモデルの生成が切り離されているので、一度DataSetを作ればモデルの変更を行ないながらの検討は捗るか・・?
- MLPベース->CNNベースに変更したらDataSetの形式を変更になるのでは?↑の旨味は薄くなる?
- シャッフルやバッチ作成など、取り回しはすごい楽そう
- 今の所kerasとの違い(旨味)が体感できていない。kerasより高自由度な感触はあるが・・・
- 使い慣れてないだけで、プログラム自体は書きやすい印象
- OSSやAPIがいっぱいあって追いつけない(゚∀゚)
DataSetも含めてまだまだ勉強だなぁと思いました。