Newer
Older
EBUS_Classify / ebus_classify.ipynb
{
 "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
}