{"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-25T09:09:15.183790Z","iopub.execute_input":"2025-07-25T09:09:15.184145Z","iopub.status.idle":"2025-07-25T09:09:15.211328Z","shell.execute_reply.started":"2025-07-25T09:09:15.184119Z","shell.execute_reply":"2025-07-25T09:09:15.210777Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Dogs vs Cats 分類 (ResNet50)\n- ResNet50をベースにした転移学習\n- categorical_crossentropyを使用\n- Kaggle提出用に確率予測を出力\n- 追加要素：EDA、コールバック、Log Loss計算","metadata":{}},{"cell_type":"markdown","source":"# 2. 必要なライブラリの読み込み","metadata":{}},{"cell_type":"code","source":"import os\nimport zipfile\nimport random\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nfrom PIL import Image","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-25T09:09:54.525287Z","iopub.execute_input":"2025-07-25T09:09:54.525610Z","iopub.status.idle":"2025-07-25T09:09:54.529425Z","shell.execute_reply.started":"2025-07-25T09:09:54.525585Z","shell.execute_reply":"2025-07-25T09:09:54.528879Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import tensorflow as tf\nfrom tensorflow.keras.preprocessing.image import ImageDataGenerator\nfrom tensorflow.keras.applications.resnet50 import ResNet50, preprocess_input\nfrom tensorflow.keras.models import Model\nfrom tensorflow.keras.layers import Dense, GlobalAveragePooling2D, Dropout, Input\nfrom tensorflow.keras.optimizers import Adam\nfrom tensorflow.keras.callbacks import EarlyStopping, ReduceLROnPlateau\nfrom sklearn.metrics import log_loss, confusion_matrix, classification_report","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-25T09:09:59.992192Z","iopub.execute_input":"2025-07-25T09:09:59.992774Z","iopub.status.idle":"2025-07-25T09:10:19.358946Z","shell.execute_reply.started":"2025-07-25T09:09:59.992748Z","shell.execute_reply":"2025-07-25T09:10:19.358359Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 3. データ解凍とディレクトリ作成","metadata":{}},{"cell_type":"code","source":"base_dir = '/kaggle/working'\ntrain_zip = '/kaggle/input/dogs-vs-cats-redux-kernels-edition/train.zip'\ntest_zip = '/kaggle/input/dogs-vs-cats-redux-kernels-edition/test.zip'","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-25T09:10:19.360009Z","iopub.execute_input":"2025-07-25T09:10:19.360440Z","iopub.status.idle":"2025-07-25T09:10:19.364490Z","shell.execute_reply.started":"2025-07-25T09:10:19.360411Z","shell.execute_reply":"2025-07-25T09:10:19.363741Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 再実行時の対策：workingディレクトリをクリーンアップ\n!rm -rf /kaggle/working/*\nos.makedirs(base_dir, exist_ok=True)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-25T09:10:27.870717Z","iopub.execute_input":"2025-07-25T09:10:27.871000Z","iopub.status.idle":"2025-07-25T09:10:28.003642Z","shell.execute_reply.started":"2025-07-25T09:10:27.870975Z","shell.execute_reply":"2025-07-25T09:10:28.002806Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"with zipfile.ZipFile(train_zip, 'r') as zip_ref:\n    zip_ref.extractall(os.path.join(base_dir, 'train_raw'))\n\nwith zipfile.ZipFile(test_zip, 'r') as zip_ref:\n    zip_ref.extractall(os.path.join(base_dir, 'test'))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-25T09:10:32.645561Z","iopub.execute_input":"2025-07-25T09:10:32.646255Z","iopub.status.idle":"2025-07-25T09:10:46.834013Z","shell.execute_reply.started":"2025-07-25T09:10:32.646227Z","shell.execute_reply":"2025-07-25T09:10:46.833295Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# データ格納用フォルダ作成\nfolders = ['images/train/dog', 'images/train/cat', 'images/val/dog', 'images/val/cat']\nfor folder in folders:\n    os.makedirs(os.path.join(base_dir, folder), exist_ok=True)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-25T09:10:56.969733Z","iopub.execute_input":"2025-07-25T09:10:56.970012Z","iopub.status.idle":"2025-07-25T09:10:56.974760Z","shell.execute_reply.started":"2025-07-25T09:10:56.969993Z","shell.execute_reply":"2025-07-25T09:10:56.973993Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 3. train_rawからtrain/valにデータを分割\ntrain_path = os.path.join(base_dir, 'train_raw/train')\nall_images = os.listdir(train_path)\nrandom.shuffle(all_images)\n\nsplit_ratio = 0.8  # 80%:20%\nsplit_idx = int(len(all_images) * split_ratio)\n\ntrain_images = all_images[:split_idx]\nval_images = all_images[split_idx:]\n\n# 4. 移動関数\ndef move_images(image_list, subset):\n    for img in image_list:\n        label = 'dog' if 'dog' in img else 'cat'\n        dest_dir = os.path.join(base_dir, f'images/{subset}/{label}')\n        src = os.path.join(train_path, img)\n        dst = os.path.join(dest_dir, img)\n        os.replace(src, dst)\n\n# 5. 実際に移動\nmove_images(train_images, 'train')\nmove_images(val_images, 'val')\n\n# 6. 分割結果を確認\nprint(f\"Train total: {len(train_images)}\")\nprint(f\"Val total: {len(val_images)}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-25T09:11:00.674993Z","iopub.execute_input":"2025-07-25T09:11:00.675630Z","iopub.status.idle":"2025-07-25T09:11:01.276906Z","shell.execute_reply.started":"2025-07-25T09:11:00.675607Z","shell.execute_reply":"2025-07-25T09:11:01.276273Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 4. データをトレーニング・バリデーションに分割","metadata":{}},{"cell_type":"code","source":"train_path = os.path.join(base_dir, 'train_raw/train')\nall_images = os.listdir(train_path)\nrandom.shuffle(all_images)\n\nsplit_ratio = 0.8\nsplit_idx = int(len(all_images) * split_ratio)\n\ntrain_images = all_images[:split_idx]\nval_images = all_images[split_idx:]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-25T09:11:08.902728Z","iopub.execute_input":"2025-07-25T09:11:08.903327Z","iopub.status.idle":"2025-07-25T09:11:08.909458Z","shell.execute_reply.started":"2025-07-25T09:11:08.903292Z","shell.execute_reply":"2025-07-25T09:11:08.908705Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def move_images(image_list, subset):\n    for img in image_list:\n        label = 'dog' if 'dog' in img else 'cat'\n        dest_dir = os.path.join(base_dir, f'images/{subset}/{label}')\n        src = os.path.join(train_path, img)\n        dst = os.path.join(dest_dir, img)\n        os.replace(src, dst)\n\nmove_images(train_images, 'train')\nmove_images(val_images, 'val')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-25T09:11:14.119261Z","iopub.execute_input":"2025-07-25T09:11:14.119509Z","iopub.status.idle":"2025-07-25T09:11:14.124673Z","shell.execute_reply.started":"2025-07-25T09:11:14.119492Z","shell.execute_reply":"2025-07-25T09:11:14.123865Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 5. データジェネレーター設定","metadata":{}},{"cell_type":"code","source":"img_size = (224, 224)\nbatch_size = 32\nclasses = ['cat', 'dog']\n\ntrain_datagen = ImageDataGenerator(preprocessing_function=preprocess_input,\n                                   rotation_range=20,\n                                   horizontal_flip=True)\n\nval_datagen = ImageDataGenerator(preprocessing_function=preprocess_input)\n\ntrain_generator = train_datagen.flow_from_directory(\n    os.path.join(base_dir, 'images/train'),\n    target_size=img_size,\n    batch_size=batch_size,\n    class_mode='categorical',\n    classes=classes\n)\n\nval_generator = val_datagen.flow_from_directory(\n    os.path.join(base_dir, 'images/val'),\n    target_size=img_size,\n    batch_size=batch_size,\n    class_mode='categorical',\n    classes=classes,\n    shuffle=False\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-25T09:11:18.456698Z","iopub.execute_input":"2025-07-25T09:11:18.456973Z","iopub.status.idle":"2025-07-25T09:11:18.693136Z","shell.execute_reply.started":"2025-07-25T09:11:18.456952Z","shell.execute_reply":"2025-07-25T09:11:18.692634Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 6. EDA: クラス分布と画像サンプル表示","metadata":{}},{"cell_type":"code","source":"dog_count = len(os.listdir(os.path.join(base_dir, 'images/train/dog')))\ncat_count = len(os.listdir(os.path.join(base_dir, 'images/train/cat')))\nplt.bar(['Dogs', 'Cats'], [dog_count, cat_count], color=['blue', 'orange'])\nplt.title('Training Data Distribution')\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-25T09:11:28.140913Z","iopub.execute_input":"2025-07-25T09:11:28.141189Z","iopub.status.idle":"2025-07-25T09:11:28.405995Z","shell.execute_reply.started":"2025-07-25T09:11:28.141170Z","shell.execute_reply":"2025-07-25T09:11:28.405272Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ランダムサンプル表示\ndog_samples = random.sample(os.listdir(os.path.join(base_dir, 'images/train/dog')), 3)\ncat_samples = random.sample(os.listdir(os.path.join(base_dir, 'images/train/cat')), 3)\n\nfig, axes = plt.subplots(2, 3, figsize=(8, 6))\nfor i, img_name in enumerate(dog_samples):\n    img = Image.open(os.path.join(base_dir, 'images/train/dog', img_name))\n    axes[0, i].imshow(img)\n    axes[0, i].axis('off')\n    axes[0, i].set_title('Dog')\nfor i, img_name in enumerate(cat_samples):\n    img = Image.open(os.path.join(base_dir, 'images/train/cat', img_name))\n    axes[1, i].imshow(img)\n    axes[1, i].axis('off')\n    axes[1, i].set_title('Cat')\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-25T09:11:32.631887Z","iopub.execute_input":"2025-07-25T09:11:32.632160Z","iopub.status.idle":"2025-07-25T09:11:33.114879Z","shell.execute_reply.started":"2025-07-25T09:11:32.632140Z","shell.execute_reply":"2025-07-25T09:11:33.114141Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"Class indices:\", train_generator.class_indices)\nprint(\"Val Generator classes shape:\", val_generator.classes.shape)\nprint(\"First 10 true labels:\", val_generator.classes[:10])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-25T09:11:40.558379Z","iopub.execute_input":"2025-07-25T09:11:40.558963Z","iopub.status.idle":"2025-07-25T09:11:40.563314Z","shell.execute_reply.started":"2025-07-25T09:11:40.558939Z","shell.execute_reply":"2025-07-25T09:11:40.562567Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 7. モデル構築（ResNet50を使用）","metadata":{}},{"cell_type":"code","source":"input_tensor = Input(shape=(224,224,3))\nbase_model = ResNet50(include_top=False, weights='imagenet', input_tensor=input_tensor)\n\nx = base_model.output\nx = GlobalAveragePooling2D()(x)\nx = Dense(256, activation='relu')(x)\nx = Dropout(0.5)(x)\noutput = Dense(2, activation='softmax')(x)\n\nmodel = Model(inputs=base_model.input, outputs=output)\n\n\n# Fine-tuning：最後の10層のみ学習\nfor layer in base_model.layers[:-10]:\n    layer.trainable = False\n\n# ===============================\n# コンパイル設定（ラベルスムージング追加）\n# ===============================\nmodel.compile(\n    optimizer=Adam(learning_rate=1e-4),\n    loss=tf.keras.losses.CategoricalCrossentropy(label_smoothing=0.1),\n    metrics=['accuracy'])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-25T09:12:17.058078Z","iopub.execute_input":"2025-07-25T09:12:17.058368Z","iopub.status.idle":"2025-07-25T09:12:22.739829Z","shell.execute_reply.started":"2025-07-25T09:12:17.058347Z","shell.execute_reply":"2025-07-25T09:12:22.739127Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 分割後のディレクトリ構造を確認\nimport os\n\nprint(\"Train folders:\", os.listdir(os.path.join(base_dir, 'images/train')))\nprint(\"Validation folders:\", os.listdir(os.path.join(base_dir, 'images/val')))\n\n# 各クラスの枚数を確認\nfor subset in ['train', 'val']:\n    for label in ['dog', 'cat']:\n        path = os.path.join(base_dir, f'images/{subset}/{label}')\n        print(f\"{subset}/{label}: {len(os.listdir(path))}\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-25T09:15:48.234123Z","iopub.execute_input":"2025-07-25T09:15:48.234438Z","iopub.status.idle":"2025-07-25T09:15:48.252652Z","shell.execute_reply.started":"2025-07-25T09:15:48.234416Z","shell.execute_reply":"2025-07-25T09:15:48.252043Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ランダムに画像を表示して分割を確認\nimport matplotlib.pyplot as plt\nimport matplotlib.image as mpimg\nimport random\nimport os\n\n# 新しいディレクトリから画像を取得\ntrain_dir = os.path.join(base_dir, 'images/train')\nclasses = ['dog', 'cat']\n\n# クラスをランダムに選んで、画像を1枚選択\nchosen_class = random.choice(classes)\nclass_dir = os.path.join(train_dir, chosen_class)\nsample_img = random.choice(os.listdir(class_dir))\n\n# 画像を表示\nimg_path = os.path.join(class_dir, sample_img)\nimg = mpimg.imread(img_path)\nplt.imshow(img)\nplt.title(f\"Sample from train set: {chosen_class}/{sample_img}\")\nplt.axis('off')\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-25T09:17:17.841443Z","iopub.execute_input":"2025-07-25T09:17:17.842045Z","iopub.status.idle":"2025-07-25T09:17:18.034438Z","shell.execute_reply.started":"2025-07-25T09:17:17.842023Z","shell.execute_reply":"2025-07-25T09:17:18.033688Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 8. 学習設定","metadata":{}},{"cell_type":"code","source":"epochs = 5\nsteps_per_epoch = train_generator.samples // batch_size\nvalidation_steps = val_generator.samples // batch_size\n\nearly_stop = EarlyStopping(monitor='val_loss', patience=3, restore_best_weights=True)\nreduce_lr = ReduceLROnPlateau(monitor='val_loss', factor=0.2, patience=2)\n\nhistory = model.fit(\n    train_generator,\n    steps_per_epoch=steps_per_epoch,\n    epochs=epochs,\n    validation_data=val_generator,\n    validation_steps=validation_steps,\n    callbacks=[early_stop, reduce_lr]\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-24T11:53:29.509624Z","iopub.execute_input":"2025-07-24T11:53:29.509925Z","iopub.status.idle":"2025-07-24T12:12:32.120313Z","shell.execute_reply.started":"2025-07-24T11:53:29.509901Z","shell.execute_reply":"2025-07-24T12:12:32.119722Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 現状のモデルでValデータを評価\nval_loss, val_acc = model.evaluate(val_generator, verbose=1)\nprint(f\"Validation Accuracy: {val_acc:.4f}, Validation Loss: {val_loss:.4f}\")\n\n# クラスインデックス確認\nprint(\"Class indices:\", train_generator.class_indices)\nprint(\"Val Generator classes shape:\", val_generator.classes.shape)\nprint(\"First 10 true labels:\", val_generator.classes[:10])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-24T12:12:32.121191Z","iopub.execute_input":"2025-07-24T12:12:32.121459Z","iopub.status.idle":"2025-07-24T12:12:50.762086Z","shell.execute_reply.started":"2025-07-24T12:12:32.121428Z","shell.execute_reply":"2025-07-24T12:12:50.761407Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from sklearn.metrics import classification_report, log_loss\n\n# 予測値を取得\nval_generator.reset()\npred_probs = model.predict(val_generator, verbose=1)\npred_classes = np.argmax(pred_probs, axis=1)\ntrue_classes = val_generator.classes\n\n# Log Loss\nval_log_loss = log_loss(true_classes, pred_probs)\nprint(f\"Validation Log Loss: {val_log_loss:.6f}\")\n\n# Classification Report\ntarget_names = list(train_generator.class_indices.keys())\nprint(\"\\nClassification Report:\")\nprint(classification_report(true_classes, pred_classes, target_names=target_names))\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-24T12:12:50.762913Z","iopub.execute_input":"2025-07-24T12:12:50.763214Z","iopub.status.idle":"2025-07-24T12:13:14.395123Z","shell.execute_reply.started":"2025-07-24T12:12:50.763184Z","shell.execute_reply":"2025-07-24T12:13:14.394311Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 9. 評価と結果可視化","metadata":{}},{"cell_type":"code","source":"plt.plot(history.history['accuracy'], label='Train Acc')\nplt.plot(history.history['val_accuracy'], label='Val Acc')\nplt.title('Accuracy')\nplt.legend()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-24T12:13:14.396047Z","iopub.execute_input":"2025-07-24T12:13:14.396593Z","iopub.status.idle":"2025-07-24T12:13:14.563865Z","shell.execute_reply.started":"2025-07-24T12:13:14.396565Z","shell.execute_reply":"2025-07-24T12:13:14.563187Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# 10. Log Loss計算","metadata":{}},{"cell_type":"code","source":"val_generator.reset()\nval_preds = model.predict(val_generator)\nval_labels = val_generator.classes\nprint(\"Validation Log Loss:\", log_loss(val_labels, val_preds))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-24T12:13:14.564546Z","iopub.execute_input":"2025-07-24T12:13:14.564789Z","iopub.status.idle":"2025-07-24T12:13:29.956935Z","shell.execute_reply.started":"2025-07-24T12:13:14.564770Z","shell.execute_reply":"2025-07-24T12:13:29.956312Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"#　提出","metadata":{}},{"cell_type":"code","source":"#Test Generator\ntest_datagen = ImageDataGenerator(preprocessing_function=preprocess_input)\ntest_generator = test_datagen.flow_from_directory(\n    directory='/kaggle/working/test',\n    target_size=img_size,\n    batch_size=batch_size,\n    class_mode=None,\n    shuffle=False\n)\n\n#予測\npredictions = model.predict(test_generator)\n\n#提出用DataFrame作成\nsubmission = pd.DataFrame({\n    'id': [int(os.path.basename(path).split('.')[0]) for path in test_generator.filenames],\n    'label': predictions[:, 1]  # 犬クラスの確率\n})\n\n#ID順に並び替え\nsubmission = submission.sort_values('id').reset_index(drop=True)\n\n#CSV出力\nsubmission.to_csv('submission.csv', index=False)\nprint(\"Kaggle提出用 submission.csv 完了\")\n\n#確認\nprint(submission.head())\nprint(submission.tail())\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-24T12:13:29.957563Z","iopub.execute_input":"2025-07-24T12:13:29.957810Z","iopub.status.idle":"2025-07-24T12:14:19.499605Z","shell.execute_reply.started":"2025-07-24T12:13:29.957792Z","shell.execute_reply":"2025-07-24T12:14:19.498846Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"unique, counts = np.unique(val_generator.classes, return_counts=True)\nprint(dict(zip(unique, counts)))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-24T12:14:19.500448Z","iopub.execute_input":"2025-07-24T12:14:19.500738Z","iopub.status.idle":"2025-07-24T12:14:19.505789Z","shell.execute_reply.started":"2025-07-24T12:14:19.500712Z","shell.execute_reply":"2025-07-24T12:14:19.505016Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"Train total:\", len(train_images))\nprint(\"Val total:\", len(val_images))\nprint(\"重複画像数:\", len(set(train_images) & set(val_images)))\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-07-24T12:14:19.506577Z","iopub.execute_input":"2025-07-24T12:14:19.506879Z","iopub.status.idle":"2025-07-24T12:14:19.519319Z","shell.execute_reply.started":"2025-07-24T12:14:19.506852Z","shell.execute_reply":"2025-07-24T12:14:19.518743Z"}},"outputs":[],"execution_count":null}]}