見出し画像

モバイルアプリエンジニアのためのTensorFlow 2.x 入門 (2) - いろんな処理の書き方

モバイルアプリエンジニアの方がTensorFlowに入門するための連載記事です。

今回は、基本的な処理の書き方を説明します。

TensorFlowは歴史とともにいろいろな処理の書き方ができるようになっています。それが混乱ポイントでもあるので、それぞれの書き方を紹介し、違いを説明します。

この入門は、Google Colaboratoryを使います。使い方は第1回にありますので、そちらをご覧下さい。

第1回はこちらです。

前回のコードの振り返りと、解説

最初に、前回書いたコードについて解説します。

import tensorflow as tf

// アノテーション
@tf.function
def myPlusOne(x):
   return x + 1

print(myPlusOne(2))

# 出力結果
# tf.Tensor(3, shape=(), dtype=int32)

myPlusOne関数の前に @tf.function というアノテーションがついていますが、これをつけることでPythonの関数をTensorFlowの計算グラフにコンパイルすることができます。

出力結果は、計算結果の「3」が、tf.Tensorという型に包まれて表示されています。テンソルとは多次元配列のことで、GPUのメモリに置いて並列処理できるため効率的に計算ができます。

テンソルについてはこちらの記事が分かりやすかったです。

計算グラフとはなにか

計算グラフとは、データの演算処理をノードとエッジで表現したグラフです。

例えば、上で定義したmyPlusOne関数は次のようなグラフになります。

画像1

TensorFlowではこのような計算グラフを作ってから処理を実行します。

計算グラフを作る理由は、処理の高速化ができることと、さまざまな環境で実行しやすくなるためです。

Python上で計算グラフを作ったあとはC++の高速なコードで、GPUを用いた並列処理ができます。(高速化)

また、グラフはモバイルアプリやサーバー、組み込みデバイスなどPythonのない環境でも動作できます。(さまざまな環境)

SwiftやKotlinと異なり、TensorFlowを用いたコードは「計算グラフを作ってから実行」という流れになっています。普通のプログラムと同じように読んでしまうと混乱するので、コードを読むときは計算グラフを意識する必要があります。

TensorFlow 1系の書き方

TensorFlowは1系と2系で書き方が異なっています。

書籍やブログでは時期によって1系の書き方をしていることも多いです。その当時は2系は存在しなかったので、バージョンについて何も書いていない場合は1系の場合が多いです。

ということで、1系の書き方も紹介しておきます。

# ※TensorFlow 2.3.0で動作するコードです
#  TensorFlow 1.x(例えば1.4.1)で動かしたい場合は、「tf.compat.v1.」を「tf.」に変更します

import tensorflow as tf

with tf.compat.v1.Session() as sess: // セッション(実行単位)の作成
 x = tf.Variable(2, name="x") // 変数を作成して2を代入
 c1 = tf.constant(1, name="c1") // 定数1を作成

 init_op = tf.compat.v1.global_variables_initializer() // 変数の初期化オペレーション
 add_op = tf.add(x, c1) // xとc1を加算するオペレーションの作成

 sess.run(init_op) // 変数の初期化の実行
 add_result = sess.run(add_op) // 加算オペレーションのの実行

print(add_result) // 結果の表示

# 出力結果
# 3

2系では、冒頭のコードのように普通のPythonのコードとほぼ同じ感覚で書けるのに対し、1系では、計算グラフを作成する命令を使って1つ1つグラフを作り込み、最後はセッション(実行単位)を作って実行する必要がありました。

なお、2系はセッションがなくなっているので、この書き方をするとエラーになります。(「tf.compat.v1」のように宣言することで1系と同じように書くこともできます)

Define-and-RunとDefine-by-Run

TensorFlow 2系では、Define-and-RunとDefine-by-Runの両方が使えるようになっています。これも紛らわしいので説明します。

Define-and-Runはこれまで説明した方法で、計算グラフを構築してからデータを流し込んで計算結果を評価します。

Define-by-Runは、データを流しながら動的に計算グラフを構築し、同時に評価を行います。TensorFlowではEager Executionと呼んでおり、デフォルトで有効になっています。

実際の実行結果で比較してみましょう。

Eager Executionが有効な状態で、1と2を足すコードを実行してみます。

<Eager Executionが有効な場合>

import tensorflow as tf

# tf.constantで2を定義する。これを使わずにc2=2とすると
# 単なるPythonのコードになってしまうためこうしている
c2 = tf.constant(2, name="c2") 

# @tf.functionがなくても、myPlusOneを呼び出す時にtf.constantの # インスタンスを渡すので計算グラフが作成される def myPlusOne(x): return x + 1 print(myPlusOne(c2)) # 出力結果 # tf.Tensor(3, shape=(), dtype=int32)

出力結果は3と出ており、すぐに演算処理が評価されていることが分かります。

<Eager Executionが無効な場合>

tf.compat.v1.disable_eager_execution()

c2 = tf.constant(2, name="c2") 

def myPlusOne(x):
   return x + 1

print(myPlusOne(c2))

# 出力結果
# Tensor("add:0", shape=(), dtype=int32)

出力されている"add:0"とは、足し算のオペレーションのことです。このように演算処理が評価されずに、オペレーション名がそのまま出力されてしまいました。

この状態から計算を実行するには、1系のセッションを使います。

add_op = myPlusOne(c2)

with tf.compat.v1.Session() as sess:
 add_result = sess.run(add_op)

print(add_result)

# 出力結果
# 3

Eager Executionの利点

Eager Executionを使う利点は、演算処理を動的に制御することができたり、ホスト言語のすべての機能をTensorFlowで利用できることです。

例えば、PythonのライブラリであるPandasの機能を使って計算することもできます。

import pandas as pd
import tensorflow as tf

c4 = tf.constant(4, name="c4")

def myPandasAdd(x ,data ,labels, key):
   df = pd.DataFrame(data,index=labels)
   y = int(df.loc[key])
   return x+y

print(myPandasAdd(c4, [1,2,3], ["a","b","c"], "b"))

# 出力結果
# tf.Tensor(6, shape=(), dtype=int32)

上の処理を、Eager Executionを無効にして実行すると、こんなエラーがでてしまいます。

RuntimeError: Attempting to capture an EagerTensor without building a function.

まとめ

・TensorFlowは計算グラフを作ってから計算処理を実行している
・TensorFlow 1系と2系で書き方が異なる。2系の方が直感的に書ける
・Eager Executionを使うと言語のすべての機能が利用できるようになる


最後に、若干宣伝ぽくて恐縮ですが、私はフリーランスエンジニアをしております。このような機械学習をiPhoneデバイス上で動作させるといったお仕事もできますので、お気軽にご相談下さい。

連絡先名:TokyoYoshida
連絡先: yoshidaforpublic@gmail.com

この記事が気に入ったらサポートをしてみませんか?