[2022 국민대학교 겨울 인공지능 특강] 4주차 3일 학습 내용

하지원·2022년 2월 8일
0

이번 시간에는 PyTorch를 이용해서 MNIST를 다뤘다. MNIST란 기본적인 딥러닝 학습 데이터셋(dataset)인데, 1부터 9까지 다양한 글씨체로 적힌 숫자를 학습해서 숫자들을 학습해서 판별한다. 손글씨 분류 딥러닝 학습 모델이다.

여기서 사용할 몇가지 PyTorch 모듈들을 알아보자.

우선 PyTorch를 불러오기 위해 torch를 불러와야 할 것이다.
-> import torch

그리고 신경망(neural network)을 사용하기 위해서 torch.nn을 불러올 필요가 있다. 간단하게 nn을 입력함으로써 신경망을 사용할 수 있도록 하겠다.
-> import torch.nn as nn

또한 최적화 함수를 사용하려 한다. 그러기 위해서는 torch.optim을 불러오고, optim을 입력하여 최적화 기능을 사용가능하게 하면 된다.
-> import torch.optim as optim

마지막으로 torch.nn에서 제공하는 여러가지 기능이 있다. 컨벌루션, 풀링, 비선형 활성화 함수 등이 있다.
-> import torch.nn.functional as F

여기서는 데이터셋을 불러와야 하고, 이미지 변환이 필요하기 때문에 torchvision이라는 모듈을 사용한다. 이 모듈에서 datasets와 transforms를 불러온다.
-> from torchvision import datasets, transforms

딥러닝 모델을 만들고 사용하기 전에 장치(device)라는 것이 필요한데, 그 중 CPU를 이용할것이다.

device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu") 

MNIST를 학습하기 위해 torchvision의 패키지 중 하나인 datasets의 MNIST 패키지를 datasets.MNIST로 학습용 데이터셋과 테스트용 데이터셋을 각각 불러온다.

train_dataset = datasets.MNIST(root='./mnist_data/', train=True, transform=transforms.ToTensor(), download=True)
test_dataset = datasets.MNIST(root='./mnist_data/', train=False, transform=transforms.ToTensor())

이렇게 각각 불러온 데이터셋들을 torch.utils.data.DataLoader()를 통해 이용할 수 있도록 하고, 배치(batch) 크기도 정해준다.
여기서 batch란 데이터의 묶음인데, 정해진 크기만큼의 수의 데이터가 저장된다. 많은 양의 데이터를 한꺼번에 학습하기에는 과부하가 걸릴 수 있기 때문에 묶음별로 나눠서 학습을 하게 하는 것이다. 사람도 시험 준비등으로 공부할 때 모든 내용을 한꺼번에 익히려다가는 머리가 아플것이다. 따라서 과목별로 나누거나 시간을 나눠서 공부한다. 컴퓨터 또한 마찬가지이다.
여기서 배치 크기는 64로 설정했다.

batch_size = 64
train_loader = torch.utils.data.DataLoader(dataset=train_dataset, batch_size=batch_size, shuffle=True)
test_loader = torch.utils.data.DataLoader(dataset=test_dataset, batch_size=batch_size, shuffle=False)

그리고 학습할 MNIST 이미지의 픽셀 크기가 784인데, 480으로 줄인 뒤, 200, 80 순으로 줄이다가 이 크기가 80인 이미지를 10개로 분류하려 한다. 각 과정마다 활성함수 ReLU를 통해 값이 0 미만인 것들은 모두 생략하고 진행하려 한다. 따라서 이 모델은 다음과 같이 짜면 된다.

class Net(nn.Module):
	def __init__(self):
    	super(Net, self).__init__()
    	self.l1 = nn.Linear(784, 480)  # 입력 크기: 784 차원(픽셀)
    	self.l2 = nn.Linear(480, 200)
    	self.l3 = nn.Linear(200, 80)
    	self.l4 = nn.Linear(80, 10)  # 10개로 분류 (출력층)

	def forward(self, x):
    	x = x.view(-1, 784)  # (배치 사이즈, 1, 28, 28) 크기의 데이터를 (배치 사이즈, 784) 형태로 변경
    	x = F.relu(self.l1(x))
    	x = F.relu(self.l2(x))
    	x = F.relu(self.l3(x))
    	return self.l4(x)

이 모델의 이름은 Net이라고 설정했다.

하지만 이 모델을 이용하기 위해선 장치(device)가 필요하므로 사용할 모델 클래스 객체에 .to() 함수를 통해 해당 device를 사용할 수 있도록 한다.

model = Net().to(device)

이렇게 학습 모델까지 모두 설정했으면, 손실함수(loss function)와 최적화(optimization)가 필요하다.

답을 10개로 분류하기로 했으니, 클래스를 여러개로 분류할 수 있게하는 것이 필요하다. 이 때 손실 함수는 cross entropy를 이용한다.

criterion = nn.CrossEntropyLoss()

최적화는 SGD를 사용하려 한다. 손실함수를 여러번 빨리 계산하기 위함으로 보인다.

optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.2)

momentum은 0보다 큰 실수이며, GD를 적절한 방향으로 가속화하며, 흔들림(진동)을 줄여주는 매개변수이다.

이제 모델을 훈련시키려 하는데, 학습할 데이터와 정답 데이터를 따로 분류한다. 데이터를 모델에 입력해서 얻은 가설을 output이라는 변수를 만들어 저장하고, 실제값인 ground truth를 target이라는 변수에 저장한 뒤, 손실함수로 둘을 비교한다. 물론 손실을 낮추기 위해 역전파를 할 필요가 있고, 그 이후에 다시 앞으로 나아가면서 최적화를 한다.

def train(epoch):
	model.train()  # 모델을 학습 모드로 변환
	for batch_idx, (data, target) in enumerate(train_loader):
    	data, target = data.to(device), target.to(device)  # 각 data와 target을 앞서device에 보내는 것
    	optimizer.zero_grad()  # 각 학습마다 새 기울기를 계산하기 위해 0으로 초기화
    	output = model(data)  # data를 모델에 넣어서 가설(hypothesis) 얻기
    	loss = criterion(output, target)  # 가설인 output과 groundtruth인 target을 비교해서 loss계산
    	loss.backward()  # 역전파 알고리즘으로 loss계산
    	optimizer.step()  # 모델 가중치 업데이트
    	if batch_idx % 100 == 0:
        	print('Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:.8f}'.format(
            epoch, batch_idx * len(data), len(train_loader.dataset),
            100. * batch_idx / len(train_loader), loss.data))

loss += criterion(output, target).data.item()

  • .item(): 딕셔너리 값들을 쌍으로 불러냄

pred = output.data.max(1, keepdim=True)[1]

  • 가설 output의 최대값을 얻기 위한 max() 함수에서 1은 어느 방향으로 max 값을 찾을지를 의미하고, 여기서 출력되는 max값은 값과 인덱스다. 여기서 필요한 것은 레이블(label)인데, 여기에 해당하는 것은 인덱스이므로 두 번째 원소를 불러오기 위해 [1]을 입력한다.

correct += pred.eq(target.data.view_as(pred)).cpu().sum()

  • pred 변수에서 예측한 값이 맞을 가능성이 높다면 이 correct 변수의 값이 점점 높아지면서 최종 정확도가 높아진다.
def test():
	model.eval()
	loss = 0
	correct = 0
	for data, target in test_loader:
    	data, target = data.to(device), target.to(device)
    	output = model(data)
    	loss += criterion(output, target).data.item()
    	pred = output.data.max(1, keepdim=True)[1]
    	correct += pred.eq(target.data.view_as(pred)).cpu().sum()
	loss /= len(test_loader.dataset)
	print('\nTest set: Average loss: {:.4f}, Accuracy: {}/{} ({:.0f}%)\n'.format(
    loss, correct, len(test_loader.dataset), 100. * correct / len(test_loader.dataset)))

결과
Test set: Average loss: 0.0024, Accuracy: 9556/10000 (96%)

for epoch in range(1, 11):
	train(epoch)
	test()

함수 train과 test를 1에서 10까지 10번 학습하게 한다.

image_data = test_dataset[0][0].to(device)
image_label = test_dataset[0][1]
print('숫자 이미지 X의 크기:', image_data.size())
print('숫자 이미지 X의 레이블:', image_label)
print(model(image_data))

위와 같이 해당 숫자의 이미지 데이터와 레이블(label)을 출력하고, 그 데이터의 텐서 구조도 같이 확인한다.

profile
국민대 전자공학부, 서강대학교 석사과정, 크래프톤 정글 2기

0개의 댓글