{"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":"gpu","dataSources":[{"sourceId":3649,"databundleVersionId":46718,"sourceType":"competition"},{"sourceId":312126,"sourceType":"datasetVersion","datasetId":130727}],"dockerImageVersionId":31090,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"# ====================================================\n# CIFAR-10 Classification | Custom ResNet18 + Kaggle Output\n# ====================================================\n# This script:\n# 1) Loads CIFAR-10 dataset for training/testing\n# 2) Constructs a ResNet18-inspired network\n# 3) Trains with light augmentation and cosine LR scheduler\n# 4) Saves the top-performing model checkpoint\n# 5) Loads the optimal model\n# 6) Generates predictions for Kaggle submission\n# ====================================================\n\nimport os, math, random, glob, warnings\nimport numpy as np\nimport pandas as pd\nimport tensorflow as tf\nfrom tensorflow.keras import layers, models, regularizers\nfrom tensorflow.keras.datasets import cifar10\nfrom tensorflow.keras.utils import to_categorical\nfrom tensorflow.keras.preprocessing.image import ImageDataGenerator\nfrom tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint, LearningRateScheduler\nfrom tensorflow.keras.optimizers import AdamW\n\nwarnings.filterwarnings(\"ignore\")\n\nprint(\"TensorFlow Version:\", tf.__version__)\nprint(\"Detected GPUs:\", tf.config.list_physical_devices('GPU'))\n\n# --------------------------\n# Fixed random seed for reproducibility\n# --------------------------\nRANDOM_SEED = 1337\nrandom.seed(RANDOM_SEED)\nnp.random.seed(RANDOM_SEED)\ntf.random.set_seed(RANDOM_SEED)\n\n# --------------------------\n# Paths and filenames\n# --------------------------\nDATA_FOLDER = \"/kaggle/input/cifar10-object-recognition-in-images-zip-file\"\nSUBMISSION_TEMPLATE_PATH = os.path.join(DATA_FOLDER, \"sampleSubmission.csv\")\n\n# Candidate folders where test images might be\nTEST_FOLDER_CANDIDATES = [\n    os.path.join(DATA_FOLDER, \"train_test\"),\n    os.path.join(DATA_FOLDER, \"train_test\", \"test\"),\n    os.path.join(DATA_FOLDER, \"train_test\", \"test\", \"test\"),\n]\n\nBEST_MODEL_PATH = \"/kaggle/working/top_model_loss.h5\"\n\n# ====================================================\n# 1) Load CIFAR-10 dataset (Keras built-in)\n# ====================================================\n(x_train_set, y_train_set), (x_val_set, y_val_set) = cifar10.load_data()\n\nx_train_set = x_train_set.astype(\"float32\") / 255.0\nx_val_set = x_val_set.astype(\"float32\") / 255.0\n\ny_train_set = to_categorical(y_train_set, 10)\ny_val_set = to_categorical(y_val_set, 10)\n\nprint(\"Training Data Shape:\", x_train_set.shape, y_train_set.shape)\nprint(\"Validation Data Shape:\", x_val_set.shape, y_val_set.shape)\n\n# ====================================================\n# 2) Define ResNet18-inspired Model\n# ====================================================\ndef convolution_unit(input_tensor, filter_count, stride_val=1):\n    conv = layers.Conv2D(filter_count, 3, strides=stride_val, padding='same',\n                         kernel_regularizer=regularizers.l2(1e-4),\n                         use_bias=False)(input_tensor)\n    conv = layers.BatchNormalization()(conv)\n    conv = layers.Activation('relu')(conv)\n    return conv\n\ndef residual_unit(input_tensor, filter_count, downsample=False):\n    stride_val = 2 if downsample else 1\n    out = convolution_unit(input_tensor, filter_count, stride_val)\n    out = convolution_unit(out, filter_count)\n    shortcut = input_tensor\n    if downsample or input_tensor.shape[-1] != filter_count:\n        shortcut = layers.Conv2D(filter_count, 1, strides=stride_val, padding='same',\n                                 kernel_regularizer=regularizers.l2(1e-4),\n                                 use_bias=False)(shortcut)\n        shortcut = layers.BatchNormalization()(shortcut)\n    merged = layers.add([shortcut, out])\n    merged = layers.Activation('relu')(merged)\n    return merged\n\ndef create_resnet18_variant():\n    inp = layers.Input(shape=(32, 32, 3))\n    x = convolution_unit(inp, 64)\n    x = residual_unit(x, 64)\n    x = residual_unit(x, 64)\n\n    x = residual_unit(x, 128, downsample=True)\n    x = residual_unit(x, 128)\n\n    x = residual_unit(x, 256, downsample=True)\n    x = residual_unit(x, 256)\n\n    x = residual_unit(x, 512, downsample=True)\n    x = residual_unit(x, 512)\n\n    x = layers.GlobalAveragePooling2D()(x)\n    x = layers.Dropout(0.5)(x)\n    outputs = layers.Dense(10, activation='softmax')(x)\n    return models.Model(inp, outputs)\n\n# Instantiate model and compile\nmodel_variant = create_resnet18_variant()\nopt = AdamW(learning_rate=3e-4, weight_decay=1e-5, clipnorm=1.0)\nmodel_variant.compile(optimizer=opt, loss=\"categorical_crossentropy\", metrics=[\"accuracy\"])\n\n# ====================================================\n# 3) Augmentation pipeline\n# ====================================================\naugmentation_gen = ImageDataGenerator(\n    width_shift_range=0.1,\n    height_shift_range=0.1,\n    horizontal_flip=True\n)\naugmentation_gen.fit(x_train_set)\n\n# Callbacks\nmodel_save_cb = ModelCheckpoint(\n    BEST_MODEL_PATH, monitor=\"val_loss\", save_best_only=True, verbose=1\n)\nstop_early_cb = EarlyStopping(\n    monitor=\"val_loss\", patience=12, restore_best_weights=True, verbose=1\n)\n\ndef cosine_lr_schedule(epoch_num):\n    max_epochs = 60\n    base_lr = 3e-4\n    return 0.5 * base_lr * (1 + math.cos(math.pi * epoch_num / max_epochs))\n\nlr_cb = LearningRateScheduler(cosine_lr_schedule)\n\n# ====================================================\n# 4) Model training\n# ====================================================\nBATCH_SIZE = 128\nMAX_EPOCHS = 60\n\nif not os.path.exists(BEST_MODEL_PATH):\n    print(\"No existing model found, starting training...\")\n    history_data = model_variant.fit(\n        augmentation_gen.flow(x_train_set, y_train_set, batch_size=BATCH_SIZE),\n        validation_data=(x_val_set, y_val_set),\n        steps_per_epoch=x_train_set.shape[0] // BATCH_SIZE,\n        epochs=MAX_EPOCHS,\n        callbacks=[model_save_cb, stop_early_cb, lr_cb],\n        verbose=1\n    )\nelse:\n    print(\"Model checkpoint found, skipping training phase.\")\n\n# ====================================================\n# 5) Load best model\n# ====================================================\ndef load_best_model():\n    candidate_paths = [BEST_MODEL_PATH, \"top_model_loss.h5\", \"/kaggle/working/top_model.h5\"]\n    for path in candidate_paths:\n        if os.path.exists(path):\n            print(\"Loading model from path:\", path)\n            return tf.keras.models.load_model(path)\n\n    saved_model_search = glob.glob(\"/kaggle/working/**/saved_model.pb\", recursive=True)\n    if saved_model_search:\n        directory = os.path.dirname(saved_model_search[0])\n        print(\"Found saved model in directory:\", directory)\n        return tf.keras.models.load_model(directory)\n\n    raise FileNotFoundError(\"No saved model found. Please run training.\")\n\nbest_model_variant = load_best_model()\nprint(\"Model loaded successfully.\")\n\n# ====================================================\n# 6) Kaggle submission generation\n# ====================================================\nassert os.path.exists(SUBMISSION_TEMPLATE_PATH), \"sampleSubmission.csv not found in dataset!\"\nsample = pd.read_csv(SUBMISSION_TEMPLATE_PATH)\nid_list = sample[\"id\"].tolist()\nprint(\"IDs to predict:\", len(id_list))\n\ndef locate_image_path(img_id):\n    for base in TEST_FOLDER_CANDIDATES:\n        png_path = os.path.join(base, f\"{img_id}.png\")\n        if os.path.exists(png_path):\n            return png_path\n        jpg_path = os.path.join(base, f\"{img_id}.jpg\")\n        if os.path.exists(jpg_path):\n            return jpg_path\n    return None\n\nresolved_paths = []\nmissing_count = 0\nfor img_id in id_list:\n    path = locate_image_path(img_id)\n    if path is None:\n        missing_count += 1\n    else:\n        resolved_paths.append(path)\n\nif missing_count > 0:\n    raise FileNotFoundError(f\"{missing_count} test images could not be located.\")\n\nprint(f\"Resolved test image paths count: {len(resolved_paths)}\")\n\ndef preprocess_image(path):\n    img = tf.io.read_file(path)\n    img = tf.io.decode_image(img, channels=3, expand_animations=False)\n    img = tf.image.convert_image_dtype(img, tf.float32)\n    img = tf.image.resize(img, [32, 32])\n    return img\n\nBATCH_SIZE_PRED = 1024\ndataset_pred = tf.data.Dataset.from_tensor_slices(resolved_paths)\ndataset_pred = dataset_pred.map(preprocess_image, num_parallel_calls=tf.data.AUTOTUNE)\ndataset_pred = dataset_pred.batch(BATCH_SIZE_PRED).prefetch(tf.data.AUTOTUNE)\n\npredictions = best_model_variant.predict(dataset_pred, verbose=1)\npred_indices = np.argmax(predictions, axis=1)\n\nlabel_names_map = [\"airplane\",\"automobile\",\"bird\",\"cat\",\"deer\",\"dog\",\"frog\",\"horse\",\"ship\",\"truck\"]\npred_labels_final = [label_names_map[i] for i in pred_indices]\n\nsubmission_result = pd.DataFrame({\"id\": id_list, \"label\": pred_labels_final})\nsubmission_result.to_csv(\"submission.csv\", index=False)\n\nprint(\"Submission file generated: submission.csv\")\nprint(submission_result.head())\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-09-24T22:17:08.671355Z","iopub.execute_input":"2025-09-24T22:17:08.672087Z","iopub.status.idle":"2025-09-24T22:21:02.548644Z","shell.execute_reply.started":"2025-09-24T22:17:08.672060Z","shell.execute_reply":"2025-09-24T22:21:02.548018Z"}},"outputs":[],"execution_count":null}]}