CNN 画像分類 (2)

PyTorch を用いることで、様々なニューラルネットワークを容易に構築できる。このページでは、画像分類を行うための畳み込みニューラルネットワークを PyTorch で作成して、エポックごとに学習と検証を行う例を示す。

画像データセット

サンプル画像として Kaggle にある Blood Cell Images を使用する。このデータセットをダウンロードして展開すると、dataset-master と dataset2-master の二つのフォルダが得られる。ここでは、dataset2-master フォルダの下の images フォルダの画像を使用する。

ls dataset2-master/dataset2-master/images
## TEST        TEST_SIMPLE TRAIN

ls dataset2-master/dataset2-master/images/TRAIN
## EOSINOPHIL LYMPHOCYTE MONOCYTE   NEUTROPHIL

ls dataset2-master/dataset2-master/images/TEST
## EOSINOPHIL LYMPHOCYTE MONOCYTE   NEUTROPHIL

このデータセットは EOSINOPHIL、LYMPHOCYTE、MONOCYTE、および NEUTROPHIL の 4 つのカテゴリに整理されている。これらのデータを使ってモデル構築を行う。次のようにして、画像に対する前処理を定義し、PyTorch の ImageFolder 関数と DataLoader 関数で画像の読み込み準備を行う。

あとで学習と検証をエポックごとに繰り返したいので、for 構文で処理できるように学習データと検証データを一つのディクショナリにまとめる。

data_transforms = {
    'train': torchvision.transforms.Compose([
        torchvision.transforms.Resize((240, 320)),
        torchvision.transforms.RandomHorizontalFlip(),
        torchvision.transforms.ToTensor(),
        torchvision.transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
    ]),
    'val': torchvision.transforms.Compose([
        torchvision.transforms.Resize((240, 320)),
        torchvision.transforms.ToTensor(),
        torchvision.transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
    ]),
}

data_dir_path = 'dataset2-master/dataset2-master/images/'
image_datasets = {
    'train': torchvision.datasets.ImageFolder(root=data_dir_path + 'TRAIN',
                                              transform=data_transforms['train']),
    'val':   torchvision.datasets.ImageFolder(root=data_dir_path + 'TEST',
                                              transform=data_transforms['val'])
}
dataloaders = {
    'train': torch.utils.data.DataLoader(image_datasets['train'], batch_size=4, shuffle=True),
    'val': torch.utils.data.DataLoader(image_datasets['val'], batch_size=4)
}
dataset_sizes = {
    'train': len(image_datasets['train']),
    'val': len(image_datasets['val'])
}
class_names = image_datasets['train'].classes

CNN モデルの定義

CNN モデルのアーキテクチャを定義する。ここでは、畳み込み層、プーリング層、畳み込み層、プーリング層、全結合層、全結合層、そして出力層の順でアーキテクチャを定義する。アーキテクチャの定義方法はアーキテクチャを参考してください。また、VGG16 などのような有名なアーキテクチャを使用する場合は、torchvision パッケージに実装されているアーキテクチャを呼び出して使うと便利である。詳しくは VGG16 画像分類を参考してください。

class Net(torch.nn.Module):

    def __init__(self):
        super(Net, self).__init__()
        self.conv1 = torch.nn.Conv2d(3, 16, 5)
        self.pool1 = torch.nn.MaxPool2d(2, 2)
        self.conv2 = torch.nn.Conv2d(16, 32, 5)
        self.pool2 = torch.nn.MaxPool2d(2, 2)
        self.fc1 = torch.nn.Linear(32 * 57 * 77, 512)
        self.fc2 = torch.nn.Linear(512, 64)
        self.fc3 = torch.nn.Linear(64, 4)


    def forward(self, x):
        x = torch.nn.functional.relu(self.conv1(x))
        x = self.pool1(x)
        x = torch.nn.functional.relu(self.conv2(x))
        x = self.pool2(x)
        x = x.view(-1, 32 * 57 * 77)
        x = torch.nn.functional.relu(self.fc1(x))
        x = torch.nn.functional.relu(self.fc2(x))
        x = self.fc3(x)
        return x

学習・検証用の関数の定義

学習・検証用を行いやすいように、これら一通りの手順を関数として定義する。この際に、学習するためのパラメーターである損失関数 criterion、重み更新アルゴリズム optimizer、学習率の調整スケジューラー scheduler やエポック数 num_epochs が与えられた仮定のもとで、学習・検証用の関数を定義する。

import time
import copy
import torch
import torchvision


def train_model(model, dataloaders, criterion, optimizer, scheduler, num_epochs):
    since = time.time()
    
    # save the best model during learning
    best_model_wts = copy.deepcopy(model.state_dict())
    best_acc = 0.0
    
    for epoch in range(num_epochs):
        print('Epoch {}/{}'.format(epoch, num_epochs - 1))
        print('-' * 10)

        for phase in ['train', 'val']:
            if phase == 'train':
                model.train()  # training mode
            else:
                model.eval()   # evaluate mode

            running_loss = 0.0
            running_corrects = 0

            for inputs, labels in dataloaders[phase]:
                inputs = inputs.to(device)
                labels = labels.to(device)

                optimizer.zero_grad()
                with torch.set_grad_enabled(phase == 'train'):
                    outputs = model(inputs)
                    _, preds = torch.max(outputs, 1)

                    loss = criterion(outputs, labels)

                    if phase == 'train':
                        loss.backward()
                        optimizer.step()

                running_loss += loss.item() * inputs.size(0)
                running_corrects += torch.sum(preds == labels.data)


            if phase == 'train':
                scheduler.step()

            epoch_loss = running_loss / dataset_sizes[phase]
            epoch_acc = running_corrects.double() / dataset_sizes[phase]

            print('{} Loss: {:.4f} Acc: {:.4f}'.format(phase, epoch_loss, epoch_acc))

            if phase == 'val' and epoch_acc > best_acc:
                best_acc = epoch_acc
                best_model_wts = copy.deepcopy(model.state_dict())

    time_elapsed = time.time() - since
    print('Training complete in {:.0f}m {:.0f}s'.format(time_elapsed // 60, time_elapsed % 60))
    print('Best val Acc: {:4f}'.format(best_acc))

    model.load_state_dict(best_model_wts)
    return model

学習・検証

学習用のパラメーターなどを定義し、学習と検証を繰り返す。

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

net = Net()
net = net.to(device)

criterion = torch.nn.CrossEntropyLoss()
optimizer = torch.optim.SGD(net.parameters(), lr=0.001, momentum=0.9)
scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=7, gamma=0.1)

model = train_model(net, dataloaders, criterion, optimizer, scheduler, num_epochs=50)

学習と検証が終わってから、このモデルが期待していた精度に達していることを確認したら、最後に(学習・検証画像とは独立に集められた)テスト画像を使って、テスト(最小検証)を行う。ここでは、テストを省略する。

学習済みのモデルを保存するときは torch.save 関数を使用して、モデルの重みを保存する。

torch.save(self.model.state_dict(), weights_fpath)