CNN 목차
- 합성곱 신경망(convolutional neural network, CNN)
- 전체 구조
- 합성곱 계층
- 풀링(pooling) 계층
- 합성곱/풀링 계층 구현하기
- CNN 구현하기
- CNN 시각화하기
- 정리
CNN, 합성곱 신경망
- 이미지 인식 분야에서 이용
- 두 함수 중 하나를 반전(reverse), 이동(shift)시켜가며 나머지 함수와의 곱을 연이어 적분
- = 컨벌루션 신경망
- CNN은 완전 연결 계층이다.
[CNN 고유의 용어]
패딩 padding , 스트라이드 stride
전체 구조
- CNN도 레고 블록처럼 계층을 조합하여 만듦
- 지금까지 본 계층 +
Conv계층(합성곱 계층)
+pooling 계층
- 지금까지 본 계층 +
[완전연결 계층: Fully connected layer]
✔ 정의
- 한 층의 모든 뉴런이 그 다음층의 모든 뉴런과 연결된 상태(인접하는 계층의 모든 뉴런과 결합)
- 1차원 배열의 형채로 평탄화된 행렬을 통해 이미지를 분류하는데 사용되는 계층
- flatten을 통해 2차원 백터의 행렬을 1차원으로 평탄화하고, ReLu함수로 뉴런의 활성화 여부를 정하고, softmax함수로 이미지를 분류하는 이 모든 CNN의 전체적인 과정이 완전연경 계층이다.
-
Affine 계층
이라는 이름으로 구현할 것이다.
✔ 특징
- 완전연결 계층(Affine 계층)을 사용
- 인접하는 계층의 뉴런이 모두 연결
- 출력의 수 임의로 정하기 가능
[ex]
Affine 계층을 사용
가령 층이 5개인 완전연결 신경망 구현
[CNN의 구조]
합성곱 계층 Conv 과 풀링 계층 Pooling 이 새로 추가(회색)
- CNN의 계층은 ‘Conv-ReLU-(Pooling )’ 흐름으로 연결
- 출력에 가까운 층에서 지금까지의 ‘AffineReLU’ 구성을 사용 가능
- 마지막 출력 계층에서는 ‘Affine-Softmax’ 조합을 그대로 사용
(더 자세히 들여다 보면)
합성곱 계층
어파인과 달리 각 계층 사이에 3차원 데이터같이 입체적인 데이터가 흐름
[기존 완전연결 계층의 문제점]
데이터의 형상이 무시당함
ex)
입력 데이터가 이미지 (세로·가로·채널(색상)로 구성된 3차원 데이터) 인 경우
- 완전연결 계층에 입력할 때는 3차원 데이터를 평평한 1차원 데이터로 평탄화해줘야 함
- 공간적 정보 픽셀의 거리에 따른 값 등 의미를 갖는 본질적인 패턴 무시함
- 더 자세한 예
🚫 완전연결 계층은 모든 입력 데이터를 동등한 뉴런(같은 차원의 뉴런)으로 취급하여 형상에 담긴 정보를 살릴 수 없음.
❤ 한편, 합성곱 계층은 형상을 유지 이미지도 3차원 데이터로 입력받으며,
마찬가지로 다음 계층에도 3차원 데이터로 전달
특징 맵 (feature map)
CNN에서 합성곱 계층의 입출력 데이터
다차원의 데이터를 그대로 받기 때문에 이러한 차원을 보여주는 것을 맵이라고 지칭
입력 특징 맵 input feature map: 합성곱 계층의 입력 데이터
출력 특징 맵 output feature map: 출력 데이터
➡ ‘입출력 데이터’= ‘특징 맵’
합성 곱 연산
이미지 처리에서 말하는 필터 연산
합성곱 연산은 입력 데이터에 필터(=커널)를 적용
- 이 예에서 입력 데이터는 세로·가로 방향의 형상을 가짐
- 필터 역시 세로·가로 방향의 차원을 갖음.
- 데이터와 필터의 형상을 (높이 height , 너비 width )로 표기
[과정]
1) 합성곱 연산은 필터의 윈도우 window(회색 3×3 부분)를 일정 간격으로 이동해가며 입력 데이터에 적용.
2) 계산을 단일 곱셈-누산 (fused multiply-add, FMA) 적용: 입력과 필터에서 대응하는 원소끼리 곱한 후 그 총합을 구함
3) 결과를 출력의 해당 장소에 저장
🚧주의🚧
과학, 공학용 파이썬 라이브러이인 SciPy
의 2차원 합성곱 함수(scipy.signal.convolve2d
)로 이 예를 따라 해보면 결과가 다름
➡ 같은 결과를 얻으려면 합성곱이 아니라 교차상관(cross-correlation) 함수인 scipy.signal.correlate2d
를 사용해야 함
이유:
주어진 필터를 플리핑(flipping_행렬의 플리핑이란 원소들을 좌우, 상하로 각 한 번씩 뒤집는 것)하면 합성곱이고(원래 합성 곱),
그렇지 않으면 교차상관(위의 예)
➡ SciPy는 둘을 명확히 구분
- 딥러닝 쪽에서는 잘 구분하지 않는 경향.
- 딥러닝 라이브러리들의 합성곱 함수들은 플리핑하지 않거나, 플리핑 여부를 인수로 받기도 함
[필터]
완전연결 신경망에는 가중치 매개변수와 편향이 존재
CNN의 필터의 매개변수: 그동안의 ‘가중치’에 해당.
[편향]
위는 필터를 적용하는 단계까지만 보여준 것
➡ 편향까지 포함한 그림은 이것
- 편향은 필터를 적용한 후의 데이터에 더해짐
- 편향은 항상 하나(1×1 )만 존재
- 그 하나의 값을 필터를 적용한 모든 원소에 더하는 것
[패딩(padding)]
합성곱 연산을 수행하기 전에 입력 데이터 주변을 특정 값(예컨대 0)으로 채우기도 함 ➡ 폭 1짜리 패딩_ 입력 데이터 사방 1픽셀을 특정 값으로 채우는 것
사용 이유
- 패딩은 주로 출력 크기를 조정할 목적으로 사용.
- 합성곱 연산을 몇 번이나 되풀이하는 심층 신경망에서는 문제:
- 합성곱 연산을 거칠 때마다 크기가 작아지면 어느 시점에서는 출력 크기 1 (더 이상은 합성곱 연산을 적용할 수 없다는 뜻)
➡ 이러한 사태를 막기 위해 패딩을 사용
➡ 입력 데이터의 공간적 크기를 고정한 채로 다음 계층에 전달 가능
- 합성곱 연산을 거칠 때마다 크기가 작아지면 어느 시점에서는 출력 크기 1 (더 이상은 합성곱 연산을 적용할 수 없다는 뜻)
[스트라이드(stride 보폭)]
필터를 적용하는 위치의 간격
- 지금까지 본 예는 모두 스트라이드가 1
- 스트라이드를 키우면 출력 크기는 작아짐
📜출력 크기 계산 공식
- 입력 크기: (H, W )
- 필터 크기: (FH, FW )
- 출력 크기: (OH, OW )
- 패딩: P
- 스트라이드: S
➡ 정수로 나눠떨어지는 값이어야 함
➡ 출력 크기가 정수가 아니면 오류를 내는 등의 대응 필요
➡ 딥러닝 프레임워크 중에는 값이 딱 나눠떨어지지 않을 때는 가장 가까운 정수로 반올림하는 등, 특별히 에러를 내지 않고 진행하도록 구현하는 경우도 있음
3차원 데이터의 합성곱 연산
2차원일 때와 비교하면, 길이 방향(채널 방향)으로 특징 맵이 늘어 남
1) 입력 데이터와 필터의 합성곱 연산을 채널마다 수행
2) 그결과를 더해서 하나의 출력을 얻음
📍 주의점: 입력 데이터의 채널 수 = 필터의 채널
1️⃣ 블록으로 생각하기
- 입력 데이터: (채널 channel , 높이 height , 너비 width ) (C, H, W )
- 필터: (C, FH, FW )
➡ 출력 데이터는 한 장의 특징 맵(= 채널이 1개인 특징 맵)
❔ 그럼 합성곱 연산의 출력으로 다수의 채널을 내보내려면 ❔
- 그 답은 필터(가중치) 를 다수 사용하는 것
- 연산에서는 필터의 수도 고려 필요
- 이 그림과 같이 필터를 FN개 적용하면 출력 맵도 FN개가 생성됩니다.
- 그 FN개의 맵을 모으면 형상이 (FN, OH, OW )인 블록이 완성
- 이 완성된 블록을 다음 계층으로 넘기 겠다는 것이 CNN의 처리 흐름
필터의 가중치 데이터: 4차원 데이터 (출력 채널 수, 입력 채널 수, 높이, 너비)
2️⃣ 편향을 더한 모습
➡ 편향은 채널 하나에 값 하나씩으로 구성
3️⃣ 배치 처리
신경망 처리에서는 입력 데이터를 한 덩어리로 묶어 배치로 처리
➡ 이 방식을 지원하여 처리 효율을 높이고, 미니배치 방식의 학습도 지원
합성곱 연산도 마찬가지로 배치 처리를 지원하고자 함.
1) 각 계층을 흐르는 데이터의 차원을 하나 늘려 4차원 데이터로 저장
➡ (데이터 수, 채널 수, 높이, 너비)
2) 각 데이터의 선두에 배치용 차원을 추가
- 이처럼 데이터는 4차원 형상을 가진 채 각 계층을 타고 흐름
- 주의: 신경망에 4차원 데이터가 하나 흐를 때마다 데이터 N개에 대한 합성곱 연산이 이뤄짐
- 즉, N회 분의 처리를 한 번에 수행하는 것
풀링(pooling) 계층
풀링의 의미
세로·가로 방향의 공간을 줄이는 연산
➡ 최대 풀링(max pooling)의 스트라이드 2로 처리
풀링의 종류
[최대 풀링 max pooling]
위의 회색칸 중 숫자 하나를 뽑는 기준
최댓값 max 을 구하는 연산
- ‘2×2’는 대상 영역의 크기를 뜻함.
즉 2×2 최대 풀링은 그림과 같이 2×2 크기의 영역에서 가장 큰 원소 하나를 꺼내는 것
[평균 풀링(average pooling)]
평균 풀링은 대상 영역의 평균을 계산.
이미지 인식 분야에서는 주로 최대 풀링을 사용.
➕ 풀링의 윈도우 크기와 스트라이드는 같은 값으로 설정하는 것이 보통
풀링 계층의 특징
1️⃣ 학습해야 할 매개변수가 없다
- 풀링 계층은 합성곱 계층(채널, 가중치)과 달리 학습해야 할 매개변수가 없음
- 풀링은 대상 영역에서 최댓값이나 평균을 취하는 명확한 처리이므로 특별히 학습할 것이 없음
2️⃣ 채널 수가 변하지 않는다
- 채널마다 독립적으로 계산하기 때문
3️⃣ 입력의 변화에 영향을 적게 받는다 (강건하다)
- 입력 데이터가 조금 변해도 풀링의 결과는 잘 변하지 않음
합성곱/풀링 계층 구현하기
[4차원 배열 데이터 접근법]
앞에서 설명한 대로 CNN에서 계층 사이를 흐르는 데이터는 4차원임.
#높이 28, 너비 28, 채널 1개인 데이터가 10개
# 무작위로 데이터 생성
>>> x = np.random.rand(10, 1, 28, 28)
>>> x.shape
(10, 1, 28, 28)
여기에서 (10개 중) 첫 번째 데이터에 접근하려면 단순히 x[0]
>>> x[0].shape # (1, 28, 28)
>>> x[1].shape # (1, 28, 28)
첫 번째 데이터의 첫 채널의 공간 데이터에 접근
>>> x[0, 0] # 또는 x[0][0]
im2col로 데이터 전개
[이름의 의미]
- im2col은 ‘image to column’
- ‘이미지에서 행렬로’라는 뜻
➡ im2col이라는 이름의 함수를 만들어 합성곱 계층을 구현 시 이용
합성곱 연산의 구현을 im2col이라는 ‘트릭’이 문제를 단순화 시킴
- 넘파이에 합성곱 연산을 곧이곧대로 구현하기 위해 for 문을 사용하면 성능이 떨어짐
- for 문 대신 im2col 이라는 편의 함수를 사용해 간단하게 구현
[존재 목적] im2col은 입력 데이터 필터링(가중치 계산)하기 좋게 전개하는(펼치는) 함수 배치 안의 데이터 수까지 포함한 4차원 데이터 ➡ 2차원
im2col은 필터링하기 좋게 입력 데이터를 전개
➡ 구체적으로, 입력 데이터에서 필터를 적용하는 영역(3차원 블록)에서을 한 줄로 늘어놓음.
➡ 이 전개를 필터를 적용하는 모든 영역에서 수행하는 게 im2col
[Problem]
영역 겹침
- 실제 상황에서는 위와 달리 영역이 겹치는 경우가 대부분
- 필터 적용 영역이 겹치게 되면
- im2col로 전개한 후의 원소 수 > 원래 블록의 원소 수
- im2col을 사용해 구현하면 메모리를 더 많이 소비하는 단점 발생
하지만 컴퓨터는 큰 행렬을 묶어서 계산하는 데 탁월
- 예를 들어, 행렬 계산 라이브러리(선형 대수 라이브러리) 등은 행렬
합성곱 계층의 구현
1) im2col로 입력 데이터를 전개
2) 합성곱 계층의 필터(가중치)를 1열로 전개
3) 두행렬의 곱을 계산 ➡ 완전연결 계층의 Affine 계층에서 한 것과 거의 같음
4) 마지막으로 출력 데이터를 변형(reshape)함
[구현]
base
👀 코드 보기
# coding: utf-8
import numpy as np
def smooth_curve(x):
"""손실 함수의 그래프를 매끄럽게 하기 위해 사용"""
window_len = 11
s = np.r_[x[window_len-1:0:-1], x, x[-1:-window_len:-1]]
w = np.kaiser(window_len, 2)
y = np.convolve(w/w.sum(), s, mode='valid')
return y[5:len(y)-5]
def shuffle_dataset(x, t):
"""데이터셋을 뒤섞는다.
Parameters
----------
x : 훈련 데이터
t : 정답 레이블
Returns
-------
x, t : 뒤섞은 훈련 데이터와 정답 레이블
"""
permutation = np.random.permutation(x.shape[0])
x = x[permutation,:] if x.ndim == 2 else x[permutation,:,:,:]
t = t[permutation]
return x, t
def conv_output_size(input_size, filter_size, stride=1, pad=0):
return (input_size + 2*pad - filter_size) / stride + 1
def im2col(input_data, filter_h, filter_w, stride=1, pad=0):
"""다수의 이미지를 입력받아 2차원 배열로 변환한다(평탄화).
Parameters
----------
input_data : 4차원 배열 형태의 입력 데이터(이미지 수, 채널 수, 높이, 너비)
filter_h : 필터의 높이
filter_w : 필터의 너비
stride : 스트라이드
pad : 패딩
Returns
-------
col : 2차원 배열
"""
N, C, H, W = input_data.shape
out_h = (H + 2*pad - filter_h)//stride + 1
out_w = (W + 2*pad - filter_w)//stride + 1
img = np.pad(input_data, [(0,0), (0,0), (pad, pad), (pad, pad)], 'constant')
col = np.zeros((N, C, filter_h, filter_w, out_h, out_w))
for y in range(filter_h):
y_max = y + stride*out_h
for x in range(filter_w):
x_max = x + stride*out_w
col[:, :, y, x, :, :] = img[:, :, y:y_max:stride, x:x_max:stride]
col = col.transpose(0, 4, 5, 1, 2, 3).reshape(N*out_h*out_w, -1)
return col
def col2im(col, input_shape, filter_h, filter_w, stride=1, pad=0):
"""(im2col과 반대) 2차원 배열을 입력받아 다수의 이미지 묶음으로 변환한다.
Parameters
----------
col : 2차원 배열(입력 데이터)
input_shape : 원래 이미지 데이터의 형상(예:(10, 1, 28, 28))
filter_h : 필터의 높이
filter_w : 필터의 너비
stride : 스트라이드
pad : 패딩
Returns
-------
img : 변환된 이미지들
"""
N, C, H, W = input_shape
out_h = (H + 2*pad - filter_h)//stride + 1
out_w = (W + 2*pad - filter_w)//stride + 1
col = col.reshape(N, out_h, out_w, C, filter_h, filter_w).transpose(0, 3, 4, 5, 1, 2)
img = np.zeros((N, C, H + 2*pad + stride - 1, W + 2*pad + stride - 1))
for y in range(filter_h):
y_max = y + stride*out_h
for x in range(filter_w):
x_max = x + stride*out_w
img[:, :, y:y_max:stride, x:x_max:stride] += col[:, :, y, x, :, :]
return img[:, :, pad:H + pad, pad:W + pad]
1️⃣ im2col 함수의 인터페이스
input_data: (데이터 수, 채널 수, 높이, 너비)의 4차원 배열로 이뤄진 입력 데이터
#filter_h - 필터의 높이
#filter_w - 필터의 너비
# stride - 스트라이드
# pad - 패딩
im2col(input _data, filter _h, filter _w, stride = 1, pad = 0)
im2col은 ‘필터 크기’, ‘스트라이드’, ‘패딩’을 고려하여 입력
데이터를 2차원 배열로 전개
2️⃣ im2col을 실제로 사용
👀 코드 보기
import sys, os
sys.path.append(os.pardir)
from common.util import im2col
# 배치 크기 1 (데이터 1개), 채널 3 개, 높이·너비가 7×7의 데이터
x1 = np.random.rand(1, 3, 7, 7) # (데이터 수, 채널 수, 높이, 너비)
col1 = im2col(x1, 5, 5, stride = 1, pad = 0)
print(col1.shape) # (9, 75)
# 배치 크기만 10이고 나머지는 첫 번째와 같음
x2 = np.random.rand(10, 3, 7, 7) # 데이터 10 개
col2 = im2col(x2, 5, 5, stride = 1, pad = 0)
print(col2.shape) # (90, 75)
im2col 함수를 적용한 두 경우 모두 2번째 차원의 원소는 75
- 이 값은 필터의 원소 수와 같음(채널 3개, 5×5 데이터)
또한, 배치 크기가 1일 때는 im2col의 결과의 크기가 (9, 75 )이고,
10일 때는 그 10배인 (90, 75 ) 크기의 데이터가 저장
3️⃣ im2col을 사용하여 합성곱 계층을 구현
여기에서는 합성곱 계층을 Convolution
이라는 클래스로 구현
👀 코드 보기
# 합성곱 계층은 필터(가중치), 편향, 스트라이드, 패딩을 인수로 받아 초기화
class Convolution:
def __init __(self, W, b, stride = 1, pad = 0):
self.W = W
self.b = b
self.stride = stride
self.pad = pad
# 필터는 (FN, C, FH, FW )의 4차원 형상
#여기서 FN 필터 개수, C 채널, FH는 필터 높이, FW 필터 너비
def forward(self, x):
FN, C, FH, FW = self.W.shape
N, C, H, W = x.shape
out _h = int(1 + (H + 2*self.pad - FH) / self.stride)
out _w = int(1 + (W + 2*self.pad - FW) / self.stride)
# 입력 데이터를 im2col로 전개
# 필터도 reshape을 사용해 2차원 배열로 전개
# 그리고 이렇게 전개한 두 행렬의 곱을 구합니다
col = im2col(x, FH, FW, self.stride, self.pad)
col_W = self.W.reshape(FN, -1).T # 필터 전개 📌-1
out = np.dot(col, col_W) + self.b
# 출력 데이터를 적절한 형상으로 바꿔줌
# 이때 넘파이의 transpose 함수를 사용-> 다차원 배열의 축 순서를 바꿔주는 함수
out=out.reshape(N,out_h,out_w,1).transpose(0,3,1,2) # 📌transpose(0,3,1,2)
return out
📌 -1
- 필터 블록을 1줄로 펼쳐 세움.
- 이때 reshape의 두 번째 인수를 -1로 지정-> 이는 reshape이 제공하는 편의 기능
- reshape에 -1을 지정하면 다차원 배열의 원소 수가 변환 후에도 똑같이 유지 되도록 적절히 묶어줌
ex]
앞의 코드에서 (10, 3, 5, 5 ) 형상을 한 다차원 배열 W의 원소수는 총 750개
-> 이 배열에 reshape (10, -1 )을 호출하면 750개의 원소를 10묶음으로, 즉 형상이 (10, 75)인 배열로 만들어줌
➡ 필터의 형상을 입력데이터와 맞추기 위해
📌 transpose(0,3,1,2)
- 인덱스(0부터 시작)를 지정하여 축의 순서를 변경
- 인덱스(번호)로 축의 순서 변형
im2col로 전개한 덕분에 완전연결 계층의 Affine 계층과 거의 똑같이 구현 가능
풀링 계층 구현
풀링 계층 구현도 합성곱 계층과 마찬가지로 im2col을 사용해 입력 데이터를 전개
- 풀링의 경우엔 채널 쪽이 독립적이라는 점이 합성곱 계층 때와 다름.
- 구체적으로는 풀링 적용 영역을 채널마다 독립적으로 전개
[풀링 계층의 forward 처리 흐름]
1) 일단 이렇게 전개한 후
2) 전개한 행렬에서 행별 최댓값을 구함
3) 적절한 형상으로 성형
[풀링 계층 구현은 세 단계로 진행]
1) 입력 데이터를 전개한다.
2) 행별 최댓값을 구한다.
3) 적절한 모양으로 성형한다.
- 최댓값 계산에는 넘파이의
np.max
메서드를 사용 가능- np.max의 인수 중 축(axis): 이 인수로 지정한 축마다 최댓값을 구할 수 있음
- ex)
np.max (x, axis=1 )
과 같이 쓰면 입력 x의 1번째 차원의 축마다 최댓값을 구함 - ➡ 2차원 배열, 즉 행렬이라면 axis=0은 열 방향, axis=1은 행 방향
[풀링 계층 구현]
👀 코드 보기
class Pooling:
def __init __(self, pool_h, pool_w, stride = 1, pad = 0):
self.pool_h = pool_h
self.pool_w = pool_w
self.stride = stride
self.pad = pad
def forward(self, x):
N, C, H, W = x.shape
out_h = int(1 + (H - self.pool_h) / self.stride)
out_w = int(1 + (W - self.pool_w) / self.stride)
# 전개 (1)
col = im2col(x, self.pool_h, self.pool_w, self.stride, self.pad)
col = col.reshape(-1, self.pool_h*self.pool_w)
# 최댓값 (2)
out = np.max(col, axis = 1)
# 성형 (3)
out = out.reshape(N, out_h, out_w, C).transpose(0, 3, 1, 2)
return out
CNN 구현하기
합성곱 계층(계산)과 풀링 계층(크기 조정)을 구현했으니, 이 계층들을 조합하여 CNN 을 조립
[단순한 CNN의 네트워크 구성] CNN 네트워크 Convolution
-ReLU
-Pooling
➡Affine
-ReLU
➡AffineSoftmax
- 이를
SimpleConvNet
이라는 이름의 클래스로 구현
초기화
SimpleConvNet
의 초기화(__init__
)
[초기화 때 받는 인수]
input_dim
: 입력 데이터(채널 수, 높이, 너비)의 차원conv_param
: 합성곱 계층의 하이퍼파라미터(딕셔너리).- 딕셔너리의 키는 다음과 같다
filter_num
: 필터 수filter_size
: 필터 크기stride
: 스트라이드pad
: 패딩
hidden_size
: 은닉층(완전연결)의 뉴런 수output_size
: 출력층(완전연결)의 뉴런 수weight_init_std
: 초기화 때의 가중치 표준편차
➕ conv_param
의 딕셔너리형 하이퍼 파라미터 예시
이것은 필요한 하이퍼파라미터의 값이, {'filter_num':30, 'filter_size':5, 'pad':0, 'stride':1}
처럼 저장.
👀 코드 보기
# 초기화 인수로 주어진 합성곱 계층의 하이퍼파라미터를 딕셔너리에서 꺼냄 (나중에 쓰기 쉽도록)
class SimpleConvNet:
def __init __(self, input _dim = (1, 28, 28),
conv _param = {'filter_num':30, 'filter_size':5, 'pad':0, 'stride':1},
hidden_size = 100, output_size = 10,weight_init_std = 0.01):
filter_num = conv_param['filter_num']
filter_size = conv_param['filter_size']
filter_pad = conv_param['pad']
filter_stride = conv_param['stride']
input_size = input_dim[1]
# 합성곱 계층의 출력 크기를 계산합니다
Conv_output_size = (input_size - filter_size + 2*filter_pad) / filter_stride + 1
pool_output_size = int(filter_num * (conv_output_size/2) * (conv _output _size/2))
.
# 가중치 매개변수를 초기화하는 부분입니다.
# 학습에 필요한 매개변수는 1번째 층의 합성곱 계층과 나머지 두 완전연결 계층의 가중치와 편향
# 이 매개변수들을 인스턴스 변수 params 딕셔너리에 저장
#1번째 층의 합성곱 계층의 가중치를 W1, 편향을 b1
# 마찬가지로 2번째 층의 완전연결 계층의 가중치와 편향을 W2와 b2
# 마지막 3번째 층의 완전연결 계층의 가중치와 편향을 W3 와
# b3라는 키로 각각 저장
self.params = {}
self.params['W1'] = weight_init_std * np.random.randn(filter_num,input_dim[0],filter_size, filter_size)
self.params['b1'] = np.zeros(filter_num)
self.params['W2'] = weight_init_std * np.random.randn(pool_output_size, hidden_size)
self.params['b2'] = np.zeros(hidden _size)
self.params['W3'] = weight_init_std * np.random.randn(hidden_size, output_size)
self.params['b3'] = np.zeros(output_size)
# CNN을 구성하는 계층들을 생성합니다.
# 순서가 있는 딕셔너리 OrderedDict 인 layers 에 계층들을 차례로 추가
# 마지막 SoftmaxWithLoss 계층만큼은 last_layer라는 별도 변수에 저장-> SimpleConvNet의 초기화
self.layers = OrderedDict()
self.layers['Conv1'] = Convolution(self.params['W1'],
self.params['b1'],
conv_param['stride'],
conv_param['pad'])
self.layers['Relu1'] = Relu()
self.layers['Pool1'] = Pooling(pool _h = 2, pool _w = 2, stride = 2)
self.layers['Affine1'] = Affine(self.params['W2'], self.params['b2'])
self.layers['Relu2'] = Relu()
self.layers['Affine2'] = Affine(self.params['W3'], self.params['b3'])
self.last _layer = SoftmaxWithLoss()
# 추론을 수행하는 predict 메서드와 손실 함수의 값을 구하는 loss 메서드를 다음과 같이 구현
def predict(self, x):
for layer in self.layers.values():
x = layer.forward(x)
return x
def gradient(self, x, t):
# 순전파
self.loss(x, t)
# 역전파
# 매개변수의 기울기는 오차역전파법으로 구합니다
dout = 1
dout = self.last _layer.backward(dout)
layers = list(self.layers.values())
layers.reverse()
for layer in layers:
dout = layer.backward(dout)
# 결과 저장
grads = {}
grads['W1'] = self.layers['Conv1'].dW
grads['b1'] = self.layers['Conv1'].db
grads['W2'] = self.layers['Affine1'].dW
grads['b2'] = self.layers['Affine1'].db
grads['W3'] = self.layers['Affine2'].dW
grads['b3'] = self.layers['Affine2'].db
return grads
# 이 과정은 순전파와 역전파를 반복합니다.
지금까지 각 계층의 순전파와 역전파 기능을 제대로 구현했다면,
여기에서는 단지 그것들을 적절한 순서로 호출만 해주면 됨.
마지막으로 grads라는 딕셔너리 변수에 각 가중치 매개변수 기울기 저장
CNN 시각화하기
1번째 층의 가중치 시각화
합성곱 계층의 가중치는 그 형상이 (30, 1, 5, 5 )
➡ (필터 30개, 채널 1개, 5×5 크기) 필터의 크기가 5×5이고 채널이 1개
➡ 이 필터를 1채널의 회색조 이미지로 시각화 가능하다는 뜻
그럼 합성곱 계층(1층째) 필터를 이미지로 나타내 보자
가중치의 원소는 실수이지만, 이미지에서는
- 가장 작은 값(0): 검은색
- 가장 큰 값(255): 흰색으로 정규화하여 표시
학습을 마친 필터: 규칙성 있는 이미지가 됨
- 흰색에서 검은색으로 점차 변화하는 필터, 덩어리(블롭 blob)가 진 필터 등, 규칙을 띄는 필터로 바뀜
[규칙성 있는 필터가 보는 것]
- 에지(색상이 바뀐 경계선)
- 블롭(국소적으로 덩어리진 영역) 등
➡ 원시적인 정보 추출가능
ex)
가령 왼쪽 절반이 흰색이고 오른쪽 절반이 검은색인 필터는 세로 방향의 에지에 반응하는 필터. ➡ 이런 원시적인 정보가 뒷단 계층에 전달된다는 것이 앞에서 구현한 CNN에서 일어나는 일
층 깊이에 따른 추출 정보 변화
앞 절의 결과는 1번째 층의 합성곱 계층을 대상으로 한 것
➡ 1 층의 합성곱 계층 에서는 에지나 블롭 등의 저수준 정보가 추출
❔ 겹겹이 쌓인 CNN의 각 계층에서 추출 되는 정보는 ❔
계층이 깊어질수록 추출되는 정보(정확히는 강하게 반응하는 뉴런)는 더 추상화된다는 것을 알 수 있음
- 이와 같이 합성곱 계층을 여러 겹 쌓으면, 층이 깊어지면서더 복잡하고 추상화된 정보가 추출
- 처음 층은 단순한 에지에 반응하고, 이어서 텍스처에 반응하고, 더 복잡한 사물의 일부에 반응하도록 변화.
- 즉, 층이 깊어지면서 뉴런이 반응하는 대상이 단순한 모양에서 ‘고급’ 정보로 변화. ➡ 다시 말하면 사물의 ‘의미’를 이해하도록 변화하는 것
대표적인 CNN
특히 중요한 네트워크를 두 개 소개
- LeNet: CNN의 원조
- AlexNet: 딥러닝이 주목받도록 이끈 AlexNet
1️⃣ LeNet
손글씨 숫자를 인식하는 네트워크
1) 합성곱 계층과 풀링 계층(정확히는 단순히 ‘원소를 줄이기’만 하는 서브샘플링 계층)을 반복
2) 마지막으로 완전연결 계층을 거치면서 결과를 출력
[LeNet과 ‘현재의 CNN’차이점]
- 활성화 함수
- LeNet: 시그모이드 함수를 사용
- 현재: 주로 ReLU를 사용
- 데이터 크기 줄이는 방법
- 원래의 LeNet” 서브샘플링을 하여 중간 데이터의 크기를 줄임
- 현재: 최대 풀링이 주류
2️⃣ AlexNet 1) AlexNet은 합성곱 계층과 풀링 계층을 거듭하며
2) 마지막으로 완전연결 계층을 거쳐
3) 결과를 출력
LeNet에서 큰 구조는 바뀌지 않음
[AlexNet의 차이점]
- 활성화 함수: ReLU를 이용.
- LRN Local Response Normalization: 국소적 정규화를 실시하는 계층을 이용.
- 드롭아웃을 사용
- 빅데이터와 GPU, 이것이 딥러닝 발전의 큰 원동력.
➕ 딥러닝(심층 신경망)에는 대부분 수많은 매개변수가 쓰임.
- 학습하려면 엄청난 양의 계산 필요.
- 또한, 그 매개변수를 ‘피팅 fitting, 적합 ’시키는 데이터도 대량 필요
➡ GPU와 빅데이터로 해결
정리
● CNN은 지금까지의 완전연결 계층 네트워크에 합성곱 계층과 풀링 계층을 새로 추가한다.
● 합성곱 계층과 풀링 계층은 im2col (이미지를 행렬로 전개하는 함수)을 이용하면 간단하고 효율적으로 구현할 수 있다.
● CNN을 시각화해보면 계층이 깊어질수록 고급 정보가 추출되는 모습을 확인할 수 있다.
● 대표적인 CNN에는 LeNet과 AlexNet이 있다.
● 딥러닝의 발전에는 빅데이터와 GPU가 크게기여