{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"pygments_lexer":"ipython3","nbconvert_exporter":"python","version":"3.6.4","file_extension":".py","codemirror_mode":{"name":"ipython","version":3},"name":"python","mimetype":"text/x-python"}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"from kaggle_datasets import KaggleDatasets\nimport math, re, os\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nimport seaborn as sns\nfrom functools import partial\n\nimport tensorflow as tf\nfrom tensorflow import keras\nfrom tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint, ReduceLROnPlateau\nfrom tensorflow.keras.utils import plot_model\nfrom sklearn.model_selection import train_test_split, StratifiedKFold\n\nimport cv2\nprint(\"Tensorflow version \" + tf.__version__)","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","execution":{"iopub.status.busy":"2022-01-27T06:50:07.255265Z","iopub.execute_input":"2022-01-27T06:50:07.255661Z","iopub.status.idle":"2022-01-27T06:50:09.545821Z","shell.execute_reply.started":"2022-01-27T06:50:07.255598Z","shell.execute_reply":"2022-01-27T06:50:09.544842Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Nu ser vi om vi kan få forbindelse/ se vores TPU'er:","metadata":{}},{"cell_type":"code","source":"try:\n    tpu = tf.distribute.cluster_resolver.TPUClusterResolver()\n    print('Device:', tpu.master())\n    tf.config.experimental_connect_to_cluster(tpu)\n    tf.tpu.experimental.initialize_tpu_system(tpu)\n    strategy = tf.distribute.experimental.TPUStrategy(tpu)\nexcept:\n    strategy = tf.distribute.get_strategy()\nprint('Number of replicas:', strategy.num_replicas_in_sync)","metadata":{"execution":{"iopub.status.busy":"2022-01-27T06:50:09.551002Z","iopub.execute_input":"2022-01-27T06:50:09.551406Z","iopub.status.idle":"2022-01-27T06:50:17.912916Z","shell.execute_reply.started":"2022-01-27T06:50:09.551363Z","shell.execute_reply":"2022-01-27T06:50:17.911995Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Setup","metadata":{}},{"cell_type":"markdown","source":"Nu sætter vi vores konfigurerings variabler og bestemmer hvordan vi skal håndtere vores billeder i datasættet:\n\nTPU'er læser data fra Google Cloud Storage (GCS) buckets.","metadata":{}},{"cell_type":"code","source":"AUTOTUNE = tf.data.experimental.AUTOTUNE\nGCS_PATH = KaggleDatasets().get_gcs_path() # Google Cloud Storage (CGS) Bucket\nBATCH_SIZE = 16 * strategy.num_replicas_in_sync\nIMAGE_SIZE = [512, 512]\nCLASSES = ['0', '1', '2', '3', '4']\nEPOCHS = 15","metadata":{"execution":{"iopub.status.busy":"2022-01-27T06:50:17.914118Z","iopub.execute_input":"2022-01-27T06:50:17.914637Z","iopub.status.idle":"2022-01-27T06:50:18.322030Z","shell.execute_reply.started":"2022-01-27T06:50:17.914601Z","shell.execute_reply":"2022-01-27T06:50:18.321116Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def decode_image(image):\n    image = tf.image.decode_jpeg(image, channels=3)\n    image = tf.cast(image, tf.float32) / 255.0  # konverterer image 255 RGB values til floats i [0, 1] range\n    image = tf.reshape(image, [*IMAGE_SIZE, 3])\n    return image","metadata":{"execution":{"iopub.status.busy":"2022-01-27T06:50:18.324254Z","iopub.execute_input":"2022-01-27T06:50:18.324501Z","iopub.status.idle":"2022-01-27T06:50:18.330094Z","shell.execute_reply.started":"2022-01-27T06:50:18.324473Z","shell.execute_reply":"2022-01-27T06:50:18.329093Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Nu sætter vi vores \"features\" som \"image\", og vores label som \"target\".\n\nEndnu mere vigtigt at forklare, så er datasættet allerede blevet formatteret til TFRecords for os (fra kaggles side).\nTFRecords er en serialisering af datasættet, så datasættet omdannes til bytes. Derfor skal vores data omdannes til byte-strings før de kan komme i en TFRecord.\n\nEfterfølgende kan man konvertere bytestrings tilbage til tensors","metadata":{}},{"cell_type":"code","source":"def read_tfrecord(example, labeled):\n    tfrecord_format = {\n        \"image\": tf.io.FixedLenFeature([], tf.string), #Her betyder tf.string at det er en byte-string\n        \"target\": tf.io.FixedLenFeature([], tf.int64)\n    } if labeled else {\n        \"image\": tf.io.FixedLenFeature([], tf.string),\n        \"image_name\": tf.io.FixedLenFeature([], tf.string)\n    }\n    example = tf.io.parse_single_example(example, tfrecord_format)\n    image = decode_image(example['image'])\n    if labeled:\n        label = tf.cast(example['target'], tf.int32)\n        return image, label\n    idnum = example['image_name']\n    return image, idnum","metadata":{"execution":{"iopub.status.busy":"2022-01-27T06:50:18.331666Z","iopub.execute_input":"2022-01-27T06:50:18.331907Z","iopub.status.idle":"2022-01-27T06:50:18.343865Z","shell.execute_reply.started":"2022-01-27T06:50:18.331879Z","shell.execute_reply":"2022-01-27T06:50:18.343058Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#train_df = tf.io.gfile.glob(GCS_PATH + 'train.csv')\n#display(train_df.head())","metadata":{"execution":{"iopub.status.busy":"2022-01-27T06:50:18.345024Z","iopub.execute_input":"2022-01-27T06:50:18.345277Z","iopub.status.idle":"2022-01-27T06:50:18.355951Z","shell.execute_reply.started":"2022-01-27T06:50:18.345240Z","shell.execute_reply":"2022-01-27T06:50:18.355313Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def read_dataset(tfrecords, labeled = True, ordered = False):\n    ignore_order = tf.data.Options()\n    if not ordered:\n        ignore_order.experimental_deterministic = False\n        # Ved at slukke for sortering, øger vi hastigheden.\n        \n    dataset = tf.data.TFRecordDataset(\n        tfrecords, num_parallel_reads=AUTOTUNE\n    ) # Her blandes der automatisk adskillige filer til at blive læst på samme tid.\n    \n    dataset = dataset.with_options(ignore_order)\n    # Bruger dataen så snart den kommer ind, istedet for at sortere den.\n    \n    dataset = dataset.map(\n        partial(read_tfrecord, labeled=labeled),\n        num_parallel_calls=AUTOTUNE)\n    return dataset\n        ","metadata":{"execution":{"iopub.status.busy":"2022-01-27T06:50:18.357265Z","iopub.execute_input":"2022-01-27T06:50:18.357502Z","iopub.status.idle":"2022-01-27T06:50:18.373706Z","shell.execute_reply.started":"2022-01-27T06:50:18.357466Z","shell.execute_reply":"2022-01-27T06:50:18.372773Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Training & Validation datasæt","metadata":{}},{"cell_type":"markdown","source":"Nu gør jeg brug af SciKit Learns \"train_test_split\" funktion til at adskille datasættet i et trænings sæt, og et sæt til validering.\n\nHvis jeg havde tid, ville jeg have gjort brug af K-fold Cross Validation i stedet, men det er noget mere kompliceret.","metadata":{}},{"cell_type":"code","source":"train_tfrecords, valid_tfrecords = train_test_split(\n    tf.io.gfile.glob(GCS_PATH + '/train_tfrecords/ld_train*.tfrec'),\n    test_size=0.30, random_state = 42\n)\n\n\ntrain_images = GCS_PATH + '/train_images/'\n\ntest_tfrecords = tf.io.gfile.glob(GCS_PATH + '/test_tfrecords/ld_test*.tfrec')","metadata":{"execution":{"iopub.status.busy":"2022-01-27T06:50:18.374702Z","iopub.execute_input":"2022-01-27T06:50:18.374950Z","iopub.status.idle":"2022-01-27T06:50:18.524362Z","shell.execute_reply.started":"2022-01-27T06:50:18.374922Z","shell.execute_reply":"2022-01-27T06:50:18.523487Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Data Augmentation\n\nHvis man selv udførte data augmentation på billederne ned til mindste detalje, ville man bruge keras ImageDataGenerator.\n\nJeg bruger dog en simpel metode først.","metadata":{}},{"cell_type":"code","source":"#ImageDataGenerator(\n                  #  rotation_range = 30,\n                  #  width_shift_range = 0.2,\n                  #  height_shift_range = 0.2,\n                  #  shear_range = 0.2,\n                  #  zoom_range = 0.2,\n                  #  brightness_range = [0.5,1.5],\n                   # horizontal_flip = True,\n                   # vertical_flip = True,\n                   # fill_mode = 'nearest'\n#)","metadata":{"execution":{"iopub.status.busy":"2022-01-27T06:50:18.525561Z","iopub.execute_input":"2022-01-27T06:50:18.525805Z","iopub.status.idle":"2022-01-27T06:50:18.529827Z","shell.execute_reply.started":"2022-01-27T06:50:18.525777Z","shell.execute_reply":"2022-01-27T06:50:18.528952Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#def data_augmentation(image, label):\n   # image = tf.image.random_brightness(image, 0.2)\n    #image = tf.image.random_contrast(image, 0.2, 0.4)\n    #image = tf.image.random_flip_left_right(image)\n   # image = tf.image.random_flip_up_down(image)\n   # return image, label","metadata":{"execution":{"iopub.status.busy":"2022-01-27T06:50:18.530850Z","iopub.execute_input":"2022-01-27T06:50:18.531209Z","iopub.status.idle":"2022-01-27T06:50:18.542605Z","shell.execute_reply.started":"2022-01-27T06:50:18.531182Z","shell.execute_reply":"2022-01-27T06:50:18.541633Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def data_augmentation(image, label):\n   \n    return image, label","metadata":{"execution":{"iopub.status.busy":"2022-01-27T06:50:18.544023Z","iopub.execute_input":"2022-01-27T06:50:18.544460Z","iopub.status.idle":"2022-01-27T06:50:18.553887Z","shell.execute_reply.started":"2022-01-27T06:50:18.544412Z","shell.execute_reply":"2022-01-27T06:50:18.552958Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Men vi er så heldige at den følgende \"dataset.prefetch(AUTO)\" funktion gør dette for os, \"gratis\" ved brug af TPU.","metadata":{}},{"cell_type":"code","source":"def get_train_dataset():\n    dataset = read_dataset(train_tfrecords, labeled = True)  \n    dataset = dataset.map(data_augmentation, num_parallel_calls = AUTOTUNE)  \n    dataset = dataset.repeat()\n    dataset = dataset.shuffle(2048)\n    dataset = dataset.batch(BATCH_SIZE)\n    dataset = dataset.prefetch(AUTOTUNE)\n    return dataset","metadata":{"execution":{"iopub.status.busy":"2022-01-27T06:50:18.555383Z","iopub.execute_input":"2022-01-27T06:50:18.556106Z","iopub.status.idle":"2022-01-27T06:50:18.567998Z","shell.execute_reply.started":"2022-01-27T06:50:18.556046Z","shell.execute_reply":"2022-01-27T06:50:18.566947Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def get_valid_dataset():\n    dataset = read_dataset(valid_tfrecords, labeled = True, ordered = True)\n    dataset = dataset.batch(BATCH_SIZE)\n    dataset = dataset.cache()\n    dataset = dataset.prefetch(AUTOTUNE)\n    return dataset","metadata":{"execution":{"iopub.status.busy":"2022-01-27T06:50:18.571006Z","iopub.execute_input":"2022-01-27T06:50:18.571553Z","iopub.status.idle":"2022-01-27T06:50:18.583890Z","shell.execute_reply.started":"2022-01-27T06:50:18.571519Z","shell.execute_reply":"2022-01-27T06:50:18.583138Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def get_test_dataset(ordered = False):\n    dataset = read_dataset(test_tfrecords, labeled = False, ordered = True)\n    dataset = dataset.batch(BATCH_SIZE)\n    dataset = dataset.prefetch(AUTOTUNE)\n    return dataset","metadata":{"execution":{"iopub.status.busy":"2022-01-27T06:50:18.585030Z","iopub.execute_input":"2022-01-27T06:50:18.585667Z","iopub.status.idle":"2022-01-27T06:50:18.596704Z","shell.execute_reply.started":"2022-01-27T06:50:18.585636Z","shell.execute_reply":"2022-01-27T06:50:18.595893Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Lad os få antal på dataen:","metadata":{}},{"cell_type":"code","source":"def count_data(tfrecords):\n    num = [int(re.compile(r\"-([0-9]*)\\.\").search(tfrecord).group(1))\n           for tfrecord in tfrecords]\n    return np.sum(num)","metadata":{"execution":{"iopub.status.busy":"2022-01-27T06:50:18.597817Z","iopub.execute_input":"2022-01-27T06:50:18.598514Z","iopub.status.idle":"2022-01-27T06:50:18.609876Z","shell.execute_reply.started":"2022-01-27T06:50:18.598482Z","shell.execute_reply":"2022-01-27T06:50:18.609116Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"num_train_images = count_data(train_tfrecords)\nnum_valid_images = count_data(valid_tfrecords)\nnum_test_images = count_data(test_tfrecords)\n\nprint(\"Datasæt: {} billeder til træning, {} billeder til validering, {} (unlabeled) test billeder\".format(\nnum_train_images, num_valid_images, num_test_images))","metadata":{"execution":{"iopub.status.busy":"2022-01-27T06:50:18.611037Z","iopub.execute_input":"2022-01-27T06:50:18.611385Z","iopub.status.idle":"2022-01-27T06:50:18.623475Z","shell.execute_reply.started":"2022-01-27T06:50:18.611345Z","shell.execute_reply":"2022-01-27T06:50:18.622707Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# EDA?","metadata":{}},{"cell_type":"code","source":"#plt.figure(figsize = (20,20))\n#for i in range(20):\n   # plt.subplot(4,5,i+1)\n   # img = cv2.imread(train_images + images[i])\n    #img = cv2.imread(tf.io.gfile.glob(GCS_PATH + '/train_images/10*.jpg'))\n   # img = cv2.cvtColor(img,cv2.COLOR_BGR2RGB)\n    #plt.imshow(img)\n   # plt.title(data[str(labels[i])])","metadata":{"execution":{"iopub.status.busy":"2022-01-27T06:50:18.624838Z","iopub.execute_input":"2022-01-27T06:50:18.625332Z","iopub.status.idle":"2022-01-27T06:50:18.638716Z","shell.execute_reply.started":"2022-01-27T06:50:18.625300Z","shell.execute_reply":"2022-01-27T06:50:18.637809Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Valg af model","metadata":{}},{"cell_type":"code","source":"with strategy.scope():\n        \n    adjust_img_layer = tf.keras.layers.Lambda(\n        tf.keras.applications.resnet50.preprocess_input,\n        input_shape=[IMAGE_SIZE, IMAGE_SIZE, 3])\n        \n    base_model = tf.keras.applications.ResNet50(\n        weights = \"imagenet\",\n        include_top = False)\n    base_model.trainable = False\n        \n    model = tf.keras.Sequential([\n        tf.keras.layers.BatchNormalization(renorm = True),\n        adjust_img_layer,\n        base_model,\n        tf.keras.layers.GlobalAveragePooling2D(),\n        tf.keras.layers.Dense(8, activation = \"relu\"),\n        #tf.keras.layers.BatchNormalization(renorm=True),\n        tf.keras.layers.Dense(len(CLASSES), activation = 'softmax')\n    ])\n    model.compile(\n        optimizer = tf.keras.optimizers.Adam(\n            learning_rate = 0.001),\n        loss = \"sparse_categorical_crossentropy\",\n        metrics = [\"sparse_categorical_accuracy\"])","metadata":{"execution":{"iopub.status.busy":"2022-01-27T06:50:18.639973Z","iopub.execute_input":"2022-01-27T06:50:18.640217Z","iopub.status.idle":"2022-01-27T06:50:29.201381Z","shell.execute_reply.started":"2022-01-27T06:50:18.640187Z","shell.execute_reply":"2022-01-27T06:50:29.200390Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Træning af model","metadata":{}},{"cell_type":"code","source":"train_dataset = get_train_dataset()\nvalid_dataset = get_valid_dataset()","metadata":{"execution":{"iopub.status.busy":"2022-01-27T06:50:29.202742Z","iopub.execute_input":"2022-01-27T06:50:29.203074Z","iopub.status.idle":"2022-01-27T06:50:29.501833Z","shell.execute_reply.started":"2022-01-27T06:50:29.203032Z","shell.execute_reply":"2022-01-27T06:50:29.500777Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Stopper træningen når validation loss metric er stoppet med at falde i 5 epochs.\nearly_stopping = EarlyStopping(monitor = 'val_loss',\n                               patience = 5,\n                               mode = 'min',\n                               restore_best_weights = True)\n\n# Gemmer modellen med det maksimale validerings præcision, virker ikke?\ncheckpoint = ModelCheckpoint('best_model.hdf5', \n                             monitor = 'val_sparse_categorical_accuracy',\n                             verbose = 1,\n                             mode = 'max', \n                             save_best_only = True)\n# Reducerer lærings raten\nreduce_lr = ReduceLROnPlateau(monitor = 'val_loss',\n                              factor = 0.2,\n                              patience = 2,\n                              mode = 'min',\n                              verbose = 1)","metadata":{"execution":{"iopub.status.busy":"2022-01-27T06:50:29.503083Z","iopub.execute_input":"2022-01-27T06:50:29.503356Z","iopub.status.idle":"2022-01-27T06:50:29.512580Z","shell.execute_reply.started":"2022-01-27T06:50:29.503323Z","shell.execute_reply":"2022-01-27T06:50:29.511671Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"epoch_steps = num_train_images // BATCH_SIZE\nvalid_steps = num_valid_images // BATCH_SIZE","metadata":{"execution":{"iopub.status.busy":"2022-01-27T06:50:29.514384Z","iopub.execute_input":"2022-01-27T06:50:29.514672Z","iopub.status.idle":"2022-01-27T06:50:29.523322Z","shell.execute_reply.started":"2022-01-27T06:50:29.514643Z","shell.execute_reply":"2022-01-27T06:50:29.522438Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"history = model.fit(train_dataset,\n                    validation_data = valid_dataset,\n                    epochs = EPOCHS,\n                    steps_per_epoch = epoch_steps, \n                    validation_steps = valid_steps, \n                    callbacks = [early_stopping, reduce_lr]\n                   )","metadata":{"execution":{"iopub.status.busy":"2022-01-27T06:50:29.524771Z","iopub.execute_input":"2022-01-27T06:50:29.525103Z","iopub.status.idle":"2022-01-27T07:02:26.554012Z","shell.execute_reply.started":"2022-01-27T06:50:29.525057Z","shell.execute_reply":"2022-01-27T07:02:26.553052Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"model.summary()","metadata":{"execution":{"iopub.status.busy":"2022-01-27T07:02:26.555765Z","iopub.execute_input":"2022-01-27T07:02:26.556635Z","iopub.status.idle":"2022-01-27T07:02:26.581702Z","shell.execute_reply.started":"2022-01-27T07:02:26.556598Z","shell.execute_reply":"2022-01-27T07:02:26.580744Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"print(history.history.keys())","metadata":{"execution":{"iopub.status.busy":"2022-01-27T07:02:26.583120Z","iopub.execute_input":"2022-01-27T07:02:26.583531Z","iopub.status.idle":"2022-01-27T07:02:26.596760Z","shell.execute_reply.started":"2022-01-27T07:02:26.583496Z","shell.execute_reply":"2022-01-27T07:02:26.596039Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"Nu fremviser vi lærings kurven + loss funktion for at evaluere vores model","metadata":{}},{"cell_type":"code","source":"acc = history.history['sparse_categorical_accuracy']\nval_acc = history.history['val_sparse_categorical_accuracy']\nloss = history.history['loss']\nval_loss = history.history['val_loss']\n\nepochs = range(len(acc))\n\nplt.plot(epochs, acc, 'c-', label='Training sparse categorical accuracy')\nplt.plot(epochs, val_acc, 'y-', label='Validation sparse categorical accuracy')\nplt.title('Training and validation accuracy')\nplt.legend()\n\nplt.figure()\n\nplt.plot(epochs, loss, 'c-', label='Training Loss')\nplt.plot(epochs, val_loss, 'y-', label='Validation Loss')\nplt.title('Training and validation loss')\nplt.legend()\n\nplt.show()","metadata":{"execution":{"iopub.status.busy":"2022-01-27T07:02:26.598164Z","iopub.execute_input":"2022-01-27T07:02:26.598612Z","iopub.status.idle":"2022-01-27T07:02:27.120701Z","shell.execute_reply.started":"2022-01-27T07:02:26.598566Z","shell.execute_reply":"2022-01-27T07:02:27.119010Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"plot_model(model, show_shapes = True)","metadata":{"execution":{"iopub.status.busy":"2022-01-27T07:02:27.121833Z","iopub.execute_input":"2022-01-27T07:02:27.122072Z","iopub.status.idle":"2022-01-27T07:02:28.172617Z","shell.execute_reply.started":"2022-01-27T07:02:27.122044Z","shell.execute_reply":"2022-01-27T07:02:28.171357Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Resultats historie:","metadata":{}},{"cell_type":"markdown","source":"* ResNet50 - image_size = (512, 512), batch_size = 128 - 0.6901\n\n\n### Med Callbacks/flere augmentationer:\n\n\n* ResNet50 - image_size = (512, 512), batch_size = 128 - 0.7169\n* EfficientNetB3 - image_size = (512, 512), batch_size = 128 - 0.61584\n* ResNet50 - Ekstra augmentation (ændring af contrast + brightness) - image_size = (512, 512), batch_size = 128 - 0.6486 - endnu værre.\n\n\n### Skiftet train/test split til 15% + Accuracy metric i stedet for sparse categorical accuracy: \n\n* EfficientNetB3 - image_size = (512, 512), batch_size = 128 - 0.61968","metadata":{}},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]}]}