【零基础入门】Python机器视觉第四阶段:深度学习基础与PyTorch实战

在前三个阶段,我们已经掌握了Python基础、OpenCV图像处理以及传统缺陷检测算法。从本阶段开始,我们将正式进入深度学习的世界。深度学习是当前机器视觉领域最强大的工具,它能够自动从数据中学习特征,解决传统方法难以处理的复杂问题。

本文将按照以下目录,带你系统学习PyTorch的核心概念,并通过实战案例掌握神经网络的搭建与训练。


一、本阶段学习目标

  • 理解深度学习基本概念:张量、自动求导、神经网络结构
  • 掌握PyTorch基础操作:张量创建、索引、运算、与NumPy互转
  • 学会使用torch.nn模块搭建全连接网络和卷积神经网络
  • 掌握数据加载器DataLoader的使用
  • 能够训练一个简单的线性回归模型
  • 能够在MNIST数据集上训练全连接网络,达到90%以上准确率
  • 理解卷积神经网络原理,并在CIFAR-10数据集上搭建LeNet进行图像分类

二、深度学习核心概念速览(通俗版)

概念 通俗解释 对应PyTorch模块
张量 多维数组,类似于NumPy的ndarray,但可以在GPU上加速 torch.Tensor
自动求导 自动计算梯度,反向传播的核心 torch.autograd
神经网络层 对数据进行的线性变换(如全连接、卷积) torch.nn.Linear, torch.nn.Conv2d
激活函数 引入非线性,让网络能学习复杂模式 torch.nn.ReLU, torch.nn.Sigmoid
损失函数 衡量模型预测与真实值的差距 torch.nn.MSELoss, torch.nn.CrossEntropyLoss
优化器 根据梯度更新参数,使损失最小化 torch.optim.SGD, torch.optim.Adam
数据集 封装数据和标签的类 torch.utils.data.Dataset
数据加载器 批量加载数据,支持多线程 torch.utils.data.DataLoader

三、PyTorch基础

3.1 安装PyTorch

根据你的硬件选择安装命令:

# CPU版本(如果没有NVIDIA显卡)
pip install torch torchvision

# GPU版本(以CUDA 11.8为例)
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118

验证安装:

import torch
print(torch.__version__)               # 版本号
print(torch.cuda.is_available())       # 是否有可用的GPU

3.2 张量(Tensor)操作

张量是PyTorch中的基本数据结构,与NumPy数组非常相似,但支持GPU加速和自动求导。

3.2.1 创建张量
import torch
import numpy as np

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

# 创建全零张量(3行4列)
t2 = torch.zeros(3, 4)
print(t2)

# 创建全一张量(2x3)
t3 = torch.ones(2, 3)
print(t3)

# 创建随机张量(均匀分布[0,1))
t4 = torch.rand(3, 3)
print(t4)

# 创建随机张量(标准正态分布)
t5 = torch.randn(3, 3)
print(t5)

# 创建等差张量
t6 = torch.arange(0, 10, 2)       # [0,2,4,6,8]
t7 = torch.linspace(0, 1, 5)      # [0.0000, 0.2500, 0.5000, 0.7500, 1.0000]

# 创建单位矩阵
t8 = torch.eye(3)
print(t8)
3.2.2 张量属性
t = torch.randn(3, 224, 224)   # 模拟一张3通道224x224的图像
print(f"形状: {t.shape}")       # torch.Size([3, 224, 224])
print(f"维度: {t.ndim}")         # 3
print(f"数据类型: {t.dtype}")     # torch.float32
print(f"所在设备: {t.device}")    # cpu
print(f"元素个数: {t.numel()}")   # 3*224*224 = 150528
3.2.3 索引与切片(和NumPy完全一致)
t = torch.tensor([[1,2,3], [4,5,6], [7,8,9]])
print(t[0, 1])       # 2(第1行第2列)
print(t[:, 1])       # [2,5,8](所有行的第2列)
print(t[0:2, 1:3])   # [[2,3], [5,6]](行0-1,列1-2)
3.2.4 张量运算
a = torch.tensor([1,2,3])
b = torch.tensor([4,5,6])

print(a + b)        # [5,7,9]
print(a * b)        # [4,10,18]
print(a ** 2)       # [1,4,9]
print(a + 10)       # [11,12,13]  广播机制

# 矩阵乘法(二维)
A = torch.tensor([[1,2], [3,4]])
B = torch.tensor([[5,6], [7,8]])
C = torch.mm(A, B)  # 矩阵乘法
print(C)
3.2.5 与NumPy相互转换
# NumPy -> Tensor
np_arr = np.array([1,2,3])
tensor_from_np = torch.from_numpy(np_arr)

# Tensor -> NumPy
tensor = torch.tensor([4,5,6])
np_arr_back = tensor.numpy()

3.3 自动求导(Autograd)

深度学习的核心是反向传播,PyTorch的autograd包可以自动计算梯度。

import torch

# 创建一个需要计算梯度的张量
x = torch.tensor(2.0, requires_grad=True)
y = x ** 2 + 3 * x + 1   # y = x² + 3x + 1

# 反向传播,计算梯度
y.backward()

# 查看梯度:dy/dx = 2x + 3,当x=2时,梯度=7
print(x.grad)   # tensor(7.)

# 更复杂的例子:矩阵
x = torch.tensor([[1.,2.], [3.,4.]], requires_grad=True)
y = (x ** 2).sum()   # 所有元素的平方和
y.backward()
print(x.grad)   # 2*x 的每个元素

3.4 构建第一个神经网络:线性回归

我们将用PyTorch实现一个简单的线性回归模型,拟合 y = 2x + 1

import torch
import torch.nn as nn
import torch.optim as optim
import matplotlib.pyplot as plt

# ---------- 1. 生成训练数据 ----------
# 真实关系:y = 2x + 1 + 噪声
x_train = torch.rand(100, 1) * 10   # 100个0-10之间的随机数
y_train = 2 * x_train + 1 + torch.randn(100, 1) * 2

# ---------- 2. 定义模型 ----------
class LinearRegression(nn.Module):
    def __init__(self):
        super(LinearRegression, self).__init__()
        self.linear = nn.Linear(1, 1)   # 输入1维,输出1维
    
    def forward(self, x):
        return self.linear(x)

model = LinearRegression()
print(model)

# ---------- 3. 定义损失函数和优化器 ----------
criterion = nn.MSELoss()                 # 均方误差损失
optimizer = optim.SGD(model.parameters(), lr=0.01)   # 随机梯度下降

# ---------- 4. 训练 ----------
losses = []
for epoch in range(500):
    # 前向传播
    y_pred = model(x_train)
    loss = criterion(y_pred, y_train)
    
    # 反向传播
    optimizer.zero_grad()   # 清空之前的梯度
    loss.backward()         # 计算梯度
    optimizer.step()        # 更新参数
    
    losses.append(loss.item())
    if (epoch+1) % 100 == 0:
        print(f'Epoch {epoch+1}, Loss: {loss.item():.4f}')

# ---------- 5. 可视化 ----------
plt.figure(figsize=(12,4))
plt.subplot(1,2,1)
plt.plot(losses)
plt.title('训练损失曲线')
plt.xlabel('Epoch')
plt.ylabel('Loss')

plt.subplot(1,2,2)
with torch.no_grad():
    y_pred = model(x_train)
plt.scatter(x_train.numpy(), y_train.numpy(), label='真实数据', alpha=0.6)
plt.scatter(x_train.numpy(), y_pred.numpy(), label='预测数据', alpha=0.6)
plt.legend()
plt.title('拟合结果')
plt.show()

# 测试
x_test = torch.tensor([[5.0]])
print(f'预测值: {model(x_test).item():.2f} (真实值: {2*5+1})')

3.5 数据加载器(DataLoader)

在实际项目中,数据通常不会一次性全部加载,而是**分批(batch)**加载,这样可以节省内存并加速训练。

from torch.utils.data import Dataset, DataLoader
import torchvision.transforms as transforms
from torchvision.datasets import MNIST

# ---------- 使用内置数据集 ----------
# 定义数据预处理
transform = transforms.Compose([
    transforms.ToTensor(),                     # 转为张量,像素值缩放到[0,1]
    transforms.Normalize((0.1307,), (0.3081,)) # 标准化(MNIST数据集的均值和标准差)
])

# 下载训练集
train_dataset = MNIST(root='./data', train=True, download=True, transform=transform)

# 创建DataLoader
train_loader = DataLoader(
    train_dataset,
    batch_size=64,      # 每批64张图片
    shuffle=True,        # 打乱数据
    num_workers=2        # 使用2个进程加载数据
)

# 遍历DataLoader
for batch_idx, (data, target) in enumerate(train_loader):
    print(f'Batch {batch_idx}: 数据形状 {data.shape}, 标签形状 {target.shape}')
    # data.shape: [64, 1, 28, 28] (batch_size, 通道数, 高, 宽)
    # target.shape: [64]
    if batch_idx == 0:
        break

# ---------- 自定义数据集 ----------
class MyImageDataset(Dataset):
    """自定义图像数据集"""
    def __init__(self, image_paths, labels, transform=None):
        self.image_paths = image_paths
        self.labels = labels
        self.transform = transform
    
    def __len__(self):
        return len(self.image_paths)
    
    def __getitem__(self, idx):
        # 加载第idx个样本
        img_path = self.image_paths[idx]
        label = self.labels[idx]
        
        # 用OpenCV读取图片
        import cv2
        img = cv2.imread(img_path)
        img = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
        
        # 应用预处理
        if self.transform:
            img = self.transform(img)
        
        return img, label

四、MNIST手写数字分类(全连接网络)

这是深度学习的“Hello World”项目,我们将用全连接网络对MNIST手写数字进行分类。

4.1 加载数据

import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import DataLoader
from torchvision import datasets, transforms

transform = transforms.Compose([
    transforms.ToTensor(),
    transforms.Normalize((0.1307,), (0.3081,))
])

train_dataset = datasets.MNIST('./data', train=True, download=True, transform=transform)
test_dataset = datasets.MNIST('./data', train=False, transform=transform)

train_loader = DataLoader(train_dataset, batch_size=64, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=1000, shuffle=False)

4.2 定义网络

class SimpleMLP(nn.Module):
    def __init__(self):
        super(SimpleMLP, self).__init__()
        self.flatten = nn.Flatten()               # 将28x28展平为784
        self.fc1 = nn.Linear(28*28, 128)          # 第一隐藏层
        self.fc2 = nn.Linear(128, 64)             # 第二隐藏层
        self.fc3 = nn.Linear(64, 10)              # 输出层(10个类别)
        self.relu = nn.ReLU()
    
    def forward(self, x):
        x = self.flatten(x)        # [batch, 1, 28, 28] -> [batch, 784]
        x = self.relu(self.fc1(x))
        x = self.relu(self.fc2(x))
        x = self.fc3(x)             # 输出10个值,对应10个类别的分数
        return x

model = SimpleMLP()
print(model)

4.3 训练配置

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)

4.4 训练函数

def train(epoch):
    model.train()
    total_loss = 0
    for batch_idx, (data, target) in enumerate(train_loader):
        data, target = data.to(device), target.to(device)
        
        optimizer.zero_grad()
        output = model(data)
        loss = criterion(output, target)
        loss.backward()
        optimizer.step()
        
        total_loss += loss.item()
        if batch_idx % 100 == 0:
            print(f'Train Epoch {epoch}: [{batch_idx*len(data)}/{len(train_loader.dataset)}] Loss: {loss.item():.6f}')
    
    return total_loss / len(train_loader)

4.5 测试函数

def test():
    model.eval()
    correct = 0
    total = 0
    with torch.no_grad():
        for data, target in test_loader:
            data, target = data.to(device), target.to(device)
            output = model(data)
            pred = output.argmax(dim=1)   # 取分数最高的类别
            correct += (pred == target).sum().item()
            total += target.size(0)
    
    accuracy = 100. * correct / total
    print(f'Test set: Accuracy: {correct}/{total} ({accuracy:.2f}%)')
    return accuracy

4.6 开始训练

print("开始训练...")
for epoch in range(1, 6):   # 训练5个epoch
    loss = train(epoch)
    acc = test()
    print(f'Epoch {epoch}: 平均损失 {loss:.4f}, 准确率 {acc:.2f}%\n')

预期结果:5个epoch后,测试准确率应达到95%以上。


五、卷积神经网络(CNN)原理与实战

全连接网络对图像分类效果有限,因为它忽略了图像的空间结构。卷积神经网络通过卷积层和池化层,能够有效提取局部特征。

5.1 核心概念

层类型 作用 参数 PyTorch实现
卷积层 用卷积核扫描图像,提取特征 输入通道、输出通道、核大小 nn.Conv2d(in_ch, out_ch, kernel_size)
池化层 下采样,降低维度 核大小、步长 nn.MaxPool2d(kernel_size)
全连接层 最后分类 输入特征数、输出类别数 nn.Linear(in_features, out_features)
激活函数 引入非线性 - nn.ReLU()

5.2 在CIFAR-10上搭建LeNet

CIFAR-10数据集包含10类32x32的彩色图像:飞机、汽车、鸟、猫、鹿、狗、青蛙、马、船、卡车。

5.2.1 加载数据
import torch
import torch.nn as nn
import torch.optim as optim
import torchvision
import torchvision.transforms as transforms
from torch.utils.data import DataLoader

transform = transforms.Compose([
    transforms.ToTensor(),
    transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))   # 归一化到[-1,1]
])

trainset = torchvision.datasets.CIFAR10(root='./data', train=True, download=True, transform=transform)
testset = torchvision.datasets.CIFAR10(root='./data', train=False, download=True, transform=transform)

trainloader = DataLoader(trainset, batch_size=64, shuffle=True, num_workers=2)
testloader = DataLoader(testset, batch_size=100, shuffle=False, num_workers=2)

classes = ('plane', 'car', 'bird', 'cat', 'deer', 'dog', 'frog', 'horse', 'ship', 'truck')
5.2.2 定义LeNet网络
class LeNet(nn.Module):
    def __init__(self):
        super(LeNet, self).__init__()
        # 卷积层1:输入3通道,输出6通道,卷积核5x5
        self.conv1 = nn.Conv2d(3, 6, 5)
        self.pool1 = nn.MaxPool2d(2, 2)
        # 卷积层2:输入6通道,输出16通道,卷积核5x5
        self.conv2 = nn.Conv2d(6, 16, 5)
        self.pool2 = nn.MaxPool2d(2, 2)
        # 全连接层
        self.fc1 = nn.Linear(16 * 5 * 5, 120)
        self.fc2 = nn.Linear(120, 84)
        self.fc3 = nn.Linear(84, 10)
        self.relu = nn.ReLU()
    
    def forward(self, x):
        x = self.pool1(self.relu(self.conv1(x)))   # [batch, 6, 14, 14]
        x = self.pool2(self.relu(self.conv2(x)))   # [batch, 16, 5, 5]
        x = x.view(-1, 16 * 5 * 5)                  # 展平 [batch, 400]
        x = self.relu(self.fc1(x))
        x = self.relu(self.fc2(x))
        x = self.fc3(x)
        return x

model = LeNet()
print(model)
5.2.3 训练配置
device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')
model.to(device)

criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(model.parameters(), lr=0.001, momentum=0.9)
5.2.4 训练函数
def train(epoch):
    model.train()
    running_loss = 0.0
    for i, data in enumerate(trainloader, 0):
        inputs, labels = data[0].to(device), data[1].to(device)
        
        optimizer.zero_grad()
        outputs = model(inputs)
        loss = criterion(outputs, labels)
        loss.backward()
        optimizer.step()
        
        running_loss += loss.item()
        if i % 100 == 99:   # 每100个batch打印一次
            print(f'Epoch {epoch}, Batch {i+1}, Loss: {running_loss/100:.3f}')
            running_loss = 0.0
5.2.5 测试函数
def test():
    model.eval()
    correct = 0
    total = 0
    with torch.no_grad():
        for data in testloader:
            images, labels = data[0].to(device), data[1].to(device)
            outputs = model(images)
            _, predicted = torch.max(outputs, 1)
            total += labels.size(0)
            correct += (predicted == labels).sum().item()
    
    accuracy = 100. * correct / total
    print(f'Test set: Accuracy: {correct}/{total} ({accuracy:.2f}%)')
    return accuracy
5.2.6 开始训练
print("开始训练...")
for epoch in range(1, 11):   # 训练10个epoch
    train(epoch)
    test()
    print('-' * 50)

print("训练完成!")

预期结果:10个epoch后,测试准确率应达到50%-60%(LeNet对CIFAR-10的baseline)。可以通过加深网络、数据增强等方式提高准确率。

5.3 练习:尝试修改网络结构

# 练习1:增加卷积层数
class DeeperCNN(nn.Module):
    def __init__(self):
        super(DeeperCNN, self).__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.5)
    
    def forward(self, x):
        x = self.pool(torch.relu(self.bn1(self.conv1(x))))
        x = self.pool(torch.relu(self.bn2(self.conv2(x))))
        x = self.pool(torch.relu(self.bn3(self.conv3(x))))
        x = x.view(-1, 128 * 4 * 4)
        x = torch.relu(self.fc1(x))
        x = self.dropout(x)
        x = self.fc2(x)
        return x

观察:网络越深,准确率不一定越高,可能出现过拟合(训练集准确率高,测试集低)。需要引入Dropout、Batch Normalization等技巧。


六、总结与下一步

通过本阶段的学习,你已经掌握了:

  • ✅ PyTorch张量操作和自动求导
  • ✅ 构建全连接网络解决回归和分类问题
  • ✅ 使用数据加载器批量处理数据
  • ✅ 在MNIST上训练分类器
  • ✅ 理解卷积神经网络原理,并在CIFAR-10上实现LeNet

下一步:进入第五阶段——目标检测实战(YOLOv8),你将学会用最先进的目标检测框架训练自己的缺陷检测模型。


📚 参考文档链接


如果在学习过程中遇到任何问题,欢迎随时交流!下一阶段我们将进入激动人心的目标检测实战。

Logo

DAMO开发者矩阵,由阿里巴巴达摩院和中国互联网协会联合发起,致力于探讨最前沿的技术趋势与应用成果,搭建高质量的交流与分享平台,推动技术创新与产业应用链接,围绕“人工智能与新型计算”构建开放共享的开发者生态。

更多推荐