はじめに
本記事では、時系列データに対する高速機械学習手法であるリザバーコンピューティング、特にEcho State Networkについて解説します。
忙しい人のため
- Echo State Networkとは、時系列データに対する高速機械学習手法である
- 入力層・リザバー・出力層の3つで構成されている
- 入力層とリザバーを通して高次元空間へ非線形に写像しつつ、RNN構造で時系列情報を取得することで、出力層(Ridge回帰)の予測を手助けする
- 入力層とリザバーのパラメータはランダムな初期値で固定して出力層のみ学習する。そのため従来の深層学習よりも学習コスト(計算機性能, 消費電力, 学習時間)が少ない!
- エッジコンピューティングのように、時系列データストリームの速度に合わせてリアルタイムにモデルを更新する必要があるケースで有用
リザバーコンピューティングとは?
通常のニューラルネットワークでは、学習時にGPUを使用してモデル全体のパラメータを更新します。これにより高い推論性能を発揮するモデルを作れますが、学習コスト(計算資源, 消費電力, 学習時間)がかかります。近年はモデルサイズ拡大のトレンドが見られるため、学習コストは馬鹿になりません。
監視カメラやスマートウォッチのようなエッジコンピューティングを想定してみましょう。これらの端末からはリアルタイムで大量の時系列データが得られます。このデータストリームに合わせてリアルタイムにモデル全体を更新するのは、現実的ではありません。
リザバーコンピューティングとは、時系列データをCPUでも高速に学習できる学習手法です。そのため、エッジコンピューティングと相性がいいとされています。リザバーコンピューティングが高速に学習できる理由は、学習するパラメータを極端に絞るからです。リザバーコンピューティングは入力層・リザバー・リードアウトで構成されますが、入力層とリザバーのパラメータはランダムな初期値で固定してリードアウトのみ学習します。リードアウトに使用するモデルもRidge回帰といったシンプルなモデルであるため、CPUでも高速に学習可能です。
つまり、学習コスト支払って高い性能を獲得するのが通常の学習、性能を多少犠牲にしても高速に学習できるのがリザバーコンピューティングです。そのため、どちらかが優れているというものではなく、目的に合わせて使い分けます。
Echo State Network
リザバーコンピューティングにはいくつか手法がありますが、今回は代表的なアーキテクチャであるEcho State Networkを紹介します。アーキテクチャを見るとむずそうに感じてしまいますが、本節をお読みいただければ見た目ほど難しくないことがお分かりになると思います。
アーキテクチャ
入力層
入力層では、全結合層による高次元空間への写像を行います。
リザバー
リザバーでは、RNN構造を用いることで状態ベクトルに時間情報を付与します。具体的には、入力層を通過したn時点目のデータとn-1時点目までの状態ベクトルを用いてn時点目の状態ベクトルを作成します。さらに、非線形活性化関数($tanh$)を用いることで、入力を非線形に変換します。
出力層
出力層はRidge回帰が用いられます。L2正則化を加えるのは、高次元空間に写像したことによる過学習の抑制のためです。学習ではこのRidge回帰の部分のみ学習します。
入力層とリザバーは、入力を高次元空間へ非線形に写像しつつ時系列情報を獲得する変換器と言えます。この変換器に入力を通すことで、入力をそのままRidge回帰に渡すよりも豊富な情報を渡すことができます。SVMのカーネル法に近いことを行っています。
数式での表現
【各時点の入力ベクトル, 状態ベクトル, 出力ベクトル】
\displaylines{
\mathbf{u}(n) = (u_1(n), ..., u_{N_u}(n))^\top \\
\mathbf{x}(n) = (x_1(n), ..., x_{N_x}(n))^\top \\
\mathbf{y}(n) = (y_1(n), ..., y_{N_y}(n))^\top \\
}
【リザバー状態ベクトルの時間発展】
\begin{align}
\displaylines{
\mathbf{x}(n+1) &=& \mathbf{f}(W^{in}\mathbf{u}(n+1)+W\mathbf{x}(n)) \;\;\;\;\;\;(n=0, 1, 2, ...)\\
\mathbf{x}(n+1) &=& \mathbf{f}(W^{in}\mathbf{u}(n+1)+W\mathbf{x}(n)+W^{fb}\mathbf{y}(n)) \;\;\;\;\;\;(n=0, 1, 2, ...)\\
\mathbf{y}(n+1) &=& W^{out}\mathbf{x}(n+1)\;\;\;\;\;\;(n=0, 1, 2, ...)
}
\end{align}
なぜリザバーがランダム値のままうまくいくのか?
入力層とリザバーのパラメータがランダムな初期値ままでよいというのは直観に反しますが、今回は問題ありません。なぜなら、ランダムに決められたパラメータではあるものの、そのパラメータに従って入力を高次元・非線形・時系列的に変換してくれさえすればいいからです。
たしかに、通常のRNNのように誤差逆伝播法を用いてモデル全体のパラメータを更新するほうが性能は高くなります。しかし、GPUのような計算資源と学習時間を多く必要とします。
一方で、リザバーコンピューティングはRNNを単なる入力の変換器として扱うため、学習するパラメータが少なくCPUなどの少ない計算資源で高速に学習することを可能です!
ハイパーパラメータ
Echo State Networkの性能を左右するハイパーパラメータがいくつかあります。ニューラルネットワークでは決め打ちになりがちなハイパーパラメータですが、リザバーコンピューティングならGrid Searchできます。
Leaky Integrator
状態を更新する際に現在の情報をどれだけ優先するのか決めるパラメータ(=モデルの状態変動の速さを調整するパラメータ)です。αが大きいほど現在の情報を重視して更新します。
\begin{align}
\displaylines{
\mathbf{x}(n+1) &=& (1-α)\mathbf{x}(n)+α\mathbf{f}(W^{in}\mathbf{u}(n+1)+W\mathbf{x}(n))\\
\mathbf{y}(n+1) &=& W^{out}\mathbf{x}(n+1)
}
\end{align}
Wの初期パラメータ
入力層、リザバー層、出力層のパラメータを一様分布から初期設定します。繰り返しますが、学習させるのは出力層のパラメータのみです。
リザバーの大きさ
リザバーのノード数、つまり入力をどれだけ高次元な空間に写像するか設定します($N_x$)。学習に用いる時系列の長さ$T$より大きくし過ぎないことが好まれます。リザバーが大きすぎると、学習時間の増加や過学習につながります。
ネットワークのスパース性
リザバーの重み$w_{ij}$を0にする確率を任意に設定できます。疎な結合にすることで、密なネットワークで起こりやすいノード状態の均一化を防ぎます。確率$p$は0.05~0.2程度が良いようです。
実験. 異常波形検知
センサーデータや生体信号は正常な時系列データがほとんどであり、異常データをたくさん集めるのは困難です。そこで、正常波形を学習したモデルに予測させ、予測が当たらない箇所を異常変動と判断する異常検知を行います。使用するデータは、リザバーコンピューティング 時系列パターン認識のための高速機械学習の理論とハードウェアで使用されているMIT-BIH不整脈のデータを利用しました。
import pandas as pd
from scipy.stats import uniform
import matplotlib.pyplot as plt
%matplotlib inline
from sklearn.metrics import mean_squared_error, make_scorer
from sklearn.model_selection import RandomizedSearchCV
from pyrcn.echo_state_network import ESNRegressor
from pyrcn.model_selection import SequentialSearchCV
# データの読み込み
normal_df = pd.read_csv("normal.csv").iloc[1:, :]
anomaly_df = pd.read_csv("anomaly.csv").iloc[1:, :]
print(f"{len(normal_df)}件")
display(normal_df.head())
print(f"{len(anomaly_df)}件")
display(anomaly_df.head())
# 学習・テストデータ
unit_sample = 128 # 周波数
T_start = unit_sample
T_end = 59*unit_sample
X_train = normal_df["'ECG1'"][T_start:T_end].astype(float).to_numpy().reshape(-1, 1)
y_train = normal_df["'ECG1'"][T_start+1:T_end+1].astype(float).to_numpy().reshape(-1, 1)
X_test = anomaly_df["'ECG1'"][T_start:T_end].astype(float).to_numpy().reshape(-1, 1)
y_test = anomaly_df["'ECG1'"][T_start+1:T_end+1].astype(float).to_numpy().reshape(-1, 1)
# 探索範囲の設定
params = {
"input_scaling": uniform(0, 1),
"hidden_layer_size":[36, 48, 60],
"spectral_radius":uniform(0.5, 1),
"leaking_rate":uniform(0, 1),
"reservoir_activation":["tanh"],
}
scorer = make_scorer(mean_squared_error)
kwargs = {
"n_iter":300, "n_jobs":-1, "scoring":scorer, "cv":3
}
searches = [("experiment", RandomizedSearchCV, params, kwargs)]
# ランダムサーチ
esn_regressor = ESNRegressor()
esn_opti = SequentialSearchCV(esn_regressor, searches, error_score="raise").fit(X_train, y_train)
print("最適パラメータ(300回探索)")
print("best_params", esn_opti.best_params_)
print("best_score", esn_opti.best_score_)
# 可視化(予測と正解の差分)
y_pred = esn_opti.predict(X=X_test)
plt.plot(abs(y_pred-y_test), label="Predict", color="black")
plt.xlabel("Time[sec]")
plt.ylabel("Error[mV]")
plt.title("Anormaly Detection")
plt.legend()
plt.show()
# 可視化(正解=異常データ)
plt.plot(y_test, label="True", color="black")
plt.xlabel("Time[sec]")
plt.ylabel("Error[mV]")
plt.title("Anormaly Data")
plt.legend()
plt.show()
予測と実測の差分を可視化したものが以下の図です。差分の大きい箇所が閾値を超えたら異常波形と予測できそうです。下の図の差分の大きい箇所が、上の図の正解データ(異常データ)の異常個所と一致していることから、しっかり予測できています。