10
16

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

DeepLearningを使った時系列分類

Posted at

前書き

昨年度までは諸事情によりCaffeを利用させていただいていましたが、
今年度はなるべくTensorFlowで開発を実施したいなぁと考えております。

kerasを使ったことはありますが、ここ最近のTensorFlowのアップデートを見ると
どうもEstimatorというAPIがキテいる模様・・・

ということもありますので、以前紹介させていただきました、時系列データを使用して
Estimatorを使ったDeepLearningを行ないます。

下準備

私は以下環境で開発しました。

足りないものあれば適宜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も含めてまだまだ勉強だなぁと思いました。

10
16
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
16

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?