【零基础入门】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

这里是“一人公司”的成长家园。我们提供从产品曝光、技术变现到法律财税的全栈内容,并连接云服务、办公空间等稀缺资源,助你专注创造,无忧运营。

更多推荐