본문 바로가기
고민의 흔적../AI Agent 행동예측 해커톤

(연구일지 #10 이전 작업 부검하기)Dacon AI Agent 행동 예측 대회

by sungjinp0805 2026. 7. 11.

실험 검증 체계 재정비: “했다”가 아니라 “검증됐다”가 중요했다

이전 글에서는 민트 확장 실험을 정리했다.

v37에서 [SRC]+민트를 고전 ML에 연결하며 0.780을 넘었고, v39에서는 관계형 9피처를 추가해 소폭 상승했다.

 

이후 v40~v46에서는 민트를 더 늘리거나, 메타데이터를 복원하거나, 두 번째 고전에 관계형v2 피처를 넣는 실험을 진행했다.

결과만 보면 v45가 가장 좋았다.

v39 LB: 0.7809016418
v45 LB: 0.7809379356
v46 LB: 0.7805260271
 

v45는 챔피언 고전을 유지하면서, 복원 민트 기반 고전을 보조로 섞은 이중고전 구조였다. 반면 v46은 관계형v2 12피처를 추가해 로컬 고전 holdout은 올랐지만, 실제 LB는 반납했다.

 

이 구간을 지나면서 문제는 단순히 “다음에 어떤 실험을 할까”가 아니게 됐다.

실험 수가 많아졌고, Claude가 만든 코드와 제출물이 늘어났다.


그만큼 특정 실험이 정말 해당 가설을 검증한 것인지 확인하기 어려워졌다.

나는 이때부터 실험 결과 자체보다, 실험이 무엇을 검증했는지를 다시 확인해야 한다고 느꼈다.


Claude의 결론을 그대로 믿기 어려워졌다

Claude는 실험 속도를 높이는 데 큰 도움이 됐다.

새로운 아이디어를 코드로 빠르게 만들고, 제출 파일을 포장하고, 결과를 정리하는 데는 확실히 효율적이었다.

 

특히 하루 제출 횟수와 학습 시간이 모두 제한된 상황에서는 속도가 중요했다.

하지만 속도가 빨라질수록 다른 문제가 생겼다.

Claude는 특정 실험이 실패하면 그 축 전체를 빠르게 닫는 경우가 많았다.

specialist가 실패했다 → 구분 신호가 부족하다
holdout이 낮다 → 더 볼 필요가 없다
한 구현이 실패했다 → 축이 죽었다
 

초반에는 이런 판단이 크게 문제 되지 않았다.
하지만 점수 차이가 0.001 단위로 줄어든 후반부에는 위험했다.

실패한 것은 특정 구현일 수 있다.


그런데 그것을 접근 전체의 실패로 정리하면, 아직 남아 있는 가능성까지 같이 버리게 된다.

v37과 v39가 그 예시였다.

 

고전 ML은 거의 방치되어 있었고, [SRC]나 관계형 피처처럼 아직 반영되지 않은 신호가 남아 있었다. 그걸 넣자 실제 LB가 올랐다.

그래서 이 시점부터는 Claude가 정리한 결론을 그대로 받아들이기보다, 그 결론이 실제 코드와 산출물 기준으로 맞는지 확인해야 했다.


검증 기준을 다시 세우다

이후에는 실험을 볼 때 기준을 바꿨다.

기존에는 결과표를 보고 판단하는 경우가 많았다.

holdout이 올랐는가?
LB가 올랐는가?
Claude가 어떤 결론을 냈는가?
 

하지만 이제는 이 정도로는 부족했다.

중요한 것은 다음 질문들이었다.

실제로 어떤 코드가 실행됐는가?
제출 zip 안의 script가 기록과 일치하는가?
holdout 예측은 해당 row를 학습하지 않은 모델에서 나온 것인가?
비교 대상은 같은 seed, 같은 split, 같은 전처리인가?
확률을 섞은 것인가, raw logit을 섞은 것인가?
단일 변수만 바뀐 실험인가?
 

이 기준이 없으면, 실험 결과를 잘못 해석할 수 있었다.

예를 들어 어떤 실험이 holdout에서 좋아졌다고 해도, 그 모델이 holdout row를 학습에 포함했다면 의미가 없다.


또 어떤 앙상블이 좋아 보인다고 해도, 한쪽은 softmax probability이고 다른 한쪽은 raw logit이라면 섞는 방식 자체가 깨진다.

Claude의 실수를 감사할 방법이 필요했다.

 

마침 Codex에서 GPT 5.6모델을 출시하였고 여러번의 사용량 초기화를 예고하였다.

그래서 Codex를 독립 감사 용도로 사용하기 시작했다.

Claude는 실험과 zip 생성을 담당하고, Codex는 요청이 있을 때 코드·산출물·전제 조건을 따로 확인하는 역할로 나눴다.


첫 번째 문제: holdout 누수

가장 먼저 크게 걸린 것은 holdout 누수였다.

특정 모델의 holdout 예측을 만든다고 했지만, 실제로는 전체 데이터로 refit된 배포 모델을 사용한 경우가 있었다.

 

이 경우 holdout row가 이미 학습에 포함되어 있으므로, 그 예측은 공정한 holdout 평가가 아니다.

Codex 감사에서는 s48/s51 MPS 추론 결과가 현재 멤버 그리드에 그대로 사용되면 안 된다는 점이 확인됐다.

 

해당 모델들은 전체 데이터 refit 배포 모델이었기 때문에 hidx 행이 학습에 포함되어 있었고, 따라서 hidx 예측은 누수였다.

이건 단순한 실수처럼 보일 수 있지만, 후반부 실험에서는 치명적이다.

 

점수 차이가 0.001 단위인데, holdout 예측 하나가 누수되어 있으면 멤버 교체 판단이 완전히 틀어질 수 있다.

그래서 이후 원칙을 세웠다.

holdout 예측은 반드시 해당 row를 학습하지 않은 모델에서 나온 것이어야 한다.
전체 데이터 refit 모델의 holdout 예측은 멤버 비교에 사용하지 않는다.
 

두 번째 문제: 직렬화 불일치

같은 모델을 사용하더라도 입력 직렬화가 다르면 같은 실험이 아니다.

예를 들어 v45에서 사용한 transcript 직렬화와, 나중에 MPS 스크립트에서 사용한 직렬화가 다르면 두 결과는 직접 비교할 수 없다.

 

감사 과정에서 이런 차이도 확인됐다.

기록상으로는 “배포 transcript와 동일”이라고 되어 있었지만, 실제로는 v45와 MPS 스크립트의 직렬화 형식이 달랐다.

 

v45는 [META] tier/ci/dirty/turn/open 형태였고, MPS 쪽은 tier/lang/turn/files/dirty/ci처럼 다른 구성을 사용했다.

이 대회에서는 작은 입력 차이가 점수에 영향을 줄 수 있었다.


특히 Transformer 계열은 입력 문자열의 구조에 민감했다.

그래서 이후에는 “같은 모델”인지뿐 아니라, “같은 입력 직렬화”인지도 함께 확인해야 했다.

모델이 같다 ≠ 실험이 같다
모델 + 전처리 + 직렬화 + split이 같아야 같은 비교다
 

세 번째 문제: logit과 probability 혼용

앙상블을 하려면 각 모델의 출력을 같은 의미로 맞춰야 한다.

보통은 각 클래스에 대한 probability를 섞는다.

 

하지만 감사 과정에서 일부 파일은 이름과 달리 raw logit이었고, 다른 파일은 softmax probability인 경우가 확인됐다.

raw logit은 행별 합이 1이 아니고 값 범위도 probability와 다르다.

이런 값을 probability와 그대로 더하면 앙상블이 수학적으로 깨진다.

 

이 문제는 특히 위험하다.

겉으로는 모두 .npy 파일이고 shape도 같아 보인다.

 

하지만 내부 값의 의미가 다르면 완전히 다른 데이터다.

그래서 이후 산출물 검증 항목에 다음을 추가했다.

shape 확인
finite 값 확인
행별 합이 1인지 확인
probability인지 raw logit인지 확인
필요하면 softmax 후 사용
 

이후부터는 파일명만 보고 믿지 않고, 실제 값을 확인해야 했다.


네 번째 문제: specialist 결론의 재검토

specialist는 이전에 여러 번 시도했지만, 대부분 실패로 정리되어 있었다.

특히 v24에서는 4형제 specialist가 로컬에서 크게 좋아 보였고, 이후 LB에서는 크게 반납했다.

 

당시에는 “holdout에서는 좋지만 서버에서는 안 맞는다” 정도로 받아들였다.

하지만 감사 결과, v24의 로컬 +0.0124 자체가 공정한 비교가 아니었다.

full-data refit specialist가 holdout 평가에 섞였고, 비교 대상도 잘못되어 있었다.

 

따라서 이 결과를 근거로 specialist 전체를 강하게 닫는 것은 부적절했다.

최신 로그에서도 v24 로컬 +0.0124는 누수와 잘못된 비교로 무효라고 정리되어 있다.

 

이 부분은 중요했다.

specialist가 서버에서 성공했다는 뜻은 아니다.
하지만 “specialist 축 전체가 죽었다”고 말할 근거도 부족했다.

 

정확한 결론은 더 좁아야 했다.

틀린 결론:
specialist는 불가능하다.

수정된 결론:
당시 구현한 specialist와 비교 방식은 앙상블을 넘지 못했다.
일부 로컬 개선 주장은 공정 비교가 아니었다.
 

이후부터는 “축 종결”이라는 표현을 더 조심하게 됐다.


다섯 번째 문제: 민트 메타 복원도 완전하지 않았다

민트 실험에서도 검증 문제가 있었다.

 

이전 글에서는 민트의 핵심 문제가 메타데이터 시점이라고 정리했다.
과거 step을 복원했다면, 그 시점의 open_files, CI 상태, git_dirty 등을 맞춰야 한다.

 

처음에는 run_tests 기록을 재생하면 CI 상태를 꽤 잘 복원할 수 있다고 봤다.

하지만 이후 재검토에서 이 해석도 수정됐다.

 

rolling history의 시작 상태를 none으로 가정한 것이 문제였다.

실제로는 이전 history window 밖에서 이미 테스트가 실행되었을 수 있다.

최신 로그에서는 train_mint_ci.jsonl을 폐기 대상으로 정리했고, ci74의 holdout 상승도 “마스킹 단독효과”가 아니라 잘못된 CI 처리, zero-sentinel 도메인 표식, 텍스트 변경이 섞인 묶음 효과로 재분류했다.

 

이것도 중요한 정정이었다.

처음에는 “CI를 복원했다”고 생각했지만, 실제로는 rolling window 밖의 상태를 알 수 없었다.


즉, 복원은 완벽하지 않았고, 일부는 추정에 가까웠다.

그래서 민트 관련 결론도 더 좁게 잡아야 했다.

틀린 결론:
민트 메타 복원은 완료됐다.

수정된 결론:
일부 메타는 복원 가능하지만, rolling history 밖의 상태는 알 수 없다.
복원 불가능한 메타는 마스크하거나 별도 처리해야 한다.
 

여섯 번째 문제: “결정론”의 의미도 좁혀야 했다

v23a를 같은 zip으로 다시 제출했을 때 LB가 소수점 10자리까지 동일하게 나왔다.

이 결과는 유용했다.

 

같은 제출 파일이면 같은 점수가 나온다는 것을 확인했기 때문이다.

하지만 이것을 너무 넓게 해석하면 안 된다.

같은 zip이 같은 LB를 낸다는 것은 채점 실행이 결정적이라는 뜻이다.


그렇다고 해서 모든 0.000x 차이가 일반화 가능한 성능 차이라는 뜻은 아니다.

LB는 여전히 하나의 유한한 테스트셋에서 계산된 점수다.


또 로컬 holdout이 서버 분포를 항상 대표하는 것도 아니다.

따라서 이후 기준은 이렇게 정리했다.

같은 zip → 같은 LB
하지만 holdout → 서버 우열 보장은 아님
0.000x 차이도 제출 판단에는 의미가 있지만,
그 자체가 보편적인 모델 우열을 증명하는 것은 아님
 

이 기준은 이후 제출 전략에도 영향을 줬다.

holdout은 후보를 거르는 필터로 사용하되, 최종 우열은 서버 LB로 확인한다는 규칙을 유지했다.


이후의 운영 규칙

감사 이후에는 실험 운영 규칙을 더 명확히 했다.

첫째, Claude와 Codex의 역할을 분리했다.

Claude:
실험 코드 작성
학습 실행
제출 zip 생성
결과 정리

Codex:
사용자 요청 시 독립 검증
실제 zip/script 확인
산출물 shape/probability/provenance 확인
비교 조건 검토
 

둘째, 제출물마다 메모를 남기기로 했다.

어떤 멤버를 사용했는지, 어떤 파일에서 온 산출물인지, 기존 챔피언과 어떤 차이가 있는지 기록해야 했다.

 

로그에도 제출 메모 규칙과 handoff 문서 정비가 운영 지시로 남아 있다.

셋째, 실험 결론의 표현을 바꿨다.

X: 이 축은 죽었다.
O: 이 구현에서는 챔피언을 넘지 못했다.

X: holdout이 올랐으니 대박이다.
O: holdout 기준으로 제출 후보가 됐다.

X: 메타 복원 완료.
O: 복원 가능한 범위와 불가능한 범위를 분리해야 한다.
 

넷째, 비교 실험은 가능한 한 matched control로 하기로 했다.

같은 seed
같은 split
같은 preprocessing
같은 직렬화
같은 멤버 구성
바뀐 변수 하나
 

이 조건이 맞지 않으면, 실험 결과를 강한 결론으로 쓰지 않기로 했다.


이 구간에서 배운 것

이 구간에서 가장 크게 배운 것은, 실험을 많이 하는 것과 실험이 검증되는 것은 다르다는 점이었다.

대회 후반에는 실험 수가 빠르게 늘어났다.

모델도 많아졌고, seed도 많아졌고, 고전 ML 버전도 많아졌다.
민트, specialist, relation feature, kf seed, MPS 추론, refit 산출물까지 섞이면서 관리해야 할 파일도 많아졌다.

이 상태에서 기록만 보고 판단하면 위험했다.

했다고 적혀 있는 것
실제로 실행된 것
제출 zip에 들어간 것
공정하게 비교 가능한 것
 

이 네 가지는 모두 다를 수 있었다.

그래서 이후부터는 단순히 결과표만 보는 것이 아니라, 실제 산출물 기준으로 확인하는 방식이 필요했다.

이건 대회 점수뿐 아니라, 실험 태도 자체에 대한 교정이었다.


정리

v45까지는 실험을 빠르게 늘리며 점수를 조금씩 올렸다.

하지만 v46 이후에는 더 중요한 문제가 드러났다.

어떤 실험이 실패했을 때, 그 실패가 정말 가설 전체의 실패인지 알기 어려웠다.
어떤 실험이 좋아 보였을 때도, 그 개선이 공정한 비교에서 나온 것인지 확인해야 했다.

Codex 감사로 확인한 문제들은 다음과 같았다.

holdout 누수 가능성
입력 직렬화 불일치
raw logit과 probability 혼용
specialist 비교 오류
민트 메타 복원의 한계
holdout 해석 과신
 

이후 기준은 명확해졌다.

실험은 많이 하는 것보다,
무엇을 검증했는지 정확히 아는 것이 중요하다.
 

이 편은 점수를 크게 올린 이야기는 아니다.

하지만 이후 실험을 이어가기 위한 기준을 다시 세운 구간이었다.
다음 글에서는 이 검증 체계를 바탕으로 멤버풀을 다시 정리하고, v47과 v49에서 실제 LB를 끌어올린 과정을 정리할 예정이다.