What is torch.nn really? の和訳

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

```python
%matplotlib inline
```


What is `torch.nn` *really*?
============================
by Jeremy Howard, fast.ai. レイチェル・トーマスとフランシスコ・インガムに感謝します。




このチュートリアルはスクリプトではなく、ノートブックとして実行することをお勧めします。ノートブック(.ipynb)ファイルをダウンロードするには、ページ上部のリンクをクリックしてください。


PyTorch は、エレガントにデザインされたモジュールとクラス torch.nn , torch.optim , Dataset , DataLoader を提供し、ニューラルネットワークの作成とトレーニングを支援します。ニューラルネットワークのパワーをフルに活用し、問題に合わせてカスタマイズするためには、ニューラルネットワークが何をしているのかを正確に理解する必要があります。この理解を深めるために、まずMNISTのデータセット上で基本的なニューラルネットを学習します。次に、torch.nn, torch.optim, Dataset, DataLoaderから1つずつ特徴を追加していき、それぞれが何をしているのか、コードをより簡潔に、より柔軟にするためにどのように働くのかを正確に示します。




**このチュートリアルでは、PyTorch がインストールされており、テンソル演算の基本に精通していることを前提としています（Numpy の配列演算に精通している場合、ここで使用する PyTorch のテンソル演算はほぼ同じです）。


MNIST data setup
----------------

ここでは、手書きの数字（0から9まで）の白黒画像で構成された古典的なMNISTデータセットを使用します。


パスを扱うためにpathlib（Python 3標準ライブラリの一部）を使用し、リクエストを使用してデータセットをダウンロードします。モジュールを使用するときだけインポートするので、各時点で何が使用されているかを正確に見ることができます。





```python
from pathlib import Path
import requests

DATA_PATH = Path("data")
PATH = DATA_PATH / "mnist"

PATH.mkdir(parents=True, exist_ok=True)

URL = "http://deeplearning.net/data/mnist/"
FILENAME = "mnist.pkl.gz"

if not (PATH / FILENAME).exists():
        content = requests.get(URL + FILENAME).content
        (PATH / FILENAME).open("wb").write(content)
```

このデータセットは numpy 配列形式で、pickle という python 固有のデータをシリアライズするためのフォーマットを使って保存されています。




```python
import pickle
import gzip

with gzip.open((PATH / FILENAME).as_posix(), "rb") as f:
        ((x_train, y_train), (x_valid, y_valid), _) = pickle.load(f, encoding="latin-1")
```

それぞれの画像は 28 x 28 で、長さ 784 (=28x28) の平坦化された行として保存されています。まず画像を2次元にリシェイプする必要があります。




```python
from matplotlib import pyplot
import numpy as np

pyplot.imshow(x_train[0].reshape((28, 28)), cmap="gray")
print(x_train.shape)
```

PyTorch は numpy 配列ではなく ``torch.tensor`` を使っているので、データを変換する必要があります。




```python
import torch

x_train, y_train, x_valid, y_valid = map(
    torch.tensor, (x_train, y_train, x_valid, y_valid)
)
n, c = x_train.shape
x_train, x_train.shape, y_train.min(), y_train.max()
print(x_train, y_train)
print(x_train.shape)
print(y_train.min(), y_train.max())
```

Neural net from scratch (no torch.nn)
---------------------------------------------

まずはPyTorchのテンソル演算以外は何も使わずにモデルを作成してみましょう。ここでは、ニューラルネットワークの基本に精通していることを前提としています。そうでない場合は、https://course.fast.ai で学ぶことができます。

PyTorchには、単純な線形モデルの重みとバイアスを作成するためのランダムテンソルやゼロフィルテンソルを作成するメソッドが用意されています。これらのテンソルは通常のテンソルですが、1つだけ特別なものがあります。これにより、PyTorchはテンソルに行われたすべての操作を記録し、バックプロパゲーション中に自動的に勾配を計算できるようになります。


重みについては、初期化の後に ``requires_grad`` を設定します。(PyTorchの末尾の ``_`` は、この操作がその場で実行されたことを意味していることに注意してください)。




```python
import math

weights = torch.randn(784, 10) / math.sqrt(784)
weights.requires_grad_()
bias = torch.zeros(10, requires_grad=True)
```

PyTorch の勾配を自動的に計算する機能のおかげで、Python の標準的な関数（または呼び出し可能なオブジェクト）をモデルとして使うことができます。そこで、単純な線形モデルを作るために、単純な行列の乗算とブロードキャストされた加算を書いてみましょう。また、活性化関数も必要なので、`log_softmax`を書いてそれを使います。覚えておいてください: PyTorchにはたくさんの損失関数や活性化関数などが用意されていますが、Pythonを使えば簡単に自分のものを書くことができます。PyTorch は、あなたの関数のために高速な GPU コードやベクトル化された CPU コードを自動的に作成します。






```python
def log_softmax(x):
    return x - x.exp().sum(-1).log().unsqueeze(-1)

def model(xb):
    return log_softmax(xb @ weights + bias)
```

上では、``@``はドット積演算を表しています。ここでは、1つのバッチのデータ（この場合は64枚の画像）に対して関数を呼び出します。 これは1回の *forward pass* です。 この段階では，ランダムな重みを用いているので，予測値はランダムよりも優れたものにはならないことに注意してください．




```python
bs = 64  # batch size

xb = x_train[0:bs]  # a mini-batch from x
preds = model(xb)  # predictions
preds[0], preds.shape
print(preds[0], preds.shape)
```

ご覧のように、``preds``テンソルにはテンソルの値だけでなく、勾配関数も含まれています。これは後でバックプロップをするときに使います。

損失関数として使う負の対数尤度を実装してみましょう（ここでも標準のPythonでいいです）。




```python
def nll(input, target):
    return -input[range(target.shape[0]), target].mean()

loss_func = nll
```

ランダムモデルで損失を確認してみましょう、バックプロップパス後に改善されるかどうかは後で確認できます。




```python
yb = y_train[0:bs]
print(loss_func(preds, yb))
```

モデルの精度を計算する関数も実装してみましょう。各予測について、最大値を持つインデックスが目標値と一致すれば、予測が正しかったことになります。




```python
def accuracy(out, yb):
    preds = torch.argmax(out, dim=1)
    return (preds == yb).float().mean()
```

ランダムモデルの精度を確認してみましょう。





```python
print(accuracy(preds, yb))
```

これでトレーニングループを実行できるようになりました。 各反復について、以下のようにします。

- データのミニバッチを選択する (サイズ ``bs``)
- 予測を立てる
- 損失を計算する
- ``loss.backward()``はモデルの勾配を更新します。

現在、これらの勾配を使用して、重みとバイアスを更新しています。 これは ``torch.no_grad()`` コンテキストマネージャの中で行います。 PyTorch の Autograd がどのように操作を記録するかについては、 https://pytorch.org/docs/stable/notes/autograd.html

そして、次のループの準備ができるように、勾配をゼロに設定します。そうしないと、グラデーションは起こったすべての操作の実行中の集計を記録してしまいます(つまり ``loss.backward()``はグラデーションを置き換えるのではなく、すでに保存されているものにグラデーションを追加します)。

tip: 標準の python デバッガを使って、PyTorch のコードをステップスルーして、各ステップで変数の値をチェックすることができます。 以下の ``set_trace()`` をアンコメントして試してみてください。





```python
from IPython.core.debugger import set_trace

lr = 0.5  # learning rate
epochs = 2  # how many epochs to train for

for epoch in range(epochs):
    for i in range((n - 1) // bs + 1):
        #         set_trace()
        start_i = i * bs
        end_i = start_i + bs
        xb = x_train[start_i:end_i]
        yb = y_train[start_i:end_i]
        pred = model(xb)
        loss = loss_func(pred, yb)

        loss.backward()
        with torch.no_grad():
            weights -= weights.grad * lr
            bias -= bias.grad * lr
            weights.grad.zero_()
            bias.grad.zero_()
```

それだけです：私たちは完全にゼロから最小のニューラルネットワーク（この場合、隠れた層がないので、ロジスティック回帰）を作成し、訓練しました。

損失と精度を確認して、先ほどのものと比較してみましょう。損失は減少し、精度は上昇していると予想しています。




```python
print(loss_func(model(xb), yb), accuracy(model(xb), yb))
```

Using torch.nn.functional
------------------------------

ここからは、PyTorch の ``nn`` クラスを利用して、より簡潔で柔軟性のあるコードになるようにリファクタリングしていきます。ここからの各ステップでは、コードをより短く、より分かりやすく、より柔軟にしていきます。

最初の最も簡単なステップは、手書きで書いた活性化と損失の関数を ``torch.nn.functional`` (一般的には名前空間 ``F`` にインポートされます) のものに置き換えることで、コードを短くすることです。このモジュールには ``torch.nn`` ライブラリのすべての関数が含まれています (ライブラリの他の部分にはクラスが含まれています)。広範囲の損失関数や活性化関数に加えて、プーリング関数のようなニューラルネットを作成するための便利な関数もここにあります。(畳み込みや線形層などを行う関数もありますが、見ての通り、これらは通常ライブラリの他の部分を使った方が良いでしょう)



負の対数尤度損失と対数ソフトマックス活性化を使用している場合、Pytorchはこの2つを組み合わせた単一の関数 ``F.cross_entropy`` を提供する。だからモデルから活性化関数を削除することもできます。




```python
import torch.nn.functional as F

loss_func = F.cross_entropy

def model(xb):
    return xb @ weights + bias
```

この関数ではもう ``log_softmax`` を呼ばないことに注意してください。損失と精度が以前と同じであることを確認してみましょう。




```python
print(loss_func(model(xb), yb), accuracy(model(xb), yb))
```

Refactor using nn.Module
-----------------------------
次は ``nn.Module`` と ``nn.Parameter`` を使って、より明確で簡潔な学習ループを作りましょう。私たちは ``nn.Module`` (それ自体がクラスであり、状態を追跡することができます)をサブクラス化します。 この場合、前方ステップの重み、バイアス、メソッドを保持するクラスを作りたいと思います。 ``nn.Module``にはたくさんの属性とメソッド（例えば ``.parameters()``や ``.zero_grad()``など）があり、それらを使うことになります。

``nn.Module`` (大文字のM)はPyTorch特有の概念であり、これからよく使うクラスです。``nn.Module`` は Python の概念である (小文字の ``m``) `module と混同しないようにしてください。



```python
from torch import nn

class Mnist_Logistic(nn.Module):
    def __init__(self):
        super().__init__()
        self.weights = nn.Parameter(torch.randn(784, 10) / math.sqrt(784))
        self.bias = nn.Parameter(torch.zeros(10))

    def forward(self, xb):
        return xb @ self.weights + self.bias
```

関数を使うのではなくオブジェクトを使うようになったので、まずモデルのインスタンスを作成しなければなりません。




```python
model = Mnist_Logistic()
```

これで、先ほどと同じように損失を計算することができます。ただし、 ``nn.Module`` オブジェクトは関数であるかのように使われている（つまり、*呼び出し可能*である）が、裏では Pytorch が ``forward`` メソッドを自動的に呼び出していることに注意してほしい。




```python
print(loss_func(model(xb), yb))
```

以前の学習ループでは、各パラメータの値を名前で更新し、以下のように各パラメータの学生を個別に手動でゼロにする必要がありました。

```Python
  with torch.no_grad():
      weights -= weights.grad * lr
      bias -= bias.grad * lr
      weights.grad.zero_()
      bias.grad.zero_()
```

これで、model.parameters() と model.zero_grad() (どちらも ``nn.Module`` のために PyTorch で定義されています) を利用して、これらのステップをより簡潔にすることができます。

```Python
  with torch.no_grad():
      for p in model.parameters(): p -= p.grad * lr
      model.zero_grad()
```

トレーニングループを``fit``関数でラップして、後でもう一度実行できるようにします。





```python
def fit():
    for epoch in range(epochs):
        for i in range((n - 1) // bs + 1):
            start_i = i * bs
            end_i = start_i + bs
            xb = x_train[start_i:end_i]
            yb = y_train[start_i:end_i]
            pred = model(xb)
            loss = loss_func(pred, yb)

            loss.backward()
            with torch.no_grad():
                for p in model.parameters():
                    p -= p.grad * lr
                model.zero_grad()

fit()
```

損失が減ったことを再確認しましょう。



```python
print(loss_func(model(xb), yb))
```

Refactor using nn.Linear
-------------------------

私たちはコードのリファクタリングを続けています。 手動で ``self.weights`` と ``self.bias`` を定義して初期化し、 ``xb @ self.weights + self.bias`` を計算する代わりに、Pytorch クラス `nn.Linear` を線形レイヤーとして使用します。Pytorch には多くのタイプの定義済みレイヤーがあり、コードを大幅に単純化し、高速化してくれることが多い。





```python
class Mnist_Logistic(nn.Module):
    def __init__(self):
        super().__init__()
        self.lin = nn.Linear(784, 10)

    def forward(self, xb):
        return self.lin(xb)
```

私たちのモデルをインスタンス化し、以前と同じ方法で損失を計算します。




```python
model = Mnist_Logistic()
print(loss_func(model(xb), yb))
```

これまでと同じ ``fit`` メソッドを使うことができます。




```python
fit()

print(loss_func(model(xb), yb))
```

Refactor using optim
------------------------------

Pytorch には ``torch.optim`` という様々な最適化アルゴリズムのパッケージもある。各パラメータを手動で更新する代わりに、オプティマイザの ``step`` メソッドを使って前進することができます。

これで、以前に手動でコード化した最適化ステップを置き換えることができます。

```Python
  with torch.no_grad():
      for p in model.parameters(): p -= p.grad * lr
      model.zero_grad()
```

代わりに

```Pyrhon
  opt.step()
  opt.zero_grad()
```

``optim.zero_grad()``はグラデーションを0にリセットするので、次のミニバッチのグラデーションを計算する前にこれを呼び出す必要があります。




```python
from torch import optim
```

モデルとオプティマイザを作成するための小さな関数を定義して、将来的に再利用できるようにします。




```python
def get_model():
    model = Mnist_Logistic()
    return model, optim.SGD(model.parameters(), lr=lr)

model, opt = get_model()
print(loss_func(model(xb), yb))

for epoch in range(epochs):
    for i in range((n - 1) // bs + 1):
        start_i = i * bs
        end_i = start_i + bs
        xb = x_train[start_i:end_i]
        yb = y_train[start_i:end_i]
        pred = model(xb)
        loss = loss_func(pred, yb)

        loss.backward()
        opt.step()
        opt.zero_grad()

print(loss_func(model(xb), yb))
```

Refactor using Dataset
------------------------------

PyTorchには抽象Datasetクラスがあります。 Datasetは、 ``__len__`` 関数（Pythonの標準的な ``len`` 関数によって呼び出されます）と ``__getitem__`` 関数を持っていて、その中にインデックスを作成する方法を持っているものであれば何でも構いません。このチュートリアル https://pytorch.org/tutorials/beginner/data_loading_tutorial.html は、``Dataset``のサブクラスとしてカスタムの```FacialLandmarkDataset```クラスを作成する例を紹介しています。


PyTorchの `TensorDataset` はテンソルをラップしたデータセットです。長さとインデックス付けの方法を定義することで、テンソルの1次元目に沿って反復、インデックス付け、スライスする方法を提供してくれます。これにより、訓練を行う際に、独立変数と従属変数の両方に同一行でアクセスすることが容易になります。





```python
from torch.utils.data import TensorDataset
```

``x_train``と``y_train``の両方を1つの``TensorDataset```にまとめることができます。




```python
train_ds = TensorDataset(x_train, y_train)
```

以前は、xとyの値のミニバッチを別々に反復処理しなければなりませんでした。

```Python
    xb = x_train[start_i:end_i]
    yb = y_train[start_i:end_i]
```

これで、この2つのステップを一緒にできるようになりました。

```Python
    xb,yb = train_ds[i*bs : i*bs+bs]
```




```python
model, opt = get_model()

for epoch in range(epochs):
    for i in range((n - 1) // bs + 1):
        xb, yb = train_ds[i * bs: i * bs + bs]
        pred = model(xb)
        loss = loss_func(pred, yb)

        loss.backward()
        opt.step()
        opt.zero_grad()

print(loss_func(model(xb), yb))
```

Refactor using DataLoader
------------------------------

Pytorch の ``DataLoader`` はバッチの管理を担当する。データセットから ``DataLoader``` を作成することができる。DataLoader``はバッチの反復処理をより簡単にしてくれます。データローダ```はバッチの反復処理をより簡単にしてくれます。





```python
from torch.utils.data import DataLoader

train_ds = TensorDataset(x_train, y_train)
train_dl = DataLoader(train_ds, batch_size=bs)
```

以前のループでは、バッチ(xb, yb)をこのように反復処理していました。

```Python
      for i in range((n-1)//bs + 1):
          xb,yb = train_ds[i*bs : i*bs+bs]
          pred = model(xb)
```

(xb, yb) がデータローダから自動的にロードされるので、ループがよりきれいになりました。

```Python
      for xb,yb in train_dl:
          pred = model(xb)
```



```python
model, opt = get_model()

for epoch in range(epochs):
    for xb, yb in train_dl:
        pred = model(xb)
        loss = loss_func(pred, yb)

        loss.backward()
        opt.step()
        opt.zero_grad()

print(loss_func(model(xb), yb))
```

Pytorchの ``nn.Module``, ``nn.Parameter``, ``Dataset``, ``DataLoader`` のおかげで、学習ループは劇的に小さくなり、理解しやすくなった。それでは、実際に効果的なモデルを作成するために必要な基本的な機能を追加してみましょう。


Add validation
-----------------------

第1節では、学習データに使用するための合理的な学習ループを設定しようとしていました。 実際には、オーバーフィットしているかどうかを確認するために、**常に**バリデーションセット <https://www.fast.ai/2017/11/13/validation-sets/>`_を用意しておくべきです。

学習データをシャッフルすることは、バッチ間の相関やオーバーフィッティングを防ぐために重要である<https://www.quora.com/Does-the-order-of-training-data-matter-when-training-neural-networks>`_。一方、検証データをシャッフルしてもしなくても、検証の損失は同じになります。シャッフルには余分な時間がかかるので、検証データをシャッフルしても意味がない。


検証セットには、学習セットの2倍のバッチサイズを使用します。これは、検証セットはバックプロパゲーションを必要としないため、メモリ消費量が少なくて済むからです（グラデーションを保存する必要がない）。これを利用して、より大きなバッチサイズを使用し、損失をより迅速に計算します。




```python
train_ds = TensorDataset(x_train, y_train)
train_dl = DataLoader(train_ds, batch_size=bs, shuffle=True)

valid_ds = TensorDataset(x_valid, y_valid)
valid_dl = DataLoader(valid_ds, batch_size=bs * 2)
```

各エポック終了時の検証損失を計算して出力します。

(学習の前には必ず ``model.train()`` を、推論の前には必ず ``model.eval()`` を呼び出していることに注意してください。)




```python
model, opt = get_model()

for epoch in range(epochs):
    model.train()
    for xb, yb in train_dl:
        pred = model(xb)
        loss = loss_func(pred, yb)

        loss.backward()
        opt.step()
        opt.zero_grad()

    model.eval()
    with torch.no_grad():
        valid_loss = sum(loss_func(model(xb), yb) for xb, yb in valid_dl)

    print(epoch, valid_loss / len(valid_dl))
```

Create fit() and get_data()
----------------------------------

ここで少しリファクタリングをしてみましょう。学習集合と検証集合の両方の損失を計算するという似たようなプロセスを2回繰り返しているので、これを独自の関数 ``loss_batch`` にしてみましょう。


学習セットにはオプティマイザを渡し、それを使用してバックプロップを実行します。 検証セットでは、オプティマイザを渡さないので、このメソッドは backprop を実行しません。




```python
def loss_batch(model, loss_func, xb, yb, opt=None):
    loss = loss_func(model(xb), yb)

    if opt is not None:
        loss.backward()
        opt.step()
        opt.zero_grad()

    return loss.item(), len(xb)
```

fit``はモデルを訓練するために必要な操作を行い、各エポックの訓練損失と検証損失を計算します。




```python
import numpy as np

def fit(epochs, model, loss_func, opt, train_dl, valid_dl):
    for epoch in range(epochs):
        model.train()
        for xb, yb in train_dl:
            loss_batch(model, loss_func, xb, yb, opt)

        model.eval()
        with torch.no_grad():
            losses, nums = zip(
                *[loss_batch(model, loss_func, xb, yb) for xb, yb in valid_dl]
            )
        val_loss = np.sum(np.multiply(losses, nums)) / np.sum(nums)

        print(epoch, val_loss)
```

get_data```は学習セットと検証セットのデータローダを返します。




```python
def get_data(train_ds, valid_ds, bs):
    return (
        DataLoader(train_ds, batch_size=bs, shuffle=True),
        DataLoader(valid_ds, batch_size=bs * 2),
    )
```

これで、データローダの取得とモデルのフィッティングの全プロセスが3行のコードで実行できるようになりました。




```python
train_dl, valid_dl = get_data(train_ds, valid_ds, bs)
model, opt = get_model()
fit(epochs, model, loss_func, opt, train_dl, valid_dl)
```

この基本的な3行のコードを使えば、様々なモデルを訓練することができます。これらを使って畳み込みニューラルネットワーク(CNN)を訓練できるか見てみましょう!

Switch to CNN
-------------

ここでは、3つの畳み込み層を持つニューラルネットワークを構築しようとしています。前節の関数はどれもモデルの形を想定していないので、CNNの学習には何も変更せずに使うことができます。

畳み込み層としてPytorchの定義済みの `Conv2d <https://pytorch.org/docs/stable/nn.html#torch.nn.Conv2d>`_ クラスを使う。3つの畳み込み層を持つCNNを定義する。それぞれの畳み込みにはReLUが続く。 最後に平均的なプーリングを行う。 ( ``view`` は numpy の ``reshape`` の PyTorch 版であることに注意)




```python
class Mnist_CNN(nn.Module):
    def __init__(self):
        super().__init__()
        self.conv1 = nn.Conv2d(1, 16, kernel_size=3, stride=2, padding=1)
        self.conv2 = nn.Conv2d(16, 16, kernel_size=3, stride=2, padding=1)
        self.conv3 = nn.Conv2d(16, 10, kernel_size=3, stride=2, padding=1)

    def forward(self, xb):
        xb = xb.view(-1, 1, 28, 28)
        xb = F.relu(self.conv1(xb))
        xb = F.relu(self.conv2(xb))
        xb = F.relu(self.conv3(xb))
        xb = F.avg_pool2d(xb, 4)
        return xb.view(-1, xb.size(1))

lr = 0.1
```

Momentum <https://cs231n.github.io/neural-networks-3/#sgd>`_ は確率的勾配降下のバリエーションで、過去の更新も考慮に入れており、一般的にはより高速な学習が可能です。




```python
model = Mnist_CNN()
opt = optim.SGD(model.parameters(), lr=lr, momentum=0.9)

fit(epochs, model, loss_func, opt, train_dl, valid_dl)
```

nn.Sequential
------------------------

torch.nn```にはもう一つ便利なクラスがあり、コードを単純化するのに使うことができます。それは `Sequential <https://pytorch.org/docs/stable/nn.html#torch.nn.Sequential>`_ です。シーケンシャルオブジェクトは、その中に含まれるモジュールを順番に実行します。これは、我々のニューラルネットワークをより簡単に書く方法です。

これを利用するには、与えられた関数から **カスタムレイヤー** を簡単に定義できるようにする必要があります。 例えば、PyTorchには `view` レイヤがないので、ネットワーク用のレイヤを作る必要があります。Lambda``はネットワークを ``Sequential`` で定義するときに使えるレイヤーを作成します。




```python
class Lambda(nn.Module):
    def __init__(self, func):
        super().__init__()
        self.func = func

    def forward(self, x):
        return self.func(x)


def preprocess(x):
    return x.view(-1, 1, 28, 28)
```

シーケンシャル``で作成されたモデルは、単純に



```python
model = nn.Sequential(
    Lambda(preprocess),
    nn.Conv2d(1, 16, kernel_size=3, stride=2, padding=1),
    nn.ReLU(),
    nn.Conv2d(16, 16, kernel_size=3, stride=2, padding=1),
    nn.ReLU(),
    nn.Conv2d(16, 10, kernel_size=3, stride=2, padding=1),
    nn.ReLU(),
    nn.AvgPool2d(4),
    Lambda(lambda x: x.view(x.size(0), -1)),
)

opt = optim.SGD(model.parameters(), lr=lr, momentum=0.9)

fit(epochs, model, loss_func, opt, train_dl, valid_dl)
```

Wrapping DataLoader
-----------------------------

我々のCNNは非常に簡潔だが MNISTでしか使えない．
 - 入力が28*28の長さのベクトルであると仮定する
 - これはCNNの最終的なグリッドサイズが4であると仮定しています（これは我々が使用した平均的なプーリングカーネルサイズです）。

これらの2つの仮定を取り除いてみましょう。まず、初期のラムダ層を取り除き、データの前処理をジェネレータに移すことができます。




```python
def preprocess(x, y):
    return x.view(-1, 1, 28, 28), y


class WrappedDataLoader:
    def __init__(self, dl, func):
        self.dl = dl
        self.func = func

    def __len__(self):
        return len(self.dl)

    def __iter__(self):
        batches = iter(self.dl)
        for b in batches:
            yield (self.func(*b))

train_dl, valid_dl = get_data(train_ds, valid_ds, bs)
train_dl = WrappedDataLoader(train_dl, preprocess)
valid_dl = WrappedDataLoader(valid_dl, preprocess)
```

次に、 ``nn.AvgPool2d`` を ``nn.AdaptiveAvgPool2d`` に置き換えることができます。これにより、今持っている *input* テンソルではなく、欲しい *output* テンソルのサイズを定義することができます。その結果、私たちのモデルはどのようなサイズの入力でも動作します。




```python
model = nn.Sequential(
    nn.Conv2d(1, 16, kernel_size=3, stride=2, padding=1),
    nn.ReLU(),
    nn.Conv2d(16, 16, kernel_size=3, stride=2, padding=1),
    nn.ReLU(),
    nn.Conv2d(16, 10, kernel_size=3, stride=2, padding=1),
    nn.ReLU(),
    nn.AdaptiveAvgPool2d(1),
    Lambda(lambda x: x.view(x.size(0), -1)),
)

opt = optim.SGD(model.parameters(), lr=lr, momentum=0.9)
```

試しにやってみましょう。




```python
fit(epochs, model, loss_func, opt, train_dl, valid_dl)
```

Using your GPU
---------------

運良く CUDA 対応の GPU を利用できる場合（ほとんどのクラウドプロバイダーから 1 時間あたり約 0.50 ドルでレンタルできます）、それを使ってコードを高速化できます。まず、お使いのGPUがPytorchで動作していることを確認してください。




```python
print(torch.cuda.is_available())
```

そして、そのためのデバイスオブジェクトを作成します。




```python
dev = torch.device(
    "cuda") if torch.cuda.is_available() else torch.device("cpu")
```

バッチをGPUに移動させるために ``preprocess`` を更新してみましょう。




```python
def preprocess(x, y):
    return x.view(-1, 1, 28, 28).to(dev), y.to(dev)


train_dl, valid_dl = get_data(train_ds, valid_ds, bs)
train_dl = WrappedDataLoader(train_dl, preprocess)
valid_dl = WrappedDataLoader(valid_dl, preprocess)
```

最後に、モデルをGPUに移動させます。




```python
model.to(dev)
opt = optim.SGD(model.parameters(), lr=lr, momentum=0.9)
```

速く走るのを見つけるべきだ




```python
fit(epochs, model, loss_func, opt, train_dl, valid_dl)
```

Closing thoughts
-----------------

現在、一般的なデータパイプラインとトレーニングループがあり、Pytorchを使って多くの種類のモデルをトレーニングする際に利用することができます。モデルのトレーニングがどれだけ簡単にできるかは、`mnist_sample` のサンプルノートを見てください。

もちろん、データの増強、ハイパーパラメータのチューニング、モニタリング学習、転送学習など、追加したくなるものはたくさんあります。これらの機能はfastaiライブラリで利用可能で、このチュートリアルで示したのと同じ設計アプローチを使って開発されており、モデルをさらに進化させたいと考えている実践者に自然な次のステップを提供しています。

このチュートリアルの最初に、 ``torch.nn``, ``torch.optim``, ``Dataset``, ``DataLoader`` のそれぞれについて例を挙げて説明することを約束しました。それでは、これまで見てきたことをまとめてみましょう。

 - **torch.nn**

   + モジュール```: 関数のように振る舞いますが、状態(ニューラルネット層の重みのようなもの)を含むこともできる callable を作成します。それはどのような ``Parameter`` (s) が含まれているかを知っており、すべての勾配をゼロにしたり、重みを更新するためにそれらをループしたりすることができます。
   + ``Parameter``: ``Module`` に，バックプロップ中に更新が必要な重みを持っていることを伝えるテンソルのラッパーです．requires_grad` 属性がセットされたテンソルのみが更新されます。
   + 活性化関数や損失関数などを含むモジュール(通常は ``F`` 名前空間にインポートされます)で、畳み込みや線形層のような非統計的なレイヤーのバージョンも含まれます。
 - torch.optim```. SGD``のようなオプティマイザが含まれており、後方ステップの間に ``Parameter``の重みを更新します。
 - データセット```。データセット``: ``TensorDataset``のようなPytorchで提供されているクラスを含む、 ``__len__`` や ``__getitem__`` を持つオブジェクトの抽象的なインターフェースです。
 - データローダ```です。任意の ``Dataset`` を受け取り、データのバッチを返すイテレータを作成します。