Mixup 및 CutMix: 두 장의 사진과 라벨을 소프트 타겟(soft target)으로 혼합하여 존재하지 않는 이미지로 학습하기
요약
Mixup과 CutMix는 두 이미지를 결합할 때 라벨도 소프트 타겟으로 혼합하여 모델의 정규화를 돕는 데이터 증강 기법입니다. Mixup은 픽셀을 투명하게 겹치는 방식이며, CutMix는 이미지의 특정 패치를 잘라 붙여 선명한 질감을 유지합니다.
핵심 포인트
- Mixup은 픽셀과 라벨을 λ 비율로 볼록 결합하여 유령 같은 이미지를 생성함
- CutMix는 이미지 패치를 잘라 붙여 픽셀의 선명도를 유지하며 면적에 따라 라벨을 혼합함
- 라벨을 소프트 타겟으로 혼합해야 모델이 잘못된 정보를 학습하는 것을 방지할 수 있음
- 두 기법 모두 강력한 정규화 도구로서 모델의 일반화 성능을 향상시킴
일반적인 증강 (augmentation)은 하나의 이미지를 변형하고 그 라벨을 유지합니다. Mixup과 CutMix는 여기서 더 나아가 처음에는 다소 황당하게 들릴 수도 있는 방식을 취합니다. 이들은 두 개의 학습 이미지를 가져와서, 라벨 또한 소프트(soft)하게 — 예를 들어 부분적으로는 고양이, 부분적으로는 개인 상태로 — 결합하여 새로운 예시를 만듭니다. 왜냐하면 실제로 사진 속에 각 클래스가 그만큼 포함되어 있기 때문입니다. 픽셀과 함께 라벨을 혼합한다는 이 단 하나의 아이디어는 진정으로 강력한 정규화 도구 (regulariser)입니다. 저는 두 개의 손으로 그린 캔버스와 λ 슬라이더를 사용하여 이미지가 혼합되고 라벨이 그와 함께 혼합되는 과정을 볼 수 있는 라이브 데모를 제작했습니다. 실제로 어떤 일이 일어나는지 살펴보겠습니다.
두 개의 예시, 두 개의 원-핫(one-hot) 라벨
모든 작업은 한 쌍(pair)을 대상으로 작동합니다. 배치 (batch)에서 두 개의 샘플을 가져옵니다. 고양이로 라벨링된 이미지 A와 개로 라벨링된 이미지 B입니다. 라벨은 깨끗한 원-핫 벡터 (one-hot vectors)로 시작하며, 소프트함은 혼합을 통해 생성되는 것이지 결코 미리 가정된 것이 아닙니다. 실제 루프 (loop)에서 B는 단순히 A를 셔플한 것입니다: xb = xa[torch.randperm(n)].
const A = { img: canvasA, y: [1, 0] }; // 고양이 -> 원-핫 고양이
const B = { img: canvasB, y: [0, 1] }; // 개 -> 원-핫 개
Mixup — 픽셀 단위의 볼록 결합 (convex blend)
모든 출력 픽셀은 λ·A + (1−λ)·B입니다. globalAlpha를 사용하여 두 개의 그림이 그려진 캔버스 위에서 작업한다면, A를 완전히 칠한 다음 그 위에 불투명도 1−λ로 B를 칠하면, 합성기 (compositor)가 정확히 볼록 결합 (convex combination) — 즉 두 동물의 반투명한 유령 같은 모습 — 을 만들어냅니다.
function mixup(out, A, B, lam) {
const ctx = out.getContext("2d");
ctx.globalAlpha = 1; ctx.drawImage(A, 0, 0); // 완전한 고양이
...
동일한 방식으로 라벨을 혼합하기 — 핵심 비결
이 부분은 초보자들이 건너뛰는 부분입니다. 픽셀은 혼합하면서 하드 라벨 (hard label)을 유지하는 것은 "이 30%의 개가 포함된 이미지는 100% 고양이다"라는 거짓을 가르치는 것과 같습니다. 따라서 동일한 λ를 사용하여 라벨을 혼합하십시오. 타겟 (target)은 λ·y_A + (1−λ)·y_B가 되며, 이는 각 클래스가 얼마나 존재하는지를 정확하게 말해주는 정직한 소프트 라벨 (soft label)이 됩니다. 이제 100% 확신을 갖는 것은 말 그대로 틀린 답이 되므로, 모델은 제어되지 않는 급격한 스파이크 (spike)를 정당화할 수 없게 됩니다.
function mixLabels(yA, yB, lam) {
return yA.map((v, k) => lam * v + (1 - lam) * yB[k]);
}
...
CutMix — 패치를 잘라내고 면적에 따라 라벨을 혼합하기
Mixup의 반투명한 오버레이(translucent overlay)는 부자연스러워 보이고 국소적인 질감(local texture)을 흐릿하게 만듭니다. CutMix는 모든 픽셀을 실제 상태로 유지합니다. 즉, 이미지 A를 그린 다음 그 위에 B의 직사각형 패치(rectangular patch)를 도장 찍듯 얹는 방식입니다. 박스의 한 변의 길이는 무작위 위치에서 √(1−λ)·W가 되므로, 박스의 _면적(area)_은 이미지의 (1−λ)가 되며, 라벨은 해당 면적에 따라 혼합됩니다. 모든 픽셀은 한 이미지의 선명하고 실제적인 픽셀로 유지되며, 두 객체 모두 온전하게 살아남습니다.
function cutmix(out, A, B, lam) {
const W = out.width, H = out.height, ctx = out.getContext("2d");
ctx.drawImage(A, 0, 0);
...
실제 붙여넣은 면적으로 λ 재도출하기
제가 좋아하는 미묘한 디테일이 하나 있습니다. 만약 박스가 가장자리 밖으로 벗어나면 잘리게(clipped) 되므로, 실제 패치는 의도한 것보다 작아집니다. 따라서 실제 붙여넣은 면적으로 λ를 다시 계산하여, 라벨이 화면에 실제로 존재하는 픽셀과 항상 일치하도록 만듭니다. 즉, 박스가 잘리더라도 정직하게 대응하는 것입니다. 데모에서 "move patch"를 실행하면 박스가 구석으로 밀려나며, 개(dog)의 비율과 라벨이 그에 맞춰 줄어드는 것을 확인할 수 있습니다.
const area = (bw * bh) / (W * H); // 개(dog)가 차지하는 실제 비율
const lamAdj = 1 - area; // 여전히 고양이(cat)인 비율
const y = mixLabels(A.y, B.y, lamAdj);
두 방법 모두를 위한 하나의 소프트 라벨 손실 함수 (soft-label loss)
타겟이 두 개의 원-핫(one-hot) 벡터의 혼합이기 때문에, 교차 엔트로피(cross-entropy)는 λ로 가중치를 둔 각 클래스에 대한 일반적인 손실 함수로 인수분해됩니다. 별도의 특수한 손실 함수를 작성할 필요는 없습니다. 그저 소프트 타겟(soft target)을 입력하거나, 이와 동일하게 가중치가 적용된 두 개의 하드 타겟(hard targets)을 입력하면 됩니다.
function mixLoss(logits, yA, yB, lam) {
return lam * crossEntropy(logits, yA) + (1 - lam) * crossEntropy(logits, yB);
}
...
이것이 규제화(regularises)를 수행하는 이유, 그리고 실제 사례
중간 라벨 (in-between labels)과 함께 중간 지점 (in-between points)에 대해 학습하는 것은 네트워크가 클래스 사이에서 대략적으로 _선형적 (linearly)_으로 동작하도록 강제합니다. 이는 결정 경계 (decision boundaries)를 직선화하고 넓히며, 손상된 라벨의 암기 (memorisation)를 급격히 차단하고, 캘리브레이션 (calibration)을 개선합니다. 이는 라벨 스무딩 (label smoothing)과 동일한 과잉 확신 방지 처방이며, 이제는 두 번째 클래스의 실제 픽셀로부터 소스를 얻습니다. CutMix는 국소화 보너스 (localisation bonus)를 추가하는데, 이는 실제 객체의 부분들이 살아남기 때문에 모델이 파편으로부터 동물을 인식하는 법을 배우기 때문입니다. λ 자체는 배치(batch)마다 대칭적인 Beta(α, α) 분포에서 추출됩니다. 작은 α는 0과 1 근처에 질량을 집중시키고 (대부분 완만한 혼합), α=1은 균등하며, 큰 α는 0.5에 집중합니다 (공격적인 혼합). 그리고 α는 당신이 조절하는 유일한 노브 (knob)입니다.
실제 운영 환경에서는 이를 직접 구현하지 않습니다. timm은 배치별로 이를 적용하며, Mixup과 CutMix 사이를 무작위로 전환하고 라벨 스무딩 (label smoothing)을 통합하여 적용합니다.
from timm.data import Mixup
mixup_fn = Mixup(mixup_alpha=0.2, cutmix_alpha=1.0,
prob=1.0, switch_prob=0.5, label_smoothing=0.1, num_classes=1000)
...
주의사항을 유념하세요: 수렴 (convergence)이 약간 더 느려지므로 조금 더 오래 학습해야 하며, 세그멘테이션 (segmentation)과 같이 픽셀 단위로 정확해야 하는 작업에는 박스 인식형 변형 (box-aware variants)이 필요합니다. 하지만 분류 (classification) 작업의 경우, 확실한 Top-1 이득을 얻기 위한 추가 연산량이 거의 제로에 가까워 사실상 공짜나 다름없습니다.
슬라이더를 움직이며 라벨이 녹아내리는 것을 확인해 보세요:
https://dev48v.infy.uk/dl/day37-mixup-cutmix.html
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기