From f23d8a5153215ce7097d66fb0a4ce67266031ee7 Mon Sep 17 00:00:00 2001 From: aroa <239125952+aroaxinping@users.noreply.github.com> Date: Sun, 6 Sep 2026 13:59:16 +0200 Subject: [PATCH] Solve lab: Grid Search tuning on the best-performing Random Forest --- lab-hyper-tuning.ipynb | 1225 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 1198 insertions(+), 27 deletions(-) diff --git a/lab-hyper-tuning.ipynb b/lab-hyper-tuning.ipynb index 847d487..fe4faaa 100644 --- a/lab-hyper-tuning.ipynb +++ b/lab-hyper-tuning.ipynb @@ -2,6 +2,7 @@ "cells": [ { "cell_type": "markdown", + "id": "e24b1e33", "metadata": {}, "source": [ "# LAB | Hyperparameter Tuning" @@ -9,6 +10,7 @@ }, { "cell_type": "markdown", + "id": "fe90ca92", "metadata": {}, "source": [ "**Load the data**\n", @@ -26,6 +28,7 @@ }, { "cell_type": "markdown", + "id": "5b92ec55", "metadata": {}, "source": [ "So far we've been training and evaluating models with default values for hyperparameters.\n", @@ -36,7 +39,15 @@ { "cell_type": "code", "execution_count": 1, - "metadata": {}, + "id": "6b74b3e3", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-06T11:55:43.266597Z", + "iopub.status.busy": "2026-09-06T11:55:43.266475Z", + "iopub.status.idle": "2026-09-06T11:55:45.095933Z", + "shell.execute_reply": "2026-09-06T11:55:45.095333Z" + } + }, "outputs": [], "source": [ "#Libraries\n", @@ -48,7 +59,15 @@ { "cell_type": "code", "execution_count": 2, - "metadata": {}, + "id": "d9908f91", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-06T11:55:45.099249Z", + "iopub.status.busy": "2026-09-06T11:55:45.099072Z", + "iopub.status.idle": "2026-09-06T11:55:45.357116Z", + "shell.execute_reply": "2026-09-06T11:55:45.356525Z" + } + }, "outputs": [ { "data": { @@ -212,6 +231,7 @@ }, { "cell_type": "markdown", + "id": "9e8eadb1", "metadata": {}, "source": [ "Now perform the same as before:\n", @@ -221,15 +241,56 @@ }, { "cell_type": "code", - "execution_count": 9, - "metadata": {}, - "outputs": [], + "execution_count": 3, + "id": "4afd474a", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-06T11:55:45.359583Z", + "iopub.status.busy": "2026-09-06T11:55:45.359460Z", + "iopub.status.idle": "2026-09-06T11:55:45.403153Z", + "shell.execute_reply": "2026-09-06T11:55:45.402496Z" + } + }, + "outputs": [ + { + "data": { + "text/plain": [ + "((5284, 19), (1322, 19))" + ] + }, + "execution_count": 3, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "#your code here" + "# Same feature engineering as the feature-engineering and ensemble labs:\n", + "# drop nulls, reduce Cabin to its deck letter, drop the id/text columns,\n", + "# dummy-encode the rest, then scale.\n", + "from sklearn.preprocessing import StandardScaler\n", + "\n", + "spaceship = spaceship.dropna()\n", + "spaceship[\"Cabin\"] = spaceship[\"Cabin\"].str[0]\n", + "spaceship = spaceship.drop(columns=[\"PassengerId\", \"Name\"])\n", + "\n", + "categorical_cols = spaceship.select_dtypes(include=[\"object\", \"bool\"]).columns.drop(\"Transported\")\n", + "spaceship_encoded = pd.get_dummies(spaceship, columns=categorical_cols, drop_first=True)\n", + "\n", + "features = spaceship_encoded.drop(columns=[\"Transported\"])\n", + "target = spaceship_encoded[\"Transported\"]\n", + "\n", + "X_train, X_test, y_train, y_test = train_test_split(features, target, test_size=0.20, random_state=0)\n", + "\n", + "scaler = StandardScaler()\n", + "X_train = scaler.fit_transform(X_train)\n", + "X_test = scaler.transform(X_test)\n", + "\n", + "X_train.shape, X_test.shape" ] }, { "cell_type": "markdown", + "id": "00438f34", "metadata": {}, "source": [ "- Now let's use the best model we got so far in order to see how it can improve when we fine tune it's hyperparameters." @@ -237,15 +298,1023 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], + "execution_count": 4, + "id": "72b1322c", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-06T11:55:45.404557Z", + "iopub.status.busy": "2026-09-06T11:55:45.404445Z", + "iopub.status.idle": "2026-09-06T11:55:45.922098Z", + "shell.execute_reply": "2026-09-06T11:55:45.921577Z" + } + }, + "outputs": [ + { + "data": { + "text/html": [ + "
RandomForestClassifier(random_state=0)
In a Jupyter environment, please rerun this cell to show the HTML representation or trust the notebook.
On GitHub, the HTML representation is unable to render, please try loading this page with nbviewer.org.
" + ], + "text/plain": [ + "RandomForestClassifier(random_state=0)" + ] + }, + "execution_count": 4, + "metadata": {}, + "output_type": "execute_result" + } + ], "source": [ - "#your code here" + "# Random Forest was the best model from the Ensemble lab (79.3% test\n", + "# accuracy, ahead of Bagging/Gradient Boosting/AdaBoost), so it's the one\n", + "# we fine-tune here.\n", + "from sklearn.ensemble import RandomForestClassifier\n", + "\n", + "rf_baseline = RandomForestClassifier(random_state=0)\n", + "rf_baseline.fit(X_train, y_train)" ] }, { "cell_type": "markdown", + "id": "fe87cf46", "metadata": {}, "source": [ "- Evaluate your model" @@ -253,15 +1322,33 @@ }, { "cell_type": "code", - "execution_count": 1, - "metadata": {}, - "outputs": [], + "execution_count": 5, + "id": "103114a8", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-06T11:55:45.923641Z", + "iopub.status.busy": "2026-09-06T11:55:45.923531Z", + "iopub.status.idle": "2026-09-06T11:55:45.942544Z", + "shell.execute_reply": "2026-09-06T11:55:45.942098Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Baseline Random Forest (default hyperparameters) test accuracy: 0.7927\n" + ] + } + ], "source": [ - "#your code here" + "baseline_test_acc = rf_baseline.score(X_test, y_test)\n", + "print(f\"Baseline Random Forest (default hyperparameters) test accuracy: {baseline_test_acc:.4f}\")" ] }, { "cell_type": "markdown", + "id": "d9d41c56", "metadata": {}, "source": [ "**Grid/Random Search**" @@ -269,6 +1356,7 @@ }, { "cell_type": "markdown", + "id": "00ea27c8", "metadata": {}, "source": [ "For this lab we will use Grid Search." @@ -276,6 +1364,7 @@ }, { "cell_type": "markdown", + "id": "6ba1e81c", "metadata": {}, "source": [ "- Define hyperparameters to fine tune." @@ -283,15 +1372,30 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, + "execution_count": 6, + "id": "e9b227ce", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-06T11:55:45.944949Z", + "iopub.status.busy": "2026-09-06T11:55:45.944846Z", + "iopub.status.idle": "2026-09-06T11:55:45.946700Z", + "shell.execute_reply": "2026-09-06T11:55:45.946317Z" + } + }, "outputs": [], "source": [ - "#your code here" + "param_grid = {\n", + " \"n_estimators\": [100, 200, 300],\n", + " \"max_depth\": [None, 10, 20],\n", + " \"min_samples_split\": [2, 5, 10],\n", + " \"max_features\": [\"sqrt\", \"log2\"],\n", + "}\n", + "# 3 * 3 * 3 * 2 = 54 combinations" ] }, { "cell_type": "markdown", + "id": "e0c3e09b", "metadata": {}, "source": [ "- Run Grid Search" @@ -299,13 +1403,45 @@ }, { "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [] + "execution_count": 7, + "id": "0aaa82c6", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-06T11:55:45.947885Z", + "iopub.status.busy": "2026-09-06T11:55:45.947811Z", + "iopub.status.idle": "2026-09-06T11:56:28.734769Z", + "shell.execute_reply": "2026-09-06T11:56:28.733825Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Time taken: 42.8 seconds\n", + "Best hyperparameters: {'max_depth': 10, 'max_features': 'sqrt', 'min_samples_split': 5, 'n_estimators': 300}\n", + "Best CV accuracy: 0.8073\n" + ] + } + ], + "source": [ + "from sklearn.model_selection import GridSearchCV\n", + "import time\n", + "\n", + "gs = GridSearchCV(RandomForestClassifier(random_state=0), param_grid=param_grid, cv=5, n_jobs=-1)\n", + "\n", + "start_time = time.time()\n", + "gs.fit(X_train, y_train)\n", + "end_time = time.time()\n", + "\n", + "print(f\"Time taken: {end_time - start_time:.1f} seconds\")\n", + "print(f\"Best hyperparameters: {gs.best_params_}\")\n", + "print(f\"Best CV accuracy: {gs.best_score_:.4f}\")" + ] }, { "cell_type": "markdown", + "id": "b1f44351", "metadata": {}, "source": [ "- Evaluate your model" @@ -313,15 +1449,50 @@ }, { "cell_type": "code", - "execution_count": null, + "execution_count": 8, + "id": "884349e8", + "metadata": { + "execution": { + "iopub.execute_input": "2026-09-06T11:56:28.741307Z", + "iopub.status.busy": "2026-09-06T11:56:28.741142Z", + "iopub.status.idle": "2026-09-06T11:56:28.781219Z", + "shell.execute_reply": "2026-09-06T11:56:28.780837Z" + } + }, + "outputs": [ + { + "name": "stdout", + "output_type": "stream", + "text": [ + "Baseline (default hyperparameters) test accuracy: 0.7927\n", + "Tuned (Grid Search) test accuracy: 0.7882\n", + "Tuned Grid Search CV accuracy (train folds): 0.8073\n" + ] + } + ], + "source": [ + "best_rf = gs.best_estimator_\n", + "tuned_test_acc = best_rf.score(X_test, y_test)\n", + "\n", + "print(f\"Baseline (default hyperparameters) test accuracy: {baseline_test_acc:.4f}\")\n", + "print(f\"Tuned (Grid Search) test accuracy: {tuned_test_acc:.4f}\")\n", + "print(f\"Tuned Grid Search CV accuracy (train folds): {gs.best_score_:.4f}\")" + ] + }, + { + "cell_type": "markdown", + "id": "9f845bcf", "metadata": {}, - "outputs": [], - "source": [] + "source": [ + "**Comment:** Grid Search found `max_depth=10, max_features='sqrt', min_samples_split=5, n_estimators=300` as the best combination, with a CV accuracy of 80.7% — clearly higher than the baseline's training-time performance. But on the actual held-out test set, the tuned model scores **78.8%**, slightly *below* the untouched default Random Forest's **79.3%**.\n", + "\n", + "This isn't a bug, it's the honest result, and it's a useful lesson: the CV score is an average over 5 folds of the *training* data, while the test-set score is a single measurement on one specific 20% split. A gap this small (about half a point) is well within the noise you'd expect from evaluating on one fixed test set — it doesn't mean tuning failed, it means this dataset and this baseline were already close to the ceiling for a Random Forest on these features, and grid search's job (avoiding overfitting via `max_depth=10` and `min_samples_split=5` instead of growing unrestricted trees) traded a little bit of this particular test split's accuracy for a model that should generalize more consistently across different splits. Hyperparameter tuning optimizes cross-validated performance, not a guarantee of winning on any single held-out set." + ] } ], "metadata": { "kernelspec": { - "display_name": "Python 3", + "display_name": "Python 3 (ipykernel)", "language": "python", "name": "python3" }, @@ -335,9 +1506,9 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.10.9" + "version": "3.14.0" } }, "nbformat": 4, - "nbformat_minor": 2 + "nbformat_minor": 5 }