TIL(Today I Learned)
1️⃣최종 프로젝트(16)
도메인 지식으로 만든 파생변수는 살아남을까 + 실시간 품질 대시보드
반도체·디스플레이 공정 센서로 제품 품질(Y_Quality)을 회귀 예측하고, 확정 임계값으로 등급(0·1·2)을 나누는 프로젝트다. 제품군 A와 TO는 결측 구조가 달라 모델을 따로 만든다.
오늘 한 일은 두 가지다. 공정 도메인 지식으로 파생변수를 만들어 피처선택 파이프라인에 태우고 어디서 잘리는지 검증한 것, 그리고 그렇게 고른 피처로 돌아가는 실시간 품질 모니터링 대시보드를 만든 것. 별개 작업처럼 보이지만 같은 파이프라인의 앞뒤다.
1부. 도메인 파생변수는 피처선택을 통과하는가
배경 — 549개가 12개까지 줄었다
피처선택은 네 단계로 돌아간다.
| STEP | 하는 일 |
|---|---|
| 1 | 99.9% 이상 같은 값인 준상수 컬럼 제거 |
| 2 | 상관 클러스터링 후 대표 1개만 유지 |
| 3 | gain 상위 150개로 후보를 좁힌 뒤 permutation importance로 순위 매김 |
| 4 | 피처 개수를 늘려가며 CV RMSE 재측정, 1-SE 규칙으로 최적 개수 결정 |
결과가 꽤 가혹하다. A는 549개 → 12개, TO는 334개 → 3개만 남는다.
여기서 생각이 하나 들었다. 원시 센서값만 쓰고 있는데 공정 지식을 넣어 파생변수를 만들면 더 좋은 피처가 나오지 않을까. 동시에 이런 예상도 들었다. *"어차피 변수 중요도 단계에서 다 잘릴 것 같은데."*
예상을 그냥 두지 않고 검증해보기로 했다.
파생변수 설계 — 세 가지 축
센서 컬럼에 의미명이 매칭되어 있어(소스 온도, 칠러 순환수 온도 계열, RF 플라즈마 파워 등) 물리량 단위로 묶을 수 있다.
(1) 그룹 내 안정성·균일도 — mean · std · range · cv
증착 공정에서 품질을 좌우하는 것은 절대값보다 균일도다. 증발원 온도가 200 °C인지 210 °C인지보다, 존 간 온도가 얼마나 흔들리는지가 증착률(∝ exp(−Ea/kT))의 공간 편차를 만들고 두께 불균일로 이어진다. 그래서 수준값뿐 아니라 산포를 같이 만들었다.
(2) 그룹 간 물리 관계
| 파생변수 | 정의 | 공정공학적 의미 |
|---|---|---|
| 열구배_기판_칠러 | 기판온도 − 칠러온도 | 기판 열부하 |
| 소스온도_제어오차 | |설정계열 − 측정계열| | 온도 제어 루프의 추종 오차 |
| 압력비_공정_베이스 | 챔버압력 / 베이스압력 | 가스 부하 정도 |
| 막두께_균일도 | range / mean | 두께 편차율 |
| 에너지밀도_RF_유량 | RF파워 / 가스유량 | 분자당 투입 에너지 |
| 발광효율_광출력_RF | 광출력 / RF파워 | 전력 대비 발광 효율 |
| 파워_압력비 | RF파워 / 챔버압력 | 이온 에너지 프록시(E/p) |
(3) 카운트성 신호 — 파티클·누설전류·적산카운터는 롱테일이라 log1p로 압축하고, 발생 항목 수도 같이 만들었다.
제품군당 51개가 나왔다. 중요한 제약을 하나 걸었다. 모든 파생변수는 한 행 안에서의 집계·비율만 쓴다. 타깃도, 다른 행의 값도 참조하지 않는다. 누수를 원천 차단하기 위해서다.
검증 설계 — 두 질문을 분리했다
처음엔 "잘리나 안 잘리나"만 보려 했는데, 질문이 두 개였다.
- 통과하는가 — STEP 1~4를 뚫고 최종 피처에 남는가
- 기여하는가 — 남는다면 실제로 성능이 오르는가
이 둘은 다르다. 통과하지만 기여하지 않을 수 있다. 그래서 검증도 두 갈래로 나눴다.
- 통과 여부: 기존 피처선택 함수에
trace딕셔너리만 끼워 넣어, 변수 하나하나가 어느 STEP에서 왜 잘렸는지 기록하게 했다. - 성능 기여: 피처 선택 전체를 학습 fold 안에서만 수행하고(시드 3개 × 5-fold = 30 fold), 같은 fold끼리 짝지어 Wilcoxon 부호순위검정을 걸었다.
결과 — 예상은 절반만 맞았다
A 제품군은 51개 중 2개가 최종 12개에 살아남았다. FE_열구배_기판_칠러(기판 열부하)와 FE_기판온도_std(기판 온도 불균일도). permutation importance 6위·7위/150이고, 시드를 바꾼 30 fold 재현 검사에서 각각 12/15, 9/15 fold에서 선택됐다. 우연이 아니다.
TO 제품군은 0개다. 그런데 이유가 "정보가 없어서"가 아니었다. FE_기판온도_std가 6위, FE_큐어베이크_range가 13위였는데 1-SE 규칙이 최종 5개에서 잘랐다.
탈락 지점도 제품군마다 달랐다.
| 단계 | A | TO |
|---|---|---|
| STEP1 준상수 | 0 | 1 |
| STEP2 상관중복 | 14 | 17 |
| STEP3 gain 후보컷 | 24 | 10 |
| STEP4 1-SE 개수컷 | 11 | 23 |
| 최종 생존 | 2 | 0 |
A는 원본 543개와 경쟁해 gain 150위 안에 못 드는 게 최대 관문이고, TO는 순위엔 들었는데 개수 제한이 극단적으로 빡빡한 게 관문이다.
그런데 성능은 오르지 않았다
이게 진짜 결론이다.
| 제품군 | macro-F1 원본 | 파생 포함 | Δ | Wilcoxon p |
|---|---|---|---|---|
| A | 0.6619 ± 0.0594 | 0.6558 ± 0.0694 | −0.0061 | 0.389 |
| TO | 0.6313 ± 0.0827 | 0.6266 ± 0.0739 | −0.0047 | 0.847 |
유의하지 않을 뿐 아니라 방향도 미세하게 음수다. 그리고 fold 간 표준편차(±0.06~0.08)가 효과 크기(0.005)보다 10배 이상 크다. 이 표본 크기(A 316행, TO 592행)에서는 이 정도 차이를 탐지할 검정력 자체가 없다.
왜 안 오르는지는 단변량 스크리닝에 이미 답이 있었다.
| 제품군 | 원본 최고 / 중앙값 |r| | 파생 최고 / 중앙값 |r| |
|---|---|---|
| A | 0.634 / 0.110 | 0.330 / 0.087 |
| TO | 0.445 / 0.041 | 0.131 / 0.041 |
A에서는 파생변수 중앙값이 원본보다 낮고, 상위 30위 안에 든 파생변수가 0개다. 개별 원시 센서가 이미 더 강한 신호를 갖고 있고, mean·std·cv 같은 집계는 오히려 개별 센서의 판별력을 평균으로 희석시킨다. 도메인 논리는 타당하지만 이 데이터에서는 그 정보가 이미 원시 센서에 담겨 있었던 셈이다.
덤으로 발견한 파이프라인 버그 2개
검증하려고 파이프라인 내부를 뜯어보다 파생변수와 무관한 문제 둘을 찾았다.
(1) STEP2 상관중복 제거가 원본에는 거의 적용되지 않는다
Z_cols = data[features].apply(pd.to_numeric, errors="coerce").dropna(axis=1, how="any")
# ↑ 여기가 문제
결측이 하나라도 있는 컬럼은 군집 대상에서 빠지고 심사 없이 그대로 통과한다. A 제품군은 평균 결측률이 30%라 원본의 92%가 면제된다.
| 제품군 | 원본 심사율 | 파생 심사율 |
|---|---|---|
| A | 44 / 543 (8%) | 39 / 51 (76%) |
| TO | 99 / 329 (30%) | 39 / 50 (78%) |
파생변수는 집계 과정에서 결측이 메워져 대부분 전수 심사를 받는다. 파생변수에 불리한 구조적 비대칭인 동시에, STEP2가 원본에 대해 제 역할을 못 하고 있다는 뜻이다. pairwise 결측 처리로 바꿔야 한다.
(2) 클러스터 대표 선정이 스케일 의존적이다
표준화 없이 원분산으로 대표를 뽑고 있어서, 같은 클러스터에서 FE_열구배_기판_칠러(분산 1.76e+03)가 FE_반사율_mean(분산 3.72e-03)을 이긴다. 물리적 중요도가 아니라 단위 크기가 대표를 정한다. (다만 그 클러스터의 쌍상관은 최소 0.855·중앙 0.931로 중복 자체는 실재해서, 문제는 대표 선정 기준에 국한된다.)
2부. 실시간 품질 모니터링 대시보드
1부에서 고른 피처(A 12개 · TO 3개)가 실제로 어떻게 쓰이는지 보여주는 화면을 만들었다.
구조
브라우저 대시보드 ──매 판정──▶ FastAPI /ingest ──(0·2 불량)──▶ n8n Webhook ──▶ Discord 알림
└ predictions.jsonl 기록
n8n 매일 09:00 ──▶ FastAPI /daily_report(집계) ──▶ Gmail 리포트
등급 정의가 조금 특이하다. 0·2가 불량, 1이 정상이다. 규격을 벗어나면 품질이 낮아도(0) 높아도(2) 불량이라는 공정 관점을 그대로 반영했다.
판정은 data.js에 선택피처 CSV를 그대로 실어 재생하는 방식이다. 실제 값 y에 KFold RMSE 크기의 가우시안 노이즈를 얹어 예측값을 만들고, 확정 임계값으로 등급을 나눈다. 불량이면 정밀검사 큐에 넣고 알림을 띄우며, 핵심 센서는 별도로 ±3σ 관리한계 이탈을 감시한다.
서버는 모델을 들고 있지 않다. 로그와 집계만 하는 경량 서버다. /ingest가 예측을 적재하고 불량만 n8n으로 중계하며, /daily_report가 일별 집계를 낸다. n8n 워크플로우는 두 개다.
| 워크플로우 | 트리거 | 동작 |
|---|---|---|
wf_discord |
Webhook /webhook/defect |
불량 수신 → Discord 채널 알림 |
wf_daily |
스케줄 0 9 * * * |
/daily_report 호출 → Gmail SMTP 발송 |
서버가 꺼져 있어도 대시보드는 SIM 모드로 계속 동작한다. 사이드바 배지가 SIM 모드 / API 연결 · n8n 대기 / 연동 LIVE 세 단계로 상태를 알려준다. 발표 중에 서버가 죽어도 화면은 살아 있게 하려는 장치다.
화면에서 신경 쓴 것
숫자를 띄우는 것보다 그 숫자가 어디서 나왔는지 라벨을 붙이는 데 시간을 더 썼다.
- 스코프를 라벨로 분리했다. 평가셋 macro-F1(학습 시점 고정값)과 불량 검출률·운영(재생 중 누적)은 다른 지표다. 같은 "F1"이라도 스코프가 다르면 다른 숫자다.
- 정확도에는 경고를 병기했다. "불균형 착시라 판단 근거로 쓰지 않음". TO는 정확도 0.861인데 macro-F1은 0.651이다. 정상(1) 등급이 490/592라 정확도만 보면 착시가 생긴다.
- 예측값에 불확실성을 같이 적었다.
예측 Y_Quality 0.5312 ± 0.0049 (KFold RMSE) - 드릴다운 기여도는 gain importance × |z| 근사값이라고 명시했다. SHAP이 아니다. 근거 없이 "기여도"라고만 쓰면 읽는 사람이 SHAP으로 오해한다.
- KPI 비교 기준을 지어내지 않았다.
vs 전주를 쓰고 싶었지만 이 데이터셋에는 주 단위 축이 없다. 대신 실제 산출한 학습 기준선(전체 불량률 29.5% · A 52.5% · TO 17.2%)을 비교 대상으로 썼다.
용어 실수도 하나 잡았다. 처음에 A·TO를 "A 라인 / TO 라인"으로 표기했는데, A·TO는 제품군이지 생산 라인이 아니다. 표 헤더·스테이지 라벨·KPI 스코프를 전부 되돌렸다. 공정 애니메이션이 두 갈래로 갈라지는 것도 라인이 둘이어서가 아니라 모델이 제품군별로 분리되어 있어서라, 카드 제목을 공정 흐름 · 제품군별 판정 경로로 바꿔 제목이 직접 그 이유를 말하게 했다.
화면으로 확인한 것
- 정확도와 macro-F1의 괴리가 TO에서 크다. 정확도 0.861 / macro-F1 0.651이고 등급별 F1이 0.558 · 0.909 · 0.485로 소수 등급(0·2)이 확연히 낮다. 불균형 데이터에서 정확도를 헤드라인으로 쓰면 안 된다는 걸 눈으로 확인했다.
- A는 센서 12개 중 절반이 상시 결측이다. 재생 데이터에
null이 그대로 들어 있어서 표시·집계 로직을 처음부터 결측 전제로 짜야 했다. - 드리프트 z값이 항상 0 근처다. 재생 데이터가 학습 데이터와 같은 분포라 당연한 결과인데, 구조는 동작하지만 실제 드리프트 상황은 재현되지 않는다는 한계로 문서에 남겼다.
- 공정 애니메이션은 체류 시간이 전체의 40%가 되도록 타임라인을 짰다(1× 기준 총 7.27초). 등속으로 미끄러지게 두니 "공정"으로 안 읽혀서 이송 → 정지 → 처리 → 이송으로 다시 썼다. 물체 하나 = 실제 판정 1건이고, 장식용 더미는 넣지 않았다.
정리
- 도메인 파생변수는 51개 중 2개만 살아남았고, 그 2개도 성능을 올리지 못했다. "다 잘릴 것"이라는 예상은 절반만 맞았지만 실질적 결론은 같은 방향이었다.
- 통과 여부와 기여 여부는 다른 질문이다. 선택 통과는 시드 반복으로, 성능 기여는 정직한 nested CV + 짝지은 검정으로 따로 확인해야 한다.
- 안 되는 이유를 모델 단계에서 찾지 말고 단변량 스크리닝부터 보는 게 빠르다. 파생변수 상관이 원본 중앙값 수준이면 그 뒤는 볼 것도 없다.
- 집계(
mean·std·cv)는 정보를 만들기도 하지만 개별 센서의 판별력을 희석시키기도 한다. 원시 신호가 이미 강할 때는 손해다. - 파이프라인 내부를 뜯어보는 과정 자체가 소득이었다.
dropna(axis=1, how="any")한 줄 때문에 STEP2가 원본 피처의 8%만 심사하고 있었다. - 대시보드에서 어려운 건 차트가 아니라 라벨이었다. 같은 F1도 스코프가 다르면 다른 지표이고, 근사값을 SHAP처럼 보이게 두면 안 된다.
💡 느낀점
안 될 것 같다"는 직감을 그냥 두지 않고 실험 설계로 옮긴 게 오늘의 수확이다. 결과적으로 파생변수는 쓰지 않기로 했지만, 살아남은 두 변수가 모두 "기판 열 불균일"을 가리키고 원본 선택 피처에도 기판·서셉터 온도 계열이 항상 포함된다는 걸 알게 됐다. 성능은 못 올렸어도 **왜 이 센서가 중요한지 설명할 물리적 근거는 얻었다.
'최종 프로젝트' 카테고리의 다른 글
| Chapter.9 최종 실전 자동화 프로젝트 (17) (0) | 2026.07.27 |
|---|---|
| Chapter.9 최종 실전 자동화 프로젝트 (16) (0) | 2026.07.23 |
| Chapter.9 최종 실전 자동화 프로젝트 (15) (0) | 2026.07.22 |
| Chapter.9 최종 실전 자동화 프로젝트 (14) (0) | 2026.07.21 |
| Chapter.9 최종 실전 자동화 프로젝트 (13) (0) | 2026.07.20 |