アカデミック

【超初心者向け】PyTorchのチュートリアルを読み解く。<その2>

ディープラーニングを実装したい!
便利な道具があるらしいじゃんか??

 

本シリーズでは,ディープラーニングを実装する際に強力な手助けをしてくれる「PyTorch」についてです。公式チュートリアルを,初心者に向けてかみ砕きながら翻訳していこうと思います。(公式ページはこちらより

今回はNo.2で,「自動微分」編です。その他の記事は,こちらの「PyTorchの公式チュートリアルを初心者向けに読み解く」をご覧ください。

コーディングに関して未熟な部分がたくさんあると思いますので,もし何かお気づきの方は教えていただけると幸いです。また,誤りについてもご指摘していただけると非常に助かります。

自動微分

深層学習では計算に各変数の微分を利用します。ですので,深層学習のパッケージで自動微分ができると重宝します。

Pytorchの特徴として,「define-by-run」が挙げられます。define-by-runとは,計算をしていく中でネットワークを構成していく方法のことを指します。対照的な概念として「define-and-run」が挙げられ,TensorflowやKerasが該当します。

メリットとしては,エポック(繰り返し単位)ごとに計算グラフを柔軟に変更することができるうえ,データ構造に対応してモデルを変更させることが可能になります。一方で,大量にメモリを食う危険性があるのと,最適化を行うのが難しという欠点も挙げられます。

「torch.Tensor」でテンソルを生成するときに「.requires_grad」をTrueにしておけば,微分の結果(勾配)を保持することができます。ですので,「.backward()」をすれば簡単に誤差逆伝播を計算することができます。

実際に具体例を見ていきましょう。

 

Torchのインポート

import torch

 

requires_grad=Trueを指定したテンソルの生成

x = torch.ones(2, 2, requires_grad=True)
print(x)
out:
tensor([[1., 1.],
        [1., 1.]], requires_grad=True)

「requires_grad=True」を指定することで勾配を保持します。

 

演算の実行

y = x + 2
print(y)
out:
tensor([[3., 3.],
        [3., 3.]], grad_fn=<AddBackward0>)
print(y.grad_fn)
out:<AddBackward0 object at 0x7f059d478940>

上で「requires_grad=True」を設定したので,テンソルの計算結果に「grad_fn」が現れました。「grad_fn」はFunctionパッケージを参照しており,演算が足し算ですので「AddBackward」と表示されています。さらに,演算を追加していきます。

z = y * y * 3
out = z.mean()

print(z, out)
out:
tensor([[27., 27.],
        [27., 27.]], grad_fn=<MulBackward0>) tensor(27., grad_fn=<MeanBackward0>)

今回はzは掛け算,outは平均を利用して定義していますので,それぞれ「MulBackward0」「MeanBackward0」となっています。

ちなみに,「requires_grad=True」は以下のようにして後から変更することもできます。

a = torch.randn(2, 2)
a = ((a * 3) / (a - 1))
print(a.requires_grad)
a.requires_grad_(True)
print(a.requires_grad)
b = (a * a).sum()
print(b.grad_fn)
out:
False
True
<SumBackward0 object at 0x7f055113d908>

 

バックプロップの実行

out.backward()
print(x.grad)
out:
tensor([[4.5000, 4.5000],
        [4.5000, 4.5000]])

out.backwardで$\frac{\partial \rm{out}}{\partial \rm{x}}$を求める計算を実行しています。$\frac{\partial out}{\partial x}$は「x.grad」で呼び出すことができます。ちなみに,4.5という数字が出てきましたが,これは正しい数字ですね。

\begin{eqnarray}
y &=& x + 2\\
z &=& 3y^2\\
\rm{out} &=& \frac{1}{4} \sum_i z_i
\end{eqnarray}

\begin{eqnarray}
\frac{\partial \rm{out}}{\partial \rm{x_i}} &=& \frac{3}{2} (x_i + 2)
\end{eqnarray}

となるので,$x_i=1$を代入すると$\frac{9}{2}=4.5$となります。

 

自動微分を利用した具体例

x = torch.randn(3, requires_grad=True)

y = x * 2
while y.data.norm() < 1000:
    y = y * 2

print(y)
out:
tensor([ -815.1063, -1030.5084,   837.1931], grad_fn=<MulBackward0>)

今回は,xを乱数で生成して,xを2倍したものをyと定義しています。そして,yのユークリッドノルムが1000未満となるようにさらにyを2倍する操作を繰り返していきます。1000を超えた時点で操作は打ち切りです。

こんな演算にも,自動微分は適用できます。

gradients = torch.tensor([0.1, 1.0, 0.0001], dtype=torch.float)
y.backward(gradients)

print(x.grad)
tensor([1.0240e+02, 1.0240e+03, 1.0240e-01])

gradientsというのは,勾配の重みパラメータに対応します。つまり,[0.1, 1.0, 0.0001]という重み付けでバックプロップしていきますよという意味です。

 

記憶の停止

print(x.requires_grad)
print((x ** 2).requires_grad)

with torch.no_grad():
	print((x ** 2).requires_grad)
True
True
False

「torch.no_grad()」ブロック内では「requires_grad」はFalseになります。つまり,勾配の記憶は停止しています。

COMMENT

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