PyTorch を使用して画像分類子モデルをトレーニングする

このチュートリアルの前の段階では、PyTorch を使用して画像分類子をトレーニングするためのデータセットを手に入れました。 今度は、そのデータを使用する番です。

PyTorch を使用して画像分類子をトレーニングするには、次の手順を実行する必要があります。

  1. データを読み込みます。 このチュートリアルの前の手順を完了している場合は、既にこれに対応しています。
  2. 畳み込みニューラルネットワークを定義します。
  3. loss 関数を定義します。
  4. トレーニング データに対してモデルをトレーニングします。
  5. テスト データに対してネットワークをテストします。

畳み込みニューラルネットワークを定義します。

PyTorch でニューラル ネットワークを構築するには、torch.nn パッケージを使用します。 このパッケージには、モジュール、拡張可能なクラス、ニューラル ネットワークの構築に必要なすべてのコンポーネントが含まれています。

ここでは、画像を分類するための基本的な畳み込みニューラルネットワーク (CNN) を構築し、CIFAR10 データセットの画像を分類します。

CNN はニューラル ネットワークの一種であり、データの複雑な特徴量を検出するために設計された多層ニューラル ネットワークと定義されています。 これらが最もよく使用されるのは、Custom Vision アプリケーションです。

このネットワークは、次の 14 層で構成されます。

Conv -> BatchNorm -> ReLU -> Conv -> BatchNorm -> ReLU -> MaxPool -> Conv -> BatchNorm -> ReLU -> Conv -> BatchNorm -> ReLU -> Linear

畳み込み層

畳み込み層は CNN のメイン層であり、画像の特徴量を検出するのに役立ちます。 各層には、画像の特定の特徴量を検出するためのいくつかのチャネルと、検出された特徴量のサイズを定義するためのいくつかのカーネルがあります。 そのため、64 個のチャネルを持ちカーネル サイズが 3 × 3 の畳み込み層の場合、それぞれのサイズが 3 × 3 である 64 個の異なる特徴量が検出されます。 畳み込み層を定義する際には、インチャネル数、アウトチャネル数、カーネル サイズを指定します。 その層のアウトチャネル数は、次の層へのインチャネル数として機能します。

例: in-channels=3、out-channels=10、kernel-size=6 の畳み込み層の場合、入力として RGB 画像 (3 チャネル) が取得され、カーネル サイズが 6 x 6 の画像に 10 個の特徴量検出器が適用されます。 カーネル サイズを小さくすると、計算時間と重み付けの共有が減ります。

その他の層

このネットワークには、次のような他の層が関与しています。

  • ReLU 層は、受信するすべての特徴量を 0 以上に定義するアクティブ化関数です。 この層を適用すると、0 未満の数値は 0 に変更され、それ以外はそのままになります。
  • BatchNorm2d 層により、入力に正規化が適用され、平均と単位の分散が 0 になり、ネットワークの正確性が高まります。
  • MaxPool 層を使用すると、画像内のオブジェクトの位置が、そのオブジェクトの特定の特徴量を検出するニューラル ネットワークの能力に影響を与えないようにすることができます。
  • Linear 層は、各クラスのスコアを計算するこのネットワークの最終層です。 CIFAR10 データセットには、10 クラスのラベルがあります。 スコアが最も高いラベルが、モデルによって予測されるラベルになります。 線形層の場合、クラス数に対応する入力特徴量の数と出力特徴量の数を指定する必要があります。

ニューラル ネットワークのしくみ

CNN はフィード転送ネットワークです。 トレーニング プロセス中に、ネットワークによって入力がすべての層で処理され、画像の予測ラベルが正しいものからどれだけ離れているかを理解するために損失が計算され、勾配がネットワークに伝播され、層の重みが更新されます。 ネットワークにより、入力の膨大なデータセットを反復処理することで、最良の結果が得られるように重みを設定することが "学習" されます。

forward 関数を使用して loss 関数の値を計算し、backward 関数を使用して学習可能なパラメーターの勾配を計算します。 PyTorch を使用してニューラル ネットワークを作成する際に必要なのは、forward 関数を定義することだけです。 backward 関数は自動的に定義されます。

  1. Visual Studio で次のコードを PyTorchTraining.py ファイルにコピーして、CCN を定義します。
import torch
import torch.nn as nn
import torchvision
import torch.nn.functional as F

# Define a convolution neural network
class Network(nn.Module):
    def __init__(self):
        super(Network, self).__init__()
        
        self.conv1 = nn.Conv2d(in_channels=3, out_channels=12, kernel_size=5, stride=1, padding=1)
        self.bn1 = nn.BatchNorm2d(12)
        self.conv2 = nn.Conv2d(in_channels=12, out_channels=12, kernel_size=5, stride=1, padding=1)
        self.bn2 = nn.BatchNorm2d(12)
        self.pool = nn.MaxPool2d(2,2)
        self.conv4 = nn.Conv2d(in_channels=12, out_channels=24, kernel_size=5, stride=1, padding=1)
        self.bn4 = nn.BatchNorm2d(24)
        self.conv5 = nn.Conv2d(in_channels=24, out_channels=24, kernel_size=5, stride=1, padding=1)
        self.bn5 = nn.BatchNorm2d(24)
        self.fc1 = nn.Linear(24*10*10, 10)

    def forward(self, input):
        output = F.relu(self.bn1(self.conv1(input)))      
        output = F.relu(self.bn2(self.conv2(output)))     
        output = self.pool(output)                        
        output = F.relu(self.bn4(self.conv4(output)))     
        output = F.relu(self.bn5(self.conv5(output)))     
        output = output.view(-1, 24*10*10)
        output = self.fc1(output)

        return output

# Instantiate a neural network model 
model = Network()

Note

PyTorch を使用したニューラル ネットワークの詳細については関心がある場合は PyTorch のドキュメントを参照してください。

loss 関数を定義する

loss 関数により、出力が目標からどれだけ離れているかを推定する値が計算されます。 この主な目的は、ニューラル ネットワークで逆伝搬法によって重みのベクトル値を変更することで、loss 関数の値を小さくすることです。

loss 値はモデルの正確性とは異なります。 loss 関数を使用すると、トレーニング セットに対する最適化の各反復処理の後に、モデルがどれ程度適切に動作するかを理解できます。 モデルの正確性は、テスト データに対して計算され、正しい予測の割合を示します。

PyTorch のニューラル ネットワーク パッケージには、ディープ ニューラル ネットワークの構成要素となるさまざまな loss 関数が含まれています。 このチュートリアルでは、Classification Cross-Entropy loss と Adam Optimizer を使用した loss 関数の定義に基づいて、Classification loss 関数を使用します。 学習率 (lr) には、損失の勾配に対してネットワークの重みをどの程度制御するかが設定されます。 ここでは 0.001 に設定します。 これを低くするほど、トレーニング速度は遅くなります。

  1. Visual Studio で次のコードを PyTorchTraining.py ファイルにコピーし、loss 関数とオプティマイザーを定義します。
from torch.optim import Adam
 
# Define the loss function with Classification Cross-Entropy loss and an optimizer with Adam optimizer
loss_fn = nn.CrossEntropyLoss()
optimizer = Adam(model.parameters(), lr=0.001, weight_decay=0.0001)

トレーニング データに対してモデルをトレーニングします。

モデルをトレーニングするには、データ反復子をループさせ、ネットワークに入力をフィードし、最適化する必要があります。 PyTorch に GPU 専用ライブラリはありませんが、実行デバイスを手動で定義することができます。 デバイスは、マシンに存在する場合は Nvidia GPU になり、存在しない場合は CPU になります。

  1. 次のコードを PyTorchTraining.py ファイルに追加します
from torch.autograd import Variable

# Function to save the model
def saveModel():
    path = "./myFirstModel.pth"
    torch.save(model.state_dict(), path)

# Function to test the model with the test dataset and print the accuracy for the test images
def testAccuracy():
    
    model.eval()
    accuracy = 0.0
    total = 0.0
    device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
    
    with torch.no_grad():
        for data in test_loader:
            images, labels = data
            # run the model on the test set to predict labels
            outputs = model(images.to(device))
            # the label with the highest energy will be our prediction
            _, predicted = torch.max(outputs.data, 1)
            total += labels.size(0)
            accuracy += (predicted == labels.to(device)).sum().item()
    
    # compute the accuracy over all test images
    accuracy = (100 * accuracy / total)
    return(accuracy)


# Training function. We simply have to loop over our data iterator and feed the inputs to the network and optimize.
def train(num_epochs):
    
    best_accuracy = 0.0

    # Define your execution device
    device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
    print("The model will be running on", device, "device")
    # Convert model parameters and buffers to CPU or Cuda
    model.to(device)

    for epoch in range(num_epochs):  # loop over the dataset multiple times
        running_loss = 0.0
        running_acc = 0.0

        for i, (images, labels) in enumerate(train_loader, 0):
            
            # get the inputs
            images = Variable(images.to(device))
            labels = Variable(labels.to(device))

            # zero the parameter gradients
            optimizer.zero_grad()
            # predict classes using images from the training set
            outputs = model(images)
            # compute the loss based on model output and real labels
            loss = loss_fn(outputs, labels)
            # backpropagate the loss
            loss.backward()
            # adjust parameters based on the calculated gradients
            optimizer.step()

            # Let's print statistics for every 1,000 images
            running_loss += loss.item()     # extract the loss value
            if i % 1000 == 999:    
                # print every 1000 (twice per epoch) 
                print('[%d, %5d] loss: %.3f' %
                      (epoch + 1, i + 1, running_loss / 1000))
                # zero the loss
                running_loss = 0.0

        # Compute and print the average accuracy fo this epoch when tested over all 10000 test images
        accuracy = testAccuracy()
        print('For epoch', epoch+1,'the test accuracy over the whole test set is %d %%' % (accuracy))
        
        # we want to save the model if the accuracy is the best
        if accuracy > best_accuracy:
            saveModel()
            best_accuracy = accuracy

テスト データに対してモデルをテストします。

これで、テスト セットの画像のバッチを使用してモデルをテストすることができます。

  1. 次のコードを PyTorchTraining.py ファイルに追加します。
import matplotlib.pyplot as plt
import numpy as np

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


# Function to test the model with a batch of images and show the labels predictions
def testBatch():
    # get batch of images from the test DataLoader  
    images, labels = next(iter(test_loader))

    # show all images as one image grid
    imageshow(torchvision.utils.make_grid(images))
   
    # Show the real labels on the screen 
    print('Real labels: ', ' '.join('%5s' % classes[labels[j]] 
                               for j in range(batch_size)))
  
    # Let's see what if the model identifiers the  labels of those example
    outputs = model(images)
    
    # We got the probability for every 10 labels. The highest (max) probability should be correct label
    _, predicted = torch.max(outputs, 1)
    
    # Let's show the predicted labels on the screen to compare with the real ones
    print('Predicted: ', ' '.join('%5s' % classes[predicted[j]] 
                              for j in range(batch_size)))

最後に、メイン コードを追加します。 これで、モデルのトレーニングが開始され、モデルが保存され、結果が画面に表示されます。 ここではトレーニング セットに対して [train(2)] という 2 回の反復処理を実行するだけなので、トレーニング プロセスにはそれほど時間はかかりません。

  1. 次のコードを PyTorchTraining.py ファイルに追加します。
if __name__ == "__main__":
    
    # Let's build our model
    train(5)
    print('Finished Training')

    # Test which classes performed well
    testAccuracy()
    
    # Let's load the model we just created and test the accuracy per label
    model = Network()
    path = "myFirstModel.pth"
    model.load_state_dict(torch.load(path))

    # Test with batch of images
    testBatch()

テストを実行しましょう。 上部のツール バーのドロップダウン メニューが [デバッグ] に設定されていることを確認します。 ローカル コンピューター上でプロジェクトを実行するように、デバイスが 64 ビットの場合は x64 に、32 ビットの場合は x86 にソリューション プラットフォームを変更します。

エポック数 (トレーニング データセットを完全に処理する回数) を 2 ([train(2)]) に設定すると、10,000 枚の画像からなるテスト データセット全体が 2 回反復処理されます。 第 8 世代の Intel CPU 上でトレーニングを完了するには約 20 分かかり、10 個のラベルを分類する際のモデルの成功率は 65% 程度になるはずです。

  1. プロジェクトを実行するには、ツール バーの [デバッグの開始] ボタンをクリックするか、F5 キーを押します。

コンソール ウィンドウがポップアップ表示され、トレーニングのプロセスを確認できます。

定義したとおり、1,000 バッチの画像ごとに、またはトレーニング セットの反復処理ごとに、loss 値は 5 回出力されます。 loss 値はループごとに減少すると予想されます。

また、各反復処理の後にモデルの正確性も確認できます。 モデルの正確性は、loss 値とは異なります。 loss 関数を使用すると、トレーニング セットに対する最適化の各反復処理の後に、モデルがどれ程度適切に動作するかを理解できます。 モデルの正確性は、テスト データに対して計算され、正しい予測の割合を示します。 この例では、トレーニングの各反復処理の後に、モデルによって 10,000 枚のテスト セットから正しく分類することができた画像数が表示されます。

トレーニングが完了すると、次のような出力が表示されます。 分類はさまざまな要因に左右されるため、常に同じ結果が得られるわけではありませんが、似たような結果が得られるはずです。

Output from initial model training

わずか 5 エポックを実行しただけで、モデルの成功率は 70% になります。 これは、短時間でトレーニングした基本的なモデルとしては良い結果です。

画像のバッチを使用してテストし、モデルにより、10 枚のバッチから 7 枚の画像が正しく認識されました。 これは、モデルの成功率と一致しており、悪くはありません。

Successfully classified images

モデルが最適な予測を行うことができるクラスを確認できます。 次のコードを追加して実行するだけです。

  1. 省略可能 - 次の testClassess 関数を PyTorchTraining.py ファイルに追加し、この関数 (main 関数 __name__ == "__main__" 内の testClassess()) の呼び出しを追加します。
# Function to test what classes performed well
def testClassess():
    class_correct = list(0. for i in range(number_of_labels))
    class_total = list(0. for i in range(number_of_labels))
    with torch.no_grad():
        for data in test_loader:
            images, labels = data
            outputs = model(images)
            _, predicted = torch.max(outputs, 1)
            c = (predicted == labels).squeeze()
            for i in range(batch_size):
                label = labels[i]
                class_correct[label] += c[i].item()
                class_total[label] += 1

    for i in range(number_of_labels):
        print('Accuracy of %5s : %2d %%' % (
            classes[i], 100 * class_correct[i] / class_total[i]))

出力は次のようになります。

Initial classification accuracy

次のステップ

分類モデルができたので、次の手順はモデルを ONNX 形式に変換することです