import torch
import torchvision
import matplotlib.pyplot as plt
1. imports
'figure.figsize'] = (4.5, 3.0) plt.rcParams[
2. CNN 핵심 레이어
A. torch.nn.ReLU
B. torch.nn.MaxPool2d
C. torch.nn.Conv2d
Note
입력이 1장의 흑백이미지이고 출력도 1장의 흑백이미지일 경우 컨볼루션 계산과정 요약[1]
- 윈도우생성:
kernel_size
= (?,?) 인 윈도우를 만듦- sub-img생성: 입력 이미지에 윈도우를 통과시켜 (?,?) 크기의 sub-img를 만듦.
- 연산: sub-img의 각 원소에
conv.weight
의 값을 원소별로 (=element-wisely) 곱하고 결과를 더함. (만약에conv.bias
가 있다면 최종결과에 bias를 더함)- 이동&반복: 윈도우를
stride
만큼 이동하여 반복. (stride
=1 이라면 한칸씩,stride
=2 라면 두칸씩 이동)
-
(예시1) 재현
“A guide to convolution arithmetic for deep learning” (Dumoulin and Visin 2016) 에 나온 그림재현

[1] 입력shape=(1,1,?,?) 이고 출력의shape=(1,1,?,?)일 경우
= torch.tensor([
img 3,3,2,1,0],
[0,0,1,3,1],
[3,1,2,2,3],
[2,0,0,2,2],
[2,0,0,0,1]
[1,1,5,5).float()
]).reshape( img
tensor([[[[3., 3., 2., 1., 0.],
[0., 0., 1., 3., 1.],
[3., 1., 2., 2., 3.],
[2., 0., 0., 2., 2.],
[2., 0., 0., 0., 1.]]]])
= torch.nn.Conv2d(in_channels=1,out_channels=1,kernel_size=3,bias=False)
conv = torch.tensor([[[
conv.weight.data 0.0, 1.0, 2.0],
[ 2.0, 2.0, 0.0],
[ 0.0, 1.0, 2.0]
[ ]]])
conv(img)
tensor([[[[12., 12., 17.],
[10., 17., 19.],
[ 9., 6., 14.]]]], grad_fn=<ConvolutionBackward0>)
-
(예시2) 이동평균
= torch.arange(1,17).float().reshape(1,1,4,4)
img img
tensor([[[[ 1., 2., 3., 4.],
[ 5., 6., 7., 8.],
[ 9., 10., 11., 12.],
[13., 14., 15., 16.]]]])
= torch.nn.Conv2d(in_channels=1,out_channels=1,kernel_size=2,stride=1,bias=False)
conv = conv.weight.data*0 + 1/4
conv.weight.data conv.weight.data
tensor([[[[0.2500, 0.2500],
[0.2500, 0.2500]]]])
conv(img)
tensor([[[[ 3.5000, 4.5000, 5.5000],
[ 7.5000, 8.5000, 9.5000],
[11.5000, 12.5000, 13.5000]]]], grad_fn=<ConvolutionBackward0>)
-
(예시3) 2개의 이미지
개념: (1,1,?,?) \(\to\) (1,1,?,?) 의 conv를 observation 별로 적용
conv
에 포함된 파라메터 수는 (1,1,?,?) \(\to\) (1,1,?,?) 인 경우와 (n,1,?,?) \(\to\) (n,1,?,?)인 경우가 동일
= torch.arange(1,33).float().reshape(2,1,4,4)
imgs = torch.nn.Conv2d(in_channels=1,out_channels=1,kernel_size=2,stride=1,bias=False)
conv = conv.weight.data*0 + 1/4 conv.weight.data
imgs
tensor([[[[ 1., 2., 3., 4.],
[ 5., 6., 7., 8.],
[ 9., 10., 11., 12.],
[13., 14., 15., 16.]]],
[[[17., 18., 19., 20.],
[21., 22., 23., 24.],
[25., 26., 27., 28.],
[29., 30., 31., 32.]]]])
conv(imgs)
tensor([[[[ 3.5000, 4.5000, 5.5000],
[ 7.5000, 8.5000, 9.5000],
[11.5000, 12.5000, 13.5000]]],
[[[19.5000, 20.5000, 21.5000],
[23.5000, 24.5000, 25.5000],
[27.5000, 28.5000, 29.5000]]]], grad_fn=<ConvolutionBackward0>)
conv.weight.shape
torch.Size([1, 1, 2, 2])
-
(예시4) 2개의 이미지, 2개의 out_channels
개념: (1,1,?,?) \(\to\) (1,1,?,?) 의 conv를 한번 적용, 그것과 별개로 (1,1,?,?) \(\to\) (1,1,?,?) 인 다른 conv를 적용함. (즉 하나의 observation당 2번 conv변환) 이것을 observation별로 반복
(1,1,?,?) \(\to\) (1,2,?,?) 인 경우는 (1,1,?,?) \(\to\) (1,1,?,?)인 경우보다
conv
에 포함된 파라메터 수가 2배 많음그런데 (1,1,?,?) \(\to\) (1,2,?,?) 인 경우와 (n,1,?,?) \(\to\) (n,2,?,?)인 경우는
conv
에 포함된 파라메터 수가 같음.따라서 (n,1,?,?) \(\to\) (n,2,?,?) 인 경우는 (1,1,?,?) \(\to\) (1,1,?,?)인 경우보다
conv
에 포함된 파라메터 수가 2배 많음
= torch.arange(1,33).float().reshape(2,1,4,4)
img = torch.nn.Conv2d(in_channels=1,out_channels=2,kernel_size=2,stride=1,bias=False) conv
img
tensor([[[[ 1., 2., 3., 4.],
[ 5., 6., 7., 8.],
[ 9., 10., 11., 12.],
[13., 14., 15., 16.]]],
[[[17., 18., 19., 20.],
[21., 22., 23., 24.],
[25., 26., 27., 28.],
[29., 30., 31., 32.]]]])
0] = conv.weight.data[0]*0 +1/4
conv.weight.data[1] = conv.weight.data[0]*0 conv.weight.data[
conv(img)
tensor([[[[ 3.5000, 4.5000, 5.5000],
[ 7.5000, 8.5000, 9.5000],
[11.5000, 12.5000, 13.5000]],
[[ 0.0000, 0.0000, 0.0000],
[ 0.0000, 0.0000, 0.0000],
[ 0.0000, 0.0000, 0.0000]]],
[[[19.5000, 20.5000, 21.5000],
[23.5000, 24.5000, 25.5000],
[27.5000, 28.5000, 29.5000]],
[[ 0.0000, 0.0000, 0.0000],
[ 0.0000, 0.0000, 0.0000],
[ 0.0000, 0.0000, 0.0000]]]], grad_fn=<ConvolutionBackward0>)
conv(img)
tensor([[[[ 3.5000, 4.5000, 5.5000],
[ 7.5000, 8.5000, 9.5000],
[11.5000, 12.5000, 13.5000]],
[[ 0.0000, 0.0000, 0.0000],
[ 0.0000, 0.0000, 0.0000],
[ 0.0000, 0.0000, 0.0000]]],
[[[19.5000, 20.5000, 21.5000],
[23.5000, 24.5000, 25.5000],
[27.5000, 28.5000, 29.5000]],
[[ 0.0000, 0.0000, 0.0000],
[ 0.0000, 0.0000, 0.0000],
[ 0.0000, 0.0000, 0.0000]]]], grad_fn=<ConvolutionBackward0>)
4. CNN의 학습원리
A. data
-
아래의 4개의 이미지
= torch.tensor([
img0 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
[0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
[0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
[0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
[0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
[0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
[0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
[0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
[0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
[0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
[0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
[0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
[0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
[0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
[0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0],
[0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.0]
[1, 1, 16, 16)
]).reshape(= 0.1-torch.einsum('nchw->ncwh', img0.clone())
img1 = torch.zeros((1, 1, 16, 16))
img2 for i in range(16):
for j in range(16):
if j <= i: # 대각선 아래 삼각형
0, 0, i, j] = 0.1
img2[# 빈 이미지
= torch.zeros((1, 1, 16, 16))
img3 = 2
block_size # 블록 단위로 채우기
for i in range(0, 16, block_size):
for j in range(0, 16, block_size):
if ((i // block_size) + (j // block_size)) % 2 == 0:
0, 0, i:i+block_size, j:j+block_size] = 0.1 img3[
-
squeeze()
차원이 1인 것을 없애줌
= plt.subplots(2,2)
fig, axs 8)
fig.set_figheight(8)
fig.set_figwidth(0][0].imshow(img0.squeeze(),cmap="gray")
axs[0][1].imshow(img1.squeeze(),cmap="gray")
axs[1][0].imshow(img2.squeeze(),cmap="gray")
axs[1][1].imshow(img3.squeeze(),cmap="gray") axs[
= torch.concat([img0,img1,img2,img3],axis=0)
imgs imgs.shape
torch.Size([4, 1, 16, 16])
B. vertical edge
= torch.nn.Conv2d(
v_conv =1,
in_channels=1,
out_channels=4,
kernel_size=False
bias )
= torch.tensor([[[
v_conv.weight.data 0, 0, 0, 0],
[ 0, 1.0, -1.0, 0],
[ 0, 1.0, -1.0, 0],
[0, 0, 0, 0]
[ ]]])
-
v_conv는 좌우방향의 픽셀 변화, 즉 수직 방향의 엣지를 감지하는데 적절
= plt.subplots(2,2)
fig, axs 8)
fig.set_figheight(8)
fig.set_figwidth(0][0].imshow(v_conv(imgs)[0].squeeze().data,cmap="gray")
axs[0][1].imshow(v_conv(imgs)[1].squeeze().data,cmap="gray")
axs[1][0].imshow(v_conv(imgs)[2].squeeze().data,cmap="gray")
axs[1][1].imshow(v_conv(imgs)[3].squeeze().data,cmap="gray") axs[
C. horizontal edge
= torch.nn.Conv2d(
h_conv =1,
in_channels=1,
out_channels=4,
kernel_size=False
bias )
= torch.tensor([[[
h_conv.weight.data 0, 0, 0, 0],
[ 0, -1.0, -1.0, 0],
[ 0, 1.0, 1.0, 0],
[0, 0, 0, 0]
[ ]]])
-
h_conv는 위아래 방향의 픽셀 변화, 즉 수평 방향의 엣지를 감지하는데 적절
= plt.subplots(2,2)
fig, axs 8)
fig.set_figheight(8)
fig.set_figwidth(0][0].imshow(h_conv(imgs)[0].squeeze().data,cmap="gray")
axs[0][1].imshow(h_conv(imgs)[1].squeeze().data,cmap="gray")
axs[1][0].imshow(h_conv(imgs)[2].squeeze().data,cmap="gray")
axs[1][1].imshow(h_conv(imgs)[3].squeeze().data,cmap="gray") axs[
D. 이동평균
= torch.nn.Conv2d(
m_conv =1,
in_channels=1,
out_channels=4,
kernel_size
)= m_conv.weight.data*0 + 1/16
m_conv.weight.data = m_conv.bias.data*0 - 0.05 m_conv.bias.data
= plt.subplots(2,2)
fig, axs 8)
fig.set_figheight(8)
fig.set_figwidth(0][0].imshow(m_conv(imgs)[0].squeeze().data,cmap="gray")
axs[0][1].imshow(m_conv(imgs)[1].squeeze().data,cmap="gray")
axs[1][0].imshow(m_conv(imgs)[2].squeeze().data,cmap="gray")
axs[1][1].imshow(m_conv(imgs)[3].squeeze().data,cmap="gray") axs[
E. (B,C,D) + relu + mp
= torch.nn.ReLU()
relu = torch.nn.MaxPool2d(kernel_size=13) mp
mp(relu(v_conv(imgs)))
tensor([[[[0.2000]]],
[[[0.0000]]],
[[[0.1000]]],
[[[0.2000]]]], grad_fn=<MaxPool2DWithIndicesBackward0>)
mp(relu(h_conv(imgs)))
tensor([[[[0.0000]]],
[[[0.2000]]],
[[[0.1000]]],
[[[0.2000]]]], grad_fn=<MaxPool2DWithIndicesBackward0>)
mp(relu(m_conv(imgs)))
tensor([[[[5.0000e-02]]],
[[[5.0000e-02]]],
[[[5.0000e-02]]],
[[[9.3132e-10]]]], grad_fn=<MaxPool2DWithIndicesBackward0>)
F. 구조
= torch.nn.Sequential(
net =1,out_channels=3,kernel_size=4),
torch.nn.Conv2d(in_channels
torch.nn.ReLU(),=13),
torch.nn.MaxPool2d(kernel_size
torch.nn.Flatten()
)0].weight.data = torch.concat(
net[
[v_conv.weight.data,
h_conv.weight.data,=0)
m_conv.weight.data],axis0].bias.data = torch.tensor([0.0,0.0, -0.05]) net[
="gray") plt.matshow(net(imgs).data,cmap
net(imgs).shape
torch.Size([4, 3])
출력은 (n,3)으로 정리되어서 나온다. 이 시점부터는 더 이상 이미지가 입력이라고 생각하지 않아도 되고, 단순히 (n, 3) 크기의 숫자 데이터가 입력으로 주어진 것처럼 보면 된다. 즉 이제부터는 이 (n,3) 데이터를 입력으로 받는 신경망을 설계하면 된다.
G. mp의 역할?
-
샘플이미지
= torch.zeros((1, 1, 16, 16))
img = 4
triangle_size for i in range(triangle_size):
for j in range(triangle_size):
if j <= i: # 아래 방향 직각삼각형 (왼쪽 위 꼭짓점 기준)
0, 0, i, j] = 1.0 img[
="gray") plt.imshow(img.squeeze(),cmap
-
mp 1회
= torch.nn.MaxPool2d(kernel_size=2)
mp ="gray") plt.imshow(mp(img).squeeze(),cmap
-
mp 2~4회
= torch.nn.MaxPool2d(kernel_size=2)
mp ="gray") plt.imshow(mp(mp(img)).squeeze(),cmap
= torch.nn.MaxPool2d(kernel_size=2)
mp ="gray") plt.imshow(mp(mp(mp(img))).squeeze(),cmap
-
maxpooling은 이미지를 “캐리커처화” 한다고 비유할 수 있음. 디테일은 버리고, 중요한 특징만 뽑아서 과장되게 요약한다.
4. FashionMNIST
-
CNN
- \(\to\) 2d // flatten(conv(특징) - relu(특징추가) - maxpooling(요약))
- \(\to\) 1d // 단순신경망(그냥 펼친걸로 신경망)
-
데이터
= torchvision.datasets.FashionMNIST(root='./data', train=True, download=False)
train_dataset = torch.utils.data.Subset(train_dataset, range(5000))
train_dataset = torchvision.transforms.ToTensor()
to_tensor = torch.stack([to_tensor(img) for img, lbl in train_dataset]).to("cuda:0")
X = torch.tensor([lbl for img, lbl in train_dataset])
y = torch.nn.functional.one_hot(y).float().to("cuda:0") y
-
2d를 처리하고 flatten하는 네트워크
= torch.nn.Sequential(
net1 =1,out_channels=16,kernel_size=5),
torch.nn.Conv2d(in_channels
torch.nn.ReLU(),=2),
torch.nn.MaxPool2d(kernel_size
torch.nn.Flatten()"cuda:0") ).to(
net1(X).shape
torch.Size([5000, 2304])
출력은 (n,2304)으로 정리되어서 나온다. 이 시점부터는 더 이상 이미지가 입력이라고 생각하지 않아도 되고, 단순히 (n, 2304) 크기의 숫자 데이터가 입력으로 주어진 것처럼 보면 된다. 즉 이제부터는 이 (n,2304) 데이터를 입력으로 받는 신경망을 설계하면 된다.
-
1d를 처리하는 네트워크
= torch.nn.Sequential(
net22304,10),
torch.nn.Linear("cuda:0") ).to(
-
두 네트워크를 결합
= torch.nn.Sequential(
net
net1,
net2
) net(X).shape
torch.Size([5000, 10])
-
최종적인 코드
= torch.nn.Sequential(
net
net1,
net2
)= torch.nn.CrossEntropyLoss()
loss_fn =torch.optim.Adam(net.parameters())
optimizr#---#
for epoc in range(100):
#1
= net(X)
netout #2
= loss_fn(netout,y)
loss #3
loss.backward()#4
optimizr.step() optimizr.zero_grad()
=1) == y.argmax(axis=1)).float().mean() (net(X).argmax(axis
tensor(0.8806, device='cuda:0')