如何端到端解决预测建模机器学习问题?我们将通过一个案例研究Python中的回归预测建模问题,包括应用机器学习过程的每一步。完成这个项目后,我们要知道:
-
如何端到端解决回归预测建模问题
-
如何使用数据转换来提高模型性能
-
如何使用算法调优来提高模型性能
-
如何使用集成方法和集成方法的调优来提高模型性能
代码示例
# coding=utf-8
"""
本程序用于加载和分析波士顿房价数据集,对数据进行可视化,特征选择,数据转换,
并使用多种算法对模型进行训练和评估,旨在预测波士顿房价。
数据集中指标(属性)解释,如下:
1. CRIM:犯罪率
2. ZN:住宅用地所占比例
3. INDUS:城镇中非住宅用地所占比例
4. CHAS:是否穿过查尔斯河
5. NOX:一氧化氮氧化污染物
6. RM:每套住宅的平均房间数
7. AGE:1940年以前建成的自住单位的比例
8. DIS:距离5个波士顿的就业中心的加权距离
9. RAD:距离高速公路的便利指数
10. TAX:每1万美元的全额房产税税率
11. PRTATIO:城镇中的教师学生比例
12. B:城镇中的黑人比例
13. LSTAT:人口低收入群比例
14. MEDV:自住房屋价值中位数,以1000美元为单位
"""
"""
步骤1:定义问题
- 加载库
- 加载数据集
"""
# 加载必要的库
from pathlib import Path # 用于处理文件路径
import joblib # 用于数据的存储和加载
import numpy as np # 用于科学计算
import pandas as pd # 用于数据处理和分析
import matplotlib.pyplot as plt # 用于数据可视化
from sklearn.ensemble import AdaBoostRegressor, ExtraTreesRegressor, GradientBoostingRegressor, RandomForestRegressor # 用于集成学习的回归模型
from sklearn.metrics import mean_squared_error # 用于评估模型性能的均方误差
from sklearn.preprocessing import StandardScaler # 用于数据标准化
from sklearn.linear_model import ElasticNet, Lasso, LinearRegression # 用于线性回归的模型
from sklearn.model_selection import GridSearchCV, KFold, cross_val_score, train_test_split # 用于模型选择和数据划分
from sklearn.neighbors import KNeighborsRegressor # 用于K近邻回归
from sklearn.pipeline import Pipeline # 用于构建数据处理和模型训练的流水线
from sklearn.svm import SVR # 用于支持向量机回归
from sklearn.tree import DecisionTreeRegressor # 用于决策树回归
# 设置matplotlib的图形参数,用于控制图形的大小和字体大小等
plt.rcParams['figure.figsize'] = (4, 4)
plt.rcParams['font.size'] = 6
plt.rcParams['figure.dpi'] = 150
# 加载数据集
filename = Path(__file__).parent / 'data/boston_house_prices.csv'
column_names = ['CRIM', 'ZN', 'INDUS', 'CHAS', 'NOX', 'RM', 'AGE', 'DIS', 'RAD', 'TAX', 'PTRATIO', 'B', 'LSTAT', 'MEDV']
data = pd.read_csv(filename, header=1, names=column_names)
"""
步骤2:总结数据
- 描述型统计
- 数据可视化
"""
# 描述性统计
"""
该代码块主要用于对数据集'data'进行基本的探索性分析。
函数参数:
- 无
返回值:
- 无
"""
# 打印数据集的前20行
print(data.head(20))
# 打印数据集的形状,即行数和列数
print(data.shape)
# 设置显示精度为1,以简化输出(注:pandas新版写法)
pd.options.display.precision=1
# 打印数据集的描述性统计信息,包括均值、标准差、最小值、分位数和最大值
print(data.describe())
# 打印数据集的信息,包括每列的非空值数量、数据类型等
print(data.info())
# 计算并打印数据集中每个列的空值数量
print(data.isnull().sum())
# 将显示精度恢复至2,以提供更准确的数值比较
pd.options.display.precision=2
# 打印数据集的皮尔逊相关系数矩阵
print(data.corr(method='pearson'))
# 数据可视化
"""
该代码片段展示了使用Pandas和Matplotlib对数据进行多种可视化的方法,包括:
1. 直方图:展示每个变量的分布情况。
2. 密度图:展示每个变量的分布密度,可用来观察分布形状。
3. 箱线图:展示每个变量的四分位数、中位数和异常值等统计特性。
4. 散点图:通过两两变量的组合,展示变量间的相关性。
5. 相关关系矩阵:以热力图形式展示数据集中所有变量之间的相关性。
注:原代码中有一些可调整的参数和未使用的变量,这里保留了原代码的结构和注释规范要求。
"""
## 直方图
# 为单个变量绘制直方图,不共享x或y轴的刻度,标签大小为1
data.hist(sharex=False, sharey=False, xlabelsize=1, ylabelsize=1)
## 密度图
# 绘制每个变量的密度图,以子图形式展示,不共享x轴,不显示图例
data.plot(kind='density', subplots=True, layout=(4, 4), sharex=False, legend=False)
## 箱线图
# 绘制每个变量的箱线图,以子图形式展示,不共享x或y轴
data.plot(kind='box', subplots=True, layout=(4, 4), sharex=False, sharey=False)
## 散点图
# 生成数据集中所有变量两两组合的散点图矩阵
pd.plotting.scatter_matrix(data)
## 相关关系矩阵
# 绘制数据集中所有变量之间的相关性热力图
fig = plt.figure()
ax = fig.add_subplot(111)
cax = ax.matshow(data.corr(), vmin=-1, vmax=1, interpolation='none') # 显示相关性矩阵热力图
fig.colorbar(cax) # 添加颜色条
ticks = np.arange(0, 14, 1) # 定义刻度
ax.set_xticks(ticks)
ax.set_yticks(ticks)
ax.set_xticklabels(column_names) # 设置x轴标签
ax.set_yticklabels(column_names) # 设置y轴标签
fig.text(0.5, 1.02, 'Correlation Heatmap', ha='center') # 设置标题
plt.show()
"""
步骤3:准备数据
- 数据清洗
- 特征选择
- 数据转换
"""
# 划分数据集为训练集和测试集
"""
该代码片段主要功能是将数据集划分为训练集和测试集。
data: 包含特征和目标变量的DataFrame,其中特征位于列的前面,目标变量位于最后一列。
test_size: 测试集占总数据集的比例。
random_state: 用于分组随机化操作的种子值,以确保可复现性。
shuffle: 是否在划分之前对数据进行洗牌。
返回值: 不返回任何值,但打印出了训练集和测试集的形状。
该代码首先从data中分离出特征X和目标变量y,然后使用train_test_split函数将数据集划分为训练集和测试集,
并打印出各个部分的形状。
"""
# 分离特征和目标变量
X = data.iloc[:, :-1]
y = data.iloc[:, -1]
# 划分数据集为训练集和测试集
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=7, shuffle=True)
# 打印训练集和测试集的形状
print(X_train.shape, X_test.shape, y_train.shape, y_test.shape)
"""
步骤4:评估算法
- 划分验证数据集
- 测试选项和评估指标
- 快速抽查算法
- 比较算法
"""
# 定义评估指标和划分验证集的参数
# 设置交叉验证的参数
# 参数说明:
# num_folds: 交叉验证的折数,这里设置为10,表示数据将被分为10个部分进行验证。
# seed: 随机种子,用以确保结果的可复现性。这里使用np.random.randint(1000)生成一个1到1000之间的随机整数作为种子。
# scoring: 评估指标,这里选择'neg_mean_squared_error',即负均方误差,常用于回归模型的评估。
num_folds, seed, scoring = 10, np.random.randint(1000), 'neg_mean_squared_error'
# 快速评估多种算法
# 定义模型集合
# 模型集合是一个包含不同机器学习模型及其名称的元组列表。
# 每个元素都是一个二元组,其中第一个元素是模型的名称(字符串),第二个元素是该模型的实例。
models = [('LR', LinearRegression()),
('LASSO', Lasso()),
('EN', ElasticNet()),
('KNN', KNeighborsRegressor()),
('CART', DecisionTreeRegressor()),
('SVR', SVR())]
# 快速评估算法
# 初始化用于存储模型评分的结果列表和模型名称列表。
results, names = [], []
print("\n\033[1;30m评估算法-算法基线:\033[0m")
"""
对提供的模型进行交叉验证,计算每个模型的平均分数和标准差。
参数:
- models: 模型列表,每个元素是一个名称和模型的元组。
- num_folds: 交叉验证的折数。
- seed: 随机种子,用于确保结果的可复现性。
- X_train: 训练数据集的特征部分。
- y_train: 训练数据集的目标部分。
- scoring: 用于评估模型的指标。
返回值:
- 无。该函数直接打印每个模型的平均分数和标准差。
"""
for name, model in models:
try:
# 初始化K-Fold交叉验证器
kfold = KFold(n_splits=num_folds, random_state=seed, shuffle=True)
# 进行交叉验证并存储结果
cv_results = cross_val_score(model, X_train, y_train, cv=kfold, scoring=scoring)
# 将结果添加到结果列表中
results.append(cv_results)
# 存储模型名称
names.append(name)
# 打印每个模型的平均分数和标准差
print('%s: %f (%f)' % (name, cv_results.mean(), cv_results.std()))
except Exception as e:
# 如果在评估模型时发生异常,则打印错误信息
print(f'Error occurred while evaluating {name}: {e}')
"""
结果输出:
LR: -22.006009 (12.188886)
LASSO: -27.105803 (13.165915)
EN: -27.923014 (13.156405)
KNN: -39.808936 (16.507968)
CART: -25.876366 (18.622815)
SVR: -67.824695 (32.801531)
"""
# 比较算法
fig = plt.figure()
fig.suptitle('Algorithm Comparison')
ax = fig.add_subplot(111)
plt.boxplot(results)
ax.set_xticklabels(names)
# 算法标准化
## 标准化数据
"""
我们怀疑原始数据的不同分布可能会对技能产生负面影响一些算法。让我们用一个标准化的拷贝来计算相同的算法数据集。
在这里对数据进行转换,使每个属性的平均值为零,标准差是1。我们还需要在转换时避免数据泄漏数据。
避免泄漏的一种好方法是使用标准化数据的管道,并构建在交叉验证测试工具中为每个折叠建立模型。
这样我们就能得到一个公平的估计每个具有标准化数据的模型如何处理未见过的数据。
定义一系列管道对象,每个管道都包含一个预处理器(标准化)和一个模型。
预处理器用于将输入数据标准化,模型包括线性回归(LR)、Lasso回归(LASSO)、ElasticNet回归(EN)、
K近邻回归(KNN)和决策树回归(CART)。
返回值:
pipelines - 一个包含多个管道对象的列表,每个管道对象都由标准化预处理器和一个回归模型组成。
"""
pipelines = [('ScaledLR', Pipeline([('Scaler', StandardScaler()),('LR', LinearRegression())])),
('ScaledLASSO', Pipeline([('Scaler', StandardScaler()),('LASSO', Lasso())])),
('ScaledEN', Pipeline([('Scaler', StandardScaler()),('EN', ElasticNet())])),
('ScaledKNN', Pipeline([('Scaler', StandardScaler()),('KNN', KNeighborsRegressor())])),
('ScaledCART', Pipeline([('Scaler', StandardScaler()),('CART', DecisionTreeRegressor())]))]
results, names = [], []
print("\n\033[1;30m评估算法-算法标准化:\033[0m")
"""
在给定的模型管道集合中,对每个模型执行交叉验证,并打印出每个模型的平均分数和标准差。
同时,将每个模型的交叉验证结果和名称存储在列表中。
参数:
- pipelines: 模型管道的列表,其中每个元素是一个名称和模型的元组。
- num_folds: 交叉验证的折数。
- seed: 随机状态种子,用于确保结果的可复现性。
- X_train: 训练数据集的特征部分。
- y_train: 训练数据集的目标部分。
- scoring: 用于评估模型的评分方法。
返回值:
- 无明确返回值,但会打印出每个模型的性能指标,并将结果存储在内部变量中。
"""
for name, model in pipelines:
try:
# 初始化K-Fold交叉验证器
kfold = KFold(n_splits=num_folds, random_state=seed, shuffle=True)
# 对当前模型执行交叉验证,并存储结果
cv_results = cross_val_score(model, X_train, y_train, cv=kfold, scoring=scoring)
results.append(cv_results)
names.append(name)
# 打印模型名称、平均分数和标准差
print('%s: %f (%f)' % (name, cv_results.mean(), cv_results.std()))
except Exception as e:
# 如果在评估模型时发生错误,打印错误信息
print(f'Error occurred while evaluating {name}: {e}')
## 比较算法
"""
生成一个包含箱线图的图形,用于比较不同算法的缩放性能。
参数:
- results: 一个列表的列表,其中每个子列表包含特定算法的缩放性能数据。
- names: 一个字符串列表,包含对应算法的名称。
返回值:
- 无。该函数直接显示生成的图形。
"""
# 创建一个新的图形窗口
fig = plt.figure()
# 设置图形的主标题
fig.suptitle('Scaled Algorithm Comparison')
# 在图形窗口中添加一个子图,并返回其轴对象
ax = fig.add_subplot(111)
# 在当前子图上绘制箱线图
plt.boxplot(results)
# 设置x轴的标签
ax.set_xticklabels(names)
# 显示图形
# plt.show()
"""
结果输出:
ScaledLR: -22.160898 (8.220699)
ScaledLASSO: -26.976571 (9.658686)
ScaledEN: -28.289409 (10.405312)
ScaledKNN: -21.703487 (12.237569)
ScaledCART: -24.251940 (10.590830)
"""
"""
步骤5:提高准确度(通过调优)
- 调整算法参数
- 集成方法
从步骤4结果,发现KNN在数据集的标准版本取得了很好的结果。
"""
## 调整算法参数(通过调优KNN算法参数)
print("\n\033[1;30m以下结果是通过【调优KNN算法参数】得出:\033[0m")
# 根据给定的训练集X_train和目标变量y_train,使用KNN回归模型进行参数网格搜索以找到最佳的k值。
# 使用标准缩放器对训练集进行缩放,确保特征以相同的尺度被比较。
# 参数:
# - X_train: 训练集的特征矩阵
# - y_train: 训练集的目标变量
# 返回值:无,打印出最佳的k值及其对应的评分,以及每个k值的平均得分和标准差。
scaler = StandardScaler().fit(X_train)
X_train_scaled = scaler.transform(X_train) # 对训练集进行标准化处理
# 设定要测试的k值范围
k_values = np.array([1, 3, 5, 7, 9, 11, 13, 15, 17, 19, 21])
# 创建参数网格,唯一参数是n_neighbors
param_grid = dict(n_neighbors=k_values)
model = KNeighborsRegressor() # 初始化KNeighborsRegressor模型
# 设定交叉验证的折数、随机种子和是否打乱数据
kfold = KFold(n_splits=num_folds, random_state=seed, shuffle=True)
# 使用GridSearchCV进行交叉验证和参数搜索
grid_search_cv = GridSearchCV(estimator=model, param_grid=param_grid, scoring=scoring, cv=kfold)
grid_result = grid_search_cv.fit(X_train_scaled, y_train) # 搜索最佳参数
# 打印最佳评分和对应的k值
print("Best: %f using %s" % (grid_result.best_score_, grid_result.best_params_))
# 打印每个k值的平均得分和标准差
means = grid_result.cv_results_['mean_test_score']
stds = grid_result.cv_results_['std_test_score']
params = grid_result.cv_results_['params']
for mean, stdev, param in zip(means, stds, params):
print("%f (%f) with: %r" % (mean, stdev, param))
## 集成方法
"""
在这个问题上我们可以提高算法性能的另一种方法:使用集成方法。我们将评估四种不同的集成机器学习算法,两次提升和两次装袋方法:
增强方法:适应提升(AdaBoost, AB)和梯度提升(Gradient Boosting, GB)。
袋装方法:随机森林(Random Forest, RF)和极端随机树(Extra Trees, ET)。
可以翻阅机器学习本栏中:《如何通过Ensemble提高机器学习性能》
"""
# 初始化模型集合,每个模型都包括预处理步骤(标准化)和一个回归模型。
# ensembles列表中包含了不同的模型名称和对应的pipeline对象。
ensembles = [('ScaledAB', Pipeline([('Scaler', StandardScaler()), ('AB', AdaBoostRegressor())])),
('ScaledGBM', Pipeline([('Scaler', StandardScaler()), ('GBM', GradientBoostingRegressor())])),
('ScaledRF', Pipeline([('Scaler', StandardScaler()), ('RF', RandomForestRegressor())])),
('ScaledET', Pipeline([('Scaler', StandardScaler()), ('ET', ExtraTreesRegressor())]))]
# 初始化用于存储模型评分的结果列表和模型名称列表。
results, names = [], []
print("\n\033[1;30m评估算法-集成方法:\033[0m")
# 在给定的模型集合(ensembles)中,对每个模型使用交叉验证(cross-validation)来评估其在训练集(X_train, y_train)上的性能。
# num_folds: 交叉验证的折数。
# seed: 随机数种子,用于确保结果的可复现性。
# scoring: 用于评估模型的指标。
# ensembles: 模型集合,每个元素是一个(name, model)对,其中name是模型名称,model是待评估的模型实例。
# 返回值: 无。该代码段主要打印每个模型的平均分数和标准差,以及在评估过程中遇到的任何错误。
for name, model in ensembles:
try:
# 初始化K-Fold交叉验证器
kfold = KFold(n_splits=num_folds, random_state=seed, shuffle=True)
# 对当前模型进行交叉验证,并存储结果
cv_results = cross_val_score(model, X_train, y_train, cv=kfold, scoring=scoring)
results.append(cv_results)
names.append(name)
# 打印当前模型的平均分数和标准差
print('%s: %f (%f)' % (name, cv_results.mean(), cv_results.std()))
except Exception as e:
# 如果在评估过程中发生异常,打印错误信息
print(f'Error occurred while evaluating {name}: {e}')
"""
比较集成方法算法:展示和比较不同集成算法的性能。
参数:
- results: 一个包含多个集成算法结果的列表,每个算法的结果应为一个列表,以便进行箱线图比较。
- names: 一个字符串列表,对应于 'results' 中每个算法的标签。
返回值:
- 无。该函数直接绘制箱线图用于显示算法性能的比较。
"""
## 比较集成方法算法
print("\n\033[1;30m比较集成方法算法:\033[0m")
# 创建一个新的图形
fig = plt.figure()
# 设置图形的主标题
fig.suptitle('Scaled Ensemble Algorithm Comparison')
# 在图形中添加一个子图
ax = fig.add_subplot(111)
# 绘制箱线图
plt.boxplot(results)
# 设置x轴的标签
ax.set_xticklabels(names)
# 显示图形
plt.show()
"""
结果输出:
ScaledAB: -15.679634 (11.272571)
ScaledGBM: -11.062894 (8.820895)
ScaledRF: -12.882659 (8.692827)
ScaledET: -11.476644 (10.679596)
"""
## 调优集成方法算法
"""
从上面结果,发现Gradient Boosting Machines(GBM) 在数据集的标准版本取得了很好的结果。
"""
print("\n\033[1;30m以下结果是通过【调优集成方法算法】参数得出:\033[0m")
# 标准化训练集数据
scaler = StandardScaler().fit(X_train)
X_train_scaled = scaler.transform(X_train)
# 定义梯度提升回归器参数网格
param_grid = dict(n_estimators=np.array([50, 100, 150, 200, 250, 300, 350, 400]))
# 设定交叉验证的折数、随机种子和是否打乱数据
kfold = KFold(n_splits=num_folds, random_state=seed, shuffle=True)
# 初始化待调参的梯度提升回归模型
tuned_model = GradientBoostingRegressor(random_state=seed)
# 使用网格搜索进行交叉验证调参
grid_result_cv = GridSearchCV(tuned_model, param_grid, scoring=scoring, cv=kfold)
# 在标准化后的训练集上拟合模型并进行参数调优
grid_result = grid_result_cv.fit(X_train_scaled, y_train)
# 打印最佳评分和对应的参数设置
print("Best: %f using %s" % (grid_result.best_score_, grid_result.best_params_))
# 打印各参数组合的平均测试分数和标准差
means = grid_result.cv_results_['mean_test_score']
stds = grid_result.cv_results_['std_test_score']
params = grid_result.cv_results_['params']
for mean, stdev, param in zip(means, stds, params):
print("%f (%f) with: %r" % (mean, stdev, param))
"""
结果输出:
以下结果是通过【调优集成方法算法】参数得出:
Best: -10.097317 using {'n_estimators': 300}
-11.695323 (9.526200) with: {'n_estimators': 50}
-10.843661 (8.548238) with: {'n_estimators': 100}
-10.507347 (8.347537) with: {'n_estimators': 150}
-10.361741 (8.191638) with: {'n_estimators': 200}
-10.216698 (8.046798) with: {'n_estimators': 250}
-10.097317 (7.873761) with: {'n_estimators': 300}
-10.116740 (7.928236) with: {'n_estimators': 350}
-10.101904 (7.893034) with: {'n_estimators': 400}
"""
"""
步骤6:最终确定模型且展示结果
- 在验证数据集上进行预测
- 在整个训练数据集上创建独立的模型
- 保存模型以供以后使用
"""
"""
该脚本用于训练和保存一个GradientBoostingRegressor模型。
模型训练基于训练数据集(X_train, y_train),并在验证数据集(X_test, y_test)上进行评估。
最后,将训练好的模型保存到文件中。
模型训练步骤包括:
1. 对训练数据进行标准化处理;
2. 使用GradientBoostingRegressor算法训练模型;
3. 对验证数据集进行标准化处理,并使用训练好的模型进行预测;
4. 计算预测结果与真实结果之间的均方误差。
最后,将训练好的模型保存到'./model/finalized_model.pkl'文件中。
"""
# 创建模型
# 对训练数据X_train进行标准化处理
scaler = StandardScaler().fit(X_train)
X_train_scaled = scaler.transform(X_train)
# 训练GradientBoostingRegressor模型
model = GradientBoostingRegressor(n_estimators=300, random_state=seed)
model.fit(X_train_scaled, y_train)
# 转换验证数据集,并进行预测
X_test_scaled = scaler.transform(X_test)
y_pred = model.predict(X_test_scaled)
# 打印预测结果与真实结果之间的均方误差
print(mean_squared_error(y_test, y_pred))
# 保存模型
filename = Path(__file__).parent / 'model/finalized_model.pkl'
joblib.dump(model, filename)
直方图、密度图、箱线图、散点图、相关性矩阵图,如下: