{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.11","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":5441,"databundleVersionId":38425,"sourceType":"competition"}],"dockerImageVersionId":31040,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":" # ライブラリのインポート","metadata":{}},{"cell_type":"code","source":"import os, zipfile, glob, time\nfrom sklearn.model_selection import train_test_split\nimport pandas as pd\nfrom PIL import Image\nfrom tqdm import tqdm\nimport torch\nfrom torch.utils.data import DataLoader, Dataset\nimport torchvision\nfrom torchvision import transforms\nfrom torchvision.models import vit_b_16\nimport timm\nimport torch.nn as nn\nimport torch.optim as optim\nimport matplotlib.pyplot as plt\nimport torch.nn.functional as F","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-25T06:45:47.268822Z","iopub.execute_input":"2025-07-25T06:45:47.269071Z","iopub.status.idle":"2025-07-25T06:46:00.299795Z","shell.execute_reply.started":"2025-07-25T06:45:47.269053Z","shell.execute_reply":"2025-07-25T06:46:00.298956Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 各種設定","metadata":{}},{"cell_type":"code","source":"# デバイスの設定（GPUを指定）\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nresize = 224 # 画像の入力サイズ\nmean = (0.485, 0.456, 0.406) # ImageNetの平均値\nstd = (0.229, 0.224, 0.225) # ImageNetの標準偏差\nbatch_size = 32 # バッチサイズ\nnum_epochs = 10 # エポック数","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-25T06:46:00.300589Z","iopub.execute_input":"2025-07-25T06:46:00.300943Z","iopub.status.idle":"2025-07-25T06:46:00.364314Z","shell.execute_reply.started":"2025-07-25T06:46:00.300925Z","shell.execute_reply":"2025-07-25T06:46:00.363642Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":" # データセットの準備","metadata":{}},{"cell_type":"code","source":"# データディレクトリのパス設定\nbase_dir = \"/kaggle/input/dogs-vs-cats-redux-kernels-edition\"\n\n# 解凍先ディレクトリのパス設定\ndata_dir = \"/kaggle/working/data\"\ntrain_dir = \"/kaggle/working/data/train\"\ntest_dir = \"/kaggle/working/data/test\"\n\n# dataディレクトリを作成\nos.makedirs(data_dir, exist_ok=True)\n\n# データの解凍\nwith zipfile.ZipFile(os.path.join(base_dir, \"train.zip\")) as train_zip:\n    train_zip.extractall(data_dir)\nwith zipfile.ZipFile(os.path.join(base_dir, \"test.zip\")) as test_zip:\n    test_zip.extractall(data_dir)\n\n# 画像ファイル全てのパスを取得してリスト化\ntrain_list = glob.glob(os.path.join(train_dir, \"*.jpg\"))\ntest_list = glob.glob(os.path.join(test_dir, \"*.jpg\"))\n\n# 学習データのリストをシャッフルして、学習:検証 = 8:2に分割する\ntrain_list, val_list = train_test_split(train_list, test_size=0.2)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-25T06:46:00.366151Z","iopub.execute_input":"2025-07-25T06:46:00.366585Z","iopub.status.idle":"2025-07-25T06:46:14.587052Z","shell.execute_reply.started":"2025-07-25T06:46:00.366555Z","shell.execute_reply":"2025-07-25T06:46:14.586529Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":" # データ拡張・前処理\n","metadata":{}},{"cell_type":"code","source":"class ImageTransform():\n    # 画像をリサイズ、クロップ、Tensor化、正規化するクラス\n    def __init__(self, resize, mean, std):\n        self.data_transform = {\n            \"train\": transforms.Compose([\n                transforms.RandomResizedCrop(resize),\n                transforms.RandomHorizontalFlip(),\n                transforms.ToTensor(),\n                transforms.Normalize(mean, std)\n            ]),\n            \"val\": transforms.Compose([\n                transforms.Resize(int(resize * 1.14)),\n                transforms.CenterCrop(resize),\n                transforms.ToTensor(),\n                transforms.Normalize(mean, std)\n            ]),\n        }\n\n    def __call__(self, img, phase):\n        return self.data_transform[phase](img)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-25T06:46:14.587791Z","iopub.execute_input":"2025-07-25T06:46:14.588062Z","iopub.status.idle":"2025-07-25T06:46:14.593126Z","shell.execute_reply.started":"2025-07-25T06:46:14.588036Z","shell.execute_reply":"2025-07-25T06:46:14.592415Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"class ImageDataset(Dataset):\n    def __init__(self, file_list, transform=None, phase=\"train\"):\n        self.file_list = file_list\n        self.transform = transform\n        self.phase = phase\n\n    def __len__(self):\n        return len(self.file_list)\n\n    def __getitem__(self, idx):\n        img_path = self.file_list[idx]\n        img = Image.open(img_path)\n        img_transformed = self.transform(img, self.phase)\n\n        # ファイル名からラベルを取得\n        label_str = os.path.basename(img_path).split(\".\")[0]\n        if label_str == \"dog\":\n            label = 1\n        elif label_str == \"cat\":\n            label = 0\n\n        return img_transformed, label","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-25T06:46:14.593878Z","iopub.execute_input":"2025-07-25T06:46:14.594225Z","iopub.status.idle":"2025-07-25T06:46:14.614924Z","shell.execute_reply.started":"2025-07-25T06:46:14.594202Z","shell.execute_reply":"2025-07-25T06:46:14.614314Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# DataLoaderの準備","metadata":{}},{"cell_type":"code","source":"# データの前処理\n# 前処理インスタンスの作成\ntransform = ImageTransform(resize, mean, std)\n\n# DatasetとDataLoaderの生成\ntrain_dataset = ImageDataset(train_list, transform, phase=\"train\")\nval_dataset = ImageDataset(val_list, transform, phase=\"val\")\ntrain_dataloader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True, num_workers=4)\nval_dataloader = DataLoader(val_dataset, batch_size=batch_size, shuffle=True, num_workers=4)\n\ndataloader_dict = {\"train\": train_dataloader, \"val\": val_dataloader}","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-25T06:46:14.615562Z","iopub.execute_input":"2025-07-25T06:46:14.615773Z","iopub.status.idle":"2025-07-25T06:46:14.632526Z","shell.execute_reply.started":"2025-07-25T06:46:14.615758Z","shell.execute_reply":"2025-07-25T06:46:14.631880Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":" # モデルの定義","metadata":{}},{"cell_type":"code","source":"# 事前学習済みのViTを読み込み\nnet = vit_b_16(weights=True)\n\nfor name, param in net.named_parameters():\n    if \"heads\" not in name:\n        param.requires_grad = False\n\n# 2クラス分類用にヘッドを置換\nin_features = net.heads.head.in_features\nnet.heads.head = nn.Linear(in_features, 2)\n\nnet = net.to(device)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-25T06:46:14.633096Z","iopub.execute_input":"2025-07-25T06:46:14.633279Z","iopub.status.idle":"2025-07-25T06:46:17.942901Z","shell.execute_reply.started":"2025-07-25T06:46:14.633264Z","shell.execute_reply":"2025-07-25T06:46:17.942307Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 損失関数・最適化","metadata":{}},{"cell_type":"code","source":"criterion = nn.CrossEntropyLoss() # クロスエントロピー誤差\noptimizer = torch.optim.Adam(net.heads.head.parameters(), lr=0.0001) # ヘッドのみ学習","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-25T06:46:17.943564Z","iopub.execute_input":"2025-07-25T06:46:17.943758Z","iopub.status.idle":"2025-07-25T06:46:17.948092Z","shell.execute_reply.started":"2025-07-25T06:46:17.943743Z","shell.execute_reply":"2025-07-25T06:46:17.947424Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":" # 学習関数","metadata":{}},{"cell_type":"code","source":"def train_model(model, dataloaders, criterion, optimizer, num_epochs):\n    since = time.time()\n    best_model_wts = model.state_dict()\n    best_acc = 0.0\n\n    # 損失と精度の履歴\n    history = {'train_loss': [], 'val_loss': [], 'train_acc': [], 'val_acc': []}\n\n    for epoch in range(num_epochs):\n        print(f\"Epoch {epoch+1}/{num_epochs}\")\n        print('-'*20)\n        for phase in [\"train\", \"val\"]:\n            if phase == \"train\":\n                model.train()\n            else :\n                model.eval()\n\n            running_loss = 0.0\n            running_corrects = 0\n\n            # ミニバッチを順次取得\n            for inputs, labels in tqdm(dataloaders[phase]):\n                inputs = inputs.to(device)\n                labels = labels.to(device)\n                \n                optimizer.zero_grad() # 勾配を初期化\n                with torch.set_grad_enabled(phase == \"train\"):\n                    outputs = model(inputs)\n                    loss = criterion(outputs, labels)\n                    _, preds = torch.max(outputs, 1)\n                    if phase == \"train\":\n                        loss.backward()\n                        optimizer.step()\n\n                # 統計量の更新\n                running_loss += loss.item() * inputs.size(0)\n                running_corrects += torch.sum(preds == labels.data)\n\n            # エポックごとの損失・精度を計算して表示\n            epoch_loss = running_loss / len(dataloaders[phase].dataset)\n            epoch_acc = running_corrects.double() / len(dataloaders[phase].dataset)\n            print(f'{phase} Loss: {epoch_loss:.4f} Acc: {epoch_acc:.4f}')\n\n            # 履歴に保存\n            history[f'{phase}_loss'].append(epoch_loss)\n            history[f'{phase}_acc'].append(epoch_acc.cpu().numpy())\n\n            # ベストモデル更新\n            if phase == 'val' and epoch_acc > best_acc:\n                best_acc = epoch_acc\n                best_model_wts = model.state_dict()\n\n    # 学習時間の表示\n    time_elapsed = time.time() - since #学習の総時間を計算\n    print('Training complete in {:.0f}m {:.0f}s'.format(time_elapsed // 60, time_elapsed % 60))\n    print('Best val Acc: {:4f}'.format(best_acc))\n\n    # ベストモデルの重みを返す\n    model.load_state_dict(best_model_wts)\n    return model, history","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-25T06:46:17.950147Z","iopub.execute_input":"2025-07-25T06:46:17.950499Z","iopub.status.idle":"2025-07-25T06:46:17.966043Z","shell.execute_reply.started":"2025-07-25T06:46:17.950482Z","shell.execute_reply":"2025-07-25T06:46:17.965344Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":" # モデルの学習","metadata":{}},{"cell_type":"code","source":"net, history = train_model(net, dataloader_dict, criterion, optimizer, num_epochs)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-25T06:46:17.966747Z","iopub.execute_input":"2025-07-25T06:46:17.966962Z","iopub.status.idle":"2025-07-25T07:15:23.199769Z","shell.execute_reply.started":"2025-07-25T06:46:17.966938Z","shell.execute_reply":"2025-07-25T07:15:23.198687Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":" # 損失の可視化","metadata":{}},{"cell_type":"code","source":"# 損失のプロット\nplt.figure(figsize=(12, 5))\nplt.subplot(1, 2, 1)\nplt.plot(history['train_loss'], label='Train Loss')\nplt.plot(history['val_loss'], label='Val Loss')\nplt.xlabel('Epoch')\nplt.ylabel('Loss')\nplt.title('Loss over epochs')\nplt.legend()\n\n# 精度のプロット\nplt.subplot(1, 2, 2)\nplt.plot(history['train_acc'], label='Train Accuracy')\nplt.plot(history['val_acc'], label='Val Accuracy')\nplt.xlabel('Epoch')\nplt.ylabel('Accuracy')\nplt.title('Accuracy over epochs')\nplt.legend()\n\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-25T07:15:23.200738Z","iopub.execute_input":"2025-07-25T07:15:23.200981Z","iopub.status.idle":"2025-07-25T07:15:23.573990Z","shell.execute_reply.started":"2025-07-25T07:15:23.200957Z","shell.execute_reply":"2025-07-25T07:15:23.573292Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":" # 推論","metadata":{}},{"cell_type":"code","source":"# 変数の初期化\nid_list = []\npred_list = []\n\nwith torch.no_grad():\n    for test_path in tqdm(test_list):\n        img = Image.open(test_path) #画像読み込み\n        _id = int(test_path.split('/')[-1].split('.')[0]) #ID抽出\n\n        transform = ImageTransform(resize, mean, std)\n        img = transform(img, phase='val') #画像変換\n        img = img.unsqueeze(0) #バッチ次元の追加\n        img = img.to(device) #画像をGPUに転送\n\n        net.eval() #モデルを評価モードに設定\n\n        outputs = net(img) #モデルによる推論\n        preds = F.softmax(outputs, dim=1)[:, 1].tolist() #クラス1(dog)の確率を算出\n        \n        id_list.append(_id) #IDをリストに追加\n        pred_list.append(preds[0]) #予測値をリストに追加\n    \n    \nres = pd.DataFrame({'id': id_list, 'label': pred_list}) #IDと予測値からデータフレームを作成\n\n\nres.sort_values(by='id', inplace=True) #ID順に並び替え\nres.reset_index(drop=True, inplace=True)\n\nres.to_csv('/kaggle/working/submission.csv', index=False) #データフレームをCSVファイルに保存","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-25T07:15:23.574909Z","iopub.execute_input":"2025-07-25T07:15:23.575187Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# /usr/local/lib/python3.11/dist-packages/torchvision/models/_utils.py:208: UserWarning: The parameter 'pretrained' is deprecated since 0.13 and may be removed in the future, please use 'weights' instead.\n  warnings.warn(\n/usr/local/lib/python3.11/dist-packages/torchvision/models/_utils.py:223: UserWarning: Arguments other than a weight enum or `None` for 'weights' are deprecated since 0.13 and may be removed in the future. The current behavior is equivalent to passing `weights=ViT_B_16_Weights.IMAGENET1K_V1`. You can also use `weights=ViT_B_16_Weights.DEFAULT` to get the most up-to-date weights.\n  warnings.warn(msg)\nDownloading: \"https://download.pytorch.org/models/vit_b_16-c867db91.pth\" to /root/.cache/torch/hub/checkpoints/vit_b_16-c867db91.pth\n100%|██████████| 330M/330M [00:03<00:00, 88.8MB/s] \n","metadata":{}}]}