時系列データから自動で特徴抽出するライブラリ tsfresh


Ateam Lifestyle Advent Calendar 2019の5日目は、
株式会社エイチームライフスタイル CTO室 エンジニアの小林が担当します。
会社では機械学習のプロジェクトに取り組んでいます。

はじめに

最近仕事で時系列データを扱っており、tsfreshという時系列のデータから自動で特徴抽出を行ってくれる便利なライブラリを利用したのでその紹介です。
時系列のデータは順序に意味がありますが、そこの意味づけがあまりできていなかったので特徴抽出方法を調べたことが背景です。
時系列データの予測の例でいうと、

  • 過去の商品の売上から未来の売上を予測する
  • ユーザの行動ログから次の行動を予測する
  • センサーが取り込んだデータから異常を検知する

などが挙げられます。どれも時系列に並んだデータを個別に扱うよりも、順序に意味を持たせてデータを扱ったほうが精度がよくなると予想できます。
tsfreshは時系列データから特徴を抽出するため、精度改善に貢献できそうです。

tsfreshのGithub上に使い方のnotebookがあるので、それを参考にGoogle Colaboratoryで実行しました。
Google ColaboratoryはJupyter Notebookを無料で使える環境です。
用途は機械学習の研究や教育に限られますが、GPUやTPUが無料で使えて、始めから機械学習用のPythonライブラリが整っているすごいサービスなのでおすすめです。

準備

まずはtsfreshをインストールしていきます。
以下はJupyter Notebook上での記述法になります。

!pip install tsfresh

必要なパッケージをインポート

%matplotlib inline
import matplotlib.pylab as plt
from tsfresh.examples.har_dataset import download_har_dataset, load_har_dataset, load_har_classes
from tsfresh import extract_features, extract_relevant_features, select_features
from sklearn.model_selection import train_test_split
from sklearn.metrics import classification_report
import xgboost as xgb
import pandas as pd
import numpy as np

データをダウンロード

# fetch dataset from uci
download_har_dataset()
df = load_har_dataset()
print(df.head())
df.shape
        0         1         2    ...       125       126       127
0  0.000181  0.010139  0.009276  ... -0.001147 -0.000222  0.001576
1  0.001094  0.004550  0.002879  ... -0.004646 -0.002941 -0.001599
2  0.003531  0.002285 -0.000420  ...  0.001246  0.003117  0.002178
3 -0.001772 -0.001311  0.000388  ... -0.000867 -0.001172 -0.000028
4  0.000087 -0.000272  0.001022  ... -0.000698 -0.001223 -0.003328

[5 rows x 128 columns]
(7352, 128)

このデータは各行が128個の時系列の加速度センサの数値になっており、
6つに分類(歩く、階段を登る、階段を降りる、座っている、立っている、寝ている)されるようです。
データ元: https://archive.ics.uci.edu/ml/datasets/Human+Activity+Recognition+Using+Smartphones

1行をグラフで表示してみます。

plt.title('accelerometer reading')
plt.plot(df.ix[0,:])
plt.show()

特徴抽出

データをサンプリングして整形します。

N = 500
master_df = pd.DataFrame({0: df[:N].values.flatten(),
                          1: np.arange(N).repeat(df.shape[1])})
master_df.head()
    0   1
0   0.000181    0
1   0.010139    0
2   0.009276    0
3   0.005066    0
4   0.010810    0

tsfreshが扱えるように、横に128列並んでいたデータを縦に並び替えています。0の列がデータで、1の列がインデックス(元データの行番号)になっています。
このデータを使って特徴抽出していきます。
column_idでインデックスのカラムを指定しています。

X = extract_features(master_df, column_id=1)
Feature Extraction: 100%|██████████| 5/5 [01:02<00:00, 12.48s/it]

500行で1分ほどかかりました。
実際に10万行以上のデータで試したときはかなり時間がかかるのと、メモリも必要だったので、大規模なデータに適用するときは工夫が必要そうです。

X.shape
(500, 754)

754個もの特徴量が抽出されています。
内容は詳しく確認できていませんが、平均や中央値などの基本的なものから、フーリエ変換など様々な特徴量を計算してくれているようです。

精度検証

抽出された特徴量を使って学習と予測をしてみます。
まずは教師データを準備します。

y = load_har_classes()[:N]
y.hist(bins=12)

そこまで極端にばらついてはいなさそうです。
学習データとテストデータに分けます。

X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=.2)

xgboostで学習と予測をしてみます。

cl = xgb.XGBClassifier()
cl.fit(X_train, y_train)
print(classification_report(y_test, cl.predict(X_test)))
              precision    recall  f1-score   support

           1       1.00      1.00      1.00        24
           2       1.00      1.00      1.00        11
           3       1.00      1.00      1.00        12
           4       0.62      0.53      0.57        15
           5       0.70      0.74      0.72        19
           6       0.60      0.63      0.62        19

    accuracy                           0.81       100
   macro avg       0.82      0.82      0.82       100
weighted avg       0.81      0.81      0.81       100

80%くらいの精度が出ているようです。
特徴量の重要度も見てみます。

importances = pd.Series(index=X_train.columns, data=cl.feature_importances_)
importances.sort_values(ascending=False).head(10)
variable
0__spkt_welch_density__coeff_8                                   0.054569
0__time_reversal_asymmetry_statistic__lag_3                      0.041737
0__agg_linear_trend__f_agg_"max"__chunk_len_5__attr_"stderr"     0.036145
0__standard_deviation                                            0.035886
0__change_quantiles__f_agg_"var"__isabs_False__qh_0.4__ql_0.0    0.028676
0__spkt_welch_density__coeff_2                                   0.027741
0__augmented_dickey_fuller__attr_"pvalue"                        0.019172
0__autocorrelation__lag_2                                        0.018580
0__linear_trend__attr_"stderr"                                   0.018235
0__cid_ce__normalize_True                                        0.018181
dtype: float32

変数名からどんな特徴量を作っているかが分かりそうです。
比較するために元のデータセットでも学習させてみます。

X_1 = df.ix[:N-1,:]
X_1.shape
(500, 128)
X_train, X_test, y_train, y_test = train_test_split(X_1, y, test_size=.2)
cl = xgb.XGBClassifier()
cl.fit(X_train, y_train)
print(classification_report(y_test, cl.predict(X_test)))
              precision    recall  f1-score   support

           1       0.79      0.83      0.81        36
           2       0.71      0.67      0.69        15
           3       0.58      0.58      0.58        12
           4       0.25      0.43      0.32         7
           5       0.67      0.53      0.59        19
           6       0.44      0.36      0.40        11

    accuracy                           0.64       100
   macro avg       0.57      0.57      0.56       100
weighted avg       0.65      0.64      0.64       100

元データだと64%ほどの精度なので、tsfreshで特徴抽出したデータの方が精度が出ています。

まとめ

tsfreshを使って時系列データから特徴を抽出することで、精度を上げることができました。
抽出前は時系列データの順序に意味づけができていなかったところから、意味づけできた結果だと考えていますが、
どんなデータが抽出されたは詳しく見れていないので、どんなデータなのかを調べていきたいと思います。

Ateam Lifestyle Advent Calendar 2019 の 7日目は、 @maonem がお送りします。楽しみですね!

"挑戦"を大事にするエイチームグループでは、一緒に働けるチャレンジ精神旺盛な仲間を募集しています。興味を持たれた方はぜひエイチームグループ採用サイトを御覧ください。
https://www.a-tm.co.jp/recruit/