简介:面向Python深度学习初学者的实践资源包,内容以回归建模为主线,同时覆盖神经网络分类应用,帮助学习者打通从数据预处理、模型结构设计到训练评估的完整流程。压缩包共26个文件,包含12个Python脚本、6个CSV数据集、6个Jupyter Notebook,另附README说明与配置文档,整体约1006KB,目录清晰,便于按模块对照学练。项目提供随机梯度下降、Adam优化、多层全连接网络、Logistic回归、LSTM/RNN序列建模等关键主题,涵盖数据清洗、归一化、损失函数与超参数调整等环节的可运行示例;并配有汇率预测、气候变化、泰坦尼克号生存预测、MNIST手写识别等真实数据集,可复用于时间序列预测、价格趋势分析、购买行为预测等典型任务。当前已有277人学习,适合希望快速积累实战经验的中级开发者,可利用脚本与Notebook逐步理解模型训练、调参与评估方法。
1. 深度学习回归不是分类的附属品:这套 Python 技术栈要解决什么
把温度、销量、浓度这类连续数值预测出来,是生产环境里比图像分类更频繁出现的需求。工业软测量、电力负荷预测、供应链补货,本质上都是回归:给一批高维特征,映射出一个带小数点的数。标题里的 Deep-Learning-in-Python、深度学习回归、神经网络,拆开看是一条清晰的技术链路——用 Python 组织数据,用神经网络建模连续值输出,最后把模型落成一个能回答“是多少”而不是“是哪类”的推理服务。
有个反直觉的判断先说在前面:同样的数据和网络结构,回归任务比分类更容易出现“静默失败”。分类错了会有明确的误报率,回归错了可能只是数值偏差 0.3,但业务上已经不可用,尤其是软测量这类场景,一个偏差直接导致工艺参数误调。另一个常见误区是拿分类那套评估习惯来验回归,准确率、混淆矩阵在这里全部失效。这篇文章按“原理辨析 → 最小实现 → 参数解读 → 踩坑边界 → 上线验证”的顺序,把深度学习回归从选损失函数到导出校验的完整路径过一遍,读完可以直接在本地把第一版模型跑起来。
2. 从线性回归到带隐层的深度回归:模型到底在拟合什么
2.1 回归和分类的分界在输出端,不在网络结构
前馈神经网络的基础结构在回归和分类里几乎一样:输入层、隐藏层、输出层,中间用权重连接。区别只在最后一层。分类任务一般在输出层接 softmax 或 sigmoid,把网络输出压成概率分布,再配合交叉熵计算梯度;回归任务不需要概率语义,输出层是一个纯线性单元,直接输出连续的实数值。这个差别直接决定了反向传播时损失对权重的梯度形态——回归的梯度来自预测值和真实值的差值,分类的梯度来自概率分布之间的距离。
理论上支撑深度回归成立的是通用近似定理:一个带足够宽隐藏层的前馈神经网络,可以逼近任意连续函数。这个结论保证了神经网络做回归在表达能力上没有天花板,但工程上真正难的不是“能不能逼近”,而是“样本量撑不撑得起这么强的表达”。线性回归只有一个权重矩阵,几十个样本就能拟合;一个 128 宽、3 层的回归网络有上万参数,没有成百上千条样本,训练结果通常比线性基线还差。所以选型时的朴素判断标准是:特征和输出之间如果是明显非线性、且数据量足够,才值得往深度方向走。
2.2 回归的损失函数和评估指标:别再盯着 accuracy 看
分类模型的评估天然清晰:对的比上错的。回归没有“对错”,只有“偏差有多大”,所以要先选一个损失函数来量化偏差。常见的有 MSE、MAE、Huber 三种,它们的本质区别是对大误差的容忍度不同。
| 损失函数 | 数学形式 | 对大误差的态度 | 典型适用场景 |
|---|---|---|---|
| MSE | (y - ŷ)² | 平方放大,极敏感 | 常规默认,误差必须被严格惩罚时 |
| MAE | |y - ŷ| | 线性惩罚,一视同仁 | 数据含离群点,不想被个别大误差主导 |
| Huber | 误差小于 δ 时平方,大于 δ 时线性 | 折中,δ 控制拐点 | 工业现场数据常见离群值,首选 |
Dense 层里常见的 activation="relu" 和输出层不接激活函数,配合 mse 损失,就是深度学习回归的最小配置。评估时同样要换一套语言:R²(决定系数)回答“模型解释了百分之多少的方差”,MAE 回答“平均偏差几个单位”,RMSE 回答“大误差被平方放大后的量级”。这三者和准确率没有任何换算关系,也不能用“预测对了 90%”这种话描述回归结果。
2.3 深度和宽度怎么选:先宽后深是更稳的起点
网络拓扑层面,一个被我反复验证过的经验是:表格型回归数据上,先保证第一层足够宽,再考虑加深。第一层宽度决定模型能捕获多少个输入特征组合,深度则负责在这些组合之上做非线性变换。初始化阶段直接用两层宽隐藏层,而不是一上来堆五层窄网络。
import tensorflow as tf from tensorflow.keras import layers, models model = models.Sequential([ layers.Dense(128, activation="relu", input_shape=(X.shape[1],)), layers.Dense(64, activation="relu"), layers.Dense(1, activation=None) # 输出层线性,不加 sigmoid 或 softmax ]) model.compile( optimizer=tf.keras.optimizers.Adam(learning_rate=1e-3), loss="mse", metrics=["mae"] )这个代码片段的三个关键点:第一,input_shape 不写样本数只写特征维度,Keras 会自动推断 batch 维度;第二,隐藏层全部用 relu,因为它对正值的梯度恒为 1,不容易被困在饱和区,比 sigmoid 和 tanh 更适合深层网络的反向传播;第三,输出层 activation=None,保证输出范围不受限制,否则预测值会被强行截在 [0,1] 或 [-1,1] 区间里,这是回归任务最隐蔽的错误之一。CNN 卷积神经网络、图神经网络这类结构,只有在输入是图像、序列或图结构时才需要替换这里的全连接层,普通表格特征用 Dense 就已经覆盖了绝大多数场景。
3. 用 Keras 在 Python 里跑通第一个回归网络:代码与参数解读
3.1 数据形状:回归网络的输入输出都是什么
先明确深度回归模型的输入输出形状。输入张量 X 的 shape 是 (样本数 n, 特征数 d),输出张量 y 的 shape 是 (样本数 n,),也就是说每条样本对应一个连续数值。下面这段代码生成一个带噪声的非线性函数作为演示数据,避免依赖任何外部数据集,也方便验证网络是否真正学到了非线性关系。
import numpy as np from sklearn.model_selection import train_test_split rng = np.random.default_rng(42) n = 2000 x1 = rng.uniform(-3, 3, n) x2 = rng.uniform(-2, 2, n) x3 = rng.normal(0, 1, n) y = 2.5 * np.sin(x1) + 1.2 * x2 ** 2 + 0.8 * x3 + rng.normal(0, 0.3, n) X = np.column_stack([x1, x2, x3]) X_train, X_val, y_train, y_val = train_test_split(X, y, test_size=0.2, random_state=42) print("X_train.shape:", X_train.shape, "y_train.shape:", y_train.shape)生成的数据里,y 对 x1 是正弦关系、对 x2 是平方关系,这是纯线性的回归模型拟合不好的结构,正好用来考察神经网络。train_test_split 把数据按 8:2 划分,随机种子固定保证可复现。需要注意的是 X 的 dtype 默认是 float64,Keras 内部会转成 float32,数据量特别大时可以在生成后直接.astype(np.float32)省一半内存。
3.2 训练配置:归一化、验证集与 EarlyStopping
回归任务对特征数值尺度极其敏感。特征 x1 在 [-3,3],x2 的平方项直接到 4,如果商品价格类的特征动辄几千,不归一化会让权重更新被大数值特征主导。标准做法是用 StandardScaler 把每个特征按训练集的均值和标准差做标准化,而且必须先只 fit 训练集,再 transform 训练集和验证集。
from sklearn.preprocessing import StandardScaler from tensorflow.keras.callbacks import EarlyStopping, ModelCheckpoint scaler = StandardScaler() scaler.fit(X_train) X_train_s = scaler.transform(X_train) X_val_s = scaler.transform(X_val) model = models.Sequential([ layers.Dense(128, activation="relu", input_shape=(X_train_s.shape[1],)), layers.Dense(64, activation="relu"), layers.Dense(1, activation=None) ]) model.compile(optimizer="adam", loss="mse", metrics=["mae"]) callbacks = [ EarlyStopping(monitor="val_loss", patience=30, restore_best_weights=True), ModelCheckpoint("regression_checkpoint.keras", monitor="val_loss", save_best_only=True) ] history = model.fit( X_train_s, y_train, validation_data=(X_val_s, y_val), epochs=200, batch_size=32, callbacks=callbacks, verbose=1 )这里每个参数都有实际意义:patience=30 表示验证集损失连续 30 轮不改善才停止,给训练足够的余量又防止空跑;restore_best_weights=True 让模型在训练停止后自动回滚到验证集指标最好那一轮的权重;save_best_only 配合 checkpoint 文件,把最好的模型存下来,避免机器宕机或后续调试时丢掉结果。batch_size=32 是回归任务的稳妥默认值,太小噪声大,太大会让梯度更新变慢,一般不需要一上来就调它。
提示:如果 y 本身的量级很大,比如在 0 到 10000 之间,对 y 做标准化也能加速收敛。但必须在验证阶段用 inverse_transform 还原预测值,否则算出来的 MAE 单位是标准化后的,业务看不懂。
3.3 训练曲线怎么读:loss 降了,val_loss 不动,就是过拟合信号
训练完成后第一件事不是急着保存,而是对比训练损失和验证损失的走势。Keras 的 history 对象里记录了每个 epoch 的 loss、val_loss、mae、val_mae,打印出来才能判断模型状态。
import matplotlib.pyplot as plt plt.plot(history.history["loss"], label="train_loss") plt.plot(history.history["val_loss"], label="val_loss") plt.xlabel("epoch") plt.ylabel("loss") plt.legend() plt.show() from sklearn.metrics import r2_score y_pred = model.predict(X_val_s).ravel() print("val R2:", r2_score(y_val, y_pred)) print("val MAE:", abs(y_val - y_pred).mean())训练曲线有四种典型走向,判断逻辑如下表:
| 现象 | 判断 | 对策 |
|---|---|---|
| train loss 和 val loss 同步下降 | 拟合正常 | 继续训练 |
| train loss 持续降,val loss 先降后升 | 过拟合 | 提前停止、加 Dropout、减小网络宽度 |
| 两个 loss 都在高位抖动 | 学习率偏大 | 把 Adam 的 learning_rate 降到 1e-4 |
| val loss 全程高于 train loss 且差距大 | 正则化过度或数据划分不均 | 检查验证集分布,降低 Dropout |
R² 在这个演示数据上应该稳定在 0.95 以上,如果低于这个值,问题通常不在网络结构,而是前面的预处理出了漏。
4. 深度学习回归的 4 个常见坑:过拟合、数据泄露与超参数边界
4.1 数据泄露:StandardScaler 必须在划分训练集之后 fit
这是回归任务里最隐蔽、后果最严重的错误范式:拿到全部样本后先做标准化,再做 train_test_split。表面上代码能跑、指标很漂亮,但验证集的信息已经被statistics 泄漏到了训练过程——验证集本应扮演“从未见过的数据”,结果它的均值和方差已经参与了训练数据的变换,评估结果会系统性偏高。
# 错误做法:先整体标准化,再划分 scaler_wrong = StandardScaler() X_all_s = scaler_wrong.fit_transform(X) # 泄漏已发生 X_train_w, X_val_w, y_train_w, y_val_w = train_test_split(X_all_s, y, test_size=0.2) # 正确做法:先划分,再 fit 训练集,transform 验证集 X_train, X_val, y_train, y_val = train_test_split(X, y, test_size=0.2, random_state=42) scaler = StandardScaler() X_train_s = scaler.fit_transform(X_train) X_val_s = scaler.transform(X_val) # 只用 transform,不用 fit要点在最后一行:验证集只能调用 transform,系数全部来自训练集。如果有缺失值填充、PCA 降维这类预处理步骤,同样要遵循“先划分后 fit”的顺序,所有从数据里统计出来的参数都只能在训练集上估计。
4.2 用 R²、MAE、RMSE 而不是“正确率”:评估体系先摆正
回归任务的评估如果还在纠结“准确率是多少”,说明分类思维的惯性没改过来。回归的误差是多维度的:MAE 能直观说明平均偏差,但它对离群点不敏感;RMSE 与 MSE 同源,放大了大误差的代价;R² 能回答相对解释度,但样本量小的时候容易虚高。按业务场景选指标,生产环境最常用的是 MAE + R² 的组合,前者报价给业务方看,后者用来筛模型。
| 指标 | 计算公式 | 数值含义 | 注意边界 |
|---|---|---|---|
| MAE | mean(|y - ŷ|) | 平均绝对偏差 | 对离群点不敏感 |
| RMSE | sqrt(mean((y - ŷ)²)) | 大误差被放大后的平均偏差 | 与 MAE 差距大说明存在大偏差样本 |
| R² | 1 - SS_res / SS_tot | 模型解释了 y 方差的比例 | 样本少时虚高,需结合样本量看 |
| MAPE | mean(|y - ŷ| / |y|) | 相对误差百分比 | y 有 0 值时直接爆炸 |
提到这组指标是因为有大量基线模型也做回归预测,常见做法是先把岭回归、回归树、随机森林回归模型、xgboost 回归模型跑一遍,如果它们的 R² 已经到 0.9,而训练样本只有几千条,深度学习回归的增益通常有限。神经网络的优势要等到样本量过万、特征交互复杂时才会体现,所以评估体系的第一步其实是和简单基线对齐。
4.3 输出层误用 sigmoid / tanh:预测值被悄悄截断
2.3 节里特别强调过输出层不加激活函数,但实际项目里屡次看到有人拿着图像分类的模板改回归,输出层顺手写了个 sigmoid。后果是:预测值永远落在 (0,1) 区间,只要真实 y 出现 1.5 或 0.2,模型的表达空间就被直接限制,训练时 loss 会下降得很慢,甚至停在某个高值不再动。tanh 则会把范围限制在 [-1,1],更隐蔽,因为部分标准化后的标签恰好落在这个区间,测试集指标看着还行,一上真实数据就崩。检查方法很简单:打印 model.output 的激活函数,或者直接看预测值的 min/max。
NaN 的排查秩序也值得一提。训练几轮后 loss 变成 nan,最常见的原因是输入特征没有归一化导致梯度爆炸,其次才是学习率过大。排查时先检查 StandardScaler 是否生效,再把 learning_rate 调到 1e-4,最后看数据里有没有 inf。不要在没确认这几步之前去换网络结构。
4.4 调参顺序:先锁结构,再调学习率,最后动正则
回归模型的超参数空间很大,但没必要一上来就做全量网格搜索。先固定一个 2 层、宽 128/64 的结构,把学习率跑通,再加入 Dropout 和权重衰减。下面这段循环代码,用少量配置对比快速找到当前数据上的可用区间。
configs = [ {"units": 64, "lr": 1e-3, "dropout": 0.0}, {"units": 128, "lr": 1e-3, "dropout": 0.1}, {"units": 128, "lr": 1e-4, "dropout": 0.1}, {"units": 256, "lr": 1e-4, "dropout": 0.2}, ] for i, cfg in enumerate(configs): model = models.Sequential([ layers.Dense(cfg["units"], activation="relu", input_shape=(X_train_s.shape[1],)), layers.Dropout(cfg["dropout"]), layers.Dense(cfg["units"] // 2, activation="relu"), layers.Dense(1, activation=None), ]) model.compile( optimizer=tf.keras.optimizers.Adam(learning_rate=cfg["lr"]), loss="mse", metrics=["mae"] ) model.fit(X_train_s, y_train, epochs=100, validation_data=(X_val_s, y_val), batch_size=32, callbacks=[EarlyStopping(patience=20)], verbose=0) val_loss, val_mae = model.evaluate(X_val_s, y_val, verbose=0) print(f"config_{i}: val_loss={val_loss:.4f}, val_mae={val_mae:.4f}")这里调参的关键是锁住变量:每次只改变一个维度,否则无法判断是哪个超参数起了作用。Dropout 在回归里的角色和分类不完全一样,它对 y 的偏差非常敏感,加得过大会让预测值整体向均值收缩,所以表格型回归里一般只加到 0.1 ~ 0.2,而权重衰减(L2 正则)在 Keras 里等价于岭回归的思想,更适合作为默认手段。
5. 给回归模型做“新数据体检”:残差图、模型导出与数值一致性验证
5.1 残差图:训练指标再好看,也要看误差长什么样
R² 和 MAE 只给了误差的统计量,丢了误差的分布结构。残差图把预测值放在横轴、残差(y - ŷ)放在纵轴,能暴露出指标掩盖的两类问题。如果残差随预测值增大呈扇形展开,说明方差随输出增大而增大,即异方差,常见解法是对 y 做 log1p 变换后再训练,预测时再指数还原。如果残差呈现出明显的弯曲或周期形态,说明模型漏掉了某个非线性特征组合,这时优先加特征或增大第一层宽度,而不是继续加深网络。
import matplotlib.pyplot as plt y_pred = model.predict(X_val_s).ravel() residuals = y_val - y_pred plt.scatter(y_pred, residuals, alpha=0.5) plt.axhline(0, color="red", linestyle="--") plt.xlabel("predicted value") plt.ylabel("residual") plt.show()这段代码的价值在于把“模型拟合得好不好”从一维视角变成二维视角。两点判断技巧:残差点分布呈水平带状说明模型结构基本正确,有系统性偏移则要检查特征是否漏入了时间信息或批次信息。
5.2 模型导出后的数值对账:joblib 与量化场景的精度验证
模型训练结束后要经历导出、重载、换推理环境三个环节,每一个环节都可能引入数值偏差。常见做法是用 joblib 保存完整的 Keras 模型,也可以转成 ONNX 部署,但导出的模型必须和新数据对一次账,确认输出和一版模型一致。
import joblib joblib.dump({"model": model, "scaler": scaler}, "regression_bundle.joblib") bundle = joblib.load("regression_bundle.joblib") loaded_model = bundle["model"] loaded_scaler = bundle["scaler"] X_new_s = loaded_scaler.transform(X_val_s) pred_original = model.predict(X_val_s).ravel() pred_loaded = loaded_model.predict(X_new_s).ravel() max_abs_diff = np.max(np.abs(pred_original - pred_loaded)) print("max abs diff between original and loaded:", max_abs_diff)阈值一般定在 1e-6 量级,超过这个数说明导出链路里有精度丢失。还有一个在边缘部署时才显现的现象值得提前知道:回归模型不量化时一切正常,int8 量化后精度下降、数值不动的情况比分类模型更频繁。根因在于分类模型输出层后有 softmax 做归一化,量化误差会被概率分布稀释;回归输出是裸的连续数值,权重量化误差直接叠加到预测结果上,所以量化后的验证阈值要更严,校准集的覆盖范围也要更大,不能只在训练分布中心采样。
把残差图和模型对账这两段脚本固化进每次上线前的检查流程,比反复看训练曲线更能发现真实世界里的数据漂移和部署链路问题。
本文还有配套的精品资源,点击获取