{"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":"nvidiaTeslaT4","dataSources":[{"sourceId":29193,"databundleVersionId":2318453,"sourceType":"competition"}],"dockerImageVersionId":31042,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nimport os\nimport torch\nimport torch.nn as nn\nimport torch.optim as optim\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision import transforms, models\nfrom PIL import Image\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.preprocessing import LabelEncoder\nfrom tqdm import tqdm\nimport matplotlib.pyplot as plt\n\n# デバイスの設定 (GPUが利用可能ならGPUを使用)\ndevice = torch.device(\"cuda\" if torch.cuda.is_available() else \"cpu\")\nprint(f\"Using device: {device}\")\n\n# データパス\ndata_dir = \"/kaggle/input/classify-leaves\"\ntrain_csv_path = os.path.join(data_dir, \"train.csv\")\ntest_csv_path = os.path.join(data_dir, \"test.csv\")\n# image_dir = os.path.join(data_dir, \"images\")\nimage_dir = data_dir\n\n\n# CSVファイルの読み込み\ntrain_df = pd.read_csv(train_csv_path)\ntest_df = pd.read_csv(test_csv_path)\n\nprint(\"Train DataFrame:\")\nprint(train_df.head())\nprint(\"\\nTest DataFrame:\")\nprint(test_df.head())","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-07-02T06:56:54.775419Z","iopub.execute_input":"2025-07-02T06:56:54.775642Z","iopub.status.idle":"2025-07-02T06:57:10.047788Z","shell.execute_reply.started":"2025-07-02T06:56:54.77562Z","shell.execute_reply":"2025-07-02T06:57:10.046961Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ラベルエンコーディング\n# ラベル(文字列)を整数に変換\nle = LabelEncoder()\ntrain_df['label_encoded'] = le.fit_transform(train_df['label'])\n\n# ラベルとインデックスの対応を保存 (後で元に戻すために必要)\nlabel_to_index = {label: index for index, label in enumerate(le.classes_)}\nindex_to_label = {index: label for index, label in enumerate(le.classes_)}\nnum_classes = len(le.classes_)\nprint(f\"Number of classes: {num_classes}\")\n\n# 学習データと検証データに分割\n# stratify=train_df['label_encoded'] を指定することで、各クラスの比率を保ったまま分割\ntrain_data, val_data = train_test_split(\n    train_df,\n    test_size=0.2,       # 20%を検証用データとする\n    random_state=42,     # 再現性のための乱数シード\n    stratify=train_df['label_encoded']\n)\n\nprint(f\"Training data size: {len(train_data)}\")\nprint(f\"Validation data size: {len(val_data)}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-02T06:57:17.18095Z","iopub.execute_input":"2025-07-02T06:57:17.181219Z","iopub.status.idle":"2025-07-02T06:57:17.222108Z","shell.execute_reply.started":"2025-07-02T06:57:17.181196Z","shell.execute_reply":"2025-07-02T06:57:17.221454Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 画像変換の定義\n# ImageNetの平均と標準偏差を使用するのが一般的\nmean = [0.485, 0.456, 0.406]\nstd = [0.229, 0.224, 0.225]\n\n# 学習用データ拡張\ntrain_transforms = transforms.Compose([\n    transforms.RandomResizedCrop(224),       # ランダムな位置でクロップし、224x224にリサイズ\n    transforms.RandomHorizontalFlip(),       # 50%の確率で水平反転\n    transforms.RandomRotation(15),           # -15度から+15度の範囲でランダムに回転\n    transforms.ToTensor(),                   # PIL ImageをTensorに変換\n    transforms.Normalize(mean, std)          # 正規化\n])\n\n# 検証・テスト用（データ拡張はしない）\nval_test_transforms = transforms.Compose([\n    transforms.Resize(256),                  # まず256x256にリサイズ\n    transforms.CenterCrop(224),              # 中央を224x224でクロップ\n    transforms.ToTensor(),\n    transforms.Normalize(mean, std)\n])\n\n# カスタムDatasetクラス\nclass LeafDataset(Dataset):\n    def __init__(self, df, image_dir, transform=None, is_test=False):\n        self.df = df\n        self.image_dir = image_dir\n        self.transform = transform\n        self.is_test = is_test\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n        img_name = self.df.iloc[idx]['image']\n        img_path = os.path.join(self.image_dir, img_name)\n        image = Image.open(img_path).convert('RGB')\n\n        if self.transform:\n            image = self.transform(image)\n        \n        if self.is_test:\n            return image\n        else:\n            label = self.df.iloc[idx]['label_encoded']\n            return image, torch.tensor(label, dtype=torch.long)\n\n# Datasetのインスタンスを作成\ntrain_dataset = LeafDataset(train_data, image_dir, transform=train_transforms)\nval_dataset = LeafDataset(val_data, image_dir, transform=val_test_transforms)\ntest_dataset = LeafDataset(test_df, image_dir, transform=val_test_transforms, is_test=True)\n\n# DataLoaderの作成\nBATCH_SIZE = 32\ntrain_loader = DataLoader(train_dataset, batch_size=BATCH_SIZE, shuffle=True, num_workers=2)\nval_loader = DataLoader(val_dataset, batch_size=BATCH_SIZE, shuffle=False, num_workers=2)\ntest_loader = DataLoader(test_dataset, batch_size=BATCH_SIZE, shuffle=False, num_workers=2)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-02T06:57:20.19883Z","iopub.execute_input":"2025-07-02T06:57:20.199532Z","iopub.status.idle":"2025-07-02T06:57:20.20812Z","shell.execute_reply.started":"2025-07-02T06:57:20.199507Z","shell.execute_reply":"2025-07-02T06:57:20.207604Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 事前学習済みのResNet50をロード\nmodel = models.resnet50(pretrained=True)\n\n# 最後の全結合層を新しいものに置き換える\nnum_ftrs = model.fc.in_features  # 最後の層の入力チャネル数を取得\nmodel.fc = nn.Linear(num_ftrs, num_classes)\n\n# モデルをGPUに移動\nmodel = model.to(device)\n\nprint(\"Model architecture:\")\nprint(model)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-02T06:57:24.802798Z","iopub.execute_input":"2025-07-02T06:57:24.803077Z","iopub.status.idle":"2025-07-02T06:57:26.227513Z","shell.execute_reply.started":"2025-07-02T06:57:24.803057Z","shell.execute_reply":"2025-07-02T06:57:26.226719Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ハイパーパラメータ\nEPOCHS = 15\nLEARNING_RATE = 0.001\n\n# 損失関数とオプティマイザ\ncriterion = nn.CrossEntropyLoss()\noptimizer = optim.Adam(model.parameters(), lr=LEARNING_RATE)\n# 学習率を5エポックごとに0.1倍にするスケジューラ\nscheduler = optim.lr_scheduler.StepLR(optimizer, step_size=7, gamma=0.1)\n\nbest_val_acc = 0.0\nbest_model_path = \"best_model.pth\"\n\n### 1. 損失の履歴を保存するリストを初期化 ###\ntrain_loss_history = []\nval_loss_history = []\n\n# 学習ループ\nfor epoch in range(EPOCHS):\n    # --- 学習フェーズ ---\n    model.train()\n    running_loss = 0.0\n    train_corrects = 0\n    \n    for inputs, labels in tqdm(train_loader, desc=f\"Epoch {epoch+1}/{EPOCHS} [Train]\"):\n        inputs, labels = inputs.to(device), labels.to(device)\n        optimizer.zero_grad()\n        outputs = model(inputs)\n        _, preds = torch.max(outputs, 1)\n        loss = criterion(outputs, labels)\n        loss.backward()\n        optimizer.step()\n        running_loss += loss.item() * inputs.size(0)\n        train_corrects += torch.sum(preds == labels.data)\n\n    train_loss = running_loss / len(train_dataset)\n    train_acc = train_corrects.double() / len(train_dataset)\n\n    # --- 検証フェーズ ---\n    model.eval()\n    val_loss = 0.0\n    val_corrects = 0\n    \n    with torch.no_grad():\n        for inputs, labels in tqdm(val_loader, desc=f\"Epoch {epoch+1}/{EPOCHS} [Val]\"):\n            inputs, labels = inputs.to(device), labels.to(device)\n            outputs = model(inputs)\n            _, preds = torch.max(outputs, 1)\n            loss = criterion(outputs, labels)\n            val_loss += loss.item() * inputs.size(0)\n            val_corrects += torch.sum(preds == labels.data)\n\n    val_loss = val_loss / len(val_dataset)\n    val_acc = val_corrects.double() / len(val_dataset)\n    \n    ### 2. 履歴リストに損失を追加 ###\n    train_loss_history.append(train_loss)\n    val_loss_history.append(val_loss)\n    # (参考) 精度の履歴を追加する場合\n    # train_acc_history.append(train_acc.cpu().numpy())\n    # val_acc_history.append(val_acc.cpu().numpy())\n\n\n    print(f\"Epoch {epoch+1}/{EPOCHS} -> Train Loss: {train_loss:.4f}, Train Acc: {train_acc:.4f} | Val Loss: {val_loss:.4f}, Val Acc: {val_acc:.4f}\")\n    \n    scheduler.step()\n    \n    if val_acc > best_val_acc:\n        best_val_acc = val_acc\n        torch.save(model.state_dict(), best_model_path)\n        print(f\"Best model saved with validation accuracy: {best_val_acc:.4f}\")\n\nprint(\"Training finished.\")\n\n# ----------------------------------------------------------------\n### 3. 学習曲線（損失）をプロット ###\nplt.figure(figsize=(10, 6))\nplt.plot(range(1, EPOCHS + 1), train_loss_history, label='Train Loss')\nplt.plot(range(1, EPOCHS + 1), val_loss_history, label='Validation Loss')\nplt.title('Training and Validation Loss')\nplt.xlabel('Epochs')\nplt.ylabel('Loss')\nplt.legend()\nplt.grid(True)\nplt.show()\n# ----------------------------------------------------------------","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-02T06:57:32.878174Z","iopub.execute_input":"2025-07-02T06:57:32.878474Z","iopub.status.idle":"2025-07-02T07:38:25.794509Z","shell.execute_reply.started":"2025-07-02T06:57:32.878454Z","shell.execute_reply":"2025-07-02T07:38:25.793591Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 最高のモデルをロード\nbest_model_path = \"best_model.pth\"\nmodel.load_state_dict(torch.load(best_model_path))\nmodel.eval() # 評価モードに設定\n\npredictions = []\n\nwith torch.no_grad():\n    for inputs in tqdm(test_loader, desc=\"Predicting on test data\"):\n        inputs = inputs.to(device)\n        outputs = model(inputs)\n        _, preds = torch.max(outputs, 1)\n        predictions.extend(preds.cpu().numpy())\n\n# 予測結果を元の文字列ラベルに変換\npredicted_labels = le.inverse_transform(predictions)\n\n# 提出用DataFrameを作成\nsubmission_df = pd.DataFrame({\n    'image': test_df['image'],\n    'label': predicted_labels\n})\n\n# CSVファイルとして保存\nsubmission_df.to_csv('submission.csv', index=False)\n\nprint(\"Submission file created successfully!\")\nprint(submission_df.head())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-02T07:41:20.66575Z","iopub.execute_input":"2025-07-02T07:41:20.666102Z","iopub.status.idle":"2025-07-02T07:42:15.448575Z","shell.execute_reply.started":"2025-07-02T07:41:20.666068Z","shell.execute_reply":"2025-07-02T07:42:15.447475Z"}},"outputs":[],"execution_count":null}]}