- 순환 신경망을 만들고
- Dataset 과 DataLoader를 사용한 데이터를 로딩합니다.
- 분류 손실도 알아봅시다 !
이번 시간에서는 단순 신경망을 만들어 간단한 이미지 인식 문제를 단계별로 접근하여 나아가보자. 먼저 ! 이미지 데이터셋을 내려받아 처리해야겠지?
이번 챕터에서는 이미지 데이터셋의 고전인 CIFAR-10 데이터를 import해서 가져온다.
from torchvision import datasets
data_path = '../data-unversioned/p1ch7/'
cifar10 = datasets.CIFAR10(data_path, train=True, download=True)
cifar10_val = datasets.CIFAR10(data_path, train=False, download=True)
datasets.CIFAR10에서 첫번째 인자는 데이터를 받을 위치이고, 두 번째 인자는 훈련셋인지, 검증셋인지를 지정하며, 세 번째 인자는 파이토치에게 데이터를 찾지 못한 경우 첫 번째 인자로 주어진 위치에 데이터를 내려받으라고 지정한다.
torch.utils.data.Dataset 서브클래스
안에 들어가면 어떤 의미들이 들어있는지 알아보자.
Dataset은 __len__
과 __getitem__
을 구현하기 위하여 필요한 객체이다. __len__
은 데이터셋의 아이템 수를 반환해야하며, __getitem__
은 샘플과 레이블로 이루어진 아이템을 반환한다.
데이터를 가져왔으니 PIL 이미지를 처리 가능한 파이토치 텐서로 변환을 진행해보자!
torchvision.transforms
가 필요하다. transforms에 어떤 기능이 있는지 우선 확인해볼까?
from torchvision import transforms
dir(transforms)
#results
['AugMix',
'AutoAugment',
'AutoAugmentPolicy',
'CenterCrop',
'ColorJitter',
'Compose',
'ConvertImageDtype',
'ElasticTransform',
'FiveCrop',
'GaussianBlur',
'Grayscale',
'InterpolationMode',
'Lambda',
'LinearTransformation',
'Normalize',
'PILToTensor',
'Pad',
'RandAugment',
'RandomAdjustSharpness',
'RandomAffine',
'RandomApply',
'RandomAutocontrast',
'RandomChoice',
'RandomCrop',
'RandomEqualize',
'RandomErasing',
'RandomGrayscale',
'RandomHorizontalFlip',
'RandomInvert',
'RandomOrder',
'RandomPerspective',
'RandomPosterize',
'RandomResizedCrop',
'RandomRotation',
'RandomSolarize',
'RandomVerticalFlip',
'Resize',
'TenCrop',
'ToPILImage',
'ToTensor',
'TrivialAugmentWide',
'__builtins__',
'__cached__',
'__doc__',
'__file__',
'__loader__',
'__name__',
'__package__',
'__path__',
'__spec__',
'_functional_pil',
'_functional_tensor',
'_presets',
'autoaugment',
'functional',
'transforms']
중요한 부분만 뜯어보면, ToTensor의 경우 넘파이 배열과 PIL 이미지를 텐서로 바꾸는 역할을 하며, 출력 텐서의 차원 레이아웃을 C X H X W 로 맞춰준다.
from torchvision import transforms
to_tensor = transforms.ToTensor()
img_t = to_tensor(img)
img_t.shape
#results
torch.Size([3, 32, 32])
이미지가 우선 3 X 32 X 32 텐서로 바뀐 것을 확인할 수 있으며, RGB 세개의 채널을 가지는 32 X 32 이미지가 되었다 ! 추가적으로 matplotlib 에서 쓰이는 H X W X C 형태를 위해서 C X H X W 축을 permute
를 활용하여 변경한다.
transforms.Compose
를 통해 여러 변환을 진행하여, 정규화와 데이터 증강(augmentation) 까지 해낼 수 있다.
정규화 식은 다음과 같다 !
이 식을 적용한 함수가 transforms.Normalize
이다.
자, 그럼 이 과정을 진행하기 위해 데이터셋이 반환하는 모든 텐서를 추가 차원을 만들어 쌓아놓는 코드를 작성하고, 평균과 표준편차까지 구해보면,
imgs = torch.stack([img_t for img_t, _ in tensor_cifar10], dim=3)
imgs.shape
#results
torch.Size([3, 32, 32, 50000])
imgs.view(3, -1).std(dim=1)
#results
tensor([0.2470, 0.2435, 0.2616])
transforms.Normalize((0.4915, 0.4823, 0.4468), (0.2470, 0.2435, 0.2616))
#results
Normalize(mean=(0.4915, 0.4823, 0.4468), std=(0.247, 0.2435, 0.2616))
자, 이제 제일 중요한 데이터 전처리 시 가장 잘 쓰이는 코드를 확인해보면,
transformed_cifar10 = datasets.CIFAR10(
data_path, train=True, download=False,
transform=transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.4915, 0.4823, 0.4468),
(0.2470, 0.2435, 0.2616))
]))
transformed_cifar10_val = datasets.CIFAR10(
data_path, train=False, download=False,
transform=transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.4915, 0.4823, 0.4468),
(0.2470, 0.2435, 0.2616))
]))
다음과 같이 전처리를 진행할 때 코드를 작성할 수 있다. Compose 구성 단계 안에 tensor
로 변경하는 과정과 , 정규화를 진행하는 과정을 담을 수 있으며, 훈련셋과 검증셋
으로 나눠서 데이터 전처리를 진행한다.
classifier 모델을 만들고, 이에 대한 출력값을 확률
로 나타내고 싶을 때, 사용하는 함수이다. 소프트맥스는 벡터값을 받아 동일한 차원의 다른 벡터를 만든느데, 값이 확률로 표현되어야한다. 소프트맥스 식은
출력값의 특징은 보존 ! ( 1. 각 요소 값은 0과 1 사이 , 2. 모든 요소의 합은 1 )
loss는 확률에 의미를 부여하며, 이제부터는 정답 클래스와 관련된 확률을 극대화 할때 사용하도록 한다. 가능도(likelihood)
란, 정답 클래스에 대한 확률 수치를 이야기한다. 가능도가 낮을 때, 다른 클래스의 확률이 매우 높을 때 값이 커지는 손실 함수가 필요하게 된다.
반대로 가능도가 다른 클래스보다 높으면 loss 는 낮아야한다. 이렇게 likelihood를 조절할 때 loss 를 사용하게 된다.
분류를 위한 손실은 다음과 같이 계산된다
파이토치에서는 완전 간단하게 사용가능하다 !
바로 nn.NLLLoss - (업뎃) nn.LogSoftmax 함수를 사용하면 되는 것 !
이걸 한번에 줄인 것이 nn.CrossEntropyLoss !
crossentropy
손실함수는 예측이 타깃값에서 멀어지는 경우 그래프의 경사가 적당히 생기지만, MSE
손실함수의 경우 훨씬 더 일찍 포화가되기 때문에 좋지 않은 예측을 하게된다. 따라서 crossentropy
손실함수가 더 확률 loss값 보정에 좋은 역할을 한다고 볼 수 있다.
이미지를 픽셀값의 벡터로 간주하고 일반 숫자 데이터처럼 완전 연결 신경망으로 다루는 것은 좋지만, fully connected layer의 경우 평행이동의 불변성
이 없기 때문에 , 하나를 이동하면 다른 모든 픽셀값들을 이동시켜야한다.
이를 보완하기 위해서 데이터셋을 augmentation(증강)
하여 후녈ㄴ 때 이미지를 랜덤하게 평행이동시켜 이미지의 모든 영역에서 볼 수 있도록 하면 되지만 상당한 비용이 들기 때문에 문제가 발생하게 된다..
이를 해결하기 위한 방법은 8장에서 배우게된다 !