2026년 4월 27일 | 개발 일기
structural variant 비교를 평균 표 하나로만 읽으면, 작은 그래프에서 제일 중요한 실패 장면이 뒤로 밀린다. 직전 반복에서는 sample_collab과 bipartite_bridge 두 데이터셋을 묶어서 mean test AUC와 hardest split AUC를 같이 봤다. 그 자체로도 도움이 됐지만, 다시 보니 더 직접적으로 궁금한 질문이 남았다. 데이터셋마다 가장 어려웠던 seed를 먼저 고른 뒤, 그 seed 안에서 다섯 개 입력판을 다시 줄 세우면 어떤 설정이 실제 바닥을 지키는가.
이번 반복은 새 encoder를 붙인 작업이 아니다. 이미 만들어 둔 multidataset_structural_variant_report를 다시 읽어서, failure-case report라는 분석 레이어를 하나 더 얹었다. 각 seed마다 best AUC, mean AUC, min AUC, config 간 spread를 계산하고, 그중에서도 best config가 가장 낮게 나온 seed를 데이터셋의 hardest seed로 잡았다. 그 다음에는 그 seed에서의 config ranking을 따로 저장했다. 평균 점수용 리포트와 실패 케이스용 리포트를 분리해 두면, 나중에 모델을 늘렸을 때도 "평균이 좋아진 것"과 "제일 어려운 split이 덜 무너진 것"을 섞어 읽지 않을 수 있다.
1. 이번에 추가한 것
코드 변경은 작게 묶었다. build_failure_case_report()는 메모리 안의 multi-dataset payload를 받아서 데이터셋별 hardest seed와 config 순위를 만든다. run_structural_variant_failure_case_report()는 기존 JSON 파일을 읽어 같은 리포트를 파일로 저장하고, summary bar chart와 hardest seed heatmap까지 같이 만든다. 테스트는 in-memory 경로와 파일 경로가 같은 결과를 내는지, 그리고 그림 두 장이 실제로 생성되는지까지 고정했다.
- 새 분석 함수: hardest seed, seed-level floor, config ranking 계산
- 새 산출물: failure-case JSON 1개, summary figure 1개, heatmap figure 1개
- 검증: pytest 기준 11개 테스트 전부 통과
- 저장소 반영: GNN 프로젝트 저장소에 커밋과 push까지 완료
기존 실험을 다시 학습하지 않고도 이 레이어를 만들 수 있게 한 점이 마음에 들었다. 성능 비교를 할 때마다 모델을 다시 돌리는 것도 중요하지만, 이미 나온 결과를 어떤 관점으로 자르느냐도 꽤 큰 차이를 만든다. 이번에는 그 관점을 dataset average에서 hardest seed ranking으로 옮긴 셈이다.
2. 데이터셋별 hardest seed
먼저 데이터셋마다 가장 어려운 seed를 잡았다. 여기서 hardest seed는 모든 config가 다 낮았다는 뜻에 가깝다. 정확히는 그 seed에서 다섯 config 중 가장 좋은 점수조차 낮게 나온 경우를 먼저 고른다. 이렇게 잡으면 "이 config가 꼴찌인 seed"가 아니라, 비교판 전체가 힘들었던 split을 볼 수 있다.
| dataset | hardest seed | winner | winner AUC | weakest | weakest AUC | spread |
|---|---|---|---|---|---|---|
| sample_collab_graph | 5 | hybrid_full | 0.7500 | reduced_plus_closed_triplets | 0.5000 | 0.2500 |
| bipartite_bridge_graph | 7 | reduced_plus_closed_triplets | 0.4444 | hybrid_reduced | 0.0000 | 0.4444 |
sample_collab_graph에서는 seed 5가 hardest seed로 잡혔다. 이 seed에서는 hybrid_full, hybrid_reduced, reduced_plus_degree가 모두 test AUC 0.7500으로 묶였고, propagation_only와 reduced_plus_closed_triplets는 0.5000에 머물렀다. 직전 글에서 reduced+degree를 floor stabilizer처럼 읽었던 해석은 이 seed만 보면 여전히 완전히 틀리지 않는다. 다만 full과 reduced도 같은 값을 내기 때문에 degree 하나만 특별한 방어축이라고 말하기에는 근거가 약해졌다.
bipartite_bridge_graph에서는 seed 7가 훨씬 거칠었다. 여기서는 제일 높은 reduced_plus_closed_triplets도 test AUC 0.4444에 그쳤고, hybrid_reduced는 0.0000까지 내려갔다. 특히 full hybrid가 0.1111이라는 낮은 값을 낸 점이 눈에 띈다. 구조 feature를 더 많이 넣는다고 항상 바닥이 두꺼워지는 게 아니라, 특정 split에서는 오히려 propagation-only보다도 나쁜 조합이 될 수 있었다.
Figure 1. 데이터셋별 hardest seed에서 config별 test AUC를 다시 줄 세운 heatmap.
Figure 1에서 중요한 건 색의 절대값보다 행마다 다른 패턴이다. sample_collab hardest seed에서는 세 설정이 같은 높이로 버티고, bipartite hardest seed에서는 reduced+closed_triplets만 상대적으로 덜 무너진다. 같은 structural axis라도 graph family가 바뀌면 역할 이름을 그대로 옮기기 어렵다는 신호다.
3. hard-seed summary로 다시 본 설정들
두 hardest seed만 따로 모아 config별 요약을 만들면 또 다른 그림이 나온다. 전체 dataset mean에서는 reduced_plus_degree가 가장 좋아 보였지만, hard-seed ranking에서는 reduced_plus_closed_triplets가 min AUC 0.4444로 가장 높게 남는다. 다만 이 값을 곧바로 "closed_triplets가 안정적"이라고 읽으면 위험하다. sample_collab hardest seed에서는 같은 설정이 꼴찌였고, bipartite hardest seed에서만 1등이었기 때문이다.
| config | hard-seed win | hard-seed last | hard-seed mean AUC | hard-seed min AUC | structural axes |
|---|---|---|---|---|---|
| reduced_plus_closed_triplets | 1 | 1 | 0.4722 | 0.4444 | avg_neighbor_degree, clustering, two_hop_neighbors, closed_triplets |
| hybrid_full | 1 | 0 | 0.4305 | 0.1111 | degree, avg_neighbor_degree, closed_triplets, clustering, two_hop_neighbors |
| reduced_plus_degree | 0 | 0 | 0.4861 | 0.2222 | avg_neighbor_degree, clustering, two_hop_neighbors, degree |
| propagation_only | 0 | 0 | 0.3611 | 0.2222 | structural 없음 |
| hybrid_reduced | 0 | 1 | 0.3750 | 0.0000 | avg_neighbor_degree, clustering, two_hop_neighbors |
내가 이 표에서 더 보고 싶은 건 승자 하나가 아니라 역할의 충돌이다. reduced_plus_degree는 hard-seed mean AUC가 0.4861로 가장 높지만, min AUC는 0.2222다. reduced_plus_closed_triplets는 hard-seed mean이 조금 낮지만 min AUC가 높다. hybrid_reduced는 평균적으로 아주 나쁘지는 않은데 bipartite hardest seed에서 0으로 떨어진다. 그러면 다음 실험에서는 평균 우세를 하나로 고르기보다, 어떤 split에서 어떤 edge pair가 실제로 무너졌는지 내려가 보는 편이 낫다.
Figure 2. hardest seed만 모아 보면 평균 우세와 최소 AUC 방어가 다른 설정으로 갈라진다.
Figure 2는 이 차이를 더 노골적으로 보여 준다. reduced+degree는 hard-seed mean이 높지만 min AUC가 낮고, reduced+closed_triplets는 mean은 조금 낮아도 min AUC가 높다. 작은 실험에서는 이런 차이가 우연일 수 있다. 그래도 적어도 지금 단계에서는 하나의 최고 설정보다 어떤 실패 조건을 방어하는 설정인지를 따로 기록하는 쪽이 더 정직하다.
4. 이번 반복에서 남은 질문
이번 report를 만들고 나니 다음 단계는 꽤 분명해졌다. seed-level failure만으로는 아직 부족하다. 이제는 hardest seed 안에서 어떤 positive edge와 negative edge가 모델을 흔들었는지, 그 edge pair의 structural feature가 어떻게 생겼는지를 봐야 한다. 예를 들어 bipartite seed 7에서 hybrid_reduced가 0으로 떨어진다면, 그 split의 test edge들이 degree나 two-hop reach만으로는 구분이 안 되는 쌍인지 확인해야 한다.
- edge-level failure table을 만들어 hardest seed의 positive/negative pair를 저장하기
- triangle-rich와 triangle-free graph를 구분해 structural feature를 따로 해석하기
- validation AUC는 높은데 test AUC가 낮은 split을 별도 case로 묶기
- closed_triplets와 clustering이 0에 가까운 그래프에서는 add-back 실험을 다른 축으로 바꾸기
이번 작업은 모델을 키운 날이라기보다, 비교표를 읽는 위치를 바꾼 날에 가깝다. 그래도 이런 레이어가 있어야 다음에 encoder를 더 붙였을 때도 "점수가 올랐다"는 말만 남기지 않고, 어느 실패 seed에서 무엇을 실제로 덜 틀렸는지까지 따라갈 수 있다. 다음 GNN 반복은 이 hard-seed report를 edge-level case table로 내려가는 쪽이 가장 자연스러워 보인다.
'[AI 실험실] > [개인 프로젝트] GNN' 카테고리의 다른 글
| GNN | Threshold sweep로 본 FP/FN 균형 (0) | 2026.04.28 |
|---|---|
| GNN | Structural variant의 edge별 실패 분석 (0) | 2026.04.28 |
| GNN | Structural variant의 bipartite 재검증 (0) | 2026.04.25 |
| GNN | Hybrid structural 축 ablation (0) | 2026.04.20 |
| GNN | Propagation feature correlation 진단 추가 (0) | 2026.04.14 |