一、我的踩坑经历
2023年4月我决定转行做CV算法。网上找了一堆课程推荐,先看了吴恩达《深度学习专项课程》(Coursera上1-3门),视频讲得很清楚,但每次交作业的TensorFlow 1.x代码跑起来报错一堆。后来换PyTorch,发现课程里的网络结构图跟实际API对不上。折腾两周连个LeNet都没跑通。
又去啃Stanford CS231n的笔记和视频,资料详细但代码全是numpy手写,跟现在工程用的PyTorch差太多。最后发现李沐的《动手学深度学习》(PyTorch版)直接把理论和代码绑在一起,随书代码跑在1998年的MNIST上,我改到CIFAR-10又踩了batch size和learning rate的坑。
这篇博客就是我从3个月的混乱中提炼出的最佳路线:先用吴恩达打理论底子,再跟李沐动手敲代码,最后用CS231n补CNN/RNN细节。下面给出三门课的详细对比和实战代码。
二、方案对比:三门课程怎么选
2.1 吴恩达深度学习专项课程
- 版本:Coursera上5门课,2022年更新,含TensorFlow和PyTorch选项(但早期课程仍以TF为主)
- 优点:数学推导清晰,梯度消失、Batch Normalization讲得透
- 缺点:代码作业老旧,依赖tf1.x,PyTorch版本需要自己额外补充
- 学习时长:40小时视频 + 30小时作业
- 适合人群:零基础,需要先理解“为什么”
2.2 李沐《动手学深度学习》(PyTorch版)
- 版本:第二版,2023年更新,代码基于PyTorch 2.0+
- 优点:代码完整,每章配套notebook,可以直接在Colab跑,从线性回归到Transformer全覆盖
- 缺点:理论深度不够,比如注意力机制的数学推导跳过很多
- 学习时长:30小时视频 + 30小时动手
- 适合人群:有一定数学基础,想快速上手PyTorch
2.3 Stanford CS231n / CS224n
- 版本:2023版,视频在YouTube,作业含numpy手写 + PyTorch可选
- 优点:CNN、RNN细节深入,反向传播手算,适合面试刷题
- 缺点:没有完整的项目代码,需自己拼装
- 学习时长:50小时视频 + 60小时作业
- 适合人群:想过大厂算法面试,想做科研
2.4 组合路线推荐
我的建议:吴恩达(只看1-2门,搞懂前向/反向传播、激活函数、梯度问题)→ 李沐(从头到尾敲一遍,重点在卷积神经网络、循环神经网络、注意力机制)→ CS231n(只补作业中的手写backprop和图像分类项目)。这样理论+代码+面试深度都有了。
三、实战代码:用PyTorch实现CIFAR-10分类
以下代码基于PyTorch 2.1.0 + torchvision 0.16.0,CUDA 12.1,显卡T4(云GPU)。按照李沐课程中的代码风格,搭建一个简单CNN,训练5个epoch,测试准确率。
3.1 环境准备
# 创建conda环境
conda create -n dl_course python=3.10
conda activate dl_course
# 安装PyTorch 2.1.0(CUDA 12.1)
pip install torch==2.1.0 torchvision==0.16.0 torchaudio==2.1.0 --index-url https://download.pytorch.org/whl/cu121
# 安装辅助库
pip install matplotlib tqdm
3.2 数据加载与预处理
import torch
import torchvision
import torchvision.transforms as transforms
import torch.nn as nn
import torch.optim as optim
from tqdm import tqdm
import time
# 数据增强(参考李沐课程)
transform_train = transforms.Compose([
transforms.RandomHorizontalFlip(),
transforms.RandomCrop(32, padding=4),
transforms.ToTensor(),
transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010))
])
transform_test = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.4914, 0.4822, 0.4465), (0.2023, 0.1994, 0.2010))
])
trainset = torchvision.datasets.CIFAR10(root='./data', train=True, download=True, transform=transform_train)
testset = torchvision.datasets.CIFAR10(root='./data', train=False, download=True, transform=transform_test)
trainloader = torch.utils.data.DataLoader(trainset, batch_size=128, shuffle=True, num_workers=4)
testloader = torch.utils.data.DataLoader(testset, batch_size=128, shuffle=False, num_workers=4)
3.3 定义模型(简单CNN)
class SimpleCNN(nn.Module):
def __init__(self):
super().__init__()
self.conv1 = nn.Conv2d(3, 32, 3, padding=1)
self.bn1 = nn.BatchNorm2d(32)
self.conv2 = nn.Conv2d(32, 64, 3, padding=1)
self.bn2 = nn.BatchNorm2d(64)
self.conv3 = nn.Conv2d(64, 128, 3, padding=1)
self.bn3 = nn.BatchNorm2d(128)
self.pool = nn.MaxPool2d(2, 2)
self.fc1 = nn.Linear(128 * 4 * 4, 256)
self.fc2 = nn.Linear(256, 10)
self.dropout = nn.Dropout(0.3)
self.relu = nn.ReLU()
def forward(self, x):
x = self.pool(self.relu(self.bn1(self.conv1(x))))
x = self.pool(self.relu(self.bn2(self.conv2(x))))
x = self.pool(self.relu(self.bn3(self.conv3(x))))
x = x.view(x.size(0), -1)
x = self.relu(self.fc1(x))
x = self.dropout(x)
x = self.fc2(x)
return x
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model = SimpleCNN().to(device)
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(model.parameters(), lr=0.001)
3.4 训练与测试
def train(epochs):
model.train()
for epoch in range(epochs):
running_loss = 0.0
loop = tqdm(trainloader, desc=f'Epoch {epoch+1}/{epochs}')
for inputs, labels in loop:
inputs, labels = inputs.to(device), labels.to(device)
optimizer.zero_grad()
outputs = model(inputs)
loss = criterion(outputs, labels)
loss.backward()
optimizer.step()
running_loss += loss.item()
loop.set_postfix(loss=loss.item())
print(f'Epoch {epoch+1} loss: {running_loss/len(trainloader):.4f}')
def evaluate():
model.eval()
correct = 0
total = 0
with torch.no_grad():
for inputs, labels in testloader:
inputs, labels = inputs.to(device), labels.to(device)
outputs = model(inputs)
_, predicted = torch.max(outputs, 1)
total += labels.size(0)
correct += (predicted == labels).sum().item()
print(f'Test Accuracy: {100 * correct / total:.2f}%')
if __name__ == '__main__':
start = time.time()
train(5)
print(f'Training time: {time.time()-start:.2f}s')
evaluate()
3.5 效果数据
在T4 GPU(16GB显存)上运行,batch size=128,结果如下:
| Epoch | 训练Loss | 耗时(s/epoch) |
|---|---|---|
| 1 | 1.3245 | 18.2 |
| 2 | 0.9876 | 18.3 |
| 3 | 0.8123 | 18.5 |
| 4 | 0.7142 | 18.4 |
| 5 | 0.6501 | 18.3 |
5个epoch总耗时约91.7秒。测试集准确率:78.23%。如果换用更大的ResNet50(torchvision自带),epoch时间约45秒,准确率可到92.1%。但本文重点不是刷榜,是为了展示完整流程。
四、避坑指南
以下是这3个月我实际遇到的坑:
- PyTorch版本与CUDA不匹配:安装torch 2.1.0后,一定要用
torch.cuda.is_available()检查。我一开始装了CPU版本,训练一个epoch要5分钟。建议用pip list | grep torch确认版本。 - 数据加载num_workers设置:在Windows上num_workers>0会报多进程错误,建议设为0或2。Linux上可以设4-8。我租的云服务器是Linux,设4没问题。
- 李沐课程代码的坑:他的代码里经常用
d2l包,版本和PyTorch 2.0有冲突。直接安装pip install d2l可能会覆盖torch版本。建议从GitHub下载notebook,单独跑里面的模型定义。 - 吴恩达作业的TF版本:即使选了PyTorch选项,作业框架还是TF的变量scope习惯。我直接放弃吴恩达的代码部分,只看了视频推导。
- CS231n作业的numpy手写:如果你只做后两题(PyTorch版本),记得先读
cs231n/classifiers/fc_net.py里的实现,batch normalization的numpy版很容易写错。 - GPU显存不够:CIFAR-10图片小还好,但如果用ImageNet分类网络,batch size设64以上可能爆显存。建议先用
torch.cuda.memory_summary()检查。
五、总结
没有一门课程能从头包到尾。我的路线是:吴恩达视频1~2门(2周)→ 李沐动手学深度学习PyTorch版(4周,每天2小时)→ CS231n作业选做(2周)。看完这篇博客,你直接复制代码跑一轮,再对着李沐的notebook改网络结构,就能建立完整的从理论到代码的映射。别再像我一样浪费时间填坑。