PyTorch 2.0 チュートリアル : 学習 : 分類器の訓練

PyTorch 2.0 チュートリアル : 学習 : 分類器の訓練 (翻訳/解説)

翻訳 : (株)クラスキャット セールスインフォメーション
作成日時 : 03/22/2023 (2.0.0)

* 本ページは、PyTorch 2.0 Tutorials の以下のページを翻訳した上で適宜、補足説明したものです:

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

 

クラスキャット 人工知能 研究開発支援サービス

クラスキャット は人工知能・テレワークに関する各種サービスを提供しています。お気軽にご相談ください :

◆ 人工知能とビジネスをテーマに WEB セミナーを定期的に開催しています。スケジュール
  • お住まいの地域に関係なく Web ブラウザからご参加頂けます。事前登録 が必要ですのでご注意ください。

お問合せ : 本件に関するお問い合わせ先は下記までお願いいたします。

  • 株式会社クラスキャット セールス・マーケティング本部 セールス・インフォメーション
  • sales-info@classcat.com  ;  Website: www.classcat.com  ;   ClassCatJP

 

PyTorch 2.0 チュートリアル : 学習 : 分類器の訓練

This is it. 貴方はどのようにニューラルネットワークを定義するか、損失を計算するかそしてネットワークの重みを更新するかを見てきました。

今は次のように考えているかもしれません、

 

データについてはどうでしょう?

一般に、画像、テキスト、音声あるいは動画データを処理する必要がある時、データを numpy 配列にロードする標準的な python パッケージが使用できます。それからこの配列を torch.*Tensor に変換できます。

  • 画像については、Pillow, OpenCV のようなパッケージが有用です。
  • 音声については、scipy と librosa のようなパッケージ。
  • テキストについては、raw Python や Cython ベースのロード、あるいは NLTK と SpaCy が有用です。

特にビジョンについては、torchvision と呼ばれるパッケージを作成しました、これは ImageNet, CIFAR10, MNIST 等のような一般的なデータセットのためのデータローダと画像のためのデータ変換器を持ちます、すなわち torchvision.datasets と torch.utils.data.DataLoader です。

これは多大な便利さを提供してボイラープレートなコードを書くことを回避します。

このチュートリアルのためには、CIFAR10 データセットを使用します。それはクラス: ‘飛行機’, ‘自動車’, ‘鳥’, ‘猫’, ‘鹿’, ‘犬’, ‘蛙’, ‘馬’, ‘船’, ‘トラック’ を持ちます。CIFAR-10 の画像はサイズ 3x32x32、つまりサイズが 32×32 ピクセルの 3-チャネルカラー画像です。

 

画像分類器の訓練

以下のステップを順番に行ないます :

  1. CIFAR10 訓練とテスト・データセットを torchvision を使用してロードして正規化します。
  2. 畳込みニューラルネットワークを定義します。
  3. 損失関数を定義します。
  4. 訓練データ上でネットワークを訓練します。
  5. テストデータ上でネットワークをテストします。

 

1. CIFAR10 をロードして正規化する

torchvision を使用すれば、CIFAR10 のロードは非常に簡単です。

import torch
import torchvision
import torchvision.transforms as transforms

torchvision データセットの出力は範囲 [0, 1] の PILImage 画像です。それらを正規化された範囲 [-1, 1] のテンソルに変換します。

Note

Windows 上で実行していて BrokenPipeError を得る場合には、torch.utils.data.DataLoader() の num_worker を 0 に設定してみてください。

transform = transforms.Compose(
    [transforms.ToTensor(),
     transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])

batch_size = 4

trainset = torchvision.datasets.CIFAR10(root='./data', train=True,
                                        download=True, transform=transform)
trainloader = torch.utils.data.DataLoader(trainset, batch_size=batch_size,
                                          shuffle=True, num_workers=2)

testset = torchvision.datasets.CIFAR10(root='./data', train=False,
                                       download=True, transform=transform)
testloader = torch.utils.data.DataLoader(testset, batch_size=batch_size,
                                         shuffle=False, num_workers=2)

classes = ('plane', 'car', 'bird', 'cat',
           'deer', 'dog', 'frog', 'horse', 'ship', 'truck')

楽しみのために、訓練画像の幾つかを表示してみましょう。

import matplotlib.pyplot as plt
import numpy as np

# functions to show an image


def imshow(img):
    img = img / 2 + 0.5     # unnormalize
    npimg = img.numpy()
    plt.imshow(np.transpose(npimg, (1, 2, 0)))
    plt.show()


# get some random training images
dataiter = iter(trainloader)
images, labels = next(dataiter)

# show images
imshow(torchvision.utils.make_grid(images))
# print labels
print(' '.join(f'{classes[labels[j]]:5s}' for j in range(batch_size)))

dog truck   dog  frog

 

2. 畳込みニューラルネットワークの定義

前のニューラルネットワークのセクションからニューラルネットワークをコピーして (それが定義された 1-チャネル画像の替わりに) それを 3-チャネル画像を取るように変更します。

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


class Net(nn.Module):
    def __init__(self):
        super().__init__()
        self.conv1 = nn.Conv2d(3, 6, 5)
        self.pool = nn.MaxPool2d(2, 2)
        self.conv2 = nn.Conv2d(6, 16, 5)
        self.fc1 = nn.Linear(16 * 5 * 5, 120)
        self.fc2 = nn.Linear(120, 84)
        self.fc3 = nn.Linear(84, 10)

    def forward(self, x):
        x = self.pool(F.relu(self.conv1(x)))
        x = self.pool(F.relu(self.conv2(x)))
        x = torch.flatten(x, 1) # flatten all dimensions except batch
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = self.fc3(x)
        return x


net = Net()

 

3. 損失関数と optimizer の定義

分類 Cross-Entropy 損失とモメンタムを持つ SGD を使用しましょう。

import torch.optim as optim

criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(net.parameters(), lr=0.001, momentum=0.9)

 

4. ネットワークの訓練

これが物事が興味深くなり始める時です。データ iterator に対して単純にループさせて、ネットワークに入力を供給して最適化しなければなりません。

for epoch in range(2):  # loop over the dataset multiple times

    running_loss = 0.0
    for i, data in enumerate(trainloader, 0):
        # get the inputs; data is a list of [inputs, labels]
        inputs, labels = data

        # zero the parameter gradients
        optimizer.zero_grad()

        # forward + backward + optimize
        outputs = net(inputs)
        loss = criterion(outputs, labels)
        loss.backward()
        optimizer.step()

        # print statistics
        running_loss += loss.item()
        if i % 2000 == 1999:    # print every 2000 mini-batches
            print(f'[{epoch + 1}, {i + 1:5d}] loss: {running_loss / 2000:.3f}')
            running_loss = 0.0

print('Finished Training')
[1,  2000] loss: 2.189
[1,  4000] loss: 1.903
[1,  6000] loss: 1.683
[1,  8000] loss: 1.585
[1, 10000] loss: 1.508
[1, 12000] loss: 1.507
[2,  2000] loss: 1.410
[2,  4000] loss: 1.397
[2,  6000] loss: 1.356
[2,  8000] loss: 1.334
[2, 10000] loss: 1.294
[2, 12000] loss: 1.287
Finished Training

訓練されたモデルを素早くセーブしましょう :

PATH = './cifar_net.pth'
torch.save(net.state_dict(), PATH)

PyTorch モデルのセーブについての詳細は ここ を見てください。

 

5. テストデータ上でネットワークをテストする

ネットワークを訓練データセットに対して 2 パスの間訓練しました。しかしネットワークが何かを学習したかどうかを確認する必要があります。

ニューラルネットワークが出力するクラスラベルを予測して、それを正解に対してチェックすることでこれを確認します。予測が正しければ、サンプルを正解予測のリストに追加します。

オーケー、最初のステップです。馴染むためにテストセットから画像を表示しましょう。

dataiter = iter(testloader)
images, labels = next(dataiter)

# print images
imshow(torchvision.utils.make_grid(images))
print('GroundTruth: ', ' '.join(f'{classes[labels[j]]:5s}' for j in range(4)))

GroundTruth:    cat  ship  ship plane

次に、セーブされたモデルをロードし戻しましょう (note: モデルのセーブと再ロードはここでは必要ありませんでした、それをどのように行なうかを示すためにそれを行なっただけです) :

net = Net()
net.load_state_dict(torch.load(PATH))
<All keys matched successfully>

オーケー、さてニューラルネットワークがこれらの上のサンプルを何であると考えているかを見てみましょう :

outputs = net(images)

出力は 10 クラスのためのエネルギーです。クラスに対するエネルギーが高いほど、ネットワークは画像が特定のクラスに所属するとより考えています。そこで、最も高いエネルギーのインデックスを取得しましょう :

_, predicted = torch.max(outputs, 1)

print('Predicted: ', ' '.join(f'{classes[predicted[j]]:5s}'
                              for j in range(4)))
Predicted:    cat  ship  ship plane

結果はかなり良いようです。

ネットワークがデータセット全体の上でどのように上手く遂行するかを見ましょう。

correct = 0
total = 0
# since we're not training, we don't need to calculate the gradients for our outputs
with torch.no_grad():
    for data in testloader:
        images, labels = data
        # calculate outputs by running images through the network
        outputs = net(images)
        # the class with the highest energy is what we choose as prediction
        _, predicted = torch.max(outputs.data, 1)
        total += labels.size(0)
        correct += (predicted == labels).sum().item()

print(f'Accuracy of the network on the 10000 test images: {100 * correct // total} %')
Accuracy of the network on the 10000 test images: 55 %

それはただの偶然よりも良いようです、それは 10 % の精度です (10 クラスから無作為に一つのクラスを選択)。ネットワークは何かを学習したようです。

Hmmm, what are the classes that performed well, and the classes that did not perform well:

# prepare to count predictions for each class
correct_pred = {classname: 0 for classname in classes}
total_pred = {classname: 0 for classname in classes}

# again no gradients needed
with torch.no_grad():
    for data in testloader:
        images, labels = data
        outputs = net(images)
        _, predictions = torch.max(outputs, 1)
        # collect the correct predictions for each class
        for label, prediction in zip(labels, predictions):
            if label == prediction:
                correct_pred[classes[label]] += 1
            total_pred[classes[label]] += 1


# print accuracy for each class
for classname, correct_count in correct_pred.items():
    accuracy = 100 * float(correct_count) / total_pred[classname]
    print(f'Accuracy for class: {classname:5s} is {accuracy:.1f} %')
Accuracy for class: plane is 54.7 %
Accuracy for class: car   is 79.0 %
Accuracy for class: bird  is 34.4 %
Accuracy for class: cat   is 24.8 %
Accuracy for class: deer  is 41.5 %
Accuracy for class: dog   is 52.4 %
Accuracy for class: frog  is 75.5 %
Accuracy for class: horse is 59.0 %
Accuracy for class: ship  is 81.7 %
Accuracy for class: truck is 48.2 %

オーケー、さて次は何でしょう?

これらのニューラルネットワークを GPU 上でどのように実行するのでしょう?

 

GPU 上で訓練する

ちょうどテンソルを GPU 上にどのように転送するかのように、ニューラルネットワークを GPU 上に転送します。

利用可能な CUDA を持つ場合、device を最初の可視な cuda デバイスとしてまずは定義しましょう :

device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')

# Assuming that we are on a CUDA machine, this should print a CUDA device:

print(device)
cuda:0

このセクションの残りは device が CUDA デバイスであると仮定します。

それからこれらのメソッドは総てのモジュールに対して再帰的に進み、そしてそれらのパラメータとバッファを CUDA テンソルに変換します :

net.to(device)

入力とターゲットを総てのステップで GPU に送らなければならないことも忘れないでください :

inputs, labels = data[0].to(device), data[1].to(device)

CPU と比較して何故「大幅な」スピードアップに気がつかないのでしょう?それは貴方のネットワークが非常に小さいからです。

課題:

貴方のネットワークの幅を増やしてみましょう (最初の nn.Conv2d の引数 2、そして 2 番目の nn.Conv2d の引数 1 – それらは同じ数である必要があります)、そしてどのようなスピードアップが得られるか見てみましょう。

達成された目標:

  • PyTorch のテンソル・ライブラリと高位のニューラルネットワークを理解する。
  • 画像を分類するために小さなニューラルネットワークを訓練する。

 

複数の GPU 上で訓練する

GPU の総てを使用して更に「大幅な」高速化を見ることを望むのであれば、オプションの: データ並列 を確認してください。

 

次にどこへ行きますか?

del dataiter

 

以上