アダコテックでは、コア技術として HLAC (Higher-order Local Auto-Correlation; 高次局所自己相関特徴) と多変量解析手法を組み合わせて異常検知技術をさまざまな分野へ応用しています。
今回は、HLACの亜種である HLIC について解説します。
HLICとは
HLICとは、Higher-order Local Inter-Correlation の略で、HLAC を多チャンネル時系列データ向けにアレンジした特徴抽出手法です。
HLACについては、以下の記事をご覧ください。
N次のHLACの定義は以下になります。
HLICはHLACと同じ定義ですが、HLACとの違いは、HLACは画像データに適用するのに対して、HLICは多チャンネル時系列データに適用します。
多チャンネル時系列データってどんなもの?
多チャンネル時系列データとは、時間に沿って取得されるデータのうち、複数の情報チャンネルやソースからのデータを同時に取得するものを指します。
多チャンネル時系列データは多岐にわたる分野で利用されます。
- 心電図
- 複数の電極が体の異なる位置に配置され、それぞれの位置からの信号が時系列として記録されます。
- 機械のモニタリング
- 各部品の振動や温度をセンサーで取得して、時系列データとして分析することで、異常検知や予測保全などに利用します。
- スポーツ分析
- アスリートの動きをセンサーやモーションキャプチャで記録する場合、複数の関節や筋肉の動きが時系列データとして取得できます。
- 気象
- 同じ地点や異なる地点での気温、湿度、気圧、風速などを同時に記録する場合。これにより、気象パターンや異常な気象現象の予測などに利用されます。
このように、時系列データの中でも複数のチャンネルからデータが取得されるものを多チャンネル時系列データと言います。
HLICのマスクパターン
HLICの場合は、現在時刻のデータ、その1つ前の時刻のデータ、その2つ前の時刻のデータを各チャンネル間との間での掛け算のパターンであらわします。
2フレーム(時間方向)×2チャンネルの場合のマスクパターン
1チャンネル間の相関が3パターン、2チャンネル間の相関が3パターンとなります。
任意の チャンネルのパターン数 は、
となります。
3フレーム(時間方向)×3チャンネルの場合のマスクパターン
1チャンネル間の相関が10パターン、2チャンネル間の相関が29パターン、3チャンネル間の相関が19パターンとなります。
任意の チャンネルのパターン数 は、
となります。
異常検知の流れ
基本的な異常検知の流れは、CHLACの場合と同様です。
HLIC特徴量は、元となる時系列データの各チャンネルの単位やスケールが異なるため、標準化(平均0分散1で正規化)します。 そして、標準化したHLIC特徴量をもとに、統計的な多変量解析手法によって、異常検知を行います。 時系列データにおける正常パターンは出現頻度が高いため、そのような分布を部分空間に近似して、正常部分空間をもとめます。 正常部分空間からの距離を異常値とし、閾値を超えたら異常として判定します。
心電図での異常検知の例
心電図のデータを使ってHLICの異常検知をやってみます。
心電図は、心臓の電気信号を体表から記録したものです。この信号は心臓を正しく動かすためのもので、心電図は心臓の問題、特に不整脈や心臓の血流の問題を見つけるのに役立ちます。
普通の心電図には12の誘導(I, II, III, aVR, aVL, aVF, V1, V2, V3, V4, V5, V6)という部分があります。これは、体の異なる場所から心臓の電気活動を捉えるためのものです。手足に関しては6つの誘導があり、胸部にも6つの誘導があります。特に不整脈をチェックするときは、これらの中の第II誘導とV1誘導に特に注目することが多いです。
By <a href="//commons.wikimedia.org/wiki/User:Npatchett" title="User:Npatchett">Npatchett</a> - <span class="int-own-work" lang="en">Own work</span>, CC BY-SA 4.0, Link
心電図のデータは↓こちらを利用させていただきました。 www.physionet.org
HLIC特徴を抽出するPythonのプログラムはこちらです。 この例では、3フレーム×2チャンネル間、2次までのHLIC特徴を使いました。
import numpy as np def extract_hlic_features(signal: np.ndarray, corrwidth: int) -> np.ndarray: """HLIC特徴を抽出する (3フレーム, 2チャンネル, 2次まで) Parameters ---------- signal : np.ndarray 多チャンネル時系列データ [フレーム, チャンネル] corrwidth : int 相関幅 Returns ------- np.ndarray HLAC特徴 [フレーム, 特徴次元] """ feat = [] length = signal.shape[0] t0 = slice(2 * corrwidth, length) t1 = slice(1 * corrwidth, length - 1 * corrwidth) t2 = slice(0, length - 2 * corrwidth) # 要素{0} : 0次 feat.append(signal[t0, 0]) # 要素{0} : 1次 feat.append(signal[t0, 0] * signal[t0, 0]) feat.append(signal[t0, 0] * signal[t1, 0]) feat.append(signal[t0, 0] * signal[t2, 0]) # 要素{0} : 2次 feat.append(signal[t0, 0] * signal[t0, 0] * signal[t0, 0]) feat.append(signal[t0, 0] * signal[t0, 0] * signal[t1, 0]) feat.append(signal[t0, 0] * signal[t0, 0] * signal[t2, 0]) feat.append(signal[t0, 0] * signal[t1, 0] * signal[t1, 0]) feat.append(signal[t0, 0] * signal[t1, 0] * signal[t2, 0]) feat.append(signal[t0, 0] * signal[t2, 0] * signal[t2, 0]) # 要素{1} : 0次 feat.append(signal[t0, 1]) # 要素{1} : 1次 feat.append(signal[t0, 1] * signal[t0, 1]) feat.append(signal[t0, 1] * signal[t1, 1]) feat.append(signal[t0, 1] * signal[t2, 1]) # 要素{1} : 2次 feat.append(signal[t0, 1] * signal[t0, 1] * signal[t0, 1]) feat.append(signal[t0, 1] * signal[t0, 1] * signal[t1, 1]) feat.append(signal[t0, 1] * signal[t0, 1] * signal[t2, 1]) feat.append(signal[t0, 1] * signal[t1, 1] * signal[t1, 1]) feat.append(signal[t0, 1] * signal[t1, 1] * signal[t2, 1]) feat.append(signal[t0, 1] * signal[t2, 1] * signal[t2, 1]) # 要素{0,1} : 1次 feat.append(signal[t0, 0] * signal[t0, 1]) feat.append(signal[t0, 0] * signal[t1, 1]) feat.append(signal[t0, 0] * signal[t2, 1]) feat.append(signal[t1, 0] * signal[t0, 1]) feat.append(signal[t2, 0] * signal[t0, 1]) # 要素{0,1} : 2次 feat.append(signal[t0, 0] * signal[t0, 0] * signal[t0, 1]) feat.append(signal[t0, 0] * signal[t0, 0] * signal[t1, 1]) feat.append(signal[t0, 0] * signal[t0, 0] * signal[t2, 1]) feat.append(signal[t0, 0] * signal[t1, 0] * signal[t0, 1]) feat.append(signal[t0, 0] * signal[t1, 0] * signal[t1, 1]) feat.append(signal[t0, 0] * signal[t1, 0] * signal[t2, 1]) feat.append(signal[t0, 0] * signal[t2, 0] * signal[t0, 1]) feat.append(signal[t0, 0] * signal[t2, 0] * signal[t1, 1]) feat.append(signal[t0, 0] * signal[t2, 0] * signal[t2, 1]) feat.append(signal[t1, 0] * signal[t1, 0] * signal[t0, 1]) feat.append(signal[t1, 0] * signal[t2, 0] * signal[t0, 1]) feat.append(signal[t2, 0] * signal[t2, 0] * signal[t0, 1]) feat.append(signal[t0, 0] * signal[t0, 1] * signal[t0, 1]) feat.append(signal[t0, 0] * signal[t0, 1] * signal[t1, 1]) feat.append(signal[t0, 0] * signal[t0, 1] * signal[t2, 1]) feat.append(signal[t0, 0] * signal[t1, 1] * signal[t1, 1]) feat.append(signal[t0, 0] * signal[t1, 1] * signal[t2, 1]) feat.append(signal[t0, 0] * signal[t2, 1] * signal[t2, 1]) feat.append(signal[t1, 0] * signal[t0, 1] * signal[t0, 1]) feat.append(signal[t1, 0] * signal[t0, 1] * signal[t1, 1]) feat.append(signal[t1, 0] * signal[t0, 1] * signal[t2, 1]) feat.append(signal[t2, 0] * signal[t0, 1] * signal[t0, 1]) feat.append(signal[t2, 0] * signal[t0, 1] * signal[t1, 1]) feat.append(signal[t2, 0] * signal[t0, 1] * signal[t2, 1]) return np.array(feat).transpose()
結果は↓こちらです。
グラフは上から、入力の第II誘導とV1誘導、結果の異常値となっています。 背景の青い部分が学習に使用した区間、赤い部分が異常を示すラベルが付けられていた区間となっています。 異常値グラフの赤点線にしきい値を決めれば、異常を検出できます。
このように、HLIC特徴を使って心電図の異常を検出できることがわかりました。
故障予兆検知
このHLIC特徴量は、異常検知以外にも故障予兆検知などへの応用も可能です。
故障予兆では、異常値の累積値を使用します。 累積値の上昇傾向と通常使用した場合の故障時期から、あらかじめしきい値を決めておき、累積値がしきい値を超えたら、アラートをユーザーに通知します。
この故障予兆検知の技術を取り入れた AdaMonitor という当社製品があります。 この AdaMonitor を用いた、福井大学による機械設備のしゅう動面状態監視システムに関する研究成果があるので、ご参考ください。
機械学習を用いたしゅう動面状態監視システムに関する研究(日本機械学会論文集 Vol.84, No.868, 2018)
https://www.jstage.jst.go.jp/article/transjsme/84/868/84_18-00275/_pdf/-char/ja
上図 Fig.16では、赤矢印の段階で鉄粉油を投入していますが、この段階で、異常値が上がり始め摩耗状態や潤滑状態に変化が現れ始め、故障の原因につながることがわかります。
おわりに
多チャンネル時系列データにおける特徴抽出手法のHLICと、HLICを使った異常検知・故障予兆検知について解説しました。 この記事が皆さんの知識の一助となれば嬉しいです。