跳转至

线性回归(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

如何解决?


线性回归就是用以下方程拟合:

\[ f_{\mathbf{w},b}(\mathbf{x}) = w_0x_0 + w_1x_1 +... + w_{n-1}x_{n-1} + b \tag{1}\]

用向量形式表示:

\[ f_{\mathbf{w},b}(\mathbf{x}) = \mathbf{w} \cdot \mathbf{x} + b \tag{2} \]

使用代价函数(cost function)来评估拟合程度。

\[J(\mathbf{w},b) = \frac{1}{2m} \sum\limits_{i = 0}^{m-1} (f_{\mathbf{w},b}(\mathbf{x}^{(i)}) - y^{(i)})^2 \tag{3}\]

where:

\[ f_{\mathbf{w},b}(\mathbf{x}^{(i)}) = \mathbf{w} \cdot \mathbf{x}^{(i)} + b \tag{4} \]

通过梯度下降来找到最小的cost fuction,更新各个参数

\[ w_j = w_j - \alpha \frac{\partial J(\mathbf{w},b)}{\partial w_j} \tag{5} \]
\[ \begin{align} \frac{\partial J(\mathbf{w},b)}{\partial w_j} &= \frac{1}{m} \sum\limits_{i = 0}^{m-1} (f_{\mathbf{w},b}(\mathbf{x}^{(i)}) - y^{(i)})x_{j}^{(i)} \tag{6} \\ \frac{\partial J(\mathbf{w},b)}{\partial b} &= \frac{1}{m} \sum\limits_{i = 0}^{m-1} (f_{\mathbf{w},b}(\mathbf{x}^{(i)}) - y^{(i)}) \tag{7} \end{align} \]

  • 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\) 来简化计算,使所有线性组合都可以通过矩阵来表示。

于是,之前的线性回归式子可以被改写为:

\[ y=\mathbf{X} \mathbf{\Theta} \tag{8} \]

其中,\(\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' \frac{x - \mu}{s} \]

这可以被重写为:

\[ h_{\theta'}(x') = \theta_0' - \theta_1' \frac{\mu}{s} + \theta_1' \frac{x}{s} \]

这与原始模型 \(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()  # 显示图像