{"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":"BATCH_SIZE = 4\nEDGE_CROP = 16\nNB_EPOCHS = 5\nGAUSSIAN_NOISE = 0.1\nUPSAMPLE_MODE = 'SIMPLE'\n# downsampling inside the network\nNET_SCALING = None\n# downsampling in preprocessing\nIMG_SCALING = (1, 1)\n# number of validation images to use\nVALID_IMG_COUNT = 200\n# maximum number of steps_per_epoch in training\nMAX_TRAIN_STEPS = 100\nAUGMENT_BRIGHTNESS = False","metadata":{"_uuid":"f969cd48-bd4c-42a9-bd8a-fda8fa94f14d","_cell_guid":"8abd4da9-5908-40b6-9614-b96c58f917c7","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"!ls ../input/airbus-ship-detection","metadata":{"_uuid":"dde07b08-b633-44ce-b75b-52ae970be842","_cell_guid":"af8e923f-d699-4569-955f-41f1e0ac45c8","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import os\nimport numpy as np # linear algebra\nimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\nfrom skimage.io import imread\nimport matplotlib.pyplot as plt\nfrom skimage.segmentation import mark_boundaries\nfrom skimage.util import montage\nmontage_rgb = lambda x: np.stack([montage(x[:, :, :, i]) for i in range(x.shape[3])], -1)\nship_dir = '../input/airbus-ship-detection'\ntrain_image_dir = os.path.join(ship_dir, 'train_v2')\ntest_image_dir = os.path.join(ship_dir, 'test_v2')\nimport gc; gc.enable() # memory is tight\n\nfrom skimage.morphology import label\ndef multi_rle_encode(img):\n    labels = label(img[:, :, 0])\n    return [rle_encode(labels==k) for k in np.unique(labels[labels>0])]\n\n# ref: https://www.kaggle.com/paulorzp/run-length-encode-and-decode\ndef rle_encode(img):\n    '''\n    img: numpy array, 1 - mask, 0 - background\n    Returns run length as string formated\n    '''\n    pixels = img.T.flatten()\n    pixels = np.concatenate([[0], pixels, [0]])\n    runs = np.where(pixels[1:] != pixels[:-1])[0] + 1\n    runs[1::2] -= runs[::2]\n    return ' '.join(str(x) for x in runs)\n\ndef rle_decode(mask_rle, shape=(768, 768)):\n    '''\n    mask_rle: run-length as string formated (start length)\n    shape: (height,width) of array to return \n    Returns numpy array, 1 - mask, 0 - background\n    '''\n    s = mask_rle.split()\n    starts, lengths = [np.asarray(x, dtype=int) for x in (s[0:][::2], s[1:][::2])]\n    starts -= 1\n    ends = starts + lengths\n    img = np.zeros(shape[0]*shape[1], dtype=np.uint8)\n    for lo, hi in zip(starts, ends):\n        img[lo:hi] = 1\n    return img.reshape(shape).T  # Needed to align to RLE direction\n\ndef masks_as_image(in_mask_list):\n    # Take the individual ship masks and create a single mask array for all ships\n    all_masks = np.zeros((768, 768), dtype = np.int16)\n    #if isinstance(in_mask_list, list):\n    for mask in in_mask_list:\n        if isinstance(mask, str):\n            all_masks += rle_decode(mask)\n    return np.expand_dims(all_masks, -1)","metadata":{"_uuid":"556b34ab-a102-4298-8e16-b66a697d8a1e","_cell_guid":"da6bf38d-8802-4bac-8417-71bb2c53e599","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"masks = pd.read_csv(os.path.join('../input/airbus-ship-detection/',\n                                 'train_ship_segmentations_v2.csv'))\n\nprint(masks.shape[0], 'masks found')\nprint(masks['ImageId'].value_counts().shape[0])\nmasks.head()","metadata":{"_uuid":"626dde9a-d9db-4f23-85d7-4b2b3f715e65","_cell_guid":"44781fb7-d8a1-4ae5-911a-4da8c162ac48","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"\nrle_0 = masks.query('ImageId==\"00021ddc3.jpg\"')['EncodedPixels']\nimg_0 = masks_as_image(rle_0)\nplt.imshow(img_0[:, :, 0])\n\n","metadata":{"_uuid":"bc16e8a4-a103-46ba-b1a0-909066b3f483","_cell_guid":"c56d6490-9ca4-419d-af79-526bd7b301ae","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"rle_0.to_csv(r'rle.csv')","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"masks['ships'] = masks['EncodedPixels'].map(lambda c_row: 1 if isinstance(c_row, str) else 0)\nunique_img_ids = masks.groupby('ImageId').agg({'ships': 'sum'}).reset_index()\nunique_img_ids['has_ship'] = unique_img_ids['ships'].map(lambda x: 1.0 if x>0 else 0.0)\nunique_img_ids['has_ship_vec'] = unique_img_ids['has_ship'].map(lambda x: [x])\n# some files are too small/corrupt\nunique_img_ids['file_size_kb'] = unique_img_ids['ImageId'].map(lambda c_img_id: \n                                                               os.stat(os.path.join(train_image_dir, \n                                                                                    c_img_id)).st_size/1024)\nunique_img_ids = unique_img_ids[unique_img_ids['file_size_kb']>50] # keep only 50kb files\nunique_img_ids['file_size_kb'].hist()\nmasks.drop(['ships'], axis=1, inplace=True)\nunique_img_ids.sample(5)","metadata":{"_uuid":"e7f85a6a-8d07-4bf6-bfb5-2c12dce91618","_cell_guid":"133064fa-2775-4826-a996-8df2302c80f3","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from sklearn.model_selection import train_test_split\ntrain_ids, valid_ids = train_test_split(unique_img_ids, \n                 test_size = 0.3, \n                 stratify = unique_img_ids['ships'])\ntrain_df = pd.merge(masks, train_ids)\nvalid_df = pd.merge(masks, valid_ids)\nprint(train_df.shape[0], 'training masks')\nprint(valid_df.shape[0], 'validation masks')","metadata":{"_uuid":"9938d7e6-36ff-4e1f-934c-f93ce1d62b00","_cell_guid":"6baf5626-cfd2-416b-a0f8-e57257bf063c","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train_df['ships'].hist()","metadata":{"_uuid":"17e09d98-cd83-4614-bd93-8bb532eddfd0","_cell_guid":"a5c6d59c-587c-4df8-afdc-a8c987fbeeb5","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train_df['grouped_ship_count'] = train_df['ships'].map(lambda x: (x+1)//2).clip(0, 7)\ndef sample_ships(in_df, base_rep_val=1500):\n    if in_df['ships'].values[0]==0:\n        return in_df.sample(base_rep_val//3) # even more strongly undersample no ships\n    else:\n        return in_df.sample(base_rep_val, replace=(in_df.shape[0]<base_rep_val))\n    \nbalanced_train_df = train_df.groupby('grouped_ship_count').apply(sample_ships)\nbalanced_train_df['ships'].hist(bins=np.arange(10))","metadata":{"_uuid":"8fea9eea-d174-4ee0-92c3-1eb382d71b09","_cell_guid":"494b54d3-600d-4e43-962f-c205de433154","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"#del train_df\n#gc.collect()","metadata":{"_uuid":"7d90ace8-0907-4a3e-ac33-ce33dccc0eeb","_cell_guid":"8a595fa5-130a-486f-b297-171279e9b6d8","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def make_image_gen(in_df, batch_size = BATCH_SIZE):\n    all_batches = list(in_df.groupby('ImageId'))\n    out_rgb = []\n    out_mask = []\n    while True:\n        np.random.shuffle(all_batches)\n        for c_img_id, c_masks in all_batches:\n            rgb_path = os.path.join(train_image_dir, c_img_id)\n            c_img = imread(rgb_path)\n            c_mask = masks_as_image(c_masks['EncodedPixels'].values)\n            if IMG_SCALING is not None:\n                c_img = c_img[::IMG_SCALING[0], ::IMG_SCALING[1]]\n                c_mask = c_mask[::IMG_SCALING[0], ::IMG_SCALING[1]]\n            out_rgb += [c_img]\n            out_mask += [c_mask]\n            if len(out_rgb)>=batch_size:\n                yield np.stack(out_rgb, 0)/255.0, np.stack(out_mask, 0)\n                out_rgb, out_mask=[], []","metadata":{"_uuid":"c46282a2-2f6e-46a1-8a25-3e0f762ccefd","_cell_guid":"daec6249-68b3-4c9e-b916-52175424aea7","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"train_gen = make_image_gen(balanced_train_df)\ntrain_x, train_y = next(train_gen)\nprint('x', train_x.shape, train_x.min(), train_x.max())\nprint('y', train_y.shape, train_y.min(), train_y.max())","metadata":{"_uuid":"bff97a27-45bd-48fc-a842-a6448ed34cd8","_cell_guid":"c62e9252-e5b9-48a0-a8eb-6cc0f82fc0ec","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"fig, (ax1, ax2, ax3) = plt.subplots(1, 3, figsize = (30, 10))\nbatch_rgb = montage_rgb(train_x)\nbatch_seg = montage(train_y[:, :, :, 0])\nax1.imshow(batch_rgb)\nax1.set_title('Images')\nax2.imshow(batch_seg)\nax2.set_title('Segmentations')\nax3.imshow(mark_boundaries(batch_rgb, \n                           batch_seg.astype(int)))\nax3.set_title('Outlined Ships')\nfig.savefig('overview.png')","metadata":{"_uuid":"2b24ef28-3c85-4392-893f-4663bad96f69","_cell_guid":"ba412e04-4133-4c63-862e-70da2eadc4f3","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"valid_x, valid_y = next(make_image_gen(valid_df, VALID_IMG_COUNT))\nprint(valid_x.shape, valid_y.shape)","metadata":{"_uuid":"9a28b4a9-f5ee-412d-ac8e-9cec60cf8cfd","_cell_guid":"703ce223-0659-48fb-8d39-b16a46c7cd99","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"del train_x","metadata":{"_uuid":"bc64a61d-0926-4853-b7e4-526f48258399","_cell_guid":"89dfacdf-5b7b-4f96-8583-afaebb0b2eef","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"del train_y","metadata":{"_uuid":"45b217e1-80b3-4fe8-ad56-b44af80f41d5","_cell_guid":"cf1efc2d-14b9-41bb-874d-ef066592ddcf","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from keras.preprocessing.image import ImageDataGenerator\ndg_args = dict(featurewise_center = False, \n                  samplewise_center = False,\n                  rotation_range = 15, \n                  width_shift_range = 0.1, \n                  height_shift_range = 0.1, \n                  shear_range = 0.01,\n                  zoom_range = [0.9, 1.25],  \n                  horizontal_flip = True, \n                  vertical_flip = True,\n                  fill_mode = 'reflect',\n                   data_format = 'channels_last')\n# brightness can be problematic since it seems to change the labels differently from the images \nif AUGMENT_BRIGHTNESS:\n    dg_args[' brightness_range'] = [0.5, 1.5]\nimage_gen = ImageDataGenerator(**dg_args)\n\nif AUGMENT_BRIGHTNESS:\n    dg_args.pop('brightness_range')\nlabel_gen = ImageDataGenerator(**dg_args)\n\ndef create_aug_gen(in_gen, seed = None):\n    np.random.seed(seed if seed is not None else np.random.choice(range(9999)))\n    for in_x, in_y in in_gen:\n        seed = np.random.choice(range(9999))\n        # keep the seeds syncronized otherwise the augmentation to the images is different from the masks\n        g_x = image_gen.flow(255*in_x, \n                             batch_size = in_x.shape[0], \n                             seed = seed, \n                             shuffle=True)\n        g_y = label_gen.flow(in_y, \n                             batch_size = in_x.shape[0], \n                             seed = seed, \n                             shuffle=True)\n\n        yield next(g_x)/255.0, next(g_y)","metadata":{"_uuid":"eaef6a42-1cb0-47de-a58f-8102c65e745e","_cell_guid":"f27815e7-9fd1-4948-a6b4-78e9b2246e40","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"cur_gen = create_aug_gen(train_gen)\nt_x, t_y = next(cur_gen)\nprint('x', t_x.shape, t_x.dtype, t_x.min(), t_x.max())\nprint('y', t_y.shape, t_y.dtype, t_y.min(), t_y.max())\n# only keep first 9 samples to examine in detail\nt_x = t_x[:9]\nt_y = t_y[:9]\nfig, (ax1, ax2) = plt.subplots(1, 2, figsize = (20, 10))\nax1.imshow(montage_rgb(t_x), cmap='gray')\nax1.set_title('images')\nax2.imshow(montage(t_y[:, :, :, 0]), cmap='gray_r')\nax2.set_title('ships')","metadata":{"_uuid":"a3b39ae9-9b94-4463-b7bb-93e8956e1f10","_cell_guid":"a9f5b9b2-fe46-49d6-8356-af5fcf993946","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"\ngc.collect()","metadata":{"_uuid":"d93ee742-dcb7-402c-ac60-9946ea165a8e","_cell_guid":"3bb68958-1f9d-4b19-9322-2960c079e511","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from keras import models, layers\n# Build U-Net model\ndef upsample_conv(filters, kernel_size, strides, padding):\n    return layers.Conv2DTranspose(filters, kernel_size, strides=strides, padding=padding)\ndef upsample_simple(filters, kernel_size, strides, padding):\n    return layers.UpSampling2D(strides)\n\nif UPSAMPLE_MODE=='DECONV':\n    upsample=upsample_conv\nelse:\n    upsample=upsample_simple\n    \ninput_img = layers.Input(t_x.shape[1:], name = 'RGB_Input')\npp_in_layer = input_img\nif NET_SCALING is not None:\n    pp_in_layer = layers.AvgPool2D(NET_SCALING)(pp_in_layer)\n    \npp_in_layer = layers.GaussianNoise(GAUSSIAN_NOISE)(pp_in_layer)\npp_in_layer = layers.BatchNormalization()(pp_in_layer)\n\nc1 = layers.Conv2D(8, (3, 3), activation='relu', padding='same') (pp_in_layer)\nc1 = layers.Conv2D(8, (3, 3), activation='relu', padding='same') (c1)\np1 = layers.MaxPooling2D((2, 2)) (c1)\n\nc2 = layers.Conv2D(16, (3, 3), activation='relu', padding='same') (p1)\nc2 = layers.Conv2D(16, (3, 3), activation='relu', padding='same') (c2)\np2 = layers.MaxPooling2D((2, 2)) (c2)\n\nc3 = layers.Conv2D(32, (3, 3), activation='relu', padding='same') (p2)\nc3 = layers.Conv2D(32, (3, 3), activation='relu', padding='same') (c3)\np3 = layers.MaxPooling2D((2, 2)) (c3)\n\nc4 = layers.Conv2D(64, (3, 3), activation='relu', padding='same') (p3)\nc4 = layers.Conv2D(64, (3, 3), activation='relu', padding='same') (c4)\np4 = layers.MaxPooling2D(pool_size=(2, 2)) (c4)\n\n\nc5 = layers.Conv2D(128, (3, 3), activation='relu', padding='same') (p4)\nc5 = layers.Conv2D(128, (3, 3), activation='relu', padding='same') (c5)\n\nu6 = upsample(64, (2, 2), strides=(2, 2), padding='same') (c5)\nu6 = layers.concatenate([u6, c4])\nc6 = layers.Conv2D(64, (3, 3), activation='relu', padding='same') (u6)\nc6 = layers.Conv2D(64, (3, 3), activation='relu', padding='same') (c6)\n\nu7 = upsample(32, (2, 2), strides=(2, 2), padding='same') (c6)\nu7 = layers.concatenate([u7, c3])\nc7 = layers.Conv2D(32, (3, 3), activation='relu', padding='same') (u7)\nc7 = layers.Conv2D(32, (3, 3), activation='relu', padding='same') (c7)\n\nu8 = upsample(16, (2, 2), strides=(2, 2), padding='same') (c7)\nu8 = layers.concatenate([u8, c2])\nc8 = layers.Conv2D(16, (3, 3), activation='relu', padding='same') (u8)\nc8 = layers.Conv2D(16, (3, 3), activation='relu', padding='same') (c8)\n\nu9 = upsample(8, (2, 2), strides=(2, 2), padding='same') (c8)\nu9 = layers.concatenate([u9, c1], axis=3)\nc9 = layers.Conv2D(8, (3, 3), activation='relu', padding='same') (u9)\nc9 = layers.Conv2D(8, (3, 3), activation='relu', padding='same') (c9)\n\nd = layers.Conv2D(1, (1, 1), activation='sigmoid') (c9)\nd = layers.Cropping2D((EDGE_CROP, EDGE_CROP))(d)\nd = layers.ZeroPadding2D((EDGE_CROP, EDGE_CROP))(d)\nif NET_SCALING is not None:\n    d = layers.UpSampling2D(NET_SCALING)(d)\n\nseg_model = models.Model(inputs=[input_img], outputs=[d])\nseg_model.summary()","metadata":{"_uuid":"6f4c386f-f9bd-4e21-bd1b-d42a04766cf4","_cell_guid":"b281b105-af0a-45c0-9c7a-802b18ded110","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import keras.backend as K\nfrom keras.optimizers import Adam\nfrom keras.losses import binary_crossentropy\n\ndef dice_coef(y_true, y_pred, smooth=1):\n    intersection = K.sum(y_true * y_pred, axis=[1,2,3])\n    union = K.sum(y_true, axis=[1,2,3]) + K.sum(y_pred, axis=[1,2,3])\n    return K.mean( (2. * intersection + smooth) / (union + smooth), axis=0)\n\ndef dice_coef_loss(y_true, y_pred):\n    return -dice_coef(y_true, y_pred)\n\ndef dice_p_bce(in_gt, in_pred):\n    return 1e-3*binary_crossentropy(in_gt, in_pred) - dice_coef(in_gt, in_pred)\n\ndef true_positive_rate(y_true, y_pred):\n    return K.sum(K.flatten(y_true)*K.flatten(K.round(y_pred)))/K.sum(y_true)\n\n\n################################################\n# new functions\n################################################\n\n\ndef gdice_coef(y_true, y_pred):\n    #y_true_f = K.flatten(y_true)\n    #y_pred_f = K.flatten(y_pred)\n    A = K.sum(y_true, axis=[1,2,3])\n    B = K.sum(y_pred, axis=[1,2,3])\n    \n    TP = K.sum(y_true * y_pred, axis=[1,2,3])\n    two_TP = 2. * TP\n    \n    FN = A - TP\n    FP = B - TP\n    FP_1 = FP + (K.square(FP) / ( TP + FN + K.epsilon() ))\n    \n    G_DICE = (two_TP + K.epsilon()) / (two_TP + FN + FP_1 + K.epsilon())\n    return (G_DICE)\n\ndef gdice_coef_loss(y_true, y_pred):\n    return -gdice_coef(y_true, y_pred)\n\ndef sgdice_coef(y_true, y_pred):\n\n    y_true_f = K.flatten(y_true)\n    y_pred_f = K.flatten(y_pred)\n    A = K.sum(y_true_f, axis=[1,2,3])\n    B = K.sum(y_pred_f, axis=[1,2,3])\n    \n    TP = K.sum(y_true_f * y_pred_f, axis=[1,2,3])\n    two_TP = 2. * TP\n    \n    FN = A - TP\n    FP = B - TP\n    FP_1 = FP + (K.square(FP) / ( TP + FN - FP + K.epsilon() ))\n    \n    SG_DICE = (two_TP + K.epsilon()) / (two_TP + FN + FP_1 + K.epsilon())\n    return (SG_DICE)\n\ndef sgdice_coef_loss(y_true, y_pred):\n    return -sgdice_coef(y_true, y_pred)\n\n\n\n\n\nseg_model.compile(optimizer=Adam(1e-4, decay=1e-6), loss=gdice_coef_loss, metrics=[dice_coef, 'binary_accuracy', true_positive_rate])","metadata":{"_uuid":"298c3b14-bf64-4fac-84b5-c6aba18ed147","_cell_guid":"665a8923-e02f-45fe-a28c-92ac96a87abf","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from keras.callbacks import ModelCheckpoint, LearningRateScheduler, EarlyStopping, ReduceLROnPlateau\nweight_path=\"{}_weights.best.hdf5\".format('seg_model')\n\ncheckpoint = ModelCheckpoint(weight_path, monitor='val_dice_coef', verbose=1, \n                             save_best_only=True, mode='max', save_weights_only = True)\n\nreduceLROnPlat = ReduceLROnPlateau(monitor='val_dice_coef', factor=0.5, \n                                   patience=3, \n                                   verbose=1, mode='max', epsilon=0.0001, cooldown=2, min_lr=1e-6)\nearly = EarlyStopping(monitor=\"val_dice_coef\", \n                      mode=\"max\", \n                      patience=15) # probably needs to be more patient, but kaggle time is limited\ncallbacks_list = [checkpoint, early, reduceLROnPlat]","metadata":{"_uuid":"fee54861-7208-44fd-8381-5ee2723c180c","_cell_guid":"a591d27e-d685-4283-a47d-683431562e00","trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"step_count = min(MAX_TRAIN_STEPS, balanced_train_df.shape[0]//BATCH_SIZE)\naug_gen = create_aug_gen(make_image_gen(balanced_train_df))\nloss_history = [seg_model.fit_generator(aug_gen, \n                             steps_per_epoch=step_count, \n                             epochs=NB_EPOCHS, \n                             validation_data=(valid_x, valid_y),\n                             callbacks=callbacks_list,\n                            workers=1 # the generator is not very thread safe\n                                       )]","metadata":{"_uuid":"be7abcb7-c1bb-4149-a7d0-1d8d071332b1","_cell_guid":"983fc7fa-b4cb-4afc-b3b3-84e9a381e749","trusted":true},"execution_count":null,"outputs":[]}]}