2
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【PyTorchチュートリアル⑤】Learning PyTorch with Examples (後編)

Last updated at Posted at 2020-07-09

はじめに

前回に引き続き、PyTorch 公式チュートリアル の第5弾です。
今回は Learning PyTorch with Examples の後編です。
前編こちらです。

3. nn module

3.1. PyTorch: nn

autograd だけでは、ニューラルネットワークのモデルを作成することはできません。
モデルの構築は、nnパッケージを利用します。
nnパッケージには、入力層、隠れ層、出力層を定義する Sequential クラスや、損失関数も含まれています。
下の例では、nnパッケージを使用して2層ネットワークを実装します。

import torch

# N     : バッチサイズ 
# D_in  : 入力次元数
# H     : 隠れ層の次元数
# D_out : 出力次元数
N, D_in, H, D_out = 64, 1000, 100, 10

# ランダムな入力データと教師データを作成します
x = torch.randn(N, D_in)
y = torch.randn(N, D_out)

# Use the nn package to define our model as a sequence of layers. nn.Sequential
# is a Module which contains other Modules, and applies them in sequence to
# produce its output. Each Linear Module computes output from input using a
# linear function, and holds internal Tensors for its weight and bias.

# nnパッケージを使用して、モデルを一連のレイヤーとして定義します。 
# nn.Sequentialは、他のモジュールを順番に適用してモデルを生成します。
# Linear は、線形関数を使用して入力から出力を計算し、重みとバイアスを
# 内部テンソルで保持します。
model = torch.nn.Sequential(
    torch.nn.Linear(D_in, H),
    torch.nn.ReLU(),
    torch.nn.Linear(H, D_out),
)

# nnパッケージには、損失関数も含まれています。
# 今回は、損失関数として平均二乗誤差(MSE)を使用します。
loss_fn = torch.nn.MSELoss(reduction='sum')

learning_rate = 1e-4
for t in range(500):
    # 順伝播:モデルにxを渡すことにより、予測値 y を計算します。
    # 基底クラスである Module は__call__演算子をオーバーライドするため、
    # 関数のように呼び出すことができます。
    # その場合、入力データのテンソルをモジュールに渡し、出力データのテンソルを生成します。
    y_pred = model(x)

    # ロス(損失)を計算し出力します。
    # 損失関数に y の予測値と教師データのテンソルを渡すと
    # 損失を含むテンソルを返します。
    loss = loss_fn(y_pred, y)
    if t % 100 == 99:
        print(t, loss.item())

    # 逆伝播を計算する前に、勾配をゼロにします。
    model.zero_grad()

    # 逆伝播:損失の勾配を計算します。
    # 内部的には、モジュールのパラメーターはrequire_grad = True で保持されているため
    # すべてのパラメーターの勾配を計算します。
    loss.backward()

    # 確率的勾配降下法を使用して重みを更新します。

    with torch.no_grad():
        for param in model.parameters():
            param -= learning_rate * param.grad

3.2. PyTorch: optim

ここまでは、モデルの重みを以下のように自分で計算して更新していました。

param -= learning_rate * param.grad

この計算方法は確率的勾配降下法と呼ばれます。
モデルの重みの計算方法( optimizer / 最適化アルゴリズム)は他にもあり、AdaGrad、RMSProp、Adam など、より高度なアルゴリズムを使用したいケースもでてきます。
PyTorchのoptimパッケージには、いろいろな最適化アルゴリズムがあります。
以下の例では、上の例のように nn パッケージでモデルを定義しますが、重みの更新に optimパッケージのAdamアルゴリズムを使用します。

import torch

# N     : バッチサイズ 
# D_in  : 入力次元数
# H     : 隠れ層の次元数
# D_out : 出力次元数
N, D_in, H, D_out = 64, 1000, 100, 10

# ランダムな入力データと教師データを作成します
x = torch.randn(N, D_in)
y = torch.randn(N, D_out)

# nnパッケージを使用して、モデルと損失関数を定義します。
model = torch.nn.Sequential(
    torch.nn.Linear(D_in, H),
    torch.nn.ReLU(),
    torch.nn.Linear(H, D_out),
)
loss_fn = torch.nn.MSELoss(reduction='sum')

# optimパッケージを使用して、モデルの重みを更新する最適化アルゴリズム(オプティマイザ)を定義します。
# ここでは Adam を使用します。
# optim パッケージには、他にもいろいろな最適化アルゴリズムが含まれています。 
# Adam コンストラクタへの最初の引数は、どの Tensor を更新するかを指定します。
learning_rate = 1e-4
optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)
for t in range(500):
    # 順伝播:モデルにxを渡すことにより、予測値 y を計算します。
    y_pred = model(x)

    # ロス(損失)を計算し出力します。
    loss = loss_fn(y_pred, y)
    if t % 100 == 99:
        print(t, loss.item())

    # 逆伝播の計算前に、更新する変数(重み)のすべての勾配をゼロにします。
    # これは、backward が呼び出されるたびに、勾配が累積される(上書きされない)ためです。
    optimizer.zero_grad()

    # 逆伝播:損失の勾配を計算します。
    loss.backward()

    # オプティマイザのステップ関数を呼び出すと、パラメーターが更新されます。
    optimizer.step()

3.3. PyTorch: Custom nn Modules

複雑なモデルを構築したい場合、nn.Moduleをサブクラス化して実現できます。
サブクラスで forward 関数をオーバーライドし、入力 Tensor から出力 Tensor を返却する処理を記述することで独自のモジュールを定義できます。
この例では、2層ネットワークをカスタムモジュールサブクラスとして実装します。

import torch

class TwoLayerNet(torch.nn.Module):
    def __init__(self, D_in, H, D_out):
        """
        コンストラクタで、2つの nn.Linear モジュールをインスタンス化し、
        それらをメンバー変数として割り当てます。
        """
        super(TwoLayerNet, self).__init__()
        self.linear1 = torch.nn.Linear(D_in, H)
        self.linear2 = torch.nn.Linear(H, D_out)

    def forward(self, x):
        """
        forward 関数では、入力 Tensor を元に、出力 Tensor を返す必要があります。
        コンストラクタで定義されたモジュールを使用できます。
        """
        h_relu = self.linear1(x).clamp(min=0)
        y_pred = self.linear2(h_relu)
        return y_pred


# N     : バッチサイズ 
# D_in  : 入力次元数
# H     : 隠れ層の次元数
# D_out : 出力次元数
N, D_in, H, D_out = 64, 1000, 100, 10

# ランダムな入力データと教師データを作成します
x = torch.randn(N, D_in)
y = torch.randn(N, D_out)

# 上で定義したニューラルネットワークモジュールをインスタンス化してモデルを構築します
model = TwoLayerNet(D_in, H, D_out)

# 損失関数と最適化アルゴリズム(オプティマイザ)を定義します。 
# SGD の引数 model.parameters() には、定義したクラスのメンバーである
# 2つの nn.Linear モジュールのパラメーターも含まれます。
criterion = torch.nn.MSELoss(reduction='sum')
optimizer = torch.optim.SGD(model.parameters(), lr=1e-4)
for t in range(500):
    # 順伝播:モデルに x を渡すことにより、予測値 y を計算します。
    y_pred = model(x)

    # ロス(損失)を計算し出力します。
    loss = criterion(y_pred, y)
    if t % 100 == 99:
        print(t, loss.item())

    # 勾配をゼロにし、逆伝播を計算して、重みを更新します。
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()

3.4. PyTorch: Control Flow + Weight Sharing

動的グラフと重み共有(Weight Sharing)の例として、特殊なモデルを実装します。以下のReLUネットワークは、forward 関数で0から3の乱数を選択し、複数の隠れ層で同じ重みを共有して計算します。
このモデルをModuleサブクラスとして実装します。

import random
import torch


class DynamicNet(torch.nn.Module):
    def __init__(self, D_in, H, D_out):
        """
        コンストラクターでは、forward 関数で使用する3つの nn.Linear インスタンス(入力層、隠れ層、出力層)を作成します。
        """
        super(DynamicNet, self).__init__()
        self.input_linear = torch.nn.Linear(D_in, H)
        self.middle_linear = torch.nn.Linear(H, H)
        self.output_linear = torch.nn.Linear(H, D_out)

    def forward(self, x):
        """
        forward 関数では、0回 から 3回 の値をランダムに選択し、
        middle_linearモジュールを複数回再利用して、隠れ層の処理を計算します。

        autograd は動的計算グラフのため、順伝播時に構築されます。
        そのため、forward 関数内では、ループや条件ステートメントなど、通常のPythonの処理を記述できます。

        計算グラフを定義するときに同じモジュールを何度も利用することができます。
        これはLua Torchからの改善点で、Lua Torch では各モジュールは1回しか使用できませんでした。
        """
        h_relu = self.input_linear(x).clamp(min=0)
        for _ in range(random.randint(0, 3)):
            h_relu = self.middle_linear(h_relu).clamp(min=0)
            print(str(_))
        print(h_relu.size())
        y_pred = self.output_linear(h_relu)
        return y_pred


# N     : バッチサイズ 
# D_in  : 入力次元数
# H     : 隠れ層の次元数
# D_out : 出力次元数
N, D_in, H, D_out = 64, 1000, 100, 10

# ランダムな入力データと教師データを作成します
x = torch.randn(N, D_in)
y = torch.randn(N, D_out)

# 上で定義したニューラルネットワークモジュールをインスタンス化してモデルを構築します
model = DynamicNet(D_in, H, D_out)

# 損失関数とオプティマイザーを作成します。
# このモデルは、通常の確率勾配降下法で学習させる(収束させる)のは難しいので、momentumを指定します。
criterion = torch.nn.MSELoss(reduction='sum')
optimizer = torch.optim.SGD(model.parameters(), lr=1e-4, momentum=0.9)
for t in range(500):
    # 順伝播:モデルにxを渡すことにより、予測値 y を計算します。
    y_pred = model(x)

    # ロス(損失)を計算し出力します。
    loss = criterion(y_pred, y)
    if t % 100 == 99:
        print(t, loss.item())

    # 勾配をゼロにし、逆伝播を計算して、重みを更新します。
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()

#終わりに

以上が、PyTorch の5つ目のチュートリアル「Learning PyTorch with Examples」の内容です。
autograd、torch.nn パッケージ、torch.optim パッケージの理解を深めることができました。

次回は6つ目のチュートリアル「What is torch.nn really?」を進めてみたいと思います。

#履歴
2020/07/10 初版公開
2020/07/10 前編のリンク修正
2020/10/10 次回のリンク追加

2
5
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?