タグ別アーカイブ: python

TensorFlow GET STARTED その1

この記事は TensorFlow > Develop > GET STARTED > Getting Started With TensorFlow を自分なりに和訳しながら理解しようとしたメモです。

正確な情報は公式ページを確認してください。
このページの情報に不備があったとしてもいかなる責任も負えません。

TensorFlowを始める

低レベルAPI -> 研究者などの細かいレベルで制御したい人向け
高レベルAPI -> 共通した処理を内包してくれているので、ユーザプログラムは同じようなものになる
contrib と名のつくものはまだ開発中のAPIだそうな。正式に採用されているAPIではないため今後廃れていく可能性がある。

このドキュメントでは 低レベルAPIと高レベルAPIの2つの側面から確認する。
それによって内部的にどういう処理がなされているかということをイメージすることができるようになる。

Tensors

Tensorとはベクトル概念の一般化。
というわけではよくわからないのでもうちょっとイメージを高めると
ものすごく大きなものでもその様子に線形性があるときには、わざわざ別に全部の情報を記載する必要はなく、基底の情報だけを格納すればデータとしては十分である。
その正規化された情報のことをテンソルと呼ぶ。

tensor-flowにおいては特定のデータの集合のことをテンソルと呼び、任意の次元の配列で表現された値の集合で構成される。
テンソルは rank という概念を持つ。これは次元の深さに近い
たとえば
3 -> rank 0
 ただのスカラ値であるため次元を持たない
[1., 2., 3.] -> rank 1 (shape [3])
 要素3個で構成される配列
[[1., 2., 3.], [4., 5., 6.]] -> rank 2 (shape [2,3])
 (要素3個で構成される配列)を持つ(要素2個で構成される配列)
[[[1., 2., 3.]], [7., 8., 9.]]] -> rank 3 (shape [2,1,3])
 (要素3個で構成される配列)を持つ(要素1個構成される配列)を持つ(要素2個で構成される配列)

チュートリアル

import

TensorFlowをimportする際には下記の構文を用いる

import tensorflow as tf

computational graph

内部の計算処理をオペレーションノードとして表現し、その処理の流れを連結してできたグラフのことをcomputational graph(コンピュータグラフ)というような呼び方で表現する
各ノードは0個以上のテンソルをinputとしてうけとり、1個のテンソルをoutputとして出力する。

定数もテンソルとして表現される。
定数テンソルは0個のinputをうけとり、定数をoutputとして出力するノードとして表現できる。
定数テンソルを定義するコードは例えば下記のようになる。

import tensorflow as tf

node1 = tf.constant(3.0, tf.float32)
node2 = tf.constant(4.0)

print(node1, node2)

ここでこのコードを実行したときには下記のようになる。

$ python hoge.py
(<tf.Tensor 'Const:0' shape=() dtype=float32>, <tf.Tensor 'Const_1:0' shape=() dtype=float32>)

printはノードの保持するテンソルの値(この場合3.0とか4.0)を出力しない。
テンソル値は評価されるときに処理される。

computational graphを実行するには session を用いる。
session は tensorflow の実行時の内部状態を保持して computational graphを実行するためのインタフェースである。
先程の computational graph を実行するコードは下記のようになる

import tensorflow as tf

node1 = tf.constant(3.0, tf.float32)
node2 = tf.constant(4.0)

sess = tf.Session()
print(sess.run([node1, node2]))

その実行結果を下記に示す。
node1, node2のテンソル値が表示されることが確認できる。

$ python hoge.py
[3.0, 4.0]

また node Tensorノードが提供するオペレーションを利用してより複雑なノードを構成することができる。
例えば下記のように node1, node2 を結合して 新しいノード node3 を作成することができる。

import tensorflow as tf

node1 = tf.constant(3.0, tf.float32)
node2 = tf.constant(4.0)

node3 = tf.add(node1, node2)

sess = tf.Session()
print(sess.run([node3]))

これは実行すると下記のようなnode1とnode2の結合されたテンソルの値を表示する

$ python hoge.py
[7.0]

この例はあまり面白くない。
というのも定数値を利用したら常に同じ結果しか表示されないためである。
次に ノードのinput値を変数として取り扱う placeholders という概念を取り上げる。

import tensorflow as tf

a = tf.placeholder(tf.float32)
b = tf.placeholder(tf.float32)
adder_node = a + b  # + provides a shortcut for tf.add(a, b)

sess = tf.Session()
print(sess.run(adder_node, {a: 3, b: 4.5}))
print(sess.run(adder_node, {a: [1,3], b: [2, 4]}))

placeholder はあとでパラメータを受け取ることを宣言する。
イメージ的には関数やラムダに近い。
また ノードに対するオペレータ a + b は tf.add のエイリアスとして機能する。

結果は下記のようになる。
任意のパラメータをinputとして取り扱えていることが確認できる。

$ python hoge.py
7.5
[ 3.  7.]

また更に複雑なノードも表現することができる。

import tensorflow as tf

a = tf.placeholder(tf.float32)
b = tf.placeholder(tf.float32)
adder_node = a + b  # + provides a shortcut for tf.add(a, b)

add_and_triple = adder_node * 3.

sess = tf.Session()
print(sess.run(add_and_triple, {a: 3, b: 4.5}))

結果は下記のようになる

$ python hoge.py
22.5

公式ページに掲載されている TensorBoard による computational graph を確認すると下記のようになっており、確認すると
adder_node * 3 という表現を用いたときに内部的には 3 という表現を 定数ノードとして表現する。
add_and_triple ノードは adder_node ノードのoutputテンソルと 定数3 のノードの出力テンソルを入力として受け取るノードとして表現されていることが見て取れる。

機械学習においては典型的には入力値は様々な値を取る。
モデルを訓練可能にするためには graph を同じ入力から新しい output を得るために更新する必要がある。
これを実現するために variables を用いることで、訓練可能なパラメータをグラフ上に表現することが可能になる。

tf.constant はコールされた際に初期化されるのに対して、 tf.Variable はコールされた時点では初期化されない。
session.run を実行する前に tf.global_variables_initializer() をコールして variables を初期化する必要がある。

試しにパラメータを追加した線形モデルを構築する

import tensorflow as tf

W = tf.Variable([.3], tf.float32)
b = tf.Variable([-.3], tf.float32)
x = tf.placeholder(tf.float32)
linear_model = W * x + b

sess = tf.Session()
init = tf.global_variables_initializer()

sess.run(init)
print(sess.run(linear_model, {x:[1,2,3,4]}))

実行結果は下記のようになる

$ python hoge.py
[ 0.          0.30000001  0.60000002  0.90000004]

ここまででモデルを作成することに成功した。
だけれどもこのままではまだこのモデルが正しいかどうかを評価することができない。
したがって 求めるべき答え y と、誤差関数を実装する必要がある。

誤差関数は提供されたデータから現在のモデルがどのくらい正確なものかということを評価する。
ここでは linear_model を評価するための誤差関数として線形回帰のための標準的なものを使用する。
提供されたデータと現在のモデルの誤差を自乗して加算する、いわゆる自乗誤差を用いる。

import tensorflow as tf

W = tf.Variable([.3], tf.float32)
b = tf.Variable([-.3], tf.float32)
x = tf.placeholder(tf.float32)
y = tf.placeholder(tf.float32)
linear_model = W * x + b

squared_deltas = tf.square(linear_model - y)
loss = tf.reduce_sum(squared_deltas)

sess = tf.Session()
init = tf.global_variables_initializer()

sess.run(init)
print(sess.run(loss, {x:[1,2,3,4], y:[0,-1,-2,-3]}))

ここで y を定義して目的関数の評価をしてみると下記のようになる

$ python hoge.py
23.66

手動で w や b の定義を変更すると誤差関数を 0 に導くことができる(当たり前だが)
だが機械学習の目的は自動的に適切なパラメータを学習してくれることだ。
次にどうやってこれを達成するかを示す。

tf.train API

tensorflow では誤差関数を最小化するために variable を少しずつ変更する optimizer と言うものが存在する。
シンプルな optimizer として gradient descent(最急降下法)これは誤差関数の導関数から傾きを算出し、誤差が小さくなるように parameter を更新していく手法です。
ふつう導関数を手動で計算するのはうんざりするほど面倒くさい作業で、かつ間違いやすい。

そこで tensorflow では tf.gradients 関数を用いるだけでモデルの記述のみから導関数を自動的に生成することができる(すげえ)
コードはシンプルに下記のようになる

import tensorflow as tf

W = tf.Variable([.3], tf.float32)
b = tf.Variable([-.3], tf.float32)
x = tf.placeholder(tf.float32)
y = tf.placeholder(tf.float32)
linear_model = W * x + b

squared_deltas = tf.square(linear_model - y)
loss = tf.reduce_sum(squared_deltas)

optimizer = tf.train.GradientDescentOptimizer(0.01)
train = optimizer.minimize(loss)

init = tf.global_variables_initializer()
sess = tf.Session()

sess.run(init)
for i in range(1000):
    sess.run(train, {x:[1,2,3,4], y:[0,-1,-2,-3]})

print(sess.run([W, b]))

学習された W, b の結果は下記のようにほぼ正解に収束されていることがわかる。

$ python hoge.py
[array([-0.9999969], dtype=float32), array([ 0.99999082], dtype=float32)]

tf.contrib.learn API

tf.contrib.learn は高レベルのAPIで機械学習の実装をシンプルに行うことができます。その内容は下記を含みます。
・training loops を実行する
・evaluation loops を実行する
・データセットの管理
・feeding の管理(?)
また tf.contrib.learn は共通のモデルを多く定義しています

また既存のモデルだけしか使用できないかというとそういうわけでもなく、モデルをカスタムして用いることもできます。

(※サンプルコードに関しては割愛します)