2026년 5월 15일 | 개발 일기
Negative edge context audit는 직전 histogram에서 남긴 찜찜함을 조금 더 아래로 접어 보는 작업이었다. 어제는 GNN structural variant 실험에서 false positive가 많이 나는 설정을 score bucket으로 나눴고, 그 결과가 생각보다 애매했다. 둘 다 negative edge leak으로 보이긴 했는데, 하나는 모든 음성 edge가 threshold 바로 위에 붙었고, 다른 하나는 특정 edge 하나가 유난히 크게 튀었다.
처음에는 이걸 그냥 "FP-heavy 설정"으로 묶어도 되지 않나 싶었다. 그런데 그렇게 적어 두면 다음에 열 파일이 너무 흐려진다. threshold를 다시 만져야 하는지, feature 조합을 잘라야 하는지, 아니면 특정 edge case를 따로 봐야 하는지 결론이 다 달라지기 때문이다. 그래서 이번에는 새 모델을 얹지 않고, 기존 negative score histogram을 입력으로 삼아 leaked negative edge의 context를 다시 라우팅하는 얇은 report를 붙였다.
히스토그램 다음에 바로 필요한 질문
직전 산출물에서 이미 한 가지는 분명했다. reduced_plus_degree는 negative edge 5개가 전부 0.50~0.60 사이에 걸렸다. 강한 outlier는 없지만 전부 양성 쪽으로 살짝 넘어간 모양이다. 반대로 reduced_plus_closed_triplets는 negative edge 5개 중 4개가 leak이었고, 그중 하나가 0.9476까지 치솟았다. 평균만 보면 비슷한 실패처럼 보이는데, 실제로는 넓게 밀린 실패와 한 점이 터진 실패가 섞여 있었다.
그래서 이번 함수는 histogram report에서 next_probe가 negative_edge_context_audit인 설정만 골라 다시 읽는다. 각 leaked edge에는 세 가지를 붙였다. 같은 edge를 다른 config도 leak으로 봤는지, score bucket이 hard leak인지 borderline leak인지, 그리고 degree gap이나 two-hop 같은 structural snapshot이 어떤 태그를 갖는지다. 성능을 올리는 코드라기보다, 다음 실험을 덜 헷갈리게 만드는 정리 레이어에 가깝다.
결과를 두 줄로 나누면
| config | dominant context | leaked / negative | hard | borderline | shared | next probe |
|---|---|---|---|---|---|---|
reduced_plus_closed_triplets |
hard_outlier_context |
4 / 5 | 1 | 3 | 4 | hard_edge_case_table |
reduced_plus_degree |
broad_borderline_context |
5 / 5 | 0 | 5 | 4 | feature_bias_slice |
표로 놓고 보니 둘의 차이가 훨씬 선명해졌다. reduced_plus_closed_triplets는 leak rate가 80%인데도 dominant context가 hard_outlier_context로 잡혔다. 이유는 간단하다. hard leak 하나가 있기 때문이다. sample graph의 [0, 7] negative edge가 0.9476까지 올라왔고, 이 edge는 degree_gap_high와 triangle_free tag를 동시에 갖고 있었다.
여기서 약간 무릎을 쳤다. 이름은 closed-triplets variant인데, 정작 hard leak edge의 closed_triplets_mean과 clustering_mean은 0이었다. 그러면 "closed triplets를 넣어서 삼각형 신호 때문에 터졌다"고 쓰면 안 된다. 오히려 triangle signal이 없는 edge에서 다른 structural 조합이 score를 과하게 밀어 올린 장면에 가깝다. 이름이 원인을 설명해 주는 게 아니라는 걸 다시 확인한 셈이다.
reduced_plus_degree는 반대 방향이다. hard leak은 하나도 없고, negative edge 5개가 전부 borderline leak이다. 평균 leaked snapshot은 degree_gap=1.0, avg_neighbor_degree_mean=2.6833, two_hop_neighbors_mean=2.8로 나왔다. 특정 edge 하나를 열어서 해결될 문제라기보다, degree가 들어간 입력판이 threshold 근처 score surface를 전반적으로 양성 쪽으로 밀었는지 보는 쪽이 자연스럽다.
shared leak edge가 남긴 힌트
이번 report에서 제일 마음에 든 필드는 shared_with_configs였다. 같은 dataset과 hardest seed에서 같은 negative edge를 여러 config가 동시에 leak으로 보는지 붙였다. 실제 결과에서는 shared leak edge가 4개나 있었다. closed-triplets 쪽의 hard outlier [0, 7]도 degree variant에서는 borderline leak으로 잡혔고, bipartite graph 쪽의 [0, 11], [1, 8], [4, 8]도 두 config가 같이 놓쳤다.
이건 꽤 중요하다. 만약 어떤 edge가 한 config에서만 튀었다면 그 config의 feature 조합을 먼저 의심하면 된다. 그런데 여러 config가 같은 edge를 같이 틀린다면, negative sampling 난이도나 graph family 자체의 context가 더 큰 원인일 수 있다. 나는 이런 경우를 바로 모델 튜닝으로 보내기보다 common failure set으로 따로 묶어 두는 편이 낫다고 본다. 그래야 다음 실험에서 "설정 A가 나쁜가"와 "이 edge가 원래 어려운가"를 섞지 않는다.
테스트로 고정한 부분
이번에도 작은 테스트부터 넣었다. hard leak이 있는 config는 hard_outlier_context로 가고, broad borderline leak은 broad_borderline_context로 가야 한다. 그리고 같은 negative edge를 두 config가 같이 leak으로 보면 shared_with_configs에 다른 config 이름이 들어와야 한다. 이 테스트를 먼저 실패시킨 뒤 구현했고, runner test에서는 JSON, CSV, PNG가 모두 만들어지는지만 확인했다.
전체 테스트는 28개로 늘었고 전부 통과했다. 이번 변경은 모델 학습 루프를 건드리지 않았지만, 실험 읽기 방식은 꽤 달라졌다. 기존에는 score histogram에서 "다음에 context를 봐야 한다"고 적어 둔 정도였는데, 이제는 그 다음 context를 어떤 파일로 열지까지 report가 말해 준다.
다음에 열 파일
다음 후보는 두 갈래다. closed-triplets variant는 hard_edge_case_table로 내려가서 [0, 7] edge의 positive neighbor, two-hop context, config별 score 차이를 개별 표로 봐야 한다. degree variant는 feature_bias_slice로 보내서 degree gap, neighbor degree, two-hop tag별 borderline score 평균을 잘라 보는 편이 맞다.
이런 작은 report를 계속 붙이다 보면 코드가 조금 장황해지는 단점은 있다. 그래도 지금 단계에서는 평균 AUC 한 줄보다 이런 inspection queue가 더 믿음직하다. 적어도 다음 세션에서 내가 뭘 열어야 하는지 다시 추측하지 않아도 된다. 이번 반복의 수확은 성능 개선이 아니라, 실패를 다시 볼 순서를 코드에 박아 둔 것이다.
'[개발 일기]' 카테고리의 다른 글
| GNN | Score bias probe 추가 (0) | 2026.05.14 |
|---|---|
| GNN | FP/FN 비용 queue 추가 (0) | 2026.05.07 |
| GraphRAG | History 상태 반복 횟수 추가 (0) | 2026.05.01 |
| GraphRAG | History Baseline Guard (1) | 2026.04.25 |
| GNN | GraphSAGE hybrid 입력 비교 (0) | 2026.04.16 |