페이지

2018년 4월 18일 수요일

import numpy as np

# 7.1 전체 구조
"""
합성곱 신경망convolutional neural network, CNN
(합성곱 : 컨볼루션. 두 함수 중 하나를 반전, 이동시켜가며 나머지 함수와의 곱을 연이어 적분하는 것)
지금까지 본 신경망은 인접하는 계층의 모든 뉴런과 결합되어 있었다. 이를 완전연결fully-connected
라고 하며, 이를 Affine 계층이라는 이름으로 구현했다.
CNN에는 여기에 합성곱 계층과 풀링 계층이 추가된다.
Affine-ReLu가 Conv-ReLU-(Pooling)으로 바뀌며 출력에 가까운 층에서는 Affine-ReLU 구성을
사용할 수 있다. 출력 계층에서는 Affine-Softmax 조합을 사용한다.
"""

# 7.2 합성곱 계층
# 7.2.1 완전연결 계층의 문제점
"""
완전연결Affine 계층 : 인접하는 계층의 뉴런이 모두 연결되고 출력의 수는 임의로 정할 수 있다.
단점은 데이터의 형상이 무시된다.(가로, 세로, 채널로 구성된 3차원 데이터인 이미지를 1차원 데이터로
평탄화해줘야 한다.)
합성곱 계층은 형상을 유지하기 때문에 형상을 가진 데이터의 패턴을 이해할 수 있다.
CNN에서는 입출력 데이터를 특징 맵feature map이라고도 한다.
"""

# 7.2.2 합성곱 연산
"""
이미지 처리에서 말하는 필터 연산에 해당한다.
입력데이터  * 필터  →  출력 데이터
(4, 4)     (3, 3)    (2, 2)
1 2 3 0    2 0 1     15 16
0 1 2 3    0 1 2      6 15
3 0 1 2    1 0 2
2 3 0 1
필터의 윈도우를 일정 간격으로 이동해가며 입력 데이터에 적용한다. 입력과 필터의 원소를 곱한 후
총합을 구한다(단일 곱셈-누산fused multiply-add, FMA) 결과를 출력에 저장한다.
이 과정을 모든 장소에서 수행한다.
(합성곱은 교차상관과 비슷한 연산이다. 플리핑을 하면 합성곱, 아니면 교차상관. 딥러닝에서는 이를
잘 구분하지 않으며, 딥러닝 라이브러리에서는 플리핑하지 않거나 플리핑 여부를 인수로 받는다.)

CNN에서는 필터의 매개변수가 가중치에 해당하며, 편향 역시 존재한다.
입력데이터  * 필터              + 편향  → 출력데이터
(4, 4)     (3, 3)    (2, 2)           (2, 2)
1 2 3 0    2 0 1     15 16     3      18 19
0 1 2 3    0 1 2      6 15             9 18
3 0 1 2    1 0 2
2 3 0 1
"""

# 7.2.3 패딩
"""
패딩 : 합성곱을 수행 전 입력 데이터 주변을 특정 값(0 등)으로 채우는 것.
주로 출력 크기 조정을 목적으로 사용한다. 출력 크기가 커지기 때문에 입력 데이터의 공간적 크기를
유지한 채로 다음 계층에 전달할 수 있다.
"""

# 7.2.4 스트라이드
"""
스트라이드stride : 필터를 적용하는 위치의 간격. 일반적으로는 1이지만
2로 하면 필터를 적용하는 윈도우가 두 칸씩 이동한다. 스트라이드를 키우면 출력 크기가 작아진다.

이 값들에는 아래의 관계가 있다.
OH = (H + 2P + FH)/S + 1
OW = (W + 2P + FW)/S + 1
입력 크기 : (H, W)
필터 크기 : (FH, FW)
출력 크기 : (OH, OW)
패딩 : P, 스트라이드 : S
최종 결과는 정수로 나와야 한다.
"""

# 7.2.5 3차원 데이터의 합성곱 연산
"""
이미지는 가로 세로 채널이 있는 3차원 데이터이다. 채널의 수 만큼 필터가 필요하다.
모든 채널의 필터는 같은 크기여야 한다.
"""

# 7.2.6 블록으로 생각하기
"""
데이터와 필터를 직육면체 블록이라고 생각하면 쉽다. 이를 (채널, 높이, 너비) 순서로 나타낸다.
입력 데이터 * 필터    →    출력 데이터
(C, H, W)  (C, FH, FW) (1, OH, OW)

출력으로 다수의 채널을 내보내려면 필터를 여러 개(FN개) 사용하면 된다.
따라서 필터의 가중치 데이터는 4차원이 된다. 편향은 채널 하나에 값 하나씩으로 구성된다.(FN, 1, 1)
입력 데이터 *    필터         + 편향  →    출력 데이터
(C, H, W)  (FN, C, FH, FW)  (FN, 1, 1) (FN, OH, OW)
"""

# 7.2.7 배치 처리
"""
데이터 N개를 배치 처리한다면 데이터 형태는 다음과 같다.

입력 데이터(N개) * 필터(FN개)  +  편향   →   출력 데이터(N개)
(N, C, H, W) (FN, C, FH, FW) (FN, 1, 1) (N, FN, OH, OW)
"""

# 7.3 풀링 계층
"""
풀링 : 가로 세로 방향의 공간을 줄이는 연산. 여러 영역을 원소 하나로 집약하여 공간 크기를 줄인다.
2*2 최대 풀링max pooling을 스트라이드 2로 처리하는 예
(2*2 영역에서 가장 큰 원소 하나를 꺼낸다.)
1 2 3 0    2  3
0 1 2 3
3 0 1 2    4  2
2 3 0 1

일반적으로 윈도우 크기와 스트라이드는 같은 값으로 설정한다.
최대 풀링 외에도 평균 풀링 등이 있다. 이미지 인식 분야에서는 주로 최대 풀링을 사용한다.
"""

# 7.3.1 풀링 계층의 특징
"""
학습해야 할 매개변수가 없다 : 최댓값이나 평균을 취하는 명확한 연산이다.
채널 수가 변하지 않는다 : 채널 독립적으로 계산한다.
입력의 변화에 영향을 적게 받는다(강건하다) : 입력데이터가 조금 변해도 풀링의 결과는 잘 변하지 않는다.
"""

# 7.4 합성곱/풀링 계층 구현하기
# 7.4.1 4차원 배열
"""
CNN의 데이터는 4차원이다.
"""
x = np.random.rand(10, 1, 28, 28)  # 무작위 데이터 생성
print(x.shape)

# 첫 번째 데이터, 두 번째 데이터
print(x[0].shape)  # (1, 28, 28)
print(x[1].shape)  # (1, 28, 28)

# 첫 번째 데이터의 첫 채널의 공간 데이터
print(x[0, 0])  # or x[0][0]. 28 * 28의 벡터

# 7.4.2 im2cal로 데이터 전개하기
"""
합성곱을 구현하려면 다중 for문을 사용한다. 이는 복잡하고 numpy에서는 for문을 사용하면
성능이 떨어지기 때문에 im2col()이라는 함수를 사용해 구현한다.
im2col(image to column)은 필터를 적용하는 영역(3차원 블록)을 한 줄로 늘어놓는다.
4차원 데이터가 2차원이 된다. 여러 딥러닝 프레임워크에서 해당 함수를 구현해 사용하고 있다.
메모리를 더 많이 소비하는 단점이 있지만 행렬 계산 라이브러리는 큰 행렬의 계산에 장점이 있으므로
효율이 높아진다.

입력데이터를 2차원으로, 필터를 세로 1열로 전개해 내적을 취한 뒤 이를 4차원으로 reshape한다.
"""


# coding: utf-8
import sys, os
sys.path.append(os.pardir)  # 부모 디렉터리의 파일을 가져올 수 있도록 설정
import numpy as np
import matplotlib.pyplot as plt
from simple_convnet import SimpleConvNet
from matplotlib.image import imread
from common.layers import Convolution

def filter_show(filters, nx=4, show_num=16):
    """
    c.f. https://gist.github.com/aidiary/07d530d5e08011832b12#file-draw_weight-py
    """
    FN, C, FH, FW = filters.shape
    ny = int(np.ceil(show_num / nx))

    fig = plt.figure()
    fig.subplots_adjust(left=0, right=1, bottom=0, top=1, hspace=0.05, wspace=0.05)

    for i in range(show_num):
        ax = fig.add_subplot(4, 4, i+1, xticks=[], yticks=[])
        ax.imshow(filters[i, 0], cmap=plt.cm.gray_r, interpolation='nearest')


network = SimpleConvNet(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)

# 학습된 가중치
network.load_params("params.pkl")

filter_show(network.params['W1'], 16)

img = imread('../dataset/lena_gray.png')
img = img.reshape(1, 1, *img.shape)

fig = plt.figure()

w_idx = 1

for i in range(16):
    w = network.params['W1'][i]
    b = 0  # network.params['b1'][i]

    w = w.reshape(1, *w.shape)
    #b = b.reshape(1, *b.shape)
    conv_layer = Convolution(w, b)
    out = conv_layer.forward(img)
    out = out.reshape(out.shape[2], out.shape[3])
   
    ax = fig.add_subplot(4, 4, i+1, xticks=[], yticks=[])
    ax.imshow(out, cmap=plt.cm.gray_r, interpolation='nearest')

plt.show()










# coding: utf-8
import numpy as np


def smooth_curve(x):
    """손실 함수의 그래프를 매끄럽게 하기 위해 사용
   
    참고:http://glowingpython.blogspot.jp/2012/02/convolution-with-numpy.html
    """
    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]



2018년 4월 16일 월요일

chapter 6 학습관련 기술들

http://shuuki4.github.io/deep%20learning/2016/05/20/Gradient-Descent-Algorithm-Overview.html


http://umbum.tistory.com/217?category=751025


2018년 4월 1일 일요일

CHAPTER 14 가중치의 진짜 업데이트

아직 우리는 인공신경망에서 가중치를 어떻게 업데이트해야 하는가의 핵심적인 질문에 답하지 않았습니다. 지금가지의 고정은 이에 대한 답을 하기 위한 과정이었으며 이제 거의 다 왔습니다. 이 비밀을 풀기 위해 반드시 이해해야 할 핵심 아이디어 한 가지만 더 이해하면 됩니다!

지금까지는 네트워크의 각 계층에 걸쳐 역전파되는 오차를 구해봤습니다. 이처럼 오차를 구하는 이유는 인공 신경망이 보다 나은 답을 출력하게 하기 위해 가중치를 조정해가는 데 지침 역할 을 하는 것이 오차이기 때문입니다. 이러한 과정은 이 책의 앞 부분에 나왔던 선형 분류자 예제에서부터 우리가 보아왔던 것입니다.
 하지만 신경망에서 노드는 단순한 선형 분류자가 아닙니다. 노드는 입력되는 신호에 가중치를 적용한 후 이의 합을 구하고 다시 여기에 시그모이드 활성화 함수를 적용하는 식으로 좀 더 복잡한 구조를 가집니다. 그렇다면 이처럼 정교한 노드 사이를 연결하는 연결 노드의 가중치를 어떻게 업데이트해야 할까요? 우리는 왜 어떤 끝내주는 대수학공식을 이용해 가중치를 단번에 구해낼 수 없는 것일까요?

- 가중치 계산
신경망에는 너무나도 많은 가중치의 조합이 존재합니다. 또한 신호가 여러 개의 계층을 타고 전파되어나갈 때 한 계층을 거칠 때마다 직전 계층의 출력 값이 다음 계층의 입력값이 되므로 함수의 함수,ㅡ 함수의 함수의 함수...같은 식으로 수많은 함수의 조합이 필요하게 됩니다. 따라서 수학 연산의 과정이 너무 복잡하게 되므로 가중치를 한 방에 풀어주는 대수학을 활용할 수 없는 것입니다.

CHAPTER 13 행렬곱을 이용한 오차의 연전파

우리는 앞 장에서 실제 숫자를 대입해 역전파를 이해하고자 했습니다. 하지만 실제 역전파는 매우 복잡하겠죠? 그렇다면 우리는 역전파에도 행렬곱을 이용할 수 있을까요? 기억하겠지만 우리는 이미 9장에서 앞쪽으로 전파법에 대해 행렬곱으로 계산한 바 있습니다.

이번에는 오차의 역전파를 행렬곱으로 간결하게 만들 수 있는지 보기 위해서 단계별 기호를 이용해 적어보겠습니다. 이렇게 행렬곱으로 표현하는 방식을 벡터화한다(vectorize)라고 합니다.

수많은 연산을 행렬의 형태로 표현하게 되면 우리가 손으로 쓰기에도 편하지만 무엇보다도 컴퓨터가 반복적인 유사한 연산을 순식간에 처리할 수 있게 되므로 훨씬 더 효율적으로 작업을 수행할 수 있다는 장점을 가지게 됩니다.

최종 출력 계층으로 부터 나오는 오차로부터 시작해보겠습니다. 출력 계층에 2개의 노등가 있는 예제를 보고 있었으니 여기에서의 오차는 다음과 같이 표현할 수 있습니다.

error(output) = ( e1, e2)

다음으로는 은닉계층의 오차를 행렬로 구성해보겠습니다. 어렵게 느껴질 수 있으니 단계별로 차근차근 보겠습니다. 우선 은닉 계층의 첫 번째 노드부터 보겠습니다. 앞장의 그림을 다시 보면 은닉 계층의 첫 번째 노드의 오차에 영향을 주는 것은 출력 계층으로 부터의 2개의 경로임을 확인할 수 있습니다. 이 2개의 경로를 통해 전달되는 오차 신호는 각각 e1 * w11 /(w11 + w21)과 e2 * w12/(w12 + w22)입니다. 은닉 계층의 두 번째 노드는 마찬가지로 각각 e1 * w21 /(w21 + w11)과 e2 * w22 /(w22 + w12)가 됩니다. 만약 이 식이 혼동이 되는 분은 반드시 12장을 다시 한번 학습하고 돌아오기 바랍니다.

그렇다면 이제 은닉 계층의 오류를 다음과 같이 행렬로 표현할 수 있습니다.

이 행렬을 보다 간결하게 행렬곱으로 다시 쓸 수 있다면 좋을 것 같ㅅ브니다. 마치 전파법에서 봤던 것 처럼 말입니다.

하지만 안타깝게도 전파법에서 봤던 수준으로 간결하게 이 행렬을 다시 쓸 수는 없습니다. 위의 복잡한 수식을 풀어내기가 쉽지는 않기 때문입니다. 그렇다면 어떻게 해야 할까요? 정말 간결하게 쓰고 싶기는 한데 말입니다.

위의 수식을 다시 봅시다. 이 수식에서 핵심은 출력 오차인 en과 가중치 wij의 곱셈 연산입니다. 가중치가 크면 클수록 더 많은 출력 오차가 은닉계층으로 돌아오게 됩니다. 이것이 핵심입니다. 분수에서 분모 부분은 일정의 정규화 인자(nomalizing factor)라고 할 수 있습니다. 만약 정규화 인자를 무시한다고 해도 우리는 되돌아오는 오창의 일정 비율 정도를 잃을 뿐입니다. 따라서 e1 * w11/(w11+w21)을 간단하게 e1 * w11로 표현해도 되는 것입니다.

가중치 행렬은 우리가 8장에서 만들었던 행렬과 유사해 보이지만 자세히 보면 대각선 방향으로 원소가 뒤바뀐 것을 알 수 있습니다. 즉 우측 상단의 가중치와 좌측 하단의가중치가 바뀌어 있습니다. 이처럼 핻렬릐 행과 열을 바꾸는 것을 전치(transpose)한다고 부르며, 행렬 w의 전치 행렬을 기호로는 wt라고 표기합니다.

다음 그림에서 전치 행렬의 두 가지 예를 보겠습니다. 전치 행렬은 행과 열의 개수가 다른 경우에도 가능함을 이해하기 바랍니다.
이제 다음과 같이 오차의 역전파를 행렬로 간결하게 표현할 수 있게 되었습니다.

error(hidden) = wt(hidden_output) * error(output)

간결하게 표현해서 좋기는 하지만 정규화 인자를 생략했다는 점이 마음에 걸립니다. 이렇게 생각해 버려도 괜찮은 것일까요? 결론부터 말하면 이처럼 오차 신호의 피드백을 단순화하더라도 정규화 인자가 있을 때와 마찬가지로 잘 동작한다는 사실이 밝혀져 있습니다. 오차를 역전파하는 몇 가지 방ㅂ버을 비굑한 결과를 블로그에 올렸으니 관심 있는 분들은 참고하기 바랍니다(http://bit.ly/2m2z2YY). 간결한 방식이 문제 없이 작동한다면. 더 어려운 방법을 쓸 필요는 없겠죠!

이점에 대해 조금 더 말하면, 설령 다소 크거나 작은 오차가 역전파되었다고 하더라도 우리의 인공 신경망은 다음 학습 단계에서 이를 스스로 바로 잡아갑니다.
오차에 대한 책임을 분산시키는 데 지침이 되는 것은 가중치의 크기이므로 역전파되는 오차가 연결 노드의 가중치의 크기를 중시한다는 점이 핵심입니다.

지금까지 정말 많은 것을 학습하느라 고생 많으셨습니다. 이론과 관련한 마지막 장이 될 다음 장으로 넘어가기 전에 잠깐 쉬어도 좋을 것 같습니다. 다음 장은 정말 멋지지만 살짝 골치 아픈 내용을 다루기 때문입니다.

핵심 정리
- 오차의 역전파를 행렬곱으로 표현할 수 있습니다.
- 오차의 역전파를 행렬곱으로 표현함으로써 우리는 네트워크의 크기에 상관없이 이를 간결하게 표현할 수 있으며, 컴퓨터를 통해 보다 효율적이고 빠르게 업무를 처리하게 할 수 있습니다.
- 이제 우리는 전파법과 역전파 모두 행렬곱을 통해 효율적으로 처리할 수 있다는 사실을 알게 되었습니다.