{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.10.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"tpu1vmV38","dataSources":[{"sourceId":61446,"databundleVersionId":6962461,"sourceType":"competition"}],"dockerImageVersionId":30589,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"<style>\n    .header {font-size: 40px}\n</style>\n<p style=\"font-size: 40px\">\n    <img align=\"right\" width=\"400\" height=\"200\" src=\"https://www.kaggle.com/competitions/61446/images/header\">\n    <b>SenNet + HOA - Hacking the Human Vasculature in 3D</b>\n    <p style=\"font-size: 18px\">Segment vasculature in 3D scans of human kidney</p>\n</p>","metadata":{}},{"cell_type":"markdown","source":"This solution was implemented as part of a blood vessel segmentation competition organized by the Common Fund’s Cellular Senescence Network (SenNet) Programm in cooperation with the Human Organ Atlas (HOA). \n - Competition overview page: [SenNet + HOA - Hacking the Human Vasculature in 3D](https://www.kaggle.com/competitions/blood-vessel-segmentation)\n - Competition dataset is [here](https://www.kaggle.com/competitions/blood-vessel-segmentation/data)","metadata":{}},{"cell_type":"markdown","source":"**NB**: This notebook contains only the training stage, the inference stage is [here](https://www.kaggle.com/code/pib73nl/sennet-hoa-bvs-12th-place-solution-infer/edit)","metadata":{}},{"cell_type":"code","source":"! pip install scikit-image","metadata":{"execution":{"iopub.status.busy":"2024-02-15T01:24:35.233814Z","iopub.execute_input":"2024-02-15T01:24:35.234187Z","iopub.status.idle":"2024-02-15T01:24:41.845936Z","shell.execute_reply.started":"2024-02-15T01:24:35.234146Z","shell.execute_reply":"2024-02-15T01:24:41.845141Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import numpy as np \nimport pandas as pd \nimport os, glob\nimport matplotlib.pyplot as plt\nfrom tqdm import tqdm\nfrom tqdm.contrib.concurrent import thread_map\nfrom tqdm.asyncio import tqdm as async_tqdm\nimport tensorflow as tf\nimport skimage as ski\nfrom skimage import io\nimport pickle\n\nprint(f'TF version - {tf.__version__}')","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","execution":{"iopub.status.busy":"2024-02-15T01:24:41.847663Z","iopub.execute_input":"2024-02-15T01:24:41.847962Z","iopub.status.idle":"2024-02-15T01:24:56.663958Z","shell.execute_reply.started":"2024-02-15T01:24:41.847935Z","shell.execute_reply":"2024-02-15T01:24:56.663265Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# detect hardware, return appropriate distribution strategy\n# training was carried out on TPU since there is not enough memory on the instance with GPU \ntry:\n    tpu = tf.distribute.cluster_resolver.TPUClusterResolver()\n    tf.config.experimental_connect_to_cluster(tpu)\n    tf.tpu.experimental.initialize_tpu_system(tpu)\n    strategy = tf.distribute.TPUStrategy(tpu)\n    print('Running on TPU ', tpu.master())\nexcept ValueError:\n    strategy = tf.distribute.MirroredStrategy() # for CPU/GPU or multi-GPU machines\n\nprint(\"Number of replicas: \", strategy.num_replicas_in_sync)","metadata":{"execution":{"iopub.status.busy":"2024-02-15T01:24:56.664873Z","iopub.execute_input":"2024-02-15T01:24:56.665319Z","iopub.status.idle":"2024-02-15T01:25:05.851932Z","shell.execute_reply.started":"2024-02-15T01:24:56.665290Z","shell.execute_reply":"2024-02-15T01:25:05.850844Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"MAIN_PATH = '/kaggle/input/blood-vessel-segmentation'\nTRAIN_PATH = MAIN_PATH + '/train'\nTRAIN_PATH_KIDNEY_1_DENSE = TRAIN_PATH + '/kidney_1_dense'\nTRAIN_PATH_KIDNEY_1_VOI = TRAIN_PATH + '/kidney_1_voi'\nTRAIN_PATH_KIDNEY_2 = TRAIN_PATH + '/kidney_2'\nTRAIN_PATH_KIDNEY_3_DENSE = TRAIN_PATH + '/kidney_3_dense'\nTRAIN_PATH_KIDNEY_3_SPARSE = TRAIN_PATH + '/kidney_3_sparse'\nTEST_PATH = MAIN_PATH + '/test'\nTEST_PATH_KIDNEY_5 = TEST_PATH + '/kidney_5'\nWORKING_TRAIN = '/kaggle/working/train'\nWORKING_VALID = '/kaggle/working/valid'\nBATCH_SIZE = 4 * strategy.num_replicas_in_sync\nINPUT_SHAPE = [1024, 704]\n# INPUT_SHAPE = [768, 512]\n# INPUT_SHAPE = [576, 384]\n# INPUT_SHAPE = [384, 256]\n\n# global seed\ntf.random.set_seed(42)\n# for operation seeds\nSEED = 42","metadata":{"execution":{"iopub.status.busy":"2024-02-15T01:25:05.852870Z","iopub.execute_input":"2024-02-15T01:25:05.853126Z","iopub.status.idle":"2024-02-15T01:25:05.859013Z","shell.execute_reply.started":"2024-02-15T01:25:05.853099Z","shell.execute_reply":"2024-02-15T01:25:05.858035Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"image_path = (\n    sorted(glob.glob(TRAIN_PATH_KIDNEY_1_DENSE +'/images/*.tif')) + \n    sorted(glob.glob(TRAIN_PATH_KIDNEY_2 +'/images/*.tif')) + \n    sorted(glob.glob(TRAIN_PATH_KIDNEY_3_SPARSE +'/images/*.tif')) +\n    sorted(glob.glob(TRAIN_PATH_KIDNEY_1_VOI +'/images/*.tif'))\n)\n                             \nlabel_path = (\n    sorted(glob.glob(TRAIN_PATH_KIDNEY_1_DENSE +'/labels/*.tif')) + \n    sorted(glob.glob(TRAIN_PATH_KIDNEY_2 +'/labels/*.tif')) + \n    sorted(glob.glob(TRAIN_PATH_KIDNEY_3_SPARSE +'/labels/*.tif')) +\n    sorted(glob.glob(TRAIN_PATH_KIDNEY_1_VOI +'/labels/*.tif'))\n)\n\n# for experiments\n# image_path = sorted(glob.glob(TRAIN_PATH_KIDNEY_1_DENSE +'/images/*.tif'))                            \n# label_path = sorted(glob.glob(TRAIN_PATH_KIDNEY_1_DENSE +'/labels/*.tif'))\n\n# each 25-th image using for validation\nimage_path_val = image_path[::25]\nlabel_path_val = label_path[::25]\ndel image_path[::25]\ndel label_path[::25]\n\nprint(\n    f'''images in training set - {len(image_path)}, \n    images in validation set - {len(image_path_val)}, \n    ratio - {len(image_path_val) / len(image_path): .0%}'''\n)\n","metadata":{"execution":{"iopub.status.busy":"2024-02-15T01:25:05.861101Z","iopub.execute_input":"2024-02-15T01:25:05.861365Z","iopub.status.idle":"2024-02-15T01:25:10.205238Z","shell.execute_reply.started":"2024-02-15T01:25:05.861338Z","shell.execute_reply":"2024-02-15T01:25:10.204199Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Data preparation","metadata":{}},{"cell_type":"markdown","source":"## Preprocessing images and labels and writing tfrecord files","metadata":{}},{"cell_type":"code","source":"def read_image_label(im_path, lb_path = None):\n    \"\"\"\n    Pre-process images and labels (if exist) and remember metadata to restore the original size\n    \"\"\"\n    image = io.imread(im_path)\n    label = io.imread(lb_path) if lb_path else None\n    # for restoring original size and taking ID\n    meta = {\n            'orig_shape': image.shape,\n            'source':im_path.split('/')[-3::2], # last three elements [kidney_*, images, *.tif] with step 2 - [kidney_*, *.tif]\n           }\n    \n    # normalizing before applying roi\n    image = (image - image.mean()) / image.std()\n    image, label, pad_width = apply_roi(image, label)\n    # normalizing after applying roi\n    image = (image - image.mean()) / image.std()\n    \n    meta['roi_shape'] = image.shape\n    meta['pad_width'] = pad_width\n    image = ski.transform.resize(image, INPUT_SHAPE).astype(np.float16)\n\n    if isinstance(label, np.ndarray):\n        label = (label / 255).astype(np.uint8)\n        label = ski.transform.resize(label, INPUT_SHAPE, order=0) \n        # the following construction is not used further (but should be used); left here for further improvement\n        class_weights = [(1 - label.mean()).astype(np.float32), label.mean().astype(np.float32)] if label.mean() != 0 else [1.0, 0.0]\n    else:\n        class_weights = None\n    \n    \n    return image, label, class_weights, meta\n\ndef apply_roi(image, label=None):\n    \"\"\"\n    Exclusion of uninformative image fields \n    \"\"\"\n    # just throw out rows and columnt with low std\n    row_mask = image.std(axis=1)>0.22\n    clmn_mask = image.std(axis=0)>0.22\n    \n    # cleaning up the of noize of this approach and taking a solid region\n    row_mask = cleaning_mask(row_mask)\n    clmn_mask = cleaning_mask(clmn_mask)\n    \n    image = image[row_mask,:][:, clmn_mask]\n    label = label[row_mask,:][:, clmn_mask] if isinstance(label, np.ndarray) else None \n    \n    # remember the size of the pads for subsequent correct restoration\n    row_pad = (row_mask.argmax(), row_mask[::-1].argmax())\n    clmn_pad = (clmn_mask.argmax(), clmn_mask[::-1].argmax())\n    \n    return image, label, (row_pad, clmn_pad)\n\ndef cleaning_mask(mask):\n    \"\"\"\n    Selecting a solid region from a noisy mask\n    \"\"\"\n    # if frame starts from the first element or finishes at the last\n    mask[0] = False\n    mask[-1] = False\n    \n    # taking edges of frames\n    frames = np.nonzero(mask[:-1]!=mask[1:])[0]\n    # taking length of frames\n    delta = frames[1:]-frames[:-1]\n    # taking index of max len frame\n    max_solid_block_begin = np.argmax(delta)\n    # other is garbage\n    garbage = np.delete(frames, [max_solid_block_begin, max_solid_block_begin+1])\n    # clearing the mask\n    for a, b in zip(garbage[::2], garbage[1::2]):\n        mask[a+1:b+1] = False\n    \n    return mask\n\ndef _bytes_feature(value):\n    try:\n        value = tf.io.serialize_tensor(value).numpy()\n    except ValueError: # if value can't be convert into tensor\n        value = pickle.dumps(value)\n\n    return tf.train.Feature(bytes_list=tf.train.BytesList(value=[value]))\n\ndef serialize_example(image, label, class_weights, meta):\n    \"\"\"\n    Creates serialized string ready to be written to a file.\n    \"\"\"\n    # Create a dictionary mapping the feature name to the tf.train.Example-compatible\n    # data type.\n    feature = {\n        'image': _bytes_feature(image),\n        'label': _bytes_feature(label),\n#         'class_weights': _bytes_feature(class_weights), # for bigger size 20240110 \n        'meta': _bytes_feature(meta)\n        }\n    # Create a Features message using tf.train.Example.\n    example_proto = tf.train.Example(features=tf.train.Features(feature=feature))\n\n    return example_proto.SerializeToString()\n\ndef make_rec(im_path, lb_path):\n    \"\"\"\n    Сollecting preprocessing and preparation of serialized strings\n    \"\"\"\n    image, label, class_weights, meta = read_image_label(im_path, lb_path)\n    serialized_example = serialize_example(image, label, class_weights, meta)\n\n    return serialized_example\n","metadata":{"execution":{"iopub.status.busy":"2024-02-15T01:25:10.206252Z","iopub.execute_input":"2024-02-15T01:25:10.206496Z","iopub.status.idle":"2024-02-15T01:25:10.221363Z","shell.execute_reply.started":"2024-02-15T01:25:10.206470Z","shell.execute_reply":"2024-02-15T01:25:10.220351Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"nrows_train = len(image_path)\nnrows_valid = len(image_path_val)\nCHUNKSIZE = 75 # trying to make a tfrecord files no larger than 100 MB\n\ntotal_tfrec_train = np.ceil(nrows_train / CHUNKSIZE)\ntotal_tfrec_valid = np.ceil(nrows_valid / CHUNKSIZE)\n\nprint(f'train files - {total_tfrec_train}, valid files - {total_tfrec_valid}')","metadata":{"execution":{"iopub.status.busy":"2024-02-15T01:25:10.222339Z","iopub.execute_input":"2024-02-15T01:25:10.222558Z","iopub.status.idle":"2024-02-15T01:25:10.237049Z","shell.execute_reply.started":"2024-02-15T01:25:10.222535Z","shell.execute_reply":"2024-02-15T01:25:10.236220Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def writing_tfrec_parallel(input_data):\n    \"\"\"\n    Writing tfrecord file\n        inpit_data - tuple of:\n            i - index of file (tfr001.tfrecord, tfr001.tfrecord, etc.)\n            chunk_im_path - list of images paths\n            chunk_lb_path - list of labels paths\n            path - path to write tfrecord files\n    \"\"\"\n    i, chunk_im_path, chunk_lb_path, path = input_data    \n    tfr_file = f'{path}/tfr{i:03d}.tfrecords'\n\n    with tf.io.TFRecordWriter(tfr_file) as writer:\n        for image, label in zip(chunk_im_path, chunk_lb_path):\n            se = make_rec(image, label)\n            writer.write(se)","metadata":{"execution":{"iopub.status.busy":"2024-02-15T01:25:10.237911Z","iopub.execute_input":"2024-02-15T01:25:10.238173Z","iopub.status.idle":"2024-02-15T01:25:10.248645Z","shell.execute_reply.started":"2024-02-15T01:25:10.238148Z","shell.execute_reply":"2024-02-15T01:25:10.247985Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"try:\n    os.mkdir('train')\nexcept FileExistsError: \n    pass\ntry:\n    os.mkdir('valid')\nexcept FileExistsError: \n    pass\n    \nprint('[Writing train tfrec...]')\n_ = thread_map(writing_tfrec_parallel, \n               [\n                   (i, image_path[start:start+CHUNKSIZE], label_path[start:start+CHUNKSIZE], WORKING_TRAIN) \n                   for i, start\n                   in enumerate(range(0, nrows_train, CHUNKSIZE))\n               ], \n               tqdm_class=async_tqdm, total=total_tfrec_train, ncols=100)\n\nprint('[Writing valid tfrec...]')\n_ = thread_map(writing_tfrec_parallel, \n               [\n                   (i, image_path_val[start:start+CHUNKSIZE], label_path_val[start:start+CHUNKSIZE], WORKING_VALID) \n                   for i, start\n                   in enumerate(range(0, nrows_valid, CHUNKSIZE))\n               ], \n               tqdm_class=async_tqdm, total=total_tfrec_valid, ncols=100)","metadata":{"execution":{"iopub.status.busy":"2024-02-15T01:25:10.249483Z","iopub.execute_input":"2024-02-15T01:25:10.249749Z","iopub.status.idle":"2024-02-15T01:27:07.926253Z","shell.execute_reply.started":"2024-02-15T01:25:10.249722Z","shell.execute_reply":"2024-02-15T01:27:07.925325Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Preparing an input pipeline using tfrecord files","metadata":{}},{"cell_type":"code","source":"def read_labeled_tfrecord(example):\n    \"\"\"\n    Reading a single entry from a tfrecord file\n    \"\"\"\n    # setting record format\n    LABELED_TFREC_FORMAT = {\n        \"image\": tf.io.FixedLenFeature([], tf.string), # tensor encoded as bytestring\n        \"label\": tf.io.FixedLenFeature([], tf.string),  # shape [] means single element\n        #\"class_weights\": tf.io.FixedLenFeature([], tf.string), # for bigger size 20240110\n        \"meta\": tf.io.FixedLenFeature([], tf.string),\n    }\n    \n    # deserialize record\n    example = tf.io.parse_single_example(example, LABELED_TFREC_FORMAT)\n    \n    # getting image\n    image = tf.io.parse_tensor(example['image'], tf.float16)\n    image = tf.reshape(image, [*INPUT_SHAPE, 1]) # explicite size needed for TPU\n    \n    #getting label\n    label = tf.io.parse_tensor(example['label'], tf.uint8)\n    label = tf.reshape(label, [*INPUT_SHAPE, 1])\n    \n    # getting metadata\n    meta = example['meta']\n    \n    ###########################\n    # class balancing\n    # class_weights = tf.io.parse_tensor(example['class_weights'], tf.float32)\n\n    # Create an image of `sample_weights` by using the label at each pixel as an \n    # index into the `class weights` .\n    # sample_weights = tf.gather(class_weights, indices=tf.cast(label, tf.int32))\n    # sample_weights = tf.reshape(sample_weights, INPUT_SHAPE)\n    # sample_weights = tf.cast(tf.transpose(label, perm=[1,2,0]), tf.int16) \n    ###########################\n    \n    # some hardcoding... \n    # the solution above has not been finalized, so for now it’s like this\n    # It works! Well, or it seems to work... :)\n    sample_weights = tf.cast(label, tf.int16) * 900 + 100\n    \n    # 'meta' is not needed for training, but only for inference\n    # since the inference is in another notebook, it is not needed here\n    return image, label, sample_weights#, meta \n    \n    \ndef load_dataset(filenames, ordered=False):\n    \"\"\"\n    Creating a dataset\n    \"\"\"\n    ignore_order = tf.data.Options()\n    if not ordered:\n        ignore_order.deterministic = False # disable order, increase speed\n\n    dataset = tf.data.TFRecordDataset(filenames, num_parallel_reads=tf.data.AUTOTUNE) # automatically interleaves reads from multiple files\n    dataset = dataset.with_options(ignore_order) # uses data as soon as it streams in, rather than in its original order\n    dataset = dataset.map(read_labeled_tfrecord)\n\n    return dataset\n\ndef get_training_dataset(path):\n    \"\"\"\n    Creating a training pipeline\n    \"\"\"\n    dataset = load_dataset(sorted(tf.io.gfile.glob(path)))\n    dataset = dataset.repeat() \n    dataset = dataset.shuffle(2048)\n    dataset = dataset.batch(BATCH_SIZE)\n    dataset = dataset.prefetch(2)\n    return dataset\n\ndef get_validation_dataset(path):\n    \"\"\"\n    Creating a validation pipeline\n    \"\"\"\n    dataset = load_dataset(sorted(tf.io.gfile.glob(path)))\n    dataset = dataset.batch(BATCH_SIZE)\n    dataset = dataset.cache()\n    return dataset","metadata":{"execution":{"iopub.status.busy":"2024-02-15T01:27:07.927539Z","iopub.execute_input":"2024-02-15T01:27:07.928116Z","iopub.status.idle":"2024-02-15T01:27:07.938158Z","shell.execute_reply.started":"2024-02-15T01:27:07.928072Z","shell.execute_reply":"2024-02-15T01:27:07.937491Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"tr_ds = get_training_dataset(f'{WORKING_TRAIN}/*.tfrecords')\nprint(tr_ds)\nvl_ds = get_validation_dataset(f'{WORKING_VALID}/*.tfrecords')\nprint(vl_ds)","metadata":{"execution":{"iopub.status.busy":"2024-02-15T01:27:07.939051Z","iopub.execute_input":"2024-02-15T01:27:07.939331Z","iopub.status.idle":"2024-02-15T01:27:15.303879Z","shell.execute_reply.started":"2024-02-15T01:27:07.939304Z","shell.execute_reply":"2024-02-15T01:27:15.303121Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"# Training","metadata":{}},{"cell_type":"markdown","source":"## Model utilities","metadata":{}},{"cell_type":"code","source":"# Encoder Utilities\n\ndef conv2d_block(input_tensor, n_filters, kernel_size = 3):\n    \n    '''\n    Adds 2 convolutional layers with the parameters passed to it\n\n    Args:\n    input_tensor (tensor) -- the input tensor\n    n_filters (int) -- number of filters\n    kernel_size (int) -- kernel size for the convolution\n\n    Returns:\n    tensor of output features\n    '''\n    \n    # first layer\n    x = input_tensor\n    for _ in range(2):\n        x = tf.keras.layers.Conv2D(filters = n_filters, kernel_size = (kernel_size, kernel_size), \n                                   kernel_initializer = tf.initializers.random_uniform(seed=SEED), # operation seed\n                                   padding = 'same')(x)\n        x = tf.keras.layers.Activation('relu')(x)\n\n    return x\n\n\ndef encoder_block(inputs, n_filters=64, pool_type = 'max', pool_size=(2,2), dropout=0.5, normalize = True):\n    '''\n    Adds two convolutional blocks and then perform down sampling on output of convolutions.\n\n    Args:\n    input_tensor (tensor) -- the input tensor\n    n_filters (int) -- number of filters\n    pool_type (str) -- type of pooling ('max', 'avg')\n    pool_size (tuple) -- size of a pooling window\n    dropout (float) -- dropout rate\n    normalize (bool) -- whether to use normalization\n\n    Returns:\n    f - the output features of the convolution block \n    p - the pooled features with dropout and normalization\n    '''\n\n    f = conv2d_block(inputs, n_filters=n_filters)\n    \n    if pool_type == 'max':\n        p = tf.keras.layers.MaxPooling2D(pool_size=pool_size)(f)\n    elif pool_type == 'avg':\n        p = tf.keras.layers.AveragePooling2D(pool_size=pool_size)(f)\n    \n    if normalize:\n        p = tf.keras.layers.BatchNormalization()(p)\n        \n    p = tf.keras.layers.Dropout(dropout)(p)\n\n    return f, p\n\n\ndef encoder(inputs):\n    '''\n    This function defines the encoder or downsampling path.\n\n    Args:\n    inputs (tensor) -- batch of input images\n\n    Returns:\n    p4 - the output maxpooled features of the last encoder block\n    (f1, f2, f3, f4) - the output features of all the encoder blocks\n    '''\n    f1, p1 = encoder_block(inputs, n_filters=64, pool_type='avg')\n    f2, p2 = encoder_block(p1, n_filters=128, pool_type='avg')\n    f3, p3 = encoder_block(p2, n_filters=256, pool_type='avg', normalize=False)\n    f4, p4 = encoder_block(p3, n_filters=512, pool_type='avg', normalize=False)\n\n    return p4, (f1, f2, f3, f4)","metadata":{"execution":{"iopub.status.busy":"2024-02-15T01:27:15.304975Z","iopub.execute_input":"2024-02-15T01:27:15.305264Z","iopub.status.idle":"2024-02-15T01:27:18.122125Z","shell.execute_reply.started":"2024-02-15T01:27:15.305236Z","shell.execute_reply":"2024-02-15T01:27:18.121233Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"def bottleneck(inputs):\n    '''\n    This function defines the bottleneck convolutions to extract more features before the upsampling layers.\n    '''\n\n    bottle_neck = conv2d_block(inputs, n_filters=1024)\n\n    return bottle_neck","metadata":{"execution":{"iopub.status.busy":"2024-02-15T01:27:18.123237Z","iopub.execute_input":"2024-02-15T01:27:18.123514Z","iopub.status.idle":"2024-02-15T01:27:23.714031Z","shell.execute_reply.started":"2024-02-15T01:27:18.123477Z","shell.execute_reply":"2024-02-15T01:27:23.713126Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Decoder Utilities\n\ndef decoder_block(inputs, conv_output, n_filters=64, kernel_size=3, strides=(2,2), dropout=0.5, normalize = True):\n    '''\n    defines the one decoder block of the UNet\n\n    Args:\n    inputs (tensor) -- batch of input features\n    conv_output (tensor) -- features from an encoder block\n    n_filters (int) -- number of filters\n    kernel_size (int) -- kernel size\n    strides (int) -- strides for the deconvolution/upsampling\n    dropout (float) -- dropout rate\n    normalize (bool) -- whether to use normalization\n\n    Returns:\n    c (tensor) -- output features of the decoder block\n    '''\n    \n    u = tf.keras.layers.Conv2DTranspose(n_filters, kernel_size, strides = strides, \n                                        kernel_initializer = tf.initializers.random_uniform(seed=SEED), # operation seed\n                                        padding = 'same')(inputs)\n    c = tf.keras.layers.concatenate([u, conv_output])\n    \n    if normalize:\n        c = tf.keras.layers.BatchNormalization()(c)\n    \n    c = tf.keras.layers.Dropout(dropout)(c)\n    \n    c = conv2d_block(c, n_filters, kernel_size=kernel_size)\n\n    return c\n\n\ndef decoder(inputs, convs, output_channels):\n    '''\n    Defines the decoder of the UNet chaining together several decoder blocks. \n\n    Args:\n    inputs (tensor) -- batch of input features\n    convs (tuple) -- features from the encoder blocks\n    output_channels (int) -- number of classes in the label map\n\n    Returns:\n    outputs (tensor) -- the pixel wise label map of the image\n    '''\n\n    f1, f2, f3, f4 = convs\n\n    c6 = decoder_block(inputs, f4, n_filters=512, normalize=False)\n    c7 = decoder_block(c6, f3, n_filters=256, normalize=False)\n    c8 = decoder_block(c7, f2, n_filters=128)\n    c9 = decoder_block(c8, f1, n_filters=64)\n\n    outputs = tf.keras.layers.Conv2D(output_channels, (1, 1), activation='sigmoid',\n                                     kernel_initializer = tf.initializers.random_uniform(seed=SEED))(c9)\n\n    return outputs","metadata":{"execution":{"iopub.status.busy":"2024-02-15T01:27:23.716616Z","iopub.execute_input":"2024-02-15T01:27:23.716868Z","iopub.status.idle":"2024-02-15T01:27:27.073166Z","shell.execute_reply.started":"2024-02-15T01:27:23.716843Z","shell.execute_reply":"2024-02-15T01:27:27.072289Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"OUTPUT_CHANNELS = 1\n\ndef unet():\n    '''\n    Defines the UNet by connecting the encoder, bottleneck and decoder.\n    '''\n\n    # specify the input shape\n    inputs = tf.keras.layers.Input(shape=(*INPUT_SHAPE, 1))\n\n    # feed the inputs to the encoder\n    encoder_output, convs = encoder(inputs)\n\n    # feed the encoder output to the bottleneck\n    bottle_neck = bottleneck(encoder_output)\n\n    # feed the bottleneck and encoder block outputs to the decoder\n    # specify the number of classes via the `output_channels` argument\n    outputs = decoder(bottle_neck, convs, output_channels=OUTPUT_CHANNELS)\n\n    # create the model\n    model = tf.keras.Model(inputs=inputs, outputs=outputs)\n\n    return model\n","metadata":{"execution":{"iopub.status.busy":"2024-02-15T01:27:27.074185Z","iopub.execute_input":"2024-02-15T01:27:27.074429Z","iopub.status.idle":"2024-02-15T01:27:32.682277Z","shell.execute_reply.started":"2024-02-15T01:27:27.074406Z","shell.execute_reply":"2024-02-15T01:27:32.681334Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Setting training parameters and train","metadata":{}},{"cell_type":"code","source":"EPOCHS = 60\ntotal_train_steps = nrows_train//BATCH_SIZE * BATCH_SIZE * EPOCHS\nwarmup_steps = int(total_train_steps * 0.10)\ndecay_steps = total_train_steps - warmup_steps\n\nprint(f'{total_train_steps=}')\nprint(f'{warmup_steps=}')\nprint(f'{decay_steps=}')\n\nwith strategy.scope():\n    # instantiate the model\n    model = unet()\n    \n    loss = tf.keras.losses.BinaryCrossentropy()\n    metric = tf.keras.metrics.BinaryIoU(target_class_ids=[1])\n    \n    # learning rate schedule\n    cosine_decay = tf.keras.optimizers.schedules.CosineDecay(\n        initial_learning_rate=1e-5,\n        decay_steps=decay_steps,\n        alpha=0.0,\n        warmup_target=1e-2,\n        warmup_steps=warmup_steps,\n    )    ","metadata":{"execution":{"iopub.status.busy":"2024-02-15T01:27:32.683503Z","iopub.execute_input":"2024-02-15T01:27:32.683877Z","iopub.status.idle":"2024-02-15T01:27:42.852883Z","shell.execute_reply.started":"2024-02-15T01:27:32.683826Z","shell.execute_reply":"2024-02-15T01:27:42.851947Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"model.summary()","metadata":{"execution":{"iopub.status.busy":"2024-02-15T01:27:42.854079Z","iopub.execute_input":"2024-02-15T01:27:42.854453Z","iopub.status.idle":"2024-02-15T01:27:42.952916Z","shell.execute_reply.started":"2024-02-15T01:27:42.854415Z","shell.execute_reply":"2024-02-15T01:27:42.951881Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# tf.keras.utils.plot_model(model, show_shapes=True)","metadata":{"execution":{"iopub.status.busy":"2024-02-15T01:27:42.954240Z","iopub.execute_input":"2024-02-15T01:27:42.954583Z","iopub.status.idle":"2024-02-15T01:27:42.958316Z","shell.execute_reply.started":"2024-02-15T01:27:42.954544Z","shell.execute_reply":"2024-02-15T01:27:42.957463Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# configure the optimizer, loss and metrics for training\nmodel.compile(optimizer=tf.keras.optimizers.Adam(learning_rate=cosine_decay),\n              loss=loss,\n#               metrics=[metric],\n              weighted_metrics = [metric],\n             )","metadata":{"execution":{"iopub.status.busy":"2024-02-15T01:27:42.959545Z","iopub.execute_input":"2024-02-15T01:27:42.959930Z","iopub.status.idle":"2024-02-15T01:27:43.095797Z","shell.execute_reply.started":"2024-02-15T01:27:42.959888Z","shell.execute_reply":"2024-02-15T01:27:43.095130Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# configure the training parameters and train the model\nTRAIN_LENGTH = nrows_train\nSTEPS_PER_EPOCH = TRAIN_LENGTH // BATCH_SIZE\nVALIDATION_STEPS = nrows_valid//BATCH_SIZE\n\n# save the weights of the best epoch\ncp_path = f'{os.getcwd()}/tmp/checkpoint'\ncp_callback = tf.keras.callbacks.ModelCheckpoint(\n    filepath=cp_path, \n    save_weights_only=True,\n    save_best_only=True\n)","metadata":{"execution":{"iopub.status.busy":"2024-02-15T01:27:43.096944Z","iopub.execute_input":"2024-02-15T01:27:43.097433Z","iopub.status.idle":"2024-02-15T01:27:43.102326Z","shell.execute_reply.started":"2024-02-15T01:27:43.097390Z","shell.execute_reply":"2024-02-15T01:27:43.101479Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# let's train!\nmodel_history = model.fit(tr_ds, epochs=EPOCHS,\n                          steps_per_epoch=STEPS_PER_EPOCH,\n                          callbacks=[cp_callback], \n                          validation_steps=VALIDATION_STEPS,\n                          validation_data=vl_ds,\n                         )","metadata":{"execution":{"iopub.status.busy":"2024-02-15T01:27:43.103653Z","iopub.execute_input":"2024-02-15T01:27:43.104344Z","iopub.status.idle":"2024-02-15T06:47:59.840936Z","shell.execute_reply.started":"2024-02-15T01:27:43.104302Z","shell.execute_reply":"2024-02-15T06:47:59.839724Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# remove datasets, leave only model weights for inference\n!rm -rf /kaggle/working/train /kaggle/working/valid","metadata":{"execution":{"iopub.status.busy":"2024-02-15T06:47:59.842598Z","iopub.execute_input":"2024-02-15T06:47:59.842888Z","iopub.status.idle":"2024-02-15T06:48:04.616483Z","shell.execute_reply.started":"2024-02-15T06:47:59.842851Z","shell.execute_reply":"2024-02-15T06:48:04.615044Z"},"trusted":true},"execution_count":null,"outputs":[]}]}