Learning PyTorch with examples の和訳

https://pytorch.org/tutorials/beginner/pytorch_with_examples.html の和訳です。

# Learning PyTorch with examples

このチュートリアルでは、PyTorch の基本的な概念を、自給自足の例を使って紹介します。

PyTorch のコアとなる機能は 2 つあります。

* numpyに似ていますが、GPU上で実行可能なn次元テンソル
* ニューラルネットワークの構築と訓練のための自動微分

実行例として、完全に接続されたReLUネットワークを使用します。このネットワークは単一の隠れ層を持ち、ネットワーク出力と真の出力の間のユークリッド距離を最小化することでランダムなデータに適合するように勾配降下法で訓練されます。

# Tensors

## Warm-up: numpy

PyTorchを導入する前に、まずはnumpyを使ってネットワークを実装します。

Numpyはn次元の配列オブジェクトと、その配列を操作するための多くの関数を提供しています。Numpyは科学計算のための汎用的なフレームワークであり、計算グラフやディープラーニング、グラディエントについては何も知りません。しかし、2層のネットワークをランダムなデータに適合させるには、numpyの演算を使ってネットワークの前進と後退のパスを手動で実装することで、簡単にnumpyを使うことができます。


```Python
# -*- coding: utf-8 -*-
import numpy as np

# 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 input and output data
x = np.random.randn(N, D_in)
y = np.random.randn(N, D_out)

# Randomly initialize weights
w1 = np.random.randn(D_in, H)
w2 = np.random.randn(H, D_out)

learning_rate = 1e-6
for t in range(500):
    # Forward pass: compute predicted y
    h = x.dot(w1)
    h_relu = np.maximum(h, 0)
    y_pred = h_relu.dot(w2)

    # Compute and print loss
    loss = np.square(y_pred - y).sum()
    print(t, loss)

    # Backprop to compute gradients of w1 and w2 with respect to loss
    grad_y_pred = 2.0 * (y_pred - y)
    grad_w2 = h_relu.T.dot(grad_y_pred)
    grad_h_relu = grad_y_pred.dot(w2.T)
    grad_h = grad_h_relu.copy()
    grad_h[h < 0] = 0
    grad_w1 = x.T.dot(grad_h)

    # Update weights
    w1 -= learning_rate * grad_w1
    w2 -= learning_rate * grad_w2
```

# PyTorch: Tensors

Numpyは素晴らしいフレームワークですが、GPUを利用して数値計算を高速化することはできません。最近のディープニューラルネットワークでは、GPUで50倍以上の高速化が可能な場合が多いので、残念ながらnumpyだけでは十分ではありません。

ここでは、PyTorchの最も基本的な概念であるテンソルを紹介します。PyTorch のテンソルは、概念的には numpy 配列と同じです。裏では、テンソルは計算グラフやグラデーションを追跡することができますが、科学的な計算のための汎用的なツールとしても役立ちます。

また、numpyとは異なり、PyTorch TensorsはGPUを利用して数値計算を高速化することができます。GPU上でPyTorchテンソルを実行するには、単に新しいデータ型にキャストする必要があります。

ここでは、ランダムなデータに2層のネットワークをフィットさせるためにPyTorchテンソルを使用しています。上記のnumpyの例のように、ネットワークを通過する前方と後方のパスを手動で実装する必要があります。

```Python
# -*- coding: utf-8 -*-

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 input and output data
x = torch.randn(N, D_in, device=device, dtype=dtype)
y = torch.randn(N, D_out, device=device, dtype=dtype)

# Randomly initialize weights
w1 = torch.randn(D_in, H, device=device, dtype=dtype)
w2 = torch.randn(H, D_out, device=device, dtype=dtype)

learning_rate = 1e-6
for t in range(500):
    # Forward pass: compute predicted y
    h = x.mm(w1)
    h_relu = h.clamp(min=0)
    y_pred = h_relu.mm(w2)

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

    # Backprop to compute gradients of w1 and w2 with respect to loss
    grad_y_pred = 2.0 * (y_pred - y)
    grad_w2 = h_relu.t().mm(grad_y_pred)
    grad_h_relu = grad_y_pred.mm(w2.t())
    grad_h = grad_h_relu.clone()
    grad_h[h < 0] = 0
    grad_w1 = x.t().mm(grad_h)

    # Update weights using gradient descent
    w1 -= learning_rate * grad_w1
    w2 -= learning_rate * grad_w2
```

# Autograd

## PyTorch: Tensors and autograd

上記の例では、ニューラルネットワークの前進パスと後退パスの両方を手動で実装しなければなりませんでした。後退パスを手動で実装することは、小さな 2 層ネットワークでは大したことではありませんが、大規模で複雑なネットワークではすぐに非常に面倒なことになります。

ありがたいことに、自動微分を使って、ニューラルネットワークの後方パスの計算を自動化することができます。PyTorchのautogradパッケージは、まさにこの機能を提供します。autogradを使うと、ネットワークのフォワードパスは計算グラフを定義します。グラフのノードはテンソルで、エッジは入力テンソルから出力テンソルを生成する関数です。このグラフをバックプロパゲーションすることで、勾配を簡単に計算することができます。

これは複雑に聞こえますが、実際にはとても簡単に使えます。各テンソルは計算グラフのノードを表します。x が x.require_grad=True のテンソルであれば、x.grad はスカラ値に対する x の勾配を保持する別のテンソルです。

ここでは、PyTorchテンソルとautogradを使って2層ネットワークを実装しています。

```Python
# -*- coding: utf-8 -*-
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.
# Setting requires_grad=False indicates that we do not need to compute gradients
# with respect to these Tensors during the backward pass.
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.
# Setting requires_grad=True indicates that we want to compute gradients with
# respect to these Tensors during the backward pass.
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; these
    # are exactly the same operations we used to compute the forward pass using
    # Tensors, but we do not need to keep references to intermediate values since
    # we are not implementing the backward pass by hand.
    y_pred = x.mm(w1).clamp(min=0).mm(w2)

    # Compute and print loss using operations on Tensors.
    # Now loss is a Tensor of shape (1,)
    # loss.item() gets the scalar value held in the loss.
    loss = (y_pred - y).pow(2).sum()
    if t % 100 == 99:
        print(t, loss.item())

    # Use autograd to compute the backward pass. This call will compute the
    # gradient of loss with respect to all Tensors with requires_grad=True.
    # After this call w1.grad and w2.grad will be Tensors holding the gradient
    # of the loss with respect to w1 and w2 respectively.
    loss.backward()

    # Manually update weights using gradient descent. Wrap in torch.no_grad()
    # because weights have requires_grad=True, but we don't need to track this
    # in autograd.
    # An alternative way is to operate on weight.data and weight.grad.data.
    # Recall that tensor.data gives a tensor that shares the storage with
    # tensor, but doesn't track history.
    # You can also use torch.optim.SGD to achieve this.
    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_()

```

# PyTorch: Defining new autograd functions

その下では、各プリミティブ autograd 演算子は、実際にはテンソルで動作する 2 つの関数です。前方関数は入力テンソルから出力テンソルを計算します。後方関数は、スカラ値に対する出力テンソルの勾配を受け取り、同じスカラ値に対する入力テンソルの勾配を計算します。

PyTorchでは、torch.autograd.Functionのサブクラスを定義し、前方関数と後方関数を実装することで、簡単に独自のautograd演算子を定義することができます。インスタンスを作成して関数のように呼び出し、入力データを含むテンソルを渡すことで、新しい autograd 演算子を使用することができます。

この例では、ReLU 非線形性を実行するための独自の autograd 関数を定義し、それを使用して 2 層ネットワークを実装しています。

```Python
# -*- coding: utf-8 -*-
import torch


class MyReLU(torch.autograd.Function):
    """
    We can implement our own custom autograd Functions by subclassing
    torch.autograd.Function and implementing the forward and backward passes
    which operate on Tensors.
    """

    @staticmethod
    def forward(ctx, input):
        """
        In the forward pass we receive a Tensor containing the input and return
        a Tensor containing the output. ctx is a context object that can be used
        to stash information for backward computation. You can cache arbitrary
        objects for use in the backward pass using the ctx.save_for_backward method.
        """
        ctx.save_for_backward(input)
        return input.clamp(min=0)

    @staticmethod
    def backward(ctx, grad_output):
        """
        In the backward pass we receive a Tensor containing the gradient of the loss
        with respect to the output, and we need to compute the gradient of the loss
        with respect to the input.
        """
        input, = ctx.saved_tensors
        grad_input = grad_output.clone()
        grad_input[input < 0] = 0
        return grad_input


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):
    # To apply our Function, we use Function.apply method. We alias this as 'relu'.
    relu = MyReLU.apply

    # Forward pass: compute predicted y using operations; we compute
    # ReLU using our custom autograd operation.
    y_pred = relu(x.mm(w1)).mm(w2)

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

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

    # Update weights using gradient descent
    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_()
```

# nn module

## PyTorch: nn

計算グラフとオートグラッドは、複雑な演算子を定義し、自動的に導関数を取るための非常に強力なパラダイムですが、大規模なニューラルネットワークの場合、生のオートグラッドは少し低レベルすぎるかもしれません。

ニューラルネットワークを構築する際には、計算をレイヤーに配置することをよく考えますが、その中には学習可能なパラメータがあり、学習中に最適化されます。

TensorFlowでは、Keras, TensorFlow-Slim, TFLearnなどのパッケージが、ニューラルネットワークを構築するのに便利な生の計算グラフの上に高レベルの抽象化を提供しています。

PyTorchでは、NNパッケージがこれと同じ目的を果たしています。nn パッケージは、ニューラルネットワーク層とほぼ同等のモジュールを定義します。モジュールは入力テンソルを受け取り、出力テンソルを計算しますが、学習可能なパラメータを含むテンソルのような内部状態を保持することもできます。nnパッケージはまた、ニューラルネットワークを学習する際に一般的に使用される有用な損失関数のセットを定義しています。

この例では、NNパッケージを使用して2層ネットワークを実装しています。

```Python
# -*- coding: utf-8 -*-
import torch

# 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 inputs and outputs
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.
model = torch.nn.Sequential(
    torch.nn.Linear(D_in, H),
    torch.nn.ReLU(),
    torch.nn.Linear(H, D_out),
)

# The nn package also contains definitions of popular loss functions; in this
# case we will use Mean Squared Error (MSE) as our loss function.
loss_fn = torch.nn.MSELoss(reduction='sum')

learning_rate = 1e-4
for t in range(500):
    # Forward pass: compute predicted y by passing x to the model. Module objects
    # override the __call__ operator so you can call them like functions. When
    # doing so you pass a Tensor of input data to the Module and it produces
    # a Tensor of output data.
    y_pred = model(x)

    # Compute and print loss. We pass Tensors containing the predicted and true
    # values of y, and the loss function returns a Tensor containing the
    # loss.
    loss = loss_fn(y_pred, y)
    if t % 100 == 99:
        print(t, loss.item())

    # Zero the gradients before running the backward pass.
    model.zero_grad()

    # Backward pass: compute gradient of the loss with respect to all the learnable
    # parameters of the model. Internally, the parameters of each Module are stored
    # in Tensors with requires_grad=True, so this call will compute gradients for
    # all learnable parameters in the model.
    loss.backward()

    # Update the weights using gradient descent. Each parameter is a Tensor, so
    # we can access its gradients like we did before.
    with torch.no_grad():
        for param in model.parameters():
            param -= learning_rate * param.grad
```

# PyTorch: optim

ここまでは、学習可能なパラメータを保持しているテンソルを手動で変異させることでモデルの重みを更新してきました（autogradでの追跡履歴を避けるために、torch.no_grad()や.dataを使用しています）。これは確率的勾配降下のような単純な最適化アルゴリズムでは大きな負担ではありませんが、実際にはAdaGrad, RMSProp, Adamなどのより高度なオプティマイザを使ってニューラルネットワークを訓練することがよくあります。

PyTorch の optim パッケージは、最適化アルゴリズムの概念を抽象化し、一般的に使用されている最適化アルゴリズムの実装を提供します。

この例では、以前と同様に nn パッケージを使用してモデルを定義しますが、optim パッケージが提供する Adam アルゴリズムを使用してモデルを最適化します。

```Python
# -*- coding: utf-8 -*-
import torch

# 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 inputs and outputs
x = torch.randn(N, D_in)
y = torch.randn(N, D_out)

# Use the nn package to define our model and loss function.
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')

# Use the optim package to define an Optimizer that will update the weights of
# the model for us. Here we will use Adam; the optim package contains many other
# optimization algoriths. The first argument to the Adam constructor tells the
# optimizer which Tensors it should update.
learning_rate = 1e-4
optimizer = torch.optim.Adam(model.parameters(), lr=learning_rate)
for t in range(500):
    # Forward pass: compute predicted y by passing x to the model.
    y_pred = model(x)

    # Compute and print loss.
    loss = loss_fn(y_pred, y)
    if t % 100 == 99:
        print(t, loss.item())

    # Before the backward pass, use the optimizer object to zero all of the
    # gradients for the variables it will update (which are the learnable
    # weights of the model). This is because by default, gradients are
    # accumulated in buffers( i.e, not overwritten) whenever .backward()
    # is called. Checkout docs of torch.autograd.backward for more details.
    optimizer.zero_grad()

    # Backward pass: compute gradient of the loss with respect to model
    # parameters
    loss.backward()

    # Calling the step function on an Optimizer makes an update to its
    # parameters
    optimizer.step()
```

# PyTorch: Custom nn Modules

このような場合には、nn.Module をサブクラス化し、入力テンソルを受け取り、他のモジュールやテンソルに対する他の自動処理を使用して出力テンソルを生成するフォワードを定義することで、独自のモジュールを定義することができます。

この例では、2 層ネットワークをカスタムモジュールのサブクラスとして実装しています。

```Python
# -*- coding: utf-8 -*-
import torch


class TwoLayerNet(torch.nn.Module):
    def __init__(self, D_in, H, D_out):
        """
        In the constructor we instantiate two nn.Linear modules and assign them as
        member variables.
        """
        super(TwoLayerNet, self).__init__()
        self.linear1 = torch.nn.Linear(D_in, H)
        self.linear2 = torch.nn.Linear(H, D_out)

    def forward(self, x):
        """
        In the forward function we accept a Tensor of input data and we must return
        a Tensor of output data. We can use Modules defined in the constructor as
        well as arbitrary operators on Tensors.
        """
        h_relu = self.linear1(x).clamp(min=0)
        y_pred = self.linear2(h_relu)
        return y_pred


# 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 inputs and outputs
x = torch.randn(N, D_in)
y = torch.randn(N, D_out)

# Construct our model by instantiating the class defined above
model = TwoLayerNet(D_in, H, D_out)

# Construct our loss function and an Optimizer. The call to model.parameters()
# in the SGD constructor will contain the learnable parameters of the two
# nn.Linear modules which are members of the model.
criterion = torch.nn.MSELoss(reduction='sum')
optimizer = torch.optim.SGD(model.parameters(), lr=1e-4)
for t in range(500):
    # Forward pass: Compute predicted y by passing x to the model
    y_pred = model(x)

    # Compute and print loss
    loss = criterion(y_pred, y)
    if t % 100 == 99:
        print(t, loss.item())

    # Zero gradients, perform a backward pass, and update the weights.
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()
```

# PyTorch: Control Flow + Weight Sharing

動的グラフと重みの共有の例として、非常に奇妙なモデルを実装します：完全に接続されたReLUネットワークは、各フォワードパスで1から4の間の乱数を選択し、それだけの数の隠された層を使用し、最内層の隠された層を計算するために同じ重みを複数回再利用します。

このモデルでは、ループを実装するために通常のPythonのフローコントロールを使用することができ、フォワードパスを定義する際に同じモジュールを複数回再利用することで、最内層間での重みの共有を実装することができます。

このモデルはモジュールのサブクラスとして簡単に実装できます。

```Python
# -*- coding: utf-8 -*-
import random
import torch


class DynamicNet(torch.nn.Module):
    def __init__(self, D_in, H, D_out):
        """
        In the constructor we construct three nn.Linear instances that we will use
        in the forward pass.
        """
        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):
        """
        For the forward pass of the model, we randomly choose either 0, 1, 2, or 3
        and reuse the middle_linear Module that many times to compute hidden layer
        representations.

        Since each forward pass builds a dynamic computation graph, we can use normal
        Python control-flow operators like loops or conditional statements when
        defining the forward pass of the model.

        Here we also see that it is perfectly safe to reuse the same Module many
        times when defining a computational graph. This is a big improvement from Lua
        Torch, where each Module could be used only once.
        """
        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)
        y_pred = self.output_linear(h_relu)
        return y_pred


# 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 inputs and outputs
x = torch.randn(N, D_in)
y = torch.randn(N, D_out)

# Construct our model by instantiating the class defined above
model = DynamicNet(D_in, H, D_out)

# Construct our loss function and an Optimizer. Training this strange model with
# vanilla stochastic gradient descent is tough, so we use momentum
criterion = torch.nn.MSELoss(reduction='sum')
optimizer = torch.optim.SGD(model.parameters(), lr=1e-4, momentum=0.9)
for t in range(500):
    # Forward pass: Compute predicted y by passing x to the model
    y_pred = model(x)

    # Compute and print loss
    loss = criterion(y_pred, y)
    if t % 100 == 99:
        print(t, loss.item())

    # Zero gradients, perform a backward pass, and update the weights.
    optimizer.zero_grad()
    loss.backward()
    optimizer.step()
```