{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.7.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":4104,"databundleVersionId":46661,"sourceType":"competition"},{"sourceId":265751,"sourceType":"datasetVersion","datasetId":110097},{"sourceId":2269470,"sourceType":"datasetVersion","datasetId":1366461}],"dockerImageVersionId":30162,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"markdown","source":"# Diabetic Retinopathy \n\nDiabetic retinopathy (DR), also known as diabetic eye disease, is a medical condition in which damage occurs to the retina due to diabetes mellitus. It is a leading cause of blindness. Diabetic retinopathy affects up to 80 percent of those who have had diabetes for 20 years or more. Diabetic retinopathy often has no early warning signs. **Retinal (fundus) photography with manual interpretation is a widely accepted screening tool for diabetic retinopathy**, with performance that can exceed that of in-person dilated eye examinations. \n\nThe below figure shows an example of a healthy patient and a patient with diabetic retinopathy as viewed by fundus photography ([source](https://www.biorxiv.org/content/biorxiv/early/2018/06/19/225508.full.pdf)):\n\n![image.png](data:image/jpeg;base64,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)\n\nAn automated tool for grading severity of diabetic retinopathy would be very useful for accerelating detection and treatment. Recently, there have been a number of attempts to utilize deep learning to diagnose DR and automatically grade diabetic retinopathy. This includes this [competition](https://kaggle.com/c/diabetic-retinopathy-detection) and [work by Google](https://ai.googleblog.com/2016/11/deep-learning-for-detection-of-diabetic.html). Even one deep-learning based system is [FDA approved](https://www.fda.gov/NewsEvents/Newsroom/PressAnnouncements/ucm604357.htm). \n\nClearly, this dataset and deep learning problem is quite well-characterized. \n\n# A look at the data:\n\nData description from the competition:\n\n>You are provided with a large set of high-resolution retina images taken under a variety of imaging conditions. A left and right field is provided for every subject. >Images are labeled with a subject id as well as either left or right (e.g. 1_left.jpeg is the left eye of patient id 1).\n>\n>A clinician has rated the presence of diabetic retinopathy in each image on a scale of 0 to 4, according to the following scale:\n>\n>0 - No DR\n>\n>1 - Mild\n>\n>2 - Moderate\n>\n>3 - Severe\n>\n>4 - Proliferative DR\n>\n>Your task is to create an automated analysis system capable of assigning a score based on this scale.\n\n...\n\n> Like any real-world data set, you will encounter noise in both the images and labels. Images may contain artifacts, be out of focus, underexposed, or overexposed. A major aim of this competition is to develop robust algorithms that can function in the presence of noise and variation.\n\n**A minor problem!**\n\nDue to the large image file size, there are **only 1000 files with labels** and one csv file with the labels of all the images in the directory available in the kernel, as demonstrated below. The actual competition had on the order of 35,000 files so this is clearly **a very small subset of the data**. \nIn addition, this is a highly imbalanced dataset. \n\nOriginally, I had aimed to create a model that would be close to the SOTA, but clearly, with only 1000 images that's not possible.\n\n**AIM OF KERNEL:** I will utilize transfer learning, oversampling, and progressive resizing on this small, imbalanced dataset.\nGiven that many real-world datasets are also small and imbalanced, it will be interesting to see how far these techniques will take us.","metadata":{}},{"cell_type":"code","source":"import torch\n\ntorch.cuda.is_available()","metadata":{"execution":{"iopub.status.busy":"2024-10-18T07:20:16.320269Z","iopub.execute_input":"2024-10-18T07:20:16.320533Z","iopub.status.idle":"2024-10-18T07:20:17.989474Z","shell.execute_reply.started":"2024-10-18T07:20:16.320456Z","shell.execute_reply":"2024-10-18T07:20:17.988636Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import os\nimport glob\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nimport tensorflow as tf\nimport tensorflow.keras.backend as K\nimport itertools\nimport seaborn as sns\nfrom tensorflow.keras.applications import ResNet50\nfrom tensorflow.keras.optimizers import Adam\nfrom tensorflow.keras.losses import SparseCategoricalCrossentropy\nfrom tensorflow.keras.layers import Dense, Flatten\nfrom tensorflow.keras.models import Sequential\nfrom tensorflow.keras.callbacks import EarlyStopping\nfrom tensorflow.keras.preprocessing.image import ImageDataGenerator\nfrom tensorflow.data import Dataset\nfrom skimage.io import imread\nfrom sklearn.metrics import *\nfrom sklearn.model_selection import *\nfrom skimage.io import *\nfrom glob import glob\nimport warnings\n\n\nwarnings.filterwarnings('ignore')\nprint(\"Necessary modules have been imported\")\n","metadata":{"execution":{"iopub.status.busy":"2024-10-18T07:20:17.99115Z","iopub.execute_input":"2024-10-18T07:20:17.991388Z","iopub.status.idle":"2024-10-18T07:20:24.622918Z","shell.execute_reply.started":"2024-10-18T07:20:17.991358Z","shell.execute_reply":"2024-10-18T07:20:24.621976Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# 定義圖像解析函數\ndef parse_image(filename, label):\n    image = tf.io.read_file(filename)\n    image = tf.image.decode_jpeg(image, channels=3)\n    image = tf.image.resize(image, [224, 224])\n    image = image / 255.0\n    return image, label\n\n# 定義數據集加載函數\ndef load_dataset(file_paths, labels, batch_size=32):\n    dataset = Dataset.from_tensor_slices((file_paths, labels))\n    dataset = dataset.map(parse_image, num_parallel_calls=tf.data.AUTOTUNE)\n    dataset = dataset.shuffle(buffer_size=len(file_paths)).batch(batch_size)\n    dataset = dataset.prefetch(buffer_size=tf.data.AUTOTUNE)\n    return dataset\n","metadata":{"execution":{"iopub.status.busy":"2024-10-18T07:20:33.63409Z","iopub.execute_input":"2024-10-18T07:20:33.634373Z","iopub.status.idle":"2024-10-18T07:20:33.642195Z","shell.execute_reply.started":"2024-10-18T07:20:33.634341Z","shell.execute_reply":"2024-10-18T07:20:33.641365Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Importing labels","metadata":{}},{"cell_type":"code","source":"# 解壓和加載數據標籤\n!unzip -o ../input/diabetic-retinopathy-detection/trainLabels.csv.zip\ntrainLabels = pd.read_csv(\"./trainLabels.csv\")\n","metadata":{"execution":{"iopub.status.busy":"2024-10-18T07:20:36.265143Z","iopub.execute_input":"2024-10-18T07:20:36.265684Z","iopub.status.idle":"2024-10-18T07:20:37.311582Z","shell.execute_reply.started":"2024-10-18T07:20:36.265646Z","shell.execute_reply":"2024-10-18T07:20:37.310879Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"!apt install p7zip-full -y\n!7z x ../input/diabetic-retinopathy-detection/train.zip.001 \"-i!train/11*.jpeg\" -y \n# restrict extracted file to about 100 for the disk restriction\n!mkdir data\n!mv train data/train_11\n","metadata":{"execution":{"iopub.status.busy":"2024-10-18T07:20:41.169657Z","iopub.execute_input":"2024-10-18T07:20:41.170007Z","iopub.status.idle":"2024-10-18T07:21:06.40668Z","shell.execute_reply.started":"2024-10-18T07:20:41.169966Z","shell.execute_reply":"2024-10-18T07:21:06.405548Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# 獲取所有圖像文件路徑\nfile_paths = glob(\"./data/train_11/*.jpeg\")\n\n# 假設file_paths包含圖像的完整路徑，從中提取圖像的基礎名稱\nfile_basenames = [os.path.basename(f).replace(\".jpeg\", \"\") for f in file_paths]\n\n# 根據文件名來篩選出對應的標籤\nfiltered_labels = trainLabels[trainLabels['image'].isin(file_basenames)]['level'].values\n\n# 確認file_paths與filtered_labels的數量相同\nprint(f\"Number of image files: {len(file_paths)}\")\nprint(f\"Number of filtered labels: {len(filtered_labels)}\")\n\n# 加載數據集\ndataset = load_dataset(file_paths, filtered_labels)","metadata":{"execution":{"iopub.status.busy":"2024-10-18T07:21:14.891574Z","iopub.execute_input":"2024-10-18T07:21:14.892444Z","iopub.status.idle":"2024-10-18T07:21:20.166736Z","shell.execute_reply.started":"2024-10-18T07:21:14.89239Z","shell.execute_reply":"2024-10-18T07:21:20.166093Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# 定義顯示批量圖像的函數\ndef show_batch(image_batch, label_batch):\n    plt.figure(figsize=(10, 10))\n    for n in range(5):  # 顯示5張圖片\n        ax = plt.subplot(1, 5, n + 1)\n        plt.imshow(image_batch[n])\n        plt.title(int(label_batch[n]))\n        plt.axis(\"off\")\n    plt.show()\n\n# 過濾出每個類別的一張圖片\ndef get_images_by_label(dataset, num_classes=5):\n    images = [None] * num_classes  # 用None初始化一個大小為num_classes的列表\n    labels = [None] * num_classes\n    label_counts = {i: 0 for i in range(num_classes)}  # 用來追蹤每個類別已經收集到的圖片數量\n\n    for image_batch, label_batch in dataset:\n        for img, lbl in zip(image_batch, label_batch):\n            label = int(lbl)\n            if label_counts[label] == 0:  # 只收集一次該類別的圖片\n                images[label] = img\n                labels[label] = lbl\n                label_counts[label] += 1\n            if sum(label_counts.values()) == num_classes:  # 如果已經收集到每個類別一張圖片，退出\n                return np.array(images), np.array(labels)\n    return np.array(images), np.array(labels)\n\n# 從數據集中獲取每個類別的圖片\nimage_batch, label_batch = get_images_by_label(dataset)\n\n# 按照類別順序排列圖片和標籤\nsorted_indices = np.argsort(label_batch)\nimage_batch = image_batch[sorted_indices]\nlabel_batch = label_batch[sorted_indices]\n\n# 顯示批量圖像\nshow_batch(image_batch, label_batch)\n","metadata":{"execution":{"iopub.status.busy":"2024-10-18T07:21:23.209637Z","iopub.execute_input":"2024-10-18T07:21:23.210512Z","iopub.status.idle":"2024-10-18T07:21:37.989124Z","shell.execute_reply.started":"2024-10-18T07:21:23.210467Z","shell.execute_reply":"2024-10-18T07:21:37.988269Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## Filtering csv file","metadata":{}},{"cell_type":"code","source":"# Filtering and Balancing Data\nbase_image_dir = os.path.join('.', 'data/train_11')\ntrainLabels['path'] = trainLabels['image'].map(lambda x: os.path.join(base_image_dir,'{}.jpeg'.format(x)))\ntrainLabels['exists'] = trainLabels['path'].map(os.path.exists)  # Filter out missing images\ndf = trainLabels[trainLabels['exists']]\ndf = df.drop(columns=['image', 'exists'])\ndf = df.sample(frac=1).reset_index(drop=True)  # Shuffle dataframe\ndf['level'] = df['level'].astype(str)\ndf.head(10)","metadata":{"execution":{"iopub.status.busy":"2024-10-18T07:21:43.133766Z","iopub.execute_input":"2024-10-18T07:21:43.134059Z","iopub.status.idle":"2024-10-18T07:21:43.650442Z","shell.execute_reply.started":"2024-10-18T07:21:43.134029Z","shell.execute_reply":"2024-10-18T07:21:43.649604Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"# Visualize distribution\ndf['level'].hist(figsize=(10, 5))\n","metadata":{"execution":{"iopub.status.busy":"2024-10-18T07:21:46.504352Z","iopub.execute_input":"2024-10-18T07:21:46.504748Z","iopub.status.idle":"2024-10-18T07:21:46.742819Z","shell.execute_reply.started":"2024-10-18T07:21:46.504696Z","shell.execute_reply":"2024-10-18T07:21:46.741991Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"**The dataset is highly imbalanced, with many samples for level 0, and very little for the rest of the levels.**","metadata":{}},{"cell_type":"markdown","source":"## CNN Model","metadata":{}},{"cell_type":"code","source":"from tensorflow.keras.applications import InceptionV3\nfrom tensorflow.keras.layers import GlobalAveragePooling2D, Dense, Dropout, Input, Conv2D, multiply, Lambda, BatchNormalization\nfrom tensorflow.keras.models import Model\nfrom tensorflow.keras.optimizers import Adam\nfrom tensorflow.keras.regularizers import l2\nimport numpy as np\nfrom tensorflow.keras.utils import plot_model\n\ndef create_deeper_attention_model(input_shape, num_classes):\n    in_lay = Input(input_shape)\n    base_pretrained_model = InceptionV3(input_shape=input_shape, include_top=False, weights='imagenet')\n    base_pretrained_model.trainable = False  # 保持大部分預訓練層凍結\n    pt_depth = base_pretrained_model.output_shape[-1]\n    pt_features = base_pretrained_model(in_lay)\n    \n    bn_features = BatchNormalization()(pt_features)\n    \n    # 注意力機制並加上正則化\n    attn_layer = Conv2D(128, kernel_size=(3,3), padding='same', activation='relu', kernel_regularizer=l2(0.001))(Dropout(0.5)(bn_features))\n    attn_layer = Conv2D(64, kernel_size=(3,3), padding='same', activation='relu', kernel_regularizer=l2(0.001))(attn_layer)\n    attn_layer = Conv2D(32, kernel_size=(3,3), padding='same', activation='relu', kernel_regularizer=l2(0.001))(attn_layer)\n    attn_layer = Conv2D(1, kernel_size=(1,1), padding='valid', activation='sigmoid')(attn_layer)\n    \n    up_c2_w = np.ones((1, 1, 1, pt_depth))\n    up_c2 = Conv2D(pt_depth, kernel_size=(1,1), padding='same', activation='linear', use_bias=False, weights=[up_c2_w])\n    up_c2.trainable = False\n    attn_layer = up_c2(attn_layer)\n    \n    mask_features = multiply([attn_layer, bn_features])\n    gap_features = GlobalAveragePooling2D()(mask_features)\n    gap_mask = GlobalAveragePooling2D()(attn_layer)\n    \n    gap = Lambda(lambda x: x[0]/x[1], name='RescaleGAP')([gap_features, gap_mask])\n    gap_dr = Dropout(0.5)(gap)  # 增加Dropout比例\n    dr_steps = Dropout(0.5)(Dense(256, activation='relu', kernel_regularizer=l2(0.001))(gap_dr))  # 增加全連接層的單元數\n    out_layer = Dense(num_classes, activation='softmax')(dr_steps)\n    \n    model = Model(inputs=[in_lay], outputs=[out_layer])\n    model.compile(optimizer='adam', loss='categorical_crossentropy', metrics=['categorical_accuracy','AUC'])\n    \n    return model\n\n# 使用 create_attention_model 函數來創建注意力模型\nattention_model = create_deeper_attention_model((256, 256, 3), 5)  # 有5個類別\nattention_model.summary()\n\n# 繪製模型架構圖\nplot_model(attention_model, show_shapes=True, show_layer_names=True, dpi=60, to_file='model_architecture.png')\n","metadata":{"execution":{"iopub.status.busy":"2024-10-18T07:21:49.080997Z","iopub.execute_input":"2024-10-18T07:21:49.081796Z","iopub.status.idle":"2024-10-18T07:21:53.433109Z","shell.execute_reply.started":"2024-10-18T07:21:49.081756Z","shell.execute_reply":"2024-10-18T07:21:53.432273Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":" **Model Training with Stratified K-Fold Cross-Validation and Epoch Incrementing**","metadata":{}},{"cell_type":"code","source":"from tensorflow.keras.callbacks import ModelCheckpoint, EarlyStopping, ReduceLROnPlateau\nimport matplotlib.pyplot as plt\nimport datetime\nimport numpy as np\nfrom sklearn.model_selection import StratifiedKFold\nfrom sklearn.metrics import classification_report, confusion_matrix\nimport itertools\n\n# Initialize lists to store fold results\nall_train_accu = []\nall_val_accu = []\nall_train_loss = []\nall_val_loss = []\nall_train_auc = []\nall_val_auc = []\nall_history = {'categorical_accuracy': [], 'val_categorical_accuracy': [], \n               'loss': [], 'val_loss': [], 'auc': [], 'val_auc': []}\n\n# Balance data function\ndef balance_data(class_size, df):\n    train_df = df.groupby(['level']).apply(lambda x: x.sample(class_size, replace=True)).reset_index(drop=True)\n    train_df = train_df.sample(frac=1).reset_index(drop=True)\n    print('New Data Size:', train_df.shape[0], 'Old Size:', df.shape[0])\n    train_df['level'].hist(figsize=(10, 5))\n    return train_df\n\n# Initialize StratifiedKFold\nn_splits = 10\nskf = StratifiedKFold(n_splits=n_splits, shuffle=True, random_state=42)\n\n# Initialize cumulative confusion matrix\ncumulative_cm = None\n\n# Iterate through each fold\nfor fold_index, (train_index, val_index) in enumerate(skf.split(df, df['level'])):\n    print(f\"Training fold {fold_index + 1}/{n_splits}\")\n\n    # Split data into training and validation sets\n    train_df, val_df = df.iloc[train_index], df.iloc[val_index]\n\n    # Ensure balanced class distribution\n    max_class_size = train_df.pivot_table(index='level', aggfunc=len).max().max()\n    train_df = balance_data(max_class_size, train_df)\n\n    # ImageDataGenerator for training and validation\n    train_datagen = ImageDataGenerator(rescale=1.0/255, shear_range=0.2, horizontal_flip=True, zoom_range=0.2)\n    test_datagen = ImageDataGenerator(rescale=1.0/255)\n\n    x_train = train_datagen.flow_from_dataframe(train_df, directory=\".\", x_col=\"path\", y_col=\"level\", target_size=(256, 256), batch_size=32, class_mode='categorical')\n    x_test = test_datagen.flow_from_dataframe(val_df, x_col=\"path\", y_col=\"level\", directory=\".\", target_size=(256, 256), batch_size=32, class_mode='categorical')\n\n    # Initialize cumulative confusion matrix\n    if cumulative_cm is None:\n        cumulative_cm = np.zeros((len(x_train.class_indices), len(x_train.class_indices)), dtype=int)\n\n    # Create and train the model\n    attention_model = create_deeper_attention_model((256, 256, 3), len(x_train.class_indices))\n    current_time = datetime.datetime.now().strftime(\"%Y%m%d-%H%M%S\")\n    filepath = f\"dr-detector-fold{fold_index + 1}-{current_time}-{{epoch:02d}}-{{val_categorical_accuracy:.2f}}.hdf5\"\n\n    # Callbacks\n    checkpoint = ModelCheckpoint('cnn_model_best.hdf5', monitor=\"val_categorical_accuracy\", verbose=1, save_best_only=True, mode=\"max\")\n    earlystop = EarlyStopping(monitor='val_categorical_accuracy', verbose=1, min_delta=0, patience=15, restore_best_weights=True)\n    reduce_lr = ReduceLROnPlateau(monitor='val_loss', verbose=1, factor=0.1, patience=7, min_delta=0.0001, cooldown=0, min_lr=1e-7)\n    callbacks = [checkpoint, earlystop, reduce_lr]\n\n    # Train the model\n    history = attention_model.fit(x_train, steps_per_epoch=x_train.samples // 32, epochs=5, validation_data=x_test, validation_steps=x_test.samples // 32, callbacks=callbacks)\n\n    # Evaluate the model\n    result_train = attention_model.evaluate(x_train)\n    result_test = attention_model.evaluate(x_test)\n    train_loss, train_accu, train_auc = result_train\n    test_loss, test_accu, test_auc = result_test\n\n    # Store results\n    all_train_accu.append(train_accu)\n    all_val_accu.append(test_accu)\n    all_train_loss.append(train_loss)\n    all_val_loss.append(test_loss)\n    all_train_auc.append(train_auc)\n    all_val_auc.append(test_auc)\n\n    all_history['categorical_accuracy'].append(history.history['categorical_accuracy'])\n    all_history['val_categorical_accuracy'].append(history.history['val_categorical_accuracy'])\n    all_history['loss'].append(history.history['loss'])\n    all_history['val_loss'].append(history.history['val_loss'])\n    all_history['auc'].append(history.history['auc'])\n    all_history['val_auc'].append(history.history['val_auc'])\n\n    # Generate predictions and compute confusion matrix\n    y_pred_train = attention_model.predict(x_train)\n    y_pred_train = np.argmax(y_pred_train, axis=1)\n    y_true_train = x_train.classes\n    cm_train = confusion_matrix(y_true_train, y_pred_train)\n    cumulative_cm += cm_train\n\n    print(f\"Final training accuracy for fold {fold_index + 1} = {train_accu * 100:.2f}%, validation accuracy = {test_accu * 100:.2f}%\")\n    print(f\"Final training loss = {train_loss:.2f}, validation loss = {test_loss:.2f}\")\n    print(f\"Final training AUC = {train_auc:.2f}, validation AUC = {test_auc:.2f}\")\n\n# Calculate mean and standard deviation for all metrics\nmean_train_accu = np.mean(all_train_accu)\nmean_val_accu = np.mean(all_val_accu)\nmean_train_loss = np.mean(all_train_loss)\nmean_val_loss = np.mean(all_val_loss)\nmean_train_auc = np.mean(all_train_auc)\nmean_val_auc = np.mean(all_val_auc)\n\nstd_train_accu = np.std(all_train_accu)\nstd_val_accu = np.std(all_val_accu)\nstd_train_loss = np.std(all_train_loss)\nstd_val_loss = np.std(all_val_loss)\nstd_train_auc = np.std(all_train_auc)\nstd_val_auc = np.std(all_val_auc)\n\n# Print overall results\nprint(\"\\nOverall Results:\")\nprint(f\"Mean training accuracy = {mean_train_accu * 100:.2f}% ± {std_train_accu * 100:.2f}%\")\nprint(f\"Mean validation accuracy = {mean_val_accu * 100:.2f}% ± {std_val_accu * 100:.2f}%\")\nprint(f\"Mean training loss = {mean_train_loss:.2f} ± {std_train_loss:.2f}\")\nprint(f\"Mean validation loss = {mean_val_loss:.2f} ± {std_val_loss:.2f}\")\nprint(f\"Mean training AUC = {mean_train_auc:.2f} ± {std_train_auc:.2f}\")\nprint(f\"Mean validation AUC = {mean_val_auc:.2f} ± {std_val_auc:.2f}\")\n\n# Visualize mean results over all folds\nmean_history = {key: np.mean(np.array(all_history[key]), axis=0) for key in all_history.keys()}\nplt.figure(figsize=(18, 6))\n\nplt.subplot(1, 3, 1)\nplt.plot(mean_history['categorical_accuracy'], label='Train Accuracy')\nplt.plot(mean_history['val_categorical_accuracy'], label='Test Accuracy')\nplt.title('Average Model Accuracy')\nplt.xlabel('Epochs')\nplt.ylabel('Accuracy')\nplt.legend(loc='upper left')\n\nplt.subplot(1, 3, 2)\nplt.plot(mean_history['loss'], label='Train Loss')\nplt.plot(mean_history['val_loss'], label='Test Loss')\nplt.title('Average Model Loss')\nplt.xlabel('Epochs')\nplt.ylabel('Loss')\nplt.legend(loc='upper left')\n\nplt.subplot(1, 3, 3)\nplt.plot(mean_history['auc'], label='Train AUC')\nplt.plot(mean_history['val_auc'], label='Test AUC')\nplt.title('Average Model AUC')\nplt.xlabel('Epochs')\nplt.ylabel('AUC')\nplt.legend(loc='upper left')\n\nplt.show()\n","metadata":{"execution":{"iopub.status.busy":"2024-10-18T07:21:57.924255Z","iopub.execute_input":"2024-10-18T07:21:57.924627Z","iopub.status.idle":"2024-10-18T14:21:20.403867Z","shell.execute_reply.started":"2024-10-18T07:21:57.924569Z","shell.execute_reply":"2024-10-18T14:21:20.403083Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"markdown","source":"## CNN Model with learning rate 0.0001 và ReduceLROnPlateau","metadata":{}},{"cell_type":"code","source":"from tensorflow.keras.applications import InceptionV3\nfrom tensorflow.keras.layers import GlobalAveragePooling2D, Dense, Dropout, Input, Conv2D, multiply, Lambda, BatchNormalization\nfrom tensorflow.keras.models import Model\nfrom tensorflow.keras.optimizers import Adam\nfrom tensorflow.keras.regularizers import l2\nimport numpy as np\nfrom tensorflow.keras.utils import plot_model\n\ndef create_deeper_attention_model(input_shape, num_classes):\n    in_lay = Input(input_shape)\n    base_pretrained_model = InceptionV3(input_shape=input_shape, include_top=False, weights='imagenet')\n    base_pretrained_model.trainable = False  # 保持大部分預訓練層凍結\n    pt_depth = base_pretrained_model.output_shape[-1]\n    pt_features = base_pretrained_model(in_lay)\n    \n    bn_features = BatchNormalization()(pt_features)\n    \n    # 注意力機制並加上正則化\n    attn_layer = Conv2D(128, kernel_size=(3,3), padding='same', activation='relu', kernel_regularizer=l2(0.001))(Dropout(0.5)(bn_features))\n    attn_layer = Conv2D(64, kernel_size=(3,3), padding='same', activation='relu', kernel_regularizer=l2(0.001))(attn_layer)\n    attn_layer = Conv2D(32, kernel_size=(3,3), padding='same', activation='relu', kernel_regularizer=l2(0.001))(attn_layer)\n    attn_layer = Conv2D(1, kernel_size=(1,1), padding='valid', activation='sigmoid')(attn_layer)\n    \n    up_c2_w = np.ones((1, 1, 1, pt_depth))\n    up_c2 = Conv2D(pt_depth, kernel_size=(1,1), padding='same', activation='linear', use_bias=False, weights=[up_c2_w])\n    up_c2.trainable = False\n    attn_layer = up_c2(attn_layer)\n    \n    mask_features = multiply([attn_layer, bn_features])\n    gap_features = GlobalAveragePooling2D()(mask_features)\n    gap_mask = GlobalAveragePooling2D()(attn_layer)\n    \n    gap = Lambda(lambda x: x[0]/x[1], name='RescaleGAP')([gap_features, gap_mask])\n    gap_dr = Dropout(0.5)(gap)  # 增加Dropout比例\n    dr_steps = Dropout(0.5)(Dense(256, activation='relu', kernel_regularizer=l2(0.001))(gap_dr))  # 增加全連接層的單元數\n    out_layer = Dense(num_classes, activation='softmax')(dr_steps)\n    \n    model = Model(inputs=[in_lay], outputs=[out_layer])\n    learning_rate = 0.0001  # Ví dụ, bạn có thể thay đổi giá trị này\n\n# Compile the model with custom learning rate\n    model.compile(optimizer=Adam(learning_rate=learning_rate), \n              loss='categorical_crossentropy', \n              metrics=['categorical_accuracy', 'AUC'])\n    \n    return model\n\n# 使用 create_attention_model 函數來創建注意力模型\nattention_model = create_deeper_attention_model((256, 256, 3), 5)  # 有5個類別\nattention_model.summary()\n\n# 繪製模型架構圖\nplot_model(attention_model, show_shapes=True, show_layer_names=True, dpi=60, to_file='model_architecture.png')\n","metadata":{"execution":{"iopub.status.busy":"2024-10-18T00:29:21.992278Z","iopub.execute_input":"2024-10-18T00:29:21.993023Z","iopub.status.idle":"2024-10-18T00:29:26.216944Z","shell.execute_reply.started":"2024-10-18T00:29:21.992985Z","shell.execute_reply":"2024-10-18T00:29:26.21604Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from tensorflow.keras.callbacks import ModelCheckpoint, EarlyStopping, ReduceLROnPlateau\nimport matplotlib.pyplot as plt\nimport datetime\nimport numpy as np\nfrom sklearn.model_selection import StratifiedKFold\nfrom sklearn.metrics import classification_report, confusion_matrix\nimport itertools\n\n# Initialize lists to store fold results\nall_train_accu = []\nall_val_accu = []\nall_train_loss = []\nall_val_loss = []\nall_train_auc = []\nall_val_auc = []\nall_history = {'categorical_accuracy': [], 'val_categorical_accuracy': [], \n               'loss': [], 'val_loss': [], 'auc': [], 'val_auc': []}\n\n# Balance data function\ndef balance_data(class_size, df):\n    train_df = df.groupby(['level']).apply(lambda x: x.sample(class_size, replace=True)).reset_index(drop=True)\n    train_df = train_df.sample(frac=1).reset_index(drop=True)\n    print('New Data Size:', train_df.shape[0], 'Old Size:', df.shape[0])\n    train_df['level'].hist(figsize=(10, 5))\n    return train_df\n\n# Initialize StratifiedKFold\nn_splits = 10\nskf = StratifiedKFold(n_splits=n_splits, shuffle=True, random_state=42)\n\n# Initialize cumulative confusion matrix\ncumulative_cm = None\n\n# Iterate through each fold\nfor fold_index, (train_index, val_index) in enumerate(skf.split(df, df['level'])):\n    print(f\"Training fold {fold_index + 1}/{n_splits}\")\n\n    # Split data into training and validation sets\n    train_df, val_df = df.iloc[train_index], df.iloc[val_index]\n\n    # Ensure balanced class distribution\n    max_class_size = train_df.pivot_table(index='level', aggfunc=len).max().max()\n    train_df = balance_data(max_class_size, train_df)\n\n    # ImageDataGenerator for training and validation\n    train_datagen = ImageDataGenerator(rescale=1.0/255, shear_range=0.2, horizontal_flip=True, zoom_range=0.2)\n    test_datagen = ImageDataGenerator(rescale=1.0/255)\n\n    x_train = train_datagen.flow_from_dataframe(train_df, directory=\".\", x_col=\"path\", y_col=\"level\", target_size=(256, 256), batch_size=32, class_mode='categorical')\n    x_test = test_datagen.flow_from_dataframe(val_df, x_col=\"path\", y_col=\"level\", directory=\".\", target_size=(256, 256), batch_size=32, class_mode='categorical')\n\n    # Initialize cumulative confusion matrix\n    if cumulative_cm is None:\n        cumulative_cm = np.zeros((len(x_train.class_indices), len(x_train.class_indices)), dtype=int)\n\n    # Create and train the model\n    attention_model = create_deeper_attention_model((256, 256, 3), len(x_train.class_indices))\n    current_time = datetime.datetime.now().strftime(\"%Y%m%d-%H%M%S\")\n    filepath = f\"dr-detector-fold{fold_index + 1}-{current_time}-{{epoch:02d}}-{{val_categorical_accuracy:.2f}}.hdf5\"\n\n    # Callbacks\n    checkpoint = ModelCheckpoint(filepath, monitor=\"val_categorical_accuracy\", verbose=1, save_best_only=True, mode=\"max\")\n    earlystop = EarlyStopping(monitor='val_categorical_accuracy', verbose=1, min_delta=0, patience=15, restore_best_weights=True)\n    reduce_lr = ReduceLROnPlateau(monitor='val_loss', verbose=1, factor=0.1, patience=5, min_delta=0.0001, cooldown=0, min_lr=1e-7)\n    callbacks = [checkpoint, earlystop, reduce_lr]\n\n    # Train the model\n    history = attention_model.fit(x_train, steps_per_epoch=x_train.samples // 64, epochs=5, validation_data=x_test, validation_steps=x_test.samples // 64, callbacks=callbacks)\n\n    # Evaluate the model\n    result_train = attention_model.evaluate(x_train)\n    result_test = attention_model.evaluate(x_test)\n    train_loss, train_accu, train_auc = result_train\n    test_loss, test_accu, test_auc = result_test\n\n    # Store results\n    all_train_accu.append(train_accu)\n    all_val_accu.append(test_accu)\n    all_train_loss.append(train_loss)\n    all_val_loss.append(test_loss)\n    all_train_auc.append(train_auc)\n    all_val_auc.append(test_auc)\n\n    all_history['categorical_accuracy'].append(history.history['categorical_accuracy'])\n    all_history['val_categorical_accuracy'].append(history.history['val_categorical_accuracy'])\n    all_history['loss'].append(history.history['loss'])\n    all_history['val_loss'].append(history.history['val_loss'])\n    all_history['auc'].append(history.history['auc'])\n    all_history['val_auc'].append(history.history['val_auc'])\n\n    # Generate predictions and compute confusion matrix\n    y_pred_train = attention_model.predict(x_train)\n    y_pred_train = np.argmax(y_pred_train, axis=1)\n    y_true_train = x_train.classes\n    cm_train = confusion_matrix(y_true_train, y_pred_train)\n    cumulative_cm += cm_train\n\n    print(f\"Final training accuracy for fold {fold_index + 1} = {train_accu * 100:.2f}%, validation accuracy = {test_accu * 100:.2f}%\")\n    print(f\"Final training loss = {train_loss:.2f}, validation loss = {test_loss:.2f}\")\n    print(f\"Final training AUC = {train_auc:.2f}, validation AUC = {test_auc:.2f}\")\n\n# Calculate mean and standard deviation for all metrics\nmean_train_accu = np.mean(all_train_accu)\nmean_val_accu = np.mean(all_val_accu)\nmean_train_loss = np.mean(all_train_loss)\nmean_val_loss = np.mean(all_val_loss)\nmean_train_auc = np.mean(all_train_auc)\nmean_val_auc = np.mean(all_val_auc)\n\nstd_train_accu = np.std(all_train_accu)\nstd_val_accu = np.std(all_val_accu)\nstd_train_loss = np.std(all_train_loss)\nstd_val_loss = np.std(all_val_loss)\nstd_train_auc = np.std(all_train_auc)\nstd_val_auc = np.std(all_val_auc)\n\n# Print overall results\nprint(\"\\nOverall Results:\")\nprint(f\"Mean training accuracy = {mean_train_accu * 100:.2f}% ± {std_train_accu * 100:.2f}%\")\nprint(f\"Mean validation accuracy = {mean_val_accu * 100:.2f}% ± {std_val_accu * 100:.2f}%\")\nprint(f\"Mean training loss = {mean_train_loss:.2f} ± {std_train_loss:.2f}\")\nprint(f\"Mean validation loss = {mean_val_loss:.2f} ± {std_val_loss:.2f}\")\nprint(f\"Mean training AUC = {mean_train_auc:.2f} ± {std_train_auc:.2f}\")\nprint(f\"Mean validation AUC = {mean_val_auc:.2f} ± {std_val_auc:.2f}\")\n\n# Visualize mean results over all folds\nmean_history = {key: np.mean(np.array(all_history[key]), axis=0) for key in all_history.keys()}\nplt.figure(figsize=(18, 6))\n\nplt.subplot(1, 3, 1)\nplt.plot(mean_history['categorical_accuracy'], label='Train Accuracy')\nplt.plot(mean_history['val_categorical_accuracy'], label='Test Accuracy')\nplt.title('Average Model Accuracy')\nplt.xlabel('Epochs')\nplt.ylabel('Accuracy')\nplt.legend(loc='upper left')\n\nplt.subplot(1, 3, 2)\nplt.plot(mean_history['loss'], label='Train Loss')\nplt.plot(mean_history['val_loss'], label='Test Loss')\nplt.title('Average Model Loss')\nplt.xlabel('Epochs')\nplt.ylabel('Loss')\nplt.legend(loc='upper left')\n\nplt.subplot(1, 3, 3)\nplt.plot(mean_history['auc'], label='Train AUC')\nplt.plot(mean_history['val_auc'], label='Test AUC')\nplt.title('Average Model AUC')\nplt.xlabel('Epochs')\nplt.ylabel('AUC')\nplt.legend(loc='upper left')\n\nplt.show()\n","metadata":{"execution":{"iopub.status.busy":"2024-10-18T00:29:29.963108Z","iopub.execute_input":"2024-10-18T00:29:29.963395Z","iopub.status.idle":"2024-10-18T04:54:14.213428Z","shell.execute_reply.started":"2024-10-18T00:29:29.963361Z","shell.execute_reply":"2024-10-18T04:54:14.212593Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"","metadata":{},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import tensorflow as tf\nprint(\"Num GPUs Available: \", len(tf.config.experimental.list_physical_devices('GPU')))\n","metadata":{"execution":{"iopub.status.busy":"2024-10-18T00:26:09.588516Z","iopub.execute_input":"2024-10-18T00:26:09.589188Z","iopub.status.idle":"2024-10-18T00:26:09.594086Z","shell.execute_reply.started":"2024-10-18T00:26:09.589149Z","shell.execute_reply":"2024-10-18T00:26:09.593301Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"import tensorflow as tf\ngpus = tf.config.experimental.list_physical_devices('GPU')\nif gpus:\n    try:\n        # Kích hoạt memory growth cho mỗi GPU\n        for gpu in gpus:\n            tf.config.experimental.set_memory_growth(gpu, True)\n    except RuntimeError as e:\n        print(e)\n","metadata":{"execution":{"iopub.status.busy":"2024-10-18T00:27:08.39476Z","iopub.execute_input":"2024-10-18T00:27:08.395489Z","iopub.status.idle":"2024-10-18T00:27:08.401753Z","shell.execute_reply.started":"2024-10-18T00:27:08.39545Z","shell.execute_reply":"2024-10-18T00:27:08.400895Z"},"trusted":true},"execution_count":null,"outputs":[]},{"cell_type":"code","source":"from matplotlib.colors import LinearSegmentedColormap  # Add this import\n# Compute and visualize the average confusion matrix\naverage_cm = cumulative_cm / n_splits\ntarget_names = list(x_train.class_indices.keys())\n\ncolors = [(0.8, 1, 0.8), (0.7, 0.9, 1)]\ncmap = LinearSegmentedColormap.from_list(\"custom_cmap\", colors)\n\nplt.figure(figsize=(8, 8))\nplt.imshow(average_cm, interpolation='nearest', cmap=cmap)\nplt.title('Average Confusion Matrix')\nplt.colorbar()\ntick_marks = np.arange(len(target_names))\nplt.xticks(tick_marks, target_names, rotation=90)\nplt.yticks(tick_marks, target_names)\n\nfor i, j in itertools.product(range(average_cm.shape[0]), range(average_cm.shape[1])):\n    plt.text(j, i, format(average_cm[i, j], '.2f'), horizontalalignment=\"center\", color=\"black\")\n\nplt.tight_layout()\nplt.ylabel('True label')\nplt.xlabel('Predicted label')\nplt.show()\n\n# Completion message\nprint(\"All folds completed.\")\n","metadata":{"execution":{"iopub.status.busy":"2024-10-18T14:36:46.133676Z","iopub.execute_input":"2024-10-18T14:36:46.134458Z","iopub.status.idle":"2024-10-18T14:36:46.500348Z","shell.execute_reply.started":"2024-10-18T14:36:46.134414Z","shell.execute_reply":"2024-10-18T14:36:46.499536Z"},"trusted":true},"execution_count":null,"outputs":[]}]}