{"cells":[{"metadata":{"trusted":true,"_uuid":"27393d3b533b1928609f3bceda21209b7a08f6fa"},"cell_type":"code","source":"!pip install albumentations > /dev/null","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"0cfaa55cedb7e80727b272bf5b648dc38f93bf62","scrolled":true},"cell_type":"code","source":"# !pip install pretrainedmodels > /dev/null","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"36a7ea31f1a40c2dc6fe2d4f6817a00111139626"},"cell_type":"code","source":"ls ../input","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"cell_type":"code","source":"import os\n\nimport albumentations\nfrom albumentations import torch as AT\n# import pretrainedmodels\n\nimport numpy as np\nimport pandas as pd\n\nimport cv2\n\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nimport torch.optim as optim\nfrom torch.optim import lr_scheduler\nfrom torch.utils.data import DataLoader, Dataset\n\nfrom PIL import Image\nfrom sklearn.preprocessing import LabelEncoder, OneHotEncoder\n\nfrom tqdm import tqdm, tqdm_notebook\n\nfrom matplotlib import pyplot as plt\n%matplotlib inline\nimport warnings\nwarnings.simplefilter(\"ignore\", category=DeprecationWarning)","execution_count":null,"outputs":[]},{"metadata":{"_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","trusted":true},"cell_type":"code","source":"BBOX_TRAIN = '../input/bboxsplit/bounding_boxes_train.csv'\nBBOX_TEST = '../input/bboxsplit/bounding_boxes_test.csv'\ntrain_df = pd.read_csv(\"../input/humpback-whale-identification/train.csv\")\ntrain_df.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"9cb0255335540c1936b3e3555f3d41c5240727b4"},"cell_type":"code","source":"# del_ind = []\n# for i in range(len(train_df)):\n#     if train_df.iloc[i]['Id'] == 'new_whale':\n#         del_ind.append(i)\n# train_df = train_df.drop(train_df.index[del_ind])","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"89ba93ca996c77a29ed25dcfea80b6b8cb8e2291"},"cell_type":"code","source":"for i in range(4):\n    newdf = train_df.groupby(\"Id\").filter(lambda x: len(x) == i+1 )\n#     newdf =pd.concat([newdf]*(4-i), ignore_index=False)\n    train_df = train_df.append([newdf]*(4-i), ignore_index=True)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"8f62fc320145a438147b857169f6725c88c58274"},"cell_type":"code","source":"train_df.shape, train_df.Id.nunique()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"b4fffbaf19f8371fe63f07f8ff72ae4afb0e2dc7"},"cell_type":"code","source":"NUM_CLASSES = train_df.Id.nunique()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"969283f4f5a1f732297a9ed4335910d3e594de84"},"cell_type":"code","source":"NUM_CLASSES","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"cc066a56726375d28143b284158716305d200d99"},"cell_type":"code","source":"train_df.Id.value_counts().iloc[1:].hist(bins=40)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"24acf1683033a1e7d882fa4cfca4bdf7a62dd182"},"cell_type":"code","source":"RESIZE_H = 299\nRESIZE_W = 299\n\ndata_transforms = albumentations.Compose([\n    albumentations.Resize(RESIZE_H, RESIZE_W),\n    albumentations.HorizontalFlip(),\n    albumentations.OneOf([\n        albumentations.RandomContrast(),\n        albumentations.RandomBrightness(),\n    ]),\n    albumentations.ShiftScaleRotate(rotate_limit=10, scale_limit=0.15),\n    albumentations.JpegCompression(80),\n    albumentations.HueSaturationValue(),\n#     albumentations.Normalize(),\n    AT.ToTensor()\n])\n\ndata_transforms_test = albumentations.Compose([\n    albumentations.Resize(RESIZE_H, RESIZE_W),\n#     albumentations.Normalize(),\n    AT.ToTensor()\n])","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"eaef0437168af0b48db1b8cf43589901734606b3"},"cell_type":"code","source":"def prepare_labels(y):\n    # From here: https://www.kaggle.com/pestipeti/keras-cnn-starter\n    values = np.array(y)\n    label_encoder = LabelEncoder()\n    integer_encoded = label_encoder.fit_transform(values)\n\n    onehot_encoder = OneHotEncoder(sparse=False)\n    integer_encoded = integer_encoded.reshape(len(integer_encoded), 1)\n    onehot_encoded = onehot_encoder.fit_transform(integer_encoded)\n\n    y = onehot_encoded\n    return y, label_encoder","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"28538a1976383479893384ed691d116fc878845b"},"cell_type":"code","source":"y, lab_encoder = prepare_labels(train_df['Id'])","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"efffa7e82f50f339aab100fed67835e49dd8da89"},"cell_type":"code","source":"class WhaleDataset(Dataset):\n    def __init__(self, datafolder, datatype='train', df=None, df_bbox = None, transform=None, y=None):\n        self.datafolder = datafolder\n        self.datatype = datatype\n        self.y = y\n        if self.datatype == 'train':\n            self.df = df.values\n        self.df_bbox = df_bbox\n        if self.datatype == 'train':\n            self.image_files_list = list(df.Image)\n        else:\n            self.image_files_list = [s for s in os.listdir(datafolder)]\n        self.transform = transform\n\n\n    def __len__(self):\n        return len(self.image_files_list)\n    \n    def __getitem__(self, idx):\n        if self.datatype == 'train':\n            img_name = os.path.join(self.datafolder, self.df[idx][0])\n            bbox = self.df_bbox.loc[self.df[idx][0]]\n            x0, y0, x1, y1 = int(bbox['x0']), int(bbox['y0']), int(bbox['x1']),  int(bbox['y1'])\n            label = self.y[idx]\n            \n        elif self.datatype == 'test':\n            img_name = os.path.join(self.datafolder, self.image_files_list[idx])\n            bbox = self.df_bbox.loc[self.image_files_list[idx]]\n            x0, y0, x1, y1 = int(bbox['x0']), int(bbox['y0']), int(bbox['x1']),  int(bbox['y1'])\n            label = np.zeros((NUM_CLASSES,))\n        img = cv2.imread(img_name)\n        img = img[y0:y1, x0:x1]\n#         img = Image.fromarray(img)\n        image = self.transform(image = img)['image']\n        if self.datatype == 'train':\n            return image, label\n        elif self.datatype == 'test':\n            # so that the images will be in a correct order\n            return image, label, self.image_files_list[idx]","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"4f8f979e52230182c0b86275394210178ba72f40"},"cell_type":"code","source":"bbox_df_train = pd.read_csv(BBOX_TRAIN).set_index('Image')\nbbox_df_test = pd.read_csv(BBOX_TEST).set_index('Image')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"3fefb49961af3d467d4b667d8c07423f5db40097"},"cell_type":"code","source":"bbox = bbox_df_train.loc['0000e88ab.jpg']","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"72e200e47e7e96ea4744bd702ccc65d7858401e6"},"cell_type":"code","source":"img = cv2.imread('../input/humpback-whale-identification/train/0000e88ab.jpg')\nimg = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\nimg = img[int(bbox['y0']):int(bbox['y1']), int(bbox['x0']):int(bbox['x1'])]\nimage = Image.fromarray(img)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"6ece11388a770967dd880781eec0a2c553af7921"},"cell_type":"code","source":"image","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"52816552dfa11714cb8e7866b422474971f78d82"},"cell_type":"code","source":"train_dataset = WhaleDataset(\n    datafolder='../input/humpback-whale-identification/train/', \n    datatype='train', \n    df=train_df, df_bbox =  bbox_df_train,\n    transform=data_transforms, \n    y=y\n)\n\ntest_set = WhaleDataset(\n    datafolder='../input/humpback-whale-identification/test/', \n    datatype='test', df_bbox =  bbox_df_test,\n    transform=data_transforms_test\n)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"68d634c72ad9eb5b0ebd551661983e3d93e6ce79"},"cell_type":"code","source":"batch_size = 32\nnum_workers = 0\n\ntrain_loader = DataLoader(train_dataset, batch_size=batch_size, num_workers=num_workers, pin_memory=True)\ntest_loader = DataLoader(test_set, batch_size=batch_size, num_workers=num_workers, pin_memory=True)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"83aad34612550cc393aeec42dc0201ae2b422ac3"},"cell_type":"code","source":"\"\"\"\nPorted to pytorch thanks to [tstandley](https://github.com/tstandley/Xception-PyTorch)\n@author: tstandley\nAdapted by cadene\nCreates an Xception Model as defined in:\nFrancois Chollet\nXception: Deep Learning with Depthwise Separable Convolutions\nhttps://arxiv.org/pdf/1610.02357.pdf\nThis weights ported from the Keras implementation. Achieves the following performance on the validation set:\nLoss:0.9173 Prec@1:78.892 Prec@5:94.292\nREMEMBER to set your image size to 3x299x299 for both test and validation\nnormalize = transforms.Normalize(mean=[0.5, 0.5, 0.5],\n                                  std=[0.5, 0.5, 0.5])\nThe resize parameter of the validation transform should be 333, and make sure to center crop at 299x299\n\"\"\"\nfrom __future__ import print_function, division, absolute_import\nimport math\nimport torch\nimport torch.nn as nn\nimport torch.nn.functional as F\nimport torch.utils.model_zoo as model_zoo\nfrom torch.nn import init\n\n__all__ = ['xception']\n\npretrained_settings = {\n    'xception': {\n        'imagenet': {\n            'url': 'http://data.lip6.fr/cadene/pretrainedmodels/xception-43020ad28.pth',\n            'input_space': 'RGB',\n            'input_size': [3, 299, 299],\n            'input_range': [0, 1],\n            'mean': [0.5, 0.5, 0.5],\n            'std': [0.5, 0.5, 0.5],\n            'num_classes': 1000,\n            'scale': 0.8975 # The resize parameter of the validation transform should be 333, and make sure to center crop at 299x299\n        }\n    }\n}\n\n\nclass SeparableConv2d(nn.Module):\n    def __init__(self,in_channels,out_channels,kernel_size=1,stride=1,padding=0,dilation=1,bias=False):\n        super(SeparableConv2d,self).__init__()\n\n        self.conv1 = nn.Conv2d(in_channels,in_channels,kernel_size,stride,padding,dilation,groups=in_channels,bias=bias)\n        self.pointwise = nn.Conv2d(in_channels,out_channels,1,1,0,1,1,bias=bias)\n\n    def forward(self,x):\n        x = self.conv1(x)\n        x = self.pointwise(x)\n        return x\n\n\nclass Block(nn.Module):\n    def __init__(self,in_filters,out_filters,reps,strides=1,start_with_relu=True,grow_first=True):\n        super(Block, self).__init__()\n\n        if out_filters != in_filters or strides!=1:\n            self.skip = nn.Conv2d(in_filters,out_filters,1,stride=strides, bias=False)\n            self.skipbn = nn.BatchNorm2d(out_filters)\n        else:\n            self.skip=None\n\n        self.relu = nn.ReLU(inplace=True)\n        rep=[]\n\n        filters=in_filters\n        if grow_first:\n            rep.append(self.relu)\n            rep.append(SeparableConv2d(in_filters,out_filters,3,stride=1,padding=1,bias=False))\n            rep.append(nn.BatchNorm2d(out_filters))\n            filters = out_filters\n\n        for i in range(reps-1):\n            rep.append(self.relu)\n            rep.append(SeparableConv2d(filters,filters,3,stride=1,padding=1,bias=False))\n            rep.append(nn.BatchNorm2d(filters))\n\n        if not grow_first:\n            rep.append(self.relu)\n            rep.append(SeparableConv2d(in_filters,out_filters,3,stride=1,padding=1,bias=False))\n            rep.append(nn.BatchNorm2d(out_filters))\n\n        if not start_with_relu:\n            rep = rep[1:]\n        else:\n            rep[0] = nn.ReLU(inplace=False)\n\n        if strides != 1:\n            rep.append(nn.MaxPool2d(3,strides,1))\n        self.rep = nn.Sequential(*rep)\n\n    def forward(self,inp):\n        x = self.rep(inp)\n\n        if self.skip is not None:\n            skip = self.skip(inp)\n            skip = self.skipbn(skip)\n        else:\n            skip = inp\n\n        x+=skip\n        return x\n\n\nclass Xception(nn.Module):\n    \"\"\"\n    Xception optimized for the ImageNet dataset, as specified in\n    https://arxiv.org/pdf/1610.02357.pdf\n    \"\"\"\n    def __init__(self, num_classes=1000):\n        \"\"\" Constructor\n        Args:\n            num_classes: number of classes\n        \"\"\"\n        super(Xception, self).__init__()\n        self.num_classes = num_classes\n        self.conv1 = nn.Conv2d(3, 32, 3,2, 0, bias=False)\n        self.bn1 = nn.BatchNorm2d(32)\n        self.relu = nn.ReLU(inplace=True)\n        self.conv2 = nn.Conv2d(32,64,3,bias=False)\n        self.bn2 = nn.BatchNorm2d(64)\n        #do relu here\n        self.block1=Block(64,128,2,2,start_with_relu=False,grow_first=True)\n        self.block2=Block(128,256,2,2,start_with_relu=True,grow_first=True)\n        self.block3=Block(256,728,2,2,start_with_relu=True,grow_first=True)\n\n        self.block4=Block(728,728,3,1,start_with_relu=True,grow_first=True)\n        self.block5=Block(728,728,3,1,start_with_relu=True,grow_first=True)\n        self.block6=Block(728,728,3,1,start_with_relu=True,grow_first=True)\n        self.block7=Block(728,728,3,1,start_with_relu=True,grow_first=True)\n\n        self.block8=Block(728,728,3,1,start_with_relu=True,grow_first=True)\n        self.block9=Block(728,728,3,1,start_with_relu=True,grow_first=True)\n        self.block10=Block(728,728,3,1,start_with_relu=True,grow_first=True)\n        self.block11=Block(728,728,3,1,start_with_relu=True,grow_first=True)\n\n        self.block12=Block(728,1024,2,2,start_with_relu=True,grow_first=False)\n\n        self.conv3 = SeparableConv2d(1024,1536,3,1,1)\n        self.bn3 = nn.BatchNorm2d(1536)\n\n        #do relu here\n        self.conv4 = SeparableConv2d(1536,2048,3,1,1)\n        self.bn4 = nn.BatchNorm2d(2048)\n        self.fc = nn.Linear(2048, num_classes)\n    def features(self, input):\n        x = self.conv1(input)\n        x = self.bn1(x)\n        x = self.relu(x)\n        x = self.conv2(x)\n        x = self.bn2(x)\n        x = self.relu(x)\n        x = self.block1(x)\n        x = self.block2(x)\n        x = self.block3(x)\n        x = self.block4(x)\n        x = self.block5(x)\n        x = self.block6(x)\n        x = self.block7(x)\n        x = self.block8(x)\n        x = self.block9(x)\n        x = self.block10(x)\n        x = self.block11(x)\n        x = self.block12(x)\n\n        x = self.conv3(x)\n        x = self.bn3(x)\n        x = self.relu(x)\n\n        x = self.conv4(x)\n        x = self.bn4(x)\n        return x\n\n    def logits(self, features):\n        x = self.relu(features)\n\n        x = F.adaptive_avg_pool2d(x, (1, 1))\n        x = x.view(x.size(0), -1)\n        x = self.last_linear(x)\n        return x\n\n    def forward(self, input):\n        x = self.features(input)\n        x = self.logits(x)\n        return x\n\n\ndef xception(num_classes=1000, pretrained='imagenet'):\n    model = Xception(num_classes=num_classes)\n    if pretrained is not None:\n        settings = pretrained_settings['xception'][pretrained]\n        assert num_classes == settings['num_classes'], \\\n            \"num_classes should be {}, but is {}\".format(settings['num_classes'], num_classes)\n#         model = Xception(num_classes=num_classes)\n        model.load_state_dict(model_zoo.load_url(settings['url']))\n        model.input_space = settings['input_space']\n        model.input_size = settings['input_size']\n        model.input_range = settings['input_range']\n        model.mean = settings['mean']\n        model.std = settings['std']\n    # TODO: ugly\n    model.last_linear = model.fc\n    del model.fc\n    return model\nclass Xception_base(nn.Module):\n    \"\"\"\n    Xception optimized for the ImageNet dataset, as specified in\n    https://arxiv.org/pdf/1610.02357.pdf\n    \"\"\"\n    def __init__(self, num_classes=1000, pretrained='imagenet'):\n        \"\"\" Constructor\n        Args:\n            num_classes: number of classes\n        \"\"\"\n        super(Xception_base, self).__init__()\n        if not pretrained is None:\n            base_num_classes = 1000\n            self.base_model = Xception(num_classes = base_num_classes)\n            settings = pretrained_settings['xception'][pretrained]\n            assert base_num_classes == settings['num_classes'], \\\n                \"num_classes should be {}, but is {}\".format(settings['num_classes'], num_classes)\n    #         model = Xception(num_classes=num_classes)\n            self.base_model.load_state_dict(model_zoo.load_url(settings['url']))\n            self.base_model.input_space = settings['input_space']\n            self.base_model.input_size = settings['input_size']\n            self.base_model.input_range = settings['input_range']\n            self.base_model.mean = settings['mean']\n            self.base_model.std = settings['std']\n        else:\n            self.base_model = Xception(num_classes=num_classes)\n        del self.base_model.fc #= SeparableConv2d(2048,1536,3,1,1)\n        self.conv5 = SeparableConv2d(2048, 3072,3,1,1)\n        self.bn5 = nn.BatchNorm2d(3072)\n        self.conv6 = SeparableConv2d(3072, 5120,3,1,1)\n        self.bn6 = nn.BatchNorm2d(5120)\n        self.last_linear = nn.Linear(5120, num_classes)\n#         self.fc = nn.Linear(2048, num_classes)\n    def forward(self, input):\n        x = self.base_model.features(input)\n        x = self.base_model.relu(x)\n        x = self.conv5(x)\n        x = self.bn5(x)\n        x = self.base_model.relu(x)\n        x = self.conv6(x)\n        x = self.bn6(x)\n        x = self.base_model.relu(x)\n        x = F.adaptive_avg_pool2d(x, (1, 1))\n        x = x.view(x.size(0), -1)\n        x = self.last_linear(x)\n        return x","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"5cf93496790a29ae1a1216b8b4cfa058bac1543f"},"cell_type":"code","source":"ls ../input","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"e8bb266e6b806b43d1bc7e0308dc3c75291c5227"},"cell_type":"code","source":"model = Xception_base(5005, pretrained = None)\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"a7755b4730cdfef20e2e28c18e1d1b21cb010123"},"cell_type":"code","source":"model.cuda();","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"99783b5807b8a8d68f9380a5ad089f5ed6d06f29"},"cell_type":"code","source":"def save_checkpoint(state, is_best, fpath='checkpoint_5005.pth'):\n    torch.save(state, fpath)\n    if is_best:\n        torch.save(state, 'best_model.pth')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"cc485dbfb03f61c128424e7add4c2e487b4ea9bb"},"cell_type":"code","source":"criterion = nn.BCEWithLogitsLoss()\noptimizer = optim.Adam(model.parameters(), lr=0.0005)\ntry:\n    checkpoint = torch.load('../input/cpoint/checkpoint_5005.pth')\n    model.load_state_dict(checkpoint['state_dict'])\n    start = checkpoint['epoch']\n    print(start)\n    optimizer.load_state_dict(checkpoint['optimizer'])\nexcept:\n    start = 0\nscheduler = lr_scheduler.StepLR(optimizer, step_size=3, gamma=0.1)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"4f3c57883af3f42ff55d5a34ef1c7dcf5001dbcd"},"cell_type":"code","source":"def cuda(x):\n    return x.cuda(non_blocking=True) if torch.cuda.is_available() else x\ndef eval_(output, target, maxk):\n    e_batch_size = target.size(0)\n    _, pred = output.topk(maxk, 1, True, True)\n#     pred = pred.t()\n    correct = pred.eq(target.topk(1, 1, True, True)[1].expand_as(pred))\n    res = []\n    k = maxk\n    correct_k = correct[:k].view(-1).float().sum(0)\n    total = correct_k.item()\n    res.append(correct_k.mul_(100.0 / batch_size))\n    return res, total","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"23aa1fbfacbf9c73bb3651dd70de1c78c4b21408"},"cell_type":"code","source":"n_epochs = start + 6\ncur_epoch = 0\nsum_true_pred = 0\nfor epoch in range(start, n_epochs):\n    train_loss = []\n    cur_epoch = epoch\n    save_checkpoint({'epoch': epoch,\n                     'state_dict': model.state_dict(),\n                     'optimizer' : optimizer.state_dict(),\n                    }, False)\n    for batch_i, (data, target) in tqdm_notebook(enumerate(train_loader), total = len(train_loader)):\n        data, target = cuda(data), cuda(target)\n\n        optimizer.zero_grad()\n        output = model(data)\n        \n        loss = criterion(output, target.float())\n        with open('results.txt', 'a') as file:\n            file.write(str(loss.item())+'\\n')\n        sum_true_pred+=(eval_(output, target, 5))[1]\n        train_loss.append(loss.item())\n        loss.backward()\n        optimizer.step()\n    scheduler.step()\n    \n    print(f'Epoch {epoch}, train loss: {np.mean(train_loss):.4f}')\nsave_checkpoint({'epoch': cur_epoch,\n         'state_dict': model.state_dict(),\n         'optimizer' : optimizer.state_dict(),\n        }, False)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true,"_uuid":"e3f5def101bccf9065e00c83f0597a96437bfb81"},"cell_type":"code","source":"sub = pd.read_csv('../input/humpback-whale-identification/sample_submission.csv')\nmodel.eval()\nfor (data, target, name) in tqdm_notebook(test_loader):\n    data = cuda(data)\n    output = model(data)\n    output = output.cpu().detach().numpy()\n    for i, (e, n) in enumerate(list(zip(output, name))):\n        sub.loc[sub['Image'] == n, 'Id'] = ' '.join(lab_encoder.inverse_transform(e.argsort()[-5:][::-1]))\nsub.to_csv('submission.csv', index=False)","execution_count":null,"outputs":[]}],"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"name":"python","version":"3.6.6","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"}},"nbformat":4,"nbformat_minor":1}