{"metadata":{"kernelspec":{"language":"python","display_name":"Python 3","name":"python3"},"language_info":{"name":"python","version":"3.11.13","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[{"sourceId":25563,"databundleVersionId":2094376,"sourceType":"competition"}],"dockerImageVersionId":31192,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":false}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# # This Python 3 environment comes with many helpful analytics libraries installed\n# # It is defined by the kaggle/python Docker image: https://github.com/kaggle/docker-python\n# # For example, here's several helpful packages to load\n\n# import numpy as np # linear algebra\n# import pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)\n\n# # Input data files are available in the read-only \"../input/\" directory\n# # For example, running this (by clicking run or pressing Shift+Enter) will list all files under the input directory\n\n# import os\n# for dirname, _, filenames in os.walk('/kaggle/input'):\n#     for filename in filenames:\n#         print(os.path.join(dirname, filename))\n\n# # You can write up to 20GB to the current directory (/kaggle/working/) that gets preserved as output when you create a version using \"Save & Run All\" \n# # You can also write temporary files to /kaggle/temp/, but they won't be saved outside of the current session","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-12-04T17:15:03.02702Z","iopub.execute_input":"2025-12-04T17:15:03.027411Z","iopub.status.idle":"2025-12-04T17:15:03.032329Z","shell.execute_reply.started":"2025-12-04T17:15:03.027387Z","shell.execute_reply":"2025-12-04T17:15:03.031298Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# imports\n\nimport os\nimport json\nimport numpy as np\nimport pandas as pd\nimport tensorflow as tf\nfrom tensorflow import keras\nfrom tensorflow.keras import layers\nfrom sklearn.model_selection import train_test_split","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-04T17:15:19.553611Z","iopub.execute_input":"2025-12-04T17:15:19.554218Z","iopub.status.idle":"2025-12-04T17:15:19.558817Z","shell.execute_reply.started":"2025-12-04T17:15:19.554175Z","shell.execute_reply":"2025-12-04T17:15:19.557768Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# CONFIG\n\nSEED = 42\nIMG_SIZE = 224\nBATCH_SIZE = 32\nEPOCHS = 30\nMODEL_NAME = \"efficientnetb0_plantpath\"\nBEST_MODEL_FILE = f\"/kaggle/working/{MODEL_NAME}_best.h5\"\nMODEL_FILE = f\"/kaggle/working/{MODEL_NAME}.h5\"\nSUBMISSION_FILE = \"/kaggle/working/submission.csv\"\nTHRESHOLD = 0.5","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-04T17:04:07.378086Z","iopub.execute_input":"2025-12-04T17:04:07.379344Z","iopub.status.idle":"2025-12-04T17:04:07.385364Z","shell.execute_reply.started":"2025-12-04T17:04:07.379303Z","shell.execute_reply":"2025-12-04T17:04:07.383999Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"TRAIN_CSV = \"/kaggle/input/plant-pathology-2021-fgvc8/train.csv\"\nTRAIN_DIR = \"/kaggle/input/plant-pathology-2021-fgvc8/test_images\"\nTEST_DIR = \"/kaggle/input/plant-pathology-2021-fgvc8/test_images\"\nSAMPLE_SUB = \"sample_submission.csv\"\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-04T17:08:37.745158Z","iopub.execute_input":"2025-12-04T17:08:37.74552Z","iopub.status.idle":"2025-12-04T17:08:37.75098Z","shell.execute_reply.started":"2025-12-04T17:08:37.745498Z","shell.execute_reply":"2025-12-04T17:08:37.749688Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Read train.csv and create multi-hot labels\ndf = pd.read_csv(TRAIN_CSV)\nif \"image\" not in df.columns or \"labels\" not in df.columns:\n    raise ValueError(\"train.csv must contain 'image' and 'labels' columns\")\n\n# Understand values and generate list of categories (unique labels)\ndf[\"label_list\"] = df[\"labels\"].astype(str).str.split()\nall_labels = sorted({lbl for lst in df[\"label_list\"] for lbl in lst})\nprint(\"Detected labels:\", all_labels)\n\n# Map label -> index and reverse\nlabel_to_index = {label: idx for idx, label in enumerate(all_labels)}\nindex_to_label = {idx: label for label, idx in label_to_index.items()}\n\n# Function to convert to multi-hot vector\ndef labels_to_multi_hot(label_list):\n    vec = np.zeros(len(all_labels), dtype=np.float32)\n    for lbl in label_list:\n        vec[label_to_index[lbl]] = 1.0\n    return vec\n\ndf[\"multi_hot\"] = df[\"label_list\"].apply(labels_to_multi_hot)\ndf[\"image_path\"] = df[\"image\"].apply(lambda x: os.path.join(TRAIN_DIR, x))\nprint(\"Total training samples:\", len(df))\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-04T17:17:58.824941Z","iopub.execute_input":"2025-12-04T17:17:58.825338Z","iopub.status.idle":"2025-12-04T17:17:59.13811Z","shell.execute_reply.started":"2025-12-04T17:17:58.825313Z","shell.execute_reply":"2025-12-04T17:17:59.137199Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"df","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-04T17:18:44.139527Z","iopub.execute_input":"2025-12-04T17:18:44.139847Z","iopub.status.idle":"2025-12-04T17:18:44.155699Z","shell.execute_reply.started":"2025-12-04T17:18:44.139824Z","shell.execute_reply":"2025-12-04T17:18:44.154768Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Train and Validation\ntrain_df, val_df = train_test_split(df, test_size=0.15, random_state=SEED, shuffle=True)\nprint(\"Train:\", len(train_df), \"Val:\", len(val_df))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-04T17:20:21.354868Z","iopub.execute_input":"2025-12-04T17:20:21.355328Z","iopub.status.idle":"2025-12-04T17:20:21.369961Z","shell.execute_reply.started":"2025-12-04T17:20:21.355301Z","shell.execute_reply":"2025-12-04T17:20:21.369001Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#  data augmentation and read image\n\nAUTOTUNE = tf.data.AUTOTUNE\nfrom tensorflow.keras.applications.efficientnet import preprocess_input as efficientnet_preprocess\n\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.convert_image_dtype(image, tf.float32)  # [0,1] #Normalization\n    image = tf.image.resize(image, [IMG_SIZE, IMG_SIZE])\n    return image, label\n\ndata_augmentation = keras.Sequential([\n    layers.RandomFlip(\"horizontal_and_vertical\"),\n    layers.RandomRotation(0.12),\n    layers.RandomZoom(0.08),\n    layers.RandomContrast(0.10),\n], name=\"data_augmentation\")\n\ndef preprocess_for_train(image, label):\n    image = data_augmentation(image)\n    image = efficientnet_preprocess(image * 255.0)\n    return image, label\n\ndef preprocess_for_val(image, label):\n    image = efficientnet_preprocess(image * 255.0)\n    return image, label\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-04T17:24:29.43187Z","iopub.execute_input":"2025-12-04T17:24:29.432252Z","iopub.status.idle":"2025-12-04T17:24:29.459841Z","shell.execute_reply.started":"2025-12-04T17:24:29.432228Z","shell.execute_reply":"2025-12-04T17:24:29.459172Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#  DataFrame <--- tf.data.Dataset\ndef df_to_dataset(dataframe, shuffle=True, augment=False):\n    paths = dataframe[\"image_path\"].tolist()\n    labels = np.stack(dataframe[\"multi_hot\"].values)\n    ds = tf.data.Dataset.from_tensor_slices((paths, labels))\n    if shuffle:\n        ds = ds.shuffle(buffer_size=len(paths), seed=SEED)\n    \n    ds = ds.map(lambda p, l: tf.py_function(func=parse_image, inp=[p, l], Tout=(tf.float32, tf.float32)),\n                num_parallel_calls=AUTOTUNE)\n    ds = ds.map(lambda im, lb: (tf.ensure_shape(im, [IMG_SIZE, IMG_SIZE, 3]), tf.ensure_shape(lb, [len(all_labels)])),\n                num_parallel_calls=AUTOTUNE)\n    if augment:\n        ds = ds.map(preprocess_for_train, num_parallel_calls=AUTOTUNE)\n    else:\n        ds = ds.map(preprocess_for_val, num_parallel_calls=AUTOTUNE)\n    ds = ds.batch(BATCH_SIZE).prefetch(AUTOTUNE)\n    return ds\n\ntrain_ds = df_to_dataset(train_df, shuffle=True, augment=True)\nval_ds = df_to_dataset(val_df, shuffle=False, augment=False)\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-04T17:24:31.509799Z","iopub.execute_input":"2025-12-04T17:24:31.510211Z","iopub.status.idle":"2025-12-04T17:24:31.924901Z","shell.execute_reply.started":"2025-12-04T17:24:31.510184Z","shell.execute_reply":"2025-12-04T17:24:31.923955Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# Build (EfficientNetB0 base) modal\nnum_classes = len(all_labels)\nprint(\"Num classes:\", num_classes)\n\nbase_model = tf.keras.applications.EfficientNetB0(include_top=False, weights='imagenet', input_shape=(IMG_SIZE, IMG_SIZE, 3))\nbase_model.trainable = False  \n\ninputs = layers.Input(shape=(IMG_SIZE, IMG_SIZE, 3))\nx = base_model(inputs, training=False)\nx = layers.GlobalAveragePooling2D()(x)\nx = layers.Dropout(0.3)(x)\nx = layers.Dense(256, activation='relu')(x)\nx = layers.Dropout(0.3)(x)\noutputs = layers.Dense(num_classes, activation='sigmoid')(x)  # multi-label\nmodel = tf.keras.Model(inputs, outputs, name=MODEL_NAME)\n\nmodel.compile(\n    optimizer=keras.optimizers.Adam(learning_rate=1e-4),\n    loss='binary_crossentropy',\n    metrics=[keras.metrics.AUC(name='val_auc'), keras.metrics.BinaryAccuracy(name='binary_accuracy')]\n)\n\nmodel.summary()\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-04T17:25:13.771504Z","iopub.execute_input":"2025-12-04T17:25:13.771823Z","iopub.status.idle":"2025-12-04T17:25:15.627525Z","shell.execute_reply.started":"2025-12-04T17:25:13.7718Z","shell.execute_reply":"2025-12-04T17:25:15.62647Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#  callbacks\n\ncallbacks = [\n    keras.callbacks.ModelCheckpoint(BEST_MODEL_FILE, monitor='val_auc', mode='max', save_best_only=True, verbose=1),\n    keras.callbacks.EarlyStopping(monitor='val_auc', mode='max', patience=6, verbose=1, restore_best_weights=True),\n    keras.callbacks.ReduceLROnPlateau(monitor='val_auc', mode='max', factor=0.5, patience=3, verbose=1, min_lr=1e-7)\n]\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-04T17:25:42.187952Z","iopub.execute_input":"2025-12-04T17:25:42.188768Z","iopub.status.idle":"2025-12-04T17:25:42.194928Z","shell.execute_reply.started":"2025-12-04T17:25:42.188736Z","shell.execute_reply":"2025-12-04T17:25:42.193849Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"#(base frozen) fit\n\nhistory = model.fit(\n    train_ds,\n    validation_data=val_ds,\n    epochs=EPOCHS,\n    callbacks=callbacks\n)\nprint(\"Finished training the head.\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-12-04T17:26:04.974226Z","iopub.execute_input":"2025-12-04T17:26:04.974553Z","iopub.status.idle":"2025-12-04T17:26:17.770642Z","shell.execute_reply.started":"2025-12-04T17:26:04.974532Z","shell.execute_reply":"2025-12-04T17:26:17.766888Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}