线性回归(linear regression)¶
约 1197 个字 110 行代码 预计阅读时间 5 分钟
解决什么样的问题?¶
例如:使用给定的房屋价格数据集,构建一个线性回归模型来预测房价。
以下是一个实例数据集:
| Size (sqft) | Number of Bedrooms | Number of floors | Age of Home | Price (1000s dollars) |
|---|---|---|---|---|
| 2104 | 5 | 1 | 45 | 460 |
| 1416 | 3 | 2 | 40 | 232 |
| 852 | 2 | 1 | 35 | 178 |
如何解决?¶
线性回归就是用以下方程拟合:
用向量形式表示:
使用代价函数(cost function)来评估拟合程度。
where:
通过梯度下降来找到最小的cost fuction,更新各个参数
The model function for linear regression, which is a function that maps from \(x\) (city population) to \(y\) (your restaurant's monthly profit for that city) is represented as \(f_{w,b}(x) = wx + b\)
To train a linear regression model, you want to find the best \((w,b)\) parameters that fit your dataset.
To compare how one choice of \((w,b)\) is better or worse than another choice, you can evaluate it with a cost function \(J(w,b)\)
- \(J\) is a function of \((w,b)\). That is, the value of the cost \(J(w,b)\) depends on the value of \((w,b)\).
The choice of \((w,b)\) that fits your data the best is the one that has the smallest cost \(J(w,b)\).
To find the values \((w,b)\) that gets the smallest possible cost \(J(w,b)\), you can use a method called gradient descent.
With each step of gradient descent, your parameters \((w,b)\) come closer to the optimal values that will achieve the lowest cost \(J(w,b)\).
The trained linear regression model can then take the input feature \(x\) (city population) and output a prediction \(f_{w,b}(x)\) (predicted monthly profit for a restaurant in that city).
简化代码写法¶
为了解决 \((2)\)式 中的 \(b\) (bias) 的问题,我们可以通过在 \(X_{train}\)中添加一列 \(1\) 来简化计算,使所有线性组合都可以通过矩阵来表示。
于是,之前的线性回归式子可以被改写为:
其中,\(\mathbf{\Theta}\)表示所有参数 \(w_0,w_1,\cdots,w_n,b\)
特征缩放(feature scaling)¶
为了加快梯度下降的速度,我们采取特征缩放的办法。
特征缩放是机器学习中一个常用的预处理步骤,其目的是为了使各个特征的尺度相近。在使用梯度下降法进行优化时,特征缩放能够帮助算法更快地收敛。
假设我们有两个特征 \(x_1\) 和 \(x_2\),并且 \(x_1\) 的范围是 \([1, 1000]\),\(x_2\) 的范围是 \([0, 1]\)。代价函数 \(J(\Theta)\) 在高维空间中是一个椭球形状。
没有特征缩放的情况下,椭球的一个轴会远远大于另一个,这导致梯度下降法需要多次迭代才能找到最优解。特征缩放后,椭球更接近于一个圆(在二维情况下),梯度下降能更直接地朝着最低点前进。
特征缩放(通常使用标准化或归一化)的目的是改变特征的尺度,而不是改变数据集中信息的含义。换句话说,我们只是在数学上进行了变换,而这些变换并不影响问题的本质。当我们进行特征缩放时,实际上是在应用一个可逆的线性变换到输入特征上。虽然这确实会改变每个特征(以及对应的权重)的数值表现形式,但问题的优化结构(例如,损失函数的最小值)本质上并没有改变。
假设我们有一个未缩放的特征 \(x\) 和一个缩放后的特征 \(x' = \frac{x - \mu}{s}\),其中 \(\mu\) 是均值,\(s\) 是标准差。
对于线性回归模型 \(h_\theta(x) = \theta_0 + \theta_1 x\),我们进行特征缩放后得到新模型 \(h_{\theta'}(x') = \theta_0' + \theta_1' x'\)。
我们可以发现,这两个模型可以相互转换:
这可以被重写为:
这与原始模型 \(h_\theta(x) = \theta_0 + \theta_1 x\) 是等价的,只要我们取 \(\theta_0' - \theta_1' \frac{\mu}{s} = \theta_0\) 和 \(\theta_1' \frac{1}{s} = \theta_1\)。
特征缩放之后,模型的参数范围和特征范围都发生了改变。因此,可能需要重新调整学习率和迭代次数。
最终代码¶
import numpy as np
# 1. 数据准备
# 定义训练数据
X_train = np.array([
[2104, 5, 1, 45],
[1416, 3, 2, 40],
[852, 2, 1, 35]
])
y_train = np.array([460, 232, 178])
# 特征缩放
mean = np.mean(X_train, axis=0)
std = np.std(X_train, axis=0)
X_train_scaled = (X_train - mean) / std
# 添加偏置项
X_train_bias = np.c_[np.ones(X_train_scaled.shape[0]), X_train_scaled]
# 2. 梯度下降法
def cost_function(X, y, theta):
m = len(y)
predictions = X.dot(theta)
cost = (1 / (2 * m)) * np.sum(np.square(predictions - y))
return cost
def gradient_descent(X, y, theta, learning_rate, num_iterations):
m = len(y)
J_history = []
for _ in range(num_iterations):
gradient = (1 / m) * X.T.dot(X.dot(theta) - y)
theta -= learning_rate * gradient
J_history.append(cost_function(X, y, theta))
return theta, J_history
# 初始化参数、学习率和迭代次数
theta = np.zeros(X_train_bias.shape[1])
learning_rate = 0.01
num_iterations = 1000
# 进行梯度下降
theta_optimal, _ = gradient_descent(X_train_bias, y_train, theta, learning_rate, num_iterations)
# 3. 预测
# 定义预测函数
def predict(X, theta, mean, std):
X_scaled = (X - mean) / std
X_bias = np.append(1, X_scaled)
return X_bias.dot(theta)
# 预测1200 sqft, 3 bedrooms, 1 floor, 40 years old的房价
X_test = np.array([1200, 3, 1, 40])
predicted_price = predict(X_test, theta_optimal, mean, std)
print(f"Predicted Price for 1200 sqft, 3 bedrooms, 1 floor, 40 years old house: {predicted_price:.2f} (in 1000s dollars)")
使用Scikit-Learn实现同样效果¶
import numpy as np
from sklearn.linear_model import SGDRegressor # SGDRegressor用于实现梯度下降的线性回归
from sklearn.preprocessing import StandardScaler # StandardScaler用于特征缩放
import matplotlib.pyplot as plt
# 加载数据集
X_train = np.array([
[2104, 5, 1, 45],
[1416, 3, 2, 40],
[852, 2, 1, 35]
])
y_train = np.array([460, 232, 178]) # 存储每个样本对应的目标值(房价)
X_features = ['size(sqft)', 'bedrooms', 'floors', 'age'] # 特征名称列表
# 2. 缩放/规范化训练数据
# 创建StandardScaler对象,用于后续的特征缩放
scaler = StandardScaler()
# 使用fit_transform方法进行特征缩放,并保存缩放后的特征矩阵
X_norm = scaler.fit_transform(X_train)
# 3. 创建并拟合回归模型
# 创建SGDRegressor对象,设置最大迭代次数10000
sgdr = SGDRegressor(max_iter=10000)
# 使用fit方法拟合模型,输入是缩放后的特征矩阵和目标值
sgdr.fit(X_norm, y_train)
# 4. 查看模型参数
# intercept_ 属性存储截距项(偏置项),coef_ 属性存储权重(特征系数)
b_norm = sgdr.intercept_
w_norm = sgdr.coef_
print(f"Model parameters: w: {w_norm}, b: {b_norm}")
# 5. 进行预测
# 使用predict方法进行预测,输入是缩放后的特征矩阵
y_pred_sgd = sgdr.predict(X_norm)
# 使用权重和截距手动计算预测值,以验证两种方法是否一致
y_pred = np.dot(X_norm, w_norm) + b_norm
print(f"Predictions using np.dot() and sgdr.predict() match: {(y_pred == y_pred_sgd).all()}")
# 6. 绘制结果
# 创建一个1行4列的子图,每个子图对应一个特征
fig, ax = plt.subplots(1, 4, figsize=(12, 3), sharey=True)
for i in range(len(ax)):
# 绘制目标值
ax[i].scatter(X_train[:, i], y_train, label='target')
ax[i].set_xlabel(X_features[i])
# 绘制预测值
ax[i].scatter(X_train[:, i], y_pred, color='orange', label='predict')
ax[0].set_ylabel("Price") # 设置y轴标签
ax[0].legend() # 添加图例
plt.show() # 显示图像