- 教程
- 人工智能
- 机器学习
- 深度学习
【免费下载链接】AI-For-Beginners
12 Weeks, 24 Lessons, AI for All!
本文是 AI-For-Beginners 课程第 05 课(lessons/3-NeuralNetworks/05-Frameworks/README.md,含保加利亚语译本translations/bg/lessons/3-NeuralNetworks/05-Frameworks/README.md)的技术解读。你将系统理解「为什么需要深度学习框架」「TensorFlow / PyTorch 的高低层 API 如何分工协同」「如何用 PyTorch 完成从张量运算、自动求导到端到端训练」等完整链路,并掌握机器学习中最重要的概念之一——过拟合(overfitting)的成因、检测与防范方法。
为什么需要神经网络框架:两项核心能力
要高效训练神经网络,本质上只需要做两件事:
- 操作张量(Tensor):例如做乘法、加法,计算 sigmoid、softmax 等函数;
- 计算梯度(Gradient):对全部表达式求梯度,从而执行梯度下降优化。
numpy可以出色地完成第一部分(张量运算),但它缺少第二部分所需的「自动求梯度」机制。在课程上一节自行搭建的微型框架 OwnFramework.ipynb 中,我们不得不在执行反向传播的backward方法里手动编写所有导数函数。而一个成熟的框架,理应能对「你能定义的任意表达式」自动计算梯度。
另一个关键需求是在 GPU 或 TPU 等专用计算单元上执行计算。深层神经网络的训练量极其庞大,能否把这些计算并行化(术语「并行化」即指将计算分布到多个设备上)到 GPU 上,直接决定了训练是否可行。
两大主流框架与 API 分层
当前最流行的两个神经网络框架是TensorFlow与PyTorch。二者都提供在 CPU 与 GPU 上操作张量的低层 API;在其之上又分别叠加了高层 API——Keras 与 PyTorch Lightning:
| API 层级 | TensorFlow | PyTorch |
|---|---|---|
| 低层 API | TensorFlow | PyTorch |
| 高层 API | Keras | PyTorch Lightning |
低层 API允许你构建所谓的计算图(Computational Graph):这张图定义了在给定输入参数下如何计算出输出(通常是损失函数),并且可以被提交到 GPU 上执行(若有)。框架还提供对计算图求导的函数,计算出可用于优化模型参数的梯度。
高层 API将神经网络视为层的序列(sequence of layers),大大简化了绝大多数网络的构建过程。训练模型通常只需要准备好数据,然后调用一个fit函数即可完成。
- 高层 API 能让你快速搭建典型神经网络,无需操心大量细节;
- 低层 API 则对训练过程提供远超前者、灵活得多的控制力,因此常用于研究场景中探索全新的网络架构。
更重要的是,两种 API 可以混合使用:你可以用低层 API 开发自定义的网络层结构,再把它放进由高层 API 构建并训练的大网络中;反过来,也可以用高层 API 把网络定义为层序列,再用自写的低层训练循环来做优化。两种 API 共享同一套底层概念,被设计为可以很好地协同工作——这正是本课程反复强调的低层/高层「一脉相承」的思想。
课程学习路径:如何选择与起步
本课程的大部分内容同时提供 PyTorch 与 TensorFlow 两个版本。你可以选定一个偏好的框架,只跟随对应的 notebook 学习;如果拿不定主意,可以在网上查阅「PyTorch vs TensorFlow」的讨论,或把两个框架都过一遍加深理解。
课程在「能用高层 API 的地方就用高层 API」以保持简洁,但同时也坚持「从底层理解神经网络如何工作」的原则,因此学习顺序是:先低层 API 与张量,后高层 API。若你想快速上手、暂时不深究底层细节,也可以直接跳到高层 API 的 notebook。
配套练习 Notebook 如下(均位于lessons/3-NeuralNetworks/05-Frameworks/目录):
| API 层级 | TensorFlow | PyTorch |
|---|---|---|
| 低层 API | IntroKerasTF.ipynb | IntroPyTorch.ipynb |
| 高层 API | IntroKeras.ipynb | PyTorch Lightning(见 IntroPyTorch.ipynb 末节) |
安装 PyTorch 通常只需一行命令(详见 IntroPyTorch.ipynb):
pip install torch torchvision或使用 conda:
conda install pytorch -c pytorchPyTorch Lightning 的安装命令同样在 notebook 中给出:
pip install pytorch-lightning # 或 conda install -c conda-forge pytorch-lightningPyTorch 源码级实战:从张量到自动求导
以下内容源自 IntroPyTorch.ipynb,是课程中低层 API 学习的核心。
张量基础与就地操作
张量(Tensor)是多维数组,非常适合表示各类数据:
400x400:一张黑白图片;400x400x3:一张彩色图片;16x400x400x3:16 张彩色图片组成的小批量(minibatch);25x400x400x3:1 秒 25 帧的视频;8x25x400x400x3:8 段 1 秒视频组成的小批量。
从列表或 numpy 数组创建张量、或生成随机张量都非常直接:
a = torch.tensor([[1,2],[3,4]]) print(a) a = torch.randn(size=(10,3)) print(a)张量上的算术运算与 numpy 一样是逐元素执行的,且会自动扩张到所需维度;用.numpy()可取出 numpy 数组:
print(a - a[0]) # 逐元素减法 print(torch.exp(a)[0].numpy()) # 指数函数后转回 numpy注意 PyTorch 存在就地(in-place)与非就地(out-of-place)操作之分:+/add这类操作返回新张量,而大部分操作都有以_结尾的就地版本,直接修改原张量:
u = torch.tensor(5) print("Result when adding out-of-place:", u.add(torch.tensor(3))) # 返回新张量 8 u.add_(torch.tensor(3)) # 就地加 3 print("Result after adding in-place:", u) # u 变成 8自动求导:requires_grad、backward 与 grad_fn
反向传播需要计算梯度。把任意张量的requires_grad属性设为True,该张量参与的所有运算都会被追踪以用于梯度计算。随后调用backward(),梯度便可通过grad属性获得:
a = torch.randn(size=(2, 2), requires_grad=True) b = torch.randn(size=(2, 2)) c = torch.mean(torch.sqrt(torch.square(a) + torch.square(b))) # 用 a 做点数学运算 c.backward() # 调用 backward() 计算所有梯度 print(a.grad) # c 对 a 的梯度两个容易被忽视的关键细节:
- 梯度是「累加」的:若在
backward(retain_graph=True)中保留计算图,新梯度会叠加进grad字段。需要从头重算时,必须显式调用a.grad.zero_()把梯度清零。这正是标准训练循环中「每个 batch 先optimizer.zero_grad()再loss.backward()」这一铁律的底层原因。 grad_fn与计算图:每个requires_grad=True的张量都挂着一个特殊函数grad_fn,它按照链式求导规则计算表达式导数。例如c由mean计算而来,其grad_fn就指向MeanBackward0。整个自动微分体系就是围绕这张计算图展开的。
当你要计算张量对张量的梯度时,PyTorch 并不会直接给出完整的 Jacobian 矩阵,而是计算Jacobian 与某个向量 v 的乘积v^T·J。做法是把v作为参数传给backward,v的尺寸需与原张量一致:
c = torch.sqrt(torch.square(a) + torch.square(b)) c.backward(torch.eye(2)) # eye(2) 表示 2x2 单位矩阵 print(a.grad)示例 0:用自动微分做梯度下降优化
用自动微分寻找二元函数 f(x₁,x₂)=(x₁-3)²+(x₂+2)² 的最小值。梯度下降迭代公式为 x^(n+1) = x^(n) − η·∇f,其中 η 是学习率(代码中记作lr):
x = torch.zeros(2, requires_grad=True) f = lambda x : (x - torch.tensor([3,-2])).pow(2).sum() lr = 0.1 for i in range(15): y = f(x) y.backward() gr = x.grad x.data.add_(-lr*gr) # 沿负梯度方向更新(注意通过 .data 修改,避开 Autograd 追踪) x.grad.zero_() print("Step {}: x[0]={}, x[1]={}".format(i, x[0], x[1]))从输出可见坐标从 (0,0) 逐步逼近理论最小值点 (3,-2)。此例完整演示了「前向传播 → 自动求导 → 参数更新 → 梯度清零」的微缩训练循环。
示例 1:线性回归与示例 2:分类
线性回归用直线 f_{W,b}(x)=Wx+b 拟合数据,误差(损失函数)取均方误差 L(W,b)=(1/N)·Σ(f_{W,b}(xᵢ)-yᵢ)²。notebook 首先生成带噪声的合成数据(真实参数 W=2, b≈0.9),随后用 mini-batch 梯度下降训练:
w = torch.tensor([100.0], requires_grad=True, dtype=torch.float32) b = torch.zeros(size=(output_dim,), requires_grad=True) def f(x): return torch.matmul(x, w) + b def compute_loss(labels, predictions): return torch.mean(torch.square(labels - predictions)) def train_on_batch(x, y): predictions = f(x) loss = compute_loss(y, predictions) loss.backward() w.data.sub_(learning_rate * w.grad) b.data.sub_(learning_rate * b.grad) w.grad.zero_() b.grad.zero_() return loss训练时把数据打乱、切成大小为 4 的 minibatch,迭代 10 个 epoch 后,学习到的参数收敛到 W≈1.86、b≈1.07,与生成数据时的真实值一致。
二分类问题(如根据肿瘤尺寸与年龄判别恶性/良性)的核心模型与回归类似,但要改用逻辑损失:先用 sigmoid 把网络输出 z 映射到 [0,1] 得到概率 p=σ(z),再计算 Lᵢ=−(yᵢlog pᵢ + (1−yᵢ)log(1−pᵢ))。PyTorch 中这两步可用一个调用完成:
loss = torch.nn.functional.binary_cross_entropy_with_logits(input=z, target=y)(binary_crossentropy_with_logits等价于「先sigmoid,再binary_crossentropy」,且自动对 minibatch 内所有元素求平均。)
数据管理上,PyTorch 提供Dataset(数据来源,分为 Iterable 与 Map-style 两类)与Dataloader(负责从 dataset 加载并切分 minibatch)两个概念:
dataset = torch.utils.data.TensorDataset(torch.tensor(train_x), torch.tensor(train_labels, dtype=torch.float32)) dataloader = torch.utils.data.DataLoader(dataset, batch_size=16)训练 15 个 epoch 后,在验证集上用pred.view(-1)>0.5与真实标签逐元素比较再取均值,即可算出准确率(示例输出约 0.733)。
GPU 计算:一行 device 切换
利用 GPU 只需在代码开头定义可用计算设备,再把张量统一搬过去:
device = 'cuda' if torch.cuda.is_available() else 'cpu' print('Doing computations on ' + device) w = torch.tensor([100.0], requires_grad=True, dtype=torch.float32, device=device) b = torch.zeros(size=(output_dim,), requires_grad=True, device=device)数据送入网络前调用.to(device)即可:train_on_batch(features[i:i+batch_size].view(-1,1).to(device), labels[i:i+batch_size].to(device))。这段代码无需改动即可在 CPU 与 GPU 间自由切换。
高层 API:nn.Module、Optimizer 与 PyTorch Lightning
把单层感知机封装为类(注意用W.data.zero_()而非W.zero_(),因为无法直接修改被 Autograd 机制追踪的张量):
class Network(): def __init__(self): self.W = torch.randn(size=(2,1), requires_grad=True) self.b = torch.zeros(size=(1,), requires_grad=True) def forward(self, x): return torch.matmul(x, self.W) + self.b def zero_grad(self): self.W.data.zero_() self.b.data.zero_() def update(self, lr=0.1): self.W.data.sub_(lr * self.W.grad) self.b.data.sub_(lr * self.b.grad)PyTorch 用torch.nn.Module表示神经网络,有两种定义方式:
- Sequential 层序列(适合标准网络):
net = torch.nn.Sequential(torch.nn.Linear(2,5), torch.nn.Sigmoid(), torch.nn.Linear(5,1)) print(net)- 继承
torch.nn.Module的类(更灵活,可表达任意复杂架构,Module 会自动收集成员层参数):
class MyNet(torch.nn.Module): def __init__(self, hidden_size=10, func=torch.nn.Sigmoid()): super().__init__() self.fc1 = torch.nn.Linear(2, hidden_size) self.func = func self.fc2 = torch.nn.Linear(hidden_size, 1) def forward(self, x): x = self.fc1(x) x = self.func(x) x = self.fc2(x) return x net = MyNet(func=torch.nn.ReLU())parameters()返回所有待优化参数(对应权重矩阵 W 与偏置 b)。内置优化器实现了梯度下降之外的多种优化算法,例如随机梯度下降与 Adam:
optim = torch.optim.SGD(net.parameters(), lr=0.05)于是标准训练循环简化为「前向 → 算损失 →optim.zero_grad()→loss.backward()→optim.step()」,并可封装成通用train函数。注意nn.Module实现了__call__(),因此可直接写net(x)而无需net.forward(x)。
notebook 末尾把模型包装为PyTorch Lightning模块(继承pl.LightningModule),只需四步:在__init__定义架构与forward;把优化器挪进configure_optimizers();分别用training_step与validation_step定义训练与验证过程(可选实现test_step/predict_step);删除所有.cuda()/.to(device)调用,交给pl.Trainer自动处理设备迁移:
trainer = pl.Trainer(max_epochs=30, log_every_n_steps=1, accelerator='gpu', devices=1) trainer.fit(model=net, train_dataloaders=dataloader, val_dataloaders=valid_dataloader)过拟合(Overfitting):必须理解的重要概念
过拟合是机器学习中极其重要的概念。看一个用曲线逼近 5 个点(图中用x标记)的例子:
| 线性模型,2 个参数 | 非线性模型,7 个参数 |
|---|---|
| 训练误差 = 5.3 | 训练误差 = 0 |
| 验证误差 = 5.1 | 验证误差 = 20 |
- 左侧是一条很好的直线逼近:参数数量恰当,模型正确把握了点的分布规律;
- 右侧模型过于强大:只有 5 个点却配 7 个参数,模型可以调整到穿过全部点,使训练误差为 0,但这反而妨碍了模型理解数据背后的真实模式,导致验证误差飙升至 20。
因此,在模型复杂度(参数数量)与训练样本数量之间取得正确平衡至关重要。
过拟合的成因
- 训练数据不足;
- 模型过于强大;
- 输入数据中噪声过多。
如何检测过拟合
由上图可见,过拟合可通过「训练误差极低 + 验证误差很高」来识别。通常训练初期训练误差与验证误差都会同步下降,但到达某个节点后,验证误差可能停止下降并开始回升——这就是过拟合的信号,提示我们应该在此处停止训练(至少保存一份模型快照)。
如何预防过拟合
如果发现过拟合发生,可以采取以下任一措施:
- 增加训练数据量;
- 降低模型复杂度;
- 使用某种正则化(regularization)技术,例如课程后续会讲到的 Dropout。完整正则化技巧可参考 TrainingTricks.md。
过拟合与偏差-方差权衡(Bias-Variance Tradeoff)
过拟合本质上是统计学中更一般问题——偏差-方差权衡——的一个特例。模型误差的来源可分为两类:
- 偏差误差(Bias Errors):由算法无法正确捕获训练数据之间的关系所致,通常源于模型不够强大(欠拟合 / underfitting);
- 方差误差(Variance Errors):由模型拟合了输入数据中的噪声而非有意义的规律所致(过拟合)。
训练过程中,偏差误差不断下降(模型在学会逼近数据),而方差误差不断上升。因此关键在于及时停止训练——要么手动(检测到过拟合时),要么自动(通过引入正则化),以防止过拟合的发生。
实验作业:Iris 与 MNIST 分类
本课的课后实验(作业说明、实验 Notebook)要求你用 PyTorch 或 TensorFlow,分别使用单层与多层全连接网络解决两个分类问题:
- 鸢尾花(Iris)分类:经典的表格数据问题,根据 4 个数值特征将鸢尾花分为 3 类,传统机器学习即可处理;
- MNIST 手写数字分类:课程中已见过的经典图像分类问题。
实验鼓励尝试不同的网络架构,尽可能取得最高准确率。Notebook 末尾的 Task 也提示了进阶思路:在训练过程中绘制损失与准确率曲线;用crossentropy_with_logits作为损失函数把模型迁移到 MNIST 多分类任务。
复习与自测
围绕本课建议针对以下主题自行调研:TensorFlow、PyTorch、Overfitting,并尝试回答:
- TensorFlow 与 PyTorch 有什么区别?
- 过拟合与欠拟合有什么区别?
小结
本课围绕lessons/3-NeuralNetworks/05-Frameworks/README.md及其配套 Notebook(IntroPyTorch.ipynb、IntroKeras.ipynb、IntroKerasTF.ipynb)展开,你掌握了三大要点:其一,两大主流 AI 框架 TensorFlow 与 PyTorch 各自的高低层 API 的区别与协同方式;其二,以 PyTorch 为例的完整工程链路——张量运算、自动求导、GPU 设备迁移、nn.Module/Sequential网络定义、优化器与 Lightning 训练器;其三,机器学习中的关键概念——过拟合及其与偏差-方差权衡的关系、检测方法与预防手段。这些能力将直接支撑你在后续课程(CNN、NLP、强化学习等)中自如地选择与驾驭框架。
- 教程
- 人工智能
- 机器学习
- 深度学习
【免费下载链接】AI-For-Beginners
12 Weeks, 24 Lessons, AI for All!
相关推荐
AI-For-Beginners 神经网络框架实战:TensorFlow/Keras 与 PyTorch 双栈入门及过拟合防治
AI For Beginners 神经网络框架实战:TensorFlow/Keras 与 PyTorch 双栈入门及过拟合防治 本指南基于 AI For Beg
教程人工智能机器学习深度学习generative-ai-for-beginners 神经网络框架实战:TensorFlow 与 PyTorch 双层 API 解析及过拟合防控
generative ai for beginners 神经网络框架实战:TensorFlow 与 PyTorch 双层 API 解析及过拟合防控 本文以《ge
教程人工智能大模型AI-For-Beginners 神经网络框架指南:TensorFlow 与 PyTorch 双栈实战与过拟合原理
AI For Beginners 神经网络框架指南:TensorFlow 与 PyTorch 双栈实战与过拟合原理 本篇技术指南基于开源课程 AI For Be
教程人工智能机器学习深度学习
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考