PyTorch深度学习实战01:从环境搭建到模型训练的全流程深度体验
发布日期:2025-11-26 23:48 点击次数:95
PyTorch深度学习实战01:从环境搭建到模型训练的全流程深度体验
摘要
本文以“全流程体验深度学习”为核心,基于《深入浅出PyTorch》教程精髓,系统梳理了PyTorch项目从环境配置到模型训练的全链路实践。内容涵盖PyTorch基础环境搭建、数据加载与预处理、神经网络模型构建、损失函数与优化器配置、训练循环实现及可视化监控等关键环节,并通过30%篇幅的完整实操代码示例(包含MNIST手写数字分类任务的端到端实现),深入解析每个步骤的技术细节与常见问题。无论是深度学习新手还是希望巩固基础的开发者,都能通过本文获得从理论到实践的完整指导。
一、PyTorch环境配置:开启深度学习的第一步
1,1 为什么选择PyTorch?
在深度学习框架的“江湖”中,PyTorch凭借其动态计算图(Dynamic Computation Graph)、直观的Pythonic语法和活跃的社区生态,已成为学术界和工业界的首选工具之一。与TensorFlow的静态图相比,PyTorch的动态图允许开发者在代码中直接操作张量(Tensor),像编写普通Python程序一样构建和调试模型,极大降低了学习门槛。
展开剩余97%
1,2 环境准备:从零搭建PyTorch开发环境
(1)基础依赖:Python与包管理工具
PyTorch基于Python生态,推荐使用Python 3,8+版本(兼容性最佳)。通过conda或pip管理依赖包(本文以conda为例,因其能更好地处理CUDA环境):
# 创建名为pytorch_env的虚拟环境(隔离依赖,避免冲突)
conda create -n pytorch_env python=3,9 -y
conda activate pytorch_env # 激活环境
(2)安装PyTorch:匹配CUDA版本(可选)
PyTorch支持CPU和GPU(NVIDIA CUDA)两种计算模式。若你的电脑有NVIDIA显卡且已安装CUDA驱动(通过nvidia-smi命令查看CUDA版本,如11,8),建议安装GPU版本的PyTorch以加速神经网络训练;否则选择CPU版本。
• GPU版本安装命令(以CUDA 11,8为例,通过PyTorch官网https://pytorch,org/get-started/locally/获取最新适配命令):
conda install pytorch torchvision torchaudio pytorch-cuda=11,8 -c pytorch -c nvidia
• CPU版本安装命令(无GPU时使用):
conda install pytorch torchvision torchaudio cpuonly -c pytorch
(3)验证安装:检查PyTorch与CUDA是否可用
打开Python交互环境(或Jupyter Notebook),运行以下代码:
import torch
# 检查PyTorch版本
print(f"PyTorch版本: {torch,__version__}") # 示例输出: 2,0,1
# 检查CUDA是否可用(GPU支持)
print(f"CUDA是否可用: {torch,cuda,is_available()}") # 若为True则支持GPU加速
if torch,cuda,is_available():
print(f"当前GPU设备: {torch,cuda,get_device_name(0)}") # 示例输出: NVIDIA GeForce RTX 3060
print(f"CUDA版本: {torch,version,cuda}") # 示例输出: 11,8
常见问题:若torch,cuda,is_available()返回False,请检查:① NVIDIA显卡驱动是否安装(通过nvidia-smi查看);② CUDA Toolkit版本是否与PyTorch版本匹配(如PyTorch 2,0,1通常需要CUDA 11,7/11,8);③ 虚拟环境中是否安装了pytorch-cuda(GPU版本)。
二、数据加载与预处理:深度学习的“燃料”准备
2,1 数据:深度学习的核心输入
深度学习模型的性能高度依赖数据质量。在实战中,我们需要完成以下数据相关任务:
• 数据加载:从本地文件(如CSV、图片)或公开数据集(如MNIST、CIFAR-10)中读取原始数据。
• 数据预处理:对原始数据进行标准化(如像素值缩放到[0,1])、归一化(如减去均值除以标准差)、数据增强(如旋转、翻转图片)等操作,提升模型泛化能力。
• 数据封装:将处理后的数据封装为PyTorch的Dataset和DataLoader对象,支持批量加载(batch)、随机打乱(shuffle)和多线程加速(num_workers)。
2,2 实战案例:MNIST手写数字数据集
MNIST是深度学习的“Hello World”级数据集,包含60,000张训练图片和10,000张测试图片,每张图片是28×28像素的手写数字(0-9)。
(1)使用torchvision加载MNIST
PyTorch的torchvision库提供了常用数据集的直接加载接口(自动下载到本地):
import torch
from torchvision import datasets, transforms
# 定义数据预处理流程:转换为Tensor并归一化到[-1,1](均值0,5,标准差0,5)
transform = transforms,Compose([
transforms,ToTensor(), # 将PIL图片或numpy数组转换为Tensor(形状[1,28,28],值范围[0,1])
transforms,Normalize((0,5,), (0,5,)) # 对单通道(灰度图)做归一化:(x - 0,5)/0,5 → 范围[-1,1]
])
# 下载并加载训练集(root指定存储路径,train=True表示训练集)
train_dataset = datasets,MNIST(
root=',/data',
train=True,
download=True, # 首次运行自动下载数据集
transform=transform
# 下载并加载测试集
test_dataset = datasets,MNIST(
root=',/data',
train=False,
download=True,
transform=transform
print(f"训练集样本数: {len(train_dataset)}") # 输出: 60000
print(f"测试集样本数: {len(test_dataset)}") # 输出: 10000
(2)封装DataLoader:支持批量与随机化
DataLoader是PyTorch的数据加载器,可将Dataset对象封装为支持批量迭代、随机打乱和多线程加载的迭代器:
from torch,utils,data import DataLoader
# 定义批量大小(batch_size)和是否随机打乱(shuffle)
batch_size = 64
train_loader = DataLoader(
dataset=train_dataset,
batch_size=batch_size,
shuffle=True # 训练时随机打乱数据,避免模型学习到顺序偏差
test_loader = DataLoader(
dataset=test_dataset,
batch_size=batch_size,
shuffle=False # 测试时通常不需要随机化
# 查看一个批次的数据形状
for images, labels in train_loader:
print(f"单批次图片形状: {images,shape}") # [batch_size, channels, height, width] → [64, 1, 28, 28]
print(f"单批次标签形状: {labels,shape}") # [batch_size] → [64](每个元素是0-9的数字)
break # 仅查看第一个批次
关键参数说明:
- transforms,ToTensor():将图片数据转换为PyTorch的Tensor类型,并自动将像素值从[0,255]缩放到[0,1]。
- transforms,Normalize(mean, std):对数据进行标准化,公式为(x - mean) / std。对于MNIST的灰度图(单通道),mean和std均为标量(若为RGB三通道图片,需传入长度为3的元组,如(0,5, 0,5, 0,5)和(0,5, 0,5, 0,5))。
- DataLoader的shuffle=True:训练时随机打乱数据顺序,防止模型因数据顺序固定而学习到偏差(如总是先看到数字“0”的样本)。
三、模型构建:从全连接网络到神经网络设计
3,1 PyTorch模型定义:继承nn,Module
PyTorch中所有神经网络模型均需继承torch,nn,Module基类,并实现__init__(定义网络层)和forward(定义数据流向)两个核心方法。
(1)全连接网络(MLP)实现MNIST分类
针对MNIST的28×28像素图片,首先将其展平为784维向量(28×28=784),然后通过多个全连接层(Linear)提取特征,最后用Softmax输出10个类别的概率分布。
import torch,nn as nn
import torch,nn,functional as F
class MNISTNet(nn,Module):
def __init__(self):
super(MNISTNet, self),__init__()
# 定义网络层:输入784维(展平后的图片),输出10维(10个数字类别)
self,fc1 = nn,Linear(28 * 28, 128) # 第一层全连接:784 → 128
self,fc2 = nn,Linear(128, 64) # 第二层全连接:128 → 64
self,fc3 = nn,Linear(64, 10) # 输出层:64 → 10(对应数字0-9)
def forward(self, x):
# 展平输入图片(从[batch_size, 1, 28, 28] → [batch_size, 784])
x = x,view(-1, 28 * 28) # -1表示自动推断batch_size
# 逐层前向传播(激活函数用ReLU)
x = F,relu(self,fc1(x)) # 第一层输出:ReLU(128维)
x = F,relu(self,fc2(x)) # 第二层输出:ReLU(64维)
x = self,fc3(x) # 输出层:10维(不经过激活函数,后续用CrossEntropyLoss自带Softmax)
return x
# 实例化模型
model = MNISTNet()
print(model) # 打印模型结构
代码解析:
- nn,Linear(in_features, out_features):定义一个全连接层,输入维度为in_features,输出维度为out_features(内部包含权重矩阵W和偏置b)。
- F,relu():ReLU激活函数(Rectified Linear Unit),公式为max(0, x),用于引入非线性能力(避免多层线性变换退化为单层线性变换)。
- view(-1, 28*28):将输入的4D张量([batch_size, 1, 28, 28])展平为2D张量([batch_size, 784]),-1表示自动计算该维度的大小(根据总元素数和已知维度推断)。
(2)模型参数初始化(可选)
PyTorch默认会对线性层的权重进行初始化(通常为均匀分布或正态分布),但为了提升训练稳定性,可以手动初始化(例如用Xavier初始化):
def init_weights(m):
if isinstance(m, nn,Linear):
nn,init,xavier_uniform_(m,weight) # Xavier初始化权重
nn,init,zeros_(m,bias) # 偏置初始化为0
model,apply(init_weights) # 对模型的所有子模块应用初始化函数
四、损失函数与优化器:指导模型学习的“指挥棒”
4,1 损失函数:衡量预测与真实的差距
对于多分类任务(如MNIST的10个数字),常用交叉熵损失(Cross Entropy Loss),它结合了Softmax函数和负对数似然损失(NLL Loss),公式为:
\[ \text{Loss} = -\log\left(\frac{e^{z_y}}{\sum_{j=1}^{C} e^{z_j}}\right) \]
其中 z_y 是真实类别对应的输出logit, C 是类别总数(10)。
PyTorch中直接使用nn,CrossEntropyLoss(注意:该函数内部已包含Softmax,因此模型的输出层不需要手动添加Softmax):
criterion = nn,CrossEntropyLoss() # 定义损失函数
4,2 优化器:更新模型参数的算法
优化器的核心任务是根据损失函数的梯度,调整模型的参数(权重和偏置)以最小化损失。常用的优化器包括:
• SGD(随机梯度下降):基础优化器,通过学习率(lr)控制参数更新步长。
• Adam:自适应学习率优化器(结合动量和自适应调整),通常收敛更快且效果更好。
本文选择Adam优化器(学习率lr=0,001):
import torch,optim as optim
optimizer = optim,Adam(model,parameters(), lr=0,001) # 传入模型的所有参数(通过model,parameters()获取)
关键参数说明:
- model,parameters():返回模型中所有需要训练的参数(即nn,Module子类中通过nn,Parameter定义的张量,如nn,Linear的权重和偏置)。
- lr(learning rate):学习率,控制参数更新的步长(过大可能导致震荡,过小可能导致收敛缓慢)。
五、训练循环:模型的“成长之路”
5,1 训练流程的核心步骤
一个完整的训练循环包含以下步骤(以一个epoch为例):
1, 前向传播:输入数据通过模型得到预测输出。
2, 计算损失:用损失函数对比预测输出和真实标签。
3, 反向传播:计算损失对模型参数的梯度(通过loss,backward())。
4, 参数更新:优化器根据梯度更新参数(通过optimizer,step())。
5, 清零梯度:避免梯度累积(通过optimizer,zero_grad())。
此外,通常还会监控训练过程中的损失值和准确率,以便调试模型。
5,2 完整训练代码(含验证)
配置示例(mongod,conf):
storage:
wiredTiger:
engineConfig:
cacheSizeGB: 16 # 设置 WiredTiger 缓存为 16GB
游戏循环与事件处理
游戏开发的核心是主循环机制,典型结构包含三个关键组件:
def main_game_loop():
clock = pygame,time,Clock()
running = True
while running:
# 1, 事件处理阶段
for event in pygame,event,get():
if event,type == pygame,QUIT:
running = False
elif event,type == pygame,KEYDOWN:
handle_key_press(event,key)
# 2, 游戏逻辑更新
update_game_state(delta_time)
# 3, 渲染绘制阶段
screen,fill((0, 0, 0)) # 黑色背景
draw_game_objects()
pygame,display,flip() # 双缓冲交换
clock,tick(60) # 限制60FPS
事件类型对照表:
事件类型 触发条件 典型用途
QUIT 点击窗口关闭按钮 游戏安全退出
KEYDOWN/KEYUP 键盘按键动作 角色移动控制
MOUSEMOTION 鼠标移动 射击瞄准系统
USEREVENT 自定义定时器 子弹自动发射
高级事件处理技巧:
# 组合键检测示例
keys = pygame,key,get_pressed()
if keys[pygame,K_LEFT] and keys[pygame,K_SPACE]:
player,move_left_with_boost()
# 自定义事件系统
SHOOT_EVENT = pygame,USEREVENT + 1
pygame,time,set_timer(SHOOT_EVENT, 200) # 每200ms触发一次
2,2 坐标系与基础图形
Pygame采用左手坐标系(原点(0,0)在左上角,x向右递增,y向下递增),所有绘图操作需明确指定颜色参数(RGB/RGBA格式)。
核心绘图函数:
# 基本几何图形
pygame,draw,rect(screen, (255,0,0), (x,y,width,height), border_radius=5)
pygame,draw,;ep.bnnb29.cn@163.com;circle(screen, (0,255,0), (center_x,center_y), radius)
pygame,draw,line(screen, (255,255,0), start_pos, end_pos, width=3)
# 图像渲染优化
player_img = pygame,image,load("spaceship,png"),convert_alpha()
screen,;ep.bnnh26.com@163.com;blit(player_img, (x,y)) # 支持透明通道
坐标变换实战:
# 实现旋转动画
angle = (angle + 5) % 360
rotated_img = pygame,transform,rotate(original_img, angle)
new_rect = rotated_img,get_rect(center=original_rect,center)
screen,;ep.bnnb28.com@163.com;blit(rotated_img, new_rect,topleft)
第三章 实战项目:太空射击游戏(字 + 完整代码)
3,1 游戏架构设计
本项目采用面向对象设计模式,核心类结构如下:
├── GameEngine (游戏主控制器)
├── Player (玩家飞船类)
├── Enemy (敌机生成系统)
├── Bullet (子弹管理系统)
└── GameUI (界面渲染模块)
完整项目文件结构:
space_shooter/
│── main,py # 程序入口
│── config,py # 游戏配置常量
│── game_objects/ # 游戏实体类
│ ├── __init__,py
│ ├── player,py
│ ├── enemy,py
│ └── projectile,py
│── utils/ # 工具模块
│ ├── collision,py
│ └── assets,py
└── assets/ # 资源文件
├── images/
└── sounds/
3,2 核心代码实现(50%篇幅)
3,2,1 游戏初始化模块(config,py)
# 游戏基础配置
SCREEN_WIDTH = 1024
SCREEN_HEIGHT = 768
FPS = 60
BACKGROUND_COLOR = (0, 0, 20)
# 玩家设置
PLAYER_SPEED = 5
PLAYER_HEALTH = 100
PLAYER_IMG_PATH = "assets/images/player_ship,png"
# 敌机配置
ENEMY_SPAWN_RATE = 0,02 # 每帧生成概率
ENEMY_SPEED_RANGE = (2, 4)
ENEMY_TYPES = {
'basic': {'health': 30, 'speed': 2, 'score': 10},
'elite': {'health': 80, 'speed': 1, 'score': 30}
}
# 子弹系统
BULLET_SPEED = 8
BULLET_DAMAGE = 25
MAX_BULLETS = 10
3,2,2 玩家控制系统(player,py)
import pygame
from pygame,math import Vector2
class Player(pygame,sprite,Sprite):
def __init__(self, x, y):
super(),__init__()
self,image = pygame,image,load(config,PLAYER_IMG_PATH),convert_alpha()
self,rect = self,;ep.bnnb29.com@163.com;mage,get_rect(center=(x, y))
self,velocity = Vector2(0, 0)
self,health = config,PLAYER_HEALTH
self,;ep.okxhup25.com@163.com;shoot_cooldown = 0
def update(self):
# 处理移动输入
keys = pygame,key,get_pressed()
self,velocity,x = 0
self,velocity,y = 0
if keys[pygame,K_LEFT] or keys[pygame,K_a]:
self,velocity,x = -config,PLAYER_SPEED
if keys[pygame,;top.bnnb28.cn@163.com;K_RIGHT] or keys[pygame,K_d]:
self,velocity,x = config,PLAYER_SPEED
if keys[pygame,K_UP] or keys[pygame,K_w]:
self,velocity,y = -config,PLAYER_SPEED
if keys[pygame,;top.bnnb29.cn@163.com;K_DOWN] or keys[pygame,K_s]:
self,velocity,y = config,PLAYER_SPEED
# 对角线移动速度修正
if self,velocity,length() > 0:
self,velocity,scale_to_length(config,PLAYER_SPEED)
self,rect,x += int(self,velocity,x)
self,rect,y += int(self,velocity,y)
# 边界检测
self,rect,clamp_ip(pygame,Rect(0, 0, SCREEN_WIDTH, SCREEN_HEIGHT))
# 射击冷却计时
if self,shoot_cooldown > 0:
self;top.bnnh26.com@163.com;shoot_cooldown -= 1
def shoot(self):
if self,shoot_cooldown <= 0:
bullet = Bullet(self,rect,centerx, self,rect,top, -config,BULLET_SPEED)
return bullet
return None
3,2,3 敌机生成系统(enemy,py)
import random
from pygame,;top.bnnb28.com@163.com;math import Vector2
class Enemy(pygame,sprite,Sprite):
def __init__(self, enemy_type='basic'):
super(),__init__()
self,type = enemy_type
self,config = config,ENEMY_TYPES,get(enemy_type, config,ENEMY_TYPES['basic'])
# 根据类型加载不同图像
img_path = f"assets/images/enemy_{enemy_type},png"
try:
self,image = pygame,image,load(img_path),convert_alpha()
except:
self,image = pygame,Surface((40, 40))
self,image,;top.bnnb29.com@163.com;fill((255, 0, 0))
self,rect = self,image,get_rect()
self,rect,x = random,randint(0, SCREEN_WIDTH - self,rect,width)
self,rect,y = -self,rect,height
self,health = self,config['health']
self,speed = random,uniform(*config,ENEMY_SPEED_RANGE)
self,score_value = self,config['score']
def update(self):
self,rect,;top.okxhup25.com@163.com;y += int(self,speed)
# 超出屏幕边界自动销毁
if self,rect,top > SCREEN_HEIGHT:
self,kill()
def take_damage(self, damage):
self,health -= damage
if self,;top.bka1.cn@163.com;health <= 0:
return True # 标记为需要销毁
return False
3,2,4 碰撞检测系统(utils/collision,py)
def check_collision(sprite1, sprite2):
"""精确像素级碰撞检测"""
return pygame,sprite,collide_mask(sprite1, sprite2)
def check_rect_collision(rect1, rect2):
"""基础矩形碰撞检测(性能优化版)"""
return rect1,;top.bka2.cn@163.com;colliderect(rect2)
def group_collision(sprite, group):
"""精灵与精灵组的碰撞检测"""
return pygame,sprite,spritecollide(sprite, group, False, check_rect_collision)
3,2,5 主游戏循环(main,py)
import pygame
import sys
from game_objects,player import Player
from game_objects,enemy import Enemy
from game_objects,projectile import Bullet
from utils,collision import check_collision
class SpaceShooterGame:
def __init__(self):
pygame,init()
self,screen = pygame,display,set_mode((SCREEN_WIDTH, SCREEN_HEIGHT))
pygame,display,set_caption("Python太空射击游戏")
self,;top.bka3.cn@163.com;clock = pygame,time,Clock()
self,running = True
self,score = 0
self,font = pygame,font,Font(None, 36)
# 游戏对象组
self,all_sprites = pygame,sprite,Group()
self,enemies = pygame,sprite,Group()
self,bullets = pygame,sprite,Group()
self,particles = pygame,sprite,Group()
# 创建玩家
self,player = Player(SCREEN_WIDTH//2, SCREEN_HEIGHT - 100)
self,all_sprites,add(self,player)
def handle_events(self):
for event in pygame,event,get():
if event,;top.bak4.cn@163.com;type == pygame,QUIT:
self,running = False
elif event,type == pygame,KEYDOWN:
if event,key == pygame,K_SPACE:
bullet = self,player,shoot()
if bullet:
self,;top.bak5.cn@163.com;bullets,add(bullet)
self,all_sprites,add(bullet)
def update_game(self):
# 生成敌机
if random,random() < config,ENEMY_SPAWN_RATE:
enemy_type = random,choice(list(config,ENEMY_TYPES,keys()))
enemy = Enemy(enemy_type)
self,enemies,add(enemy)
self,;top.bak6.cn@163.com;all_sprites,add(enemy)
# 更新所有精灵
self,all_sprites,update()
# 子弹与敌机碰撞
for bullet in self,bullets:
hit_enemies = pygame,sprite,spritecollide(bullet, self,enemies, False)
for enemy in hit_enemies:
if enemy,;top.bak7.cn@163.com;take_damage(config,BULLET_DAMAGE):
self,score += enemy,score_value
enemy,kill()
bullet,kill()
# 玩家与敌机碰撞
if pygame,sprite,spritecollide(self,player, self,enemies, False):
self,player,health -= 10
if self,player,health <= 0:
self,;ep.bnnb25.cn@163.com;game_over()
def render(self):
self,screen,fill(config,BACKGROUND_COLOR)
self,all_sprites,draw(self,screen)
# 绘制UI信息
score_text = self,font,render(f"Score: {self,score}", True, (255, 255, 255))
health_text = self,font,render(f"Health: {self,player,health}", True, (255, 255, 255))
self,screen,blit(score_text, (10, 10))
self,screen,blit(health_text, (10, 50))
pygame,;ep.bnnb26.cn@163.com;display,flip()
def game_over(self):
game_over_text = self,font,render("GAME OVER - Press R to Restart", True, (255, 0, 0))
text_rect = game_over_text,get_rect(center=(SCREEN_WIDTH//2, SCREEN_HEIGHT//2))
self,screen,blit(game_over_text, text_rect)
pygame,display,flip()
waiting = True
while waiting:
for event in pygame,event,get():
if event,;ep.bnnb28.cn@163.com;type == pygame,QUIT:
self,running = False
waiting = False
elif event,type == pygame,KEYDOWN:
if event,key == pygame,K_r:
self,__init__() # 重置游戏状态
waiting = False
def run(self):
while self,running:
self,handle_events()
self,update_game()
self,render()
self,clock,tick(FPS)
pygame,quit()
sys,exit()
if __name__ == "__main__":
game = SpaceShooterGame()
game,run()
import torch
# 训练函数
def train(model, train_loader, criterion, optimizer, epoch):
model,train() # 设置模型为训练模式(启用Dropout/BatchNorm的训练行为)
total_loss = 0
correct = 0
total = 0
for batch_idx, (images, labels) in enumerate(train_loader):
# 1, 将数据移动到GPU(如果可用)
if torch,cuda,is_available():
images, labels = images,cuda(), labels,cuda()
# 2, 前向传播
outputs = model(images)
loss = criterion(outputs, labels) # 计算损失
# 3, 反向传播与参数更新
optimizer,zero_grad() # 清零梯度(避免累积)
loss,backward() # 计算梯度
optimizer,step() # 更新参数
# 4, 统计损失和准确率
total_loss += loss,item() * images,size(0) # 累计损失(loss,item()是当前批次的平均损失)
_, predicted = torch,max(outputs,data, 1) # 获取预测的类别(取概率最大的索引)
total += labels,size(0) # 累计样本总数
correct += (predicted == labels),sum(),item() # 累计正确预测数
# 每100个批次打印一次进度
if batch_idx % 100 == 0:
print(f'Train Epoch: {epoch} [{batch_idx * len(images)}/{len(train_loader,dataset)} '
f'({100, * batch_idx / len(train_loader):,0f}%)]\tLoss: {loss,item():,6f}')
# 计算平均损失和准确率
avg_loss = total_loss / total
accuracy = 100, * correct / total
print(f'\nTrain Epoch: {epoch} \tAverage Loss: {avg_loss:,4f}, Accuracy: {accuracy:,2f}%\n')
return avg_loss, accuracy
# 验证函数(测试集评估)
def test(model, test_loader):
model,eval() # 设置模型为评估模式(禁用Dropout/BatchNorm的训练行为)
test_loss = 0
correct = 0
total = 0
with torch,no_grad(): # 禁用梯度计算(节省内存和计算资源)
for images, labels in test_loader:
if torch,cuda,is_available():
images, labels = images,cuda(), labels,cuda()
outputs = model(images)
test_loss += criterion(outputs, labels),item() * images,size(0)
_, predicted = torch,max(outputs,data, 1)
total += labels,size(0)
correct += (predicted == labels),sum(),item()
test_loss /= total
accuracy = 100, * correct / total
print(f'Test set: Average Loss: {test_loss:,4f}, Accuracy: {correct}/{total} ({accuracy:,2f}%)\n')
return test_loss, accuracy
# 主训练流程
num_epochs = 5 # 训练轮数
for epoch in range(1, num_epochs + 1):
train_loss, train_acc = train(model, train_loader, criterion, optimizer, epoch)
test_loss, test_acc = test(model, test_loader)
代码解析:
- model,train() 和 model,eval():分别设置模型的训练模式和评估模式。训练模式下,Dropout层会随机丢弃部分神经元,BatchNorm层会使用当前批次的统计量;评估模式下,Dropout层不丢弃神经元,BatchNorm层使用训练阶段统计的全局均值和方差。
- torch,no_grad():上下文管理器,禁用梯度计算(验证时不需要反向传播,可节省资源)。
- torch,max(outputs,data, 1):返回每行(每个样本)的最大值及其索引,索引即为预测的类别(如输出为[0,1, 0,8, 0,1],则预测类别为1)。
六、可视化与模型保存:让训练过程“看得见”
6,1 使用TensorBoard可视化训练指标
TensorBoard是PyTorch官方推荐的可视化工具,可实时监控损失值、准确率等指标的变化趋势。
(1)安装与启动
pip install tensorboard
tensorboard --logdir=runs # 启动服务(默认端口6006,浏览器访问http://localhost:6006)
(2)在代码中记录指标
from torch,utils,tensorboard import SummaryWriter
writer = SummaryWriter('runs/mnist_experiment') # 指定日志保存目录
for epoch in range(1, num_epochs + 1):
train_loss, train_acc = train(model, train_loader, criterion, optimizer, epoch)
test_loss, test_acc = test(model, test_loader)
# 记录损失和准确率到TensorBoard
writer,add_scalar('Loss/train', train_loss, epoch)
writer,add_scalar('Accuracy/train', train_acc, epoch)
writer,add_scalar('Loss/test', test_loss, epoch)
writer,add_scalar('Accuracy/test', test_acc, epoch)
writer,close() # 关闭写入器
6,2 保存与加载模型
训练完成后,通常需要保存模型的参数(或整个模型),以便后续推理或继续训练。
(1)保存模型参数(推荐)
仅保存模型的state_dict(包含所有可学习参数的权重和偏置),体积小且灵活(可加载到不同结构的模型中,只要参数名匹配):
torch,save(model,state_dict(), 'mnist_model,pth') # 保存到当前目录
# 加载模型参数(需先实例化相同结构的模型)
loaded_model = MNISTNet()
loaded_model,load_state_dict(torch,load('mnist_model,pth'))
loaded_model,eval() # 设置为评估模式
(2)保存整个模型(包含结构和参数)
直接保存整个模型对象(包括网络结构定义),但灵活性较低(若模型代码修改后可能无法加载):
torch,save(model, 'mnist_model_full,pth') # 保存整个模型
loaded_model = torch,load('mnist_model_full,pth') # 直接加载整个模型
loaded_model,eval()
七、总结与进阶方向
7,1 本文核心知识点总结
• 环境配置:通过conda安装PyTorch(匹配CUDA版本),验证GPU可用性。
• 数据加载:使用torchvision,datasets和DataLoader实现MNIST数据的高效加载与预处理。
• 模型构建:继承nn,Module定义全连接网络,通过forward方法指定数据流向。
• 训练循环:结合损失函数(CrossEntropyLoss)和优化器(Adam),实现前向传播、反向传播与参数更新。
• 可视化与保存:通过TensorBoard监控训练指标,保存模型参数供后续使用。
7,2 进阶学习方向
• 卷积神经网络(CNN):针对图像数据,用卷积层(Conv2d)替代全连接层,提升特征提取能力(MNIST的准确率可从95%+提升到99%+)。
• 迁移学习:使用预训练模型(如ResNet、VGG)处理更复杂的任务(如CIFAR-10、ImageNet分类)。
• 超参数调优:通过网格搜索或贝叶斯优化调整学习率、批量大小、网络层数等参数。
通过本文的全流程实践,你已经掌握了PyTorch深度学习项目的核心流程。接下来,可以尝试更复杂的数据集(如CIFAR-10)或模型结构(如CNN),开启真正的深度学习之旅!
发布于:广东省