Posted:      Updated:

AI 스터디를 하며 ‘파이토치 트랜스포머를 활용한 자연어 처리와 컴퓨터비전 심층학습’ 교재를 정리한 글입니다.

데이터 증강(Data Augmentation)

이미지 데이터

객체 검출, 인식, 이미지 분류 등 이미지 처리 모델 구성 시 데이터셋 크기를 쉽게 늘리기 위해 사용
회전, 대칭, 이동, 크기 조정 등이 있음

토치비전(torchvision) 라이브러리와 이미지 증강(imgaug) 라이브러리

토치비전 라이브러리: 이미지 데이터 증강, 변형을 위한 기본적인 메서드 제공
이미지 증강 라이브러리: 토치비전에서 제공하지 않는 기능 제공

pip install imgaug

변환(transfroms) 모듈

이미지 데이터 증강을 위해 사용
이미지 변환과 관련된 기능, 여러 모델 매개변수를 묶어주는 시퀀셜(Sequential) 역할을 하는 통합(Compose) 클래스 사용

from PIL import Image
from torchvision import transforms

transform = transforms.Compose(
    [
        transforms.Resize(size=(512, 512)), # 이미지 크기 변환
        # 텐서로 변환
        # PIL.Image to Tensor, 최대 최소 정규화(Min-max Normalization) 진행
        # [높이, 너비, 채널] -> [채널, 높이, 너비]
        transforms.ToTensor()
    ]
)

image = Image.open("datasets/images/cat.jpg")
transformed_image = transform(image)
transformed_image.shape

출력

torch.Size([3, 512, 512])

회전 및 대칭

이미지를 회전하거나 대칭하면 변형된 이미지에 강한 모델을 구축해 일반화된 성능 구현 가능
과도한 증강 시 본래 특징 소실, 실제 데이터에 존재하지 않는 데이터 생성 가능

import matplotlib.pyplot as plt

transform = transforms.Compose(
    [
        transforms.RandomRotation(degrees=30, expand=False, center=None), # 무작위 회전: -30~30 사이 각도로 회전, expand: True 설정 시 여백 생성되지 않음, center: 시퀀스 형태로 전달하며 미전달 시 왼쪽 상단 기준
        transforms.RandomHorizontalFlip(p=0.5), # 수평 대칭 50% 확률로 적용
        transforms.RandomVerticalFlip(p=0.5) # 수직 대칭 50% 확률로 적용
    ]
)

image = Image.open("datasets/images/cat.jpg")
transformed_image = transform(image)
plt.imshow(transformed_image)
plt.axis('off')
plt.show()

출력

rotate

자르기 및 패딩

객체 인식 모델 구성 시 학습 데이터 크기가 일정하지 않거나 주요 객체가 일부 영역에만 작게 존재할 수 있음
객체가 존재하는 위치로 이미지를 잘라 불필요한 특징 감소, 패딩을 주어 이미지 크기를 동일하게 맞출 수 있음
과도하게 자르면 검출하려는 객체가 없거나 패딩을 너무 많이 주어 특징 영향이 감소할 수 있으므로 주의

transform = transforms.Compose(
    [
        # 무작위 자르기 클래스: 정수 입력 시 정사각형, 시퀀스 입력 시 높이, 너비 순서로 자름
        # 패딩으로 자를 때 발생하는 여백 공간 할당 가능
        transforms.RandomCrop(size=(512, 512)),
        # 패딩 클래스: 이미지 테두리에 특정 방식이나 고정값으로 이미지 확장
        # RGB로 테두리 생성 가능, reflect, symmetric을 주면 RGB 무시하고 이미지 픽셀 반사 혹은 대칭
        transforms.Pad(padding=50, fill=(127, 127, 255), padding_mode="constant")  
    ]
)

image = Image.open("datasets/images/cat.jpg")
transformed_image = transform(image)
plt.imshow(transformed_image)
plt.axis('off')
plt.show()

출력

crop

크기 조정

원활한 학습을 위해서 학습 데이터에 사용되는 이미지 크기가 일정해야 함
데이터 자체 수정 시 입력받는 이미지 크기가 달라지면 데이터 관리가 어려움
따라서 증강 클래스로 이미지 크기 변환

transform = transforms.Compose(
    [
        transforms.Resize(size=(512, 512))
    ]
)

image = Image.open("datasets/images/cat.jpg")
transformed_image = transform(image)
plt.imshow(transformed_image)
plt.axis('off')
plt.show()

출력

resize

변형

기하학적 변환(Geometric Transfrom): 인위적으로 확대, 축소, 위치 변경, 회전, 외곡 등 이미지 형태 변환
아핀 변환(Affine Transformation): $2 \times 3$ 행렬 사용, 행렬 곱셈에 벡터 합 활용
원근 변환(Perspective Transformation): $3 \times 3$ 행렬 사용, 호모그래피(Homography)로 모델링할 수 있는 변환

transform = transforms.Compose(
    [
        transforms.RandomAffine( # 아핀 변환
            # 각도, 이동, 척도, 전단 입력
            degrees=15, translate=(0.2, 0.2), scale=(0.8, 1.2), shear=15
        )
    ]
)

image = Image.open("datasets/images/cat.jpg")
transformed_image = transform(image)
plt.imshow(transformed_image)
plt.axis('off')
plt.show()

출력

affine

색상 변환

색상(Hue), 채도(Saturation), 명도(Brightness), 대비(Contrast) 등을 변경
특정 색상에 편향되지 않도록 픽셀값 변환, 정규화
색상 변경을 통해 간접적으로 데이터셋의 일반화 효과를 얻을 수 있음

transform = transforms.Compose(
    [
        transforms.ColorJitter(
            brightness=0.3, contrast=0.3, saturation=0.3, hue=0.3
        ),
        transforms.ToTensor(),
        transforms.Normalize( # 픽셀 평균, 표준편차 활용하여 정규화
            mean=[0.485, 0.456, 0.406],
            std=[0.229, 0.224, 0.225]
        ),
        transforms.ToPILImage()
    ]
)

image = Image.open("datasets/images/cat.jpg")
transformed_image = transform(image)
plt.imshow(transformed_image)
plt.axis('off')
plt.show()

출력

color

노이즈

특정 픽셀값에 편향되지 않도록 임의의 노이즈 추가
학습에 직접 포함하지 않고 일반화 능력, 강건성(Robustness) 평가에 사용 가능

import numpy as np
np.bool = np.bool_ # Deprecated 오류 방지
from PIL import Image
from torchvision import transforms
from imgaug import augmenters as iaa


class IaaTransforms:
    def __init__(self): # 증강 방법 설정
        self.seq = iaa.Sequential([
            iaa.SaltAndPepper(p=(0.03, 0.07)), # 점잡음(Sail and pepper noise) 적용
            iaa.Rain(speed=(0.3, 0.7)) # 빗방울 레이어 적용
        ])
    
    def __call__(self, images): 
        images = np.array(images) #  # PIL 이미지를 ndarray 형식으로 변환
        print(images.shape, images.dtype)
        augmented = self.seq.augment_image(images) # augment_image로 증강 적용
        return Image.fromarray(augmented) # 다시 PIL.Image 형식으로 반환


transform = transforms.Compose([
    IaaTransforms()
])

image = Image.open("datasets/images/cat.jpg")
transformed_image = transform(image)
plt.imshow(transformed_image)
plt.axis('off')
plt.show()

출력

(857, 1280, 3) uint8

iaa

컷아웃 및 무작위 지우기

컷아웃(Cutout): 이미지에서 임의의 사각형 영역 삭제, 픽셀값을 0으로 채우는 방법, 폐색 영역(Occlusion)에 강하게 해줌
무작위 지우기(Random Erasing): 임의의 사각형 영역을 삭제해 무작위 픽셀값으로 채우는 방법, 일부 영역 누락에 강함
두 방법 모두 이미지 객체 일부 누락 시에도 모델을 견고하게 함

transform = transforms.Compose([
    transforms.ToTensor(), # 무작위 지우기를 위해 Tensor로 변환
    transforms.RandomErasing(p=1.0, value=0), # 0으로 할당하여 컷아웃 실행
    transforms.RandomErasing(p=1.0, value="random"), # 랜덤 할당 시 무작위 지우기, Tensor 형식만 지원
    transforms.ToPILImage()
])

image = Image.open("datasets/images/cat.jpg")
transformed_image = transform(image)
plt.imshow(transformed_image)
plt.axis('off')
plt.show()

출력

erasing

혼합 및 컷믹스

혼합(Mixup): 두 개 이상 이미지를 혼합(Blending)해 새로운 이미지 생성, 픽셀값을 선형으로 결합하여 두 개의 이미지가 겹쳐 흐릿한 형상을 지님, 레이블링이 달라도 낮은 오류를 보이며 다중 레이블(Multi-label) 문제에 강함
컷믹스(CutMix): 네이버 클로바에서 발표한 방법, 이미지 패치(patch) 영역에 다른 이미지 덮어씌우는 잘라내고 붙여넣기 방식, 이미지 특정 영역 기억하지 않고 전체를 보고 판단할 수 있게 함

import numpy as np
from PIL import Image
from torchvision import transforms


class Mixup: # 혼합
    def __init__(self, target, scale, alpha=0.5, beta=0.5):
        self.target = target # 혼합하려는 이미지
        self.scale = scale # 크기
        self.alpha = alpha # 혼합 비율
        self.beta = beta # 혼합 비율

    def __call__(self, image):
        image = np.array(image)
        target = self.target.resize(self.scale)
        target = np.array(target)
        mix_image = image * self.alpha + target * self.beta
        return Image.fromarray(mix_image.astype(np.uint8))


transform = transforms.Compose(
    [
        transforms.Resize((512, 512)),
        Mixup(
            target=Image.open("datasets/images/dog.jpg"),
            scale=(512, 512),
            alpha=0.5,
            beta=0.5
        )
    ]
)

image = Image.open("datasets/images/cat.jpg")
transformed_image = transform(image)
plt.imshow(transformed_image)
plt.axis('off')
plt.show()

출력

mixup

토치비전 이미지 데이터 증강 클래스

범위 클래스 입력 설명
통합 transforms.Compose() - 조합한 변형을 하나로 묶음
텐서 변환(1) transforms.ToTensor() PIL PIL 이미지 또는 ndarray 형식을 Tensor로 변환
      형식: (H x W x C) -> (C x H x W)
      범위: [0, 255] -> [0, 1]
텐서 변환(2) transforms.PILToTensor() PIL PIL 이미지 또는 ndarray 형식을 Tensor로 변환
      형식: (H x W x C) -> (C x H x W)
      범위: 변환 없음
PIL 변환 transforms.ToPILImage( Tensor Tensor를 PIL 이미지로 변환 ndarray 형식 변환
  mode=str or number type)   형식: (C x H x W) -> (H x W x C)
      모드(mode): 4채널(RGBA), 3채널(RGB), 2채널(LA), 1채널(int, float, short)
이미지 형식 transforms.ConvertImageDtype   텐서 이미지 형식을 특정 dtype으로 변환
변환 dtype=torch.dtype)    
회전 transforms.RandomRotation(   각도(degrees): 동적 각도로 회전
  degrees=number or sequence,   보간(interpolation): 이미지 보간 방법을 설정
  interpolation=InterpolationMode,   확장(expand): 범위 확장 여부
  expand=bool,   중심점(center): 이미지 회전 중심점 설정
  center=sequence,   채우기(fill): 범위의 빈 공간
  fill=number or sequence)    
수평 대칭 transforms.RandomHorizontalFlip(   수평 뒤집기: 확률에 따라 대칭 사용
  p=float)   0.0 = 대칭하지 않음
      1.0 = 항상 대칭
수직 대칭 transforms.RandomVerticalFlip(   수직 뒤집기: 확률에 따라 대칭 사용
  p=float)   0.0 = 대칭하지 않음
      1.0 = 항상 대칭
무작위 자르기 transforms.RandomCrop(   크기(size): 동적 크기로 자르기(최소 크기)
  size=number or sequence,   패딩(padding): 여백 공간
  padding=int or sequence,   가능한 패딩(pad_if_needed): 이미지 크기가 자르는 크기보다 작은 경우 자동 패딩
  pad_if_needed=bool,   채우기(fill): 패딩 색상
  fill=number or tuple,   패딩 방식(padding_mode): 패딩 방식 설정
  padding_mode=str)   상수(constant), 테두리(edge), 반사(reflect), 대칭(symmetric)
무작위 자르기 transforms.RandomResizedCrop(   크기(size): 동적 크기로 자르기(최소 크기)
및 크기 변경 size=number or sequence,   스케일(scale): 이미지 크기의 상한 및 하한
  scale=tuple of float,   비율(ratio): 이미지 종횡비의 상한 및 하한
  ratio=tuple of float,   보간(interpolation): 이미지 보간 방법
  interpolation=InterpolationMode)   옵션(NEAREST, 이중 선형(BILINEAR), 바이큐빅(BICUBIC), 박스(BOX), 해밍(HAMMING), 란초스(LANCZOS))
중앙 자르기 transforms.CenterCrop(   크기(size): 동적 크기로 자르기(최소 크기)
  size=number or sequence)    
패딩 transforms.Pad(   패딩(padding): 여백 공간
  padding=number or sequence,   채우기(fill): 패딩 색상
  fill=number or tuple,   패딩 방식(padding_mode): 패딩 방식 설정
  padding_mode=str)   상수(constant), 테두리(edge), 반사(reflect), 대칭(symmetric)
크기 조정 transforms.Resize(   크기(size): 동적 크기로 조정(최소 크기)
  size=int or sequence,   보간(interpolation): 이미지 보간 방법
  interpolation=InterpolationMode,   최대 크기(max_size): 크기를 정수로 표시하는 경우 최대 크기
  max_size=int,   앤티알리아싱(antialias): 계단 현상 최소화 여부
  antialias=bool)    
아핀 변환 transforms.RandomAffine(   각도(degrees): 동적 각도로 회전
  degrees=number or sequence,   이동(translate): 수평 및 수직 이동
  translate=tuple,   스케일(scale): 이미지 크기의 상한 및 하한
  scale=tuple,   진자(shear): 진자 값
  shear=number or sequence,   X 축: -shear, shear 또는 shear[0], shear[1]
  interpolation=InterpolationMode,   Y 축: -shear, shear 또는 shear[2], shear[3]
  fill=number or sequence,   보간(interpolation): 이미지 보간 방법
  center=sequence)   옵션(NEAREST, 이중 선형(BILINEAR), 바이큐빅(BICUBIC), 박스(BOX), 해밍(HAMMING), 란초스(LANCZOS))
원근 변환 transforms.RandomPerspective(   왜곡(distortion_scale): 원근 변화의 최대 측도
  distortion_scale=float,   확률(p): 변환 확률
  p=float,   보간(interpolation): 이미지 보간 방법
  interpolation=InterpolationMode,   옵션(NEAREST, 이중 선형(BILINEAR), 바이큐빅(BICUBIC), 박스(BOX), 해밍(HAMMING), 란초스(LANCZOS))
  fill=number or sequence)   채우기(fill): 원근 변화의 빈 공간
색상 변환 transforms.ColorJitter(   밝기(brightness): 밝기 변화 범위 (min, max)
  brightness=float or tuple of float,   대비(contrast): 대비 변화

Categories:

댓글남기기