Data Science/Tensorflow2.0, Pytorch

pytorch) 합성곱 신경망

HJChung 2020. 6. 18. 12:00

합성곱 신경망이란?

 

뉴런이 모여 서로 연결된 형태인 인공신경망을 알아보았고 구현을 해 보았습니다. 그러나 이미지 데이터의 경우 해당 모델의 성능이 좋지 않습니다.

그 이유는

1. 이미지는 하나의 숫자가 아닌 공간적 특성(weight x height x channel)이 있고, 

2. 예외적 모양(예를 들어 그냥 X와 기울어진 X)이 들어올 때 마다 인공 신경망은 전혀 다른 값으로 인식하여 가중치들을 다시 학습시켜야합니다. 이미지의 크기가 커질수록 이런 변형의 가능성은 더 커집니다. 

그래서 동물의 시각 뉴런에 대한 연구로부터 얻은 개념을 바탕으로 1980년대 네오코그니트론을 거쳐 Yann LeCun 교수의 합성곱 신경망(CNN)이 개발되었습니다. 

 

그래서 합성곱 연산에서는 filter로 이미지를 슬라이드하면서 계산하여 공간적 특성을 유지 할 수 있는 방식이 적용됩니다.

이처럼 한 filter가 이미지를 이동하면서 convolution연산을 통해 하나의 dot product가 계산되고, 다 수행하면 그 결과로 activation map( = feature map)이 생성됩니다.

또한 하나의 결과값이 생성 될 때 입력값 전체가 들어가지 않고 filter가 지나가는 부분만 연산에 ㅍ포함되고, 하나의 이미지에 같은 filter를 연달아 사용하기 때문에 가중치(weight)가 공유되어 학습이 필요한 변수가 적습니다. 

 

https://libertegrace.tistory.com/category/Study/Convolutional%20Neural%20Network%28CNN%29

libertegrace.tistory.com/entry/Lecture-5-Convolutional-Neural-Networks

 

Lecture 5. Convolutional Neural Networks

1.    A bit of history 1)    Hubel & Wiesel의 연구 연구진은 고양이의 뇌에 전극을 이식하여 여러 시각적 자극을 뇌에서 어떻게 인식하는지에 대해 연구하였습니다. 연구 결과, 각 시각 세포들이 여��

libertegrace.tistory.com

Pytorch에서의 합성곱 신경망 구현

0. 필요한 모듈 불러오기

#0. 모듈 불러오기
import torch
import torch.nn as nn #신경망 모델들이 있는 라이브러리
import torch.optim as optim #경사하강법 알고리즘이 있는 라이브러리
import torch.nn.init as init #텐서에 초기값을 주기 위해 필요한 함수들이 있는 라이브러리
#torchvision: 유명한 영상처리용 데이터셋, 모델, 이미지 변환기가 들어있는 패키지
import torchvision.datasets as dset #torchvision에서 데이터를 읽어오는 라이브러리
import torchvision.transforms as transforms #불러온 이미지를 필요에따라 변환해주는 라이브러리
from torch.utils.data import DataLoader #데이털를 모델에 전달할 때, batchsize로 묶어서 전달하고, 효율적인 학습을 위해 어떤 규칙에 따라 정렬하거나 섞는데 필요한 라이브러리

1. hyperparameter 설정

#1. hyperparameter설정
batch_size = 256
learning_rate = 0.0002
num_epoch = 10

2. 앞에서 불러온 모듈을 사용하여 데이터를 읽어오고(torchvision.datasets), 불러온 이미지를 필요에 따라 변환하고(torchvision.transforms), 데이터 알맞게 생성(DataLoader)

  • torchvision.datasets.MNIST(root[경로], train[학습 데이터를 불러올지(True), 테스트 데이터를 불러올지(False)]=True, transform[이미지데이터에 대한 변형]=None, target_transform[label에 대한 변형]=None, download[현재 경로에 MNIST 데이터가 없으면 다운로그하겠다.]=False)
  • torch.utils.data.DataLoader(dataset, batch_size[batch_size개수만큼 묶는다]=1, shuffle[shuffle 여부]=False, sampler=None, batch_sampler=None, num_workers[데이터를 묶을 때 사용할 프로세스 개수]=0, collate_fn=None, pin_memory=False, drop_last[묶고 남는 데이터는 버릴지 여부]=False, timeout=0, worker_init_fn=None)
#2. 데이터 생성
mnist_train = dset.MNIST("./", train=True, transform=transforms.ToTensor(), target_transform=None, download=True)
mnist_test = dset.MNIST("./", train=False, transform=transforms.ToTensor(), target_transform=None, download = True)

train_loader = torch.utils.data.DataLoader(mnist_train, batch_size=batch_size, shuffle=True, num_workers=2, drop_last=True)
test_loader = torch.utils.data.DataLoader(mnist_tesst, batch_size=batch_size, shuffle=False, num_workers=2, drop_last=True)

3. 모델 및 loss function 생성

  •  __init__(self): 모델의 각 연산들이 정의
  • super: CNN 클래스의 부모 클래스인 nn.Module을 초기화
  • forword(self, x): 연산들을 순차적으로 실행하여 결과값만 리턴
  • view(batch_size, -1): view함수의 인수에 목표한는 새로운 형태인 [batch_size, -1]를 전달합니다.. 즉 view는 텐서의 형상을 reshape,or 차원을 늘리거나 줄일 때 사용하며 -1은 -1인 부분을 알아서 계산하란는 것입니다. 이렇게 형태를 바꿔주는 이유는 합성곱연산ㄴ에서 요구되는 텐서의 형태와 Linear 연산에서 요구되는 텐서의 형태가 다르기 때문입니다. 
#3. 모델생성
class CNN(nn.Module):
	def __init__(self): #__init__: 모델의 각 연산들이 정의
        super(CNN, self).__init__() #super: CNN 클래스의 부모 클래스인 nn.Module을 초기화
        self.layer = nn.Sequential(
            nn.Conv2d(1, 16, 5),#Conv2d: torch.nn.Conv2d(in_channels: int, out_channels: int, kernel_size: , stride=1  , padding=0 으로 기본 설정되어있음 ...)
            nn.ReLU(),
            nn.Conv2d(16, 32, 5),
            nn.ReLU(),
            nn.MaxPool2d(2,2),
            nn.Conv2d(32, 64, 5),
            nn.ReLU(),
            nn.MaxPool2d(2, 2),
         )
       self.fc_layer = nn.Sequential(
            nn.Linear(64*3*3, 100),
            nn.ReLU(),
            nn.Linear(100, 10)
       )
       
   def forward(self, x): #연산들을 순차적으로 실행하여 결과값만 리턴
   	out = self.layer(x)
        out = out.view(batch_size, -1)
        out = self.fc_layer(out)
        return out
#3. loss function 생성
use_cuda = not no_cuda and torch.cuda.is_available()
device = torch.device("cuda" if use_cuda else "cpu")

loss_func = nn.CrossEntropyLosss()

4. 최적화 함수 선언

#4. 최적화 함수 선언
optimizer = torch.optim.Adam(model.parameters(), lr = learning_rate)

5. 모델 불러오고 학습진행

#5. 모델 불러오고 학습진행
model = CNN().to(device)

loss_arr = []

for i in range(num_epoch):
	for j,[image, label] in enumerate(train_loader):
        	x = image.to(device) #.to(device): 이미지 데이터를 cpu 또는 gpu와 같은 device에 compile: enumerate로 나온 data와 label을 device(내 경우엔 cpu)에 탑재를 한다. 
          	y = label.to(device)
        
        optimizer.zero_grad() #optimizer를 clear해서 새로운 최적화 값을 찾기 위해 준비
        output = mode.forward(x) # 준비한 데이터를 model에 input으로 넣어 output을 얻음
        loss = loss_func(output, target) # Model에서 예측한 결과를 앞에서 설정한 Loss Function에 넣음
        loss.backward() #Back Propagation을 통해 Gradients를 계산
        optimizer.step() ##optimizer에다가 업데이트
        
         #학습이 잘 되는지 확인하는 Log
         if j%1000 == 0:
             print(loss)
             loss_arr.append(loss.cpu().detach().numpy())

6. 학습된 모델을 테스트 데이터에 대해 검증

#6. 학습된 모델을 테스트 데이터에 대해 검증해보는 부분
correct = 0
total = 0

with torch.no_grad():#no_grad(): 이 조건에서 테스트를 진행하는데, 이는 기울기를 계산하지 않겠다는 것을 의미한다. 
  for image, label in test_loader:
    	x = image.to(device)
        y = label.to(device)
        
        output = model.forward(x)
        _, output_index = torch.max(output, 1) #torch.max: 최대값과 그 인덱스를 구하고, 그 인덱스가 정답인 label과 일치하는지에 따라 맞는 개수를 변수에 더해준다. 
        
        total += label.size(0)
        correct += (output_index == y_).sum().float()
        
   print("Accuracy of Test Data:{}".format(100*correct/total))
        
        

 

Reference

www.yes24.com/Product/Goods/73741253