本シリーズでは,ディープラーニングを実装する際に強力な手助けをしてくれる「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になります。つまり,勾配の記憶は停止しています。