{"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":31090,"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-25T14:34:09.255145Z","iopub.execute_input":"2025-07-25T14:34:09.255734Z","iopub.status.idle":"2025-07-25T14:34:09.261336Z","shell.execute_reply.started":"2025-07-25T14:34:09.255710Z","shell.execute_reply":"2025-07-25T14:34:09.260637Z"}},"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-25T14:34:13.584503Z","iopub.execute_input":"2025-07-25T14:34:13.584767Z","iopub.status.idle":"2025-07-25T14:34:20.979835Z","shell.execute_reply.started":"2025-07-25T14:34:13.584746Z","shell.execute_reply":"2025-07-25T14:34:20.979144Z"}},"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    # 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-25T14:34:26.674882Z","iopub.execute_input":"2025-07-25T14:34:26.675186Z","iopub.status.idle":"2025-07-25T14:34:26.965031Z","shell.execute_reply.started":"2025-07-25T14:34:26.675163Z","shell.execute_reply":"2025-07-25T14:34:26.964470Z"}},"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-25T14:34:42.867361Z","iopub.execute_input":"2025-07-25T14:34:42.867609Z","iopub.status.idle":"2025-07-25T14:34:44.660731Z","shell.execute_reply.started":"2025-07-25T14:34:42.867593Z","shell.execute_reply":"2025-07-25T14:34:44.660218Z"}},"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=10,\n    callbacks=callbacks\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-25T14:34:52.154595Z","iopub.execute_input":"2025-07-25T14:34:52.154866Z","iopub.status.idle":"2025-07-25T15:26:40.246487Z","shell.execute_reply.started":"2025-07-25T14:34:52.154844Z","shell.execute_reply":"2025-07-25T15:26:40.245846Z"}},"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,"execution":{"iopub.status.busy":"2025-07-25T15:36:52.195035Z","iopub.execute_input":"2025-07-25T15:36:52.195539Z","iopub.status.idle":"2025-07-25T15:58:33.094763Z","shell.execute_reply.started":"2025-07-25T15:36:52.195516Z","shell.execute_reply":"2025-07-25T15:58:33.093985Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 学習済みモデルを保存\nmodel.save(\"/kaggle/working/best_model.h5\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-25T16:01:35.792283Z","iopub.execute_input":"2025-07-25T16:01:35.793060Z","iopub.status.idle":"2025-07-25T16:01:36.612086Z","shell.execute_reply.started":"2025-07-25T16:01:35.793034Z","shell.execute_reply":"2025-07-25T16:01:36.611314Z"}},"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-25T16:01:42.385573Z","iopub.execute_input":"2025-07-25T16:01:42.386270Z","iopub.status.idle":"2025-07-25T16:04:25.112420Z","shell.execute_reply.started":"2025-07-25T16:01:42.386245Z","shell.execute_reply":"2025-07-25T16:04:25.111781Z"}},"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-25T16:04:35.200746Z","iopub.execute_input":"2025-07-25T16:04:35.201260Z","iopub.status.idle":"2025-07-25T16:04:35.269795Z","shell.execute_reply.started":"2025-07-25T16:04:35.201235Z","shell.execute_reply":"2025-07-25T16:04:35.269191Z"}},"outputs":[],"execution_count":null}]}