TensorFlow 低レベルAPI メモ

自分用メモ。Pythonの文法と機械学習に関するざっくりとした知識があるけど TensorFlow について全く知らない人には有用かもしれない

# 0. 参考文献
- Python 機械学習プログラミング 第二版 13, 14章
- [TensorFlow 公式Introduction](https://www.tensorflow.org/guide/low_level_intro) と付随するページ

# 1. 環境構築
Google Colaboratory を使うので環境構築は不要。以下のおまじないで使えるようになる

```py
import numpy as np
import tensorflow as tf
```

# 2. Hello World
## 2-1. Graph と Session
実行環境を Session といい、 実行環境上で動作させるプログラムを Graph という。「Graph を作って、 Session を作って、 Session 上で Graph を動作させる」というのが基本的なプログラムの全体像になる。

```HelloWorldを出力.py
g = tf.Graph()
with g.as_default():
  x = tf.constant('hello world')  
with tf.Session(graph=g) as session:
  print(session.run(x)) # -> this prints 'hello world'
```
Graph 上に ts.constant などのノードを配置していくイメージだが、どのグラフ上にノードを配置するのかを指定するために、上記のような with 文が必要になる。 with 文がない場合はデフォルトの Graph にノードが配置されるが、Notebook の環境だと色々ややこしいことになってしまうので上記のようにやるのが筋らしい。Session に関しても with 文でくくるのが筋らしい。どの Graph を使うのかを明示するのに必要ということだろう。

`sesson.run(x)` によって、 x に関連するノード（のみ）において実際の計算が走り結果を得る。

## 2-2. Graph にデータを渡す(placeholder)
Graph はプログラムみたいなものなので入力は外から与えるのが普通。それを実現するのが placeholder という概念。session.run() を実行する際に、ディクショナリの形で値を渡す。

```二つの数字を渡して足し算をさせる.py
g = tf.Graph()
with g.as_default():
  x = tf.placeholder(tf.float32)
  y = tf.placeholder(tf.float32)
  z = x + y
with tf.Session(graph=g) as session:
  dic = {x: 0.1, y: 0.2}
  print(session.run(z, feed_dict=dic)) # -> 0.3
```

## 2-3. 変数を使う
変数は**一つのSessionに縛られず長寿命であるのが特徴**。つまり(デフォルトでは)グローバルということ。つまり、トレーニングで更新される重みを格納するのに最適。さらに、変数はデフォルトでトレーナブルである。これは、tf のオプティマイザに対するヒントであり、トレーニングの計算グラフを自動生成する際の情報になる。

また、変数は初期化しないと使えない。初期化関数は `tf.global_variables_initializer()` の返り値である。これをどこかで呼んであげる必要がある。

```py
g = tf.Graph()
with g.as_default():
  x = tf.constant(1)
  v = tf.get_variable("v", (2,), dtype=tf.int32, initializer=tf.constant_initializer(10))
  z = x + v * 3 # variables are not special. you can use it as a normal tensor.
with tf.Session(graph=g) as session:
  session.run(tf.global_variables_initializer()) # side effect(memory allocation and initialization)  
  print(session.run(z)) # -> [31 31]
```

変数に値を代入する(assign)のには専用の関数があるのでそれを使うこと。

```py
g = tf.Graph()
with g.as_default():
  x = tf.constant(1, shape=(2,))
  v = tf.get_variable("v", (2,), dtype=tf.int32, initializer=tf.constant_initializer(10))
  z = v.assign(v + x)
with tf.Session(graph=g) as session:
  session.run(tf.global_variables_initializer()) # side effect(memory allocation and initialization)  
  print(session.run(z)) # -> [11 11]
  print(session.run(z)) # -> [12 12] NOTE: v is global.
```

## 2-4. Ops と Tensors
Graph のエッジノードは Tensor であり、それ以外のノードは計算を担う Ops となる（注: pythonのオブジェクト名としては両方Tensorに見える）。原則として `session.run()` で指定するのはエッジノードである Tensor である（だって知りたいのは計算した数値だもの）が、Ops を実行する必要があることもある。

Ops を実行することで副作用を起こしたい場合があるということ。具体的には二つ典型例があり、**初期化** と **トレーニング** である。どちらも、グローバル変数の値を tf に弄ってもらうために実行する。初期化についてはすでに言及済み。トレーニングは後述。

## 2-5. Layer
ニューラルネットは、tf の言葉でいえばただの Graph であるが、一つ一つのノードをいちいち定義するのは煩雑すぎるので、それを緩和するための道具立てとして、Layer がある。Layer は Variable と関連する Ops をパッケージ化したもので、ニューラルネットを扱うときはこれを使うのが推奨される。

```py
g = tf.Graph()
with g.as_default():
  x = tf.placeholder(tf.float32, shape=(None,3))
  y = tf.layers.dense(x, units=1)
with tf.Session(graph=g) as session:
  session.run(tf.global_variables_initializer())
  dic = { x: [[0.1, 0.2, 0.3], [0.2, 0.3, 0.4]] }
  session.run(y, dic) # meaningless val
```

# 3. はじめてのトレーニング
## 3-1. 概要
入力 x をプレースホルダーで用意して、x をLayer で作ったモデルにつなげて、出力 y_pred を定義。教師データ y_answer を使ってコスト/loss を定義し、勾配降下法でコストを最小化するためのトレーニングOps を取得し、runする。

## 3-2. コード

```py
g = tf.Graph()
with g.as_default():
  x = tf.placeholder(tf.float32, shape=(None, 1))
  y_answer = tf.placeholder(tf.float32, shape=(None, 1))
  y_pred = tf.layers.dense(x, units=1)
  loss = tf.losses.mean_squared_error(labels=y_answer, predictions=y_pred)
  optimizer = tf.train.GradientDescentOptimizer(0.01)
  train = optimizer.minimize(loss)
  
with tf.Session(graph=g) as session:
  session.run(tf.global_variables_initializer())
  dic = { x: [[1.], [2.], [3.]], y_answer: [[3.], [4.], [5.]] }
  for i in range(1000):
    _, loss_value = session.run((train, loss), dic)
    if i % 100 == 0: print(loss_value)
  print('prediction:', session.run(y_pred, dic)) # -> [[2.9348474], [3.9860182], [5.0371895]]
```

なんとなくうまくいった。