{"cells":[{"metadata":{},"cell_type":"markdown","source":"# About\n\nA similar comptetion called [Histopathologic Cancer Detection](https://www.kaggle.com/c/histopathologic-cancer-detection) was hosted in 2019. It had about **220025** train images. \nA model trained on that dataset can generalize well and can give improved performances on this dataset too.\n\nSo the kernel uses [pretrained public model](https://www.kaggle.com/jionie/tta-power-densenet169) from that comptetion. Freezing weights of all its layers (except for some dense layers).\n\nOne can unfreeze more layers and play further with loss functions and architecture."},{"metadata":{"trusted":true},"cell_type":"code","source":"#### Set number of epochs and learning rate ####\n\nset_epochs = 2\nset_lr = 1e-4","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"cell_type":"code","source":"import numpy as np\nimport pandas as pd\nimport os\nimport skimage.io\nfrom glob import glob\nfrom random import shuffle\nimport cv2\nfrom tqdm import tqdm\nimport matplotlib.pyplot as plt\nfrom sklearn.model_selection import train_test_split\nfrom keras.preprocessing.image import ImageDataGenerator\n\nimport keras.backend as K\nfrom keras import layers,Input\n\nfrom keras.models import Model\nfrom keras.applications.nasnet import  preprocess_input\nfrom keras.optimizers import Adam, RMSprop\nfrom keras.callbacks import ModelCheckpoint, EarlyStopping, ReduceLROnPlateau\nfrom imgaug import augmenters as iaa\nimport imgaug as ia\nprint(os.listdir(\"../input/\"))\n","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# QWK metric\n\nEvaluation metric for PANDA comptetion"},{"metadata":{"trusted":true},"cell_type":"code","source":"def quadratic_kappa_coefficient(y_true, y_pred):\n    y_true = K.cast(y_true, \"float32\")\n    n_classes = K.cast(y_pred.shape[-1], \"float32\")\n    weights = K.arange(0, n_classes, dtype=\"float32\") / (n_classes - 1)\n    weights = (weights - K.expand_dims(weights, -1)) ** 2\n\n    hist_true = K.sum(y_true, axis=0)\n    hist_pred = K.sum(y_pred, axis=0)\n\n    E = K.expand_dims(hist_true, axis=-1) * hist_pred\n    E = E / K.sum(E, keepdims=False)\n\n    O = K.transpose(K.transpose(y_true) @ y_pred)  # confusion matrix\n    O = O / K.sum(O)\n\n    num = weights * O\n    den = weights * E\n\n    QWK = (1 - K.sum(num) / K.sum(den))\n    return QWK","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Model"},{"metadata":{"_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","trusted":true},"cell_type":"code","source":"def get_model_classif_nasnet_1():  \n    \n    inputs = Input((256, 256, 3))\n\n    x1 = layers.Conv2D(32,3,padding='same')(inputs)\n    x1 = layers.BatchNormalization()(x1)\n    x1 = layers.Activation('relu')(x1)\n    x1 = layers.Conv2D(32,3,padding='same')(x1)\n    x1 = layers.BatchNormalization()(x1)\n    x1 = layers.Activation('relu')(x1)\n    x1 = layers.Conv2D(32,3,padding='same')(x1)\n    x1 = layers.BatchNormalization()(x1)\n    x1 = layers.Activation('relu')(x1)\n\n    \n    x1_s = layers.SeparableConv2D(32,3,padding='same')(inputs)\n    x1_s = layers.BatchNormalization()(x1_s)\n    x1_s = layers.Activation('relu')(x1_s)\n    x1_s = layers.SeparableConv2D(32,3,padding='same')(x1_s)\n    x1_s = layers.BatchNormalization()(x1_s)\n    x1_s = layers.Activation('relu')(x1_s)    \n    x1_s = layers.SeparableConv2D(32,3,padding='same')(x1_s)\n    x1_s = layers.BatchNormalization()(x1_s)\n    x1_s = layers.Activation('relu')(x1_s)\n    concetenated_0 = layers.concatenate([x1,x1_s])\n\n    x2 = layers.Conv2D(64,3,padding='same')(concetenated_0)\n    x2 = layers.BatchNormalization()(x2)\n    x2 = layers.Activation('relu')(x2)\n    x2 = layers.Conv2D(64,3,padding='same')(x2)\n    x2 = layers.BatchNormalization()(x2)\n    x2 = layers.Activation('relu')(x2)\n    x2 = layers.Conv2D(64,3,padding='same')(x2)\n    x2 = layers.BatchNormalization()(x2)\n    x2 = layers.Activation('relu')(x2)\n    residual_concetenated_0 = layers.Conv2D(64,1,strides=1,padding='same')(concetenated_0)\n    x2 = layers.add([x2,residual_concetenated_0])\n    concetenates_x2_x1_s = layers.concatenate([x2,x1_s])\n    x2 = layers.MaxPool2D(2,2)(concetenates_x2_x1_s)\n    \n    \n\n    x2_s = layers.SeparableConv2D(64,3,padding='same')(x2)\n    x2_s = layers.BatchNormalization()(x2_s)\n    x2_s = layers.Activation('relu')(x2_s)\n    x2_s = layers.SeparableConv2D(64,3,padding='same')(x2_s)\n    x2_s= layers.BatchNormalization()(x2_s)\n    x2_s= layers.Activation('relu')(x2_s)\n    x2_s = layers.SeparableConv2D(64,3,padding='same')(x2_s)\n    x2_s= layers.BatchNormalization()(x2_s)\n    x2_s= layers.Activation('relu')(x2_s)\n    x2_s = layers.Conv2D(96,1,strides=1,padding='same')(x2_s)\n    x2_s = layers.add([x2_s,x2]) \n    x2_s = layers.MaxPool2D(2,2)(x2_s)\n    \n    x3 = layers.Conv2D(128,3,padding='same')(x2)\n    x3 = layers.BatchNormalization()(x3)\n    x3 = layers.Activation('relu')(x3)\n    x3 = layers.Conv2D(128,3,padding='same')(x3)\n    x3 = layers.BatchNormalization()(x3)\n    x3 = layers.Activation('relu')(x3)\n    x3 = layers.Conv2D(128,3,padding='same')(x3)\n    x3 = layers.BatchNormalization()(x3)\n    x3 = layers.Activation('relu')(x3)\n    residual_x2 = layers.Conv2D(128,1,strides=1,padding='same')(x2)\n    x3 = layers.add([residual_x2,x3]) \n    \n    x3_x3 = layers.Conv2D(128,3,padding='same')(x3)\n    x3_x3 = layers.BatchNormalization()(x3_x3)\n    x3_x3 = layers.Activation('relu')(x3_x3)\n    x3_x3 = layers.Conv2D(128,3,padding='same')(x3_x3)\n    x3_x3 = layers.BatchNormalization()(x3_x3)\n    x3_x3 = layers.Activation('relu')(x3_x3)\n    x3_x3 = layers.Conv2D(128,3,padding='same')(x3_x3)\n    x3_x3 = layers.BatchNormalization()(x3_x3)\n    x3_x3 = layers.Activation('relu')(x3_x3)\n    x3_x3 = layers.add([x3,x3_x3]) \n    x3_x3 = layers.MaxPool2D(2,2)(x3_x3)\n    \n    \n    concetenated_1 = layers.concatenate([x3_x3,x2_s])\n    x3_s = layers.SeparableConv2D(128,3,padding='same')(concetenated_1)\n    x3_s = layers.BatchNormalization()(x3_s)\n    x3_s = layers.Activation('relu')(x3_s)\n    x3_s = layers.SeparableConv2D(128,3,padding='same')(x3_s)\n    x3_s= layers.BatchNormalization()(x3_s)\n    x3_s= layers.Activation('relu')(x3_s)\n    x3_s = layers.SeparableConv2D(128,3,padding='same')(x3_s)\n    x3_s= layers.BatchNormalization()(x3_s)\n    x3_s= layers.Activation('relu')(x3_s)\n    x3_s = layers.add([x3_s,x3_x3]) \n    x3_s = layers.MaxPool2D(2,2)(x3_s)\n    \n    x4 = layers.Conv2D(256,3,padding='same')(x3_x3)\n    x4 = layers.BatchNormalization()(x4)\n    x4 = layers.Activation('relu')(x4)\n    x4 = layers.Conv2D(256,3,padding='same')(x4)\n    x4 = layers.BatchNormalization()(x4)\n    x4 = layers.Activation('relu')(x4)\n    x4 = layers.Conv2D(256,3,padding='same')(x4)\n    x4 = layers.BatchNormalization()(x4)\n    x4 = layers.Activation('relu')(x4)\n    residual_x3 = layers.Conv2D(256,1,strides=1,padding='same')(x3_x3)\n    x4 = layers.add([residual_x3,x4]) \n    \n    x4_x4 = layers.Conv2D(256,3,padding='same')(x4)\n    x4_x4 = layers.BatchNormalization()(x4_x4)\n    x4_x4 = layers.Activation('relu')(x4_x4)\n    x4_x4 = layers.Conv2D(256,3,padding='same')(x4_x4)\n    x4_x4 = layers.BatchNormalization()(x4_x4)\n    x4_x4 = layers.Activation('relu')(x4_x4)\n    x4_x4 = layers.Conv2D(256,3,padding='same')(x4_x4)\n    x4_x4 = layers.BatchNormalization()(x4_x4)\n    x4_x4 = layers.Activation('relu')(x4_x4)\n    x4_x4 = layers.add([x4,x4_x4]) \n    x4_x4 = layers.MaxPool2D(2,2)(x4_x4)\n    \n\n    concetenated_2 = layers.concatenate([x4_x4,x3_s])\n    x4_s = layers.SeparableConv2D(256,3,padding='same')(concetenated_2)\n    x4_s = layers.BatchNormalization()(x4_s)\n    x4_s = layers.Activation('relu')(x4_s)\n    x4_s = layers.SeparableConv2D(256,3,padding='same')(x4_s)\n    x4_s= layers.BatchNormalization()(x4_s)\n    x4_s= layers.Activation('relu')(x4_s)\n    x4_s = layers.SeparableConv2D(256,3,padding='same')(x4_s)\n    x4_s= layers.BatchNormalization()(x4_s)\n    x4_s= layers.Activation('relu')(x4_s)\n    x4_s = layers.add([x4_s,x4_x4]) \n    x4_s = layers.MaxPool2D(2,2)(x4_s)\n    \n    x5 = layers.Conv2D(512,3,padding='same')(x4_x4)\n    x5 = layers.BatchNormalization()(x5)\n    x5 = layers.Activation('relu')(x5)\n    x5 = layers.Conv2D(512,3,padding='same')(x5)\n    x5 = layers.BatchNormalization()(x5)\n    x5 = layers.Activation('relu')(x5)\n    x5 = layers.Conv2D(512,3,padding='same')(x5)\n    x5 = layers.BatchNormalization()(x5)\n    x5 = layers.Activation('relu')(x5)\n    residual_x4 = layers.Conv2D(512,1,strides=1,padding='same')(x4_x4)\n    x5 = layers.add([residual_x4,x5])\n\n    x5_x5 = layers.Conv2D(512,3,padding='same')(x5)\n    x5_x5 = layers.BatchNormalization()(x5_x5)\n    x5_x5 = layers.Activation('relu')(x5_x5)\n    x5_x5 = layers.Conv2D(512,3,padding='same')(x5_x5)\n    x5_x5 = layers.BatchNormalization()(x5_x5)\n    x5_x5 = layers.Activation('relu')(x5_x5)\n    x5_x5 = layers.Conv2D(512,3,padding='same')(x5_x5)\n    x5_x5 = layers.BatchNormalization()(x5_x5)\n    x5_x5 = layers.Activation('relu')(x5_x5)\n    x5_x5 = layers.add([x5,x5_x5])\n    x5_x5 = layers.MaxPool2D(2,2)(x5_x5)\n    \n    concetenated_3 = layers.concatenate([x5_x5,x4_s])\n    x5_s = layers.SeparableConv2D(512,3,padding='same')(concetenated_3)\n    x5_s = layers.BatchNormalization()(x5_s)\n    x5_s = layers.Activation('relu')(x5_s)\n    x5_s = layers.SeparableConv2D(512,3,padding='same')(x5_s)\n    x5_s= layers.BatchNormalization()(x5_s)\n    x5_s= layers.Activation('relu')(x5_s)\n    x5_s = layers.SeparableConv2D(512,3,padding='same')(x5_s)\n    x5_s= layers.BatchNormalization()(x5_s)\n    x5_s= layers.Activation('relu')(x5_s)\n    x5_s = layers.add([x5_s,x5_x5]) \n\n    x = layers.GlobalAveragePooling2D()(x5_s)\n\n    x = layers.Dense(64)(x)\n    x = layers.BatchNormalization()(x)\n    x = layers.Activation('relu')(x)\n    x = layers.Dropout(0.3)(x)\n    \n    output_tensor_1 = layers.Dense(1,activation='sigmoid')(x)\n\n    ### Extra layers for model 2 ####    \n    \n    x = layers.Dense(32)(x)\n    x = layers.BatchNormalization()(x)\n    x = layers.Activation('relu')(x)\n    x = layers.Dropout(0.3)(x)\n    output_tensor_2 = layers.Dense(6,activation='softmax')(x)\n    \n    model_2 = Model(inputs,output_tensor_2)\n    \n    model_1 = Model(inputs,output_tensor_1)\n    \n    return model_1, model_2","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"**model_1** - Original architecture to load weights<br>\n**model_2** - New modified architecture with an extra intermediate dense layer and an output layer"},{"metadata":{"trusted":true},"cell_type":"code","source":"model_1,model_2 = get_model_classif_nasnet_1()\nmodel_1.load_weights('../input/pretrained-model/model_4.h5')","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Freeze & Compile"},{"metadata":{"trusted":true},"cell_type":"code","source":"### Freeze weights\nfor layer in model_1.layers:\n    if 'conv' in layer.name:\n        layer.trainable = False\n        \n### Compile model 2\nmodel_2.compile(optimizer=Adam(set_lr), loss='categorical_crossentropy', metrics=['acc',quadratic_kappa_coefficient])","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Augmentation function"},{"metadata":{"trusted":true},"cell_type":"code","source":"def get_seq():\n    sometimes = lambda aug: iaa.Sometimes(0.5, aug)\n    seq = iaa.Sequential(\n        [\n            # apply the following augmenters to most images\n            iaa.Fliplr(0.5), # horizontally flip 50% of all images\n            iaa.Flipud(0.2), # vertically flip 20% of all images\n            sometimes(iaa.Affine(\n                scale={\"x\": (0.9, 1.1), \"y\": (0.9, 1.1)}, # scale images to 80-120% of their size, individually per axis\n                translate_percent={\"x\": (-0.1, 0.1), \"y\": (-0.1, 0.1)}, # translate by -20 to +20 percent (per axis)\n                rotate=(-10, 10), # rotate by -45 to +45 degrees\n                shear=(-5, 5), # shear by -16 to +16 degrees\n                order=[0, 1], # use nearest neighbour or bilinear interpolation (fast)\n                cval=(0, 255), # if mode is constant, use a cval between 0 and 255\n                mode=ia.ALL # use any of scikit-image's warping modes\n            )),\n            # execute 0 to 5 of the following (less important) augmenters per image\n            # don't execute all of them, as that would often be way too strong\n            iaa.SomeOf((0, 5),\n                [\n                    sometimes(iaa.Superpixels(p_replace=(0, 1.0), n_segments=(20, 200))), # convert images into their superpixel representation\n                    iaa.OneOf([\n                        iaa.GaussianBlur((0, 1.0)), # blur images with a sigma between 0 and 3.0\n                        iaa.AverageBlur(k=(3, 5)), # blur image using local means with kernel sizes between 2 and 7\n                        iaa.MedianBlur(k=(3, 5)), # blur image using local medians with kernel sizes between 2 and 7\n                    ]),\n                    iaa.Sharpen(alpha=(0, 1.0), lightness=(0.9, 1.1)), # sharpen images\n                    iaa.Emboss(alpha=(0, 1.0), strength=(0, 2.0)), # emboss images\n                    # search either for all edges or for directed edges,\n                    # blend the result with the original image using a blobby mask\n                    iaa.SimplexNoiseAlpha(iaa.OneOf([\n                        iaa.EdgeDetect(alpha=(0.5, 1.0)),\n                        iaa.DirectedEdgeDetect(alpha=(0.5, 1.0), direction=(0.0, 1.0)),\n                    ])),\n                    iaa.AdditiveGaussianNoise(loc=0, scale=(0.0, 0.01*255), per_channel=0.5), # add gaussian noise to images\n                    iaa.OneOf([\n                        iaa.Dropout((0.01, 0.05), per_channel=0.5), # randomly remove up to 10% of the pixels\n                        iaa.CoarseDropout((0.01, 0.03), size_percent=(0.01, 0.02), per_channel=0.2),\n                    ]),\n                    iaa.Invert(0.01, per_channel=True), # invert color channels\n                    iaa.Add((-2, 2), per_channel=0.5), # change brightness of images (by -10 to 10 of original value)\n                    iaa.AddToHueAndSaturation((-1, 1)), # change hue and saturation\n                    # either change the brightness of the whole image (sometimes\n                    # per channel) or change the brightness of subareas\n                    iaa.OneOf([\n                        iaa.Multiply((0.9, 1.1), per_channel=0.5),\n                        iaa.FrequencyNoiseAlpha(\n                            exponent=(-1, 0),\n                            first=iaa.Multiply((0.9, 1.1), per_channel=True),\n                            second=iaa.ContrastNormalization((0.9, 1.1))\n                        )\n                    ]),\n                    sometimes(iaa.ElasticTransformation(alpha=(0.5, 3.5), sigma=0.25)), # move pixels locally around (with random strengths)\n                    sometimes(iaa.PiecewiseAffine(scale=(0.01, 0.05))), # sometimes move parts of the image around\n                    sometimes(iaa.PerspectiveTransform(scale=(0.01, 0.1)))\n                ],\n                random_order=True\n            )\n        ],\n        random_order=True\n    )\n    return seq","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Data generator"},{"metadata":{"trusted":true},"cell_type":"code","source":"def chunker(seq, size):\n    return (seq[pos:pos + size] for pos in range(0, len(seq), size))\n\ndef get_image(img_name, data_dir='../input/prostate-cancer-grade-assessment/train_images'):\n    \n    img_path = os.path.join(data_dir, f'{img_name}.tiff')\n    img = skimage.io.MultiImage(img_path)\n    img = cv2.resize(img[-1], (256,256))\n    img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n    return img\n\ndef data_gen(list_files, id_label_map, batch_size, augment=False):\n    seq = get_seq()\n    while True:\n        shuffle(list_files)\n        for batch in chunker(list_files, batch_size):\n            X = [get_image(x) for x in batch]\n            Y = np.zeros((len(batch),6))\n            for i in range(len(batch)):\n                Y[i,id_label_map[get_id_from_file_path(batch[i])]] = 1.0\n            if augment:\n                X = seq.augment_images(X)\n            X = [preprocess_input(x) for x in X]\n\n            yield np.array(X), np.array(Y)\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def get_id_from_file_path(file_path):\n    return file_path.split(os.path.sep)[-1].replace('.tiff', '')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"batch_size=16\ndf_train = pd.read_csv(\"../input/prostate-cancer-grade-assessment/train.csv\")\nid_label_map = {k:v for k,v in zip(df_train.image_id.values, df_train.isup_grade.values)}","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Train-val split"},{"metadata":{"trusted":true},"cell_type":"code","source":"labeled_files = pd.read_csv(\"../input/prostate-cancer-grade-assessment/train.csv\").image_id.values\ntest_files = pd.read_csv(\"../input/prostate-cancer-grade-assessment/test.csv\").image_id.values\n\ntrain, val = train_test_split(labeled_files, test_size=0.1, random_state=101010)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Callbacks for saving, earlystopping, and reducing learning rate"},{"metadata":{"trusted":true},"cell_type":"code","source":"check_point = ModelCheckpoint('./model.h5',monitor='val_loss',verbose=True, save_best_only=True, save_weights_only=True)\nearly_stop = EarlyStopping(monitor='val_loss',patience=5,verbose=True)\nreduce_lr = ReduceLROnPlateau(monitor='val_loss', factor=0.2, patience=1)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Train"},{"metadata":{"trusted":true},"cell_type":"code","source":"history = model_2.fit_generator(\n    data_gen(train, id_label_map, batch_size, augment=True),\n    validation_data=data_gen(val, id_label_map, batch_size),\n    epochs=set_epochs, verbose=1,\n    callbacks=[check_point,early_stop,reduce_lr],\n    steps_per_epoch=len(train) // batch_size,\n    validation_steps=len(val) // batch_size)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"# Inference"},{"metadata":{},"cell_type":"markdown","source":"### TTA"},{"metadata":{"trusted":true},"cell_type":"code","source":"def TTA(img):\n    img1 = img\n    img2 = cv2.rotate(img, cv2.ROTATE_90_CLOCKWISE)\n    img3 = cv2.rotate(img, cv2.ROTATE_90_COUNTERCLOCKWISE)\n    img4 = cv2.rotate(img, cv2.ROTATE_180)\n    images = [img1, img2, img3, img4]\n    \n    return model_2.predict(np.array(images), batch_size=4)","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### Post-process predictions"},{"metadata":{"trusted":true},"cell_type":"code","source":"def post_process(preds):\n    avg = np.sum(preds,axis = 0)\n    label = np.argmax(avg)\n    return label","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"### Predicting on test images"},{"metadata":{"trusted":true},"cell_type":"code","source":"data_dir = '../input/prostate-cancer-grade-assessment/test_images'\nsample_submission = pd.read_csv('../input/prostate-cancer-grade-assessment/sample_submission.csv')\n# data_dir = '../input/prostate-cancer-grade-assessment/train_images'\n# sample_submission = pd.read_csv('../input/prostate-cancer-grade-assessment/train.csv').head(50)\n\ntest_images = sample_submission.image_id.values\nlabels = []\n\ntry:    \n    for image in tqdm(test_images):\n        img = get_image(image, data_dir)\n        preds = TTA(img)\n        label = post_process(preds)\n        labels.append(label)\n    sample_submission['isup_grade'] = labels\nexcept:\n    print('Test dir not found')\n    \nsample_submission['isup_grade'] = sample_submission['isup_grade'].astype(int)\nsample_submission.to_csv('submission.csv', index=False)\nsample_submission.head()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"Other notebooks\n\n[Efficientnet_keras_train-(QWK loss + Augmentation)](https://www.kaggle.com/prateekagnihotri/efficientnet-keras-train-qwk-loss-augmentation) - Notebook to train efficientnet model using quadratic weighted kappa and a lot of augmentation<br>\n[Efficientnet_keras_infernce (+ TTA)](https://www.kaggle.com/prateekagnihotri/efficientnet-keras-infernce-tta) - Inference kernel for above trained model"},{"metadata":{},"cell_type":"markdown","source":"Thanks for reading. Please upvote if you found it helpful."}],"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":4,"nbformat_minor":4}