{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.11","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"}],"dockerImageVersionId":31041,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import torch\n\ntorch.cuda.is_available()","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-05-28T13:34:33.106581Z","iopub.execute_input":"2025-05-28T13:34:33.107117Z","iopub.status.idle":"2025-05-28T13:34:37.380556Z","shell.execute_reply.started":"2025-05-28T13:34:33.107091Z","shell.execute_reply":"2025-05-28T13:34:37.379757Z"}},"outputs":[],"execution_count":null},{"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\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-28T13:34:37.382127Z","iopub.execute_input":"2025-05-28T13:34:37.382490Z","iopub.status.idle":"2025-05-28T13:34:38.039472Z","shell.execute_reply.started":"2025-05-28T13:34:37.382462Z","shell.execute_reply":"2025-05-28T13:34:38.038597Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def 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, [256, 256])\n    image = tf.cast(image, tf.float32) / 255.0  # chắc chắn dtype float32\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","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-28T13:34:38.040486Z","iopub.execute_input":"2025-05-28T13:34:38.041424Z","iopub.status.idle":"2025-05-28T13:34:38.046856Z","shell.execute_reply.started":"2025-05-28T13:34:38.041401Z","shell.execute_reply":"2025-05-28T13:34:38.046176Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"!unzip -o ../input/diabetic-retinopathy-detection/trainLabels.csv.zip\ntrainLabels = pd.read_csv(\"./trainLabels.csv\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-28T13:34:38.048052Z","iopub.execute_input":"2025-05-28T13:34:38.048390Z","iopub.status.idle":"2025-05-28T13:34:38.253734Z","shell.execute_reply.started":"2025-05-28T13:34:38.048369Z","shell.execute_reply":"2025-05-28T13:34:38.252889Z"}},"outputs":[],"execution_count":null},{"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","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-28T13:34:38.254794Z","iopub.execute_input":"2025-05-28T13:34:38.255052Z","iopub.status.idle":"2025-05-28T13:35:00.784778Z","shell.execute_reply.started":"2025-05-28T13:34:38.255024Z","shell.execute_reply":"2025-05-28T13:35:00.783513Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"file_paths = glob(\"./data/train_11/*.jpeg\")\n\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\nprint(f\"Number of image files: {len(file_paths)}\")\nprint(f\"Number of filtered labels: {len(filtered_labels)}\")\n\ndataset = load_dataset(file_paths, filtered_labels)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-28T13:35:00.786894Z","iopub.execute_input":"2025-05-28T13:35:00.787183Z","iopub.status.idle":"2025-05-28T13:35:01.141680Z","shell.execute_reply.started":"2025-05-28T13:35:00.787152Z","shell.execute_reply":"2025-05-28T13:35:01.138790Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def show_batch(image_batch, label_batch):\n    plt.figure(figsize=(10, 10))\n    for n in range(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  \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)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-28T13:35:01.142729Z","iopub.execute_input":"2025-05-28T13:35:01.143061Z","iopub.status.idle":"2025-05-28T13:35:19.943979Z","shell.execute_reply.started":"2025-05-28T13:35:01.143030Z","shell.execute_reply":"2025-05-28T13:35:19.943073Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\n\nbase_image_dir = os.path.join('.', 'data/train_11')\n\ntrainLabels['path'] = trainLabels['image'].map(lambda x: os.path.join(base_image_dir, '{}.jpeg'.format(x)))\ntrainLabels['exists'] = trainLabels['path'].map(os.path.exists)\ndf = trainLabels[trainLabels['exists']]\ndf = df.drop(columns=['image', 'exists'])\ndf = df.sample(frac=1).reset_index(drop=True)\ndf['level'] = df['level'].astype(str)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-28T13:35:19.945031Z","iopub.execute_input":"2025-05-28T13:35:19.945293Z","iopub.status.idle":"2025-05-28T13:35:20.204419Z","shell.execute_reply.started":"2025-05-28T13:35:19.945274Z","shell.execute_reply":"2025-05-28T13:35:20.203752Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import tensorflow as tf\nfrom tensorflow.keras.preprocessing.image import ImageDataGenerator\nimport os\n\n# Thư mục ảnh gốc\nbase_image_dir = './data/train_11/'\n\n# Tạo thư mục lưu ảnh tăng cường\naug_dir = './data/augmented'\nos.makedirs(aug_dir, exist_ok=True)\n\n# Đếm số ảnh mỗi nhãn\nlevels = df['level'].value_counts()\nmax_count = levels.max()\n\n# Thiết lập ImageDataGenerator với augmentation cơ bản\ndatagen = ImageDataGenerator(\n    rotation_range=20,\n    width_shift_range=0.1,\n    height_shift_range=0.1,\n    zoom_range=0.1,\n    horizontal_flip=True,\n    brightness_range=[0.8, 1.2],\n    fill_mode='nearest'\n)\n\n# Duyệt từng nhãn thiếu ảnh\nfor level, count in levels.items():\n    if count >= max_count:\n        continue  # Bỏ qua nhãn đã đủ dữ liệu\n\n    n_to_augment = max_count - count  # số ảnh cần tạo thêm\n\n    # Lấy danh sách ảnh của nhãn đó\n    img_paths = df[df['level'] == level]['path'].tolist()\n\n    # Thư mục con lưu ảnh tăng cường theo nhãn\n    save_dir = os.path.join(aug_dir, f'level_{level}')\n    os.makedirs(save_dir, exist_ok=True)\n\n    augmented_count = 0\n    i = 0\n\n    # Lặp sinh ảnh tăng cường cho tới đủ số lượng cần thiết\n    while augmented_count < n_to_augment:\n        img_path = img_paths[i % len(img_paths)]\n        img = tf.keras.preprocessing.image.load_img(img_path)\n        x = tf.keras.preprocessing.image.img_to_array(img)\n        x = x.reshape((1,) + x.shape)  # reshape batch size 1\n\n        # Tạo 1 ảnh tăng cường/lần, lưu vào thư mục tương ứng\n        for batch in datagen.flow(x, batch_size=1,\n                                  save_to_dir=save_dir,\n                                  save_prefix='aug',\n                                  save_format='jpeg'):\n            augmented_count += 1\n            if augmented_count >= n_to_augment:\n                break\n        i += 1\n\nprint(\"Augmentation completed.\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-28T13:35:20.205403Z","iopub.execute_input":"2025-05-28T13:35:20.205636Z","iopub.status.idle":"2025-05-28T14:59:03.594267Z","shell.execute_reply.started":"2025-05-28T13:35:20.205619Z","shell.execute_reply":"2025-05-28T14:59:03.593454Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Thư mục ảnh gốc và ảnh tăng cường\nbase_dir_original = './data/train_11'\nbase_dir_aug = './data/augmented'\n\n# 1. Lấy dữ liệu ảnh gốc từ DataFrame df đã có (đã lọc ảnh tồn tại và nhãn)\ndf_original = df.copy()  # df bạn đã tạo trước đó với cột 'path' và 'level'\n\n# 2. Lấy dữ liệu ảnh tăng cường từ thư mục augmented\naugmented_data = []\n\n# Lặp qua các thư mục con tương ứng với từng level\nfor level_dir in os.listdir(base_dir_aug):\n    level_path = os.path.join(base_dir_aug, level_dir)\n    if not os.path.isdir(level_path):\n        continue\n    level_num = int(level_dir.split('_')[-1])  # Lấy số level từ tên thư mục như 'level_2' -> 2\n\n    # Lấy tất cả file ảnh trong thư mục con\n    for file_name in os.listdir(level_path):\n        if file_name.lower().endswith(('.jpeg', '.jpg', '.png')):\n            img_path = os.path.join(level_path, file_name)\n            augmented_data.append({'path': img_path, 'level': str(level_num)})\n\n# Tạo DataFrame cho dữ liệu ảnh tăng cường\ndf_augmented = pd.DataFrame(augmented_data)\n\n# 3. Kết hợp 2 DataFrame lại\ndf_full = pd.concat([df_original, df_augmented], ignore_index=True)\n\n# Kiểm tra số lượng ảnh mỗi level trong bộ dữ liệu kết hợp\nprint(df_full['level'].value_counts())\n\n# Hiển thị 5 dòng đầu tiên để kiểm tra\nprint(df_full.head())","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-28T14:59:57.309734Z","iopub.execute_input":"2025-05-28T14:59:57.310321Z","iopub.status.idle":"2025-05-28T14:59:57.336683Z","shell.execute_reply.started":"2025-05-28T14:59:57.310292Z","shell.execute_reply":"2025-05-28T14:59:57.335916Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import matplotlib.pyplot as plt\n\n# In số lượng sample trong dataset kết hợp\nprint(f\"Total number of samples in combined dataset: {len(df_full)}\")\n\n# Đếm số lượng sample của từng lớp\nlevel_counts = df_full['level'].value_counts().sort_index()\n\n# Vẽ biểu đồ cột\nplt.figure(figsize=(8,5))\nlevel_counts.plot(kind='bar')\nplt.title('Number of samples per class after combining datasets')\nplt.xlabel('Class Level')\nplt.ylabel('Number of samples')\nplt.xticks(rotation=0)\nplt.show()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-28T15:00:01.653872Z","iopub.execute_input":"2025-05-28T15:00:01.655133Z","iopub.status.idle":"2025-05-28T15:00:01.925549Z","shell.execute_reply.started":"2025-05-28T15:00:01.655079Z","shell.execute_reply":"2025-05-28T15:00:01.924736Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import tensorflow as tf\nimport os\nfrom tqdm import tqdm  # Thanh tiến trình\n\ndef gaussian_kernel_3ch(size: int, sigma: float):\n    # Tạo kernel Gaussian 2D chuẩn\n    x = tf.range(-size // 2 + 1, size // 2 + 1, dtype=tf.float32)\n    y = tf.range(-size // 2 + 1, size // 2 + 1, dtype=tf.float32)\n    X, Y = tf.meshgrid(x, y)\n    kernel_2d = tf.exp(-(X**2 + Y**2) / (2 * sigma ** 2))\n    kernel_2d /= tf.reduce_sum(kernel_2d)  # chuẩn hóa\n\n    # reshape kernel thành [size, size, 1, 1]\n    kernel_2d = tf.reshape(kernel_2d, [size, size, 1, 1])\n\n    # tạo ma trận đơn vị 3x3\n    identity = tf.eye(3, dtype=tf.float32)  # shape [3, 3]\n\n    # nhân kernel gaussian với ma trận đơn vị để có kernel 4D [size, size, 3, 3]\n    kernel = kernel_2d * identity\n\n    return kernel\n\ndef denoise_gaussian_filter(image, kernel_size=5, sigma=1.0):\n    # image: tensor [H, W, 3], float32 trong [0,1]\n    kernel = gaussian_kernel_3ch(kernel_size, sigma)  # shape [size, size, 3, 3]\n    image = tf.expand_dims(image, axis=0)  # Thêm batch dim: [1, H, W, 3]\n    filtered = tf.nn.conv2d(image, kernel, strides=1, padding='SAME')\n    filtered = tf.squeeze(filtered, axis=0)  # Bỏ batch dim -> [H, W, 3]\n    filtered = tf.clip_by_value(filtered, 0.0, 1.0)  # Giới hạn giá trị\n    return filtered\n\ndef load_and_preprocess_image(image_path):\n    # Đọc file ảnh, decode, resize, chuẩn hóa về [0,1]\n    image = tf.io.read_file(image_path)\n    image = tf.image.decode_jpeg(image, channels=3)\n    image = tf.image.resize(image, [256, 256])\n    image = tf.cast(image, tf.float32) / 255.0\n    return image\n\ndef save_image(image_tensor, save_path):\n    # Chuyển ảnh float32 [0,1] thành uint8 [0,255], encode jpeg rồi lưu file\n    image_uint8 = tf.image.convert_image_dtype(image_tensor, dtype=tf.uint8)\n    encoded = tf.io.encode_jpeg(image_uint8)\n    tf.io.write_file(save_path, encoded)\n\n# Thư mục lưu ảnh đã khử nhiễu\ndenoised_base_dir = './data/denoised'\nos.makedirs(denoised_base_dir, exist_ok=True)\n\n# Duyệt toàn bộ ảnh trong df_full (DataFrame bạn đã tạo, gồm cột 'path' và 'level')\nfor idx, row in tqdm(df_full.iterrows(), total=len(df_full)):\n    img_path = row['path']\n    level = row['level']\n\n    # Tạo thư mục con theo nhãn level nếu chưa có\n    save_dir = os.path.join(denoised_base_dir, f'level_{level}')\n    os.makedirs(save_dir, exist_ok=True)\n\n    filename = os.path.basename(img_path)\n    save_path = os.path.join(save_dir, filename)\n\n    # Load ảnh, khử nhiễu và lưu ảnh\n    image = load_and_preprocess_image(img_path)\n    denoised_image = denoise_gaussian_filter(image, kernel_size=5, sigma=1.0)\n    save_image(denoised_image, save_path)\n\nprint(\"Hoàn thành khử nhiễu toàn bộ dataset.\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-28T15:00:06.221435Z","iopub.execute_input":"2025-05-28T15:00:06.222103Z","iopub.status.idle":"2025-05-28T15:02:56.196838Z","shell.execute_reply.started":"2025-05-28T15:00:06.222077Z","shell.execute_reply":"2025-05-28T15:02:56.195980Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport pandas as pd\n\ndenoised_base_dir = './data/denoised'\n\ndenoised_paths = []\ndenoised_labels = []\n\nfor level in os.listdir(denoised_base_dir):\n    label = level.split('_')[-1]  # 'level_2' → '2'\n    level_dir = os.path.join(denoised_base_dir, level)\n    for fname in os.listdir(level_dir):\n        if fname.endswith(('.jpeg', '.jpg', '.png')):\n            denoised_paths.append(os.path.join(level_dir, fname))\n            denoised_labels.append(label)\n\ndf_denoised = pd.DataFrame({'path': denoised_paths, 'level': denoised_labels})\ndf_denoised = df_denoised.sample(frac=1).reset_index(drop=True)\n\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-28T15:03:07.043335Z","iopub.execute_input":"2025-05-28T15:03:07.044067Z","iopub.status.idle":"2025-05-28T15:03:07.056816Z","shell.execute_reply.started":"2025-05-28T15:03:07.044043Z","shell.execute_reply":"2025-05-28T15:03:07.055942Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from sklearn.model_selection import train_test_split\n\ndf = df_denoised.copy()\n\ndf_train, df_temp = train_test_split(\n    df, \n    test_size=0.3, \n    stratify=df['level'], \n    random_state=42\n)\n\ndf_val, df_test = train_test_split(\n    df_temp, \n    test_size=0.5, \n    stratify=df_temp['level'], \n    random_state=42\n)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-28T15:03:09.813818Z","iopub.execute_input":"2025-05-28T15:03:09.814099Z","iopub.status.idle":"2025-05-28T15:03:09.826105Z","shell.execute_reply.started":"2025-05-28T15:03:09.814077Z","shell.execute_reply":"2025-05-28T15:03:09.825427Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"Train:\", df_train['level'].value_counts(normalize=True))\nprint(\"Val:  \", df_val['level'].value_counts(normalize=True))\nprint(\"Test: \", df_test['level'].value_counts(normalize=True))\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-28T15:03:12.904922Z","iopub.execute_input":"2025-05-28T15:03:12.905245Z","iopub.status.idle":"2025-05-28T15:03:12.914385Z","shell.execute_reply.started":"2025-05-28T15:03:12.905177Z","shell.execute_reply":"2025-05-28T15:03:12.913497Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"print(\"📦 Tổng số ảnh:\")\nprint(f\"Train: {len(df_train)} ảnh\")\nprint(f\"Val:   {len(df_val)} ảnh\")\nprint(f\"Test:  {len(df_test)} ảnh\")\n\nprint(\"\\n🔍 Phân phối nhãn trong từng tập (số lượng):\")\nprint(\"Train:\")\nprint(df_train['level'].value_counts().sort_index())\nprint(\"\\nVal:\")\nprint(df_val['level'].value_counts().sort_index())\nprint(\"\\nTest:\")\nprint(df_test['level'].value_counts().sort_index())\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-28T15:03:15.261452Z","iopub.execute_input":"2025-05-28T15:03:15.262231Z","iopub.status.idle":"2025-05-28T15:03:15.271575Z","shell.execute_reply.started":"2025-05-28T15:03:15.262176Z","shell.execute_reply":"2025-05-28T15:03:15.270799Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df_train.to_csv(\"train_metadata.csv\", index=False)\ndf_val.to_csv(\"val_metadata.csv\", index=False)\ndf_test.to_csv(\"test_metadata.csv\", index=False)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-28T15:29:34.387449Z","iopub.execute_input":"2025-05-28T15:29:34.387986Z","iopub.status.idle":"2025-05-28T15:29:34.404775Z","shell.execute_reply.started":"2025-05-28T15:29:34.387965Z","shell.execute_reply":"2025-05-28T15:29:34.403988Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import numpy as np\nimport tensorflow as tf\nfrom tensorflow.keras import layers, models, optimizers\nimport random\nfrom collections import deque\n\n# --- Cấu hình ---\nnum_classes = 5\ninput_shape = (256, 256, 3)\nlearning_rate = 1e-4\ngamma = 0.99\nepsilon = 1.0\nepsilon_min = 0.1\nepsilon_decay = 0.995\nbatch_size = 8\nmemory_capacity = 10000\n\n# --- Mạng Q-network ---\ndef create_q_network():\n    model = models.Sequential([\n        layers.Conv2D(32, 3, activation='relu', input_shape=input_shape),\n        layers.MaxPooling2D(2),\n        layers.Conv2D(64, 3, activation='relu'),\n        layers.MaxPooling2D(2),\n        layers.Conv2D(128, 3, activation='relu'),\n        layers.MaxPooling2D(2),\n        layers.Flatten(),\n        layers.Dense(256, activation='relu'),\n        layers.Dense(num_classes)\n    ])\n    model.compile(optimizer=optimizers.Adam(learning_rate), loss='mse')\n    return model\n\n# --- Bộ nhớ replay buffer ---\nclass ReplayBuffer:\n    def __init__(self, capacity):\n        self.memory = deque(maxlen=capacity)\n\n    def add(self, state, action, reward, next_state, done):\n        self.memory.append((state, action, reward, next_state, done))\n\n    def sample(self, batch_size):\n        return random.sample(self.memory, batch_size)\n\n    def __len__(self):\n        return len(self.memory)\n\n# --- Môi trường giả lập ---\nclass RetinopathyEnv:\n    def __init__(self, df):\n        self.df = df.reset_index(drop=True)\n        self.index = 0\n\n    def reset(self):\n        self.index = 0\n        return self.get_state()\n\n    def get_state(self):\n        img_path = self.df.loc[self.index, 'path']\n        img = tf.keras.preprocessing.image.load_img(img_path, target_size=input_shape[:2])\n        img = tf.keras.preprocessing.image.img_to_array(img) / 255.0\n        return img\n\n    def step(self, action):\n        true_label = int(self.df.loc[self.index, 'level'])\n        reward = 1 if action == true_label else -1\n        done = (self.index == len(self.df) - 1)\n        self.index += 1\n        next_state = self.get_state() if not done else None\n        return next_state, reward, done\n\n# --- DQN Agent ---\nclass DQNAgent:\n    def __init__(self):\n        self.q_network = create_q_network()\n        self.target_network = create_q_network()\n        self.update_target_network()\n\n        self.memory = ReplayBuffer(memory_capacity)\n        self.epsilon = epsilon\n\n    def update_target_network(self):\n        self.target_network.set_weights(self.q_network.get_weights())\n\n    def act(self, state):\n        if np.random.rand() < self.epsilon:\n            return np.random.randint(num_classes)\n        q_values = self.q_network.predict(state[np.newaxis], verbose=0)[0]\n        return np.argmax(q_values)\n\n    def remember(self, state, action, reward, next_state, done):\n        self.memory.add(state, action, reward, next_state, done)\n\n    def replay(self):\n        if len(self.memory) < batch_size:\n            return\n        minibatch = self.memory.sample(batch_size)\n\n        states = np.array([m[0] for m in minibatch])\n        actions = np.array([m[1] for m in minibatch])\n        rewards = np.array([m[2] for m in minibatch])\n        next_states = np.array([m[3] if m[3] is not None else np.zeros(input_shape) for m in minibatch])\n        dones = np.array([m[4] for m in minibatch])\n\n        q_next = self.target_network.predict(next_states, verbose=0)\n        q_target = self.q_network.predict(states, verbose=0)\n\n        for i in range(batch_size):\n            if dones[i]:\n                q_target[i][actions[i]] = rewards[i]\n            else:\n                q_target[i][actions[i]] = rewards[i] + gamma * np.amax(q_next[i])\n\n        self.q_network.train_on_batch(states, q_target)\n\n        if self.epsilon > epsilon_min:\n            self.epsilon *= epsilon_decay\n\n# --- Huấn luyện DQN với lưu mô hình tốt nhất ---\ndef train_dqn(agent, env, episodes=10, save_path='best_dqn_model.h5'):\n    best_reward = -np.inf  # Khởi tạo giá trị reward tốt nhất rất thấp\n    for e in range(episodes):\n        state = env.reset()\n        total_reward = 0\n        done = False\n        step = 0\n        while not done:\n            action = agent.act(state)\n            next_state, reward, done = env.step(action)\n            agent.remember(state, action, reward, next_state, done)\n            agent.replay()\n            state = next_state\n            total_reward += reward\n            step += 1\n        agent.update_target_network()\n        print(f\"Episode {e+1}/{episodes} - Total reward: {total_reward} - Steps: {step} - Epsilon: {agent.epsilon:.3f}\")\n\n        # Lưu mô hình nếu total_reward cao hơn trước đó\n        if total_reward > best_reward:\n            best_reward = total_reward\n            agent.q_network.save(save_path)\n            print(f\"Best model saved with total reward: {best_reward}\")\n\n# --- Sử dụng ---\nenv = RetinopathyEnv(df_train)  # df_full chứa dữ liệu ảnh + nhãn\nagent = DQNAgent()\ntrain_dqn(agent, env, episodes=10, save_path='best_dqn_model.h5')","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-28T15:29:36.590159Z","iopub.execute_input":"2025-05-28T15:29:36.590882Z","iopub.status.idle":"2025-05-28T17:12:02.847844Z","shell.execute_reply.started":"2025-05-28T15:29:36.590859Z","shell.execute_reply":"2025-05-28T17:12:02.846980Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport shutil\n\n# Các biến chứa dataframe chia split\nsplits = {\n    'train': df_train,\n    'val': df_val,\n    'test': df_test\n}\n\n# Thư mục gốc để tạo\nbase_output_dir = './data/split_dataset'\n\nfor split_name, df_split in splits.items():\n    print(f\"Processing {split_name} set with {len(df_split)} images...\")\n\n    for idx, row in df_split.iterrows():\n        src_path = row['path']\n        label = row['level']\n\n        # Tạo thư mục đích theo split và label\n        dest_dir = os.path.join(base_output_dir, split_name, label)\n        os.makedirs(dest_dir, exist_ok=True)\n\n        # Tên file giữ nguyên\n        filename = os.path.basename(src_path)\n        dest_path = os.path.join(dest_dir, filename)\n\n        # Copy file ảnh sang thư mục đích\n        shutil.copy(src_path, dest_path)\n\nprint(\"Copy hoàn tất, cấu trúc thư mục dataset đã sẵn sàng.\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-28T15:08:51.886503Z","iopub.execute_input":"2025-05-28T15:08:51.886850Z","iopub.status.idle":"2025-05-28T15:08:52.572731Z","shell.execute_reply.started":"2025-05-28T15:08:51.886826Z","shell.execute_reply":"2025-05-28T15:08:52.572046Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import shutil\n\n# Đường dẫn thư mục gốc dataset đã chia\nbase_dir = './data/split_dataset'\n\n# Danh sách các tập\nsplits = ['train', 'val', 'test']\n\nfor split in splits:\n    folder_path = f'{base_dir}/{split}'\n    zip_path = f'/mnt/data/{split}.zip'  # Nén vào thư mục /mnt/data để dễ tải về\n    shutil.make_archive(base_name=zip_path.replace('.zip', ''), format='zip', root_dir=folder_path)\n    print(f\"Đã nén {split} thành {zip_path}\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-28T15:10:31.709015Z","iopub.execute_input":"2025-05-28T15:10:31.709334Z","iopub.status.idle":"2025-05-28T15:10:32.804090Z","shell.execute_reply.started":"2025-05-28T15:10:31.709311Z","shell.execute_reply":"2025-05-28T15:10:32.803418Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import numpy as np\nimport matplotlib.pyplot as plt\nimport seaborn as sns\nfrom sklearn.metrics import accuracy_score, precision_recall_fscore_support, confusion_matrix\nimport tensorflow as tf\n\ndef evaluate_dqn(agent, df_eval):\n    y_true = []\n    y_pred = []\n\n    for idx, row in df_eval.iterrows():\n        img_path = row['path']\n        true_label = int(row['level'])\n\n        # Load ảnh, resize và chuẩn hóa\n        img = tf.keras.preprocessing.image.load_img(img_path, target_size=(256, 256))\n        img_array = tf.keras.preprocessing.image.img_to_array(img) / 255.0\n\n        # Dự đoán nhãn\n        pred = agent.act(img_array)\n\n        y_true.append(true_label)\n        y_pred.append(pred)\n\n    # Tính các thước đo\n    accuracy = accuracy_score(y_true, y_pred)\n    precision, recall, f1, _ = precision_recall_fscore_support(y_true, y_pred, average='weighted')\n    cm = confusion_matrix(y_true, y_pred)\n\n    print(f\"Accuracy:  {accuracy:.4f}\")\n    print(f\"Precision: {precision:.4f}\")\n    print(f\"Recall:    {recall:.4f}\")\n    print(f\"F1 Score:  {f1:.4f}\")\n\n    # Vẽ confusion matrix\n    plt.figure(figsize=(8, 6))\n    sns.heatmap(cm, annot=True, fmt='d', cmap='Blues',\n                xticklabels=[str(i) for i in range(agent.q_network.output_shape[-1])],\n                yticklabels=[str(i) for i in range(agent.q_network.output_shape[-1])])\n    plt.xlabel('Predicted Label')\n    plt.ylabel('True Label')\n    plt.title('Confusion Matrix')\n    plt.show()\n\n    return accuracy, precision, recall, f1\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-28T17:12:45.311100Z","iopub.execute_input":"2025-05-28T17:12:45.311415Z","iopub.status.idle":"2025-05-28T17:12:45.319668Z","shell.execute_reply.started":"2025-05-28T17:12:45.311395Z","shell.execute_reply":"2025-05-28T17:12:45.318717Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Ví dụ đánh giá trên tập test\naccuracy, precision, recall, f1 = evaluate_dqn(agent, df_test)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-05-28T17:12:47.632181Z","iopub.execute_input":"2025-05-28T17:12:47.632759Z","iopub.status.idle":"2025-05-28T17:13:26.137898Z","shell.execute_reply.started":"2025-05-28T17:12:47.632734Z","shell.execute_reply":"2025-05-28T17:13:26.137157Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}