- 32 x 32, RGV, 6만 개, 1부터 10까지 Label
from torchvision import datasets data_path = '.../content/...' cifar10 = datasets.CIFAR10(data_path, train= True, download=True) cifar10_val = datasets.CIFAR10(data_path, train=False, download=True)
- 인자 : 데이터 받을 위치, 훈련셋인지 검증셋인지 지정, 데이터 찾지 못할 경우 데이터 내려받으라고 지정.
- torch.utils.data.Dataset의 서브클래스로 반환된다.
- Dataset 은 __len, getitem__ 을 구현하기 위해 필요한 객체.
데이터 변환
- PIL 이미지를 tensor로 변환, torchvision.transforms 필요
- ToTensor() : numpy 배열과 PIL이미지를 텐서로 바꾸는 역할, 또한 출력 텐서의 차원을 CxHxW ( 채널,높이,너비)로 맞춰준다.
to_tensor = transforms.ToTensor() img_t = to_tensor(img) img_t.shape ''' torch.Size([3, 32, 32])'''
- 채널은 첫번째 차원 ,스칼라값은 float32, 값이 0.0~1.0 사이로 범위가 줄어든다
plt.imshow(img_t.permute(1,2,0)) plt.show()
- .permute(1,2,0) : CxHxW -> HxWxC 바꾼다.
데이터 정규화
- transforms.Compose 로 여러 변환을 엮어서 사용하면 유용, 정규화와 데이터 증강도 데이터 로딩과 함께 수행 가능.
- 정규화로 각 채널이 평균값 0과 단위 표준 편차를 가지게 만드는 연습을하면 도움이 된다.
- 데이터를 같은 범위에서 평균을 가지게 한다면 뉴런은 0이 아닌 기울기를 가지므로 더 빠른 학습 가능.
- 각 채널을 정규화 해 동일한 분산을 가지게 한다면
채널 정보가 동일한 학습률로 경사 하강을 통해 섞이고 업데이트되는 것도 보장.import torch imgs = torch.stack([img_t for img_t,_ in tensor_cifar10], dim=3) imgs.shape '''torch.Size([3, 32, 32, 50000])''' imgs.view(3,-1).mean(dim=1) #채널별 평균 계산 '''tensor([0.4914, 0.4822, 0.4465])''' imgs.view(3,-1).std(dim=1) # 채널 별 표준 편차 계산 ''tensor([0.2470, 0.2435, 0.2616])''' transforms.Normalize((0.4914, 0.4822, 0.4465),(0.2470, 0.2435, 0.2616)) # 구한 값으로 Normalize 변환 초기화
- view(3,-1)은 세 채널을 유지하고 나머지 차원을 적절한 크기 하나로 합친다.
3x32x32 -> 3x1024, 평균은 각 채널의 1024개 요소에 대해 계산.transformed_cifar10 = datasets.CIFAR10( data_path, train=True, download=False, transform = transforms.Compose([ transforms.ToTensor(), transforms.Normalize((0.4914, 0.4822, 0.4465),(0.2470, 0.2435, 0.2616)) ]) )
1. 데이터셋 구축
제일 먼저 데이터의 차원 정보 맞춰야 한다.
label_map = {0:0, 2:1} class_names = ['airplane','bird'] cifar2 = [(img,label_map[label]) for img,label in cifar10 if label in [0,2]] cifar2_val = [(img, label_map[label]) for img,label in cifar10_val if label in [0,2]]
- cifar2 객체는 __len__, __getitem__ 정의되어 있으므로 Dataset의 기본 요구사항 만족.
2. 완전 연결 모델
- 간단하게, 신경망은 feature 텐서가 들어가고 feature 텐서가 나오는 것.
이미지도 결국 공간 설정에 따라 적절히 배치된 숫자 집합.
이미지 픽셀을 받아 긴 1차원 벡터로 늘어뜨린다면 일련의 숫자들을 입력 feature로 볼 수 있다.- 32323 이므로 샘플마다 3072개 입력 feature가 있는 셈.
import torch.nn as nn n_out =2 model = nn.Sequential( nn.Linear( 3072, 512, ), nn.Tanh(), nn.Linear( 512, n_out ) )
- 임의로 512개 은닉된 feature 골랐다.
- 신경망은 최소 하나 이상의 비선형성을 가진 은닉층이 필요 -> 임의의 함수를 학습할 수 있어야 하기 때문.
- 은닉된 feature는 가중치 행렬로 인코딩된 입력값들 간의 학습된 관계를 나타낸다.
3. 분류기의 출력
- 출력값이 카테고리이므로, 비행기는 [1,0], 새는 [0,1]하는 것 처럼 원핫 인코딩으로 바꿔줘야 한다.
이상적인 경우 [1,0]이나 [0,1]로 출력, 실제는 출력이 두 값 사이가 될 것.
중요한 점은 출력을 확률로 해석할 수 있다는 점.- 문제를 확률로 보게 되면 신경망의 출력에 추가적인 제약을 수반.
- 확률은 0이상 1이하이므로 출력값의 요소가 가질 수 있는 값은 [0,1] 범위로 제한.
- 모든 출력 요소 값의 합은 1.0이다.
- 이런 제약 극복을 위해 Softmax 사용
4. 출력을 확률로 표현
- 소프트맥스는 벡터값을 받아 동일한 차원의 다른 벡터를 만드는데, 값이 확률로 표현되어야 하는 제약 만족.
벡터의 각 요소 단위로 지수 연산 후 각 요소를 지수 값의 총합으로 나눈다.import torch def softmax(x): return torch.exp(x) / torch.exp(x).sum() x = torch.tensor([1.0,2,3]) softmax(x) ''' tensor([0.0900, 0.2447, 0.6652]) ''' softmax(x).sum() ''' tensor(1.) '''
- softmax는 단조 함수라 입력값이 낮아지면 출력값도 따라 낮아지지만, 각 값들 간의 비율이 유지되지 않는다.
- nn 모듈로 softmax를 모듈처럼 사용, nn.Softmax는 차원 지정하도록 요구.
softmax = nn.Softmax(dim=1) x= torch.tensor([[1.0,2,3], [1.0,2,3]]) softmax(x) ''' tensor([[0.0900, 0.2447, 0.6652], [0.0900, 0.2447, 0.6652]]) '''
model = nn.Sequential( nn.Linear(3072, 512), nn.Tanh(), nn.Linear(512,2), nn.Softmax(dim=1) ) img, _= transformed_cifar10[0] plt.imshow(img.permute(1,2,0)) plt.show()
- 모델을 호출하려면 입력 차원이 맞아야 한다.
현재 모델은 입력이 3072개의 feature를 갖고 있으며
0번 차원을 따라 배치로 이뤄지는 데이터를 대상으로 nn에서 작업.
따라서 3x32x32 이미지를 1차원 텐서로 만들고 추가 차원을 0번 포지션에 넣는다.img_batch = img.view(-1).unsqueeze(0)
out = model(img_batch) out ''' tensor([[0.4290, 0.5710]], grad_fn=<SoftmaxBackward0>) '''
- 두 숫자값에 역전파 후 의미를 부여하는 것은 바로 활성 함수다.
훈련 후에 출력된 확률에 대해 argmax 연산으로 레이블 얻을 수 있다. (argmax는 제일 높은 확률에 대한 인덱스)
차원이 주어지는 경우 torch.max는 해당 차원에서 가장 높은 요소와 인덱스 리턴._, index = torch.max(out,dim=1) index ''' tensor([1]) '''
5. 분류를 위한 손실값
- 손실값은 확률에 의미를 부여한다.