{"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":"none","dataSources":[{"sourceId":13836,"databundleVersionId":1718836,"sourceType":"competition"}],"dockerImageVersionId":31089,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"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-08-11T18:51:31.218900Z","iopub.execute_input":"2025-08-11T18:51:31.219232Z","iopub.status.idle":"2025-08-11T18:52:05.001406Z","shell.execute_reply.started":"2025-08-11T18:51:31.219202Z","shell.execute_reply":"2025-08-11T18:52:05.000314Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport json\nimport pandas as pd\nimport tensorflow as tf\nfrom tensorflow.keras import layers, models, callbacks\nfrom sklearn.model_selection import train_test_split\n\n# ========== Paths ==========\nWORK_DIR = \"/kaggle/input/cassava-leaf-disease-classification\"\nTRAIN_CSV = os.path.join(WORK_DIR, \"train.csv\")\nTRAIN_IMG_DIR = os.path.join(WORK_DIR, \"train_images\")\nLABEL_MAP_JSON = os.path.join(WORK_DIR, \"label_num_to_disease_map.json\")\n\n# ========== Load dataset ==========\ntrain_df = pd.read_csv(TRAIN_CSV)\nwith open(LABEL_MAP_JSON) as f:\n    label_map = json.load(f)\n\ntrain_df[\"label\"] = train_df[\"label\"].astype(str)\n\n# Stratified train-validation split\ntrain_df, val_df = train_test_split(train_df, test_size=0.2, stratify=train_df[\"label\"], random_state=42)\n\n# ========== Parameters ==========\nIMG_SIZE = 224\nBATCH_SIZE = 32\nNUM_CLASSES = len(label_map)\n\n# ========== Data Generators ==========\ntrain_datagen = tf.keras.preprocessing.image.ImageDataGenerator(\n    rescale=1./255,\n    rotation_range=20,\n    width_shift_range=0.1,\n    height_shift_range=0.1,\n    shear_range=0.1,\n    zoom_range=0.1,\n    horizontal_flip=True,\n    fill_mode='nearest'\n)\n\nval_datagen = tf.keras.preprocessing.image.ImageDataGenerator(rescale=1./255)\n\ntrain_data = train_datagen.flow_from_dataframe(\n    train_df,\n    directory=TRAIN_IMG_DIR,\n    x_col=\"image_id\",\n    y_col=\"label\",\n    target_size=(IMG_SIZE, IMG_SIZE),\n    class_mode=\"categorical\",\n    batch_size=BATCH_SIZE,\n    shuffle=True\n)\n\nval_data = val_datagen.flow_from_dataframe(\n    val_df,\n    directory=TRAIN_IMG_DIR,\n    x_col=\"image_id\",\n    y_col=\"label\",\n    target_size=(IMG_SIZE, IMG_SIZE),\n    class_mode=\"categorical\",\n    batch_size=BATCH_SIZE,\n    shuffle=False\n)\n\n# ========== Soft Attention Module ==========\ndef soft_attention_module(input_tensor):\n    # Channel Attention\n    channel_avg = layers.GlobalAveragePooling2D()(input_tensor)\n    channel_avg = layers.Reshape((1, 1, channel_avg.shape[1]))(channel_avg)\n    channel_weights = layers.Conv2D(filters=input_tensor.shape[-1] // 8, kernel_size=1, activation='relu')(channel_avg)\n    channel_weights = layers.Conv2D(filters=input_tensor.shape[-1], kernel_size=1, activation='sigmoid')(channel_weights)\n    channel_refined = layers.Multiply()([input_tensor, channel_weights])\n    \n    # Spatial Attention\n    spatial_weights = layers.Conv2D(filters=1, kernel_size=7, padding='same', activation='sigmoid')(channel_refined)\n    refined_features = layers.Multiply()([channel_refined, spatial_weights])\n    return refined_features\n\n# ========== Build Model ==========\ndef build_cddnet(input_shape=(IMG_SIZE, IMG_SIZE, 3), num_classes=NUM_CLASSES):\n    base_model = tf.keras.applications.MobileNetV3Small(\n        input_shape=input_shape,\n        include_top=False,\n        weights='imagenet'\n    )\n    base_model.trainable = False  # Freeze backbone initially\n    \n    inputs = layers.Input(shape=input_shape)\n    x = base_model(inputs, training=False)\n    x = soft_attention_module(x)\n    x = layers.GlobalAveragePooling2D()(x)\n    x = layers.Dense(512, activation='relu')(x)\n    x = layers.Dropout(0.3)(x)\n    outputs = layers.Dense(num_classes, activation='softmax')(x)\n    \n    model = models.Model(inputs, outputs, name='CDDNet')\n    return model\n\nmodel = build_cddnet()\n\n# ========== Compile ==========\nmodel.compile(\n    optimizer=tf.keras.optimizers.Adam(learning_rate=1e-3),\n    loss='categorical_crossentropy',\n    metrics=['accuracy']\n)\n\nmodel.summary()\n\n# ========== Callbacks ==========\nreduce_lr = callbacks.ReduceLROnPlateau(monitor='val_loss', factor=0.5, patience=3, verbose=1)\nearly_stop = callbacks.EarlyStopping(monitor='val_loss', patience=7, restore_best_weights=True, verbose=1)\n\n# ========== Train ==========\nhistory = model.fit(\n    train_data,\n    validation_data=val_data,\n    epochs=30,\n    callbacks=[reduce_lr, early_stop]\n)\n\n# ========== Optional Fine-tuning ==========\n# Unfreeze backbone for fine-tuning\nbase_model = model.get_layer('mobilenetv3small_100')\nbase_model.trainable = True\n\nmodel.compile(\n    optimizer=tf.keras.optimizers.Adam(learning_rate=1e-4),\n    loss='categorical_crossentropy',\n    metrics=['accuracy']\n)\n\nhistory_finetune = model.fit(\n    train_data,\n    validation_data=val_data,\n    epochs=30,\n    callbacks=[reduce_lr, early_stop]\n)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-11T18:52:05.003124Z","iopub.execute_input":"2025-08-11T18:52:05.003548Z","iopub.status.idle":"2025-08-11T22:13:12.134259Z","shell.execute_reply.started":"2025-08-11T18:52:05.003523Z","shell.execute_reply":"2025-08-11T22:13:12.132615Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from tensorflow.keras import callbacks\n\ncheckpoint_best = callbacks.ModelCheckpoint(\n    \"cassava_best.h5\",          # filename for best model\n    monitor=\"val_accuracy\",     # metric to monitor\n    mode=\"max\",                 # want to maximize accuracy\n    save_best_only=True,        # save only when val_accuracy improves\n    verbose=1\n)\n\ncheckpoint_last = callbacks.ModelCheckpoint(\n    \"cassava_last.h5\",          # filename for last epoch model\n    save_best_only=False,       # always save last epoch model\n    verbose=1\n)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-11T22:33:17.798429Z","iopub.execute_input":"2025-08-11T22:33:17.798802Z","iopub.status.idle":"2025-08-11T22:33:17.804435Z","shell.execute_reply.started":"2025-08-11T22:33:17.798774Z","shell.execute_reply":"2025-08-11T22:33:17.803439Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Fix: get backbone layer by correct name\nbase_model = model.get_layer('MobileNetV3Small')\nbase_model.trainable = True\n\n# Recompile before fine-tuning\nmodel.compile(\n    optimizer=tf.keras.optimizers.Adam(learning_rate=1e-4),\n    loss='categorical_crossentropy',\n    metrics=['accuracy']\n)\n\n# Visualize model architecture\nfrom tensorflow.keras.utils import plot_model\nplot_model(model, to_file='model_architecture.png', show_shapes=True, show_layer_names=True)\n\nfrom IPython.display import Image\nImage('model_architecture.png')\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-11T23:20:22.361915Z","iopub.execute_input":"2025-08-11T23:20:22.362858Z","iopub.status.idle":"2025-08-11T23:20:23.013362Z","shell.execute_reply.started":"2025-08-11T23:20:22.362810Z","shell.execute_reply":"2025-08-11T23:20:23.012156Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"val_loss, val_accuracy = model.evaluate(val_data)\nprint(f\"Validation Accuracy: {val_accuracy:.4f}\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-11T23:21:05.014057Z","iopub.execute_input":"2025-08-11T23:21:05.014390Z","iopub.status.idle":"2025-08-11T23:21:46.599944Z","shell.execute_reply.started":"2025-08-11T23:21:05.014367Z","shell.execute_reply":"2025-08-11T23:21:46.599019Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import numpy as np\nimport matplotlib.pyplot as plt\nfrom sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay\n\n# Predict class probabilities\nval_data.reset()  # Make sure data is at start\ny_pred_probs = model.predict(val_data, verbose=1)\n\n# Convert predicted probabilities to class indices\ny_pred = np.argmax(y_pred_probs, axis=1)\n\n# True labels from validation generator\ny_true = val_data.classes\n\n# Generate confusion matrix\ncm = confusion_matrix(y_true, y_pred)\n\n# Visualize confusion matrix\ndisp = ConfusionMatrixDisplay(confusion_matrix=cm, display_labels=val_data.class_indices.keys())\ndisp.plot(cmap=plt.cm.Blues)\nplt.title('Confusion Matrix')\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-11T23:22:26.734119Z","iopub.execute_input":"2025-08-11T23:22:26.734383Z","iopub.status.idle":"2025-08-11T23:23:02.228433Z","shell.execute_reply.started":"2025-08-11T23:22:26.734362Z","shell.execute_reply":"2025-08-11T23:23:02.227488Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt\n\ndef plot_training_history(history, title_suffix=\"\"):\n    acc = history.history['accuracy']\n    val_acc = history.history['val_accuracy']\n    loss = history.history['loss']\n    val_loss = history.history['val_loss']\n    epochs = range(1, len(acc) + 1)\n\n    plt.figure(figsize=(14, 5))\n\n    plt.subplot(1, 2, 1)\n    plt.plot(epochs, acc, 'b-', label='Training Accuracy')\n    plt.plot(epochs, val_acc, 'r-', label='Validation Accuracy')\n    plt.title(f'Training and Validation Accuracy {title_suffix}')\n    plt.xlabel('Epochs')\n    plt.ylabel('Accuracy')\n    plt.legend()\n\n    plt.subplot(1, 2, 2)\n    plt.plot(epochs, loss, 'b-', label='Training Loss')\n    plt.plot(epochs, val_loss, 'r-', label='Validation Loss')\n    plt.title(f'Training and Validation Loss {title_suffix}')\n    plt.xlabel('Epochs')\n    plt.ylabel('Loss')\n    plt.legend()\n\n    plt.show()\n\n# Plot for initial training\nplot_training_history(history, title_suffix=\"(Initial Training)\")\n\n# Plot for fine-tuning if available\nif 'history_finetune' in locals():\n    plot_training_history(history_finetune, title_suffix=\"(Fine-tuning)\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-11T23:26:14.814172Z","iopub.execute_input":"2025-08-11T23:26:14.814514Z","iopub.status.idle":"2025-08-11T23:26:15.206469Z","shell.execute_reply.started":"2025-08-11T23:26:14.814490Z","shell.execute_reply":"2025-08-11T23:26:15.205652Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import numpy as np\nimport matplotlib.pyplot as plt\nimport tensorflow as tf\nimport cv2\nfrom tensorflow.keras.preprocessing import image\nfrom tensorflow.keras.models import Model\nimport os\n\n# ========== Paths ==========\nimg_path = \"/kaggle/input/cassava-leaf-disease-classification/train_images/288080098.jpg\"\n\n# ========== Parameters ==========\nIMG_SIZE = 224\nlast_conv_layer_name = 'multiply_10'  # Last conv layer with spatial info\nclass_names = ['Cassava Bacterial Blight', 'Cassava Brown Streak Disease', 'Cassava Green Mottle', 'Cassava Mosaic Disease', 'Healthy']\n\n# ========== Load model (assuming it's already loaded as `model`) ==========\n# If not loaded, load here:\n# model = tf.keras.models.load_model('your_model_path')\n\n# ========== Preprocess Image ==========\ndef preprocess_img(img_path, target_size=(IMG_SIZE, IMG_SIZE)):\n    img = image.load_img(img_path, target_size=target_size)\n    img_array = image.img_to_array(img)\n    img_array = img_array / 255.0\n    img_array = np.expand_dims(img_array, axis=0)\n    return img_array\n\n# ========== Generate Grad-CAM heatmap ==========\ndef make_gradcam_heatmap(img_array, model, last_conv_layer_name, pred_index=None):\n    grad_model = Model(\n        inputs=[model.inputs],\n        outputs=[model.get_layer(last_conv_layer_name).output, model.output]\n    )\n    \n    with tf.GradientTape() as tape:\n        conv_outputs, predictions = grad_model(img_array)\n        if pred_index is None:\n            pred_index = tf.argmax(predictions[0])\n        class_channel = predictions[:, pred_index]\n    \n    grads = tape.gradient(class_channel, conv_outputs)\n    \n    pooled_grads = tf.reduce_mean(grads, axis=(0, 1, 2))\n    conv_outputs = conv_outputs[0]\n    heatmap = conv_outputs @ pooled_grads[..., tf.newaxis]\n    heatmap = tf.squeeze(heatmap)\n    heatmap = tf.maximum(heatmap, 0) / (tf.math.reduce_max(heatmap) + 1e-8)\n    \n    return heatmap.numpy()\n\n# ========== Overlay heatmap on image ==========\ndef overlay_heatmap(img_path, heatmap, alpha=0.4, colormap=cv2.COLORMAP_JET):\n    img = cv2.imread(img_path)\n    img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n    heatmap = cv2.resize(heatmap, (img.shape[1], img.shape[0]))\n    heatmap = np.uint8(255 * heatmap)\n    heatmap_colored = cv2.applyColorMap(heatmap, colormap)\n    \n    overlayed_img = heatmap_colored * alpha + img\n    overlayed_img = np.clip(overlayed_img, 0, 255).astype(np.uint8)\n    return overlayed_img\n\n# ========== Plot training history ==========\ndef plot_history(history):\n    plt.figure(figsize=(12,5))\n    plt.subplot(1,2,1)\n    plt.plot(history.history['accuracy'], label='Train Accuracy')\n    plt.plot(history.history['val_accuracy'], label='Val Accuracy')\n    plt.title('Accuracy')\n    plt.xlabel('Epoch')\n    plt.ylabel('Accuracy')\n    plt.legend()\n\n    plt.subplot(1,2,2)\n    plt.plot(history.history['loss'], label='Train Loss')\n    plt.plot(history.history['val_loss'], label='Val Loss')\n    plt.title('Loss')\n    plt.xlabel('Epoch')\n    plt.ylabel('Loss')\n    plt.legend()\n    plt.show()\n\n# ========== Run Grad-CAM and display ==========\nimg_array = preprocess_img(img_path)\npreds = model.predict(img_array)\npredicted_class = np.argmax(preds[0])\nprint(f\"Predicted Class: {class_names[predicted_class]} (Confidence: {preds[0][predicted_class]:.3f})\")\n\nheatmap = make_gradcam_heatmap(img_array, model, last_conv_layer_name, pred_index=predicted_class)\noverlay_img = overlay_heatmap(img_path, heatmap)\n\nplt.figure(figsize=(10, 10))\nplt.subplot(1, 2, 1)\nplt.title('Original Image')\nplt.axis('off')\nplt.imshow(image.load_img(img_path))\n\nplt.subplot(1, 2, 2)\nplt.title('Grad-CAM Overlay')\nplt.axis('off')\nplt.imshow(overlay_img)\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-11T23:35:21.810871Z","iopub.execute_input":"2025-08-11T23:35:21.811193Z","iopub.status.idle":"2025-08-11T23:35:22.686300Z","shell.execute_reply.started":"2025-08-11T23:35:21.811169Z","shell.execute_reply":"2025-08-11T23:35:22.685367Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"for layer in model.layers:\n    print(layer.name, layer.output.shape)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-11T23:31:43.641542Z","iopub.execute_input":"2025-08-11T23:31:43.641857Z","iopub.status.idle":"2025-08-11T23:31:43.647321Z","shell.execute_reply.started":"2025-08-11T23:31:43.641834Z","shell.execute_reply":"2025-08-11T23:31:43.646541Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import time\n\ndef measure_fps(model, data_generator, steps=100):\n    start_time = time.time()\n    for i, (x_batch, _) in enumerate(data_generator):\n        if i >= steps:\n            break\n        _ = model.predict(x_batch)\n    end_time = time.time()\n    total_time = end_time - start_time\n    fps = (steps * data_generator.batch_size) / total_time\n    return fps\n\n# Measure your model's FPS on validation set\nfps_your_model = measure_fps(model, val_data, steps=50)\nprint(f\"Your model FPS: {fps_your_model:.2f}\")\n\n# For SOTA models, you'd need their implementations and run similar timing.\n# You can create a dictionary to compare:\nfps_comparison = {\n    \"Your Model (CDDNet)\": fps_your_model,\n    \"MobileNetV2\": 50,  # Hypothetical FPS\n    \"ResNet50\": 30,\n    \"EfficientNetB0\": 45\n}\n\n# Plot FPS comparison\nplt.bar(fps_comparison.keys(), fps_comparison.values())\nplt.ylabel(\"FPS\")\nplt.title(\"FPS Comparison with SOTA Models\")\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-11T23:26:57.592891Z","iopub.execute_input":"2025-08-11T23:26:57.593208Z","iopub.status.idle":"2025-08-11T23:27:22.102260Z","shell.execute_reply.started":"2025-08-11T23:26:57.593187Z","shell.execute_reply":"2025-08-11T23:27:22.101486Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import numpy as np\nimport matplotlib.pyplot as plt\nimport tensorflow as tf\nimport cv2\nimport os\nimport random\nfrom tensorflow.keras.preprocessing import image\nfrom tensorflow.keras.models import Model\n\n# ========== Paths ==========\nIMG_DIR = \"/kaggle/input/cassava-leaf-disease-classification/train_images\"\nIMG_SIZE = 224\nlast_conv_layer_name = 'multiply_10'\nclass_names = ['Cassava Bacterial Blight', 'Cassava Brown Streak Disease', 'Cassava Green Mottle', 'Cassava Mosaic Disease', 'Healthy']\n\n# ========== Helper functions ==========\n\ndef preprocess_img(img_path, target_size=(IMG_SIZE, IMG_SIZE)):\n    img = image.load_img(img_path, target_size=target_size)\n    img_array = image.img_to_array(img)\n    img_array = img_array / 255.0\n    img_array = np.expand_dims(img_array, axis=0)\n    return img_array\n\ndef make_gradcam_heatmap(img_array, model, last_conv_layer_name, pred_index=None):\n    grad_model = Model(\n        inputs=[model.inputs],\n        outputs=[model.get_layer(last_conv_layer_name).output, model.output]\n    )\n    \n    with tf.GradientTape() as tape:\n        conv_outputs, predictions = grad_model(img_array)\n        if pred_index is None:\n            pred_index = tf.argmax(predictions[0])\n        class_channel = predictions[:, pred_index]\n    \n    grads = tape.gradient(class_channel, conv_outputs)\n    pooled_grads = tf.reduce_mean(grads, axis=(0, 1, 2))\n    conv_outputs = conv_outputs[0]\n    heatmap = conv_outputs @ pooled_grads[..., tf.newaxis]\n    heatmap = tf.squeeze(heatmap)\n    heatmap = tf.maximum(heatmap, 0) / (tf.math.reduce_max(heatmap) + 1e-8)\n    \n    return heatmap.numpy()\n\ndef overlay_heatmap(img_path, heatmap, alpha=0.4, colormap=cv2.COLORMAP_JET):\n    img = cv2.imread(img_path)\n    img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n    heatmap = cv2.resize(heatmap, (img.shape[1], img.shape[0]))\n    heatmap = np.uint8(255 * heatmap)\n    heatmap_colored = cv2.applyColorMap(heatmap, colormap)\n    \n    overlayed_img = heatmap_colored * alpha + img\n    overlayed_img = np.clip(overlayed_img, 0, 255).astype(np.uint8)\n    return overlayed_img\n\n# ========== Main code ==========\n\n# Randomly pick 4 images from training directory\nall_images = os.listdir(IMG_DIR)\nsample_images = random.sample(all_images, 4)\n\nplt.figure(figsize=(20, 5))\nfor i, img_name in enumerate(sample_images):\n    img_path = os.path.join(IMG_DIR, img_name)\n    img_array = preprocess_img(img_path)\n    \n    preds = model.predict(img_array)\n    pred_class = np.argmax(preds[0])\n    confidence = preds[0][pred_class]\n    \n    heatmap = make_gradcam_heatmap(img_array, model, last_conv_layer_name, pred_index=pred_class)\n    overlay_img = overlay_heatmap(img_path, heatmap)\n    \n    # Plot original image\n    plt.subplot(2, 4, i + 1)\n    plt.imshow(image.load_img(img_path))\n    plt.title(f\"Orig\\n{img_name[:15]}\")\n    plt.axis('off')\n    \n    # Plot Grad-CAM overlay\n    plt.subplot(2, 4, i + 5)\n    plt.imshow(overlay_img)\n    plt.title(f\"Pred: {class_names[pred_class]}\\nConf: {confidence:.2f}\")\n    plt.axis('off')\n\nplt.tight_layout()\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-11T23:38:55.024992Z","iopub.execute_input":"2025-08-11T23:38:55.025321Z","iopub.status.idle":"2025-08-11T23:38:58.224800Z","shell.execute_reply.started":"2025-08-11T23:38:55.025274Z","shell.execute_reply":"2025-08-11T23:38:58.223824Z"}},"outputs":[],"execution_count":null}]}