【TensorFlow入門】Getting Started + MNISTのまとめでTensorFlowの大枠を理解する

# はじめに
TensorFlow初心者の私が、"Getting Started with TensorFlow" を勉強していると

- 日本語の記事ほとんどMNISTの話ばっかじゃねーか！
- しかもCNNの内容とコードの話しかまとめられてねぇ！
- 公式のチュートリアルは途中でいろんな機能の話になるから体系的に理解できねぇ！

と感じたので、自分なりにまとめてみようと思います。

本記事のゴールは、**様々なページに散らばった情報をまとめ、TensorFlowの大枠を理解する**です。
今回はMNISTのコードを追いつつ、そこで使われるTensorFlowの機能などを紹介していきます。
コードについての細かな説明は他の方がされているので、あまりしません。
一応簡単な説明は英語でコードにコメントで入れてあります。
気になる方はこれを読み終わった後に他の記事なり公式のコードなりを追って勉強しましょう。
あと**英語が読めるならこんな稚拙な記事を読まずに公式のチュートリアルを読みましょう**。
クッソわかりやすいです。

# 環境
TensorFlow API **r1.5**です。

# TensorFlowのAPIについて
そもそもTensorFlowって何？という方は、「データフローグラフで計算を表現する～」うんぬん説明するより、[こちらの記事](https://qiita.com/yanosen_jp/items/70e6d6afc36e1c0a3ef3)を読んだ後に[こちらの記事](https://qiita.com/rindai87/items/4b6f985c0583772a2e21)を読んだ方が直感的に理解できると思います。
TensorFlowには*API layer*（以下API）が複数存在します。TensorFlowではAPIの中の様々なclassを呼び出すことで楽をしながらプログラムを書いていきます。TensorFlowには以下のAPIが存在します。
![tensorflow_programming_environment.png](https://qiita-image-store.s3.amazonaws.com/0/204294/c6255e25-7dae-c420-d6ee-68c87c66f5bd.png)
ちなみにGetting Startedでは「*Estimators*と*Datasets*を使ってプログラミングを書こうね！！」と書かれていました。
[2017年のプレゼン](https://youtu.be/t64ortpgS-E)を見た感じだと、TensorFlowはもともとLow-levelなAPIの集まりで、「自由度はめっちゃ高いけど高度なプログラムを実装するのは大変」という状態だったのが、よく使われる手法を*High-Level APIs*としてTensorFlowに組み込むことで、実装がとても簡単になったそうです。よりLevelの高い*APIs*には機械学習でのベストプラクティスとされてきた手法が組み込まれているので、Getting Startedに「*Estimators*と*Datasets*を使おう」と書いているのだと思います。


- *Estimators*
完成したmodelを表す。modelを訓練したり、accuracyを計算したり、予想したりするときに使います。

- *Datasets*
データをinputするときのパイプラインを作るAPI。データを読み込んだり操作したり、扱ってるmodelに組み込むときに使います。

# チュートリアルに出てきた内容まとめ

## はじめに
先述の通り、公式のコードやQiitaの他の記事を次に読むことを想定し、MNISTを例に説明していきます。
プログラムの流れは大体こんな感じです。

- cnn_model_fnを定義
 - modelを選択する
 - predictingの設定
 - loss functionの設定
 - trainingの設定
 - evaluatingの設定

- mainを定義
 - データを読み込む
 - *Estimator*の作成
 - *Log*の作成
 - modelをtrainingする
 - modelをevaluatingして結果をprint

なお、今回使用するコードは[公式のGitHub](https://github.com/tensorflow/tensorflow/blob/r1.5/tensorflow/examples/tutorials/layers/cnn_mnist.py)から引っ張ってきています。
CNNってなんだよって方は[世界一わかりやすい動画](https://youtu.be/2-Ol7ZB0MmU)を見るなり他の記事を見るなりしてください。
以下、CNNについて少しは知っているという体で話を進めていきます。

## 今回扱うモデルの説明

今回扱うモデルは以下のような階層になっています。

- Convolutional Layer #1:
5x5のフィルターが32枚、activation functionはReLUを使います。

- Pooling Layer #1:
2x2のフィルターをstride=2でずらしていきます。（フィルターが重ならないようストライドする）

- Convolutional Layer #2:
5x5のフィルターが32枚、activation functionはReLUを使います。

- Pooling Layer #2:
2x2のフィルターをstride=2でずらしていきます。（フィルターが重ならないようストライドする）

- Dense Layer #1:
ニューロンが1024個、40％をドロップアウトする。

- Dense Layer #2 (Logits Layer):
ニューロンが10個、それぞれにtarget class (0–9)を持つ。

それでは順に見ていきましょう。
最初に関数ごとのコードを載せ、その後に個々の説明をしていきます。
TensorFlowの概要だけつかみたい人は、コードを飛ばして機能面の説明だけ読んでください。

## cnn_model_fnを定義


```python3:cnn_model_fn.py
def cnn_model_fn(features, labels, mode):
    """Model function for CNN."""

    # Input Layer
    # Reshapes X to 4-D tensor: [batch_size, witdth, height, channels].
    # MNIST images are 28x28 pixels, and have one color channel.
    # -1 specifies that this dimension should be dynamically computed
    # based on the number of input values in features["x"],
    # holding the size of all other dimensions constant.
    input_layer = tf.reshape(features["x"], [-1, 28, 28, 1])

    # Convolutional Layer #1
    # Conputes 32 features using a 5x5 filter with ReLU activation.
    # Padding is added to preserve width and height.
    # Input Tensor Shape: [batch_size, 28, 28, 1]
    # Output Tensor Shape: [batch_size, 28, 28, 32]
    conv1 = tf.layers.conv2d(
        inputs = input_layer,
        filters = 32,
        kernel_size = [5, 5],
        padding = "same",
        activation = tf.nn.relu)

    # Pooling Layer #1
    # First max pooling layer with a 2x2 filter and stride of 2
    # Input Tensor Shape: [batch_size, 28, 28, 32]
    # Output Tensor Shape: [batch_size, 14, 14, 32]
    pool1 = tf.layers.max_pool2d(inputs = conv1, pool_size = [2, 2], strides = 2)

    # Convolutional Layer #2
    # Conputes 64 features using a 5x5 filter.
    # Padding is added to preserve width and height.
    # Input Tensor Shape: [batch_size, 14, 14, 32]
    # Output Tensor Shape: [batch_size, 14, 14, 64]
    conv2 = tf.layers.conv2d(
        inputs = pool1,
        filters = 64,
        kernel_size = [5, 5],
        padding = "same",
        activation = tf.nn.relu)

    # Pooling Layer #2
    # Second max pooling layer with a 2x2 filter and stride of 2
    # Input Tensor Shape: [batch_size, 14, 14, 64]
    # Output Tensor Shape; [batch_size, 7, 7, 64]
    pool2 = tf.layers.max_pool2d(inputs = conv2, pool_size = [2, 2], strides = 2)

    # Flatten tensor into a batch of vectors
    # Input Tensor Shape: [batch_size, 7, 7, 64]
    # Output Tensor Shape: [batch_size, 7 * 7 * 64]
    pool2_flat = tf.reshape(pool2, [-1, 7 * 7 * 64])

    # Dense Layer
    # Densely connected layer with 1024 neurons
    # Input Tensor Shape: [batch_size, 7 * 7 * 64]
    # Output Tensor Shape: [batch_size, 1024]
    dense = tf.layers.dense(inputs = pool2_flat, units = 1024, activation = tf.nn.relu)

    # Add dropout operation; 0.6 probability that element will be kept
    dropout = tf.layers.dropout(
        inputs = dense, rate = 0.4, training = mode == tf.estimator.ModeKeys.TRAIN)

    # Logits layer
    # Input Tensor Shape: [batch_size, 1024]
    # Output Tensor Shape: [batch_size, 10]
    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)
    # First, transfer one-hot
    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)

```

コードを見るとわかる通り、modelのlayerに関しては*Layers API*を呼び出して実装しています。`tf.layers.****`でレイヤーを実装できるので、すごく便利です。
最初は「ああ、*APIs*にあるモデルやレイヤーを使うとこうやってすぐに実装できるんだな」程度の認識でいいと思います。

### *Modekeys*, *EstimatorSpec*について
チュートリアルを追っていてわかりにくかったのはmodeを選択する部分です。

```python:mode_predictions.py
    if mode == tf.estimator.ModeKeys.PREDICT:
        return tf.estimator.EstimatorSpec(mode = mode, predictions = predictions)
```
TensorFlowではmodel functionは必ずmodeを受け取らなければなりません。
`TRAIN` `EVAL` `PREDICT`の中から一つを3つから選び、`tf.estimator.ModeKeys.*******`の形で、modelを実行する際に指定します。
また、どんなmodelでも実行後は実行者が求める結果を返す必要があります。その結果を受け取るのが`EstimatorSpec`です。2行目では`tf.estimator.EstimatorSpec`が返され、実行したmodeを`mode`に、modelの実行結果を`predictions`に保持しています。

#### *Estimators* について
*Estimators API*には、*Pre-made Estimators*と*Custom Estimators*があります。
![estimator_types.png](https://qiita-image-store.s3.amazonaws.com/0/204294/1798555c-b75e-71ad-a3ec-a7f1542a1862.png)
*Pre-made Estimators*には上図のような一般的な機械学習のmodelがプリセットで組み込まれています。また、*Pre-made Estimators*を改良してmodelを作ることもできます。*Custom Estimators*は*Estimators*のインスタンスで、*Pre-made Estimators*では対応できないようなモデルを使いたいときに、自ら*Custom Estimators*を作ります。*Custom Estimators*の作り方は[こちらのドキュメント](https://www.tensorflow.org/get_started/custom_estimators)、*Estimators API*自体の詳しい説明は[こちらのProgrammer's Guide](https://www.tensorflow.org/programmers_guide/estimators)をご覧ください。

今回はCNN modelを*Custom Estimator*として実装しています。

#### *Pre-made Estimators*の場合
*Pre-made Estimators*を使う際は、以下の手順が必要になります。
1. 一つ以上のinput functionを作成する
2. modelの*Feature Columns*を定義する
3. *Estimators*のインスタンスを作成し、*feature columns*とhyperparametersを設定する
4. Estimator objectを呼び出し、dataをinput functionに通す
input function、*Feature Columns*については後で触れます。
詳しく知りたい人は[こちらのチュートリアル](https://www.tensorflow.org/get_started/premade_estimators)をご覧ください。


## mainを定義

```python3:main.py
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)

    # Create the Estimator
    mnist_classifier = tf.estimator.Estimator(
        model_fn=cnn_model_fn, model_dir="/tmp/mnist_convnet_model")

    # Set up logging for predictions
    # Log the values in the "Softmax" tensor with label "probabilities"
    tensors_to_log = {"probabilities": "softmax_tensor"}
    logging_hook = tf.train.LoggingTensorHook(
        tensors=tensors_to_log, every_n_iter=50)

    # 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])

    # 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)
```


#### データについて
便利なことにMNISTの学習データは、すでにTensorFlowの中に組み込まれているので、今回はそれを呼び出します。
仮に自分の手で1からdataをimportする際は、先ほどの*APIs*の組織図のところで言及されていた、*Datasets API*を使用すると良いでしょう。Importing dataに関する詳しい解説は[公式のProgrammer's Guide](https://www.tensorflow.org/programmers_guide/datasets)を読んでください。
読み込んだdataを操作する際も*Datasets API*を使うと便利です。

### Input Fucnction について
今回は必要ありませんでしたが、本来training, evaluating, predictingをする際にデータを受け取るために、input functionsを作成する必要があります。
input functionsは以下の2つの要素を持つtupleを返します。

- features
それぞれのkeyがfeatureの名前、valueがfeature's valuesのpython dictionary

- labels
すべてのexampleのlabelの値を持つarray

ごくシンプルなinput funtionの例を示します。

```python3:input_fn.py
def input_evaluation_set():
    features = {'SepalLength': np.array([6.4, 5.0]),
                'SepalWidth':  np.array([2.8, 2.3]),
                'PetalLength': np.array([5.6, 3.3]),
                'PetalWidth':  np.array([2.2, 1.0])}
    labels = np.array([2, 1])
    return features, labels
```

input functionsは先ほど紹介した*Datasets API*を使うと、データの処理が楽になります。

```phython3:with_datasets.py
def train_input_fn(features, labels, batch_size):
    """An input function for training"""
    # Convert the inputs to a Dataset.
    dataset = tf.data.Dataset.from_tensor_slices((dict(features), labels))

    # Shuffle, repeat, and batch the examples.
    dataset = dataset.shuffle(1000).repeat().batch(batch_size)

    # Build the Iterator, and return the read end of the pipeline.
    return dataset.make_one_shot_iterator().get_next()
```

#### *Feature Columns* について
こちらも今回は出てきませんでしたが、本来は必要なものです。
*Feature Columns*とは読み込まれたraw dataと、*Estimators*をつなぐ仲介役みたいなものです。
![inputs_to_model_bridge.jpg](https://qiita-image-store.s3.amazonaws.com/0/204294/1402d322-c531-159f-56cd-c8deba7d3d3a.jpeg)
時にはカテゴリー別に、また時には数値として、あるいはその両方として分類したりと、汎用性が高く多様な形で*Estimators*が処理可能なデータに変換できるので、奥が深そうです。
<img width="586" alt="some_constructors.jpg" src="https://qiita-image-store.s3.amazonaws.com/0/204294/a878834f-30bf-26d4-97ec-f72b01818ab9.jpeg">
*Feature Columns*の詳しい説明は[こちら](https://www.tensorflow.org/get_started/feature_columns)をご覧ください。


### *Checkpoints*, *SavedModel*について
TensorFlowには、オートセーブ機能が大きく分けて2つ存在します。

- *Checkpoints*
modelのコードに依存したオートセーブフォーマット

- *SavedModel*
modelのコードに依存しないオートセーブフォーマット

*Estimators*にプリセットされてる機能で簡単なセーブポイントを設定したい！って人は*Checkpoints*を、もっと自分でカスタマイズしたい！って人は*SavedModel*を使えばいいと思います。

*Checkpoints*では、`model_dir`という引数を設定し、保存先を指定します。

```python3:checkpoint.py
classifier = tf.estimator.DNNClassifier(
    feature_columns=my_feature_columns,
    hidden_units=[10, 10],
    n_classes=3,
    model_dir='models/iris')
```

そうすると、training中の様々なバージョンの*checkpoints*が`model_dir`に保存されます。
また、*event files*というものも作成されますが、これは後述する*TensorBoard*がtrainingの様子を可視化する際に使用する情報です。
![first_train_calls.png](https://qiita-image-store.s3.amazonaws.com/0/204294/be7d647a-e0f7-ac3a-c76d-fced7e5b5fb8.png)

*Checkpoints*については[こちら](https://www.tensorflow.org/get_started/checkpoints)、*SavedModel*については[こちら](https://www.tensorflow.org/programmers_guide/saved_model)をご覧ください。


# その他
## 公式のドキュメントについて
公式には
- [Getting Started](https://www.tensorflow.org/get_started/)
- [Tutorials](https://www.tensorflow.org/tutorials/)
- [Programmer's Guide](https://www.tensorflow.org/programmers_guide/)

という3つのドキュメントがあります。

[Getting Started](https://www.tensorflow.org/get_started/)の内容は、この記事であらかたまとめたので、基本的に読まなくてもいいと思います。もし説明中に気になる部分があったなら、リンク先に飛んで読んでください。

この記事を読んだ方は、[Tutorials](https://www.tensorflow.org/tutorials/)を読み進めるのがいいと思います。Imagesの[A Guide to TF Layers: Building a Convolutional Neural Network](https://www.tensorflow.org/tutorials/layers)というのが、今回扱ったMNISTのチュートリアルです。それ以降で気になる項目を読み進めましょう。

[Programmer's Guide](https://www.tensorflow.org/programmers_guide/)というのは、実際にTensorFlowでコードを実装し始めたプログラマー向けのドキュメントだそうです。内容も具体的なTensorFlowの機能に絞られています。プログラムを書き進める中で*APIs*などで分からないことがあったときは、このドキュメントが役に立ちそうです。


## TensorBoardについて
TensorFlowにはTensorBoardというとても便利なツールがあります。
自分の書いたgraph（プログラム）を可視化したり、trainingの過程を可視化したり、とりあえず可視化しまくれるものです。話を聞くのが好きな人は[こちらのプレゼン](https://youtu.be/eBbEDRsCmv4)、サクッと使い方を知りたい人は[こちらの記事](https://qiita.com/uosansatox/items/b552c9c4d8f1cebbf044)を、じっくり勉強したい人は公式のProgrammer's Guideにある[リファレンス](https://www.tensorflow.org/programmers_guide/summaries_and_tensorboard)を読んでください。

# 最後に
TensorFlowの公式ホームページには検索ボックスがあります。
こうやってキーワードを打つと、キーワードとしても、クエリとしても検索してくれます。
![1519736724333.jpg](https://qiita-image-store.s3.amazonaws.com/0/204294/6e6551e9-3356-eb4a-3ca4-0e7f35edfe5c.jpeg)

TensorFlowではバージョンが変わると今まで使ってたAPIsのインスタンスの階層が変わっていたりします。
なので、コードを参考にする前にしっかりとバージョンを確認し、間違っていないのにエラーを吐かれたら公式でリファレンスを確認するようにしてください。

# 参照
- [TensorFlowのキーコンセプト: Opノード、セッション、変数](https://qiita.com/yanosen_jp/items/70e6d6afc36e1c0a3ef3)
- [TensorFlowの基礎](https://qiita.com/rindai87/items/4b6f985c0583772a2e21)
- [TensorFlow High-Level APIs: Models in a Box (TensorFlow Dev Summit 2017)](https://youtu.be/t64ortpgS-E)
- [A friendly introduction to Convolutional Neural Networks and Image Recognition](https://youtu.be/2-Ol7ZB0MmU)
- [cnn_mnist.py - GitHub](https://github.com/tensorflow/tensorflow/blob/r1.5/tensorflow/examples/tutorials/layers/cnn_mnist.py)
- [Getting Started for ML Beginners](https://www.tensorflow.org/get_started/get_started_for_beginners)
- [Getting Started with TensorFlow](https://www.tensorflow.org/get_started/premade_estimators)
- [A Guide to TF Layers: Building a Convolutional Neural Network](https://www.tensorflow.org/tutorials/layers)
- [Estimators - Programmer's Guide](https://www.tensorflow.org/programmers_guide/estimators)
- [Creating Custom Estimators - Getting Started](https://www.tensorflow.org/get_started/custom_estimators)
- [Importing data - Programmer's Guide](https://www.tensorflow.org/programmers_guide/datasets)
- [Feature Columns - Getting Started](https://www.tensorflow.org/get_started/feature_columns)
- [Checkpoints - Getting Started](https://www.tensorflow.org/get_started/checkpoints)
- [Saving and Restoring - Programmer's Guide](https://www.tensorflow.org/programmers_guide/saved_model)
- [Hands-on TensorBoard (TensorFlow Dev Summit 2017)](https://youtu.be/eBbEDRsCmv4)
- [TensorBoardの最も基本的な使い方](https://qiita.com/uosansatox/items/b552c9c4d8f1cebbf044)
- [TensorBoard: Visualizing Learning - Programmer's Guide](https://www.tensorflow.org/programmers_guide/summaries_and_tensorboard)