{"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"codemirror_mode":{"name":"ipython","version":3},"file_extension":".py","mimetype":"text/x-python","name":"python","nbconvert_exporter":"python","pygments_lexer":"ipython3","version":"3.6.6"},"kaggle":{"accelerator":"gpu","dataSources":[{"sourceId":14774,"databundleVersionId":875431,"sourceType":"competition"},{"sourceId":187731,"sourceType":"datasetVersion","datasetId":80814}],"dockerImageVersionId":28772,"isInternetEnabled":false,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import json\nimport math\nimport os\n\nimport cv2\nfrom PIL import Image\nimport numpy as np\nfrom keras import layers\nfrom keras.applications import DenseNet121\nfrom keras.callbacks import Callback, ModelCheckpoint\nfrom keras.preprocessing.image import ImageDataGenerator\nfrom keras.models import Sequential\nfrom keras.optimizers import Adam\nimport matplotlib.pyplot as plt\nimport pandas as pd\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import cohen_kappa_score, accuracy_score\nimport scipy\nimport tensorflow as tf\nfrom tqdm import tqdm\n\n%matplotlib inline","metadata":{"_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","trusted":true,"execution":{"iopub.status.busy":"2025-08-17T08:38:49.294394Z","iopub.execute_input":"2025-08-17T08:38:49.294666Z","iopub.status.idle":"2025-08-17T08:38:52.302637Z","shell.execute_reply.started":"2025-08-17T08:38:49.294631Z","shell.execute_reply":"2025-08-17T08:38:52.301945Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Set random seed for reproducibility.","metadata":{}},{"cell_type":"code","source":"np.random.seed(2019)\ntf.set_random_seed(2019)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-17T08:38:52.304665Z","iopub.execute_input":"2025-08-17T08:38:52.304884Z","iopub.status.idle":"2025-08-17T08:38:52.330730Z","shell.execute_reply.started":"2025-08-17T08:38:52.304848Z","shell.execute_reply":"2025-08-17T08:38:52.330131Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Loading & Exploration","metadata":{}},{"cell_type":"code","source":"train_df = pd.read_csv('../input/aptos2019-blindness-detection/train.csv')\ntest_df = pd.read_csv('../input/aptos2019-blindness-detection/test.csv')\nprint(train_df.shape)\nprint(test_df.shape)\ntrain_df.head()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-17T08:38:52.332175Z","iopub.execute_input":"2025-08-17T08:38:52.332456Z","iopub.status.idle":"2025-08-17T08:38:52.366001Z","shell.execute_reply.started":"2025-08-17T08:38:52.332403Z","shell.execute_reply":"2025-08-17T08:38:52.365277Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"train_df['diagnosis'].hist()\ntrain_df['diagnosis'].value_counts()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-17T08:38:52.366933Z","iopub.execute_input":"2025-08-17T08:38:52.367124Z","iopub.status.idle":"2025-08-17T08:38:52.556869Z","shell.execute_reply.started":"2025-08-17T08:38:52.367091Z","shell.execute_reply":"2025-08-17T08:38:52.556169Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Displaying some Sample Images","metadata":{}},{"cell_type":"code","source":"def display_samples(df, columns=4, rows=3):\n    fig=plt.figure(figsize=(5*columns, 4*rows))\n\n    for i in range(columns*rows):\n        image_path = df.loc[i,'id_code']\n        image_id = df.loc[i,'diagnosis']\n        img = cv2.imread(f'../input/aptos2019-blindness-detection/train_images/{image_path}.png')\n        img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)\n        \n        fig.add_subplot(rows, columns, i+1)\n        plt.title(image_id)\n        plt.imshow(img)\n    \n    plt.tight_layout()\n\ndisplay_samples(train_df)","metadata":{"_kg_hide-input":true,"trusted":true,"execution":{"iopub.status.busy":"2025-08-17T08:38:52.559275Z","iopub.execute_input":"2025-08-17T08:38:52.559506Z","iopub.status.idle":"2025-08-17T08:38:59.871137Z","shell.execute_reply.started":"2025-08-17T08:38:52.559458Z","shell.execute_reply":"2025-08-17T08:38:59.869955Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Resize Images\n\nWe will resize the images to 224x224, then create a single numpy array to hold the data.","metadata":{}},{"cell_type":"code","source":"def get_pad_width(im, new_shape, is_rgb=True):\n    pad_diff = new_shape - im.shape[0], new_shape - im.shape[1]\n    t, b = math.floor(pad_diff[0]/2), math.ceil(pad_diff[0]/2)\n    l, r = math.floor(pad_diff[1]/2), math.ceil(pad_diff[1]/2)\n    if is_rgb:\n        pad_width = ((t,b), (l,r), (0, 0))\n    else:\n        pad_width = ((t,b), (l,r))\n    return pad_width\n\ndef preprocess_image(image_path, desired_size=224):\n    im = Image.open(image_path)\n    im = im.resize((desired_size, )*2, resample=Image.LANCZOS)\n    \n    return im","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-17T08:38:59.873319Z","iopub.execute_input":"2025-08-17T08:38:59.873534Z","iopub.status.idle":"2025-08-17T08:38:59.880168Z","shell.execute_reply.started":"2025-08-17T08:38:59.873497Z","shell.execute_reply":"2025-08-17T08:38:59.879464Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"N = train_df.shape[0]\nx_train = np.empty((N, 224, 224, 3), dtype=np.uint8)\n\nfor i, image_id in enumerate(tqdm(train_df['id_code'])):\n    x_train[i, :, :, :] = preprocess_image(\n        f'../input/aptos2019-blindness-detection/train_images/{image_id}.png'\n    )","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-17T08:38:59.881282Z","iopub.execute_input":"2025-08-17T08:38:59.881476Z","iopub.status.idle":"2025-08-17T08:48:33.182227Z","shell.execute_reply.started":"2025-08-17T08:38:59.881442Z","shell.execute_reply":"2025-08-17T08:48:33.181487Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"N = test_df.shape[0]\nx_test = np.empty((N, 224, 224, 3), dtype=np.uint8)\n\nfor i, image_id in enumerate(tqdm(test_df['id_code'])):\n    x_test[i, :, :, :] = preprocess_image(\n        f'../input/aptos2019-blindness-detection/test_images/{image_id}.png'\n    )","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-17T08:48:33.183744Z","iopub.execute_input":"2025-08-17T08:48:33.184073Z","iopub.status.idle":"2025-08-17T08:50:21.524387Z","shell.execute_reply.started":"2025-08-17T08:48:33.184016Z","shell.execute_reply":"2025-08-17T08:50:21.523658Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"y_train = pd.get_dummies(train_df['diagnosis']).values\n\nprint(x_train.shape)\nprint(y_train.shape)\nprint(x_test.shape)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-17T08:50:21.525551Z","iopub.execute_input":"2025-08-17T08:50:21.525789Z","iopub.status.idle":"2025-08-17T08:50:21.533437Z","shell.execute_reply.started":"2025-08-17T08:50:21.525742Z","shell.execute_reply":"2025-08-17T08:50:21.532508Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Creating multilabels\n\nInstead of predicting a single label, we will change our target to be a multilabel problem; i.e., if the target is a certain class, then it encompasses all the classes before it. E.g. encoding a class 4 retinopathy would usually be `[0, 0, 0, 1]`, but in our case we will predict `[1, 1, 1, 1]`. For more details, please check out [Lex's kernel](https://www.kaggle.com/lextoumbourou/blindness-detection-resnet34-ordinal-targets).","metadata":{}},{"cell_type":"code","source":"y_train_multi = np.empty(y_train.shape, dtype=y_train.dtype)\ny_train_multi[:, 4] = y_train[:, 4]\n\nfor i in range(3, -1, -1):\n    y_train_multi[:, i] = np.logical_or(y_train[:, i], y_train_multi[:, i+1])\n\nprint(\"Original y_train:\", y_train.sum(axis=0))\nprint(\"Multilabel version:\", y_train_multi.sum(axis=0))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-17T08:50:21.534646Z","iopub.execute_input":"2025-08-17T08:50:21.535125Z","iopub.status.idle":"2025-08-17T08:50:21.544315Z","shell.execute_reply.started":"2025-08-17T08:50:21.534885Z","shell.execute_reply":"2025-08-17T08:50:21.543590Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"Now we can split it into a training and validation set.","metadata":{}},{"cell_type":"code","source":"x_train, x_val, y_train, y_val = train_test_split(\n    x_train, y_train_multi, \n    test_size=0.15, \n    random_state=2019\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-17T08:50:21.545486Z","iopub.execute_input":"2025-08-17T08:50:21.545672Z","iopub.status.idle":"2025-08-17T08:50:22.013935Z","shell.execute_reply.started":"2025-08-17T08:50:21.545640Z","shell.execute_reply":"2025-08-17T08:50:22.013320Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Mixup & Data Generator\n\nPlease Note: Although I show how to construct Mixup, **it is currently unused**. Please see notice at the top of the kernel.","metadata":{}},{"cell_type":"code","source":"class MixupGenerator():\n    def __init__(self, X_train, y_train, batch_size=32, alpha=0.2, shuffle=True, datagen=None):\n        self.X_train = X_train\n        self.y_train = y_train\n        self.batch_size = batch_size\n        self.alpha = alpha\n        self.shuffle = shuffle\n        self.sample_num = len(X_train)\n        self.datagen = datagen\n\n    def __call__(self):\n        while True:\n            indexes = self.__get_exploration_order()\n            itr_num = int(len(indexes) // (self.batch_size * 2))\n\n            for i in range(itr_num):\n                batch_ids = indexes[i * self.batch_size * 2:(i + 1) * self.batch_size * 2]\n                X, y = self.__data_generation(batch_ids)\n\n                yield X, y\n\n    def __get_exploration_order(self):\n        indexes = np.arange(self.sample_num)\n\n        if self.shuffle:\n            np.random.shuffle(indexes)\n\n        return indexes\n\n    def __data_generation(self, batch_ids):\n        _, h, w, c = self.X_train.shape\n        l = np.random.beta(self.alpha, self.alpha, self.batch_size)\n        X_l = l.reshape(self.batch_size, 1, 1, 1)\n        y_l = l.reshape(self.batch_size, 1)\n\n        X1 = self.X_train[batch_ids[:self.batch_size]]\n        X2 = self.X_train[batch_ids[self.batch_size:]]\n        X = X1 * X_l + X2 * (1 - X_l)\n\n        if self.datagen:\n            for i in range(self.batch_size):\n                X[i] = self.datagen.random_transform(X[i])\n                X[i] = self.datagen.standardize(X[i])\n\n        if isinstance(self.y_train, list):\n            y = []\n\n            for y_train_ in self.y_train:\n                y1 = y_train_[batch_ids[:self.batch_size]]\n                y2 = y_train_[batch_ids[self.batch_size:]]\n                y.append(y1 * y_l + y2 * (1 - y_l))\n        else:\n            y1 = self.y_train[batch_ids[:self.batch_size]]\n            y2 = self.y_train[batch_ids[self.batch_size:]]\n            y = y1 * y_l + y2 * (1 - y_l)\n\n        return X, y","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-17T08:50:22.015250Z","iopub.execute_input":"2025-08-17T08:50:22.015450Z","iopub.status.idle":"2025-08-17T08:50:22.027775Z","shell.execute_reply.started":"2025-08-17T08:50:22.015415Z","shell.execute_reply":"2025-08-17T08:50:22.026809Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"BATCH_SIZE = 32\n\ndef create_datagen():\n    return ImageDataGenerator(\n        zoom_range=0.15,  # set range for random zoom\n        # set mode for filling points outside the input boundaries\n        fill_mode='constant',\n        cval=0.,  # value used for fill_mode = \"constant\"\n        horizontal_flip=True,  # randomly flip images\n        vertical_flip=True,  # randomly flip images\n    )\n\n# Using original generator\ndata_generator = create_datagen().flow(x_train, y_train, batch_size=BATCH_SIZE, seed=2019)\n# Using Mixup\nmixup_generator = MixupGenerator(x_train, y_train, batch_size=BATCH_SIZE, alpha=0.2, datagen=create_datagen())()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-17T08:50:22.029083Z","iopub.execute_input":"2025-08-17T08:50:22.029435Z","iopub.status.idle":"2025-08-17T08:50:23.447680Z","shell.execute_reply.started":"2025-08-17T08:50:22.029382Z","shell.execute_reply":"2025-08-17T08:50:23.447000Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"true_labels = np.array([1, 0, 1, 1, 0, 1])\npred_labels = np.array([1, 0, 0, 0, 0, 1])","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-17T08:50:23.448693Z","iopub.execute_input":"2025-08-17T08:50:23.448887Z","iopub.status.idle":"2025-08-17T08:50:23.452549Z","shell.execute_reply.started":"2025-08-17T08:50:23.448853Z","shell.execute_reply":"2025-08-17T08:50:23.451796Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"accuracy_score(true_labels, pred_labels)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-17T08:50:23.453692Z","iopub.execute_input":"2025-08-17T08:50:23.453973Z","iopub.status.idle":"2025-08-17T08:50:23.465196Z","shell.execute_reply.started":"2025-08-17T08:50:23.453926Z","shell.execute_reply":"2025-08-17T08:50:23.464382Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"cohen_kappa_score(true_labels, pred_labels)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-17T08:50:23.466737Z","iopub.execute_input":"2025-08-17T08:50:23.467067Z","iopub.status.idle":"2025-08-17T08:50:23.476710Z","shell.execute_reply.started":"2025-08-17T08:50:23.467005Z","shell.execute_reply":"2025-08-17T08:50:23.475850Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"### Creating keras callback for QWK","metadata":{}},{"cell_type":"code","source":"class Metrics(Callback):\n    def on_train_begin(self, logs={}):\n        self.val_kappas = []\n\n    def on_epoch_end(self, epoch, logs={}):\n        X_val, y_val = self.validation_data[:2]\n        y_val = y_val.sum(axis=1) - 1\n        \n        y_pred = self.model.predict(X_val) > 0.5\n        y_pred = y_pred.astype(int).sum(axis=1) - 1\n\n        _val_kappa = cohen_kappa_score(\n            y_val,\n            y_pred, \n            weights='quadratic'\n        )\n\n        self.val_kappas.append(_val_kappa)\n\n        print(f\"val_kappa: {_val_kappa:.4f}\")\n        \n        if _val_kappa == max(self.val_kappas):\n            print(\"Validation Kappa has improved. Saving model.\")\n            self.model.save('model.h5')\n\n        return","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-17T08:50:23.477704Z","iopub.execute_input":"2025-08-17T08:50:23.477947Z","iopub.status.idle":"2025-08-17T08:50:23.484837Z","shell.execute_reply.started":"2025-08-17T08:50:23.477880Z","shell.execute_reply":"2025-08-17T08:50:23.484260Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Model: DenseNet-121","metadata":{}},{"cell_type":"code","source":"densenet = DenseNet121(\n    weights='../input/densenet-keras/DenseNet-BC-121-32-no-top.h5',\n    include_top=False,\n    input_shape=(224,224,3)\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-17T08:50:23.485942Z","iopub.execute_input":"2025-08-17T08:50:23.486202Z","iopub.status.idle":"2025-08-17T08:50:43.662953Z","shell.execute_reply.started":"2025-08-17T08:50:23.486153Z","shell.execute_reply":"2025-08-17T08:50:43.662163Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"def build_model():\n    model = Sequential()\n    model.add(densenet)\n    model.add(layers.GlobalAveragePooling2D())\n    model.add(layers.Dropout(0.5))\n    model.add(layers.Dense(5, activation='sigmoid'))\n    \n    model.compile(\n        loss='binary_crossentropy',\n        optimizer=Adam(lr=0.00005),\n        metrics=['accuracy']\n    )\n    \n    return model","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-17T08:50:43.664156Z","iopub.execute_input":"2025-08-17T08:50:43.664384Z","iopub.status.idle":"2025-08-17T08:50:43.673997Z","shell.execute_reply.started":"2025-08-17T08:50:43.664348Z","shell.execute_reply":"2025-08-17T08:50:43.673166Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model = build_model()\nmodel.summary()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-17T08:50:43.675184Z","iopub.execute_input":"2025-08-17T08:50:43.675471Z","iopub.status.idle":"2025-08-17T08:50:51.635744Z","shell.execute_reply.started":"2025-08-17T08:50:43.675420Z","shell.execute_reply":"2025-08-17T08:50:51.634794Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"# Training & Evaluation","metadata":{}},{"cell_type":"code","source":"kappa_metrics = Metrics()\n\nhistory = model.fit_generator(\n    data_generator,\n    steps_per_epoch=x_train.shape[0] / BATCH_SIZE,\n    epochs=15,\n    validation_data=(x_val, y_val),\n    callbacks=[kappa_metrics]\n)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-17T08:50:51.637468Z","iopub.execute_input":"2025-08-17T08:50:51.637772Z","iopub.status.idle":"2025-08-17T08:59:31.309083Z","shell.execute_reply.started":"2025-08-17T08:50:51.637716Z","shell.execute_reply":"2025-08-17T08:59:31.307689Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"with open('history.json', 'w') as f:\n    json.dump(history.history, f)\n\nhistory_df = pd.DataFrame(history.history)\nhistory_df[['loss', 'val_loss']].plot()\nhistory_df[['acc', 'val_acc']].plot()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-17T08:59:31.310284Z","iopub.execute_input":"2025-08-17T08:59:31.310508Z","iopub.status.idle":"2025-08-17T08:59:31.630937Z","shell.execute_reply.started":"2025-08-17T08:59:31.310472Z","shell.execute_reply":"2025-08-17T08:59:31.630293Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"plt.plot(kappa_metrics.val_kappas)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-17T08:59:31.632298Z","iopub.execute_input":"2025-08-17T08:59:31.632585Z","iopub.status.idle":"2025-08-17T08:59:31.767703Z","shell.execute_reply.started":"2025-08-17T08:59:31.632535Z","shell.execute_reply":"2025-08-17T08:59:31.767057Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"model.load_weights('model.h5')\ny_val_pred = model.predict(x_val)\n\ndef compute_score_inv(threshold):\n    y1 = y_val_pred > threshold\n    y1 = y1.astype(int).sum(axis=1) - 1\n    y2 = y_val.sum(axis=1) - 1\n    score = cohen_kappa_score(y1, y2, weights='quadratic')\n    \n    return 1 - score\n\nsimplex = scipy.optimize.minimize(\n    compute_score_inv, 0.5, method='nelder-mead'\n)\n\nbest_threshold = simplex['x'][0]","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-17T08:59:31.768673Z","iopub.execute_input":"2025-08-17T08:59:31.768917Z","iopub.status.idle":"2025-08-17T08:59:36.066444Z","shell.execute_reply.started":"2025-08-17T08:59:31.768854Z","shell.execute_reply":"2025-08-17T08:59:36.065846Z"}},"outputs":[],"execution_count":null},{"cell_type":"markdown","source":"## Submit","metadata":{}},{"cell_type":"code","source":"y_test = model.predict(x_test) > 0.5\ny_test = y_test.astype(int).sum(axis=1) - 1\n\ntest_df['diagnosis'] = y_test\ntest_df.to_csv('submission.csv',index=False)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-17T09:00:12.838996Z","iopub.execute_input":"2025-08-17T09:00:12.839316Z","iopub.status.idle":"2025-08-17T09:00:17.304825Z","shell.execute_reply.started":"2025-08-17T09:00:12.839264Z","shell.execute_reply":"2025-08-17T09:00:17.304252Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"loss, acc = model.evaluate(x_val, y_val, verbose=0)\nprint(\"Validation Accuracy:\", acc)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-17T09:07:36.169473Z","iopub.execute_input":"2025-08-17T09:07:36.169748Z","iopub.status.idle":"2025-08-17T09:07:37.483645Z","shell.execute_reply.started":"2025-08-17T09:07:36.169711Z","shell.execute_reply":"2025-08-17T09:07:37.482941Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}