Skip to article frontmatterSkip to article content
Site not loading correctly?

This may be due to an incorrect BASE_URL configuration. See the MyST Documentation for reference.

🔑 Solutions: Machine Learning Basics

Exercise 1: Psy111 Recap

Remember the materials from last semester. Can you implement regression models using statsmodels as well as the sklearn package? Which degree of polynomial will be most suited for the synthetic data?

Please plot the result

                            OLS Regression Results                            
==============================================================================
Dep. Variable:                      y   R-squared:                       0.832
Model:                            OLS   Adj. R-squared:                  0.828
Method:                 Least Squares   F-statistic:                     240.0
Date:                Mon, 12 Oct 2026   Prob (F-statistic):           2.76e-38
Time:                        13:42:47   Log-Likelihood:                -75.074
No. Observations:                 100   AIC:                             156.1
Df Residuals:                      97   BIC:                             164.0
Df Model:                           2                                         
Covariance Type:            nonrobust                                         
==============================================================================
                 coef    std err          t      P>|t|      [0.025      0.975]
------------------------------------------------------------------------------
const          0.0738      0.078      0.945      0.347      -0.081       0.229
x1            -0.0593      0.045     -1.329      0.187      -0.148       0.029
x2             0.9357      0.043     21.868      0.000       0.851       1.021
==============================================================================
Omnibus:                        0.699   Durbin-Watson:                   2.252
Prob(Omnibus):                  0.705   Jarque-Bera (JB):                0.279
Skew:                          -0.027   Prob(JB):                        0.870
Kurtosis:                       3.253   Cond. No.                         3.25
==============================================================================

Notes:
[1] Standard Errors assume that the covariance matrix of the errors is correctly specified.

Numpy coefficients: [ 0.93568778 -0.05931311  0.07376739]
<Figure size 640x480 with 1 Axes>

Exercise 2: Bias-variance Tradeoff

To get a better understanding of the bias-variance tradeoff, we will fit polynomial regression models to synthetic data from a known function, y=sin⁡(x)+εy = \sin(x) + \varepsilon.

Please perform the following tasks:

  1. Visualize the data. Which model do you think would be optimal?

  2. Split the data into a training set (70%) and a test set (30%). Use random_state=42 so that your results are reproducible.

  3. Fit polynomial regression models for degrees 1 to 12 and calculate the training and test MSE for each of them

  4. Plot the training and test MSE against the polynomial degree

  5. Which degree has the lowest test MSE? Does it match your guess from task 1? Now repeat the split with a few different values for random_state. Does the best degree change? How much does the test MSE vary?

Bonus: Because we know the true function, we can also measure bias and variance directly, just like in the bias-variance session. Simulate many datasets with the same X but new noise, fit a model of each degree to each dataset, and calculate bias² and variance of the predictions.

Hint: You can split the data with the train_test_split() function from sklearn.model_selection.

<Figure size 640x480 with 1 Axes>
<Figure size 640x480 with 1 Axes>
random_state=0: best degree = 3, test MSE = 0.204
random_state=1: best degree = 3, test MSE = 0.260
random_state=2: best degree = 3, test MSE = 0.398
random_state=3: best degree = 5, test MSE = 0.307
random_state=4: best degree = 4, test MSE = 0.373
<Figure size 640x480 with 1 Axes>

With random_state=42, degrees 3 and 4 have the lowest test MSE (both 0.272). This matches what we would expect from the scatterplot: between 0 and 2π2\pi, the sine curve has two bends, which a cubic polynomial can capture. The training MSE, in contrast, keeps decreasing with every additional degree.

With other splits, the best degree varies between 3 and 5, and the lowest test MSE between about 0.20 and 0.40. A single train/test split therefore gives quite a noisy estimate of the test error, which is why we will use resampling methods such as cross-validation in the next session.

<Figure size 640x480 with 1 Axes>

Bias² drops sharply at degree 3, as soon as the model can follow both bends of the sine curve. It drops a second time at degree 5, because a sine is not exactly a cubic, so degrees 3 and 4 still have a small bias. The variance, in contrast, grows steadily with every additional degree. Their sum, the expected test MSE, is lowest at degree 3 (0.265), with degree 5 almost as good (0.265), and it never drops below the irreducible error σ2=0.25\sigma^2 = 0.25.

Exercise 3: Resampling Methods

The dataset we are using for the exercise is the California Housing Dataset. It contains 20640 samples and 8 features. In this dataset, we have information regarding the demography (income, population, house occupancy) in the districts, the location of the districts (latitude, longitude), and general information regarding the house in the districts (number of rooms, number of bedrooms, age of the house). Since these statistics are at the granularity of the district, they correspond to averages or medians.

Familiarize yourself with the dataset by exploring the documentation and looking at the data. What are the features and target?

Hints:

  • print(data.DESCR) prints the documentation of the dataset

  • df.head() shows the first rows of the DataFrame, and df.describe() gives summary statistics for every column

  • The names of the features and the target are stored in data.feature_names and data.target_names

Loading...

The dataset has 8 features (MedInc, HouseAge, AveRooms, AveBedrms, Population, AveOccup, Latitude, Longitude). The target is MedHouseVal, the median house value of each district in units of $100,000.

Let’s have a quick look at the distribution of these features by plotting their histograms.

Hint: DataFrames have a .hist() method that plots a histogram for every column. Use its figsize and bins arguments to make the plot readable.

<Figure size 1200x1000 with 9 Axes>
  1. Prepare the data for cross validation. This will require you to have a variable (e.g. X) for the features and a variable for the target (e.g. y).

  2. Set up a k-fold cross validation for a linear regression

    • Choose an appropriate k

    • Define the model

    • Perform cross validation

    • Use the mean squared error (MSE) to assess model performance

  3. Visualize which rows end up in the validation fold in each of the 5 rounds, once with KFold(n_splits=5) and once with KFold(n_splits=5, shuffle=True). Then run the cross-validation with both splitters and compare the MSE of the individual folds. What do you notice?

Hints:

  • For 1: You can achieve this by e.g. creating them from the DataFrame, or by using the return_X_y parameter on the fetch_california_housing() function

  • For 2: You can use the LinearRegression() model from sklearn. You can further evaluate the model and specify the (negative) MSE as a performance measure in cross_val_score()

  • For 3: Have a look at the plot_cv_indices() function in the scikit-learn example on visualizing cross-validation behavior as an example. A simpler version: create an array of zeros with one row per fold and one column per observation, set the validation rows of each fold to 1, and plot it with plt.imshow(..., aspect='auto').

  • The scikit-learn user guide on cross-validation explains all splitters and functions used in this exercise, with examples.

Average MSE:       0.5305724639238875
MSE for each fold: [0.5558916  0.52765625 0.50928321 0.50485078 0.55518048]
Without shuffling: [0.485 0.622 0.646 0.543 0.495]   mean: 0.558
With shuffling:    [0.556 0.528 0.509 0.505 0.555]   mean: 0.531
<Figure size 1000x400 with 2 Axes>

Without shuffling, each validation fold is a block of about 4100 consecutive rows. The rows of the dataset are not in random order: neighbouring rows tend to be districts from the same region. The folds therefore cover different parts of California, with average house values between 1.68 and 2.36 (in units of $100,000). Each model is validated on a region that is underrepresented in its training data, and the fold MSEs vary between 0.49 and 0.65.

With shuffling, every fold is a random mix of all districts. The fold MSEs are much more similar (0.51 to 0.56), and the mean MSE is slightly lower. In this dataset the difference is moderate, but as you saw with the iris data in the session, it can also be dramatic.

Exercise 4: LOOCV

  1. Draw a random subset of 5000 observations (LOOCV on all 20640 observations would take a while). Run LOOCV on this subset and compare the average MSE with a 5-fold CV on the same subset.

  2. Get the minimum and maximum MSE value. Discuss the range!

  3. Plot the MSE values in a histogram (the x-range should be from 0 to 6)

  4. Calculate the median MSE and discuss if it might be a more appropriate measure than the mean

Hint: You can draw a random subset of row indices with np.random.default_rng(42).choice(len(y), size=5000, replace=False).

Average MSE (LOOCV):  0.568
Average MSE (5-fold): 0.581
Minimum MSE:          1.89e-08
Maximum MSE:          115.4
Median MSE:           0.166
<Figure size 640x480 with 1 Axes>
{'MedInc': np.float64(1.6), 'HouseAge': np.float64(8.0), 'AveRooms': np.float64(7.6), 'AveBedrms': np.float64(1.0), 'Population': np.float64(1275.0), 'AveOccup': np.float64(63.8), 'Latitude': np.float64(34.0), 'Longitude': np.float64(-117.3)}

1. The average LOOCV MSE (0.568) is close to the 5-fold MSE on the same subset (0.581). Both estimate the same test error, LOOCV just needs 5000 instead of 5 model fits.

2. The individual MSE values range from almost 0 to over 100. Most observations are predicted reasonably well, but a few are predicted very badly. The worst one is a district with an average occupancy of 64 people per household, while 99% of all districts have fewer than 6 (probably a dormitory or a similar institution). The linear model has to extrapolate far beyond the typical data for this district.

3. The histogram shows a strongly right-skewed distribution: most squared errors are close to 0, with a long tail to the right.

4. The median (0.166) is much lower than the mean (0.568). It is robust to the few extreme errors, but it is not a good replacement for the MSE: squared errors are always right-skewed, so their median is systematically smaller than their mean, even without any outliers. It is better to report the mean, and to look at the observations with the largest errors (as we did above) to understand where the model fails.