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)