ml-regression-demo:基于 Python 和 scikit-learn 的机器学习回归预测实验,完成数据读取、数据处理、训练集划分、线性回归模型训练、MAE/MSE/RMSE/R² 模型评估以及预测结果可视化,用于体验华为云码道辅助完成完整机器学习实验流程。 - AtomGit
深圳技术大学 - 开源代码托管,代码协作 - AtomGit
模型还能怎么优化?用华为云码道诊断并改进机器学习回归项目
前言
在上一篇实践中,我使用华为云码道辅助完成了一个完整的 Python 机器学习回归预测实验。
项目使用 scikit-learn 自带的 Diabetes 数据集,以 LinearRegression 作为回归模型,完成了数据读取、数据检查、训练集与测试集划分、模型训练、模型预测以及结果评价。
整个数据集共有442 条样本、10 个特征。按照 8:2 的比例进行数据划分后,训练集包含 353 条样本,测试集包含 89 条样本。
程序运行得到的模型评价结果如下:
MAE(平均绝对误差):42.7941 MSE(均方误差):2900.1936 RMSE(均方根误差):53.8534 R²(决定系数):0.4526这说明项目已经能够完成一个完整的机器学习回归预测流程,但模型仍然有进一步分析的空间。
相比从零重新建立一个机器学习项目,这一次我更想解决另一个实际开发中非常常见的问题:
当代码已经能够正常运行,但模型效果还有提升空间时,应该从哪里开始排查?
因此,我继续使用原来的ml-regression-demo项目,通过华为云码道读取已有代码,在现有程序基础上完成模型诊断、残差分析、模型对比和交叉验证。
整个过程由“生成一个机器学习程序”进一步进入到:
已有项目 ↓ 读取现有代码 ↓ 复现模型结果 ↓ 分析预测误差 ↓ 比较不同模型 ↓ 增加交叉验证 ↓ 综合评价模型效果华为云码道支持结合文件、文件夹、终端输出等上下文理解当前项目,并围绕已有代码继续完成代码分析、生成和问题排查,因此除了从零开发项目,也适合继续维护和迭代已有工程。
一、从原始模型结果开始
本次实验并没有立即删除之前的 LinearRegression,而是首先将它作为后续模型对比的基线。
原来的模型训练代码很简单:
from sklearn.linear_model import LinearRegressionmodel = LinearRegression()model.fit(X_train, y_train)y_pred = model.predict(X_test)数据仍然采用固定的数据划分方式:
X_train, X_test, y_train, y_test = train_test_split( X, y, test_size=0.2, random_state=42)使用固定的random_state=42,可以保证后续增加其他模型时仍然使用相同的训练集和测试集。
原项目实际运行得到:
训练集样本数量:353 测试集样本数量:89 MAE:42.7941 MSE:2900.1936 RMSE:53.8534 R²:0.4526其中:
MAE表示预测值与真实值之间平均相差多少。
MSE会对较大的预测误差给予更高的惩罚。
RMSE与目标变量保持相同量纲,因此更加直观。
R²用于反映模型能够解释目标变量变化的程度。
当前得到的R²=0.4526,说明线性回归已经能够学习数据中的一部分规律,但仍然存在一部分变化没有被当前模型充分解释。
这也为后续的模型诊断提供了一个明确的基线。
图1 LinearRegression 基线模型运行结果
二、先检查流程,而不是立即更换模型
模型效果没有达到预期,并不一定意味着模型本身存在问题。
很多机器学习项目的异常结果,实际上可能来自数据处理、数据划分或者评价方式。
因此,这一次我首先让华为云码道直接读取现有的ml_regression.py,检查整个机器学习流程。
主要包括:
特征和目标变量是否正确划分 训练集与测试集是否正确划分 数据是否存在缺失值 是否存在数据泄漏 评价指标是否正确 模型训练和预测逻辑是否完整原项目的数据检查结果显示:
是否存在缺失值:否并且 10 个输入特征以及目标变量的缺失值数量均为 0。
训练集与测试集的维度也符合预期:
训练集特征维度:(353, 10) 测试集特征维度:(89, 10)因此,本次实验中的基础数据处理流程没有出现明显问题。
这意味着当前模型效果并不是由一个简单的代码错误造成的,而需要进一步从模型本身和预测误差入手分析。
这也是使用 AI 编程工具处理已有项目时很重要的一点:
并不是每次都需要重新生成代码。
华为云码道能够通过文件、文件夹、终端、Problems 等上下文了解已有项目,使智能体能够围绕当前代码进行更有针对性的分析和处理。
图2 使用华为云码道读取并分析已有机器学习项目
三、通过残差进一步观察模型
确认代码流程没有明显问题以后,下一步就是观察模型到底在哪些样本上预测得不够准确。
这里可以引入一个很常见的概念:
残差。
残差的计算方式非常简单:
residuals = y_test - y_pred也就是:
残差 = 真实值 - 预测值如果某一个样本:
真实值 = 200 预测值 = 180那么残差就是:
20如果预测结果与真实值完全相同,那么残差就是 0。
因此,将预测值与残差画成散点图,可以比较直观地观察模型的预测误差。
代码如下:
residuals = y_test - y_predplt.figure(figsize=(8, 6))plt.scatter( y_pred, residuals, alpha=0.7)plt.axhline( y=0, linestyle="--")plt.xlabel("Predicted Value")plt.ylabel("Residual")plt.title("Residual Plot")plt.grid(True)plt.tight_layout()plt.savefig( "residual_plot.png", dpi=300)plt.show()在理想情况下,大部分点应该比较均匀地分布在:
y = 0附近。
如果残差出现明显的规律、趋势或者大量偏离 0 的点,就说明当前模型仍然没有充分描述数据中的某些关系。
相比只看R²=0.4526这一个数字,残差图能够更加直观地告诉我们:
模型究竟是怎样产生误差的。
图3 LinearRegression 模型残差分布
四、保留线性回归作为基线
完成残差分析以后,我没有直接删除原来的模型。
LinearRegression 仍然作为整个实验中的基线模型。
这是因为模型优化需要一个可以进行比较的参照。
如果直接不断替换模型,很容易出现一个问题:
模型A ↓ 换成模型B ↓ 换成模型C最后虽然得到了新的结果,却很难清楚判断每一次修改到底带来了多少变化。
所以本次实验保留三个模型:
LinearRegression Ridge GradientBoostingRegressor并让它们使用完全相同的数据进行训练和测试。
五、加入 Ridge 回归
第二个模型使用 Ridge:
from sklearn.linear_model import Ridgeridge_model = Ridge( alpha=0.1)Ridge 与普通线性回归非常接近。
不同的是,Ridge 在训练过程中增加了 L2 正则化,用于对模型参数进行一定程度的约束。
因此,这一步主要是观察:
在保持线性模型结构的情况下,加入正则化能否改善模型效果?
在相同测试集上,Ridge 得到:
MAE:42.9969 MSE:2856.4869 RMSE:53.4461 R²:0.4609与原来的 LinearRegression 相比:
LinearRegression R²:0.4526 Ridge R²:0.4609Ridge 的 R² 出现了小幅提高,同时 RMSE 从:
53.8534下降到:
53.4461说明正则化对当前模型带来了一定变化,但整体提升幅度并不大。
这也说明:
单纯在线性模型上增加正则化,并不能完全解决当前数据中的建模问题。
六、加入 Gradient Boosting 回归模型
接下来增加第三个模型:
from sklearn.ensemble import GradientBoostingRegressorgbr_model = GradientBoostingRegressor( random_state=42, n_estimators=100, learning_rate=0.03, max_depth=2, min_samples_leaf=5, loss="huber")与 LinearRegression 和 Ridge 不同,Gradient Boosting 可以学习更加复杂的非线性关系。
这使得它能够作为一个很好的对照,用来验证:
当前数据中是否存在单纯线性模型难以充分表达的关系?
在相同训练集与测试集条件下,本次运行得到:
MAE:42.3410 MSE:2721.6434 RMSE:52.1694 R²:0.4863三个模型的测试结果可以整理为:
| 模型 | MAE | RMSE | R² |
|---|---|---|---|
| LinearRegression | 42.7941 | 53.8534 | 0.4526 |
| Ridge | 42.9969 | 53.4461 | 0.4609 |
| GradientBoostingRegressor | 42.3410 | 52.1694 | 0.4863 |
从测试集结果来看,GradientBoostingRegressor 的:
- MAE 最低;
- RMSE 最低;
- R² 最高。
也就是说,在当前这一次固定数据划分下,Gradient Boosting 对数据的拟合表现优于两个线性模型。
但机器学习模型评价到这里还没有结束。
七、为什么还要加入5折交叉验证?
如果只看上面的结果,很容易直接得出:
GradientBoostingRegressor 最好但这样的判断其实还不够完整。
因为目前的所有结果都来自同一次训练集和测试集划分。
如果重新随机划分数据,评价结果就可能发生变化。
因此,还需要使用交叉验证观察模型在不同数据划分下的稳定表现。
本次采用:
from sklearn.model_selection import KFoldkf = KFold( n_splits=5, shuffle=True, random_state=42)5折交叉验证会将数据划分为5个部分。
每一次使用其中4份训练,另外1份验证。
可以简单理解为:
第1轮: 1 2 3 4 训练 5 验证 第2轮: 1 2 3 5 训练 4 验证 第3轮: 1 2 4 5 训练 3 验证 …… 共进行5轮最后再取5次评价结果的平均值。
代码可以使用:
from sklearn.model_selection import cross_val_scorecv_scores = cross_val_score( model, X, y, cv=kf, scoring="r2")cv_mean = cv_scores.mean()这样可以减少一次随机划分所带来的偶然性。
八、三个模型的交叉验证结果
在相同的 5 折交叉验证设置下,本次实验得到:
| 模型 | 测试集 R² | 5折交叉验证平均 R² |
|---|---|---|
| LinearRegression | 0.4526 | 0.4785 |
| Ridge | 0.4609 | 0.4791 |
| GradientBoostingRegressor | 0.4863 | 0.4470 |
这里出现了一个很有意思的结果。
如果只观察当前测试集:
GradientBoostingRegressor:0.4863明显高于:
LinearRegression:0.4526 Ridge:0.4609但是在5折交叉验证中:
LinearRegression:0.4785 Ridge:0.4791 GradientBoostingRegressor:0.4470Gradient Boosting 的平均 R² 反而低于两个线性模型。
这正好体现了为什么不能只根据一次测试结果判断模型。
九、测试集更高,不代表模型一定更稳定
这一组结果非常适合作为机器学习模型评价的一个例子。
GradientBoostingRegressor 在固定测试集上取得了最高的:
R² = 0.4863同时 MAE 和 RMSE 也更低。
说明它在当前这组测试数据上确实取得了更好的预测表现。
但5折交叉验证平均 R² 为:
0.4470没有超过两个线性模型。
这意味着它的优势并没有在不同数据划分中始终保持。
因此,对于一个模型,不能只问:
它这一次的 R² 是多少?
还应该继续问:
换一批训练数据和验证数据以后,它的效果是否依然稳定?
这也是交叉验证的重要价值。
十、三个模型应该怎样理解?
经过这次对比,可以更加清楚地看到三个模型各自的特点。
LinearRegression
测试集:
MAE:42.7941 RMSE:53.8534 R²:0.45265折交叉验证:
平均 R²:0.4785它的结构最简单,也可以作为整个实验很好的基线。
Ridge
测试集:
MAE:42.9969 RMSE:53.4461 R²:0.46095折交叉验证:
平均 R²:0.4791相比普通线性回归,Ridge 的测试集 R² 和 RMSE有所改善。
交叉验证平均 R² 也略高。
整体变化并不大,说明正则化虽然带来了一定效果,但当前问题并不只是简单的参数约束问题。
GradientBoostingRegressor
测试集:
MAE:42.3410 RMSE:52.1694 R²:0.48635折交叉验证:
平均 R²:0.4470它在当前测试集上的表现最好。
但交叉验证平均结果低于两个线性模型。
因此,它证明了更加复杂的模型能够在部分数据划分中捕捉更多关系,同时也说明:
更加复杂并不等于在所有数据划分下都更稳定。
十一、将模型结果可视化
只观察终端中的数字仍然不够直观。
因此,我让华为云码道继续生成三个模型的测试集 R² 对比图。
核心代码如下:
plt.figure(figsize=(8, 6))bars = plt.bar( result_df["模型"], result_df["R²"])plt.xlabel("Model")plt.ylabel("R²")plt.title( "Model R2 Comparison")plt.grid( axis="y", alpha=0.3)for bar, value in zip( bars, result_df["R²"]): plt.text( bar.get_x() + bar.get_width() / 2, bar.get_height(), f"{value:.4f}", ha="center", va="bottom" )plt.tight_layout()plt.savefig( "model_comparison.png", dpi=300通过柱状图,可以直接看到当前测试集上的模型差异:
LinearRegression 0.4526 Ridge 0.4609 GradientBoosting 0.4863相比单纯阅读表格,可视化可以更加直观地呈现模型之间的变化。
图4 三种回归模型测试集 R² 对比
十二、从“优化参数”转向“诊断模型”
完成这次实验后,我发现“优化机器学习模型”并不应该理解为:
换模型 ↓ 调参数 ↓ 看R²有没有提高更完整的流程应该是:
得到基线模型 ↓ 检查数据处理 ↓ 检查模型训练流程 ↓ 观察预测误差 ↓ 增加候选模型 ↓ 使用相同数据比较 ↓ 加入交叉验证 ↓ 综合分析结果例如这次实验,如果只看一次测试集:
GradientBoostingRegressor表现最好。
但加入5折交叉验证以后,就可以看到:
Ridge LinearRegression在不同数据划分下反而表现得更加稳定。
这说明模型评价不能只追求一个更高的数字。
十三、华为云码道在已有项目中能做什么?
三号实验主要体验的是:
从自然语言需求开始,生成一个可以运行的机器学习实验。
而这一次重点已经发生变化。
项目本身已经存在,代码也已经可以正常运行。
华为云码道需要完成的是:
读取已有项目 ↓ 理解现有代码 ↓ 结合终端结果分析问题 ↓ 继续修改原有文件 ↓ 增加残差分析 ↓ 增加新的机器学习模型 ↓ 加入交叉验证 ↓ 再次运行并分析结果这更接近日常开发中维护已有代码的过程。
华为云官方目前将码道定位为能够理解项目上下文和开发意图,并完成需求分析、代码编写、调试优化和测试验证等研发任务的代码智能体;码道IDE还支持项目代码索引以及文件、文件夹、Git、Terminal、Problems 等上下文信息。
对于机器学习实验来说,这意味着开发者不需要每次出现问题都重新解释整个项目。
可以把现有 Python 文件和运行结果作为上下文,让智能体围绕当前工程继续分析。
十四、AI生成代码之后,仍然需要理解结果
AI可以快速帮助我们补充:
模型代码 评价代码 交叉验证代码 可视化代码但机器学习实验真正重要的部分并不只是“程序能够运行”。
例如本次结果中:
GradientBoosting测试集R²最高如果只看到这里,很容易认为模型优化已经成功。
但继续加入交叉验证后,会发现:
GradientBoosting平均R²反而更低这时候就需要自己理解:
- 测试集结果代表什么;
- 交叉验证代表什么;
- 模型复杂度意味着什么;
- 一次较好的结果是否具有稳定性。
因此,AI编程工具更适合承担:
代码实现和开发辅助。
而开发者仍然需要负责:
理解实验逻辑和解释模型结果。
十五、总结
这次实验继续使用上一篇已经完成的ml-regression-demo项目,没有重新创建新的机器学习工程。
实验从原始 LinearRegression 的:
MAE:42.7941 MSE:2900.1936 RMSE:53.8534 R²:0.4526开始。
在确认数据处理和模型训练流程正常以后,进一步增加了:
残差分析 Ridge回归 GradientBoostingRegressor 5折交叉验证 模型结果可视化最终三个模型得到:
| 模型 | MAE | RMSE | 测试集 R² | 5折CV平均 R² |
|---|---|---|---|---|
| LinearRegression | 42.7941 | 53.8534 | 0.4526 | 0.4785 |
| Ridge | 42.9969 | 53.4461 | 0.4609 | 0.4791 |
| GradientBoostingRegressor | 42.3410 | 52.1694 | 0.4863 | 0.4470 |
这组结果也让我对机器学习模型优化有了更直观的认识:
模型优化不是不断寻找更复杂的算法,而是通过数据检查、误差分析、模型对比和交叉验证,确认模型是否真的取得了稳定改善。
这次华为云码道的作用也不再只是生成一段新的 Python 代码,而是围绕一个已经存在的机器学习项目继续完成代码理解、功能增加、模型诊断和结果分析。
对于学习机器学习或者日常开发中的小型数据分析项目来说,这种基于现有代码继续协作的方式,能够明显减少重复查找 API、编写实验框架和整理评价代码的时间,让开发者把更多精力放在真正重要的模型分析上。