{"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":5441,"databundleVersionId":38425,"sourceType":"competition"}],"dockerImageVersionId":31091,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load\n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\n# Input data files are available in the read-only \"../input/\" directory\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\nimport os\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))\n\n# You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-07-11T16:06:53.737029Z","iopub.execute_input":"2025-07-11T16:06:53.737333Z","iopub.status.idle":"2025-07-11T16:06:53.752086Z","shell.execute_reply.started":"2025-07-11T16:06:53.737311Z","shell.execute_reply":"2025-07-11T16:06:53.751447Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## zipファイルの解凍と画像の振り分け","metadata":{}},{"cell_type":"code","source":"import os, zipfile, shutil, pathlib\n\n# 解凍先ディレクトリ\nWORK_DIR = '/kaggle/working'\n\n# train.zip の解凍\nwith zipfile.ZipFile('/kaggle/input/dogs-vs-cats-redux-kernels-edition/train.zip', 'r') as zip_ref:\n    zip_ref.extractall(f'{WORK_DIR}')\n\n# 犬・猫を分類するためのフォルダ作成\nbase_dir = pathlib.Path(f'{WORK_DIR}/dataset')\ncat_dir = base_dir / 'cats'\ndog_dir = base_dir / 'dogs'\ncat_dir.mkdir(parents=True, exist_ok=True)\ndog_dir.mkdir(parents=True, exist_ok=True)\n\n# trainフォルダの画像をcats/dogsに仕分け\ntrain_dir = pathlib.Path(f'{WORK_DIR}/train')\nfor file in train_dir.iterdir():\n    if 'cat' in file.name:\n        shutil.move(str(file), cat_dir / file.name)\n    elif 'dog' in file.name:\n        shutil.move(str(file), dog_dir / file.name)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-11T16:06:53.753200Z","iopub.execute_input":"2025-07-11T16:06:53.753492Z","iopub.status.idle":"2025-07-11T16:07:06.507266Z","shell.execute_reply.started":"2025-07-11T16:06:53.753474Z","shell.execute_reply":"2025-07-11T16:07:06.506740Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## データの読み込み","metadata":{}},{"cell_type":"code","source":"import cv2\nimport numpy as np\nfrom tensorflow.keras.preprocessing.image import ImageDataGenerator\nfrom tensorflow.keras.applications.convnext import preprocess_input\n\nIMG_SIZE = (224, 224)\nBATCH_SIZE = 32\n\nimport cv2\nimport numpy as np\nfrom tensorflow.keras.applications.convnext import preprocess_input\n\n# 画像の中心以外をぼかす\ndef focus_blur_preprocessing(img):\n    # BGR変換\n    img = cv2.cvtColor(img, cv2.COLOR_RGB2BGR)\n\n    # 中心にフォーカス\n    h, w = img.shape[:2]\n    mask = np.zeros((h, w), dtype=np.uint8)\n    cv2.circle(mask, (w // 2, h // 2), int(min(h, w) * 0.4), 255, -1)  # 半径は40%程度\n\n    blurred = cv2.GaussianBlur(img, (25, 25), 0)\n    focused = np.where(mask[..., None] == 255, img, blurred)\n\n    # BGR → RGB\n    focused = cv2.cvtColor(focused, cv2.COLOR_BGR2RGB)\n\n    # ConvNeXt用の前処理\n    return preprocess_input(focused.astype(np.float32))\n\n# ImageDataGeneratorの設定\ndatagen = ImageDataGenerator(\n    preprocessing_function=focus_blur_preprocessing,\n    validation_split=0.2,\n    horizontal_flip=True,\n    rotation_range=15,\n    zoom_range=0.2\n)\n\ntrain_gen = datagen.flow_from_directory(\n    directory='/kaggle/working/dataset',\n    target_size=IMG_SIZE,\n    batch_size=BATCH_SIZE,\n    class_mode='binary',\n    subset='training',\n    shuffle=True\n)\n\nval_gen = datagen.flow_from_directory(\n    directory='/kaggle/working/dataset',\n    target_size=IMG_SIZE,\n    batch_size=BATCH_SIZE,\n    class_mode='binary',\n    subset='validation',\n    shuffle=False\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-11T16:07:06.507870Z","iopub.execute_input":"2025-07-11T16:07:06.508076Z","iopub.status.idle":"2025-07-11T16:07:06.777979Z","shell.execute_reply.started":"2025-07-11T16:07:06.508059Z","shell.execute_reply":"2025-07-11T16:07:06.777251Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## ConvNeXt-Small の構築","metadata":{}},{"cell_type":"code","source":"from tensorflow.keras.applications import ConvNeXtSmall\nfrom tensorflow.keras import layers, models, optimizers\n\nbase_model = ConvNeXtSmall(\n    weights='imagenet',\n    include_top=False,\n    input_shape=(224, 224, 3)\n)\nbase_model.trainable = False  # 転移学習初期は凍結\n\nmodel = models.Sequential([\n    base_model,\n    layers.GlobalAveragePooling2D(),\n    layers.Dropout(0.3),\n    layers.Dense(1, activation='sigmoid')\n])\n\nmodel.compile(\n    optimizer=optimizers.Adam(learning_rate=1e-4),\n    loss='binary_crossentropy',\n    metrics=['accuracy']\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-11T16:07:52.567477Z","iopub.execute_input":"2025-07-11T16:07:52.568051Z","iopub.status.idle":"2025-07-11T16:07:57.863109Z","shell.execute_reply.started":"2025-07-11T16:07:52.568026Z","shell.execute_reply":"2025-07-11T16:07:57.862308Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## モデルの学習","metadata":{}},{"cell_type":"code","source":"from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint\n\ncallbacks = [\n    EarlyStopping(patience=3, restore_best_weights=True),\n    ModelCheckpoint('convnext_small_best.h5', save_best_only=True)\n]\n\nhistory = model.fit(\n    train_gen,\n    validation_data=val_gen,\n    epochs=18,\n    callbacks=callbacks\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-11T16:08:32.766799Z","iopub.execute_input":"2025-07-11T16:08:32.767075Z","iopub.status.idle":"2025-07-11T17:01:29.154255Z","shell.execute_reply.started":"2025-07-11T16:08:32.767056Z","shell.execute_reply":"2025-07-11T17:01:29.153692Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ConvNeXt の全層を再学習させる\nbase_model.trainable = True  # 凍結解除\n\nfor layer in base_model.layers[:-20]:\n    layer.trainable = False\n\n# 再コンパイル\nmodel.compile(\n    optimizer=optimizers.Adam(learning_rate=1e-6),\n    loss='binary_crossentropy',\n    metrics=['accuracy']\n)\n\n# エポック数を定義\ninitial_epochs = len(history.epoch)\nfine_tune_epochs = 4\ntotal_epochs = initial_epochs + fine_tune_epochs\n\n# ファインチューニング実行\nhistory_fine = model.fit(\n    train_gen,\n    validation_data=val_gen,\n    epochs=total_epochs,\n    initial_epoch=initial_epochs,\n    callbacks=callbacks\n)","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 学習済みモデルを保存\nmodel.save(\"/kaggle/working/best_model.h5\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-11T17:03:25.291255Z","iopub.execute_input":"2025-07-11T17:03:25.291825Z","iopub.status.idle":"2025-07-11T17:03:25.946727Z","shell.execute_reply.started":"2025-07-11T17:03:25.291797Z","shell.execute_reply":"2025-07-11T17:03:25.946085Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## テスト画像の読み込みと推論","metadata":{}},{"cell_type":"code","source":"# test.zip の解凍\nwith zipfile.ZipFile('/kaggle/input/dogs-vs-cats-redux-kernels-edition/test.zip', 'r') as zip_ref:\n    zip_ref.extractall('/kaggle/working/test')\n\ntest_datagen = ImageDataGenerator(\n    preprocessing_function=focus_blur_preprocessing  # 学習と同じ前処理関数\n)\n\n# ImageDataGeneratorでテスト画像を読み込み\ntest_gen = ImageDataGenerator(preprocessing_function=focus_blur_preprocessing).flow_from_directory(\n    directory='/kaggle/working',\n    classes=['test'],\n    target_size=IMG_SIZE,\n    batch_size=1,\n    shuffle=False,\n    class_mode=None\n)\n\n# 推論（0〜1の確率）\npreds = model.predict(test_gen, verbose=1)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-11T17:03:29.758461Z","iopub.execute_input":"2025-07-11T17:03:29.759167Z","iopub.status.idle":"2025-07-11T17:06:25.155348Z","shell.execute_reply.started":"2025-07-11T17:03:29.759145Z","shell.execute_reply":"2025-07-11T17:06:25.154749Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## 提出用ファイル作成","metadata":{}},{"cell_type":"code","source":"import pandas as pd\n\n# sampleSubmission.csv 読み込み\nsubmission = pd.read_csv('/kaggle/input/dogs-vs-cats-redux-kernels-edition/sample_submission.csv')\n\n# ファイル名からIDを抽出\ntest_ids = [int(fname.split('/')[-1].split('.')[0]) for fname in test_gen.filenames]\n\n# 推論結果をDataFrameにする\nsubmission = pd.DataFrame({\n    'id': test_ids,\n    'label': np.clip(preds.flatten(), 1e-6, 1 - 1e-6)\n})\n\n# id順にソート\nsubmission = submission.sort_values('id')\nsubmission.to_csv('submission.csv', index=False)\n\n# 表示\nsubmission.head(10)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-11T17:06:29.924864Z","iopub.execute_input":"2025-07-11T17:06:29.925370Z","iopub.status.idle":"2025-07-11T17:06:30.013333Z","shell.execute_reply.started":"2025-07-11T17:06:29.925345Z","shell.execute_reply":"2025-07-11T17:06:30.012635Z"}},"outputs":[],"execution_count":null}]}