今回も、画像認識のサンプルコードを解説します。facebookが作成している、Pytorchで、アルゴリズムは王道のCNN(convolutional neural network)です。
細かいところは、省いて説明していきますので、動作することを目指しましょう!
はじめに
今回紹介するのは、サンプルコードはこちらのものです。pytorchは、tensorflow kerasに比べてコードが長くなりますが、どんな計算を行っているか具体的にイメージしやすくなりますので、ぜひ最後までお付き合いください。
Transfer Learning for Computer Vision Tutorial — PyTorch Tutorials 2.2.2+cu121 documentation
サンプルコード解説
ライブラリのインポート
# License: BSD
# Author: Sasank Chilamkurthy
from __future__ import print_function, division
import torch
import torch.nn as nn
import torch.optim as optim
from torch.optim import lr_scheduler
import torch.backends.cudnn as cudnn
import numpy as np
import torchvision
from torchvision import datasets, models, transforms
import matplotlib.pyplot as plt
import time
import os
import copy
cudnn.benchmark = True
plt.ion() # interactive mode
各種ライブラリのインポートです
- 6行目:pytorch
- 7行目:pytorchのニューラルネットワーク関連
- 8行目:最適化関連
- 9行目:最適化の学習率のスケジューラ
- 10行目:cudnnを使うためのもの。gpuを使うときの設定ですね
- 11行目:numpy
- 12行目:pytorchのコンピュータビジョン関連
- 13行目:画像のデータセット、モデル、画像変換・画像処理
- 14行目:マットプロット
- 15行目:時間計測
- 16行目:ファイル操作
- 17行目:コピー
- 19行目:cudnn使います
- 20行目:マットプロットをインタラクティブモードにします。
画像データを読み込んだり前処理を加えるところ
# Data augmentation and normalization for training
# Just normalization for validation
data_transforms = {
'train': transforms.Compose([
transforms.RandomResizedCrop(224),
transforms.RandomHorizontalFlip(),
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
]),
'val': transforms.Compose([
transforms.Resize(256),
transforms.CenterCrop(224),
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
]),
}
data_dir = 'data/hymenoptera_data'
image_datasets = {x: datasets.ImageFolder(os.path.join(data_dir, x),
data_transforms[x])
for x in ['train', 'val']}
dataloaders = {x: torch.utils.data.DataLoader(image_datasets[x], batch_size=4,
shuffle=True, num_workers=4)
for x in ['train', 'val']}
dataset_sizes = {x: len(image_datasets[x]) for x in ['train', 'val']}
class_names = image_datasets['train'].classes
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
- 3~16行目データの前処理です 4~9行目が学習データ、10~15行目が検証データに行う処理です。
- 5行目:画像からランダムな位置で切り取って、224×224pixにリサイズします。
- 6行目:ランダムに水平方向に画像を反転させます。
- 7行目:画像データをテンソルに変換します。
- 8行目:標準化方法に合わせて、数値を変換します。初めの3つが、RGBそれぞれの平均値、次の3つがRGBの標準偏差です。
- 11行目:256×256pixにリサイズします。
- 12行目:画像中心224×224pixを切り取ります
- 13行目:画像データをテンソルに変換します。
- 14行目:標準化します。
- 18行目:画像データのフォルダ
- 19~20行目:データセットを辞書形式で作成しておきます。上で作成したtransfromsを使います
- 22行目~24行目:データローダーの設定です。学習データと、検証データそれぞれで設定します。batch_sizeは一度に計算する画像データ数。shuffleは、データを取り出す順番をシャッフルするか。num_workers、計算機の数。
- 25行目:データセットのサイズを把握しておきます
- 26行目:各データのラベル名
- 28行目:デバイス設定。gpuが使えるならgpuを使います。つかえないなら、仕方がないのでcpu。
試しに画像をいくつか見てみよう
def imshow(inp, title=None):
"""Display image for Tensor."""
inp = inp.numpy().transpose((1, 2, 0))
mean = np.array([0.485, 0.456, 0.406])
std = np.array([0.229, 0.224, 0.225])
inp = std * inp + mean
inp = np.clip(inp, 0, 1)
plt.imshow(inp)
if title is not None:
plt.title(title)
plt.pause(0.001) # pause a bit so that plots are updated
# Get a batch of training data
inputs, classes = next(iter(dataloaders['train']))
# Make a grid from batch
out = torchvision.utils.make_grid(inputs)
imshow(out, title=[class_names[x] for x in classes])
- 1~11行目:画像表示する自作関数です。
- 3行目:numpyの配列を変換します。RGBの順番にします。
- 4行目:標準化した時の平均値
- 5行目:標準化した時の標準偏差
- 6行目:標準化します。
- 7行目:0~1に制限します
- 8行目:マットプロットで描画します
- 9~10行目:タイトルを表示します
- 11行目:0.001s表示を止めます。
- 15~20行目:実施に自作関数をつかって表示
- 15行目:画像を一枚づつ取り出します
- 18行目:グリッド上に画像を並べて表示します
- 20行目:自作関数で画像データを表示します
学習(の関数)
def train_model(model, criterion, optimizer, scheduler, num_epochs=25):
since = time.time()
best_model_wts = copy.deepcopy(model.state_dict())
best_acc = 0.0
for epoch in range(num_epochs):
print(f'Epoch {epoch}/{num_epochs - 1}')
print('-' * 10)
# Each epoch has a training and validation phase
for phase in ['train', 'val']:
if phase == 'train':
model.train() # Set model to training mode
else:
model.eval() # Set model to evaluate mode
running_loss = 0.0
running_corrects = 0
# Iterate over data.
for inputs, labels in dataloaders[phase]:
inputs = inputs.to(device)
labels = labels.to(device)
# zero the parameter gradients
optimizer.zero_grad()
# forward
# track history if only in train
with torch.set_grad_enabled(phase == 'train'):
outputs = model(inputs)
_, preds = torch.max(outputs, 1)
loss = criterion(outputs, labels)
# backward + optimize only if in training phase
if phase == 'train':
loss.backward()
optimizer.step()
# statistics
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(f'{phase} Loss: {epoch_loss:.4f} Acc: {epoch_acc:.4f}')
# deep copy the model
if phase == 'val' and epoch_acc > best_acc:
best_acc = epoch_acc
best_model_wts = copy.deepcopy(model.state_dict())
print()
time_elapsed = time.time() - since
print(f'Training complete in {time_elapsed // 60:.0f}m {time_elapsed % 60:.0f}s')
print(f'Best val Acc: {best_acc:4f}')
# load best model weights
model.load_state_dict(best_model_wts)
return model
- 1~57行目:学習するための関数。長いです。。。頑張って理解しましょう!
- 4行目:一番よかったモデルの重みをコピーしておきます。
- 5行目:最高精度。ゼロにしておきます。
- 7行目から、1エポックずつ計算
- 12行目:学習と検証の切り替え。学習、検証の順番です
- 13~16行目:モデル設定を学習モードか、推論モードかにする
- 18行目:lossの初期値
- 19行目:corretctsの初期値
- 22行目から、データを取り出しつつ学習または検証していきます
- 23~24行目:デバイス(cpuまたは)
- 27行目:勾配をゼロにしておきます
- 29行目からは、順方向の計算です
- 31行目:学習ならば、パラメータを調整できるようにしておきます
- 32行目:モデルに画像を入れて出力計算
- 33行目:誤差を計算します。criterionで出力と正解ラベルの比較をおこないます
- 36行目以降で逆方向の計算
- 37行目:学習中モードならば。
- 38行目:誤差逆伝搬させて、
- 39行目:重みを調整します。
- 42行目:ランニングロスを集計
- 43行目:ランニングコレクトを集計
- 44~45行目:学習モードならば、スケジューラのステップを進めます
- 47行目:今のエポックでのロスを計算
- 48行目:今のエポックでのcorrects計算
- 53行目~55行目で、ベスト解が出たらモデルをほぞんしておきます
- 54行目:ベストaccを更新
- 55行目:ベスト解を出したモデルの重みを更新
- 57行目:見やすいように開業を入れて。
- 59行目:時間を計っておきます
- 60行目:学習が終わったことを出力
- 61行目:最高精度を出力
- 64行目:モデルにベスト解の時の重みをロードしておいて。(そのままだと、最後に計算したときの重み)
- 65行目:モデルを返す
長いです。。。が、CNNの学習がどんなことをしているかよくわかりますね!
推論 画像を数枚で試してみる(自作関数)
def visualize_model(model, num_images=6):
was_training = model.training
model.eval()
images_so_far = 0
fig = plt.figure()
with torch.no_grad():
for i, (inputs, labels) in enumerate(dataloaders['val']):
inputs = inputs.to(device)
labels = labels.to(device)
outputs = model(inputs)
_, preds = torch.max(outputs, 1)
for j in range(inputs.size()[0]):
images_so_far += 1
ax = plt.subplot(num_images//2, 2, images_so_far)
ax.axis('off')
ax.set_title(f'predicted: {class_names[preds[j]]}')
imshow(inputs.cpu().data[j])
if images_so_far == num_images:
model.train(mode=was_training)
return
model.train(mode=was_training)
- 2行目:学習済みのモデル
- 3行目:モデルを推論モードにする
- 4行目:画像の通し番号
- 5行目:フィギュアのオブジェクトをつくっておいて
- 7行目:勾配は変えずに
- 8行目:データローダーから順番に画像をとってくる(バッチ数分の画像を一度に)
- 9行目:デバイスに画像をのっける
- 10行目:デバイスにラベルを乗っける
- 12行目:モデルに画像を入れて、推論実行
- 13行目:確率が一番大きいのを計算して、推論ラベルを計算
- 15行目画像を書いていきます。
- 16行目:画像の通し番号を一つ増やして
- 17行目:サブプロットの指定です。画像数//2 行、2列、で画像を表示します。
- 18行目:軸は邪魔なのでけしておきます
- 19行目:タイトルは、予測したクラス名
- 20行目:忘れちゃいけない imshowです。
- 22行目:最後の画像まで終わったら。
- 23行目:モデルを学習モードに戻しておきます
- 24行目:関数抜けます。
- 25行目:データがなかったときも。。。モデルを学習モードに戻しておきます。手が込んでますね。。。
学習と推論の自作関数を 使います。resnet18のファインチューニングです。
model_ft = models.resnet18(weights='IMAGENET1K_V1')
num_ftrs = model_ft.fc.in_features
# Here the size of each output sample is set to 2.
# Alternatively, it can be generalized to ``nn.Linear(num_ftrs, len(class_names))``.
model_ft.fc = nn.Linear(num_ftrs, 2)
model_ft = model_ft.to(device)
criterion = nn.CrossEntropyLoss()
# Observe that all parameters are being optimized
optimizer_ft = optim.SGD(model_ft.parameters(), lr=0.001, momentum=0.9)
# Decay LR by a factor of 0.1 every 7 epochs
exp_lr_scheduler = lr_scheduler.StepLR(optimizer_ft, step_size=7, gamma=0.1)
ここからは、上で作った関数を使うのでらくですよ
- 1行目:resnet18を読み込みます。イメージネットで学習済みのモデルです。
- 2行目:resnet18の特徴量の数です
- 3行目以降は、resnet18の出力を2クラス分類になるように改造します
- 5行目:全結合は、特徴量の数から、2つの出力にまとめるものにしますよ
- 9行目:精度は、クロスエントロピーです。2クラスなのでバイナリーですね。
- 12行目:オプティマイザーの設定です。シンプルなSGDで学習率0.001、モメンタム0.9です。
- 15行目:学習率のスケジューラ設定です。step_sizeが7、gammaが0.1です。ここは、初心者のかたは、気になさらず。
学習(自作関数使って)
model_conv = train_model(model_conv, criterion, optimizer_conv,
exp_lr_scheduler, num_epochs=25)
上で作成した関数を使って学習します。ファインチューニングなので、エポック数25くらいでいいのでしょう。
推論と可視化(自作関数使って)
visualize_model(model_ft)
1行で推論結果を表示できます
特徴抽出器を固定する場合(転移学習)
学習済みモデルの特徴抽出器部分の重みを変えずに、全結合部だけ追加学習するほうほうもありますよ。転移学習とか呼ばれています。
model_conv = torchvision.models.resnet18(weights='IMAGENET1K_V1')
for param in model_conv.parameters():
param.requires_grad = False
# Parameters of newly constructed modules have requires_grad=True by default
num_ftrs = model_conv.fc.in_features
model_conv.fc = nn.Linear(num_ftrs, 2)
model_conv = model_conv.to(device)
criterion = nn.CrossEntropyLoss()
# Observe that only parameters of final layer are being optimized as
# opposed to before.
optimizer_conv = optim.SGD(model_conv.fc.parameters(), lr=0.001, momentum=0.9)
# Decay LR by a factor of 0.1 every 7 epochs
exp_lr_scheduler = lr_scheduler.StepLR(optimizer_conv, step_size=7, gamma=0.1)
2~3行目が、ファインチューニングと異なるところで、重みの更新をしないように設定しています。
まとめ
いかかでしたが、少し長かったかもしれませんが、CNNの学習・推論をイメージできたのではないでしょうか?
tensorflow版の CNNのサンプルコード解説や
Tensorflow keras CNN(convolutional neural network)のサンプルコード説明 初心者向けです。
初心者の方向けに、Tensorflow keras CNN(convolutional neural network)のサンプルコードを解説します。画像認識のアルゴリズムの王道ですので、画像系AIのエンジニアを目指す方は、ぜひ参考になさってください。
CNNより主流になりつつあるVisionTransformaerの解説もよかったら参考になさってください。
Vision transformer (ViT)を用いた画像認識のコード解説。初心者向 けにtensorflow keras APIのコードをわかりやすく解説します。
画像認識のアルゴリズムで最近注目されている、Vision Transformer(ViT)のサンプルコードを解説します(Tensorflow keras API)。初心者の方にも理解しやすいように、必要以上に情報を詰め込まずに平易な文章で説明します。まずは手軽に実行してみましょう!
Pytorch、ビジョントランスフォーマー DeiTサンプルコード解説
Facebookが公開している画像分類タスク向け、VisionTransforme(ビジョントランスフォーマー)モデル、DeiTのサンプルコードを、初心者の方向けに解説します。細部の理論はとりあえずおいて、動作することを目標に解説しますので、ご活用ください。