Posts ai basic 5
Post
Cancel

ai basic 5

CNN



0116. 전체 구조

img

img

  • 위의 신경망은 인접하는 계층의 모든 뉴런과 결합되어 있었고 이를 완전연결 fully-connected라고 하며, 완전히 연결된 계층을 Affine계층이라 함.
  • 지금까지의 ‘Affine-ReLU’연결이 아래 그림의 ‘Conv-ReLU-(Pooling)’으로 바뀜. (풀링 계층은 생략하기도 함.)
  • 출력에 가까운 층에서는 지금까지의 ‘Affine-ReLU’구성을 사용할 수 있고 마지막 출력 계층에서는 ‘Affine-Softmax’조합을 그대로 사용.



0117. 완전연결 계층의 문제점: 데이터의 형상이 무시됨.

  • 이미지는 세로, 가로, 채널(색상)으로 구성된 3차원 데이터, 그러나 완전연결 계층에 입력할 때는 3차원 데이터를 평평한 1차원 데이터로 평탄화해줘야 함.
  • 반면 합성곱 계층은 형상을 유지함. 이미지를 3차원 데이터로 입력받으며, 마찬가지로 다음 계층에도 3차원 데이터로 전달.

0118. 합성곱 연산.

img

  • 합성곱 연산은 필터의 윈도우window를 일정 간격으로 이동해가며 입력 데이터에 적용.
  • 입력과 필터에 대응하는 원소끼리 곱한 후 그 총합을 구함.(단일 곱셈-누산, fused multiply-add, FMA)
  • CNN에서는 필터의 매개변수가 ‘가중치’에 해당. 편향까지 포함하면 아래와 같음.

img

  • 편향은 항상 하나(1x1)만 존재. 그 하나의 값을 필터를 적용한 모든 원소에 더함.

0119. 패딩padding: 합성곱 연산을 수행하기 전에 입력 데이터 주변을 특정 값(예컨대 0)으로 채우기도 함.

img

  • 출력 크기를 조정할 목적으로 사용.
  • 입력 데이터의 공간적 크기를 고정한 채로 다음 계층에 전달할 수 있음.

0120. 스트라이드stride: 필터를 적용하는 위치의 간격.

  • 스트라이드를 2로 하면 필터를 적용하는 윈도우가 두 칸씩 이동.

0121. 출력크기 계산: 입력크기를 (H, W), 필터 크기를 (FH, FW), 출력 크기를 (OH, OW), 패딩을 P, 스트라이드를 S라 하면

img

  • (W+2P-FW)/S 와 (H+2P-FH)/S가 정수로 나누어떨어지는 값이어야 함.

0122. 3차원 데이터 합성곱 연산

img

  • 3차원 데이터를 2차원 데이터와 비교하면, 길이 방향(채널 방향)으로 특징 맵이 늘어났음.
  • 채널쪽으로 특징 맵이 여러 개 있다면 입력 데이터와 필터의 합성곱 연산을 채널마다 수행하고, 그 결과를 더해서 하나의 출력을 얻음.
  • 3차원 합성곱 연산 주의할 점: 입력 데이터의 채널 수와 필터의 채널 수가 같아야 함.
  • 필터 자체의 크기는 원하는 값으로 설정할 수 있음.(단, 모든 채널의 필터가 같은 크기여야 함.)

0123. 3차원의 합성곱 연한은 데이터와 필터를 직육면체 블록이라고 생각하면 쉬움.

img

  • 필터의 가중치 데이터는 4차원 데이터이며 (출력 채널 수, 입력 채널 수, 높이, 너비).
  • 편향의 형상은 (FN, 1, 1)이고, 필터의 출력 결과의 형상은 (FN, OH, OW)인데 이 두 블록을 더하면 편향의 각 값이 필터의 출력인 (FN, OH, OW) 블록의 대응 채널의 원소 모두에 더해짐.

0124. 합성곱 연산을 배치 처리하기 위해 각 계층을 흐르는 데이터의 차원 수를 하나 늘려 4차원 데이터로 저장. (데이터 수, 채널 수, 높이, 너비)

img

  • 신경망에 4차원 데이터가 하나 흐를 때마다 데이터 N개에 대한 합성곱 연산이 이루어짐. 즉, N회 분의 처리를 한 번에 수행.



0125. 풀링: 가로, 세로 방향의 공간을 줄이는 연산. 최대 풀링max pooling, 평균 풀링average pooling. 풀링의 윈도우 크기와 스트라이드는 같은 값으로 설정하는 것이 보통.

  • 풀링 계층의 특징
    • 학습해야 할 매개변수 없음.
    • 채널 수가 변하지 않음. 채널마다 독립적으로 계산.
    • 입력의 변화에 영향을 적게 받음.



0126.

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
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
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
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]


0127.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
class Convolution:
    def __init__(self, W, b, stride=1, pad=0):
        self.W = W
        self.b = b
        self.stride = stride
        self.pad = pad
        
    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)
        
        col = im2col(x, FH, FW, self.stride, self.pad)
        col_W = self.W.reshape(FN, -1).T # 필터 전개
        out = np.dot(col, col_W) + self.b
        
        out = out.reshape(N, out_h, out_w, -1).transpose(0, 3, 1, 2)
        
        return out
  • reshape에 -1을 지정하면 다차원 배열의 원소 수가 변환 후에도 똑같이 유지되도록 적절하게 묶어줌.
    • (10, 3, 5, 5) 형상을 한 다차원 배열의 원소 수는 총 750개임. 이 배열에 reshape(10, -1)을 호출하면 750개의 원소를 10묶음으로, 즉 형상이 (10, 75)인 배열로 만들어줌.
  • transpose: 다차원 배열의 축 순서를 바꿔주는 함수.
    • (N, H, W, C) -> (N, C, H, W)
    • (0, 1, 2, 3) -> (0, 3, 1, 2)

0128.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
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) # 2차원 배열, 즉 행렬이라면 axis=0은 열 방향, axis=1은 행 방향을 뜻함.
        
        # 적절한 성형 (3)
        out = out.reshape(N, out_h, out_w, C).transpose(0, 3, 1, 2)


0129.
초기화 때 받는 인수

  • input_dim: 입력 데이터(채널 수, 높이, 너비)의 차원
  • conv_param: 합성곱 계층의 하이퍼파라미터(딕셔너리), 딕셔너리의 키는 다음과 같음.
    • filter_num: 필터 수
    • filter_size: 필터 크기
    • stride: 스트라이드
    • pad: 패딩
  • hidden_size: 은닉층(완전연결)의 뉴런 수
  • output_size: 출력층(완전연결)의 뉴런 수
  • weight_init_std: 초기화 때의 가중치 표준편차
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
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
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_strite = 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))
        
        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)
        
        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()
        
    def predic(self, x):
        for layer in self.layers.values():
            x = layer.forward(x)
        return x
        
    def loss(self, x, t):
        y = self.predict(x)
        return self.last_layer.forward(y, t)
        
    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




0130. 층 깊이에 따른 추출 정보 변화

  • 계층이 깊어질수록 추출되는 정보(정확히는 강하게 반응하는 뉴런)는 더 추상화됨.
  • 처음 층은 단순한 에지에 반응하고, 이어서 텍스처에 반응하고, 더 복잡한 사물의 일부에 반응하도록 변화.
  • 층이 깊어지면서 뉴런이 반응하는 대상이 단순한 모야에서 ‘고급’ 정보로 변화해감. = 사물의 ‘의미’를 이해하도록 변화함.
This post is licensed under CC BY 4.0 by the author.