{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.10.12","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":20270,"databundleVersionId":1222630,"sourceType":"competition"},{"sourceId":1253590,"sourceType":"datasetVersion","datasetId":720563},{"sourceId":1322494,"sourceType":"datasetVersion","datasetId":688574},{"sourceId":1322517,"sourceType":"datasetVersion","datasetId":689329},{"sourceId":1322552,"sourceType":"datasetVersion","datasetId":688719},{"sourceId":1322612,"sourceType":"datasetVersion","datasetId":689578},{"sourceId":1324349,"sourceType":"datasetVersion","datasetId":762108},{"sourceId":1324366,"sourceType":"datasetVersion","datasetId":762176},{"sourceId":1324412,"sourceType":"datasetVersion","datasetId":762168}],"dockerImageVersionId":30559,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# image size:\n# choose one between 256, 384, 512, 768\ntfrec_shape = 384\n\n# competition data:\n# choose between \"2020\" (only 2020 competition data) or \"2019-2020\" (2020 + 2019 competition data, including 2017 and 2018)\ncomp_data = \"2020\"","metadata":{"execution":{"iopub.status.busy":"2025-08-06T13:46:48.574182Z","iopub.execute_input":"2025-08-06T13:46:48.574425Z","iopub.status.idle":"2025-08-06T13:46:48.585563Z","shell.execute_reply.started":"2025-08-06T13:46:48.574403Z","shell.execute_reply":"2025-08-06T13:46:48.584915Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"#### 3rd place models:\n\n|  Model      | tfrec_shape |   comp_data   |\n|  :----:     |    :----:   |     :----:    |\n|    1        |   256       |     \"2020\"    |\n|    2        |   384       |     \"2020\"    |\n|    3        |   512       |     \"2020\"    |\n|    4        |   768       |     \"2020\"    |\n|    5        |   256       |  \"2019-2020\"  |\n|    6        |   384       |  \"2019-2020\"  |\n|    7        |   512       |  \"2019-2020\"  |\n|    8        |   768       |  \"2019-2020\"  |","metadata":{}},{"cell_type":"markdown","source":"### Parameters/Configuration Specification","metadata":{}},{"cell_type":"code","source":"# random crop size for each original image size (256, 384, 512, 768):\ncrop_size = {256: 250, 384: 370, 512: 500, 768: 750}\n\n# net size for each original image size (in case you want to resize the images after the crop):\nif comp_data == \"2020\":\n    net_size = {256: 248, 384: 370, 512: 500, 768: 750}\nelif comp_data == \"2019-2020\":\n    net_size = {256: 250, 384: 370, 512: 500, 768: 750}\n\n# hair augmentation\nif comp_data == \"2020\":\n    hair_augm = {256: False, 384: False, 512: False, 768: False}\nelif comp_data == \"2019-2020\":\n    hair_augm = {256: True, 384: True, 512: True, 768: False}\n    \n# epochs\nif comp_data == \"2020\":\n    epochs_num = {256: 30, 384: 30, 512: 30, 768: 30}\nelif comp_data == \"2019-2020\":\n    epochs_num = {256: 25, 384: 25, 512: 12, 768: 10}\n\n# model weights\nmodel_weights = 'imagenet' # 'noisy-student'\n\n# device\nDEVICE = \"TPU\"","metadata":{"execution":{"iopub.status.busy":"2025-08-06T13:46:48.587703Z","iopub.execute_input":"2025-08-06T13:46:48.587947Z","iopub.status.idle":"2025-08-06T13:46:48.602708Z","shell.execute_reply.started":"2025-08-06T13:46:48.587926Z","shell.execute_reply":"2025-08-06T13:46:48.602039Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"CFG = dict(\n    \n    batch_size = 16,\n    \n    read_size = tfrec_shape,\n    crop_size = crop_size[tfrec_shape],\n    net_size = net_size[tfrec_shape],\n    \n    # LEARNING RATE\n    LR_START = 0.000003,\n    LR_MAX = 0.000020,\n    LR_MIN = 0.000001,\n    LR_RAMPUP_EPOCHS  = 5,\n    LR_SUSTAIN_EPOCHS = 0,\n    LR_EXP_DECAY = 0.8,\n    \n    # EPOCHS:\n    epochs = epochs_num[tfrec_shape],\n    \n    # DATA AUGMENTATION\n    rot = 180.0,\n    shr = 1.5,\n    hzoom = 6.0,\n    wzoom = 6.0,\n    hshift = 6.0,\n    wshift = 6.0,\n    \n    # COARSE DROPOUT\n    DROP_FREQ = 0, # Determines proportion of train images to apply coarse dropout to / Between 0 and 1.\n    DROP_CT = 0, # How many squares to remove from train images when applying dropout.\n    DROP_SIZE = 0, # The size of square side equals IMG_SIZE * DROP_SIZE / Between 0 and 1.  \n    \n    # HAIR AUGMENTATION:\n    hair_augm = hair_augm[tfrec_shape],\n    \n    optimizer = 'adam',\n    label_smooth_fac = 0.05,\n    tta_steps =  25\n)","metadata":{"execution":{"iopub.status.busy":"2025-08-06T13:46:48.603539Z","iopub.execute_input":"2025-08-06T13:46:48.603794Z","iopub.status.idle":"2025-08-06T13:46:48.618994Z","shell.execute_reply.started":"2025-08-06T13:46:48.603765Z","shell.execute_reply":"2025-08-06T13:46:48.6175Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Install EfficientNet","metadata":{}},{"cell_type":"code","source":"! pip install -q efficientnet","metadata":{"execution":{"iopub.status.busy":"2025-08-06T13:46:48.620137Z","iopub.execute_input":"2025-08-06T13:46:48.621219Z","iopub.status.idle":"2025-08-06T13:46:58.729063Z","shell.execute_reply.started":"2025-08-06T13:46:48.621191Z","shell.execute_reply":"2025-08-06T13:46:58.727928Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Import required libraries","metadata":{}},{"cell_type":"code","source":"import os, random, re, math, time\nrandom.seed(a=42)\n\nfrom glob import glob\nimport numpy as np\nimport pandas as pd\n\nimport tensorflow as tf\nimport tensorflow.keras.backend as K\nimport efficientnet.tfkeras as efn\n# from keras.callbacks import ModelCheckpoint\n# from sklearn.model_selection import KFold\n\nfrom kaggle_datasets import KaggleDatasets\nimport PIL","metadata":{"execution":{"iopub.status.busy":"2025-08-06T13:46:58.730459Z","iopub.execute_input":"2025-08-06T13:46:58.730734Z","iopub.status.idle":"2025-08-06T13:47:09.439321Z","shell.execute_reply.started":"2025-08-06T13:46:58.730712Z","shell.execute_reply":"2025-08-06T13:47:09.438614Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Read the data","metadata":{}},{"cell_type":"code","source":"BASEPATH = \"/kaggle/input/siim-isic-melanoma-classification\"\ndf_train = pd.read_csv(os.path.join(BASEPATH, 'train.csv'))\ndf_test  = pd.read_csv(os.path.join(BASEPATH, 'test.csv'))\ndf_sub   = pd.read_csv(os.path.join(BASEPATH, 'sample_submission.csv'))\n\n# 2020 TFRecords\nGCS_PATH = KaggleDatasets().get_gcs_path(f'melanoma-{tfrec_shape}x{tfrec_shape}')\n\n# 2019 TFRecords\nGCS_PATH_2019 = KaggleDatasets().get_gcs_path(f'isic2019-{tfrec_shape}x{tfrec_shape}')","metadata":{"execution":{"iopub.status.busy":"2025-08-06T13:47:09.440304Z","iopub.execute_input":"2025-08-06T13:47:09.440728Z","iopub.status.idle":"2025-08-06T13:47:10.278481Z","shell.execute_reply.started":"2025-08-06T13:47:09.440705Z","shell.execute_reply":"2025-08-06T13:47:10.277806Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# TRAIN\nif comp_data == \"2020\":\n    files_train = np.sort(np.array(tf.io.gfile.glob(GCS_PATH + '/train*.tfrec')))\nelif comp_data == \"2019-2020\":\n    ## 2020 + 2019 (all, including 2017+2018):\n    files_train = tf.io.gfile.glob(GCS_PATH + '/train*.tfrec')\n    files_train += tf.io.gfile.glob(GCS_PATH_2019 + '/train*.tfrec')\n    files_train = np.sort(np.array(files_train)) # np.random.shuffle(files_train)\n\n\n# TEST\nfiles_test = np.sort(np.array(tf.io.gfile.glob(GCS_PATH + '/test*.tfrec')))","metadata":{"execution":{"iopub.status.busy":"2025-08-06T13:47:10.280734Z","iopub.execute_input":"2025-08-06T13:47:10.281003Z","iopub.status.idle":"2025-08-06T13:47:11.672964Z","shell.execute_reply.started":"2025-08-06T13:47:10.280982Z","shell.execute_reply":"2025-08-06T13:47:11.672327Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## TPU configuration","metadata":{}},{"cell_type":"code","source":"if DEVICE == \"TPU\":\n    print(\"connecting to TPU...\")\n    try:\n        tpu = tf.distribute.cluster_resolver.TPUClusterResolver()\n        print('Running on TPU ', tpu.master())\n    except ValueError:\n        print(\"Could not connect to TPU\")\n        tpu = None\n\n    if tpu:\n        try:\n            print(\"initializing  TPU ...\")\n            tf.config.experimental_connect_to_cluster(tpu)\n            tf.tpu.experimental.initialize_tpu_system(tpu)\n            strategy = tf.distribute.experimental.TPUStrategy(tpu)\n            print(\"TPU initialized\")\n        except _:\n            print(\"failed to initialize TPU\")\n    else:\n        DEVICE = \"GPU\"\n\nif DEVICE != \"TPU\":\n    print(\"Using default strategy for CPU and single GPU\")\n    strategy = tf.distribute.get_strategy()\n\nif DEVICE == \"GPU\":\n    print(\"Num GPUs Available: \", len(tf.config.experimental.list_physical_devices('GPU')))\n\nAUTO = tf.data.experimental.AUTOTUNE\nREPLICAS = strategy.num_replicas_in_sync\nprint(f'REPLICAS: {REPLICAS}')","metadata":{"execution":{"iopub.status.busy":"2025-08-06T13:47:11.673889Z","iopub.execute_input":"2025-08-06T13:47:11.674106Z","iopub.status.idle":"2025-08-06T13:47:12.163249Z","shell.execute_reply.started":"2025-08-06T13:47:11.674087Z","shell.execute_reply":"2025-08-06T13:47:12.162342Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Functions","metadata":{}},{"cell_type":"code","source":"# HAIR AUGMENTATION\n\n# loading hairs\nGCS_PATH_hair_images = KaggleDatasets().get_gcs_path('melanoma-hairs')\nhair_images = tf.io.gfile.glob(GCS_PATH_hair_images + '/*.png')\nhair_images_tf=tf.convert_to_tensor(hair_images)\n\n# the maximum number of hairs to augment:\nn_max= 20\n\n# The hair images were originally designed for the 256x256 size, so they need to be scaled to use with images of different sizes.\n# Scaling factor:\nif tfrec_shape != 256:\n    scale=tf.cast(CFG['crop_size']/256, dtype=tf.int32)\n\n    \ndef hair_aug_tf(input_img, augment=True):\n    \n    if augment:\n    \n        # Copy the input image, so it won't be changed\n        img = tf.identity(input_img)\n\n        # Unnormalize: Returning the image from 0-1 to 0-255:\n        img = tf.multiply(img, 255)\n\n        # Randomly choose the number of hairs to augment (up to n_max)\n        n_hairs = tf.random.uniform(shape=[], maxval=tf.constant(n_max)+1,dtype=tf.int32)\n\n        im_height = tf.shape(img)[0]\n        im_width = tf.shape(img)[1]\n\n        if n_hairs == 0:\n            # Normalize the image to [0,1]\n            img = tf.multiply(img, 1/255)\n            return img\n\n        for _ in tf.range(n_hairs):\n\n            # Read a random hair image\n            i = tf.random.uniform(shape=[], maxval=tf.shape(hair_images_tf)[0],dtype=tf.int32)\n            fname = hair_images_tf[i]\n            bits = tf.io.read_file(fname)\n            hair = tf.image.decode_jpeg(bits)\n\n            # Rescale the hair image to the right size\n            if tfrec_shape != 256:\n                # new_height, new_width, _  = scale*tf.shape(hair)\n                new_width = scale*tf.shape(hair)[1]\n                new_height = scale*tf.shape(hair)[0]\n                hair = tf.image.resize(hair, [new_height, new_width])\n\n            # Random flips of the hair image\n            hair = tf.image.random_flip_left_right(hair)\n            hair = tf.image.random_flip_up_down(hair)\n\n            # Random number of 90 degree rotations\n            n_rot = tf.random.uniform(shape=[], maxval=4,dtype=tf.int32)\n            hair = tf.image.rot90(hair, k=n_rot)\n\n            # The hair image height and width (ignore the number of color channels)\n            h_height = tf.shape(hair)[0]\n            h_width = tf.shape(hair)[1]\n\n            # The top left coord's of the region of interest (roi) where the augmentation will be performed\n            roi_h0 = tf.random.uniform(shape=[], maxval=im_height - h_height + 1, dtype=tf.int32)\n            roi_w0 = tf.random.uniform(shape=[], maxval=im_width - h_width + 1, dtype=tf.int32)\n\n            # The region of interest\n            roi = img[roi_h0:(roi_h0 + h_height), roi_w0:(roi_w0 + h_width)]  \n\n            # Convert the hair image to grayscale (slice to remove the trainsparency channel)\n            hair2gray = tf.image.rgb_to_grayscale(hair[:, :, :3])\n\n            # Threshold:\n            mask = hair2gray>10\n\n            img_bg = tf.multiply(roi, tf.cast(tf.image.grayscale_to_rgb(~mask), dtype=tf.float32))\n            hair_fg = tf.multiply(tf.cast(hair[:, :, :3], dtype=tf.int32), tf.cast(tf.image.grayscale_to_rgb(mask), dtype=tf.int32))\n\n            dst = tf.add(img_bg, tf.cast(hair_fg, dtype=tf.float32))\n\n            paddings = tf.stack([[roi_h0, im_height-(roi_h0 + h_height)], [roi_w0, im_width-(roi_w0 + h_width)],[0, 0]])\n            # Pad dst with zeros to make it the same shape as image.\n            dst_padded=tf.pad(dst, paddings, \"CONSTANT\")\n\n            # Create a boolean mask with zeros at the pixels of the augmentation segment and ones everywhere else\n            mask_img=tf.pad(tf.ones_like(dst), paddings, \"CONSTANT\")\n            mask_img=~tf.cast(mask_img, dtype=tf.bool)\n\n            # Make a hole in the original image at the location of the augmentation segment\n            img_hole=tf.multiply(img, tf.cast(mask_img, dtype=tf.float32))\n\n            # Inserting the augmentation segment in place of the hole\n            img = tf.add(img_hole, dst_padded)\n\n        # Normalize the image to [0,1]\n        img = tf.multiply(img, 1/255)\n        \n        return img\n    else:\n        return input_img\n","metadata":{"execution":{"iopub.status.busy":"2025-08-06T13:47:12.164619Z","iopub.execute_input":"2025-08-06T13:47:12.164949Z","iopub.status.idle":"2025-08-06T13:47:13.854124Z","shell.execute_reply.started":"2025-08-06T13:47:12.164919Z","shell.execute_reply":"2025-08-06T13:47:13.853184Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# COARSE DROPOUT\n\ndef dropout(image, DIM=256, PROBABILITY = 0.75, CT = 8, SZ = 0.2):\n    # input image - is one image of size [dim,dim,3] not a batch of [b,dim,dim,3]\n    # output - image with CT squares of side size SZ*DIM removed\n    \n    # DO DROPOUT WITH PROBABILITY DEFINED ABOVE\n    P = tf.cast( tf.random.uniform([],0,1)<PROBABILITY, tf.int32)\n    if (P==0)|(CT==0)|(SZ==0): return image\n    \n    for k in range(CT):\n        # CHOOSE RANDOM LOCATION\n        x = tf.cast( tf.random.uniform([],0,DIM),tf.int32)\n        y = tf.cast( tf.random.uniform([],0,DIM),tf.int32)\n        # COMPUTE SQUARE\n        WIDTH = tf.cast( SZ*DIM,tf.int32) * P\n        ya = tf.math.maximum(0,y-WIDTH//2)\n        yb = tf.math.minimum(DIM,y+WIDTH//2)\n        xa = tf.math.maximum(0,x-WIDTH//2)\n        xb = tf.math.minimum(DIM,x+WIDTH//2)\n        # DROPOUT IMAGE\n        one = image[ya:yb,0:xa,:]\n        two = tf.zeros([yb-ya,xb-xa,3]) \n        three = image[ya:yb,xb:DIM,:]\n        middle = tf.concat([one,two,three],axis=1)\n        image = tf.concat([image[0:ya,:,:],middle,image[yb:DIM,:,:]],axis=0)\n            \n    # RESHAPE HACK SO TPU COMPILER KNOWS SHAPE OF OUTPUT TENSOR\n    image = tf.reshape(image,[DIM,DIM,3])\n    return image","metadata":{"execution":{"iopub.status.busy":"2025-08-06T13:47:13.855412Z","iopub.execute_input":"2025-08-06T13:47:13.856092Z","iopub.status.idle":"2025-08-06T13:47:13.863886Z","shell.execute_reply.started":"2025-08-06T13:47:13.85606Z","shell.execute_reply":"2025-08-06T13:47:13.863054Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# FOCAL LOSS\n\ndef binary_focal_loss(gamma=2., alpha=.25):\n    \"\"\"\n    Binary form of focal loss.\n      FL(p_t) = -alpha * (1 - p_t)**gamma * log(p_t)\n      where p = sigmoid(x), p_t = p or 1 - p depending on if the label is 1 or 0, respectively.\n    References:\n        https://arxiv.org/pdf/1708.02002.pdf\n    Usage:\n     model.compile(loss=[binary_focal_loss(alpha=.25, gamma=2)], metrics=[\"accuracy\"], optimizer=adam)\n    \"\"\"\n    def binary_focal_loss_fixed(y_true, y_pred):\n        \"\"\"\n        :param y_true: A tensor of the same shape as `y_pred`\n        :param y_pred:  A tensor resulting from a sigmoid\n        :return: Output tensor.\n        \"\"\"\n        pt_1 = tf.where(tf.equal(y_true, 1), y_pred, tf.ones_like(y_pred))\n        pt_0 = tf.where(tf.equal(y_true, 0), y_pred, tf.zeros_like(y_pred))\n        epsilon = K.epsilon()\n        # clip to prevent NaN's and Inf's\n        pt_1 = K.clip(pt_1, epsilon, 1. - epsilon)\n        pt_0 = K.clip(pt_0, epsilon, 1. - epsilon)\n        return -K.sum(alpha * K.pow(1. - pt_1, gamma) * K.log(pt_1)) \\\n               -K.sum((1 - alpha) * K.pow(pt_0, gamma) * K.log(1. - pt_0))\n    return binary_focal_loss_fixed","metadata":{"execution":{"iopub.status.busy":"2025-08-06T13:47:13.865073Z","iopub.execute_input":"2025-08-06T13:47:13.865406Z","iopub.status.idle":"2025-08-06T13:47:13.891938Z","shell.execute_reply.started":"2025-08-06T13:47:13.865377Z","shell.execute_reply":"2025-08-06T13:47:13.891041Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def get_mat(rotation, shear, height_zoom, width_zoom, height_shift, width_shift):\n    # returns 3x3 transformmatrix which transforms indicies\n        \n    # CONVERT DEGREES TO RADIANS\n    rotation = math.pi * rotation / 180.\n    shear = math.pi * shear / 180.\n\n    def get_3x3_mat(lst):\n        return tf.reshape(tf.concat([lst],axis=0), [3,3])\n    \n    # ROTATION MATRIX\n    c1   = tf.math.cos(rotation)\n    s1   = tf.math.sin(rotation)\n    one  = tf.constant([1],dtype='float32')\n    zero = tf.constant([0],dtype='float32')\n    \n    rotation_matrix = get_3x3_mat([c1,   s1,   zero, \n                                   -s1,  c1,   zero, \n                                   zero, zero, one])    \n    # SHEAR MATRIX\n    c2 = tf.math.cos(shear)\n    s2 = tf.math.sin(shear)    \n    \n    shear_matrix = get_3x3_mat([one,  s2,   zero, \n                                zero, c2,   zero, \n                                zero, zero, one])        \n    # ZOOM MATRIX\n    zoom_matrix = get_3x3_mat([one/height_zoom, zero,           zero, \n                               zero,            one/width_zoom, zero, \n                               zero,            zero,           one])    \n    # SHIFT MATRIX\n    shift_matrix = get_3x3_mat([one,  zero, height_shift, \n                                zero, one,  width_shift, \n                                zero, zero, one])\n    \n    return K.dot(K.dot(rotation_matrix, shear_matrix), \n                 K.dot(zoom_matrix,     shift_matrix))","metadata":{"execution":{"iopub.status.busy":"2025-08-06T13:47:13.892905Z","iopub.execute_input":"2025-08-06T13:47:13.893428Z","iopub.status.idle":"2025-08-06T13:47:13.909165Z","shell.execute_reply.started":"2025-08-06T13:47:13.893407Z","shell.execute_reply":"2025-08-06T13:47:13.908337Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def transform(image, cfg):    \n    # input image - is one image of size [dim,dim,3] not a batch of [b,dim,dim,3]\n    # output - image randomly rotated, sheared, zoomed, and shifted\n    DIM = cfg[\"read_size\"]\n    XDIM = DIM%2 #fix for size 331\n    \n    rot = cfg['rot'] * tf.random.normal([1], dtype='float32')\n    shr = cfg['shr'] * tf.random.normal([1], dtype='float32') \n    h_zoom = 1.0 + tf.random.normal([1], dtype='float32') / cfg['hzoom']\n    w_zoom = 1.0 + tf.random.normal([1], dtype='float32') / cfg['wzoom']\n    h_shift = cfg['hshift'] * tf.random.normal([1], dtype='float32') \n    w_shift = cfg['wshift'] * tf.random.normal([1], dtype='float32') \n\n    # GET TRANSFORMATION MATRIX\n    m = get_mat(rot,shr,h_zoom,w_zoom,h_shift,w_shift)\n\n    # LIST DESTINATION PIXEL INDICES\n    x   = tf.repeat(tf.range(DIM//2, -DIM//2,-1), DIM)\n    y   = tf.tile(tf.range(-DIM//2, DIM//2), [DIM])\n    z   = tf.ones([DIM*DIM], dtype='int32')\n    idx = tf.stack( [x,y,z] )\n    \n    # ROTATE DESTINATION PIXELS ONTO ORIGIN PIXELS\n    idx2 = K.dot(m, tf.cast(idx, dtype='float32'))\n    idx2 = K.cast(idx2, dtype='int32')\n    idx2 = K.clip(idx2, -DIM//2+XDIM+1, DIM//2)\n    \n    # FIND ORIGIN PIXEL VALUES           \n    idx3 = tf.stack([DIM//2-idx2[0,], DIM//2-1+idx2[1,]])\n    d    = tf.gather_nd(image, tf.transpose(idx3))\n        \n    return tf.reshape(d,[DIM, DIM,3])","metadata":{"execution":{"iopub.status.busy":"2025-08-06T13:47:13.910233Z","iopub.execute_input":"2025-08-06T13:47:13.9106Z","iopub.status.idle":"2025-08-06T13:47:13.926722Z","shell.execute_reply.started":"2025-08-06T13:47:13.910569Z","shell.execute_reply":"2025-08-06T13:47:13.925962Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def read_labeled_tfrecord(example):\n    tfrec_format = {\n        'image'                        : tf.io.FixedLenFeature([], tf.string),\n        'image_name'                   : tf.io.FixedLenFeature([], tf.string),\n        'patient_id'                   : tf.io.FixedLenFeature([], tf.int64),\n        'sex'                          : tf.io.FixedLenFeature([], tf.int64),\n        'age_approx'                   : tf.io.FixedLenFeature([], tf.int64),\n        'anatom_site_general_challenge': tf.io.FixedLenFeature([], tf.int64),\n        'diagnosis'                    : tf.io.FixedLenFeature([], tf.int64),\n        'target'                       : tf.io.FixedLenFeature([], tf.int64)\n    }           \n    example = tf.io.parse_single_example(example, tfrec_format)\n    return example['image'], example['target']","metadata":{"execution":{"iopub.status.busy":"2025-08-06T13:47:13.927597Z","iopub.execute_input":"2025-08-06T13:47:13.927829Z","iopub.status.idle":"2025-08-06T13:47:13.942241Z","shell.execute_reply.started":"2025-08-06T13:47:13.927809Z","shell.execute_reply":"2025-08-06T13:47:13.941546Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def read_unlabeled_tfrecord(example, return_image_name):\n    tfrec_format = {\n        'image'                        : tf.io.FixedLenFeature([], tf.string),\n        'image_name'                   : tf.io.FixedLenFeature([], tf.string),\n    }\n    example = tf.io.parse_single_example(example, tfrec_format)\n    return example['image'], example['image_name'] if return_image_name else 0","metadata":{"execution":{"iopub.status.busy":"2025-08-06T13:47:13.943256Z","iopub.execute_input":"2025-08-06T13:47:13.943578Z","iopub.status.idle":"2025-08-06T13:47:13.964001Z","shell.execute_reply.started":"2025-08-06T13:47:13.943549Z","shell.execute_reply":"2025-08-06T13:47:13.963265Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def prepare_image(img, cfg=None, augment=True):\n    \n    img = tf.image.decode_jpeg(img, channels=3)\n    img = tf.image.resize(img, [cfg['read_size'], cfg['read_size']])\n    img = tf.cast(img, tf.float32) / 255.0 # # Cast and normalize the image to [0,1]\n    \n    if augment:\n        \n        # Data augmentation\n        img = transform(img, cfg)\n        img = tf.image.random_crop(img, [cfg['crop_size'], cfg['crop_size'], 3]) \n        # Coarse dropout\n        # img = dropout(img, DIM=cfg['crop_size'], PROBABILITY=cfg['DROP_FREQ'], CT=cfg['DROP_CT'], SZ=cfg['DROP_SIZE'])\n        # Other augmentations\n        img = tf.image.random_flip_left_right(img)\n        img = tf.image.random_hue(img, 0.01)\n        img = tf.image.random_saturation(img, 0.7, 1.3)\n        img = tf.image.random_contrast(img, 0.8, 1.2)\n        img = tf.image.random_brightness(img, 0.1)\n        # Hair augmentation\n        img = hair_aug_tf(img, augment=cfg['hair_augm'])\n    else:\n        img = tf.image.central_crop(img, cfg['crop_size'] / cfg['read_size'])\n                                   \n    img = tf.image.resize(img, [cfg['net_size'], cfg['net_size']])\n    img = tf.reshape(img, [cfg['net_size'], cfg['net_size'], 3])        \n    return img","metadata":{"execution":{"iopub.status.busy":"2025-08-06T13:47:13.964988Z","iopub.execute_input":"2025-08-06T13:47:13.965277Z","iopub.status.idle":"2025-08-06T13:47:13.97712Z","shell.execute_reply.started":"2025-08-06T13:47:13.965248Z","shell.execute_reply":"2025-08-06T13:47:13.976322Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# function to count how many photos we have in\ndef count_data_items(filenames):\n    # the number of data items is written in the name of the .tfrec files, i.e. flowers00-230.tfrec = 230 data items\n    n = [int(re.compile(r\"-([0-9]*)\\.\").search(filename).group(1)) for filename in filenames]\n    return np.sum(n)","metadata":{"execution":{"iopub.status.busy":"2025-08-06T13:47:13.978155Z","iopub.execute_input":"2025-08-06T13:47:13.978463Z","iopub.status.idle":"2025-08-06T13:47:13.993582Z","shell.execute_reply.started":"2025-08-06T13:47:13.978434Z","shell.execute_reply":"2025-08-06T13:47:13.992722Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def get_dataset(files, cfg, augment = False, shuffle = False, repeat = False, labeled=True, return_image_names=True):\n    \n    ds = tf.data.TFRecordDataset(files, num_parallel_reads=AUTO)\n    ds = ds.cache()\n    \n    if repeat:\n        ds = ds.repeat()\n    \n    if shuffle: \n        ds = ds.shuffle(1024*8)\n        opt = tf.data.Options()\n        opt.experimental_deterministic = False\n        ds = ds.with_options(opt)\n        \n    if labeled: \n        ds = ds.map(read_labeled_tfrecord, num_parallel_calls=AUTO)\n    else:\n        ds = ds.map(lambda example: read_unlabeled_tfrecord(example, return_image_names), num_parallel_calls=AUTO)      \n    \n    ds = ds.map(lambda img, imgname_or_label: (prepare_image(img, augment=augment, cfg=cfg),imgname_or_label), num_parallel_calls=AUTO)\n    \n    ds = ds.batch(cfg['batch_size'] * REPLICAS)\n    ds = ds.prefetch(AUTO)\n    return ds","metadata":{"execution":{"iopub.status.busy":"2025-08-06T13:47:13.994655Z","iopub.execute_input":"2025-08-06T13:47:13.995116Z","iopub.status.idle":"2025-08-06T13:47:14.012501Z","shell.execute_reply.started":"2025-08-06T13:47:13.995087Z","shell.execute_reply":"2025-08-06T13:47:14.011403Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def show_dataset(thumb_size, cols, rows, ds):\n    mosaic = PIL.Image.new(mode='RGB', size=(thumb_size*cols + (cols-1), thumb_size*rows + (rows-1)))\n   \n    for idx, data in enumerate(iter(ds)):\n        img, target_or_imgid = data\n        ix  = idx % cols\n        iy  = idx // cols\n        img = np.clip(img.numpy() * 255, 0, 255).astype(np.uint8)\n        img = PIL.Image.fromarray(img)\n        img = img.resize((thumb_size, thumb_size), resample=PIL.Image.BILINEAR)\n        mosaic.paste(img, (ix*thumb_size + ix, \n                           iy*thumb_size + iy))\n\n    display(mosaic)","metadata":{"execution":{"iopub.status.busy":"2025-08-06T13:47:14.013606Z","iopub.execute_input":"2025-08-06T13:47:14.013964Z","iopub.status.idle":"2025-08-06T13:47:14.033828Z","shell.execute_reply.started":"2025-08-06T13:47:14.013931Z","shell.execute_reply":"2025-08-06T13:47:14.032862Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# LEARNING RATE SCHEDULER\n\ndef get_lr_callback(cfg):\n    lr_start = cfg['LR_START']\n    lr_max = cfg['LR_MAX'] * strategy.num_replicas_in_sync\n    lr_min = cfg['LR_MIN']\n    lr_ramp_ep = cfg['LR_RAMPUP_EPOCHS']\n    lr_sus_ep = cfg['LR_SUSTAIN_EPOCHS']\n    lr_decay = cfg['LR_EXP_DECAY']\n   \n    def lrfn(epoch):\n        if epoch < lr_ramp_ep:\n            lr = (lr_max - lr_start) / lr_ramp_ep * epoch + lr_start\n            \n        elif epoch < lr_ramp_ep + lr_sus_ep:\n            lr = lr_max\n            \n        else:\n            lr = (lr_max - lr_min) * lr_decay**(epoch - lr_ramp_ep - lr_sus_ep) + lr_min\n            \n        return lr\n\n    lr_callback = tf.keras.callbacks.LearningRateScheduler(lrfn, verbose=False)\n    return lr_callback","metadata":{"execution":{"iopub.status.busy":"2025-08-06T13:47:14.035078Z","iopub.execute_input":"2025-08-06T13:47:14.036021Z","iopub.status.idle":"2025-08-06T13:47:14.044511Z","shell.execute_reply.started":"2025-08-06T13:47:14.035986Z","shell.execute_reply":"2025-08-06T13:47:14.043572Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import tensorflow as tf\n\n# --- Model registry: chọn backbone và preprocess phù hợp ---\nMODEL_ZOO = {\n    'InceptionResNetV2': (\n        tf.keras.applications.InceptionResNetV2,\n        tf.keras.applications.inception_resnet_v2.preprocess_input,\n        299\n    ),\n    'Xception': (\n        tf.keras.applications.Xception,\n        tf.keras.applications.xception.preprocess_input,\n        299\n    ),\n    'ResNet101': (\n        tf.keras.applications.ResNet101,\n        tf.keras.applications.resnet.preprocess_input,\n        224\n    ),\n    'DenseNet201': (\n        tf.keras.applications.DenseNet201,\n        tf.keras.applications.densenet.preprocess_input,\n        224\n    ),\n    'NASNetLarge': (\n        tf.keras.applications.NASNetLarge,\n        tf.keras.applications.nasnet.preprocess_input,\n        331\n    ),\n}\n\n# ===== BUILD MODEL =====\ndef get_model(cfg, model_name, weights='imagenet', dropout_rate=None):\n    if model_name not in MODEL_ZOO:\n        raise ValueError(f\"Unknown model_name={model_name}. Options: {list(MODEL_ZOO.keys())}\")\n\n    constructor, preprocess_fn, default_size = MODEL_ZOO[model_name]\n    input_size = int(cfg.get('net_size', default_size))\n    if dropout_rate is None:\n        dropout_rate = float(cfg.get('dropout', 0.0))\n\n    inputs = tf.keras.Input(shape=(input_size, input_size, 3), name='imgIn')\n\n    # Chuẩn hoá đúng theo backbone\n    x = tf.keras.layers.Lambda(preprocess_fn, name='preprocess')(inputs)\n\n    # Base CNN\n    base = constructor(include_top=False, weights=weights,\n                       input_shape=(input_size, input_size, 3), pooling='avg')\n    x = base(x)\n\n    if dropout_rate and dropout_rate > 0:\n        x = tf.keras.layers.Dropout(dropout_rate)(x)\n\n    outputs = tf.keras.layers.Dense(1, activation='sigmoid', name='pred')(x)\n    model = tf.keras.Model(inputs, outputs, name=f'{model_name}_melanoma_cls')\n    model.summary()\n    return model\n\n# ===== COMPILE MODEL =====\ndef compile_new_model(cfg, model_name):\n    with strategy.scope():\n        model = get_model(cfg, model_name)\n        loss_fn = tf.keras.losses.BinaryCrossentropy(\n            label_smoothing=float(cfg.get('label_smooth_fac', 0.0))\n        )\n        model.compile(\n            optimizer=cfg['optimizer'],\n            loss=loss_fn,\n            metrics=[tf.keras.metrics.AUC(name='auc')]\n        )\n    return model\n","metadata":{"execution":{"iopub.status.busy":"2025-08-06T13:47:14.045893Z","iopub.execute_input":"2025-08-06T13:47:14.046226Z","iopub.status.idle":"2025-08-06T13:47:14.065335Z","shell.execute_reply.started":"2025-08-06T13:47:14.046195Z","shell.execute_reply":"2025-08-06T13:47:14.064702Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Train & Test datasets -> Image examples","metadata":{}},{"cell_type":"code","source":"num_training_images = int(count_data_items(files_train))\nnum_test_images = count_data_items(files_test)\nprint('Dataset: {} training images, {} unlabeled test images'.format(num_training_images, num_test_images))","metadata":{"execution":{"iopub.status.busy":"2025-08-06T13:47:14.069442Z","iopub.execute_input":"2025-08-06T13:47:14.069682Z","iopub.status.idle":"2025-08-06T13:47:14.084787Z","shell.execute_reply.started":"2025-08-06T13:47:14.069661Z","shell.execute_reply":"2025-08-06T13:47:14.084011Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Train Data\nds = get_dataset(files_train, CFG).unbatch().take(12*5) # augment = False\nshow_dataset(64, 12, 5, ds)","metadata":{"execution":{"iopub.status.busy":"2025-08-06T13:47:14.08563Z","iopub.execute_input":"2025-08-06T13:47:14.085869Z","iopub.status.idle":"2025-08-06T13:47:19.622614Z","shell.execute_reply.started":"2025-08-06T13:47:14.085849Z","shell.execute_reply":"2025-08-06T13:47:19.621751Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Image Augmentation\nds = tf.data.TFRecordDataset(files_train, num_parallel_reads=AUTO)\nds = ds.take(1).cache().repeat()\nds = ds.map(read_labeled_tfrecord, num_parallel_calls=AUTO)\nds = ds.map(lambda img, target: (prepare_image(img, cfg=CFG, augment=True), target), num_parallel_calls=AUTO)\nds = ds.take(12*5)\nds = ds.prefetch(AUTO)\nshow_dataset(64, 12, 5, ds)","metadata":{"execution":{"iopub.status.busy":"2025-08-06T13:47:19.623519Z","iopub.execute_input":"2025-08-06T13:47:19.623775Z","iopub.status.idle":"2025-08-06T13:47:25.189377Z","shell.execute_reply.started":"2025-08-06T13:47:19.623752Z","shell.execute_reply":"2025-08-06T13:47:25.188452Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Test Data\nds = get_dataset(files_test, CFG, labeled=False).unbatch().take(12*5)\nshow_dataset(64, 12, 5, ds)","metadata":{"execution":{"iopub.status.busy":"2025-08-06T13:47:25.190449Z","iopub.execute_input":"2025-08-06T13:47:25.190701Z","iopub.status.idle":"2025-08-06T13:47:28.632885Z","shell.execute_reply.started":"2025-08-06T13:47:25.190679Z","shell.execute_reply":"2025-08-06T13:47:28.632059Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"ds_train = get_dataset(files_train, CFG, augment=True, shuffle=True, repeat=True)\nds_train = ds_train.map(lambda img, label: (img, tuple([label])))\nsteps_train = count_data_items(files_train) / (CFG['batch_size'] * REPLICAS)","metadata":{"execution":{"iopub.status.busy":"2025-08-06T13:47:28.634098Z","iopub.execute_input":"2025-08-06T13:47:28.63443Z","iopub.status.idle":"2025-08-06T13:47:28.819746Z","shell.execute_reply.started":"2025-08-06T13:47:28.634402Z","shell.execute_reply":"2025-08-06T13:47:28.818855Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# CFG['net_size'] = 299\nmodel_irnv2 = compile_new_model(CFG, 'Xception')\nhistory_irnv2 = model_irnv2.fit(\n    ds_train, verbose=1, steps_per_epoch=steps_train,\n    epochs=CFG['epochs'],\n    callbacks=[get_lr_callback(CFG)]\n)","metadata":{"execution":{"iopub.status.busy":"2025-08-06T13:47:28.820887Z","iopub.execute_input":"2025-08-06T13:47:28.821128Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Save model:\nmodel_B6.save(f\"./EfficientNetB6_{tfrec_shape}x{tfrec_shape}_{comp_data}_epoch{CFG['epochs']}_auc_{round(history_B6.history['auc'][CFG['epochs']-1], 2)}.h5\")\n\n# Serialize model architecture to JSON:\nmodel_json = model_B6.to_json()\nwith open(f\"./EfficientNetB6_{tfrec_shape}x{tfrec_shape}_{comp_data}_epoch{CFG['epochs']}_auc_{round(history_B6.history['auc'][CFG['epochs']-1], 2)}_architecture.json\", \"w\") as json_file:\n    json_file.write(model_json)\n\n# Serialize weights to h5:\nmodel_B6.save_weights(f\"./EfficientNetB6_{tfrec_shape}x{tfrec_shape}_{comp_data}_epoch{CFG['epochs']}_auc_{round(history_B6.history['auc'][CFG['epochs']-1], 2)}_weights.h5\")","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}