だんだんPyTorchで見慣れた形のプログラム構成になっていきいます。
このチュートリアルはひとつづつPyTorchの機能を適用してくれるからわかりやすいですね。
今回は、チュートリアル「LEARNING PYTORCH WITH EXAMPLES」の「autograd」の項目から進めていきます。
前回の記事はこちらを参考ください。
こんな人の役に立つかも
・機械学習プログラミングを勉強している人
・PyTorchで機械学習プログラミングを勉強している人
・PyTorchでニューラルネットワークを勉強している人
Autogradについて
前回までの実装では、ニューラルネットワークの順伝播と逆伝播を手動で作成する必要がありました。チュートリアルのような小規模な2層ネットワークでは大したことではありませんが、大規模で複雑なネットワークではすぐに非常に困難になります。
PyTorchの「autograd」を利用すると、ニューラルネットワークの逆方向パスの計算を自動化できます。autogradを使用する場合、ネットワークの順伝播で計算グラフを定義します。そして、このグラフを逆伝播すると、勾配を簡単に計算できます。
計算グラフについては、以下の記事でも取り上げていますので、ご参考ください。
今回のチュートリアルでは、PyTorch Tensorsとautogradを使用して2層ネットワークを実装しています。これで、ネットワークを介して逆方向パスを手動で実装する必要がなくなりました。
autogradを利用すると
・逆伝播での勾配を求めるのに順伝播の計算を追う必要がなくなる。
・そのため、大規模なニューラルネットを構築できる
確かに、手動でいちいちw1とかw2 の勾配を求めるのは大変です。
チュートリアルの実装
この辺りは前回とほぼ同様です。GPU処理でやっていきいます。
# -*- coding: utf-8 -*-
import torch
dtype = torch.float
#device = torch.device("cpu")
device = torch.device("cuda:0") # Uncomment this to run on GPU
print(device)
google colaboでGPU実行の場合、ランタイムの「ハードウェアアクセラレータ」でGPUの設定が必要となります。
# N :バッチサイズ
# D_in :入力次元数
# H :隠れ層の次元数
# D_out:出力次元数
N, D_in, H, D_out = 64, 1000, 100, 10
# ランダムな入力データと出力データの作成
x = torch.randn(N, D_in, device=device, dtype=dtype)
y = torch.randn(N, D_out, device=device, dtype=dtype)
# 重みをランダムに初期化
# 勾配を計算したいテンソルなので、「requires_grad=True」を指定しておく。
w1 = torch.randn(D_in, H, device=device, dtype=dtype, requires_grad=True)
w2 = torch.randn(H, D_out, device=device, dtype=dtype, requires_grad=True)
重みを保持しておく「w1」と「w2」が今回勾配を求めたいテンソルになるので、「 requires_grad=True」を指定しておきます。今回の重要な点です。
#学習率
learning_rate = 1e-6
for t in range(500):
# ①順伝播: 1行で表記
y_pred = x.mm(w1).clamp(min=0).mm(w2)
# 損失
loss = (y_pred - y).pow(2).sum()
if t % 100 == 99:
print(t, loss.item())
# ②「autograd」機能を利用して逆伝播を行い勾配を計算します。
loss.backward()
# ③重みの更新時の計算は、「autograd」で追跡する必要がないので
# 「autograd」を停止するため、「torch.no_grad()」で処理を囲みます。
# 「torch.optim.SGD」を利用しても同じことができます。
with torch.no_grad():
w1 -= learning_rate * w1.grad
w2 -= learning_rate * w2.grad
# 手動で「grad」を初期化します。
w1.grad.zero_()
w2.grad.zero_()
autogradを利用することで、プログラムが大幅に短縮されました。
①の順伝播では、1行で結果までを計算しています。というのも、逆伝播を手動で行わないので、中間の値を保持しておく必要がなくなったからです。「h_relu」という変数で中間の結果を後から利用しないので、一気に「y_pred」までを計算しています。
②の逆伝播では、最終的な損失に対して「backward()」を行うことでその算出に関与しているw1とw2の勾配を計算しています。backward実行後に「w1.grad」と「w2.grad」に勾配が入ります。
③では、勾配降下法により、重みの更新を行っています。前回と違い、PyTorchのAutogradを利用しているため、ここでの計算で計算を追跡しないように「no_grad」という処理で囲む必要があります。また、重みを更新したら、w1とw2の勾配「grad」を明示的に初期化してあげる必要があります。ここの点は行数が少し増えてしまった点になります。
PyTorchのAutogradを利用することで、逆伝播を手動で定義する必要がなくなりました。そのため、より複雑なネットワークも記載することが可能となります。
Autogradの機能をカスタマイズ
「torch.autograd.Function」をサブクラスかして、forward()とbackward()をカスタマイズすることができる様です。チュートリアルのプログラムでは、ReLU関数の機能をforward()に組み込んでいる様です。
# -*- coding: utf-8 -*-
import torch
class MyReLU(torch.autograd.Function):
"""
サブクラス化することで、独自のカスタムautograd関数を実装できます。
torch.autograd.Functionを継承
"""
@staticmethod
def forward(ctx, input):
"""
順伝播では、「ctx」と「input」を受け取ります。
「ctx」は逆伝播のために情報を保持しておくコンテキストオブジェクトです。
"""
ctx.save_for_backward(input)
return input.clamp(min=0)
@staticmethod
def backward(ctx, grad_output):
"""
逆伝播では、出力側からの損失の勾配を含むTensorを受け取ります
そして私たちは入力側からの損失の勾配を計算する必要があります
"""
input, = ctx.saved_tensors
grad_input = grad_output.clone()
grad_input[input < 0] = 0
return grad_input
dtype = torch.float
device = torch.device("cuda:0") # Uncomment this to run on GPU
N, D_in, H, D_out = 64, 1000, 100, 10
x = torch.randn(N, D_in, device=device, dtype=dtype)
y = torch.randn(N, D_out, device=device, dtype=dtype)
w1 = torch.randn(D_in, H, device=device, dtype=dtype, requires_grad=True)
w2 = torch.randn(H, D_out, device=device, dtype=dtype, requires_grad=True)
learning_rate = 1e-6
for t in range(500):
#定義したautogradを利用するために、「apply」を行います。
relu = MyReLU.apply
# 順伝播:定義したforwardの処理を利用
y_pred = relu(x.mm(w1)).mm(w2)
# 損失
loss = (y_pred - y).pow(2).sum()
if t % 100 == 99:
print(t, loss.item())
# 逆伝播
loss.backward()
# 重みの更新:勾配降下法
with torch.no_grad():
w1 -= learning_rate * w1.grad
w2 -= learning_rate * w2.grad
w1.grad.zero_()
w2.grad.zero_()
backwardの処理については理解しきることができていません^^;
GitHubに今回のプログラムをアップロードしました。
続きの記事はこちらです。