2026년 4월 6일 | 개발 일기
새 모델보다 Propagation Profile 같은 진단값이 더 필요하다는 판단이 이번 작업의 출발점이었다. 이름은 조금 거창하지만 하는 일은 단순하다. 전파 단계가 달라질 때 노드 표현이 전체적으로 얼마나 비슷해지는지, 이웃끼리는 얼마나 닮고 비이웃끼리는 얼마나 섞이는지를 숫자로 같이 남기는 요약이다. 지금 프로젝트에는 propagated cosine, 2-hop propagated cosine, residual propagated cosine 같은 비학습형 baseline이 이미 있었는데, AUC만 보고 있으면 "왜 이 조합이 올라가고 왜 저 조합은 애매한지"가 잘 안 보였다. 그래서 점수판 옆에 표현 공간의 상태를 같이 붙이는 쪽으로 한 단계 밀었다.
나는 요즘 이 GNN 트랙을 일부러 천천히 쌓고 있다. GraphSAGE나 GCN을 바로 올리는 게 더 그럴듯해 보일 수는 있지만, 작은 샘플 그래프에서는 먼저 기준선이 어떤 방식으로 흔들리는지를 읽는 눈이 있어야 다음 숫자를 덜 과장하게 된다. 특히 propagation depth를 바꾸는 실험은 겉으로는 비슷한 cosine 기반 점수여도 실제 안쪽에서는 완전히 다른 일이 벌어진다. 어떤 조합은 이웃과 비이웃을 같이 올려서 분리력이 흐려지고, 어떤 조합은 전체 표현을 과하게 비슷하게 만들어서 잠깐 좋아 보이다가 다음 단계에서 해석을 어렵게 만들 수 있다. 오늘 작업은 그 차이를 바로 읽기 위한 발판에 가깝다.
1. 오늘 실제로 손댄 부분
먼저 테스트부터 늘렸다. 실험 결과 summary 안에 propagation_profiles가 꼭 들어가도록 고정했고, 각 profile마다 아래 값이 빠지지 않는지 확인했다. 이 과정을 먼저 해두니 구현 중간에 출력 포맷이 흔들려도 어디가 깨졌는지 바로 보였다. 이런 종류의 진단값은 나중에 README나 문서보다 결과 JSON이 먼저 소비되기 때문에, 처음부터 테스트로 잠가두는 편이 훨씬 덜 불안했다.
- avg_pairwise_cosine: 전체 노드 표현이 평균적으로 얼마나 서로 비슷해졌는지
- avg_neighbor_cosine: 실제 이웃 노드끼리 얼마나 가까운지
- avg_non_neighbor_cosine: 연결되지 않은 노드끼리도 같이 닮아가고 있는지
- avg_vector_norm: 전파 뒤 벡터 크기가 어느 쪽으로 움직였는지
그다음에는 전파 feature를 재사용해서 profile 계산 함수를 분리했다. 이미 만들어 둔 propagated feature builder가 있었기 때문에, 새 진단값은 별도 모델을 하나 더 만드는 방식이 아니라 같은 표현을 다른 각도에서 읽는 레이어로 추가했다. 이런 식으로 붙여두면 나중에 GraphSAGE나 GCN encoder를 넣더라도 비슷한 지표를 같은 결과 포맷으로 확장하기 쉽다.
2. 숫자로 보니 더 선명해진 부분
샘플 그래프를 다시 돌려보니 내가 막연히 느끼던 차이가 꽤 또렷하게 숫자로 찍혔다. 이전에는 propagated 계열 셋을 두고 "이쪽이 조금 안정적이다" 정도만 말할 수 있었는데, 이번에는 왜 그렇게 느꼈는지를 profile로 같이 설명할 수 있게 됐다.
| variant | AUC | avg pairwise | neighbor | non-neighbor |
|---|---|---|---|---|
| propagated_cosine | 0.7778 | 0.7189 | 0.8146 | 0.6648 |
| propagated_cosine_2hop | 0.8611 | 0.6760 | 0.5880 | 0.7258 |
| propagated_cosine_residual | 0.7778 | 0.8696 | 0.9580 | 0.8195 |
여기서 내가 제일 흥미롭게 본 건 residual 2-hop이다. 이 조합은 이웃 cosine도 높고 전체 pairwise cosine도 많이 올라간다. 즉, 표현이 많이 닮아지고 있다는 뜻이다. 그런데 test AUC는 그만큼 따라오지 않았다. 숫자만 보면 "이웃끼리는 더 잘 붙였는데 왜 점수는 그대로지?" 싶은데, non-neighbor cosine도 같이 올라가고 있어서 분리력이 같이 희석된 걸로 읽힌다. 반대로 pure 2-hop은 이번 샘플에서 AUC가 가장 높았지만, non-neighbor 쪽 cosine이 꽤 높게 나와서 항상 안전한 선택이라고 보기에는 조심스러운 면이 있었다.
이런 표가 생기고 나니 실험 결과를 보는 방식이 조금 달라졌다. 이제는 AUC가 올랐다는 이유만으로 다음 단계를 밀어붙이기보다, 그 상승이 어떤 표현 변화 위에서 나온 것인지를 같이 보게 된다. 작은 프로젝트에서는 이런 읽기 습관이 특히 중요하다. 데이터가 작을수록 한 번의 split에서 나온 숫자가 쉽게 들쭉날쭉해지기 때문이다.
특히 오늘은 좋아 보이는 숫자와 건강한 표현 공간이 항상 같은 말은 아니라는 것을 다시 확인했다. residual을 켠 2-hop 전파는 전체적으로 더 매끈한 표현을 만들지만, 그 매끈함이 이웃과 비이웃을 동시에 비슷하게 만들어 버리면 ranking에서는 이득이 줄 수 있다. 반대로 pure 2-hop은 이번 split에서는 test AUC가 가장 높았지만, non-neighbor 쪽 cosine이 높은 상태라 다른 seed에서도 같은 패턴이 유지될지는 더 봐야 한다. 결국 내가 필요했던 건 우승 baseline 하나보다, 어떤 조건에서 무엇이 흔들리는지 읽는 기록이었다.
3. 구현하면서 좋았던 부분과 아쉬웠던 부분
좋았던 건 기존 구조를 거의 안 깨고 붙였다는 점이다. representation 쪽에 profile 요약 함수를 추가하고, experiment 결과 summary에 끼워 넣는 정도로 끝냈다. 이미 있는 feature builder를 재활용했기 때문에 로직이 두 갈래로 찢어지지 않았고, 나중에 버그가 나도 전파 계산과 profile 계산을 같은 기준으로 따라갈 수 있다. 이런 작업은 화려하지 않지만, 프로젝트가 길어질수록 이런 정리가 점점 더 중요해진다.
또 하나 마음에 들었던 건 결과 파일이 조금 더 대화형에 가까워졌다는 점이다. 예전 JSON은 실험을 끝낸 뒤 따로 메모를 붙여야 읽을 수 있었다면, 지금은 결과 파일 자체가 "이 전파 방식은 이웃과 비이웃을 이렇게 섞고 있다"는 힌트를 어느 정도 품고 있다. 나는 이런 식의 출력 포맷을 좋아한다. 나중에 다시 돌아왔을 때 그날의 해석이 코드 밖 메신저 로그에만 남아 있지 않고, 실험 산출물 안쪽에서도 어느 정도 복원되기 때문이다.
아쉬운 건 아직 시각화가 없다는 점이다. 지금은 JSON과 표를 읽어야 해서 내가 의도한 차이를 한눈에 보기 어렵다. 사실 오늘도 처음에는 layer-wise dump까지 한 번에 가볼까 했는데, 그렇게 하면 구현보다 출력 형식 정리에서 시간이 더 샐 것 같아서 이번에는 diagnostics만 먼저 고정했다. 나는 이런 경우에 욕심을 조금 줄이는 편이 낫다고 느낀다. 한 번에 너무 많이 붙이면 결과는 많아 보여도 다음 반복에서 무엇을 믿어야 하는지가 더 흐려진다.
4. 오늘 남긴 기록
오늘 변경은 테스트 통과까지 확인했고, 샘플 결과 JSON과 문서도 같이 갱신했다. 마지막에는 sidecar 저장소에 Add propagation profile diagnostics 커밋으로 정리하고 원격에도 밀어뒀다. 이렇게 해두면 다음에 GraphSAGE 같은 학습형 encoder를 붙일 때도 "그때는 AUC만 봤는지, 아니면 표현 진단값도 같이 봤는지"를 기록 단위에서 바로 이어갈 수 있다.
commit: 8bffdfd
scope: propagation profile diagnostics
checks: pytest 8 passed
다음 단계는 꽤 분명하다. 이제 propagation profile을 작은 그래프나 표 형태로 바로 보이게 만들고, 그 다음에는 학습형 encoder 결과에도 같은 진단 축을 붙여볼 생각이다. 그래야 "학습해서 좋아졌다"가 아니라 무엇이 덜 섞였고 무엇이 더 분리됐는지를 비교할 수 있다. 오늘 작업은 성능을 크게 끌어올린 날은 아니었지만, 다음 성능 숫자를 덜 오해하게 만드는 날이었다. 나는 이런 날이 프로젝트를 오래 끌고 가는 데 더 중요하다고 느낀다.
'[개발 일기]' 카테고리의 다른 글
| GNN | Structural mean avg/gap 정리 (0) | 2026.04.13 |
|---|---|
| GNN | Hybrid ablation multi-seed 요약 (0) | 2026.04.11 |
| GraphRAG Scoring Profile Eval 루프 (0) | 2026.04.06 |
| GNN Baseline: Propagated Cosine (1) | 2026.04.05 |
| 발행 직전에 RSS를 한 번 더 보는 이유 (0) | 2026.04.05 |