标签: #Python #PyTorch #深度学习 #神经网络
学习周期:3 天 | 核心目标:掌握 PyTorch 张量操作、自动微分、构建神经网络、训练循环及模型保存


6.1 PyTorch 基础

PyTorch 是目前最主流、最易用、最适合科研与工业落地的深度学习框架,由 Facebook AI Research 开发。它提供动态计算图、GPU 加速、自动微分等功能,设计思想贴近 Python,易于调试。

安装与导入

# CPU 版本
pip install torch torchvision

# GPU 版本(需根据 CUDA 版本选择,参考 https://pytorch.org)
pip install torch torchvision --index-url https://download.pytorch.org/whl/cu118
import torch
import torch.nn as nn
import torch.optim as optim
import torch.nn.functional as F
from torch.utils.data import DataLoader, TensorDataset
import numpy as np

6.1.1 Tensor 对象

Tensor 是 PyTorch 的核心数据结构,类似于 NumPy 的 ndarray,但支持 GPU 加速和自动微分。

创建张量

# 从列表创建
t1 = torch.tensor([1, 2, 3])
t2 = torch.tensor([[1, 2], [3, 4]])

# 全零/全一/单位矩阵
zeros = torch.zeros(2, 3)
ones = torch.ones(2, 2)
eye = torch.eye(3)

# 随机初始化
rand = torch.rand(2, 3)      # 均匀分布 [0,1)
randn = torch.randn(2, 3)    # 标准正态分布
randint = torch.randint(0, 10, (2, 3))

# 从 NumPy 转换
np_arr = np.array([1, 2, 3])
t_from_np = torch.from_numpy(np_arr)   # 共享内存
back_to_np = t_from_np.numpy()

张量属性

t = torch.randn(2, 3)
print("形状:", t.shape)          # torch.Size([2, 3])
print("数据类型:", t.dtype)      # torch.float32
print("设备:", t.device)         # cpu
print("是否需要梯度:", t.requires_grad)  # False

GPU 加速(关键)

if torch.cuda.is_available():
    device = torch.device("cuda")
    print("GPU 可用:", torch.cuda.get_device_name(0))
elif torch.backends.mps.is_available():
    device = torch.device("mps")
    print("mps 可用")
else:
    device = torch.device("cpu")

# 将张量移到 GPU
t_cpu = torch.randn(2, 3)
t_gpu = t_cpu.to(device)
# 创建时直接指定设备
t_gpu2 = torch.randn(2, 3, device=device)
# 移回 CPU
t_back = t_gpu.cpu()

常用运算(与 NumPy 类似)

a = torch.tensor([[1, 2], [3, 4]])
b = torch.tensor([[5, 6], [7, 8]])

print(a + b)          # 加法
print(a - b)          # 减法
print(a * b)          # 逐元素乘法
print(a @ b)          # 矩阵乘法
print(a.T)            # 转置
print(a.sum())
print(a.mean())

# 索引和切片
print(a[0, 1])        # 2
print(a[:, 1])        # [2, 4]
print(a[0, :])        # [1, 2]

# 改变形状
c = torch.arange(12)
print(c.view(3, 4))   # reshape
print(c.reshape(2, 6))

6.1.2 自动微分:torch.autograd

PyTorch 自动计算梯度,核心是 requires_grad 和 backward()

基本用法

# 创建需要梯度的张量
x = torch.tensor([2.0], requires_grad=True)
y = x ** 2 + 3 * x + 1

# 反向传播
y.backward()

# 查看梯度 dy/dx = 2x+3 = 7
print(x.grad)   # tensor([7.])

多变量示例

x = torch.tensor([1.0, 2.0], requires_grad=True)
y = x[0] ** 2 + x[1] ** 3
y.backward()
print(x.grad)   # tensor([2., 12.])

停止梯度追踪

# 方法1:detach
x = torch.tensor([1.0], requires_grad=True)
y = x.detach()   # 不再追踪梯度

# 方法2:with torch.no_grad()
with torch.no_grad():
    y = x * 2     # 此操作不会记录梯度

6.1.3 构建神经网络:继承 nn.Module

所有模型必须继承 nn.Module 并实现 forward() 方法。

# 定义一个线性模型类,继承自 PyTorch 的 nn.Module 基类
class LinearModel(nn.Module):
    # 构造函数,在创建类的实例时自动调用
    # input_dim: 输入特征的维度(例如 10)
    # output_dim: 输出特征的维度(例如 1)
    def __init__(self, input_dim, output_dim):
        # 调用父类 nn.Module 的构造函数,完成必要的初始化
        super().__init__()
        # 创建一个全连接层(线性层),输入大小为 input_dim,输出大小为 output_dim
        # 该层会自动初始化权重和偏置
        self.linear = nn.Linear(input_dim, output_dim)
    
    # 定义前向传播函数,当把数据传入模型实例时会自动调用
    # x: 输入张量,形状通常是 (batch_size, input_dim)
    def forward(self, x):
        # 将输入 x 传入线性层,计算输出并返回
        # 输出形状为 (batch_size, output_dim)
        return self.linear(x)

# 实例化线性模型:输入维度为 10,输出维度为 1
model = LinearModel(10, 1)

# 创建一个随机输入张量,形状为 (5, 10),表示 5 个样本,每个样本有 10 个特征
x = torch.randn(5, 10)

# 将输入 x 传入模型,执行前向传播,得到输出
output = model(x)

# 打印输出的形状,预期为 (5, 1),即 5 个样本,每个样本输出一个标量值
print(output.shape)   # torch.Size([5, 1])

多层感知机(MLP)

# 定义一个多层感知机(MLP)类,继承自 PyTorch 的 nn.Module 基类
class MLP(nn.Module):
    # 构造函数,在创建类的实例时自动调用
    # input_dim:  输入特征的维度(例如 784,对应 MNIST 图像展平)
    # hidden_dim: 隐藏层的神经元数量(例如 128)
    # output_dim: 输出特征的维度(例如 10,对应 10 个分类)
    def __init__(self, input_dim, hidden_dim, output_dim):
        # 调用父类 nn.Module 的构造函数,完成必要的初始化
        super().__init__()
        # 第一个全连接层:输入 input_dim 维,输出 hidden_dim 维
        self.fc1 = nn.Linear(input_dim, hidden_dim)
        # 第二个全连接层:输入 hidden_dim 维,输出 hidden_dim 维
        self.fc2 = nn.Linear(hidden_dim, hidden_dim)
        # 第三个全连接层:输入 hidden_dim 维,输出 output_dim 维
        self.fc3 = nn.Linear(hidden_dim, output_dim)
        # ReLU 激活函数,用于引入非线性(in-place 操作通常不写,这里单独定义以便多次复用)
        self.relu = nn.ReLU()
    
    # 定义前向传播函数,当把数据传入模型实例时会自动调用
    # x: 输入张量,形状通常是 (batch_size, input_dim)
    def forward(self, x):
        # 第一层:线性变换后接 ReLU 激活
        # 先调用 fc1(x) 得到线性输出,再通过 relu 激活
        x = self.relu(self.fc1(x))
        # 第二层:线性变换后接 ReLU 激活
        x = self.relu(self.fc2(x))
        # 第三层:仅线性变换,不再使用激活(对于回归任务或分类的 logits 常用)
        x = self.fc3(x)
        # 返回最终输出,形状为 (batch_size, output_dim)
        return x

6.1.4 常用层

用途 示例
nn.Linear(in, out) 全连接层 nn.Linear(256, 128)
nn.Conv2d(in_ch, out_ch, kernel) 二维卷积 nn.Conv2d(3, 64, 3, padding=1)
nn.MaxPool2d(kernel) 最大池化 nn.MaxPool2d(2)
nn.RNN(input_size, hidden_size) 简单循环神经网络 nn.RNN(100, 256)
nn.LSTM(input_size, hidden_size) 长短期记忆网络 nn.LSTM(100, 256, num_layers=2)
nn.Dropout(p) Dropout 正则化 nn.Dropout(0.5)
nn.BatchNorm1d/2d(num_features) 批归一化 nn.BatchNorm2d(64)
nn.Embedding(num_embeddings, emb_dim) 词嵌入 nn.Embedding(10000, 256)

激活函数(通常用 F 或 nn

# 使用 torch.nn.functional(推荐)
x = torch.randn(10)
out = F.relu(x)
out = F.sigmoid(x)
out = F.tanh(x)
out = F.softmax(x, dim=-1)

# 使用 nn 模块(作为层)
layer = nn.ReLU()
out = layer(x)

6.1.5 损失函数

损失函数 适用场景
nn.MSELoss() 回归任务(均方误差)
nn.L1Loss() 回归(平均绝对误差)
nn.CrossEntropyLoss() 多分类(含 Softmax)
nn.BCELoss() 二分类(需先 Sigmoid)
nn.BCEWithLogitsLoss() 二分类(直接输入 logits,更稳定)
nn.NLLLoss() 配合 F.log_softmax 使用
# 回归
criterion = nn.MSELoss()
pred = torch.randn(3, 1)
target = torch.randn(3, 1)
loss = criterion(pred, target)

# 多分类(最常用)
criterion = nn.CrossEntropyLoss()
pred = torch.randn(3, 5)   # logits
target = torch.tensor([0, 2, 1])
loss = criterion(pred, target)

# 二分类(推荐 BCEWithLogitsLoss)
criterion = nn.BCEWithLogitsLoss()
pred = torch.randn(3, 1)   # logits
target = torch.tensor([1., 0., 1.]).view(-1, 1)
loss = criterion(pred, target)

6.1.6 优化器

优化器 特点
optim.SGD 随机梯度下降,可带动量
optim.Adam 自适应学习率,最常用
optim.RMSprop 适合 RNN
optim.AdamW Adam 解耦权重衰减
# 创建优化器
optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.9)
optimizer = optim.Adam(model.parameters(), lr=0.001, weight_decay=1e-5)

# 训练时更新参数
optimizer.zero_grad()   # 清空梯度
loss.backward()         # 反向传播
optimizer.step()        # 更新参数

6.1.7 训练循环(标准模板)

线性回归完整示例

# 0. 导入必要的库(原代码未写出,但实际需要)
import torch
import torch.nn as nn
import torch.optim as optim

# ========== 1. 生成数据 ==========
# 生成从0到10的100个等间距点,并转换为列向量(形状为 [100, 1])
x = torch.linspace(0, 10, 100).reshape(-1, 1)

# 生成目标值 y = 2*x + 1,并加上均值为0、标准差为0.5的高斯噪声
# torch.randn(x.size()) 生成与 x 形状相同的随机噪声
y = 2 * x + 1 + torch.randn(x.size()) * 0.5

# ========== 2. 定义模型 ==========
# 创建一个线性回归模型:输入维度1,输出维度1(即 y = w*x + b)
model = nn.Linear(1, 1)

# 定义损失函数:均方误差损失(MSE),用于衡量预测值与真实值的差异
criterion = nn.MSELoss()

# 定义优化器:随机梯度下降(SGD),用于更新模型参数(权重和偏置)
# 学习率 lr = 0.01
optimizer = optim.SGD(model.parameters(), lr=0.01)

# ========== 3. 训练循环 ==========
epochs = 500  # 训练迭代次数

for epoch in range(epochs):
    # 清除之前的梯度(因为PyTorch默认会累积梯度)
    optimizer.zero_grad()

    # 前向传播:输入 x,得到预测值 pred
    pred = model(x)

    # 计算损失:预测值与真实值 y 之间的 MSE
    loss = criterion(pred, y)

    # 反向传播:自动计算损失相对于所有可训练参数的梯度
    loss.backward()

    # 更新参数:根据梯度执行一步SGD优化
    optimizer.step()

    # 每100个epoch打印一次当前损失值
    if (epoch + 1) % 100 == 0:
        print(f'Epoch {epoch+1}, Loss: {loss.item():.4f}')

# 训练结束后,打印学习到的权重和偏置(保留两位小数)
print(f"权重: {model.weight.item():.2f}, 偏置: {model.bias.item():.2f}")

分类示例(MNIST 手写数字)

from torchvision import datasets, transforms

# 1. 加载数据
transform = transforms.Compose([
    transforms.ToTensor(),
    transforms.Normalize((0.1307,), (0.3081,))
])
train_dataset = datasets.MNIST('.', train=True, download=True, transform=transform)
test_dataset = datasets.MNIST('.', train=False, transform=transform)
train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=64, shuffle=False)

# 2. 定义模型
class SimpleCNN(nn.Module):
    def __init__(self):
        super().__init__()
        self.conv1 = nn.Conv2d(1, 32, 3, 1)
        self.conv2 = nn.Conv2d(32, 64, 3, 1)
        self.fc1 = nn.Linear(64*12*12, 128)
        self.fc2 = nn.Linear(128, 10)
    
    def forward(self, x):
        x = F.relu(self.conv1(x))
        x = F.relu(self.conv2(x))
        x = F.max_pool2d(x, 2)
        x = torch.flatten(x, 1)
        x = F.relu(self.fc1(x))
        x = self.fc2(x)
        return x

model = SimpleCNN()
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model.to(device)

criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)

# 3. 训练
epochs = 5
for epoch in range(epochs):
    model.train()
    total_loss = 0
    for images, labels in train_loader:
        images, labels = images.to(device), labels.to(device)
        optimizer.zero_grad()
        output = model(images)
        loss = criterion(output, labels)
        loss.backward()
        optimizer.step()
        total_loss += loss.item()
    print(f"Epoch {epoch+1}, Loss: {total_loss/len(train_loader):.4f}")

# 4. 测试
model.eval()
correct = 0
with torch.no_grad():
    for images, labels in test_loader:
        images, labels = images.to(device), labels.to(device)
        output = model(images)
        pred = output.argmax(dim=1)
        correct += (pred == labels).sum().item()
print(f"Test accuracy: {100 * correct / len(test_dataset):.2f}%")

6.1.8 模型保存与加载

# 保存整个模型(不推荐,可能跨版本不兼容)
torch.save(model, 'model.pth')

# 保存 state_dict(推荐,仅参数)
torch.save(model.state_dict(), 'model_weights.pth')

# 加载
model = SimpleCNN()
model.load_state_dict(torch.load('model_weights.pth'))
model.eval()   # 重要:切换到评估模式

# 保存检查点(包括优化器状态,用于恢复训练)
checkpoint = {
    'epoch': 10,
    'model_state_dict': model.state_dict(),
    'optimizer_state_dict': optimizer.state_dict(),
    'loss': loss
}
torch.save(checkpoint, 'checkpoint.pth')

# 加载检查点
checkpoint = torch.load('checkpoint.pth')
model.load_state_dict(checkpoint['model_state_dict'])
optimizer.load_state_dict(checkpoint['optimizer_state_dict'])
start_epoch = checkpoint['epoch']

🎯 完整实战模板(鸢尾花分类)

# 导入 PyTorch 核心库
import torch
# 导入神经网络模块,用于定义层和模型结构
import torch.nn as nn
# 导入优化器模块,用于参数更新算法
import torch.optim as optim
# 从 sklearn 导入鸢尾花数据集
from sklearn.datasets import load_iris
# 导入数据集划分工具
from sklearn.model_selection import train_test_split
# 导入标准化工具,用于特征缩放
from sklearn.preprocessing import StandardScaler

# ========== 1. 数据准备 ==========
# 加载鸢尾花数据集(特征+标签)
iris = load_iris()
# X 为特征数据(150个样本,4个特征),y 为标签(0,1,2 三类)
X, y = iris.data, iris.target
# 划分训练集和测试集:测试集占20%,随机种子42保证可复现
X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42)

# 创建标准化器(将特征转换为均值为0、标准差为1的正态分布形式)
scaler = StandardScaler()
# 对训练集进行拟合和转换(计算均值和标准差,然后标准化)
X_train = scaler.fit_transform(X_train)
# 对测试集仅进行转换(使用训练集的均值和标准差,避免数据泄露)
X_test = scaler.transform(X_test)

# 将训练集特征转换为 PyTorch 张量,数据类型为 float32
X_train = torch.tensor(X_train, dtype=torch.float32)
# 将训练集标签转换为长整型(分类问题要求标签为 long 类型)
y_train = torch.tensor(y_train, dtype=torch.long)

# ========== 2. 定义模型 ==========
# 定义一个继承自 nn.Module 的神经网络类
class IrisNet(nn.Module):
    # 构造函数,初始化网络的层结构
    def __init__(self):
        # 调用父类 nn.Module 的构造函数
        super().__init__()
        # 全连接层1:输入4个特征,输出16个神经元
        self.fc1 = nn.Linear(4, 16)
        # 全连接层2:输入16个神经元,输出3个类别(对应鸢尾花的3种)
        self.fc2 = nn.Linear(16, 3)
        # ReLU 激活函数,引入非线性
        self.relu = nn.ReLU()

    # 定义前向传播过程
    def forward(self, x):
        # 输入 x 经过第一层线性变换,然后通过 ReLU 激活
        x = self.relu(self.fc1(x))
        # 输出经过第二层线性变换(得到3个原始分数,不经过 Softmax 因为 CrossEntropyLoss 内部会做)
        x = self.fc2(x)
        return x

# 实例化模型对象
model = IrisNet()

# ========== 3. 损失函数和优化器 ==========
# 交叉熵损失函数,适用于多分类问题(内部包含 Softmax 运算)
criterion = nn.CrossEntropyLoss()
# Adam 优化器,学习率为0.01,更新模型的所有可训练参数
optimizer = optim.Adam(model.parameters(), lr=0.01)

# ========== 4. 训练循环 ==========
# 总共训练 100 轮(epoch)
for epoch in range(100):
    # 清除之前累积的梯度(避免梯度叠加)
    optimizer.zero_grad()
    # 前向传播:将训练集特征输入模型,得到预测输出(类别分数)
    outputs = model(X_train)
    # 计算损失:比较预测输出与真实标签
    loss = criterion(outputs, y_train)
    # 反向传播:自动计算损失对每个参数的梯度
    loss.backward()
    # 更新参数:根据梯度执行一步优化
    optimizer.step()
    # 每 20 轮打印一次当前损失值(保留4位小数)
    if (epoch + 1) % 20 == 0:
        print(f"Epoch {epoch+1}, Loss: {loss.item():.4f}")

# ========== 5. 保存模型 ==========
# 将训练好的模型参数(state_dict)保存到文件 "iris_model.pth" 中
torch.save(model.state_dict(), "iris_model.pth")
# 打印提示信息
print("训练完成,模型已保存")

📚 学习资料(Obsidian 可直接收藏)


🎯 学习建议(3 天计划)

  1. 第 1 天:掌握 Tensor 基本操作、GPU 加速、自动微分(requires_gradbackward)。
  2. 第 2 天:学习 nn.Module 构建网络、常用层、损失函数和优化器。
  3. 第 3 天:完整训练循环(线性回归 → MNIST/鸢尾花分类),理解前向传播、反向传播、参数更新,练习模型保存与加载。

✅ 核心要点总结

  1. Tensor = 支持 GPU 的数组,类似 NumPy。
  2. 自动微分:设置 requires_grad=True,调用 backward() 自动计算梯度。
  3. 构建网络:继承 nn.Module,实现 forward(),使用 nn.Linearnn.Conv2d 等层。
  4. 损失函数:回归用 MSELoss,多分类用 CrossEntropyLoss,二分类用 BCEWithLogitsLoss
  5. 优化器optim.SGDoptim.Adam,训练时 zero_grad() → backward() → step()
  6. 训练循环:前向传播 → 计算 loss → 反向传播 → 更新参数。
  7. 模型保存:推荐保存 state_dict,加载后调用 model.eval()
  8. 一套模板走天下:所有深度学习训练流程高度一致。

练习题(自测)

  1. 创建两个随机 3×3 矩阵,计算它们的矩阵乘法,并将结果移到 GPU(如果有)。
  2. 手动实现一个线性回归(不使用 nn.Linear),用 requires_grad 自动求导优化参数。
  3. 构建一个 3 层全连接网络(输入 784,隐藏层 256→128,输出 10),使用 ReLU 激活,计算对随机输入的输出。
  4. 加载 CIFAR-10 数据集(torchvision.datasets.CIFAR10),训练一个简单的 CNN 分类器,准确率达到 60% 以上。
  5. 在训练过程中保存最佳验证集准确率的模型,并在测试集上评估。

建议在 Jupyter Notebook 或本地 Python 环境中运行示例,尝试修改网络结构、学习率等超参数,观察对训练的影响。

更多推荐