{"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":"nvidiaTeslaT4","dataSources":[{"sourceId":5048,"databundleVersionId":868335,"sourceType":"competition"}],"dockerImageVersionId":31193,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"nbformat_minor":4,"nbformat":4,"cells":[{"cell_type":"code","source":"import os\nimport shutil\nimport random\n\n# Paths\ndataset_dir = \"/kaggle/input/state-farm-distracted-driver-detection/imgs/train\"  # adjust this path\noutput_dir = \"/kaggle/working/split\"\ntrain_dir = os.path.join(output_dir, \"train\")\nval_dir = os.path.join(output_dir, \"val\")\ntest_dir = os.path.join(output_dir, \"test\")\n\n# Create directories\nfor folder in [train_dir, val_dir, test_dir]:\n    os.makedirs(folder, exist_ok=True)\n\n# Get all class folders\nclasses = [d for d in os.listdir(dataset_dir) if os.path.isdir(os.path.join(dataset_dir, d))]\n\n# Split and copy images\nrandom.seed(42)\nfor cls in classes:\n    cls_path = os.path.join(dataset_dir, cls)\n    images = os.listdir(cls_path)\n    random.shuffle(images)\n    \n    n_total = len(images)\n    n_train = int(0.7 * n_total)\n    n_val = int(0.15 * n_total)\n    n_test = n_total - n_train - n_val\n\n    splits = {\n        train_dir: images[:n_train],\n        val_dir: images[n_train:n_train+n_val],\n        test_dir: images[n_train+n_val:]\n    }\n\n    for split_folder, split_images in splits.items():\n        cls_split_dir = os.path.join(split_folder, cls)\n        os.makedirs(cls_split_dir, exist_ok=True)\n        for img in split_images:\n            shutil.copy(os.path.join(cls_path, img), os.path.join(cls_split_dir, img))\n\nprint(\"Dataset split completed!\")\n","metadata":{"_uuid":"8f2839f25d086af736a60e9eeb907d3b93b6e0e5","_cell_guid":"b1076dfc-b9ad-4769-8c92-a6c4dae69d19","trusted":true,"execution":{"iopub.status.busy":"2025-11-22T18:02:44.273548Z","iopub.execute_input":"2025-11-22T18:02:44.273806Z","iopub.status.idle":"2025-11-22T18:04:43.679155Z","shell.execute_reply.started":"2025-11-22T18:02:44.273788Z","shell.execute_reply":"2025-11-22T18:04:43.678336Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport numpy as np\nimport matplotlib.pyplot as plt\nimport seaborn as sns\nimport tensorflow as tf\nfrom tensorflow.keras.models import Sequential\nfrom tensorflow.keras.layers import Input, Conv2D, AveragePooling2D, Flatten, Dense, Dropout\nfrom tensorflow.keras.optimizers import Adam\nfrom tensorflow.keras.callbacks import EarlyStopping\nfrom tensorflow.keras.preprocessing.image import ImageDataGenerator\nfrom sklearn.metrics import confusion_matrix, classification_report\n\n# Paths\ndataset_dir = \"/kaggle/working/split\"  # your folder\ntrain_dir = os.path.join(dataset_dir, \"train\")\nval_dir = os.path.join(dataset_dir, \"val\")\ntest_dir = os.path.join(dataset_dir, \"test\")\n\n# Hyperparameters\nEPOCHS = 20\nBATCH_SIZE = 32\nLR = 0.0001\nDROPOUT_RATE = 0.3\nINPUT_SHAPE = (28, 28, 1)  # grayscale\n\n# Data preprocessing\ntrain_datagen = ImageDataGenerator(rescale=1./255)\nval_datagen = ImageDataGenerator(rescale=1./255)\n\ntrain_generator = train_datagen.flow_from_directory(\n    train_dir,\n    target_size=(28,28),\n    color_mode='grayscale',\n    batch_size=BATCH_SIZE,\n    class_mode='categorical',\n    shuffle=True,\n    seed=42\n)\n\nval_generator = val_datagen.flow_from_directory(\n    val_dir,\n    target_size=(28,28),\n    color_mode='grayscale',\n    batch_size=BATCH_SIZE,\n    class_mode='categorical',\n    shuffle=False\n)\n\n# LeNet-5 model\nmodel = Sequential([\n    Input(shape=INPUT_SHAPE),\n    Conv2D(6, kernel_size=(5,5), activation='relu', padding='same'),\n    AveragePooling2D(pool_size=(2,2)),\n    Conv2D(16, kernel_size=(5,5), activation='relu', padding='valid'),\n    AveragePooling2D(pool_size=(2,2)),\n    Flatten(),\n    Dense(120, activation='relu'),\n    Dropout(DROPOUT_RATE),\n    Dense(84, activation='relu'),\n    Dropout(DROPOUT_RATE),\n    Dense(train_generator.num_classes, activation='softmax')\n])\n\n# Compile model\nmodel.compile(\n    optimizer=Adam(learning_rate=LR),\n    loss='categorical_crossentropy',\n    metrics=['accuracy']\n)\n\n# Early stopping\nearly_stop = EarlyStopping(monitor='val_loss', patience=3, restore_best_weights=True)\n\n# Train model\nhistory = model.fit(\n    train_generator,\n    epochs=EPOCHS,\n    validation_data=val_generator,\n    callbacks=[early_stop]\n)\n\n# Plot accuracy and loss curves\nplt.figure(figsize=(12,5))\n\nplt.subplot(1,2,1)\nplt.plot(history.history['accuracy'], label='Train Acc')\nplt.plot(history.history['val_accuracy'], label='Val Acc')\nplt.title('Accuracy vs Validation Accuracy')\nplt.xlabel('Epoch')\nplt.ylabel('Accuracy')\nplt.legend()\n\nplt.subplot(1,2,2)\nplt.plot(history.history['loss'], label='Train Loss')\nplt.plot(history.history['val_loss'], label='Val Loss')\nplt.title('Loss vs Validation Loss')\nplt.xlabel('Epoch')\nplt.ylabel('Loss')\nplt.legend()\n\nplt.show()\n\n# Test evaluation\ntest_datagen = ImageDataGenerator(rescale=1./255)\ntest_generator = test_datagen.flow_from_directory(\n    test_dir,\n    target_size=(28,28),\n    color_mode='grayscale',\n    batch_size=BATCH_SIZE,\n    class_mode='categorical',\n    shuffle=False\n)\n\ntest_loss, test_acc = model.evaluate(test_generator)\nprint(f\"Test Accuracy: {test_acc*100:.2f}%\")\n\n# Confusion matrix\ny_true = test_generator.classes\ny_pred = model.predict(test_generator)\ny_pred_classes = np.argmax(y_pred, axis=1)\n\ncm = confusion_matrix(y_true, y_pred_classes)\nplt.figure(figsize=(10,8))\nsns.heatmap(cm, annot=True, fmt='d', cmap='Blues', \n            xticklabels=test_generator.class_indices.keys(),\n            yticklabels=test_generator.class_indices.keys())\nplt.xlabel('Predicted')\nplt.ylabel('Actual')\nplt.title('Confusion Matrix')\nplt.show()\n\n# Classification report\nprint(\"Classification Report:\")\nprint(classification_report(y_true, y_pred_classes, target_names=test_generator.class_indices.keys()))\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-21T09:34:55.575163Z","iopub.execute_input":"2025-11-21T09:34:55.575963Z","iopub.status.idle":"2025-11-21T09:48:17.294281Z","shell.execute_reply.started":"2025-11-21T09:34:55.575935Z","shell.execute_reply":"2025-11-21T09:48:17.293679Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"import os\nimport numpy as np\nimport matplotlib.pyplot as plt\nimport seaborn as sns\nimport tensorflow as tf\nfrom tensorflow.keras.models import Sequential\nfrom tensorflow.keras.layers import Input, Conv2D, AveragePooling2D, Flatten, Dense, Dropout\nfrom tensorflow.keras.optimizers import SGD\nfrom tensorflow.keras.callbacks import EarlyStopping\nfrom tensorflow.keras.preprocessing.image import ImageDataGenerator\nfrom sklearn.metrics import confusion_matrix, classification_report\n\n# Paths\ndataset_dir = \"/kaggle/working/split\"  # your folder\ntrain_dir = os.path.join(dataset_dir, \"train\")\nval_dir = os.path.join(dataset_dir, \"val\")\ntest_dir = os.path.join(dataset_dir, \"test\")\n\n# Hyperparameters\nEPOCHS = 40\nBATCH_SIZE = 32\nLR = 0.001\nDROPOUT_RATE = 0.5\nINPUT_SHAPE = (28, 28, 1)  # grayscale\n\n# Data preprocessing\ntrain_datagen = ImageDataGenerator(rescale=1./255)\nval_datagen = ImageDataGenerator(rescale=1./255)\n\ntrain_generator = train_datagen.flow_from_directory(\n    train_dir,\n    target_size=(28,28),\n    color_mode='grayscale',\n    batch_size=BATCH_SIZE,\n    class_mode='sparse',  # use sparse for integer labels\n    shuffle=True,\n    seed=42\n)\n\nval_generator = val_datagen.flow_from_directory(\n    val_dir,\n    target_size=(28,28),\n    color_mode='grayscale',\n    batch_size=BATCH_SIZE,\n    class_mode='sparse',\n    shuffle=False\n)\n\n# LeNet-5 model\nmodel = Sequential([\n    Input(shape=INPUT_SHAPE),\n    Conv2D(6, kernel_size=(5,5), activation='relu', padding='same'),\n    AveragePooling2D(pool_size=(2,2)),\n    Conv2D(16, kernel_size=(5,5), activation='relu', padding='valid'),\n    AveragePooling2D(pool_size=(2,2)),\n    Flatten(),\n    Dense(120, activation='relu'),\n    Dropout(DROPOUT_RATE),\n    Dense(84, activation='relu'),\n    Dropout(DROPOUT_RATE),\n    Dense(train_generator.num_classes, activation='softmax')\n])\n\n# Compile model\nmodel.compile(\n    optimizer=SGD(learning_rate=LR, momentum=0.9),\n    loss='sparse_categorical_crossentropy',  # updated loss function\n    metrics=['accuracy']\n)\n\n# Early stopping\nearly_stop = EarlyStopping(monitor='val_loss', patience=3, restore_best_weights=True)\n\n# Train model\nhistory = model.fit(\n    train_generator,\n    epochs=EPOCHS,\n    validation_data=val_generator,\n    callbacks=[early_stop]\n)\n\n# Plot accuracy and loss curves\nplt.figure(figsize=(12,5))\n\nplt.subplot(1,2,1)\nplt.plot(history.history['accuracy'], label='Train Acc')\nplt.plot(history.history['val_accuracy'], label='Val Acc')\nplt.title('Accuracy vs Validation Accuracy')\nplt.xlabel('Epoch')\nplt.ylabel('Accuracy')\nplt.legend()\n\nplt.subplot(1,2,2)\nplt.plot(history.history['loss'], label='Train Loss')\nplt.plot(history.history['val_loss'], label='Val Loss')\nplt.title('Loss vs Validation Loss')\nplt.xlabel('Epoch')\nplt.ylabel('Loss')\nplt.legend()\n\nplt.show()\n\n# Test evaluation\ntest_datagen = ImageDataGenerator(rescale=1./255)\ntest_generator = test_datagen.flow_from_directory(\n    test_dir,\n    target_size=(28,28),\n    color_mode='grayscale',\n    batch_size=BATCH_SIZE,\n    class_mode='sparse',\n    shuffle=False\n)\n\ntest_loss, test_acc = model.evaluate(test_generator)\nprint(f\"Test Accuracy: {test_acc*100:.2f}%\")\n\n# Confusion matrix\ny_true = test_generator.classes\ny_pred = model.predict(test_generator)\ny_pred_classes = np.argmax(y_pred, axis=1)\n\ncm = confusion_matrix(y_true, y_pred_classes)\nplt.figure(figsize=(10,8))\nsns.heatmap(cm, annot=True, fmt='d', cmap='Blues', \n            xticklabels=test_generator.class_indices.keys(),\n            yticklabels=test_generator.class_indices.keys())\nplt.xlabel('Predicted')\nplt.ylabel('Actual')\nplt.title('Confusion Matrix')\nplt.show()\n\n# Classification report\nprint(\"Classification Report:\")\nprint(classification_report(y_true, y_pred_classes, target_names=test_generator.class_indices.keys()))\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-21T09:52:43.944416Z","iopub.execute_input":"2025-11-21T09:52:43.944763Z","iopub.status.idle":"2025-11-21T10:18:55.354286Z","shell.execute_reply.started":"2025-11-21T09:52:43.944736Z","shell.execute_reply":"2025-11-21T10:18:55.353662Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\n# =========================================================\n# ResNet-50 FROM SCRATCH (no keras.applications) on State Farm\n# - Directory structure: /kaggle/working/split/{train,val,test}\n# - Hyperparams: Adam(lr=1e-4), dropout=0.3, batch_size=32, epochs=20\n# - Loss: categorical_crossentropy\n# - Callbacks: ReduceLROnPlateau, EarlyStopping, ModelCheckpoint\n# - Outputs: accuracy/loss curves, confusion matrix, classification report\n# =========================================================\n\nimport os\nimport numpy as np\nimport tensorflow as tf\nfrom tensorflow.keras import layers, models\nfrom tensorflow.keras.preprocessing.image import ImageDataGenerator\nfrom tensorflow.keras.callbacks import ReduceLROnPlateau, EarlyStopping, ModelCheckpoint\nimport matplotlib.pyplot as plt\nfrom sklearn.metrics import confusion_matrix, classification_report\nimport itertools\n\n# -------------------------------\n# Paths & basic config\n# -------------------------------\nBASE_DIR = \"/kaggle/working/split\"  # change if needed\nTRAIN_DIR = os.path.join(BASE_DIR, \"train\")\nVAL_DIR   = os.path.join(BASE_DIR, \"val\")\nTEST_DIR  = os.path.join(BASE_DIR, \"test\")\n\nIMG_SIZE = (224, 224)     # Standard for ResNet-50\nBATCH_SIZE = 32\nEPOCHS = 20\nLR = 1e-4\nDROPOUT_RATE = 0.3\nSEED = 42\n\n# GPU memory growth (safer on Kaggle)\nos.environ[\"TF_FORCE_GPU_ALLOW_GROWTH\"] = \"true\"\n\nnp.random.seed(SEED)\ntf.random.set_seed(SEED)\n\nprint(\"TensorFlow:\", tf.__version__)\nprint(\"Devices:\", tf.config.list_physical_devices())\n\n# -------------------------------\n# Data generators (with light aug)\n# -------------------------------\ntrain_datagen = ImageDataGenerator(\n    rescale=1./255,\n    rotation_range=12,\n    width_shift_range=0.08,\n    height_shift_range=0.08,\n    zoom_range=0.10,\n    shear_range=0.08,\n    horizontal_flip=True\n)\n\nval_datagen = ImageDataGenerator(rescale=1./255)\ntest_datagen = ImageDataGenerator(rescale=1./255)\n\ntrain_gen = train_datagen.flow_from_directory(\n    TRAIN_DIR,\n    target_size=IMG_SIZE,\n    batch_size=BATCH_SIZE,\n    class_mode='categorical',\n    shuffle=True,\n    seed=SEED\n)\n\nval_gen = val_datagen.flow_from_directory(\n    VAL_DIR,\n    target_size=IMG_SIZE,\n    batch_size=BATCH_SIZE,\n    class_mode='categorical',\n    shuffle=False\n)\n\ntest_gen = test_datagen.flow_from_directory(\n    TEST_DIR,\n    target_size=IMG_SIZE,\n    batch_size=BATCH_SIZE,\n    class_mode='categorical',\n    shuffle=False\n)\n\nnum_classes = len(train_gen.class_indices)\nclass_indices = train_gen.class_indices\nclass_names = [None]*num_classes\nfor name, idx in class_indices.items():\n    class_names[idx] = name\nprint(\"Classes:\", class_names)\n\n# =========================================================\n# ResNet-50 architecture (FROM SCRATCH)\n#   - conv_block: changes dimensions with a 1x1 shortcut\n#   - identity_block: same dimensions, shortcut is identity\n#   - Stage layout: (2D filters per paper)\n#       conv1: 7x7, 64, stride 2 -> maxpool\n#       conv2_x: [64,64,256] x 3 blocks (first is conv_block)\n#       conv3_x: [128,128,512] x 4 blocks\n#       conv4_x: [256,256,1024] x 6 blocks\n#       conv5_x: [512,512,2048] x 3 blocks\n#   - Bottleneck design: 1x1 -> 3x3 -> 1x1 with strides in first 1x1 of conv_block\n# =========================================================\n\ndef bn_relu_conv(x, filters, kernel_size, strides=1, name=None):\n    x = layers.BatchNormalization(axis=3, name=None if name is None else name+'_bn')(x)\n    x = layers.Activation('relu', name=None if name is None else name+'_relu')(x)\n    x = layers.Conv2D(filters, kernel_size, strides=strides, padding='same',\n                      use_bias=False, kernel_initializer='he_normal',\n                      name=None if name is None else name+'_conv')(x)\n    return x\n\ndef conv_block(x, filters, strides=(2,2), name=None):\n    \"\"\"A block that has a conv shortcut (for dimension change).\"\"\"\n    f1, f2, f3 = filters\n\n    shortcut = layers.Conv2D(f3, (1,1), strides=strides, padding='same',\n                             use_bias=False, kernel_initializer='he_normal',\n                             name=None if name is None else name+'_shortcut_conv')(x)\n    shortcut = layers.BatchNormalization(axis=3, name=None if name is None else name+'_shortcut_bn')(shortcut)\n\n    # Main path\n    x = layers.Conv2D(f1, (1,1), strides=strides, padding='same',\n                      use_bias=False, kernel_initializer='he_normal',\n                      name=None if name is None else name+'_conv1')(x)\n    x = layers.BatchNormalization(axis=3, name=None if name is None else name+'_bn1')(x)\n    x = layers.Activation('relu', name=None if name is None else name+'_relu1')(x)\n\n    x = layers.Conv2D(f2, (3,3), strides=1, padding='same',\n                      use_bias=False, kernel_initializer='he_normal',\n                      name=None if name is None else name+'_conv2')(x)\n    x = layers.BatchNormalization(axis=3, name=None if name is None else name+'_bn2')(x)\n    x = layers.Activation('relu', name=None if name is None else name+'_relu2')(x)\n\n    x = layers.Conv2D(f3, (1,1), strides=1, padding='same',\n                      use_bias=False, kernel_initializer='he_normal',\n                      name=None if name is None else name+'_conv3')(x)\n    x = layers.BatchNormalization(axis=3, name=None if name is None else name+'_bn3')(x)\n\n    x = layers.Add(name=None if name is None else name+'_add')([x, shortcut])\n    x = layers.Activation('relu', name=None if name is None else name+'_out')(x)\n    return x\n\ndef identity_block(x, filters, name=None):\n    \"\"\"A block where the shortcut is identity (no dimension change).\"\"\"\n    f1, f2, f3 = filters\n    shortcut = x\n\n    x = layers.Conv2D(f1, (1,1), strides=1, padding='same',\n                      use_bias=False, kernel_initializer='he_normal',\n                      name=None if name is None else name+'_conv1')(x)\n    x = layers.BatchNormalization(axis=3, name=None if name is None else name+'_bn1')(x)\n    x = layers.Activation('relu', name=None if name is None else name+'_relu1')(x)\n\n    x = layers.Conv2D(f2, (3,3), strides=1, padding='same',\n                      use_bias=False, kernel_initializer='he_normal',\n                      name=None if name is None else name+'_conv2')(x)\n    x = layers.BatchNormalization(axis=3, name=None if name is None else name+'_bn2')(x)\n    x = layers.Activation('relu', name=None if name is None else name+'_relu2')(x)\n\n    x = layers.Conv2D(f3, (1,1), strides=1, padding='same',\n                      use_bias=False, kernel_initializer='he_normal',\n                      name=None if name is None else name+'_conv3')(x)\n    x = layers.BatchNormalization(axis=3, name=None if name is None else name+'_bn3')(x)\n\n    x = layers.Add(name=None if name is None else name+'_add')([x, shortcut])\n    x = layers.Activation('relu', name=None if name is None else name+'_out')(x)\n    return x\n\ndef build_resnet50(input_shape=(224,224,3), num_classes=10, dropout_rate=0.3):\n    inputs = layers.Input(shape=input_shape)\n\n    # Initial conv + maxpool (conv1)\n    x = layers.Conv2D(64, (7,7), strides=2, padding='same',\n                      use_bias=False, kernel_initializer='he_normal', name='conv1')(inputs)\n    x = layers.BatchNormalization(axis=3, name='bn_conv1')(x)\n    x = layers.Activation('relu', name='conv1_relu')(x)\n    x = layers.MaxPooling2D((3,3), strides=2, padding='same', name='pool1')(x)\n\n    # conv2_x: 3 blocks -> [64,64,256], first is conv_block (stride 1)\n    x = conv_block(x, filters=[64,64,256], strides=(1,1), name='conv2_block1')\n    x = identity_block(x, filters=[64,64,256], name='conv2_block2')\n    x = identity_block(x, filters=[64,64,256], name='conv2_block3')\n\n    # conv3_x: 4 blocks -> [128,128,512], first conv_block with stride 2\n    x = conv_block(x, filters=[128,128,512], strides=(2,2), name='conv3_block1')\n    x = identity_block(x, filters=[128,128,512], name='conv3_block2')\n    x = identity_block(x, filters=[128,128,512], name='conv3_block3')\n    x = identity_block(x, filters=[128,128,512], name='conv3_block4')\n\n    # conv4_x: 6 blocks -> [256,256,1024], first conv_block with stride 2\n    x = conv_block(x, filters=[256,256,1024], strides=(2,2), name='conv4_block1')\n    x = identity_block(x, filters=[256,256,1024], name='conv4_block2')\n    x = identity_block(x, filters=[256,256,1024], name='conv4_block3')\n    x = identity_block(x, filters=[256,256,1024], name='conv4_block4')\n    x = identity_block(x, filters=[256,256,1024], name='conv4_block5')\n    x = identity_block(x, filters=[256,256,1024], name='conv4_block6')\n\n    # conv5_x: 3 blocks -> [512,512,2048], first conv_block with stride 2\n    x = conv_block(x, filters=[512,512,2048], strides=(2,2), name='conv5_block1')\n    x = identity_block(x, filters=[512,512,2048], name='conv5_block2')\n    x = identity_block(x, filters=[512,512,2048], name='conv5_block3')\n\n    # Top\n    x = layers.GlobalAveragePooling2D(name='avg_pool')(x)\n    x = layers.Dropout(dropout_rate, name='dropout')(x)\n    outputs = layers.Dense(num_classes, activation='softmax', name='fc')(x)\n\n    model = models.Model(inputs=inputs, outputs=outputs, name='ResNet50_scratch')\n    return model\n\n# Build the model FROM SCRATCH\nmodel = build_resnet50(input_shape=(IMG_SIZE[0], IMG_SIZE[1], 3), num_classes=num_classes, dropout_rate=DROPOUT_RATE)\nmodel.summary()\n\n# -------------------------------\n# Compile\n# -------------------------------\nopt = tf.keras.optimizers.Adam(learning_rate=LR)\nmodel.compile(optimizer=opt, loss='categorical_crossentropy', metrics=['accuracy'])\n\n# -------------------------------\n# Callbacks\n# -------------------------------\nreduce_lr = ReduceLROnPlateau(monitor='val_loss', factor=0.5, patience=2, min_lr=1e-6, verbose=1)\nearly_stop = EarlyStopping(monitor='val_loss', patience=5, restore_best_weights=True, verbose=1)\nckpt = ModelCheckpoint(filepath='best_resnet50_scratch_manual.h5',\n                       monitor='val_loss', save_best_only=True, verbose=1)\n\n# -------------------------------\n# Train\n# -------------------------------\nhistory = model.fit(\n    train_gen,\n    validation_data=val_gen,\n    epochs=EPOCHS,\n    callbacks=[reduce_lr, early_stop, ckpt],\n    verbose=1\n)\n\n# -------------------------------\n# Plot training curves\n# -------------------------------\ndef plot_curves(hist, save_path_prefix=\"resnet50_scratch_manual\"):\n    acc = hist.history['accuracy']\n    val_acc = hist.history['val_accuracy']\n    loss = hist.history['loss']\n    val_loss = hist.history['val_loss']\n    epochs_range = range(1, len(acc)+1)\n\n    plt.figure(figsize=(14,5))\n    plt.subplot(1,2,1)\n    plt.plot(epochs_range, acc, 'b-', label='Train Accuracy')\n    plt.plot(epochs_range, val_acc, 'r-', label='Val Accuracy')\n    plt.xlabel('Epoch')\n    plt.ylabel('Accuracy')\n    plt.title('Accuracy vs Epochs')\n    plt.legend()\n    plt.grid(True)\n\n    plt.subplot(1,2,2)\n    plt.plot(epochs_range, loss, 'b-', label='Train Loss')\n    plt.plot(epochs_range, val_loss, 'r-', label='Val Loss')\n    plt.xlabel('Epoch')\n    plt.ylabel('Loss')\n    plt.title('Loss vs Epochs')\n    plt.legend()\n    plt.grid(True)\n\n    plt.tight_layout()\n    plt.savefig(f\"{save_path_prefix}_curves.png\", dpi=120)\n    plt.show()\n\nplot_curves(history)\n\n# -------------------------------\n# Evaluate + Confusion Matrix\n# -------------------------------\ntest_loss, test_acc = model.evaluate(test_gen, verbose=1)\nprint(f\"Test Loss: {test_loss:.4f} | Test Accuracy: {test_acc:.4f}\")\n\ny_prob = model.predict(test_gen, verbose=1)\ny_pred = np.argmax(y_prob, axis=1)\ny_true = test_gen.classes  # integer labels\ncm = confusion_matrix(y_true, y_pred)\nprint(\"Confusion Matrix:\\n\", cm)\n\nprint(\"\\nClassification Report:\")\nprint(classification_report(y_true, y_pred, target_names=class_names, digits=4))\n\ndef plot_confusion_matrix(cm, classes, normalize=False, title='Confusion Matrix', cmap=plt.cm.Blues, save_path='cm_manual.png'):\n    if normalize:\n        cm = cm.astype('float') / (cm.sum(axis=1)[:, np.newaxis] + 1e-12)\n    plt.figure(figsize=(8,6))\n    plt.imshow(cm, interpolation='nearest', cmap=cmap)\n    plt.title(title)\n    plt.colorbar(fraction=0.046, pad=0.04)\n    tick_marks = np.arange(len(classes))\n    plt.xticks(tick_marks, classes, rotation=45, ha='right')\n    plt.yticks(tick_marks, classes)\n    fmt = '.2f' if normalize else 'd'\n    thresh = cm.max() / 2.\n    for i, j in itertools.product(range(cm.shape[0]), range(cm.shape[1])):\n        plt.text(j, i, format(cm[i, j], fmt),\n                 horizontalalignment=\"center\",\n                 color=\"white\" if cm[i, j] > thresh else \"black\")\n    plt.tight_layout()\n    plt.ylabel('True label')\n    plt.xlabel('Predicted label')\n    plt.savefig(save_path, dpi=120, bbox_inches='tight')\n    plt.show()\n\nplot_confusion_matrix(cm, class_names, normalize=False, title='Confusion Matrix (Counts)', save_path='cm_counts_manual.png')\nplot_confusion_matrix(cm, class_names, normalize=True, title='Confusion Matrix (Normalized)', save_path='cm_normalized_manual.png')\n\n# -------------------------------\n# Save final model & history\n# -------------------------------\nmodel.save(\"resnet50_scratch_manual_final.h5\")\nnp.savez(\"resnet50_scratch_manual_history.npz\",\n         accuracy=history.history['accuracy'],\n         val_accuracy=history.history['val_accuracy'],\n         loss=history.history['loss'],\n         val_loss=history.history['val_loss'])\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-22T16:07:34.927205Z","iopub.execute_input":"2025-11-22T16:07:34.927747Z","iopub.status.idle":"2025-11-22T17:12:39.151395Z","shell.execute_reply.started":"2025-11-22T16:07:34.927719Z","shell.execute_reply":"2025-11-22T17:12:39.150765Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"\n# =========================================================\n# VGG-16 FROM SCRATCH on State Farm distracted driver dataset\n# - Directory structure: /kaggle/working/split/{train,val,test}\n# - Hyperparams: Adam(lr=1e-4), dropout=0.3, batch_size=32, epochs=20\n# - Loss: categorical_crossentropy\n# - Callbacks: ReduceLROnPlateau, EarlyStopping, ModelCheckpoint\n# - Outputs: accuracy/loss curves, confusion matrix, classification report\n# =========================================================\n\nimport os\nimport numpy as np\nimport tensorflow as tf\nfrom tensorflow.keras import layers, models\nfrom tensorflow.keras.preprocessing.image import ImageDataGenerator\nfrom tensorflow.keras.callbacks import ReduceLROnPlateau, EarlyStopping, ModelCheckpoint\nimport matplotlib.pyplot as plt\nfrom sklearn.metrics import confusion_matrix, classification_report\nimport itertools\n\n# -------------------------------\n# Paths & basic config\n# -------------------------------\nBASE_DIR = \"/kaggle/working/split\"  # change if needed\nTRAIN_DIR = os.path.join(BASE_DIR, \"train\")\nVAL_DIR   = os.path.join(BASE_DIR, \"val\")\nTEST_DIR  = os.path.join(BASE_DIR, \"test\")\n\nIMG_SIZE = (224, 224)     # VGG-16 standard input size\nBATCH_SIZE = 32\nEPOCHS = 20\nLR = 1e-4\nDROPOUT_RATE = 0.3\nSEED = 42\n\n# Optional: make GPU memory growth safe in Kaggle\nos.environ[\"TF_FORCE_GPU_ALLOW_GROWTH\"] = \"true\"\n\n# Reproducibility\nnp.random.seed(SEED)\ntf.random.set_seed(SEED)\n\nprint(\"TensorFlow:\", tf.__version__)\nprint(\"Devices:\", tf.config.list_physical_devices())\n\n# -------------------------------\n# Data generators (with light augmentation)\n# -------------------------------\ntrain_datagen = ImageDataGenerator(\n    rescale=1./255,\n    rotation_range=12,\n    width_shift_range=0.08,\n    height_shift_range=0.08,\n    zoom_range=0.10,\n    shear_range=0.08,\n    horizontal_flip=True\n)\n\nval_datagen = ImageDataGenerator(rescale=1./255)\ntest_datagen = ImageDataGenerator(rescale=1./255)\n\ntrain_gen = train_datagen.flow_from_directory(\n    TRAIN_DIR,\n    target_size=IMG_SIZE,\n    batch_size=BATCH_SIZE,\n    class_mode='categorical',\n    shuffle=True,\n    seed=SEED\n)\n\nval_gen = val_datagen.flow_from_directory(\n    VAL_DIR,\n    target_size=IMG_SIZE,\n    batch_size=BATCH_SIZE,\n    class_mode='categorical',\n    shuffle=False\n)\n\ntest_gen = test_datagen.flow_from_directory(\n    TEST_DIR,\n    target_size=IMG_SIZE,\n    batch_size=BATCH_SIZE,\n    class_mode='categorical',\n    shuffle=False\n)\n\nnum_classes = len(train_gen.class_indices)\nclass_indices = train_gen.class_indices\nclass_names = [None]*num_classes\nfor name, idx in class_indices.items():\n    class_names[idx] = name\nprint(\"Classes:\", class_names)\n\n# =========================================================\n# VGG-16 architecture (FROM SCRATCH)\n#   - 5 conv blocks: [64, 128, 256, 512, 512]\n#   - Each block uses stacks of 3x3 conv + ReLU, then MaxPool\n#   - Classifier: Flatten -> Dense(4096) -> Dense(4096) -> Dropout(0.3) -> Dense(num_classes)\n#   - We incorporate Dropout(0.3) in the classifier as requested\n# =========================================================\n\ndef build_vgg16(input_shape=(224,224,3), num_classes=10, dropout_rate=0.3):\n    inputs = layers.Input(shape=input_shape)\n\n    # Block 1: 64 filters\n    x = layers.Conv2D(64, (3,3), padding='same', activation='relu', kernel_initializer='he_normal')(inputs)\n    x = layers.Conv2D(64, (3,3), padding='same', activation='relu', kernel_initializer='he_normal')(x)\n    x = layers.MaxPooling2D((2,2), strides=2)(x)\n\n    # Block 2: 128 filters\n    x = layers.Conv2D(128, (3,3), padding='same', activation='relu', kernel_initializer='he_normal')(x)\n    x = layers.Conv2D(128, (3,3), padding='same', activation='relu', kernel_initializer='he_normal')(x)\n    x = layers.MaxPooling2D((2,2), strides=2)(x)\n\n    # Block 3: 256 filters (3 convs)\n    x = layers.Conv2D(256, (3,3), padding='same', activation='relu', kernel_initializer='he_normal')(x)\n    x = layers.Conv2D(256, (3,3), padding='same', activation='relu', kernel_initializer='he_normal')(x)\n    x = layers.Conv2D(256, (3,3), padding='same', activation='relu', kernel_initializer='he_normal')(x)\n    x = layers.MaxPooling2D((2,2), strides=2)(x)\n\n    # Block 4: 512 filters (3 convs)\n    x = layers.Conv2D(512, (3,3), padding='same', activation='relu', kernel_initializer='he_normal')(x)\n    x = layers.Conv2D(512, (3,3), padding='same', activation='relu', kernel_initializer='he_normal')(x)\n    x = layers.Conv2D(512, (3,3), padding='same', activation='relu', kernel_initializer='he_normal')(x)\n    x = layers.MaxPooling2D((2,2), strides=2)(x)\n\n    # Block 5: 512 filters (3 convs)\n    x = layers.Conv2D(512, (3,3), padding='same', activation='relu', kernel_initializer='he_normal')(x)\n    x = layers.Conv2D(512, (3,3), padding='same', activation='relu', kernel_initializer='he_normal')(x)\n    x = layers.Conv2D(512, (3,3), padding='same', activation='relu', kernel_initializer='he_normal')(x)\n    x = layers.MaxPooling2D((2,2), strides=2)(x)\n\n    # Classifier\n    x = layers.Flatten()(x)\n    x = layers.Dense(4096, activation='relu', kernel_initializer='he_normal')(x)\n    x = layers.Dropout(dropout_rate)(x)  # Dropout 0.3\n    x = layers.Dense(4096, activation='relu', kernel_initializer='he_normal')(x)\n    x = layers.Dropout(dropout_rate)(x)  # Dropout 0.3\n    outputs = layers.Dense(num_classes, activation='softmax')(x)\n\n    model = models.Model(inputs=inputs, outputs=outputs, name='VGG16_scratch')\n    return model\n\n# Build the model FROM SCRATCH\nmodel = build_vgg16(input_shape=(IMG_SIZE[0], IMG_SIZE[1], 3),\n                    num_classes=num_classes,\n                    dropout_rate=DROPOUT_RATE)\n\nmodel.summary()\n\n# -------------------------------\n# Compile\n# -------------------------------\nopt = tf.keras.optimizers.Adam(learning_rate=LR)\nmodel.compile(optimizer=opt, loss='categorical_crossentropy', metrics=['accuracy'])\n\n# -------------------------------\n# Callbacks\n# -------------------------------\nreduce_lr = ReduceLROnPlateau(monitor='val_loss', factor=0.5, patience=2, min_lr=1e-6, verbose=1)\nearly_stop = EarlyStopping(monitor='val_loss', patience=5, restore_best_weights=True, verbose=1)\nckpt = ModelCheckpoint(filepath='best_vgg16_scratch.h5',\n                       monitor='val_loss', save_best_only=True, verbose=1)\n\n# -------------------------------\n# Train\n# -------------------------------\nhistory = model.fit(\n    train_gen,\n    validation_data=val_gen,\n    epochs=EPOCHS,\n    callbacks=[reduce_lr, early_stop, ckpt],\n    verbose=1\n)\n\n# -------------------------------\n# Plot training curves (accuracy & loss)\n# -------------------------------\ndef plot_curves(hist, save_path_prefix=\"vgg16_scratch\"):\n    acc = hist.history['accuracy']\n    val_acc = hist.history['val_accuracy']\n    loss = hist.history['loss']\n    val_loss = hist.history['val_loss']\n    epochs_range = range(1, len(acc)+1)\n\n    plt.figure(figsize=(14,5))\n    # Accuracy\n    plt.subplot(1,2,1)\n    plt.plot(epochs_range, acc, 'b-', label='Train Accuracy')\n    plt.plot(epochs_range, val_acc, 'r-', label='Val Accuracy')\n    plt.xlabel('Epoch')\n    plt.ylabel('Accuracy')\n    plt.title('Accuracy vs Epochs')\n    plt.legend()\n    plt.grid(True)\n\n    # Loss\n    plt.subplot(1,2,2)\n    plt.plot(epochs_range, loss, 'b-', label='Train Loss')\n    plt.plot(epochs_range, val_loss, 'r-', label='Val Loss')\n    plt.xlabel('Epoch')\n    plt.ylabel('Loss')\n    plt.title('Loss vs Epochs')\n    plt.legend()\n    plt.grid(True)\n\n    plt.tight_layout()\n    plt.savefig(f\"{save_path_prefix}_curves.png\", dpi=120)\n    plt.show()\n\nplot_curves(history, save_path_prefix=\"vgg16_scratch\")\n\n# -------------------------------\n# Evaluate on test set + Confusion Matrix\n# -------------------------------\ntest_loss, test_acc = model.evaluate(test_gen, verbose=1)\nprint(f\"Test Loss: {test_loss:.4f} | Test Accuracy: {test_acc:.4f}\")\n\n# Predict class probabilities on test set\ny_prob = model.predict(test_gen, verbose=1)\ny_pred = np.argmax(y_prob, axis=1)\ny_true = test_gen.classes  # integer labels from directory order\n\n# Confusion matrix\ncm = confusion_matrix(y_true, y_pred)\nprint(\"Confusion Matrix:\\n\", cm)\n\n# Classification report\nprint(\"\\nClassification Report:\")\nprint(classification_report(y_true, y_pred, target_names=class_names, digits=4))\n\n# Plot Confusion Matrix\ndef plot_confusion_matrix(cm, classes, normalize=False, title='Confusion Matrix', cmap=plt.cm.Blues, save_path='cm_vgg16.png'):\n    if normalize:\n        cm = cm.astype('float') / (cm.sum(axis=1)[:, np.newaxis] + 1e-12)\n\n    plt.figure(figsize=(8, 6))\n    plt.imshow(cm, interpolation='nearest', cmap=cmap)\n    plt.title(title)\n    plt.colorbar(fraction=0.046, pad=0.04)\n\n    tick_marks = np.arange(len(classes))\n    plt.xticks(tick_marks, classes, rotation=45, ha='right')\n    plt.yticks(tick_marks, classes)\n\n    fmt = '.2f' if normalize else 'd'\n    thresh = cm.max() / 2.\n    for i, j in itertools.product(range(cm.shape[0]), range(cm.shape[1])):\n        plt.text(j, i, format(cm[i, j], fmt),\n                 horizontalalignment=\"center\",\n                 color=\"white\" if cm[i, j] > thresh else \"black\")\n\n    plt.tight_layout()\n    plt.ylabel('True label')\n    plt.xlabel('Predicted label')\n    plt.savefig(save_path, dpi=120, bbox_inches='tight')\n    plt.show()\n\nplot_confusion_matrix(cm, class_names, normalize=False, title='Confusion Matrix (Counts)', save_path='cm_counts_vgg16.png')\nplot_confusion_matrix(cm, class_names, normalize=True, title='Confusion Matrix (Normalized)', save_path='cm_normalized_vgg16.png')\n\n# -------------------------------\n# Save final model & history\n# -------------------------------\nmodel.save(\"vgg16_scratch_final.h5\")\n\nnp.savez(\"vgg16_scratch_history.npz\",\n         accuracy=history.history['accuracy'],\n         val_accuracy=history.history['val_accuracy'],\n         loss=history.history['loss'],\n         val_loss=history.history['val_loss'])\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-11-22T18:04:51.993276Z","iopub.execute_input":"2025-11-22T18:04:51.993956Z","iopub.status.idle":"2025-11-22T19:51:41.133522Z","shell.execute_reply.started":"2025-11-22T18:04:51.99393Z","shell.execute_reply":"2025-11-22T19:51:41.132721Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}