AI study – week 4

by

in

1. CNN

이번에는 CNN 에 대해서 알아볼 차례이다. NN (Neural Network) 은 지난 시간에 Spiral data 를 Classify 하기 위해서 사용했었는데, 간단히 정리해보자면 데이터가 입력계층, 은닉계층, 출력계층을 거치며 가중치에 따라 출력이 정해지게 되는 구조이다. (각각의 Class 가 될 확률을 출력)

물론 앞서 실습해보았던 $x, y$ 좌표에 따른 Class 는 Quantitative data 였기에, 별 고민 없이 바로 입력에 $x, y$를 넣을 수 있었지만, 우리가 흔히 생각하는 이미지의 경우에는 다르다. 직관적으로 생각해 보았을 때도, 이미지는 2차원 데이터로써 존재하기에 차원이 존재하지 않는 단순 NN 에 사용하기에는 손실되는 정보 (위치와 주변 픽셀과의 조합으로써 존재하는 정보 등)가 너무나 많다. 따라서 이미지의 데이터를 손실되지 않게끔 NN 에 적용하기 위하여 CNN 이 탄생하게 되었다.

CNN
CNN 구조

위 그림에서 보다시피, Input data 가 이미지 자체로 들어가는 것을 볼 수 있다. 하지만 layer을 거치며 점점 크기는 작아지고 두께는 두꺼워지게 되는데, 여기서 두께가 의미하는 것은 채널의 수, 즉 NN 에서의 노드 개수를 의미한다. 따라서 점점 이미지의 크기를 줄여나가는 방식으로 진행되게 된다.

자, 그렇다면 어떻게 이미지를 줄여나가는 것일까?

1) Kernel

Kernel 은 $n \times n$ 꼴의 행렬이다. 백번 입으로 설명하기보다 이미지로 보는 것이 더욱 효율적일 것 같으니 아래 그림으로 먼저 살펴보자 ㅎㅎ

위의 그림에서 보다시피, kernel 은 input 행렬을 스캔하며 합성곱 (CNN의 풀네임은 Convolutioinal NN으로, 합성곱 신경망을 뜻한다) 을 output 으로 출력한다. 그림의 $2 \times 2$ 행렬이 $5 \times 5$ 이미지를 스캔하며 $4 \times 4$ 꼴의 행렬을 출력하는 것을 볼 수 있다. 당연하게도 kernel 의 크기가 점점 커질수록 이미지의 폭이 줄어드는 정도는 더욱 커질 것이다.

하지만, 예상했다시피 단순히 위의 방식으로만 kernel 연산을 진행한다면 수많은 layer 들을 거쳐야만 작은 크기로 줄일 수 있을 것이다. 실제로 $512 \times 512$ 정도의 이미지가 들어오게 되면, 약 500번의 kernel 들을 사용해야만 충분히 작은 크기로 이미지를 줄일 수 있을 것이다.

이에 따라 등장한 2가지 변수가 있는데, 바로 PaddingStride 이다.

먼저, Padding 은 암호학에서도 들어볼 수 있는데, 기존의 데이터의 겉에 어떤 값들을 덧붙여 사이즈를 강제로 맞추어주는 행위이다. 위 그림에서도 볼 수 있듯이, $6 \times 6$ Input 이 들어왔음에도 불구하고, padding 을 1로 설정하여 각 변의 겉부분에 길이 1인 padding 을 진행해주었다. (우리가 입는 패딩과 비슷하죠..? ㅎㅎ)

두번째로 Stride은 직역하면 “보폭” 이라는 뜻으로, kernel 의 보폭을 의미한다. 다시 말해 몇 칸씩 건너뛰어 합성곱을 계산할 것인지를 의미한다. 위의 그림에서 보다시피 padding 이 완료된 데이터는 총 $8 \times 8$이 되는데, Stride 를 2로 설정하여 결국 $4 \times 4$ 크기의 output 이 나왔다.

이렇게 Stride, Padding, Input data의 크기에 따라 Output 의 크기가 달라지는 것을 볼 수 있는데, 추후에 코드를 짤 때는 직접 계산해야 한다. 따라서 이를 수식으로 정리해보면, 다음과 같다.

$$Width(output)= \frac{Width(input)-Size_{kernel}+2 \times Padding}{Stride}+1$$ $$Height(output)= \frac{Height(input)-Size_{kernel}+2 \times Padding}{Stride}+1$$

간단하게 $28 \times 28$ 크기의 이미지를 kernel_size = 3, stride=1, padding=1 (여기서 padding 은 output 에 적용되는 것이다) 의 변수들로 output 크기를 계산해 보았을 때, $\frac{28 – 3 + 2 \times 1}{1} + 1$ 이기 때문에 output 이 그대로 $28 \times 28$ 이 되겠다.

2) Pooling

하지만 아직 부족하다. 여전히 매우 많은 layer 들을 거쳐야 하고, 보다 효과적인 차원 감소 (Dimension Reduction) 방법이 필요하다. 여기서 나는 왜 이미지 사이즈를 줄이는 것을 “차원 감소” 라고 부르지? 라는 생각을 했었는데, 단순 이미지 크기 감소는 channel 의 수를 유지하지 못한다. 따라서 이 channel 수를 유지하며 (이는 곧 feature의 수를 줄여 Overfitting을 방지하는 효과도 있다.) 이미지 크기를 줄이는 것이 바로 Pooling 이라는 방식이다.

또한 특징 추출(Feature Extraction) 효과도 있는데, 후에 다룰 MaxPool은 각 구간에서 가장 큰 값을 채택하기에 가장 특징이 강한 부분을 추출해내어 더욱 효과적인 학습이 가능하기도 하다.

보통 Pooling 에는 크게 2가지 방식이 있는데, 앞서 말했던 Max Pooling 과 Average Pooling 이다. $n \times n$ 사이즈로 행렬을 잘라 그 구간 내에서 Max 값을 뽑거나 Average 값을 계산하여 뽑는 방식이다. (매우 간단하지만 학습의 효율과 정확도를 위해 필수적인 과정이다.)

3) ReLu

그 후에, 계속해서 Convolution 과 Pooling 작업을 반복하게 되는데, NN 이 아닌 CNN 또한 당연하게도 ReLu function 과 같은 Activation 함수가 적용되어야 한다. 비선형성을 추가하기 위하여 보통 Convolution $\rightarrow$ ReLu $\rightarrow$ Pooling 순서로 layer 을 진행해준다.

4) Full Connected Layer

이렇게 차원 축소를 진행하여 큰 $n \times n$ 크기의 이미지를 작게 만들고 나면, 결국은 Classify 를 진행해야 한다. 차원 축소가 효과적이라곤 해도, 예를 들어 $2 \times 2$ 크기만큼 줄여버리게 되면, 학습에 진행될 feature 이 매우 줄어들어 학습이 되지 않는다. 따라서 적당히 작은 크기의 행렬로 만들어 Classify 를 진행해야 한다.

그리고 CNN 은 그 Classify 방식으로 축소한 행렬을 일렬로 늘어놓아 기존의 NN 방식을 채택한다. 지금까지 이미지 자체의 정보를 유지하기 위하여 행렬로써 CNN 을 진행했었는데, 왜 갑자기 일렬로 차원을 줄여 이미지의 정보성을 삭제하느냐 물을 수 있겠지만, 사실 그 외에 Classify로 진행할 방법이 없기 때문인 것 같다. 현재 내가 블로그를 쓰는 시점에도 NN 말고 더 효과적인 방법이 존재하지 않을까 싶지만, 그 외에 Classify 를 진행할 모델이 없기에 이를 사용하는 것 같다는 생각이 든다.

이미지를 Flattening 시켜서 이렇게 NN 모델에 넣게 되고, 이는 앞서 배웠던 Spiral data를 Classify 하는 것과 별반 다르지 않다.

2. MNIST image Classify

이제 이미지를 Classify 할 수 있는 CNN 기법에 대해 알아보았으니, 직접 MNIST의 숫자 이미지 데이터를 통해서 CNN 을 학습시켜보자.

듣기로 MNIST 는 상당히 학습에 용이한 모델을 제공하기 때문에, 최소 98% 이상의 학습 정확도를 가지고 있어야 한다고 한다.

먼저, 모델을 설계해야 하는데, MNIST 이미지 데이터를 받아왔을 때, 크기가 $28 \times 28$ 인 것을 확인할 수 있다. 따라서 다음과 같이 CNN 모델을 설계할 수 있다.

#(channel num, width, height)
(1,28,28) → {(32,28,28) → (32,14,14)} → {(64,14,14) → (64,7,7)} → (1, 3136) → (1, 1024) → (1, 128) → (1, 10)
Python

결국 마지막에 출력계층은 10가지 숫자 중 1개로 나와야 하므로 $(1, 10)$ 이 되겠고, 계속해서 Convolution 과 MaxPooling 을 이용해서 차원을 줄여나갈 수 있다.

이대로 모델을 짠다면 다음과 같은 코드가 되겠다.

class CNN(nn.Module):

  def __init__(self):
    super(CNN, self).__init__()
    self.layer1 = nn.Sequential(
      nn.Conv2d(in_channels = 1, out_channels = 32, kernel_size = 3, stride=1, padding=1, bias=True, ),
      nn.ReLU(),
      nn.MaxPool2d(2),
    )

    self.layer2 = nn.Sequential(
      nn.Conv2d(in_channels = 32, out_channels = 64, kernel_size = 3, stride=1, padding=1, bias=True, ),
      nn.ReLU(),
      nn.MaxPool2d(2),
    )

    self.linear = nn.Sequential(
      nn.Linear(3136, 1024),
      nn.Linear(1024, 128),
      nn.Linear(128, 10)
    )
  
  def forward(self, x):
      x = self.layer1(x)
      x = self.layer2(x)
      x = x.reshape(-1, 3136)
      x = self.linear(x)
      # x = nn.Softmax(x)
      
      return x
Python

주의할 부분은 forward 에 reshape 부분으로, 이미지를 Flatten 해주는 작업이다. 마지막에 Softmax 함수를 이용해서 더욱 확률을 증가시켜줄 수 있지만, 일단은 사용하지 않기로 했다.

그리고 학습은 다음과 같이 진행해줄 수 있다.

from torch.utils.data import DataLoader

transform = transforms.Compose([
    transforms.ToTensor(),
    transforms.Normalize((0.5,), (0.5,))
])

train_MNIST = torchvision.datasets.MNIST('./data', train=True, transform=transform)
test_MNIST = torchvision.datasets.MNIST('./data', train=False, transform=transform)

model = CNN()
learning_rate = 1E-3
optimizer = torch.optim.Adam(model.parameters(), lr = learning_rate)

epoch = 3
batch_size = 100

train_loader = DataLoader(train_MNIST, batch_size=batch_size, shuffle=True)
test_loader = DataLoader(test_MNIST, shuffle=False)

# From saneo
def accuracy(predict, truth):
    return torch.sum(torch.argmax(predict, axis=1)==truth)/predict.size()[0]
    
# train
for epoch_cnt in range(epoch):
  for batch_idx, (data, answer) in enumerate(train_loader):
  
    optimizer.zero_grad()
    prediction = model(data)

    loss_function = nn.CrossEntropyLoss()
    loss = loss_function(prediction, answer)

    loss.backward()
    optimizer.step()

    last_loss=loss.data
    
  with torch.no_grad():
    cnt = 0
    for index, (input, output) in enumerate(test_loader):
        prediction = model(input)
        prediction = torch.argmax(prediction, dim=1)
        cnt += torch.sum(prediction==output)
    print(f"acc : {round(int(cnt)/100,2)}%, loss : {last_loss}")
Python

이미지를 텐서로 변경하고, 이를 표준화 해줌으로써 더욱 효과적인 학습을 진행할 수 있다. 또, train dataset과 test dataset 을 각각 다운받은 뒤에, 언제나 그랬든 Saneo의 accuracy 함수를 인용하여 정확도를 측정해주었다. (각각의 epoch 마다 출력했다.)

결과는 다음과 같다.

정확도가 98%로 수렴했고, Over fitting 된다면 100%가 나오는 것을 감안하면, 적절히 학습이 진행되었음을 확인해볼 수 있다 !!

“AI study – week 4”에 대한 2개의 응답

  1. sklep 아바타

    Wow, fantastic blog layout! How long have you ever been running a blog for?
    you made blogging look easy. The overall glance of your website is excellent, let alone
    the content! You can see similar here sklep online

  2. dobry sklep 아바타

    Wow, marvelous blog format! How lengthy have you ever been running a
    blog for? you make running a blog look easy. The full
    look of your site is wonderful, let alone the content material!
    You can see similar here sklep internetowy

답글 남기기

이메일 주소는 공개되지 않습니다. 필수 필드는 *로 표시됩니다