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. \n",
+ "
\n",
+ "
\n",
+ " Parameters \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " n_estimators\n",
+ " n_estimators: int, default=100 The number of trees in the forest. .. versionchanged:: 0.22 The default value of ``n_estimators`` changed from 10 to 100 in 0.22. \n",
+ " \n",
+ " \n",
+ " 100 \n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " criterion\n",
+ " criterion: {\"gini\", \"entropy\", \"log_loss\"}, default=\"gini\" The function to measure the quality of a split. Supported criteria are \"gini\" for the Gini impurity and \"log_loss\" and \"entropy\" both for the Shannon information gain, see :ref:`tree_mathematical_formulation`. Note: This parameter is tree-specific. \n",
+ " \n",
+ " \n",
+ " 'gini' \n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " max_depth\n",
+ " max_depth: int, default=None The maximum depth of the tree. If None, then nodes are expanded until all leaves are pure or until all leaves contain less than min_samples_split samples. \n",
+ " \n",
+ " \n",
+ " None \n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " min_samples_split\n",
+ " min_samples_split: int or float, default=2 The minimum number of samples required to split an internal node: - If int, then consider `min_samples_split` as the minimum number. - If float, then `min_samples_split` is a fraction and `ceil(min_samples_split * n_samples)` are the minimum number of samples for each split. .. versionchanged:: 0.18 Added float values for fractions. \n",
+ " \n",
+ " \n",
+ " 2 \n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " min_samples_leaf\n",
+ " min_samples_leaf: int or float, default=1 The minimum number of samples required to be at a leaf node. A split point at any depth will only be considered if it leaves at least ``min_samples_leaf`` training samples in each of the left and right branches. This may have the effect of smoothing the model, especially in regression. - If int, then consider `min_samples_leaf` as the minimum number. - If float, then `min_samples_leaf` is a fraction and `ceil(min_samples_leaf * n_samples)` are the minimum number of samples for each node. .. versionchanged:: 0.18 Added float values for fractions. \n",
+ " \n",
+ " \n",
+ " 1 \n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " min_weight_fraction_leaf\n",
+ " min_weight_fraction_leaf: float, default=0.0 The minimum weighted fraction of the sum total of weights (of all the input samples) required to be at a leaf node. Samples have equal weight when sample_weight is not provided. \n",
+ " \n",
+ " \n",
+ " 0.0 \n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " max_features\n",
+ " max_features: {\"sqrt\", \"log2\", None}, int or float, default=\"sqrt\" The number of features to consider when looking for the best split: - If int, then consider `max_features` features at each split. - If float, then `max_features` is a fraction and `max(1, int(max_features * n_features_in_))` features are considered at each split. - If \"sqrt\", then `max_features=sqrt(n_features)`. - If \"log2\", then `max_features=log2(n_features)`. - If None, then `max_features=n_features`. .. versionchanged:: 1.1 The default of `max_features` changed from `\"auto\"` to `\"sqrt\"`. Note: the search for a split does not stop until at least one valid partition of the node samples is found, even if it requires to effectively inspect more than ``max_features`` features. \n",
+ " \n",
+ " \n",
+ " 'sqrt' \n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " max_leaf_nodes\n",
+ " max_leaf_nodes: int, default=None Grow trees with ``max_leaf_nodes`` in best-first fashion. Best nodes are defined as relative reduction in impurity. If None then unlimited number of leaf nodes. \n",
+ " \n",
+ " \n",
+ " None \n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " min_impurity_decrease\n",
+ " min_impurity_decrease: float, default=0.0 A node will be split if this split induces a decrease of the impurity greater than or equal to this value. The weighted impurity decrease equation is the following:: N_t / N * (impurity - N_t_R / N_t * right_impurity - N_t_L / N_t * left_impurity) where ``N`` is the total number of samples, ``N_t`` is the number of samples at the current node, ``N_t_L`` is the number of samples in the left child, and ``N_t_R`` is the number of samples in the right child. ``N``, ``N_t``, ``N_t_R`` and ``N_t_L`` all refer to the weighted sum, if ``sample_weight`` is passed. .. versionadded:: 0.19 \n",
+ " \n",
+ " \n",
+ " 0.0 \n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " bootstrap\n",
+ " bootstrap: bool, default=True Whether bootstrap samples are used when building trees. If False, the whole dataset is used to build each tree. \n",
+ " \n",
+ " \n",
+ " True \n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " oob_score\n",
+ " oob_score: bool or callable, default=False Whether to use out-of-bag samples to estimate the generalization score. By default, :func:`~sklearn.metrics.accuracy_score` is used. Provide a callable with signature `metric(y_true, y_pred)` to use a custom metric. Only available if `bootstrap=True`. For an illustration of out-of-bag (OOB) error estimation, see the example :ref:`sphx_glr_auto_examples_ensemble_plot_ensemble_oob.py`. \n",
+ " \n",
+ " \n",
+ " False \n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " n_jobs\n",
+ " n_jobs: int, default=None The number of jobs to run in parallel. :meth:`fit`, :meth:`predict`, :meth:`decision_path` and :meth:`apply` are all parallelized over the trees. ``None`` means 1 unless in a :obj:`joblib.parallel_backend` context. ``-1`` means using all processors. See :term:`Glossary` for more details. \n",
+ " \n",
+ " \n",
+ " None \n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " random_state\n",
+ " random_state: int, RandomState instance or None, default=None Controls both the randomness of the bootstrapping of the samples used when building trees (if ``bootstrap=True``) and the sampling of the features to consider when looking for the best split at each node (if ``max_features < n_features``). See :term:`Glossary ` for details. \n",
+ " \n",
+ " \n",
+ " 0 \n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " verbose\n",
+ " verbose: int, default=0 Controls the verbosity when fitting and predicting. \n",
+ " \n",
+ " \n",
+ " 0 \n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " warm_start\n",
+ " warm_start: bool, default=False When set to ``True``, reuse the solution of the previous call to fit and add more estimators to the ensemble, otherwise, just fit a whole new forest. See :term:`Glossary ` and :ref:`tree_ensemble_warm_start` for details. \n",
+ " \n",
+ " \n",
+ " False \n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " class_weight\n",
+ " class_weight: {\"balanced\", \"balanced_subsample\"}, dict or list of dicts, default=None Weights associated with classes in the form ``{class_label: weight}``. If not given, all classes are supposed to have weight one. For multi-output problems, a list of dicts can be provided in the same order as the columns of y. Note that for multioutput (including multilabel) weights should be defined for each class of every column in its own dict. For example, for four-class multilabel classification weights should be [{0: 1, 1: 1}, {0: 1, 1: 5}, {0: 1, 1: 1}, {0: 1, 1: 1}] instead of [{1:1}, {2:5}, {3:1}, {4:1}]. The \"balanced\" mode uses the values of y to automatically adjust weights inversely proportional to class frequencies in the input data as ``n_samples / (n_classes * np.bincount(y))`` The \"balanced_subsample\" mode is the same as \"balanced\" except that weights are computed based on the bootstrap sample for every tree grown. For multi-output, the weights of each column of y will be multiplied. Note that these weights will be multiplied with sample_weight (passed through the fit method) if sample_weight is specified. \n",
+ " \n",
+ " \n",
+ " None \n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " ccp_alpha\n",
+ " ccp_alpha: non-negative float, default=0.0 Complexity parameter used for Minimal Cost-Complexity Pruning. The subtree with the largest cost complexity that is smaller than ``ccp_alpha`` will be chosen. By default, no pruning is performed. See :ref:`minimal_cost_complexity_pruning` for details. See :ref:`sphx_glr_auto_examples_tree_plot_cost_complexity_pruning.py` for an example of such pruning. .. versionadded:: 0.22 \n",
+ " \n",
+ " \n",
+ " 0.0 \n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " max_samples\n",
+ " max_samples: int or float, default=None If bootstrap is True, the number of samples to draw from X to train each base estimator. - If None (default), then draw `X.shape[0]` samples. - If int, then draw `max_samples` samples. - If float, then draw `max(round(n_samples * max_samples), 1)` samples. Thus, `max_samples` should be in the interval `(0.0, 1.0]`. .. versionadded:: 0.22 \n",
+ " \n",
+ " \n",
+ " None \n",
+ " \n",
+ " \n",
+ "\n",
+ " \n",
+ " \n",
+ " \n",
+ " \n",
+ " monotonic_cst\n",
+ " monotonic_cst: array-like of int of shape (n_features), default=None Indicates the monotonicity constraint to enforce on each feature. - 1: monotonic increase - 0: no constraint - -1: monotonic decrease If monotonic_cst is None, no constraints are applied. Monotonicity constraints are not supported for: - multiclass classifications (i.e. when `n_classes > 2`), - multioutput classifications (i.e. when `n_outputs_ > 1`), - classifications trained on data with missing values. The constraints hold over the probability of the positive class. Read more in the :ref:`User Guide `. .. versionadded:: 1.4 \n",
+ " \n",
+ " \n",
+ " None \n",
+ " \n",
+ " \n",
+ " \n",
+ "
\n",
+ " \n",
+ "
\n",
+ "
"
+ ],
+ "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
}