1. CNN 등장배경
기존의 Neural Network는 모두 Fully-Connected Layer
- 장점: 세상의 모든 정보가 공개되어 있고. 그 정보가 데이터로 존재한다면 알고 싶은 모든 정보를 예측해낼 수 있다
- 단점: 현실은 모든 데이터가 정리되어 있지 않고 Overfitting의 위험이 존재한다
Image Data
- Gray Scale (흑백 사진): $H \times W \times 1$
- Channel이 1개만 존재
- Channel이 없다고 생각하면 안됨: $H \times W$ 가 아니라 $H \times W \times 1$로 Channel까지 반드시 고려해줘야 함
- Color Image (칼러 사진): $H \times W \times C$
- Channel이 R, G, B로 3개 존재
일반적으로 Matrix Shape을 부르는 형태와 동일하게 $H \times W (HW)$ 순서로 불러준다.
$W \times H$와 같이 순서를 바꿔서 부르면 안된다.
Q. PyTorch에서 Image Data를 사용할 때 tensor의 shape이 일반적으로 사용하는 shape과는 어떻게 다른가?
A. Computer Vision 분야 (Channel Last): $H \times W \times C$ (HWC)
PyTorch Library (Channel First): $C \times H \times W$ (CHW)
두 가지 중 어떠한 경우라도 $H \times W$ 순서는 동일하다. (HW)
FC Layer 일 때의 단점
- 2차원의 데이터를 1차원으로 Flatten시켜서 모든 pixel에 가중치를 부여함
- 한 픽셀마다 너무 세세하게 본다
- 주어진 이미지에만 너무 학습하여 조금의 변화만 주어도 예측해내지 못함
- 각 pixel들 위치를 서로 바꿔서 학습하는 것과 다를게 없음
CNN의 장점
- 신경 다발 (Connections)을 잘 끊어냄
- 이미지를 인식할 때 뇌의 일부분만 활성화된다는 실험 결과
- 그렇기 때문에 Input Layer에서 모든 Pixel에 가중치를 부여하는 것이 좋지 않음
- 위치별 특징을 추출함
- 위치 정보를 유지한 채로 특정 Pattern(특징)을 찾음
- Convolution 연산은 위치별 패턴을 찾는 연산
2. Convolution
Convolution (합성곱)
- 두 함수 $f, g$ 가운데 하나의 함수를 반전(Reverse), 전이(Shift)시킨 후 다른 하나의 함수와 곱한 결과 적분
- 독립인 두 확률변수의 확률분포의 합을 구할 때 사용함
- Reverse & Shift: 구하고자 하는 값($t$)에 대해 $f, g$중 하나의 함수를 $\frac {t} {2}$축 선대칭시킨 후 곱했다고 생각
두 독립적인 확률 변수의 확률 분포의 합
서로 independent한 두 Random Variable X, Y가 있고 둘의 합인 W는 다음과 같다. $W = X + Y$
$W$의 확률분포 (PDF: Probability Distribution Function)을 구해보자
$W$의 cumulative distribution function (CDF)은 다음을 통해 구할 수 있다.
$F_{W}(w) = Pr[X + Y \leq w] = \int_{x=-\infty }^{\infty} \left ( \int_{y=-\infty}^{w-x} f_{X,Y}(x, y) dy \right ) dx$
이제 CDF를 이용하여 PDF(Probability Distribution Function)을 구해보자.
$f_W(w) = \frac{\mathrm{d} F_W(w)}{\mathrm{d} w} = \int_{x=-\infty}^{\infty} \left ( \frac{\mathrm{d} }{\mathrm{d} w} \int_{y=-\infty}^{w-x} f_{X,Y}(x,y) dy \right )dx$
여기서 라이프니츠 적분 공식을 이용한다.
라이프니츠의 적분공식은 다음과 같다.
$\frac{\mathrm{d}}{\mathrm{d}w} \int_{a(w)}^{b(w)} f(x,w) \, dx = f(b(w),w) \cdot b'(w) - f(a(w),w) \cdot a'(w) + \int_{a(w)}^{b(w)} \frac{\partial f}{\partial w} \, dx$
이변수 함수에 대하여 적분을 시행하고 적분 변수가 아닌 다른 변수에 대해 구간을 설정하고 & 해당 적분의 도함수를 구할 때:
- 적분 변수가 아닌 변수에 대해 편미분하고 적분 Integral은 유지
- 적분 범위인 위끝과 아래끝을 대입하고 속미분한 것을 곱함
- 두 대입값을 (-) 뺄셈 처리
라이프니츠의 적분 공식에 의해 다음과 같이 정리가 가능하다.
$f_{X, Y} (x, y)$가 직접 $w$에 의존하지 않기 때문에 ($w$를 명시적으로 포함하지 않기 때문에)
$ \frac{\partial f_{X, Y}}{\partial w} = 0$ 이다.
또한, $a(w) = -\infty$이고, $b(w) = w-x$인데, $a(w)$는 $w$에 의존하지 않으므로 $\frac{\mathrm{d} a}{\mathrm{d} w} = 0$
따라서 다음의 식이 성립한다. $f_W(w) = \int_{x=-\infty}^{\infty} f_{X,Y}(x, w-x) \, dx$
Two Independent Random Variable의 합인 $W$ RV의 PDF: $f_W(w) = \int_{x=-\infty}^{\infty} f_{X,Y}(x, w-x) \, dx$
이때, $X, Y$모두 Independent 하므로 다음과 같이 식 변형이 가능하다.
$f_{X+Y}(w) = \int_{-\infty}^{\infty} f_{X,Y}(x, w - x) \, dx = \int_{-\infty}^{\infty} f_X(x) f_Y(w - x) \, dx = (f_X * f_Y)(w)$
$f_{X+Y}(w) = \int_{-\infty}^{\infty} f_X(x) f_Y(w - x) \, dx = (f_X * f_Y)(w) $: Convolution of $f_{X}$ and $f_{Y}$
즉, 2 Random Variable의 합인 $W$의 확률분포는 $W$를 구성하는 각 변수의 확률분포 $f_{X}, f_{Y}$의 Convolution 연산이다.
실제 두 확률분포의 합 $a+b$의 PDF 살펴보기: Convolution 연산
각 주사위의 확률 분포 함수 (probability distribution function, PDF)는 다음과 같다.
$X, Y$ 둘 다 Discrete Random Variable 이고 Distribution Function은
Uniform Distribution으로, $f_X(x) = f_Y(y) = \frac{1}{6} \text{ for } x,y \in \{1,2,3,4,5,6\}$ 이다.
이제 두 주사위의 합 $Z = X + Y$의 확률 분포를 구하기 위해 Convolution 연산을 적용한다.
$Z$의 가능범위는 2 ~ 12인데, $Z$의 PDF는 $f_Z(z) = (f_X * f_Y)(z) = \sum_{k=1}^{6} f_X(k) f_Y(z-k)$ 이다.
그렇기에 $f_{X}(x)$와 $f_{Y}(y)$ 1부터 6까지의 6등분점에 value가 $\frac {1} {6}$로 Discrete하게 찍혀있다고 상상하자.
그리고 구하고자 하는 $Z$값에 대하여 둘 중 하나의 그래프를 $\frac {Z} {2}$에 대해 선대칭하여 겹치는 Point만 곱했다고 보면 이해가 쉽다.
3. CNN Structure
Learning Process
- Feature Learning: 실제로 사진을 학습하는 과정, 사진의 특징을 추출
- Classification:Feature Learning의 결과를 도출하는 과정
Convolution Layer
- Channel(kernel): 사진 정보가 들어오는 통로
- Filter: Input Feature에서 특징을 감지하는 가중치 행렬 (Weight Matrix)
- 한 개의 Filter당 3D tensor로 구성되어 있음 ($Shape: F \times F \times C$): 여러개의 Kernel(Channel)로 구성
- 한 개의 Filter 당 한 개의 Feature Map(Activaion Map) 생성
- Feature Map: 연산을 마친 결과물
- Activation Map: Feature Map에 Activation Function(비선형성)을 적용한 결과
Activation Map과 Feature Map은 딱히 구별하지는 않고, 대체로 같은 의미로 주로 사용되고 있다
입력 Feature Map($Shape: H \times W \times C$) Channel 수 C = Convolution 연산을 적용할 Filter($Shape: F \times F \times C \times K$)의 Channel 수 C
- Channel 수가 동일해야 Convolution 연산이 가능
- Input Feature Map의 각 Channel에 단일 FIlter 기준 Kernel을 분배하여 Convolution 연산을 수행
출력 Feature Map($Shape: H \times W \times C$) Channel 수 C = Convolution 연산을 적용할 Filter($Shape: F \times F \times C \times K$)의 Filter 수 K
- FIlter 1개당 Feature Map 1개 (1:1 대응)
Input Feature($Shape: H \times W \times C$)와 각각의 Filter($Shape: F \times F \times C$)의 Receptive Field끼리 원소곱을 하여, Filter의 Kernel이 이동하면서 결과를 도출한다.
Convolution 연산 후 Feature Map의 크기: $H' \times W' \times C$
- $H_{\text{out}} = \frac {H_{\text{in}} - F + 2P} {S} + 1$
- $W_{\text{out}} = \frac {W_{\text{in}} - F + 2P} {S} + 1$
- $C = K$
- $H, W$: Input Feature Map의 Height, Width
- $S, P, K$: Stride, Padding, # of Filters
Q. CNN에서 원소 곱을 하는 이유는 무엇인가?
A. 원소 곱을 하는 이유:
1) 각 Pixel마다 같은 Kernel을 사용함으로써 사용되는 Parameter의 개수를 줄이기 위함: 가중치 공유 (weight sharing)
2) 지역적인 패턴을 감지할 수 있기 때문: Local Pattern Detection
학습된 Parameter의 개수: $(F \times F \times C + 1) \times K$
이후 Activation Function(ReLU)를 적용한다. (이전에 BatchNorm 적용)
- 비선형성 추가
- 더 빠른 학습
- Gradient Vanishing Problem 완화
Activation Function과 Pooling Layer의 경우 Model Parameter가 존재하지 않으므로 학습되는 Parameter가 없다
4. Filter @ Convolution Operation
https://woochan-autobiography.tistory.com/883
4번의 내용은 위의 블로그를 참고하여 작성했다.
1) Filter
- 단일(1개) Filter는 3D(3차원) tensor: $F \times F \times C$
- 각 Filter는 여러 개의 Kernel로 구성되어 있으며 개별 Kernel은 Filter 내에서 서로 다른 값을 가질 수 있다
- Kernel의 개수 = Channel의 개수: Channel = Kernel
- CNN에서는 이러한 3차원 Filter K개를 각각 Input Feature Map에 적용한다
2) Input/Output Feature Map과 Filter의 Channel 개수 $C$/Filter 개수 $K$ 개의 관계
입력 Feature Map($Shape: H \times W \times C$) Channel 수 C = Convolution 연산을 적용할 Filter($Shape: F \times F \times C \times K$)의 Channel 수 C
- Channel 수가 동일해야 Convolution 연산이 가능
- Input Feature Map의 각 Channel에 단일 FIlter 기준 Kernel을 분배하여 Convolution 연산을 수행
출력 Feature Map($Shape: H \times W \times C$) Channel 수 C = Convolution 연산을 적용할 Filter($Shape: F \times F \times C \times K$)의 Filter 수 K
- FIlter 1개당 Feature Map 1개 (1:1 대응)
- Batch Size를 제외한: Input Image Data, Feature Map 모두 3-Dimension
- Each Filter도 3-Dimension
- Input Image Data는 RGB이므로 ($H \times W \times 3$) 3-Dimension이다
- 하나의 단일 Filter는 여러 개의 Kernel(Channel)을 가진다: $F \times F \times C$
- 그리고 여러 개의 Kernel 개수 $K$ = Channel 개수 $C$이므로 Channel = Kernel
모델을 만들 때, Conv2D 연산을 보면 단일 Filter의 Channel 개수 $C$가 나와있지 않다.
이는 Input Tensor의 Channel 수와 Conv2D의 Channel 수가 무조건 같을 것이라고 생각을 하는 것이다.
그렇기에 단일 Filter의 Channel 수 $C = 1$이다. (28, 28, 1)
그리고 첫번째 Conv2D 연산의 Output은 Filter의 개수만큼인 $K$가 32가 나온다. (28, 28, 32)
이후 두 번째 Conv2D 연산을 적용하기 위해서는 두 번째 Conv2D 단일 Filter의 Channel 수는 $C = 32$여야 한다.
단일 Filter에 32개의 Kernel이 들어가 있는 것으로 적용이 된다고 볼 수 있다.
위의 내용들은 모두 생략이 되면서 계산이 수행된다. 당연히 맞아야 Convolution 연산이 가능하기 때문이다.
Padding $P = 1$, Stride $S = 1$이라고 하자.
$3 \times 3 \: Kernel$, $depth = 1$인 단일 Filter가 32개 있는 Layer
- $320 = (3 \times 3 \times 1 + 1) \times 32$
$3 \times 3 \: Kernel$, $depth = 32$인 단일 Filter가 64개 있는 Layer
- $18496 = (3 \times 3 \times 32 + 1) \times 64$
3) Input Feature Map w/ Multiple Channels에 Convolution Filter 적용 Mechanism
Input Feature Map의 Channel 수 $C$ = Filter의 Kernel(Channel) 수 $C$는 무조건 같아야 한다.
Filter의 개수 $K=1$ 이므로 Output Feature Map의 Channel 수 $C=1$이다.
4) Input Feature Map w/ Multiple Channels에 $3 \times 3$ Filter 적용 Mechanism
5) Input Feature Map w/ Multiple Channels에 여러개의 Filter 적용
Output Feature Map의 Channel 수 $C$ = Conv를 적용한 Filter의 수 $K$
절대로 단일 Filter의 Kernel(Channel)수 $C$가 아니다!
왜냐하면 FIlter 한 개당 Feature Map이 한 개씩 1:1 대응되어 나오기 때문이다.
Input Feature Map의 Channel 개수가 3이면 당연히 동일하게 앙옆으로 계산하므로 단일 Filter의 Kernel 개수는 3이다.
6) Filter, Kernel, Channel 수
Filter의 개수: 128개
Kernel의 크기: $3\times 3$
Filter의 Channel수: 3개 (input에 들어있는 Channel 수와 동일)
Output Feature Map의 Channel 수: 128개 (Filter의 개수와 동일)
7) Filter의 shape
위의 두 Network에서 각 Layer에 적용된 Filter의 shape은?
- $5 \times 5 \times 1$, $5 \times 5 \times 6$
- $11 \times 11 \times 3$, $5 \times 5 \times 96$
첫번째는 피쳐맵의 채널이 6개(6x)가 있고 colvolution 연산은 5x5 이다.
고로 Filter의 개수는 6개이다.
Filter의 size는 5x5
input의 채널 수는 1개이다.
따라서 (5x5x1)의 Filter 6개가 연산이 된다.
두번째는 convolution 연산도 5x5로 계산 된다.
단일 Filter에 들어가는 채널 개수는 당연히 6개이다.
따라서 (5x5x6)의 Filter 16개가 연산이 된다.
첫번째는 11x11 커널 사이즈를 가지는 걸로 convolution 연산을 진행한다.
여기서 단일 Filter의 채널수는 3개이다.
그리고 Filter의 개수는 96개이다.
왜냐하면 output으로 96개가 나왔기 때문이다.
따라서 (11x11x3)의 Filter 96개가 연산이 된다.
두번째는 5x5 커널 사이즈를 가지는 걸로 convolution 연산을 진행한다.
패딩은 same을 주고 단일 Filter의 채널은 96개이다.
그리고 Filter는 256개이다.
따라서 (5x5x96)의 Filter 256개가 연산이 된다.
5. Receptive Field
각 Kernel들이 배출한 하나의 결과값은 하나의 뉴런(Pixel)이다.
특정한 하나의 뉴런(Pixel)이 원본 이미지에서 담당하는 범위를 Receptive Field라 한다.
이는 Feature Map 혹은 Input Image Data의 일부 영역이며,
Convolution Layer의 각 뉴런은 입력 이미지의 작은 Field에 연결된다.
위 사진의 초록색 뉴런의 Receptive Field는 원본 이미지의 $3 \times 3$ 부분이고,
노란색 뉴런은 원본 이미지의 $5 \times 5$ 부분이다.
즉 Kernel의 크기가 커질수록, 층이 깊어질수록 더 넓은 Receptive Field를 가지게 된다.
Receptive Field의 크기는 뉴런이 출력을 계산할 때 고려할 수 있는 Context의 양을 결정하기 때문에 중요하다.
Receptive Field가 작은 뉴런은 이미지의 작은 부분만 볼 수 있으며 작고 국소적인 특징에 민감하다.
반면에 Receptive Field가 큰 뉴런은 이미지의 더 많은 부분을 볼 수 있고 더 크고 전역적인 특징에 민감하다.
6. Stride & Padding
Monotone(Gray Scale) Image: Channel 1개 (Filter도 1개)
RGB Image: Channel 3개 (Filter도 1개)
Q. $32 \times 32 \times 3$ Input Image에 $5 \times 5 \times 3$ Filter 1개를 적용했을 때 Output Feature Map의 Size? (stride = 1, padding = 0)
A. $28 \times 28 \times 1$의 Output Feature Map이다.
RGB Image에 Filter를 여러 개 적용하는 경우 Output Feature Map의 Channel수가 Filter의 개수와 동일하게 나온다.
Q. $28 \times 28 \times 4$ Input Image에 $a \times a \times b$ Filter c개를 적용했을 때 Output Feature Map의 Size가 $21 \times 21 \times 10$이 나왔다면 $a + b + c$는 ?(stride = 1, padding = 0)
A. $8 \times 8 \times 4$의 Filter가 10개가 있으므로, $a + b + c = 22$ 이다.
아래의 2가지 질문에 대한 답을 구해보자.
각 질문에 대한 답은 Stride, Padding 이다.
6-1) Stride
Input Image의 크기가 매우 클 경우, CNN을 적용할 때 계산량이 너무 많다.
이 경우 Stride의 값을 조절하여 해당 Kernel이 움직이는 step을 크게할 수 있다.
Stride를 반영한 Output Feature Map의 Width $W$, Height $H$는 다음과 같이 알 수 있다
- $\frac {H' -F} {S} + 1 = H$
- $\frac {W' -F} {S} + 1 = W$
만약 +1 하기 전의 값에서 나누어 떨어지지 않아 소수점이 발생하면, 소수점은 버리고 정수만 취한다.
6-2) Padding
필터의 크기가 클 경우, 계속 필터를 적용하다 보면 Output Feature Map의 크기가 줄어들 텐데,
이때 크기 감소를 피하기 위해, 그리고 모서리 부분 정보 손실을 피하기 위해 Padding을 이용한다.
Q. Padding을 하는 이유는 무엇인가?
A. Convolution Operation을 지속적으로 수행했을 때, Feature Map의 크기가 줄어드는 것을 방지함과 동시에
모서리 부분의 정보가 손실되는 것을 방지하기 위해 Padding을 수행한다.
Stride와 Padding을 반영한 Output Feature Map의 Width $W$, Height $H$는 다음과 같이 알 수 있다
- $\frac {H' -F + 2P} {S} + 1 = H$
- $\frac {W' -F + 2P} {S} + 1 = W$
Q. $9 \times 9 \times 4$ Input Image에 $5 \times 5 \times 4$ Filter 8개를 적용했을 때
1) Input의 Size가 유지되기 위한 Padding의 크기는?
2) 1)에서 구한 크기의 Padding 적용 후, Stride를 2로 바꿨을 때의 Output Size을 구하시오
3) 이 과정에서 학습된 총 Parameter 수는?
A. 1) Input의 크기가 유지되기 위한 Padding의 크기는 2이다.
2) Output의 Size는 $5 \times 5 \times 8$이다.
3) 해당 과정에서 학습된 총 Parameter의 수는 $(5 \times 5 \times 4 + 1) \times 8 = 808$개 이다.
7. Pooling Layer
7-1) Pooling Operation
- Feature Map의 Dimension을 Down-Sampling하여 연산량을 감소시킨다
- 중요한 Feature Vector를 추출하여 효과적으로 학습한다
- 공간적 불변성을 증가시킨다
7-2) Pooling의 종류
- Max Pooling: 대상 영역에서 Maximum value를 추출
- Average Pooling: 대상 영역에서 Average value를 반환
대부분의 Convolutional Neural Network(CNN)에서는 Max-Pooling이 사용된다.
왜냐하면 Average Pooling은 각 Kernel 값을 평균화시켜 중요한 가중치를 갖는 값의 특성이 희미해질 수 있기 때문이다.
7-3) Pooling Layer연산
Pooling Operation은 각 Channel 별로 독립적으로 적용된다.
Max Pooling을 기본적으로 사용하는데, 이를 통해 Feature Map의 크기를 조정한다.
일반적으로 Kernel Size와 Stride SIze가 동일하고, 주로 $2 \times 2$ Kernel과 $2$ Stride를 이용한다.
해당 과정에서 Data의 약 75%가 버려지며,
예를 들어, $224 \times 224 \times 64$에서 $112 \times 112 \times 64$으로 줄이는 효과가 있다.
Pooling Layer 연산에서는 학습되는 Model Parameter가 없다!
7-4) Pooling Layer Size
Kernel의 Size를 $F \times F$, Stride를 $S$라 하자.
- $W_{\text{out}} = \left\lfloor \frac{W_{\text{in}} - F}{S} + 1 \right\rfloor$
- $H_{\text{out}} = \left\lfloor \frac{H_{\text{in}} - F}{S} + 1 \right\rfloor$
- $W_{\text{out}}$ 과 $H_{\text{out}}$ 은 각각 출력의 너비와 높이
- $W_{\text{in}}$ 과 $H_{\text{in}}$ 은 각각 입력의 너비와 높이
- $F$ 는 Kernel의 크기 ($F \times F$ 의 정사각형)
- $S$ 는 Stride
- $\left\lfloor \cdot \right\rfloor$ 는 바닥 함수로, 결과값을 가장 가까운 작은 정수로 내린다
Q. Convolution 연산시에는 Pooling 연산과 다르게 공식에 내림 함수를 명시적으로 적용하지 않아?
A. 공식은 출력 높이와 너비를 계산할 때 정수 결과를 얻기 위해 일반적으로 결과값을 자연스럽게 내림한다.
그러나 실제 계산에서는 결과값이 실수일 경우, 명시적으로 내림하여 정수 출력 크기를 결정하는 경우가 많다.
이 내림 과정은 때때로 구현에 따라 명시적으로 표현되거나, 내부적으로 처리되기도 하기 때문에 실제 적용 시에는 해당 구현의 문서나 설명을 참고하는 것이 중요하다.
8. FC Layer
앞의 Convolution Operation & Pooling Operation을 반복하여
RGB 3개의 Channel을 가지는 Input Image Data로부터 Feature Extraction을 수행한다.
이후 해당 Output Feature Map을 지닌 3D tensor를 Flatten 시켜 FC Layer를 구축한다.
다중 분류의 경우 Output Activation으로 Softmax Activation을 사용한다.
Q. nn.Dropout( )을 FC Layer에 사용한다면 nn.Linear( )과 nn.ReLU( )간의 순서는 어떻게 되는가?
A. 가장 일반적인 구성은 nn.Linear 레이어 이후와 활성화 함수인 nn.ReLU 이전에 넣는 것이다. 이렇게 하면 레이어의 출력에 대해 Dropout을 적용하고, 그 결과를 활성화 함수로 전달할 수 있다.
그럼에도 불구하고, nn.Dropout의 위치는 실험을 통해 최적화할 수 있다는 점을 명심해야 한다. 어떤 경우에는 `nn.ReLU` 활성화 함수 이후에 `nn.Dropout`을 적용하는 것이 더 좋은 결과를 가져올 수도 있다. 따라서, 두 가지 방법을 모두 시도해보고, 검증 세트(validation set)에서의 성능을 기준으로 더 나은 구성을 선택하는 것이 좋다.
요약하면, nn.Linear -> nn.Dropout -> nn.ReLU 순서로 적용하는 것이 가장 일반적인 방식이지만, 모델과 문제에 따라 nn.Linear - > nn.ReLU -> nn.Dropout 순서도 고려할 수 있다. 항상 모델의 성능을 확인하면서 최적의 구성을 찾아야 한다.
일반적인 CNN 구조에서의 BatchNorm, Dropout 순서
BatchNorm2d( ): nn.Conv2d( )와 nn.ReLU( ) 사이
nn.Dropout(p): nn.Linear( )와 nn.ReLU( ) 사이
9. Formula
Convolution Operation
- $H_{\text{out}} = \frac {H_{\text{in}} - F + 2P} {S} + 1$
- $W_{\text{out}} = \frac {W_{\text{in}} - F + 2P} {S} + 1$
Pooling Operation
- $W_{\text{out}} = \left\lfloor \frac{W_{\text{in}} - F}{S} + 1 \right\rfloor$
- $H_{\text{out}} = \left\lfloor \frac{H_{\text{in}} - F}{S} + 1 \right\rfloor$
10. Summary
1) 선형 결합의 가중치를 적용해서 가중합을 구하게 되는 convolutional operation 을 먼저 적용
- Output Feature Map을 구한다
- Batch Normalization을 이용
- RELU Layer를 그 다음 적용하여 Activation Map을 구한다
2) Image를 조금 더 축약된 형태로 바꿔주는 Max Pooling Layer를 적용
3) Convolution - BatchNorm - RELU - Max Pooling을 여러 번 반복하면서 Stacking
4) Fully Connected Layer를 이용
- output activation map를 일자로 핀 column vector를 Input으로 설정
- model parameter와 linear combination후 sigmoid 함수 통과하여 확률비교 (binary-classification의 경우)
5) 최종적으로 두 개의 Class 중 하나로 구분
- binary cross-entropy loss
- mean square loss
6) 위의 loss function으로 설계한 후 optimizer로 최적화
- Fully connected Layer에서 model parameter 존재
- Pooling, RELU는 model parameter 존재 X
- Convolution Layer에서는 이미지의 특정 위치들에 적용한 convolution filter들의 coefficient or 패턴 자체가 model parameter로 작용: GD를 통해 filter의 coefficient를 최적의 값으로 도출하게 됨
11. PyTorch API
10-1) Convolution Operation
nn.Conv1d
- 1D Conv Layer를 정의하는 Class
- FIlter가 한 방향으로만 이동할 수 있는 (시계열의 경우, 시간을 축으로만) Convolution 연산
nn.Conv2d(in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, groups=1, bias=True, padding_mode='zeros', device=None, dtype=None)
- 2D Conv Layer를 정의하는 Class
- FIlter가 두 방향으로 이동 (이미지와 같은 2차원 데이터에 주로 사용)
순서가: in_channels, out_channels, kernel_size, stride, padding 순서라는 것을 기억하자
stride = 1, padding = 0이 기본값이다!
$\text{Input: } (N, C_{\text{in}}, H_{\text{in}}, W_{\text{in}}) \text{ or } (C_{\text{in}}, H_{\text{in}}, W_{\text{in}})$
$\text{Output: } (N, C_{\text{out}}, H_{\text{out}}, W_{\text{out}}) \text{ or } (C_{\text{out}}, H_{\text{out}}, W_{\text{out}}), \text{where}$
$H_{\text{out}} = \left\lfloor \frac{H_{\text{in}} + 2 \times \text{padding}[0] - \text{dilation}[0] \times (\text{kernel_size}[0] - 1) - 1}{\text{stride}[0]} + 1 \right\rfloor$
$W_{\text{out}} = \left\lfloor \frac{W_{\text{in}} + 2 \times \text{padding}[1] - \text{dilation}[1] \times (\text{kernel_size}[1] - 1) - 1}{\text{stride}[1]} + 1 \right\rfloor$
- in_channels(int): 입력 채널의 개수
- out_channels(int): 출력 채널의 개수
- kernel_size(int or tuple): Filter의 크기, 예를 들어 $3 \times 3$ Filter는 3 또는 (3, 3)으로 지정
- stride=1(int or tuple, optional): Filter 이동 간격
- padding=0(int or tuple, optional): Padding의 크기
- dilation=1: 커널의 간격을 지정
- groups=1: 입력 채널을 나눌 그룹의 수
- bias=True: 편향(bias) 항을 사용할지 여부를 결정
nn.Conv3d
- 3D Conv Layer를 정의하는 Class
- FIlter가 세 방향으로 이동
10-2) Pooling Operation
nn.MaxPool#d(kernel_size, stride=None, padding=0, dilation=1, return_indices=False, cell_mode=False)
- kernel_size(int or tuple): Pooling Window의 크기를 나타내는 값
- stride(int): Pooling Window의 이동 간격
- Padding=0(int or tuple, optional): Padding의 크기
10-3) Batch Normalization
nn.BatchNorm#d(num_features, eps=1e-05, momentum=0.1, affine=True, track_running_stats=True, devices=None, dtype=None)
- num_features: Channel 개수
FC Layer or Conv Layer 뒤, Activation Function 전(활성화 함수를 통과하기 전) Batch Normalization
즉, Convolution과 Activation 사이에 Batch Normalization을 적용한다
12. CNN - Code Implementation
FashionMNIST Dataset은 $28 \times 28$의 size를 갖는다
Library 호출
import pandas as pd
import matplotlib.pyplot as plt
import torch
import torch.nn as nn
from torch.autograd import Variable
import torch.nn.functional as F
import torchvision
import torchvision.transforms as transforms
from torch.utils.data import Dataset, DataLoader
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
device = torch.device("mps:0" if torch.backends.mps.is_available() else "cpu")
print(device)
Dataset, DataLoader 정의
transform = transforms.Compose([transforms.ToTensor()])
train_dataset = torchvision.datasets.FashionMNIST(
"./", download=True, transform=transform, train=True
)
test_dataset = torchvision.datasets.FashionMNIST(
"./", download=True, transform=transform, train=False
)
- "./": FashionMNIST를 내려받을 경로 지정
- download: True일 시 첫번째 파라미터의 위치에 해당 데이터셋이 있는지 확인한 후 내려받음t
- transform: Image를 tensor(0 ~ 1)로 변환
train_loader = DataLoader(train_dataset, batch_size = 100)
test_loader = DataLoader(test_dataset, batch_size = 100)
분류에 사용될 Class 정의
labels_map = {
0: "T-Shirt",
1: "Trouser",
2: "Pullover",
3: "Dress",
4: "Coat",
5: "Sandal",
6: "Shirt",
7: "Sneaker",
8: "Bag",
9: "Ankle Boot",
}
fig = plt.figure(figsize=(8, 8))
columns = 4
rows = 5
for i in range(1, columns * rows + 1):
img_xy = np.random.randint(len(train_dataset))
img = train_dataset[img_xy][0][0, :, :]
fig.add_subplot(rows, columns, i)
plt.title(labels_map[train_dataset[img_xy][1]])
plt.axis("off")
plt.imshow(img, cmap="gray")
plt.show()
CNN Model
class FashionCNN(nn.Module):
def __init__(self):
super(FashionCNN, self).__init__()
self.layer1 = nn.Sequential(
nn.Conv2d(in_channels=1, out_channels=32, kernel_size=3, padding=1),
nn.BatchNorm2d(32),
nn.ReLU(),
nn.MaxPool2d(kernel_size=2, stride=2),
)
self.layer2 = nn.Sequential(
nn.Conv2d(in_channels=32, out_channels=64, kernel_size=3),
nn.LazyBatchNorm2d(64),
nn.ReLU(),
nn.MaxPool2d(kernel_size=2),
)
self.fc1 = nn.Linear(64 * 6 * 6, 600)
self.drop = nn.Dropout2d(0.25)
self.fc2 = nn.Linear(600, 120)
self.fc3 = nn.Linear(120, 10)
def forward(self, x):
x = self.layer1(x)
x = self.layer2(x)
x = x.view(x.shape[0], -1)
x = self.fc1(x)
x = self.drop(x)
x = self.fc2(x)
x = self.fc3(x)
return x
- nn.Sequential: 여러 개의 Layer을 차례로 하나의 컨테이너로 구현하는 방법
- Conv Layer: Convolution Operation을 수행 (Kernel이 Input Fearue Map을 처음부터 끝까지 훑으면서 각 원소끼리 곱한 후 모두 더한 값을 출력)하면서 각 이미지의 특징을 추출하는 2D CNN
- 입력 채널의 수는 1, 출력 채널의 수는 32, Kernel Size는 (3, 3)으로 정사각형, Padding의 크기는 1로 설정
- BatchNorm2d: Normalization을 통해 분포를 Gaussian 형태로 만들어서 평균은 0, 표준편차는 1로 데이터의 분포를 조정함
- MaxPool2d: 이미지의 크기를 축소시키는 용도로 사용함 (출력 데이터의 크기를 줄이거나, 특정 데이터를 강조하는 용도로 사용됨)
- $2 \times 2$ 행렬로 구성된 가중치, 입력 데이터에 커널을 적용할 때 2 간격으로 이동하도록 stride 설정
- Multiclass-Classification Task를 수행하기 위해서는 Image 형태의 2D tensor를 flatten하여 1D tensor의 배열 형태로 변환해야 한다
- 출력 결과를 FC Layer로 보내기 위해서는 1D로 변환해주는 과정이 필요하다
- Conv2d에서 사용하는 Hyperparameter(padding, stride)에 따라 출력 크기가 달라지고, 줄어든 출력 크기는 최종 분류를 담당하는 FC Layer로 전달된다
- Input Data size는 64 * 6 *6, 600, 120의 크기를 거쳐 최종 출력 데이터의 크기는 10 (10개의 Class)이다
- nn.Dropout2d(p): p만큼의 비율로 tensor의 값이 0이 되고(p 비율만큼의 뉴런이 무작위로 제거됨), 0이 되지 않은 값들은 기존 값에 $\frac {1} {1-p}$만큼 곱해져 커니다
- 이는 모델의 출력이 특정 뉴런에만 의지하지 않고 다양한 특성을 학습하도록 유도하여 과적합을 방지하기 위한 방법이다
- forward() 함수는 모델이 학습 데이터를 입력받아서 Forward Propagation을 진행시키며, 반드시 forward라는 이름의 함수여야 함
- Conv Layer에서 FC Layer로 변경되기 때문에 데이터의 형태를 1차원으로 바꾸어 줌
- x.view(x.size(0), -1): batch size만 그대로 두고 나머지를 전부 Flatten 시키겠다는 뜻이다
- (batch_size, # of Channels, Height, Width)가 x(tensor)의 형태이다
LR = 0.001
model = FashionCNN().to(device)
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=LR)
print(model)
Convolution Operation 수행시 $H, W$의 value를 계산할 때 반드시 '1'을 먼저 더하고 시작할 것
def train(model, train_loader, optimizer, criterion, alpha=1.0):
model.train()
train_loss, correct, total = 0.0, 0, 0
for images, labels in train_loader:
images, labels = images.to(device), labels.to(device)
outputs = model(images)
loss = criterion(outputs, labels)
train_loss += loss.item()
correct += (labels == torch.argmax(outputs.data, dim=1)).sum().item()
total += labels.size(0)
optimizer.zero_grad()
loss.backward()
optimizer.step()
train_loss /= total
train_accruacy = 100 * (correct / total)
return train_loss, train_accruacy
def test(model, test_loader):
model.eval()
correct, total, test_loss = 0, 0, 0.0
with torch.no_grad():
for images, labels in test_loader:
images, labels = images.to(device), labels.to(device)
outputs = model(images)
loss = criterion(outputs, labels)
correct += (torch.argmax(outputs.data, 1) == labels).sum().item()
total += labels.size(0)
test_loss += loss.item()
test_loss /= total
test_accuracy = (correct / total) * 100
return test_loss, test_accuracy
train_losses, train_accuracies = [], []
epochs = 5
cnt = 0
for epoch in range(epochs):
train_loss, train_accuracy = train(model, train_loader, optimizer, criterion)
train_losses.append(train_loss)
train_accuracies.append(train_accuracy)
print(
f"Epoch: {epoch+1}/{epochs}, Train Loss: {train_loss:.4f}, Train_Accuracy: {train_accuracy:.2f}%"
)
cnt += 1
test_loss, test_accuracy = test(model, test_loader)
print(f"Test Accuracy: {test_accuracy:.2f}")
Epoch: 1/5, Train Loss: 0.0041, Train_Accuracy: 85.06%
Epoch: 2/5, Train Loss: 0.0027, Train_Accuracy: 90.17%
Epoch: 3/5, Train Loss: 0.0023, Train_Accuracy: 91.71%
Epoch: 4/5, Train Loss: 0.0021, Train_Accuracy: 92.52%
Epoch: 5/5, Train Loss: 0.0019, Train_Accuracy: 93.40%
Test Accuracy: 89.94%