ビギナーが「エキスパートのための TensorFlow 2.0 入門」読んでみた


# やること:point_up:
[tensorflow2.0のチュートリアル](https://www.tensorflow.org/tutorials/quickstart/advanced)を読む。
チュートリアルでよく分からなかったものを調べて補足して備忘録としたいと思います。
なのでチュートリアルと並べながら読むと良いかもしれません。

# 背景
いままでchainerを使ってたけど
そろそろtensorflowも学ぼうとしていたらいつの間にか主流が2.0に移っていた
tensorflow1系すら知らないけど，今やるなら2系から始めるのが合理的かなとtensorflow2のチュートリアルを読み始める


# 環境
windows10
anacondaの仮想環境で行いました
環境構築は以下

```
#python3.6までしか対応してないらしいのでバージョンは3.6
conda create -n tensorflow2.0 python=3.6 anaconda
conda install tensorflow==2.0.0
conda install jupyter
```

# 解説
はじめに、TensorFlowライブラリをプログラムにインポートします。

```
from __future__ import absolute_import, division, print_function, unicode_literals

!pip install -q tensorflow-gpu==2.0.0-rc1
import tensorflow as tf

from tensorflow.keras.layers import Dense, Flatten, Conv2D
from tensorflow.keras import Model
```
MNIST データセットをロードして準備します。

```
mnist = tf.keras.datasets.mnist

(x_train, y_train), (x_test, y_test) = mnist.load_data()

#画素値（0~255）を0~1に押し込む
x_train, x_test = x_train / 255.0, x_test / 255.0

# tf.newaxisで次元数を取り出せるらしい
# 各データ（各画像）に次元数の情報を足す
# CNNは次元情報が必要みたい。全結合では逆にやってはいけない処理かも
x_train = x_train[..., tf.newaxis]
x_test = x_test[..., tf.newaxis]
```

データセットをシャッフルし、バッチ化するために tf.data を使います。

```
# 10000はバッファサイズ。CNNやる上では10000あれば十分のよう？？
# 32はバッチサイズ。
train_ds = tf.data.Dataset.from_tensor_slices(
    (x_train, y_train)).shuffle(10000).batch(32)
test_ds = tf.data.Dataset.from_tensor_slices((x_test, y_test)).batch(32)
```
Kerasのmodel subclassing APIを使ってtf.kerasモデルを作ります。

```
# Model classの継承
class MyModel(Model):
# initでモデルの構築
  def __init__(self):
    super(MyModel, self).__init__()
# フィルタの数が32種類
# フィルタのサイズが3*3
# 活性化関数はRelu
    self.conv1 = Conv2D(32, 3, activation='relu')
# 二次元データ*フィルタ数のデータを1次元にフラット化する
    self.flatten = Flatten()
    self.d1 = Dense(128, activation='relu')
# 出力層なので活性化関数はsoftmax関数
    self.d2 = Dense(10, activation='softmax')

# xはインプット画像群？？
# -> tf.tensor型のデータのようです　画像群っちゃ画像群
  def call(self, x):
    x = self.conv1(x)
    x = self.flatten(x)
    x = self.d1(x)
# NNにより演算した結果を吐き出す？？
    return self.d2(x)

model = MyModel()
```
訓練のためにオプティマイザと損失関数を選びます。

```
# 安定のクロスエントロピー誤差
loss_object = tf.keras.losses.SparseCategoricalCrossentropy()
# 今（一昔前？）一番と噂のADAM
optimizer = tf.keras.optimizers.Adam()
```
モデルの損失と正解率を計測するためのメトリクスを選択します。これらのメトリクスはエポックごとに値を集計し、最終結果を出力します。

```
# 行列の平均をだすインスタンス
train_loss = tf.keras.metrics.Mean(name='train_loss')
# 正答率をだすインスタンス
train_accuracy = tf.keras.metrics.SparseCategoricalAccuracy(name='train_accuracy')

test_loss = tf.keras.metrics.Mean(name='test_loss')
test_accuracy = tf.keras.metrics.SparseCategoricalAccuracy(name='test_accuracy')
```

tf.GradientTapeを使ってモデルを訓練します。



```
@tf.function
def train_step(image, label):
# with 内部の計算を記録。tape.~~で取り出してる
  with tf.GradientTape() as tape:
# imageをもとに予測，ロスの計算
    predictions = model(image)
    loss = loss_object(label, predictions)
# model.trainable_variablesで重みを渡す
  gradients = tape.gradient(loss, model.trainable_variables)
# optimizerで重みを更新
  optimizer.apply_gradients(zip(gradients, model.trainable_variables))

# さっきのインスタンスでlossとacciracyを算出
  train_loss(loss)
  train_accuracy(label, predictions)
```
参考：[低レベルAPIユーザーのためのTensorFlow2.0入門](https://qiita.com/mgmk2/items/59e070dfbdb95137e511)

`@tf.function`について
NNの回し方は主に以下の2つがあります
Define-and-run: 計算グラフを定義(Define)してからデータを流し込み(run)結果を得る
Define-by-run: python の通常コードのように、 x + y とグラフ定義をすると同時にグラフが実行されて結果が得られる（[出典](https://qiita.com/halhorn/items/09a64e98a02022e6ccc2)）
'by'のほうが書きやすいというメリットからtensorflow2.0.0では'by'を使ってるみたいです
けど'by'はめっちゃ遅いので`@tf.function`を付ける事で'and'と同じような動作を出来るようにしてるようです
詳しくは[検証記事などを参照](https://qiita.com/nj_ryoo0/items/7f4495aaef07ccc6a303)


次にモデルをテストします。

```
@tf.function
def test_step(image, label):
  predictions = model(image)
  t_loss = loss_object(label, predictions)

  test_loss(t_loss)
  test_accuracy(label, predictions)
```

```
EPOCHS = 5

for epoch in range(EPOCHS):
# バッチ毎に全画像でトレインを実行
  for image, label in train_ds:
    train_step(image, label)

# バッチ毎に全画像でテストを実行
  for test_image, test_label in test_ds:
    test_step(test_image, test_label)

# いろいろ表示
  template = 'Epoch {}, Loss: {}, Accuracy: {}, Test Loss: {}, Test Accuracy: {}'
  print (template.format(epoch+1,
                         train_loss.result(),
                         train_accuracy.result()*100,
                         test_loss.result(),
                         test_accuracy.result()*100))
```

結果

```
WARNING:tensorflow:Layer my_model is casting an input tensor from dtype float64 to the layer's dtype of float32, which is new behavior in TensorFlow 2.  The layer has dtype float32 because it's dtype defaults to floatx.

If you intended to run this layer in float32, you can safely ignore this warning. If in doubt, this warning is likely only an issue if you are porting a TensorFlow 1.X model to TensorFlow 2.

To change all layers to have dtype float64 by default, call `tf.keras.backend.set_floatx('float64')`. To change just this layer, pass dtype='float64' to the layer constructor. If you are the author of this layer, you can disable autocasting by passing autocast=False to the base Layer constructor.

Epoch 1, Loss: 0.14364087581634521, Accuracy: 95.62000274658203, Test Loss: 0.06367728859186172, Test Accuracy: 97.88999938964844
Epoch 2, Loss: 0.09373863786458969, Accuracy: 97.1483383178711, Test Loss: 0.056961096823215485, Test Accuracy: 98.07500457763672
Epoch 3, Loss: 0.07041392475366592, Accuracy: 97.84444427490234, Test Loss: 0.05455232039093971, Test Accuracy: 98.17666625976562
Epoch 4, Loss: 0.05662970244884491, Accuracy: 98.25749969482422, Test Loss: 0.05664524435997009, Test Accuracy: 98.19499969482422
Epoch 5, Loss: 0.047065384685993195, Accuracy: 98.54966735839844, Test Loss: 0.057572390884160995, Test Accuracy: 98.23799896240234
```

P.S.
＠を```で辞書登録したら便利だった