정지홍 2023. 1. 15. 15:28

CNN컨볼루션 신경망이란?
-CNN은 컨볼루션층,풀링층,전결합층이 나온다.

-이미지를 잘 인식한다. 그러기 위해 일반적인 신경망과 조금 다르다.

-출력값이 입력값의 일부만 영향받는 국소성이 강한 처리가 수행된다.

-input(image) -> 컨볼루션층 -> 풀링층 -> 전결합층 -> 출력

-위의 층들은 모두 오차의 전파가 있다.

-컨볼루션은 학습되는 파라미터가 필터,편향이며 풀링층은 없고 전결합층은 가중치와 편향이 있다.

-위에서 컨볼루션층에서 몇 번 반복된후에 (컨볼루션층->풀링층)과정이 또 몇 번 반복된다. 이후 전결합층에서 몇 번 반복된후에야 출력이 나온다.

-컨볼루션층을 입력되는 이미지에 여러개의 필터처리가 된 후에 이미지의 특징을 나타내는 여러개의 이미지로 변환된다.

-풀링층에서는 이미지의 크기가 축소된다. (이미지의 특징을 잃어버리지 않기 위해)

 

국소성(집약성)이란?

-이미지에서 각 픽셀이 가까이 있는 픽셀과 가지고 있는 관련성이 얼마나 강한지 보여주는 성질이다.

 

컨볼루션층이란?

-이미지의 특징을 강화or약화시킬 수 있다.

-이미지의 국소성을 이용하여 이미지의 특징을 뽑아낸다. 필터마다 뽑아내는 특징을 다르다.

-컨볼루션을 적용하면 이미지의 크기가 줄어든다.

-일반적인 이미제 데이터는 각 픽셀이 RGB의 3개을 가지며 이 RCB의 장수를 채널수라고 부르며 만약 흑백이라면 채널수는 1개이다.

-각 필터들은 입력 이미지와 같은 수의 채널 수를 가진다. 만약 input img가 채널수가 3이면 필터도 3개다. 

-위에서 입력이미지의 필터에서 채널을 각각 컨볼루션을 적용시키면 각 필터마다 처리 수행된 이미지가 나온다. 이것의 갯수도 채널 수와 같다. 이렇게 생성된 이미지들은 편향을 더해서 활성화 함수에서 처리하며 나머지는 순전파의 과정과 같다.

 

풀링층이란?

-이미지를 여러 영역으로 구획하고 각 영역을 대표하는 값을 추출하여 새로운 이미지를 만든다. 이 과정을 풀링이라 한다.

-맥스 풀링(max pooling):각 영역의 최댓값을 대표값으로 설정

-평균 풀링(average pooling):각 영역의 평균값을 선택

-풀링 처리시 이미지가 축소된다.

 

전결합층이란?

-컨볼루션층와 풀링층의 몇 번 반복후에 이용되는 층이며 여기서 나온 값을 가지고 계산이 수행된다.

-입력받은 이미지를 벡터로 변환해준다.

 

패딩이란?

-입력이미지를 둘러싸는 것을 패딩이라한다.

-제로 패딩: 이미지의 주위에 0픽셀로 둘러 싼다.

-패딩은 입력이미지를 둘러싸아주니 이미지의 크기가 늘어난다. 이것으로 컨볼루션 처리를 하면 이미지가 계속 작아지는 것을 막아준다.

-가장자리 데이터의 컨볼루션 적용횟수도 증가시켜주는 장점도 있다.

 

스트라이드란?

-컨볼루션에서 필터가 이동하는 간격이다.

-스트라이드가 증가하며 이동 간격도 증가하여 생성 이미지 크기가 작아진다.

 

im2col (image to columns)와 col2im (columns to images)를 사용하는 이유는?

-컨볼루션층과 폴링층 코드를 단순하게 유지해서 실행속도를 빠르게 하기 위한 알고리즘

-컨볼루션층에서 (입력이미지의 높이x입력이미지의 너비)의 입력이미지가 (배치사이즈x입력이미지 채널수)의 수만큼 존재한다. 즉 4차원 배열, 4차원 텐서이다. 

-출력도 마찬가지로 (필터의 너비x필터의 높이)가 (배치사이즈x입력이미지 채널수)의 수만큼 존재한다. 이것도 4차원 텐서이며 이러한 다차원 배열을 for문을 여용하며 코드가 복잡하고 시간이 매우 오래걸린다. 그래서 반복을 줄이기 위해여 이를 사용한다.

-순전파에서는 im2col를 역전파에서는 col2im를 사용(두개다 풀링층에서도 활용된다.)

 

im2col ?

-이미지를 나타내는 4차원 배열을 행렬로 변환.(im2col)

-행렬을 이미지를 나타내는 4차원 배열로 변환.(col2im)

-기본적으로 이미지상의 직사각형 영역을 행렬의 열로 변환한다.

-배치와 채널의 수를 고려하지 않은 경우 생성되는 행렬은 행은 (출력이미지의 높이x출력이미지의 너비)이며 열은 (필터의 높이x필터의 너비)이다. 

-배치와 채널수를 고려하며 위에서 행에 배치사이즈가 곱해지며 열에는 채널 수가 곱해진다.

-필터도 (채널수x필터의 높이x필터의 너비)x(필터의 수)의 행렬로 나타낼수 있다.

-위에 두개의 행렬을 행렬곱에 의한 컨볼루션을 시키면 (출력이미지의 높이x출력이미지의 너비x배치사이즈)x필터수 행렬이 생성된다. 

 

 

 

import numpy as np
import matplotlib.pyplot as plt
from sklearn import datasets

# -- 손글씨 숫자 데이터 불러오기 --
digits_data = datasets.load_digits()
input_data = digits_data.data
correct = digits_data.target
n_data = len(correct)

# -- 입력 데이터 표준화 --
ave_input = np.average(input_data)
std_input = np.std(input_data)
input_data = (input_data - ave_input) / std_input

# -- 정답을 원-핫 인코딩으로 표현 --
correct_data = np.zeros((n_data, 10))
for i in range(n_data):
    correct_data[i, correct[i]] = 1.0

# -- 훈련데이터와 테스트데이터 --
index = np.arange(n_data)
index_train = index[index%3 != 0]
index_test = index[index%3 == 0]

input_train = input_data[index_train, :]  # 훈련 데이터 입력
correct_train = correct_data[index_train, :]  # 훈련 데이터 정답
input_test = input_data[index_test, :]  # 테스트 데이터 입력
correct_test = correct_data[index_test, :]  # 테스트 데이터 정답

n_train = input_train.shape[0]  # 훈련데이터 샘플 수
n_test = input_test.shape[0]  # 테스트 데이터 샘플 수

# -- 각 설정값 --
img_h = 8  # 입력 이미지 높이
img_w = 8  # 입력 이미지 너비
img_ch = 1  # 입력 이미지 채널 수

wb_width = 0.1  # 가중치와 편향 설정을 위한 정규분포 표준편차
eta = 0.01  # 학습률
epoch = 50
batch_size = 8
interval = 10  # 경과표시 간격
n_sample = 200  # 오차 계산 샘플 수

def im2col(images, flt_h, flt_w, out_h, out_w, stride, pad):
   
    n_bt, n_ch, img_h, img_w = images.shape
   
    img_pad = np.pad(images, [(0,0), (0,0), (pad, pad), (pad, pad)], "constant")
    cols = np.zeros((n_bt, n_ch, flt_h, flt_w, out_h, out_w))

    for h in range(flt_h):
        h_lim = h + stride*out_h
        for w in range(flt_w):
            w_lim = w + stride*out_w
            cols[:, :, h, w, :, :] = img_pad[:, :, h:h_lim:stride, w:w_lim:stride]

    cols = cols.transpose(1, 2, 3, 0, 4, 5).reshape(n_ch*flt_h*flt_w, n_bt*out_h*out_w)
    return cols


def col2im(cols, img_shape, flt_h, flt_w, out_h, out_w, stride, pad):
 
    n_bt, n_ch, img_h, img_w = img_shape
   
    cols = cols.reshape(n_ch, flt_h, flt_w, n_bt, out_h, out_w).transpose(3, 0, 1, 2, 4, 5)
    images = np.zeros((n_bt, n_ch, img_h+2*pad+stride-1, img_w+2*pad+stride-1))
   
    for h in range(flt_h):
        h_lim = h + stride*out_h
        for w in range(flt_w):
            w_lim = w + stride*out_w
            images[:, :, h:h_lim:stride, w:w_lim:stride] += cols[:, :, h, w, :, :]

    return images[:, :, pad:img_h+pad, pad:img_w+pad]

class ConvLayer:
    #(self , 입력된 채널수 , 입력이미지 높이 , 입력이미지 폭 , 필터수 , 필터 높이 , 필터 너비 , 스트라이드 너비 , 패딩 너비)
    def __init__(self , x_ch , x_h , x_w , n_flt , flt_h , flt_w , stride , pad):

        self.params=(x_ch , x_h , x_w , n_flt , flt_h , flt_w , stride , pad)#파라미터들을 정리한다.
        self.w= wb_width * np.random.randn(n_flt , x_ch , flt_h , flt_w )#필터의 초기값
        self.b=wb_width * np.random.randn(1, n_flt)#편향의 초기값

        self.y_ch = n_flt  #출력되는 채널 수
        self.y_h = (x_h - flt_h + 2*pad) // stride + 1  #출력 높이
        self.y_w = (x_w - flt_w + 2*pad) // stride + 1  #출력 너비


#_____컨볼루션의 순전파 과정______#
#1.입력이미지를 행렬로 바꾼다 im2col
#2.필터와 im2col로 나온 행렬을 곱한다.
#3.편향을 더한다.
#4.활성화 함수로 ㄱㄱ
    def forward(self, x):
            n_bt = x.shape[0]
            x_ch, x_h, x_w, n_flt, flt_h, flt_w, stride, pad = self.params#파라미터들을 받는다
            y_ch, y_h, y_w = self.y_ch, self.y_h, self.y_w#파라미터들을 받는다.
       
            #입렫된 이미지를 행렬로 바꾼다 im2col
            self.cols = im2col(x, flt_h, flt_w, y_h, y_w, stride, pad)
            self.w_col = self.w.reshape(n_flt, x_ch*flt_h*flt_w)
       
            #필터와 im2col행렬 곱하고 전치한다음에 편향을 더해준다.
            u = np.dot(self.w_col, self.cols).T + self.b
            #출력의 형태로 만들어주고
            self.u = u.reshape(n_bt, y_h, y_w, y_ch).transpose(0, 3, 1, 2)
            #활성화 함수로 ReLu를 써준다.
            self.y = np.where(self.u <= 0, 0, self.u)

#_____컨볼루션의 역전파 과정______#
    def backward(self, grad_y):
        n_bt = grad_y.shape[0]
        x_ch, x_h, x_w, n_flt, flt_h, flt_w, stride, pad = self.params
        y_ch, y_h, y_w = self.y_ch, self.y_h, self.y_w
       
        #활성화 함수의 미분
        delta = grad_y * np.where(self.u <= 0, 0, 1)#ReLu함수의 미분형태
        delta = delta.transpose(0,2,3,1).reshape(n_bt*y_h*y_w, y_ch)#행렬로 변환
       
       
        grad_w = np.dot(self.cols, delta)#필터의 기울기를 구하기 위해 먼저 행렬곱을 구한다.
        self.grad_w = grad_w.T.reshape(n_flt, x_ch, flt_h, flt_w)
        self.grad_b = np.sum(delta, axis=0)#기울기 구한다.
        #입력의 기울기를 구한다.
        grad_cols = np.dot(delta, self.w_col)
        x_shape = (n_bt, x_ch, x_h, x_w)
        self.grad_x = col2im(grad_cols.T, x_shape, flt_h, flt_w, y_h, y_w, stride, pad)
       
    def update(self, eta):
        self.h_w += self.grad_w * self.grad_w
        self.w -= eta / np.sqrt(self.h_w) * self.grad_w
       
        self.h_b += self.grad_b * self.grad_b
        self.b -= eta / np.sqrt(self.h_b) * self.grad_b
       

class PoolingLayer:
    #(self , 입력 채널수 , 이미지 높이 , 이미지 폭 , 풀링영역 크기 , 패딩 너비)
    def __init__(self, x_ch, x_h, x_w, pool, pad):
       
        #파라미터 정리
        self.params = (x_ch, x_h, x_w, pool, pad)
       
        self.y_ch = x_ch  #출력 채널 수
        self.y_h = x_h//pool if x_h%pool==0 else x_h//pool+1  #출력 높이
        self.y_w = x_w//pool if x_w%pool==0 else x_w//pool+1  #출력 너비
       
    def forward(self, x):
        n_bt = x.shape[0]
        x_ch, x_h, x_w, pool, pad = self.params
        y_ch, y_h, y_w = self.y_ch, self.y_h, self.y_w
       
        #im2col을 이용하여 입력 이미지를 행렬로 변환
        cols = im2col(x, pool, pool, y_h, y_w, pool, pad)
        cols = cols.T.reshape(n_bt*y_h*y_w*x_ch, pool*pool)#정치
       
        #맥스풀링을 이용하여 각열의 최댓값을 구한다
        y = np.max(cols, axis=1)
        self.y = y.reshape(n_bt, y_h, y_w, x_ch).transpose(0, 3, 1, 2)
       
        #나중에 역전파에서 사용하기 위해서 최대값 인덱스를 저장해둔다.
        self.max_index = np.argmax(cols, axis=1)
   
    def backward(self, grad_y):
        n_bt = grad_y.shape[0]
        x_ch, x_h, x_w, pool, pad = self.params
        y_ch, y_h, y_w = self.y_ch, self.y_h, self.y_w
        grad_y = grad_y.transpose(0, 2, 3, 1)
       
        grad_cols = np.zeros((pool*pool, grad_y.size))
        grad_cols[self.max_index.reshape(-1), np.arange(grad_y.size)] = grad_y.reshape(-1)
        grad_cols = grad_cols.reshape(pool, pool, n_bt, y_h, y_w, y_ch)
        grad_cols = grad_cols.transpose(5,0,1,2,3,4)
        grad_cols = grad_cols.reshape( y_ch*pool*pool, n_bt*y_h*y_w)

        x_shape = (n_bt, x_ch, x_h, x_w)
        self.grad_x = col2im(grad_cols, x_shape, pool, pool, y_h, y_w, pool, pad)

#전결합층의 부모 클래스
class BaseLayer:
    def __init__(self, n_upper, n):
        self.w = wb_width * np.random.randn(n_upper, n)
        self.b = wb_width * np.random.randn(n)

        self.h_w = np.zeros(( n_upper, n)) + 1e-8
        self.h_b = np.zeros(n) + 1e-8
       
    def update(self, eta):
        self.h_w += self.grad_w * self.grad_w
        self.w -= eta / np.sqrt(self.h_w) * self.grad_w
       
        self.h_b += self.grad_b * self.grad_b
        self.b -= eta / np.sqrt(self.h_b) * self.grad_b
       
#은닉층
class MiddleLayer(BaseLayer):
    def forward(self, x):
        self.x = x
        self.u = np.dot(x, self.w) + self.b
        self.y = np.where(self.u <= 0, 0, self.u)
   
    def backward(self, grad_y):
        delta = grad_y * np.where(self.u <= 0, 0, 1)
       
        self.grad_w = np.dot(self.x.T, delta)
        self.grad_b = np.sum(delta, axis=0)
       
        self.grad_x = np.dot(delta, self.w.T)

#전결합 출력층
class OutputLayer(BaseLayer):    
    def forward(self, x):
        self.x = x
        u = np.dot(x, self.w) + self.b
        self.y = np.exp(u)/np.sum(np.exp(u), axis=1).reshape(-1, 1)

    def backward(self, t):
        delta = self.y - t
       
        self.grad_w = np.dot(self.x.T, delta)
        self.grad_b = np.sum(delta, axis=0)
       
        self.grad_x = np.dot(delta, self.w.T)

Epoch:0/50 Error_train:2.313444925243301 Error_test:2.2732680735004114 Epoch:10/50 Error_train:0.06301925868300119 Error_test:0.11549727354094838 Epoch:20/50 Error_train:0.03182391916705235 Error_test:0.06491675693204245 Epoch:30/50 Error_train:0.02024532185366062 Error_test:0.07937429718617148 Epoch:40/50 Error_train:0.014171703376369495 Error_test:0.06639814055505816