{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"nvidiaTeslaT4","dataSources":[{"sourceId":112509,"databundleVersionId":14254895,"sourceType":"competition"},{"sourceId":13713707,"sourceType":"datasetVersion","datasetId":8724279}],"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os, json\nfrom pathlib import Path\n\nimport numpy as np\nimport pandas as pd\nfrom PIL import Image\n\nimport torch\nimport torch.nn as nn\nfrom torch.utils.data import Dataset, DataLoader\nfrom torchvision import transforms\nimport timm\n\n# ======================\n# パス・基本設定\n# ======================\nDEVICE   = \"cuda\" if torch.cuda.is_available() else \"cpu\"\nDATA_DIR = Path(\"/kaggle/input/csiro-biomass\")\nMODEL_DIR = Path(\"/kaggle/input/csiro-biomass-effb3-v1\")\n\nprint(\"DEVICE   =\", DEVICE)\nprint(\"DATA_DIR =\", DATA_DIR)\nprint(\"MODEL_DIR=\", MODEL_DIR)\n\n# ファイル存在チェック\nassert (DATA_DIR / \"test.csv\").exists(), \"test.csv が見つかりません\"\nassert (MODEL_DIR / \"effb3_v1_config.json\").exists(), \"config が見つかりません\"\nassert (MODEL_DIR / \"effb3_v1_best.pth\").exists(), \"モデル重みが見つかりません\"\n\n# ======================\n# 設定ファイルの読み込み\n# ======================\nwith open(MODEL_DIR / \"effb3_v1_config.json\", \"r\") as f:\n    config = json.load(f)\n\nIMG_SIZE = config[\"img_size\"]\nTARGETS  = config[\"targets\"]          # [\"Dry_Green_g\", ...] の順\nBACKBONE = config.get(\"backbone\", \"tf_efficientnet_b3\")\nprint(\"TARGETS =\", TARGETS)\n\n# ======================\n# モデル定義 & 重みロード\n# ======================\nclass EffNetRegressor(nn.Module):\n    def __init__(self, backbone_name=\"tf_efficientnet_b3\",\n                 out_dim=5, pretrained=False):\n        super().__init__()\n        self.backbone = timm.create_model(\n            backbone_name,\n            pretrained=pretrained,\n            num_classes=0,\n            global_pool=\"avg\"\n        )\n        in_features = self.backbone.num_features\n        self.head = nn.Sequential(\n            nn.Linear(in_features, 512),\n            nn.ReLU(inplace=True),\n            nn.Dropout(0.2),\n            nn.Linear(512, out_dim)\n        )\n\n    def forward(self, x):\n        feat = self.backbone(x)\n        out  = self.head(feat)\n        return out\n\nmodel = EffNetRegressor(\n    backbone_name=BACKBONE,\n    out_dim=len(TARGETS),\n    pretrained=False\n).to(DEVICE)\n\nstate = torch.load(MODEL_DIR / \"effb3_v1_best.pth\",\n                   map_location=DEVICE)\nmodel.load_state_dict(state)\nmodel.eval()\nprint(\"Loaded model.\")\n\n# ======================\n# test.csv 読み込み & パス整形\n# ======================\ntest_df = pd.read_csv(DATA_DIR / \"test.csv\")\nprint(test_df.head())\n\ndef make_path(rel_path: str) -> str:\n    return str(DATA_DIR / rel_path)\n\ntest_df[\"abs_path\"] = test_df[\"image_path\"].apply(make_path)\n\n# 画像ごとに重複排除（1画像につき1回だけ推論）\nunique_imgs = (\n    test_df[[\"image_path\", \"abs_path\"]]\n    .drop_duplicates()\n    .reset_index(drop=True)\n)\nprint(\"unique images:\", len(unique_imgs))\nassert len(unique_imgs) > 0, \"test.csv が空です\"\n\n# ======================\n# Dataset / DataLoader\n# ======================\ntfm = transforms.Compose([\n    transforms.Resize((IMG_SIZE, IMG_SIZE)),\n    transforms.ToTensor(),\n    transforms.Normalize(\n        mean=[0.485, 0.456, 0.406],\n        std =[0.229, 0.224, 0.225],\n    ),\n])\n\nclass TestDataset(Dataset):\n    def __init__(self, df, tfm):\n        self.df = df.reset_index(drop=True)\n        self.tfm = tfm\n\n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n        row = self.df.iloc[idx]\n        img = Image.open(row[\"abs_path\"]).convert(\"RGB\")\n        img = self.tfm(img)\n        return img, row[\"image_path\"]\n\ntest_ds = TestDataset(unique_imgs, tfm)\ntest_loader = DataLoader(\n    test_ds,\n    batch_size=32,\n    shuffle=False,\n    num_workers=2,\n)\n\n# ======================\n# 推論：image_path -> 5次元ベクトル\n# ======================\nimage_to_pred = {}   # image_path -> np.array shape (5,)\n\nmodel.eval()\nwith torch.no_grad():\n    for imgs, paths in test_loader:\n        imgs = imgs.to(DEVICE)\n        outputs = model(imgs)              # (B, 5)\n        outputs = outputs.cpu().numpy()    # np.float32\n        for p, o in zip(paths, outputs):\n            image_to_pred[p] = o\n\n# ======================\n# sample_submission に予測を流し込む\n# ======================\nsub = pd.read_csv(DATA_DIR / \"sample_submission.csv\")\nprint(\"original sub columns:\", sub.columns)\n\n# test.csv から image_path, target_name を付与\nsub = sub.merge(\n    test_df[[\"sample_id\", \"image_path\", \"target_name\"]],\n    on=\"sample_id\",\n    how=\"left\",\n)\nprint(\"merged sub columns:\", sub.columns)\n\n# merge に失敗していないかチェック\nassert sub[\"image_path\"].notnull().all(), \"image_path に NaN が含まれています\"\n\n# target_name -> index の対応（モデル出力との対応を保証）\nname_to_idx = {name: i for i, name in enumerate(TARGETS)}\n\npreds = []\nfor _, row in sub.iterrows():\n    img   = row[\"image_path\"]\n    tname = row[\"target_name\"]\n    vec   = image_to_pred[img]           # その画像の 5次元出力\n    val   = float(vec[name_to_idx[tname]])  # 念のため Python float に\n    preds.append(val)\n\nsub[\"target\"] = preds\n\n# 提出形式に整形（sample_id, target の 2列）\nsubmission = sub[[\"sample_id\", \"target\"]]\n\n# 行数が sample_submission と一致しているか確認\nsample_sub = pd.read_csv(DATA_DIR / \"sample_submission.csv\")\nassert len(submission) == len(sample_sub), \"行数が sample_submission と一致していません\"\n\nsubmission.to_csv(\"submission.csv\", index=False)\nprint(\"saved submission.csv\")\nprint(submission.head())\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-15T08:06:41.831186Z","iopub.execute_input":"2025-11-15T08:06:41.831911Z","iopub.status.idle":"2025-11-15T08:06:42.436375Z","shell.execute_reply.started":"2025-11-15T08:06:41.831874Z","shell.execute_reply":"2025-11-15T08:06:42.435491Z"}},"outputs":[],"execution_count":null}]}