Predict Housing Value utilizing Linear Regression in Python

A walk-through of value computation, gradient descent, and regularization utilizing Boston Housing dataset

Linear Regression appears outdated and naive when Giant Language Fashions (LLMs) dominate individuals’s consideration via their sophistication just lately. Is there nonetheless some extent of understanding it?

My reply is “Sure”, as a result of it’s a constructing block of extra complicated fashions, together with LLMs.

Making a Linear Regression mannequin will be as simple as working 3 strains of code:

from sklearn.linear_model import LinearRegression
regressor = LinearRegression()
regressor.match(X_train, y_train)

Nonetheless, this doesn’t present us the construction of the mannequin. To supply optimum modeling outcomes, we have to perceive what goes on behind the scenes. On this article, I’ll break down the technique of implementing Linear Regression in Python utilizing a easy dataset generally known as “Boston Housing”, step-by-step.

Linear — when plotted in a 2-dimensional house, if the dots exhibiting the connection of predictor x and predicted variable y scatter alongside a straight line, then we predict this relationship will be represented by this line.

Regression — a statistical technique for estimating the connection between a number of predictors (unbiased variables) and a predicted (dependent variable).

Linear Regression describes the expected variable as a linear mixture of the predictors. The road that abstracts this relationship is known as line of finest match, see the pink straight line within the under determine for example.

A figure showing x and y values in a 2 dimensional space, with blue dots representing the data points, and a red straight line crossing the points, capturing the linear relationship of x and y values
Instance of Linear Relationship and Line of Finest Match (Picture by writer)

To maintain our aim targeted on illustrating the Linear Regression steps in Python, I picked the Boston Housing dataset, which is:

  • Small — makes debugging simple
  • Easy — so we spend much less time in understanding the info or function engineering
  • Clear — requires minimal information cleansing

The dataset was first curated in Harrison and Rubinfeld’s (1978) examine of Hedonic Housing Costs. It initially has:

  • 13 predictors — together with demographic attributes, environmental attributes, and economics attributes

– CRIM — per capita crime price by city
– ZN — proportion of residential land zoned for heaps over 25,000 sq.ft.
– INDUS — proportion of non-retail enterprise acres per city.
– CHAS — Charles River dummy variable (1 if tract bounds river; 0 in any other case)
– NOX — nitric oxides focus (components per 10 million)
– RM — common variety of rooms per dwelling
– AGE — proportion of owner-occupied items constructed previous to 1940
– DIS — weighted distances to 5 Boston employment centres
– RAD — index of accessibility to radial highways
– TAX — full-value property-tax price per $10,000
– PTRATIO — pupil-teacher ratio by city
– LSTAT — % decrease standing of the inhabitants

  • 1 goal (with variable identify “MEDV”) — median worth of owner-occupied properties in $1000’s, at a particular location

You possibly can obtain the uncooked information right here.

Load information into Python utilizing pandas:

import pandas as pd

# Load information
information = pd.read_excel("Boston_Housing.xlsx")

See the dataset’s variety of rows (observations) and columns (variables):

information.form
# (506, 14)

The modeling downside of our train is: given the attributes of a location, attempt to predict the median housing value of this location.

We retailer the goal variable and predictors utilizing 2 separate objects, x and y, following math and ML notations.

# Break up up predictors and goal
y = information['MEDV']
X = information.drop(columns=['MEDV'])

Visualize the dataset by histogram and scatter plot:

import numpy as np
import matplotlib.pyplot as plt

# Distribution of predictors and relationship with goal
for col in X.columns:
fig, ax = plt.subplots(1, 2, figsize=(6,2))
ax[0].hist(X[col])
ax[1].scatter(X[col], y)
fig.suptitle(col)
plt.present()

Two figures shown side by side. On the left, a histogram showing the distribution of CRIM variable; on the right, a scatter plot showing CRIM on x axis and MEDV on y axis.
Instance output of histogram and scatter plot (Picture by writer)

The purpose of visualizing the variables is to see if any transformation is required for the variables, and determine the sort of relationship between particular person variables and goal. For instance, the goal might have a linear relationship with some predictors, however polynomial relationship with others. This additional infers which fashions to make use of for fixing the issue.

How nicely the mannequin captures the connection between the predictors and the goal will be measured by how a lot the expected outcomes deviate from the bottom reality. The operate that quantifies this deviation is known as Value Operate.

The smaller the value is, the higher the mannequin captures the connection the predictors and the goal. This implies, mathematically, the mannequin coaching course of goals to reduce the results of value operate.

There are totally different value features that can be utilized for regression issues: Sum of Squared Errors (SSE), Imply Squared Error (MSE), Imply Absolute Error (MAE)…

MSE is the preferred value operate used for Linear Regression, and is the default value operate in lots of statistical packages in R and Python. Right here’s its math expression:

Be aware: The two within the denominator is there to make calculation neater.

To make use of MSE as our value operate, we are able to create the next operate in Python:

def compute_cost(X, y, w, b): 
m = X.form[0]

f_wb = np.dot(X, w) + b
value = np.sum(np.energy(f_wb - y, 2))

total_cost = 1 / (2 * m) * value

return total_cost

Gradient — the slope of the tangent line at a sure level of the operate. In multivariable calculus, gradient is a vector that factors within the path of the steepest ascent at a sure level.

Descent — shifting in the direction of the minimal of the price operate.

Gradient Descent — a technique that iteratively adjusts the parameters in small steps, guided by the gradient, to succeed in the bottom level of a operate. It’s a method to numerically attain the specified parameters for Linear Regression.

In distinction, there’s a method to analytically remedy for the optimum parameters — Abnormal Least Squares (OLS). See this GeekforGeeks article for particulars of how you can implement it in Python. In follow, it doesn’t scale in addition to the Gradient Descent strategy due to increased computational complexity. Subsequently, we use Gradient Descent in our case.

In every iteration of the Gradient Descent course of:

  • The gradients decide the path of the descent
  • The studying price determines the scale of the descent

To calculate the gradients, we have to perceive that there are 2 parameters that alter the worth of the price operate:

  • w — the vector of every predictor’s weight
  • b — the bias time period

Be aware: as a result of the values of all of the observations (xⁱ) don’t change over the coaching course of, they contribute to the computation end result, however are constants, not variables.

Mathematically, the gradients are:

Correspondingly, we create the next operate in Python:

def compute_gradient(X, y, w, b):
m, n = X.form
dj_dw = np.zeros((n,))
dj_db = 0.

err = (np.dot(X, w) + b) - y
dj_dw = np.dot(X.T, err) # dimension: (n,m)*(m,1)=(n,1)
dj_db = np.sum(err)

dj_dw = dj_dw / m
dj_db = dj_db / m

return dj_db, dj_dw

Utilizing this operate, we get the gradients of the price operate, and with a set studying price, replace the parameters iteratively.

Because it’s a loop logically, we have to outline the stopping situation, which could possibly be any of:

  • We attain the set variety of iterations
  • The value will get to under a sure threshold
  • The enchancment drops under a sure threshold

If we select the variety of iterations because the stopping situation, we are able to write the Gradient Descent course of to be:

def gradient_descent(X, y, w_in, b_in, cost_function, gradient_function, alpha, num_iters):
J_history = []
w = copy.deepcopy(w_in)
b = b_in

for i in vary(num_iters):
dj_db, dj_dw = gradient_function(X, y, w, b)

w = w - alpha * dj_dw
b = b - alpha * dj_db

value = cost_function(X, y, w, b)
J_history.append(value)

if i % math.ceil(num_iters/10) == 0:
print(f"Iteration {i:4d}: Value {J_history[-1]:8.2f}")

return w, b, J_history

Apply it to our dataset:

iterations = 1000
alpha = 1.0e-6

w_out, b_out, J_hist = gradient_descent(X_train, y_train, w_init, b_init, compute_cost, compute_gradient, alpha, iterations)

Iteration    0: Value   169.76
Iteration 100: Value 106.96
Iteration 200: Value 101.11
Iteration 300: Value 95.90
Iteration 400: Value 91.26
Iteration 500: Value 87.12
Iteration 600: Value 83.44
Iteration 700: Value 80.15
Iteration 800: Value 77.21
Iteration 900: Value 74.58

We will visualize the method of value decreases because the quantity iteration will increase utilizing the under operate:

def plot_cost(information, cost_type):
plt.determine(figsize=(4,2))
plt.plot(information)
plt.xlabel("Iteration Step")
plt.ylabel(cost_type)
plt.title("Value vs. Iteration")
plt.present()

Right here’s the the plot for our coaching course of:

Cost vs Iteration chart with MSE as the y axis and Iteration Step on the x axis, the line looks like an elbow shape with a sharp decline from 0 to 10, and a flatter slope from 10 to 1000.
How the worth of value operate adjustments over the iterations (Picture by writer)

Making predictions is basically making use of the mannequin to our dataset of curiosity to get the output values. These values are what the mannequin “thinks” the goal worth must be, given a set of predictor values.

In our case, we apply the linear operate:

def predict(X, w, b):
p = np.dot(X, w) + b
return p

Get the prediction outcomes utilizing:

y_pred = predict(X_test, w_out, b_out)

How will we get an concept of the mannequin efficiency?

A technique is thru the price operate, as acknowledged earlier:

def compute_mse(y1, y2):
return np.imply(np.energy((y1 - y2),2))
mse = compute_mse(y_test, y_pred)
print(mse)

Right here’s the MSE on our take a look at dataset:

132.83636802687786

One other manner is extra intuitive — visualizing the expected values in opposition to the precise values. If the mannequin makes excellent predictions, then every ingredient of y_test ought to all the time equal to the corresponding ingredient of y_pred. If we plot y_test on x axis, y_pred on y axis, the dots will type a diagonal straight line.

Right here’s our customized plotting operate for the comparability:

def plot_pred_actual(y_actual, y_pred):
x_ul = int(math.ceil(max(y_actual.max(), y_pred.max()) / 10.0)) * 10
y_ul = x_ul

plt.determine(figsize=(4,4))
plt.scatter(y_actual, y_pred)
plt.xlim(0, x_ul)
plt.ylim(0, y_ul)
plt.xlabel("Precise values")
plt.ylabel("Predicted values")
plt.title("Predicted vs Precise values")
plt.present()

After making use of to our coaching end result, we discover that the dots look nothing like a straight line:

A scatter plot showing Predicted Values on the y axis, and Actual Values on the x axis.
Scatter plot of predicted values in opposition to precise values (Picture by writer)

This could get us considering: how can we enhance the mannequin’s efficiency?

The Gradient Descent course of is delicate to the size of options. As proven within the contour plot on the left, when the educational price of various options are saved the identical, then if the options are in several scales, the trail of reaching world minimal might bounce forwards and backwards alongside the price operate.

The trail in the direction of world minimal of the price operate when options are not-scaled vs scaled (Supply: DataMListic)

After scaling all of the options to the identical ranges, we are able to observe a smoother and extra straight-forward path to world minimal of the price operate.

There are a number of methods to conduct function scaling, and right here we select Standardization to show all of the options to have imply of 0 and normal deviation of 1.

Right here’s how you can standardize options in Python:

from sklearn.preprocessing import StandardScaler

standard_scaler = StandardScaler()
X_train_norm = standard_scaler.fit_transform(X_train)
X_test_norm = standard_scaler.rework(X_test)

Now we conduct Gradient Descent on the standardized dataset:

iterations = 1000
alpha = 1.0e-2

w_out, b_out, J_hist = gradient_descent(X_train_norm, y_train, w_init, b_init, compute_cost, compute_gradient, alpha, iterations)

print(f"Coaching end result: w = {w_out}, b = {b_out}")
print(f"Coaching MSE = {J_hist[-1]}")

Coaching end result: w = [-0.87200786  0.83235112 -0.35656148  0.70462672 -1.44874782  2.69272839
-0.12111304 -2.55104665 0.89855827 -0.93374049 -2.151963 -3.7142413 ], b = 22.61090500500162
Coaching MSE = 9.95513733581214

We get a steeper and smoother decline of value earlier than 200 iterations, in comparison with the earlier spherical of coaching:

Value by every iteration on the standardized dataset (Picture by writer)

If we plot the expected versus precise values once more, we see the dots look a lot nearer to a straight line:

A scatter plot showing Predicted Values on the y axis, and Actual Values on the x axis. The dots are roughly aligned in a straight line.
Scatter plot of predicted values in opposition to precise values on standardized dataset (Picture by writer)

To quantify the mannequin efficiency on the take a look at set:

mse = compute_mse(y_test, y_pred)
print(f"Check MSE = {mse}")
Check MSE = 35.66317674147827

We see an enchancment from MSE of 132.84 to 35.66! Can we do extra to enhance the mannequin?

We discover that within the final spherical of coaching, the coaching MSE is 9.96, and the testing MSE is 35.66. Can we push the take a look at set efficiency to be nearer to coaching set?

Right here comes Regularization. It penalizes massive parameters to forestall the mannequin from being too particular to the coaching set.

There are primarily 2 standard methods of regularization:

  • L1 Regularization — makes use of the L1 norm (absolute values, a.ok.a. “Manhattan norm”) of the weights because the penalty time period.
  • L2 Regularization — makes use of the L2 norm (squared values, a.ok.a. “Euclidean norm”) of the weights because the penalty time period.

Let’s first strive Ridge Regression which makes use of L2 regularization as our new model of mannequin. Its Gradient Descent course of is less complicated to know than LASSO Regression, which makes use of L1 regularization.

The associated fee operate with L1 regularization appears like this:

Lambda controls the diploma of penalty. When lambda is excessive, the extent of penalty is excessive, then the mannequin leans to underfitting.

We will flip the calculation into the next operate:

def compute_cost_ridge(X, y, w, b, lambda_ = 1): 
m = X.form[0]

f_wb = np.dot(X, w) + b
value = np.sum(np.energy(f_wb - y, 2))

reg_cost = np.sum(np.energy(w, 2))

total_cost = 1 / (2 * m) * value + (lambda_ / (2 * m)) * reg_cost

return total_cost

For the Gradient Descent course of, we use the next operate to compute the gradients with regularization:

def compute_gradient_ridge(X, y, w, b, lambda_):
m = X.form[0]

err = np.dot(X, w) + b - y
dj_dw = np.dot(X.T, err) / m + (lambda_ / m) * w
dj_db = np.sum(err) / m

return dj_db, dj_dw

Mix the 2 steps collectively, we get the next Gradient Descent operate for Ridge Regression:

def gradient_descent(X, y, w_in, b_in, cost_function, gradient_function, alpha, lambda_=0.7, num_iters=1000):
J_history = []
w = copy.deepcopy(w_in)
b = b_in

for i in vary(num_iters):
dj_db, dj_dw = gradient_function(X, y, w, b, lambda_)

w = w - alpha * dj_dw
b = b - alpha * dj_db

value = cost_function(X, y, w, b, lambda_)
J_history.append(value)

if i % math.ceil(num_iters/10) == 0:
print(f"Iteration {i:4d}: Value {J_history[-1]:8.2f}")

return w, b, J_history

Prepare the mannequin on our standardized dataset:

iterations = 1000
alpha = 1.0e-2
lambda_ = 1

w_out, b_out, J_hist = gradient_descent(X_train_norm, y_train, w_init, b_init, compute_cost_ridge, compute_gradient_ridge, alpha, lambda_, iterations)

print(f"Coaching end result: w = {w_out}, b = {b_out}")
print(f"Coaching MSE = {J_hist[-1]}")
Coaching end result: w = [-0.86996629  0.82769399 -0.35944104  0.7051097  -1.43568137  2.69434668
-0.12306667 -2.53197524 0.88587909 -0.92817437 -2.14746836 -3.70146378], b = 22.61090500500162
Coaching MSE = 10.005991756561285

The coaching value is barely increased than our earlier model of mannequin.

The educational curve appears similar to the one from the earlier spherical:

Value by every iteration for Ridge Regression (Picture by writer)

The expected vs precise values plot appears virtually equivalent to what we obtained from the earlier spherical:

Scatter plot of predicted values in opposition to precise values for Ridge Regression (Picture by writer)

We obtained take a look at set MSE of 35.69, which is barely increased than the one with out regularization.

Lastly, let’s check out LASSO Regression! LASSO stands for Least Absolute Shrinkage and Choice Operator.

That is the price operate with L2 regularization:

What’s tough concerning the coaching technique of LASSO Regression, is that the spinoff of absolutely the operate is undefined at w=0. Subsequently, Coordinate Descent is utilized in follow for LASSO Regression. It focuses on one coordinate at a time to seek out the minimal, after which swap to the following coordinate.

Right here’s how we implement it in Python, impressed by Sicotte (2018) and D@Kg’s pocket book (2022).

First, we outline the delicate threshold operate, which is the answer to the one variable optimization downside:

def soft_threshold(rho, lamda_):
if rho < - lamda_:
return (rho + lamda_)
elif rho > lamda_:
return (rho - lamda_)
else:
return 0

Second, calculate the residuals of the prediction:

def compute_residuals(X, y, w, b):
return y - (np.dot(X, w) + b)

Use the residual to calculate rho, which is the subderivative:

def compute_rho_j(X, y, w, b, j):
X_k = np.delete(X, j, axis=1) # take away the jth ingredient
w_k = np.delete(w, j) # take away the jth ingredient

err = compute_residuals(X_k, y, w_k, b)

X_j = X[:,j]
rho_j = np.dot(X_j, err)

return rho_j

Put every part collectively:

def coordinate_descent_lasso(X, y, w_in, b_in, cost_function, lambda_, num_iters=1000, tolerance=1e-4):
J_history = []
w = copy.deepcopy(w_in)
b = b_in
n = X.form[1]

for i in vary(num_iters):
# Replace weights
for j in vary(n):
X_j = X[:,j]
rho_j = compute_rho_j(X, y, w, b, j)
w[j] = soft_threshold(rho_j, lambda_) / np.sum(X_j ** 2)

# Replace bias
b = np.imply(y - np.dot(X, w))
err = compute_residuals(X, y, w, b)

# Calculate complete value
value = cost_function(X, y, w, b, lambda_)
J_history.append(value)

if i % math.ceil(num_iters/10) == 0:
print(f"Iteration {i:4d}: Value {J_history[-1]:8.2f}")

# Test convergence
if np.max(np.abs(err)) < tolerance:
break

return w, b, J_history

Apply it to our coaching set:

iterations = 1000
lambda_ = 1e-4
tolerance = 1e-4

w_out, b_out, J_hist = coordinate_descent_lasso(X_train_norm, y_train, w_init, b_init, compute_cost_lasso, lambda_, iterations, tolerance)

The coaching course of converged drastically, in comparison with Gradient Descent on Ridge Regression:

Value by every iteration for LASSO Regression (Picture by writer)

Nonetheless, the coaching end result isn’t considerably improved:

Scatter plot of predicted values in opposition to precise values for LASSO Regression (Picture by writer)

Finally, we achieved MSE of 34.40, which is the bottom among the many strategies we tried.

How will we interpret the mannequin coaching outcomes utilizing human language? Let’s use the results of LASSO Regression for example, because it has one of the best efficiency among the many mannequin variations we tried out.

We will get the weights and the bias by printing the w_out and b_out we obtained within the earlier part:

print(f"Coaching end result: w = {w_out}, b = {b_out}")
Coaching end result: w = [-0.86643384  0.82700157 -0.35437324  0.70320366 -1.44112303  2.69451013
-0.11649385 -2.53543865 0.88170899 -0.92308699 -2.15014264 -3.71479811], b = 22.61090500500162

In our case, there are 13 predictors, so this dataset has 13 dimensions. In every dimension, we are able to plot the predictor x_i in opposition to the goal y as a scatterplot. The regression line’s slope is the load w_i.

In particulars, the primary dimension is “CRIM — per capita crime price by city”, and our w_1 is -0.8664. This implies, every unit of improve in x_i, y is anticipated to lower by -0.8664 unit.

Be aware that we’ve scaled our dataset earlier than we run the coaching course of, so now we have to reverse that course of to get the intuitive relationship between the predictor “per capita crime price by city” and our goal variable “median worth of owner-occupied properties in $1000’s, at a particular location”.

To reverse the scaling, we have to get the vector of scales:

print(standard_scaler.scale_)
[8.12786482e+00 2.36076347e+01 6.98435113e+00 2.53975353e-01
1.15057872e-01 6.93831576e-01 2.80721481e+01 2.07800639e+00
8.65042138e+00 1.70645434e+02 2.19210336e+00 7.28999160e+00]

Right here we discover the size we used for our first predictor: 8.1278. We divide the load of -0.8664 by scale or 8.1278 to get -0.1066.

This implies: when all different components stays the identical, if the per capita crime price will increase by 1 share level, the medium housing value of that location drops by $1000 * (-0.1066) = $106.6 in worth.

This text unveiled the main points of implementing Linear Regression in Python, going past simply calling excessive degree scikit-learn features.

  • We appeared into the goal of regression — minimizing the price operate, and wrote the price operate in Python.
  • We broke down Gradient Descent course of step-by-step.
  • We created plotting features to visualise the coaching course of and assessing the outcomes.
  • We mentioned methods to enhance mannequin efficiency, and discovered that LASSO Regression achieved the bottom take a look at MSE for our downside.
  • Lastly, we used one predictor for example for example how the coaching end result must be interpreted.

[1] A. Ng, Supervised Machine Studying: Regression and Classification (2022), https://www.coursera.org/be taught/machine-learning

[2] D. Harrison and D. L. Rubinfeld, Hedonic Housing Costs and the Demand for Clear Air (1978), https://www.legislation.berkeley.edu/recordsdata/Hedonic.PDF

[3] Linear Regression (Python Implementation) (2024), https://www.geeksforgeeks.org/linear-regression-python-implementation/

[4] Why We Carry out Function Scaling In Machine Studying (2022), https://www.youtube.com/watch?v=CFA7OFYDBQY

[5] X. Sicotte, Lasso regression: implementation of coordinate descent (2018), https://xavierbourretsicotte.github.io/lasso_implementation.html

[6] D@Kg, Coordinate Descent for LASSO & Regular Regression (2022), https://www.kaggle.com/code/ddatad/coordinate-descent-for-lasso-normal-regression/pocket book

[7] Fairlearn, Revisiting the Boston Housing Dataset, https://fairlearn.org/essential/user_guide/datasets/boston_housing_data.html#revisiting-the-boston-housing-dataset

[8] V. Rathod, All about Boston Housing (2020), https://rpubs.com/vidhividhi/LRversusDT

[9] A. Gupta, Regularization in Machine Studying (2023), https://www.geeksforgeeks.org/gradient-descent-in-linear-regression/

[10] The College of Melbourne, Rescaling explanatory variables in linear regression, https://scc.ms.unimelb.edu.au/sources/reporting-statistical-inference/rescaling-explanatory-variables-in-linear-regression