{"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_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"TRAIN_FD = \n\nUMODEL = '' # 'unet', 'fpn', 'link'\nBACKBONE = 'efficientnetb' # efficientnetbX, resnet34/50, resnext50, seresnet34, seresnext50\n\n\nVER = ''\nSEED = \n\nMSKS_PATH_ = ''             \nIMGS_PATH_ = ''\n           ","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"!pip install ../input/keras-applications/Keras_Applications-1.0.8/ -f ./ --no-index\n!pip install ../input/image-classifiers/image_classifiers-1.0.0/ -f ./ --no-index\n!pip install ../input/efficientnet-1-0-0/efficientnet-1.0.0/ -f ./ --no-index\n!pip install ../input/segmentation-models/segmentation_models-1.0.1/ -f ./ --no-index","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"%env SM_FRAMEWORK=tf.keras","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import warnings\nwarnings.filterwarnings('ignore')\nimport os\nimport gc\nimport cv2\nimport json\nimport time\nimport random\nimport numpy as np\nimport pandas as pd \nimport tifffile as tiff\nimport tensorflow as tf\nimport tensorflow_addons as tfa\nimport matplotlib.pyplot as plt\nimport albumentations as albu\nfrom sklearn.model_selection import train_test_split, KFold, GroupKFold\nimport tensorflow.keras.backend as K\nfrom tensorflow.keras import Model, Sequential\nfrom tensorflow.keras.models import load_model\nfrom tensorflow.keras.utils import Sequence\nfrom tensorflow.keras.losses import binary_crossentropy\nfrom tensorflow.keras.layers import *\nfrom tensorflow.keras.optimizers import Adam, SGD\nfrom tensorflow.keras.callbacks import *\nimport segmentation_models as sm\nfrom segmentation_models import Unet, FPN, Linknet\nfrom segmentation_models.losses import bce_jaccard_loss\nprint('tensorflow version:', tf.__version__)\ngpu_devices = tf.config.experimental.list_physical_devices('GPU')\nif gpu_devices:\n    for gpu_device in gpu_devices:\n        print('device available:', gpu_device)\n#policy = tf.keras.mixed_precision.experimental.Policy('mixed_float16')\n#tf.keras.mixed_precision.experimental.set_policy(policy)\npd.set_option('display.max_columns', None)","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# VER = 'v46'\nPARAMS = {\n    'version': VER,\n    'folds': 5,\n    'img_size': 256,\n    'resize': 4,\n    'batch_size': 24,\n    'epochs': 1000,\n    'patience': 20,\n    'decay': False,\n    'backbone': BACKBONE, # efficientnetbX, resnet34/50, resnext50, seresnet34, seresnext50\n    'bce_weight': 1,\n    'loss': 'bce_jaccard_loss', # bce_jaccard_loss bce_dice\n    'seed': SEED,\n    'split': 'kfold', # 'kfold', 'group' or 'twos'\n    'mirror': False,\n    'aughard': True,\n    'umodel': UMODEL, # 'unet', 'fpn', 'link'\n    'pseudo': False,\n    'lr': .0005,\n    'shift': True,\n    'external': 'ext', # 'None' or 'ext' otherwise\n    'comments': 'new data'\n}\nDATA_PATH = '../input/hubmap-kidney-segmentation'\nresize = PARAMS['resize']\nsize = PARAMS['img_size']\next = PARAMS['external']\npseudo = PARAMS['pseudo']\nif PARAMS['shift']:\n    if ext:\n        if pseudo: \n            IMGS_PATH = f'./tiles_r{resize}_s{size}_shft_{ext}_{pseudo}/'\n            MSKS_PATH = f'./masks_r{resize}_s{size}_shft_{ext}_{pseudo}/'\n        else:\n            IMGS_PATH = f'./tiles_r{resize}_s{size}_shft_{ext}/'\n            MSKS_PATH = f'./masks_r{resize}_s{size}_shft_{ext}/'\n    else:\n        if pseudo: \n            IMGS_PATH = f'./tiles_r{resize}_s{size}_shft_{pseudo}/'\n            MSKS_PATH = f'./masks_r{resize}_s{size}_shft_{pseudo}/'\n        else:\n            IMGS_PATH = f'./tiles_r{resize}_s{size}_shft/'\n            MSKS_PATH = f'./masks_r{resize}_s{size}_shft/'\nelse:\n    if ext:\n        if pseudo: \n            IMGS_PATH = f'./tiles_r{resize}_s{size}_{ext}_{pseudo}/'\n            MSKS_PATH = f'./masks_r{resize}_s{size}_{ext}_{pseudo}/'\n        else:\n            IMGS_PATH = f'./tiles_r{resize}_s{size}_{ext}/'\n            MSKS_PATH = f'./masks_r{resize}_s{size}_{ext}/'\n    else:\n        if pseudo: \n            IMGS_PATH = f'./tiles_r{resize}_s{size}_{pseudo}/'\n            MSKS_PATH = f'./masks_r{resize}_s{size}_{pseudo}/'\n        else:\n            IMGS_PATH = f'./tiles_r{resize}_s{size}/'\n            MSKS_PATH = f'./masks_r{resize}_s{size}/'\n\n            \nIMGS_PATH = IMGS_PATH_\nMSKS_PATH = MSKS_PATH_            \n            \nMDLS_PATH = f'./models_{VER}'\nif not os.path.exists(MDLS_PATH):\n    os.mkdir(MDLS_PATH)\nwith open(f'{MDLS_PATH}/params.json', 'w') as file:\n    json.dump(PARAMS, file)\nif not PARAMS['mirror']:\n    os.environ['CUDA_VISIBLE_DEVICES'] = '1'\n    STRATEGY = tf.distribute.get_strategy() \nelse:\n    STRATEGY = tf.distribute.MirroredStrategy()\n    \ndef seed_all(seed):\n    random.seed(seed)\n    os.environ['PYTHONHASHSEED'] = str(seed)\n    np.random.seed(seed)\n    tf.random.set_seed(seed)\n\nseed_all(PARAMS['seed'])\nstart_time = time.time()\n# pd.read_csv('results.csv', sep='\\t', index_col=0)","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"df_masks = pd.read_csv(f'{DATA_PATH}/train.csv').set_index('id')\ndf_masks","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Utils","metadata":{}},{"cell_type":"code","source":"def enc2mask(encs, shape):\n    img = np.zeros(shape[0] * shape[1], dtype=np.uint8)\n    for m, enc in enumerate(encs):\n        if isinstance(enc, np.float) and np.isnan(enc): continue\n        s = enc.split()\n        for i in range(len(s) // 2):\n            start = int(s[2 * i]) - 1\n            length = int(s[2 * i + 1])\n            img[start : start + length] = 1 + m\n    return img.reshape(shape).T\n\ndef show_img_n_mask(df, img_num, resize):\n    img = tiff.imread(os.path.join(f'{DATA_PATH}/train', df.index[img_num] + '.tiff'))\n    if len(img.shape) == 5: img = np.transpose(img.squeeze(), (1, 2, 0))\n    mask = enc2mask(df.iloc[img_num], (img.shape[1], img.shape[0]))\n    print(img.shape, mask.shape)\n    img = cv2.resize(img,\n                     (img.shape[1] // resize, img.shape[0] // resize),\n                     interpolation=cv2.INTER_AREA)\n    mask = cv2.resize(mask,\n                      (mask.shape[1] // resize, mask.shape[0] // resize),\n                      interpolation=cv2.INTER_NEAREST)\n    plt.figure(figsize=(8, 8))\n    plt.axis('off')\n    plt.imshow(img)\n    plt.imshow(mask, alpha=.4)\n    plt.show()","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# show_img_n_mask(df=df_masks, img_num=4, resize=PARAMS['resize'])","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"if PARAMS['aughard']:\n    aug = albu.Compose([\n        albu.OneOf([\n            albu.RandomBrightness(limit=.2, p=1), \n            albu.RandomContrast(limit=.2, p=1), \n            albu.RandomGamma(p=1)\n        ], p=.5),\n        albu.OneOf([\n            albu.Blur(blur_limit=3, p=1),\n            albu.MedianBlur(blur_limit=3, p=1)\n        ], p=.25),\n        albu.OneOf([\n            albu.GaussNoise(0.002, p=.5),\n            albu.IAAAffine(p=.5),\n        ], p=.25),\n        albu.RandomRotate90(p=.5),\n        albu.HorizontalFlip(p=.5),\n        albu.VerticalFlip(p=.5),\n        albu.Cutout(num_holes=10, \n                    max_h_size=int(.1 * size), max_w_size=int(.1 * size), \n                    p=.25),\n        albu.ShiftScaleRotate(p=.25)\n    ])\nelse:\n    aug = albu.Compose([\n        albu.OneOf([\n            albu.RandomBrightness(limit=.2, p=1), \n            albu.RandomContrast(limit=.2, p=1), \n            albu.RandomGamma(p=1)\n        ], p=.5),\n        albu.RandomRotate90(p=.25),\n        albu.HorizontalFlip(p=.25),\n        albu.VerticalFlip(p=.25)\n    ])","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"class DataGenKid(Sequence):\n    \n    def __init__(self, imgs_path, msks_path, imgs_idxs, img_size,\n                 batch_size=32, mode='fit', shuffle=False, \n                 aug=None, resize=None):\n        self.imgs_path = imgs_path\n        self.msks_path = msks_path\n        self.imgs_idxs = imgs_idxs\n        self.img_size = img_size\n        self.batch_size = batch_size\n        self.mode = mode\n        self.shuffle = shuffle\n        self.aug = aug\n        self.resize = resize\n        self.on_epoch_end()\n        \n    def __len__(self):\n        return int(np.floor(len(self.imgs_idxs) / self.batch_size))\n    \n    def on_epoch_end(self):\n        self.indexes = np.arange(len(self.imgs_idxs))\n        if self.shuffle:\n            np.random.shuffle(self.indexes)\n            \n    def __getitem__(self, index):\n        batch_size = min(self.batch_size, len(self.imgs_idxs) - index*self.batch_size)\n        X = np.zeros((batch_size, self.img_size, self.img_size, 3), dtype=np.float32)\n        imgs_batch = self.imgs_idxs[index * self.batch_size : (index+1) * self.batch_size]\n        if self.mode == 'fit':\n            y = np.zeros((batch_size, self.img_size, self.img_size), dtype=np.float32)\n            for i, img_idx in enumerate(imgs_batch):\n                X[i, ], y[i] = self.get_tile(img_idx)\n            return X, y\n        elif self.mode == 'predict':\n            for i, img_idx in enumerate(imgs_batch):\n                X[i, ] = self.get_tile(img_idx)\n            return X\n        else:\n            raise AttributeError('fit mode parameter error')\n            \n    def get_tile(self, img_idx):\n        img_path = f'{self.imgs_path}/{img_idx}.png'\n        img = cv2.imread(img_path)\n        if img is None:\n            print('error load image:', img_path)\n        if self.resize:\n            img = cv2.resize(img, (int(img.shape[1] / self.resize), int(img.shape[0] / self.resize)))\n        img = img.astype(np.float32) / 255\n        if self.mode == 'fit':\n            msk_path = f'{self.msks_path}/{img_idx}.png'\n            msk = cv2.imread(msk_path, cv2.IMREAD_GRAYSCALE)\n            if msk is None:\n                print('error load mask:', msk_path)\n            if self.resize:\n                msk = cv2.resize(msk, (int(msk.shape[1] / self.resize), int(msk.shape[0] / self.resize)))\n            msk = msk.astype(np.float32)\n            if self.aug:\n                augmented = self.aug(image=img, mask=msk)\n                img = augmented['image']\n                msk = augmented['mask']\n            return img, msk\n        else:\n            if self.aug:\n                img = self.aug(image=img)['image']\n            return img","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"imgs_idxs = [x.replace('.png', '') for x in os.listdir(IMGS_PATH) if '.png' in x]\ntrain_datagen = DataGenKid(\n        imgs_path=IMGS_PATH, \n        msks_path=MSKS_PATH, \n        imgs_idxs=imgs_idxs, \n        img_size=PARAMS['img_size'], \n        batch_size=PARAMS['batch_size'], \n        mode='fit', \n        shuffle=True,           \n        aug=aug, \n        resize=None\n)\nval_datagen = DataGenKid(\n    imgs_path=IMGS_PATH, \n    msks_path=MSKS_PATH, \n    imgs_idxs=imgs_idxs, \n    img_size=PARAMS['img_size'], \n    batch_size=PARAMS['batch_size'], \n    mode='fit', \n    shuffle=False,           \n    aug=None, \n    resize=None\n)","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"bsize = min(8, PARAMS['batch_size'])\nXt, yt = train_datagen.__getitem__(6)\nprint('test X: ', Xt.shape)\nprint('test y: ', yt.shape)\nfig, axes = plt.subplots(figsize=(16, 4), nrows=2, ncols=bsize)\nfor j in range(bsize):\n    axes[0, j].imshow(Xt[j])\n    axes[0, j].set_title(j)\n    axes[0, j].axis('off')\n    axes[1, j].imshow(yt[j])\n    axes[1, j].axis('off')\nplt.show()","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"bsize = min(8, PARAMS['batch_size'])\nXt, yt = val_datagen.__getitem__(5)\nprint('test X: ', Xt.shape)\nprint('test y: ', yt.shape)\nfig, axes = plt.subplots(figsize=(16, 4), nrows=2, ncols=bsize)\nfor j in range(bsize):\n    axes[0, j].imshow(Xt[j])\n    axes[0, j].set_title(j)\n    axes[0, j].axis('off')\n    axes[1, j].imshow(yt[j])\n    axes[1, j].axis('off')\nplt.show()","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def dice_coef(y_true, y_pred, smooth=1):\n    y_true_f = K.flatten(y_true)\n    y_pred_f = K.flatten(y_pred)\n    intersection = K.sum(y_true_f * y_pred_f)\n    return (2 * intersection + smooth) / (K.sum(y_true_f) + K.sum(y_pred_f) + smooth)\n\ndef dice_loss(y_true, y_pred, smooth=1):\n    return (1 - dice_coef(y_true, y_pred, smooth))\n\ndef bce_dice_loss(y_true, y_pred):\n    return PARAMS['bce_weight'] * binary_crossentropy(y_true, y_pred) + \\\n        (1 - PARAMS['bce_weight']) * dice_loss(y_true, y_pred)\n\ndef get_model(backbone, input_shape, loss_type='bce_dice', \n              umodel='unet', classes=1, lr=.001):\n    with STRATEGY.scope():\n        if loss_type == 'bce_dice': \n            loss = bce_dice_loss\n        elif loss_type == 'bce_jaccard_loss':\n            loss = bce_jaccard_loss\n        else:\n            raise AttributeError('loss mode parameter error')\n        if umodel == 'unet':\n            model = Unet(backbone_name=backbone, encoder_weights='imagenet',\n                         input_shape=input_shape,\n                         classes=classes, activation='sigmoid')\n        elif umodel == 'fpn':\n            model = FPN(backbone_name=backbone, encoder_weights='imagenet',\n                        input_shape=input_shape,\n                        classes=classes, activation='sigmoid')\n        elif umodel == 'link':\n            model = Linknet(backbone_name=backbone, encoder_weights='imagenet',\n                            input_shape=input_shape,\n                            classes=classes, activation='sigmoid')\n        else:\n            raise AttributeError('umodel mode parameter error')\n        model.compile(\n            optimizer=tfa.optimizers.Lookahead(\n                tf.keras.optimizers.Adam(learning_rate=lr),\n                sync_period=max(6, int(PARAMS['patience'] / 4))\n            ),\n            loss=loss, \n            metrics=[dice_coef]\n        )\n    return model","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def get_lr_callback(batch_size=10, epochs=100, warmup=5, plot=False):\n    lr_start = 1e-5\n    lr_max = 1e-3\n    lr_min = lr_start / 100\n    lr_ramp_ep = warmup\n    lr_sus_ep = 0\n    lr_decay = .95\n    \n    def lr_scheduler(epoch):\n        if epoch < lr_ramp_ep:\n            lr = (lr_max - lr_start) / lr_ramp_ep * epoch + lr_start\n        elif epoch < lr_ramp_ep + lr_sus_ep:\n            lr = lr_max\n        else:\n            lr = (lr_max - lr_min) * lr_decay ** (epoch - lr_ramp_ep - lr_sus_ep) + lr_min\n        return lr\n        \n    if not plot:\n        lr_callback = tf.keras.callbacks.LearningRateScheduler(lr_scheduler, verbose=False)\n        return lr_callback \n    else: \n        return lr_scheduler\n    \nif PARAMS['decay']:\n    lr_scheduler_plot = get_lr_callback(\n        batch_size=PARAMS['batch_size'], \n        epochs=PARAMS['epochs'], \n        plot=True\n    )\n    xs = [i for i in range(PARAMS['epochs'])]\n    y = [lr_scheduler_plot(x) for x in xs]\n    plt.plot(xs, y)\n    plt.title(f'lr schedule from {y[0]:.5f} to {max(y):.3f} to {y[-1]:.8f}')\n    plt.show()","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Train","metadata":{}},{"cell_type":"code","source":"def train_model(mparams, n_fold, train_datagen, val_datagen):\n    model = get_model(\n        mparams['backbone'], \n        input_shape=(mparams['img_size'], mparams['img_size'], 3),\n        loss_type=mparams['loss'],\n        umodel=mparams['umodel'],\n        lr=mparams['lr']\n    )\n    checkpoint_path = f'{MDLS_PATH}/model_{n_fold}.hdf5'\n    earlystopper = EarlyStopping(\n        monitor='val_dice_coef', \n        patience=mparams['patience'], \n        verbose=0,\n        restore_best_weights=True,\n        mode='max'\n    )\n    lrreducer = ReduceLROnPlateau(\n        monitor='val_dice_coef', \n        factor=.1, \n        patience=int(mparams['patience'] / 2), \n        verbose=0, \n        min_lr=1e-7,\n        mode='max'\n    )\n    checkpointer = ModelCheckpoint(\n        checkpoint_path, \n        monitor='val_dice_coef', \n        verbose=0, \n        save_best_only=True,\n        save_weights_only=True, \n        mode='max'\n    )\n    callbacks = [earlystopper, checkpointer]\n    if mparams['decay']:\n        callbacks.append(get_lr_callback(mparams['batch_size']))\n        print('lr warmup and decay')\n    else:\n        callbacks.append(lrreducer)\n        print('lr reduce on plateau')\n    history = model.fit(\n        train_datagen,\n        validation_data=val_datagen,\n        callbacks=callbacks,\n        epochs=mparams['epochs'],\n        verbose=1\n    )\n    history_file = f'{MDLS_PATH}/history_{n_fold}.json'\n    dict_to_save = {}\n    for k, v in history.history.items():\n        dict_to_save.update({k: [np.format_float_positional(x) for x in history.history[k]]})\n    with open(history_file, 'w') as file:\n        json.dump(dict_to_save, file)\n    model.load_weights(checkpoint_path)\n    return model, history","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"for iname in list(set([x[:9] for x in imgs_idxs])):\n    print('img name:', iname, \n          '| imgs number:', len([x for x in imgs_idxs if x[:9] == iname]))\nif PARAMS['split'] == 'kfold':\n    kfold = KFold(n_splits=PARAMS['folds'],     \n                  random_state=PARAMS['seed'],\n                  shuffle=True).split(imgs_idxs)\nelif PARAMS['split'] == 'group':\n    grps = [x[:9] for x in imgs_idxs]\n    kfold = GroupKFold(n_splits=PARAMS['folds']).split(imgs_idxs, imgs_idxs, grps)\nelif PARAMS['split'] == 'twos':\n    grps = []\n    for x in imgs_idxs:\n        if x[:9] in ['e79de561c', '1e2425f28']: grps.append(0)\n        elif x[:9] in ['cb2d976f4', 'aaa6a05cc']: grps.append(1)\n        elif x[:9] in ['095bf7a1f', '54f2eec69']: grps.append(2)\n        else: grps.append(3)\n    kfold = GroupKFold(n_splits=PARAMS['folds']).split(imgs_idxs, imgs_idxs, grps)\nelse:\n    raise AttributeError('split mode parameter error')","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"epoch_by_folds = []\nloss_by_folds = []\ndice_coef_by_folds = []\n        \nfor n, (tr, te) in enumerate(kfold):\n    if n==TRAIN_FD:\n        print('=' * 10, f'FOLD {n}', '=' * 10)\n        X_tr = [imgs_idxs[i] for i in tr]; X_val = [imgs_idxs[i] for i in te]\n        print('train:', len(X_tr), '| test:', len(X_val))\n        print('groups train:', set([x[:9] for x in X_tr]), \n              '\\ngroups test:', set([x[:9] for x in X_val]))\n        train_datagen = DataGenKid(\n            imgs_path=IMGS_PATH, \n            msks_path=MSKS_PATH, \n            imgs_idxs=X_tr, \n            img_size=PARAMS['img_size'], \n            batch_size=PARAMS['batch_size'], \n            mode='fit', \n            shuffle=True,           \n            aug=aug, \n            resize=None\n        )\n        val_datagen = DataGenKid(\n            imgs_path=IMGS_PATH, \n            msks_path=MSKS_PATH, \n            imgs_idxs=X_val, \n            img_size=PARAMS['img_size'], \n            batch_size=PARAMS['batch_size'], \n            mode='fit', \n            shuffle=False,           \n            aug=None, \n            resize=None\n        )\n        model, history = train_model(PARAMS, n, train_datagen, val_datagen)\n        plt.plot(history.history['loss'], label='loss')\n        plt.plot(history.history['val_loss'], label='val_loss')\n        plt.legend()\n        plt.show()\n        plt.plot(history.history['dice_coef'], label='dice_coef')\n        plt.plot(history.history['val_dice_coef'], label='val_dice_coef')\n        plt.legend()\n        plt.show()\n        best_epoch = np.argmax(history.history['val_dice_coef'])\n        best_loss = history.history['val_loss'][best_epoch]\n        best_dice_coef = history.history['val_dice_coef'][best_epoch]\n        print('best epoch:', best_epoch, \n              '| best loss:', best_loss,\n              '| best dice coef:', best_dice_coef)\n        epoch_by_folds.append(best_epoch)\n        loss_by_folds.append(best_loss)\n        dice_coef_by_folds.append(best_dice_coef)\n        del train_datagen, val_datagen, model; gc.collect()\n    \nelapsed_time = time.time() - start_time\nprint(f'time elapsed: {elapsed_time // 60:.0f} min {elapsed_time % 60:.0f} sec')","metadata":{"scrolled":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"result = PARAMS.copy()\nresult['bavg_epoch'] = np.mean(epoch_by_folds)\nresult['bavg_loss'] = np.mean(loss_by_folds)\nresult['bavg_dice_coef'] = np.mean(dice_coef_by_folds)\nresult['dice_by_folds'] = ' '.join([f'{x:.4f}' for x in dice_coef_by_folds])\nwith open(f'{MDLS_PATH}/params.json', 'w') as file:\n    json.dump(result, file)\nif not os.path.exists('results.csv'):\n    df_save = pd.DataFrame(result, index=[0])\n    df_save.to_csv('results.csv', sep='\\t')\nelse:\n    df_old = pd.read_csv('results.csv', sep='\\t', index_col=0)\n    df_save = pd.DataFrame(result, index=[df_old.index.max() + 1])\n    df_save = df_old.append(df_save, ignore_index=True)\n    df_save.to_csv('results.csv', sep='\\t')","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"pd.read_csv('results.csv', sep='\\t', index_col=0)","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Predict","metadata":{}}]}