#!/usr/bin/env python # coding: utf-8 # # 5 다중 계층 신경망 # # **합성곱 신경망** # # 1998년 얀 르쿤 등 몇몇 사람들에 의해 처음 소개 # # 손글씨 숫자 인식 문제에 대해 99% 이상의 정확도 달성. # # 중요개념: 합성곱, 풀링 # ## 5.1 합성곱 신경망 # In[1]: from tensorflow.examples.tutorials.mnist import input_data mnist = input_data.read_data_sets("MNIST_data/", one_hot=True) # * 코드 설명 # # 현재 디렉터리 하위에 MNIST_data 디렉터리가 생성. 자동으로 필요한 데이터가 다운로드됨. # # 위 코드를 통해 mnist.train, mnist.test를 얻게 됨. # # mnist.train: 훈련데이터, mnist.test: 테스트데이터 # # mnist.train.image: 훈련이미지, mnist.train.labels: 훈련레이블 # In[2]: import tensorflow as tf x = tf.placeholder("float", shape=[None, 784]) y_ = tf.placeholder("float", shape=[None, 10]) # * 코드 설명 # # 텐서플로 플레이스 홀더를 정의. # In[3]: x_image = tf.reshape(x, [-1, 28, 28, 1]) # * 코드 설명 # # 입력 데이터를 원래 이미지 구조로 재구성. # # 4D 텐서. 마지막 차원은 이미지의 컬러 채널로 여기서는 1. # ### 5.1.1 합성곱 계층 # # 목적: 테두리, 선, 색 등 이미지의 시각적 특징(characteristic)이나 특성(feature)을 감지 # # 입력 데이터가 첫번째 은닉 계층의 뉴런에 완전 연결되어(fully connected)있지 않음. # # 은닉 계층의 각 뉴런은 입력 계층의 윈도(window) 영역(이 예시는 5x5)과 연결. # # 윈도 영역이 전체 입력 계층을 훑고 지나가면서 (여기서는) 24x24 크기의 은닉 계층을 만들게 됨. # # 스트라이드(stride): 윈도우 영역이 한 번에 얼만큼 움직일 지 결정하는 매개변수. # # 패딩(padding): 0(또는 다른 값)으로 이미지의 바깥 테두리를 채움. 체울 테두리의 크기를 지정하는 매개변수. # # http://cs231n.github.io/convolutional-networks/ # # 스탠퍼드의 강의문서: 매개변수에 대한 더 자세한 내용이 실려있음. # # 윈도(window) 영역(이 예시는 5x5)에 대한 가중치 행렬 W, 편향 b가 필요. # # 은닉 계층의 모든 뉴런이 행렬 W, 편향 b를 공유함. (24x24(576)개의 뉴런이 같은 W,b를 사용.) # # 커널(Kernel), 필터(filter): 공유 행렬 W와 편향 b # # https://docs.gimp.org/en/plug-in-convmatrix.html # # GIMP 매뉴얼의 예제문서: 합성곱이 어떻게 작동하는지? # # 하나의 가중치 행렬과 하나의 편향으로 하나의 커널을 정의. # # 감지하고 싶은 각 특징에 한 개씩 여러 개의 커널을 사용하는 것이 좋음. # # 합성곱 계층(convolution layer): 첫 번째 은닉 계층은 여러 개의 특징 맵으로 구성. (이 예시는 32개 커널 사용) # ### 5.1.2 풀링 계층 # # 풀링 계층(pooling): 합성곱 계층의 출력 값을 단순하게 압축하고 합성공 계층이 생산한 정보를 컴팩트한 버전으로 만들어줌. # # 합성곱 계층 뒤에 따라오는 것이 일반적. # # 맥스 풀링(max-polling): 2x2영역에서 가장 큰 값을 선택해서 정보를 다시 압축. # # 24x24 합성곱의 결과를 2x2 영역으로 분할하면 12x12 개의 조각에 해당하는 12x12 크기의 맥스 풀링 계층이 만들어짐. # **풀링을 하는 이유** # # local invariance 측면에서 강한 구조를 만들기 위함. # # 어떤 물체를 인식한다고 했을 때, 그 물체가 image상 어디에 위치하더라도 잘 인식해낼 수 있게 하는 과정 중 하나 # ## 5.2 모델구현 # # https://www.tensorflow.org/versions/master/tutorials/mnist/pros/ # # 텐서플로 웹사이트에 있는 전문가를 위한 MNIST 예제문서를 참조 # In[4]: def weight_variable(shape): initial = tf.truncated_normal(shape, stddev=0.1) return tf.Variable(initial) def bias_variable(shape): initial = tf.constant(0.1, shape=shape) return tf.Variable(initial) # * 코드 설명 # # 가중치는 임의잡음(random noise)으로 초기화, 편향은 0.1을 갖도록 초기화. # # **tf.truncated_normal(shape, mean=0.0, stddev=1.0, dtype=tf.float32, seed=None, name=None)** # # 일부 버려진 정규분포로부터 임의의 값을 출력합니다. # 특정 평균값과 표준 편차로 정규 분포를 따르는 생성된 값은 지정한 평균의 2 표준편차보다 큰 값은 제외하며 이 값은 버려지고 다시 선택됩니다. # # https://www.tensorflow.org/versions/master/api_docs/python/constant_op/random_tensors#truncated_normal # In[5]: def conv2d(x, W): return tf.nn.conv2d(x, W, strides=[1, 1, 1, 1], padding='SAME') # * 코드 설명 # # 각 차원 방향으로 스트라이드를 1로 하고 패딩은 'SAME'으로 지정합니다. (출력이 입력과 같은 크기로 나오도록 합니다.) # # padding='VALID'로 바꾸면 패딩을 하지 않고 출력이 나오게 됩니다. (출력데이터가 손실됩니다.) # # https://www.tensorflow.org/versions/master/api_docs/python/nn/convolution#conv2d # In[6]: def max_pool_2x2(x): return tf.nn.max_pool(x, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME') # * 코드 설명 # # 풀링은 2x2 크기의 맥스 풀링을 적용. # In[7]: W_conv1 = weight_variable([5, 5, 1, 32]) b_conv1 = bias_variable([32]) # * 코드 설명 # # 윈도우 크기가 5x5인 32개 필터를 사용. [5, 5, 1, 32]인 가중치 행렬 W를 저장한 텐서를 정의. # # 처음 2개는 윈도우 크기. 세 번째는 컬러채널. 우리 예제는 1. 마지막은 얼마나 많은 특징을 사용할 지 정의. # In[8]: h_conv1 = tf.nn.relu(conv2d(x_image, W_conv1) + b_conv1) print(h_conv1) # shape = (?, 28, 28, 32) h_pool1 = max_pool_2x2(h_conv1) print(h_pool1) # shape = (?, 14, 14, 32) # * 코드 설명 # # 렐루(Rectified Linear Unit, ReLU): 은닉셰층에서 거의 기본적으로 사용되는 활성화 함수. # # max(0,x)를 리턴. # # x_image에 대해 합성곱을 적용. 합성곱의 결과를 2D 텐서 W_conv1에 리턴. # # 여기에 편향을 더해 렐루 활성화 함수 적용. # # 출력 값을 구하기 위해 맥스 풀링을 적용. # In[9]: W_conv2 = weight_variable([5, 5, 32, 64]) b_conv2 = bias_variable([64]) # * 코드 설명 # # 5x5 윈도에 64개 필터를 갖는 두 번째 합성곱 계층을 만듬. # # 이전 계층의 출력 값의 크기(32)를 채널의 수로 넘김. # In[10]: h_conv2 = tf.nn.relu(conv2d(h_pool1, W_conv2) + b_conv2) print(h_conv2) # shape = (?, 14, 14, 64) h_pool2 = max_pool_2x2(h_conv2) print(h_pool2) # shape = (?, 7, 7, 64) # * 코드 설명 # # 14x14 크기 행렬 h_pool1에 스트라이드 1로 5x5 윈도를 적용하여 합성곱 계층을 만듬. # # 맥스 풀링까지 거쳐 크기는 7x7이 됨. # In[11]: W_fc1 = weight_variable([7 * 7 * 64, 1024]) b_fc1 = bias_variable([1024]) # * 코드 설명 # # 마지막 소프트맥스 계층에 주입하기 위해 7x7 출력 값을 완전 연결 계층에 연결. # # 전체 이미지 처리를 위해 1024개의 뉴런을 사용. # # 첫 번째 차원: 7x7 크기의 64개 필터를 뜻함. # # 두 번째 차원: 임의로 선택한 뉴런의 개수. (여기서는 1024) # In[12]: h_pool2_flat = tf.reshape(h_pool2, [-1, 7 * 7 * 64]) h_fc1 = tf.nn.relu(tf.matmul(h_pool2_flat, W_fc1) + b_fc1) # * 코드 설명 # # 소프트맥스 함수는 이미지를 직렬화해서 벡터 형태로 입력. # # 가중치 행렬 W_fc1과 일차원 벡터를 곱하고 편향 b_fc1을 더한 후 렐루 활성화 함수를 적용. # In[13]: keep_prob = tf.placeholder("float") h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob) # 드롭아웃(dropout, 중도탈락): 신경망에서 필요한 매개변수 수를 줄이는 것. # # 노드를 삭제하여 입력과 출력사이의 연결을 무작위로 제거. # # 뉴런이 제거되거나 그렇지 않을 확률은 코드로 처리하지 않고 텐서플로에 위임. # # 오버피팅(overfitting, 과적합) # # 아주 많은 뉴런을 사용한다면 매우 상세한 모델이 만들어짐. # # 임의의 잡음(혹은 오차)도 모델에 포함될 수 있음. # # * 코드 설명 # # 마지막 소프트맥스 계층 전에 tf.nn.dropout 함수를 사용하여 드롭아웃을 적용. # # 뉴런이 드롭아웃되지 않을 확률을 저장할 플레이스홀더를 만듬. # In[14]: W_fc2 = weight_variable([1024,10]) b_fc2 = bias_variable([10]) y_conv = tf.nn.softmax(tf.matmul(h_fc1_drop, W_fc2) + b_fc2) # * 코드 설명 # # 소프트맥스 계층을 추가. # # 입력 이미지가 각 클래스(여기서는 0~9까지 숫자)에 속할 확률을 리턴. 이 확률의 전체 합은 1이 됨. # ## 5.3 모델 훈련 및 평가 # # 모델이 얼마나 잘 수행되는지 알기 위해서는 이전 장의 예제들이 했던 방식을 따라야 함. # # 경사 하강법 최적화 알고리즘 => ADAM 알고리즘 # # https://www.tensorflow.org/versions/master/api_docs/python/train/optimizers#AdamOptimizer # # ADAM 최적화 알고리즘이 특별한 장점을 가지기 때문에 사용함. # # 드롭아웃 계층의 확률을 조절하는 추가 매개변수 keep_prob도 feed_dict 인수를 통해 전달. # In[15]: cross_entropy = -tf.reduce_sum(y_*tf.log(y_conv)) train_step = tf.train.AdamOptimizer(1e-4).minimize(cross_entropy) correct_prediction = tf.equal(tf.argmax(y_conv,1), tf.argmax(y_,1)) accuracy = tf.reduce_mean(tf.cast(correct_prediction, "float")) sess = tf.Session() sess.run(tf.initialize_all_variables()) for i in range(1000): batch = mnist.train.next_batch(100) if i % 100 == 0: train_accuracy = sess.run(accuracy, feed_dict={x:batch[0], y_:batch[1], keep_prob: 1.0}) print("step %d, training accuracy %g" % (i, train_accuracy)) sess.run(train_step, feed_dict={x:batch[0], y_:batch[1], keep_prob: 0.5}) print("test accuracy %g" % sess.run(accuracy, feed_dict={x: mnist.test.images, y_: mnist.test.labels, keep_prob: 1.0})) # 실행 모델은 99.2% 정확도를 보임. # # 많은 계층을 가진 네트워크는 훈련시간이 많이 걸리기 때문에 # # 훈련 시간을 대폭 줄일 수 있는 CPU를 어떻게 사용하는지 다음에 설명.