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

(연구일지 #5 SRC와 seed 앙상블)Dacon AI Agent 행동 예측 대회

by sungjinp0805 2026. 7. 9.

[SRC]와 seed 앙상블로 0.775까지

이전 글에서는 트랜스포머 모델을 실제 대회 서버에 제출하면서 겪은 문제들을 정리했다.

 

v12에서는 입력을 풀 트랜스크립트 형태로 바꾸면서 성능이 올랐고, v15f에서는 fp16 추론 문제를 해결하면서 large 모델을 다시 사용할 수 있었다.

그 결과 LB는 0.72 근처까지 올라왔다.

v11 LB:   0.6849
v12 LB:   0.6921
v15f LB:  0.7207
v18 LB:   0.7200
 

하지만 여기서 다시 정체가 시작됐다.

큰 모델을 넣고, 고전 ML 모델과 함께 앙상블해도 기대만큼 점수가 오르지 않았다.


로컬 holdout에서는 좋아 보이는 조합도 LB에서는 상승폭이 작거나, 오히려 기대보다 낮게 나왔다.

이 시점부터는 단순히 모델을 더 키우는 것보다, 데이터 안에 다른 분포를 가진 집단이 있는지를 확인해야겠다고 생각했다.


sess_au_* 집단 발견

데이터를 다시 보다가 sess_au_* 형태의 세션들이 눈에 들어왔다.

처음에는 단순한 세션 ID 패턴 정도로 생각했지만, 따로 분리해서 확인해보니 일반 세션과 라벨 분포가 꽤 달랐다.

 

전체 train에서 차지하는 비중은 크지 않았지만, 무시하기에는 애매했다.
특히 특정 action의 비율이 일반 세션과 다르게 나타났고, 기존 모델들이 이 집단에서 약한 모습을 보였다.

 

이건 중요한 신호였다.

기존 모델은 입력만 보고 이 세션이 au 계열인지, 일반 sim 계열인지 알 수 없었다.
분포가 다른 두 집단을 같은 데이터처럼 보고 학습하고 있었던 것이다.

 

같은 문장이라도 집단에 따라 다음 행동의 분포가 다르다면, 모델 입장에서는 혼란스러울 수밖에 없다.

그래서 다음 실험에서는 모델에게 “이 입력이 어느 집단에서 왔는지”를 알려주는 방향을 시도했다.


[SRC] 프리픽스 추가

가장 단순한 방법은 입력 텍스트 앞에 출처 정보를 붙이는 것이었다.

예를 들어 au 계열 세션이면 다음과 같이 시작하게 만들었다.

[SRC] au
[U] ...
[A] ...
[P] ...
 

일반 세션이면 이렇게 넣었다.

[SRC] sim
[U] ...
[A] ...
[P] ...
 

이렇게 하면 모델은 같은 문장이라도 au 세션에서 나온 문장인지, 일반 세션에서 나온 문장인지 구분할 수 있다.

처음부터 이게 큰 효과를 낼 것이라고 확신한 것은 아니었다.


하지만 이전 실험에서 session_meta와 상태 정보가 계속 중요하게 작동했다는 점을 생각하면, 세션의 출처 정보도 충분히 유효한 신호일 수 있다고 봤다.

특히 au 집단의 라벨 분포가 다르다면, 이를 숨기는 것보다 명시적으로 알려주는 쪽이 더 자연스러웠다.


v17: [SRC] 레시피 정리

[SRC]를 붙인 뒤에는 입력과 학습 설정도 다시 정리했다.

이때 사용한 기본 방향은 다음과 같았다.

[SRC] au|sim
+ 풀 트랜스크립트 입력
+ left truncation
+ MAX_LEN 512
+ 메타 피처 결합
+ sqrt 클래스 가중치
 

이전 글에서 정리했듯이, 긴 입력에서는 truncation 방향이 중요했다.
현재 프롬프트와 최근 history가 잘리지 않도록 오래된 history부터 자르는 방식을 유지했다.

 

또한 트랜스포머 입력만으로 끝내지 않고, 메타 피처도 계속 결합했다.
고전 ML과 하이브리드 실험에서 확인한 것처럼, 이번 문제는 문장 의미만으로 풀리는 문제가 아니었기 때문이다.

이 설정을 바탕으로 [SRC]가 들어간 base 모델을 학습하기 시작했다.


epoch을 더 늘려보기

기존에는 관성적으로 3 epoch 근처에서 학습을 멈추는 경우가 많았다.

GPU가 없는 상황에서 colab과 kaggle 무료 GPU 클라우드를 사용하고 있었다.

 

위 환경에서는 모델 하나를 학습하는데 시간이 오래 걸렸고,

여러 아이디어를 빠르게 확인해야 했기 때문에 3 epoch은 현실적인 선택이었다.

 

하지만 [SRC]를 넣은 모델에서 성능이 더 나올 여지가 있어 보였다.
그래서 epoch을 늘린 seed 모델들을 추가로 학습해보기로 했다.

이 시점에서 GPU 클라우드 서비스를 사용하기 시작했다.

학습을 위한 투자였다..

 

결과는 꽤 흥미로웠다.

seed44: 0.7440
seed45: 0.7467
seed46: 0.7449
seed47: 0.7481
seed48: 0.7482
 

3 epoch에서 끝내던 이전 모델보다, 6~8 epoch까지 더 학습한 base 모델들이 강하게 나왔다.

물론 epoch을 무작정 늘리는 것이 답이라고 보기는 어렵다.
하지만 이 구간에서는 적당한 epoch를 찾는 실험 역시 필요하다고 생각했다.


large보다 base seed 앙상블

처음에는 large 모델을 계속 가져가는 것이 자연스러운 선택처럼 보였다.

large는 모델 크기가 더 크고, 로컬 holdout에서도 괜찮은 성능을 보였다.

 

하지만 제출 결과를 보면 large를 추가한다고 항상 LB가 좋아지지는 않았다.

반면 [SRC]를 넣고 장기학습한 base seed들은 서버 점수에서 안정적으로 강했다.

 

이 시점에서 전략을 조금 바꿨다.

큰 모델 하나에 기대기보다는, 비슷한 구조의 base 모델을 여러 seed로 학습한 뒤 평균내는 방식이 더 안전할 수 있다고 봤다.

 

seed가 다르면 학습 과정에서 조금씩 다른 결정 경계를 만들 수 있다.
각 모델이 완전히 다른 문제를 푸는 것은 아니지만, 미세하게 다른 샘플을 맞히고 틀릴 가능성이 있다.

 

따라서 여러 seed의 예측 확률을 평균내면 단일 모델보다 예측이 안정될 수 있다.


블렌드 방식 다시 보기

이전까지는 LogisticRegression 같은 결합기를 사용해 모델들의 예측 확률을 다시 학습시키는 방식도 시도했다.

하지만 이 구간에서는 의외로 강한 튜닝이 항상 좋은 결과로 이어지지 않았다.

 

holdout에 맞춰 결합기를 세게 학습하면 로컬 점수는 좋아질 수 있다.
하지만 서버 LB에서는 그 이득이 그대로 유지되지 않는 경우가 있었다.

그래서 더 단순한 방식을 사용했다.

0.6 × 평균(seed 모델들)
+
0.4 × 고전 ML 블렌드
 

즉, [SRC] base seed들의 예측 확률을 평균내고, 여기에 고전 ML 블렌드를 일정 비율로 섞었다.

고전 ML을 완전히 버리지 않은 이유는 이전 실험에서 확인한 보완성 때문이다.


트랜스포머 seed들이 전체적으로 강해졌지만, 고전 ML만 맞히는 샘플도 여전히 존재했다.

따라서 고전 ML은 보조축으로 유지했다.


v21: 순수 블렌드 시작

먼저 seed44와 seed45를 사용해 단순 블렌드를 만들었다.

v21 = 0.6 × 평균(seed44, seed45) + 0.4 × 고전 ML
 

결과는 다음과 같았다.

v21 LB: 0.7701
 

이전 구간에서 0.72 근처에 머물던 것과 비교하면 큰 상승이었다.

여기서 중요했던 점은 구조가 복잡하지 않았다는 것이다.


새로운 결합기를 강하게 학습한 것이 아니라, seed 평균과 고전 ML을 고정 비율로 섞은 단순한 방식이었다.

이 결과를 보고, 현재 서버 분포에서는 과도하게 튜닝된 결합기보다 단순하고 안정적인 블렌드가 더 나을 수 있다고 판단했다.


v21b, v21c: 튜닝된 결합기와 고정 블렌드 비교

그 다음에는 두 가지 결합 방식을 비교했다.

하나는 여러 모델의 예측 확률을 다시 입력으로 넣고, LogisticRegression 메타러너가 최종 예측을 학습하도록 하는 방식이었다. 이 방식은 holdout 기준으로 각 모델을 얼마나 믿을지 학습하기 때문에 로컬 검증 점수에는 더 잘 맞을 수 있다.

다른 하나는 별도의 결합기를 학습하지 않고, seed 모델들의 평균 확률과 고전 ML 블렌드를 정해진 비율로 섞는 단순 고정 블렌드였다.

v21b CV: 0.7661
v21b LB: 0.7714

v21c CV: 0.7551
v21c LB: 0.7728

 

결과는 흥미로웠다. holdout 기준으로는 학습된 결합기가 더 좋아 보였지만, 실제 LB에서는 단순 고정 블렌드가 더 높게 나왔다. 

이 시점부터는 holdout에 맞춰 결합기를 강하게 튜닝하는 것이 서버 점수로 그대로 이어지지 않을 수 있다고 판단했다.


v22: 3 seed 앙상블

다음으로 seed를 3개로 늘렸다.

v22 = 0.6 × 평균(seed45, seed46, seed47) + 0.4 × 고전 ML
 

결과는 다음과 같았다.

v22 LB: 0.7746
 

2 seed보다 다시 상승했다.

이 결과를 보고 seed 앙상블이 아직 포화되지 않았다고 판단했다.
단일 모델의 성능을 더 크게 올리는 것보다, 강한 seed들을 안정적으로 평균내는 쪽이 더 현실적인 개선 방향으로 보였다.


v23a: 4 seed 앙상블

마지막으로 seed48을 추가해 4 seed 앙상블을 만들었다.

v23a = 0.6 × 평균(seed45, seed46, seed47, seed48) + 0.4 × 고전 ML
 

결과는 다음과 같았다.

v23a holdout: 0.7572
v23a LB:      0.7758666688
 

이 시점 기준으로 가장 좋은 제출 결과였다.

흥미로운 점은 v23a의 구조가 특별히 복잡하지 않다는 것이다.


핵심은 다음 세 가지였다.

1. [SRC]로 세션 출처를 모델에 알려준다
2. 장기학습한 base seed들을 여러 개 확보한다
3. seed 평균과 고전 ML을 단순 비율로 섞는다
 

복잡한 후처리나 강한 결합기보다, 이 단순한 조합이 더 안정적으로 작동했다.


large 제거

v23a 이후 large 모델을 다시 포함한 조합도 확인했다.

하지만 large를 넣은 조합이 v23a를 넘지는 못했다.

v23b LB: 0.7732
 

large 모델은 로컬에서는 좋아 보였지만, 이 시점의 서버 점수에서는 [SRC] base seed 앙상블보다 덜 유리했다.

그래서 large를 무조건 포함해야 한다는 생각을 버렸다.

 

모델이 크다고 항상 좋은 것은 아니었다.
서버 테스트셋의 분포와 현재 앙상블 구성에 맞는 모델이 더 중요했다.


이 구간에서의 관찰

이 구간에서 확인한 점은 크게 세 가지다.

 

첫째, 데이터 안의 세션 집단 차이를 무시하면 손해가 날 수 있다.

au 집단은 전체에서 큰 비중은 아니었지만, 라벨 분포가 달랐고 기존 모델의 약점으로 남아 있었다.
[SRC] 프리픽스로 이 정보를 알려주자, 모델이 세션 출처에 따른 차이를 반영할 수 있었다.

 

둘째, base 모델은 3 epoch에서 끝낼 문제가 아니었다.

초기에는 시간 때문에 짧게 학습했지만, [SRC]를 넣은 base 모델은 더 오래 학습했을 때 성능이 좋아졌다.
6~8 epoch 구간에서 강한 seed들이 나왔고, 이 seed들이 이후 앙상블의 핵심이 됐다.

 

셋째, 복잡한 결합기가 항상 좋은 것은 아니었다.

holdout에 맞춘 결합기는 로컬 점수를 올릴 수 있지만, 서버 LB에서는 단순 고정 블렌드가 더 좋은 경우가 있었다.
그래서 이 구간에서는 “더 많이 튜닝하기”보다 “강한 seed를 안정적으로 평균내기”가 더 유효했다.


정리

v18까지는 0.72 근처에서 정체가 있었다.

v18 LB: 0.7200
 

이후 au 집단을 확인하고, [SRC] 프리픽스를 추가했으며, 장기학습한 base seed들을 앙상블하면서 점수가 다시 올랐다.

v21 LB:   0.7701
v22 LB:   0.7746
v23a LB:  0.7758666688
 

이번 구간의 핵심은 데이터 안의 다른 집단을 찾고, 그 정보를 모델 입력에 반영한 것과,
큰 모델을 사용하는 것 보다 장기학습한 base seed들을 단순하게 앙상블한 것이 성능 상승으로 이어졌다는 것이다.

 

아직 결론을 내리기엔 이르다. 
모든 가능성을 열어두고 남은 기간을 잘 활용해봐야겠다.