Mark Pedigo 2025-03-05
线性回归是统计学和机器学习中的一项基础技术,用于建模变量之间的关系。简单来说,它使我们能够根据一个或多个影响因素来预测结果。它被广泛应用于房地产定价、销售预测、风险评估以及许多其他领域。
在本教程中,我们将探讨 scikit-learn 中的线性回归,涵盖其工作原理、为何有用,以及如何使用 scikit-learn 实现它。到最后,你将能够构建并评估一个线性回归模型,以进行数据驱动的预测。
房价与房间数量的散点图
线性回归与机器学习
除了在确定房价方面的直接用途外,线性回归在机器学习中也扮演着重要角色:
- 它是理解更高级技术(如逻辑回归、神经网络和支持向量机)的基础模型。
- 训练速度快,非常适合快速原型设计。
- 它还可作为比较的基准。如果更复杂的模型没有显著优于它,那么它们增加的复杂性可能并不合理。
- 与某些技术(如深度学习)不同,它是易于解释的。
- 它有助于特征选择,识别最有用的预测因子。
尽管简单,但由于其高效性、可解释性和多功能性,线性回归在机器学习中仍然是不可或缺的工具。
线性回归与 scikit-learn 库
scikit-learn 库使得线性回归的实现变得非常简单。该库具有诸多优势:
- 它拥有统一的接口。实现不同机器学习算法所需的代码结构相似。
- 代码简洁,复杂的数学和实现细节被封装起来。例如,只需使用
model.fit(X_train, y_train)这一行代码即可在训练数据上拟合模型。 - 它提供对模型系数的便捷访问。
- 它内置了用于评估模型性能的指标。
- 它可以轻松地将线性回归(或其他任何机器学习算法)与预处理步骤(如缩放和特征选择)通过 Pipeline 集成。
理解线性回归
正如我们所见,在简单线性回归中,数据使用一条“最佳拟合直线”进行建模。该直线的公式为:
其中, 是直线的斜率, 是截距。
“多元线性回归”将单个预测变量的情况推广到多个预测变量(如房间数量、距海洋的距离、社区的中位收入)。其公式被推广为:
其中每个 是一个自变量,对应的 是其系数。在三维空间中,这条直线被推广为一个平面。在更高维空间中,该平面变成一个“超平面”。
我们如何解释这些系数和截距?截距是在所有自变量都为 0 时 的预测值;换句话说,它是当预测变量没有任何贡献时因变量的基线值。每个系数 表示在其他所有自变量保持不变的情况下, 每变化一个单位,因变量 的变化量。
设置环境
安装 scikit-learn 非常简单。只需使用命令 pip install scikit-learn。如果你想安装特定版本,比如 1.2.2,则修改命令为:pip install scikit-learn==1.2.2。如果你使用 Anaconda,scikit-learn 应该已经预装。如果出于某种原因仍需在 Anaconda 环境中安装,请使用命令 conda install scikit-learn。
在使用 scikit-learn 时,有几个库是必需或推荐的。numpy 库用于存储特征和标签。pandas 库推荐用于加载、预处理和探索数据集。
如果你正在使用 scikit-learn,很可能已经在使用 pandas 进行数据准备。为了绘制结果,你可能会使用 matplotlib 或 seaborn,或者两者结合。这些库都可以使用类似于上面的 pip install 命令进行安装。你甚至可以使用一条命令安装多个库:
pip install scikit-learn numpy pandas matplotlib seaborn
在 sklearn 中实现线性回归
在加载数据集之前,让我们先导入常用的库。
# 导入库。
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
加载数据集
让我们使用著名的加州住房数据集。
# 读取加州住房数据集。
from sklearn.datasets import fetch_california_housing
housing = fetch_california_housing()
准备数据
让我们将数据拆分为训练集和测试集。从 sklearn.model_selection 导入 train_test_split() 方法,然后调用它,指定测试集比例和 random_state。我们还将使用简单线性回归,仅使用对应于平均房间数的特征。
# 导入 train_test_split。
from sklearn.model_selection import train_test_split
# 创建特征 X 和目标 y。
X = pd.DataFrame(housing.data, columns=housing.feature_names)
y = housing.target # 房屋价值中位数,单位为 $100,000
# 将数据集拆分为训练集(80%)和测试集(20%)。
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
现在我们已经将数据拆分为训练集和测试集,接下来对特征进行标准化。此过程确保所有变量处于同一尺度,这可以提高模型性能和数值稳定性。
# 导入 StandardScaler。
from sklearn.preprocessing import StandardScaler
# 实例化 StandardScaler。
scaler = StandardScaler()
# 拟合并转换训练数据。
X_train_scaled = scaler.fit_transform(X_train)
# 同样转换测试数据。
X_test_scaled = scaler.transform(X_test)
在此代码中,StandardScaler 是一种数据预处理工具,用于去除均值并将特征缩放到单位方差。这有助于防止某些特征因其尺度差异而在模型中占据主导地位。
使用 fit_transform() 方法在训练数据上拟合缩放器。然后使用 transform() 方法单独转换测试数据,以确保其使用与训练数据相同的缩放参数,从而防止数据泄露。
训练线性回归模型
要创建线性回归模型,从 sklearn.linear_model 导入 LinearRegression()。调用它并将其赋值给一个变量。
# 导入 LinearRegression。
from sklearn.linear_model import LinearRegression
# 实例化线性回归模型。
model = LinearRegression()
使用训练数据拟合模型非常简单。
# 将模型拟合到训练数据。
model.fit(X_train_scaled, y_train)
进行预测
现在我们已经训练好了模型,可以在测试集上进行预测。
# 对测试数据进行预测。
y_pred = model.predict(X_test_scaled)
评估模型性能
现在我们已经在测试集上做出了预测,需要了解这些预测与实际情况的匹配程度。有几种指标可用于评估回归算法的性能。其中最常见的是决定系数(R²)、均方误差(MSE)和均方根误差(RMSE)。
决定系数(记作 R²)衡量回归模型解释目标变量变异性的能力。换句话说,它量化了目标变量中的变异性有多少是由预测变量解释的,也称为拟合优度。
为了进一步理解这一点,让我们看一下其公式:
其中, 是目标变量的实际值, 是模型的预测值, 是实际值的均值。该公式帮助我们理解模型解释了多少目标变量的方差。分母表示数据中的总方差,而分子表示应用回归模型后未被解释的方差。因此,该比率给出了模型所解释的方差百分比。
我们如何解释 R²?
- R² = 1:模型完美解释了目标变量的所有方差。
- R² = 0:模型无法解释任何方差;预测效果不比直接使用均值更好。
- R² < 0:模型表现比使用均值还差,表明拟合效果很差。
一些需要注意的关键点:
- 更高的 R² 并不总是更好。高 R² 可能表示过拟合,尤其是在复杂模型中。
- 添加更多特征会人为地提高 R²,因此更高的值不一定代表更好的模型。
- 对于多元回归,应使用调整后的 R²,它考虑了预测变量的数量,避免因引入不必要的变量而导致的误导性改进。
使用 scikit-learn 评估模型性能中的决定系数非常简单。
# 导入指标。
from sklearn.metrics import mean_squared_error, r2_score
# 计算并打印 R² 分数。
r2 = r2_score(y_test, y_pred)
print(f"R-squared: {r2:.4f}")
输出:
R-squared: 0.0138
其他常用指标包括均方误差(MSE)和均方根误差(RMSE)。这些指标衡量模型预测值与实际值之间的偏离程度。
MSE 计算实际值与预测值之间平方差的平均值:
由于在求平均前对误差进行了平方,较大的误差会被更严重地惩罚,因此 MSE 对异常值敏感。较低的 MSE 表示模型拟合得更好。
为了解决这个问题,使用 RMSE,它只是 MSE 的平方根。由于 RMSE 与目标变量具有相同的单位,因此它提供了一个更直观的度量,表示预测平均偏离实际值的程度。
使用 scikit-learn 计算 MSE 和 RMSE 非常简单。
# 计算并打印 MSE。
mse = mean_squared_error(y_test, y_pred)
print(f"Mean squared error: {mse:.4f}")
# 计算并打印 RMSE。
rmse = mse ** 0.5
print(f"Root mean squared error: {rmse:.4f}")
输出:
Mean squared error: 1.2923
Root mean squared error: 1.1368
在 scikit-learn 中使用多元线性回归
让我们使用所有可用特征重新运行模型,而不仅仅是平均房间数。你预期结果会更好还是更差?
# 使用所有特征。
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.datasets import fetch_california_housing
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error, r2_score
# 加载数据集。
housing = fetch_california_housing()
# 拆分为 X, y。
X = pd.DataFrame(housing.data, columns=housing.feature_names)
y = housing.target # 房屋价值中位数,单位为 $100,000
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# 缩放数据。
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)
# 创建模型并将其拟合到训练数据。
model = LinearRegression()
model.fit(X_train_scaled, y_train)
# 进行预测。
y_pred = model.predict(X_test_scaled)
# 计算并打印误差。
r2 = r2_score(y_test, y_pred)
print(f"R-squared: {r2:.4f}")
mse = mean_squared_error(y_test, y_pred)
print(f"Mean squared error: {mse:.4f}")
rmse = mse ** 0.5
print(f"Root mean squared error: {rmse:.4f}")
输出:
R-squared: 0.5758
Mean squared error: 0.5559
Root mean squared error: 0.7456
我们可以看到,结果比仅使用一个特征时要好得多。然而,这引发了一个问题:我们是否真的需要所有特征?有些特征是否比其他特征更重要?从数据集中选择最相关的特征被称为特征选择。
特征选择之所以重要,原因如下:
- 减少过拟合:特征越少,模型越简单,从而降低过拟合风险。
- 提高准确性:移除无关或冗余特征有助于模型聚焦于有意义的模式。
- 增强可解释性:通过突出最重要的因素,使模型更易于理解。
- 加快训练速度:减少特征数量可降低计算时间和内存消耗。
当多个特征高度相关时,它们是冗余的,意味着它们本质上向模型提供了相同的信息。这种情况被称为多重共线性。虽然多重共线性并不总是影响预测模型的准确性,但它会使特征选择和解释变得复杂,尤其是在线性回归及相关模型中。
方差膨胀因子(VIF)是一种用于检测预测变量之间多重共线性的指标。对于每个预测变量,VIF 的计算公式为:
其中, 是当预测变量 对模型中所有其他预测变量进行回归时得到的 R² 值。VIF 越高,说明该预测变量与其他变量的相关性越强。
- VIF = 1:无多重共线性(理想情况)。
- VIF < 5:低至中等多重共线性(通常可接受)。
- VIF > 5:高多重共线性(考虑移除或合并相关变量)。
- VIF > 10:严重多重共线性(强烈建议存在变量冗余)。
# 导入库。
import numpy as np
import pandas as pd
import seaborn as sns
import matplotlib.pyplot as plt
from sklearn.datasets import fetch_california_housing
from statsmodels.stats.outliers_influence import variance_inflation_factor
# 加载数据集。
housing = fetch_california_housing()
X = pd.DataFrame(housing.data, columns=housing.feature_names)
# 计算相关矩阵。
corr_matrix = X.corr()
# 识别高共线性特征对(相关系数 > 0.8 或 < -0.8)。
high_corr_features = [(col1, col2, corr_matrix.loc[col1, col2])
for col1 in corr_matrix.columns
for col2 in corr_matrix.columns
if col1 != col2 and abs(corr_matrix.loc[col1, col2]) > 0.8]
# 转换为 DataFrame 以便更好地可视化。
collinearity_df = pd.DataFrame(high_corr_features, columns=["Feature 1", "Feature 2", "Correlation"])
print("\nHighly Correlated Features:\n", collinearity_df)
# 计算每个特征的方差膨胀因子(VIF)。
vif_data = pd.DataFrame()
vif_data["Feature"] = X.columns
vif_data["VIF"] = [variance_inflation_factor(X.values, i) for i in range(X.shape[1])]
# 打印 VIF 值。
print("\nVariance Inflation Factor (VIF) for each feature:\n", vif_data)
输出:
Highly Correlated Features:
Feature 1 Feature 2 Correlation
0 AveRooms AveBedrms 0.847621
1 AveBedrms AveRooms 0.847621
2 Latitude Longitude -0.924664
3 Longitude Latitude -0.924664
Variance Inflation Factor (VIF) for each feature:
Feature VIF
0 MedInc 11.511140
1 HouseAge 7.195917
2 AveRooms 45.993601
3 AveBedrms 43.590314
4 Population 2.935745
5 AveOccup 1.095243
6 Latitude 559.874071
7 Longitude 633.711654
让我们从模型中移除 AveBedrms 特征。
# 导入库。
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
from sklearn.datasets import fetch_california_housing
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn.linear_model import LinearRegression
from sklearn.metrics import mean_squared_error, r2_score
# 加载加州住房数据集。
housing = fetch_california_housing()
# 创建 DataFrame 并移除 "AveBedrms" 特征。
X = pd.DataFrame(housing.data, columns=housing.feature_names).drop(columns=["AveBedrms"])
y = housing.target # 房屋价值中位数,单位为 $100,000
# 将数据拆分为训练集和测试集。
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)
# 缩放数据(标准化)。
scaler = StandardScaler()
X_train_scaled = scaler.fit_transform(X_train)
X_test_scaled = scaler.transform(X_test)
# 创建线性回归模型并进行训练。
model = LinearRegression()
model.fit(X_train_scaled, y_train)
# 对测试集进行预测。
y_pred = model.predict(X_test_scaled)
# 计算性能指标。
r2 = r2_score(y_test, y_pred)
mse = mean_squared_error(y_test, y_pred)
rmse = np.sqrt(mse)
# 打印评估指标
print(f"R-squared: {r2:.4f}")
print(f"Mean squared error: {mse:.4f}")
print(f"Root mean squared error: {rmse:.4f}")
输出:
R-squared: 0.5823
Mean squared error: 0.5473
Root mean squared error: 0.7398
结果(略微)有所改善。
提取模型洞察
构建回归模型只是第一步;理解其输出同样重要。通过分析模型的系数,我们可以确定哪些特征对预测的影响最大。
理解回归系数
一旦线性回归模型被训练完成,可以使用 model.coef_ 访问系数。可以使用 model.intercept_ 访问截距。
print("Intercept:", model.intercept_)
coeff_df = pd.DataFrame({"Feature": X.columns, "Coefficient": model.coef_})
print("\nFeature Coefficients:\n", coeff_df)
输出:
Intercept: 2.0719469373788777
Feature Coefficients:
Feature Coefficient
0 MedInc 0.725747
1 HouseAge 0.121519
2 Latitude -0.943105
3 Longitude -0.900735
总结模型结果
由于 Scikit-Learn 不像 Statsmodels 那样提供内置的 summary() 方法,我们可以手动提取并使用回归系数可视化每个特征的重要性。绝对值较大的系数对应的特征对目标变量的影响更强。请参考以下代码。
# 按系数对 dataframe 排序。
coef_df_sorted = coef_df.sort_values(by="Coefficient", ascending=False)
# 创建图表。
plt.figure(figsize=(8,6))
plt.barh(coef_df["Feature"], coef_df_sorted["Coefficient"], color="blue")
plt.xlabel("Coefficient Value")
plt.ylabel("Feature")
plt.title("Feature Importance (Linear Regression Coefficients)")
plt.show()
基于系数值的特征重要性图
现在,让我们可视化残差和回归拟合情况。
# 计算残差。
residuals = y_test - y_pred
# 创建图表。
plt.figure(figsize=(12,5))
# 图1:残差分布。
plt.subplot(1,2,1)
sns.histplot(residuals, bins=30, kde=True, color="blue")
plt.axvline(x=0, color='red', linestyle='--')
plt.title("Residuals Distribution")
plt.xlabel("Residuals (y_actual - y_predicted)")
plt.ylabel("Frequency")
# 图2:回归拟合(实际值 vs 预测值)。
plt.subplot(1,2,2)
sns.scatterplot(x=y_test, y=y_pred, alpha=0.5)
plt.plot([min(y_test), max(y_test)], [min(y_test), max(y_test)], color='red', linestyle='--') # 完美拟合线
plt.title("Regression Fit: Actual vs Predicted")
plt.xlabel("Actual Prices (in $100,000s)")
plt.ylabel("Predicted Prices (in $100,000s)")
# 显示图表。
plt.tight_layout()
plt.show()
残差分布与回归拟合图
残差分布(左图)应围绕零值居中,表明误差是随机分布的。如果残差服从正态分布,则说明模型拟合良好;但如果存在偏斜或趋势,则可能表明存在系统性误差。回归拟合图(右图)比较了实际值与预测值,红色虚线代表完美拟合。如果点紧密围绕该线分布,则预测准确;但如果出现某种模式(例如曲线),则说明变量间的关系可能并非真正的线性。
这些可视化有助于诊断过拟合或欠拟合,揭示残差中的模式(暗示缺失关系),并清晰评估模型的有效性。
现实世界的应用
线性回归在各行各业中被广泛用于预测和决策制定。在房地产领域,它根据面积和位置等因素估算房价。
销售和市场营销部门使用它进行需求预测和预算优化,医疗保健领域则将其应用于疾病风险评估。在金融领域,它有助于股票价格预测和信用评分;在制造业中,它用于质量控制和故障预测。
何时使用线性回归
- 特征与目标变量之间存在线性关系。
- 可解释性和简洁性比复杂建模更重要。
- 数据需要最少的特征工程。
何时不使用线性回归
- 目标变量与特征之间的关系复杂且非线性。
- 数据高度相关。
- 数据包含无法移除的异常值。在这种情况下,你可能需要应用数据变换或使用策略来减轻其影响。
结论
线性回归仍然是机器学习和统计建模中最基本、应用最广泛的技术之一。尽管其简单,但它是一种强大的工具,可用于理解变量之间的关系,并在各种现实应用场景中进行预测。
以下是本教程的关键要点:
- 多样化的应用:线性回归在多个行业和问题领域提供有价值的见解。
- 可解释性强:与复杂的黑盒模型不同,线性回归提供基于系数的清晰解释,使其易于理解和说明。
- 特征选择:恰当地选择特征并处理多重共线性,可确保模型准确、稳定且可靠。