【初心者向け】基礎&実践プログラミング

初心者がつまづきやすいところ、最短で実力が身につく方法をお伝えします。

【PyTorch】サンプル④ 〜Defining New autograd Functions(自動微分関数の定義)〜

f:id:AIProgrammer:20200413020244p:plain

目的

PyTorchのチュートリアルPyTorch: Defining New autograd Functionsを参考にPyTorchテンソル(tensor)と自動微分(autograd)を使って、損失(loss)や重み(weight)の計算をする。

前回の、【PyTorch】サンプル③ 〜TENSORS AND AUTOGRAD(テンソルと自動微分)〜では、自動微分の基本的な扱いをご紹介しました。

今回は、前回使用した自動微分のコードを関数化してみましょう。

前準備

PyTorchのインストールはこちらから。

初めて、Google Colaboratoryを使いたい方は、こちらをご覧ください。

コマンドラインの「>>>」の行がPythonで実行するコマンドです。 それ以外の行は、コマンドの実行結果です。

>>> print("PyTorch")    # コマンドラインに打ち込む
PyTorch    # 出力結果

関連記事

チュートリアル

サンプル

PyTorchのインポート

import torch

クラスの定義

自動微分のコード部分を以下のようなMyReLUというクラスで定義します。

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を定義します。

class MyReLU(torch.autograd.Function):

こちらが、パーセプトロンにデータをインプット(forward)する部分です。

@staticmethodはスタティックメソッドといって、そのクラスでしか使用しない関数を定義する時に使います。

また、スタティックメソッドの特徴として、クラスでよくあるselfを第一引数として取りません。

ctxは、逆伝播(backward)計算の際に用いる隠しオブジェクトです。

ctx.save_for_backwardメソッドを使うことで逆伝播計算に用いるオブジェクトのキャッシュを取って置くことができる。

input.clamp(min=0)で、入力されたinputの要素の中で0以下は0、0以上はそのままの要素の値として出力を返す。

    @staticmethod
    def forward(ctx, input):
        ctx.save_for_backward(input)
        return input.clamp(min=0)

この部分が、逆伝播計算の部分。

ctx.saved_tensorsで、パーセプトロンに入力された値inputを取り出します。

パーセプトロンからの出力勾配をgrad_output.clone()でコピーする。

    @staticmethod
    def backward(ctx, grad_output):
        input, = ctx.saved_tensors
        grad_input = grad_output.clone()
        grad_input[input < 0] = 0
        return grad_input

このように、PyTorchにおける自動微分関数の定義では、forwardbackwardをクラスのメソッドとして定義します。

データタイプとデバイスの選択

これから作成するテンソルのデータ型dtypefloatにします。

テンソル計算をするデバイスは、torch.deviceで指定することができます。 今回は、CPUを使用することにします。

dtype = torch.float
device = torch.device("cpu")

GPUを使う方は、deviceの部分を以下のコードに変えて下さい。

device = torch.device("cuda:0") # Uncomment this to run on GPU

使用するデータ

バッチサイズNを64、入力の次元D_inを1000、隠れ層の次元Hを100、出力の次元D_outを10とします。

# N is batch size; D_in is input dimension;
# H is hidden dimension; D_out is output dimension.
N, D_in, H, D_out = 64, 1000, 100, 10

入力(x)と予測したい(y)を乱数で定義します。

PyTorchテンソルを定義する場合には、どのデバイスでdevice、どのデータ型dtypeかを指定することができます。

# Create random input and output data
x = torch.randn(N, D_in, device=device, dtype=dtype)
y = torch.randn(N, D_out, device=device, dtype=dtype)

重み付けの初期化

乱数を使って重みを初期化します。

自動微分をしたい場合には、初期化する時点でrequires_gradのフラグをTrueにしておきます。デフォルトはrequires_grad=Falseです。

# Randomly initialize weights
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)

学習

学習率を1e-6として、学習回数を500回とします。

learning_rate = 1e-6
for t in range(500):

データの入力 (forward)

データをパーセプトロンに入力して、パーセプトロンの予測値y_predを計算します。

以前の自動微分のサンプルでは、

    y_pred = x.mm(w1).clamp(min=0).mm(w2)

このように記述しましたが、今回は自動微分の関数をMyReLUとしてに定義してあるので、上のコードをMyReLUを用いて以下のように書き換えます。

    # Forward pass: compute predicted y
    relu = MyReLU.apply
    y_pred = relu(x.mm(w1)).mm(w2)

損失の計算

パーセプトロンが予測した値(y_pred)と答え(y)との間の二乗誤差を計算しこれを損失(loss)とします。

損失の値を、tensor配列からスカラー値で取得したい場合には、item()メソッドを用います。

各学習回数ごとに、学習回数(t)と二乗誤差(loss)を表示します。

今回は、結果を見やすくするためにif t % 100 == 99:の部分で、学習回数100回ごとに結果を出力します。 やっていることは、学習回数(t)を100で割ったときのあまりが99であるときにprint(t, loss)を実行です。

    # Compute and print loss
    loss = (y_pred - y).pow(2).sum()
    if t % 100 == 99:
        print(t, loss.item())

重み(Weight)の勾配計算

これより先は、パーセプトロンが予測した値(y_pred)と答え(y)を見比べて、正しく答え(y)を予測できるようにパーセプトロンのパラメータを更新していきます。

    loss.backward()

重みの更新

計算した勾配(grad_w1, grad_w2)をもとに、重み(w1, w2)を更新します。自動微分を用いた場合のパラメータの更新は、torch.no_grad()で対象のパラメータをくくることで計算できます。

確率勾配降下法(SGD: stochastic gradient descent)は、重みを更新する上でよく使われる最適化アルゴリズムで、以下の式で表されます。

weight = weight - learning_rate * gradient

SGDを用いてパラメータを更新するには、このように記述します。

一度更新した、パラメータはgrad.zero()でゼロに初期化します。

    with torch.no_grad():
        w1 -= learning_rate * w1.grad
        w2 -= learning_rate * w2.grad

        # Manually zero the gradients after updating weights
        w1.grad.zero_()
        w2.grad.zero_()

実行

以下のコードを4_autograd_func.pyとして保存します。

4_autograd_func.py

import torch

dtype = torch.float
device = torch.device("cpu")
# device = torch.device("cuda:0") # Uncomment this to run on GPU

# N is batch size; D_in is input dimension;
# H is hidden dimension; D_out is output dimension.
N, D_in, H, D_out = 64, 1000, 100, 10

# Create random Tensors to hold input and outputs.
x = torch.randn(N, D_in, device=device, dtype=dtype)
y = torch.randn(N, D_out, device=device, dtype=dtype)

# Create random Tensors for weights.
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):
    # Forward pass: compute predicted y using operations on Tensors
    y_pred = x.mm(w1).clamp(min=0).mm(w2)

    # Compute and print loss using operations on Tensors.
    loss = (y_pred - y).pow(2).sum()
    if t % 100 == 99:
        print(t, loss.item())

    # Use autograd to compute the backward pass.
    loss.backward()

    # Manually update weights using gradient descent. Wrap in torch.no_grad()
    with torch.no_grad():
        w1 -= learning_rate * w1.grad
        w2 -= learning_rate * w2.grad

        # Manually zero the gradients after updating weights
        w1.grad.zero_()
        w2.grad.zero_()

保存ができたら実行しましょう。

左の数字が学習回数、右の数値がパーセプトロンの推定値と実際の答えと二乗誤差です。

学習を重ねるごとに、二乗誤差が小さくなることがわかります。

$ python3 4_autograd_func.py 
99 763.6893310546875
199 9.174347877502441
299 0.22351056337356567
399 0.007267479319125414
499 0.0004901751526631415



頑張れ!喝!!の代わりにB!ブックマークを押していただけるとただただうれしいです(^^)! ↓