2026년 5월 13일 | 개발 일기
GNN 링크 예측 실험에서 비용 routing까지 붙이고 나니, 다음 문제는 숫자보다 작업 순서였다. `hybrid_reduced`가 cost-sensitive라는 말은 맞지만, 그 상태로는 다음 커밋을 어떻게 시작해야 할지 잘 안 보였다. false negative를 줄이는 쪽에서는 좋아 보이고, false positive를 줄이는 쪽에서는 나빠 보인다면, 나는 그 config를 성능 후보로 올릴 게 아니라 비용 정의 대기열로 보내야 한다.
그래서 이번 반복에서는 새 모델을 더 붙이지 않았다. 이미 만들어 둔 threshold cost report를 다시 읽어서, 각 config를 다음 행동으로 바로 바꾸는 Threshold action queue를 추가했다. 성능표를 한 장 더 만든 게 아니라, 실험을 이어 갈 때 무엇부터 열어야 하는지 적어 두는 작은 큐다. 이런 레이어는 화려하진 않지만, 여러 주에 걸쳐 같은 프로젝트를 이어 갈 때 꽤 큰 차이를 만든다.
1. cost routing 다음에 남은 빈칸
직전 단계에서는 validation-selected threshold를 test edge에 적용한 뒤, FP/FN 비용 시나리오 세 개로 다시 읽었다. balanced, false-negative-heavy, false-positive-heavy. 이 세 칸만으로도 `tradeoff_only` config가 꽤 분명하게 갈라졌다. `hybrid_reduced`와 `propagation_only`는 balanced에서는 cost delta가 0.0인데, false-negative-heavy에서는 +3.0, false-positive-heavy에서는 -3.0으로 움직였다.
여기까지는 괜찮았다. 문제는 그 다음이다. 이 숫자를 보고 "그럼 다음은?"이라고 물으면 다시 사람이 해석해야 한다. `hybrid_full`은 threshold rule을 더 넓게 검증할 후보이고, `hybrid_reduced`는 비용 정책을 먼저 정해야 하는 후보이고, `reduced_plus_degree`는 cutoff보다 score bias를 봐야 하는 후보라는 식이다. 이 판단을 매번 머릿속에서 다시 하면 실수가 생긴다.
나는 이런 애매한 해석을 코드 쪽에 조금 더 남겨 두는 편을 좋아한다. 블로그 글에는 자연어로 쓰더라도, 프로젝트 산출물에는 다음 행동의 이름이 있어야 한다. 그래야 며칠 뒤에 다시 열었을 때 "왜 이 config를 보류했더라"가 아니라 "이 config는 비용 정의가 먼저였지"로 바로 이어진다.
2. 이번에 만든 action label
이번 구현은 기존 `multidataset_structural_variant_threshold_cost.json`을 입력으로 받는다. 새 학습은 없다. cost summary의 routing label과 behavior label을 읽고, config별로 action label을 붙인다. 처음부터 복잡한 decision tree를 만들지는 않았다. 지금 필요한 건 정교한 자동 의사결정이 아니라, 다음 실험 파일을 열 순서다.
- choose_cost_policy: FP/FN 비용 정의를 먼저 고정해야 하는 후보
- expand_calibration_check: harmful scenario 없이 helpful scenario가 남아 multi-seed calibration으로 넓혀 볼 후보
- inspect_score_bias: threshold보다 score distribution이나 feature 조합 bias를 먼저 봐야 하는 후보
- park_cost_neutral: 현재 비용 시나리오에서는 우선순위가 낮은 후보
이름을 붙이면서 일부러 성능처럼 들리는 단어를 피했다. `choose_cost_policy`는 "좋은 config"가 아니다. 비용 정의가 없으면 아직 판단할 수 없다는 뜻이다. `inspect_score_bias`도 마찬가지다. threshold를 더 찾는 문제가 아니라, 점수 분포 자체가 한쪽 오류로 기울어 있는지 열어 보라는 표시다.
3. 결과: 모든 config가 아직 queue 안에 남음
Figure 1: threshold cost routing 결과를 다음 action label로 압축한 queue
이번 action report에서는 보류된 config가 하나도 없었다. 조금 의외였다. 비용표에서 cost-neutral로 보이는 config도 behavior label까지 같이 보면 그냥 덮어 둘 대상이 아니었다. `reduced_plus_closed_triplets`와 `reduced_plus_degree`는 cost delta는 조용하지만 FP-heavy 성향이 남아 있어서 score bias 쪽으로 보내야 했다.
| config | action label | 근거 | 다음 산출물 |
|---|---|---|---|
| hybrid_reduced | choose_cost_policy | false-negative-heavy +3.0, false-positive-heavy -3.0 | cost policy note |
| propagation_only | choose_cost_policy | false-negative-heavy +3.0, false-positive-heavy -3.0 | cost policy note |
| hybrid_full | expand_calibration_check | helpful scenario는 있고 harmful scenario는 없음 | multi-seed calibration |
| reduced_plus_closed_triplets | inspect_score_bias | cost-neutral이지만 FP-heavy label 유지 | score distribution probe |
| reduced_plus_degree | inspect_score_bias | cost-neutral이지만 FP-heavy label 유지 | score distribution probe |
표로 보면 이번 반복의 의미가 더 분명하다. `hybrid_full`은 지금 단계에서 가장 얌전한 후보지만, 바로 결론을 내리기에는 아직 표본이 작다. 그래서 action은 배포나 채택이 아니라 calibration check 확장이다. 반대로 `hybrid_reduced`와 `propagation_only`는 action score가 높지만, 그 높음은 좋은 점수라기보다 판단 유예 비용에 가깝다. 비용 정책을 정하지 않으면 같은 숫자를 두고 계속 다른 말을 하게 된다.
4. 구현하면서 헷갈렸던 부분
처음에는 cost report의 `cost_sensitive_queue`를 그대로 다음 작업 큐로 쓰면 되지 않을까 했다. 그런데 그렇게 하면 `reduced_plus_degree` 같은 FP-heavy 후보가 빠진다. 비용 변화가 없다고 해서 볼 필요가 없는 게 아니다. 오히려 threshold를 움직여도 비용 변화가 없다면, cutoff 문제가 아니라 score 분포가 이미 한쪽으로 기울었을 가능성이 있다.
그래서 action label은 routing label만 보지 않고 behavior label도 같이 보게 했다. `cost_neutral`이라도 dominant behavior가 `fp_heavy`나 `fn_heavy`면 `inspect_score_bias`로 보낸다. 이 작은 규칙 하나가 꽤 마음에 든다. 성능 개선 후보만 큐에 남기는 게 아니라, 왜 그대로 멈췄는지 확인해야 하는 후보도 같이 살린다.
테스트도 그 기준에 맞춰 잡았다. synthetic calibration payload에서 `sensitive_config`, `tradeoff_config`, `fp_config`를 만들고, 각각 `expand_calibration_check`, `choose_cost_policy`, `inspect_score_bias`로 들어가는지 먼저 고정했다. runner 쪽은 JSON, CSV, PNG가 모두 만들어지는지만 확인했다. 작은 실험이지만 TDD 순서로 고정해 두니, 나중에 action label을 더 늘릴 때 기준선이 생겼다.
5. 다음 실험을 여는 방식
이번 작업 이후 다음 단계는 세 갈래로 나뉜다. 첫째, `choose_cost_policy` 큐에 있는 두 config는 toy 추천 상황을 하나 정해서 FP/FN 비용을 명시해야 한다. 예를 들어 없는 링크를 추천하는 비용이 더 큰지, 있는 링크를 놓치는 비용이 더 큰지부터 정해야 한다. 그 전에는 threshold 조정이 성능 개선인지 비용 이동인지 구분하기 어렵다.
둘째, `hybrid_full`은 multi-seed calibration으로 넓혀 볼 만하다. 지금은 hardest seed 중심 report라서 작은 표본의 우연을 완전히 배제하기 어렵다. harmful scenario가 없다는 점은 좋지만, 그 말이 여러 seed에서도 유지되는지 확인해야 한다.
셋째, FP-heavy 후보는 score distribution probe로 내려가야 한다. 어떤 negative edge가 반복적으로 높게 잡히는지, degree gap이나 clustering 같은 structural snapshot과 같이 보면 다음 feature cleanup 후보가 보일 수 있다. 나는 이 셋 중에서 다음에는 cost policy note를 먼저 만들 가능성이 높다. action queue를 만들었으니, 큐의 첫 줄부터 실제로 처리해 보는 게 맞다.
이번 반복은 모델을 더 똑똑하게 만든 작업이라기보다, 실험을 덜 흐리게 만든 작업에 가깝다. 그래도 이런 작업이 쌓이면 프로젝트가 훨씬 덜 흔들린다. 평균 AUC 하나를 붙잡고 계속 해석을 바꾸는 대신, 애매한 config를 비용 정의, calibration 확장, score bias 점검이라는 다른 칸으로 나눠 둘 수 있기 때문이다. 작은 그래프 실험에서는 이 정도의 정리만으로도 다음 실험의 시작점이 꽤 선명해진다.
'[AI 실험실] > [개인 프로젝트] GNN' 카테고리의 다른 글
| GNN | Threshold behavior label 추가 (0) | 2026.05.06 |
|---|---|
| GNN | Validation threshold의 test 적용 리포트 (0) | 2026.05.04 |
| GNN | Threshold sweep로 본 FP/FN 균형 (0) | 2026.04.28 |
| GNN | Structural variant의 edge별 실패 분석 (0) | 2026.04.28 |
| GNN | Structural variant failure-case report (0) | 2026.04.27 |