PyTorch机器学习基础:从数据流程到模型训练全解析
发布时间:2026/9/30 8:52:32来源:尧图网络
1. 机器学习的基本流程先看全貌再动手编码很多人一上来就装 PyTorch、跑 MNIST结果代码写得飞起但对“机器学习到底在干嘛”却是一笔糊涂账。我在带新手时最常说的一句话先把流程刻在脑子里再谈框架和代码。这一章的主题虽然是 PyTorch 里的机器学习基础但核心并不是某个 API 怎么调而是帮你把“数据 - 模型 - 损失 - 优化 - 评估”这条主线焊死后面所有实战都不会偏。1.1 机器学习到底解决什么问题机器学习的本质是从数据中自动总结规律然后用规律去预测新样本。拿我几年前做过的房价预测举例给出一堆历史房屋数据面积、楼层、位置、房龄模型学到“面积越大价格越高、市中心溢价严重”这类关系后再给一套新房子信息它就能估出价格。这个过程没有人为写死规则全是数据驱动的。所以你会看到机器学习解决的问题大致分三类回归预测连续数值比如房价、分类预测离散类别比如邮件是否垃圾邮件、聚类把相似样本自动分组比如用户分群。前两类属于监督学习得有带标签的数据聚类属于无监督学习只有特征没有标签。除此之外还有强化学习那个是智能体与环境交互的范式在第三章通常只做概念了解重点还是监督学习。1.2 常见任务类型监督、无监督、强化学习做个表直观点类型是否有标签典型任务常见算法监督学习有分类、回归线性回归、逻辑回归、决策树、SVM、神经网络无监督学习无聚类、降维K-Means、DBSCAN、PCA强化学习有延迟奖励决策、控制Q-Learning、DQN、PPO这里有个新手容易懵的地方逻辑回归明明做分类为什么名字叫回归因为它的历史渊源来自统计学本质是对线性回归的输出做了个 Sigmoid 变换把结果压到 0~1 之间再当概率用。理解这点后你再看 PyTorch 里的nn.Linear、nn.Sigmoid组合就不会觉得乱。1.3 机器学习项目的一般流程一个标准项目基本走这六步明确问题回归还是分类业务指标是什么采集与清洗数据缺值、异常值、重复值处理。特征工程与预处理标准化、编码、特征选择。训练模型选算法、调超参。评估模型在没见过的数据上看效果。部署与监控上线后持续跟踪表现。很多教程只讲第 4 步导致新手以为机器学习就是“写个模型然后 fit”。实际上真实项目中 70% 的时间都花在数据清洗和特征工程上建模反而是最机械的一步。记住这句话数据决定上限模型只是逼近上限。这一章后面给的所有代码示例其实都嵌在这个大流程里。2. 数据与特征机器学习的地基2.1 数据集的划分训练/验证/测试先讲一个所有入门者都容易踩的坑把用于训练的数据拿去评估模型得到的结果一定虚高。就好比你考试前把答案背完了模拟考当然满分但一到高考就露馅。机器学习里把数据集的划分想成“练习册”、“模拟卷”、“高考卷”就很好懂了。通常会把数据分成三份训练集约 60%~80%核心学习材料模型看着它调整参数。验证集约 10%~20%用来调超参数比如学习率、网络层数相当于模拟卷可以多做几套。测试集约 10%~20%最终评估用整个训练期间绝不碰它。在 PyTorch 里手动划分可以这样写import torch from torch.utils.data import random_split, DataLoader, TensorDataset # 假设 X 是特征张量y 是标签张量 dataset TensorDataset(X, y) train_len int(0.8 * len(dataset)) val_len (len(dataset) - train_len) // 2 test_len len(dataset) - train_len - val_len train_ds, val_ds, test_ds random_split( dataset, [train_len, val_len, test_len] ) train_loader DataLoader(train_ds, batch_size32, shuffleTrue) val_loader DataLoader(val_ds, batch_size32) test_loader DataLoader(test_ds, batch_size32)注意shuffleTrue只用在训练集验证和测试不要打乱顺序保证批次之间没有关联。2.2 数据处理标准化、归一化、缺失值处理机器学习里有个词叫“数据处理”听起来基础但直接决定训练能不能收敛。先看特征量纲问题假设房价预测里“面积”是几十平方米级别“房龄”是几年级别如果放任不管模型会自动把数值大的特征当成重点小数值特征容易被忽略。更麻烦的是很多优化器对梯度的量级很敏感量纲差异会导致损失震荡。解决办法就两个归一化Min-Max Scaling把所有值缩放到 [0,1] 区间。标准化Standardization减去均值除以标准差让数据变成均值为 0、方差为 1 的分布。实际用哪个我的经验是如果特征分布没有强异常值标准化优先因为 Min-Max 受最大最小值影响太大万一将来某条新数据的值超出原范围归一化结果会超出 [0,1]处理起来反而麻烦。但在图像处理里像素值本来就是 0~255直接除以 255 归一化是最常见的。PyTorch 里最灵活的做法是自定义一个预处理层import torch import torch.nn as nn class StandardScaler(nn.Module): def __init__(self, mean, std): super().__init__() self.register_buffer(mean, mean) self.register_buffer(std, std) def forward(self, x): return (x - self.mean) / self.stdregister_buffer是个关键点它会让张量跟着模型一起去.to(device)并且不会参与梯度计算。如果你直接用普通属性存均值方差换 GPU 时会踩坑。均值方差一定要在训练集上计算然后再应用到验证/测试集不能全体数据一起算否则会引入“未来信息”评估结果失真。缺失值处理更要小心如果缺失比例低于 5%可以直接删掉对应样本如果较高建议用均值、中位数或模型预测填充。千万不要用 0 随便填尤其当特征本质上不可能是 0 时比如年龄那会让模型学到错误分布。2.3 特征工程入门从原始数据到可用特征特征工程听起来高大上其实就是把原始数据转换成模型更容易学习的形态。举个现实例子拿到一个“注册时间”字段模型本来不知道“2020-05-01”比“2019-05-01”意味着什么但你把它拆成年、月、日、星期几这就是特征加工。常见的操作有三类数值特征分箱比如把年龄分成年龄段、取对数处理长尾分布。类别特征独热编码One-Hot或 Embedding。独热编码在 PyTorch 里可以借助torch.nn.functional.one_hot。组合特征比如面积和楼层单独给模型不如再来一个“面积/总价”比例。有一个反直觉的真相特征工程做得好有时比换一个复杂模型更有效。我刚入行时迷信神经网络万能后来做工业数据发现简单模型加靠谱特征往往比复杂模型加脏特征效果更好。这一章不要求你把特征工程学透但要建立这个意识——后续在 PyTorch 中写Dataset类时预处理逻辑最好都放在__getitem__里配合在线增强很顺手。3. PyTorch 承载机器学习基础张量与自动求导3.1 张量机器学习的通用语言PyTorch 里最基础的对象就是张量Tensor你可以把它理解成支持 GPU 计算的“多维数组”。标量是 0 维张量向量是 1 维矩阵是 2 维图片可以看作 3 维通道、高、宽视频是 4 维Transformer 里的 batch 更是高达 5 维。新手常问NumPy 的 ndarray 也有多维数组为什么要用张量答案就两句话第一张量能放到 GPU 上并行计算第二张量能自动记录运算轨迹用于反向传播。拿面点做类比NumPy 是案板上的面团你得自己揉手动求导PyTorch 张量是面团加了一台揉面机你只需把配方给它它自己会揉完送进烤箱。创建张量最简单的方式import torch a torch.tensor([1, 2, 3]) # 普通张量 b torch.zeros(2, 3) # 全 0 c torch.randn(2, 3) # 标准正态分布 d torch.arange(0, 10, 2) # [0, 2, 4, 6, 8]注意torch.tensor和torch.Tensor的细微差别小写tensor()会复制数据并推断 dtype大写Tensor()实际是torch.FloatTensor的别名在新版里直接有一个torch.tensor(..., dtypetorch.float32)的推荐写法。另一点是默认整数张量是int64做除法时容易得 0我见过太多人栽在这个坑里两个整数张量相除得到的结果不是浮点要养成指定 dtype 的习惯x torch.tensor([1, 2, 3], dtypetorch.float32)3.2 自动求导反向传播的引擎机器学习里绕不开求导因为梯度下降要算损失对每个参数的导数。手推一个 10 层的网络公式能写满一页纸还容易错。PyTorch 的autograd机制就是来解决这件事的你只管定义前向计算反向传播自动完成。工作机制可以这样理解创建张量时把requires_gradTrue打开相当于在简历上标了“我需要被求导”。在这个张量基础上做运算PyTorch 会构建一个计算图每个节点记录“从哪来、到哪里去”。调用backward()时梯度沿计算图反向流动每个叶子节点需要求导的模型参数的.grad被自动填上。一个完整的最小示例import torch x torch.tensor(2.0, requires_gradTrue) y x ** 2 3 * x 1 # 前向计算 y.backward() # 反向传播 print(x.grad) # 结果是 2x 3 7求导结果当然没问题。但这里有个需要提醒的backward()之后计算图默认会被释放如果还想用同一批数据再做一次反向传播需要把retain_graphTrue传进去。模型训练时一般不这么干只会在某些特殊结构比如对抗网络里同一个网络多次更新才需要。再提醒一个细节.grad是累加的。如果你在一个循环里连续对同一参数调backward()梯度会把之前的累在一起。PyTorch 每步更新完参数后都要用optimizer.zero_grad()把梯度清零否则梯度会越滚越大损失直接飞掉。这个顺序问题后面专门说。3.3 用 PyTorch 实现一个极简线性回归理论知识讲再多不如跑一次最能理解。线性回归是最简单的监督学习模型正好用来把前面的张量、自动求导串起来。假设我们要模拟一条直线y 2x 1加一点噪声然后让模型学出来。import torch import torch.nn as nn import torch.optim as optim # 1. 生成数据 torch.manual_seed(42) x torch.randn(100, 1) * 5 y 2 * x 1 torch.randn(100, 1) * 1.5 # 2. 定义模型 model nn.Linear(1, 1) # 一个输入特征一个输出 # 3. 损失和优化器 criterion nn.MSELoss() optimizer optim.SGD(model.parameters(), lr0.01) # 4. 训练 epochs 500 for epoch in range(epochs): optimizer.zero_grad() pred model(x) loss criterion(pred, y) loss.backward() optimizer.step() if (epoch 1) % 100 0: print(fEpoch {epoch1}, Loss{loss.item():.4f}) print(模型权重:, model.weight.item(), 偏置:, model.bias.item())你会发现训练 500 轮后weight非常接近 2bias接近 1。这个过程里最核心的顺序是固定的zero_grad - forward - loss - backward - step。这个顺序一个都不能乱。很多人容易把zero_grad放在backward后面或者干脆忘了最后模型不稳定还以为是模型结构问题。4. 损失函数与优化器模型怎么学会4.1 损失函数怎么选MSE、交叉熵等损失函数是“模型做得有多差”的量化指标。不同任务用不同损失最常用的几个任务类型损失函数说明回归Mean Squared Error预测值与真实值差的平方均值对大误差惩罚重回归Mean Absolute Error绝对误差均值对异常值更鲁棒二分类Binary Cross Entropy用于输出为概率的二分类比如垃圾邮件判断多分类Cross Entropy通常配合nn.LogSoftmax使用PyTorch 里有个让人困惑的点nn.CrossEntropyLoss()本身已经包含了LogSoftmax所以模型最后一层不用再加Softmax。如果你自己加了等于是做了两次 softmax概率分布会被压得更尖训练反而出问题。我确实见过有人因为这个原因调了好几天的损失曲线最后发现是结构写重复了。回归里还有个选择细节MSE 对异常值特别敏感因为误差被平方放大。如果你的数据里有几个离谱的噪声点它们会把模型往自己那边拽。这种情况建议试试 MAE或者用 Huber Loss平滑平均绝对误差PyTorch 里对应nn.SmoothL1Loss它结合了 MAE 和 MSE 的优点离得远时表现像 MAE离得近时像 MSE。4.2 优化器梯度下降与它的变体有了损失函数就得想办法让损失变小。最朴素的思路是沿着梯度的反方向更新参数这就是梯度下降。但原始梯度下降一次要用全量数据计算梯度太慢随机梯度下降SGD虽然一次只用一个样本又快又省但太噪声。实际中我们用 mini-batch 梯度下降每次拿一小撮样本batch算平均梯度。PyTorch 里优化器都在torch.optim下最常用的有三种optimizer optim.SGD(model.parameters(), lr0.01, momentum0.9) optimizer optim.Adam(model.parameters(), lr1e-3) optimizer optim.AdamW(model.parameters(), lr1e-3, weight_decay0.01)SGD momentummomentum 相当于惯性沿之前方向继续冲能抑制震荡适合比较规则的凸问题。Adam结合了动量思想和自适应学习率每个参数有自己的步长上手快是目前默认选择。AdamW修正了 Adam 的权重衰减实现方式在 Transformer 和现代大模型里更常用。我的建议是最先跑通模型用 Adam追求最终精度时可以换 AdamW 或 SGDmomentum 加学习率调度。Adam 不等于万能它可能收敛到尖锐极小值泛化性能有时不如 SGD但那是进阶话题入门不用太纠结。4.3 训练循环的标准写法无论模型多复杂训练循环的基本骨架都不会变。下面是一个封装过的训练函数注释里写清楚了每一步的职责def train_one_epoch(model, dataloader, criterion, optimizer, device): model.train() # 启用训练模式影响 Dropout 和 BatchNorm total_loss 0.0 for inputs, labels in dataloader: inputs inputs.to(device) labels labels.to(device) optimizer.zero_grad() outputs model(inputs) loss criterion(outputs, labels) loss.backward() optimizer.step() total_loss loss.item() * inputs.size(0) return total_loss / len(dataloader.dataset)这里的model.train()很多人不写但非常关键。如果你的模型里有nn.Dropout或nn.BatchNorm训练模式和数据评估模式的行为完全不同。验证和测试时必须调用model.eval()并且用torch.no_grad()包裹推理过程否则会白占显存还会因为 Dropout 导致预测结果随机波动。我自己在实际项目中还会额外加一个步骤——给每个 epoch 输出训练损失和验证损失方便实时观察for epoch in range(epochs): train_loss train_one_epoch(...) val_loss evaluate(model, val_loader, criterion, device) print(fEpoch {epoch1:03d} | Train Loss {train_loss:.4f} | Val Loss {val_loss:.4f})损失曲线是训练过程的“心电图”比起只看准确率我强烈建议新手先学会看损失曲线的形状。正常情况是训练和验证损失都缓慢下降如果训练损失下降但验证损失反弹说明已经开始过拟合了得赶紧停。5. 模型评估与过拟合问题5.1 评估指标准确率、精确率、召回率、F1分类任务不能只看准确率尤其是类别不平衡的时候。举一个经典例子某举报系统里97% 的评论是正常的3% 是垃圾评论。如果模型全部预测“正常”准确率也有 97%看起来挺牛但实际上垃圾评论一条都没找出来系统彻底失效。所以分类评估指标要看这几位准确率Accuracy预测正确的样本占比。精确率Precision预测为垃圾的里面真是垃圾的比例。它关心“杀错不杀错”。召回率Recall真实垃圾里面被找出来的比例。它关心“有没有漏网”。F1 Score精确率和召回率的调和平均平衡两者。这三者关系用一个渔网来类比精确率是指“捞上来的都是鱼”的比例召回率是指“整个池塘里的鱼被捞上来多少”。撒网撒得太密精确率低但召回率高会捞出垃圾撒得太稀则会漏鱼。F1 就是帮你找一个平衡点。PyTorch 本身不直接提供这些指标可以配合 scikit-learn 来计算from sklearn.metrics import accuracy_score, precision_score, recall_score, f1_score # 先用模型预测得到概率再取 argmax 得到类别 preds torch.argmax(model(test_x), dim1).cpu().numpy() print(Accuracy:, accuracy_score(test_y, preds)) print(Precision:, precision_score(test_y, preds, averagebinary)) print(Recall:, recall_score(test_y, preds, averagebinary)) print(F1:, f1_score(test_y, preds, averagebinary))averagebinary用于二分类多分类时要换成macro或weighted。Macro 是各类别指标的简单平均更关注小类weighted 按样本量加权更符合实际场景。这块在机器学习期末复习里经常被考建议做几道计算题上手。5.2 过拟合与欠拟合现象与应对模型学得太好把训练集的噪声一并背了下来就是过拟合连训练集都学不透损失居高不下就是欠拟合。用记忆来类比过拟合是背书只记原题答案题目换个数字就懵欠拟合是课本压根没看懂原题也不会做。怎么判断是哪种画训练/验证损失曲线是最直观的办法训练损失高验证损失高欠拟合。换更强的模型、加特征、减小正则化。训练损失低验证损失高过拟合。增加数据、加正则化、提前停止、做数据增强。在 PyTorch 里有个非常简单有效的处理过拟合的手段Dropout。在nn.Linear之间插入nn.Dropout(p0.5)训练时随机让一半神经元失活迫使模型学到更鲁棒的特征。不过要注意model.eval()时 Dropout 自动关闭不用你手动操作前提是你正确调用了eval()。5.3 正则化与早停小技巧正则化的核心思路是限制模型的复杂度让它不要“太自由”。最常用的有 L1 和 L2 正则。L2 正则也叫权重衰减Weight Decay它给损失函数加上权重平方和的一项逼着参数往小里走。PyTorch 里直接在优化器中加weight_decay参数就行optimizer optim.AdamW(model.parameters(), lr1e-3, weight_decay1e-2)weight_decay通常设 1e-4 到 1e-2具体可以网格搜索。不要设得太大否则模型会欠拟合。早停更简单训练过程中实时监控验证集损失如果连续 N 个 epoch 没有下降可以设 N10就停止训练并恢复历史上验证损失最低时的模型参数。PyTorch 保存最佳模型的写法best_val_loss float(inf) best_state None for epoch in range(epochs): train_loss train_one_epoch(...) val_loss evaluate(...) if val_loss best_val_loss: best_val_loss val_loss best_state {k: v.clone() for k, v in model.state_dict().items()} # 训练结束后加载最优参数 model.load_state_dict(best_state)这里clone()很重要因为state_dict()里的张量是引用如果不克隆后面训练继续更新之前“最佳模型”的备份也被改了早停就名存实亡。这个细节是我被坑过一次才记住的。6. 常见坑与调试经验6.1 环境安装的坑PyTorch 版本与 CUDA网上关于 PyTorch 安装的教程一搜一大把但真正动手时最坑的是版本对应关系。核心原则是PyTorch 版本、CUDA 工具包版本、显卡驱动版本三者要匹配。不是你装个最新版就万事大吉也不是有 N 卡就代表能用 GPU。简单说下判断方法# 查看 CUDA 是否可用 python -c import torch; print(torch.cuda.is_available()) # 查看 PyTorch 对应的 CUDA 版本 python -c print(torch.version.cuda)如果is_available()是 False先别急着重装系统检查一下驱动是不是太旧。在 Windows 或 Linux 上建议直接用 conda 创建独立环境避免把系统 Python 搞乱conda create -n pytorch python3.10 conda activate pytorch pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118--index-url选cu118、cu121这些具体 CUDA 版本标识比默认pip install torch更容易匹配。另外如果你的显卡比较新比如最新的 RTX 40 系或 AMD RX 7000 系直接无脑装带 CUDA 的版本反而可能得到 CPU 版本。装完记得跑一个实际操作a torch.randn(10000, 10000, devicecuda) print(a.sum().item())能跑出结果说明 GPU 环境没问题。我见过不少人在这一步卡住问题往往不是代码而是没有让 PyTorch 使用 GPU模型和输入数据都必须执行.to(cuda)否则 CPU/GPU 数据不在同一设备会直接报错。6.2 数据处理相关坑第一个坑DataLoader返回的数据类型和你想的不一样。比如TensorDataset里的标签是long类型但nn.CrossEntropyLoss要求标签必须是torch.long如果你用 float 就会报错。这个初期很容易遇到解决办法就是统一在预处理时指定 dtype。第二个坑batch 大小和数据维度的关系。很多新人对batch_size32怎么影响张量形状没概念。假设一个样本是(3, 224, 224)的图片通道 3、高 224、宽 224一个 batch 就是(32, 3, 224, 224)通常记为(N, C, H, W)。一旦你的输入形状写错了常见报错是矩阵乘法维度不匹配。调试时第一时间打印inputs.shape和labels.shape别猜。第三个坑做标准化时统计量用错了。前面讲过必须在训练集上算均值方差但新手常常把整个数据一起算。这会带来一个隐蔽的问题验证集的一部分信息均值/方差已经泄漏进训练过程中导致评估结果偏高。我自己做竞赛时吃过这个亏后来给团队定了个规矩凡是数据处理相关的统计量都必须只在训练部分计算。6.3 调试技巧从损失曲线到梯度检查调试深度学习模型有一套固定顺序按这个顺序做能省下大量时间检查损失是否下降如果损失不降先看数据预处理、模型结构、优化器设置。打印梯度范数多个参数梯度的平方和开根号也就是grad_norm。如果梯度为 0说明网络可能死了比如 ReLU 把大量神经元置零如果梯度爆炸大概率是学习率太大或损失函数不稳定。试试先拟合一个小数据比如只用 5 个样本训练看模型能不能记住。如果损失能降到接近 0说明模型有足够表达能力问题出在数据或训练设置上如果连小样本都拟合不动那肯定是 bug。梯度范数打印可以这样加total_norm 0.0 for p in model.parameters(): if p.grad is not None: param_norm p.grad.data.norm(2) total_norm param_norm.item() ** 2 total_norm total_norm ** 0.5 print(fGrad norm: {total_norm:.4f})如果发现梯度爆炸直接调小学习率或者对梯度做裁剪torch.nn.utils.clip_grad_norm_(model.parameters(), max_norm1.0)这个操作在训练 RNN 时尤其常用。另外还有个很实用的调试技巧在训练循环里设一个断点拿一个固定 batch 反复跑观察 loss 是否稳定下降。不同的 batch 抽到不同数据loss 有波动正常但趋势应该是下降的。如果固定 batch 都不下降那就是代码本身有问题。这个方法能帮你把“数据问题”和“代码问题”快速分开。最后再分享一个经验机器学习基础阶段不要急着上大模型、调超参而要先能稳住一个最简单的模型。哪怕是线性模型只要能跑通完整流程理解了每一行代码在干什么后面换成卷积、Transformer 都是水到渠成的事。我在带新人时第一条要求就是“把线性回归的每一步讲到别人能听懂”达到这个标准基础才算真正打牢。
网站建设高端定制企业官网