{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.10.14","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":5048,"databundleVersionId":868335,"sourceType":"competition"},{"sourceId":9337484,"sourceType":"datasetVersion","datasetId":5658320},{"sourceId":122587,"sourceType":"modelInstanceVersion","isSourceIdPinned":true,"modelInstanceId":103163,"modelId":127394},{"sourceId":122607,"sourceType":"modelInstanceVersion","isSourceIdPinned":true,"modelInstanceId":103182,"modelId":127413}],"dockerImageVersionId":30761,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os\nimport math\n\nimport numpy as np\nimport matplotlib.pyplot as plt\nimport matplotlib as mpl\nimport tensorflow as tf\nfrom sklearn.metrics import confusion_matrix, ConfusionMatrixDisplay","metadata":{"execution":{"iopub.status.busy":"2024-10-11T16:05:43.869465Z","iopub.execute_input":"2024-10-11T16:05:43.870180Z","iopub.status.idle":"2024-10-11T16:05:58.117432Z","shell.execute_reply.started":"2024-10-11T16:05:43.870130Z","shell.execute_reply":"2024-10-11T16:05:58.116521Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Data Splitting","metadata":{}},{"cell_type":"code","source":"base_dir = './data/base'\ntrain_base_dir = './data/train'\ntest_base_dir = './data/test'\nvalid_base_dir = './data/valid'\nlabels = os.listdir(base_dir)\n\n\nall_images = []\nall_labels = []\n\n\nfor label in labels:\n    img_dir = os.path.join(base_dir, label)\n    images = os.listdir(img_dir)\n\n    for img in images:\n        all_images.append(os.path.join(img_dir, img))\n        all_labels.append(label)\n\n\nall_images = np.array(all_images)\nall_labels = np.array(all_labels)\n\n\nsss = StratifiedShuffleSplit(n_splits=1, test_size=0.2, random_state=42)\ntrain_idx, test_idx = next(sss.split(all_images, all_labels))\n\nbase_data = all_images[train_idx]\nbase_labels = all_labels[train_idx]\ntest_data = all_images[test_idx]\ntest_labels = all_labels[test_idx]\n\nsss = StratifiedShuffleSplit(n_splits=1, test_size=0.2, random_state=42)\ntrain_idx, test_idx = next(sss.split(base_data, base_labels))\n\ntrain_data = base_data[train_idx]\ntrain_labels = base_labels[train_idx]\nvalid_data = base_data[test_idx]\nvalid_labels = base_labels[test_idx]\n\n# Function to move files to their respective directories\ndef move_files(file_list, labels_list, subset):\n    for file_path, label in zip(file_list, labels_list):\n        target_dir = os.path.join(\"./data/\", subset, label)\n        os.makedirs(target_dir, exist_ok=True)\n        shutil.move(file_path, target_dir)\n\n\nmove_files(train_data, train_labels, \"train\")\nmove_files(test_data, test_labels, \"test\")\nmove_files(valid_data, valid_labels, \"valid\")","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Load Dataset","metadata":{}},{"cell_type":"code","source":"train = tf.keras.preprocessing.image_dataset_from_directory(\"/kaggle/input/prapered/prepared-data/train\", image_size=(224, 224), batch_size=32,label_mode=\"categorical\")\ntest = tf.keras.preprocessing.image_dataset_from_directory(\"/kaggle/input/prapered/prepared-data/test\", image_size=(224, 224), batch_size=32, label_mode=\"categorical\",)\nvalid = tf.keras.preprocessing.image_dataset_from_directory(\"/kaggle/input/prapered/prepared-data/valid\", image_size=(224, 224), batch_size=32 ,label_mode=\"categorical\",)","metadata":{"execution":{"iopub.status.busy":"2024-10-11T16:06:04.936378Z","iopub.execute_input":"2024-10-11T16:06:04.937048Z","iopub.status.idle":"2024-10-11T16:06:09.415082Z","shell.execute_reply.started":"2024-10-11T16:06:04.937000Z","shell.execute_reply":"2024-10-11T16:06:09.413881Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"### Augment Data","metadata":{}},{"cell_type":"code","source":"augmentation = tf.keras.Sequential([\n    tf.keras.layers.RandomFlip(\"horizontal\"),\n    tf.keras.layers.RandomRotation(0.1),\n    tf.keras.layers.RandomZoom(0.1),\n    tf.keras.layers.RandomContrast(0.1),\n    tf.keras.layers.RandomBrightness(0.1),\n])\n\ntrain = train.map(lambda X, y: (augmentation(X), y))","metadata":{"execution":{"iopub.status.busy":"2024-10-11T16:06:09.417361Z","iopub.execute_input":"2024-10-11T16:06:09.417843Z","iopub.status.idle":"2024-10-11T16:06:09.684484Z","shell.execute_reply.started":"2024-10-11T16:06:09.417765Z","shell.execute_reply":"2024-10-11T16:06:09.683465Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Functions","metadata":{}},{"cell_type":"code","source":"def display_confusion_matrix(model, data):\n    y = []\n    y_pred = []\n    for X, label in data.take(-1):\n        pred = model.predict(X, verbose=0)\n        pred = tf.argmax(pred, axis=1)\n        y_true = tf.argmax(label, axis=1)\n        y += y_true.numpy().tolist()\n        y_pred += pred.numpy().tolist()\n\n\n    cm = confusion_matrix(y, y_pred)\n\n    disp = ConfusionMatrixDisplay(confusion_matrix=cm)\n    disp.plot()\n    plt.show()\n    \n    \ndef display_training_history(history):\n    loss = history.history.get(\"loss\")\n    val_loss = history.history.get(\"val_loss\")\n\n    macro_f1 = history.history.get(\"macro_f1\")\n    val_macro_f1 = history.history.get(\"val_macro_f1\")\n\n    micro_f1 = history.history.get(\"micro_f1\")\n    val_micro_f1 = history.history.get(\"val_micro_f1\")\n\n\n    fig, axs = plt.subplots(2, 2, figsize=(10, 10))\n\n    axs[0,0].plot(micro_f1, color=\"orange\", label=\"train\")\n    axs[0,0].plot(val_micro_f1, color=\"blue\", label=\"valid\")\n    axs[0,0].set_title(\"Micro F1\")\n    axs[0,0].legend()\n    axs[0,0].grid()\n\n\n    axs[0,1].plot(macro_f1, color=\"orange\", label=\"train\")\n    axs[0,1].plot(val_macro_f1, color=\"blue\", label=\"valid\")\n    axs[0,1].set_title(\"Macro F1\")\n    axs[0,1].legend()\n    axs[0,1].grid()\n    \n    axs[1,0].plot(loss, color=\"orange\", label=\"train\")\n    axs[1,0].plot(val_loss, color=\"blue\", label=\"valid\")\n    axs[1,0].set_title(\"Loss\")\n    axs[1,0].legend()\n    axs[1,0].grid()\n\n\n    axs[1, 1].axis('off')\n\n    plt.show()\n\ndef make_gradcam_heatmap(img_array, model, last_conv_layer_name):\n    grad_model = tf.keras.models.Model(\n        [model.input], [model.get_layer(last_conv_layer_name).output, model.output]\n    )\n\n    with tf.GradientTape() as tape:\n        last_conv_layer_output, preds = grad_model(img_array)\n        pred_index = tf.argmax(preds[0])\n        class_channel = preds[:, pred_index]\n\n    grads = tape.gradient(class_channel, last_conv_layer_output)\n    pooled_grads = tf.reduce_mean(grads, axis=(0, 1, 2))\n\n    last_conv_layer_output = last_conv_layer_output[0]\n    heatmap = last_conv_layer_output @ pooled_grads[..., tf.newaxis]\n    heatmap = tf.squeeze(heatmap)\n\n    heatmap = tf.maximum(heatmap, 0) / tf.math.reduce_max(heatmap)\n    return heatmap.numpy()\n\n\ndef display_grad_imgs(model, layer_name, images, labels, alpha=0.4):\n    col = 3\n    rows = len(images)\n    rows = math.ceil(rows / col)\n    \n    jet = mpl.colormaps[\"jet\"]\n    jet_colors = jet(np.arange(256))[:, :3]\n\n    fig, axs = plt.subplots(rows, col, figsize=(15, 15))\n    \n    for i in range(rows):\n        for j in range(col):\n            img = images[i+j]\n\n            heatmap = make_gradcam_heatmap(img, model, layer_name)\n            img = tf.squeeze(img)\n            heatmap = np.uint8(255 * heatmap)\n            jet_heatmap = jet_colors[heatmap]\n            jet_heatmap = tf.keras.preprocessing.image.array_to_img(jet_heatmap)\n            jet_heatmap = jet_heatmap.resize((img.shape[1], img.shape[0]))\n            jet_heatmap = tf.keras.preprocessing.image.img_to_array(jet_heatmap)\n            superimposed_img = jet_heatmap * alpha + img\n            superimposed_img = tf.keras.preprocessing.image.array_to_img(superimposed_img)\n\n            axs[i, j].imshow(superimposed_img)\n            axs[i, j].axis('off') \n            axs[i, j].set_title(labels[i+j])\n    \n    plt.tight_layout()\n    plt.show()\n","metadata":{"execution":{"iopub.status.busy":"2024-10-11T17:05:48.169226Z","iopub.execute_input":"2024-10-11T17:05:48.169620Z","iopub.status.idle":"2024-10-11T17:05:48.189093Z","shell.execute_reply.started":"2024-10-11T17:05:48.169582Z","shell.execute_reply":"2024-10-11T17:05:48.187891Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Base Model","metadata":{}},{"cell_type":"code","source":"base_model = tf.keras.applications.MobileNetV3Large(\n    input_shape=(224, 224, 3),\n    include_top=False,\n    weights='imagenet',\n    include_preprocessing=True\n)\n\nfor l in base_model.layers:\n    l.trainable = False\n\n\npooling = tf.keras.layers.GlobalAveragePooling2D()(base_model.output)\noutput = tf.keras.layers.Dense(10, activation=\"softmax\")(pooling)\nmodel = tf.keras.Model(inputs=base_model.input, outputs=output)\n\ncheckpoint = tf.keras.callbacks.ModelCheckpoint(\"/kaggle/working/base-model.keras\",\n                                               save_best_only=True)\n\nmacro = tf.keras.metrics.F1Score(average=\"macro\", name=\"macro_f1\")\nmicro =  tf.keras.metrics.F1Score(average=\"micro\", name=\"micro_f1\")\n\nmodel.compile(loss=\"categorical_crossentropy\", optimizer=\"nadam\", metrics=[macro, micro])\n\nhistory = model.fit(train, validation_data=valid, epochs=10, callbacks=[checkpoint])\n","metadata":{"execution":{"iopub.status.busy":"2024-10-11T11:21:08.227139Z","iopub.execute_input":"2024-10-11T11:21:08.228200Z","iopub.status.idle":"2024-10-11T12:23:52.047423Z","shell.execute_reply.started":"2024-10-11T11:21:08.228147Z","shell.execute_reply":"2024-10-11T12:23:52.045976Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":" display_training_history(history)","metadata":{"execution":{"iopub.status.busy":"2024-10-11T12:30:46.442508Z","iopub.execute_input":"2024-10-11T12:30:46.442968Z","iopub.status.idle":"2024-10-11T12:30:47.254047Z","shell.execute_reply.started":"2024-10-11T12:30:46.442923Z","shell.execute_reply":"2024-10-11T12:30:47.252801Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"model = tf.keras.models.load_model(\"/kaggle/working/base-model.keras\")","metadata":{"execution":{"iopub.status.busy":"2024-10-11T12:31:08.068868Z","iopub.execute_input":"2024-10-11T12:31:08.069330Z","iopub.status.idle":"2024-10-11T12:31:10.550955Z","shell.execute_reply.started":"2024-10-11T12:31:08.069286Z","shell.execute_reply":"2024-10-11T12:31:10.549398Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"display_confusion_matrix(model, valid)","metadata":{"execution":{"iopub.status.busy":"2024-10-11T13:16:32.277301Z","iopub.execute_input":"2024-10-11T13:16:32.277768Z","iopub.status.idle":"2024-10-11T13:17:46.348734Z","shell.execute_reply.started":"2024-10-11T13:16:32.277724Z","shell.execute_reply":"2024-10-11T13:17:46.347505Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"img = []\nlabels = []\nfor X, y in valid.take(1):\n    labels = tf.argmax(y[10:20], axis=1).numpy().tolist()\n    for i in X[10:20]:\n        img.append(tf.expand_dims(i, axis=0).numpy()) \n        \ndisplay_grad_imgs(model, \"activation_39\", img, labels)","metadata":{"execution":{"iopub.status.busy":"2024-10-11T13:15:51.392584Z","iopub.execute_input":"2024-10-11T13:15:51.393032Z","iopub.status.idle":"2024-10-11T13:16:00.444621Z","shell.execute_reply.started":"2024-10-11T13:15:51.392988Z","shell.execute_reply":"2024-10-11T13:16:00.443369Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## CBAM","metadata":{}},{"cell_type":"code","source":"class SpatialAttention(tf.keras.layers.Layer):\n    def __init__(self, **kwargs):\n        super().__init__(**kwargs)\n\n    def build(self, batch_input_shape):\n        self.conv = tf.keras.layers.Conv2D(1, kernel_size=7, padding=\"same\", use_bias=False)\n        self.batchNorm = tf.keras.layers.BatchNormalization(epsilon=1e-5, momentum=0.01)   \n        self.sigmoid = tf.keras.layers.Activation(\"sigmoid\")\n\n    def _channelPool(self, X):\n        x_avg = tf.reduce_mean(X, axis=-1, keepdims=True)    \n        x_max = tf.reduce_max(X, axis=-1, keepdims=True)\n        return tf.concat([x_avg, x_max], axis=-1)\n\n    def call(self, X):\n        X_att = self._channelPool(X)\n        X_att = self.conv(X_att)\n        X_att = self.batchNorm(X_att)\n        X_att = self.sigmoid(X_att)\n        return X_att * X\n    \n\nclass ChannelAttention(tf.keras.layers.Layer):\n    def __init__(self, reduction_ratio=16, **kwargs):\n        self.reduction_ratio = reduction_ratio\n        super().__init__(**kwargs)\n\n    def build(self, batch_input_shape):\n        n_channel = batch_input_shape[-1]\n        self.avgPool = tf.keras.layers.GlobalAveragePooling2D()\n        self.maxPool = tf.keras.layers.GlobalMaxPooling2D()\n        self.sequention = tf.keras.Sequential([\n            tf.keras.layers.Dense(n_channel // self.reduction_ratio, activation=\"relu\"),\n            tf.keras.layers.Dense(n_channel, activation=\"sigmoid\")\n        ])\n        self.reshape = tf.keras.layers.Reshape((1, 1, n_channel))\n\n    def call(self, X): \n        avg_X = self.avgPool(X)\n        avg_X = self.reshape(avg_X)\n        avg_X = self.sequention(avg_X)\n\n        max_X = self.maxPool(X)\n        max_X = self.reshape(max_X)\n        max_X = self.sequention(max_X)\n\n        attention = avg_X + max_X\n        attention = tf.keras.activations.sigmoid(attention)\n        return attention * X","metadata":{"execution":{"iopub.status.busy":"2024-10-11T13:22:06.895398Z","iopub.execute_input":"2024-10-11T13:22:06.895886Z","iopub.status.idle":"2024-10-11T13:22:06.913387Z","shell.execute_reply.started":"2024-10-11T13:22:06.895844Z","shell.execute_reply":"2024-10-11T13:22:06.912019Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"base_model = tf.keras.applications.MobileNetV3Large(\n    input_shape=(224, 224, 3),\n    include_top=False,\n    weights='imagenet',\n    include_preprocessing=True\n)\n\nfor l in base_model.layers:\n    l.trainable = False\n\nattention = ChannelAttention()(base_model.output)\nattention = SpatialAttention()(attention)    \npooling = tf.keras.layers.GlobalAveragePooling2D()(attention)\noutput = tf.keras.layers.Dense(10, activation=\"softmax\")(pooling)\nmodel = tf.keras.Model(inputs=base_model.input, outputs=output)\n\ncheckpoint = tf.keras.callbacks.ModelCheckpoint(\"./checkpoint/cbam-attention.keras\",\n                                               save_best_only=True)\n\nmacro = tf.keras.metrics.F1Score(average=\"macro\", name=\"macro_f1\")\nmicro =  tf.keras.metrics.F1Score(average=\"micro\", name=\"micro_f1\")\n\nmodel.compile(loss=\"categorical_crossentropy\", optimizer=\"nadam\", metrics=[macro, micro])\n\nhistory = model.fit(train, validation_data=valid, epochs=10, callbacks=[checkpoint])\n","metadata":{"execution":{"iopub.status.busy":"2024-10-11T13:22:14.549598Z","iopub.execute_input":"2024-10-11T13:22:14.550026Z","iopub.status.idle":"2024-10-11T14:26:55.055063Z","shell.execute_reply.started":"2024-10-11T13:22:14.549984Z","shell.execute_reply":"2024-10-11T14:26:55.053495Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":" display_training_history(history)","metadata":{"execution":{"iopub.status.busy":"2024-10-11T14:27:49.025765Z","iopub.execute_input":"2024-10-11T14:27:49.026221Z","iopub.status.idle":"2024-10-11T14:27:49.857294Z","shell.execute_reply.started":"2024-10-11T14:27:49.026176Z","shell.execute_reply":"2024-10-11T14:27:49.856171Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"display_confusion_matrix(model, valid)","metadata":{"execution":{"iopub.status.busy":"2024-10-11T14:30:08.659763Z","iopub.execute_input":"2024-10-11T14:30:08.660259Z","iopub.status.idle":"2024-10-11T14:31:17.322386Z","shell.execute_reply.started":"2024-10-11T14:30:08.660213Z","shell.execute_reply":"2024-10-11T14:31:17.321161Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"img = []\nlabels = []\nfor X, y in valid.take(1):\n    labels = tf.argmax(y[10:20], axis=1).numpy().tolist()\n    for i in X[10:20]:\n        img.append(tf.expand_dims(i, axis=0).numpy()) \n        \ndisplay_grad_imgs(model, \"spatial_attention\", img, labels)","metadata":{"execution":{"iopub.status.busy":"2024-10-11T14:32:04.376125Z","iopub.execute_input":"2024-10-11T14:32:04.376610Z","iopub.status.idle":"2024-10-11T14:32:14.273803Z","shell.execute_reply.started":"2024-10-11T14:32:04.376566Z","shell.execute_reply":"2024-10-11T14:32:14.272511Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Multi-headed Attention","metadata":{}},{"cell_type":"code","source":"class MultiHeadAttention(tf.keras.layers.Layer):\n    def __init__(self, key_dim=10, head=3, **kwargs):\n        super().__init__(**kwargs)\n        self.key_dim = key_dim\n        self.head = head\n    \n    def build(self, batch_input_shape):\n        _, height, width, channels = batch_input_shape\n        \n        self.attention_layer = tf.keras.layers.MultiHeadAttention(self.head, key_dim=self.key_dim)\n        self.attention_input_reshape = tf.keras.layers.Reshape((height*width, channels))\n        self.attention_output_reshape = tf.keras.layers.Reshape((height, width, channels))\n        \n    def call(self, X):\n        X = self.attention_input_reshape(X)\n        X = self.attention_layer(X, X, X)\n        X = self.attention_output_reshape(X)\n        return X\n    ","metadata":{"execution":{"iopub.status.busy":"2024-10-11T16:06:11.434238Z","iopub.execute_input":"2024-10-11T16:06:11.434923Z","iopub.status.idle":"2024-10-11T16:06:11.442344Z","shell.execute_reply.started":"2024-10-11T16:06:11.434883Z","shell.execute_reply":"2024-10-11T16:06:11.441194Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"base_model = tf.keras.applications.MobileNetV3Large(\n    input_shape=(224, 224, 3),\n    include_top=False,\n    weights='imagenet',\n    include_preprocessing=True\n)\n\nfor l in base_model.layers:\n    l.trainable = False\n\nattention = MultiHeadAttention(key_dim=10, head=5, name=\"attention\")(base_model.output)\nnorm = tf.keras.layers.LayerNormalization()(attention)\npooling = tf.keras.layers.GlobalAveragePooling2D()(norm)\noutput = tf.keras.layers.Dense(10, activation=\"softmax\")(pooling)\nmodel = tf.keras.Model(inputs=base_model.input, outputs=output)\n\ncheckpoint = tf.keras.callbacks.ModelCheckpoint(\"./checkpoint/multi-head-attention.keras\",\n                                               save_best_only=True)\n\nmacro = tf.keras.metrics.F1Score(average=\"macro\", name=\"macro_f1\")\nmicro =  tf.keras.metrics.F1Score(average=\"micro\", name=\"micro_f1\")\n\nmodel.compile(loss=\"categorical_crossentropy\", optimizer=\"nadam\", metrics=[macro, micro])\n\nhistory = model.fit(train, validation_data=valid, epochs=10, callbacks=[checkpoint])\n","metadata":{"execution":{"iopub.status.busy":"2024-10-11T16:06:21.229562Z","iopub.execute_input":"2024-10-11T16:06:21.230392Z","iopub.status.idle":"2024-10-11T17:01:12.398303Z","shell.execute_reply.started":"2024-10-11T16:06:21.230350Z","shell.execute_reply":"2024-10-11T17:01:12.397204Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":" display_training_history(history)","metadata":{"execution":{"iopub.status.busy":"2024-10-11T17:05:53.156016Z","iopub.execute_input":"2024-10-11T17:05:53.156962Z","iopub.status.idle":"2024-10-11T17:05:53.903780Z","shell.execute_reply.started":"2024-10-11T17:05:53.156917Z","shell.execute_reply":"2024-10-11T17:05:53.902806Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"display_confusion_matrix(model, valid)","metadata":{"execution":{"iopub.status.busy":"2024-10-11T17:07:08.831318Z","iopub.execute_input":"2024-10-11T17:07:08.832140Z","iopub.status.idle":"2024-10-11T17:08:09.084220Z","shell.execute_reply.started":"2024-10-11T17:07:08.832093Z","shell.execute_reply":"2024-10-11T17:08:09.083203Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"img = []\nlabels = []\nfor X, y in valid.take(1):\n    labels = tf.argmax(y[10:20], axis=1).numpy().tolist()\n    for i in X[10:20]:\n        img.append(tf.expand_dims(i, axis=0).numpy()) \n        \ndisplay_grad_imgs(model, \"attention\", img, labels)","metadata":{"execution":{"iopub.status.busy":"2024-10-11T17:08:42.888016Z","iopub.execute_input":"2024-10-11T17:08:42.888418Z","iopub.status.idle":"2024-10-11T17:08:51.306875Z","shell.execute_reply.started":"2024-10-11T17:08:42.888381Z","shell.execute_reply":"2024-10-11T17:08:51.305429Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]}]}