5. 텐서플로우 다중 레이어 뉴럴 네트워크 – First Contact with TensorFlow

tensorflowbookcover-1024x7122x이 글은 스페인 카탈루냐 공과대학의 Jordi Torres 교수가 텐서플로우를 소개하는 책 ‘First Contack with TensorFlow‘을 번역한 것입니다. 이 글은 원 도서의 라이센스(CC BY-NC-SA 3.0)와 동일한 라이센스를 따릅니다. 파이썬 3  notebook으로 작성한 이 섹션의 코드는 여기에서 보실 수 있습니다.

목록으로가기

 

이 장에서는 이전 장에서 보았던 MNIST 손글씨 숫자를 인식하는 문제를 가지고 간단한 딥러닝(Deep Learning) 뉴럴 네트워크를 만들어 보겠습니다.

앞서 말했듯이 딥러닝 뉴럴 네트워크는 여러개의 레이어를 차곡차곡 쌓아 구성한 것 입니다. 특별히 이 장에서는 딥러닝의 전형적인 예인 콘볼루션(convolution) 뉴럴 네트워크를 만들겠습니다. 콘볼루션 뉴럴 네트워크는 1998년 Yann LeCun외 몇몇 사람들에 의해 처음 소개되어 널리 사용되고 있습니다. 콘볼루션 네트워크는 최근 이미지 인식분야에서 놀라운 성능을 내고 있습니다. 예를 들어 우리가 다루려는 손글씨 숫자 인식 문제에 대한 정확도는 99% 이상 입니다.

이 장의 나머지 부분에서 콘볼루션 네트워크의 두가지 중요한 개념을 예제 코드와 함께 진행하면서 설명하겠습니다. 그 두가지는 콘볼루션과 풀링(pooling)으로 이 책은 입문서라 파라메타의 상세한 부분은 다루지 않습니다. 그러나 독자들이 이 코드를 모두 실행해 보고 콘볼루션 네트워크에 대한 큰 그림의 이해를 가질 수 있기를 바랍니다.

콘볼루션 뉴럴 네트워크

콘볼루션 뉴럴 넷(CNN 또는 ConvNet 으로도 불림)은 딥러닝의 특별한 케이스이며 컴퓨터 비전 분야에 커다란 영향을 주어 왔습니다.

CNN의 전형적인 특징은 거의 항상 입력 데이터로 이미지를 받는 것으로 뉴럴 네트워크를 효율적으로 구현할 수 있고 필요한 파라메타의 수를 줄일 수 있습니다. 그럼 MNIST 손글씨 이미지 인식 문제를 시작해 봅니다. 이전 장에서 했던 것처럼 MNIST 데이터를 읽고 난 후 텐서플로우 플레이스홀더를 정의합니다.

from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets('MNIST_data', one_hot=True)

import tensorflow as tf

x = tf.placeholder("float", shape=[None, 784])
y_ = tf.placeholder("float", shape=[None, 10])

아래와 같이 입력 데이터를 원래 이미지의 크기로 재 구성합니다.

x_image = tf.reshape(x, [-1,28,28,1])

여기서 우리는 입력 데이터의 크기를 4D 텐서로 바꾸었습니다. 두번째와 세번째 차원은 이미지의 넓이와 높이이고 마지막 파라메타는 이미지의 컬러 채널로 여기서는 1(역주: 흑백 이미지이므로) 입니다. 아래 그림처럼 뉴럴 네트워크의 입력을 28×28 크기의 2차원 공간의 뉴런들이라고 생각할 수 있습니다.

image072

콘볼루션 뉴럴 네트워크를 정의하는 필터(filter)와 특성 맵(characteristic map) 두가지 기본 원리가 있습니다. 이 원리들은 앞으로 보게 될 특별한 뉴런의 집합이라고 볼 수 있습니다. 먼저 CNN에서 중요한 이 두가지 요소에 대해 간단하게 소개 하겠습니다.

콘볼루션 뉴럴 네트워크의 주요 목적은 테두리(edge), 선(line), 색깔 등 이미지의 시각적 특징이나 성질을 감지하는 것입니다. 이것은 이전에 언급했었던 입력 레이어와 연결된 히든 레이어에 의해 처리됩니다. CNN은 재미있게도 입력 데이터가 첫번째 히든 레이어의 뉴런에 완전히 모두 연결되어(fully connected) 있지 않습니다. 이미지의 픽셀 정보를 저장하고 있는 입력 뉴런의 작은 일부 영역이 하나의 히든 레이어 뉴런과 연결됩니다. 그림으로 보면 아래와 같습니다.

image074

정확하게 말하면 이 예에서 히든 레이어의 각 뉴런은 입력 레이어의 5×5 영역(즉 25개의 뉴런)과 연결됩니다.

이것을 입력 이미지를 담고 있는 28×28 크기의 전체 입력 레이어를 흟고 지나가는 5×5 사이즈의 윈도우로 생각할 수 있습니다. 윈도우는 레이어의 전체 뉴런을 슬라이딩하여 지나갑니다. 그리고 윈도우의 각 위치마다 입력 데이터를 처리하기 위한 히든 레이어의 뉴런이 배정됩니다.

윈도우가 이미지의 왼쪽 위부터 시작되고 입력 레이어의 첫번째 뉴런이 그 정보를 받는다고 가정합니다. 그러면 다음 윈도우는 한 픽셀 오른쪽으로 이동하여 이 5×5 영역을 히든 레이어의 다음 뉴런과 연결합니다. 이런 식으로 왼쪽에서 오른쪽으로 위에서 아래로 전체 영역을 커버할 때까지 계속합니다.

image076

이런 방법으로 실제 적용을 해 보면 28×28 사이즈의 입력 이미지에 대해 5×5 사이즈의 윈도우는 24×24  크기의 첫번째 히든 레이어 뉴런들을 만듭니다. 왜냐하면 입력 이미지의 오른쪽 아래 끝까지 가려면 오른쪽으로 23번 아래로 23번만 움직일 수 있기 때문입니다. 윈도우는 1 픽셀씩만 움직이므로 새 윈도우는 같은 선상에 있었던 이전 윈도우와 겹쳐지게 됩니다.

그러나 콘볼루션 레이어에서 한번에 1 픽셀 이상 움질일 수도 있습니다. 이 파라메타를 스트라이드(stride)라고 부릅니다. 또 한가지 생각할 것은 좀 더 좋은 결과를 내기 위해 이미지 밖으로 윈도우가 넘어갈 수 있도록 0(또는 다른 값)으로 테두리를 채우는 것입니다. 이 기능을 하는 파라메타는 채울 테두리의 크기를 지정하는 것으로 패딩(padding)이라고 부릅니다. 이 책은 입문 수준이므로 이 두 파라메타에 대해 더 자세히 들어가진 않겠습니다.

이 예제에서 입력 레이어와 히든 레이어의 뉴런을 연결하기 위해서는 앞장의 공식을 따라 5×5 가중치 행렬 W 와 바이어스 b 가 필요합니다. CNN의 핵심 특징은 가중치 행렬 W 와 바이어스 b 를 히든레이어의 모든 뉴런이 공유하는 점입니다. 즉 히든 레이어의 모든 뉴런에 대해, 여기서는 24×24(576)개의 뉴런, 같은 Wb 를 사용합니다. 이로인해 완전 연결 뉴럴 네트워크에 비해 상당한 양의 가중치 파라메타가 감소된다는 것을 알 수 있습니다. 정확하게는 가중치 행렬 W 를 공유함으로써 14000(5x5x24x24)에서 단 25(5×5)개로 줄어듭니다.

이 공유 행렬 W 와 바이어스 b 를 CNN 에서는 보통 커널(kernel) 혹은 필터라고 부릅니다. 고유한 특징을 찾기 위해 사용하는 이런 필터는 리터치된 이미지를 만드는 이미지 프로세싱 프로그램에서 사용하는 것과 유사합니다. 콘볼루션이 어떻게 작동하는지 감을 잡는데 GIMP 매뉴얼의 예제를 확인해 보시길 추천합니다.

하나의 행렬과 바이어스로 커널을 정의했습니다. 하나의 커널은 이미지에서 한 종류의 특징만을 감지합니다. 그러므로 감지하고 싶은 각 특징에 한개씩 여러개의 커널을 사용하는 것이 좋습니다. CNN에서 완전한 콘볼루션 레이어는 보통 여러개의 커널로 구성됩니다. 여러개의 커널을 표현하는 보편적인 방법은 아래와 같습니다.

image078

첫번째 히든 레이어는 여러개의 커널로 구성됩니다. 이 예에서는 32개의 커널을 사용했습니다. 각 커널은 5×5 가중치 행렬 와 한개의 바이어스 b 로 정의되고 이 히든 레이어의 뉴런들에게 공통으로 사용됩니다.

코드를 잘 정돈하려고 가중치 행렬 W 와 바이어스 b 와 연관된 두개의 함수를 아래에 정의했습니다.

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)으로 초기화하고 바이어스는 작은 양수를 갖도록 초기화했습니다.

위에 언급한 콘볼루션 레이어 이외에 풀링(pooling) 레이어라고 불리우는 레이어가 콘볼루션 레이어 뒤에 따라 오는게 일반적입니다. 풀링 레이어는 콘볼루션 레이어의 출력 값을 단순하게 압축해주고 콘볼루션 레이어가 생산해낸 정보를 컴팩트한 버전으로 만들어 줍니다. 이 예제에서는 콘볼루션 레이어의 2×2 영역을 풀링을 사용하여 하나의 포인트로 데이터를 압축하겠습니다.

image080

정보를 압축하기 위한 풀링 방법에는 여러가지가 있습니다. 여기에서는 맥스 풀링(max-pooling) 방식을 사용합니다. 이 방법은 2×2 영역에서 가장 큰 값을 선택해서 정보를 압축합니다.

앞서 언급한 것처럼 콘볼루션 레이어는 여러개의 커널로 이루어져 있으므로 각각에 대해 따로따로 맥스 풀링을 적용할 것입니다. 일반적으로는 여러개의 풀링 레이어와 콘볼루션 레이어가 있을 수 있습니다.

image082

이 방식으로 인해 24×24 콘볼루션의 결과를 2×2 영역으로 분할했을 떄 12×12 개의 조각에 해당하는 12×12 크기의 맥스 풀링 레이어로 변환됩니다. 콘볼루션 레이어와는 달리 데이터가 슬라이딩 윈도우에 의해 생성되는 것이 아니라 타일처럼 나뉘어져 각각 만들어 집니다.

맥스 풀링은 어떤 특징이 이미지의 여러곳에 나타난다면 특징의 정확한 위치보다 다른 특징들과의 상대적 위치가 더 중요하다는 것을 설명해 줍니다.

(역주: 이 그림이 아래 실제 구현 코드와는 조금 다릅니다. 아마도 조르디 토레스 교수가 잠시 헷갈렸던 것 아닐까 생각되는데요. 아래 코드는 tf.nn.conv2d 함수를 호출할 때 padding 옵션이 ‘SAME’으로 되어 있어서 입력 데이터에 5×5 필터를 스트라이드 1으로 콘볼루션할 때 28×28 사이즈가 그대로 출력되도록 패딩이 들어가게 됩니다. 여기서는 좌우, 상하에 각각 2개의 패딩이 추가됩니다. 즉 28×28 –> 24×24 가 아니고 28×28 –> 28×28 이 됩니다. 맥스 풀링 단계에서는 padding 옵션이 ‘SAME’으로 되어 있고 스트라이드가 2라서 14×14에 맞추어 패딩이 추가됩니다. 하지만 풀링 레이어의 사이즈가 2×2 로 스트라이드 2로 하면 딱 맞아 떨어지므로 추가적인 패딩이 필요 없게 됩니다. 결국 텐서 사이즈의 변화는 28×28–>28×28–>14×14 가 됩니다. 같은 방식으로 두번째 콘볼루션 레이어를 거치면서 텐서 사이즈는 7×7이 됩니다. 텐서플로우의 padding 옵션에 대해 좀 더 자세한 정보는 텐서플로우 API 페이지를 참고하세요. 오류를 지적해 주신 강희석님께 감사드립니다.)

모델 구현

이 섹션에서는 텐서플로우 웹사이트에서 난이도가 높은 예제(Deep MNIST for experts)를 기초로 하여 CNN 을 어떻게 만드는 지 코드를 만들어가 보도록 하겠습니다. 시작하면서 언급했듯이 파라메타들에 대한 상세한 내용을 이해하려면 이 책에 있는 것 보다 훨씬 더 세세한 부분을 다루어야 하고 이론적 배경이 필요합니다. 그래서 여기에서는 텐서플로우 파라메타의 상세 내용은 제외하고 전체적인 코드를 구성하겠습니다.

앞서 보았듯이 콘볼루션 레이어와 풀링 레이어를 구성하기 위해서는 여러개의 파라메타를 정해야 합니다. 우리는 각 차원 방향으로 스트라이드(슬라이딩 윈도우가 한번에 이동하는 크기)를 1 로 하고 패딩은 0 으로 하겠습니다. 풀링은 2×2 크기의 맥스 풀링을 적용하겠습니다. 위에서와 마찬가지로 코드를 간단하게 하기 위해 콘볼루션과 맥스 풀링을 위한 두개의 함수를 아래와 같이 만들었습니다.

def conv2d(x, W):
  return tf.nn.conv2d(x, W, strides=[1, 1, 1, 1], padding='SAME')

def max_pool_2x2(x):
  return tf.nn.max_pool(x, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME')

이제 첫번째 콘볼루션 레이어와 이를 뒤따르는 풀링 레이어를 만들 차례입니다. 이 예제에서는 윈도우 크기가 5×5 인 32 개의 필터가 있습니다. 따라서 우리는 크기가 [(5, 5, 1, 32)] 인 가중치 행렬 W 를 저장할 텐서를 정의해야 합니다. 처음 두개의 차원은 윈도우의 사이즈이며 세번째는 컬러 채널로 여기서는 1 입니다. 마지막 차원은 얼마나 많은 특징을 사용할 것인지를 정의하는 것입니다. 거기에 더해 32 개 가중치 행렬에 대한 바이어스를 정의해야 합니다. 앞에서 만든 함수를 사용해 아래와 같이 코드를 만듭니다.

W_conv1 = weight_variable([5, 5, 1, 32])
b_conv1 = bias_variable([32])

렐루(ReLU, Rectified Linear Unit) 활성화 함수는 최근 딥 뉴럴 네트워크의 히든 레이어에서 사용되는 기본 활성화 함수가 되었습니다. 이 함수는 간단한데 max(0, x) 로서 음수의 경우 0 을 리턴하고 그 외에는 x 를 리턴합니다. 이 예제에서는 콘볼루션 히든 레이어의 활성화 함수로 렐루 함수를 사용하겠습니다.

우리가 작성할 코드는 입력 이미지 x_image 에 대해 콘볼루션을 적용하고 콘볼루션의 결과를 2D 텐서 W_conv1 을 리턴합니다. 그리고 바이어스를 더해 최종적으로 렐루 활성화 함수를 적용합니다. 마지막 단계에서 출력 값을 위해 맥스 풀링을 적용합니다.

h_conv1 = tf.nn.relu(conv2d(x_image, W_conv1) + b_conv1)
h_pool1 = max_pool_2x2(h_conv1)

딥 뉴럴 네트워크를 구성할 때 여러개의 레이어를 쌓아 올릴 수 있습니다. 어떻게 하는지 예시를 보이기 위해 5×5 윈도우에 64개의 필터를 갖는 두번째 콘볼루션 레이어를 만들겠습니다. 이 때에는 이전 레이어의 출력 값의 크기가 채널의 수가 되어 32로 지정해야 합니다.

W_conv2 = weight_variable([5, 5, 32, 64])
b_conv2 = bias_variable([64])

h_conv2 = tf.nn.relu(conv2d(h_pool1, W_conv2) + b_conv2)
h_pool2 = max_pool_2x2(h_conv2)

12×12 크기 행렬에 스트라이드 1 로 5×5 윈도우를 적용했기 때문에 이 콘볼루션의 결과 값은 7×7 차원을 가지게 됩니다. 다음 단계는 이전 장에서 했던 것과 비슷하게 마지막 소프트맥스 레이어에 주입하기 위해 7×7 출력 값을 완전 연결(fully connected) 레이어에 연결합니다.

전체 이미지를 처리하기 위해서는 1024 개의 뉴런을 사용하도록 하겠습니다. 가중치와 바이어스를 위한 텐서는 아래와 같습니다.

W_fc1 = weight_variable([7 * 7 * 64, 1024])
b_fc1 = bias_variable([1024])

이 텐서의 첫번째 차원은 두번째 콘볼루션 레이어의 7×7 크기의 64 개 필터로 부터 왔으며 두번째 파라메타는 우리가 임의로 선택한 뉴런의 갯수(여기서는 1024) 입니다.

이제 텐서를 벡터로 변환합니다. 이전 장에서 소프트맥스 함수는 이미지를 직렬화해서 벡터 형태로 입력을 해야하는 것을 보았습니다. 이를 위해서 가중치 행렬 W_fc1 과 일차원 벡터를 곱하고 바이어스 b_fc1 을 더한 후 렐루 활성화 함수를 적용합니다.

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)

다음 단계는 드롭아웃(dropout)이라는 테크닉을 통해 뉴럴 네트워크에서 필요한 파라메타 수를 줄이는 것입니다. 이는 노드를 삭제하여 입력과 출력 연결을 제거하는 것입니다. 어떤 뉴런을 제거하고 어떤 것을 유지할지는 무작위로 결정됩니다. 뉴런이 제거되거나 그렇지 않을 확률을 코드에서 처리하지 않고 텐서플로우에 위임할 것 입니다.

너무 세세하게 말할 순 없지만 드롭아웃은 모델이 데이터에 오버피팅(overfitting, 역주: 과적합)되는 것을 막아줍니다. 히든 레이어에 많은 수의 뉴런을 사용하면 매우 상세한 모델이 만들어 질 수 있습니다. 이런 경우에 임의의 노이즈(또는 에러)가 모델에 포함될 수 있습니다. 이를 오버피팅이라 하고 입력 데이터의 차원에 비해 더 많은 파라메타를 가지는 모델에서 자주 일어 납니다. 오퍼피팅은 예측의 성능을 떨어뜨리므로 피하는 것이 좋습니다.

우리 모델에서는 마지막 소프트맥스 레이어 전에 tf.nn.dropout 함수를 사용하여 드롭아웃을 적용합니다. 그 전에 뉴런이 드롭 아웃되지 않을 확률을 저장할 플레이스홀더를 만듭니다.

keep_prob = tf.placeholder("float")
h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob)

마지막으로 이전 장에서 했던 것 처럼 모델에 소프트맥스 레이어를 추가합니다. 소프트맥스 함수는 입력 이미지가 각 클래스(여기서는 0~9까지 숫자)에 속할 확률을 리턴하며 이 확률의 전체 합은 1이 된다는 것을 기억하십시요. 아래와 같이 소프트맥스 레이어를 만듭니다.

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)

모델 훈련 및 평가

이제 콘볼루션 레이어와 완전 연결 레이어의 가중치를 최적화하여 이미지가 어떤 클래스에 속할지 예측하는 모델을 훈련시킬 준비를 마쳤습니다. 모델이 얼마나 잘 수행되는지 알기 위해서는 이전 장의 예제들이 했던 방식을 따라야 합니다.

아래 코드는 한가지만 빼고는 이전 장의 예제와 매우 유사합니다. 그것은 그래디언트 디센트 최적화 알고리즘을 ADAM 최적화 알고리즘으로 바꾼 것입니다. 왜냐하면 문서에 따르면 ADAM 최적화 알고리즘이 좋은 장점을 가졌기 때문입니다.

또 앞서 언급한 드롭아웃 레이어의 확률을 조절하는 추가적인 파라메타 keep_probfeed_dict 인자를 통해 전달합니다.

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(20000):
  batch = mnist.train.next_batch(50)
  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%의 정확도를 내었습니다.

텐서플로우를 이용하여 딥 뉴럴 네트워크를 만들고 훈련시켜서 평가하는 짧은 안내는 마무리되어 갑니다. 이 장의 코드를 실행하면 이전 챕터의 예제보다 눈에 띠게 훈련하는데 많은 시간이 걸린다는 것을 알 수 있습니다. 왜냐하면 많은 레이어를 가진 네트워크는 훈련 시간이 많이 걸리기 때문입니다. 다음 장에서 훈련시간을 대폭 줄일 수 있는 GPU를 어떻게 사용하는지 설명합니다.

이 장의 코드가 있는 파일은 CNN.py 로 이 책의 깃허브 페이지에서 찾을 수 있습니다. 편하게 보기 위해 아래 전체 코드를 실었습니다.(역주: 파이썬 3  notebook으로 작성한 이 섹션의 코드는 여기에서 보실 수 있습니다.)

from tensorflow.examples.tutorials.mnist import input_data
mnist = input_data.read_data_sets('MNIST_data', one_hot=True)
import tensorflow as tf

x = tf.placeholder("float", shape=[None, 784])
y_ = tf.placeholder("float", shape=[None, 10])

x_image = tf.reshape(x, [-1,28,28,1])
print "x_image="
print x_image

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)

def conv2d(x, W):
  return tf.nn.conv2d(x, W, strides=[1, 1, 1, 1], padding='SAME')

def max_pool_2x2(x):
  return tf.nn.max_pool(x, ksize=[1, 2, 2, 1], strides=[1, 2, 2, 1], padding='SAME')

W_conv1 = weight_variable([5, 5, 1, 32])
b_conv1 = bias_variable([32])

h_conv1 = tf.nn.relu(conv2d(x_image, W_conv1) + b_conv1)
h_pool1 = max_pool_2x2(h_conv1)

W_conv2 = weight_variable([5, 5, 32, 64])
b_conv2 = bias_variable([64])

h_conv2 = tf.nn.relu(conv2d(h_pool1, W_conv2) + b_conv2)
h_pool2 = max_pool_2x2(h_conv2)

W_fc1 = weight_variable([7 * 7 * 64, 1024])
b_fc1 = bias_variable([1024])

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)

keep_prob = tf.placeholder("float")
h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob)

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)

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(200):
   batch = mnist.train.next_batch(50)
   if i%10 == 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}))