[AITech] 20220208 - CNN Basics
학습 내용
Convolution
일반적인 다층 신경망(MLP)은 각 뉴런들이 선형 모델과 활성 함수로 모두 연결된(fully connected) 구조이다.
Convolution 연산은 이와 달리 커널(필터)을 입력 벡터 상에서 움직여가면서 선형모델과 함성 함수가 적용되는 구조이다.
-
컨볼루션 연산의 수학적 의미는 신호를 커널을 이용해 국소적으로 증폭 또는 감소시켜서 정보를 추출 또는 필터링하는 것이다.
-
CNN에서 사용하는 연산은 엄밀히 말하면 convolution 연산이 아니고 cross-correlation 연산이다. 하지만 그 의미에서 convolution 연산과 큰 차이가 없기 때문에 옛날부터 convolution 연산으로 통칭한다.
-
커널은 정의역 내에서 움직여도 변하지 않고(translation invariant) 주어진 신호에 국소적(local)으로 적용한다.
-
컨볼루션 연산은 1차원 뿐 아니라 다양한 차원에서 계산 가능하다.
- 데이터의 성격에 따라 사용하는 커널이 달라진다.
-
2D-Conv 연산은 아래와 같이 나타낼 수 있다.
- 컨볼루션 연산에서 사용하는 용어로 스트라이드와 패딩이라는 것이 있다.
- 스트라이드: 컨볼루션 연산을 위해 한 번에 필터(커널)를 이동시키는 칸 수
- 패딩: 컨볼루션 결과의 크기를 조정하기 위해 입력 배열의 둘레를 확장하고 0으로 채우는 연산
-
결과적으로 입력의 크기 (OH, OW), 커널(필터)의 크기(FH, FW), 패딩의 폭 P, 스트라이드 크기 S를 안다면 출력의 크기는 다음과 같이 구할 수 있다.
- 컨볼루션 연산에서 사용하는 용어로 스트라이드와 패딩이라는 것이 있다.
-
채널이 여러 개인 3D-Conv 이상의 다차원 컨볼루션 연산의 경우 커널의 채널 수와 입력의 채널 수가 같아야 한다. (rank가 동일해야 함)
- 이 경우 3차원 입력과 3차원 커널을 통해 출력의 채널 크기는 1이 되며, 채널의 크기를 Oc로 만들고 싶다면 커널을 Oc개 사용하면 된다.
Convolution의 역전파
- 컨볼루션 연산은 커널이 모든 입력 데이터에 공통으로 적용되기 때문에 역전파를 계산할 때도 convolution 연산이 나오게 된다.
CNN Architecture
앞까지의 CNN 기초 내용은 지금껏 수없이 보고 들어왔기에 어려움이 없을 것입니다.
다만, 이 강의에서는 CNN 구조의 (학습가능한) 파라미터 개수를 계산할 수 있는 것에 주안점을 두고 있습니다. 정확한 수치는 아니더라도, 십만 대, 백만 대 등과 같이 단위 정도는 가늠할 수 있어야 CNN 모델을 적절히 선택하고 사용할 수 있다는 것입니다.
CNN architecture는 다음과 같이 [Convolution-Pooling]*N - FC 층이 연결되어 있는 구조를 보입니다. 아래 구조는 AlexNet의 구조입니다.
그리고 컨볼루션 층의 파라미터 개수는 다음과 같이 구할 수 있습니다.
- 3 x 3 x 128 x 64 = 73,728
그러면, 마지막으로 위의 AlexNet 모델의 각 층의 파라미터 개수를 구해보면서 포스팅 마치겠습니다. 모두들 왜 숫자가 저렇게 나오는지 계산해보세요!
CNN 실습
이번에도 똑같이 CNN 구조를 code level에서 간단히 살펴보도록 하겠습니다.
-
Define Model
- 모델의 깊이를 쉽게 커스터마이징 할 수 있도록 설계합니다.
- Convolution, Batch norm, Pooling, Dropout, ReLU, Dense 층을 사용합니다.
class ConvolutionalNeuralNetworkClass(nn.Module): # 깊이를 custom할 수 있도록 설계 # cdims에는 convolution layer들의 채널 수, hdims에는 fully connected layer들의 뉴런 수 전달 def __init__(self,name='cnn',xdim=[1,28,28], ksize=3,cdims=[32,64],hdims=[1024,128],ydim=10, USE_BATCHNORM=False): super(ConvolutionalNeuralNetworkClass,self).__init__() self.name = name self.xdim = xdim self.ksize = ksize self.cdims = cdims self.hdims = hdims self.ydim = ydim self.USE_BATCHNORM = USE_BATCHNORM ### 1. Convolutional layers self.layers = [] prev_cdim = self.xdim[0] for cdim in self.cdims: # for each hidden layer self.layers.append( nn.Conv2d( # convolution in_channels=prev_cdim, # 입력의 채널 개수 out_channels=cdim, # 출력의 채널 개수(사용할 커널 개수와 동일) kernel_size=self.ksize, # 커널의 (w,h) stride=(1,1), # stride padding=self.ksize//2 # padding(input과 output의 (h,w)가 같도록 패딩) ) ) # BN, Dropout layer와 같은 train-test 시 동작이 다른 layer들 때문에 train/eval 모드를 구분! if self.USE_BATCHNORM: self.layers.append(nn.BatchNorm2d(cdim)) # batch-norm self.layers.append(nn.ReLU(True)) # activation self.layers.append(nn.MaxPool2d(kernel_size=(2,2), stride=(2,2))) # max-pooling self.layers.append(nn.Dropout2d(p=0.5)) # dropout prev_cdim = cdim ### 2. Dense layers self.layers.append(nn.Flatten()) prev_hdim = prev_cdim*(self.xdim[1]//(2**len(self.cdims)))*(self.xdim[2]//(2**len(self.cdims))) for hdim in self.hdims: self.layers.append(nn.Linear(prev_hdim, hdim, bias=True)) # Fully connected self.layers.append(nn.ReLU(True)) # activation prev_hdim = hdim ### 3. Final layer (without activation) self.layers.append(nn.Linear(prev_hdim,self.ydim,bias=True)) ### 4. Concatenate all layers self.net = nn.Sequential() for l_idx,layer in enumerate(self.layers): layer_name = "%s_%02d"%(type(layer).__name__.lower(),l_idx) self.net.add_module(layer_name,layer) # add_module의 장점: 이름을 커스터마이징 ### 5. initialize parameters self.init_param() def init_param(self): for m in self.modules(): if isinstance(m,nn.Conv2d): # init conv nn.init.kaiming_normal_(m.weight) nn.init.zeros_(m.bias) elif isinstance(m,nn.BatchNorm2d): # init BN nn.init.constant_(m.weight,1) nn.init.constant_(m.bias,0) elif isinstance(m,nn.Linear): # lnit dense nn.init.kaiming_normal_(m.weight) nn.init.zeros_(m.bias) def forward(self,x): return self.net(x) C = ConvolutionalNeuralNetworkClass( name='cnn', xdim=[1,28,28], # (28,28) grayscale ksize=3, # kernel size = (3,3) cdims=[32,64], # channel: 32, 64 hdims=[32], # FC: 32 ydim=10).to(device) # final output: 10 loss = nn.CrossEntropyLoss() optm = optim.Adam(C.parameters(),lr=1e-3)
-
Check parameters
- Convolution, Dense layer에 있는 trainable parameters의 수와 형태를 나타낸 것입니다.
np.set_printoptions(precision=3) n_param = 0 '''activation function, Pooling, Dropout 등의 layer는 trainable parameter가 없음!''' for p_idx,(param_name,param) in enumerate(C.named_parameters()): if param.requires_grad: param_numpy = param.detach().cpu().numpy() # to numpy array n_param += len(param_numpy.reshape(-1)) print ("[%d] name:[%s] shape:[%s]."%(p_idx,param_name,param_numpy.shape)) print (" val:%s"%(param_numpy.reshape(-1)[:5])) print ("Total number of parameters:[%s]."%(format(n_param,',d'))) ''' [0] name:[net.conv2d_00.weight] shape:[(32, 1, 3, 3)]. val:[-0.457 -0.491 0.172 0.296 -0.467] [1] name:[net.conv2d_00.bias] shape:[(32,)]. val:[0. 0. 0. 0. 0.] [2] name:[net.conv2d_04.weight] shape:[(64, 32, 3, 3)]. val:[ 0.04 0.075 -0.042 0.065 -0.011] [3] name:[net.conv2d_04.bias] shape:[(64,)]. val:[0. 0. 0. 0. 0.] [4] name:[net.linear_09.weight] shape:[(32, 3136)]. val:[ 0.007 -0.009 0.001 0.01 -0.033] [5] name:[net.linear_09.bias] shape:[(32,)]. val:[0. 0. 0. 0. 0.] [6] name:[net.linear_11.weight] shape:[(10, 32)]. val:[-0.223 0.028 -0.329 0.379 -0.05 ] [7] name:[net.linear_11.bias] shape:[(10,)]. val:[0. 0. 0. 0. 0.] Total number of parameters:[119,530]. '''
-
Training
- Training code는 MLP와 달라진 게 없기 때문에 생략합니다.
- 다시 말하지만, 달라지는 것은 클래스 내의 코드들입니다.
Leave a comment