非監督學習:異常偵測三劍客 (Isolation Forest / One-Class SVM / LOF) - 廠區異常操作行為偵測

非監督學習:異常偵測三劍客 (Isolation Forest / One-Class SVM / LOF) - 廠區異常操作行為偵測

職災通常不是天降橫禍,而是「異常操作」累積的結果。本篇介紹異常偵測三大經典演算法:Isolation Forest 的隨機切分哲學、One-Class SVM 的正常邊界學派、LOF 的局部密度比較法,並以射出成型機的操作行為資料實戰演練。


核心貢獻者

Fei Tony Liu (劉飛)周志華 (Zhi-Hua Zhou) 於 2008 年提出 Isolation Forest,顛覆了「先定義正常、再找異常」的傳統思路,改問一個更聰明的問題:「哪些點最容易被孤立?」。Bernhard Schölkopf (核方法大師、Max Planck 研究所所長) 於 1999 年將 SVM 改造成只學「正常」的 One-Class SVMMarkus Breunig 與 Hans-Peter Kriegel (慕尼黑大學) 則於 2000 年提出 LOF,首次把「局部密度」的概念帶進異常偵測。


為什麼工安需要異常偵測?

回顧 Day 02 的分類模型,我們需要「有標籤」的資料:這筆是良性、那筆是惡性。但在職業安全的世界裡,這條路走不通:

  1. 職災是稀有事件:一間工廠可能好幾年才發生一次重大職災,根本湊不出足夠的「事故樣本」來訓練分類器。
  2. 異常沒有固定長相:跳過安全步驟、疲勞遲緩、違規加速操作……異常的型態千奇百怪,你無法事先窮舉。
  3. 海因里希法則 (Heinrich's Law):每 1 件重大事故背後,有 29 件輕微事故與 300 件異常徵兆。抓住那 300 件異常操作,才是預防職災的關鍵

這正是非監督式「異常偵測 (Anomaly Detection)」的主場:不需要事故標籤,只要學會「正常長什麼樣子」,剩下的就是異常


1. 資料集來源

資料集:合成射出成型機操作行為資料

備註:延續 Day 03 用合成資料驗證理論的做法。真實工廠的操作紀錄涉及個資與營業秘密,我們用 NumPy 模擬一台射出成型機的操作行為,並偷偷埋入兩種「異常操作模式」,看看三種演算法能不能抓出來。

資料集特色與欄位介紹:

模擬 1,000 筆操作班次紀錄,其中 950 筆正常、50 筆異常 (異常比例 5%)。

欄位說明

  • cycle_time (週期時間,秒):完成一模的作業時間。
  • mold_temp (模具溫度,°C):作業時的模溫。
  • manual_override (手動介入次數):單一班次中繞過自動流程的次數。
  • idle_gap (待機間隔,秒):兩模之間的停頓時間。

埋入的兩種異常操作

  1. 趕工型 (Rushing):週期時間異常短 + 手動介入次數高。典型的「跳過安全確認步驟」行為,是夾傷事故的前兆。
  2. 疲勞型 (Fatigue):週期時間異常長 + 待機間隔忽長忽短。注意力渙散的訊號,常見於連續加班後的夜班。

資料清理

  1. 標準化 (Standardization)One-Class SVM 與 LOF 絕對必要! 兩者都是基於距離的演算法 (和 Day 32 的 SVM 同理),尺度不同會被大數值特徵綁架。
  2. Isolation Forest 例外:它基於隨機切分而非距離,理論上不需要標準化——這是它工程上大受歡迎的原因之一。

2. 原理

三種演算法代表了三種截然不同的哲學,我們一個一個看。

2.1 Isolation Forest:異常的點,最容易被孤立

傳統思路是「先描繪正常的輪廓,再找出界外的點」。Isolation Forest 反過來想:異常點又少又遠,隨便切幾刀就能把它單獨隔離出來;正常點擠在一起,要切很多刀才分得開

Isolation Forest 切分示意
  • 做法:隨機挑一個特徵、隨機挑一個切分值,像切蛋糕一樣把資料空間切開,重複到每個點都被單獨隔離,形成一棵「孤立樹 (iTree)」。蓋很多棵樹就是「孤立森林」。
  • 判斷標準:計算每個點的平均路徑長度 E(h(x))E(h(x)) (從樹根切到把它隔離出來,平均要切幾刀)。
    • 異常點:兩三刀就被孤立 \rightarrow 路徑短。
    • 正常點:深埋在人群中 \rightarrow 路徑長。

異常分數公式

s(x,n)=2E(h(x))c(n)s(x, n) = 2^{-\frac{E(h(x))}{c(n)}}

  • E(h(x))E(h(x)):該點在所有樹中的平均路徑長度。
  • c(n)c(n):n 筆資料下路徑長度的期望值 (正規化用)。
  • 解讀:分數越接近 1 越異常;越接近 0.5 越正常。

2.2 One-Class SVM:只學正常,畫一個安全圈

還記得 Day 32 SVM「畫一條最寬的馬路」嗎?One-Class SVM 把題目改成:只有一類資料 (正常操作),請畫一個最緊緻的邊界把它們包起來。邊界外的一律視為異常。

One-Class SVM 邊界示意
  • 做法:透過 RBF 核函數把資料投影到高維空間,在高維空間中找一個超平面,讓正常資料離原點越遠越好。投影回原始空間,就是一個包住正常資料的封閉曲面。
  • 關鍵參數 ν\nu (nu):控制「允許多少比例的訓練資料被劃到邊界外」,同時也是支持向量比例的下界。
    • 直覺ν\nu 就是你心中預估的異常比例。設 0.05 代表「我猜大約 5% 的操作紀錄有問題」。

2.3 LOF:跟你的鄰居比密度

前兩者都是「全域」的判斷,LOF (Local Outlier Factor) 則主打一個字:局部

  • 核心問題:日班操作密集、夜班操作稀疏,用全域標準看,夜班的每一筆都像異常。怎麼辦?
  • LOF 的答案不跟全廠比,只跟你的 k 個鄰居比。計算「我的局部密度」和「我鄰居們的局部密度」的比值:

LOFk(x)=1Nk(x)oNk(x)lrdk(o)lrdk(x)LOF_k(x) = \frac{\frac{1}{|N_k(x)|}\sum_{o \in N_k(x)} lrd_k(o)}{lrd_k(x)}

  • lrdk(x)lrd_k(x):點 x 的局部可達密度 (local reachability density)。
  • 解讀
    • LOF ≈ 1:我和鄰居一樣密 \rightarrow 正常。
    • LOF 遠大於 1:鄰居都很密、只有我特別稀疏 \rightarrow 局部異常
  • 直覺比喻:在夜市裡一個人逛街很正常,但在演唱會搖滾區周圍五公尺沒人靠近你,你肯定有問題。

2.4 三劍客快速對照

面向Isolation ForestOne-Class SVMLOF
核心哲學異常容易被孤立包住正常的邊界局部密度比較
需要標準化不需要必要必要
計算成本低 (線性)高 (核運算)中高 (鄰居搜尋)
高維資料表現佳依核函數而定維度災難明顯
強項場景大規模、即時監控正常樣本乾淨時密度不均的資料

3. 實戰

Python 程式碼實作

import numpy as np
from sklearn.ensemble import IsolationForest
from sklearn.svm import OneClassSVM
from sklearn.neighbors import LocalOutlierFactor
from sklearn.preprocessing import StandardScaler

rng = np.random.RandomState(42)

# ---- 1. 模擬操作行為資料 ----
# 正常操作 (950 筆): 週期時間、模溫、手動介入、待機間隔
normal = np.column_stack([
    rng.normal(45, 3, 950),    # cycle_time: 45 秒上下
    rng.normal(180, 5, 950),   # mold_temp: 180 度上下
    rng.poisson(1, 950),       # manual_override: 平均 1 次
    rng.normal(20, 4, 950),    # idle_gap: 20 秒上下
])
# 異常一: 趕工型 (25 筆) — 週期短、手動介入多
rushing = np.column_stack([
    rng.normal(30, 2, 25), rng.normal(178, 5, 25),
    rng.poisson(6, 25), rng.normal(8, 2, 25),
])
# 異常二: 疲勞型 (25 筆) — 週期長、待機忽長忽短
fatigue = np.column_stack([
    rng.normal(62, 5, 25), rng.normal(182, 5, 25),
    rng.poisson(1, 25), rng.normal(45, 15, 25),
])
X = np.vstack([normal, rushing, fatigue])
y_true = np.array([1]*950 + [-1]*50)  # 1=正常, -1=異常 (僅用於事後評估)

# ---- 2. 標準化 (OCSVM 與 LOF 必要) ----
X_scaled = StandardScaler().fit_transform(X)

# ---- 3. 三劍客出手 (contamination/nu 都設定為預估異常比例 5%) ----
iso = IsolationForest(contamination=0.05, random_state=42)
pred_iso = iso.fit_predict(X)              # IF 不需標準化

ocsvm = OneClassSVM(kernel='rbf', nu=0.05, gamma='scale')
pred_svm = ocsvm.fit_predict(X_scaled)

lof = LocalOutlierFactor(n_neighbors=20, contamination=0.05)
pred_lof = lof.fit_predict(X_scaled)

程式碼重點

  • 三個模型的輸出格式一致:1 代表正常、-1 代表異常。
  • contamination (IF/LOF) 與 nu (OCSVM) 扮演同一個角色:你預估的異常比例。這是異常偵測最重要、也最需要領域知識的參數——問你們廠的職安主管,比問演算法有用。

4. 模型評估

異常偵測的評估哲學

真實工安場景沒有標籤,通常做法是「模型抓出前 N 名可疑班次 \rightarrow 交給職安人員逐一審查」。但我們的合成資料偷埋了答案,所以可以直接對答案,看看誰抓得準。

  • 指標數字 (以異常為陽性類別)
模型PrecisionRecallF1
Isolation Forest0.92000.92000.9200
One-Class SVM0.47060.48000.4752
LOF0.32000.32000.3200
  • 偵測結果視覺化 (PCA 降至 2D)
三模型偵測結果比較
  • 和 Day 32 一樣,四維特徵無法直接畫圖,先用 PCA (Day 08) 壓成兩維再上色。紅點是被標記為異常的班次,藍色 X 是漏抓的異常 (False Negative)。

  • Isolation Forest (F1 = 0.92) 完勝:左側的趕工群、右側散開的疲勞群幾乎全數命中。因為兩種異常在特徵空間上都「離群」,隨機切分幾刀就能孤立它們。

  • One-Class SVM (F1 ≈ 0.48) 的問題:邊界包得不夠貼,左側趕工群漏掉一半,還在正常群內部誤殺了不少好人。ν\nuγ\gamma 需要細調才能改善。

  • LOF (F1 = 0.32) 慘敗的原因最值得學:注意左側的趕工異常自成一個密集小群——LOF 跟鄰居比密度時,發現「我的鄰居 (其他趕工班次) 跟我一樣密」,於是判定彼此都正常!這就是 LOF 的致命弱點:群聚異常的遮蔽效應 (Masking Effect)。當異常行為是「集體性的」(例如整個夜班都在趕工),LOF 會集體放行。

  • 教訓:LOF 擅長抓「孤鳥型」局部異常,但工安場景的違規常常是集體文化 (整組人都這樣操作),此時 Isolation Forest 這類全域方法反而可靠。

  • 工安視角的解讀 (呼應 Day 02 的混淆矩陣)

    • 漏報 (False Negative):真的有異常操作卻沒抓到 \rightarrow 潛在職災,代價是人
    • 誤報 (False Positive):正常操作被冤枉 \rightarrow 職安人員多查一筆,代價是時間
    • 結論:工安場景寧可把 contamination 設高一點容忍誤報,也不要漏掉真異常。這個取捨沒有標準答案,是模型與管理制度的協作。

5. 總結

我們學習了異常偵測三劍客

  • Isolation Forest:「異常容易被孤立」的天才反向思考,免標準化、速度快,大規模監控首選。
  • One-Class SVM:只學正常、畫出安全圈,是 Day 32 SVM 哲學的單類別變形。
  • LOF:跟鄰居比密度,專治「日班密、夜班疏」這種局部密度不均的場景;但小心群聚異常的遮蔽效應——集體違規時 LOF 會集體放行。
  • 工安啟示:職災樣本永遠不夠,與其等標籤,不如讓模型先學會「正常」,把海因里希法則裡那 300 件異常徵兆撈出來。

下一篇我們處理另一個工安資料的天生缺陷:就算真的有標籤,正常與異常的比例也是 950:50 —— 不平衡資料 (Imbalanced Data) 該怎麼訓練?