【検証：GPT-4o】Assistantsで時系列異常検知


# はじめに
ChatGPTといった大規模言語モデル(LLM)の発展に伴い、一部のデータ分析はノーコードのエージェントに置き換えられる可能性があります。
今回はGPT-4o[[参考1](#参考)]をベースとしたAssistants[[参考2-4](#参考)]を使って、単変量時系列データの異常検知タスクについて、テキスト入力・画像入力でどこまでやれるかを検証しました。

（今回の検証では1アシスタントに複数の候補処理を与えるのではなく、1アシスタントに1処理を与えて、適切に対応してくれるかを調べます）

# Assistantsに期待すること
OpenAIのAssistantsは、以下のようなパラメータを設定することで目的を遂行するエージェントを作成できます（詳細は[[参考2-4](#参考)]）。

- Instructions：アシスタントの役割を設定するプロンプト部分
- Model：モデルを選択
- Tools：連携するAPIを選択（今回はCode Interpreterを使用）
- Functions: 関数を定義し選択（今回は使用しない）

今回、モデルにはビジョン機能を備えた以下を選択します。

-	ChatGPT API (gpt-4o) [[参考1](#参考)]
-	ChatGPT API (gpt-4-turbo-2024-04-09) [[参考5](#参考)]（比較用）

今回の検証でAssistantsに期待することは以下2点です。

**コーディング**
- 手でコーディングした処理をテキスト指示により再現してくれる
- 結果の評価を自動で実行してくれる

**画像認識**
- 画像を読み取って事前に定義した選択肢のどれにあたるかを適切に判定してくれる

# 問題設定
本記事では、可視化された時系列データを確認した担当者がその中に含まれている異常箇所を抽出するタスクを想定します。

<img width="400" alt="図1.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/892617/a6b5c3d7-2de1-3bb8-6e40-315816ec1265.png">

<strong>図1. 可視化された時系列データの確認イメージ</strong>

今回の検証では、タスクを「グラフ画像に含まれる異常の判別」と「数値データからの検知」の2種類に分け、それぞれ実験を行います。前者は正解数、後者はベースラインとの比較によって評価します。

検証の流れは以下の通りです。

1.	**データ作成**：単変量時系列データについて、典型的な異常を含んだデータを作成
2.	**ベースライン作成**：異常種類ごとに対応した異常検知手法(非深層学習)をコーディング
3.	**Assistants作成**：異常検知手法のテキストを設定したAssistantsとグラフ画像判定用のAssistantsを作成
4.	**実験と評価**：性能比較、Assistantsがグラフ画像で手法のあたりをつけられるかを確認
5.	**考察**：実験をする中で感じたことや結果についてコメント

# １．データ作成
図2を参考に、単変量異常5種類をそれぞれ含んだデータを作成します。

![図2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/892617/2b87666e-50ce-8ea5-27e4-7bb5969a8acf.png)

<strong>図2：単変量時系列データにおける異常の種類[[参考6](#参考)]</strong>

それぞれの異常の説明と今回の検証におけるデータ作成方法、および作成結果の図を以下に整理します。

|単変量異常の作成結果|異常の説明とデータ作成方法|
|---|---|
|<img width="500" alt="global.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/892617/72a8a209-9b38-2fe3-0e9c-fb497aef18f2.png">|**Global**　<br>- 残りのデータと比較した際に値が大きい外れ値（異常点）<br>- 分散の大きい正規分布からサンプリングした値の絶対値を加えて作成|
|<img width="500" alt="contextual.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/892617/fc972a44-00cb-c362-4e66-e994a32ef69a.png">|**Contextual**　<br>- 周辺の時間のデータと比較した際に大きく乖離する外れ値（異常点）<br>- 正弦波の谷に外れ値用の定数を加えて作成|
|<img width="500" alt="seasonal.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/892617/2791d16a-c957-3798-5e0a-3d0b6633e931.png">|**Seasonal**　<br>- 周期的な変動を考慮した際に乖離する部分（異常部位）<br>- 1つの正弦波を抽出し周波数を変更して作成|
|<img width="500" alt="trend.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/892617/b98ad68b-5d6c-c126-988b-38bc48a8f82d.png">|**Trend**　<br>- 何らかのイベントによりデータがシフトする部分（異常部位）<br>- 途中で正の方向にデータをシフトする|
|<img width="500" alt="shapelet.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/892617/fa3ed8ca-1e08-336c-073f-1294f7a1c1d1.png">|**Shapelet**　<br>- データを特徴づける部分列（shapelet）が通常と異なる（異常部位）<br>- 波の特徴を崩すようにある期間をノイズと置き換えることで作成|
 
# ２．ベースライン作成
異常の種類に応じて、典型的な非深層学習の異常検知手法を実装します([参考7](#参考))。今回の問題設定では、いくつかのハイパーパラメータは可視化結果からある程度は推測できるとします。

**異常種類と異常検知手法（ハイパーパラメータ）**
- **Global**：正規分布に基づいた負の対数尤度（percentile=0.99）
- **Contextual**：Local Outlier Factor (LOF) (n_neighbors=5, contamination=0.01)
- **Seasonal**： 部分系列の近傍距離(n_neighbors=5, n_neighbors=2, contamination=0.01)
- **Trend**：　部分系列の近傍距離の累積和(窓幅=5, n_neighbors=2, percentile=0.95)
- **Shapelet**：　部分系列の近傍距離の上側累積和(窓幅=2, n_neighbors=2, percentile=0.98)

```python:例：Globalのコード
import numpy as np
from scipy.stats import norm

# 正規分布に基づいて負の対数尤度を計算する関数
def negative_log_likelihood(data, mu, sigma):
    return -norm.logpdf(data, mu, sigma)

# 正規分布の平均と標準偏差をデータから推定
mean, std_dev = np.mean(data), np.std(data)

# 負の対数尤度を異常度として計算
anomaly_scores = negative_log_likelihood(data, mean, std_dev)

# 閾値を設定 (99パーセンタイルを閾値とする)
threshold = np.percentile(anomaly_scores, 99)

# 異常データを検出
anomalies_detected = anomaly_scores > threshold
```

# ３．Assistants作成
### 3.1　No instruction Assistants
比較のため、instructionsに何も設定しないAssistantsを作成します。

### 3.2　検知用Assistants
2.の手法をテキスト化したものをinstructionsとして、Assistantsを5つ作成 (Assitantsについては[参考8](#参考)参照、instructionsの作り方は[参考9](#参考)参照)します。

```markdown:例：Globalのinstructions
# Role 
You are a AI anomaly detection analyst. 
# Your task 
## Progression 
Perform the anomaly detection task according to the following strategy.  
- 1. Read the data.  
- 2. Detect global outliers with the ‘Data’ column as input by using negative log likelihood.  
- 3. Compare and evaluate the detection results(AUC and F1-score) with the ‘global_label’ column.  
## Output 
- **AUC**: <analysis result> 
- **F1-score**: <analysis result> 
```

### 3.3　グラフ画像判定用Assistants
異常の種類の特徴をinstructionsに記載し、グラフ画像にどの種類の異常が含まれているかを判定します。

```markdown:グラフ画像判定用 instructionsイメージ
# Role.
You are a first-class anomaly detection monitoring officer. 
Guess what anomalies are contained in the graphs in the images. 
# Input image data. 
One-dimensional time series data containing <anomalies> or none at all.
# Type of <anomalies>. 
- "Global": Outliers with high values when compared to the rest of the data (anomalies).
- "Contextual": Outliers that deviate significantly when compared to the surrounding time data.
- "Seasonal": Deviations when considering cyclical variations (anomalies).
- "Trend": Areas where data shift due to some event (anomaly areas).
- "Shapelet": A subsequence characterising the data that is different from the usual (abnormal site).
# Output
- **anomalies in the image**: <analysis result>.
```

# ４．実験と結果
作成したデータとグラフ画像[^1]を入力に、ベースライン、Assistantsの性能を確認しました。検知結果（AUCとF1-score）、および画像判定の性能は以下のようになりました。



### 検知結果（AUC, F1）　
※ ブランクは3回実行して数値結果が得られなかったもの

|検知結果（AUC）|Global|Contextual|Seasonal|Trend|Shapelet|
|---|:-:|:-:|:-:|:-:|:-:|
|ベースライン(コーディング)|	**0.979**|**1**|**0.994**|**0.963**|	**0.999**|
|No instruction(GPT4-turbo)|0.930|0.596|	-|	-|	-|
|No instruction(GPT-4o)|0.922|0.427|0.549|0.615|    -|
|検知 Assistants(GPT4-turbo)|**0.979**|**1**|**0.994**|0.811|0.928|	
|検知 Assistants(GPT-4o)|**0.979**|**1**|**0.994**|0.810|0.952|

	
|検知結果（F1）|Global|Contextual|Seasonal|Trend|Shapelet|
|---|:-:|:-:|:-:|:-:|:-:|
|ベースライン(コーディング)|	0.762|	**1**|	**0.960**|	**0.740**|	**0.927**|
|No instruction(GPT4-turbo)|0.050|	0.044|	-|	-	|-	|
|No instruction(GPT-4o)|0.114|	0|	0|	0.130|	-|
|検知 Assistants(GPT4-turbo)|**0.889**|	**1**|	0.939|	0.640| 0.878|
|検知 Assistants(GPT-4o)|<font color="Red">0.462</font>|	**1**|	<font color="Red">0.019</font>|	0.640|	**0.927**|

 
### 画像判定結果　
※ 3回実行して、全て正解：○, 一回以上正解：△, すべて不正解：×

||Global|Contextual|Seasonal|Trend|Shapelet|
|---|:-:|:-:|:-:|:-:|:-:|
|画像 Assistants(GPT4-turbo)	|△|	×|	×|	△|	×|
|画像 Assistants(GPT-4o)	|○	|×|	△|	○|	×|

**補足**
画像 Assistants(GPT-4o)については、含まれる異常割合の推測値も出力させてみましたが、Globalで正解0.1%であるところを5%と推測したり、Trendで正解10%であるところを60%と推測したりするなど、さらなる工夫が必要そうな結果となりました。

# ５．考察
検知性能について、Global、Contextual、Seasonalの結果を見ると、対応する典型的な手法が分かっていれば、プロンプトをある程度適切に指示することで手によるコーディングに近い結果を再現することができるように思われます（手法に検討がつかない場合に少しでも複雑な異常を検知させたい場合は[参考10](#参考)参照）。

ただGPTが名称だけで理解できない手法は、手順を詳しく教えるかFunctions (function calling)で設定するかなどの工夫を検討した方がよさそうです（今回のやり方ではTrendやShapeletで使用した累積和は理解されず、結果も少し悪くなっています）。

また、今回の実験で使用したGPT-4oはGPT4-turboと比べ、画像の識別能力は向上している印象を受けました。一方、一概には言えませんが指示していない部分の補完能力が落ちている(落とされている？)印象も受けました（検知結果（F1）の赤字部分。閾値の設定による影響）。


当初は、GPT-4oを搭載した複数のエージェントを用いて、様々な異常が含まれているデータとグラフ画像（参考図）を入力にプロンプトをざっくり与えるだけで正確な検知ができるのではと期待していましたが、分かりやすいスパイクや変化を判定する以上のことをしようとすると、引き続き時間をかけた試行錯誤が必要そうです。

![all.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/892617/d46e0867-fb97-c1ef-59c0-79d1daddf8a1.png)

<strong>参考図</strong>


# まとめ
今回の検証を通して得られた知見を以下に整理します。

-	**検知性能**：典型的な手法をプロンプトで適切に指示すればマニュアル実装に近い結果を得られる可能性がある（が、GPT-4o以前のCode interpreterから目立った変化はなし）
-	**グラフ画像の認識**：分かりやすいスパイクや変化を判定できる程度の性能はありそう
-	**その他**：GPT-4oになってプロンプトを通した画像の認識性能は向上している印象がある

（上記は現状公開されているAssistantsを使って簡単に実装した範囲の知見のため、プロンプトや入力の前処理を工夫すると大幅に改善する可能性があります）

Assistantsに画像入力させるところはCode interpreterにファイルとして渡すと画像解析しようとするので、この検証ではcontentsに一時ファイルとして渡しています[参考11](#参考)（画像ファイルの入力は現状この方法以外意図しない挙動をしました）。また、今回は画像をGPTに判定させましたが、判定処理をコーディングさせた方が性能は上がるかもしれません。
データ分析を自動化することを検討している方の参考になれば幸いです。改善点やご質問などあれば、コメントをよろしくお願いいたします。

## (2024/6/6追記)LLMによるzero-shot異常検知の性能
時系列データをzero-shotで異常検知させる手法[[参考12](#参考)]があるとのことで、今回の検証で使用したデータを用いて簡単に実装してみました。結果図を見ると、途中までしか値を出力してくれなかったり異常を再現してしまったりと、実装した範囲では全体的に不安定な結果となりました（ただ、Seasonalは綺麗に回帰できているように見えるので、外れ値に脆弱なのかもしれません）。

![スクリーンショット 2024-06-06 17.51.24.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/892617/6f654008-351e-c875-ce4a-3ecc0f4f0a5a.png)
<strong>結果図</strong>


# 参考
1. https://openai.com/index/hello-gpt-4o/
2. https://platform.openai.com/docs/assistants/overview
3. OpenAI Assistants apiとは？使い方や料金、Retrieval機能について紹介！https://aitechworld.info/assistants-api/
4. Azure OpenAI の Assistants API によるエージェント開発メモ① https://qiita.com/nohanaga/items/fa9f5260cf85a7fddc52
5. https://platform.openai.com/docs/models
6. Darban, Z.Z., Webb, G.I., Pan, S., Aggarwal, C.C., & Salehi, M. (2022). Deep Learning for Time Series Anomaly Detection: A Survey. ArXiv, abs/2211.05244.
7. 井出剛, 杉山将. (2015). 異常検知と変化検知. 講談社サイエンティフィク. https://www.kspub.co.jp/book/detail/1529083.html 
8. https://cookbook.openai.com/examples/assistants_api_overview_python
9. https://speakerdeck.com/hirosatogamo/chatgpt-azure-openai-da-quan
10. https://qiita.com/motonobu_ut/items/29e7ab1cd4b64bbc0b42
11. https://platform.openai.com/docs/assistants/how-it-works/managing-threads-and-messages
12. Alnegheimish, S., Nguyen, L., Berti-Equille, L., & Veeramachaneni, K. (2024). Large language models can be zero-shot anomaly detectors for time series?


[^1]: グラフ画像判定用Assistantsのみに使用