fabagent / agents /detection.py
hee_!J
feat: PHM 2016 CMP 톡합 + 3개 μ•ŒλžŒ 싀데이터 λ™μž‘
f606871
Raw
History Blame Contribute Delete
3.88 kB
"""Tier 1 이상 탐지 μ—μ΄μ „νŠΈ
μ•ŒλžŒμ„ λ°μ΄ν„°μ…‹μ˜ νŠΉμ • wafer에 λ§€ν•‘ν•΄ 이상 μ μˆ˜μ™€ κΈ°μ—¬ ν”Όμ²˜λ₯Ό 계산
- A1, A2: SECOM 곡개 데이터셋 (λ‹€λ₯Έ fail row λ§€ν•‘)
- A3: PHM 2016 CMP 곡개 데이터셋 (CMP step μ‹€μΈ‘, 25개 λͺ…λͺ… μ„Όμ„œ)
λͺ¨λΈ: IsolationForest (λ²€μΉ˜λ§ˆν¬μ—μ„œ PR-AUC 우수)
이상 점수: ν•™μŠ΅ 뢄포 λŒ€λΉ„ λ°±λΆ„μœ„λ‘œ 0~1 μ •κ·œν™”
κΈ°μ—¬ ν”Όμ²˜: ν‘œμ€€ν™” κ°’μ˜ μ ˆλŒ€ν¬κΈ° Top-N (정상 λΆ„ν¬μ—μ„œ κ°€μž₯ λ²—μ–΄λ‚œ μ„Όμ„œ)
"""
from functools import lru_cache
import numpy as np
from sklearn.ensemble import IsolationForest
from core.schema import Tier1
from data.phm2016.loader import load_phm_cmp
from data.secom.loader import load_secom
from data.secom.preprocess import SecomPreprocessor
RANDOM_STATE = 42
TOP_N_FEATURES = 3
# μ•ŒλžŒ -> 데이터 μ†ŒμŠ€ λ§€ν•‘
# wafers: 데λͺ¨ μ»¨ν…μŠ€νŠΈμƒ 영ν–₯ 웨이퍼 수
ALARM_WAFER = {
"A1": {"source": "secom", "row": 2, "wafers": 25},
"A2": {"source": "secom", "row": 10, "wafers": 18},
"A3": {"source": "phm_cmp", "wafer_id": 2058207580, "stage": "A", "wafers": 30},
}
@lru_cache(maxsize=1)
def _fit_secom():
"""SECOM μ „μ²΄λ‘œ μ „μ²˜λ¦¬κΈ°μ™€ IsolationForest ν•™μŠ΅, 첫 호좜 μ‹œ 1회"""
X, _ = load_secom()
pre = SecomPreprocessor().fit(X)
Xz = pre.transform(X)
model = IsolationForest(n_estimators=200, random_state=RANDOM_STATE)
model.fit(Xz)
train_scores = -model.score_samples(Xz)
return X, pre, model, train_scores
@lru_cache(maxsize=1)
def _fit_phm_cmp():
"""PHM CMP μ „μ²΄λ‘œ ν‘œμ€€ν™” + IsolationForest ν•™μŠ΅, 첫 호좜 μ‹œ 1회"""
from sklearn.preprocessing import StandardScaler
features, _ = load_phm_cmp()
scaler = StandardScaler().fit(features.values)
Xz = scaler.transform(features.values)
model = IsolationForest(n_estimators=200, random_state=RANDOM_STATE)
model.fit(Xz)
train_scores = -model.score_samples(Xz)
return features, scaler, model, train_scores
def _detect_secom(alarm: dict, mapping: dict) -> Tier1:
X, pre, model, train_scores = _fit_secom()
Xz = pre.transform(X.iloc[[mapping["row"]]])
raw_score = float(-model.score_samples(Xz)[0])
score = float((train_scores < raw_score).mean())
deviations = np.abs(Xz[0])
top_idx = np.argsort(deviations)[::-1][:TOP_N_FEATURES]
features = [
{"name": pre.keep_cols[i], "value": round(float(deviations[i]), 2)}
for i in top_idx
]
return {
"score": round(score, 2),
"features": features,
"lot": {"id": alarm["lot_id"], "wafers": mapping["wafers"]},
}
def _detect_phm_cmp(alarm: dict, mapping: dict) -> Tier1:
features, scaler, model, train_scores = _fit_phm_cmp()
key = (mapping["wafer_id"], mapping["stage"])
if key not in features.index:
raise KeyError(f"PHM CMP에 μ—†λŠ” wafer/stage: {key}")
row = features.loc[[key]].values
Xz = scaler.transform(row)
raw_score = float(-model.score_samples(Xz)[0])
score = float((train_scores < raw_score).mean())
deviations = np.abs(Xz[0])
top_idx = np.argsort(deviations)[::-1][:TOP_N_FEATURES]
col_names = list(features.columns)
out_features = [
{"name": col_names[i], "value": round(float(deviations[i]), 2)}
for i in top_idx
]
return {
"score": round(score, 2),
"features": out_features,
"lot": {"id": alarm["lot_id"], "wafers": mapping["wafers"]},
}
def run_detection(alarm: dict) -> Tier1:
mapping = ALARM_WAFER.get(alarm["id"])
if mapping is None:
raise ValueError(f"λ§€ν•‘ μ—†λŠ” μ•ŒλžŒ: {alarm['id']}")
source = mapping["source"]
if source == "secom":
return _detect_secom(alarm, mapping)
if source == "phm_cmp":
return _detect_phm_cmp(alarm, mapping)
raise ValueError(f"μ•Œ 수 μ—†λŠ” 데이터 μ†ŒμŠ€: {source}")