2017年1月13日金曜日

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

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

import tensorflow as tf
import numpy

numpyも使うので、忘れずにインポートすること。

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

SCORE_SIZE = 33
HIDDEN_UNIT_SIZE = 32
TRAIN_DATA_SIZE = 90

SCORE_SIZE: 入力データの次元
HIDDEN_UNIT_SIZE:
隠れ層(中間層)のノード数
TRAIN_DATA_SIZE:
学習データのサンプル数
変数名は、自分で設定してOK

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

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)のベクトル、score94 x 33の配列にそれぞれなる。
ここで、salaryが教師データ、scoreが入力データになる点に注意する。
http://goo.gl/hoMyGH

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

[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) 隠れ層のパラメータ指定と活性化関数の計算を定義する

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

  with tf.name_scope('hidden1') as scope:

with: pythonの文法。with以下の命令を、文脈外のコマンドに持ち越さないという命令。
tf.name_scope
: TFの名前管理関数。この関数下の入れ子の命令に、ひとつの文脈・グループを形成する。

名前の管理をするメリットは、描画である。TensorFlowTensorBoardにて構築した学習器のモデルを図示することができる。名前を管理しておくと、描画のアウトプットがわかりやすくなる。
ここでは、以下のweight biasへの代入行為がname_scope関数下にネストされている。
すわなち、これらの命令が'hidden1'という隠れ層のパラメータ計算に関する文脈に入っていることを定義づけている。
http://goo.gl/AYodFB

    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
の重みWWの初期値を乱数で生成している。この段階で、このプログラムが事前学習ないNNであると解釈できる。
hidden1_weight
は、重みWWの行列と解釈できる。
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として、ユニット数分の計算結果が、ベクトルとして算出される。

  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) 誤差関数の定義

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(確率的勾配降下法)の適用

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)に必要な情報も整理してくれる。(便利だねっ(ω))

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 =
アウトプット時の要約情報に組み込み/反映させるための文字列格納オブジェクト

  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
に仮のTensorvalueに初期値を入力。
学習データに関する辞書セットである。
このタイプの辞書が必要な理由は、下記のsession.runのところで分かる。

  feed_dict_test={
    salary_placeholder: salary_test,
    score_placeholder: score_test,
    loss_label_placeholder: `loss_test`
  }

feed_dict_test: テストデータに関する辞書データの作成
構成は、上記と同じ。

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をぶち込む。

python

  summary_op = tf.merge_all_summaries()

tf.merge_all_summaries Summary関数の情報を集約してくれる。

http://goo.gl/wQo8Rz

  init = tf.initialize_all_variables()

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

http://goo.gl/S58XJ2

  best_loss = float(`inf`)

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

  with tf.Session() as sess:

Sessionは、Graphの中でも中核になるクラスで、実行関係全般の命令が詰まっている。Sessionを宣言しないと、一切のtfオブジェクトがらみ処理が始まらない。

http://goo.gl/pDZeLI

    summary_writer = tf.train.SummaryWriter('data', graph_def=sess.graph_def)

tf.train.SummaryWriter: 要約情報をイベントファイルに書き込む関数。

    sess.run(init)

sess.run: さらに中核になる関数。

最初の引数に書いてある命令を実行する。
Windows
で言えば、.exeみたいなものだ!

    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システム。ここで必要なデータを取り込んで、学習更新計算が行われる。

      if loss_test < best_loss:
        best_loss = loss_test
        best_match = sess.run(output, feed_dict=feed_dict_test)

誤差の最小値が更新された時に限り、その誤差値と出力層(推定の年俸)を記録する。

      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回、要約情報を集めるという命令。
さらに、集めた情報をイベントファイルに書き込む。

    print sess.run(tf.nn.l2_normalize(salary_placeholder, 0), feed_dict=feed_dict_test)
    print best_match

毎ステップごとの教師データと精度の高い出力層のデータを表示する。

 

0 件のコメント:

コメントを投稿