Dive into Deep Learning
Dive into Deep Learning

自動微分

機械学習では、私達はモデルを学習させ、次々と更新していきます。モデルは、データを見れば見るほど、良くなっていきます。通常、良くなるということは、モデルがどの程度悪いかをスコアとして表すロス関数を最小化することを意味します。ニューラルネットワークとともに、パラメータに関して微分可能なロス関数を選択します。平たく言えば、モデルの各パラメータに対して、ロスをどの程度増加または減少させるかを決めることができるのです。微分をとるという計算自体は率直で、基本的な計算しか必要しませんが、複雑なモデルに対して人手で行うのは非常に困難で間違いやすいものです。

autogradというパッケージは、自動で微分を計算することによって、この作業を加速させました。多くの他のライブラリが、自動微分を行うためにシンボリックなグラフのコンパイルを必要とするのに対し、autogradは通常のコードを書くだけで微分をすることができます。モデルにデータを渡すときはいつでも、グラフがautogradによってその都度作成され、どのデータにどの演算が実行されて出力を得られるのかが追跡されます。このグラフによって、autogradは実行命令を受けると、勾配を逆伝搬します。 逆伝搬は単純に計算グラフを追跡して、各パラメータの偏微分を計算することを意味します。 微分のような数学を見慣れていなければ、Appendixの“Mathematical Basics”を参照してください。

In [1]:
from mxnet import autograd, nd

シンプルな例

単純な例として、\(y = 2\mathbf{x}^{\top}\mathbf{x}\)を列ベクトル \(\mathbf{x}\)に関して微分してみましょう。まず、変数xを作成して、初期値を与えます。

In [2]:
x = nd.arange(4).reshape((4, 1))
print(x)

[[0.]
 [1.]
 [2.]
 [3.]]
<NDArray 4x1 @cpu(0)>

xに関するyの勾配を計算したら、それを保存するための場所を用意しましょう。NDArrayに対して、attach_grad()のメソッドを利用すると、勾配を保存することができます。

In [3]:
x.attach_grad()

ここでyを計算するためにMXNetによって計算グラフを作成します。それは、記録デバイスを起動して、各変数を生成する正確なパスを取り込むようなものです。

計算グラフの作成にはそれなりの計算を必要とします。そこで陽に計算グラフを作成するよう指示したときだけ、MXNetは計算グラフを作成します。with autograd.record():のブロックの中にコードを記述することによって行うことができます。

In [4]:
with autograd.record():
    y = 2 * nd.dot(x.T, x)
print(y)

[[28.]]
<NDArray 1x1 @cpu(0)>

xのshapeは(4, 1)なのでyはスカラーになります。次に、backwardの関数を呼ぶことで勾配を自動で取得します。yがスカラーでなければ、MXNetはデフォルトでyの要素の総和をとって新しい変数とし、その変数に対するxの勾配を計算します。

In [5]:
y.backward()

関数\(y = 2\mathbf{x}^{\top}\mathbf{x}\)\(\mathbf{x}\)に対する勾配は\(4\mathbf{x}\)です。実際に計算される勾配が正しいか確かめてみましょう。

In [6]:
print((x.grad - 4 * x).norm().asscalar() == 0)
print(x.grad)
True

[[ 0.]
 [ 4.]
 [ 8.]
 [12.]]
<NDArray 4x1 @cpu(0)>

学習モードと推論モード

上記で確認したように、recordの関数を呼ぶと、MXNetは勾配を記録して計算します。また、 autogradはデフォルトで推論モードから学習モードへと実行モードを切り替えます。このことは、is_training関数を実行すると確認することができます。

In [7]:
print(autograd.is_training())
with autograd.record():
    print(autograd.is_training())
False
True

同じモデルであっても、学習と推論の各モードで違った動きをする場合があります(DropoutやBatch normalizationという技術を利用したときなど)。他にも、いくつかのモデルは勾配をより容易に計算するために、補助的な変数を追加で保存する場合もあります。以降の章では、これらの違いについて詳細を説明いたします。この章では、それらについて心配する必要はありません。

Pythonの制御フローに対する勾配を計算する

自動微分のメリットとして、たとえ関数がPythonの制御フロー(条件分岐やループ)を含んでいたとしても、その変数の微分を得られるかもしれないという点があります。次のような問題を考えてみましょう。ループ (whileループ) のイテレーション数や条件分岐 (if文) の実行が、ある入力bに依存しているような問題です。

In [8]:
def f(a):
    b = a * 2
    while b.norm().asscalar() < 1000:
        b = b * 2
    if b.sum().asscalar() > 0:
        c = b
    else:
        c = 100 * b
    return c

whileループのイテレーション数や条件分岐 (if then else)の実行はaの値に依存しています。勾配を計算するために、その計算をrecord(保存)する必要があり、また``backward``関数を実行して勾配を計算する必要があります。

In [9]:
a = nd.random.normal(shape=1)
a.attach_grad()
with autograd.record():
    d = f(a)
d.backward()

上で定義された関数fを解析してみましょう。関数fは入力aに対する区分線形関数であることは確認できると思います。言い換えれば、どのようなaに対しても、ある区間においてf(a) = g * aとなる値が存在します。従って、d / aという計算を行うことで、その勾配が正しいかどうかを検証することができます。

In [10]:
print(a.grad == (d / a))

[1.]
<NDArray 1 @cpu(0)>

Head gradients と chain rule

注意: この部分はわかりにくく、以降の節を理解するために必ずしも必要というわけではありません。とはいえ、ゼロから新しいレイヤーを作成したい場合には必要になってきます。最初に読む段階ではスキップしても構いません。

例えばxの関数であるyについてxに関して微分したい場合、backwardの関数つまりy.backward()で呼ぶでしょう。数学者は\(\frac{dy(x)}{dx}\)と書きます。また、zyの関数で、yxの関数であるときに、xに関するzの勾配を求めたい場合もあるでしょう。そうすると、\(\frac{dy(x)}{dx}\)を求めることになります。ここで、以下のchain ruleを思い出しましょう。

\[\frac{d}{dx} z(y(x)) = \frac{dz(y)}{dy} \frac{dy(x)}{dx}.\]

大きな関数zの一部にyが含まれていて、\(\frac{dz}{dx}\)の値をもつx.gradを知りたいとき、head gradient(先頭の勾配)である:math:frac{dz}{dy}backward()への入力として渡します。デフォルトの引数はnd.ones_like(y)です。詳細に関してはWikipediaを見てください。

In [11]:
with autograd.record():
    y = x * 2
    z = y * x

head_gradient = nd.array([10, 1., .1, .01])
z.backward(head_gradient)
print(x.grad)

[[0.  ]
 [4.  ]
 [0.8 ]
 [0.12]]
<NDArray 4x1 @cpu(0)>

まとめ

  • MXNetでは微分の処理を自動化するautogradパッケージを提供してます。
  • MXNetのautogradパッケージでは、一般的な手続き型のプログラムを微分することも可能です。
  • MXNetの実行モードには学習モードと推論モードがあります。autograd.is_training()を呼ぶと、実行モードを知ることができます。

練習

  1. aに関するdの微分を計算する制御フローを例としてとりあげましたが、aをランダムなベクトルや行列に変更するとどうなるでしょうか。このとき、f(a)の計算結果はスカラーではなくなってしまいます。どういった結果になるでしょうか。どのように解析すれば良いでしょうか。
  2. その制御フローの勾配を計算する例を変えてみましょう。実行して結果を解析してみましょう。
  3. eBayやComputational advertisingのようなセカンド・プライスオークションにおいては、せりに買った人は二番目に高い入札金額を支払います。autogradを使って、せりに買った人の入札金額に関する最終的な価格の勾配を計算してみましょう。その結果から、セカンド・プライスオークションのメカニズムについてわかることがありますか? もしセカンド・プライスオークションについてより深く知りたいと思うのであれば、Edelman, Ostrovski and Schwartz, 2005の論文を参照してください。
  4. なぜ2階微分は、1階微分よりもずっと多くの計算を必要とするのでしょうか。
  5. Chain rule と head gradientの関係を導きましょう。もし詰まってしまったら、“Chain rule” article on Wikipediaを見てみてください。
  6. \(f(x) = \sin(x)\)を考えます。そして、\(f(x)\)\(\frac{df(x)}{dx}\)をグラフ化してください。ただし、\(\frac{df(x)}{dx}\)については、数式の計算を使わない、つまり \(f'(x) = \cos(x)\)を使わずにグラフ化しましょう。

議論のためのQRコード

image0