2026년 4월 11일 | 개발 일기
hybrid ablation을 단일 seed 결과로만 읽으면 작은 데이터에서는 숫자가 너무 쉽게 흔들린다. 샘플 그래프처럼 작은 데이터에서는 그 흔들림이 더 크게 보인다. 결과 JSON을 다시 보다 보니, drop propagated_cosine_2hop가 어떤 seed에서는 기준선과 비슷하고 어떤 seed에서는 오히려 더 나아지는 식으로 보였다. 그 순간부터는 "어느 축이 중요하다"를 한 번의 split으로 단정하는 게 좀 불편해졌다.
그래서 오늘은 새 baseline을 더 붙이기보다, 같은 실험을 여러 seed에서 한 번에 다시 읽는 판을 만드는 쪽으로 갔다. 결과적으로는 CLI에 --seeds 옵션을 추가했고, 단일 실행 결과들을 묶어서 model_auc_summary, hybrid_ablation_multi_seed, propagation_profile_multi_seed를 같이 남기도록 바꿨다. 성능을 바로 끌어올린 날은 아니지만, 지금 단계에서는 이런 작업이 더 중요하다고 느꼈다. 다음에 encoder를 붙이더라도, 적어도 어떤 변화가 seed가 바뀌어도 남는지부터 볼 수 있게 됐기 때문이다.
1. 오늘 실제로 바꾼 부분
구현은 크게 두 갈래였다. 먼저 실험 레이어에 run_multi_seed_link_prediction_baselines()를 추가해서 단일 seed 결과를 그대로 재사용하되, 마지막에만 묶어서 집계하도록 했다. 이미 있는 실험 포맷을 뒤엎기보다는, single-seed 결과를 보존한 채 위에 aggregation 레이어만 얹는 방식으로 가는 편이 다음 반복에서도 덜 헷갈린다.
그다음에는 CLI를 손봤다. 예전에는 --seed 13처럼 한 번에 하나만 받을 수 있었는데, 이제는 --seeds 5,7,11,13,17처럼 여러 값을 넘기면 multi-seed 요약 파일이 바로 떨어진다. 출력에는 모델별 mean AUC와 std, ablation별 평균 delta, 그리고 기준선보다 높게 나온 횟수인 beat count까지 남겼다. 나는 요즘 이런 숫자를 좋아한다. 최고점 하나보다, 얼마나 자주 비슷한 결론이 반복되는지가 훨씬 실무적인 판단 재료라서 그렇다.
PYTHONPATH=src python -m gnn_lab.cli \
--dataset data/sample_collab_graph.csv \
--output experiments/sample_collab_multiseed_results.json \
--test-ratio 0.3 \
--seeds 5,7,11,13,17
테스트도 먼저 늘렸다. multi-seed 결과에 seed 목록, 정렬된 model summary, hybrid ablation summary, propagation profile summary가 모두 들어가는지부터 고정해 두고 구현했다. 이런 집계 계층은 한번 들어가면 필드가 슬쩍 바뀌기 쉬워서, 문서보다 테스트를 먼저 걸어 두는 편이 훨씬 마음이 편하다.
2. 모델 평균을 묶어 보니 보인 것
샘플 그래프 기준으로 seeds=5,7,11,13,17을 묶어 보니, 제일 먼저 눈에 들어온 건 propagation 단일 baseline이 아직 꽤 세다는 점이었다. propagated_cosine_residual의 mean AUC는 0.8444, propagated_cosine과 propagated_cosine_2hop은 둘 다 0.8000이었다. 반면 feature_linear_hybrid는 mean AUC가 0.7111에 머물렀다.
더 흥미로운 건 validation 쪽이다. feature_linear_hybrid의 mean validation AUC는 0.9556인데, test mean AUC는 그보다 한참 아래다. 이 숫자를 보면서 나는 "hybrid가 약하다"보다 작은 그래프에서 validation 숫자를 너무 곧이곧대로 읽으면 위험하다는 쪽을 먼저 떠올렸다. 지금 단계의 supervised baseline은 아직도 성능 개선 도구라기보다, propagation 신호를 어떻게 다시 섞어 읽을지 보는 탐색면에 더 가깝다.
| 모델 | mean AUC | std | 메모 |
|---|---|---|---|
| propagated_cosine_residual | 0.8444 | 0.1805 | 여전히 제일 강한 단일 propagation 기준선이었다. |
| propagated_cosine | 0.8000 | 0.2162 | 변동폭은 크지만 평균은 높게 유지됐다. |
| propagated_cosine_2hop | 0.8000 | 0.1743 | 2-hop 단일 표현도 여전히 비교판으로 가치가 컸다. |
| feature_linear_hybrid | 0.7111 | 0.1390 | validation은 높지만 test 평균은 propagation baseline보다 낮았다. |
| feature_linear | 0.5500 | 0.1871 | plain supervised baseline은 아직 기준선 역할에 가깝다. |
이 표를 보고 나니 내가 다음에 encoder를 붙일 때도 무엇을 기준선으로 삼아야 하는지가 조금 또렷해졌다. 이전에는 feature_linear_hybrid를 중심축으로만 볼 생각이 있었는데, 이제는 최소한 propagated_cosine_residual과 propagated_cosine_2hop를 더 진지하게 남겨둬야겠다는 쪽으로 기울었다. 단순한 baseline이지만, 적어도 지금은 평균 성능 면에서 더 단단하다.
3. ablation 순서는 생각보다 덜 단순했다
단일 seed에서는 어느 propagation 축이 제일 중요하다고 쉽게 말하고 싶어진다. 그런데 여러 seed를 묶어 보니 이야기가 조금 달라졌다. drop propagated_cosine_2hop의 mean AUC가 0.7389로 가장 높았고, 기준선 대비 mean delta도 +0.0278이었다. 반대로 drop propagated_cosine는 -0.0278, drop propagated_cosine_residual은 -0.0111이었다.
그렇다고 곧바로 "2-hop이 쓸모없다"고 읽을 생각은 없다. 같은 표 안에 beat count를 같이 넣어 보니, drop propagated_cosine_2hop가 기준선보다 높았던 건 5번 중 2번뿐이었다. 즉 평균은 약간 더 높아도, 그 우세가 완전히 안정적이라고 말하기는 어렵다. 나는 오히려 이 지점이 좋았다. 지금까지는 AUC 하나를 보고 단정할 뻔한 해석을, 조금 더 애매하지만 더 정직한 문장으로 바꿀 수 있게 됐기 때문이다.
| ablation | mean AUC | mean delta vs hybrid | beat count | 읽는 방식 |
|---|---|---|---|---|
| drop propagated_cosine_2hop | 0.7389 | +0.0278 | 2 / 5 | 지금 샘플에서는 2-hop 축이 과하게 들어가는 seed가 있다는 신호로 읽는 편이 맞다. |
| drop propagated_cosine_residual | 0.7000 | -0.0111 | 1 / 5 | residual 축은 평균 기준으로는 아직 남겨둘 쪽에 가깝다. |
| drop propagated_cosine | 0.6833 | -0.0278 | 2 / 5 | 1-hop 축을 빼면 평균은 더 흔들렸다. |
결국 오늘 얻은 건 어떤 propagation 축의 정답이라기보다, 지금 해석이 얼마나 불안정한지 숫자로 보는 방법에 더 가깝다. 이건 조금 허무하면서도 꽤 유용하다. 왜냐하면 다음에 GNN encoder를 붙였을 때도, 단일 run 하나로 "확실히 좋아졌다"고 말하는 습관을 줄여 주기 때문이다.
4. 다음에 이어갈 포인트
multi-seed 요약을 붙이고 나니 다음 숙제도 좀 선명해졌다. 지금 feature 안에는 여전히 left_structural_mean, right_structural_mean처럼 순서 민감한 축이 남아 있다. edge는 무방향인데 feature 이름은 좌우를 들고 가는 셈이라, 이 부분이 seed별 흔들림을 더 키우는지 한 번은 확인해 볼 필요가 있다.
그다음에는 propagation profile을 표에서 한 단계 더 밀어 그래프로 보고 싶다. 특히 propagated_cosine_residual은 mean AUC는 높은데 std도 꽤 크고, propagated_cosine_2hop은 평균은 비슷하지만 분리 양상이 다르게 나온다. 이걸 숫자 줄로만 보면 감이 느리다. 다음 반복에서는 이 차이를 한눈에 보는 그림까지 붙여서, 정말로 encoder 비교판에 올릴 만한 baseline이 무엇인지 다시 정리해 볼 생각이다.
오늘은 새 모델 하나를 더 만든 날은 아니었다. 대신 지금까지 만든 비교면을 조금 덜 성급하게 읽게 된 날에 가까웠다. 나는 이런 날이 나쁘지 않다. 작은 프로젝트일수록, 무엇이 좋아졌는지보다 무엇을 아직 믿으면 안 되는지를 빨리 알아두는 편이 결국 더 멀리 간다.
'[개발 일기]' 카테고리의 다른 글
| GNN | GraphSAGE hybrid 입력 비교 (0) | 2026.04.16 |
|---|---|
| GNN | Structural mean avg/gap 정리 (0) | 2026.04.13 |
| GNN 프로젝트: Propagation Profile 진단값 추가 (0) | 2026.04.06 |
| GraphRAG Scoring Profile Eval 루프 (0) | 2026.04.06 |
| GNN Baseline: Propagated Cosine (1) | 2026.04.05 |