아래의 링크를 참고하여 작성하였습니다.
<합성곱 신경망>
drive.google.com/drive/folders/1qVcF8-tx9LexdDT-IY6qOnHc8ekDoL03
kmooc에 정말 좋은 강의가 있다. 회원가입을 감수할 수 있다면 아래의 링크들을 참고하길 바란다. 청강이 된다!
다만 조금 디테일은 다른 것 같지만 채널에 대한 이해는 더 편하게 된 것 같다.
그리고 이걸 보다 알게 된 건데 머신러닝의 꽃은 강화학습이라더라...
<visdom>
<오늘의 사족>
본 포스팅에서는 cuda를 사용하지 않습니다.
cuda를 깔기 위해서 적어도 한 시간 이상은 헤맸으나, NVIDA가 제공되지 않는 사양이더군요...
모두 CPU를 통해서 계산했으며, 시간 소요가 꽤 있었습니다.
그래서 혹시 GPU 계산이 가능하시다면 위의 주소를 참고해서 꼭 cuda를 이용해보시길 바랍니다..ㅜㅜ
개요
1. 합성곱 신경망 배경
2. 채널 / 깊이
3. 합성곱 연산
4. 제로 패딩
5. 가중치와 편향
-1) 가중치
-2) 편향
6. 특성 맵의 크기 계산
7. 다수의 채널을 가진 합성곱의 연산
8. 풀링
9. MNIST 를 통한 계산
요렇게 생긴 걸 배운다!
1. 합성곱 신경망 배경
앞에서 MNIST를 계산할 때 어떻게 했는지 생각해보자!
데이터들은 이렇게 생겼다.
대부분의 이미지 사진은 28*28 의 픽셀로 이루어져있다. 아래의 테이블이 가로가 28, 세로가 28이라고 생각하면 된다.
각각의 픽셀에는 특정한 값이 있다. 아래의 테이블의 셀처럼 말이다.
MNIST의 데이터들로 예를 들자면, 명암의 정도라고 생각하면 된다. 0은 검은색이고 숫자가 높을수록 흰색. 테이블은 가상의 숫자라 위의 것과는 맞지 않는다.
0 | 0 | ... | 1 |
2 | 1 | ... | 0 |
... | ... | ... | ... |
3 | 2 | ... | 0 |
자, 지금 이 데이터는 2차원이다. (28,28)
우리는 이것을 torch.view 를 이용해서 일차원 벡터로 만들었다. (1, 28*28)
아래처럼 길게 말이다.
0 | ... | 1 | 2 | ... |
위의 2차원 테이블의 [0,0] 셀을 보자. 셀의 왼쪽 픽셀값은 0이고, 아래 픽셀값은 2이다.
그리고 1차원 벡터의 [0] 셀을 보자. 셀의 왼쪽 픽셀값은 0이다.
분명히 문제가 있다. 왜냐하면
-공간 정보 소실
-하나의 픽셀 값에 예민함
그 대안으로 합성곱 신경망이 떠오르게 된다.
다층 퍼셉트론에서는 input=>차원변경=>가중치의 합=>활성화함수=>output 이런식이었다면,
합성곱 신경망에서는 input=>특징 추출=> 차원변경 ... 의 과정을 겪는다
여기서 특징 추출이 바로 공간 정보 소실의 한계를 극복하기 위한 메인이다.
2. 채널 / 깊이
기본적으로 이미지는 3차원 텐서다. [높이, 너비, 채널]
높이.. 너비.. 이것들은 이해 가능한데 그렇다면 채널은 무엇인가?
앞의 MNIST의 경우만 생각하지 말고, 실제 이미지로 눈을 돌려보자.
구글의 시작화면이다.
이것과 비교했을 때 뭐가 가장 다른가? 색의 유무다!
대부분의 이미지는 색이 있다. 색을 나타내는 것을 쉽게 채널이라고 생각하면 된다.
보통은 RGB 로 채널을 나눈다.
그래서 위의 이미지는 (28,.28,3)의 텐서이다.
채널은 깊이라고도 부른다.
채널에 관해서는 헷갈리는 부분이 있으므로 그건 나중에 보도록 하자.. 여기서 굉장히 헤맸다.
3. 합성곱 연산
(채널이 하나인 경우만 생각하자)
자 그러면 이제 합성곱 연산에 대해서 알아보자. 이 연산의 목적은 이미지의 특징을 공간 정보 손실 없이 추출해내는 것이다.
그렇기 위해서 우리는 커널 혹은 필터를 이용해서 그 특징을 잡아낸다. 커널과 매핑되는 입력값의 픽셀들만 계산을 하는 것이다. 그림을 통해서 쉽게 이해할 수 있을 것이다. 커널의 사이즈는 그림에서 3*3을 이용했지만 5*5 가 될 수 있다.
스트라이드는 커널의 이동거리를 의미한다. 1번에서 2번으로 넘어과는 과정을 보면, 커널이 매핑되는 위치가 딱 1만큼 옮겨졌음을 볼 수 있다. 스트라이드 역시 얼마든지 사용자가 조절할 수 있다.
합성곱 연산을 통해서 얻어진 결과를 우리는 특성 맵이라고 한다.
여기서 중요한 것은 커널/필터는 채널과는 다른 것이다!
4. 제로 패딩
위의 그림에서도 보면 알 수 있지만, 우리의 입력과 특성맵은 그 크기가 달라져있다.
그래서 여러번 convolution layer를 거치면서 어느 정도 특성맵의 크기를 조절해주는 것이, 제로 패딩이다.
이런 식으로 input옆에 0으로 가득찬 테두리 액자를 붙여준다.
아래에서는 하나만 붙였지만 얼마든지 늘릴 수 있다.
5. 가중치와 편향
convolution layer을 통해서 특징을 추출했다면, 그 다음은 우리가 이전에 했던 것처럼 가중치의 합을 구하고 활성화 함수를 통해서 output을 계산하면 된다. 여기에서 가중치의 합을 구해줄 때는 지금까지 layer를 거친 3차원의 텐서를 다시 2차원으로 바꿔준다. 이미 위치 정보를 고려하여 텐서를 만든 것이니, 2차원으로 바꾸어도 그 손실이 거의 없을 것이다.
-1) 가중치
다층퍼셉트론과 합성곱 신경망을 비교하면 다음과 같다.
다층 퍼셉트론에서는 저 노드들을 연결하는 모든 것들이 가중치이다. 따라서 9*4 =36개가 필요하다. 반면에,
합성곱 신경망에서는 특징을 추출하는데 전 과정에서 4개의 가중치만 필요하다.
또한 이 4개를 매핑된 지역의 모든 셀에 사용하는 것이 아니라, 매핑된 픽셀에만 사용한다.
그렇기 때문에 적은 가중치로도 공간 정보를 유지할 수 있다는 것이다!
편향은 커널과 합성곱 연산을 한 특성 맵에 더해준다. 하나의 scalar 값만 가지며, 모든 셀에 더해준다.
6. 특성 맵의 크기 계산
$\mathrm{I_{h}}, \mathrm{I_{w}}$: 입력의 높이,너비
$\mathrm{K_{h}}, \mathrm{K_{w}}$: 커널의 높이,너비
$\mathrm{O_{h}}, \mathrm{O_{w}}$: 특성 맵의 높이,너비
$\mathrm{S}$ : 스트라이드
$\mathrm{P}$: 패딩의 폭
특성 맵의 계산은
$\mathrm{O_{h}}=\mathrm{floor} (\frac{mathrm{I_{w}}-\mathrm{K_{h}}+2\mathrm{P}}{\mathrm{S}}+1)$
너비도 마찬가지이다.
7. 다수의 채널을 가진 합성곱의 연산
지금까지는 하나의 채널만 고려했다. 그러나 앞에서 언급했던 것처럼 색이 있는 이미지인경우, 채널이 존재한다.
굳이 채널이 색일 필요는 없다. 단지 간결한 이해를 위한 예일 뿐이다.
그러면 위와 같이 된다. input에서 각각에 매칭되는 채널과 계산하면 된다.
계산된 특성맵을 모두 더함으로써 얻어지는 것이 최종적인 특성맵이다.
매칭되는 채널과 계산을 하면 된다고 했으니, 반드시 매칭되는 채널이 있어야 한다.
즉, input과 커널의 채널 수는 동일 해야 한다.
채널은 3개지만 결국 필터는 하나인 셈이다! 잊지 말자.
그렇다면 많은 필터가 있다면 어떻게 될까?
위에가 바로 그 경우다.
위의 경우 채널은 $\mathrm{C_{i}}$로 input과 커널이 동일하다.
다만 필터가 이 경우에는 $\mathrm{C_{o}}$이다.
그런데 결과가 어떤가?
output 의 채널 값이 변했다.
이 과정에 주목하자.
8. 풀링
풀링은 제로 패딩과는 반대인 셈이다.
특성 맵을 다운 샘플링한다. 즉 하나의 값만 추출하는 것이다.
크게 두가지이다. 최대 풀링이나 평균 풀링이다. 직관적으로 이름만 들으면 이해할 수 있다.
CNN은
colvolution => pooling => activation function => convolutin 2=>..... 이런 식으로 이루어진다.
그렇게 특징을 다 추출했다고 하면 다층 퍼셉트론의 과정을 거치면 된다.
activation function을 거친 output(여기서는 input) => fully connect => hidden layers => ... =>ouput!
다시 이미지 사진을 확인하자.
9. MNIST 를 통한 계산
visdom이라는 건 페이스북에서 만들었다고 한다. 그래서 기본 배경이 파랑인가?
그런데 이렇게 동적으로 움직이는건 plotly 라이브러리에서도 가능하다. 그냥 알아두자!
import torch
import torch.nn as nn
import torchvision.transforms as transforms
import torchvision.datasets as dsets
import torch.nn.init
기본 라이브러리 임포트
import visdom
vis=visdom.Visdom()
그런데 visdom을 이용하기 위해서는 참고로 알아야 하는 것이 미리 terminal을 열어서 아래의 코드를 입력해야 한다.
python -m visdom.server
아래는 visdom 에 그려줄 그래프 함수를 미리 설정하는 것이다.
def loss_tracker(loss_plot,loss_value,num):
vis.line(X=num,Y=loss_value,win=loss_plot,update='append')
기본 파라미터
learning_rate=0.001
training_epochs=15
batch_size=32
데이터 로드
mnist_train=dsets.MNIST(root='MNIST_data/',
train=True,transform=transforms.ToTensor(),
download=True)
mnist_test=dsets.MNIST(root='MNIST_data/',
train=False,transform=transforms.ToTensor(),
download=True)
배치
data_loader=torch.utils.data.DataLoader(dataset=mnist_train,batch_size=batch_size,
shuffle=True,drop_last=True)
모델 생성
class CNN(nn.Module):
def __init__(self):
super(CNN, self).__init__()
self.layer1 = nn.Sequential(
nn.Conv2d(1,32,kernel_size=3, stride=1, padding=1),
nn.ReLU(),
nn.MaxPool2d(2)
)
self.layer2 = nn.Sequential(
nn.Conv2d(32,64, kernel_size=3, stride=1, padding=1),
nn.ReLU(),
nn.MaxPool2d(2)
)
self.layer3 = nn.Sequential(
nn.Conv2d(64,128, kernel_size=3, stride=1, padding=1),
nn.ReLU(),
nn.MaxPool2d(2)
)
self.fc1 = nn.Linear(3*3*128, 625)
self.relu = nn.ReLU()
self.fc2 = nn.Linear(625, 10, bias =True)
torch.nn.init.xavier_uniform_(self.fc1.weight)
torch.nn.init.xavier_uniform_(self.fc2.weight)
def forward(self, x):
out = self.layer1(x)
out = self.layer2(out)
out = self.layer3(out)
out = out.view(out.size(0), -1)
out = self.fc1(out)
out = self.relu(out)
out = self.fc2(out)
return out
여기가 가장 이해하기 어려웠는데 보면,
공식 문서를 보면 Conv2d는 아래와 같이 인자를 받는다.
input과 output의 채널이 다르다는 것인데...
이게 도무지 이해가 가지 않았는데 지금 보면 쉬운 거였다.!
output의 채널은 필터의 채널 수에 영향을 받는 것이 아니고, 필터의 수로 바뀌기 때문이다!
무슨 말이냐면
self.layer2를 한번 보자. input 채널이 32인데 output 채널이 64이다.
그럼 합성곱 층의 필터의 채널 수는 32이고, 필터의 수 그 자체는 64인 것이다.
stackoverflow에 질문 올리자마자 깨달았다. 하여튼 다른 사람에게 뭘 물어보면 바로 이해가 된다니까.
비용함수 설정
criterion=nn.CrossEntropyLoss()
optimizer=torch.optim.Adam(model.parameters(),lr=learning_rate)
그래프 그리는 함수인 loss_tracker에 넣을 인자
loss_plot=vis.line(Y=torch.Tensor(1).zero_(),opts=dict(title='loss_tracker',legend=['loss'],showlegend=True))
딥러닝!
total_batch=len(data_loader)
for epoch in range(training_epochs):
avg_cost=0
for X,Y in data_loader:
optimizer.zero_grad()
hypothesis=model(X)
cost=criterion(hypothesis,Y)
cost.backward()
optimizer.step()
avg_cost+=cost/total_batch
print(f"Epoch: {epoch+1:3} cost: {avg_cost:.6f}")
loss_tracker(loss_plot,torch.Tensor([avg_cost]),torch.Tensor([epoch]))
근데 진짜 오래 걸리긴 했다! 다층 퍼셉트론의 곱절은 걸린 듯
정확도 계산
with torch.no_grad():
X_test=mnist_test.test_data.view(len(mnist_test),1,28,28).float()
Y_test=mnist_test.test_labels
prediction=model(X_test)
correct_prediction=torch.argmax(prediction,1)==Y_test
accuracy=correct_prediction.float().mean()
print(f"Accuracy : {accuracy}")
살짝 다르지만 실은 이 소수점 이하도 굉장히 중요한 거였다.
데이콘을 하다보니 그 작은 숫자에 집착을 할 수 밖에 없어졌다.. 사실 지금도 이 차이가 엄청 큰 것처럼 느껴진다.
visdom에 그려진 결과물
error 가 잘 줄어들고 있다!
다음에는 직접 input set을 만들어서 분류하는 거다! 재밌겠지?!
'딥러닝 > 모두를 위한 딥러닝 2' 카테고리의 다른 글
07 인공지능 뉴런 artificial neural network (0) | 2021.04.01 |
---|---|
06 소프트맥스 회귀 softmax regression (0) | 2021.03.31 |
02-05 regression pytorch (1) | 2021.03.30 |
02-05 regression 이론 (0) | 2021.03.24 |
02-05. regression (0) | 2021.03.23 |
댓글