{"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"}],"dockerImageVersionId":31193,"isInternetEnabled":false,"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-11-23T21:41:09.153213Z","iopub.execute_input":"2025-11-23T21:41:09.153441Z","iopub.status.idle":"2025-11-23T21:41:12.049117Z","shell.execute_reply.started":"2025-11-23T21:41:09.153423Z","shell.execute_reply":"2025-11-23T21:41:12.048465Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport numpy as np\nimport pandas as pd\nimport tensorflow as tf\nfrom sklearn.model_selection import train_test_split\n\nprint(\"TensorFlow:\", tf.__version__)\nprint(\"Num GPUs Available: \", len(tf.config.list_physical_devices('GPU')))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-23T21:41:36.108428Z","iopub.execute_input":"2025-11-23T21:41:36.108617Z","iopub.status.idle":"2025-11-23T21:41:36.113258Z","shell.execute_reply.started":"2025-11-23T21:41:36.108601Z","shell.execute_reply":"2025-11-23T21:41:36.112382Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 1. Load Data\ndf = pd.read_csv(\"/kaggle/input/csiro-biomass/train.csv\")\n\n# 2. Pivot Data to Wide Format (One row per image, 5 target columns)\n# We need to pivot on 'image_path' (or sample_id) and 'target_name'\npivot_df = df.pivot_table(index='image_path', columns='target_name', values='target').reset_index()\n\n# Ensure columns are in a consistent order\ntarget_cols = ['Dry_Green_g', 'Dry_Dead_g', 'Dry_Clover_g', 'GDM_g', 'Dry_Total_g']\npivot_df = pivot_df[['image_path'] + target_cols]\n\n# Fill NaNs if any (though dataset seems complete, good practice)\npivot_df[target_cols] = pivot_df[target_cols].fillna(0.0)\n\nprint(\"Pivoted Shape:\", pivot_df.shape)\ndisplay(pivot_df.head())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-23T21:41:36.11422Z","iopub.execute_input":"2025-11-23T21:41:36.114769Z","iopub.status.idle":"2025-11-23T21:41:36.373221Z","shell.execute_reply.started":"2025-11-23T21:41:36.114742Z","shell.execute_reply":"2025-11-23T21:41:36.372636Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 3. Split Data\ntrain_df, val_df = train_test_split(pivot_df, test_size=0.2, random_state=42)\n\nprint(\"Train images:\", len(train_df))\nprint(\"Val images:\", len(val_df))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-23T21:41:51.658884Z","iopub.execute_input":"2025-11-23T21:41:51.659142Z","iopub.status.idle":"2025-11-23T21:41:51.665255Z","shell.execute_reply.started":"2025-11-23T21:41:51.659122Z","shell.execute_reply":"2025-11-23T21:41:51.66466Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# 4. Create TensorFlow Dataset\nIMG_SIZE = 512\nBATCH_SIZE = 8\n\ndef load_image(path):\n    # Load and preprocess image\n    img = tf.io.read_file(\"/kaggle/input/csiro-biomass/\" + path)\n    img = tf.image.decode_jpeg(img, channels=3)\n    img = tf.image.resize(img, (IMG_SIZE, IMG_SIZE))\n    img = tf.cast(img, tf.float32) / 255.0\n    return img\n\ndef create_dataset(dataframe, batch_size=BATCH_SIZE, shuffle=False):\n    # Inputs: Image paths\n    image_paths = dataframe['image_path'].values\n    \n    # Outputs: 5 Target values\n    targets = dataframe[target_cols].values.astype(np.float32)\n    \n    ds = tf.data.Dataset.from_tensor_slices((image_paths, targets))\n    \n    def map_func(path, target):\n        return load_image(path), target\n    \n    ds = ds.map(map_func, num_parallel_calls=tf.data.AUTOTUNE)\n    \n    if shuffle:\n        ds = ds.shuffle(buffer_size=1000)\n        \n    ds = ds.batch(batch_size).prefetch(tf.data.AUTOTUNE)\n    return ds\n\ntrain_ds = create_dataset(train_df, shuffle=True)\nval_ds = create_dataset(val_df, shuffle=False)\n\n# Verify shapes\nfor img_batch, target_batch in train_ds.take(1):\n    print(\"Image batch shape:\", img_batch.shape)\n    print(\"Target batch shape:\", target_batch.shape)\n    print(\"Sample targets:\", target_batch[0].numpy())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-23T21:44:35.711178Z","iopub.execute_input":"2025-11-23T21:44:35.711716Z","iopub.status.idle":"2025-11-23T21:44:42.269168Z","shell.execute_reply.started":"2025-11-23T21:44:35.71169Z","shell.execute_reply":"2025-11-23T21:44:42.268482Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"for image, targets in train_ds.take(1):\n    print(targets)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-23T21:45:03.793734Z","iopub.execute_input":"2025-11-23T21:45:03.794458Z","iopub.status.idle":"2025-11-23T21:45:08.270874Z","shell.execute_reply.started":"2025-11-23T21:45:03.794432Z","shell.execute_reply":"2025-11-23T21:45:08.270221Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt\n\ntarget_names = ['Dry_Green_g', 'Dry_Dead_g', 'Dry_Clover_g', 'GDM_g', 'Dry_Total_g']\n\nplt.figure(figsize=(15, 5))\nfor images, targets in train_ds.take(1):\n    for i in range(5):\n        ax = plt.subplot(1, 5, i + 1)\n        plt.imshow(images[i].numpy())\n        \n        # Create a string for the targets\n        t = targets[i].numpy()\n        title_str = \"\\n\".join([f\"{n}: {v:.1f}\" for n, v in zip(target_names, t)])\n        \n        plt.title(title_str, fontsize=8, loc='left')\n        plt.axis(\"off\")\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-23T21:45:13.410422Z","iopub.execute_input":"2025-11-23T21:45:13.411019Z","iopub.status.idle":"2025-11-23T21:45:18.803177Z","shell.execute_reply.started":"2025-11-23T21:45:13.410995Z","shell.execute_reply":"2025-11-23T21:45:18.802294Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import tensorflow as tf\nfrom tensorflow.keras.layers import (\n    Conv2D, SeparableConv2D, BatchNormalization, ReLU, Add,\n    GlobalAveragePooling2D, Dense, Dropout, Input, Multiply, Reshape\n)\nfrom tensorflow.keras.models import Model\n\n# ---------- HELPERS ----------\n\ndef conv_bn_relu(x, filters, kernel=3, stride=1):\n    x = Conv2D(filters, kernel, strides=stride, padding=\"same\", use_bias=False)(x)\n    x = BatchNormalization()(x)\n    return ReLU()(x)\n\ndef depthwise_block(x, filters, stride=1):\n    x = SeparableConv2D(filters, 3, strides=stride, padding=\"same\", use_bias=False)(x)\n    x = BatchNormalization()(x)\n    return ReLU()(x)\n\ndef squeeze_excitation(x, reduction=16):\n    f = x.shape[-1]\n    se = GlobalAveragePooling2D()(x)\n    se = Dense(f // reduction, activation=\"relu\")(se)\n    se = Dense(f, activation=\"sigmoid\")(se)\n    se = Reshape((1, 1, f))(se)\n    return Multiply()([x, se])\n\ndef residual_block(x, filters, stride=1):\n    shortcut = x\n\n    x = depthwise_block(x, filters, stride=stride)\n    x = depthwise_block(x, filters)\n\n    if shortcut.shape[-1] != filters or stride != 1:\n        shortcut = Conv2D(filters, 1, strides=stride, padding=\"same\", use_bias=False)(shortcut)\n        shortcut = BatchNormalization()(shortcut)\n\n    x = Add()([x, shortcut])\n    return ReLU()(x)\n\n# ---------- FINAL MODEL ----------\n\ndef build_expanded_model(img_size=512):\n    inputs = Input(shape=(img_size, img_size, 3))\n\n    # Stem\n    x = conv_bn_relu(inputs, 32, stride=2)\n    x = residual_block(x, 32)\n\n    # Stage 1 (2 residual blocks)\n    x = conv_bn_relu(x, 64)\n    x = residual_block(x, 64, stride=2)\n    x = residual_block(x, 64)\n    x = squeeze_excitation(x)\n\n    # Stage 2 (3 residual blocks)\n    x = conv_bn_relu(x, 128)\n    x = residual_block(x, 128, stride=2)\n    x = residual_block(x, 128)\n    x = residual_block(x, 128)\n    x = squeeze_excitation(x)\n\n    # Stage 3 (3 residual blocks)\n    x = conv_bn_relu(x, 256)\n    x = residual_block(x, 256, stride=2)\n    x = residual_block(x, 256)\n    x = residual_block(x, 256)\n    x = squeeze_excitation(x)\n\n    # Stage 4 (2 deep blocks + final SE)\n    x = conv_bn_relu(x, 512)\n    x = residual_block(x, 512, stride=2)\n    x = residual_block(x, 512)\n    x = squeeze_excitation(x)\n\n    # Head\n    x = GlobalAveragePooling2D()(x)\n\n    x = Dense(768, activation=\"relu\")(x)\n    x = Dropout(0.25)(x)\n\n    x = Dense(512, activation=\"relu\")(x)\n    x = Dropout(0.2)(x)\n\n    x = Dense(256, activation=\"relu\")(x)\n    x = Dropout(0.15)(x)\n\n    x = Dense(128, activation=\"relu\")(x)\n\n    outputs = Dense(5)(x)\n\n    return Model(inputs, outputs)\n\n\nmodel = build_expanded_model(IMG_SIZE)\nmodel.summary()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-23T22:16:05.766095Z","iopub.execute_input":"2025-11-23T22:16:05.766403Z","iopub.status.idle":"2025-11-23T22:16:06.405868Z","shell.execute_reply.started":"2025-11-23T22:16:05.766381Z","shell.execute_reply":"2025-11-23T22:16:06.40532Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import tensorflow.keras.backend as K\n\ndef weighted_r2(y_true, y_pred):\n    # Weights for the 5 biomass components\n    # Dry_Green_g, Dry_Dead_g, Dry_Clover_g, GDM_g, Dry_Total_g\n    weights = tf.constant([0.1, 0.1, 0.1, 0.2, 0.5], dtype=tf.float32)\n    \n    # Total Sum of Squares (TSS)\n    mean_y_true = K.mean(y_true, axis=0)\n    tss = K.sum(K.square(y_true - mean_y_true), axis=0)\n    \n    # Residual Sum of Squares (RSS)\n    rss = K.sum(K.square(y_true - y_pred), axis=0)\n    \n    # R2 for each component: 1 - (RSS / (TSS + epsilon))\n    # Adding epsilon to avoid division by zero\n    r2_scores = 1 - (rss / (tss + K.epsilon()))\n    \n    # Weighted Average R2\n    weighted_r2_score = K.sum(r2_scores * weights)\n    \n    return weighted_r2_score\n\n# Compile the model\nmodel.compile(\n    optimizer=tf.keras.optimizers.Adam(learning_rate=1e-4),\n    loss='mse',\n    metrics=['mae', weighted_r2]\n)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-23T22:16:12.326077Z","iopub.execute_input":"2025-11-23T22:16:12.326818Z","iopub.status.idle":"2025-11-23T22:16:12.336223Z","shell.execute_reply.started":"2025-11-23T22:16:12.326793Z","shell.execute_reply":"2025-11-23T22:16:12.335534Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Callbacks\ncheckpoint = tf.keras.callbacks.ModelCheckpoint(\n    'best_model.keras', \n    monitor='val_weighted_r2', \n    mode='max', \n    save_best_only=True, \n    verbose=1\n)\n\nreduce_lr = tf.keras.callbacks.ReduceLROnPlateau(\n    monitor='val_weighted_r2', \n    mode='max', \n    factor=0.5, \n    patience=5, \n    min_lr=1e-6,\n    verbose=1\n)\n\n# Train for full 200 epochs, regardless of metric stagnation\nhistory = model.fit(\n    train_ds,\n    validation_data=val_ds,\n    epochs=70,\n    callbacks=[checkpoint, reduce_lr],\n    verbose=1\n)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-23T22:16:16.011901Z","iopub.execute_input":"2025-11-23T22:16:16.012165Z","iopub.status.idle":"2025-11-23T22:31:40.407893Z","shell.execute_reply.started":"2025-11-23T22:16:16.012145Z","shell.execute_reply":"2025-11-23T22:31:40.407256Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt\n\ndef plot_history(history):\n    loss = history.history['loss']\n    val_loss = history.history['val_loss']\n    mae = history.history['mae']\n    val_mae = history.history['val_mae']\n    r2 = history.history['weighted_r2']\n    val_r2 = history.history['val_weighted_r2']\n    \n    epochs = range(1, len(loss) + 1)\n    \n    plt.figure(figsize=(18, 5))\n    \n    # Plot Loss\n    plt.subplot(1, 3, 1)\n    plt.plot(epochs, loss, 'bo-', label='Training Loss')\n    plt.plot(epochs, val_loss, 'ro-', label='Validation Loss')\n    plt.title('Training and Validation Loss (MSE)')\n    plt.xlabel('Epochs')\n    plt.ylabel('Loss')\n    plt.legend()\n    \n    # Plot MAE\n    plt.subplot(1, 3, 2)\n    plt.plot(epochs, mae, 'bo-', label='Training MAE')\n    plt.plot(epochs, val_mae, 'ro-', label='Validation MAE')\n    plt.title('Training and Validation MAE')\n    plt.xlabel('Epochs')\n    plt.ylabel('MAE')\n    plt.legend()\n    \n    # Plot Weighted R2\n    plt.subplot(1, 3, 3)\n    plt.plot(epochs, r2, 'bo-', label='Training Weighted R2')\n    plt.plot(epochs, val_r2, 'ro-', label='Validation Weighted R2')\n    plt.title('Training and Validation Weighted R2')\n    plt.xlabel('Epochs')\n    plt.ylabel('Weighted R2')\n    plt.legend()\n    \n    plt.tight_layout()\n    plt.show()\n\nplot_history(history)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-23T22:37:39.276371Z","iopub.execute_input":"2025-11-23T22:37:39.27664Z","iopub.status.idle":"2025-11-23T22:37:39.8492Z","shell.execute_reply.started":"2025-11-23T22:37:39.276619Z","shell.execute_reply":"2025-11-23T22:37:39.848594Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model.save(\"model.keras\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-23T22:33:23.34723Z","iopub.execute_input":"2025-11-23T22:33:23.347487Z","iopub.status.idle":"2025-11-23T22:33:24.012969Z","shell.execute_reply.started":"2025-11-23T22:33:23.34747Z","shell.execute_reply":"2025-11-23T22:33:24.012121Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt\nimport numpy as np\n\ndef evaluate_and_visualize(model, val_ds, num_samples=5):\n    # 1. Get all predictions and true values\n    all_images = []\n    all_targets = []\n    all_preds = []\n    \n    print(\"Generating predictions for validation set...\")\n    # Unbatch the dataset to process all samples\n    for images, targets in val_ds:\n        preds = model.predict(images, verbose=0)\n        all_images.append(images.numpy())\n        all_targets.append(targets.numpy())\n        all_preds.append(preds)\n        \n    # Concatenate batches\n    all_images = np.concatenate(all_images, axis=0)\n    all_targets = np.concatenate(all_targets, axis=0)\n    all_preds = np.concatenate(all_preds, axis=0)\n    \n    # 2. Calculate Overall Weighted R2\n    weights = np.array([0.1, 0.1, 0.1, 0.2, 0.5])\n    \n    mean_true = np.mean(all_targets, axis=0)\n    tss = np.sum((all_targets - mean_true)**2, axis=0)\n    rss = np.sum((all_targets - all_preds)**2, axis=0)\n    \n    # Avoid division by zero\n    r2_scores = 1 - (rss / (tss + 1e-7))\n    weighted_r2 = np.sum(r2_scores * weights)\n    \n    print(f\"\\nOverall Weighted R2 Score: {weighted_r2:.4f}\")\n    print(f\"Component R2 Scores:\")\n    target_names = ['Dry_Green_g', 'Dry_Dead_g', 'Dry_Clover_g', 'GDM_g', 'Dry_Total_g']\n    for name, score in zip(target_names, r2_scores):\n        print(f\"  {name:<15}: {score:.4f}\")\n        \n    # 3. Visualize Samples\n    print(f\"\\nVisualizing {num_samples} random samples...\")\n    indices = np.random.choice(len(all_images), num_samples, replace=False)\n    \n    for idx in indices:\n        img = all_images[idx]\n        true = all_targets[idx]\n        pred = all_preds[idx]\n        \n        plt.figure(figsize=(12, 4))\n        \n        # Image\n        plt.subplot(1, 2, 1)\n        plt.imshow(img)\n        plt.axis('off')\n        plt.title(\"Input Image\")\n        \n        # Table of values\n        plt.subplot(1, 2, 2)\n        plt.axis('off')\n        \n        # Header\n        text_str = f\"{'Component':<15} | {'True':>6} | {'Pred':>6} | {'Diff':>6}\\n\"\n        text_str += \"-\"*45 + \"\\n\"\n        \n        # Rows\n        for i, name in enumerate(target_names):\n            diff = pred[i] - true[i]\n            text_str += f\"{name:<15} | {true[i]:6.1f} | {pred[i]:6.1f} | {diff:6.1f}\\n\"\n            \n        plt.text(0, 0.5, text_str, fontsize=12, family='monospace', va='center')\n        plt.tight_layout()\n        plt.show()\n\n# Run evaluation\nevaluate_and_visualize(model, val_ds)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-23T22:33:24.667394Z","iopub.execute_input":"2025-11-23T22:33:24.667642Z","iopub.status.idle":"2025-11-23T22:33:30.938255Z","shell.execute_reply.started":"2025-11-23T22:33:24.667623Z","shell.execute_reply":"2025-11-23T22:33:30.93761Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\nimport numpy as np\nimport tensorflow as tf\nimport os\n\ndef generate_submission(model):\n    print(\"Loading test data...\")\n    test_df = pd.read_csv(\"/kaggle/input/csiro-biomass/test.csv\")\n    \n    # Get unique images to predict on\n    unique_images = test_df['image_path'].unique()\n    print(f\"Found {len(unique_images)} unique test images.\")\n    \n    # Create Dataset\n    def load_image(path):\n        # Check if path exists\n        full_path = \"/kaggle/input/csiro-biomass/\" + path\n        img = tf.io.read_file(full_path)\n        img = tf.image.decode_jpeg(img, channels=3)\n        img = tf.image.resize(img, (IMG_SIZE, IMG_SIZE))\n        img = tf.cast(img, tf.float32) / 255.0\n        return img\n\n    test_ds = tf.data.Dataset.from_tensor_slices(unique_images)\n    test_ds = test_ds.map(load_image, num_parallel_calls=tf.data.AUTOTUNE)\n    test_ds = test_ds.batch(BATCH_SIZE).prefetch(tf.data.AUTOTUNE)\n    \n    print(\"Predicting...\")\n    # Predict\n    predictions = model.predict(test_ds, verbose=1)\n    \n    # Map predictions to image paths\n    # predictions shape: (N_images, 5)\n    # Target order: ['Dry_Green_g', 'Dry_Dead_g', 'Dry_Clover_g', 'GDM_g', 'Dry_Total_g']\n    target_cols = ['Dry_Green_g', 'Dry_Dead_g', 'Dry_Clover_g', 'GDM_g', 'Dry_Total_g']\n    \n    # Create a lookup dictionary: image_path -> {target_name: value}\n    pred_dict = {}\n    for img_path, pred_row in zip(unique_images, predictions):\n        pred_dict[img_path] = {col: val for col, val in zip(target_cols, pred_row)}\n        \n    print(\"Formatting submission...\")\n    # Create submission rows\n    submission_rows = []\n    for index, row in test_df.iterrows():\n        img_path = row['image_path']\n        target_name = row['target_name']\n        sample_id = row['sample_id']\n        \n        # Get prediction\n        if img_path in pred_dict:\n            pred_value = pred_dict[img_path][target_name]\n        else:\n            # Fallback if something went wrong (shouldn't happen)\n            pred_value = 0.0\n        \n        # Ensure non-negative (biomass can't be negative)\n        pred_value = max(0.0, pred_value)\n        \n        submission_rows.append({'sample_id': sample_id, 'target': pred_value})\n        \n    submission_df = pd.DataFrame(submission_rows)\n    \n    # Save to CSV\n    output_file = \"submission.csv\"\n    submission_df.to_csv(output_file, index=False)\n    print(f\"Saved submission to {output_file}\")\n    \n    # Print the content for user to copy if needed\n    print(\"\\n\" + \"=\"*20 + \" SUBMISSION CSV CONTENT \" + \"=\"*20)\n    print(submission_df.to_csv(index=False))\n    print(\"=\"*64)\n\n# Run generation\ngenerate_submission(model)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-23T22:43:52.754528Z","iopub.execute_input":"2025-11-23T22:43:52.75495Z","iopub.status.idle":"2025-11-23T22:43:52.887114Z","shell.execute_reply.started":"2025-11-23T22:43:52.754924Z","shell.execute_reply":"2025-11-23T22:43:52.886566Z"}},"outputs":[],"execution_count":null}]}