HELLO CYBERNETICS

深層学習、機械学習、強化学習、信号処理、制御工学、量子計算などをテーマに扱っていきます

TensorFlowのeager mode基本

 

 

follow us in feedly

f:id:s0sem0y:20180221044523j:plain

 

 

はじめに

前提として以下のimport文を書いているものとします。

また対話的に実行をするためにeagerモードを用います。

import tensorflow as tf
import tensorflow.contrib.eager as 

 

GPUのメモリを必要な分だけ利用

GPU利用時に以下のコードでTensorFlowは必要な分のメモリを逐次利用していくようになります。このように明記しない場合には、TensorFlowはeager modeであろうがなかろうが、GPUのメモリを可能な限りほとんど全て専有してしまいます。

 

config = tf.ConfigProto(allow_soft_placement=True)
config.gpu_options.allow_growth = True
tfe.enable_eager_execution(config=config)

 

このモードではある計算を実行する際に、どれくらいメモリが消費されているのかをnvidia-smiなどで確認することができます。湯水のようにGPUを使える環境でない場合は、上記のconfigを用いるのがおすすめです。

 

GPU、CPU 、numpyとの変換

eagerモードでのGPU利用とCPU利用

eagerモードではGPUを利用するかCPUを利用するかを明示しなければなりません(eagerモードでない場合は、利用可能であれば自動的にGPUを利用する)。例えば、xにGPU上のtf.Tensorをもたせる場合以下のように書く必要があります(例として10×10の2DTensorを正規分布から生成)。また/gpu:番号はGPUを指定するインデックスです。

 

with tf.device('/gpu:0'):
    x = tf.random_normal([10, 10])

 

CPU上に値をもたせる場合には以下のようになります。

 

with tf.device('/cpu:0'):
    x = tf.random_normal([10, 10])

 

GPU上に配置した値をCPUへ転送、あるいはその逆を行うメソッドがtf.Tensorには備わっているため以下のようにそれぞれ実行することができます。

 

## cpuからgpuへ
with tf.device('/cpu:0'):
    x = tf.random_normal([10, 10])
x_gpu = x.gpu()

## gpuからcpuへ
with tf.device('/gpu:0'):
    x = tf.random_normal([10, 10])
x_cpu = x.cpu()

 

TensorFlowはGPUとCPUでほとんどシームレスに動作する事が(私の中では)強い売りであったので、最初このことに気づかずにCPUで計算を行っており、「eagerモード遅すぎ!」って勝手に思っておりました。改めてGPUの偉大さを実感しましたよ。

 

 

tf.Tensorとnumpy

tf.Tensorの中にはnumpyが入っており、tf.Tensor.numpy()によって取り出すことが可能です。

 

このとき、tf.TensorはGPU上にあってもCPU上にあっても構いません。

 

これはPyTorchには無い利点でした(PyTorchだと一々cpuに転送が必要でした)。

with tf.device('/cpu:0'):
    x_cpu = tf.random_normal([10, 10])
x_gpu = x_cpu.gpu()

x_numpy1 = x_cpu.numpy()
x_numpy2 = x_gpu.numpy()

従って、上記のx_numpy1 == x_numpy2はしっかりTrueを返してくれます。

 

 

逆にnumpyからtf.Tensorへ変換する場合には以下の処理を行います。

x_numpy = np.random.randn(10,10)
x_cpu1 = tf.convert_to_tensor(x_numpy)
x_cpu2 = tf.constant(x_numpy)

tf.convert_to_tensorでもtf.constantでも構いません。with tf.deviceで指定を行わない場合にはcpuにtf.Tensorを配置することになります。

 

 

基本的な演算

四則演算

以下に四則演算やmodなどの重要な演算の関数についてまとめます。関数を使わなくとも、よく使い慣れている記号を用いても演算が実行可能です(あんまりTensorFlowの関数で四則演算を呼び出す機会は無いかと思います)。

x = tf.constant([3.])
y = tf.constant([-2.])

z_add = x + y

z_sub = x - y

z_mul = x * y

z_div = x / y

 

論理演算

論理演算も地味に準備されています。ただし、tf.bool型を扱います。

x = tf.constant([True])
y = tf.constant([False])

z_and = x & y

z_or = x | y

z_xor = x ^ y

z_not = ~x  

 

商以下の最大整数と余剰

あとはプログラミングでよく出てくるここらへんも抑えてあります。

x = tf.constant([17])
y = tf.constant([3])

z_floordiv = x // y  # 5
z_mod = x % y  # 2

 

他にも不等式などもpythonに準じて用いることができます。

 

 

 

 

(偏)微分関数

TensorFlow eagerモードで独特(TensorFlowだと普通なのかな?)だと感じたのは微分関数の取り扱いです。まず初めに、流儀としてはpythonの関数で普通に数学の関数を書きます。

ここでは例として以下の関数を扱いましょう。

 

 f(x,y) = x^2 - 2xy + 3y

 

これをPython関数で

def f(x, y):
    return x**2 - 2*x*y + 3*y

 と書いておきます。

 

これに対して、

 

grad_f = tfe.gradients_function(f)

 

によって勾配関数、grad_fが返ってきますが、この中身は

 

 \left( \frac{\partial }{\partial x}f(x,y), \frac{\partial }{\partial y}f(x,y) \right) = (2x - 2y, -2x + 3)

 

となっていると考えると良いようです。

例えばx=2, y=1を代入すればこの勾配関数は

 

 \left( \frac{\partial }{\partial x}f(x,y), \frac{\partial }{\partial y}f(x,y) \right) = (2, -1)

 

となるはずであり、実際に以下のコードによって、このタプルが返ってきました。

 

x = 2.
y = 1.

x_grad, y_grad = grad_f(x, y)
print(x_grad)  ## 2.0
print(y_grad)  ## -1.0

 

多分ニューラルネットワークのパラメータ更新を行う際にはこれは使いません。

 

ニューラルネットのパラメータに関する微分を全部やってくれる関数が別で準備されているので、ここで紹介した微分関数はもっと細かいことを自分で書いたり、あるいは勾配を必要とする他のアルゴリズムをあれこれ書く時に使うものだと思われます。

 

 

学習で用いる微分関数

学習では、入力データxと目標値yを引数とする損失関数loss(x,y)を構築し、この損失関数のパラメータwに関する勾配がほしいということになります。

すなわち損失関数というのはloss(x,y,w)という関数であり、x,yを固定したwの偏微分が欲しいということです。これがニューラルネットワークの学習の基本的形になります。

 

コードとしては以下のようになります。ここでニューラルネットワークは、単に入力との内積を取るだけのy = w^Txとしています(パラメータはw)。

 

model = tf.layers.Dense(1)

def loss(x, y):
    return tf.reduce_sum(tf.square(model(x) - y))

grad_f_w = tfe.implicit_gradients(loss)

x = tf.random_normal([5, 3])
y = tf.random_normal([5])

grads_and_vars = grad_f_w(x, y)

 

入力xと目標値yを引数に取る損失関数をpython関数でとして定義して、それをtfe.implicit_gradientsに渡します。

 

loss(x,y)はあたかもx,yの関数として書いていますが、当たり前のようにニューラルネットワークにはパラメータwが存在するので、tfe.implicit_gradientsでは暗黙的にニューラルネットワークのパラメータに関する偏微分を実行してくれます(tensorflowではパラメータに対してtf.Variableの型を与えることで区別が付くようになっている。tfe.implicit_gradientsは、計算の中でtf.Variableの型による偏微分のみを持ってきてくれる)。

 

あとは適当なoptimizerを準備して(ここでは普通の勾配降下法)、パラメータ更新のためのメソッドapply_gradientsを実行することで学習を行うことができます。

 

optimizer = tf.train.GradientDescentOptimizer(0.1)
optimizer.apply_gradients(grads_and_vars)

 

 

ちなみにloss関数をpythonで書く時、modelがグローバルに扱われるのが気持ち悪ければ以下のように書くこともできます(数式で考えた時にはmodelのパラメータを引数として明示しているようなもの)。大抵はモデルを先に定義することが多いと思うのですが、まあ好みだと思います。

 

def loss(x, y, model):
    return tf.reduce_sum(tf.square(model(x) - y))

grad_f_w = tfe.implicit_gradients(loss)

x = tf.random_normal([5, 3])
y = tf.random_normal([5])
model = tf.layers.Dense(1)

grads_and_vars = grad_f_w(x, y, model)

 

 

また、lossを逐次出力してログを取りたいということがあると思いますので、微分の計算時に同時にlossも吐いてくれる以下の関数(tfe.implicit_value_and_gradients)を活用することができます。

 

def loss(x, y, model):
    return tf.reduce_sum(tf.square(model(x) - y))

#grad_f_w = tfe.implicit_gradients(loss)
value_and_grad_f_w = tfe.implicit_value_and_gradients(loss)

x = tf.random_normal([5, 3])
y = tf.random_normal([5])
model = tf.layers.Dense(1)

loss_value, grads_and_vars = value_and_grad_f_w(x, y, model)

 

 

最後に

 

ChainerやPyTorchを使ってきた人なら、ここらへんまで抑えておけばトレーニングループを書くことは容易だと思います。ミニバッチ学習のためのイテレータはChainerやPyTorchではそれぞれ提供されていましたが、TensorFlowもきっと提供されていることでしょう(長らく触れていなかったのでよく分かりませんが)。

 

静的グラフを使う場合にはPythonでデータをfeed_dictで流すよりも、最初からイテレータの役割をするグラフを作ってしまってTensorFlowの世界だけで完結させるのが高速化の道だと思いますが、eagerモードだとどうなのだろう。

どっちにしても普通にPythonのインタプリタで学習ループ回す以外に方法が無いので、別にtensorflowが提供するデータローダみたいなのにはこだわらなくてもいいような。

ここらへんもうちょっと勉強してみてからまとめたいと思います。

 

 

 

 

 

 

s0sem0y.hatenablog.com

s0sem0y.hatenablog.com

 

s0sem0y.hatenablog.com