[AI 실험실]/[개인 프로젝트] GNN / GNN | Negative target slice 분리.md

GNN | Negative target slice 분리

조회

시리즈: GNN 실험일지 #15

이전: 14편 | 목록 | 다음: 16편

2026년 5월 18일 | 개발 일기


Negative target slice는 지난 GNN 실험에서 남긴 follow-up edge 목록을 한 번 더 접어 보는 작업이었다. 직전에는 hard_edge_case_tablefeature_bias_slice라는 큐를 만들었다. 그런데 막상 다음 작업을 시작하려고 보니, 그 큐만으로는 같은 negative edge가 여러 config에서 같이 새는지, 아니면 특정 config에서만 튀는지가 바로 보이지 않았다.

이번에는 새 모델을 붙이지 않았다. AUC를 다시 끌어올리는 작업도 아니었다. 대신 이미 만든 negative_context_followup 산출물을 읽어서 target edge를 dataset과 edge id 기준으로 다시 묶었다. 성능 개선보다 실패를 여는 순서를 고정하는 쪽에 가까운 작업이다. 이런 얇은 report가 조금 귀찮긴 한데, 작은 그래프 실험에서는 이 장부가 없으면 금방 "어느 설정이 나빴더라" 정도의 감상으로 돌아가 버린다.

큐를 edge 단위로 다시 접기

기존 follow-up report에는 config별 target edge 목록이 있었다. reduced_plus_closed_triplets는 hard outlier edge table로, reduced_plus_degree는 feature bias slice로 보내는 식이다. 이 구조는 config 중심으로는 좋지만, 공통으로 어려운 negative edge를 보기에는 조금 불편했다. 같은 edge가 두 config에 동시에 들어 있으면, 그건 특정 feature 하나의 문제가 아니라 graph family나 negative sampling context 쪽 문제일 수도 있기 때문이다.

그래서 이번에는 build_negative_target_slice_report()를 추가했다. 함수는 follow-up report의 target_edges를 모두 모은 뒤, dataset + edge를 key로 group한다. 그리고 같은 edge를 몇 개 config가 공유하는지, hard leak이 포함되어 있는지에 따라 네 가지 route로 나눴다.

  • shared_hard_negative: 여러 config가 같이 놓쳤고, 그중 hard leak이 포함된 edge
  • shared_borderline_negative: 여러 config가 같이 놓쳤지만 score는 threshold 근처에 붙은 edge
  • config_specific_hard_negative: 한 config에서만 hard하게 튄 edge
  • config_specific_borderline_negative: 한 config에서만 threshold 바로 위에 걸린 edge

이름만 보면 별것 아닌 분류지만, 다음 실험 순서를 정할 때 차이가 꽤 크다. shared hard negative는 먼저 공통 context를 열어야 하고, config-specific borderline은 해당 config의 score surface를 따로 잘라 보는 편이 맞다. 같은 false positive라도 바로 같은 처방으로 보내지 않으려는 장치다.

Negative target slice routing summary
follow-up target edge를 shared/hard/borderline route로 다시 묶은 요약. 같은 edge가 두 config에 같이 걸리는지 먼저 보게 했다.

이번에 갈라진 다섯 edge

dataset edge route configs max score
sample_collab_graph [0, 7] shared_hard_negative reduced_plus_closed_triplets, reduced_plus_degree 0.9476
bipartite_bridge_graph [0, 11] shared_borderline_negative reduced_plus_closed_triplets, reduced_plus_degree 0.5569
bipartite_bridge_graph [1, 8] shared_borderline_negative reduced_plus_closed_triplets, reduced_plus_degree 0.5545
bipartite_bridge_graph [4, 8] shared_borderline_negative reduced_plus_closed_triplets, reduced_plus_degree 0.5540
sample_collab_graph [5, 8] config_specific_borderline_negative reduced_plus_degree 0.5377

결과는 다섯 edge로 정리됐다. route count만 보면 shared_hard_negative 1개, shared_borderline_negative 3개, config_specific_borderline_negative 1개다. config_specific_hard_negative는 이번 판에서는 없었다. 이게 조금 다행이면서도, 한편으로는 공통 failure를 먼저 봐야 한다는 뜻으로 읽혔다.

가장 먼저 열 edge는 sample_collab_graph[0, 7]이다. 이 edge는 reduced_plus_closed_triplets에서는 0.9476까지 올라간 hard leak이고, reduced_plus_degree에서도 threshold 위에 걸렸다. 특정 config 하나만의 이상치가 아니라 두 add-back variant가 같이 민감하게 반응한 edge다. 다음 분석에서 이 edge는 positive neighborhood와 two-hop context를 따로 펼쳐 볼 가치가 있다.

bipartite_bridge_graph의 세 edge는 모양이 다르다. 모두 두 config가 같이 놓쳤지만 max score가 0.55 근처다. hard outlier라기보다 threshold 근처 표면이 같이 밀린 쪽에 가깝다. 여기서는 "왜 이 edge 하나가 폭발했나"보다, bipartite graph에서 negative edge score가 왜 두 config 모두에서 0.5 위로 살짝 올라오는지를 보는 편이 맞다.

config 이름보다 공통 실패 먼저 보기

이번 작업을 하면서 한 번 더 느낀 건, config 이름이 원인 설명처럼 보이면 위험하다는 점이다. reduced_plus_closed_triplets라고 적혀 있으면 hard leak도 closed triplets 때문처럼 읽고 싶어진다. 하지만 실제 top edge [0, 7]은 triangle-free context였다. 이름은 실험판의 라벨일 뿐이고, 원인은 edge snapshot과 config 간 겹침을 같이 봐야 한다.

그래서 이번 report에서는 config별 summary와 edge별 summary를 둘 다 남겼다. config summary는 각 설정이 shared target을 얼마나 갖는지, hard target과 borderline target이 몇 개인지 보여 준다. edge summary는 반대로 같은 edge가 몇 개 config에 걸렸는지 보여 준다. 둘 중 하나만 보면 해석이 쉽게 기울어진다.

나는 특히 shared_borderline_negative가 마음에 걸린다. 점수만 보면 큰 문제처럼 안 보일 수 있다. 0.55 근처라 threshold를 조금만 움직이면 빠질 수도 있다. 그런데 두 config가 같이 그 근처로 밀린다는 건, threshold 조정 전에 공통 구조를 봐야 한다는 신호일 수 있다. 작은 graph에서는 이런 약한 신호가 다음 실험 방향을 꽤 많이 바꾼다.

테스트와 정리

테스트는 두 가지를 추가했다. 첫 번째는 같은 edge가 hard config와 broad config에 동시에 들어오면 shared_hard_negative로 잡히는지 보는 테스트다. 두 번째는 runner가 JSON, CSV, PNG를 실제로 쓰는지 확인하는 smoke test다. 전체 테스트는 32개가 통과했다.

이번 변경으로 코드가 아주 조금 더 길어졌다. 대신 다음 작업의 시작점은 훨씬 선명해졌다. 이제 다음에 GNN 프로젝트를 열면, 평균 AUC 표를 다시 보는 대신 [0, 7] shared hard negative부터 열면 된다. 그다음에는 bipartite 쪽 shared borderline 세 edge를 graph family slice로 묶고, 마지막으로 [5, 8]을 degree add-back surface 전용으로 보면 된다.

성능 숫자를 올린 날은 아니지만, 실험이 덜 흐려진 날이었다. 나는 이런 날이 꽤 중요하다고 본다. 모델을 하나 더 붙이는 것보다, 이미 틀린 edge를 어떤 순서로 다시 볼지 정해 두는 쪽이 다음 반복의 속도를 더 많이 아껴 줄 때가 있다.

시리즈: GNN 실험일지 #15

이전: 14편 | 목록 | 다음: 16편

댓글

홈으로 돌아가기

검색 결과

"" 검색 결과입니다.