こしあん
2019-10-17

TensorFlow2.0のGradientTapeを複数使う場合のサンプル

Pocket
LINEで送る
Delicious にシェア

1.4k{icon} {views}



TF2.0系で少し複雑なモデルを訓練するときに、GradientTapeを複数使うことがあります。例として、微分を取りたい場所が2箇所あるケースや、2階微分を取りたいケースを挙げます。場合によって微妙に書き方が違うので注意が必要です。

微分を取りたい場所が2箇所あるケース

簡単な例にします。次の関数の微分を考えます。

$$y=\log x^2 $$

合成関数の微分の公式の要領で、$y$の$x$微分と、$x^2$の$x$微分を計算してみます。

import tensorflow as tf
import numpy as np

def get_derivative(inputs):
    x = tf.Variable(np.array([inputs, inputs], np.float32)) # gadientを取るためにVariableとする
    with tf.GradientTape() as tape1, tf.GradientTape() as tape2:
        y1 = x ** 2
        y2 = tf.math.log(y1)
    print("dy1/dx =", tape1.gradient(y1, x))
    print("dy2/dx =", tape2.gradient(y2, x))

def main():
    for i in range(1, 4):
        print("i = ", i)
        get_derivative(i)

if __name__ == "__main__":
    main()

xの値を1~3と変化させます。ちなみに数学的に計算すると、

$$\frac{dy}{dx}=\frac{2}{x}, \frac{d}{dx}x^2 = 2x $$

となります。結果は数学的に計算した通りになりました。

i =  1
dy1/dx = tf.Tensor([2. 2.], shape=(2,), dtype=float32)
dy2/dx = tf.Tensor([2. 2.], shape=(2,), dtype=float32)
i =  2
dy1/dx = tf.Tensor([4. 4.], shape=(2,), dtype=float32)
dy2/dx = tf.Tensor([1. 1.], shape=(2,), dtype=float32)
i =  3
dy1/dx = tf.Tensor([6. 6.], shape=(2,), dtype=float32)
dy2/dx = tf.Tensor([0.6666667 0.6666667], shape=(2,), dtype=float32)

ポイントはここです。

    with tf.GradientTape() as tape1, tf.GradientTape() as tape2:

このように1つのwith句の中に2つのGradientTapeをおくと、2箇所の微分を取ることができます。ちなみに、tape1をtape2にしたり、tape2をtape1にしたりと、同一のGradientTapeに対して2回以上tape.gradientを呼び出すと次のようなエラーになります。

RuntimeError: GradientTape.gradient can only be called once on non-persistent tapes.

つまりこの記事でやっているのは、「GradientTape1つに対して2回以上偏微分計算できない」という縛りを回避するために、GradientTapeを2つ定義しているということです。

ちなみに、この例は次のようにネストさせて書いてもOKです。

def get_derivative(inputs):
    x = tf.Variable(np.array([inputs, inputs], np.float32)) # gadientを取るためにVariableとする
    with tf.GradientTape() as tape1:
        with tf.GradientTape() as tape2:
            y1 = x ** 2
        y2 = tf.math.log(y1)
    print("dy1/dx =", tape2.gradient(y1, x))
    print("dy2/dx =", tape1.gradient(y2, x))

2階以上の偏微分を取る

ディープラーニングで使うケースとしては少ないと思いますが、GradientTapeをネストさせることで2階以上の偏微分が計算できます。先程の2箇所の微分計算とは少し異なる書き方をします。

import tensorflow as tf
import numpy as np

def get_higher_derivative(inputs):
    x = tf.Variable(np.array([inputs, inputs], np.float32))
    with tf.GradientTape() as tape1:
        with tf.GradientTape() as tape2:
            y = tf.math.log(x ** 2)
        dy_dx = tape2.gradient(y, x)
    d2y_dx2 = tape1.gradient(dy_dx, x)

    print("dy/dx =", dy_dx)
    print("d2y/dx2 =", d2y_dx2)

def main():
    for i in range(1, 4):
        print("i = ", i)
        get_higher_derivative(i)

if __name__ == "__main__":
    main()

ちなみに、

$$y=\log x^2 $$

この関数の2階微分までを数学的に解くと、

$$\frac{dy}{dx}=\frac{2}{x}, \frac{d^2y}{dx^2}=-\frac{2}{x^2}$$

となります。これとあっているか確認してみます。

i =  1
dy/dx = tf.Tensor([2. 2.], shape=(2,), dtype=float32)
d2y/dx2 = tf.Tensor([-2. -2.], shape=(2,), dtype=float32)
i =  2
dy/dx = tf.Tensor([1. 1.], shape=(2,), dtype=float32)
d2y/dx2 = tf.Tensor([-0.5 -0.5], shape=(2,), dtype=float32)
i =  3
dy/dx = tf.Tensor([0.6666667 0.6666667], shape=(2,), dtype=float32)
d2y/dx2 = tf.Tensor([-0.22222222 -0.22222222], shape=(2,), dtype=float32)

確かにあっているようです。GradientTapeの部分を再掲します。

    with tf.GradientTape() as tape1:
        with tf.GradientTape() as tape2:
            y = tf.math.log(x ** 2)
        dy_dx = tape2.gradient(y, x)
    d2y_dx2 = tape1.gradient(dy_dx, x)

最初に複数のTapeをネストさせて宣言するのがポイントです。yの計算はtape1, tape2両方記録しています。tape2側がgradientを計算した部分は、tape2のインデントの外側なので、この偏微分を計算するというグラフはtape1だけ記録されます。したがって、tape1でもう一回gradientを呼べば2階微分が計算できる……という仕組み。

ちなみに最後の2行のtape2とtape1を入れ替えると、

i =  1
dy/dx = tf.Tensor([2. 2.], shape=(2,), dtype=float32)
d2y/dx2 = None
i =  2
dy/dx = tf.Tensor([1. 1.], shape=(2,), dtype=float32)
d2y/dx2 = None
i =  3
dy/dx = tf.Tensor([0.6666667 0.6666667], shape=(2,), dtype=float32)
d2y/dx2 = None

となり、2階部分が正常に計算できません。これはスコープの外側になっているためです。

まとめ

少し複雑なモデルではGradientTapeを複数個作ることがありますが、ケースによって適切な書き方が微妙に違うので確かめながらやりましょうということでした。あくまで想像ですが、インデント分けて書いたほうが無駄な微分計算をしなくて厳密……なのかもしれません。



Shikoan's ML Blogの中の人が運営しているサークル「じゅ~しぃ~すくりぷと」の本のご案内

技術書コーナー

【新刊】インフィニティNumPy――配列の初期化から、ゲームの戦闘、静止画や動画作成までの221問

「本当の実装力を身につける」ための221本ノック――
機械学習(ML)で避けて通れない数値計算ライブラリ・NumPyを、自在に活用できるようになろう。「できる」ための体系的な理解を目指します。基礎から丁寧に解説し、ディープラーニング(DL)の難しいモデルで遭遇する、NumPyの黒魔術もカバー。初心者から経験者・上級者まで楽しめる一冊です。問題を解き終わったとき、MLやDLなどの発展分野にスムーズに入っていけるでしょう。

本書の大きな特徴として、Pythonの本でありがちな「NumPyとML・DLの結合を外した」点があります。NumPyを理解するのに、MLまで理解するのは負担が大きいです。本書ではあえてこれらの内容を書いていません。行列やテンソルの理解に役立つ「従来の画像処理」をNumPyベースで深く解説・実装していきます。

しかし、問題の多くは、DLの実装で頻出の関数・処理を重点的に取り上げています。経験者なら思わず「あー」となるでしょう。関数丸暗記では自分で実装できません。「覚える関数は最小限、できる内容は無限大」の世界をぜひ体験してみてください。画像編集ソフトの処理をNumPyベースで実装する楽しさがわかるでしょう。※紙の本は電子版の特典つき

モザイク除去から学ぶ 最先端のディープラーニング

「誰もが夢見るモザイク除去」を起点として、機械学習・ディープラーニングの基本をはじめ、GAN(敵対的生成ネットワーク)の基本や発展型、ICCV, CVPR, ECCVといった国際学会の最新論文をカバーしていく本です。
ディープラーニングの研究は発展が目覚ましく、特にGANの発展型は市販の本でほとんどカバーされていない内容です。英語の原著論文を著者がコードに落とし込み、実装を踏まえながら丁寧に解説していきます。
また、本コードは全てTensorFlow2.0(Keras)に対応し、Googleの開発した新しい機械学習向け計算デバイス・TPU(Tensor Processing Unit)をフル活用しています。Google Colaboratoryを用いた環境構築不要の演習問題もあるため、読者自ら手を動かしながら理解を深めていくことができます。

AI、機械学習、ディープラーニングの最新事情、奥深いGANの世界を知りたい方にとってぜひ手にとっていただきたい一冊となっています。持ち運びに便利な電子書籍のDLコードが付属しています。

「おもしろ同人誌バザールオンライン」で紹介されました!(14:03~) https://youtu.be/gaXkTj7T79Y?t=843

まとめURL:https://github.com/koshian2/MosaicDeeplearningBook
A4 全195ページ、カラー12ページ / 2020年3月発行

Shikoan's ML Blog -Vol.1/2-

累計100万PV超の人気ブログが待望の電子化! このブログが電子書籍になって読みやすくなりました!

・1章完結のオムニバス形式
・機械学習の基本からマニアックなネタまで
・どこから読んでもOK
・何巻から読んでもOK

・短いものは2ページ、長いものは20ページ超のものも…
・通勤・通学の短い時間でもすぐ読める!
・読むのに便利な「しおり」機能つき

・全巻はA5サイズでたっぷりの「200ページオーバー」
・1冊にたっぷり30本収録。1本あたり18.3円の圧倒的コストパフォーマンス!
・文庫本感覚でお楽しみください

北海道の駅巡りコーナー

日高本線 車なし全駅巡り

ローカル線や秘境駅、マニアックな駅に興味のある方におすすめ! 2021年に大半区間が廃線になる、北海道の日高本線の全区間・全29駅(苫小牧~様似)を記録した本です。マイカーを使わずに、公共交通機関(バス)と徒歩のみで全駅訪問を行いました。日高本線が延伸する計画のあった、襟裳岬まで様似から足を伸ばしています。代行バスと路線バスの織り成す極限の時刻表ゲームと、絶海の太平洋と馬に囲まれた日高路、日高の隠れたグルメを是非たっぷり堪能してください。A4・フルカラー・192ページのたっぷりのボリュームで、あなたも旅行気分を漫喫できること待ったなし!

見どころ:日高本線被災区間(大狩部、慶能舞川橋梁、清畠~豊郷) / 牧場に囲まれた絵笛駅 / 窓口のあっただるま駅・荻伏駅 / 汐見の戦争遺跡のトーチカ / 新冠温泉、三石温泉 / 襟裳岬

A4 全192ページフルカラー / 2020年11月発行


Pocket
Delicious にシェア

Add a Comment

メールアドレスが公開されることはありません。 * が付いている欄は必須項目です