{"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 CNN with Residual Connections\n# ====================================================\n# This script:\n# 1) Loads CIFAR-10 dataset for training/testing\n# 2) Constructs a custom CNN with residual connections\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.utils import plot_model\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\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 Custom CNN with Residual Connections\n# ====================================================\ndef conv_bn_relu(x, filters, kernel_size=3, stride=1):\n    x = layers.Conv2D(filters, kernel_size, strides=stride, padding=\"same\",\n                      use_bias=False, kernel_regularizer=regularizers.l2(1e-4))(x)\n    x = layers.BatchNormalization()(x)\n    x = layers.ReLU()(x)\n    return x\n\ndef residual_block(x, filters, downsample=False):\n    stride = 2 if downsample else 1\n    shortcut = x\n\n    y = conv_bn_relu(x, filters, stride=stride)\n    y = layers.Conv2D(filters, 3, strides=1, padding=\"same\",\n                      use_bias=False, kernel_regularizer=regularizers.l2(1e-4))(y)\n    y = layers.BatchNormalization()(y)\n\n    if downsample or x.shape[-1] != filters:\n        shortcut = layers.Conv2D(filters, 1, strides=stride, padding=\"same\",\n                                 use_bias=False, kernel_regularizer=regularizers.l2(1e-4))(shortcut)\n        shortcut = layers.BatchNormalization()(shortcut)\n\n    out = layers.add([shortcut, y])\n    out = layers.ReLU()(out)\n    return out\n\ndef create_custom_residual_cnn():\n    inp = layers.Input(shape=(32, 32, 3))\n    x = conv_bn_relu(inp, 64)\n\n    x = residual_block(x, 64)\n    x = residual_block(x, 64)\n\n    x = residual_block(x, 128, downsample=True)\n    x = residual_block(x, 128)\n\n    x = residual_block(x, 256, downsample=True)\n    x = residual_block(x, 256)\n\n    x = residual_block(x, 512, downsample=True)\n    x = residual_block(x, 512)\n\n    x = layers.GlobalAveragePooling2D()(x)\n    x = layers.Dropout(0.5)(x)\n    outputs = layers.Dense(10, activation=\"softmax\")(x)\n\n    return models.Model(inp, outputs)\n\n# Instantiate and compile model\nmodel_variant = create_custom_residual_cnn()\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\nplot_model(\n    model_variant,\n    to_file=\"cnn_architecture.png\",\n    show_shapes=True,\n    show_layer_names=True\n)\n\nfrom IPython.display import Image\nImage(filename=\"cnn_architecture.png\")\n\nprint(\"\\nModel Summary:\")\nmodel_variant.summary()\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, custom_objects={\"conv_bn_relu\": conv_bn_relu, \"residual_block\": residual_block})\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    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\nimport tensorflow as tf\nfrom tensorflow.keras.utils import plot_model\n\n# Save CNN diagram as an image\nplot_model(\n    best_model_variant,\n    to_file=\"cnn_architecture.png\",\n    show_shapes=True,\n    show_layer_names=True\n)\n\n","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-10-01T16:56:20.586282Z","iopub.execute_input":"2025-10-01T16:56:20.586581Z","iopub.status.idle":"2025-10-01T17:32:45.921673Z","shell.execute_reply.started":"2025-10-01T16:56:20.586560Z","shell.execute_reply":"2025-10-01T17:32:45.920829Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"from tensorflow.keras.utils import model_to_dot\nimport graphviz\nfrom IPython.display import Image\n\ndot = model_to_dot(model_variant, show_shapes=True, show_layer_names=True)\ngraph = graphviz.Source(dot.to_string())\ngraph.render(\"cnn_architecture\", format=\"png\", cleanup=True)\nImage(filename=\"cnn_architecture.png\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-10-01T17:32:45.923312Z","iopub.execute_input":"2025-10-01T17:32:45.923672Z","iopub.status.idle":"2025-10-01T17:32:49.322005Z","shell.execute_reply.started":"2025-10-01T17:32:45.923641Z","shell.execute_reply":"2025-10-01T17:32:49.321004Z"}},"outputs":[],"execution_count":null}]}