
1. 目的
2. 前準備
3. PyTorchのインポート
4. クラスの定義
5. データタイプとデバイスの選択
6. 使用するデータ
7. 重み付けの初期化
8. 学習
9. データの入力 (forward)
10. 損失の計算
11. 重み(Weight)の勾配計算
12. 重みの更新
13. 実行
13.1. 4_autograd_func.py
1. 目的
PyTorchのチュートリアルPyTorch: Defining New autograd Functionsを参考にPyTorchテンソル(tensor)と自動微分(autograd)を使って、損失(loss)や重み(weight)の計算をする。
前回の、【PyTorch】サンプル③ 〜TENSORS AND AUTOGRAD(テンソルと自動微分)〜では、自動微分の基本的な扱いをご紹介しました。
今回は、前回使用した自動微分のコードを関数化してみましょう。
2. 前準備
PyTorchのインストールはこちらから。
初めて、Google Colaboratoryを使いたい方は、こちらをご覧ください。
3. PyTorchのインポート
4. クラスの定義
自動微分のコード部分を以下のようなMyReLU
というクラスで定義します。
01 02 03 04 05 06 07 08 09 10 11 12 13 | class MyReLU(torch.autograd.Function):
@staticmethod
def forward(ctx, input ):
ctx.save_for_backward( input )
return input .clamp( min = 0 )
@staticmethod
def backward(ctx, grad_output):
input , = ctx.saved_tensors
grad_input = grad_output.clone()
grad_input[ input < 0 ] = 0
return grad_input
|
ここでは、torch.autograd.Function
を継承したサブクラスでMyReLU
を定義します。
1 | class MyReLU(torch.autograd.Function):
|
こちらが、パーセプトロンにデータをインプット(forward)する部分です。
@staticmethod
はスタティックメソッドといって、そのクラスでしか使用しない関数を定義する時に使います。
また、スタティックメソッドの特徴として、クラスでよくあるself
を第一引数として取りません。
ctx
は、逆伝播(backward)計算の際に用いる隠しオブジェクトです。
ctx.save_for_backward
メソッドを使うことで逆伝播計算に用いるオブジェクトのキャッシュを取って置くことができる。
input.clamp(min=0)
で、入力されたinput
の要素の中で0以下は0、0以上はそのままの要素の値として出力を返す。
1 2 3 4 | @staticmethod
def forward(ctx, input ):
ctx.save_for_backward( input )
return input .clamp( min = 0 )
|
この部分が、逆伝播計算の部分。
ctx.saved_tensors
で、パーセプトロンに入力された値input
を取り出します。
パーセプトロンからの出力勾配をgrad_output.clone()
でコピーする。
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 | @staticmethod
def backward(ctx, grad_output):
input , = ctx.saved_tensors
grad_input = grad_output.clone()
grad_input[ input < 0 ] = 0
return grad_input
`
このように、PyTorchにおける自動微分関数の定義では、`forward`と`backward`をクラスのメソッドとして定義します。
これから作成するテンソルのデータ型`dtype`を` float `にします。
テンソル計算をするデバイスは、`torch.device`で指定することができます。
今回は、CPUを使用することにします。
[python]
dtype = torch. float
device = torch.device( "cpu" )
|
GPUを使う方は、device
の部分を以下のコードに変えて下さい。
1 | device = torch.device( "cuda:0" )
|
6. 使用するデータ
バッチサイズNを64、入力の次元D_inを1000、隠れ層の次元Hを100、出力の次元D_outを10とします。
1 2 3 | N, D_in, H, D_out = 64 , 1000 , 100 , 10
|
入力(x)と予測したい(y)を乱数で定義します。
PyTorchテンソルを定義する場合には、どのデバイスでdevice
、どのデータ型dtype
かを指定することができます。
1 2 3 | x = torch.randn(N, D_in, device = device, dtype = dtype)
y = torch.randn(N, D_out, device = device, dtype = dtype)
|
7. 重み付けの初期化
乱数を使って重みを初期化します。
自動微分をしたい場合には、初期化する時点でrequires_grad
のフラグをTrue
にしておきます。デフォルトはrequires_grad=False
です。
1 2 3 | 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 )
|
8. 学習
学習率を1e-6
として、学習回数を500回とします。
1 2 | learning_rate = 1e - 6
for t in range ( 500 ):
|
9. データの入力 (forward)
データをパーセプトロンに入力して、パーセプトロンの予測値y_pred
を計算します。
以前の自動微分のサンプルでは、
1 | y_pred = x.mm(w1).clamp( min = 0 ).mm(w2)
|
このように記述しましたが、今回は自動微分の関数をMyReLU
としてに定義してあるので、上のコードをMyReLU
を用いて以下のように書き換えます。
1 2 3 | relu = MyReLU. apply
y_pred = relu(x.mm(w1)).mm(w2)
|
10. 損失の計算
パーセプトロンが予測した値(y_pred)と答え(y)との間の二乗誤差を計算しこれを損失(loss)とします。
損失の値を、tensor配列からスカラー値で取得したい場合には、item()
メソッドを用います。
各学習回数ごとに、学習回数(t)と二乗誤差(loss)を表示します。
今回は、結果を見やすくするためにif t % 100 == 99:
の部分で、学習回数100回ごとに結果を出力します。
やっていることは、学習回数(t)を100で割ったときのあまりが99であるときにprint(t, loss)
を実行です。
1 2 3 4 | loss = (y_pred - y). pow ( 2 ). sum ()
if t % 100 = = 99 :
print (t, loss.item())
|
11. 重み(Weight)の勾配計算
これより先は、パーセプトロンが予測した値(y_pred)と答え(y)を見比べて、正しく答え(y)を予測できるようにパーセプトロンのパラメータを更新していきます。
12. 重みの更新
計算した勾配(grad_w1, grad_w2)をもとに、重み(w1, w2)を更新します。自動微分を用いた場合のパラメータの更新は、torch.no_grad()
で対象のパラメータをくくることで計算できます。
確率勾配降下法(SGD: stochastic gradient descent)は、重みを更新する上でよく使われる最適化アルゴリズムで、以下の式で表されます。
1 | weight = weight - learning_rate gradient
|
SGDを用いてパラメータを更新するには、このように記述します。
一度更新した、パラメータはgrad.zero()
でゼロに初期化します。
1 2 3 4 5 6 7 | with torch.no_grad():
w1 - = learning_rate w1.grad
w2 - = learning_rate w2.grad
w1.grad.zero_()
w2.grad.zero_()
|
13. 実行
以下のコードを4_autograd_func.py
として保存します。
13.1. 4_autograd_func.py
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | import torch
dtype = torch. float
device = torch.device( "cpu" )
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 ):
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())
loss.backward()
with torch.no_grad():
w1 - = learning_rate w1.grad
w2 - = learning_rate w2.grad
w1.grad.zero_()
w2.grad.zero_()
|
保存ができたら実行しましょう。
左の数字が学習回数、右の数値がパーセプトロンの推定値と実際の答えと二乗誤差です。
学習を重ねるごとに、二乗誤差が小さくなることがわかります。
1 2 3 4 5 6 | $ python3 4_autograd_func .py
99 763.6893310546875
199 9.174347877502441
299 0.22351056337356567
399 0.007267479319125414
499 0.0004901751526631415
|