{"cells":[{"metadata":{},"cell_type":"markdown","source":"The code below selects 16 128x128 tiles for each image and mask based on the maximum number of tissue pixels. The kernel also provides computed image stats. Please check my kernels to see how to use this data. \n![](https://i.ibb.co/RzSWP56/convert.png)","execution_count":null},{"metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true},"cell_type":"code","source":"###lafossのモデル　nakayaさんのものをほぼ使う。","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"class CFG:\n    debug=False\n    #height=256\n    #width=256\n    lr=1e-4\n    batch_size=16\n    epochs=1 # you can train more epochs\n    seed=777\n    target_size=1\n    target_col='isup_grade'\n    n_fold=4","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"import os\nimport numpy as np \nimport pandas as pd\nos.listdir('../input/prostate-cancer-grade-assessment')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"train = pd.read_csv('../input/prostate-cancer-grade-assessment/train.csv')\ntest = pd.read_csv('../input/prostate-cancer-grade-assessment/test.csv')\nsample = pd.read_csv('../input/prostate-cancer-grade-assessment/sample_submission.csv')","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"train.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"test.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"sample.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"train['isup_grade'].hist()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# ====================================================\n# Library\n# ====================================================\n\nimport sys\n\nimport gc\nimport os\nimport random\nimport time\nfrom contextlib import contextmanager\nfrom pathlib import Path\nfrom collections import defaultdict, Counter\n\nimport skimage.io\nimport cv2\nfrom PIL import Image\nimport numpy as np\nimport pandas as pd\nimport scipy as sp\n\nimport sklearn.metrics\nfrom sklearn.metrics import accuracy_score\nfrom sklearn.model_selection import StratifiedKFold\n\nfrom functools import partial\nfrom tqdm import tqdm\n\nimport torch\nimport torch.nn as nn\nfrom torch.optim import Adam, SGD\nfrom torch.optim.lr_scheduler import CosineAnnealingLR, ReduceLROnPlateau\nfrom torch.utils.data import DataLoader, Dataset\nimport torchvision.models as models\n\nfrom albumentations import Compose, Normalize, HorizontalFlip, VerticalFlip,RandomGamma, RandomRotate90,GaussNoise\nfrom albumentations.pytorch import ToTensorV2\n\"\"\"\nimport warnings \nwarnings.filterwarnings('ignore')\"\"\"\n\n\ndevice = torch.device('cuda' if torch.cuda.is_available() else 'cpu')\ndevice","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# ====================================================\n# Utils\n# ====================================================\n\n@contextmanager\ndef timer(name):\n    t0 = time.time()\n    LOGGER.info(f'[{name}] start')\n    yield\n    LOGGER.info(f'[{name}] done in {time.time() - t0:.0f} s.')\n\n    \ndef init_logger(log_file='train.log'):\n    from logging import getLogger, DEBUG, FileHandler,  Formatter,  StreamHandler\n    \n    log_format = '%(asctime)s %(levelname)s %(message)s'\n    \n    stream_handler = StreamHandler()\n    stream_handler.setLevel(DEBUG)\n    stream_handler.setFormatter(Formatter(log_format))\n    \n    file_handler = FileHandler(log_file)\n    file_handler.setFormatter(Formatter(log_format))\n    \n    logger = getLogger('PANDA')\n    logger.setLevel(DEBUG)\n    logger.addHandler(stream_handler)\n    logger.addHandler(file_handler)\n    \n    return logger\n\nLOG_FILE = 'train.log'\nLOGGER = init_logger(LOG_FILE)\n\n#再現性の確保\ndef seed_torch(seed=42):\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\nseed_torch(seed=42)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def tile(img, sz=120, N=16):\n    shape = img.shape\n    pad0,pad1 = (sz - shape[0]%sz)%sz, (sz - shape[1]%sz)%sz\n    img = np.pad(img,[[pad0//2,pad0-pad0//2],[pad1//2,pad1-pad1//2],[0,0]],\n                 constant_values=255)\n    img = img.reshape(img.shape[0]//sz,sz,img.shape[1]//sz,sz,3)\n    img = img.transpose(0,2,1,3,4).reshape(-1,sz,sz,3)\n    if len(img) < N:\n        img = np.pad(img,[[0,N\n                           -len(img)],[0,0],[0,0],[0,0]],constant_values=255)\n    idxs = np.argsort(img.reshape(img.shape[0],-1).sum(-1))[:N]\n    img = img[idxs]\n    return img#[N,size,size,3]\n\nclass TrainDataset_lafoss(Dataset):\n    def __init__(self, df, labels, transform1=None,tensor=True):\n        self.df = df\n        self.labels = labels\n        self.transform = transform1\n        self.tensor = tensor\n        \n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n        file_name = self.df['image_id'].values[idx]\n        file_path = f'../input/prostate-cancer-grade-assessment/train_images/{file_name}.tiff'\n        images = skimage.io.MultiImage(file_path)[2]\n        images = tile(images)\n        if self.transform:\n            images = [cv2.cvtColor(self.transform(image=img)['image'], cv2.COLOR_BGR2RGB) for img in images]\n        else:\n            images = [cv2.cvtColor(img, cv2.COLOR_BGR2RGB) for img in images]\n        #ここまでは、ndarray\n        images = np.stack(images, 0)\n        if self.tensor:\n            images = torch.from_numpy(images.transpose((0,3,1,2)))\n\n        \n            \n        label = torch.tensor(self.labels[idx]).float()\n        \n        return images, label\n    \nclass TestDataset_lafoss(Dataset):\n    def __init__(self, df, dir_name, transform1=None,tensor =True):\n        self.df = df\n        self.dir_name = dir_name\n        self.transform = transform1\n        self.tensor = tensor#bool\n        \n    def __len__(self):\n        return len(self.df)\n\n    def __getitem__(self, idx):\n        file_name = self.df['image_id'].values[idx]\n        file_path = f'../input/prostate-cancer-grade-assessment/{self.dir_name}/{file_name}.tiff'\n        image = skimage.io.MultiImage(file_path)[2]\n        images = tile(image)\n        \n        if self.transform:\n            images = [cv2.cvtColor(self.transform(image=img)['image'], cv2.COLOR_BGR2RGB) for img in images]\n        else:\n            images = [cv2.cvtColor(img, cv2.COLOR_BGR2RGB) for img in images]\n        images = np.stack(images, 0)#bs,n,h,w,c\n        \n        if self.tensor:\n            images = torch.from_numpy(images.transpose((0,3,1,2)))\n        return images","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def get_transforms1(*, data):\n\n    #train,valid以外だったら怒る\n    \n    if data == 'train':\n        return Compose([\n            HorizontalFlip(p=0.5),\n            VerticalFlip(p=0.5),\n            #GaussNoise(p=0.5),\n            #RandomAugMix(severity=3, width=3, alpha=1., p=0.2),\n            #GridMask(num_grid=3, p=0.2),\n            Normalize(\n                mean=[0.485, 0.456, 0.406],\n                std=[0.229, 0.224, 0.225],\n            )\n        ])\n    \n    elif data == 'valid':\n        return Compose([\n            Normalize(\n                mean=[0.485, 0.456, 0.406],\n                std=[0.229, 0.224, 0.225],\n            )\n        ])","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"import matplotlib.pyplot as plt\n\n\ntrain_dataset = TrainDataset_lafoss(train, train[CFG.target_col], transform1=get_transforms1(data='train'),tensor=False)# train[CFG.target_col]は0~5\ntrain_loader = DataLoader(train_dataset, batch_size=1, shuffle=False)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"%%time\n\nfor img, label in train_loader:\n    for j in range(img.shape[1]):\n        plt.imshow(img[0][j])\n\n        plt.show()\n    break","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"if CFG.debug:\n    folds = train.sample(n=200, random_state=CFG.seed).reset_index(drop=True).copy()\nelse:\n    folds = train.copy()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"train_labels = folds[CFG.target_col].values\nkf = StratifiedKFold(n_splits=CFG.n_fold, shuffle=True, random_state=CFG.seed)\nfor fold, (train_index, val_index) in enumerate(kf.split(folds.values, train_labels)):\n    folds.loc[val_index, 'fold'] = int(fold)\nfolds['fold'] = folds['fold'].astype(int)\nfolds.to_csv('folds.csv', index=None)\nfolds.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"import sys\nsys.path.insert(0, '/kaggle/input/pytorch-efnet-ns/')\nimport geffnet","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"import torch\nimport torch.nn as nn\n#!pip install efficientnet_pytorch\n#!pip install geffnet\n#from efficientnet_pytorch import EfficientNet\nclass Model(nn.Module):\n    def __init__(self,n=1):\n        super().__init__()\n        m = geffnet.efficientnet_b0(pretrained=False)\n        self.enc = nn.Sequential(*list(m.children())[:-3])    \n        nc = list(m.children())[-1].in_features\n        self.head = nn.Sequential(nn.AdaptiveAvgPool2d(1),nn.Flatten(),nn.Linear(nc,512),\n                            nn.ReLU(),nn.BatchNorm1d(512), nn.Dropout(0.5),nn.Linear(512,n))\n    def forward(self,x):\n        shape = x.size()\n        n = shape[1]\n        x = x.view(-1,shape[2],shape[3],shape[4])\n          #print(x.size())##orch.Size([160, 3, 128, 128])\n        x = self.enc(x)\n          #print(\"finish_enc\",x.size())\n        shape = x.shape#torch.Size([160, 1280, 4, 4])\n          #concatenate the output for tiles into a single map\n        x = x.view(-1,n,shape[1],shape[2],shape[3]).permute(0,2,1,3,4).contiguous().view(-1,shape[1],shape[2]*n,shape[3])\n          #print(\"to_head\",x.size())#torch.Size([10, 1280, 64, 4])\n        x = self.head(x)\n        return x\n    \ndef fix_model_state_dict(state_dict):\n    from collections import OrderedDict\n    new_state_dict = OrderedDict()\n    for k, v in state_dict.items():\n        name = k\n        if name.startswith(\"enc.*.*.*.\"):\n            name = name[10:]  # remove 'model.' of dataparallel\n        elif name.startswith('head.*.'):\n            name = name[7:]\n        new_state_dict[name] = v\n    return new_state_dict","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"weights_path = \"/kaggle/input/panda-efnetb2-180-weight/fold0_efnet_2020-09-05-114528.pth\"\nmodel = Model()\nstate_dict = torch.load(weights_path,map_location=device)\nmodel.load_state_dict(state_dict)\nprint(model)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"from sklearn.metrics import cohen_kappa_score\n\ndef quadratic_weighted_kappa(y_hat, y):\n    return cohen_kappa_score(y_hat, y, weights='quadratic')\n\n#回帰に対して適切な閾値を決めて分類クラスを返す流れ。\nclass OptimizedRounder():\n    def __init__(self):\n        self.coef_ = 0\n\n    def _kappa_loss(self, coef, X, y):\n        X_p = np.copy(X)\n        for i, pred in enumerate(X_p):\n            if pred < coef[0]:\n                X_p[i] = 0\n            elif pred >= coef[0] and pred < coef[1]:\n                X_p[i] = 1\n            elif pred >= coef[1] and pred < coef[2]:\n                X_p[i] = 2\n            elif pred >= coef[2] and pred < coef[3]:\n                X_p[i] = 3\n            elif pred >= coef[3] and pred < coef[4]:\n                X_p[i] = 4\n            else:\n                X_p[i] = 5\n\n        ll = quadratic_weighted_kappa(y, X_p)\n        return -ll\n\n    def fit(self, X, y):\n        loss_partial = partial(self._kappa_loss, X=X, y=y)#self._kappa_lossの引数は3つだが、coefを固定するもの\n        initial_coef = [0.5, 1.5, 2.5, 3.5, 4.5]\n        self.coef_ = sp.optimize.minimize(loss_partial, initial_coef, method='nelder-mead')#第一引数にある関数を最適化するように第二引数のパラメータを調整。この場合は回帰→分類のための閾値\n    def predict(self, X, coef):\n        X_p = np.copy(X)\n        for i, pred in enumerate(X_p):\n            if pred < coef[0]:\n                X_p[i] = 0\n            elif pred >= coef[0] and pred < coef[1]:\n                X_p[i] = 1\n            elif pred >= coef[1] and pred < coef[2]:\n                X_p[i] = 2\n            elif pred >= coef[2] and pred < coef[3]:\n                X_p[i] = 3\n            elif pred >= coef[3] and pred < coef[4]:\n                X_p[i] = 4\n            else:\n                X_p[i] = 5\n        return X_p\n\n    def coefficients(self):\n        return self.coef_['x']","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"Ng = 6\ndef Kloss(x, target,df):\n    y_shift = df.isup_grade.mean()\n    x = Ng*torch.sigmoid(x.float()).view(-1) - 0.5\n    target = target.float()\n    return 1.0 - (2.0*((x-y_shift)*(target-y_shift)).sum() - 1e-3)/\\\n        (((x-y_shift)**2).sum() + ((target-y_shift)**2).sum() + 1e-3)","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def train_fn(fold):\n    print(f\"### fold: {fold} ###\")\n    \n    optimized_rounder = OptimizedRounder()\n        \n    trn_idx = folds[folds['fold'] != fold].index\n    val_idx = folds[folds['fold'] == fold].index\n    \n    #タイルごとに拡張を入れる\n    train_dataset = TrainDataset_lafoss(folds.loc[trn_idx].reset_index(drop=True), \n                                 folds.loc[trn_idx].reset_index(drop=True)[CFG.target_col], \n                                 transform1=get_transforms1(data='train'),tensor=True)\n    valid_dataset = TrainDataset_lafoss(folds.loc[val_idx].reset_index(drop=True), \n                                 folds.loc[val_idx].reset_index(drop=True)[CFG.target_col], \n                                 transform1=get_transforms1(data='valid'),tensor=True)\n    \n    \n    train_loader = DataLoader(train_dataset, batch_size=CFG.batch_size, shuffle=True, num_workers=4)\n    valid_loader = DataLoader(valid_dataset, batch_size=CFG.batch_size, shuffle=False, num_workers=4)\n    \n    model = Model()\n    weights_path = \"/kaggle/input/panda-efnetb2-180-weight/fold{}_efnet_2020-09-05-114528.pth\".format(fold)\n    state_dict = torch.load(weights_path,map_location=device)\n    model.load_state_dict(state_dict)\n    model.to(device)\n    \n    optimizer = Adam(model.parameters(), lr=CFG.lr, amsgrad=False)\n    scheduler = ReduceLROnPlateau(optimizer, 'min', factor=0.5, patience=2, verbose=True, eps=1e-6)\n    \n    criterion = nn.MSELoss()#分類の時はnn.CrossEntropyLoss()\n    #criterion = nn.CrossEntropyLoss()\n    best_score = -100\n    best_loss = np.inf\n    best_preds = None\n    \n    \n    for epoch in range(CFG.epochs):\n        \n        start_time = time.time()\n\n        model.train()\n        avg_loss = 0.\n\n        optimizer.zero_grad()\n        tk0 = tqdm(enumerate(train_loader), total=len(train_loader))\n\n        for i, (images, labels) in tk0:\n\n            images = images.to(device)\n            labels = labels.to(device)\n            \n            y_preds = model(images.float())\n            #loss = criterion(y_preds.view(-1), labels)\n            loss = Kloss(y_preds.view(-1), labels,folds.loc[trn_idx].reset_index(drop=True))\n            \n            loss.backward()\n            optimizer.step()\n            optimizer.zero_grad()\n\n            avg_loss += loss.item() / len(train_loader)\n            \n        model.eval()\n        avg_val_loss = 0.\n        preds = []\n        valid_labels = []\n        tk1 = tqdm(enumerate(valid_loader), total=len(valid_loader))\n\n        for i, (images, labels) in tk1:\n            \n            images = images.to(device)\n            labels = labels.to(device)\n            \n            with torch.no_grad():\n                y_preds = model(images.float())\n            \n            \n            valid_labels.append(labels.to('cpu').numpy())\n\n            #loss = criterion(y_preds.view(-1), labels)\n            loss = Kloss(y_preds.view(-1), labels,folds.loc[val_idx].reset_index(drop=True))\n            y_preds = 6*torch.sigmoid(y_preds.float()).view(-1) - 0.5\n            #print(\"valid_preds\",y_preds.size())\n            preds.append(y_preds.to('cpu').numpy())\n            \n            avg_val_loss += loss.item() / len(valid_loader)\n        \n        scheduler.step(avg_val_loss)\n            \n        preds = np.concatenate(preds)\n        #print(\"preds\",preds.shape)\n        valid_labels = np.concatenate(valid_labels)\n        #回帰の値を分類にする流れ\n        \n        optimized_rounder.fit(preds, valid_labels)\n        coefficients = optimized_rounder.coefficients()\n        final_preds = optimized_rounder.predict(preds, coefficients)\n        #print(\"final_preds\",final_preds.shape)\n        LOGGER.debug(f'Counter preds: {Counter(final_preds)}')#np.concatenate(final_preds)\n        LOGGER.debug(f'coefficients: {coefficients}')\n        score = quadratic_weighted_kappa(valid_labels, final_preds)\n        #score = quadratic_weighted_kappa(valid_labels, preds)\n\n        elapsed = time.time() - start_time#loggerのためのもの\n        \n        LOGGER.debug(f'  Epoch {epoch+1} - avg_train_loss: {avg_loss:.4f}  avg_val_loss: {avg_val_loss:.4f}  time: {elapsed:.0f}s')\n        LOGGER.debug(f'  Epoch {epoch+1} - QWK: {score}  coefficients: {coefficients}')\n        #LOGGER.debug(f'  Epoch {epoch+1} - QWK: {score}')\n        \n        if score>best_score:#QWKのスコアが良かったら予測値を更新...best_epochをきめるため\n            best_score = score\n            best_preds = preds\n            LOGGER.debug(f'  Epoch {epoch+1} - Save Best Score: {best_score:.4f}')\n            LOGGER.debug(f'  Epoch {epoch+1} - Save Best Score: {best_score:.4f} Model  coefficients: {coefficients}')\n            torch.save(model.state_dict(), f'fold{fold}_efnet_b2_ns.pth')#各epochのモデルを保存。。。best_epoch終了時のモデルを推論に使用する？\n    \n    return best_preds, valid_labels,model\n    #return preds, valid_labels","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"\"\"\"\npreds = []\nvalid_labels = []\nmodels = []\nfor fold in range(CFG.n_fold):\n    _preds, _valid_labels,_model = train_fn(fold)\n    preds.append(_preds)\n    valid_labels.append(_valid_labels)\"\"\"","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"\"\"\"\npreds = np.concatenate(preds)\nvalid_labels = np.concatenate(valid_labels)\n\noptimized_rounder = OptimizedRounder()\noptimized_rounder.fit(preds, valid_labels)\ncoefficients = optimized_rounder.coefficients()#どうする？\nfinal_preds = optimized_rounder.predict(preds, coefficients)\nLOGGER.debug(f'Counter preds: {Counter(final_preds)}')#np.concatenate()\nLOGGER.debug(f'coefficients: {coefficients}')\n\nscore = quadratic_weighted_kappa(valid_labels, final_preds)\nLOGGER.debug(f'CV QWK: {score}')\"\"\"","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def inference(model, test_loader, device):\n    \n    model.to(device) \n    \n    probs = []\n\n    for i, images in tqdm(enumerate(test_loader), total=len(test_loader)):\n            \n        images = images.to(device)\n        if i==0:\n            print(images.size())\n            \n        with torch.no_grad():\n            y_preds = model(images)\n            y_preds = 6*torch.sigmoid(y_preds.float()).view(-1) - 0.5\n            \n        probs.append(y_preds.to('cpu').numpy())\n\n    probs = np.concatenate(probs)\n    \n    return probs","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"coefficients=np.array([0.5060126 ,1.50290319, 2.56765878, 3.3614414, 4.60342127])","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"def submit_l(sample, coefficients, dir_name='test_images'):\n    if os.path.exists(f'../input/prostate-cancer-grade-assessment/{dir_name}'):\n        print('run inference')\n        test_dataset = TestDataset_lafoss(sample, dir_name,get_transforms1(data='valid'),tensor = True)\n        test_loader = DataLoader(test_dataset, batch_size=CFG.batch_size, shuffle=False)\n        probs = []\n        for fold in range(CFG.n_fold):\n            weights_path = \"/kaggle/input/panda-efnetb2-180-weight/fold{}_efnet_2020-09-09-185804.pth\".format(fold)\n            model = Model()\n            state_dict = torch.load(weights_path,map_location=device)\n            model.load_state_dict(state_dict)\n            _probs = inference(model, test_loader, device)\n            probs.append(_probs)\n            #if fold ==0:break\n        optimized_rounder = OptimizedRounder()\n        probs = np.mean(probs, axis=0)\n        preds = optimized_rounder.predict(probs, coefficients)\n        sample['isup_grade'] = preds\n    return sample\n","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# check using train_images\nsubmission = submit_l(train.head(), coefficients, dir_name='train_images')\nsubmission['isup_grade'] = submission['isup_grade'].astype(int)\n#submission.to_csv('submission.csv', index=False)\n#score = quadratic_weighted_kappa(folds[\"isup_grade\"], submission['isup_grade'])\n#print(\"QWK:\",score)\nsubmission.head()","execution_count":null,"outputs":[]},{"metadata":{"trusted":true},"cell_type":"code","source":"# test submission\nsubmission = submit_l(sample, coefficients, dir_name='test_images')\nsubmission['isup_grade'] = submission['isup_grade'].astype(int)\nsubmission.to_csv('submission.csv', index=False)\nsubmission.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}