希望通过这份教程掌握PyTorch基础、理解卷积层/通道/张量/参数等核心概念,并能独立编写和运行MobileNetV2模型的训练、推理代码。

这份教程会从环境搭建到代码实战,全程面向新手,兼顾易懂性和实用性。

一、环境准备(新手友好版)

1. 基础依赖安装

首先确保安装了Python(推荐3.8-3.10),然后通过pip安装核心库:

# 安装PyTorch(CPU版本,新手优先,无需显卡)
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cpu

# 安装辅助库(数据处理、可视化)
pip install numpy matplotlib tqdm

如果你的电脑有NVIDIA显卡,想安装GPU版本,可以参考PyTorch官方安装页面选择对应命令(新手可先跳过,CPU版本足够完成本教程)。

2. 验证安装

运行以下代码,无报错则安装成功:

import torch
import torchvision
print(f"PyTorch版本:{torch.__version__}")
print(f"CUDA是否可用:{torch.cuda.is_available()}")  # CPU版本显示False,正常

二、核心概念解析(通俗+代码验证)

先理解基础概念,再写代码会事半功倍,以下概念均结合PyTorch代码示例,可直接运行验证。

1. 张量(Tensor):深度学习的"基本数据单位"

通俗理解:张量就是多维数组,是PyTorch中存储数据的核心结构,类比:

  • 0维张量 = 标量(比如数字5)
  • 1维张量 = 向量(比如[1,2,3])
  • 2维张量 = 矩阵(比如[[1,2],[3,4]])
  • 3维张量 = 比如(通道, 高度, 宽度)的单张图片
  • 4维张量 = 比如(批量大小, 通道, 高度, 宽度)的一批图片

代码示例

import torch

# 1. 创建不同维度的张量
t0 = torch.tensor(5)  # 0维张量(标量)
t1 = torch.tensor([1, 2, 3])  # 1维张量(向量)
t2 = torch.tensor([[1, 2], [3, 4]])  # 2维张量(矩阵)
# 4维张量(模拟批量=2,通道=3,高=28,宽=28的图片数据)
t4 = torch.randn(2, 3, 28, 28)  

# 2. 核心属性(新手必看)
print("0维张量形状:", t0.shape)  # torch.Size([])
print("4维张量形状:", t4.shape)  # torch.Size([2, 3, 28, 28])
print("4维张量数据类型:", t4.dtype)  # torch.float32(默认)
print("4维张量设备:", t4.device)  # cpu(默认)

# 3. 张量操作(深度学习常用)
t = torch.randn(3, 3)
t_plus = t + 2  # 加法(广播机制)
t_mul = t * 3   # 乘法
t_matmul = torch.matmul(t, t)  # 矩阵乘法(卷积层核心操作)

2. 通道(Channel):数据的"维度特征"

通俗理解:通道是张量中用于区分"特征维度"的轴,最常见的例子是图片:

  • 灰度图:1个通道(只有亮度信息),张量形状如(1, 28, 28)
  • RGB彩色图:3个通道(红/绿/蓝),张量形状如(3, 28, 28)
  • 卷积层输出的通道:每个通道对应一个"特征图"(比如检测边缘、纹理)

代码示例

# 模拟RGB图片(3通道,高宽各28)
rgb_img = torch.randn(3, 28, 28)
print("RGB图片形状:", rgb_img.shape)  # torch.Size([3, 28, 28])

# 提取单个通道(比如红色通道)
red_channel = rgb_img[0, :, :]
print("红色通道形状:", red_channel.shape)  # torch.Size([28, 28])

3. 卷积层(Conv2d):提取特征的"核心组件"

通俗理解:卷积层是深度学习模型(如MobileNetV2)的核心,作用是用"卷积核"(小矩阵)在输入数据上滑动,提取局部特征(比如边缘、角点、纹理)。

  • 卷积核:也叫过滤器,是卷积层的"参数",训练过程中会自动学习最优值
  • 步长(stride):卷积核滑动的步长
  • 填充(padding):在输入边缘补0,保证输出尺寸与输入一致

代码示例(手动定义一个卷积层,看输入输出变化):

import torch.nn as nn

# 定义卷积层:输入通道=3(RGB),输出通道=16,卷积核大小=3,步长=1,填充=1
conv = nn.Conv2d(in_channels=3, out_channels=16, kernel_size=3, stride=1, padding=1)

# 模拟输入:批量=2,3通道,高宽=28
input_tensor = torch.randn(2, 3, 28, 28)
# 卷积层前向计算
output_tensor = conv(input_tensor)

print("输入形状:", input_tensor.shape)  # torch.Size([2, 3, 28, 28])
print("输出形状:", output_tensor.shape)  # torch.Size([2, 28, 28, 16]) → 2,16,28,28(批量,输出通道,高,宽)

4. 参数(Parameter):模型需要学习的"权重"

通俗理解:参数是模型中需要通过训练优化的数值(比如卷积层的卷积核数值、偏置值),模型的"学习"本质就是调整这些参数,让模型预测更准确。

代码示例(查看卷积层的参数):

# 延续上面的卷积层
# 查看卷积层的参数列表
params = list(conv.parameters())
print("参数数量:", len(params))  # 2(卷积核权重 + 偏置)

# 卷积核权重:形状=(输出通道, 输入通道, 卷积核高, 卷积核宽)
weight = params[0]
print("卷积核权重形状:", weight.shape)  # torch.Size([16, 3, 3, 3])
print("卷积核参数总数:", weight.numel())  # 16*3*3*3=432个参数

# 偏置:形状=(输出通道,)
bias = params[1]
print("偏置形状:", bias.shape)  # torch.Size([16])
  • numel():计算张量中元素的总数,即参数数量
  • 训练前,参数是随机初始化的;训练后,参数会被优化为最优值

三、MobileNetV2完整实战:训练+推理

MobileNetV2是轻量级卷积神经网络,适合移动端/入门学习,我们会基于CIFAR10数据集(10类分类任务:飞机、汽车、鸟等)实现完整流程。

1. 完整代码(可直接复制运行)

以下代码包含:数据加载、模型定义、训练循环、推理验证,每一步都有详细注释。

import torch
import torch.nn as nn
import torch.optim as optim
from torchvision import datasets, transforms, models
from torch.utils.data import DataLoader
from tqdm import tqdm  # 进度条库,让训练过程更直观
import matplotlib.pyplot as plt

# ====================== 1. 配置全局参数 ======================
DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu")  # 自动选择设备(CPU/GPU)
BATCH_SIZE = 32  # 每次训练的样本数(新手可保持32)
EPOCHS = 5  # 训练轮数(新手先跑5轮,验证流程)
LEARNING_RATE = 0.001  # 学习率

# ====================== 2. 数据预处理与加载 ======================
# 数据预处理:将图片转为张量,并归一化(符合PyTorch模型输入要求)
transform = transforms.Compose([
    transforms.Resize((224, 224)),  # MobileNetV2要求输入224x224
    transforms.ToTensor(),  # 转为张量(0-1范围)
    transforms.Normalize(mean=[0.485, 0.456, 0.406],  # 官方推荐的归一化参数
                         std=[0.229, 0.224, 0.225])
])

# 加载CIFAR10数据集(自动下载,第一次运行会慢一点)
train_dataset = datasets.CIFAR10(
    root="./data",  # 数据保存路径
    train=True,     # 训练集
    download=True,  # 自动下载
    transform=transform
)
test_dataset = datasets.CIFAR10(
    root="./data",
    train=False,    # 测试集
    download=True,
    transform=transform
)

# 数据加载器(批量加载数据,支持多线程)
train_loader = DataLoader(train_dataset, batch_size=BATCH_SIZE, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=BATCH_SIZE, shuffle=False)

# CIFAR10类别名称(对应标签0-9)
CLASSES = ['飞机', '汽车', '鸟', '猫', '鹿', '狗', '青蛙', '马', '船', '卡车']

# ====================== 3. 加载MobileNetV2模型 ======================
# 加载预训练的MobileNetV2(新手先用预训练,训练更快、效果更好)
model = models.mobilenet_v2(pretrained=True)
# 修改最后一层:适配CIFAR10的10类分类(原模型是ImageNet的1000类)
num_ftrs = model.classifier[1].in_features  # 获取最后一层输入特征数
model.classifier[1] = nn.Linear(num_ftrs, 10)  # 替换为10类分类层
model = model.to(DEVICE)  # 将模型移到指定设备(CPU/GPU)

# ====================== 4. 定义损失函数和优化器 ======================
criterion = nn.CrossEntropyLoss()  # 分类任务常用损失函数
optimizer = optim.Adam(model.parameters(), lr=LEARNING_RATE)  # 常用优化器

# ====================== 5. 训练函数 ======================
def train_model(model, train_loader, criterion, optimizer, epoch):
    model.train()  # 模型设为训练模式(启用dropout等)
    running_loss = 0.0  # 累计损失
    correct = 0  # 正确预测数
    total = 0    # 总样本数
    
    # tqdm创建进度条
    pbar = tqdm(train_loader, desc=f"训练第{epoch+1}轮")
    for inputs, labels in pbar:
        inputs, labels = inputs.to(DEVICE), labels.to(DEVICE)  # 数据移到设备
        
        # 前向传播
        outputs = model(inputs)  # 模型预测
        loss = criterion(outputs, labels)  # 计算损失
        
        # 反向传播+优化
        optimizer.zero_grad()  # 清空梯度(必须!否则梯度累积)
        loss.backward()        # 反向传播计算梯度
        optimizer.step()       # 更新参数
        
        # 统计指标
        running_loss += loss.item() * inputs.size(0)
        _, predicted = torch.max(outputs, 1)  # 获取预测类别(概率最大的类)
        total += labels.size(0)
        correct += (predicted == labels).sum().item()
        
        # 更新进度条显示
        pbar.set_postfix(loss=running_loss/total, acc=100.*correct/total)
    
    # 计算本轮平均损失和准确率
    epoch_loss = running_loss / len(train_loader.dataset)
    epoch_acc = 100. * correct / total
    print(f"训练轮{epoch+1} | 损失:{epoch_loss:.4f} | 准确率:{epoch_acc:.2f}%")

# ====================== 6. 测试函数 ======================
def test_model(model, test_loader, criterion):
    model.eval()  # 模型设为评估模式(关闭dropout等)
    running_loss = 0.0
    correct = 0
    total = 0
    
    # 评估时不需要计算梯度,加速并节省内存
    with torch.no_grad():
        pbar = tqdm(test_loader, desc="测试中")
        for inputs, labels in pbar:
            inputs, labels = inputs.to(DEVICE), labels.to(DEVICE)
            
            outputs = model(inputs)
            loss = criterion(outputs, labels)
            
            running_loss += loss.item() * inputs.size(0)
            _, predicted = torch.max(outputs, 1)
            total += labels.size(0)
            correct += (predicted == labels).sum().item()
            
            pbar.set_postfix(loss=running_loss/total, acc=100.*correct/total)
    
    epoch_loss = running_loss / len(test_loader.dataset)
    epoch_acc = 100. * correct / total
    print(f"测试结果 | 损失:{epoch_loss:.4f} | 准确率:{epoch_acc:.2f}%\n")
    return epoch_acc

# ====================== 7. 开始训练 ======================
if __name__ == "__main__":
    best_acc = 0.0
    for epoch in range(EPOCHS):
        # 训练
        train_model(model, train_loader, criterion, optimizer, epoch)
        # 测试
        test_acc = test_model(model, test_loader, criterion)
        
        # 保存最优模型
        if test_acc > best_acc:
            best_acc = test_acc
            torch.save(model.state_dict(), "mobilenet_v2_cifar10_best.pth")
            print(f"保存最优模型,当前最高测试准确率:{best_acc:.2f}%\n")
    
    print(f"训练完成!最高测试准确率:{best_acc:.2f}%")

    # ====================== 8. 推理示例(单张图片预测) ======================
    def predict_single_image(image_path):
        # 加载并预处理单张图片
        from PIL import Image
        image = Image.open(image_path).convert('RGB')
        image = transform(image).unsqueeze(0)  # 增加批量维度(模型要求4维输入)
        image = image.to(DEVICE)
        
        # 预测
        model.eval()
        with torch.no_grad():
            outputs = model(image)
            _, predicted = torch.max(outputs, 1)
            class_name = CLASSES[predicted.item()]
        
        print(f"预测结果:{class_name}")
        return class_name

    # 【使用说明】替换为你自己的图片路径(比如本地的一张汽车图片)
    # predict_single_image("test_car.jpg")

2. 代码关键部分解释

(1)数据加载
  • CIFAR10是入门级分类数据集,自动下载无需手动处理,适合新手;
  • transforms是数据预处理核心:将图片转为张量并归一化,符合MobileNetV2的输入要求;
  • DataLoader负责批量加载数据,shuffle=True让训练集每轮打乱,提升泛化能力。
(2)模型修改
  • 预训练的MobileNetV2默认是1000类分类,我们需要替换最后一层为10类(适配CIFAR10);
  • model.to(DEVICE)将模型移到CPU/GPU,保证数据和模型在同一设备上(否则报错)。
(3)训练循环核心
  • model.train():启用训练模式(比如dropout层生效);
  • optimizer.zero_grad():清空梯度,避免梯度累积;
  • loss.backward():反向传播计算梯度;
  • optimizer.step():更新模型参数(核心的"学习"步骤)。
(4)推理流程
  • model.eval():启用评估模式(关闭dropout,避免影响预测结果);
  • torch.no_grad():禁用梯度计算,加速预测并节省内存;
  • unsqueeze(0):给单张图片增加批量维度(模型输入要求4维:[批量, 通道, 高, 宽])。

3. 运行结果说明

  • 第一次运行会自动下载CIFAR10数据集(约170MB),耐心等待;
  • 训练过程中会显示每轮的损失和准确率,5轮后测试准确率约70%-80%(预训练模型效果);
  • 训练完成后会保存最优模型到mobilenet_v2_cifar10_best.pth
  • 推理时替换图片路径,即可预测单张图片的类别。

四、常见问题解决(新手必看)

  1. 报错:“out of memory”:降低BATCH_SIZE(比如改为16);
  2. 数据下载慢:可手动下载CIFAR10数据集,放到./data目录;
  3. 推理时图片尺寸错误:确保图片预处理后是224x224;
  4. 模型加载失败:确保加载的参数文件和模型结构一致。

总结

  1. 核心概念:张量是PyTorch的基础数据结构,通道是特征维度,卷积层负责提取特征,参数是模型需要学习的权重;
  2. MobileNetV2实战:核心流程为「数据加载→模型定义(适配任务)→损失/优化器→训练循环→测试→推理」;
  3. 关键操作:训练时用model.train(),推理时用model.eval()+torch.no_grad(),参数更新依赖反向传播和优化器.step()。

通过这份教程,你不仅能跑通完整的MobileNetV2训练推理代码,还能理解深度学习的核心基础概念。如果想进一步学习YOLO,可基于此框架替换模型为YOLOv5/YOLOv8(PyTorch版本),核心训练推理流程基本一致。

Logo

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

更多推荐