《Python AI入门》第7章 推开神经网络之门——PyTorch基础与张量操作
我们将使用目前学术界和工业界最流行的深度学习框架——PyTorch,亲手设计神经网络的每一层,定义数据如何在神经元之间流动。本章是深度学习的基石,我们将从最基础的张量(Tensor)开始,最终构建一个能识别手写数字的神经网络。
章节导语
“如果说 Scikit-learn 是自动挡的家用轿车,简单易上手;那么 PyTorch 就是手动挡的F1赛车。虽然你需要自己控制离合和换挡,但它能带你去任何你想去的地方,速度没有上限。”
欢迎来到深度学习(Deep Learning)的世界。
在前两个篇章中,我们学习了传统机器学习。你可能会觉得:“这也不难嘛,调包侠而已。” 但从本章开始,我们将进入一个全新的维度。这里的核心不再是现成的算法(如随机森林、SVM),而是“搭积木”。
我们将使用目前学术界和工业界最流行的深度学习框架——PyTorch,亲手设计神经网络的每一层,定义数据如何在神经元之间流动。本章是深度学习的基石,我们将从最基础的张量(Tensor)开始,最终构建一个能识别手写数字的神经网络。
7.1 学习目标
在学完本章后,你将能够:
-
理解核心概念:明白什么是 Tensor(张量)以及它和 NumPy 数组的区别。
-
掌握自动求导:理解 Autograd 机制,知道计算机是如何自己做微积分的。
-
像搭乐高一样建模:熟练使用
nn.Module和nn.Linear构建全连接神经网络(MLP)。 -
编写训练循环:脱离
.fit()的舒适区,掌握 PyTorch 标志性的五步训练法。 -
实战落地:训练一个神经网络,让它学会识别 MNIST 手写数字数据集。
-
工程思维:理解 CPU 与 GPU 的区别,学会把模型“搬”到显卡上加速。
7.2 张量(Tensor):深度学习的“原子”
在 PyTorch 里,一切数据都是 Tensor。 你可以把它简单理解为:可以运行在 GPU 上的 NumPy 数组。
7.2.1 创建与维度
打开 Jupyter Notebook 或 Python 文件,让我们先摸摸底。
import torch
import numpy as np
# 1. 创建 Tensor
x = torch.tensor([1, 2, 3]) # 向量 (1维)
y = torch.ones(2, 3) # 2行3列的全1矩阵 (2维)
z = torch.rand(2, 3, 4) # 随机生成的张量 (3维)
print(f"张量 y:\n{y}")
print(f"张量 y 的形状: {y.shape}") # 输出 torch.Size([2, 3])
7.2.2 张量与 NumPy 的无缝切换
实际工程中,数据通常是用 pandas 或 numpy 读取的,我们需要把它转成 Tensor。
# numpy -> tensor
np_array = np.array([10, 20, 30])
tensor_data = torch.from_numpy(np_array)
# tensor -> numpy (常用于画图或保存数据)
back_to_numpy = tensor_data.numpy()
7.2.3 关键操作:维度变换
这是新手最容易卡壳的地方。神经网络对数据的“形状”极其敏感。
img = torch.rand(1, 28, 28) # 假设这是一张图片:1个通道,28x28像素
# 1. Flatten (拉平)
# 很多神经网络层只吃向量,不吃图片。我们需要把 (28, 28) 拉成 (784,)
img_flat = img.view(1, -1) # -1 意思是“自动计算剩余维度”
print(f"拉平后的形状: {img_flat.shape}") # torch.Size([1, 784])
# 2. Squeeze/Unsqueeze (压缩/扩展维度)
# 有时候需要增加一个“批次(Batch)”维度
img_batch = img.unsqueeze(0)
print(f"增加Batch维度: {img_batch.shape}") # torch.Size([1, 1, 28, 28])
【小白避坑】view vs reshape 你可能会看到有人用
reshape。在 PyTorch 中,view是更底层的操作,要求数据在内存中是连续的;reshape更智能但稍慢。建议新手统一使用view,因为一旦报错,它会强迫你去检查数据的连续性,这对debug有好处。
7.3 Autograd:自动求导的魔法
深度学习的本质是梯度下降。在第4章,我们说“计算梯度”就像蒙眼下山找坡度。在复杂的神经网络中,这个坡度(导数)的计算极其复杂(链式法则)。
好消息是:PyTorch 会自动帮你做微积分。
# 创建一个需要求导的张量
# requires_grad=True 告诉 PyTorch: "盯着这个变量,它的每一次运算都要记录下来"
w = torch.tensor([1.0], requires_grad=True)
x = torch.tensor([2.0])
# 定义一个公式: y = w * x + 2
y = w * x + 2
# 此时 y 不仅仅是一个数,它还带着计算图
print(y) # tensor([4.], grad_fn=<AddBackward0>)
# 反向传播 (Back Propagation)
# 这一句代码,PyTorch 就在后台把链式法则算完了
y.backward()
# 看看 w 的梯度 (dy/dw = x = 2)
print(f"w 的梯度: {w.grad}") # 输出 tensor([2.])
工程意义:你只需要定义“前向传播”(怎么算结果),PyTorch 会自动搞定“反向传播”(怎么更新参数)。
7.4 构建神经网络:搭乐高积木
Scikit-learn 里的模型是黑盒,而 PyTorch 里的模型是类(Class)。我们需要继承 nn.Module,然后自己定义两件事:
-
__init__:我们有哪些积木(层)? -
forward:数据怎么流过这些积木?
import torch.nn as nn
import torch.nn.functional as F
class SimpleNet(nn.Module):
def __init__(self):
super(SimpleNet, self).__init__()
# 定义积木
# Linear(输入特征数, 输出特征数) -> 全连接层
self.fc1 = nn.Linear(784, 128) # 第一层:输入784,输出128
self.fc2 = nn.Linear(128, 10) # 第二层:输入128,输出10 (分类数)
def forward(self, x):
# 定义数据流向
x = self.fc1(x) # 数据经过第一层
x = F.relu(x) # 激活函数 (非线性变换,让网络变聪明)
x = self.fc2(x) # 数据经过第二层
return x
# 实例化模型
model = SimpleNet()
print(model)
【专业提示】什么是 ReLU? 如果没有激活函数,无论多少层神经网络叠加,本质上还是一个线性回归。
ReLU(Rectified Linear Unit) 很简单:正数保持不变,负数归零。 别看它简单,正是这个非线性操作,让神经网络有了模拟世间万物的能力。
7.5 实战案例:手写数字识别 (MNIST)
这是深度学习领域的“Hello World”。我们将训练一个模型,识别 0~9 的手写数字。
7.5.1 第一步:数据加载 (Data Loader)
PyTorch 提供了 torchvision 库,内置了常用数据集。 这里我们要引入Batch(批次)的概念:我们不一次性把几万张图塞给模型(内存会爆),也不一张张塞(太慢),而是一次塞一卡车(比如64张)。
import torchvision
import torchvision.transforms as transforms
from torch.utils.data import DataLoader
# 1. 定义预处理:转为Tensor -> 标准化
transform = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.1307,), (0.3081,)) # MNIST的均值和标准差
])
# 2. 下载并加载数据
# train=True 表示训练集,False 表示测试集
train_dataset = torchvision.datasets.MNIST(root='./data', train=True,
download=True, transform=transform)
test_dataset = torchvision.datasets.MNIST(root='./data', train=False,
download=True, transform=transform)
# 3. 创建数据加载器 (搬运工)
# shuffle=True 表示每次训练打乱顺序,防止模型死记硬背
train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=1000, shuffle=False)
7.5.2 第二步:定义模型与训练配置
我们直接使用上面定义的 SimpleNet。
import torch.optim as optim
# 1. 实例化模型
model = SimpleNet()
# 2. 定义损失函数 (Loss Function)
# CrossEntropyLoss 是分类任务的标准配置,它内部自动包含了 Softmax
criterion = nn.CrossEntropyLoss()
# 3. 定义优化器 (Optimizer)
# SGD (随机梯度下降) 是最基础的优化器,lr 是学习率
optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.9)
7.5.3 第三步:手写训练循环 (The Training Loop)
这是 PyTorch 与 Scikit-learn 最大的不同。我们要自己控制训练的每一步。 请背诵以下五步口诀:清零 -> 前向 -> 算Loss -> 后向 -> 更新。
# 训练 3 轮 (Epoch)
epochs = 3
for epoch in range(epochs):
running_loss = 0.0
# 遍历加载器中的每一个 Batch
for batch_idx, (data, target) in enumerate(train_loader):
# --- 核心数据处理 ---
# MNIST图片是 [64, 1, 28, 28],我们需要拉平成 [64, 784]
data = data.view(-1, 28*28)
# --- 训练五步曲 ---
# 1. 梯度清零 (否则梯度会累加,导致错误)
optimizer.zero_grad()
# 2. 前向传播 (模型猜结果)
output = model(data)
# 3. 计算损失 (猜得有多离谱?)
loss = criterion(output, target)
# 4. 反向传播 (求导)
loss.backward()
# 5. 更新参数 (迈出一步)
optimizer.step()
# --- 打印日志 ---
running_loss += loss.item()
if batch_idx % 300 == 299: # 每 300 个 Batch 打印一次
print(f'Epoch: {epoch+1}, Batch: {batch_idx+1}, Loss: {running_loss/300:.4f}')
running_loss = 0.0
print("训练完成!")
7.5.4 第四步:模型评估
用测试集来看看模型到底学会了没有。
correct = 0
total = 0
# torch.no_grad() 告诉 PyTorch: "现在是考试时间,不需要算梯度,节省内存"
with torch.no_grad():
for data, target in test_loader:
data = data.view(-1, 28*28) # 别忘了拉平
output = model(data)
# output 是 10 个概率值,取最大的那个作为预测结果
# torch.max 返回 (最大值, 索引),我们只需要索引
_, predicted = torch.max(output.data, 1)
total += target.size(0)
correct += (predicted == target).sum().item()
print(f'测试集准确率: {100 * correct / total:.2f}%')
如果你运气不错,准确率应该在 95%~98% 之间。仅仅用了几行代码,我们就完成了一个接近人类水平的数字识别系统!
7.6 工程思维:使用 GPU 加速
深度学习之所以在近几年爆发,离不开 NVIDIA 显卡(GPU)的算力支持。 在 PyTorch 中,把模型搬到显卡上只需要两步:
-
检测设备:
device = torch.device("cuda" if torch.cuda.is_available() else "cpu") print(f"当前使用设备: {device}") -
搬家: 你需要把模型和数据都搬到 GPU 上,它们才能运算。
# 1. 模型搬家 model = model.to(device) # 2. 训练循环中,数据搬家 for data, target in train_loader: data, target = data.to(device), target.to(device) # 关键步骤! # ... 后续代码不变 ...
【小白避坑】设备不匹配错误 這是新手报错 Top 1:
Expected object of device type cuda but got device type cpu. 这意味着你的模型在 GPU 上,但你喂给它的数据还在 CPU 上(或者反过来)。原则:运算的双方必须在同一个设备上。
7.7 章节小结
本章我们正式推开了深度学习的大门。
-
我们认识了 Tensor,它是深度学习数据的载体。
-
我们见识了 Autograd,它帮我们搞定了最头疼的数学求导。
-
我们学会了定义
nn.Module,像搭积木一样构建神经网络。 -
最重要的是,我们掌握了 PyTorch 训练五步曲,这是你未来编写所有深度学习代码的通用模板。
在这个案例中,我们把一张 $28 \times 28$ 的图片粗暴地拉平成了一条长长的向量。这破坏了图片的“空间结构”(比如眼睛不知道眉毛在它上面)。 有没有一种方法,能让机器像人眼一样,通过“扫描”图片的局部来识别物体?
有的。那就是下一章的主角——CNN 卷积神经网络。
7.8 思考与扩展练习
-
动手改模型: 目前的
SimpleNet只有一个隐藏层(128个神经元)。试着增加一层(例如:784 -> 256 -> 64 -> 10),看看准确率是否会提升?注意层与层之间的输入输出维度要对齐。 -
调试练习: 在训练循环中,尝试去掉
optimizer.zero_grad()这一行,运行代码并观察 Loss 的变化。你会发现 Loss 可能会震荡甚至爆炸。思考一下为什么?(提示:梯度累加机制)。 -
保存模型: 训练一个模型很花时间。查阅 PyTorch 文档,学习使用
torch.save(model.state_dict(), 'mnist.pth')保存你的模型权重,并尝试在另一个脚本中加载它。
更多推荐




所有评论(0)