{"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":31089,"isInternetEnabled":true,"language":"python","sourceType":"notebook","isGpuEnabled":true}},"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\nimport numpy as np # linear algebra\nimport 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\nimport os\nfor 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-08-22T11:16:37.855882Z","iopub.execute_input":"2025-08-22T11:16:37.856413Z","iopub.status.idle":"2025-08-22T11:20:08.156095Z","shell.execute_reply.started":"2025-08-22T11:16:37.856391Z","shell.execute_reply":"2025-08-22T11:20:08.155256Z"}},"outputs":[],"execution_count":null},{"cell_type":"code","source":"# ============================================================\n# High-Accuracy CNN Multi-Pipeline (State Farm) — seed=42\n# From-scratch CNN, one-technique-per-pipeline, train-only:\n#   Lighting & Aug_Rotate. Shared 70/15/15 split saved to disk.\n# ============================================================\nimport os, glob, random, math, json, gc\nfrom pathlib import Path\nimport numpy as np\nimport pandas as pd\nimport cv2\nimport matplotlib.pyplot as plt\n\nfrom sklearn.model_selection import StratifiedShuffleSplit\nimport tensorflow as tf\nfrom tensorflow import keras\nfrom tensorflow.keras import layers\nfrom tensorflow.keras.utils import to_categorical\nfrom tensorflow.keras.callbacks import ReduceLROnPlateau\n\n# -------------------- Config --------------------\nSEED = 42\nrandom.seed(SEED); np.random.seed(SEED); tf.random.set_seed(SEED)\n\nDATA_DIR = \"/kaggle/input/state-farm-distracted-driver-detection/imgs/train\"\nCLASS_NAMES = [f\"c{i}\" for i in range(10)]\nNUM_CLASSES = 10\n\nIMG_SIZE = 224\nSCALE_RANGE = \"[0,1]\"     # or \"[-1,1]\"\nMAX_IMAGES = 4000         # raise for more data if VRAM allows\nCAP_PER_CLASS = MAX_IMAGES // len(CLASS_NAMES) if MAX_IMAGES else None\n\n# Accuracy toggles (ON by default)\nHIGH_ACCURACY = True\nEPOCHS = 20 if HIGH_ACCURACY else 5\nBATCH  = 32\nLR     = 1e-3\nWEIGHT_DECAY = 1e-4 if HIGH_ACCURACY else 0.0\nLABEL_SMOOTH = 0.05 if HIGH_ACCURACY else 0.0\nUSE_STANDARDIZE = True if HIGH_ACCURACY and SCALE_RANGE==\"[0,1]\" else False\n\nSAVE_DIR   = \"/kaggle/working/cnn_multipipeline\"\nPANEL_DIR  = f\"{SAVE_DIR}/panels\"\nSPLIT_FILE = \"/kaggle/working/dd_split_shared.npz\"\nos.makedirs(SAVE_DIR, exist_ok=True); os.makedirs(PANEL_DIR, exist_ok=True)\n\n# Pipelines (fixed order)\nPIPELINE_ORDER = [\n    \"Standard\",\n    \"Lighting\",         # TRAIN-only (val/test Standard)\n    \"Noise_Gaussian\",\n    \"Feature_Edges\",\n    \"Texture_LBP\",\n    \"BackgroundRemoval\",\n    \"MultiScale\",\n    \"Aug_Rotate\"        # TRAIN-only (val/test Standard)\n]\nTRAIN_ONLY = {\"Lighting\", \"Aug_Rotate\"}\n\n# -------------------- Helpers --------------------\ndef _scale_out(x01):\n    if SCALE_RANGE == \"[-1,1]\": return (x01*2.0 - 1.0).astype(np.float32)\n    return x01.astype(np.float32)\n\ndef _imread_rgb(path):\n    bgr = cv2.imread(path, cv2.IMREAD_COLOR)\n    if bgr is None: return None\n    return cv2.cvtColor(bgr, cv2.COLOR_BGR2RGB)\n\ndef _resize(img_rgb, size=IMG_SIZE):\n    return cv2.resize(img_rgb, (size, size), interpolation=cv2.INTER_AREA)\n\n# -------------------- Pipelines --------------------\ndef pp_standard(img_bgr):\n    rgb = _resize(cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB))\n    return _scale_out(rgb.astype(np.float32)/255.0)\n\ndef pp_lighting(img_bgr, clip=2.0, tile=8, gamma=1.15, alpha=1.05, beta=0.02):\n    rgb = _resize(cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB))\n    lab = cv2.cvtColor(rgb, cv2.COLOR_RGB2LAB)\n    L,A,B = cv2.split(lab)\n    clahe = cv2.createCLAHE(clipLimit=clip, tileGridSize=(tile,tile))\n    L2 = clahe.apply(L)\n    rgb2 = cv2.cvtColor(cv2.merge([L2,A,B]), cv2.COLOR_LAB2RGB).astype(np.float32)/255.0\n    rgb2 = np.power(np.clip(rgb2,0,1), gamma)\n    rgb2 = np.clip(alpha*rgb2 + beta, 0.0, 1.0)\n    return _scale_out(rgb2)\n\ndef pp_noise_gaussian(img_bgr, k=3):\n    rgb = _resize(cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB))\n    x = cv2.GaussianBlur(rgb, (k,k), 0)\n    x = cv2.morphologyEx(x, cv2.MORPH_OPEN, np.ones((2,2), np.uint8))\n    return _scale_out(x.astype(np.float32)/255.0)\n\ndef pp_feature_edges(img_bgr):\n    rgb = _resize(cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB))\n    g  = cv2.cvtColor(rgb, cv2.COLOR_RGB2GRAY)\n    e  = cv2.Canny(g, 100, 200).astype(np.float32)/255.0\n    e3 = np.repeat(e[...,None], 3, axis=-1)\n    return _scale_out(e3)\n\ndef _lbp_simple(gray_u8):\n    g = gray_u8.astype(np.float32); H,W = g.shape\n    c = g[1:-1,1:-1]; code = np.zeros((H-2,W-2), dtype=np.uint8)\n    for dy,dx,bit in [(-1,-1,7),(-1,0,6),(-1,1,5),(0,1,4),(1,1,3),(1,0,2),(1,-1,1),(0,-1,0)]:\n        nbr = g[1+dy:H-1+dy, 1+dx:W-1+dx]\n        code |= ((nbr >= c).astype(np.uint8) << bit)\n    out = np.zeros((H,W), dtype=np.float32); out[1:-1,1:-1] = code.astype(np.float32)\n    out = (out - out.min())/(out.max()-out.min()+1e-6)\n    return out\n\ndef pp_texture_lbp(img_bgr):\n    rgb = _resize(cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB))\n    g   = cv2.cvtColor(rgb, cv2.COLOR_RGB2GRAY)\n    lbp = _lbp_simple(g)\n    e3  = np.repeat(lbp[...,None], 3, axis=-1)\n    return _scale_out(e3)\n\ndef pp_bg_grabcut(img_bgr):\n    rgb = _resize(cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB))\n    h,w = rgb.shape[:2]\n    mask = np.full((h,w), cv2.GC_PR_BGD, np.uint8)\n    rect = (w//8, h//8, 3*w//4, 3*h//4)\n    bgd, fgd = np.zeros((1,65), np.float64), np.zeros((1,65), np.float64)\n    try:\n        cv2.grabCut(rgb, mask, rect, bgd, fgd, 3, cv2.GC_INIT_WITH_RECT)\n        fgmask = (mask==cv2.GC_FGD)|(mask==cv2.GC_PR_FGD)\n        out = np.zeros_like(rgb, dtype=np.float32)\n        out[fgmask] = rgb[fgmask].astype(np.float32)/255.0\n        return _scale_out(out)\n    except:\n        return pp_standard(img_bgr)\n\ndef pp_multiscale(img_bgr):\n    base = _resize(cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)).astype(np.float32)/255.0\n    s2   = cv2.resize(base, (IMG_SIZE*3//4, IMG_SIZE*3//4), interpolation=cv2.INTER_AREA)\n    s2   = cv2.resize(s2, (IMG_SIZE, IMG_SIZE), interpolation=cv2.INTER_AREA)\n    s3   = cv2.resize(base, (IMG_SIZE//2, IMG_SIZE//2), interpolation=cv2.INTER_AREA)\n    s3   = cv2.resize(s3, (IMG_SIZE, IMG_SIZE), interpolation=cv2.INTER_AREA)\n    out  = np.clip((base+s2+s3)/3.0, 0, 1).astype(np.float32)\n    return _scale_out(out)\n\ndef _rotate_rgb01(rgb01, max_deg=15):\n    u8 = (np.clip(rgb01,0,1)*255).astype(np.uint8)\n    h,w = u8.shape[:2]\n    M = cv2.getRotationMatrix2D((w/2, h/2), np.random.uniform(-max_deg, max_deg), 1.0)\n    rot = cv2.warpAffine(u8, M, (w,h), borderMode=cv2.BORDER_REFLECT101)\n    return rot.astype(np.float32)/255.0\n\ndef pp_aug_rotate(img_bgr):\n    # rotation applied in loader when split == \"train\"\n    rgb = _resize(cv2.cvtColor(img_bgr, cv2.COLOR_BGR2RGB)).astype(np.float32)/255.0\n    return _scale_out(rgb)\n\nPIPELINES = {\n    \"Standard\":           pp_standard,\n    \"Lighting\":           pp_lighting,\n    \"Noise_Gaussian\":     pp_noise_gaussian,\n    \"Feature_Edges\":      pp_feature_edges,\n    \"Texture_LBP\":        pp_texture_lbp,\n    \"BackgroundRemoval\":  pp_bg_grabcut,\n    \"MultiScale\":         pp_multiscale,\n    \"Aug_Rotate\":         pp_aug_rotate,\n}\n\n# -------------------- Files & split --------------------\ndef list_files(root, per_class=None):\n    X, y = [], []\n    for ci, cname in enumerate(CLASS_NAMES):\n        files = sorted(glob.glob(os.path.join(root, cname, \"*.jpg\")))\n        if per_class: files = files[:per_class]\n        X += files; y += [ci]*len(files)\n    return np.array(X), np.array(y, dtype=np.int32)\n\nALL_FILES, ALL_LABELS = list_files(DATA_DIR, CAP_PER_CLASS)\nprint(f\"Total files (capped): {len(ALL_FILES)}\")\n\nif os.path.exists(SPLIT_FILE):\n    s = np.load(SPLIT_FILE, allow_pickle=True)\n    tr_idx, va_idx, te_idx = s[\"tr\"], s[\"va\"], s[\"te\"]\n    print(\"Loaded shared 70/15/15 split.\")\nelse:\n    idx = np.arange(len(ALL_LABELS))\n    sss1 = StratifiedShuffleSplit(n_splits=1, test_size=0.30, random_state=SEED)\n    (tr_idx, tmp_idx), = sss1.split(idx, ALL_LABELS)\n    sss2 = StratifiedShuffleSplit(n_splits=1, test_size=0.50, random_state=SEED)\n    (va_rel, te_rel), = sss2.split(tmp_idx, ALL_LABELS[tmp_idx])\n    va_idx, te_idx = tmp_idx[va_rel], tmp_idx[te_rel]\n    np.savez(SPLIT_FILE, tr=tr_idx, va=va_idx, te=te_idx)\n    print(\"Created shared 70/15/15 split and saved.\")\n\nTR_FILES, VA_FILES, TE_FILES = ALL_FILES[tr_idx], ALL_FILES[va_idx], ALL_FILES[te_idx]\nTR_Y,     VA_Y,     TE_Y     = ALL_LABELS[tr_idx], ALL_LABELS[va_idx], ALL_LABELS[te_idx]\n\n# -------------------- Standardization (optional) --------------------\nMEAN, STD = None, None\ndef estimate_mean_std(paths, sample_n=2000):\n    sel = paths if len(paths) <= sample_n else np.random.RandomState(SEED).choice(paths, sample_n, replace=False)\n    s_mean = np.zeros(3, dtype=np.float64); s_sq = np.zeros(3, dtype=np.float64); n_pix = 0\n    for p in sel:\n        bgr = cv2.imread(p, cv2.IMREAD_COLOR)\n        if bgr is None: continue\n        x = pp_standard(bgr)  # [0,1]\n        h,w,_ = x.shape\n        s_mean += x.reshape(-1,3).sum(axis=0)\n        s_sq   += (x.reshape(-1,3)**2).sum(axis=0)\n        n_pix  += h*w\n    mean = s_mean / max(n_pix,1)\n    var  = s_sq / max(n_pix,1) - mean**2\n    std  = np.sqrt(np.clip(var, 1e-8, None))\n    return mean.astype(np.float32), std.astype(np.float32)\n\nif USE_STANDARDIZE:\n    MEAN, STD = estimate_mean_std(TR_FILES)\n    print(\"Channel mean/std (TRAIN/Standard):\", MEAN, STD)\n\ndef standardize(x):\n    if MEAN is None or STD is None: return x\n    return (x - MEAN) / STD\n\n# -------------------- Loader Sequence --------------------\nclass PipelineSequence(keras.utils.Sequence):\n    def __init__(self, files, labels, pname, split, batch=BATCH, shuffle=True):\n        self.files = files.copy(); self.labels = labels.copy()\n        self.pname = pname; self.split = split; self.batch = batch\n        self.shuffle = shuffle; self.rng = np.random.RandomState(SEED)\n        self.on_epoch_end()\n    def __len__(self): return math.ceil(len(self.files)/self.batch)\n    def on_epoch_end(self):\n        if self.shuffle and self.split==\"train\":\n            idx = self.rng.permutation(len(self.files))\n            self.files, self.labels = self.files[idx], self.labels[idx]\n    def __getitem__(self, i):\n        sl = slice(i*self.batch, (i+1)*self.batch)\n        batch_files = self.files[sl]; batch_labels = self.labels[sl]\n        Xb, yb = [], []\n        pfunc = PIPELINES[self.pname]\n        for p, lab in zip(batch_files, batch_labels):\n            bgr = cv2.imread(p, cv2.IMREAD_COLOR)\n            if bgr is None: continue\n            if self.pname in TRAIN_ONLY and self.split != \"train\":\n                x = pp_standard(bgr)  # eval = Standard\n            else:\n                x = pfunc(bgr)\n                if self.pname == \"Aug_Rotate\" and self.split == \"train\":\n                    x = _rotate_rgb01(x, 15)\n            if USE_STANDARDIZE and SCALE_RANGE==\"[0,1]\":\n                x = standardize(x)\n            Xb.append(x); yb.append(lab)\n        X = np.array(Xb, dtype=np.float32)\n        y = to_categorical(np.array(yb, dtype=np.int32), num_classes=NUM_CLASSES)\n        return X, y\n\ndef show_panel(pname):\n    sel = TR_FILES[:min(5, len(TR_FILES))]\n    grid = []\n    for p in sel:\n        bgr = cv2.imread(p, cv2.IMREAD_COLOR)\n        if bgr is None: continue\n        if pname in TRAIN_ONLY:\n            x = PIPELINES[pname](bgr)\n            if pname==\"Aug_Rotate\": x = _rotate_rgb01(x, 15)\n        else:\n            x = PIPELINES[pname](bgr)\n        if USE_STANDARDIZE and SCALE_RANGE==\"[0,1]\": x = standardize(x)\n        grid.append(x)\n    if not grid: return None\n    cols = len(grid); plt.figure(figsize=(3*cols, 3))\n    for i, im in enumerate(grid, 1):\n        if USE_STANDARDIZE:\n            # visualize standardized by de-standardizing to [0,1]\n            vis = np.clip(im*STD + MEAN, 0, 1) if MEAN is not None else np.clip(im,0,1)\n        else:\n            vis = np.clip(im,0,1) if SCALE_RANGE==\"[0,1]\" else np.clip((im+1)/2,0,1)\n        plt.subplot(1, cols, i); plt.imshow(vis); plt.axis(\"off\")\n    plt.suptitle(f\"{pname} — TRAIN samples\")\n    out = f\"{PANEL_DIR}/panel_{pname}.png\"\n    plt.tight_layout(); plt.savefig(out, dpi=120, bbox_inches=\"tight\"); plt.close()\n    return out\n\n# -------------------- Model (4 conv blocks, BN, Dropout) --------------------\ndef build_cnn():\n    L2 = keras.regularizers.l2(WEIGHT_DECAY) if WEIGHT_DECAY>0 else None\n    inp = layers.Input(shape=(IMG_SIZE, IMG_SIZE, 3))\n    def block(x, ch, dp=0.0):\n        x = layers.Conv2D(ch, 3, padding=\"same\", use_bias=False, kernel_regularizer=L2)(x)\n        x = layers.BatchNormalization()(x); x = layers.ReLU()(x)\n        x = layers.Conv2D(ch, 3, padding=\"same\", use_bias=False, kernel_regularizer=L2)(x)\n        x = layers.BatchNormalization()(x); x = layers.ReLU()(x)\n        x = layers.MaxPooling2D()(x)\n        if dp>0: x = layers.Dropout(dp)(x)\n        return x\n    x = block(inp, 32, dp=0.1)\n    x = block(x,   64, dp=0.15)\n    x = block(x,  128, dp=0.2)\n    x = layers.Conv2D(256, 3, padding=\"same\", use_bias=False, kernel_regularizer=L2)(x)\n    x = layers.BatchNormalization()(x); x = layers.ReLU()(x)\n    x = layers.GlobalAveragePooling2D()(x)\n    x = layers.Dense(256, activation=None, use_bias=False, kernel_regularizer=L2)(x)\n    x = layers.BatchNormalization()(x); x = layers.ReLU()(x)\n    x = layers.Dropout(0.3 if HIGH_ACCURACY else 0.2)(x)\n    out = layers.Dense(NUM_CLASSES, activation=\"softmax\")(x)\n\n    model = keras.Model(inp, out)\n    # Optimizer: AdamW if available, else Adam\n    try:\n        opt = keras.optimizers.AdamW(learning_rate=LR, weight_decay=WEIGHT_DECAY)\n    except Exception:\n        opt = keras.optimizers.Adam(learning_rate=LR)\n    loss = keras.losses.CategoricalCrossentropy(label_smoothing=LABEL_SMOOTH)\n    model.compile(optimizer=opt, loss=loss, metrics=[\"accuracy\"])\n    return model\n\n# -------------------- Train one pipeline --------------------\ndef train_one_pipeline(pname):\n    print(f\"\\n===== Pipeline: {pname} =====\")\n    panel_path = show_panel(pname)\n\n    train_seq = PipelineSequence(TR_FILES, TR_Y, pname, split=\"train\", batch=BATCH, shuffle=True)\n    val_seq   = PipelineSequence(VA_FILES, VA_Y, pname, split=\"val\",   batch=BATCH, shuffle=False)\n    test_seq  = PipelineSequence(TE_FILES, TE_Y, pname, split=\"test\",  batch=BATCH, shuffle=False)\n\n    model = build_cnn()\n    cbs = [\n        ReduceLROnPlateau(monitor=\"val_accuracy\", factor=0.5, patience=3, min_lr=1e-6, verbose=1),\n        keras.callbacks.EarlyStopping(monitor=\"val_accuracy\", patience=6, restore_best_weights=True, mode=\"max\", verbose=1),\n    ]\n    hist = model.fit(train_seq, validation_data=val_seq, epochs=EPOCHS, verbose=2, callbacks=cbs)\n\n    va_acc  = float(model.evaluate(val_seq,  verbose=0)[1])\n    te_acc  = float(model.evaluate(test_seq, verbose=0)[1])\n\n    out_dir = f\"{SAVE_DIR}/{pname}\"; os.makedirs(out_dir, exist_ok=True)\n    model.save(f\"{out_dir}/cnn_model.keras\")\n    with open(f\"{out_dir}/history.json\",\"w\") as f:\n        json.dump({k:[float(v) for v in hist.history[k]] for k in hist.history}, f)\n    if panel_path:\n        os.replace(panel_path, f\"{out_dir}/\" + os.path.basename(panel_path))\n\n    print(f\"[{pname}] Val Acc: {va_acc:.4f} | Test Acc: {te_acc:.4f}\")\n    return {\"val_acc\": va_acc, \"test_acc\": te_acc}\n\n# -------------------- Run all pipelines --------------------\nresults = {}\nfor pname in PIPELINE_ORDER:\n    results[pname] = train_one_pipeline(pname); gc.collect()\n\nwith open(f\"{SAVE_DIR}/results.json\",\"w\") as f: json.dump(results, f, indent=2)\ndf = pd.DataFrame([{\"pipeline\":k, **v} for k,v in results.items()])[[\"pipeline\",\"val_acc\",\"test_acc\"]]\nprint(\"\\n=== Accuracies ===\"); print(df.sort_values(\"test_acc\", ascending=False))\n\n# Bar chart\norder = PIPELINE_ORDER\nxs = np.arange(len(order)); vals = [results[p][\"test_acc\"] for p in order]\nplt.figure(figsize=(10,4)); plt.bar(xs, vals)\nplt.xticks(xs, order, rotation=20); plt.ylabel(\"Test Accuracy\")\nplt.title(\"CNN — Test Accuracy by Pipeline (70/15/15 shared split)\")\nplt.tight_layout(); plt.savefig(f\"{SAVE_DIR}/bar_test_accuracy.png\", dpi=130); plt.show()\n\nprint(f\"\\nArtifacts saved under: {SAVE_DIR}\")\n","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2025-08-22T11:26:45.968330Z","iopub.execute_input":"2025-08-22T11:26:45.968725Z","iopub.status.idle":"2025-08-22T17:59:22.065803Z","shell.execute_reply.started":"2025-08-22T11:26:45.968706Z","shell.execute_reply":"2025-08-22T17:59:22.064962Z"}},"outputs":[],"execution_count":null}]}