Implementation note · 2026-05-21

MobileCLIP2 Multi-task 학습 구현

Sprint 2 모델 학습을 위해 landmark-assistant-model-ver2에 추가한 실제 구현 기록이다. 기존 Sprint 1 학습 경로는 보존하고, image-label classification, image-text contrastive, hard-negative margin을 함께 쓰는 새 학습 entrypoint를 분리했다. 최초 구현은 MobileCLIP2-S4를 중심으로 시작했지만, 현재 실험과 앱 후보는 MobileCLIP2-S3/S4를 같은 구조로 비교한다.

문서 관리 기준

공유 문서는 landmark-assistant/docs에서 관리한다. landmark-assistant-model-ver2 repo의 Markdown은 코드 근처의 짧은 개발 메모 또는 중앙 문서 링크로만 둔다. 팀원이 진행 상황, 실행 기준, 설계 이유를 확인할 때는 이 문서를 기준으로 삼는다.

왜 바꿨나

Sprint 1 모델은 이미지 입력에서 랜드마크를 잘 찾는 것이 중심이었다. 하지만 Sprint 2에서는 자연어 검색, 비슷한 궁궐/문/전각 구분, hard negative 학습까지 같이 다뤄야 한다. 그래서 단순 class label만 쓰지 않고, 이미지별 한/영 caption과 confusing_with를 학습 신호로 사용하도록 구현했다.

구현된 구조

Dataset labels.json
  ├─ image label / training_role
  ├─ caption_set(text_ko, text_en)
  └─ confusing_with
        ↓
LandmarkMultitaskDataset
        ↓
MobileCLIP2-S3/S4 image encoder + text encoder
        ↓
Loss = classification + image-text contrastive + hard-negative margin
        ↓
metrics.json + prediction jsonl + low-margin csv

추가된 파일

파일 역할
src/landmark_candidate/multitask_dataset.py caption_setconfusing_with를 읽어 multi-task batch를 만든다.
src/landmark_candidate/multitask_model.py MobileCLIP2-S3/S4 image/text encoder, partial unfreeze, LoRA, projection head를 구성한다.
src/landmark_candidate/losses.py CE/CosFace/ArcFace, multi-positive contrastive, hard-negative margin loss를 제공한다.
src/landmark_candidate/train_multitask.py 새 학습 entrypoint. W&B, k-fold split, 평가 출력, low-margin 분석을 수행한다.
scripts/run_multitask_tmux.sh 서버에서 tmux로 multi-task 실험을 실행한다.
scripts/mine_hard_negative_candidates.py 학습 후 prediction log에서 confusion, low-margin, nearest negative 후보를 모아 다음 라벨링 검수 목록을 만든다.
configs/experiments/*.yaml partial unfreeze, LoRA, ArcFace 비교 실험 config를 분리한다.

Loss 구성

1

Classification

이미지 embedding이 정확한 landmark class로 분류되도록 학습한다. 첫 실험은 해석이 쉬운 cross_entropy를 사용한다.

2

Image-text Contrastive

같은 landmark_id의 이미지와 caption은 가깝게, 다른 class는 멀게 만든다. 자연어 검색이 image encoder fine-tuning 이후에도 깨지지 않도록 잡아주는 역할이다.

3

Hard-negative Margin

confusing_with에 적힌 비슷한 landmark가 정답보다 너무 가까워지면 penalty를 준다. 광화문/근정문/궁궐 전각처럼 헷갈리는 케이스를 추적하기 위한 장치다.

서버 실행 명령

가장 먼저 돌릴 기준 실험은 partial unfreeze + CE + hard negative 구성이다.

cd /workspace/landmark-assistant-model-ver2
source .venv/bin/activate
export DATA_ROOT=/workspace/landmark-assistant-model-ver2/Dataset
GPUS=1,2,3,4 NPROC=4 bash scripts/run_multitask_tmux.sh mobileclip2_s4_partial_unfreeze_ce_hardneg 0

서버에 이미 /workspace/landmark-assistant-model 이름으로 같은 repo를 clone해 둔 경우에는 해당 경로를 계속 사용해도 된다. 중요한 기준은 GitHub repo가 HyunKN/landmark-assistant-model-ver2인지 확인하는 것이다.

LoRA 비교 실험은 아래 명령으로 분리해서 실행한다.

GPUS=1,2,3,4 NPROC=4 bash scripts/run_multitask_tmux.sh mobileclip2_s4_lora_ce_hardneg 0

Hard-negative 후보 채굴

한 번 학습이 끝나면 모델이 실제로 헷갈린 class pair를 다음 라벨링 작업으로 되돌려야 한다. 이를 위해 run directory의 prediction log를 읽어 review 후보를 만든다. 이 스크립트는 labels.json을 자동 수정하지 않는다. 사람이 예시 이미지를 보고 confusing_with에 넣을지 결정한다.

python scripts/mine_hard_negative_candidates.py \
  --run-dir runs/<run_name> \
  --low-margin-threshold 0.05
입력 역할
predictions_val.jsonl, predictions_test.jsonl Top-1 오답과 Top-3 안의 가까운 negative landmark를 찾는다.
low_margin_val.csv, low_margin_test.csv Top-1과 Top-2 차이가 작아 fragile한 케이스를 찾는다.
출력 사용 방법
hard_negative_candidates.json 후보 pair, 근거 count, 예시 Top-3를 포함한다. 라벨링 검수자가 먼저 본다.
hard_negative_candidates.csv 스프레드시트에서 정렬·필터링하며 검수하기 위한 요약 표다.

평가 출력

각 run은 runs/<run_name>/ 아래에 다음 파일을 남긴다.

출력 확인할 내용
metrics.json val/test Top-1, Top-3, macro F1, hard-case accuracy, text retrieval 결과.
predictions_val.jsonl, predictions_test.jsonl 각 이미지의 Top-3, margin, confusing_with 결과.
predictions_text_queries.jsonl 텍스트 카탈로그 query가 어떤 landmark prototype으로 검색되는지.
low_margin_val.csv, low_margin_test.csv Top-1과 Top-2가 가까워 애매한 입력 후보.

검증 상태

로컬에서는 python -m compileall src와 loss smoke test를 통과했다. 전체 학습 실행은 서버의 torch/open_clip/mobileclip/scikit-learn 환경과 실제 Dataset이 필요하므로, 서버에서 위 tmux 명령으로 확인한다.