PyTorch : Tutorial 初級 : Torch ユーザのための PyTorch – nn パッケージ

PyTorch : Tutorial 初級 : Torch ユーザのための PyTorch – nn パッケージ (翻訳/解説)
翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 05/11/2018 (0.4.0)

* 本ページは、PyTorch Tutorials の PyTorch for former Torch users – nn package を動作確認・翻訳した上で
適宜、補足説明したものです:

* サンプルコードの動作確認はしておりますが、適宜、追加改変している場合もあります。
* ご自由にリンクを張って頂いてかまいませんが、sales-info@classcat.com までご一報いただけると嬉しいです。

 

本文

nn パッケージを再デザインしました、その結果それは autograd に完全に統合されます。その変更をレビューしましょう。

container を autograd で置き換える :

ConcatTable のような Container や CAddTable のようなモジュールを使用することや nngraph を使用してデバッグしなければならないということはもはやありません。ニューラルネットワークを定義するために autograd をシームレスに使用します。例えば、

  • output = nn.CAddTable():forward({input1, input2}) は単純に output = input1 + input2 になります。
  • output = nn.MulConstant(0.5):forward(input) は単純に output = input * 0.5 になります。

状態はもはやモジュール内で保持されず、ネットワーク・グラフ内で保持されます :

この理由でリカレント・ネットワークの使用はより単純になるはずです。リカレント・ネットワークを作成することを望む場合、共有する重みについて考えなければならないということなく、単純に同じ Linear 層を複数回使用してください。

 
単純化されたデバッギング :

デバッギングは Python pdb デバッガーを使用して直感的です、そしてデバッガーとスタックトレースはエラーが発生した場所で正確に停止します。貴方が見ているものが貴方の得るものです (= What you see is what you get)。

 

サンプル 1: ConvNet

小さい ConvNet をどのように作成するかを見てみましょう。

貴方のネットワークの総ては基底クラス nn.Module から派生します。コンストラクタでは、使用することを望む総ての層を宣言します。forward 関数では、入力から出力まで、貴方のモデルがどのように実行されるかを定義します。

import torch
import torch.nn as nn
import torch.nn.functional as F


class MNISTConvNet(nn.Module):

    def __init__(self):
        # これは貴方が総ての貴方のモジュールをインスタンス化する場所です。
        # ここで与えた同じ名前を使用して後でそれらにアクセスできます。
        super(MNISTConvNet, self).__init__()
        self.conv1 = nn.Conv2d(1, 10, 5)
        self.pool1 = nn.MaxPool2d(2, 2)
        self.conv2 = nn.Conv2d(10, 20, 5)
        self.pool2 = nn.MaxPool2d(2, 2)
        self.fc1 = nn.Linear(320, 50)
        self.fc2 = nn.Linear(50, 10)

    # ネットワーク構造を定義する forward 関数です。
    # ここでは単一の input だけを受け取っていますが、
    # 貴方が望むのであれば自由にそれ以上を使用してください。
    def forward(self, input):
        x = self.pool1(F.relu(self.conv1(input)))
        x = self.pool2(F.relu(self.conv2(x)))

        # モデル定義内では、モデル構造を定義するために
        # 貴方は完全に熱狂して任意のコードを使用することができます。
        # これら総ては完全に正当 (= legal) で、そして autograd により正しく処理されるでしょう :
        # if x.gt(0) > x.numel() / 2:
        #      ...
        #
        # ループを遂行することさえ可能でその内側で同じモジュールを再利用できます。
        # モジュールは ephemeral な状態をもはや保持しませんので、
        # forward パスの間にそれらを複数回使用することができます。
        # while x.norm(2) < 10:
        #    x = self.conv1(x)

        x = x.view(x.size(0), -1)
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        return x

定義された ConvNet を今使用しましょう。最初にクラスのインスタンスを作成します。

net = MNISTConvNet()
print(net)

Out:

MNISTConvNet(
  (conv1): Conv2d(1, 10, kernel_size=(5, 5), stride=(1, 1))
  (pool1): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
  (conv2): Conv2d(10, 20, kernel_size=(5, 5), stride=(1, 1))
  (pool2): MaxPool2d(kernel_size=2, stride=2, padding=0, dilation=1, ceil_mode=False)
  (fc1): Linear(in_features=320, out_features=50, bias=True)
  (fc2): Linear(in_features=50, out_features=10, bias=True)
)

Note: torch.nn はミニバッチをサポートするだけです。torch.nn パッケージ全体はサンプルの (単一のサンプルではなく) ミニバッチである入力をサポートするだけです。
例えば、nn.Conv2d は nSamples x nChannels x Height x Width の 4D Tensor を受け取るでしょう。
単一のサンプルを持つ場合、fake バッチ次元を追加するために input.unsqueeze(0) を単に使用してください。

ランダムデータの単一のサンプルを含むミニバッチを作成してサンプルを ConvNet を通して送ります。

input = torch.randn(1, 1, 28, 28)
out = net(input)
print(out.size())

Out:

torch.Size([1, 10])

ダミーのターゲット・ラベルを定義して損失関数を使用してエラーを計算します。

target = torch.tensor([3], dtype=torch.long)
loss_fn = nn.CrossEntropyLoss()  # LogSoftmax + ClassNLL Loss
err = loss_fn(out, target)
err.backward()

print(err)

Out:

tensor(2.3564)

ConvNet の出力 out は Tensor です。それを使用して損失を計算し、これは err という結果になりこれもまた Tensor です。こうして err 上の .backward の呼び出しは ConvNet をずっと通して勾配をその重みに伝播します。

個々の層の重みと勾配にアクセスしてみましょう :

print(net.conv1.weight.grad.size())

Out:

torch.Size([10, 1, 5, 5])
print(net.conv1.weight.data.norm())  # norm of the weight
print(net.conv1.weight.grad.data.norm())  # norm of the gradients

Out:

tensor(2.0010)
tensor(0.1532)

 

Forward と Backward 関数 Hook

重みと勾配を調査してきました。しかし層の output と grad_output を調査 / 変更することについてはどうでしょう?

この目的のための hook を導入します。

モジュールか Tensor 上で関数を登録することができます。この hook は forward hook か a backward hook です。forward hook は forward 呼び出しが実行されたときに実行されます。backward hook は backward 段階で実行されます。サンプルを見てみましょう。

conv2 上で forward hook を登録して何某かの情報をプリントします。

def printnorm(self, input, output):
    # input is a tuple of packed inputs
    # output is a Tensor. output.data is the Tensor we are interested
    print('Inside ' + self.__class__.__name__ + ' forward')
    print('')
    print('input: ', type(input))
    print('input[0]: ', type(input[0]))
    print('output: ', type(output))
    print('')
    print('input size:', input[0].size())
    print('output size:', output.data.size())
    print('output norm:', output.data.norm())


net.conv2.register_forward_hook(printnorm)

out = net(input)

Out:

Inside Conv2d forward

input:  <class 'tuple'>
input[0]:  <class 'torch.Tensor'>
output:  <class 'torch.Tensor'>

input size: torch.Size([1, 10, 12, 12])
output size: torch.Size([1, 20, 8, 8])
output norm: tensor(17.5375)

conv2 上で backward hook を登録して何某かの情報をプリントします。

def printgradnorm(self, grad_input, grad_output):
    print('Inside ' + self.__class__.__name__ + ' backward')
    print('Inside class:' + self.__class__.__name__)
    print('')
    print('grad_input: ', type(grad_input))
    print('grad_input[0]: ', type(grad_input[0]))
    print('grad_output: ', type(grad_output))
    print('grad_output[0]: ', type(grad_output[0]))
    print('')
    print('grad_input size:', grad_input[0].size())
    print('grad_output size:', grad_output[0].size())
    print('grad_input norm:', grad_input[0].norm())


net.conv2.register_backward_hook(printgradnorm)

out = net(input)
err = loss_fn(out, target)
err.backward()

Out:

Inside Conv2d forward

input:  
input[0]:  
output:  

input size: torch.Size([1, 10, 12, 12])
output size: torch.Size([1, 20, 8, 8])
output norm: tensor(17.5375)
Inside Conv2d backward
Inside class:Conv2d

grad_input:  <class 'tuple'>
grad_input[0]:  <class 'torch.Tensor'\>
grad_output:  <class 'tuple'>
grad_output[0]:  <class 'torch.Tensor'>

grad_input size: torch.Size([1, 10, 12, 12])
grad_output size: torch.Size([1, 20, 8, 8])
grad_input norm: tensor(1.00000e-02 *
       3.1349)

 

サンプル 2: リカレント・ネット

次に、PyTorch によるリカレント・ネットの構築を見てみましょう。

ネットワークの状態は層ではなくグラフに保持されますので、nn.Linear を単純に作成してリカレンスのためにそれを何度も何度も再利用することが可能です。

class RNN(nn.Module):

    # モデル・コンストラクタで引数を受け取ることもまたできます。
    def __init__(self, data_size, hidden_size, output_size):
        super(RNN, self).__init__()

        self.hidden_size = hidden_size
        input_size = data_size + hidden_size

        self.i2h = nn.Linear(input_size, hidden_size)
        self.h2o = nn.Linear(hidden_size, output_size)

    def forward(self, data, last_hidden):
        input = torch.cat((data, last_hidden), 1)
        hidden = self.i2h(input)
        output = self.h2o(hidden)
        return hidden, output


rnn = RNN(50, 20, 10)

LSTM と Penn ツリーバンクを使用するより複雑な言語モデリング・サンプルは ここ にあります。

PyTorch はデフォルトで ConvNet とリカレント・ネットのためのシームレスな CuDNN 統合を持ちます。

loss_fn = nn.MSELoss()

batch_size = 10
TIMESTEPS = 5

# Create some fake data
batch = torch.randn(batch_size, 50)
hidden = torch.zeros(batch_size, 20)
target = torch.zeros(batch_size, 10)

loss = 0
for t in range(TIMESTEPS):
    # yes! 同じネットワークを何度も再利用可能で、損失を総計して、backward を呼び出せます!
    hidden, output = rnn(batch, hidden)
    loss += loss_fn(output, target)
loss.backward()

 

 

以上