민트 확장 실험: 데이터 증강보다 어려웠던 메타 시점
이전 글에서는 고전 ML을 다시 점검한 과정을 정리했다.
Transformer 계열에는 이미 [SRC] 구분을 넣어 학습하고 있었지만, 고전 ML은 여전히 구식 방식에 머물러 있었다.
그래서 고전 ML에도 [SRC]를 적용했고, 여기에 닫혔던 history 수확 축을 학습 데이터 증강 형태로 다시 연결했다.
그 결과 v37에서 처음으로 0.780을 넘었다.
v37 LB: 0.7806602638
이후 관계형 9피처를 추가한 v39에서는 다시 소폭 상승했다.
v39 LB: 0.7809016418
이 구간에서 확인한 것은 단순했다.
고전 ML은 끝난 축이 아니었다.
Transformer에 집중하는 동안 고전 ML 쪽에는 아직 반영되지 않은 신호가 남아 있었다.
이번 글에서는 그중 민트 축을 따로 정리한다.
민트는 새 아이디어가 아니라, 닫혔던 축의 재연결이었다
민트는 이 시점에 갑자기 새로 나온 아이디어가 아니었다.
초기 고전 ML 단계에서도 history를 이용해 라벨을 수확하려는 시도를 한 적이 있었다.
v9에서 확인한 것은 세션 연속성이었다.
train 데이터에서는 같은 세션의 step들이 이어져 있었고, 다음과 같은 관계가 성립했다.
step N의 정답 action
=
step N+1 history의 마지막 assistant_action
즉, 다음 row의 history 안에는 이전 step에서 실제로 수행된 assistant action이 들어 있었다.
로컬 시뮬레이션에서는 이 구조가 매우 강하게 보였다.
v9 holdout 시뮬레이션: 0.9912
하지만 실제 제출 결과는 v7과 같았다.
v7 LB: 0.6179
v9 LB: 0.6179
이 결과를 보고 당시에는 history 수확 축을 닫았다.
서버 test에서는 train처럼 같은 세션의 다음 row를 이용할 수 없었다.
즉, 예측 단계에서 다음 row의 history를 보고 현재 row의 정답을 직접 수확하는 방식은 통하지 않았다.
다만 여기서 확인한 사실이 하나 있었다.
history 안에는 실제 assistant action 흐름이 들어 있었다.
예측 단계에서 직접 수확할 수는 없었지만, history 자체에는 과거 user 발화와 그에 대응하는 assistant action이 남아 있었다.
이 점이 나중에 민트로 다시 연결된다.
예측용 수확에서 학습 데이터 복원으로
v9에서 실패한 방식은 예측 단계에서 정답을 직접 수확하는 것이었다.
하지만 대회 후반에 고전 ML을 다시 점검하면서, 이 아이디어를 다른 방식으로 볼 수 있었다.
test 예측 중에 정답을 수확하는 것은 불가능하다.
하지만 train 데이터의 history 안에 들어 있는 과거 step을 학습 데이터로 복원하는 것은 가능하다.
예를 들어 어떤 train row의 history 안에 다음 흐름이 있다고 하자.
user: main.py 확인해줘
assistant_action: read_file(main.py)
user: 에러 나는 부분 찾아줘
assistant_action: grep_search(error)
이 경우 history 안에서 과거 user/action 쌍을 뽑아, 다음과 같은 추가 학습 샘플을 만들 수 있다.
입력: main.py 확인해줘
정답: read_file
입력: 에러 나는 부분 찾아줘
정답: grep_search
이렇게 history에서 복원한 추가 학습 샘플을 민트라고 칭했다.
여기서 중요한 차이는 다음과 같다.
v9 수확:
test 예측 단계에서 다음 row의 history를 이용해 정답을 직접 가져오려는 방식
민트:
train history 안의 과거 user/action 쌍을 복원해 학습 데이터로 추가하는 방식
즉, 민트는 완전히 새로운 아이디어라기보다, v9에서 닫혔던 history 수확 축을 학습 데이터 증강 방식으로 다시 연결한 실험이었다.
당시에는 몰랐지만 이 복원은 완벽하지 않았다.
자세한 내용은 후속편에서 다룬다.
v37: 민트를 고전 ML에 다시 연결
이 아이디어가 실제 제출 조합에 들어간 것이 v37이었다.
v37에서는 고전 ML을 다시 손보면서 두 가지를 반영했다.
1. Transformer에서 효과를 본 [SRC] 구분을 고전 ML에도 적용
2. history에서 복원한 민트 샘플을 고전 ML 학습 데이터에 추가
즉, v37은 단순히 기존 고전 ML을 다시 제출한 것이 아니었다.
Transformer 계열에서는 이미 [SRC] au, [SRC] sim 같은 구분을 넣고 있었지만, 고전 ML은 여전히 이전 방식에 가까웠다.
여기에 [SRC]를 반영하고, 동시에 v9에서 닫혔던 history 수확 축을 민트 학습 데이터 형태로 다시 연결했다.
구성은 다음과 같았다.
v37:
개선고전([SRC] + 민트)
+ RoBERTa seed48
+ RoBERTa seed51
+ kf768
결과는 다음과 같았다.
v37 LB: 0.7806602638
처음으로 0.780을 넘었다.
다만 v37만으로는 민트 효과를 분리해서 볼 수 없었다.
v37에는 [SRC] 적용과 민트 데이터 추가가 함께 들어가 있었기 때문이다.
따라서 이 상승이 [SRC] 때문인지, 민트 때문인지, 둘의 조합 때문인지는 따로 검증이 필요했다.
그래서 v40 이후의 실험은 민트를 처음 발견한 과정이 아니라, 이미 v37에서 다시 연결한 민트 축을 분해하고 검증하는 과정에 가까웠다.
확인해야 할 질문은 다음과 같았다.
민트 데이터를 더 늘리면 좋아질까?
복원한 샘플의 메타데이터는 과거 시점과 맞는가?
민트는 고전 ML뿐 아니라 Transformer에도 도움이 될까?
챔피언 고전을 민트 고전으로 대체해도 되는가?
이 질문들이 v40~v46 실험으로 이어졌다.
v40: 민트를 더 늘렸지만 오히려 하락
먼저 확인한 것은 민트 데이터 확장이었다.
v37에서 민트가 포함된 고전 ML이 좋은 결과를 냈다면, history에서 더 많은 샘플을 뽑아 학습 데이터로 추가하면 더 좋아질 수 있다고 생각할 수 있다.
그래서 v40에서는 고전 민트를 더 확장했다.
v40:
v39
+ 고전민트 mint2
LB: 0.778768236
결과는 좋지 않았다.
v39 LB: 0.7809016418
v40 LB: 0.778768236
오히려 약 0.0021을 반납했다.
이 결과는 중요했다.
history에서 더 많이 뽑는다고 항상 정보가 늘어나는 것은 아니었다.
history는 완전한 전체 세션 로그가 아니라, 일부 최근 턴이 잘린 형태로 들어 있는 경우가 많다.
그러다 보니 추가로 뽑은 샘플 중에는 진짜 새로운 상태가 아니라, 같은 과거 이벤트가 다른 잘린 prefix로 반복된 것들이 섞일 수 있었다.
즉, 데이터 수는 늘었지만 정보량이 그대로 늘어난 것은 아니었다.
v40 이후 민트의 방향은 “더 많이 뽑기”가 아니라, “정확한 상태로 복원하기” 쪽으로 바뀌었다.
문제는 메타데이터의 시점이었다
민트 샘플의 라벨을 만드는 것은 상대적으로 쉬웠다.
history 안에는 과거 assistant action이 들어 있었기 때문이다.
문제는 메타데이터였다.
기존 train row의 session_meta는 현재 row 시점의 상태를 설명한다.
예를 들면 다음과 같다.
open_files
CI 상태
git 상태
token budget
workspace 상태
turn index
하지만 민트 샘플은 history 안에서 복원한 과거 step이다.
따라서 그 샘플에 붙는 메타데이터도 현재 row의 meta가 아니라, 그 과거 step 당시의 meta여야 한다.
예를 들어 현재 row의 open_files가 다음과 같다고 하자.
open_files = ["main.py", "utils.py"]
그런데 history에서 복원한 민트 샘플은 main.py를 열기 전의 step일 수 있다.
그렇다면 그 시점의 open_files에는 main.py가 없어야 한다.
하지만 현재 row의 meta를 그대로 붙이면, 과거 step에도 이미 main.py가 열려 있는 것처럼 들어간다.
이건 시점이 맞지 않는다.
즉, 민트는 label보다 meta가 더 어려운 실험이었다.
v41: 원본 메타와 복원 메타 비교
이 문제를 확인하기 위해 v41에서는 두 가지를 비교했다.
먼저 v41_mint1은 민트 데이터를 사용하되, 원본 메타 방식에 가까운 대조군이었다.
v41_mint1 LB: 0.7786376301
v39보다 낮았다.
v39 LB: 0.7809016418
v41_mint1 LB: 0.7786376301
다음으로 v41_full에서는 민트 샘플의 메타데이터를 과거 시점에 맞게 복원하려고 했다.
예를 들어 CI 상태는 run_tests 기록을 따라가며 재생하고, open files는 history를 바탕으로 휴리스틱하게 복원하는 방식이었다.
결과는 다음과 같았다.
v41_full LB: 0.7793256789
v41_mint1보다는 올랐다.
v41_mint1 LB: 0.7786376301
v41_full LB: 0.7793256789
이 결과는 메타데이터 시점 복원이 실제로 의미가 있다는 쪽에 가까웠다.
다만 v39에는 여전히 미치지 못했다.
v39 LB: 0.7809016418
v41_full LB: 0.7793256789
즉, 복원 메타는 원본 메타보다 나았지만, 전체 조합은 아직 챔피언 고전보다 약했다.
v42: 챔피언 고전 재현 시도
v41_full이 v39보다 낮은 이유는 두 가지로 볼 수 있었다.
하나는 민트와 복원 메타가 약했기 때문일 수 있다.
다른 하나는 더 단순한 문제였다.
새로 재구성한 고전 ML 레시피가 기존 챔피언 고전과 완전히 같지 않았을 수 있다.
이 가능성이 꽤 커 보였다.
고전 ML은 생각보다 작은 차이에 민감했다.
TF-IDF 설정
max_df
strip_accents
dtype
HGB early stopping
patience
seed
class weight 방식
v42에서는 이 차이들을 최대한 맞추려고 했다.
결과는 v41_full보다 조금 나아졌다.
v42 LB: 0.7796685295
하지만 여전히 v39에는 미치지 못했다.
v39 LB: 0.7809016418
v42 LB: 0.7796685295
이 결과로 챔피언 고전을 완전히 재현하는 것이 생각보다 어렵다는 점을 확인했다.
문제는 민트 하나가 아니었다.
고전 ML 파이프라인 자체가 vocab, IDF, 피처 순서, 텍스트 빌더 차이에 민감했다.
Transformer에 민트와 관계형 피처를 넣어봤지만 효과는 없었다
v39에서 관계형 피처는 고전 ML에 도움이 됐다.
그러면 같은 관계형 피처를 Transformer에도 넣으면 좋아질까?
이 실험도 확인했다.
kfrel
robrel
결과는 좋지 않았다.
Transformer 쪽에서는 관계형 피처를 넣었을 때 오히려 하락했다.
이유는 어느 정도 납득 가능했다.
Transformer는 이미 full transcript를 읽고 있었다.
파일명, action, args, 결과, user 발화가 모두 텍스트 안에 들어 있었다.
관계형 피처는 고전 ML에는 명시적 신호가 되지만, Transformer 입장에서는 이미 텍스트에서 볼 수 있는 정보를 다시 넣는 중복 신호일 수 있었다.
그래서 관계형 피처는 고전 ML 전용으로 보는 것이 맞다고 판단했다.
비슷하게 복원 민트를 Transformer 멤버에 적용한 실험도 좋지 않았다.
v44에서는 v39의 kf768 멤버를 복원 민트로 재학습한 kf768mf로 교체했다.
v44:
v39에서 kf768 → kf768mf 교체
LB: 0.7784027943
v39보다 크게 낮았다.
v39 LB: 0.7809016418
v44 LB: 0.7784027943
복원 민트는 고전 ML에는 일부 도움이 될 수 있었지만, Transformer에 그대로 적용하는 방식은 맞지 않았다.
Transformer는 transcript 자체를 강하게 보기 때문에, history에서 복원한 샘플과 복원 meta가 오히려 학습 분포를 흐릴 수 있다고 봤다.
v43: mint2_full도 실패
v40에서 mint2가 실패했기 때문에, 이번에는 메타데이터를 복원한 상태로 mint2를 다시 확인했다.
이 실험이 v43이었다.
v43:
v42 레시피
+ mint2_full
LB: 0.7793343642
v42보다도 낮았다.
v42 LB: 0.7796685295
v43 LB: 0.7793343642
이 결과는 단순히 “메타데이터 오염 때문에 mint2가 나빴다”로 끝나지 않았다.
메타를 복원해도 mint2는 좋아지지 않았다.
즉, mint2의 문제는 메타 시점만이 아니었다.
추가로 뽑은 샘플 자체가 얕은 history에서 반복되거나 변형된 상태를 포함하고 있었을 가능성이 컸다.
결론적으로 이 시점에서는 mint1 정도가 적절한 지점이라고 봤다.
더 많이 캐는 것이 항상 좋은 것은 아니었다.
v45: 챔피언 고전을 보존하고 새 고전을 보조로 섞기
v41~v43을 지나며 한 가지가 분명해졌다.
민트와 메타 복원에는 신호가 있었다.
하지만 새로 재구성한 고전 ML은 기존 챔피언 고전보다 약했다.
그렇다면 기존 챔피언 고전을 완전히 교체하는 방식은 위험했다.
그래서 v45에서는 고전 슬롯을 둘로 나눴다.
v45:
고전 슬롯 =
0.5 × 챔피언 고전
+
0.5 × v42 고전(복원민트)
Transformer 3멤버는 v39 그대로 유지
결과는 다음과 같았다.
v45 LB: 0.7809379356
v39보다 아주 조금 올랐다.
v39 LB: 0.7809016418
v45 LB: 0.7809379356
상승폭은 0.00004 수준으로 매우 작았다.
하지만 실험적으로는 의미가 있었다.
새 고전이 챔피언 고전을 대체할 만큼 강하지는 않았다.
하지만 챔피언 고전을 훼손하지 않고 보조 신호로 섞으면, 아주 작게나마 추가 이득을 만들 수 있었다.
즉, 이 구간의 정답은 “새 고전으로 교체”가 아니라 “챔피언 고전 보존 + 보조 고전 추가”에 가까웠다.
v45는 고전 슬롯을 챔피언 고전과 v42 고전으로 나눈 이중고전 구조였고,
v39 대비 아주 작은 신기록을 만들었다.
v46: 관계형v2는 holdout 개선에도 LB 반납
v45에서 이중고전 구조가 작게나마 통했기 때문에, 다음에는 두 번째 고전에 더 강한 피처를 넣어봤다.
v46에서는 두 번째 고전에 관계형v2 12피처를 추가했다.
예를 들면 다음과 같은 replay 상태 피처였다.
edits_since_test
zero_match
same_streak
steps_since_fail
로컬 고전 holdout은 좋아졌다.
0.6134 → 0.6158
하지만 실제 LB는 내려갔다.
v46 LB: 0.7805260271
v45보다 낮았다.
v45 LB: 0.7809379356
v46 LB: 0.7805260271
이 결과는 다시 한 번 holdout과 LB의 차이를 보여줬다.
관계형v2 피처는 로컬 holdout에서는 효과가 있어 보였지만, 서버에서는 반납했다.
가능한 이유는 history 깊이 차이였다.
edits_since_test, steps_since_fail 같은 피처는 세션이 얼마나 진행되었는지, history가 얼마나 깊은지에 영향을 받는다.
로컬 holdout은 세션의 여러 step을 포함하지만, 서버 test는 세션당 한 row에 가까운 구조로 보였다.
그렇다면 history 깊이에 강하게 묶인 피처는 로컬에서는 좋아 보여도 서버에서는 맞지 않을 수 있다.
그래서 이 시점에서는 관계형 피처를 더 복잡하게 늘리는 방향을 조심하게 됐다.
v46은 “로컬에서 좋아 보이는 replay 피처가 서버에서도 좋은가?”를 확인한 실험이었고, 결과는 아니었다.
이 구간에서 확인한 것
v40~v46은 점수만 보면 큰 상승 구간은 아니었다.
v39 LB: 0.7809016418
v40 LB: 0.778768236
v41_mint1 LB: 0.7786376301
v41_full LB: 0.7793256789
v42 LB: 0.7796685295
v44 LB: 0.7784027943
v43 LB: 0.7793343642
v45 LB: 0.7809379356
v46 LB: 0.7805260271
하지만 실험적으로는 중요한 구간이었다.
첫째, 민트는 처음 등장한 아이디어가 아니었다.
v9에서 닫힌 history 수확 축이, v37 이후 학습 데이터 증강 형태로 다시 연결된 것이었다.
둘째, history에서 더 많이 뽑는다고 좋아지지 않았다.
mint2는 원본 방식에서도 실패했고, 메타를 복원한 mint2_full에서도 실패했다.
셋째, 민트의 핵심 문제는 메타데이터 시점이었다.
과거 step을 복원했다면, 그 step에 맞는 meta도 함께 복원해야 했다. 현재 row의 meta를 그대로 붙이면 시간적으로 맞지 않는 정보가 들어갈 수 있었다.
넷째, 복원 민트는 Transformer보다 고전 ML 쪽에 더 맞았다.
Transformer는 이미 transcript를 보고 있었고, 복원 민트는 오히려 분포를 흐릴 수 있었다.
다섯째, 챔피언 고전은 쉽게 대체할 수 없었다.
새 고전이 일부 신호를 가지고 있어도, 기존 챔피언 고전을 완전히 갈아엎으면 손실이 컸다.
v45처럼 챔피언 고전을 보존하고 새 고전을 보조로 섞는 방식이 더 안전했다.
정리
이 구간의 핵심은 민트를 “많이 넣는 것”이 아니었다.
처음에는 history에서 더 많은 학습 샘플을 뽑으면 성능이 오를 수 있다고 생각했다.
하지만 실제로는 데이터 수보다 시점 정합성이 더 중요했다.
데이터를 더 많이 뽑는 것보다,
그 데이터가 어떤 시점의 상태를 나타내는지가 더 중요했다.
v37에서 민트가 다시 연결되며 0.780을 넘었지만,
v40 이후 확장 실험은 대부분 실패했다.
결국 이 구간에서 살아남은 방식은 보수적이었다.
챔피언 고전은 유지한다.
민트 기반 새 고전은 보조 신호로만 섞는다.
복잡한 replay 피처는 서버 분포와 맞지 않을 수 있다.
최종적으로 v45가 이 구간의 가장 좋은 결과였다.
v45 LB: 0.7809379356
상승폭은 작았지만, 방향은 분명했다.
닫힌 축을 다시 연결할 수는 있다.
하지만 다시 연결한 뒤에는 “얼마나 많이 넣을까”보다 “어떤 시점의 정보인가”, “기존 강한 축을 훼손하지 않는가”를 먼저 봐야 했다.
그리고 이 구간은 다음 문제로 이어졌다.
실험이 많아질수록, 특정 실험이 정말 그 가설을 검증했는지 확인하기 어려워졌다.
다음 글에서는 대회 후반에 진행한 실험 검증 체계 재정비를 정리할 예정이다.
'고민의 흔적.. > AI Agent 행동예측 해커톤' 카테고리의 다른 글
| (연구일지 #11 앙상블은 정말 필요한가?)Dacon AI Agent 행동 예측 대회 (0) | 2026.07.21 |
|---|---|
| (연구일지 #10 이전 작업 부검하기)Dacon AI Agent 행동 예측 대회 (0) | 2026.07.11 |
| (연구일지 #8 고전 ML 개선)Dacon AI Agent 행동 예측 대회 (0) | 2026.07.11 |
| (연구일지 #7 새 아키텍처 축 탐색과 정체)Dacon AI Agent 행동 예측 대회 (0) | 2026.07.11 |
| (연구일지 #6 새로운 시도와 실패의 연속..)Dacon AI Agent 행동 예측 대회 (0) | 2026.07.09 |