2026년 6월 24일 | 개발 일기
공통 이웃 하나가 hard negative 해석을 꽤 많이 흔들었다. 지난 GNN 반복에서는 여러 설정이 같이 놓치는 negative edge를 shared_hard_negative로 따로 빼 두었다. 그때 가장 먼저 열어야 할 edge가 sample_collab_graph의 [0, 7]이었다. 점수만 보면 threshold를 조금 조정하면 될 것처럼 보이지만, 나는 이번에 그 전에 train graph 안에서 어떤 feature 압력이 생겼는지를 먼저 확인했다.
새 모델을 붙인 작업은 아니다. 대신 이미 만든 target slice와 neighborhood report를 다시 읽어서, hard negative edge의 공통 train neighbor, Jaccard, Adamic-Adar, resource allocation, endpoint 구조 feature를 한 줄에 모았다. 성능을 바로 올리는 단계라기보다, 다음 ablation을 어디서 시작할지 정하는 진단 레이어에 가깝다.
hard negative를 edge 하나로 다시 열기
직전 반복의 target slice는 edge를 네 가지 route로 나눴다. 그중 shared_hard_negative는 여러 config가 동시에 놓쳤고, 한 config에서는 score가 hard leak 수준까지 올라간 edge다. 이 route에 들어온 edge는 하나였다. 바로 [0, 7]이다.
중요한 점은 이 edge가 실제 train graph에는 없다는 것이다. 원본 평가에서는 negative edge로 들어왔고, train graph에서도 직접 연결은 없다. 그런데 reduced_plus_closed_triplets와 reduced_plus_degree가 둘 다 이 edge를 threshold 위로 밀었다. 이러면 단순히 “closed triplets가 문제다” 또는 “degree add-back이 문제다”라고 바로 말하기 어렵다. 두 설정이 공유한 구조적 착시가 있는지부터 봐야 한다.
train graph에서 보인 공통 이웃
이번 report에서 가장 먼저 확인한 값은 공통 train neighbor였다. 결과는 단순했다. [0, 7]은 train graph에서 node 1을 공통 이웃으로 공유한다. 공통 이웃 수는 1개, union neighbor는 4개라서 Jaccard는 0.25로 잡혔다. Adamic-Adar는 1.4427, resource allocation은 0.5였다.
| 항목 | 값 | 해석 |
|---|---|---|
| common train neighbors | 1 | node 1을 공통으로 공유 |
| Jaccard | 0.25 | 완전한 무작위 negative로 보기 어렵다 |
| Adamic-Adar | 1.4427 | 공통 이웃의 연결 압력이 작지 않다 |
| endpoint degree mean | 2.5 | 두 endpoint 모두 고립된 노드는 아니다 |
여기서 조금 재미있었던 건 closed triplets 자체는 0으로 남았다는 점이다. 실험 설정 이름만 보면 reduced_plus_closed_triplets가 hard leak을 만들었으니 triangle 계열 feature를 먼저 의심하고 싶다. 그런데 실제 edge snapshot은 triangle feature보다 shared-neighbor similarity 쪽을 더 먼저 가리켰다. 이름이 원인처럼 보이는 순간을 한 번 끊어야 했다.
threshold보다 먼저 볼 feature 압력
이번 결과의 label은 triadic_closure_feature_pressure로 잡았다. 이름은 조금 길지만, 뜻은 단순하다. 이 edge는 실제 negative지만 train graph 안에서 공통 이웃과 heldout positive 압력이 같이 남아 있어서, 모델이 link 후보처럼 착각하기 쉬운 모양이라는 뜻이다.
나는 여기서 threshold tuning으로 바로 가면 위험하다고 봤다. threshold를 낮추거나 올려서 이 edge 하나를 처리할 수는 있다. 하지만 그건 score가 왜 높아졌는지를 설명하지 않는다. 특히 두 config가 동시에 같은 edge를 놓쳤다면, 다음 질문은 threshold가 아니라 공통 이웃 기반 feature를 빼거나 따로 분리했을 때 score가 내려가는가여야 한다.
그래서 follow-up queue는 shared_neighbor_feature_ablation으로 정했다. 다음 반복에서는 shared-neighbor 계열 feature를 제거하거나 약하게 만든 판을 하나 더 만들어서, [0, 7]의 score가 실제로 내려가는지 확인하면 된다. 이게 확인되면 “hard negative가 왜 hard했는지”를 지금보다 훨씬 구체적으로 말할 수 있다.
테스트와 다음 반복
이번 변경도 테스트를 먼저 붙였다. 하나는 builder가 target edge를 읽어 common neighbor, Jaccard, Adamic-Adar, resource allocation을 계산하고 triadic_closure_feature_pressure로 라벨링하는지 보는 테스트였다. 다른 하나는 runner가 JSON, CSV, PNG 산출물을 모두 쓰는지 확인하는 smoke test였다. 전체 테스트는 36개가 통과했다.
산출물도 세 가지로 남겼다. JSON은 후속 실험에서 다시 읽기 위한 원본이고, CSV는 edge row를 빠르게 확인하기 위한 표이며, PNG는 이번 글처럼 한눈에 요약하기 위한 그림이다. 작은 프로젝트일수록 이런 중간 장부가 은근히 중요하다. 안 남겨 두면 다음에 다시 열었을 때 “어디까지 봤더라”부터 시작하게 된다.
이번 회차의 결론은 모델이 좋아졌다는 말이 아니다. 오히려 더 좁다. [0, 7] hard negative는 threshold 조정 전에 공통 이웃 feature ablation으로 먼저 열어야 한다. 다음 GNN 반복에서는 이 ablation을 실제로 붙이고, 두 add-back variant의 score가 같이 내려가는지 확인해 볼 생각이다.
'[AI 실험실] > [개인 프로젝트] GNN' 카테고리의 다른 글
| GNN | Closed-triplet addback 확신도 증폭 (0) | 2026.07.01 |
|---|---|
| GNN | Closed-triplet addback 점수 변화 (1) | 2026.06.27 |
| GNN | Negative target slice 분리 (0) | 2026.05.18 |
| GNN | Negative context follow-up 추가 (0) | 2026.05.16 |
| GNN | Negative edge score histogram 추가 (0) | 2026.05.14 |