{"cells":[{"metadata":{"trusted":true},"cell_type":"code","source":"TRAINING = False\nMODEL_SAVE_PATH = \"model.pt\"\nMODEL_LOAD_PATH = \"../input/cassava-snapmix/model.pt\"","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"aeccdef7-4f16-47b5-a051-c4d5b4bf012c","_cell_guid":"8f017f1c-9545-4adf-9b2a-6e870ab7de52","trusted":true,"_kg_hide-output":true},"cell_type":"code","source":"!pip install ../input/timm-package/timm-0.1.26-py3-none-any.whl","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"804f4feb-f932-4326-b3a7-575ab3110ab1","_cell_guid":"cc775556-fba1-4815-86da-67eef86a6e11","trusted":true,"_kg_hide-input":true,"_kg_hide-output":true},"cell_type":"code","source":"import random\nimport numpy as np\nimport os\nimport torch\nimport pandas as pd\nimport albumentations as A\nimport torch.nn as nn\nfrom torch.cuda.amp import autocast, GradScaler\nfrom sklearn.model_selection import StratifiedKFold\nfrom albumentations import Compose\nfrom albumentations.pytorch import ToTensorV2\nfrom tqdm import tqdm\nimport cv2\nimport torch.nn.functional as F\nimport timm","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"819b6a7e-2027-4f1d-9ef0-58645fb9505d","_cell_guid":"b9c0602b-c406-488d-904d-3deffa8ffa92","trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"import warnings\nwarnings.filterwarnings(\"ignore\")","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"7b6979bb-464c-4dcc-b8c7-860db14b4074","_cell_guid":"d5d37074-f30f-4f72-a58e-a1673a5bc86f","trusted":true},"cell_type":"code","source":"DATA_PATH = '../input/cassava-leaf-disease-classification/'\nNUM_FOLDS = 5\nbatch_size = 32\nEPOCHS = 10\nimage_size = 512\nSNAPMIX_ALPHA = 5.0\nSNAPMIX_PCT = 0.5\nGRAD_ACCUM_STEPS = 1\nTIMM_MODEL = 'resnet50'","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"34af7959-7932-4600-b4e4-1616fce26e2a","_cell_guid":"eac6cb55-6629-4e7b-8eac-1c8c28a8bb14","trusted":true},"cell_type":"code","source":"def seed_everything(seed):\n    random.seed(seed)\n    os.environ['PYTHONHASHSEED'] = str(seed)\n    np.random.seed(seed)\n    torch.manual_seed(seed)\n    torch.cuda.manual_seed(seed)\n    torch.backends.cudnn.deterministic = True\n    torch.backends.cudnn.benchmark = True\n\nSEED = 1234\nseed_everything(SEED)","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"464c4730-4e0a-4704-a01b-aa264fd67bc6","_cell_guid":"c9466fed-465a-46e1-be03-dad742509f27","trusted":true},"cell_type":"markdown","source":"# Dataset"},{"metadata":{"_uuid":"affb1ae7-e0f6-458b-b16e-42250311d1b2","_cell_guid":"0d704099-d756-4d21-a921-47a3d1d18ae6","trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"class CassavaDataset(torch.utils.data.Dataset):\n\n    def __init__(self, dataframe, root_dir, transforms=None):\n        super().__init__()\n        self.dataframe = dataframe\n        self.root_dir = root_dir\n        self.transforms = transforms\n\n    def __len__(self):\n        return len(self.dataframe)\n    \n    def get_img_bgr_to_rgb(self, path):\n        im_bgr = cv2.imread(path)\n        im_rgb = im_bgr[:, :, ::-1]\n        return im_rgb\n\n    def __getitem__(self, idx):\n        if torch.is_tensor(idx):\n            idx = idx.tolist()\n        img_name = os.path.join(self.root_dir, self.dataframe.iloc[idx, 0])\n        image = self.get_img_bgr_to_rgb(img_name)\n        if self.transforms:\n            image = self.transforms(image=image)['image']\n        csv_row = self.dataframe.iloc[idx, 1:]\n        sample = {\n            'image': image, \n            'label': csv_row.label,\n        }\n        return sample","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"61e906db-f34b-4932-9c69-f508c64cc2d2","_cell_guid":"a3e62f38-dab7-4ea4-8ab5-25a407c1b248","trusted":true},"cell_type":"code","source":"train_df = pd.read_csv(DATA_PATH + \"train.csv\")","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"49a0aebd-78d3-4389-8c7b-615c344ae2ef","_cell_guid":"3a2a78f0-f10c-4567-99e1-f8789c49e469","trusted":true},"cell_type":"markdown","source":"# Transforms"},{"metadata":{"_uuid":"5c600a1e-c05f-440a-9ff8-6b8133b02055","_cell_guid":"99ca3954-6b63-4f45-8430-8c0a7eedec67","trusted":true},"cell_type":"code","source":"def train_transforms():\n    return Compose([\n            A.RandomResizedCrop(image_size, image_size),\n            A.HorizontalFlip(p=0.5),\n            A.VerticalFlip(p=0.5),\n            A.RandomBrightness (p=0.5),\n            A.GridDistortion(p=0.5),\n            A.RandomGamma(p=0.5),\n            A.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225], max_pixel_value=255.0, p=1.0),\n            ToTensorV2(p=1.0),\n        ], p=1.)\n\n\ndef valid_transforms():\n    return Compose([\n            A.Resize(image_size, image_size),\n            A.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225], max_pixel_value=255.0, p=1.0),\n            ToTensorV2(p=1.0),\n        ], p=1.)","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"21f3c1e9-2945-4abc-afac-8bab57982638","_cell_guid":"523adc50-7520-4407-8e55-8454ce124e13","trusted":true},"cell_type":"markdown","source":"# Model"},{"metadata":{"_uuid":"e846aa08-7691-46e2-a496-50e8f58e08c3","_cell_guid":"b54878d4-7b86-4149-86d0-09ca7350c2b4","trusted":true},"cell_type":"code","source":"TIMM_MODEL = 'resnet50'","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"37684342-be0a-4809-998f-3ec458c94f70","_cell_guid":"31ddd30d-2671-434c-8fbf-ef72dac448c9","trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"class CassavaNet(nn.Module):\n    def __init__(self):\n        super().__init__()\n        backbone = timm.create_model(TIMM_MODEL, pretrained=False, checkpoint_path='../input/timm-pretrained-resnet/resnet/resnet50_ram-a26f946b.pth')\n        n_features = backbone.fc.in_features\n        self.backbone = nn.Sequential(*backbone.children())[:-2]\n        self.classifier = nn.Linear(n_features, 5)\n        self.pool = nn.AdaptiveAvgPool2d((1, 1))\n\n    def forward_features(self, x):\n        x = self.backbone(x)\n        return x\n\n    def forward(self, x):\n        feats = self.forward_features(x)\n        x = self.pool(feats).view(x.size(0), -1)\n        x = self.classifier(x)\n        return x, feats","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"db59e1cc-c87b-4e6c-82c1-76a54aae8667","_cell_guid":"19063ee2-a432-435a-8727-ba2f0d27397e","trusted":true},"cell_type":"code","source":"device = torch.device(\"cuda:0\" if torch.cuda.is_available() else \"cpu\")","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"1470c206-0b36-4768-9a12-9d77d8761d72","_cell_guid":"8d8682f2-5cb9-46de-8aa2-3e1ef7cb2cda","trusted":true},"cell_type":"markdown","source":"# SnapMix Augmentations"},{"metadata":{"_uuid":"1c621ad1-8998-455b-a2b3-92bd22f98dc8","_cell_guid":"e7d829d4-aaeb-47ed-b267-1fcfe2d746fc","trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"def rand_bbox(size, lam):\n    W = size[2]\n    H = size[3]\n    cut_rat = np.sqrt(1. - lam)\n    cut_w = np.int(W * cut_rat)\n    cut_h = np.int(H * cut_rat)\n\n    # uniform\n    cx = np.random.randint(W)\n    cy = np.random.randint(H)\n\n    bbx1 = np.clip(cx - cut_w // 2, 0, W)\n    bby1 = np.clip(cy - cut_h // 2, 0, H)\n    bbx2 = np.clip(cx + cut_w // 2, 0, W)\n    bby2 = np.clip(cy + cut_h // 2, 0, H)\n\n    return bbx1, bby1, bbx2, bby2","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"5801b3e0-463f-4887-b394-66607b18a734","_cell_guid":"043abfb1-467b-4617-be16-5aca397eed0f","trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"def get_spm(inputs,target,model):\n    imgsize = (image_size, image_size)\n    batch_size = inputs.size(0)\n    with torch.no_grad():\n        output,fms = model(inputs)\n        \n        clsw = model.classifier\n        weight = clsw.weight.data\n        bias = clsw.bias.data\n        \n        weight = weight.view(weight.size(0),weight.size(1),1,1)\n        \n        fms = F.relu(fms)\n        poolfea = F.adaptive_avg_pool2d(fms,(1,1)).squeeze()\n        clslogit = F.softmax(clsw.forward(poolfea))\n        \n        logitlist = []\n        for i in range(batch_size):\n            logitlist.append(clslogit[i,target[i]])\n            \n        clslogit = torch.stack(logitlist)\n\n        out = F.conv2d(fms, weight, bias=bias)\n\n        outmaps = []\n        for i in range(batch_size):\n            evimap = out[i,target[i]]\n            outmaps.append(evimap)\n\n        outmaps = torch.stack(outmaps)\n        if imgsize is not None:\n            outmaps = outmaps.view(outmaps.size(0),1,outmaps.size(1),outmaps.size(2))\n            outmaps = F.interpolate(outmaps,imgsize,mode='bilinear',align_corners=False)\n\n        outmaps = outmaps.squeeze()\n\n        for i in range(batch_size):\n            outmaps[i] -= outmaps[i].min()\n            outmaps[i] /= outmaps[i].sum()\n\n\n    return outmaps,clslogit","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"8b96141c-9fa5-431a-85c7-84d131d160d7","_cell_guid":"127a3e11-63d6-4ac5-b9d1-6bbca5e1f302","trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"def snapmix(inputs, target, alpha, model=None):\n\n    r = np.random.rand(1)\n    lam_a = torch.ones(inputs.size(0))\n    lam_b = 1 - lam_a\n    target_b = target.clone()\n\n    wfmaps,_ = get_spm(inputs, target, model)\n    batch_size = inputs.size(0)\n    lam = np.random.beta(alpha, alpha)\n    lam1 = np.random.beta(alpha, alpha)\n    rand_index = torch.randperm(batch_size).cuda()\n    wfmaps_b = wfmaps[rand_index,:,:]\n    target_b = target[rand_index]\n\n    same_label = target == target_b\n    bbx1, bby1, bbx2, bby2 = rand_bbox(inputs.size(), lam)\n    bbx1_1, bby1_1, bbx2_1, bby2_1 = rand_bbox(inputs.size(), lam1)\n\n    area = (bby2-bby1)*(bbx2-bbx1)\n    area1 = (bby2_1-bby1_1)*(bbx2_1-bbx1_1)\n\n    if  area1 > 0 and  area>0:\n        ncont = inputs[rand_index, :, bbx1_1:bbx2_1, bby1_1:bby2_1].clone()\n        ncont = F.interpolate(ncont, size=(bbx2-bbx1,bby2-bby1), mode='bilinear', align_corners=True)\n        inputs[:, :, bbx1:bbx2, bby1:bby2] = ncont\n        lam_a = 1 - wfmaps[:,bbx1:bbx2,bby1:bby2].sum(2).sum(1)/(wfmaps.sum(2).sum(1)+1e-8)\n        lam_b = wfmaps_b[:,bbx1_1:bbx2_1,bby1_1:bby2_1].sum(2).sum(1)/(wfmaps_b.sum(2).sum(1)+1e-8)\n        tmp = lam_a.clone()\n        lam_a[same_label] += lam_b[same_label]\n        lam_b[same_label] += tmp[same_label]\n        lam = 1 - ((bbx2 - bbx1) * (bby2 - bby1) / (inputs.size()[-1] * inputs.size()[-2]))\n        lam_a[torch.isnan(lam_a)] = lam\n        lam_b[torch.isnan(lam_b)] = 1-lam\n\n    return inputs,target,target_b,lam_a.cuda(),lam_b.cuda()","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"49e273f8-6e9f-42fe-b8c5-4fbdba77e191","_cell_guid":"c29b42c4-e1b7-4583-a51c-ca9c5ca8c746","trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"class SnapMixLoss(nn.Module):\n    def __init__(self):\n        super().__init__()\n        \n    def forward(self, criterion, outputs, ya, yb, lam_a, lam_b):\n        loss_a = criterion(outputs, ya)\n        loss_b = criterion(outputs, yb)\n        loss = torch.mean(loss_a * lam_a + loss_b * lam_b)\n        return loss","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"d0dea11a-9fca-47cb-90aa-01e1bc85d8c3","_cell_guid":"e94237b8-acac-4bfa-9583-6d289abf1548","trusted":true},"cell_type":"markdown","source":"# Training"},{"metadata":{"_uuid":"8d4a24dc-81ac-47e4-887c-643ada4e4021","_cell_guid":"bfdfa327-9a91-456a-bb97-5180cc7adeff","trusted":true},"cell_type":"code","source":"if TRAINING:\n    model = CassavaNet()\n    print(\"Created New Model\")\nelse:\n    model = torch.load(MODEL_LOAD_PATH)\n    print(\"Loaded Model from\", MODEL_LOAD_PATH)\n\nmodel = model.to(device)","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"6eb3d1d4-0337-4764-9214-06ecb326fe84","_cell_guid":"614d1ebf-67f9-424e-a178-708bbbee3b6d","trusted":true,"_kg_hide-output":true},"cell_type":"code","source":"model","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"43c83fcb-75db-48aa-ad50-5dae8c297644","_cell_guid":"95e77029-2769-4bde-94cb-f861da2c22bc","trusted":true},"cell_type":"code","source":"criterion = nn.CrossEntropyLoss(reduction='none').to(device)\nsnapmix_criterion = SnapMixLoss().to(device)","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"6a6ed56f-d0b9-4f72-87d7-ba302f7fa983","_cell_guid":"995aeafb-c845-4e0f-a1b8-eca68a95477b","trusted":true},"cell_type":"code","source":"param_groups = [\n    {'params': model.backbone.parameters(), 'lr': 1e-2},\n    {'params': model.classifier.parameters()},\n]\n\noptimizer = torch.optim.SGD(param_groups, lr=1e-1, momentum=0.9, weight_decay=1e-4, nesterov=True)\nscheduler = torch.optim.lr_scheduler.MultiStepLR(optimizer, milestones=[1,20,40], gamma=0.1, last_epoch=-1)\nscaler = GradScaler()","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"d661d434-290c-42ff-ab74-d49acfe712f1","_cell_guid":"15c2425f-9a85-42fd-84c6-c43374aaa8ae","trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"def get_datasets(train_split, valid_split):\n    train_set = train_df.iloc[train_split].reset_index(drop=True)\n    valid_set = train_df.iloc[valid_split].reset_index(drop=True)\n    \n    train_ds = CassavaDataset(dataframe=train_set,\n                              root_dir=DATA_PATH + 'train_images',\n                              transforms=train_transforms()\n                             )\n    \n    valid_ds = CassavaDataset(dataframe=valid_set,\n                              root_dir=DATA_PATH + 'train_images',\n                              transforms=valid_transforms())\n    \n    train_dl = torch.utils.data.DataLoader(train_ds, batch_size=batch_size, \n                                           shuffle=True, num_workers=8, drop_last=True,\n                                           pin_memory=True)\n    valid_dl = torch.utils.data.DataLoader(valid_ds, batch_size=batch_size, \n                                           shuffle=False, num_workers=8,\n                                           pin_memory=True)\n    \n    return train_dl, valid_dl","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"2cf162e8-3651-4e43-b4ae-08dd206f6a2a","_cell_guid":"d74e0d76-7e7d-4aae-b9ef-1eadeb40571c","trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"def train_single_epoch(train_dl, epoch):\n    model.train()\n    train_loss = 0\n    train_accuracy = 0\n    \n    with tqdm(total=len(train_dl)) as t:\n        for batch_idx, data in enumerate(train_dl,1):\n            image, label = data.values()\n            X, y = image.to(device).float(), label.to(device).long()\n            \n            with autocast():\n                rand = np.random.rand()\n                if rand > (1.0-SNAPMIX_PCT):\n                    X, ya, yb, lam_a, lam_b = snapmix(X, y, SNAPMIX_ALPHA, model)\n                    outputs, _ = model(X)\n                    loss = snapmix_criterion(criterion, outputs, ya, yb, lam_a, lam_b)\n                else:\n                    outputs, _ = model(X)\n                    loss = torch.mean(criterion(outputs, y))\n            \n            scaler.scale(loss).backward()\n            \n            if (batch_idx % GRAD_ACCUM_STEPS == 0) or (batch_idx == len(train_dl)):\n                scaler.step(optimizer)\n                scaler.update()\n                optimizer.zero_grad()\n            \n            train_loss += loss.item()\n            \n            preds = F.softmax(outputs).argmax(axis=1)\n            accuracy = (preds==y).sum()/len(y)\n            train_accuracy += accuracy.cpu().item()\n            \n            t.set_description(f\"Epoch: {epoch}/{EPOCHS}\\tLoss: {train_loss/batch_idx:0.4f}\\tAccuracy: {train_accuracy/batch_idx:.4f}\")\n            t.update()\n    \n    return train_loss/len(train_dl), train_accuracy/len(train_dl)","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"609ff24f-e93b-4a7c-91b0-a1f3c2a60980","_cell_guid":"e1d214d6-9d05-4dcb-8b70-a6027416a7e6","trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"def validate_one_epoch(valid_dl):\n    model.eval()\n    \n    val_loss = 0\n    scores = []\n    \n    with torch.no_grad():\n        for data in valid_dl:\n            image, label = data.values()\n            X, y = image.to(device), label.to(device)\n            outputs, _ = model(X)\n            loss = torch.mean(criterion(outputs, y))\n            val_loss += loss.item()\n            \n            preds = F.softmax(outputs).argmax(axis=1)\n            accuracy = (preds==y).sum()/len(y)\n            scores.append(accuracy.cpu().item())\n    \n    return val_loss/len(valid_dl), np.average(scores)","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"51703c6f-0b00-4401-a845-cf55ff20145e","_cell_guid":"f935847b-fa65-49f1-b34e-3cd5306a6c5b","trusted":true,"_kg_hide-input":true},"cell_type":"code","source":"if TRAINING:\n    folds = StratifiedKFold(n_splits=NUM_FOLDS, shuffle=True, random_state=SEED).split(np.arange(train_df.shape[0]), train_df.label.values)\n\n    for fold_num, (train_split, valid_split) in enumerate(folds):\n        train_dl, valid_dl = get_datasets(train_split, valid_split)        \n\n        best_metric = 0\n\n        for epoch in range(EPOCHS):\n            train_loss, train_accuracy = train_single_epoch(train_dl, epoch+1)\n            scheduler.step()\n            if torch.cuda.is_available():\n                torch.cuda.empty_cache()\n\n            valid_loss, valid_accuracy = validate_one_epoch(valid_dl)\n\n            print(f\"Epoch: {epoch+1}/{EPOCHS}\\tLoss: {train_loss:0.4f}\\tAccuracy: {train_accuracy:.4f}\\tValid Loss: {valid_loss:0.4f}\\tValid Accuracy: {valid_accuracy:0.4f}\")\n\n            if valid_accuracy>best_metric:\n                print(f\"Accuracy increased from {best_metric} to {valid_accuracy}, Saving Model\")\n                torch.save(model, MODEL_SAVE_PATH)\n                best_metric = valid_accuracy\n\n    print(\"Best Model Accuracy:\", best_metric)\n    model = torch.load(MODEL_SAVE_PATH)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"torch.save(model, MODEL_SAVE_PATH)","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"7af0f319-e12e-4375-b8a6-b1c98c884cf7","_cell_guid":"59b16abe-ef58-47e0-b141-5236f64ab7c1","trusted":true},"cell_type":"markdown","source":"# Prediction"},{"metadata":{"_uuid":"fb7baba6-6f68-46d6-940e-638bac0623d6","_cell_guid":"898748f9-bf7a-453c-8213-6245254511ed","trusted":true},"cell_type":"code","source":"test_images_path = DATA_PATH+\"test_images\"\ntest_image_id = os.listdir(test_images_path)","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"756b829d-7ba8-4f62-a3ea-e61506107c41","_cell_guid":"9e922e26-0e59-47f5-b50d-e52fb3062ae6","trusted":true},"cell_type":"code","source":"test_df = pd.DataFrame(test_image_id, columns=[\"image_id\"])\ntest_df['label'] = -1","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"83d574b8-bf41-4919-9f68-313734a2e100","_cell_guid":"81548020-022d-414e-b972-6ab4dff20115","trusted":true},"cell_type":"code","source":"test_ds = CassavaDataset(dataframe=test_df,\n                              root_dir=DATA_PATH + 'test_images',\n                              transforms=valid_transforms())\n\ntest_dl = torch.utils.data.DataLoader(test_ds, batch_size=batch_size, \n                                           shuffle=False, num_workers=8,\n                                           pin_memory=True)","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"a59a5416-98f1-454f-9655-881e2d1813c4","_cell_guid":"03cc377c-c88a-4128-9be2-1c1b94dd8447","trusted":true},"cell_type":"code","source":"model.eval()\nlabels = []\nwith torch.no_grad():\n    for data in test_dl:\n        outputs, _ = model(data['image'].to(device))\n        preds = F.softmax(outputs).argmax(axis=1)\n        labels.extend(preds.cpu().numpy())\n\ntest_df['label'] = labels\ntest_df.head()","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"d0d4bac5-fa74-4f71-a04a-ae2a27c1fce3","_cell_guid":"c07d2b43-8d70-491d-aa39-620ad2712145","trusted":true},"cell_type":"code","source":"test_df.to_csv(\"submission.csv\", index=False)","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"94f636d9-cc2a-4df4-9dd8-ae532e90c053","_cell_guid":"fea4e5f3-fb3f-4168-8186-0699d1c9cf87","trusted":true},"cell_type":"code","source":"","execution_count":null,"outputs":[]}],"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}