TensorFlowのPythonコードの初歩を噛み砕いてみる

こんにちは。
本稿では、TensorFlow（以下TF）の基本的な書き方と文法について、噛み砕いて説明します。
対象は、TFを初めて触れる、かつPythonもあまり書いたことない方向けです。

背景として、私自身がまともにPythonのコードを書いたのが、TFに触れた時でした。
その時に残したノートなので、同じような方のTF理解の一助になるかもしれません。

見本のコードはsergeant-wizardさんの「[ニューラルネットワークでプロ野球選手の給与を査定してみる](http://qiita.com/sergeant-wizard/items/9bb45c0850aebca2bc07)」を使わせて頂きました。
掲載許可をいただき、ありがとうございました。

### (1) ライブラリのインポート

```python
import tensorflow as tf
import numpy
```
numpyも使うので、忘れずにインポートすること。

### (2) データの規模とNNの次元設定

```python
SCORE_SIZE = 33
HIDDEN_UNIT_SIZE = 32
TRAIN_DATA_SIZE = 90
```
SCORE_SIZE: 入力データの次元
HIDDEN_UNIT_SIZE: 隠れ層（中間層）のノード数
TRAIN_DATA_SIZE: 学習データのサンプル数
変数名は、自分で設定してOK！

### (3) データの規模とNNの次元設定

```python
raw_input = numpy.loadtxt(open(`input.csv`), delimiter=`,`)
[salary, score]  = numpy.hsplit(raw_input, [1])
```
numpy.loadtxt: numpyライブラリにあるデータロード関数。
> delimiterは、データ区切りの指定。
> 著者は、input.csvにデータ格納した。
> http://goo.gl/h5g8cJ
> raw_inputは配列型データを格納する。

numpy.hsplit: 垂直方向(列方向)に割線を入れ、水平方向(行方向に)配列を分割する関数
> raw_input：分割対象の配列
> [1]：分割の境界線。0列目を選択してsalaryに,1列目からをscoreに代入する。したがって、salaryは成分数＝人数(=94)のベクトル、scoreは94 x 33の配列にそれぞれなる。
> ここで、salaryが教師データ、scoreが入力データになる点に注意する。
> http://goo.gl/hoMyGH

### (4) 訓練データを整形する(訓練用の選手とテスト用の選手に分ける)

```python
[salary_train, salary_test] = numpy.vsplit(salary, [TRAIN_DATA_SIZE])
[score_train, score_test] = numpy.vsplit(score, [TRAIN_DATA_SIZE])
```
numpy.vsplit：水平方向にメスを入れて、垂直方向に配列を分ける関数
> salary_train: 成分数89のベクトル
> salary_test: 成分数5のベクトル
> score_train: 89 x 33 の配列
> score_test: 5 x 33 の配列

### (5) 隠れ層のパラメータ指定と活性化関数の計算を定義する

```python
def inference(score_placeholder):
  with tf.name_scope('hidden1') as scope:
    hidden1_weight = tf.Variable(tf.truncated_normal([SCORE_SIZE, HIDDEN_UNIT_SIZE], stddev=0.1), name=`hidden1_weight`)
    hidden1_bias = tf.Variable(tf.constant(0.1, shape=[HIDDEN_UNIT_SIZE]), name=`hidden1_bias`)
    hidden1_output = tf.nn.relu(tf.matmul(score_placeholder, hidden1_weight) + hidden1_bias)
  with tf.name_scope('output') as scope:
    output_weight = tf.Variable(tf.truncated_normal([HIDDEN_UNIT_SIZE, 1], stddev=0.1), name=`output_weight`)
    output_bias = tf.Variable(tf.constant(0.1, shape=[1]), name=`output_bias`)
    output = tf.matmul(hidden1_output, output_weight) + output_bias
  return tf.nn.l2_normalize(output, 0)
```
inference: 関数名(任意につけられる)
score_placeholder：下のコードで定義されるデータ。
> placeholder型のデータには、学習の元になるデータ／更新データ（つまり、入力データと教師データ）を指定する。TF独特のデータ型である。
> TFでは学習の更新の際に、feedという仕組みで元データを取り込む。placeholder型はfeedと連携しており、入力データが関連の計算に反映されるようになる。
> http://goo.gl/uhHk3o

```python
  with tf.name_scope('hidden1') as scope:
```
with: pythonの文法。with以下の命令を、文脈外のコマンドに持ち越さないという命令。
tf.name_scope：　TFの名前管理関数。この関数下の入れ子の命令に、ひとつの文脈・グループを形成する。
> 名前の管理をするメリットは、描画である。TensorFlowはTensorBoardにて構築した学習器のモデルを図示することができる。名前を管理しておくと、描画のアウトプットがわかりやすくなる。
> ここでは、以下のweight や biasへの代入行為がname_scope関数下にネストされている。
> すわなち、これらの命令が'hidden1'という隠れ層のパラメータ計算に関する文脈に入っていることを定義づけている。
> http://goo.gl/AYodFB

```python
    hidden1_weight = tf.Variable(tf.truncated_normal([SCORE_SIZE, HIDDEN_UNIT_SIZE], stddev=0.1), name=`hidden1_weight`)
    hidden1_bias = tf.Variable(tf.constant(0.1, shape=[HIDDEN_UNIT_SIZE]), name=`hidden1_bias`)
    hidden1_output = tf.nn.relu(tf.matmul(score_placeholder, hidden1_weight) + hidden1_bias)
```
**入力層→隠れ層**の計算である。

tf.Variable: TFの変数(Variable)クラスを適用できる
> 変数としての生成のほか、様々な機能を有する。
> 例えば、assignという命令により、変数の値を上書きできる。(詳細は下リンク)
> http://goo.gl/nUJafs

tf.truncated_normal：正規分布の乱数を返してくれる
> [SCORE_SIZE, HIDDEN_UNIT_SIZE]：欲しい乱数の配列サイズ。
> stddev：正規分布の標準偏差を指定する。標準正規分布では、「mean=0.0, stddev=1.0」と指定する。
> NNの重み${\bf W}$の初期値を乱数で生成している。この段階で、このプログラムが事前学習ないNNであると解釈できる。
> hidden1_weightは、重み${\bf W}$の行列と解釈できる。
> nameは単純にこの関数の実行に名前をつけている。
> http://goo.gl/oZkcvs

tf.constant：定数を生成する関数

> 0.1：定数0.1を生成する。
> shape: 定数を作るサイズ。隠れ層のユニット数(HIDDEN_UNIT_SIZE)だけ作り、hidden1_biasに代入する。
> hidden1_biasは隠れ層のバイアス項であり、ここでは初期値を0.1と設定した。

tf.nn.relu：活性化関数のひとつであるReLUを計算する関数
(本稿はコードの解説を目的とするので、ReLUの意味は、別途学んでくださいm(_ _)m)

tf.matmul: 行列(ベクトル)の積(ベクトル同士だったら、内積)を計算する関数

> ここでは、行列score_placeholderと行列hidden1_weightの積を計算する。下の定義でわかるが、score_placeholderの列数がSCORE_SIZEと定義されるので、この定義が成立する。
> 結果的に、outputとして、ユニット数分の計算結果が、ベクトルとして算出される。


```python
  with tf.name_scope('output') as scope:
    output_weight = tf.Variable(tf.truncated_normal([HIDDEN_UNIT_SIZE, 1], stddev=0.1), name=`output_weight`)
    output_bias = tf.Variable(tf.constant(0.1, shape=[1]), name=`output_bias`)
    output = tf.matmul(hidden1_output, output_weight) + output_bias
  return tf.nn.l2_normalize(output, 0)
```
**隠れ層→出力層**の計算である。
outputはスカラーで算出される。つまり、出力層のユニットは1であり、これが選手の年俸(教師データ)との比較対象になる。
このケースでは、出力層に対して活性化関数を恒等写像にして処理している。
tf.nn.l2_normalize：正規化を計算して返す関数
> ベクトルであれば、ベクトルの各成分にノルムを除して求める。(ベクトルの大きさが1になる変形のやーつ)
> 行列、テンソル以降も同様。
> スカラーであれば、1が返る。ここでは、元データも正規化しているため、正規化した値同士の比較が必要になる。
> https://goo.gl/NEFajc


### (6) 誤差関数の定義

```python
def loss(output, salary_placeholder, loss_label_placeholder):
  with tf.name_scope('loss') as scope:
    loss = tf.nn.l2_loss(output - tf.nn.l2_normalize(salary_placeholder, 0))
    tf.scalar_summary(loss_label_placeholder, loss)
  return loss
```
tf.nn.l2_loss: 2乗誤差を計算する関数。
> データ型に制限があるので注意する。
> 主な誤差関数には**二乗誤差関数**と**交差エントロピー誤差関数**がある。二乗誤差関数は数値予測タスク、交差エントロピー誤差関数はクラス分類タスクにそれぞれ用いる。
> salary_placeholderという指定の仕方は教師データとしての給与データをこの変数に更新ごとに投入するためである。
> http://goo.gl/V67M7c

tf.scalar_summary：対象のスカラーに文字列を付けて、値の意味付けを記録する関数。
> loss_label_placeholderは、文字列のplaceholderとして、下に定義されている。
> http://goo.gl/z7JWNe


### (7) SGD(確率的勾配降下法)の適用

```python
def training(loss):
  with tf.name_scope('training') as scope:
    train_step = tf.train.GradientDescentOptimizer(0.01).minimize(loss)
  return train_step
```
tf.train.GradientDescentOptimizer: SGDのアルゴリズムを格納するクラスである。
> 0.01: 学習係数εを表す
> minimize(loss)：トリッキーだが、tf.train.GradientDescentOptimizerというクラスに格納されている関数で、対象の変数(ここではloss)を最小化させる
> 計算過程を格納したオブジェクトなので、微分計算後の更新した重みパラメータが返される。
> http://goo.gl/5XENkX


### (8) 実行体系の記述
TensorFlowでは、学習実行にまつわるアルゴリズム群を、Graphというクラスに格納している。
計算(session.run)はもちろんのこと、名前の通りグラフ描画(session.graph)に必要な情報も整理してくれる。(便利だねっ(・ω＜))


```python
with tf.Graph().as_default():
  salary_placeholder = tf.placeholder(`float`, [None, 1], name=`salary_placeholder`)
  score_placeholder = tf.placeholder(`float`, [None, SCORE_SIZE], name=`score_placeholder`)
  loss_label_placeholder = tf.placeholder(`string`, name=`loss_label_placeholder`)
```

with tf.Graph().as_default():　：グラフクラスを宣言する文
salary_placeholder：年収の教師データを格納するオブジェクト
> [None, 1]：列数は1、行数は任意の数という意味。

score_placeholder：入力データを格納するオブジェクト
loss_label_placeholder = アウトプット時の要約情報に組み込み／反映させるための文字列格納オブジェクト

```python
  feed_dict_train={
    salary_placeholder: salary_train,
    score_placeholder: score_train,
    loss_label_placeholder: `loss_train`
  }
```
feed_dict_train：学習更新ごとに食わせる辞書型データの宣言
> 辞書型データ(feed_dict_train)に格納している。TFが都度データを読み込むためには、feedを噛ませなければならない。
> http://goo.gl/00Ikjg
> ↑辞書型データの復習
> keyに仮のTensor、valueに初期値を入力。
> 学習データに関する辞書セットである。
> このタイプの辞書が必要な理由は、下記のsession.runのところで分かる。

```python
  feed_dict_test={
    salary_placeholder: salary_test,
    score_placeholder: score_test,
    loss_label_placeholder: `loss_test`
  }
```
feed_dict_test: テストデータに関する辞書データの作成
構成は、上記と同じ。


```py:python
  output = inference(score_placeholder)
  loss = loss(output, salary_placeholder, loss_label_placeholder)
  training_op = training(loss)
```
`output`に、順伝播のNN計算関数inferenceをぶち込む。
`loss`に、二乗誤差関数の計算関数lossをぶち込む。
`training_op`に、SGDのアルゴリズム実行関数trainingをぶち込む。

```py:python
  summary_op = tf.merge_all_summaries()
```
tf.merge_all_summaries： Summary関数の情報を集約してくれる。
> http://goo.gl/wQo8Rz


```python
  init = tf.initialize_all_variables()
```

initに、全ての変数を初期化する関数tf.initialize_all_variablesを代入する。
**初期化のinit宣言するタイミングが重要だ。**必要な変数を全て定義してから宣言しないと、エラーを返す。
> http://goo.gl/S58XJ2

```python
  best_loss = float(`inf`)
```
浮動小数点数であるbest_lossを宣言。
下記で、誤差の更新をするところがある。
その値にbest_lossを使うのだが、初期値のlossを必ず入れる必要があるので、初期のbest_lossはどんな数よりも高く(inf；無限大)している。

```python
  with tf.Session() as sess:
```
Sessionは、Graphの中でも中核になるクラスで、実行関係全般の命令が詰まっている。Sessionを宣言しないと、一切のtfオブジェクトがらみ処理が始まらない。
> http://goo.gl/pDZeLI


```python
    summary_writer = tf.train.SummaryWriter('data', graph_def=sess.graph_def)
```
tf.train.SummaryWriter：　要約情報をイベントファイルに書き込む関数。


```python
    sess.run(init)
```
sess.run：　さらに中核になる関数。
> 最初の引数に書いてある命令を実行する。
> Windowsで言えば、.exeみたいなものだ！

```python
    for step in range(10000):
      sess.run(training_op, feed_dict=feed_dict_train)
      loss_test = sess.run(loss, feed_dict=feed_dict_test)
```
for step in range(10000): 10000回の繰り返し。「89人分のデータを学習し、5人分のデータをテストする。」これで1回です！
feed_dict=feed_dict_train：session.runの重要なオプション
> ここが繰り返し説明してきたfeedシステム。ここで必要なデータを取り込んで、学習更新計算が行われる。

```python
      if loss_test < best_loss:
        best_loss = loss_test
        best_match = sess.run(output, feed_dict=feed_dict_test)
```
誤差の最小値が更新された時に限り、その誤差値と出力層(推定の年俸)を記録する。

```python
      if step % 100 == 0:
        summary_str = sess.run(summary_op, feed_dict=feed_dict_test)
        summary_str += sess.run(summary_op, feed_dict=feed_dict_train)
        summary_writer.add_summary(summary_str, step)
```
100ステップに1回、要約情報を集めるという命令。
さらに、集めた情報をイベントファイルに書き込む。

```python
    print sess.run(tf.nn.l2_normalize(salary_placeholder, 0), feed_dict=feed_dict_test)
    print best_match
```
毎ステップごとの教師データと精度の高い出力層のデータを表示する。