{
"cells": [
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"import glob\r\n",
"import os.path as osp\r\n",
"import random\r\n",
"import numpy as np\r\n",
"import json\r\n",
"from PIL import Image\r\n",
"from tqdm import tqdm\r\n",
"import matplotlib.pyplot as plt\r\n",
"%matplotlib inline\r\n",
"import csv\r\n",
"\r\n",
"import torch\r\n",
"import torch.nn as nn\r\n",
"import torch.optim as optim\r\n",
"import torch.utils.data as data\r\n",
"import torchvision\r\n",
"from torchvision import models, transforms\r\n",
"#乱数のシードを設定\r\n",
"torch.manual_seed(1235)\r\n",
"np.random.seed(1235)\r\n",
"random.seed(1235)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# 入力画像の前処理クラス\n",
"# 訓練時と推論時で処理を変える\n",
"class ImageTransform():\n",
" \"\"\"\n",
" 画像の前処理クラス.訓練時と推論時で処理が異なる.\n",
" データ前処理:画像のリサイズ,色の標準化.\n",
" 訓練時データ拡張:RandomResezedCropとRandomHorizontalFlip\n",
" \n",
" Attributes\n",
" ----------\n",
" resize : int\n",
" リサイズの大きさ\n",
" mean : (R, G, B)\n",
" 各色チャネルの平均値\n",
" std : (R, G, B)\n",
" 各色チャネルの標準偏差\n",
" \"\"\"\n",
"\n",
" def __init__(self, resize, mean, std):\n",
" self.data_transform = {\n",
" 'train': transforms.Compose([\n",
" transforms.RandomResizedCrop(\n",
" resize, scale=(0.5, 1.0)),\n",
" transforms.RandomHorizontalFlip(),\n",
" transforms.ToTensor(), # Torchテンソルに変換\n",
" transforms.Normalize(mean, std) # 色の標準化\n",
" ]),\n",
" 'val': transforms.Compose([\n",
" transforms.Resize(resize),\n",
" transforms.CenterCrop(resize),\n",
" transforms.ToTensor(), # Torchテンソルに変換\n",
" transforms.Normalize(mean, std) # 色の標準化\n",
" ]),\n",
" }\n",
" \n",
" def __call__(self, img, phase='train'):\n",
" \"\"\"\n",
" Parameters\n",
" ----------\n",
" phase : 'train' or 'val'\n",
" 前処理のモード指定\n",
" \"\"\" \n",
" return self.data_transform[phase](img)"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# 訓練時の画像前処理の動作確認\r\n",
"# 実行するたびに処理結果画像が変わる\r\n",
"\r\n",
"# # 1. 画像読み込み\r\n",
"# image_file_path = './data/goldenretriever-3724972_640.jpg'\r\n",
"# img = Image.open(image_file_path) # [高さ][幅][チャネル]\r\n",
"\r\n",
"# # 2. 元画像の表示\r\n",
"# plt.imshow(img)\r\n",
"# plt.show()\r\n",
"\r\n",
"# 3. 画像の前処理\r\n",
"size = 224\r\n",
"mean = (0.485, 0.456, 0.406)\r\n",
"std = (0.229, 0.224, 0.225)\r\n",
"# transform = ImageTransform(size, mean, std)\r\n",
"# img_transformed = transform(img, phase=\"train\")\r\n",
"\r\n",
"# 4. (色,高さ,幅)を(高さ,幅,色)に変換し,0-1にクリップして表示\r\n",
"# img_transformed = img_transformed.numpy().transpose((1, 2, 0))\r\n",
"# img_transformed = np.clip(img_transformed, 0, 1)\r\n",
"# plt.imshow(img_transformed)\r\n",
"# plt.show()\r\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# アリと蜂の画像へのファイルパスのリストを作成\r\n",
"def make_datapath_list(phase=\"train\"):\r\n",
" \"\"\"\r\n",
" データのパスを格納したリストを作成\r\n",
"\r\n",
" Parameters\r\n",
" ----------\r\n",
" phase : 'train' or 'val'\r\n",
" 訓練データか,検証データかを指定\r\n",
" \r\n",
" Returns\r\n",
" -------\r\n",
" path_list : list\r\n",
" データへのパスを格納したリスト\r\n",
" \"\"\"\r\n",
"\r\n",
" rootpath = \"./data/ebus_data/\"\r\n",
" target_path = osp.join(rootpath + phase + '/**/*.jpg')\r\n",
" print(target_path)\r\n",
"\r\n",
" path_list = []\r\n",
"\r\n",
" # globを利用してサブディレクトリまでファイルパスを取得\r\n",
" for path in glob.glob(target_path):\r\n",
" path_list.append(path)\r\n",
" \r\n",
" return path_list\r\n",
"\r\n",
"# 実行\r\n",
"train_list = make_datapath_list(phase=\"train\")\r\n",
"val_list = make_datapath_list(phase=\"val\")\r\n",
"\r\n",
"train_list"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# EBUS画像のDatasetを作成する\r\n",
"\r\n",
"class EbusDataset(data.Dataset):\r\n",
" \"\"\"\r\n",
" EBUS画像のDatasetクラス.PyTorchのDatasetクラスを継承\r\n",
"\r\n",
" Attributes\r\n",
" ----------\r\n",
" file_list : list\r\n",
" 画像のパスを格納したリスト\r\n",
" transform : object\r\n",
" 前処理クラスのインスタンス\r\n",
" phase : 'train' or 'val'\r\n",
" 訓練か検証かを設定する\r\n",
" \"\"\"\r\n",
"\r\n",
" def __init__(self, file_list, transform=None, phase='train'):\r\n",
" self.file_list = file_list\r\n",
" self.transform = transform\r\n",
" self.phase = phase\r\n",
" \r\n",
" def __len__(self):\r\n",
" '''画像の枚数を返す'''\r\n",
" return len(self.file_list)\r\n",
" \r\n",
" def __getitem__(self, index):\r\n",
" '''\r\n",
" 前処理した画像のTensor形式のデータとラベルを取得\r\n",
" '''\r\n",
"\r\n",
" # index番目の画像をロード\r\n",
" img_path = self.file_list[index]\r\n",
" img = Image.open(img_path)\r\n",
"\r\n",
" # 画像の前処理を実施\r\n",
" img_transformed = self.transform(\r\n",
" img, self.phase)\r\n",
" \r\n",
" if self.phase == \"train\":\r\n",
" label = img_path[23:29]\r\n",
" elif self.phase == \"val\":\r\n",
" label = img_path[21:27]\r\n",
" \r\n",
" # ラベルを数値に変更\r\n",
" if label == \"Benign\":\r\n",
" label = 0\r\n",
" elif label == \"Malign\":\r\n",
" label = 1\r\n",
" \r\n",
" return img_transformed, label\r\n",
" \r\n",
"# 実行\r\n",
"train_dataset = EbusDataset(\r\n",
" file_list=train_list, transform=ImageTransform(size, mean, std), phase='train')\r\n",
"\r\n",
"val_dataset = EbusDataset(\r\n",
" file_list=val_list, transform=ImageTransform(size, mean, std), phase='val')\r\n",
"\r\n",
"# 動作確認\r\n",
"index = 0\r\n",
"print(train_dataset.__getitem__(index)[0].size())\r\n",
"print(train_dataset.__getitem__(index)[1])\r\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# ミニバッチのサイズを指定\r\n",
"batch_size = 16\r\n",
"\r\n",
"# DataLoaderを作成\r\n",
"train_dataloader = torch.utils.data.DataLoader(\r\n",
" train_dataset, batch_size=batch_size, shuffle=True)\r\n",
"\r\n",
"val_dataloader = torch.utils.data.DataLoader(\r\n",
" val_dataset, batch_size=batch_size, shuffle=False)\r\n",
"\r\n",
"# 辞書型変数にまとめる\r\n",
"dataloaders_dict = {\"train\": train_dataloader, \"val\": val_dataloader}\r\n",
"\r\n",
"# 動作確認\r\n",
"batch_iterator = iter(dataloaders_dict[\"train\"]) # イテレータに変換\r\n",
"inputs, labels = next(batch_iterator) # 最初の要素を取り出す\r\n",
"print(inputs.size())\r\n",
"print(labels)\r\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# 学習済みのVGG16モデルをロード\n",
"# VGG16モデルのインスタンスを生成\n",
"use_pretrained = True\n",
"net = models.vgg16(pretrained=use_pretrained)\n",
"\n",
"# VGG16の最後の出力層の出力ユニットをアリと蜂の2つに付け替える\n",
"net.classifier[6] = nn.Linear(in_features=4096, out_features=2)\n",
"\n",
"# 訓練モードに設定\n",
"net.train()\n",
"\n",
"print('ネットワーク設定完了:学習済みの重みをロードし,訓練モードに設定しました')\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# 損失関数の設定\n",
"criterion = nn.CrossEntropyLoss()\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# 転移学習で学習させるパラメータを,変数params_to_updateに格納\n",
"params_to_update = []\n",
"\n",
"# 学習させるパラメータ名\n",
"update_param_names = [\"classifier.6.weight\", \"classifier.6.bias\"]\n",
"\n",
"# 学習させるパラメータ以外は勾配計算せず固定\n",
"for name, param in net.named_parameters():\n",
" if name in update_param_names:\n",
" param.requires_grad = True\n",
" params_to_update.append(param)\n",
" print(name)\n",
" else:\n",
" param.requires_grad = False\n",
"\n",
"# params_to_updateの中身を確認\n",
"print(\"------------\")\n",
"print(params_to_update)\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# 最適化手法の設定\r\n",
"optimizer = optim.SGD(params=params_to_update, lr=0.001, momentum=0.9)\r\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# モデルを学習させる関数\r\n",
"\r\n",
"def train_model(net, dataloaders_dict, criterion, optimizer, num_epochs):\r\n",
"\r\n",
" # 初期設定\r\n",
" # GPU確認\r\n",
" device = torch.device(\"cuda:0\" if torch.cuda.is_available() else \"cpu\")\r\n",
" # device = torch.device(\"cpu\")\r\n",
" print(\"使用デバイス:\", device)\r\n",
"\r\n",
" # ネットワークをGPUへ\r\n",
" net.to(device)\r\n",
"\r\n",
" # ネットワークがある程度固定なら高速化\r\n",
" torch.backends.cudnn.benchmark = True\r\n",
"\r\n",
" # ログ出力\r\n",
" f = open('result.csv', 'w')\r\n",
" writer = csv.writer(f, lineterminator='\\n')\r\n",
" writer.writerow(['epoch', 'train_loss', 'train_acc', 'val_loss', 'val_acc'])\r\n",
" \r\n",
" # epochのループ\r\n",
" for epoch in range(num_epochs):\r\n",
" csvitems = []\r\n",
" csvitems.append(epoch+1)\r\n",
" print('')\r\n",
" print('Epoch {}/{}'.format(epoch+1, num_epochs))\r\n",
" print('---------------')\r\n",
"\r\n",
" # epochごとの学習と検証のループ\r\n",
" for phase in ['train', 'val']:\r\n",
" if phase == 'train':\r\n",
" net.train()\r\n",
" else:\r\n",
" net.eval()\r\n",
" \r\n",
" epoch_loss = 0.0 # epochの損失和\r\n",
" epoch_corrects = 0 # epochの正解数\r\n",
"\r\n",
" # 未学習時の性能を確かめるため epoch=0 の訓練は省略\r\n",
" if (epoch == 0) and (phase == 'train'):\r\n",
" csvitems.append(0)\r\n",
" csvitems.append(0)\r\n",
" continue\r\n",
"\r\n",
" # データローダーからミニバッチを取り出すループ\r\n",
" for inputs, labels in tqdm(dataloaders_dict[phase]):\r\n",
"\r\n",
" # GPUが使えるならGPUへデータ転送\r\n",
" inputs = inputs.to(device)\r\n",
" labels = labels.to(device)\r\n",
"\r\n",
" # optimizerを初期化\r\n",
" optimizer.zero_grad()\r\n",
"\r\n",
" # 順伝搬(forward)計算\r\n",
" with torch.set_grad_enabled(phase == 'train'):\r\n",
" outputs = net(inputs)\r\n",
" loss = criterion(outputs, labels)\r\n",
" _, preds = torch.max(outputs, 1)\r\n",
"\r\n",
" # 訓練時はバックプロパゲーション\r\n",
" if phase == 'train':\r\n",
" loss.backward()\r\n",
" optimizer.step()\r\n",
" \r\n",
" # イテレーション結果の計算\r\n",
" # lossの合計を更新\r\n",
" epoch_loss += loss.item() * inputs.size(0)\r\n",
" epoch_corrects += torch.sum(preds == labels.data)\r\n",
" \r\n",
" # epochごとのlossと正解率を表示\r\n",
" epoch_loss = epoch_loss / len(dataloaders_dict[phase].dataset)\r\n",
" epoch_acc = epoch_corrects.double() / len(dataloaders_dict[phase].dataset)\r\n",
" \r\n",
" print('')\r\n",
" print('{} Loss: {:.4f} Acc: {:.4f}'.format(\r\n",
" phase, epoch_loss, epoch_acc))\r\n",
" csvitems.append('{:.4f}'.format(epoch_loss))\r\n",
" csvitems.append('{:.4f}'.format(epoch_acc))\r\n",
" \r\n",
" writer.writerow(csvitems)\r\n",
" \r\n",
" f.close()\r\n"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"# 学習・検証を実行する\r\n",
"num_epochs = 30\r\n",
"train_model(net, dataloaders_dict, criterion, optimizer, num_epochs=num_epochs)\r\n",
"print('done.')\r\n"
]
}
],
"metadata": {
"interpreter": {
"hash": "ac59ebe37160ed0dfa835113d9b8498d9f09ceb179beaac4002f036b9467c963"
},
"kernelspec": {
"display_name": "Python 3.9.5 64-bit",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.9.5"
},
"orig_nbformat": 4
},
"nbformat": 4,
"nbformat_minor": 2
}