<jupyter_start><jupyter_text>1.3「転移学習」で少量データの分類を実現する方法- 本ファイルでは、学習済みのVGGモデルを使用し、転移学習でアリとハチの画像を分類するモデルを学習します  学習目標1. 画像データからDatasetを作成できるようになる2. DataSetからDataLoaderを作成できるようになる3. 学習済みモデルの出力層を任意の形に変更できるようになる4. 出力層の結合パラメータのみを学習させ、転移学習が実装できるようになる  事前準備1. 書籍の指示に従い、本章で使用するデータをダウンロード2. forループの経過時間と残り時間を計測するパッケージtqdmをインストールします。conda install -c conda-forge tqdm<jupyter_code>%load_ext autoreload
%autoreload 2
%load_ext blackcellmagic
import glob
import os.path as osp
from pathlib import Path
import random
import numpy as np
import json
from PIL import Image
from tqdm import tqdm_notebook as tqdm
import matplotlib.pyplot as plt
%matplotlib inline

import torch
import torch.nn as nn
import torch.optim as optim
import torch.utils.data as data
import torchvision
from torchvision import models, transforms
import logging
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger()
logger.setLevel(logging.INFO)<jupyter_output><empty_output><jupyter_text>パッケージのimportimport globimport os.path as ospimport randomimport numpy as npimport jsonfrom PIL import Imagefrom tqdm import tqdmimport matplotlib.pyplot as plt%matplotlib inlineimport torchimport torch.nn as nnimport torch.optim as optimimport torch.utils.data as dataimport torchvisionfrom torchvision import models, transforms<jupyter_code>seed = 1234
torch.manual_seed(seed)
np.random.seed(seed)
random.seed(seed)<jupyter_output><empty_output><jupyter_text>DataSetを作成<jupyter_code>from pytorch_advanced.c01_vgg.transfer_learning import ImageTransform<jupyter_output><empty_output><jupyter_text>入力画像の前処理をするクラス 訓練時と推論時で処理が異なるclass ImageTransform():    """    画像の前処理クラス。訓練時、検証時で異なる動作をする。    画像のサイズをリサイズし、色を標準化する。    訓練時はRandomResizedCropとRandomHorizontalFlipでデータオーギュメンテーションする。    Attributes    ----------    resize : int        リサイズ先の画像の大きさ。    mean : (R, G, B)        各色チャネルの平均値。    std : (R, G, B)        各色チャネルの標準偏差。    """    def __init__(self, resize, mean, std):        self.data_transform = {            'train': transforms.Compose([                transforms.RandomResizedCrop(                    resize, scale=(0.5, 1.0)),   データオーギュメンテーション                transforms.RandomHorizontalFlip(),   データオーギュメンテーション                transforms.ToTensor(),   テンソルに変換                transforms.Normalize(mean, std)   標準化            ]),            'val': transforms.Compose([                transforms.Resize(resize),   リサイズ                transforms.CenterCrop(resize),   画像中央をresize×resizeで切り取り                transforms.ToTensor(),   テンソルに変換                transforms.Normalize(mean, std)   標準化            ])        }    def __call__(self, img, phase='train'):        """        Parameters        ----------        phase : 'train' or 'val'            前処理のモードを指定。        """        return self.data_transform[phase](img)<jupyter_code>image_file_path = "./data/goldenretriever-3724972_640.jpg"
img = Image.open(image_file_path)
plt.imshow(img)
plt.show()
size= 224
mean = (0.485, 0.456, 0.406)
std = (0.229, 0.224, 0.225)

transform = ImageTransform(size, mean, std)
img_transformed = transform(img, phase="train")
print(img_transformed.shape)

img_transformed = img_transformed.numpy().transpose((1, 2, 0))
print(img_transformed.shape)
img_transformed = np.clip(img_transformed, 0, 1)
plt.imshow(img_transformed)
plt.show()<jupyter_output>torch.Size([3, 224, 224])
(224, 224, 3)<jupyter_text>3. 画像の前処理と処理済み画像の表示size = 224mean = (0.485, 0.456, 0.406)std = (0.229, 0.224, 0.225)transform = ImageTransform(size, mean, std)img_transformed = transform(img, phase="train")   torch.Size([3, 224, 224]) (色、高さ、幅)を (高さ、幅、色)に変換し、0-1に値を制限して表示img_transformed = img_transformed.numpy().transpose((1, 2, 0))img_transformed = np.clip(img_transformed, 0, 1)plt.imshow(img_transformed)plt.show()<jupyter_code>from pytorch_advanced.c01_vgg.transfer_learning import make_datapath_list
# 実行
train_list = make_datapath_list(phase="train")
val_list = make_datapath_list(phase="val")
from pytorch_advanced.c01_vgg.transfer_learning import HymenopteraDataset
# 実行
train_dataset = HymenopteraDataset(
    file_list=train_list, transform=ImageTransform(size, mean, std), phase="train"
)

val_dataset = HymenopteraDataset(
    file_list=val_list, transform=ImageTransform(size, mean, std), phase="val"
)
# 動作確認
index = 0
print(train_dataset[index][0].size())
print(train_dataset[index][1])<jupyter_output>torch.Size([3, 224, 224])
1<jupyter_text>DataLoader with minibatch を作成<jupyter_code>batch_size = 32
train_dataloader = data.DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
val_dataloader = data.DataLoader(val_dataset, batch_size=batch_size, shuffle=True)

dataloader_dict = {"train": train_dataloader, "val": val_dataloader}

batch_iterator = iter(dataloader_dict['train'])
inputs, labels = next(batch_iterator)
print(inputs.size())
print(labels)<jupyter_output>torch.Size([32, 3, 224, 224])
tensor([1, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 1, 1, 1, 1, 1,
        1, 1, 0, 0, 1, 1, 0, 1])<jupyter_text>ネットワークモデルの作成する<jupyter_code>pretrained = True
net = models.vgg16(pretrained=pretrained)

net.classifier[6] = nn.Linear(in_features=4096, out_features=2)

net.train()
print('ネットワーク設定完了：学習済みの重みをロードし、訓練モードに設定しました')<jupyter_output>ネットワーク設定完了：学習済みの重みをロードし、訓練モードに設定しました<jupyter_text>損失関数を定義<jupyter_code>criterion = nn.CrossEntropyLoss()<jupyter_output><empty_output><jupyter_text>最適化手法を設定<jupyter_code>params_to_update = []
update_param_names = ["classifier.6.weight", "classifier.6.bias"]

for name, param in net.named_parameters():
    if name in update_param_names:
        param.requires_grad = True
        params_to_update.append(param)
        logger.info(name)
    else:
        param.requires_grad = False

logger.info("-"*10)
logger.info(params_to_update)<jupyter_output>INFO:root:classifier.6.weight
INFO:root:classifier.6.bias
INFO:root:----------
INFO:root:[Parameter containing:
tensor([[-0.0066, -0.0145, -0.0063,  ...,  0.0099,  0.0031, -0.0011],
        [-0.0065,  0.0083,  0.0045,  ..., -0.0134, -0.0079, -0.0041]],
       requires_grad=True), Parameter containing:
tensor([-0.0045, -0.0126], requires_grad=True)]<jupyter_text>転移学習で学習させるパラメータを、変数params_to_updateに格納するparams_to_update = [] 学習させるパラメータ名update_param_names = ["classifier.6.weight", "classifier.6.bias"] 学習させるパラメータ以外は勾配計算をなくし、変化しないように設定for name, param in net.named_parameters():    if name in update_param_names:        param.requires_grad = True        params_to_update.append(param)        print(name)    else:        param.requires_grad = False params_to_updateの中身を確認print("-----------")print(params_to_update)<jupyter_code>optimizer = optim.SGD(params=params_to_update, lr=0.001, momentum=0.9)<jupyter_output><empty_output><jupyter_text>学習・検証を実施<jupyter_code># モデルを学習させる関数を作成


def train_model(net, dataloaders_dict, criterion, optimizer, num_epochs):

    # epochのループ
    for epoch in range(num_epochs):
        print('Epoch {}/{}'.format(epoch+1, num_epochs))
        print('-------------')

        # epochごとの学習と検証のループ
        for phase in ['train', 'val']:
            if phase == 'train':
                net.train()  # モデルを訓練モードに
            else:
                net.eval()   # モデルを検証モードに

            epoch_loss = 0.0  # epochの損失和
            epoch_corrects = 0  # epochの正解数

            # 未学習時の検証性能を確かめるため、epoch=0の訓練は省略
            if (epoch == 0) and (phase == 'train'):
                continue

            # データローダーからミニバッチを取り出すループ
            for inputs, labels in tqdm(dataloaders_dict[phase]):

                # optimizerを初期化
                optimizer.zero_grad()

                # 順伝搬（forward）計算
                with torch.set_grad_enabled(phase == 'train'):
                    outputs = net(inputs)
                    loss = criterion(outputs, labels)  # 損失を計算
                    _, preds = torch.max(outputs, 1)  # ラベルを予測
                    
  
                    # 訓練時はバックプロパゲーション
                    if phase == 'train':
                        loss.backward()
                        optimizer.step()

                    # イタレーション結果の計算
                    # lossの合計を更新
                    epoch_loss += loss.item() * inputs.size(0)  
                    # 正解数の合計を更新
                    epoch_corrects += torch.sum(preds == labels.data)

            # epochごとのlossと正解率を表示
            epoch_loss = epoch_loss / len(dataloaders_dict[phase].dataset)
            epoch_acc = epoch_corrects.double(
            ) / len(dataloaders_dict[phase].dataset)

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

from pytorch_advanced.c01_vgg.transfer_learning import train_model
# 学習・検証を実行する
num_epochs=2
train_model(net, dataloader_dict, criterion, optimizer, num_epochs=num_epochs)<jupyter_output>INFO:pytorch_advanced.c01_vgg.transfer_learning:Epoch 1/2
INFO:pytorch_advanced.c01_vgg.transfer_learning:--------------------
{'fraction_non_alphanumeric': 0.11903145508033491, 'fraction_numerical': 0.032360262502828696, 'mean_word_length': 3.530497180932855, 'pattern_counts': {'":': 2, '<': 39, '<?xml version=': 0, '>': 39, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'chain_length': '11', 'dir': 'jupyter-structured-clean-dedup', 'id': '334033', 'license': 'MIT', 'n_tokens_mistral': 3865, 'n_tokens_neox': 3635, 'n_words': 524, 'path': 'notebooks/1_image_classification/1-3_transfer_learning.ipynb', 'repo_name': 'banbiossa/pytorch_advanced', 'size': '8838'}