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

- Name
- Harry Chang
核心貢獻者
Fei Tony Liu (劉飛) 與 周志華 (Zhi-Hua Zhou) 於 2008 年提出 Isolation Forest,顛覆了「先定義正常、再找異常」的傳統思路,改問一個更聰明的問題:「哪些點最容易被孤立?」。Bernhard Schölkopf (核方法大師、Max Planck 研究所所長) 於 1999 年將 SVM 改造成只學「正常」的 One-Class SVM。Markus Breunig 與 Hans-Peter Kriegel (慕尼黑大學) 則於 2000 年提出 LOF,首次把「局部密度」的概念帶進異常偵測。
為什麼工安需要異常偵測?
回顧 Day 02 的分類模型,我們需要「有標籤」的資料:這筆是良性、那筆是惡性。但在職業安全的世界裡,這條路走不通:
- 職災是稀有事件:一間工廠可能好幾年才發生一次重大職災,根本湊不出足夠的「事故樣本」來訓練分類器。
- 異常沒有固定長相:跳過安全步驟、疲勞遲緩、違規加速操作……異常的型態千奇百怪,你無法事先窮舉。
- 海因里希法則 (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 (待機間隔,秒):兩模之間的停頓時間。
埋入的兩種異常操作:
- 趕工型 (Rushing):週期時間異常短 + 手動介入次數高。典型的「跳過安全確認步驟」行為,是夾傷事故的前兆。
- 疲勞型 (Fatigue):週期時間異常長 + 待機間隔忽長忽短。注意力渙散的訊號,常見於連續加班後的夜班。
資料清理
- 標準化 (Standardization):One-Class SVM 與 LOF 絕對必要! 兩者都是基於距離的演算法 (和 Day 32 的 SVM 同理),尺度不同會被大數值特徵綁架。
- Isolation Forest 例外:它基於隨機切分而非距離,理論上不需要標準化——這是它工程上大受歡迎的原因之一。
2. 原理
三種演算法代表了三種截然不同的哲學,我們一個一個看。
2.1 Isolation Forest:異常的點,最容易被孤立
傳統思路是「先描繪正常的輪廓,再找出界外的點」。Isolation Forest 反過來想:異常點又少又遠,隨便切幾刀就能把它單獨隔離出來;正常點擠在一起,要切很多刀才分得開。

- 做法:隨機挑一個特徵、隨機挑一個切分值,像切蛋糕一樣把資料空間切開,重複到每個點都被單獨隔離,形成一棵「孤立樹 (iTree)」。蓋很多棵樹就是「孤立森林」。
- 判斷標準:計算每個點的平均路徑長度 (從樹根切到把它隔離出來,平均要切幾刀)。
- 異常點:兩三刀就被孤立 路徑短。
- 正常點:深埋在人群中 路徑長。
異常分數公式
- :該點在所有樹中的平均路徑長度。
- :n 筆資料下路徑長度的期望值 (正規化用)。
- 解讀:分數越接近 1 越異常;越接近 0.5 越正常。
2.2 One-Class SVM:只學正常,畫一個安全圈
還記得 Day 32 SVM「畫一條最寬的馬路」嗎?One-Class SVM 把題目改成:只有一類資料 (正常操作),請畫一個最緊緻的邊界把它們包起來。邊界外的一律視為異常。

- 做法:透過 RBF 核函數把資料投影到高維空間,在高維空間中找一個超平面,讓正常資料離原點越遠越好。投影回原始空間,就是一個包住正常資料的封閉曲面。
- 關鍵參數 (nu):控制「允許多少比例的訓練資料被劃到邊界外」,同時也是支持向量比例的下界。
- 直覺: 就是你心中預估的異常比例。設 0.05 代表「我猜大約 5% 的操作紀錄有問題」。
2.3 LOF:跟你的鄰居比密度
前兩者都是「全域」的判斷,LOF (Local Outlier Factor) 則主打一個字:局部。
- 核心問題:日班操作密集、夜班操作稀疏,用全域標準看,夜班的每一筆都像異常。怎麼辦?
- LOF 的答案:不跟全廠比,只跟你的 k 個鄰居比。計算「我的局部密度」和「我鄰居們的局部密度」的比值:
- :點 x 的局部可達密度 (local reachability density)。
- 解讀:
- LOF ≈ 1:我和鄰居一樣密 正常。
- LOF 遠大於 1:鄰居都很密、只有我特別稀疏 局部異常。
- 直覺比喻:在夜市裡一個人逛街很正常,但在演唱會搖滾區周圍五公尺沒人靠近你,你肯定有問題。
2.4 三劍客快速對照
| 面向 | Isolation Forest | One-Class SVM | LOF |
|---|---|---|---|
| 核心哲學 | 異常容易被孤立 | 包住正常的邊界 | 局部密度比較 |
| 需要標準化 | 不需要 | 必要 | 必要 |
| 計算成本 | 低 (線性) | 高 (核運算) | 中高 (鄰居搜尋) |
| 高維資料 | 表現佳 | 依核函數而定 | 維度災難明顯 |
| 強項場景 | 大規模、即時監控 | 正常樣本乾淨時 | 密度不均的資料 |
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 名可疑班次 交給職安人員逐一審查」。但我們的合成資料偷埋了答案,所以可以直接對答案,看看誰抓得準。
- 指標數字 (以異常為陽性類別):
| 模型 | Precision | Recall | F1 |
|---|---|---|---|
| Isolation Forest | 0.9200 | 0.9200 | 0.9200 |
| One-Class SVM | 0.4706 | 0.4800 | 0.4752 |
| LOF | 0.3200 | 0.3200 | 0.3200 |
- 偵測結果視覺化 (PCA 降至 2D):

和 Day 32 一樣,四維特徵無法直接畫圖,先用 PCA (Day 08) 壓成兩維再上色。紅點是被標記為異常的班次,藍色 X 是漏抓的異常 (False Negative)。
Isolation Forest (F1 = 0.92) 完勝:左側的趕工群、右側散開的疲勞群幾乎全數命中。因為兩種異常在特徵空間上都「離群」,隨機切分幾刀就能孤立它們。
One-Class SVM (F1 ≈ 0.48) 的問題:邊界包得不夠貼,左側趕工群漏掉一半,還在正常群內部誤殺了不少好人。 與 需要細調才能改善。
LOF (F1 = 0.32) 慘敗的原因最值得學:注意左側的趕工異常自成一個密集小群——LOF 跟鄰居比密度時,發現「我的鄰居 (其他趕工班次) 跟我一樣密」,於是判定彼此都正常!這就是 LOF 的致命弱點:群聚異常的遮蔽效應 (Masking Effect)。當異常行為是「集體性的」(例如整個夜班都在趕工),LOF 會集體放行。
教訓:LOF 擅長抓「孤鳥型」局部異常,但工安場景的違規常常是集體文化 (整組人都這樣操作),此時 Isolation Forest 這類全域方法反而可靠。
工安視角的解讀 (呼應 Day 02 的混淆矩陣):
- 漏報 (False Negative):真的有異常操作卻沒抓到 潛在職災,代價是人。
- 誤報 (False Positive):正常操作被冤枉 職安人員多查一筆,代價是時間。
- 結論:工安場景寧可把
contamination設高一點容忍誤報,也不要漏掉真異常。這個取捨沒有標準答案,是模型與管理制度的協作。
5. 總結
我們學習了異常偵測三劍客:
- Isolation Forest:「異常容易被孤立」的天才反向思考,免標準化、速度快,大規模監控首選。
- One-Class SVM:只學正常、畫出安全圈,是 Day 32 SVM 哲學的單類別變形。
- LOF:跟鄰居比密度,專治「日班密、夜班疏」這種局部密度不均的場景;但小心群聚異常的遮蔽效應——集體違規時 LOF 會集體放行。
- 工安啟示:職災樣本永遠不夠,與其等標籤,不如讓模型先學會「正常」,把海因里希法則裡那 300 件異常徵兆撈出來。
下一篇我們處理另一個工安資料的天生缺陷:就算真的有標籤,正常與異常的比例也是 950:50 —— 不平衡資料 (Imbalanced Data) 該怎麼訓練?