[TensorFlowチュートリアル] A Guide to TF Layers: Building a Convolutional Neural Network [日本語翻訳]

**本記事はTensorFlowの公式チュートリアルを翻訳したものです。**
**オリジナルは[こちら](https://www.tensorflow.org/tutorials/layers)**
*google翻訳を使用しているため不自然な箇所があるかもしれません。*
*日本語として不自然な箇所、語句は適宜修正していきます。*
*気になる箇所や誤っている箇所がありましたらご指摘いただけると幸いです。*


TensorFlowは、ニューラルネットワークの構築を容易にする高水準のAPIを提供します。 全結合レイヤーやConvolutionalレイヤーの作成、アクティベーション機能の追加、ドロップアウトの正規化の適用を容易にするメソッドを提供します。 このチュートリアルでは、レイヤーを使用してConvolutionalニューラルネットワークモデルを構築し、MNISTデータセットの手書き数字を認識する方法を解説します。
##Getting Started
cnn_mnist.pyというファイルを作成し、次のコードを記述します。

```python:cnn_mnist.py
from __future__ import absolute_import
from __future__ import division
from __future__ import print_function

# Imports
import numpy as np
import tensorflow as tf

tf.logging.set_verbosity(tf.logging.INFO)

# Our application logic will be added here

if __name__ == "__main__":
  tf.app.run()
```

このチュートリアルでは、Convolutionalニューラルネットワークを構築、学習、評価するためのコードを追加していきます。 完成コードは[ここ](https://github.com/tensorflow/tensorflow/blob/r1.7/tensorflow/examples/tutorials/layers/cnn_mnist.py)にあります。

##Intro to Convolutional Neural Networks
Convolutional Neural Networks（CNN）は、画像分類タスクのための最先端のモデルアーキテクチャです。 CNNは、一連のフィルタを画像の生のピクセルデータに適用して、画像の分類に使用できる特徴を抽出してモデルの学習を行います。 CNNには3つのレイヤーがあります。

- Convolutionalレイヤー(畳み込みレイヤー)
・指定された数の畳み込みフィルターを画像に適用します。 各小領域について、レイヤーは出力特徴マップ内に単一の値を生成するために一連の数学的演算を実行します。 Convolutionalレイヤーは、通常、ReLU関数を出力に適用して、モデルに非線形を導入します。

- Poolingレイヤー(プーリングレイヤー)
・Convolutionalレイヤーによって抽出された画像データの縦、横方向サイズを小さくし、処理時間を短縮するための演算を行います。 一般的に使用されるアルゴリズムは、特徴マップのサブ領域（例えば、2×2ピクセルタイル）を抽出し、最大値を保持し、他のすべての値を破棄する「Maxプーリング」です。

- Denseレイヤー(全結合レイヤー)
・Convolutionalレイヤーによって抽出され、Poolingレイヤーによってダウンサンプリングされた特徴の分類を実行する全結合レイヤーです。 全結合レイヤーでは、レイヤー内のすべてのノードは、前のレイヤー内のすべてのノードに接続されます。

CNNは、特徴抽出を実行するモジュールの積み重ねから構成されています。 各モジュールは、Convolutionalレイヤーとそれに続くPoolingレイヤーで構成されています。 最後のモジュールの後に分類を実行する1つまたは複数の全結合レイヤーが続きます。CNNの最終的な全結合レイヤーには、モデル内の各ターゲットクラス（モデルが予測可能なすべてのクラス）の単一ノードが含まれ、各ノードの0-1の間の値を生成するsoftmaxアクティブ化関数が使用されます。 与えられたイメージのsoftmax値は、イメージが各ターゲットクラスにどれだけ適合しているかの相対的な測定値として解釈できます。

##Building the CNN MNIST Classifier
次のCNNアーキテクチャを使用して、MNISTデータセットの画像を分類するモデルを作成しましょう。

- Convolutionalレイヤー＃1：32個の5×5フィルタ（5×5ピクセルのサブリージョンを抽出）を適用しアクティベーション関数にReLuを使用する。
- Poolingレイヤー1：2x2のフィルターをストライド数２で適用してデータの縮小化を行う。

- Convolutionalレイヤー＃2：64個の5×5フィルタで畳み込みを行いReLuを適用する。
- Poolingレイヤー2：再び、2x2フィルターと2のストライドでデータの縮小化を行う。

- Denseレイヤー＃1：1,024個の全結合ニューロンで構成、ドロップアウトを適用する(ドロップアウト率：0.4)
- Denseレイヤー＃2（Logits層）：10個のニューロン、各桁ターゲットクラス（0-9）に1つ。

それぞれのレイヤーを作成するにはtf.layersメソッドを使用します。
tf.layersにはレイヤーを作成するための様々なクラスが定義されています。

- conv2d : フィルタの数、フィルタのカーネルサイズ、パディング、アクティベーション関数を引数として2次元畳み込み層を構築します。[conv2dのリファレンス](https://www.tensorflow.org/api_docs/python/tf/layers/Conv2D)

- max_pooling2d : max-poolingアルゴリズムを使用して2次元のPoolingレイヤーを構築します。フィルターのサイズとストライドを引数とします。[max_pooling2dのリファレンス](https://www.tensorflow.org/api_docs/python/tf/layers/MaxPooling2D)

- dense : 全結合レイヤーを構築します。引数としてニューロンの数と活性化関数を取ります。[denseのリファレンス](https://www.tensorflow.org/api_docs/python/tf/layers/dense)

それぞれのレイヤーは、テンソルを入力として受け取り、活性化関数によって変換されたテンソルを出力として返します。あるレイヤーから出力を取り出し、それを入力として別のレイヤーへ渡すことで簡単にレイヤー同士を接続することができます。

cnn_mnist.pyを開き、cnn_model_fn関数を追加します。cnn_mnist.pyは、MNIST特徴データ、ラベル、モデルモード（TRAIN、EVAL、PREDICT）を引数としてCNNを構築し予測、損失、およびトレーニング操作を返します。

```python:cnn_mnist.py
def cnn_model_fn(features, labels, mode):
  """Model function for CNN."""
  # Input Layer
  input_layer = tf.reshape(features["x"], [-1, 28, 28, 1])

  # Convolutional Layer #1
  conv1 = tf.layers.conv2d(
      inputs=input_layer,
      filters=32,
      kernel_size=[5, 5],
      padding="same",
      activation=tf.nn.relu)

  # Pooling Layer #1
  pool1 = tf.layers.max_pooling2d(inputs=conv1, pool_size=[2, 2], strides=2)

  # Convolutional Layer #2 and Pooling Layer #2
  conv2 = tf.layers.conv2d(
      inputs=pool1,
      filters=64,
      kernel_size=[5, 5],
      padding="same",
      activation=tf.nn.relu)
  pool2 = tf.layers.max_pooling2d(inputs=conv2, pool_size=[2, 2], strides=2)

  # Dense Layer
  pool2_flat = tf.reshape(pool2, [-1, 7 * 7 * 64])
  dense = tf.layers.dense(inputs=pool2_flat, units=1024, activation=tf.nn.relu)
  dropout = tf.layers.dropout(
      inputs=dense, rate=0.4, training=mode == tf.estimator.ModeKeys.TRAIN)

  # Logits Layer
  logits = tf.layers.dense(inputs=dropout, units=10)

  predictions = {
      # Generate predictions (for PREDICT and EVAL mode)
      "classes": tf.argmax(input=logits, axis=1),
      # Add `softmax_tensor` to the graph. It is used for PREDICT and by the
      # `logging_hook`.
      "probabilities": tf.nn.softmax(logits, name="softmax_tensor")
  }

  if mode == tf.estimator.ModeKeys.PREDICT:
    return tf.estimator.EstimatorSpec(mode=mode, predictions=predictions)

  # Calculate Loss (for both TRAIN and EVAL modes)
  loss = tf.losses.sparse_softmax_cross_entropy(labels=labels, logits=logits)

  # Configure the Training Op (for TRAIN mode)
  if mode == tf.estimator.ModeKeys.TRAIN:
    optimizer = tf.train.GradientDescentOptimizer(learning_rate=0.001)
    train_op = optimizer.minimize(
        loss=loss,
        global_step=tf.train.get_global_step())
    return tf.estimator.EstimatorSpec(mode=mode, loss=loss, train_op=train_op)

  # Add evaluation metrics (for EVAL mode)
  eval_metric_ops = {
      "accuracy": tf.metrics.accuracy(
          labels=labels, predictions=predictions["classes"])}
  return tf.estimator.EstimatorSpec(
      mode=mode, loss=loss, eval_metric_ops=eval_metric_ops)
```
次のセクションから各レイヤの作成に使用されるtf.layersコードと、損失の計算方法、トレーニングオペレーションの構成方法、および予測の生成方法について詳しく説明していきます。

###Input Layer
2次元画像データ用のConvolutionalレイヤーおよびPoolingレイヤーを作成するためのメソッドは、入力テンソルが[batch_size、image_width、image_height、channels]の形状になっています。

- batch_size  トレーニング中に使用するサンプルのサブセットのサイズ。
- image_width サンプル画像の幅。
- image_height サンプル画像の高さ。
- channels サンプル画像内のカラーチャネルの数。 カラー画像の場合、チャンネル数は3（赤、緑、青）です。 モノクロ画像の場合、1つのチャンネル（黒色）だけがあります。

MNISTデータセットは単色の28x28ピクセルイメージで構成されているため、入力レイヤの望ましい形状は[batch_size、28、28、1]です。

tf.reshapeを使用しMNISTデータセットに合わせた入力レイヤーを作成します。

```python:
input_layer = tf.reshape(features["x"], [-1, 28, 28, 1])
"""
tf.reshapeリファレンス：
reshape(tensor, shape, name=None)
tensor : 入力テンソル
shape : 変換形状
name : オプション

example:
# 入力テンソル(shape [9]) :t = [1, 2, 3, 4, 5, 6, 7, 8, 9]
# 形状を[3, 3]に変換
reshape(t, [3, 3]) ==> [[1, 2, 3],
                        [4, 5, 6],
                        [7, 8, 9]]
"""


```


batch_sizeに-1を指定することでチューニングが可能なハイパーパラメータとして扱うことができます。こうすることでbatch_sizeに基づいてこのinput_layerへの入力サイズを動的に変更することができます。 たとえば、batch_sizeを5にすると、input_layerの形状は[5,28,28、1]となり3,920の値（28*28*1サイズのデータが5つ）が入力されます。
同様に、batch_sizeを100にするとinput_layerの形状は[100,28,28,1]となります。

###Convolutional Layer #1
最初のConvolutionalレイヤーでは、ReLU関数を使用して32個の5×5サイズのフィルタを入力レイヤーに適用しています。 conv2d（）メソッドを使用すると、次のようにこのConvolutionalレイヤーを構築できます。

```python:
# Convolutional Layer 1
conv1 = tf.layers.conv2d(
    inputs=input_layer,
    filters=32,
    kernel_size=[5, 5],
    padding="same",
    activation=tf.nn.relu)
```

inputs引数は、入力テンソルを指定します。このテンソルは、[batch_size、image_width、image_height、channels]の形状でなければなりません。 ここでは、先程作成したinput_layerを最初のConvolutionalレイヤーの入力として指定しています。

filters引数は適用するフィルタの数（ここでは32）を指定し、kernel_sizeはフィルタのサイズを[width、height]（ここでは[5、5]）として指定します。

ヒント：フィルタの幅と高さが同じ値であれば、代わりにkernel_sizeに単一の整数を指定することができます（例：kernel_size = 5）。

padding引数は、[valid]（デフォルト値）または[same]のいずれかを指定します。出力テンソルが入力テンソルと同じ幅と高さの値を持つように指定するにはpadding = sameを設定します。これにより、TensorFlowは入力テンソルの端に0の値を追加して28の幅と高さを保持します。

activation引数は、出力に適用する活性化関数を指定します。ここでは、ReLU関数を使用するためtf.nn.reluを設定しています。

conv2d（）によって生成された出力テンソルは、[batch_size、28、28、32]という形状をしています。
入力と同じ幅と高さですが、32個のチャネルが各フィルタの出力を保持します。

###Pooling Layer #1
次に、conv1を入力とする最初のPoolingレイヤーを作成します。
max_pooling2d（）は指定されたスライド数でフィルタを適用しその領域内の最大値をその領域の要素として取り出します。そうすることでデータを縮小することができ効率的に処理することができるようになります。

```python:
#Pooling Layer 1
pool1 = tf.layers.max_pooling2d(inputs=conv1, pool_size=[2, 2], strides=2)
```

pool_size引数は、フィルタのサイズを[幅、高さ]（ここでは[2、2]）として指定します。 
幅、高さの値が同じ場合は、代わりに単一の整数を指定できます（pool_size = 2など）。

strides引数は、ストライドのサイズを指定します。 ここでは、ストライド2を設定しています。
これは、フィルタの移動範囲を２要素ごとになることを示しています。

pool1によって生成された出力テンソルは、[batch_size、14、14、32]の形状をしています。
2x2フィルターは幅と高さをそれぞれ50％縮小することができます。

###Convolutional Layer #2 and Pooling Layer #2
conv2d（）とmax_pooling2d（）を使用して、2番目のConvolutionalレイヤーとPoolingレイヤーを構築します。 Convolutiona Layer 2は、ReLU関数を使用した64個の5x5フィルターで構成しています、
Pooling Layer 2はPooling Layer 1と同じスペックを使用します（ストライド2の2x2フィルター）。

```python:
# Convolutional Layer 2
conv2 = tf.layers.conv2d(
    inputs=pool1,
    filters=64,
    kernel_size=[5, 5],
    padding="same",
    activation=tf.nn.relu)

# Pooling Layer 2
pool2 = tf.layers.max_pooling2d(inputs=conv2, pool_size=[2, 2], strides=2)
```

Convolutional Layer 2は、pool1を入力とし、conv2を出力として生成します。
conv2の形状は[batch_size、14、14、64]であり、pool1と同じ幅と高さ（padding = "same"のため）と64個のフィルタが適用された64チャンネルです。

Pooling Layer 2はconv2を入力とし、pool2を出力として生成します。
pool2の形状は[batch_size、7、7、64]です（conv2からの幅と高さの50％の削減）。

###Dense Layer
次に、Convolutional/Poolingレイヤーによって抽出された特徴の分類を行うために、CNNに全結合レイヤー（1,024個のニューロンとReLUアクティベーション）を追加する必要があります。 しかし、レイヤーを接続する前に、特徴マップ（pool2）を[batch_size、features]の２次元のテンソルに変換します。

```python
pool2_flat = tf.reshape(pool2, [-1, 7 * 7 * 64])
```
上記のtf.reshapeでは、-1はbatch_sizeに基づいて動的に計算されることを示します。 例では7（pool2の幅）* 7（pool2の高さ）* 64（poolのチャネル）の特徴があるため、特徴マップのサイズは7 * 7 * 64（合計3136）になります。 出力テンソルのpool2_flatの形状は[batch_size、3136]です。

今度は、dense（）メソッドを使用して全結合レイヤーを構築します。

```python:
dense = tf.layers.dense(inputs=pool2_flat, units=1024, activation=tf.nn.relu)
```

inputs引数は入力テンソルを指定します。これは２次元のテンソルに変換された特徴マップのpool2_flatです。 units引数は、全結合レイヤーのニューロンの数を指定します（1,024）。 activation引数はアクティベーション関数を指定します。 今回はReLUを使用するためにtf.nn.reluを指定しています。

モデルの結果を改善するために、全結合レイヤーからの出力テンソルに対してドロップアウトを適用します。

```python:
dropout = tf.layers.dropout(
    inputs=dense, rate=0.4, training=mode == tf.estimator.ModeKeys.TRAIN)
```

rate引数はドロップアウトレートを指定します。 ここでは、0.4を使用します。これは、要素の40％がトレーニング中にランダムにドロップアウトされることを意味します。

training引数は、モデルがトレーニングモードで実行されているかどうかを指定するboolean値を取ります。 ドロップアウトはモデルがトレーニング中の場合のみ実行されます。 ここでは、cnn_model_fnに渡されたモードがTRAINモードであるかどうかを確認します。

出力テンソルの形状は[batch_size、1024]となります。

###Logits Layer
ニューラルネットワークの最終レイヤーはlogitsレイヤーで、予測値を返します。 
10個のニューロン（各ターゲットクラス0-9に1つ）を持つレイヤーを作成します。

```python:
logits = tf.layers.dense(inputs=dropout, units=10)
```
CNNの最終出力テンソルlogitsは、形状[batch_size、10]を持ちます。

###Generate Predictions
モデルのlogitsレイヤーは[batch_size、10]の形状のテンソルで予測値を返します。 
それをモデルが返すことのできる2つの異なる形式に変換しましょう。

- 予測クラス：0〜9の数字。
- 予測クラスの確率：0である確率、1である確率、2である確率など。

予測されるクラスは、最も高い生の値を持つlogitsテンソルの対応する行の要素です。 
tf.argmax関数を使用して、最大値の要素のインデックスを取得することができます。

```python:
tf.argmax(input=logits, axis=1)
```
入力引数は、最大値を抽出するテンソル（ここではlogits）を指定します。 
axis引数は、入力テンソルの軸を指定して最大値を求めます。 ここでは、予測値に対応するインデックス1の次元に沿って最大の値を求めます（ロジットテンソルは形状[batch_size、10]を持ちます）。

tf.nn.softmaxを使用することによって、logitsレイヤーから確率を導き出すことができます。

```python
tf.nn.softmax(logits, name="softmax_tensor")
```


予測クラス(0〜9)とその確率をpredictionsとしてまとめ、tf.estimator.EstimatorSpecの引数として指定します。このメソッドは訓練、評価、および推論を行うためのオブジェクトを返却します。
```python
predictions = {
    "classes": tf.argmax(input=logits, axis=1),
    "probabilities": tf.nn.softmax(logits, name="softmax_tensor")
}
if mode == tf.estimator.ModeKeys.PREDICT:
  return tf.estimator.EstimatorSpec(mode=mode, predictions=predictions)
```

###Calculate Loss
訓練と評価の両方について、モデルの予測が目標にどれだけ近づくかを測定する損失関数を定義する必要があります。 MNISTのようなマルチクラス分類問題の場合、一般的にはクロスエントロピーが使用されます。 
次のコードは、モデルがTRAINモードまたはEVALモードで実行されているときのクロスエントロピーを計算します。

```python:
onehot_labels = tf.one_hot(indices=tf.cast(labels, tf.int32), depth=10)
loss = tf.losses.softmax_cross_entropy(
    onehot_labels = onehot_labels, logits=logits)
```
上記のコードで何を行っているのかをみてみましょう。

ラベルのテンソルは、予測のリストを含んでいます。([1,9、...]) 
クロスエントロピーを計算するには、まずラベルを対応するone_hotエンコーディングに変換する必要があります。

```python:
[[0, 1, 0, 0, 0, 0, 0, 0, 0, 0],
 [0, 0, 0, 0, 0, 0, 0, 0, 0, 1],
 ...]
```

この変換を行うには、tf.one_hot関数を使用します。 tf.one_hot（）には2つの必須引数があります：

- indices : 上に示したテンソル内の1つの値の位置、つまり値を持つone_hotテンソル内の位置。
- depth : ターゲットクラスの数。 MNISTでは10(0〜9)。

次のコードは、onehot_labelsに対しone_hotテンソルを作成します。

```python:
onehot_labels = tf.one_hot(indices=tf.cast(labels, tf.int32), depth=10)
```
ラベルには0から9までの一連の値が含まれているため、indicesはラベルのテンソルだけで、値は整数にキャストされます。 10のターゲットクラスがあるので、各桁に1つずつ、depthは10です。

次に、onehot_labelsのクロスエントロピーとlogitsレイヤーからの予測のsoftmaxを計算します。 tf.losses.softmax_cross_entropy（）はonehot_labelsとlogitsを引数とし、logitsに対してsoftmaxのアクティブ化を行い、クロスエントロピーを計算し、損失を出力します。

```python
loss = tf.losses.softmax_cross_entropy(
    onehot_labels = onehot_labels, logits=logits)
```

###Configure the Training Op
前節では、CNNのロスをlogitsレイヤーとラベルのsoftmaxクロスエントロピーとして定義しました。 トレーニング中にこの損失値を最適化するようにモデルを構成しましょう。 0.001の学習率と確率的勾配降下を最適化アルゴリズムとして使用します。

```python:
if mode == tf.estimator.ModeKeys.TRAIN:
  optimizer = tf.train.GradientDescentOptimizer(learning_rate=0.001)
  train_op = optimizer.minimize(
      loss=loss,
      global_step=tf.train.get_global_step())
  return tf.estimator.EstimatorSpec(mode=mode, loss=loss, train_op=train_op)
```

###Add evaluation metrics
モデルで精度メトリックを追加するには、EVALモードでeval_metric_opsを次のように定義します。

```python:
eval_metric_ops = {
    "accuracy": tf.metrics.accuracy(
        labels=labels, predictions=predictions["classes"])}
return tf.estimator.EstimatorSpec(
    mode=mode, loss=loss, eval_metric_ops=eval_metric_ops)
```


##Training and Evaluating the CNN MNIST Classifier
ここまでMNISTを訓練し、評価するためのCNNモデルを構築してきました。
構築したモデルを使用して実際にトレーニングを行っていきましょう。

###Load Training and Test Data
まず、トレーニングデータとテストデータを読み込みましょう。 
cnn_mnist.pyにmain()を追加し下記のコードを記述します。

```python:
def main(unused_argv):
  # Load training and eval data
  mnist = tf.contrib.learn.datasets.load_dataset("mnist")
  train_data = mnist.train.images # Returns np.array
  train_labels = np.asarray(mnist.train.labels, dtype=np.int32)
  eval_data = mnist.test.images # Returns np.array
  eval_labels = np.asarray(mnist.test.labels, dtype=np.int32)
```
training_dataとtrain_labelsのnumpy配列として、トレーニングデータ（手書き数字の55,000イメージの生のピクセル値）とトレーニングラベル（各イメージの0-9の対応値）を格納します。 
同様に、評価データ（10,000画像）と評価ラベルをそれぞれeval_dataとeval_labelsに格納します。

###Create the Estimator
次に、モデルのEstimator（モデル訓練、評価、および推論を実行するためのTensorFlowクラス）を作成しましょう。 main（）に次のコードを追加します。

```python:
# Create the Estimator
mnist_classifier = tf.estimator.Estimator(
    model_fn=cnn_model_fn, model_dir="/tmp/mnist_convnet_model")
```
model_fn引数は、トレーニング、評価、予測に使用するモデル関数を指定します。 「Building the CNN MNIST Classifier」で作成したcnn_model_fnを渡します。 model_dir引数は、モデルデータ（チェックポイント）が保存されるディレクトリを指定します（ここでは、tempディレクトリ/ tmp / mnist_convnet_modelを指定しますが、選択した別のディレクトリに自由に変更できます）。

###Set Up a Logging Hook
CNNはトレーニングに時間がかかることがあるので、トレーニング中に進捗状況を確認できるようにロギングを設定しましょう。 TensorFlowのtf.train.SessionRunHookを使用して、CNNのsoftmaxレイヤから確率値を記録するtf.train.LoggingTensorHookを作成することができます。 main（）に次の行を追加します。

```python
# Set up logging for predictions
  tensors_to_log = {"probabilities": "softmax_tensor"}
  logging_hook = tf.train.LoggingTensorHook(
      tensors=tensors_to_log, every_n_iter=50)
```

tensors_to_logにロギングしたいテンソルの辞書を保存します。 各キーは選択したラベルで出力され、対応するラベルはTensorFlowグラフのTensorの名前です。 ここでは、確率はcnn_model_fnで確率を生成したときにsoftmax操作を与えたsoftmax_tensorで見つけることができます。

次に、tensors_to_logをtensors引数に渡して、LoggingTensorHookを作成します。 
every_n_iter = 50を設定します。これは、50ステップのトレーニングごとに確率を記録することを指定します。

###Train the Model
これで、train_input_fnを作成し、mnist_classifierでtrain（）を呼び出すことで、モデルをトレーニングする準備が整いました。 main（）に次の行を追加します。

```python
# Train the model
train_input_fn = tf.estimator.inputs.numpy_input_fn(
    x={"x": train_data},
    y=train_labels,
    batch_size=100,
    num_epochs=None,
    shuffle=True)
mnist_classifier.train(
    input_fn=train_input_fn,
    steps=20000,
    hooks=[logging_hook])
```
numpy_input_fnでは、トレーニング特徴データとラベルをそれぞれx（dict）およびyに渡します。 batch_sizeを100に設定します（これは、各ステップで100例のミニバッチでモデルが訓練されることを意味します）。 num_epochs = Noneは、指定されたステップ数に達するまでモデルが訓練することを意味します。 また、shuffle = Trueを設定してトレーニングデータをシャッフルします。 
mnist_classifier.trainでは、ステップ= 20000を設定します（これは、モデルが合計20,000ステップを訓練することを意味します）。 

###Evaluate the Model
トレーニングが完了したらモデルの評価を行います。
main（）に次の行を追加します。

```python
# Evaluate the model and print results
eval_input_fn = tf.estimator.inputs.numpy_input_fn(
    x={"x": eval_data},
    y=eval_labels,
    num_epochs=1,
    shuffle=False)
eval_results = mnist_classifier.evaluate(input_fn=eval_input_fn)
print(eval_results)
```
eval_input_fnを作成するために、num_epochs = 1を設定して、モデルが1エポック分のデータのメトリックを評価し、その結果を返すようにします。 shuffle = Falseを設定して、データを順番に反復処理します。

###Run the Model
CNNモデル関数、Estimator、トレーニング/評価ロジックをコーディングしました。
早速結果を見てみましょう。 cnn_mnist.pyを実行します。

モデルが訓練すると、次のようなログ出力が表示されます。

```python
INFO:tensorflow:loss = 2.36026, step = 1
INFO:tensorflow:probabilities = [[ 0.07722801  0.08618255  0.09256398, ...]]
...
INFO:tensorflow:loss = 2.13119, step = 101
INFO:tensorflow:global_step/sec: 5.44132
...
INFO:tensorflow:Loss for final step: 0.553216.

INFO:tensorflow:Restored model from /tmp/mnist_convnet_model
INFO:tensorflow:Eval steps [0,inf) for training step 20000.
INFO:tensorflow:Input iterator is exhausted.
INFO:tensorflow:Saving evaluation summary for step 20000: accuracy = 0.9733, loss = 0.0902271
{'loss': 0.090227105, 'global_step': 20000, 'accuracy': 0.97329998}
```
ここでは、テストデータセットで97.3％の精度を達成しました。