{"cells":[{"metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"cell_type":"code","source":"# This Python 3 environment comes with many helpful analytics libraries installed\n# It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# For example, here's several helpful packages to load\n\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\n# Input data files are available in the read-only \"../input/\" directory\n# For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\nimport os\nfor dirname, _, filenames in os.walk('/kaggle/input'):\n    for filename in filenames:\n        print(os.path.join(dirname, filename))\n\n# You can write up to 5GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"!pip install -q efficientnet_pytorch\nfrom efficientnet_pytorch import EfficientNet","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"import torch\nimport random\nfrom skimage import io\nfrom torch import nn\nfrom torch.utils.data import Dataset,DataLoader\nfrom torch.utils.data.sampler import SequentialSampler, RandomSampler\nimport sklearn\n\n\nSEED = 42\n\ndef 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","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"from albumentations import (\n    HorizontalFlip,Rotate,  RandomRotate90, VerticalFlip,\n   Normalize,ToFloat, Compose\n)\n\nfrom albumentations.pytorch import ToTensor","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"import pandas as pd\nimport os\nimport matplotlib.pyplot as plt\nimport cv2\nfrom torch.utils.data import Dataset\nimport gc\nimport torch.nn.functional as F\nfrom tqdm import tqdm\nfrom sklearn import metrics\nimport torchvision","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"dir_img='../input/alaska2-image-steganalysis'","execution_count":null,"outputs":[]},{"metadata":{"_uuid":"d629ff2d2480ee46fbb7e2d37f6b5fab8052498a","_cell_guid":"79c7e3d0-c299-4dcb-8224-4455121ee9b0","trusted":true},"cell_type":"code","source":"\ntrain_img_ids = pd.Series(os.listdir(dir_img + '/Cover')).sort_values(ascending=True).reset_index(drop=True)\ntest_img_ids = pd.Series(os.listdir(dir_img + '/Test')).sort_values(ascending=True).reset_index(drop=True)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"cover_img_path = pd.Series(dir_img + '/Cover/' + train_img_ids ).sort_values(ascending=True)\nJMIPOD_img_path = pd.Series(dir_img + '/JMiPOD/'+train_img_ids).sort_values(ascending=True)\nJUNIWARD_img_path = pd.Series(dir_img + '/JUNIWARD/'+train_img_ids).sort_values(ascending=True)\nUERD_img_path = pd.Series(dir_img + '/UERD/'+train_img_ids).sort_values(ascending=True)\ntest_img_path = pd.Series(dir_img + '/Test/'+test_img_ids).sort_values(ascending=True)\nss = pd.read_csv(f'{dir_img}/sample_submission.csv')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"f, axs = plt.subplots(nrows=2, ncols=2, figsize=(30, 20))\nk=0\nfor i, row in enumerate(axs):\n    for j, col in enumerate(row):\n        img = cv2.imread(cover_img_path[k])\n        col.imshow(img)\n        col.set_title(cover_img_path[k])\n        k=k+1\nplt.suptitle('Samples from Cover Images', fontsize=14)\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"fig, axs = plt.subplots(nrows=3, ncols=4, figsize=(30, 20))\nfor i in range(3):\n   \n    cv_img = cv2.imread(cover_img_path[i])\n    uni_img = cv2.imread(JUNIWARD_img_path[i])\n    jpod_img = cv2.imread(JMIPOD_img_path[i])\n    uerd_img = cv2.imread(UERD_img_path[i])\n    \n    axs[i,0].imshow(cv_img)\n    axs[i,0].set_title('Cover_IMG'+train_img_ids[i])\n    axs[i,1].imshow(uni_img)\n    axs[i,1].set_title('JNIWARD_IMG'+train_img_ids[i])\n    axs[i,2].imshow(jpod_img)\n    axs[i,2].set_title('JMiPOD_IMG'+train_img_ids[i])\n    axs[i,3].imshow(uerd_img)\n    axs[i,3].set_title('UERD_IMG'+train_img_ids[i])","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"! git clone https://github.com/dwgoon/jpegio","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"!pip install jpegio/.\nimport jpegio as jio","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"import numpy as np\ncoverDCT = np.zeros([512,512,3])\nstego_juni_DCT = np.zeros([512,512,3])\nstego_uerd_DCT = np.zeros([512,512,3])\nstego_jmpd_DCT = np.zeros([512,512,3])\njpeg = jio.read(cover_img_path[1])\nstego_juniward = jio.read(JUNIWARD_img_path[1])\nstego_uerd = jio.read(UERD_img_path[1])\nstego_jmpd = jio.read(JMIPOD_img_path[1])","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"#Getting values from corresponding channels\ncoverDCT[:,:,0] = jpeg.coef_arrays[0] ; coverDCT[:,:,1] = jpeg.coef_arrays[1] ; coverDCT[:,:,2] = jpeg.coef_arrays[2]\nstego_juni_DCT[:,:,0] = stego_juniward.coef_arrays[0] ; stego_juni_DCT[:,:,1] = stego_juniward.coef_arrays[1] ; stego_juni_DCT[:,:,2] = stego_juniward.coef_arrays[2]\nstego_uerd_DCT[:,:,0] = stego_uerd.coef_arrays[0] ; stego_uerd_DCT[:,:,1] = stego_uerd.coef_arrays[1] ; stego_uerd_DCT[:,:,2] = stego_uerd.coef_arrays[2]\nstego_jmpd_DCT[:,:,0] = stego_jmpd.coef_arrays[0] ; stego_jmpd_DCT[:,:,1] = stego_jmpd.coef_arrays[1] ; stego_jmpd_DCT[:,:,2] = stego_jmpd.coef_arrays[2]\n\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"DCT_diff1 = coverDCT - stego_juni_DCT\nDCT_diff2 = coverDCT - stego_uerd_DCT\nDCT_diff3 = coverDCT - stego_jmpd_DCT\n# So since they are not the same Images the DCT_diff would not be zero\nfig, axs = plt.subplots(nrows=1, ncols=3, figsize=(20, 20))\nprint(len(DCT_diff1[np.where(DCT_diff1!=0)]))\nprint(np.unique(DCT_diff1))\nplt.subplot(131)\nplt.title('juniward difference')\nplt.imshow( abs(DCT_diff1) )\nprint(len(DCT_diff2[np.where(DCT_diff2!=0)]))\nprint(np.unique(DCT_diff2))\nplt.subplot(132)\nplt.title('uerd difference')\nplt.imshow( abs(DCT_diff2) )\nprint(len(DCT_diff3[np.where(DCT_diff3!=0)]))\nprint(np.unique(DCT_diff3))\nplt.subplot(133)\nplt.title('jmipod difference')\nplt.imshow( abs(DCT_diff3) )\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"so there is a really visible difference between the images","execution_count":null},{"metadata":{},"cell_type":"markdown","source":"*taken from the Author of competition's notbook itself Reni*","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"#This code extract YCbCr channels from a jpeg object\ndef JPEGdecompressYCbCr(jpegStruct):\n    \n    nb_colors=len(jpegStruct.coef_arrays)\n        \n    [Col,Row] = np.meshgrid( range(8) , range(8) )\n    T = 0.5 * np.cos(np.pi * (2*Col + 1) * Row / (2 * 8))\n    T[0,:] = T[0,:] / np.sqrt(2)\n    \n    sz = np.array(jpegStruct.coef_arrays[0].shape)\n    \n    imDecompressYCbCr = np.zeros([sz[0], sz[1], nb_colors]);\n    szDct = (sz/8).astype('int')\n    \n    \n    \n    for ColorChannel in range(nb_colors):\n        tmpPixels = np.zeros(sz)\n    \n        DCTcoefs = jpegStruct.coef_arrays[ColorChannel];\n        if ColorChannel==0:\n            QM = jpegStruct.quant_tables[ColorChannel];\n        else:\n            QM = jpegStruct.quant_tables[1];\n        \n        for idxRow in range(szDct[0]):\n            for idxCol in range(szDct[1]):\n                D = DCTcoefs[idxRow*8:(idxRow+1)*8 , idxCol*8:(idxCol+1)*8]\n                tmpPixels[idxRow*8:(idxRow+1)*8 , idxCol*8:(idxCol+1)*8] = np.dot( np.transpose(T) , np.dot( QM * D , T ) )\n        imDecompressYCbCr[:,:,ColorChannel] = tmpPixels;\n    return imDecompressYCbCr\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"coverDCT = np.zeros([512,512,3])\nstego_juni_DCT = np.zeros([512,512,3])\nstego_uerd_DCT = np.zeros([512,512,3])\nstego_jmpd_DCT = np.zeros([512,512,3])\njpeg = jio.read(cover_img_path[1])\nstego_juniward = jio.read(JUNIWARD_img_path[1])\nstego_uerd = jio.read(UERD_img_path[1])\nstego_jmpd = jio.read(JMIPOD_img_path[1])","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"Y_cover= JPEGdecompressYCbCr(jpeg)\nY_juniward=JPEGdecompressYCbCr(stego_juniward)\nY_uerd=JPEGdecompressYCbCr(stego_uerd)\nY_jmpd=JPEGdecompressYCbCr(stego_jmpd)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"diff1 = Y_cover - Y_juniward\ndiff2 = Y_cover - Y_uerd\ndiff3 = Y_cover - Y_jmpd\n# So since they are not the same Images the diff would not be zero\nfig, axs = plt.subplots(nrows=1, ncols=3, figsize=(20, 20))\nprint(len(diff1[np.where(diff1!=0)]))\nprint(np.unique(diff1))\nplt.subplot(131)\nplt.title('juniward difference')\nplt.imshow( abs(diff1[:,:,0]) ,cmap='gray')\nprint(len(diff2[np.where(DCT_diff2!=0)]))\nprint(np.unique(diff2))\nplt.subplot(132)\nplt.title('uerd difference')\nplt.imshow( abs(diff2[:,:,1]) ,cmap='gray')\nprint(len(diff3[np.where(diff3!=0)]))\nprint(np.unique(diff3))\nplt.subplot(133)\nplt.title('jmipod difference')\nplt.imshow( abs(diff3[:,:,2]) ,cmap='gray')\nplt.show()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"diff4= Y_cover-Y_cover\nprint(len(diff4[np.where(diff4!=0)]))\nprint(np.unique(diff4))\nplt.figure(figsize=(10,10))\nplt.imshow( abs(diff4) )","execution_count":null,"outputs":[]},{"metadata":{},"cell_type":"markdown","source":"from above we see that , there is definitely a huge difference between the YCbCr channels of the stego images from that of the cover images","execution_count":null},{"metadata":{"trusted":true},"cell_type":"code","source":"folder_names = ['JMiPOD/', 'JUNIWARD/', 'UERD/']\nclass_names = ['Normal', 'JMiPOD_75', 'JMiPOD_90', 'JMiPOD_95', \n               'JUNIWARD_75', 'JUNIWARD_90', 'JUNIWARD_95',\n                'UERD_75', 'UERD_90', 'UERD_95']\nclass_labels = { name: i for i, name in enumerate(class_names)}","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"train_df = pd.read_csv('../input/add-data/alaska2_train_df.csv')\nval_df = pd.read_csv('../input/add-data/alaska2_val_df.csv')\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"class Alaska2Dataset(Dataset):\n\n    def __init__(self, df, augmentations=None):\n\n        self.data = df\n        self.augment = augmentations\n\n    def __len__(self):\n        return len(self.data)\n\n    def __getitem__(self, idx):\n        fn, label = self.data.loc[idx]\n        im = cv2.imread(fn)[:, :, ::-1]\n        if self.augment:\n            # Apply transformations\n            im = self.augment(image=im)\n        return im, label\n\nimg_size = 512\nAUGMENTATIONS_TRAIN = Compose([\n    VerticalFlip(p=0.5),\n    HorizontalFlip(p=0.5),\n    RandomRotate90(p=0.5),\n    Rotate(limit=20, interpolation=0, border_mode=0, value=None, mask_value=None, always_apply=False, p=0.5),\n    \n    ToFloat(max_value=255),\n    ToTensor()\n], p=0.8)\n\n\nAUGMENTATIONS_TEST = Compose([\n    ToFloat(max_value=255),\n    ToTensor()\n], p=1)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"temp_df = train_df.sample(64).reset_index(drop=True)\ntrain_dataset = Alaska2Dataset(temp_df, augmentations=AUGMENTATIONS_TRAIN)\nbatch_size = 64\nnum_workers = 0\n\ntemp_loader = torch.utils.data.DataLoader(train_dataset,\n                                          batch_size=batch_size,\n                                          num_workers=num_workers, shuffle=False)\n\n\nimages, labels = next(iter(temp_loader))\nimages = images['image'].permute(0, 2, 3, 1)\nmax_images = 64\ngrid_width = 16\ngrid_height = int(max_images / grid_width)\nfig, axs = plt.subplots(grid_height, grid_width,\n                        figsize=(grid_width+1, grid_height+2))\n\nfor i, (im, label) in enumerate(zip(images, labels)):\n    ax = axs[int(i / grid_width), i % grid_width]\n    ax.imshow(im.squeeze())\n    ax.set_title(str(label.item()))\n    ax.axis('off')\n\nplt.suptitle(\"0: Cover, 1: JMiPOD_75, 2: JMiPOD_90, 3: JMiPOD_95, 4: JUNIWARD_75, 5:JUNIWARD_90,\\n 6: JUNIWARD_95, 7:UERD_75, 8:UERD_90, 9:UERD_95\")\nplt.show()\ndel images, temp_df\ngc.collect()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"class Net(nn.Module):\n    def __init__(self, num_classes):\n        super().__init__()\n        self.model = EfficientNet.from_pretrained('efficientnet-b0')\n        # 1280 is the number of neurons in last layer. is diff for diff. architecture\n        self.dense_output = nn.Linear(1280, num_classes)\n\n    def forward(self, x):\n        feat = self.model.extract_features(x)\n        feat = F.avg_pool2d(feat, feat.size()[2:]).reshape(-1, 1280)\n        return self.dense_output(feat)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"batch_size = 8\nnum_workers = 8\n\ntrain_dataset = Alaska2Dataset(train_df, augmentations=AUGMENTATIONS_TRAIN)\nvalid_dataset = Alaska2Dataset(val_df.sample(1000).reset_index(drop=True), augmentations=AUGMENTATIONS_TEST) #for faster validation sample\n\ntrain_loader = torch.utils.data.DataLoader(train_dataset,\n                                           batch_size=batch_size,\n                                           num_workers=num_workers,\n                                           shuffle=True)\n\nvalid_loader = torch.utils.data.DataLoader(valid_dataset,\n                                           batch_size=batch_size*2,\n                                           num_workers=num_workers,\n                                           shuffle=False)\n\ndevice = 'cuda'\nmodel = Net(num_classes=len(class_labels)).to(device)\n# pretrained model in my pc. now i will train on all images for 2 epochs\nmodel.load_state_dict(torch.load('../input/new-data/val_loss_6.08_auc_0.875.pth'))\noptimizer = torch.optim.AdamW(model.parameters(), lr=1e-6)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# https://www.kaggle.com/anokas/weighted-auc-metric-updated\n\ndef alaska_weighted_auc(y_true, y_valid):\n    tpr_thresholds = [0.0, 0.4, 1.0]\n    weights = [2,   1]\n\n    fpr, tpr, thresholds = metrics.roc_curve(y_true, y_valid, pos_label=1)\n\n    # size of subsets\n    areas = np.array(tpr_thresholds[1:]) - np.array(tpr_thresholds[:-1])\n\n    # The total area is normalized by the sum of weights such that the final weighted AUC is between 0 and 1.\n    normalization = np.dot(areas, weights)\n\n    competition_metric = 0\n    for idx, weight in enumerate(weights):\n        y_min = tpr_thresholds[idx]\n        y_max = tpr_thresholds[idx + 1]\n        mask = (y_min < tpr) & (tpr < y_max)\n        # pdb.set_trace()\n\n        x_padding = np.linspace(fpr[mask][-1], 1, 100)\n\n        x = np.concatenate([fpr[mask], x_padding])\n        y = np.concatenate([tpr[mask], [y_max] * len(x_padding)])\n        y = y - y_min  # normalize such that curve starts at y=0\n        score = metrics.auc(x, y)\n        submetric = score * weight\n        best_subscore = (y_max - y_min) * weight\n        competition_metric += submetric\n\n    return competition_metric / normalization","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"criterion = torch.nn.CrossEntropyLoss()\nnum_epochs = 2\ntrain_loss, val_loss = [], []\n\nfor epoch in range(num_epochs):\n    print('Epoch {}/{}'.format(epoch, num_epochs - 1))\n    print('-' * 10)\n    model.train()\n    running_loss = 0\n    tk0 = tqdm(train_loader, total=int(len(train_loader)))\n    for im, labels in tk0:\n        inputs = im[\"image\"].to(device, dtype=torch.float)\n        labels = labels.to(device, dtype=torch.long)\n        optimizer.zero_grad()\n        outputs = model(inputs)\n        loss = criterion(outputs, labels)\n        loss.backward()\n        optimizer.step()\n        running_loss += loss.item()\n        tk0.set_postfix(loss=(loss.item()))\n\n    epoch_loss = running_loss / (len(train_loader)/batch_size)\n    train_loss.append(epoch_loss)\n    print('Training Loss: {:.8f}'.format(epoch_loss))\n\n    tk1 = tqdm(valid_loader, total=int(len(valid_loader)))\n    model.eval()\n    running_loss = 0\n    y, preds = [], []\n    with torch.no_grad():\n        for (im, labels) in tk1:\n            inputs = im[\"image\"].to(device, dtype=torch.float)\n            labels = labels.to(device, dtype=torch.long)\n            outputs = model(inputs)\n            loss = criterion(outputs, labels)\n            y.extend(labels.cpu().numpy().astype(int))\n            preds.extend(F.softmax(outputs, 1).cpu().numpy())\n            running_loss += loss.item()\n            tk1.set_postfix(loss=(loss.item()))\n\n        epoch_loss = running_loss / (len(valid_loader)/batch_size)\n        val_loss.append(epoch_loss)\n        preds = np.array(preds)\n        # convert multiclass labels to binary class\n        y = np.array(y)\n        labels = preds.argmax(1)\n        for class_label in np.unique(y):\n            idx = y == class_label\n            acc = (labels[idx] == y[idx]).astype(np.float).mean()*100\n            print('accuracy for class', class_names[class_label], 'is', acc)\n        \n        acc = (labels == y).mean()*100\n        new_preds = np.zeros((len(preds),))\n        temp = preds[labels != 0, 1:]\n        new_preds[labels != 0] = temp.sum(1)\n        new_preds[labels == 0] = 1 - preds[labels == 0, 0]\n        y = np.array(y)\n        y[y != 0] = 1\n        auc_score = alaska_weighted_auc(y, new_preds)\n        print(`\n            f'Val Loss: {epoch_loss:.3}, Weighted AUC:{auc_score:.3}, Acc: {acc:.3}')\n\n    torch.save(model.state_dict(),\n               f\"epoch_{epoch}_val_loss_{epoch_loss:.3}_auc_{auc_score:.3}.pth\")","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"import glob\nclass Alaska2TestDataset(Dataset):\n\n    def __init__(self, df, augmentations=None):\n\n        self.data = df\n        self.augment = augmentations\n\n    def __len__(self):\n        return len(self.data)\n\n    def __getitem__(self, idx):\n        fn = self.data.loc[idx][0]\n        im = cv2.imread(fn)[:, :, ::-1]\n\n        if self.augment:\n            # Apply transformations\n            im = self.augment(image=im)\n\n        return im\n\n\ntest_filenames = test_img_path\ntest_df = pd.DataFrame({'ImageFileName': list(\n    test_filenames)}, columns=['ImageFileName'])\n\n\n\nbatch_size = 16\nnum_workers = 4\ntest_dataset = Alaska2TestDataset(test_df, augmentations=AUGMENTATIONS_TEST)\ntest_loader = torch.utils.data.DataLoader(test_dataset,\n                                          batch_size=batch_size,\n                                          num_workers=num_workers,\n                                          shuffle=False,\n                                          drop_last=False)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"model.eval()\n\npreds = []\ntk0 = tqdm(test_loader)\n\nwith torch.no_grad():\n    for i, im in enumerate(tk0):\n        inputs = im[\"image\"].to(device)\n        # flip vertical\n        im = inputs.flip(2)\n        outputs = model(im)\n        # fliplr\n        im = inputs.flip(3)\n        outputs = (0.25*outputs + 0.25*model(im))\n        outputs = (outputs + 0.5*model(inputs))        \n        preds.extend(F.softmax(outputs, 1).cpu().numpy())\n\npreds = np.array(preds)\nlabels = preds.argmax(1)\nnew_preds = np.zeros((len(preds),))\nnew_preds[labels != 0] = preds[labels != 0, 1:].sum(1)\nnew_preds[labels == 0] = 1 - preds[labels == 0, 0]\n\ntest_df['Id'] = test_df['ImageFileName'].apply(lambda x: x.split(os.sep)[-1])\ntest_df['Label'] = new_preds\n\ntest_df = test_df.drop('ImageFileName', axis=1)\ntest_df.to_csv('submission_eb0.csv', index=False)\nprint(test_df.head())","execution_count":null,"outputs":[]},{"metadata":{"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}