Published on

Stable Diffusion LoRA 얼굴 깨짐 - 버킷·해상도 튜닝

Authors

서로 다른 해상도·크롭 규칙으로 섞인 데이터셋을 그대로 LoRA에 넣으면, 훈련은 정상처럼 보이는데 결과물에서 얼굴이 유독 깨지는 경우가 많습니다. 특히 눈동자, 치아, 입술 경계, 얼굴 윤곽이 흐려지거나 비대칭으로 무너지는 현상은 디테일이 중요한 영역이 학습 중 자주 리사이즈/크롭되어 정보가 손실될 때 빈번하게 나타납니다.

이 글은 “LoRA가 약해서” 같은 추상적 설명 대신, 버킷(bucket)과 레졸루션(해상도) 튜닝만으로 얼굴 품질을 끌어올리는 방법을 중심으로 정리합니다. (kohya-ss 계열 스크립트, SD 1.5/SDXL 모두에 적용 가능한 원리 위주)

얼굴 깨짐의 전형적인 패턴과 원인 맵핑

얼굴 깨짐을 한 묶음으로 보지 말고, 어떤 깨짐인지에 따라 원인을 좁히는 게 빠릅니다.

1) 눈·치아가 “뭉개지고 번짐”

  • 원인 후보
    • 훈련 해상도 대비 얼굴이 너무 작게 들어간 샷이 많음(전신/반신 위주)
    • 버킷이 지나치게 넓어서 작은 해상도 버킷에 샘플이 몰림
    • 리사이즈 방식이 공격적(다운스케일 반복)이라 고주파 정보 손실
  • 핵심
    • 얼굴이 프레임에서 차지하는 픽셀 수가 품질을 좌우합니다.

2) 얼굴 비대칭, 눈 위치 틀어짐

  • 원인 후보
    • 크롭이 랜덤이며 얼굴 중심이 흔들림(좌우/상하 오프셋이 큼)
    • 다양한 종횡비가 섞여 있는데 버킷이 적절히 분리되지 않음
  • 핵심
    • 모델은 “얼굴이 어디에 놓이는지”를 통계적으로 학습합니다. 크롭 정책이 흔들리면 얼굴 구조가 불안정해집니다.

3) 특정 각도(측면, 3/4)에서만 깨짐

  • 원인 후보
    • 데이터셋 포즈 분포가 편향(정면 과다)
    • 버킷/해상도 문제라기보다 데이터 다양성 문제
  • 핵심
    • 이 케이스는 레졸루션만 올려도 해결이 제한적입니다.

이 글의 범위는 1)과 2)처럼 해상도·버킷·크롭 정책으로 고칠 수 있는 영역에 집중합니다.

버킷(bucket)이 얼굴 품질에 미치는 영향

버킷은 간단히 말해, 다양한 원본 이미지(예: 768x512, 1024x1024, 640x960)를 훈련 파이프라인에서 몇 개의 “대표 해상도 그룹”으로 묶어 리사이즈/패딩/크롭을 최소화하려는 장치입니다.

버킷 튜닝이 중요한 이유는 다음입니다.

  1. 리사이즈 횟수/강도 감소: 불필요한 다운스케일이 줄어 얼굴 디테일이 보존됩니다.
  2. 종횡비 보존: 얼굴이 찌그러지거나 늘어나는 학습을 줄입니다.
  3. 샘플 분포 안정화: 특정 해상도에 샘플이 몰리면 그 해상도의 통계에 과적합하기 쉽습니다.

버킷이 잘못 잡혔을 때 흔한 증상

  • 학습 로그에 특정 버킷만 압도적으로 많이 등장
  • 종횡비가 다양한데 버킷 후보가 적어 강제 리사이즈가 잦음
  • 결과 이미지에서 얼굴이 “가로로 눌리거나 세로로 늘어난 듯” 보임

레졸루션(훈련 해상도) 올리면 왜 좋아지나

얼굴 디테일은 고주파(경계, 텍스처) 성분이 많습니다. 훈련 해상도가 낮거나, 낮은 해상도 버킷으로 자주 리사이즈되면 다음이 발생합니다.

  • 눈동자 하이라이트, 속눈썹, 치아 경계가 평균화됨
  • 입술 라인과 피부 텍스처가 섞여 “플라스틱”처럼 됨

다만 “무조건 큰 해상도”가 정답은 아닙니다.

  • SD 1.5 기반에서 무리하게 고해상도로만 돌리면 VRAM/스텝 수가 부족해져 오히려 불안정해질 수 있습니다.
  • SDXL은 기본적으로 고해상도에 맞춰져 있지만, 데이터셋이 저해상도 위주면 버킷 설계가 더 중요해집니다.

실무 튜닝 절차: 버킷 분포부터 확인

훈련 전에 먼저 해야 할 일은 “내 데이터가 어떤 해상도/종횡비로 구성되어 있는지”를 보는 것입니다. 아래 스크립트로 폴더 내 이미지들의 (가로, 세로) 분포를 빠르게 확인할 수 있습니다.

from PIL import Image
from pathlib import Path
from collections import Counter

img_dir = Path("./train_images")

sizes = []
for p in img_dir.rglob("*.jpg"):
    with Image.open(p) as im:
        sizes.append(im.size)  # (w, h)

c = Counter(sizes)
print("unique sizes:", len(c))
for (w, h), n in c.most_common(20):
    print(f"{w}x{h}: {n}")

여기서 포인트는 “유니크 해상도가 많다” 자체가 문제가 아니라, 얼굴이 작게 담긴 전신샷이 다수인지, 세로로 긴 이미지가 많은지, 정사각이 많은지 같은 구성입니다.

권장 데이터 정리(얼굴 깨짐 방지 관점)

  • 가능하면 얼굴이 프레임에서 충분히 큰 샷을 일정 비율 포함(예: 30% 이상)
  • 동일 인물 LoRA라면, 최소한 일부는 얼굴 중심 크롭을 고정적으로 제공
  • 원본이 너무 작은 이미지는 과감히 제외(업스케일로 뻥튀기한 데이터는 디테일이 늘지 않음)

버킷·레졸루션 실전 설정 가이드(kohya-ss 계열)

환경마다 옵션 이름이 조금씩 다르지만, 개념은 같습니다.

1) SD 1.5 인물 LoRA의 현실적인 시작점

  • base resolution: 512
  • bucket 사용: 켬
  • bucket min/max: 384~768 정도(너무 넓히지 않기)
  • bucket step: 64

이렇게 하면 1.5의 기본 스케일을 유지하면서도, 세로/가로 샷을 무리하게 찌그러뜨리지 않습니다.

2) 얼굴 디테일이 계속 깨질 때의 개선 루트

아래는 “바꾸면 체감이 큰 순서”입니다.

  1. bucket max를 올리기: 예를 들어 768에서 896 또는 1024
  2. bucket min을 올리기: 너무 작은 버킷으로 샘플이 떨어지지 않게(예: 384에서 512로)
  3. 정사각 중심으로 데이터 정리: 인물은 1:1이 얼굴 안정성이 좋음
  4. 얼굴 중심 크롭 데이터 추가: 랜덤 크롭만 쓰지 말고 “고정된 얼굴 클로즈업”을 섞기

중요한 점은, 해상도를 올리기 전에 작은 버킷으로의 쏠림을 막는 것입니다. 작은 버킷에 샘플이 몰리면, 결국 학습이 저해상도 통계에 끌려갑니다.

크롭 정책: 랜덤 크롭이 얼굴을 망가뜨리는 방식

랜덤 크롭은 일반적인 데이터 증강으로 유용하지만, 인물 LoRA에서 얼굴이 깨지는 케이스에서는 독이 될 수 있습니다.

  • 어떤 스텝에서는 얼굴이 프레임 중앙
  • 어떤 스텝에서는 이마만 남거나 턱이 잘림
  • 어떤 스텝에서는 얼굴이 왼쪽 끝에 붙음

이렇게 되면 모델은 “얼굴의 기준 좌표계”를 안정적으로 학습하기 어렵습니다.

해결책

  • 데이터 일부는 얼굴 중심 고정 크롭으로 준비
  • 전신/반신 샷은 유지하되, 얼굴 샷을 일정 비율로 섞어 디테일 학습을 보강

자동 크롭을 쓰는 경우에도, 얼굴 검출 기반으로 중심을 맞추는 파이프라인을 추천합니다.

훈련 중 점검: 버킷 분포가 한쪽으로 쏠리는지 확인

학습을 돌려놓고 “결과가 안 좋네”라고 끝내지 말고, 중간에 아래를 확인하세요.

  • 로그에 출력되는 bucket 해상도 사용 빈도
  • 특정 해상도만 반복되는지
  • 데이터셋에서 원본 종횡비가 다양할수록, 버킷 후보가 충분한지

이 과정은 CI에서 캐시 미스 원인을 추적하듯이(원인 후보를 줄여가며) 접근하는 게 효율적입니다. 비슷한 디버깅 사고방식은 GitHub Actions 캐시가 안 먹을 때 key 전략과 디버깅 글의 “관측 가능한 지표를 먼저 만들기”와도 통합니다.

예시: kohya-ss 학습 커맨드(버킷·해상도 중심)

아래는 개념을 보여주기 위한 예시입니다. 실제 옵션명은 사용하는 스크립트/GUI에 따라 다를 수 있습니다.

accelerate launch train_network.py \
  --pretrained_model_name_or_path "./models/sd15.safetensors" \
  --train_data_dir "./train_images" \
  --resolution "512,512" \
  --enable_bucket \
  --min_bucket_reso 512 \
  --max_bucket_reso 896 \
  --bucket_reso_steps 64 \
  --network_module "networks.lora" \
  --network_dim 16 \
  --network_alpha 16 \
  --train_batch_size 2 \
  --max_train_steps 3000 \
  --learning_rate 1e-4 \
  --mixed_precision "fp16" \
  --save_every_n_steps 500

위 설정의 의도는 명확합니다.

  • min_bucket_reso512로 올려 “작은 버킷으로 떨어지는 샘플”을 줄임
  • max_bucket_reso896까지 열어 세로/가로 샷을 무리하게 줄이지 않음
  • step을 64로 둬서 버킷 후보를 충분히 제공

만약 데이터가 정사각 위주라면 max_bucket_reso768로 낮추고 스텝 수를 늘리는 쪽이 VRAM 대비 효율적일 수 있습니다.

SDXL에서의 포인트: 1024 고정이 능사는 아니다

SDXL은 기본 학습 스케일이 커서 1024 기반 버킷을 많이 씁니다. 하지만 데이터가 다음과 같다면 문제가 생깁니다.

  • 원본이 512~768 중심
  • 얼굴이 작은 전신샷이 많음

이 상태에서 무작정 1024로 올리면 업스케일 학습이 되어 “선명해지는 것 같지만 얼굴 구조는 여전히 불안정”하거나, 오히려 과한 샤프닝 같은 아티팩트가 생길 수 있습니다.

권장 접근은 다음입니다.

  • 원본이 낮다면: 먼저 데이터셋 자체를 정리(너무 작은 원본 제거, 얼굴 샷 추가)
  • 버킷은: 원본 분포를 존중하면서 min/max를 잡고, 작은 버킷 쏠림을 방지

체크리스트: 얼굴 깨짐을 줄이는 우선순위

  1. 얼굴이 충분히 크게 담긴 샷 비율 확보
  2. 버킷 min을 너무 낮게 두지 않기(작은 버킷 쏠림 방지)
  3. 종횡비에 맞는 버킷 후보 제공(step 간격 64 권장)
  4. 랜덤 크롭 남발 대신 얼굴 중심 고정 크롭 일부 포함
  5. 필요 시에만 max 해상도 상향(리소스 대비 효과 확인)

마무리: “해상도”보다 “분포”가 먼저다

얼굴이 깨지는 LoRA는 단순히 해상도를 올려 해결되는 경우도 있지만, 재현성 있게 고치려면 버킷 분포와 크롭 정책을 먼저 잡아야 합니다. 작은 버킷으로 샘플이 몰리거나, 얼굴 위치가 매 스텝 흔들리면 어떤 학습률/랭크를 쓰더라도 얼굴이 불안정해질 확률이 큽니다.

훈련 파이프라인을 운영 관점으로 본다면, 이는 트래픽 급증 시 타임아웃 지점을 체계적으로 점검하는 것과 유사합니다. 증상을 “얼굴 깨짐”으로만 뭉뚱그리지 말고, 어디서 정보가 손실되는지(리사이즈/버킷/크롭)를 계측하고 하나씩 제거해 보세요. 이런 식의 점검 프레임은 AWS ALB 502/504 급증? 타임아웃 7곳 점검처럼 계층적으로 원인을 좁히는 접근과도 결이 같습니다.

원하시면, 사용 중인 훈련 도구(예: kohya-ss GUI, SimpleTuner, OneTrainer), 베이스 모델(SD 1.5 또는 SDXL), 데이터셋 구성(전신/반신/클로즈업 비율, 원본 해상도 분포)을 알려주시면 그 조건에 맞춰 min/max bucket, step, 권장 해상도 조합을 더 구체적으로 제안해 드릴 수 있습니다.