李宏毅深度学习课程实战:从零搭建PyTorch神经网络(附作业解析)
李宏毅深度学习课程实战:从零搭建PyTorch神经网络(附作业解析)
深度学习作为人工智能领域的核心技术,正在重塑各行各业的智能化进程。对于已经掌握Python编程基础但尚未深入实践深度学习框架的学习者来说,如何将理论知识转化为实际代码能力是一个关键挑战。本文将以李宏毅教授的深度学习课程为理论基础,结合PyTorch框架,手把手带你完成从数据预处理到模型训练的全流程实战。
1. 深度学习开发环境搭建与PyTorch入门
在开始构建神经网络之前,我们需要先配置好开发环境。PyTorch作为当前最受欢迎的深度学习框架之一,以其动态计算图和Pythonic的编程风格深受研究者喜爱。
1.1 PyTorch安装与基础操作
首先确保你的Python版本在3.7以上,然后通过pip安装PyTorch:
pip install torch torchvision torchaudio
验证安装是否成功:
import torch
print(torch.__version__) # 应输出如1.12.1的版本号
print(torch.cuda.is_available()) # 检查GPU是否可用
PyTorch的核心数据结构是Tensor,它与Numpy数组类似但支持GPU加速:
# 创建Tensor的多种方式
x = torch.empty(5, 3) # 未初始化矩阵
y = torch.rand(5, 3) # 随机初始化矩阵
z = torch.zeros(5, 3, dtype=torch.long) # 全零矩阵,指定类型
# Tensor运算
result = torch.add(y, z) # 加法
result = y + z # 等价写法
1.2 数据加载与预处理
PyTorch提供了torch.utils.data.Dataset和DataLoader来高效处理数据。以宝可梦分类任务为例:
from torchvision import transforms
# 定义图像预处理流程
transform = transforms.Compose([
transforms.Resize(256),
transforms.CenterCrop(224),
transforms.ToTensor(),
transforms.Normalize(mean=[0.485, 0.456, 0.406],
std=[0.229, 0.224, 0.225])
])
# 自定义Dataset类
class PokemonDataset(torch.utils.data.Dataset):
def __init__(self, root_dir, transform=None):
self.root_dir = root_dir
self.transform = transform
# 这里添加读取图片和标签的逻辑
def __len__(self):
return len(self.image_paths)
def __getitem__(self, idx):
image = Image.open(self.image_paths[idx])
label = self.labels[idx]
if self.transform:
image = self.transform(image)
return image, label
2. 神经网络模型构建实战
理解了PyTorch基础后,我们可以开始构建完整的神经网络模型。我们将从简单的全连接网络开始,逐步过渡到更复杂的结构。
2.1 全连接神经网络实现
以宝可梦属性预测为例,实现一个三层的全连接网络:
import torch.nn as nn
import torch.nn.functional as F
class PokemonNet(nn.Module):
def __init__(self, input_size, hidden_size, output_size):
super(PokemonNet, self).__init__()
self.fc1 = nn.Linear(input_size, hidden_size)
self.fc2 = nn.Linear(hidden_size, hidden_size)
self.fc3 = nn.Linear(hidden_size, output_size)
self.dropout = nn.Dropout(0.2) # 防止过拟合
def forward(self, x):
x = F.relu(self.fc1(x))
x = self.dropout(x)
x = F.relu(self.fc2(x))
x = self.dropout(x)
x = self.fc3(x)
return x
2.2 卷积神经网络实现
对于图像分类任务,卷积神经网络(CNN)通常表现更好。以下是基于李宏毅课程中CNN理论的PyTorch实现:
class CNN(nn.Module):
def __init__(self):
super(CNN, self).__init__()
self.conv1 = nn.Conv2d(3, 32, 3, padding=1) # 输入通道3,输出32
self.conv2 = nn.Conv2d(32, 64, 3, padding=1)
self.pool = nn.MaxPool2d(2, 2)
self.fc1 = nn.Linear(64 * 56 * 56, 512)
self.fc2 = nn.Linear(512, 10) # 假设有10类宝可梦
self.dropout = nn.Dropout(0.25)
def forward(self, x):
x = self.pool(F.relu(self.conv1(x)))
x = self.pool(F.relu(self.conv2(x)))
x = x.view(-1, 64 * 56 * 56) # 展平
x = self.dropout(x)
x = F.relu(self.fc1(x))
x = self.dropout(x)
x = self.fc2(x)
return x
3. 模型训练与优化技巧
构建好模型后,训练过程同样至关重要。我们将深入探讨损失函数选择、优化器配置等关键环节。
3.1 损失函数与优化器选择
根据任务类型选择合适的损失函数:
| 任务类型 | 推荐损失函数 | 适用场景 |
|---|---|---|
| 分类任务 | CrossEntropyLoss | 多类别分类 |
| 二分类任务 | BCELoss | 二分类(需配合sigmoid) |
| 回归任务 | MSELoss | 数值预测 |
| 多标签分类 | BCEWithLogitsLoss | 每个样本可能属于多个类别 |
优化器配置示例:
model = PokemonNet(input_size=100, hidden_size=128, output_size=10)
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
# 学习率调度器
scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=5, gamma=0.1)
3.2 训练循环实现
完整的训练循环包含前向传播、损失计算、反向传播和参数更新:
def train(model, train_loader, criterion, optimizer, epochs=10):
model.train()
for epoch in range(epochs):
running_loss = 0.0
for i, (inputs, labels) in enumerate(train_loader):
optimizer.zero_grad()
outputs = model(inputs)
loss = criterion(outputs, labels)
loss.backward()
optimizer.step()
running_loss += loss.item()
if i % 100 == 99:
print(f'Epoch {epoch+1}, Batch {i+1}: loss {running_loss/100:.3f}')
running_loss = 0.0
scheduler.step()
4. 模型评估与调优策略
训练完成后,我们需要评估模型性能并针对性地进行优化。
4.1 评估指标与实现
常用的评估指标包括准确率、精确率、召回率等:
def evaluate(model, test_loader):
model.eval()
correct = 0
total = 0
with torch.no_grad():
for inputs, labels in test_loader:
outputs = model(inputs)
_, predicted = torch.max(outputs.data, 1)
total += labels.size(0)
correct += (predicted == labels).sum().item()
accuracy = 100 * correct / total
print(f'Test Accuracy: {accuracy:.2f}%')
return accuracy
4.2 常见问题与解决方案
在实际训练中常遇到的问题及对策:
-
过拟合:
- 增加Dropout层
- 使用L2正则化(weight decay)
- 数据增强
- 早停(Early Stopping)
-
欠拟合:
- 增加模型复杂度
- 减少正则化
- 延长训练时间
-
梯度消失/爆炸:
- 使用Batch Normalization
- 合适的权重初始化
- 梯度裁剪
# Batch Normalization示例
class BNNet(nn.Module):
def __init__(self):
super(BNNet, self).__init__()
self.fc1 = nn.Linear(100, 256)
self.bn1 = nn.BatchNorm1d(256)
self.fc2 = nn.Linear(256, 10)
def forward(self, x):
x = F.relu(self.bn1(self.fc1(x)))
x = self.fc2(x)
return x
5. 作业解析与实战技巧
结合李宏毅课程作业,分享几个关键实战技巧和常见错误。
5.1 宝可梦分类作业解析
作业中常见的几个技术要点:
-
数据不平衡处理:
# 使用加权交叉熵损失 class_weights = torch.tensor([1.0, 2.0, 1.5]) # 根据类别分布设置 criterion = nn.CrossEntropyLoss(weight=class_weights) -
学习率寻找技巧:
# 学习率范围测试 lr_finder = LRFinder(model, optimizer, criterion) lr_finder.range_test(train_loader, end_lr=10, num_iter=100) lr_finder.plot() # 找到损失下降最快的区间 -
模型集成提升性能:
# 多个模型的预测结果平均 outputs = (model1(inputs) + model2(inputs)) / 2
5.2 视频点击量预测作业解析
回归任务中的关键点:
-
特征工程:
# 添加多项式特征 from sklearn.preprocessing import PolynomialFeatures poly = PolynomialFeatures(degree=2, include_bias=False) X_poly = poly.fit_transform(X) -
自定义损失函数:
def weighted_mse_loss(input, target): weight = torch.abs(target - input) + 1 # 给大误差更大权重 return (weight * (input - target) ** 2).mean() -
超参数搜索:
# 使用Optuna进行超参数优化 import optuna def objective(trial): lr = trial.suggest_float('lr', 1e-5, 1e-2, log=True) hidden_size = trial.suggest_categorical('hidden_size', [64, 128, 256]) model = Net(input_size, hidden_size, output_size) optimizer = torch.optim.Adam(model.parameters(), lr=lr) # 训练和验证代码 return validation_accuracy
6. 进阶技巧与最新进展
掌握了基础后,我们可以探索更高级的技术和最新研究进展。
6.1 迁移学习实践
使用预训练模型可以大幅提升小数据集上的表现:
from torchvision import models
# 加载预训练ResNet
model = models.resnet18(pretrained=True)
# 替换最后一层
num_ftrs = model.fc.in_features
model.fc = nn.Linear(num_ftrs, len(class_names))
# 只训练最后一层
for param in model.parameters():
param.requires_grad = False
for param in model.fc.parameters():
param.requires_grad = True
6.2 自注意力机制实现
自注意力机制是Transformer的核心组件,也可以用于CV任务:
class SelfAttention(nn.Module):
def __init__(self, embed_size, heads):
super(SelfAttention, self).__init__()
self.embed_size = embed_size
self.heads = heads
self.head_dim = embed_size // heads
self.values = nn.Linear(self.head_dim, self.head_dim, bias=False)
self.keys = nn.Linear(self.head_dim, self.head_dim, bias=False)
self.queries = nn.Linear(self.head_dim, self.head_dim, bias=False)
self.fc_out = nn.Linear(heads * self.head_dim, embed_size)
def forward(self, values, keys, query, mask):
N = query.shape[0]
value_len, key_len, query_len = values.shape[1], keys.shape[1], query.shape[1]
# 拆分多头
values = values.reshape(N, value_len, self.heads, self.head_dim)
keys = keys.reshape(N, key_len, self.heads, self.head_dim)
queries = query.reshape(N, query_len, self.heads, self.head_dim)
energy = torch.einsum("nqhd,nkhd->nhqk", [queries, keys])
if mask is not None:
energy = energy.masked_fill(mask == 0, float("-1e20"))
attention = torch.softmax(energy / (self.embed_size ** (1/2)), dim=3)
out = torch.einsum("nhql,nlhd->nqhd", [attention, values]).reshape(
N, query_len, self.heads * self.head_dim
)
out = self.fc_out(out)
return out
6.3 模型解释性技术
理解模型决策过程同样重要,常用的可视化技术:
# Grad-CAM可视化
from torchcam.methods import GradCAM
cam_extractor = GradCAM(model, 'layer4')
with torch.no_grad():
out = model(input_tensor)
# 生成类别激活图
activation_map = cam_extractor(out.squeeze(0).argmax().item(), out)
深度学习实践是一个不断迭代和积累经验的过程。从理论到代码的转化能力是AI工程师的核心竞争力,建议在学习过程中多动手实践,尝试复现论文结果,参与Kaggle比赛等实战项目。遇到问题时,PyTorch官方论坛和GitHub issue区是很好的求助资源。
更多推荐


所有评论(0)