모델 운영

MLOPS / 41번째 글

데이터 증강 — 무엇을 흉내 내는가

증강이 듣는 조건과 안 듣는 조건, 레이블이 깨지는 자리, 강도를 검증셋으로 고르는 절차, 온라인·오프라인 증강의 맞바꿈, 그리고 텍스트 증강이 유독 안 듣는 이유를 정리합니다.

PALDYN Team31 MIN READ

지난 글에서 학습에 쓸 정답을 만드는 과정을 살펴봤다. 정답을 다 만들고 나면 다음 질문은 늘 같다 — 이 정도 양으로 되는가. 데이터 증강(data augmentation)은 그 질문에 데이터를 더 모으지 않고 답하려는 방법이고, 가진 데이터를 변형해 학습할 거리를 늘린다.

그런데 증강은 데이터를 늘리는 일이 아니다. 늘어나는 것은 파일 개수이고, 실제로 하는 일은 모델에게 「이 정도 차이는 같은 것으로 봐라」라고 가르치는 것이다. 사진을 좌우로 뒤집어 넣는 것은 고양이가 어느 쪽을 보고 있든 고양이라고 말해 주는 일이지, 고양이 사진이 한 장 더 생기는 일이 아니다. 이 관점을 잡아 두면 증강 설계에서 갈리는 대부분의 판단이 같은 질문으로 정리된다 — 서비스에서 실제로 마주칠 변형인가.

이 글은 그 질문을 여섯 자리에서 따라간다. 증강이 듣는 구간과 안 듣는 구간, 변형이 정답을 무효로 만드는 자리, 강도를 고르는 절차, 만들어 두는 방식과 즉석에서 만드는 방식의 맞바꿈, 표 데이터에서 소수 클래스를 합성할 때 생기는 가짜 경계, 그리고 텍스트에서 유독 잘 안 듣는 이유다.

증강이 듣는 조건

데이터 증강 기법 분류

흉내 내는 변형이 실제로 일어나는가

좋은 증강은 실제 데이터에 있는 변형을 흉내 낸다. 손으로 찍은 사진은 기울어지고 밝기가 제각각이므로 회전과 밝기 조정은 값을 한다. 반대로 위성 사진은 늘 같은 각도에서 찍히므로 회전을 크게 주면 실제로는 없는 상황을 학습시키는 셈이고, 그만큼 모델의 용량을 엉뚱한 데 쓴다.

판단 기준은 취향이 아니라 배포 환경의 데이터다. 실제 입력 표본을 몇백 장 늘어놓고 무엇이 얼마나 달라지는지 세어 보면 어떤 변형을 얼마나 줄지가 대체로 정해진다. 이 작업은 증강 코드를 쓰기 전에 하는 일이고, 건너뛰면 다른 프로젝트의 증강 설정을 그대로 베끼게 된다.

베낀 설정이 해로운 이유는 값이 틀려서가 아니라 왜 그 값인지가 같이 안 오기 때문이다. 회전 15도가 어느 데이터셋에서는 촬영 기울기의 실측 범위였고 다른 데이터셋에서는 아무 근거가 없다. 근거 없이 들어온 값은 나중에 성능이 안 나올 때 의심 목록에도 안 오른다.

이미 충분할 때의 역효과

증강의 효과는 데이터가 적을수록 크고, 어느 지점부터는 오히려 방해가 된다. 데이터가 충분하면 모델은 이미 그 변형들을 실제 표본에서 보고 있고, 거기에 인위적인 변형을 더하면 학습 신호에 잡음만 늘어난다. 증강을 켜고 끄고를 같은 조건에서 한 번 재 보면 이 구간에 들어왔는지 바로 안다.

효과가 어느 쪽인지는 학습 곡선의 모양으로도 짐작할 수 있다. 학습 손실은 계속 내려가는데 검증 손실이 일찍 꺾여 올라가면 암기하고 있다는 뜻이라 증강이 값을 할 자리이고, 둘이 나란히 높은 채로 멈춰 있으면 모델이 아직 못 배운 것이라 증강을 늘리면 더 나빠진다.

데이터가 늘어나는 프로젝트에서는 이 판정을 한 번만 하고 끝내면 안 된다. 처음에 천 장으로 시작해 증강을 세게 걸어 두었다가 데이터가 십만 장이 된 뒤에도 그 설정이 그대로 남아 있는 경우가 흔하다. 데이터 양이 한 자릿수 배로 늘 때마다 증강 강도를 다시 재 보는 편이 낫다.

분포를 벗어나는 변형

증강이 위험해지는 전형적인 자리는 변형을 세게 줘서 원래 분포 밖으로 나가는 때다. 이렇게 만든 표본은 학습에는 들어가지만 평가에는 한 번도 안 나오므로, 모델은 실제로 쓰이지 않을 능력에 용량을 쓰고 정작 필요한 구간의 정확도를 내준다. 강도를 올렸는데 검증 성능이 떨어지는 현상은 대개 과적합이 아니라 이 자리다.

이 경계를 눈으로 확인하는 방법이 하나 있다. 증강한 표본을 몇십 장 뽑아 사람에게 보여 주고 원래 레이블을 맞혀 보라고 하는 것이다. 사람이 못 맞히는 표본이 섞여 있으면 모델도 그 표본에서 배울 것이 없고, 오히려 레이블이 잘못 붙은 데이터와 같은 효과를 낸다. 증강 설정을 바꿀 때마다 이 표본 보기를 한 번 하는 것이 강도 튜닝보다 먼저다.

레이블 보존

증강에서 조용히 나는 사고의 대부분은 변형이 레이블을 무효로 만드는 것이다. 입력만 바뀌고 정답은 그대로 붙어 있으니 코드는 아무 오류도 내지 않고, 모델만 틀린 것을 배운다.

뒤집으면 깨지는 것

좌우 반전은 가장 흔한 증강이지만 깨지는 자리가 많다. 글자를 읽는 과제에서 뒤집힌 글자는 더 이상 그 글자가 아니고, 왼손·오른손을 가리는 과제나 도로 표지판의 방향 지시에서도 마찬가지다. 의료 영상에서는 장기의 좌우 위치 자체가 진단 근거인 경우가 있어 반전 한 줄이 데이터를 통째로 오염시킨다.

회전도 각도에 따라 성격이 달라진다. 몇 도짜리 회전은 촬영 각도의 흔들림을 흉내 내지만 90도 회전은 다른 방향에서 찍은 것이라, 실제로 그렇게 찍힐 일이 없는 데이터에서는 분포 밖으로 나가는 변형이 된다. 숫자 6과 9를 가르는 과제에서는 180도 회전이 레이블을 정확히 뒤집는다.

색을 바꾸면 깨지는 것

색 조정도 같은 함정이 있다. 일반 사진에서는 조명 차이를 흉내 내는 자연스러운 증강이지만, 색이 곧 정답인 과제에서는 못 쓴다. 과일의 익음 정도, 시약의 변색, 경고등의 색 구분이 그렇다. 흑백으로 찍히는 의료 영상에서 색 반전을 주는 것은 흉내 내는 대상 자체가 실제로 존재하지 않는 경우라, 레이블은 살아 있어도 학습에 보탬이 없다.

자르기도 비슷하게 갈린다. 판단 근거가 이미지 전체에 퍼져 있는 과제에서는 무작위 자르기가 안전하지만, 작은 병변이나 결함 하나가 정답인 과제에서는 그 부분이 잘려 나간 표본이 「없음」이 아니라 「있음」 레이블을 단 채로 들어간다. 정답이 차지하는 면적이 작을수록 자르기의 위험은 커진다.

낱말을 바꾸면 깨지는 것

텍스트에서는 더 자주 깨진다. 동의어 치환은 사전이 같다고 보는 낱말이 문맥에서 같지 않을 때 뜻을 바꾸고, 감성 분류에서 「괜찮다」를 「나쁘지 않다」로 바꾸면 정도가 달라진다. 무작위 삭제는 부정어를 지워 정답을 정반대로 만들 수 있다. 그래서 텍스트 증강은 만든 표본을 사람이 표본 검수하는 단계를 붙이는 것이 보통이고, 이 단계를 뺄 수 없다는 점 자체가 텍스트 증강의 비용이다.

검수는 전수로 할 필요가 없다. 기법마다 몇십 건씩 뽑아 레이블이 유지됐는지 세고, 깨진 비율이 몇 퍼센트를 넘으면 그 기법의 강도를 내리거나 빼면 된다. 이 비율을 한 번 재 두면 「동의어 치환은 5%가 깨진다」 같은 수가 남아 다음 프로젝트에서 그대로 쓰인다.

import random

def random_deletion(sentence: str, p: float = 0.1) -> str:
    """각 낱말을 확률 p로 삭제 — 부정어가 지워지는지 반드시 확인한다"""
    words = sentence.split()
    if len(words) == 1:
        return sentence
    remaining = [w for w in words if random.random() > p]
    return " ".join(remaining) if remaining else random.choice(words)

레이블을 섞는 증강

앞의 것들과 성격이 다른 갈래가 하나 있다. 두 표본을 섞고 레이블도 그 비율로 섞는 방식이다. 두 이미지를 픽셀 단위로 겹치는 mixup, 한쪽의 사각 영역을 잘라 다른 쪽에 붙이는 CutMix가 그렇다. 여기서는 레이블 보존이 목표가 아니라 레이블을 비율로 나눠 갖는 것이 설계 자체다.

이 갈래가 하는 일은 모델이 판단을 극단으로 몰지 않게 만드는 것이다. 정답에 1.0을 주는 대신 0.7과 0.3을 주면 모델이 확신을 덜 하게 되고, 그 결과 예측 확률이 실제 정확도에 더 가까워진다. 대신 잘라 붙인 영역에 정작 판단 근거가 하나도 안 들어가는 표본이 생길 수 있어, 면적 비율로 계산한 레이블이 실제 내용과 어긋나는 일이 있다. 정답이 화면의 작은 부분에 몰려 있는 데이터에서 이 방식이 잘 안 듣는 이유다.

음성 쪽의 SpecAugment는 또 다른 갈래로, 스펙트로그램의 주파수 대역과 시간 구간을 통째로 가려 모델이 한 단서에만 기대지 못하게 한다. 가리는 증강은 이미지의 무작위 지우기와 같은 발상이고, 셋 다 「일부가 없어도 맞혀라」를 가르친다는 점에서 한 묶음이다.

강도 고르기

증강을 넣을지 말지보다 어려운 것이 얼마나 줄지다. 기법 목록은 널려 있지만 강도는 데이터마다 다르고, 기본값은 대개 다른 데이터셋에서 나온 값이다.

축 하나씩 재기

한꺼번에 여러 기법을 켜 놓고 조절하면 무엇이 무엇을 망쳤는지 알 수 없다. 기법을 하나씩 축으로 놓고 강도를 몇 단계로 나눠 검증 성능을 재면 대개 완만한 언덕 모양이 나오고, 꼭대기 근처의 값을 고르면 된다. 축이 여럿이면 하나씩 정해 고정하고 다음 축으로 간다.

축을 재는 비용이 걱정이라면 데이터를 줄여서 재도 된다. 전체의 10%로 짧게 돌려 얻은 언덕의 꼭대기는 전체 데이터에서도 대체로 비슷한 자리에 있고, 필요한 것은 정확한 최적값이 아니라 강도의 자릿수다. 여기서 시간을 아껴야 축을 서넛 볼 수 있다.

이 과정에서 같이 보이는 것이 있다. 언덕이 아예 안 생기고 강도가 0일 때가 가장 좋은 기법은 이 데이터에 안 맞는 변형이라는 뜻이라, 조정할 것이 아니라 빼야 한다.

반대로 강도를 끝까지 올려도 계속 좋아지는 기법이 나오기도 한다. 이때는 그 기법이 만능이라서가 아니라 아직 꼭대기에 안 닿은 것이므로, 범위를 넓혀 한 번 더 잰다. 그러다 성능이 꺾이는 자리가 나오면 그 앞이 답이고, 끝까지 안 꺾이면 데이터가 그만큼 다양성이 부족했다는 뜻이다.

검증셋과 테스트셋에는 안 건다

증강은 학습셋에만 적용한다. 검증셋에 증강을 걸면 평가 대상이 실제 입력이 아니라 변형된 입력이 되어 지표 자체가 다른 것을 재고, 그 지표를 보고 고른 설정은 실제 서비스에서 재현되지 않는다.

예외처럼 보이는 것이 하나 있다. 추론 시점에 여러 변형을 만들어 예측을 평균하는 방식은 평가에도 변형이 들어가지만, 그것은 증강이 아니라 추론 방법이다. 그 방식을 쓰기로 했다면 배포에서도 같은 방식으로 추론해야 하고, 평가에만 켜 두면 실제보다 좋은 수가 나온다.

같은 이유로 증강 코드가 데이터 적재 계층 어디에 붙어 있는지도 한 번 확인해 둔다. 학습·검증이 같은 적재 코드를 공유하면서 분기 하나로 증강을 켜고 끄는 구조는 그 분기가 잘못 넘어가는 순간 조용히 검증셋을 오염시킨다. 검증용 적재 경로를 아예 따로 두는 편이 안전하다.

학습이 진행되면서 줄이기

강도를 학습 내내 고정할 필요는 없다. 초반에 세게 주어 넓게 보게 하고 후반에 줄여 실제 분포에 맞추는 방식이 흔히 쓰이고, 특히 미세 조정 단계에서는 증강을 거의 걷어내는 편이 낫다. 마지막에 모델이 맞춰야 하는 것은 변형된 데이터가 아니라 실제 데이터의 분포다.

이 방식을 쓸 때는 강도 일정도 기록해 둔다. 나중에 같은 결과를 다시 만들려면 최종 강도만으로는 부족하고, 어느 시점에 얼마였는지가 함께 있어야 한다. 학습률 일정과 같은 급으로 다루면 된다.

온라인과 오프라인

Albumentations 이미지 증강 파이프라인

언제 만드는가

증강한 표본을 미리 만들어 저장해 두는 방식을 오프라인 증강, 학습 중에 배치를 만들 때마다 즉석에서 변형하는 방식을 온라인 증강이라 한다. 지금 대부분의 프레임워크는 온라인이 기본이다.

맞바꿈

오프라인은 저장 공간을 먹고 변형의 가짓수가 만들어 둔 만큼으로 고정된다. 열 배로 늘리면 저장도 열 배이고, 모델은 그 열 배를 여러 번 다시 본다. 온라인은 매 에폭마다 다른 변형이 나오므로 사실상 무한한 가짓수를 얻지만, 변형 연산이 학습 루프 안에 들어가 CPU가 GPU를 못 따라가면 GPU가 놀게 된다. 학습이 느린데 GPU 사용률이 낮다면 데이터 적재와 증강 쪽을 먼저 본다.

변형 연산이 무거우면 몇 가지 길이 있다. 적재 작업자 수를 늘리거나, 연산 자체를 GPU로 옮기거나, 무거운 변환을 오프라인으로 빼는 것이다. 어느 쪽이든 먼저 할 일은 한 배치를 만드는 데 드는 시간을 단계별로 재는 것이다 — 대개 범인은 증강이 아니라 파일을 읽고 디코딩하는 단계다.

실무에서는 섞어 쓴다. 무겁고 결정적인 변환(크기 조정, 형식 변환, 얼굴 정렬 같은 전처리)은 미리 해 두고, 가볍고 무작위인 변환만 학습 중에 건다. 이 구분은 「무작위인가」로 가르면 대체로 맞는다 — 매번 달라져야 하는 것만 온라인에 남긴다.

미리 해 두는 전처리에는 한 가지 조건이 붙는다. 그 결과물이 추론 시점에도 똑같이 만들어질 수 있어야 한다. 학습 데이터만 미리 정렬해 두고 서비스에서는 그 단계를 안 거치면, 모델이 보는 입력의 모양이 학습과 배포에서 달라진다. 오프라인으로 뺀 변환은 추론 파이프라인에도 같은 코드로 들어가야 한다.

재현성

온라인 증강은 실행할 때마다 다른 데이터를 만들므로 실험 재현이 어려워진다. 난수 씨앗을 고정하는 것으로 시작하되, 데이터 적재 작업자가 여럿이면 작업자마다 씨앗을 따로 정해야 같은 변형이 여러 작업자에서 겹치지 않는다. 증강 설정 자체도 코드와 함께 버전을 남긴다 — 학습 결과를 설명할 때 「어떤 증강을 걸었는가」는 하이퍼파라미터와 같은 급의 정보다.

불균형과 SMOTE

표 형태 데이터에서 클래스가 한쪽으로 쏠려 있을 때 자주 쓰는 것이 소수 클래스를 합성해 늘리는 방법이다. SMOTE는 소수 클래스의 한 표본과 그 이웃 사이를 선형 보간해 새 점을 만든다.

from imblearn.over_sampling import SMOTE

smote = SMOTE(sampling_strategy=0.5, k_neighbors=5, random_state=42)
X_res, y_res = smote.fit_resample(X_train, y_train)

보간이 만드는 가짜 경계

보간은 두 점 사이가 같은 클래스라고 가정한다. 이 가정이 깨지는 자리가 두 곳이다. 소수 클래스가 여러 덩어리로 흩어져 있으면 서로 다른 덩어리 사이에 점이 생겨 실제로는 아무것도 없는 공간이 소수 클래스로 채워지고, 소수 표본 중에 잡음이 섞여 있으면 그 잡음 주변으로 합성 점이 번진다. 만든 뒤에 차원을 줄여 그려 보면 이 두 증상은 눈으로 보인다.

그래서 SMOTE에는 변형들이 있다. 경계 근처의 표본만 골라 합성하거나, 합성한 뒤 잘못 놓인 점을 걷어내는 식이다. 어느 쪽이든 전제는 같다 — 소수 클래스가 공간에서 하나의 덩어리를 이룬다는 가정이 성립해야 보간이 뜻을 갖는다. 범주형 변수가 많은 표 데이터에서는 이 가정 자체가 잘 안 맞아, 두 범주 사이를 보간한 값이 아예 존재할 수 없는 조합이 되기도 한다.

분할보다 먼저 하면 새는 것

가장 흔한 사고는 데이터를 나누기 전에 합성하는 것이다. 그러면 검증셋의 표본으로 만들어진 합성 점이 학습셋에 들어가 성능이 실제보다 높게 나온다. 합성은 반드시 학습셋을 나눈 다음, 학습셋 안에서만 한다. 교차 검증이라면 접기마다 따로 해야 한다.

이 실수가 특히 위험한 것은 증상이 「성능이 아주 좋음」이기 때문이다. 실험에서 소수 클래스 재현율이 갑자기 크게 뛰면 기뻐하기 전에 합성이 분할보다 앞에 있는지부터 본다. 스케일링이나 결측 대치도 같은 함정이 있어, 전처리 순서를 한 번 그려 두면 세 가지를 한꺼번에 막을 수 있다.

합성이 답이 아닐 때

불균형을 다루는 방법이 합성만 있는 것은 아니다. 손실 함수에서 클래스마다 가중치를 주거나, 판정 임계값을 옮기거나, 애초에 평가 지표를 정확도가 아닌 것으로 바꾸는 편이 나은 경우가 많다. 특히 불균형이 실제 세계의 비율을 반영하는 것이라면 — 이상 거래가 드문 것은 사실이다 — 데이터를 균형 맞추는 순간 모델이 배우는 사전 확률이 현실과 달라진다.

판단 순서는 이렇게 두면 된다. 먼저 평가 지표를 불균형에 맞는 것으로 바꾸고, 그래도 모자라면 손실 가중치나 임계값을 움직이고, 그것으로도 안 되면 합성을 본다. 합성을 가장 나중에 두는 이유는 데이터 자체를 바꾸는 유일한 방법이라 되돌리기가 가장 번거롭기 때문이다.

텍스트 증강

같은 증강이라도 이미지에서 잘 듣는 것이 텍스트에서는 잘 안 듣는다. 이유는 하나로 정리된다 — 이미지의 픽셀은 조금 흔들려도 뜻이 안 변하고, 문장의 낱말은 하나만 바뀌어도 뜻이 변한다.

의미가 깨지는 구조

이미지에서 밝기를 5% 올리는 것은 사람이 보기에 같은 사진이다. 문장에서 낱말 하나를 지우는 것은 사람이 보기에 다른 문장이거나, 아예 문장이 아니다. 무작위 삽입·삭제·교환 같은 간단한 기법이 분류 과제에서 종종 효과를 내는 것은 모델이 어차피 낱말 몇 개의 존재로 판단하고 있기 때문이고, 그래서 문장의 구조가 중요한 과제로 갈수록 효과가 사라진다.

이 차이는 재어 보면 바로 드러난다. 같은 증강을 걸고 분류 과제와 개체명 인식 과제에서 각각 성능을 보면, 앞쪽은 조금 오르고 뒤쪽은 대개 떨어진다. 개체명 인식은 낱말의 자리가 곧 정답이라 낱말을 지우거나 옮기는 순간 정답 위치가 어긋나기 때문이다. 이런 과제에서는 증강을 걸더라도 정답 구간을 건드리지 않는 변형만 남겨야 한다.

역번역

텍스트 증강 중에서 품질이 가장 나은 축은 역번역이다. 다른 언어로 옮겼다가 되돌리면 표현이 바뀌면서 뜻은 대체로 유지된다.

def back_translate(text: str, pivot: str = "en") -> str:
    mid = translate(text, source="ko", target=pivot)
    return translate(mid, source=pivot, target="ko")

대가는 셋이다. 번역 호출이 비용과 시간을 먹고, 전문 용어나 고유명사가 어긋나는 일이 있고, 만들어진 문장이 원문과 거의 같아 실질적인 변형이 안 되는 경우도 많다. 그래서 만든 표본을 원문과 비교해 너무 같은 것은 버리는 거르개를 붙이는 것이 보통이다.

경유 언어를 무엇으로 두느냐도 결과를 바꾼다. 문법 구조가 한국어와 먼 언어를 거칠수록 표현이 크게 달라지지만 그만큼 뜻이 어긋날 확률도 올라간다. 여러 언어를 돌아가며 쓰면 만들어지는 표본의 다양성이 늘어나는데, 그 대신 품질이 언어마다 달라 검수 비율을 언어별로 따로 봐야 한다.

LLM으로 만드는 것은 다른 일이다

요즘은 증강 대신 모델에게 새 문장을 만들게 하는 쪽이 흔하다. 이것은 변형이 아니라 생성이라 성격이 다르다 — 원본의 레이블을 물려받는 것이 아니라 새 레이블을 같이 만들어야 하고, 만든 데이터의 분포가 그 모델의 버릇을 따라간다. 증강과 합성 데이터는 붙어 있는 주제이지만 검증하는 방법이 다르므로, 여기서는 선을 긋고 합성 데이터에서 이어 간다. 선을 긋는 실용적인 기준은 원본 표본이 하나 대응되는가다 — 원본 하나에서 나온 변형이면 증강이고, 원본 없이 새로 만들어진 것이면 합성이다.

증강은 새 모델 구조를 찾기 전에 먼저 손볼 자리다. 다만 늘린 개수가 아니라 무엇을 같은 것으로 보게 만들었는가로 그 값을 따져야 하고, 그 판정은 언제나 손대지 않은 검증셋에서 나온다.


읽어주셔서 감사합니다. 😊

LATEST

모델 운영의 최신 글

모델 운영2026.09.04

KV 캐시 양자화 — 가중치보다 이쪽이 먼저 넘친다

긴 문맥에서 GPU 메모리를 실제로 잡아먹는 것은 가중치가 아니라 KV 캐시입니다. 캐시 크기를 계산하는 법, K와 V를 다르게 다뤄야 하는 이유, 어디까지 줄여도 되는지를 정리합니다.

16 MIN
모델 운영2026.09.04

양자화 보정 — 데이터 128개가 모델 품질을 정한다

양자화에서 스케일을 정하는 절차가 보정입니다. 무엇을 재는지, 데이터를 어디서 몇 개 뽑아야 하는지, 자르는 지점을 어떻게 고르는지, 그리고 잘못된 보정이 어떤 모양으로 드러나는지를 정리합니다.

21 MIN
모델 운영2026.09.03

과제 특화 증류 — 큰 모델의 답을 작은 모델에 옮긴다

범용 성능이 아니라 우리 과제 하나만 잘하는 작은 모델을 만드는 방법입니다. 교사에게 무엇을 받아야 하는지, 데이터를 어떻게 모으고 거르는지, 손익 분기가 어디인지를 정리합니다.

15 MIN