본문 바로가기

책/파이토치 트랜스포머를 활용한 자연어 처리와 컴퓨터비전 심층학습

04 파이토치 심화 (4) 데이터 증강 및 변환

데이터 증강 및 변환

데이터 증강(Data Augmentation)이란 데이터가 가진 고유한 특징을 유지한 채 변형하거나 노이즈를 추가해 데이터세트의 크기를 인위적으로 늘리는 방법이다.

 

데이터세트를 인위적으로 늘리면 모델의 과대적합을 줄이고 일반화 능력을 향상시키며 모델의 분산관 편향을 줄일 수 있다. 또한 클래스 불균형 문제를 완화할 수 있다. 그러나 너무 많은 변형이나 노이즈를 추가한다면 기존 데이터가 가진 특징이 파괴될 수 있다.

 

텍스트 데이터

텍스트 데이터 증강 방법은 삽입, 삭제, 교체, 대체, 생성, 반의어, 맞춤법 교정, 역번역 등이 있다. 자연어 처리 데이터 증강(NLPAUG) 라이브러리를 활용해 텍스트 데이터를 증강해보자.

 

!pip install nlpaug transformers sacremoses nltk
  • transformers : 사전 학습된 모델을 쉽게 다운로드하고 활용할 수 있는 API를 제공
  • scremoses, NLTK : 텍스트를 토큰화하고 정규화하는 라이브러리

 

삽입 및 삭제

삽입은 의미 없는 문자나 단어, 또는 문장 의미에 영향을 끼치지 않는 수식어 등을 추가하는 방법이다. 삭제는 임의의 단어나 문자를 삭제해 데이터의 특징을 유지하는 방법이다.

 

삽입과 삭제는 문장의 의미는 유지한 채 시퀀스를 변경하므로 간단하고 강력한 증강 기법이지만, 너무 적은 양을 삽입하거나 삭제한다면 오히려 과대적합 문제를 발생시킬 수 있고 너무 많은 양을 삽입하거나 삭제한다면 데이터 품질 저하로 이어질 수 있다.

 

단어 삽입

import nlpaug.augmenter.word as naw

texts = [
    "Those who can imagine anything, can create the impossible.",
    "We can only see a short distance ahead, but we can see plenty there that needs to be done.",
    "If a machine is expected to be infallible, it cannot also be intelligent.",
]

aug = naw.ContextualWordEmbsAug(model_path="bert-base-uncased", action="insert")
augmented_texts = aug.augment(texts)

for text, augmented in zip(texts, augmented_texts):
  print(f"src : {text}")
  print(f"dst : {augmented}")
  print("------------------")
src : Those who can imagine anything, can create the impossible.
dst : those who can imagine do anything, though can also create the very impossible.
------------------
src : We can only see a short distance ahead, but we can see plenty there that needs to be done.
dst : we can usually only now see a short distance straight ahead, but we still can see well plenty there for that needs to well be done.
------------------
src : If a machine is expected to be infallible, it cannot also be intelligent.
dst : if a machine language is only expected not to already be infallible, it thus cannot therefore also be intelligent.
  • ContextualWordEmbsAug 클래스는 BERT 모델을 활용해 단어를 삽입하는 기능을 제공한다. model_path를 통해 bert-base-uncased나 distilbert-base-uncased를 인수로 활용해 적용하며 허깅 페이스에서 모델을 자동으로 다운로드해 불러온다.
  • action을 insert로 지정하여 삽입을 수행할 수 있고 substitute로 대체 기능도 제공한다.

 

문자 삭제

import nlpaug.augmenter.char as nac


texts = [
  "Those who can imagine anything, can create the impossible.",
  "We can only see a short distance ahead, but we can see plenty there that needs to be done.",
  "If a machine is expected to be infallible, it cannot also be intelligent.",
]

aug = nac.RandomCharAug(action="delete")
augmented_texts = aug.augment(texts)

for text, augmented in zip(texts, augmented_texts):
  print(f"src : {text}")
  print(f"dst : {augmented}")
  print("------------------")
src : Those who can imagine anything, can create the impossible.
dst : hos who can mane anything, can crte the mpsible.
------------------
src : We can only see a short distance ahead, but we can see plenty there that needs to be done.
dst : We can ly see a short disnc hea, but we can see plenty tee th ees to be dn.
------------------
src : If a machine is expected to be infallible, it cannot also be intelligent.
dst : If a mahn is expee to be infallible, it caot ao be itlient.
  • RandomCharAug 클래스를 통해 무작위로 문자를 삭제할 수 있다.
  • 삽입(insert), 대체(substitute), 교체(swap), 삭제(delete) 기능을 제공한다.

 

교체 및 대체

교체는 단어나 문자의 위치를 교환하는 방법이다. '문제점을 찾지 말고 해결책을 찾으라'라는 문장에 교체를 적용한다면 '해결책을 찾으라 문제점을 찾지 말고'로 변경될 수 있다.

 

하지만 단어를 교체할 때 '해결책을 찾지 말고 문제점을 찾으라'로 교체된다면 본래의 의미나 맥락을 보존하지 못하게 되므로 주의해 사용해야 한다.

 

대체는 단어나 문자를 임의의 단어나 문자로 바꾸거나 동의어로 변경하는 방법이다. '사과'를 '바나나'로 변경하거나 '해'를 '태양'으로 바꾸는 작업이다. 다른 증강 방법보다 비교적 데이터의 정합성(Consistency)이 어긋나지 않아 효율적으로 데이터를 증강할 수 있다.

 

그러나 '사과는 빨갛다'라는 문장이 '바나나는 빨갛다'로 바꿔지거나 '해는 동쪽에서 뜬다'가 '태양는 동쪽에서 뜬다'와 같이 조사가 어색해질 수도 있다.

 

단어 교체

aug = naw.RandomWordAug(action="swap")
augmented_texts = aug.augment(texts)

for text, augmented in zip(texts, augmented_texts):
    print(f"src : {text}")
    print(f"dst : {augmented}")
    print("------------------")
src : Those who can imagine anything, can create the impossible.
dst : Those can who anything imagine, create can the. impossible
------------------
src : We can only see a short distance ahead, but we can see plenty there that needs to be done.
dst : We can only see a short ahead distance, but we can plenty see there needs that to be done.
------------------
src : If a machine is expected to be infallible, it cannot also be intelligent.
dst : If a is machine to expected be, infallible it cannot be also. intelligent
------------------
  • 단어 삽입에서 사용했던 nlaug.augmenter.word의 RandomWordAug 클래스를 사용해 교체를 수행할 수 있다.
  • 해당 클래스는 삽입, 대체, 교체, 삭제 기능을 제공하며 연속된 단어 집합을 한번에 삭제하는 자르기(crop) 기능도 지원한다.

 

단어 대체 (1)

aug = naw.SynonymAug(aug_src='wordnet')
augmented_texts = aug.augment(texts)

for text, augmented in zip(texts, augmented_texts):
    print(f"src : {text}")
    print(f"dst : {augmented}")
    print("------------------")
src : Those who can imagine anything, can create the impossible.
dst : Those who tush envisage anything, stool make the impossible.
------------------
src : We can only see a short distance ahead, but we can see plenty there that needs to be done.
dst : We lavatory only if see a short space forwards, but we bottom see plenty at that place that needs to be done.
------------------
src : If a machine is expected to be infallible, it cannot also be intelligent.
dst : If a auto is require to comprise infallible, it cannot also embody intelligent.
  • SynonymAug 클래스는 WordNet 데이터베이스나 의역 데이터베이스(PPDB)를 활용해 단어를 대체해 증강한다.
  • aug_src에 wordnet이나 ppdb를 인수로 활용해 문장의 의미를 변경할 수 있다.
  • 데이터베이스 내 유의어나 동의어로 변경하므로 모델을 활용해 대체하는 경우 ContextualWordEmbsAug 클래스를 사용한다.

단어 대체 (2)

reserved_tokens = [
    ["can", "can't", "cannot", "could"],
]

reserved_aug = naw.ReservedAug(reserved_tokens=reserved_tokens)
augmented_texts = reserved_aug.augment(texts)

for text, augmented in zip(texts, augmented_texts):
    print(f"src : {text}")
    print(f"dst : {augmented}")
    print("------------------")
src : Those who can imagine anything, can create the impossible.
dst : Those who cannot imagine anything, can't create the impossible.
------------------
src : We can only see a short distance ahead, but we can see plenty there that needs to be done.
dst : We could only see a short distance ahead, but we can't see plenty there that needs to be done.
------------------
src : If a machine is expected to be infallible, it cannot also be intelligent.
dst : If a machine is expected to be infallible, it can't also be intelligent.
------------------
  • ReservedAug 클래스는 reserved_tokens에서 선언한 데이터로 변경한다.

 

역번역

역번역이란 입력 텍스트를 특정 언어로 번역한 다음 다시 본래의 언어로 번역하는 방법을 의미한다. 본래의 언어로 번역하는 과정에서 원래 텍스트와 유사한 텍스트가 생성되므로 패러프레이징(paraphrasing) 효과를 얻을 수 있다.

 

back_translation = naw.BackTranslationAug(
    from_model_name='facebook/wmt19-en-de',
    to_model_name='facebook/wmt19-de-en'
)
augmented_texts = back_translation.augment(texts)

for text, augmented in zip(texts, augmented_texts):
    print(f"src : {text}")
    print(f"dst : {augmented}")
    print("------------------")
src : Those who can imagine anything, can create the impossible.
dst : Anyone who can imagine anything can achieve the impossible.
------------------
src : We can only see a short distance ahead, but we can see plenty there that needs to be done.
dst : We can only look a little ahead, but we can see a lot there that needs to be done.
------------------
src : If a machine is expected to be infallible, it cannot also be intelligent.
dst : If a machine is expected to be infallible, it cannot be intelligent.
------------------

 

 

이미지 데이터

이미지 데이터 증강 방법은 크게 회전, 대칭, 이동, 크기 조정 등이 있다. torchvision 라이브러리와 imgaug 라이브러리를 활용해 이미지 데이터를 증강한다.

 

변환 적용 방법

torchvision의 transforms 모듈을 통해 수행할 수 있고, 여러 모델 매개변수를 묶어주는 시퀀셜과 같은 역할을 하는 Compose 클래스를 함께 사용해 증강을 적용한다.

 

Compose 클래스 및 transforms 적용 방식

from matplotlib import pyplot as plt
from PIL import Image
from torchvision import transforms

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

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

print(transformed_image.shape)
torch.Size([3, 512, 512])
  • 이미지 데이터를 512 $\times$ 512 크기로 변환하고 텐서 타입으로 변환하는 과정을 수행
  • ToTensor 클래스는 [0, 255] 범위의 픽셀값을 [0.0, 1.0] 사이의 값으로 최대 최소 정규화를 수행한다.
  • 입력 데이터의 [높이, 너비, 채널] 형태를 [채널, 높이, 너비] 형태로 변환한다.
  • 데이터세트에 일괄 적용한다면 torchvision.datasets.ImageFolder 클래스의 transform 매개변수에 입력해 활용할 수 있다.

 

회전 및 대칭

회전 및 대칭

 

transform = transforms.Compose(
    [
        transforms.RandomRotation(degrees=30, expand=False, center=None),
        transforms.RandomHorizontalFlip(p=0.5),
        transforms.RandomVerticalFlip(p=0.5),
    ]
)

transformed_image = transform(image)
plt.imshow(transformed_image)

  • RandomRotation 클래스는 입력된 degrees를 음수부터 양수 사이의 각도로 변환한다. [-30, 90]과 같이 시퀀스 형태로 입력하여 임의의 범위를 설정할 수 있다. expand를 True로 설정하면 여백이 생성되지 않는다. center는 시퀀스 형태로 전달하며 입력하지 않으면 왼쪽 상단을 기준으로 회전한다.
  • RandomHorizontalFlip, RandomVerticalFlip 클래스는 수행 확률(p)에 따라 대칭을 수행한다. 만약 0.0을 입력한다면 대칭을 수행하지 않는다.

자르기 및 패딩

주요한 객체가 일부 영역에만 작게 존재할 수도 있기 때문에 이미지를 잘라 불필요한 특징을 감소시키거나 패딩을 주어 이미지 크기를 동일한 크기로 맞출 수 있다.

 

자르기 및 패딩

transform = transforms.Compose(
    [
        transforms.RandomCrop(size=(512, 512)),
        transforms.Pad(padding=50, fill=(127, 127, 255), padding_mode="constant")
    ]
)

transformed_image = transform(image)
plt.imshow(transformed_image)

  • Pad 클래스에 padding을 50으로 주면 패딩은 모든 방향으로 적용되므로 612 $\times$ 612 크기로 반환된다.
  • fill에 RGB 값을 입력할 수 있다.
  • padding_mode를 reflect나 symmetric으로 주면 입력한 RGB는 무시되면 이미지의 픽셀값을 반사하거나 대칭해 생성한다.
  • RandomCrop 클래스에서도 자른 영역에 대한 패딩을 지정할 수 있다.

크기 조정

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

transformed_image = transform(image)
plt.imshow(transformed_image)

  • size를 정수로 입력하면 높이나 너비 중 크기가 더 작은 값에 비율을 맞춰 크기가 수정된다. 예를 들어 원본 이미지가 (500, 400)일 때 size=300으로 지정하면 (300*500/400, 300)=(375, 300)으로 크기가 조정된다.
  • 시퀀스 형태로 명시적으로 설정하는게 일반적이다.

변형

아핀 변환

transform = transforms.Compose(
    [
        transforms.RandomAffine(
            degrees=15, translate=(0.2, 0.2),
            scale=(0.8, 1.2), shear=15
        )
    ]
)

transformed_image = transform(image)
plt.imshow(transformed_image)

 

색상 변환

색상의 채도(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()
    ]
)

transformed_image = transform(image)
plt.imshow(transformed_image)

 

노이즈

노이즈 추가

import numpy as np
from imgaug import augmenters as iaa

class IaaTransforms:
  def __init__(self):
    self.seq = iaa.Sequential([
        iaa.SaltAndPepper(p=(0.01, 0.07)), # 점잡음
        iaa.Rain(speed=(0.3, 0.7)),
    ])

  def __call__(self, images):
    images = np.array(images)
    augmented = self.seq.augment_images(images)
    return Image.fromarray(augmented)

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

transformed_image = transform(image)
plt.imshow(transformed_image)

  • imgaug의 augmenters 클래스는 넘파이 클래스를 입력과 출력값으로 사용한다.

컷아웃 및 무작위 지우기

무작위 지우기

transform = transforms.Compose([
    transforms.ToTensor(),
    transforms.RandomErasing(p=1.0, value=0), # 컷아웃
    transforms.RandomErasing(p=1.0, value='random'), # 무작위 지우기
    transforms.ToPILImage()
])

transformed_image = transform(image)
plt.imshow(transformed_image)

value를 0으로 할당하면 컷아웃 방법이 되고 random으로 입력하면 무작위 지우기 방법이 된다.

 

혼합 및 컷믹스

혼합(Mixup)은 두 개 이상의 이미지를 혼합해 새로운 이미지를 생성하는 방법이다. 컷믹스는 이미지 패치 영역에 다른 이미지를 덮어씌우는 방법이다.

 

혼합

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(size=(512, 512)),
        Mixup(
            target=Image.open(DATA_PATH / 'images' / 'dog.jpg'),
            scale=(512, 512),
        )
    ]
)

transformed_image = transform(image)
plt.imshow(transformed_image)