2026년 7월 15일 | 개인 프로젝트 · GNN
closed_triplets 한 칸이 링크 점수를 밀어 올렸다는 결론에는 계속 찜찜한 구석이 있었다. 이전 실험은 해당 feature가 빠진 12차원 모델과 들어간 13차원 모델을 비교했다. 점수 차이는 컸지만, 그 차이가 정말 feature 때문인지 아니면 입력 차원과 초기 가중치까지 함께 바뀐 탓인지 분리하기 어려웠다. 그래서 이번 회차에서는 양쪽 모델을 모두 13차원으로 고정하고, 같은 초기 상태에서 closed_triplets 채널 하나만 가린 뒤 다시 학습했다.
12차원과 13차원을 비교할 때 남은 질문
이번에 추적한 대상은 sample_collab_graph의 edge [0, 7]이다. 이 edge는 실제로는 negative인데, 구조 feature를 더한 모델이 높은 확률로 positive라고 판단했던 실패 사례다. 두 노드는 공통 이웃 하나를 두고 있고, 후보 edge가 연결되면 닫히는 삼각형 수를 나타내는 closed_triplets 신호가 강하게 작용했다.
앞선 회차에서 feature를 포함한 모델의 예측 확률은 약 0.948까지 올라갔다. 반면 고정된 모델에서 raw closed_triplets 값만 0으로 바꿔 본 직접 효과는 logit 기준 -0.034에 그쳤다. feature 하나를 잠깐 지웠을 때는 거의 움직이지 않는데, feature 유무를 바꿔 모델을 다시 학습하면 logit 차이가 +2.778까지 벌어진 셈이다.
처음 이 숫자를 봤을 때는 training path가 달라졌다고 해석했다. 그래도 12차원 모델과 13차원 모델은 parameter shape부터 다르다. 같은 seed를 줘도 첫 layer의 크기가 달라지니 동일한 출발점이라고 할 수 없다. 이 조건을 그대로 둔 채 “재학습 증폭”이라고 부르면 입력 차원 confound를 너무 쉽게 넘기는 셈이었다.
같은 feature order와 같은 초기 상태
이번 paired ablation은 두 branch 모두 동일한 13개 feature를 같은 순서로 받는다. factual branch에서 구한 mean과 scale을 양쪽이 공유하고, masked branch에서는 표준화가 끝난 closed_triplets 열만 정확히 0.0으로 바꾼다. 마스킹한 뒤 통계를 다시 계산하지 않았기 때문에 나머지 12개 열은 완전히 같은 값으로 유지된다.
모델 초기화도 명시적으로 분리했다. seed로 만든 GraphSAGE state dictionary를 detached clone으로 보관하고, factual과 masked 학습에 각각 복사해 넣었다. 호출자가 넘긴 원본 state가 학습 중 mutate되지 않는지, key와 tensor shape가 다르면 조용히 일부만 로드하지 않고 ValueError로 중단하는지도 테스트에 넣었다. split seed와 initialization seed도 결과 schema에서 별도 필드로 남겼다.
사소해 보이지만 empty target queue도 fail-closed로 바꿨다. Python의 all([])은 True라서 대상이 하나도 없을 때 모든 불변조건을 통과한 것처럼 artifact가 만들어질 수 있었다. 먼저 실패하는 negative test를 추가한 다음, source report에서 target을 찾지 못하면 즉시 멈추도록 고쳤다. 이런 경계는 결과 숫자보다 덜 눈에 띄지만, 자동 실험에서는 허위 성공 한 건을 막는 쪽이 더 중요할 때가 많다.
확률 0.948 대 0.539
| 항목 | Factual | Masked |
|---|---|---|
| 입력 차원 | 13 | 13 |
| Target-edge probability | 0.9476 | 0.5394 |
| Target-edge logit | 2.8956 | 0.1580 |
| Best epoch | 16 | 1 |
동일 조건에서 factual과 masked의 probability 차이는 0.4082, logit 차이는 +2.7376이었다. 기존의 독립적인 12차원/13차원 cross-model 차이 +2.7785와 비교하면 약 98.53%가 남았다. factual branch가 기존 source probability를 다시 만들었는지도 확인했는데, 오차는 0.0000275로 설정한 0.001 gate보다 충분히 작았다.
내가 확인하고 싶었던 핵심은 이 숫자가 완전히 사라지는지였다. 같은 13차원, 같은 feature order, 같은 표준화 통계, 같은 초기 state로 맞췄는데도 차이가 거의 그대로 남았다. 적어도 이 한 사례에서는 입력 차원이나 서로 다른 초기화만으로 큰 점수 차이를 설명하기 어렵다.
세 막대는 같은 개입이 아니다
그림 오른쪽에는 paired training, source cross-model, source direct 세 값을 같이 두었지만, 이들을 같은 causal effect처럼 읽으면 안 된다. paired training은 표준화된 한 채널을 0으로 만든 뒤 두 모델을 다시 학습한 비교다. source cross-model은 독립적으로 학습한 12차원 모델과 13차원 모델의 차이다. source direct는 학습된 factual 모델을 고정하고 raw feature만 바꾼 counterfactual이다.
그래프의 y축을 단순한 “factual - masked”가 아니라 target-edge logit contrast로 바꾸고, JSON과 CSV에도 세 intervention의 정의와 비교 한계를 따로 적었다. 같은 화면에 막대가 세 개 있으면 사람 눈은 자연스럽게 길이만 비교한다. plot label과 schema가 그 오해를 먼저 막아야 한다는 걸 이번에 다시 느꼈다.
또 best epoch가 factual 16, masked 1로 크게 갈렸다. 이 결과는 단순한 inference-time feature 민감도보다, 해당 채널을 사용할 수 있느냐가 representation learning과 validation checkpoint 선택 경로를 함께 바꿨다는 해석에 더 가깝다. 다만 test AUC는 factual 0.50, masked 0.75로 오히려 반대 방향이어서 “feature가 전체 성능을 개선했다”고 말할 근거는 없다. 내가 보고 있는 것은 특정 실패 edge의 점수 증폭 경로다.
다음은 epoch를 맞춘 training dynamics
이번 결과는 target edge 하나, split seed 하나, initialization seed 하나에 대한 paired probe다. 재현 gate 때문에 현재 initialization seed는 source run과 호환되는 값에 묶여 있고, seed sweep까지 일반화하지 않았다. 따라서 closed-triplet이 항상 같은 크기로 작용한다고 결론 내리기에는 아직 이르다.
다음 단계에서는 factual과 masked를 같은 epoch에서 비교해 target logit, validation loss, embedding 변화가 언제 갈라지는지 추적할 생각이다. checkpoint가 서로 다른 시점에 선택된 결과인지, 초반부터 gradient 경로가 달라지는지 분리해야 한다. 이번 회차에서 얻은 답은 작지만 분명하다. 12차원과 13차원의 모양 차이를 제거해도 closed_triplets를 사용할 수 있는 학습 경로의 증폭은 거의 그대로 남았다.
'[AI 실험실] > [개인 프로젝트] GNN' 카테고리의 다른 글
| GNN | Closed-triplet 단일 공통 이웃 분해 (1) | 2026.07.04 |
|---|---|
| GNN | Closed-triplet addback 확신도 증폭 (0) | 2026.07.01 |
| GNN | Closed-triplet addback 점수 변화 (1) | 2026.06.27 |
| GNN | 공통 이웃 feature 압력 진단 (0) | 2026.06.24 |
| GNN | Negative target slice 분리 (0) | 2026.05.18 |