{"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":"# Kích thước hình ảnh:\n# Chọn một trong các kích thước 256, 384, 512, 768\ntfrec_shape = 384\n\n# chọn giữa \"2020\" (chỉ dữ liệu cuộc thi năm 2020) hoặc \"2019-2020\" (dữ liệu cuộc thi năm 2020 + 2019, bao gồm năm 2017 và 2018)\ncomp_data = \"2020\"","metadata":{"execution":{"iopub.status.busy":"2025-08-24T07:29:13.707922Z","iopub.execute_input":"2025-08-24T07:29:13.708289Z","iopub.status.idle":"2025-08-24T07:29:13.713022Z","shell.execute_reply.started":"2025-08-24T07:29:13.708263Z","shell.execute_reply":"2025-08-24T07:29:13.712093Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"#### Mô hình:\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":"### Thông số cấu hình","metadata":{}},{"cell_type":"code","source":"# kích thước cắt ngẫu nhiên cho mỗi kích thước hình ảnh gốc (256, 384, 512, 768):\ncrop_size = {256: 250, 384: 370, 512: 500, 768: 750}\n\n# kích thước thực cho mỗi kích thước hình ảnh gốc (trong trường hợp bạn muốn thay đổi kích thước hình ảnh sau khi cắt):\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# tăng cường tóc\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: 13, 384: 15, 512: 15, 768: 15}\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-24T07:29:13.714471Z","iopub.execute_input":"2025-08-24T07:29:13.714720Z","iopub.status.idle":"2025-08-24T07:29:13.727187Z","shell.execute_reply.started":"2025-08-24T07:29:13.714700Z","shell.execute_reply":"2025-08-24T07:29:13.726348Z"},"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, # Xác định tỷ lệ hình ảnh train để áp dụng bỏ qua thô cho / Giữa 0 và 1.\n    DROP_CT = 0, # Cần loại bỏ bao nhiêu ô vuông khỏi hình ảnh train khi áp dụng dropout?\n    DROP_SIZE = 0, # Kích thước cạnh hình vuông bằng IMG_SIZE * DROP_SIZE / Giữa 0 và 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-24T07:29:13.728151Z","iopub.execute_input":"2025-08-24T07:29:13.728438Z","iopub.status.idle":"2025-08-24T07:29:13.743555Z","shell.execute_reply.started":"2025-08-24T07:29:13.728417Z","shell.execute_reply":"2025-08-24T07:29:13.742619Z"},"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-24T07:29:13.745293Z","iopub.execute_input":"2025-08-24T07:29:13.745617Z","iopub.status.idle":"2025-08-24T07:29:22.158177Z","shell.execute_reply.started":"2025-08-24T07:29:13.745596Z","shell.execute_reply":"2025-08-24T07:29:22.156884Z"},"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-24T07:29:22.159956Z","iopub.execute_input":"2025-08-24T07:29:22.160333Z","iopub.status.idle":"2025-08-24T07:29:22.166740Z","shell.execute_reply.started":"2025-08-24T07:29:22.160281Z","shell.execute_reply":"2025-08-24T07:29:22.165883Z"},"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-24T07:29:22.167832Z","iopub.execute_input":"2025-08-24T07:29:22.168064Z","iopub.status.idle":"2025-08-24T07:29:22.872834Z","shell.execute_reply.started":"2025-08-24T07:29:22.168045Z","shell.execute_reply":"2025-08-24T07:29:22.872093Z"},"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-24T07:29:22.874058Z","iopub.execute_input":"2025-08-24T07:29:22.874716Z","iopub.status.idle":"2025-08-24T07:29:23.024337Z","shell.execute_reply.started":"2025-08-24T07:29:22.874682Z","shell.execute_reply":"2025-08-24T07:29:23.023653Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df = pd.read_csv(\"/kaggle/input/siim-isic-melanoma-classification/train.csv\")\ndf","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-24T07:29:23.025413Z","iopub.execute_input":"2025-08-24T07:29:23.025658Z","iopub.status.idle":"2025-08-24T07:29:23.081569Z","shell.execute_reply.started":"2025-08-24T07:29:23.025637Z","shell.execute_reply":"2025-08-24T07:29:23.080685Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\nimport matplotlib.pyplot as plt\n\n# Đếm số lượng mỗi nhãn\ncounts = df[\"benign_malignant\"].value_counts().sort_index()\n\n# Màu cho từng cột (thứ tự alphabet: benign, malignant)\ncolors = [\"#2ca02c\", \"#d62728\"]   # xanh lá, đỏ\n\n# Vẽ biểu đồ cột\nplt.figure(figsize=(6, 4))\ncounts.plot(kind=\"bar\", color=colors)\n\nplt.title(\"Số lượng ảnh Benign vs. Malignant\")\nplt.xlabel(\"Label\")\nplt.ylabel(\"Số mẫu\")\nplt.xticks(rotation=0)\n\n# Hiện số lên đầu cột\nfor i, v in enumerate(counts.values):\n    plt.text(i, v + 5, str(v), ha=\"center\")\n\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-24T07:29:23.085587Z","iopub.execute_input":"2025-08-24T07:29:23.086019Z","iopub.status.idle":"2025-08-24T07:29:25.455210Z","shell.execute_reply.started":"2025-08-24T07:29:23.085995Z","shell.execute_reply":"2025-08-24T07:29:25.454279Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\nimport matplotlib.pyplot as plt\n\n# Đếm số lượng mỗi nhãn\ncounts = df[\"sex\"].value_counts().sort_index()\n\n# Màu cho từng cột (thứ tự alphabet: benign, malignant)\ncolors = [\"#2ca02c\", \"#d62728\"]   # xanh lá, đỏ\n\n# Vẽ biểu đồ cột\nplt.figure(figsize=(6, 4))\ncounts.plot(kind=\"bar\", color=colors)\n\nplt.title(\"Số lượng ảnh Benign vs. Malignant\")\nplt.xlabel(\"Label\")\nplt.ylabel(\"Số mẫu\")\nplt.xticks(rotation=0)\n\n# Hiện số lên đầu cột\nfor i, v in enumerate(counts.values):\n    plt.text(i, v + 5, str(v), ha=\"center\")\n\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-24T07:29:25.456359Z","iopub.execute_input":"2025-08-24T07:29:25.456652Z","iopub.status.idle":"2025-08-24T07:29:25.809946Z","shell.execute_reply.started":"2025-08-24T07:29:25.456626Z","shell.execute_reply":"2025-08-24T07:29:25.808899Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import pandas as pd\nimport matplotlib.pyplot as plt\n\n# Đếm số lượng từng nhãn\ncounts = df[\"anatom_site_general_challenge\"].value_counts().sort_index()  # đảm bảo thứ tự alphabet\n\n# Vẽ biểu đồ cột\nplt.figure(figsize=(6, 4))\ncounts.plot(kind=\"bar\")          # mặc định màu Matplotlib\nplt.title(\"Số lượng ảnh Benign vs. Malignant\")\nplt.xlabel(\"Label\")\nplt.ylabel(\"Số mẫu\")\nplt.xticks(rotation=0)           # giữ nhãn trục X nằm ngang\nfor i, v in enumerate(counts.values):\n    plt.text(i, v + 5, str(v), ha=\"center\")  # ghi con số lên đầu cột\n\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-24T07:29:25.811059Z","iopub.execute_input":"2025-08-24T07:29:25.811389Z","iopub.status.idle":"2025-08-24T07:29:26.180084Z","shell.execute_reply.started":"2025-08-24T07:29:25.811355Z","shell.execute_reply":"2025-08-24T07:29:26.179154Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## TPU configuration / GPU 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-24T07:29:26.181221Z","iopub.execute_input":"2025-08-24T07:29:26.181506Z","iopub.status.idle":"2025-08-24T07:29:26.188977Z","shell.execute_reply.started":"2025-08-24T07:29:26.181482Z","shell.execute_reply":"2025-08-24T07:29:26.188030Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Functions","metadata":{}},{"cell_type":"code","source":"# hair augmentation cấy các sợi tóc giả lên ảnh trong lúc train để mô hình học cách bỏ qua tóc che phủ (nhiễu rất hay gặp ngoài thực tế).\n# 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# số lượng tóc tối đa để tăng cường:\nn_max= 20\n\n# Hình ảnh tóc ban đầu được thiết kế cho kích thước 256x256, vì vậy chúng cần được điều chỉnh tỷ lệ để sử dụng với các hình ảnh có kích thước khác nhau.\n# Hệ số tỷ lệ:\nif tfrec_shape != 256:\n    scale=tf.cast(CFG['crop_size']/256, dtype=tf.int32)\n\ndef hair_aug_tf(input_img, augment=True):\n    \n    if augment:\n    \n        # Sao chép hình ảnh đầu vào để nó không bị thay đổi\n        img = tf.identity(input_img)\n\n        # Không chuẩn hóa: Trả về hình ảnh từ 0-1 đến 0-255:\n        img = tf.multiply(img, 255)\n\n        # Chọn ngẫu nhiên số lượng tóc để tăng thêm (tối đa 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            # Chuẩn hóa hình ảnh thành [0,1]\n            img = tf.multiply(img, 1/255)\n            return img\n\n        for _ in tf.range(n_hairs):\n\n            # Đọc một hình ảnh tóc ngẫu nhiên\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            # Thay đổi kích thước hình ảnh tóc theo đúng kích thước\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            # Hình ảnh tóc lật ngẫu nhiên\n            hair = tf.image.random_flip_left_right(hair)\n            hair = tf.image.random_flip_up_down(hair)\n\n            # Số lượng ngẫu nhiên các vòng quay 90 độ\n            n_rot = tf.random.uniform(shape=[], maxval=4,dtype=tf.int32)\n            hair = tf.image.rot90(hair, k=n_rot)\n\n            # Chiều cao và chiều rộng của hình ảnh tóc (bỏ qua số kênh màu)\n            h_height = tf.shape(hair)[0]\n            h_width = tf.shape(hair)[1]\n\n            # Tọa độ trên cùng bên trái của vùng quan tâm (roi) nơi quá trình tăng cường sẽ được thực hiện\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            # Khu vực quan tâm\n            roi = img[roi_h0:(roi_h0 + h_height), roi_w0:(roi_w0 + h_width)]  \n\n            # Chuyển đổi hình ảnh tóc sang thang độ xám (cắt để loại bỏ kênh trainsparency)\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            # Thêm số không vào dst để có cùng hình dạng với hình ảnh.\n            dst_padded=tf.pad(dst, paddings, \"CONSTANT\")\n\n            # Tạo mặt nạ boolean với số không tại các điểm ảnh của phân đoạn tăng cường và số một ở mọi nơi khác\n            mask_img=tf.pad(tf.ones_like(dst), paddings, \"CONSTANT\")\n            mask_img=~tf.cast(mask_img, dtype=tf.bool)\n\n            # Tạo một lỗ trên hình ảnh gốc tại vị trí của phân đoạn tăng cường\n            img_hole=tf.multiply(img, tf.cast(mask_img, dtype=tf.float32))\n\n            # Chèn đoạn tăng cường vào vị trí của lỗ\n            img = tf.add(img_hole, dst_padded)\n\n        # Chuẩn hóa hình ảnh thành [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-24T07:29:26.190049Z","iopub.execute_input":"2025-08-24T07:29:26.190345Z","iopub.status.idle":"2025-08-24T07:29:26.614916Z","shell.execute_reply.started":"2025-08-24T07:29:26.190296Z","shell.execute_reply":"2025-08-24T07:29:26.613642Z"},"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    # ảnh đầu vào - là một ảnh có kích thước [dim,dim,3] chứ không phải là một loạt ảnh [b,dim,dim,3]\n    # ảnh đầu ra - ảnh có các ô vuông CT có kích thước cạnh SZ*DIM bị loại bỏ\n\n    # THỰC HIỆN BỎ QUA VỚI XÁC SUẤT ĐÃ ĐỊNH Ở TRÊN\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-24T07:29:26.616397Z","iopub.execute_input":"2025-08-24T07:29:26.616755Z","iopub.status.idle":"2025-08-24T07:29:26.625766Z","shell.execute_reply.started":"2025-08-24T07:29:26.616722Z","shell.execute_reply":"2025-08-24T07:29:26.624863Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# FOCAL LOSS\n\ndef binary_focal_loss(gamma=2., alpha=.25):\n    def binary_focal_loss_fixed(y_true, y_pred):\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-24T07:29:26.626918Z","iopub.execute_input":"2025-08-24T07:29:26.627180Z","iopub.status.idle":"2025-08-24T07:29:26.641755Z","shell.execute_reply.started":"2025-08-24T07:29:26.627159Z","shell.execute_reply":"2025-08-24T07:29:26.640832Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def get_mat(rotation, shear, height_zoom, width_zoom, height_shift, width_shift):\n    # trả về ma trận biến đổi 3x3, biến đổi các chỉ số\n\n    # CHUYỂN ĐỔI ĐỘ SANG RADIA\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-24T07:29:26.642678Z","iopub.execute_input":"2025-08-24T07:29:26.642875Z","iopub.status.idle":"2025-08-24T07:29:26.656425Z","shell.execute_reply.started":"2025-08-24T07:29:26.642857Z","shell.execute_reply":"2025-08-24T07:29:26.655605Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def transform(image, cfg):    \n\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-24T07:29:26.657447Z","iopub.execute_input":"2025-08-24T07:29:26.657663Z","iopub.status.idle":"2025-08-24T07:29:26.672903Z","shell.execute_reply.started":"2025-08-24T07:29:26.657645Z","shell.execute_reply":"2025-08-24T07:29:26.672091Z"},"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-24T07:29:26.673880Z","iopub.execute_input":"2025-08-24T07:29:26.674206Z","iopub.status.idle":"2025-08-24T07:29:26.685447Z","shell.execute_reply.started":"2025-08-24T07:29:26.674176Z","shell.execute_reply":"2025-08-24T07:29:26.684730Z"},"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-24T07:29:26.686435Z","iopub.execute_input":"2025-08-24T07:29:26.686633Z","iopub.status.idle":"2025-08-24T07:29:26.696940Z","shell.execute_reply.started":"2025-08-24T07:29:26.686615Z","shell.execute_reply":"2025-08-24T07:29:26.696133Z"},"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-24T07:29:26.697941Z","iopub.execute_input":"2025-08-24T07:29:26.698242Z","iopub.status.idle":"2025-08-24T07:29:26.710348Z","shell.execute_reply.started":"2025-08-24T07:29:26.698215Z","shell.execute_reply":"2025-08-24T07:29:26.709501Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# chức năng đếm số lượng ảnh chúng ta có\ndef count_data_items(filenames):\n    # số lượng mục dữ liệu được ghi trong tên của các tệp .tfrec, tức là flowers00-230.tfrec = 230 mục dữ liệu\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-24T07:29:26.711196Z","iopub.execute_input":"2025-08-24T07:29:26.711435Z","iopub.status.idle":"2025-08-24T07:29:26.721574Z","shell.execute_reply.started":"2025-08-24T07:29:26.711415Z","shell.execute_reply":"2025-08-24T07:29:26.720794Z"},"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-24T07:29:26.722559Z","iopub.execute_input":"2025-08-24T07:29:26.722828Z","iopub.status.idle":"2025-08-24T07:29:26.735965Z","shell.execute_reply.started":"2025-08-24T07:29:26.722806Z","shell.execute_reply":"2025-08-24T07:29:26.735283Z"},"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-24T07:29:26.739089Z","iopub.execute_input":"2025-08-24T07:29:26.739427Z","iopub.status.idle":"2025-08-24T07:29:26.751434Z","shell.execute_reply.started":"2025-08-24T07:29:26.739404Z","shell.execute_reply":"2025-08-24T07:29:26.750749Z"},"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-24T07:29:26.752262Z","iopub.execute_input":"2025-08-24T07:29:26.752499Z","iopub.status.idle":"2025-08-24T07:29:26.761248Z","shell.execute_reply.started":"2025-08-24T07:29:26.752480Z","shell.execute_reply":"2025-08-24T07:29:26.760466Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# BUILD MODEL\n\ndef get_model(cfg, model):\n    \n    model_input = tf.keras.Input(shape=(cfg['net_size'], cfg['net_size'], 3), name='imgIn')\n    dummy = tf.keras.layers.Lambda(lambda x:x)(model_input)\n    outputs = []\n\n    constructor = getattr(efn, model)\n    x = constructor(include_top=False, weights=model_weights, input_shape=(cfg['net_size'], cfg['net_size'], 3), pooling='avg')(dummy)\n    x = tf.keras.layers.Dense(1, activation='sigmoid')(x)\n    outputs.append(x)\n    \n    model = tf.keras.Model(model_input, outputs, name='aNetwork')\n    model.summary()\n    \n    return model","metadata":{"execution":{"iopub.status.busy":"2025-08-24T07:29:26.762150Z","iopub.execute_input":"2025-08-24T07:29:26.762405Z","iopub.status.idle":"2025-08-24T07:29:26.775402Z","shell.execute_reply.started":"2025-08-24T07:29:26.762385Z","shell.execute_reply":"2025-08-24T07:29:26.774574Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# COMPILE MODEL\n\ndef compile_new_model(cfg, model):\n    with strategy.scope():\n        model = get_model(cfg, model)\n     \n        losses = tf.keras.losses.BinaryCrossentropy(label_smoothing = cfg['label_smooth_fac'])\n        # losses = [binary_focal_loss(gamma = 2.0, alpha = 0.80)]\n        \n        model.compile(\n            optimizer = cfg['optimizer'],\n            loss = losses,\n            metrics = [tf.keras.metrics.AUC(name='auc')]) # metrics = [tf.keras.metrics.BinaryAccuracy()]\n        \n    return model","metadata":{"execution":{"iopub.status.busy":"2025-08-24T07:29:26.776399Z","iopub.execute_input":"2025-08-24T07:29:26.776638Z","iopub.status.idle":"2025-08-24T07:29:26.787206Z","shell.execute_reply.started":"2025-08-24T07:29:26.776618Z","shell.execute_reply":"2025-08-24T07:29:26.786442Z"},"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-24T07:29:26.788701Z","iopub.execute_input":"2025-08-24T07:29:26.789053Z","iopub.status.idle":"2025-08-24T07:29:26.801631Z","shell.execute_reply.started":"2025-08-24T07:29:26.789025Z","shell.execute_reply":"2025-08-24T07:29:26.800736Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Hình ảnh ban đầu","metadata":{}},{"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-24T07:29:26.802606Z","iopub.execute_input":"2025-08-24T07:29:26.802852Z","iopub.status.idle":"2025-08-24T07:29:28.386063Z","shell.execute_reply.started":"2025-08-24T07:29:26.802832Z","shell.execute_reply":"2025-08-24T07:29:28.385211Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Hình ảnh được tăng cường","metadata":{}},{"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-24T07:29:28.387479Z","iopub.execute_input":"2025-08-24T07:29:28.387826Z","iopub.status.idle":"2025-08-24T07:29:31.628325Z","shell.execute_reply.started":"2025-08-24T07:29:28.387798Z","shell.execute_reply":"2025-08-24T07:29:31.627465Z"},"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-24T07:29:31.629957Z","iopub.execute_input":"2025-08-24T07:29:31.630222Z","iopub.status.idle":"2025-08-24T07:29:32.891450Z","shell.execute_reply.started":"2025-08-24T07:29:31.630200Z","shell.execute_reply":"2025-08-24T07:29:32.890468Z"},"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)\nds_train","metadata":{"execution":{"iopub.status.busy":"2025-08-24T07:29:32.892680Z","iopub.execute_input":"2025-08-24T07:29:32.893054Z","iopub.status.idle":"2025-08-24T07:29:33.109139Z","shell.execute_reply.started":"2025-08-24T07:29:32.893030Z","shell.execute_reply":"2025-08-24T07:29:33.108076Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model_B4 = compile_new_model(CFG, 'EfficientNetB1')\nprint(\"\\n Begin Training Models\")\nhistory_B4 = model_B4.fit(ds_train, verbose=1, steps_per_epoch=steps_train, epochs = CFG['epochs'], callbacks=[get_lr_callback(CFG)]) # callbacks=[get_lr_callback(CFG), checkpointer]\nprint(\"\\n Done Training EfficientNetB4 \\n\")","metadata":{"execution":{"iopub.status.busy":"2025-08-24T07:29:33.110205Z","iopub.execute_input":"2025-08-24T07:29:33.110665Z"},"trusted":true},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Save model:\nmodel_B4.save(f\"./EfficientNetB6_{tfrec_shape}x{tfrec_shape}_{comp_data}_epoch{CFG['epochs']}_auc_{round(history_B4.history['auc'][CFG['epochs']-1], 2)}.h5\")\n\n# Serialize model architecture to JSON:\nmodel_json = model_B4.to_json()\nwith open(f\"./EfficientNetB6_{tfrec_shape}x{tfrec_shape}_{comp_data}_epoch{CFG['epochs']}_auc_{round(history_B4.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_B4.save_weights(f\"./EfficientNetB6_{tfrec_shape}x{tfrec_shape}_{comp_data}_epoch{CFG['epochs']}_auc_{round(history_B4.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}]}