工場の現場でAI導入による稼働率向上や品質改善が検討される際、「どの最新モデルを使うか」がよく議論されます。Transformerベースか、軽量なAutoencoderかといった点です。
しかし、実務の現場において異常検知の精度が出ない根本的な理由は、モデルの複雑さではなく「距離」の定義にあります。
異常検知は「正常データから距離が遠いものを探す」作業です。距離尺度を間違えれば、高価なAIモデルでも期待する異常は見つけられず、歩留まり改善などの定量的な成果にはつながりません。
本記事では数式による解説を控え、Pythonコードでデータを可視化し、「ユークリッド距離」と「コサイン類似度」の違いを実践的に解説します。
まずは手元のデータで小さく始めて成果を可視化することが、段階的なスケールアップへの第一歩となります。Jupyter Notebookで手を動かしながら読み進めてください。
なぜ「距離」の定義が異常検知の命運を分けるのか
異常検知プロジェクトの失敗パターンの一つに、「ライブラリのデフォルト設定のまま使う」ことが挙げられます。
例えば振動センサーの異常検知では、「値の跳ね上がり」と「波形リズムの崩れ」のどちらを異常とするかで、選ぶべき距離計算方法は異なります。ここを誤ると、誤検知が多発し現場のカイゼン活動が停滞してしまいます。
ベクトル空間における「異常」の定義
扱うデータの多くは「ベクトル」で表現されます。センサーAとBの値が $(3, 4)$ なら、2次元空間上の1点となります。
ここで重要な問いです。
「$(3, 4)$ というデータと、$(6, 8)$ というデータは、似ていますか?」
- 「似ていない」と答える視点: 値の大きさ(エネルギー)が倍も違う。3Vと6Vでは全く別物だ。
- 「似ている」と答える視点: 比率は同じ $3:4$ だ。つまり波形の形状やバランスは変わっていない。
前者なら「ユークリッド距離」、後者なら「コサイン類似度」が適切です。ビジネス上の「異常の定義」と距離計算のロジックが食い違うと、AIは誤検知や見逃しを繰り返し、期待される生産性向上は実現できません。
直感と異なる高次元空間の罠
データが高次元(数百〜数千次元の埋め込みベクトルなど)になるとさらに厄介です。人間の直感は3次元までですが、高次元空間では距離の概念が直感とズレます(次元の呪い)。
そのため、まずは2次元平面でアルゴリズムがデータをどう「見て」いるか可視化して確認することが、データドリブンな改善の基本となります。
実験環境の準備:データを「見る」ためのセットアップ
実験を始めます。本質を理解しやすくするため、単純な2次元のダミーデータを生成します。
必要なライブラリをインポートし、基準となる「正常データ」を作成します。
import numpy as np
import matplotlib.pyplot as plt
from sklearn.metrics.pairwise import cosine_similarity
from scipy.spatial.distance import euclidean
# 表示設定
%matplotlib inline
plt.style.use('seaborn-v0_8-whitegrid')
# 1. 正常データの生成(中心付近に集まるデータ群)
# 平均(2, 2)、標準偏差0.5のガウス分布に従うデータを想定
np.random.seed(42)
normal_data = np.random.normal(loc=2.0, scale=0.5, size=(100, 2))
# データの可視化
plt.figure(figsize=(8, 8))
plt.scatter(normal_data[:, 0], normal_data[:, 1], label='Normal Data', alpha=0.6)
plt.xlim(-1, 6)
plt.ylim(-1, 6)
plt.axhline(0, color='grey', linewidth=0.8)
plt.axvline(0, color='grey', linewidth=0.8)
plt.title("Distribution of Normal Data")
plt.legend()
plt.grid(True)
plt.show()
実行すると、$(2, 2)$ 付近に集まった青い点の集団が表示されます。これが本実験の「正常」状態です。
異常検知モデルの役割は、この集団から「どれくらい離れたら異常とするか」の境界線を引くことです。距離アルゴリズムによる境界線の違いを見ていきます。
実験1:ユークリッド距離が描く「円形」の世界
最初は、最も一般的で直感的な「ユークリッド距離」です。
ユークリッド距離の特性
ユークリッド距離は、普段定規で測る距離そのもので、ピタゴラスの定理(三平方の定理)で計算されます。
特徴は「絶対的な位置の遠さ」を重視する点です。原点からの距離やデータの大きさ(ノルム)が変化すると、距離も大きく変わります。
ユークリッド距離の可視化コード
空間全体に対し「正常データの中心 $(2, 2)$ からのユークリッド距離」を計算し、等高線(ヒートマップ)で表示します。色が濃いほど異常度が高い(距離が遠い)ことを意味します。
# 正常データの中心(平均ベクトル)
mean_vector = np.mean(normal_data, axis=0)
# グリッドポイントの作成(空間全体を網羅的に計算するため)
x = np.linspace(-1, 6, 100)
y = np.linspace(-1, 6, 100)
X, Y = np.meshgrid(x, y)
# 各グリッドポイントでのユークリッド距離を計算
Z_euclidean = np.zeros_like(X)
for i in range(X.shape[0]):
for j in range(X.shape[1]):
point = np.array([X[i, j], Y[i, j]])
# 中心とのユークリッド距離
Z_euclidean[i, j] = euclidean(mean_vector, point)
# 可視化
plt.figure(figsize=(8, 8))
plt.contourf(X, Y, Z_euclidean, levels=20, cmap='Reds', alpha=0.7)
plt.scatter(normal_data[:, 0], normal_data[:, 1], c='blue', label='Normal Data', alpha=0.6, edgecolors='white')
plt.scatter(mean_vector[0], mean_vector[1], c='black', marker='x', s=100, label='Center')
plt.colorbar(label='Anomaly Score (Euclidean Distance)')
plt.title("Anomaly Score Landscape: Euclidean Distance")
plt.xlim(-1, 6)
plt.ylim(-1, 6)
plt.legend()
plt.show()
結果の解釈:同心円の守護者
グラフを見ると、赤いグラデーションが中心 $(2, 2)$ を起点に同心円状に広がっています。
ユークリッド距離における「異常」とは、方向に関係なく「中心から離れていること」を意味します。
どのような異常が得意か:絶対的な大きさの変化
- 製造現場での例: 電圧、圧力、温度などのセンサー値。
- 設定値が 2.0MPa なのに 5.0MPa になったら異常。
- 0.0MPa(停止)になっても異常。
- ポイント: データの「強さ」や「量」そのものが重要な管理指標であり、閾値超過が直接的な品質低下や設備停止につながる場合、このユークリッド距離が適しています。
実験2:コサイン類似度が捉える「方向」の世界
次に「コサイン類似度」を用いた異常検知です。異常度として「コサイン距離(1 - コサイン類似度)」を使用します。
コサイン類似度の特性
コサイン類似度は2つのベクトルがなす「角度」に着目します。ベクトルの長さは無視され、「どちらを向いているか」だけを評価します。
コサイン類似度の可視化コード
同じデータとグリッドに対し、コサイン距離でヒートマップを描画します。
# 各グリッドポイントでのコサイン距離を計算
Z_cosine = np.zeros_like(X)
# コサイン類似度は2次元配列を受け取るので形状を調整
mean_vec_reshaped = mean_vector.reshape(1, -1)
for i in range(X.shape[0]):
for j in range(X.shape[1]):
point = np.array([X[i, j], Y[i, j]]).reshape(1, -1)
# コサイン距離 = 1 - コサイン類似度
# 原点(0,0)付近は計算が不安定になるため回避または無視
if np.linalg.norm(point) < 0.1:
Z_cosine[i, j] = 1.0 # 原点は異常扱い(便宜上)
else:
sim = cosine_similarity(mean_vec_reshaped, point)[0][0]
Z_cosine[i, j] = 1 - sim
# 可視化
plt.figure(figsize=(8, 8))
plt.contourf(X, Y, Z_cosine, levels=20, cmap='Blues', alpha=0.7)
plt.scatter(normal_data[:, 0], normal_data[:, 1], c='orange', label='Normal Data', alpha=0.6, edgecolors='black')
plt.scatter(mean_vector[0], mean_vector[1], c='black', marker='x', s=100, label='Center')
plt.plot([0, 6], [0, 6], 'k--', alpha=0.5, label='Direction Line') # 方向を示す補助線
plt.colorbar(label='Anomaly Score (Cosine Distance)')
plt.title("Anomaly Score Landscape: Cosine Distance")
plt.xlim(-1, 6)
plt.ylim(-1, 6)
plt.legend()
plt.show()
結果の解釈:扇形の監視者
グラフはユークリッド距離のような同心円ではなく、原点 $(0, 0)$ から放射状に広がる扇形のグラデーションになります。
中心 $(2, 2)$ の方向($y=x$ の直線上)であれば、原点から遠く離れても($(5, 5)$ や $(10, 10)$ など)正常に近いままです。逆に距離が同じでも、方向がズレる($(2, 5)$ など)と急激に異常度が高まります。
どのような異常が得意か:バランスや比率の変化
製造現場での例: 原材料の配合比率、振動波形の周波数成分バランス。
- 材料Aと材料Bを $1:1$ で混ぜる工程において、投入量が倍になっても比率が $1:1$ なら「正常」とみなしたい場合に最適です。絶対量ではなく「質的なバランス」を監視し、歩留まりを維持します。
最新の自然言語処理(LLM/RAG)での活用: 意味ベースのベクトル検索。
- 2026年現在、ChatGPTやClaude 3といったLLM(大規模言語モデル)を活用したシステムでは、テキストをベクトル化(Embedding)し、コサイン類似度を用いて関連情報を検索するRAG(検索拡張生成)が主流となっています。
- 例えば、製造現場のトラブル報告書を検索する際、文章の長さや具体的な単語が異なっていても、「意味の方向性(ベクトル)」が近ければ類似文書として検出可能です。
- 公式ドキュメント等によると、最新のEmbeddingモデルでは文脈理解が高度化しており、コサイン類似度は単なるキーワード一致を超えた「意図のマッチング」における事実上の標準指標として機能しています。
比較検証:同じデータでも判定が逆転するケース
2つの距離尺度が全く異なる「正常領域」を定義することが分かりました。これが実務の現場でどのような判断につながるか、ケーススタディで確認します。
意図的に「矛盾する」2つのテストデータを用意します。
- Case A (Scale Anomaly): 方向は正常だが、値が異常に大きいデータ $(5.0, 5.0)$
- Case B (Balance Anomaly): 値の大きさは正常だが、バランスが崩れたデータ $(1.5, 3.5)$
これらを判定してみます。
# テストデータ
case_a = np.array([5.0, 5.0]) # 方向は同じ、遠い
case_b = np.array([1.5, 3.5]) # 方向が違う、距離は近い
# 距離計算
dist_euclid_a = euclidean(mean_vector, case_a)
dist_cosine_a = 1 - cosine_similarity(mean_vector.reshape(1, -1), case_a.reshape(1, -1))[0][0]
dist_euclid_b = euclidean(mean_vector, case_b)
dist_cosine_b = 1 - cosine_similarity(mean_vector.reshape(1, -1), case_b.reshape(1, -1))[0][0]
print(f"--- Case A: {case_a} ---")
print(f"ユークリッド距離(大きさ重視): {dist_euclid_a:.4f} -> 判定: {'異常' if dist_euclid_a > 1.5 else '正常'}")
print(f"コサイン距離(方向重視) : {dist_cosine_a:.4f} -> 判定: {'異常' if dist_cosine_a > 0.1 else '正常'}")
print(f"\n--- Case B: {case_b} ---")
print(f"ユークリッド距離(大きさ重視): {dist_euclid_b:.4f} -> 判定: {'異常' if dist_euclid_b > 1.5 else '正常'}")
print(f"コサイン距離(方向重視) : {dist_cosine_b:.4f} -> 判定: {'異常' if dist_cosine_b > 0.1 else '正常'}")
(※判定の閾値 1.5 や 0.1 は、可視化結果に基づく仮置きの値です)
実行結果の考察
以下の傾向が確認できます。
- Case A: ユークリッド距離では「異常」と判定されますが、コサイン距離では限りなく0に近く「正常」と判定されます。
- Case B: ユークリッド距離では(中心に近いので)比較的「正常」とみなされやすいですが、コサイン距離では明確に「異常」となります。
これが「判定の逆転」です。
「センサー出力が過大になったらアラートを出したい」場合にコサイン類似度で判定すると、Case Aのような高圧状態を「バランスが良い」として見逃す可能性があります。現場の状況に合わせた現実的なアルゴリズム選定が不可欠です。
正規化(Normalization)が距離計算に与える影響
テクニカルな補足です。データ前処理の「正規化(L2正規化など)」を行うと、全ベクトルの長さが「1」に揃えられます。
長さが1に揃うと、ユークリッド距離とコサイン類似度の計算結果は実質的に同じ意味(単調関係)を持ちます。つまり、データを正規化してユークリッド距離を使うことは、正規化せずにコサイン類似度を使うこととほぼ等価です。
「とりあえず正規化する」と考えがちですが、それは「大きさの情報を捨てて方向だけの情報にする」という意思決定です。その情報を本当に捨ててよいか、現場のカイゼン目的に照らし合わせて検討が必要です。
実務への応用:失敗しないアルゴリズム選定チャート
最後に、これまでの知見を現場で使える選定ガイドとしてまとめます。
アルゴリズム選びに迷った際は、以下の表を参考にしてください。
データ特性ごとの推奨アルゴリズム対応表
| データの種類 | 着目ポイント | 推奨アルゴリズム | 理由 |
|---|---|---|---|
| センサー時系列 (温度, 圧力, 振動) |
絶対値の逸脱 閾値超えが致命的な場合 |
ユークリッド距離 (またはマハラノビス距離) |
物理的な限界値を超えたことを検知するため。 |
| センサー時系列 (波形, スペクトル) |
形状の変化 全体の強弱よりリズム崩れ |
コサイン類似度 | 波形の振幅が変動しても、形状パターンが同じなら正常とみなすため。 |
| 画像データ (埋め込みベクトル) |
意味的な内容 「何が写っているか」 |
コサイン類似度 | 明るさやコントラストの影響を除外し、被写体の特徴を捉えるため。 |
| テキストデータ (埋め込みベクトル) |
トピック・意味 | コサイン類似度 | 文章の長さ(単語数)による影響を排除するため。 |
| 購買データ (ユーザー特徴量) |
購買規模 大口顧客か小口顧客か |
ユークリッド距離 | 売上金額の絶対値が重要な指標となるため。 |
まずは簡単な可視化から始めるアプローチ
いきなり高次元データで判断せず、まずは主要な2変数(「回転数」と「温度」など)をピックアップして散布図を描いてみてください。小さく始めて成果を可視化することが重要です。
正常データが「円形」に固まっているか、「放射状」に伸びているかを見るだけで、選ぶべき距離尺度の9割は決まります。
まとめ:ツールに使われるな、データの「形」を見よ
異常検知における「距離」の重要性を、Pythonコードを用いた実験を通して解説しました。
- ユークリッド距離は、データの「大きさ・強さ」に敏感な定規です。
- コサイン類似度は、データの「方向・バランス」に敏感な定規です。
- どちらが優れているかではなく、現場のビジネス課題がどちらの異常を定義しているかが全てです。
AI開発では数式の理解も大切ですが、「データが空間内でどのような形をしているか」をイメージする力が現場のトラブルシューティングや継続的な改善に役立ちます。
今回のコードは、手元のデータに入れ替えてそのまま実験に使えます。ぜひ、現場のデータが「円形」か「扇形」か確かめ、データドリブンなアプローチで生産性向上につなげてください。
コメント