출발점은 음성으로 말한 한국어를 3D 아바타가 수어로 표현하게 하자는 아이디어였습니다. 처음부터 번역 문제로 정해진 과제는 아니었습니다. AI허브 데이터와 한국어·수어 형태소의 구조를 분석한 결과, 한국어를 수어 좌표로 직접 번역하는 것보다 데이터셋에 정의된 한국어 수어 형태소로 번역한 뒤 수어 형태소와 좌표를 매핑하는 것이 적절하다고 판단했습니다.
3인 팀에서 저는 EDA와 번역 모델 파인튜닝을 맡았고, 후반에는 Blender로 Unity 애니메이션 오류의 원인을 진단했습니다.
모델 선정
목표 출력은 자유로운 한국어 문장이 아니라 짧은 수어 형태소 열이었습니다. 8GB 로컬 VRAM 제약과 약 13만 건의 학습 데이터를 함께 보고, 더 큰 생성 모델보다 번역에 맞는 encoder-decoder 구조의 koBART base를 먼저 쓰기로 했습니다. 이미 요약이나 번역 태스크로 파인튜닝된 모델보다는 수어의 출력 형식을 데이터에서 새로 배우게 하는 편이 낫다고 판단했습니다.
원본 데이터는 재난 상황 문장이 대부분이었습니다. 수어 형태소를 이용해 LLM으로 일상대화 문장을 만들어 학습 데이터를 약 13만 건으로 늘렸지만, 생성 문장이 서로 비슷하다는 한계가 있었습니다. 별도의 한국어 일상대화 데이터를 입력으로 쓰는 방식도 검토했으나 저장 공간 문제로 제외했습니다.
실험한 모델은 gogamza/kobart-base-v2와 hyunwoongko/kobart였습니다.
gogamza/kobart-base-v2는 파라미터를 바꿔도 같은 출력을 반복하는 현상이 이어져 제외했고,
기본 설정에서도 출력이 안정적이었던 hyunwoongko/kobart를 선택해 파인튜닝했습니다. 이 과정에서
긴 소수의 샘플에 맞춰 입출력 길이를 늘리면 validation loss가 오히려 나빠져 최대 길이는 128로
두었습니다.
10 epoch 실험에서는 validation loss가 약 5 epoch 이후 다시 올라갔습니다. 과적합으로 판단하고 이후 실험은 5 epoch로 고정하고 validation loss가 가장 낮은 checkpoint를 저장했습니다.

또 하나의 문제는 먼지1#처럼 한글과 숫자가 결합된 수어 형태소가 기존 토크나이저의 한 토큰과
일대일로 대응하지 않는다는 점이었습니다. 형태소용 vocabulary를 추가해 토크나이저를 다시
학습하기보다, 이미 알고 있는 한국어 조각의 의미를 유지하면서 데이터셋에 존재하는 형태소 토큰
시퀀스만 이어서 생성하도록 추론을 제한했습니다.
디버깅
번역된 형태소는 원본 JSON에서 해당 구간의 얼굴·자세·양손 좌표를 찾아 프레임 열로 바꿨습니다. 아래는 원본 수어 영상에 얼굴·자세·양손 keypoint가 annotation된 데이터입니다.

팀원이 이 좌표를 Unity 리깅 모델에 적용했지만 캐릭터가 정상적으로 움직이지 않았습니다. 아래는 리깅 모델과 좌표 데이터가 맞지 않아 동작이 깨진 당시 영상입니다.
번역 모델 작업을 마친 뒤 저도 원인 분석에 합류했습니다. 먼저 Blender로 변환된 수어 형태소의 프레임별 좌표를 point로 시각화해, 형태소에서 좌표를 가져오는 단계가 정상인지 확인했습니다.
다음으로 아바타 mesh를 제외하고 point가 리깅 bone에 올바르게 매핑되는지만 따로 재현했습니다.
두 단계를 분리해 대조한 결과, 좌표 데이터가 전제하는 Armature와 내려받은 리깅 모델의 bone 개수가 달라 매핑이 깨진다는 원인을 찾았습니다. 재현 과정과 근본 원인을 버그 리포트로 정리했고, Unity를 맡은 팀원이 이 진단을 바탕으로 좌표 구조와 맞는 다른 리깅 모델을 적용해 최종 캐릭터 애니메이션을 완성했습니다.
결과
모델 교체와 출력 제한을 적용한 뒤 생성된 예시는 다음과 같습니다.
"내일 오후 미세먼지 주의보가 발령되었으니 외부활동을 삼가해 주시기 바랍니다" →
먼지1# 주의보1 밖1 하지마1 내일1 오후1
아래는 원인 파악 후 팀원이 호환되는 다른 리깅 모델을 적용해 Unity에서 완성한 최종 아바타 애니메이션입니다.
팀은 텍스트에서 수어 형태소, 3D 좌표, 아바타 동작까지 이어지는 흐름을 구현했고, 저는 번역 모델과 Blender 기반 오류 진단을 진행했습니다.