| """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 |
|
|
| |
| |
| 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}") |
|
|