{"metadata":{"kernelspec":{"display_name":"Python 3","language":"python","name":"python3"},"language_info":{"name":"python","version":"3.13.15","mimetype":"text/x-python","codemirror_mode":{"name":"ipython","version":3},"pygments_lexer":"ipython3","nbconvert_exporter":"python","file_extension":".py"},"kaggle":{"accelerator":"none","dataSources":[],"dockerImageVersionId":null,"isGpuEnabled":false,"isInternetEnabled":true,"language":"python","sourceType":"notebook"}},"nbformat_minor":4,"nbformat":4,"cells":[{"id":"ff8601ad-fd4e-4d13-a173-5516485ee847","cell_type":"markdown","source":"# Assignment 1: K-Nearest Neighbours on CIFAR-10\nRun the notebook from top to bottom. The default stratified subset keeps manual KNN practical on Kaggle; increase the limits if you have more time.","metadata":{}},{"id":"3877f614-65cf-437e-8796-32ac884fad1f","cell_type":"markdown","source":"### Input / setup cell","metadata":{}},{"id":"1d0fa4e3-4819-465e-9401-a9df6d80cbee","cell_type":"code","source":"import os, glob, subprocess, warnings\nfrom pathlib import Path\nimport numpy as np\nimport pandas as pd\nimport matplotlib.pyplot as plt\nimport seaborn as sns\nfrom PIL import Image\nfrom sklearn.model_selection import train_test_split\nfrom sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score, confusion_matrix, classification_report\nwarnings.filterwarnings('ignore')\nSEED = 42\nTRAIN_LIMIT = 5000  # increase for a slower, potentially stronger experiment\nTEST_LIMIT = 1000\nK_VALUES = [1, 3, 5, 7, 9]\nCLASS_NAMES = ['airplane','automobile','bird','cat','deer','dog','frog','horse','ship','truck']\nprint('Setup complete. Kaggle inputs:', glob.glob('/kaggle/input/*'))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-06T15:10:58.972798Z","iopub.execute_input":"2026-10-06T15:10:58.973111Z","iopub.status.idle":"2026-10-06T15:11:00.175722Z","shell.execute_reply.started":"2026-10-06T15:10:58.973083Z","shell.execute_reply":"2026-10-06T15:11:00.174707Z"}},"outputs":[],"execution_count":null},{"id":"67e7c854-7038-4997-90eb-fecd28764ba8","cell_type":"markdown","source":"### Task 1: Load CIFAR-10 directly from the attached Kaggle competition input","metadata":{}},{"id":"f174b740-15e9-4c80-a1c9-c5d826e6f50f","cell_type":"code","source":"from pathlib import Path\nimport subprocess\n\nimport numpy as np\nimport pandas as pd\nfrom PIL import Image\nfrom sklearn.model_selection import train_test_split\n\nSEED = 42\nTRAIN_LIMIT = 40_000\nTEST_LIMIT = 10_000\n\nCLASS_NAMES = [\n    \"airplane\",\n    \"automobile\",\n    \"bird\",\n    \"cat\",\n    \"deer\",\n    \"dog\",\n    \"frog\",\n    \"horse\",\n    \"ship\",\n    \"truck\",\n]\n\n# Automatically locate the attached CIFAR-10 competition files.\nINPUT_ROOT = Path(\"/kaggle/input\")\nlabel_matches = list(INPUT_ROOT.rglob(\"trainLabels.csv\"))\n\nif not label_matches:\n    available = \"\\n\".join(str(path) for path in INPUT_ROOT.glob(\"*\"))\n    raise FileNotFoundError(\n        \"Could not find trainLabels.csv in /kaggle/input.\\n\"\n        \"Attach the CIFAR-10 competition data and restart the session.\\n\"\n        f\"Currently available inputs:\\n{available or '(none)'}\"\n    )\n\nDATA_DIR = label_matches[0].parent\nLABELS_FILE = DATA_DIR / \"trainLabels.csv\"\nARCHIVE_FILE = DATA_DIR / \"train.7z\"\n\nEXTRACT_DIR = Path(\"/kaggle/working/cifar10\")\nTRAIN_DIR = EXTRACT_DIR / \"train\"\n\nif not ARCHIVE_FILE.exists():\n    raise FileNotFoundError(f\"Could not find archive: {ARCHIVE_FILE}\")\n\n# Extract train.7z only when the training images are not already available.\nif not TRAIN_DIR.exists():\n    EXTRACT_DIR.mkdir(parents=True, exist_ok=True)\n    subprocess.run(\n        [\"7z\", \"x\", str(ARCHIVE_FILE), f\"-o{EXTRACT_DIR}\", \"-y\"],\n        check=True,\n    )\n\nlabels = pd.read_csv(LABELS_FILE)\n\nclass_to_index = {\n    class_name: index for index, class_name in enumerate(CLASS_NAMES)\n}\nlabels[\"target\"] = labels[\"label\"].map(class_to_index)\n\nif labels[\"target\"].isna().any():\n    unknown = labels.loc[labels[\"target\"].isna(), \"label\"].unique()\n    raise ValueError(f\"Unknown CIFAR-10 labels found: {unknown}\")\n\nlabels[\"target\"] = labels[\"target\"].astype(np.int64)\n\n# Select the requested number of images while preserving class balance.\nsample_count = min(TRAIN_LIMIT + TEST_LIMIT, len(labels))\n\nif sample_count < len(labels):\n    selected, _ = train_test_split(\n        labels,\n        train_size=sample_count,\n        stratify=labels[\"target\"],\n        random_state=SEED,\n    )\nelse:\n    selected = labels.sample(frac=1, random_state=SEED)\n\nselected = selected.reset_index(drop=True)\n\nX_all = np.stack(\n    [\n        np.asarray(\n            Image.open(TRAIN_DIR / f\"{image_id}.png\").convert(\"RGB\"),\n            dtype=np.uint8,\n        )\n        for image_id in selected[\"id\"]\n    ]\n)\n\ny_all = selected[\"target\"].to_numpy(dtype=np.int64)\n\nprint(f\"Input directory: {DATA_DIR}\")\nprint(f\"Training images: {TRAIN_DIR}\")\nprint(f\"Loaded images: {X_all.shape}\")\nprint(f\"Loaded labels: {y_all.shape}\")\nprint(f\"Classes: {CLASS_NAMES}\")","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-06T15:12:21.709206Z","iopub.execute_input":"2026-10-06T15:12:21.709573Z","iopub.status.idle":"2026-10-06T15:12:46.535987Z","shell.execute_reply.started":"2026-10-06T15:12:21.709547Z","shell.execute_reply":"2026-10-06T15:12:46.535105Z"}},"outputs":[],"execution_count":null},{"id":"b378e98d-8674-4a23-8f57-658b590c57ea","cell_type":"markdown","source":"### Task 2: Visualize one sample image from every class","metadata":{}},{"id":"4709f3a2-7258-42b4-b049-1dcc7ef76f61","cell_type":"code","source":"fig, axes = plt.subplots(2, 5, figsize=(12, 5))\nfor class_id, ax in enumerate(axes.ravel()):\n    idx = np.flatnonzero(y_all == class_id)[0]\n    ax.imshow(X_all[idx])\n    ax.set_title(CLASS_NAMES[class_id])\n    ax.axis('off')\nplt.suptitle('CIFAR-10 class samples')\nplt.tight_layout()\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-06T15:13:20.352861Z","iopub.execute_input":"2026-10-06T15:13:20.353249Z","iopub.status.idle":"2026-10-06T15:13:20.943371Z","shell.execute_reply.started":"2026-10-06T15:13:20.353206Z","shell.execute_reply":"2026-10-06T15:13:20.942326Z"}},"outputs":[],"execution_count":null},{"id":"c169f67a-08ac-4eb8-b899-2fc7f99de43e","cell_type":"markdown","source":"### Task 3: Convert images into one-dimensional feature vectors","metadata":{}},{"id":"2805cc65-8d85-41e0-81f4-d4b07e3cc1e1","cell_type":"code","source":"X_vectors = X_all.reshape(len(X_all), -1)\nprint('Images:', X_all.shape, '-> feature vectors:', X_vectors.shape)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-06T15:13:33.667462Z","iopub.execute_input":"2026-10-06T15:13:33.667782Z","iopub.status.idle":"2026-10-06T15:13:33.673046Z","shell.execute_reply.started":"2026-10-06T15:13:33.667759Z","shell.execute_reply":"2026-10-06T15:13:33.67211Z"}},"outputs":[],"execution_count":null},{"id":"658647d9-f10f-4ced-887e-b0dbba9ef0f2","cell_type":"markdown","source":"### Task 4: Normalize pixel values to [0, 1]","metadata":{}},{"id":"f525367d-8a41-4fd6-b258-d0bf5a2b2c6e","cell_type":"code","source":"X_vectors = X_vectors.astype(np.float32) / 255.0\nprint('Range:', float(X_vectors.min()), 'to', float(X_vectors.max()))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-06T15:13:48.369712Z","iopub.execute_input":"2026-10-06T15:13:48.370036Z","iopub.status.idle":"2026-10-06T15:13:48.915528Z","shell.execute_reply.started":"2026-10-06T15:13:48.370011Z","shell.execute_reply":"2026-10-06T15:13:48.914751Z"}},"outputs":[],"execution_count":null},{"id":"171a589a-1521-41e2-ae29-10495247b7ec","cell_type":"markdown","source":"### Task 5: Create stratified training and testing sets","metadata":{}},{"id":"8c928350-08d5-40a3-b7a7-3e0a94cf97ec","cell_type":"code","source":"X_train, X_test, y_train, y_test = train_test_split(X_vectors, y_all, test_size=TEST_LIMIT, stratify=y_all, random_state=SEED)\nprint('Train:', X_train.shape, 'Test:', X_test.shape)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-06T15:14:01.937184Z","iopub.execute_input":"2026-10-06T15:14:01.937558Z","iopub.status.idle":"2026-10-06T15:14:02.157281Z","shell.execute_reply.started":"2026-10-06T15:14:01.937531Z","shell.execute_reply":"2026-10-06T15:14:02.156477Z"}},"outputs":[],"execution_count":null},{"id":"4c2b6476-55a3-4006-b999-155388e14d60","cell_type":"markdown","source":"### Task 6: Manual KNN using Euclidean distance and K = 1, 3, 5, 7, 9","metadata":{}},{"id":"9749a37a-1ec1-420a-8539-5b3f1830f4ee","cell_type":"code","source":"def top_k_neighbors(X_query, X_reference, max_k=9, batch_size=100):\n    # Squared Euclidean distance: ||q-r||^2 = ||q||^2 + ||r||^2 - 2(q.r).\n    ref_norm = np.sum(X_reference * X_reference, axis=1)\n    neighbor_blocks = []\n    for start in range(0, len(X_query), batch_size):\n        q = X_query[start:start + batch_size]\n        distances = np.sum(q*q, axis=1, keepdims=True) + ref_norm[None, :] - 2.0 * (q @ X_reference.T)\n        idx = np.argpartition(distances, kth=max_k-1, axis=1)[:, :max_k]\n        selected = np.take_along_axis(distances, idx, axis=1)\n        idx = np.take_along_axis(idx, np.argsort(selected, axis=1), axis=1)\n        neighbor_blocks.append(idx)\n    return np.vstack(neighbor_blocks)\n\ndef predict_from_neighbors(neighbor_indices, y_reference, k):\n    labels = y_reference[neighbor_indices[:, :k]]\n    return np.array([np.bincount(row, minlength=10).argmax() for row in labels])\n\nneighbor_indices = top_k_neighbors(X_test, X_train, max(K_VALUES))\npredictions = {k: predict_from_neighbors(neighbor_indices, y_train, k) for k in K_VALUES}\nprint('Generated predictions for:', K_VALUES)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-06T15:14:13.363004Z","iopub.execute_input":"2026-10-06T15:14:13.36335Z","iopub.status.idle":"2026-10-06T15:14:39.179769Z","shell.execute_reply.started":"2026-10-06T15:14:13.363319Z","shell.execute_reply":"2026-10-06T15:14:39.178324Z"}},"outputs":[],"execution_count":null},{"id":"fab31069-39ed-40bb-b8e4-9db70eb6b8e8","cell_type":"markdown","source":"### Task 7: Calculate accuracy, macro precision, recall, and F1-score","metadata":{}},{"id":"124bf2b9-d0f2-4e12-ba08-0a353460301e","cell_type":"code","source":"rows = []\nfor k, y_pred in predictions.items():\n    rows.append({'K': k, 'Accuracy': accuracy_score(y_test, y_pred),\n                 'Precision': precision_score(y_test, y_pred, average='macro', zero_division=0),\n                 'Recall': recall_score(y_test, y_pred, average='macro', zero_division=0),\n                 'F1-score': f1_score(y_test, y_pred, average='macro', zero_division=0)})\nresults = pd.DataFrame(rows).set_index('K')\ndisplay(results.style.format('{:.4f}'))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-06T15:14:39.181728Z","iopub.execute_input":"2026-10-06T15:14:39.18204Z","iopub.status.idle":"2026-10-06T15:14:39.366775Z","shell.execute_reply.started":"2026-10-06T15:14:39.182008Z","shell.execute_reply":"2026-10-06T15:14:39.365846Z"}},"outputs":[],"execution_count":null},{"id":"7d434e6b-dac3-46fb-9eb0-4ab5478da0f2","cell_type":"markdown","source":"### Task 8: Plot accuracy versus K","metadata":{}},{"id":"b47d00c3-0c89-4245-88d7-f334a43f3015","cell_type":"code","source":"plt.figure(figsize=(7, 4))\nplt.plot(results.index, results['Accuracy'], marker='o', linewidth=2)\nplt.xticks(K_VALUES)\nplt.xlabel('K')\nplt.ylabel('Test accuracy')\nplt.title('KNN accuracy versus K')\nplt.grid(alpha=.3)\nplt.show()","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-06T15:14:51.605181Z","iopub.execute_input":"2026-10-06T15:14:51.606478Z","iopub.status.idle":"2026-10-06T15:14:51.737675Z","shell.execute_reply.started":"2026-10-06T15:14:51.606441Z","shell.execute_reply":"2026-10-06T15:14:51.736752Z"}},"outputs":[],"execution_count":null},{"id":"f84825a2-9ed1-438c-8235-eca78e230406","cell_type":"markdown","source":"### Task 9: Display the confusion matrix for the best K","metadata":{}},{"id":"33ee0775-258d-445d-8d20-240a25a0c7c0","cell_type":"code","source":"best_k = int(results['Accuracy'].idxmax())\nbest_pred = predictions[best_k]\ncm = confusion_matrix(y_test, best_pred, labels=np.arange(10))\nplt.figure(figsize=(10, 8))\nsns.heatmap(cm, annot=True, fmt='d', cmap='Blues', xticklabels=CLASS_NAMES, yticklabels=CLASS_NAMES)\nplt.title(f'Confusion matrix (best K = {best_k})')\nplt.xlabel('Predicted label'); plt.ylabel('True label')\nplt.tight_layout(); plt.show()\nprint(classification_report(y_test, best_pred, target_names=CLASS_NAMES, zero_division=0))","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-06T15:15:07.646715Z","iopub.execute_input":"2026-10-06T15:15:07.647089Z","iopub.status.idle":"2026-10-06T15:15:08.116827Z","shell.execute_reply.started":"2026-10-06T15:15:07.647054Z","shell.execute_reply":"2026-10-06T15:15:08.115844Z"}},"outputs":[],"execution_count":null},{"id":"a8cdb112-f6a4-4ee0-a883-50576562cde4","cell_type":"markdown","source":"### Task 10: Analyze the effect of K on classification performance","metadata":{}},{"id":"a4fe2cf1-4565-445d-9002-f4a59031fb82","cell_type":"code","source":"best_row = results.loc[best_k]\nprint(f'Best result: K={best_k}, accuracy={best_row[\"Accuracy\"]:.4f}, macro F1={best_row[\"F1-score\"]:.4f}.')\nif best_k == min(K_VALUES):\n    print('The smallest K performed best here; nearby samples are informative, while extra neighbors add bias.')\nelif best_k == max(K_VALUES):\n    print('Accuracy kept improving through the tested range; smoothing reduced sensitivity to noisy neighbors.')\nelse:\n    print('An intermediate K gave the best bias-variance balance on this split.')\nprint('Small K is flexible but noise-sensitive. Larger K produces smoother boundaries but can underfit and favor common local classes.')\ndisplay(results)","metadata":{"trusted":true,"execution":{"iopub.status.busy":"2026-10-06T15:15:26.236876Z","iopub.execute_input":"2026-10-06T15:15:26.237686Z","iopub.status.idle":"2026-10-06T15:15:26.268779Z","shell.execute_reply.started":"2026-10-06T15:15:26.23764Z","shell.execute_reply":"2026-10-06T15:15:26.267872Z"}},"outputs":[],"execution_count":null},{"id":"66181d76-1937-49f9-8f44-3b737354b155","cell_type":"code","source":"","metadata":{"trusted":true},"outputs":[],"execution_count":null}]}