0 基础也能看懂!小土堆 PyTorch 学习笔记,代码 + 原理全拆解

本文基于深度学习入门级教程:PyTorch深度学习快速入门教程(绝对通俗易懂!)【小土堆】
此篇博客为其笔记总结,但不完全依照教程,进行了适当选择和延展,旨在更好地面向深度学习入门者,希望能够帮助到更多在入门的小伙伴~
嘿嘿嘿~


目录

一、前置准备:

1.cuda环境检查:

2.安装nb_conda

3.安装本地包库

二、工具介绍

1.Dataset类

2.Tensorboard

3.numpy.array()对PIL的图像进行转换为torch.tensor格式

4.numpy.array()转图像后进行writer.add_to_tensorboard()

5.Pytorch中的Transforms

6. torchvision.datasets

7.DataLoader

8.神经网络的基本骨架nn.Module

三、深度学习深入理解:剖析神经网络核心组件

1.卷积

2.最大池化

3.非线性激活层

4.线性层

5.flatten层

6.loss(cost)函数

7.反向传播

8.深度学习中 PyTorch 优化器的使用

四、相关训练流程套路:

1.训练循环:

2.使用pytorch官方内置的网络模型

3.PyTorch 中模型的保存与读取

4.torch.no_grad() 

5.分类问题的正确率如何计算?

6.model.train()和model.eval()

7.利用gpu进行训练

五、完整的模型训练思路及套路
        1.一般的训练思路及套路:
        2.小土堆完整训练代码, 以CIFAR10为数据集,进行了代码注释和一些优化


一、前置准备:

1.cuda环境检查:

检查是否安装成功gpu版本的torch(注:在IDE中编译器设置切换到相应待检查的环境

import torch
torch.cuda.is_available()
输出结果:true则证明安装正确
输出false的可能原因:
  • 1. 硬件问题:本身无NVIDIA GPU;GPU驱动未安装、版本过低或与CUDA版本不兼容。

  • 2. 软件问题:未安装CUDA Toolkit;PyTorch版本与CUDA不匹配;环境变量设置错误。

2.安装nb_conda

        nb_conda是jupyter notebook的扩展,可在jupyter notebook中直接使用conda环境,允许在notebook的界面中创建、管理、切换conda环境,而无需通过命令行。

安装指令(先激活相应环境):

conda install nb_conda

3.安装本地包库

conda install --use-local 包名.tar.bz2

二、工具介绍

1.Dataset类

        通过继承Dataset类class MyData(Dataset),着重__init__初始化,和__len__和__getitem__方法的实现,小土堆蚂蚁蜜蜂/练手数据集:链接: https://pan.baidu.com/s/1jZoTmoFzaTLWh4lKBHVbEA 密码: 5suq

#前置包库
from torch.utils.data import Dataset
from PIL import Image
import os
#自定义类:MyData
class MyData(Dataset):
    def __init__(self, root_dir, label_dir):
        self.root_dir = root_dir
        self.label_dir = label_dir
        self.path = os.path.join(self.root_dir, self.label_dir)
        self.img_path = os.listdir(self.path)
        
    def __getitem__(self, idx):
        img_name = self.img_path[idx]
        img_item_path = os.path.join(self.root_dir, self.label_dir,img_name)
        img = Image.open(img_item_path)
        label = self.label_dir
        return img,label
    
    def __len__(self):
        return len(self.img_path)

root_dir = "蚂蚁蜜蜂数据集\\dataset\\train"
ants_label_dir = "ants"
ants_dataset = MyData(root_dir, ants_label_dir)
print(ants_dataset)
print(ants_dataset[0] )  # 根据重写的getitem返回 img与 label
img, label = ants_dataset[0] 
img.show()
bees_label_dir = "bees"
bees_dataset = MyData(root_dir, bees_label_dir)
img, label = bees_dataset[0] 
img.show()

train_dataset =ants_dataset + bees_dataset #两个数据集的拼接 未改变顺序,ants在前 bees在后
print(len(ants_dataset))
print(len(bees_dataset))
print(len(train_dataset))

2.Tensorboard

  1. 快速入门:
    Tensorboard 是 TensorFlow 官方可视化工具,也可用于 PyTorch。它能展示标量数据、图像、模型计算图等,帮你直观评估模型状态

  2. 安装与启动:
    安装:

    # 通用安装
    pip install tensorboard 
    # PyTorch需额外安装
    pip install torchvision 

    启动:
    在anaconda激活已安装tensorboard的环境输入日志位置启动后访问http://localhost:6006,即可查看可视化界面。

    tensorboard --logdir=日志路径
  3. 使用流程(以加载CIFAR10为例)

  • writer:由SummaryWriter类实例化得到的日志写入对象,用于将数据写入到指定路径的日志文件中。
  • tag:字符串类型,是数据的标签
  • value:对于add_scalar_to_tensorboard函数,value是要记录的标量值(即y坐标)
  • images:仅在add_images_to_tensorboard函数中使用,要求是torch.Tensor格式的图像数据,其形状为(batch_size, channels, height, width)
  • step:整数类型,表示训练的步数或轮次。(即x坐标)

代码示例:

from torch.utils.tensorboard import SummaryWriter
import torch
import torchvision.transforms as transforms
from torchvision.datasets import CIFAR10

# 定义添加标量数据函数
def add_scalar_to_tensorboard(writer, tag, value, step):
    writer.add_scalar(tag, value, step)

# 定义添加图像数据函数
def add_images_to_tensorboard(writer, tag, images, step):
    writer.add_images(tag, images, step)

# 初始化写入器
writer = SummaryWriter('logs/pytorch_custom_add')

# 模拟训练记录标量
for epoch in range(5):
    loss = 0.8 - 0.1 * epoch
    add_scalar_to_tensorboard(writer, "Train Loss", loss, epoch)

# 加载数据记录图像
transform = transforms.Compose([transforms.ToTensor()])
trainset = CIFAR10(root='./data', train=True, download=True, transform=transform)
trainloader = torch.utils.data.DataLoader(trainset, batch_size=4, shuffle=True)
images, _ = next(iter(trainloader))
add_images_to_tensorboard(writer, "Train Images", images, 0)

writer.close()

3.numpy.array()对PIL的图像进行转换为torch.tensor格式

def pil_image_to_tensor(pil_image):
    """
    将PIL图像转换为torch.Tensor格式
    """
    np_image = np.array(pil_image)  # 先转为numpy数组,形状为(height, width, channels)
    np_image = np.transpose(np_image, (2, 0, 1))  # 调整维度顺序为(channels, height, width)
    tensor_image = torch.from_numpy(np_image).float()  # 转为torch.Tensor并设置数据类型为float
    tensor_image = tensor_image.unsqueeze(0)  # 添加batch维度,变为(1, channels, height, width)
    return tensor_image

# 示例使用
pil_image = Image.open('your_image.jpg')  # 打开PIL图像
tensor_image = pil_image_to_tensor(pil_image)

4.numpy.array()转图像后进行writer.add_to_tensorboard()

def add_images_to_tensorboard(writer, tag, images, step):
    writer.add_images(tag, images, step)

writer = SummaryWriter('logs/images')
# 假设已获取tensor_image,这里用单张图片模拟batch,实际使用多张图时batch_size>1
add_images_to_tensorboard(writer, "Sample Image", tensor_image, 0)
writer.close()

5.Pytorch中的Transforms

        用于对图像进行预处理和数据增强操作,如调整图像大小、中心裁剪、随机裁剪、随机水平翻转、归一化、将 PIL 图像转换为 Tensor 等等

from torchvision import transforms

常用操作:

1.数据类型转换:
ToTensor()是最常用的转换之一,它将 PIL 图像或 NumPy 数组转换为 PyTorch 的张量(Tensor),并将像素值的范围从[0, 255]归一化到[0.0, 1.0],同时调整维度顺序,使其符合 PyTorch 模型的输入要求。示例代码如下:

from torchvision import transforms
from PIL import Image

# 打开图像
image = Image.open('example.jpg')
# 转换为张量
transform = transforms.ToTensor()
tensor_image = transform(image)
print(f"转换后的张量形状: {tensor_image.shape}")
print(f"张量数据示例: {tensor_image[:, :5, :5]}")  # 打印张量部分数据

2.归一化:
Normalize(mean, std)用于对图像进行归一化操作,通过减去均值并除以标准差,将图像的像素值调整到特定的分布范围。在图像分类任务中,常见的归一化设置为:

normalize = transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
# 对张量图像进行归一化
normalized_image = normalize(tensor_image)
print(f"归一化后张量的均值: {normalized_image.mean(axis=(1, 2, 3))}")
print(f"归一化后张量的标准差: {normalized_image.std(axis=(1, 2, 3))}")

3.数据增强:
为了增加数据的多样性,防止模型过拟合,Transforms提供了丰富的数据增强操作。比如RandomCrop(size)用于随机裁剪图像到指定大小;RandomHorizontalFlip(p=0.5)以一定概率对图像进行水平翻转;ColorJitter(brightness, contrast, saturation, hue)可以调整图像的亮度、对比度、饱和度和色调等。

augment_transform = transforms.Compose([
    transforms.RandomCrop(224),
    transforms.RandomHorizontalFlip(),
    transforms.ColorJitter(brightness=0.2, contrast=0.2, saturation=0.2, hue=0.1)
])
augmented_image = augment_transform(image)
# 展示增强后的图像(需安装matplotlib库)
import matplotlib.pyplot as plt
plt.imshow(augmented_image)
plt.show()

6. torchvision.datasets

torchvision.datasets是 PyTorch 用于计算机视觉数据加载的核心模块,涵盖经典数据集与自定义接口

train_set = torchvision.datasets.CIFAR10(root="./dataset", train=True,transform=dataset_transform, download=True)
test_set = torchvision.datasets.CIFAR10(root="./dataset", train=False,transform=dataset_transform, download=True)

7.DataLoader

在 PyTorch 深度学习训练中,DataLoader是优化数据加载的核心工具,能将数据集整理成批次送入模型。

1.代码示例:

import torchvision
from torchvision import transforms
from torch.utils.data import DataLoader
dataset_transform = transforms.Compose([
    transforms.ToTensor() 
])
test_set = torchvision.datasets.CIFAR10(root="./dataset", train=False,transform=dataset_transform, download=True)
test_loader = DataLoader(dataset=test_set, batch_size=4, shuffle=True, num_workers=0, drop_last=False)
img, target = test_set[0]
print("单个img:",img.shape)
print("单个target:",target)

for data in test_loader:
    imgs,targets = data
    print(imgs.shape)
    print(targets)

2.核心参数

  • batch_size:控制每批样本数,大值可加速训练但占更多显存。
  • shuffle:训练集设为True打乱数据,提升模型泛化能力;测试集设False确保结果稳定。
  • num_workers:指定数据加载子进程数,合理设置能加快加载,但过大会引发 CPU 资源竞争。
  • pin_memory:设为True可将数据存到锁页内存,加快向 GPU 传输速度,不过会占用额外内存。
  • drop_last:当样本数不能被batch_size整除时,True丢弃不完整批次,False保留。


8.神经网络的基本骨架nn.Module

1.基本介绍:

        nn.Module是 PyTorch 中所有神经网络模块的基类。当我们创建自定义的神经网络时,只需继承 nn.Module类,然后在类中定义网络的层结构与前向传播方法,就能轻松构建复杂的神经网络模型。nn.Module会自动管理模型中的所有可训练参数,方便进行参数初始化、优化器配置等操作.

import torch
from torch import nn
class Tudui(nn.Module):
    def __init__(self):
        super().__init__()
        
    def forward(self, input):
        output = input +1
        return output
tudui = Tudui()
x = torch.tensor(1.0)
myout = tudui(x)
print(myout)

        2.使用步骤:

  1. 继承nn.Module:创建自定义的网络模型类,让其继承nn.Module
  2. 定义网络层:在类的__init__方法中定义网络所需要的层。
  3. 实现前向传播方法:在类中定义forward方法,该方法描述了输入数据在网络中的前向传播过程.

        3.其他功能:

  1. 参数管理:可以通过model.parameters()获取模型的所有可训练参数,方便传递给优化器。
  2. 模型保存与加载:可以使用torch.save(model.state_dict(), 'model.pth')保存模型参数,使用model.load_state_dict(torch.load('model.pth'))加载模型参数.

三、深度学习深入理解:剖析神经网络核心组件

1.卷积

卷积的基本概念

        在深度学习里,卷积层是神经网络的核心部分,它能自动找到数据里的关键特征
简单来说,卷积就是用一个小矩阵(卷积核)在输入数据上滑动做计算,把对应位置的数值相乘再相加,最后得到一个新的数据(特征图)。

        卷积核就像一个 “特征探测器”。比如在处理图像时,不同的卷积核可以找到图像里的边缘、角点或者纹理。通过使用多个卷积核,模型就能同时找到很多种不同的特征。
通过不断训练找到最佳卷积核对于卷积神经网络的性能至关重要。

卷积是怎么工作的?

        假设我们有一张 5×5 的单通道小图像,用一个 3×3 的卷积核来处理它。卷积核会按照一定的距离(步长)在图像上滑动。

        当步长为 1 时,卷积核从图像左上角开始,每次向下或者向右移动 1 个像素。在每个位置,卷积核里的数字和图像对应位置的数字相乘,再把这 9 个乘积加起来,得到的结果就是新图像(特征图)上的一个像素值。重复这个操作,直到把整个图像都处理完。

        这样做有个很大的好处:同一个卷积核在图像的不同位置找相同特征时,用的都是一样的参数,这大大减少了模型需要学习的参数数量,也让模型不怕图像发生平移。


注意:
1.输入特征图的通道数 = 卷积核的通道数
2.输出特征图的通道数 = 卷积核的个数
卷积核的通道数一定和输入的通道数相等,输入对应的每个通道与卷积核对应的每个通道进行计算再求和得到 一个通道的卷积
输出;而输出特征图的通道数与卷积核的个数相关,有多少个卷积核最终就有多少个输出通道

卷积层的重要参数

  1. 卷积核大小(Kernel Size):常用的有 3×3、5×5。小的卷积核能找到更细致的特征,而且需要的参数少;大的卷积核能找到更大范围的特征,但参数和计算量会增加。
  2. 输入通道数(Input Channels):如果是灰度图,输入通道数是 1;如果是 RGB 彩色图,输入通道数就是 3。卷积核的通道数必须和输入数据的通道数一样。
  3. 输出通道数(Output Channels):这是我们自己设定的,代表输出特征图的通道数量。每一个输出通道对应一个卷积核,不同的卷积核会找到不同的特征。
  4. 步长(Stride):决定卷积核每次滑动的距离。步长越大,输出的特征图就越小。
  5. 填充(Padding):为了避免图像边缘信息丢失,在图像边缘补 0。合理公式设置填充,可以让输出特征图和输入图像一样大。

在 PyTorch 里使用卷积

在 PyTorch 中,用nn.Conv2d就能轻松定义卷积层,示例代码如下:

import torch
import torch.nn as nn

# 创建卷积层:输入3通道,输出16通道,卷积核3×3,步长1,填充1
conv_layer = nn.Conv2d(in_channels=3, out_channels=16, kernel_size=3, stride=1, padding=1)

# 生成模拟图像数据:1张3通道、大小224×224的图像
input_image = torch.randn(1, 3, 224, 224)

# 进行卷积操作
output = conv_layer(input_image)
print(output.shape)

运行后,会得到一个形状为(1, 16, 224, 224)的特征图,其中 1 是图像数量,16 是输出通道数,224×224 是特征图的尺寸


2.最大池化

最大池化的基本概念

        最大池化是一种下采样操作,核心逻辑非常简单:将输入的特征图划分成一个个固定大小的区域(即池化窗口,Pooling Window),在每个区域内选取数值最大的元素作为输出,从而得到下采样后的特征图。例如,常见的池化窗口大小有 2×2,这意味着会把特征图分割成若干个 2×2 的小块,每个小块只保留最大值,其余数值被舍弃。(随之而来的还有平均池化等操作)

最大池化的工作原理(压缩但保留主要特征)

假设我们有一个 4×4 的特征图作为输入,使用 2×2 的池化窗口、步长(Stride)为 2 进行最大池化操作:

  1. 从特征图左上角开始,将 2×2 的池化窗口覆盖在该区域,找出这 4 个元素中的最大值,作为输出特征图左上角的第一个元素。
  2. 按照步长 2,将池化窗口向右移动 2 个单位,继续在新的 2×2 区域内选取最大值,得到输出特征图的下一个元素。
  3. 当一行遍历完后,池化窗口向下移动 2 个单位,重新从左侧开始滑动,重复上述操作,直至遍历完整个输入特征图。
    通过这个过程,原本 4×4 的特征图就会被下采样为 2×2 的特征图,数据量大幅减少。

最大池化的关键参数

  1. 池化窗口大小(Pool Size):决定划分区域的大小,常见取值有 2×2、3×3。窗口越大,下采样的程度越高,输出特征图尺寸越小,数据压缩得越厉害。
  2. 步长(Stride):控制池化窗口每次滑动的距离。一般情况下,步长会设置得和池化窗口大小相等(如池化窗口 2×2 时,步长设为 2),以保证不重复、不遗漏地覆盖整个特征图。当然,也可以根据需求调整步长,但要确保池化窗口能完整遍历特征图。
  3. 填充(Padding):和卷积层类似,有时为了保持输出特征图的尺寸符合特定要求,会在输入特征图边缘填充 0。不过在最大池化中,填充使用得相对较少 。

最大池化的作用

  1. 减少数据量和计算量:通过下采样,大幅降低特征图的尺寸,进而减少后续网络层需要处理的数据量,降低计算复杂度,加快模型训练和推理速度。
  2. 提高模型鲁棒性:最大池化对输入数据的局部位置变化不敏感。即使特征在区域内发生小范围偏移,由于选取的是最大值,输出结果也不会受到太大影响,这使得模型在面对图像轻微平移、旋转等变化时,依然能稳定地提取关键特征,增强了模型的泛化能力。
  3. 防止过拟合:减少数据量相当于降低了模型对训练数据的依赖程度,避免模型过度学习训练数据中的噪声和细节,从而在一定程度上防止过拟合现象的发生。

PyTorch 中的最大池化实现

在 PyTorch 中,使用nn.MaxPool2d类可以方便地实现最大池化操作,示例代码如下:

import torch
import torch.nn as nn

# 定义一个最大池化层,池化窗口大小为2×2,步长为2
pool_layer = nn.MaxPool2d(kernel_size=2, stride=2)

# 假设前面卷积层输出的特征图大小为(1, 6, 26, 26),1表示批次大小,6是通道数,26×26是特征图尺寸
input_feature_map = torch.randn(1, 6, 26, 26)

# 进行最大池化操作
output = pool_layer(input_feature_map)
print(output.shape)  # 输出:torch.Size([1, 6, 13, 13])

上述代码中,先创建了nn.MaxPool2d对象,指定池化窗口和步长参数。然后生成模拟的输入特征图数据,传入池化层计算后,得到输出特征图,其尺寸变为原来的一半(在长和宽方向上),而通道数保持不变。
 注意:采取不同的卷积层➕池化层是神经网络框架构建的常用操作


3.非线性激活层

非线性激活层的基本概念

        激活层的作用是对神经网络中前一层的输出进行转换,引入非线性因素。在神经网络的计算过程中,线性层(如全连接层、卷积层)的输出结果往往是一个线性组合,如果仅由线性层堆叠而成,整个网络的输出也只是输入的线性变换,其表达能力非常有限。而激活层通过特定的激活函数,将线性输出映射为非线性输出,从而让神经网络能够学习和表示复杂的非线性关系。

常见的激活函数

  1. ReLU(Rectified Linear Unit,修正线性单元)
    • 公式f(x)=max(0,x),即当输入 x 大于 0 时,输出为 x;当输入 x 小于等于 0 时,输出为 0。
    • 特点:计算简单高效,能够有效缓解梯度消失问题,加快网络的训练速度。在实际应用中,ReLU 是使用最广泛的激活函数之一。许多研究表明,使用 ReLU 作为激活函数的神经网络在收敛速度和性能上都有不错的表现。例如,在图像识别领域的大量模型中,ReLU 被用于卷积层和全连接层之后,帮助网络快速提取和学习图像特征。
    • 缺点:存在 “Dead ReLU” 问题,即当输入为负数时,神经元将不再被激活,导致该神经元的梯度始终为 0,参数无法更新,从而使得网络的部分神经元失去作用。
  2. Sigmoid
    • 公式:f(x) = \frac{1}{1 + e^{-x}}它将输入映射到 \((0, 1)\) 区间内。
    • 特点:输出范围固定,非常适合用于二分类问题的输出层,将输出解释为属于某一类别的概率。例如,在判断邮件是否为垃圾邮件的二分类任务中,Sigmoid 函数可以将网络的输出转换为一个概率值,直观地表示邮件是垃圾邮件的可能性。
    • 缺点:容易出现梯度消失问题,当输入值较大或较小时,函数的梯度接近 0,导致参数更新缓慢,训练时间变长。此外,Sigmoid 函数的输出不是以 0 为中心,这可能会影响网络的收敛速度。
  3. Tanh(双曲正切函数)
    • 公式f(x) = \frac{e^{x} - e^{-x}}{e^{x} + e^{-x}}其输出范围是 \((-1, 1)\),相比 Sigmoid 函数,Tanh 的输出以 0 为中心。
    • 特点:在一些需要输出有正负之分的场景中较为适用,比如自然语言处理中的词性标注任务,某些词性可能具有正向或负向的语义倾向,Tanh 函数的输出特性可以更好地适应这类任务。
    • 缺点:同样存在梯度消失问题,并且计算复杂度相对较高,在实际应用中逐渐被更高效的激活函数所替代。
  4. Leaky ReLU
    • 公式f(x) = \begin{cases} x & \text{if } x > 0 \\ \alpha x & \text{if } x \leq 0 \end{cases}其中 \alpha是一个很小的正数(如 0.01),这是为了解决 ReLU 的 “Dead ReLU” 问题。
    • 特点:在保持 ReLU 计算简单、缓解梯度消失优点的同时,为负数输入提供了一个非零的梯度,使得神经元在输入为负数时也能被激活,一定程度上避免了神经元 “死亡” 的情况。

激活层在神经网络中的位置

        激活层通常紧跟在线性层(如卷积层、全连接层)之后,对线性层的输出进行非线性变换。例如,在卷积神经网络(CNN)中,一个典型的结构是 “卷积层 - 激活层 - 池化层”,激活层将卷积层提取到的线性特征转换为非线性特征,然后再通过池化层进行下采样;在全连接神经网络中,每一个全连接层之后一般也会添加激活层,逐步将输入数据映射到复杂的非线性空间中,以适应任务的需求。

PyTorch 中的激活层实现

在 PyTorch 中,实现激活层非常方便,以下是几个常见激活函数的使用示例:

import torch
import torch.nn as nn

# 使用ReLU激活函数
relu = nn.ReLU()
input_tensor = torch.randn(1, 6, 13, 13)
output_relu = relu(input_tensor)
print(output_relu.shape)

# 使用Sigmoid激活函数
sigmoid = nn.Sigmoid()
output_sigmoid = sigmoid(input_tensor)
print(output_sigmoid.shape)

# 使用Tanh激活函数
tanh = nn.Tanh()
output_tanh = tanh(input_tensor)
print(output_tanh.shape)

# 使用LeakyReLU激活函数
leaky_relu = nn.LeakyReLU(negative_slope=0.01)
output_leaky_relu = leaky_relu(input_tensor)
print(output_leaky_relu.shape)

上述代码中,分别实例化了 ReLU、Sigmoid、Tanh 和 LeakyReLU 激活函数对象,并对随机生成的输入张量进行激活操作,输出结果的形状与输入张量相同,只是数值经过了激活函数的非线性变换。 


4.线性层

线性层的基本概念

        在深度学习的神经网络体系里,线性层(也被称作全连接层)是最为基础且关键的组成部分。它承担着将输入特征进行线性变换,从而映射到输出特征的重要任务,为整个网络的信息传递和特征整合奠定了基础。

        线性层是基于线性代数中的线性变换原理构建的。对于输入向量 \mathbf{x}线性层通过一个线性方程 y=Wx+b来计算输出向量 y。其中,W 是权重矩阵,b是偏置向量。权重矩阵 W中的每一行代表了从输入特征到输出特征的一组权重系数,它决定了输入特征对输出特征的贡献程度;偏置向量 b 则为每个输出特征提供了一个额外的常数偏移,增加了模型的灵活性。

        在学习过程中,W和b即为我们不断训练的参数

线性层的工作原理

        假设我们有一个输入向量 \mathbf{x}其维度为 n,即 \mathbf{x} = [x_1, x_2, \cdots, x_n]^T。同时,线性层的输出向量 y维度为 m,即\mathbf{y} = [y_1, y_2, \cdots, y_m]^T那么,权重矩阵 W就是一个 m \times n的矩阵,偏置向量 b是一个 m 维向量。

        具体计算过程如下: 对于输出向量y 中的每一个元素y_i(i = 1, 2, \cdots, m,其计算公式为:y_i = sum_{j=1}^{n} w_{ij}x_j + b_i其中,w_{ij}是权重矩阵 W 中第 i 行第 j 列的元素,b_i是偏置向量 b 中的第 i 个元素。

        通过这种方式,线性层将输入向量 x 中的各个特征按照不同的权重进行组合,并加上偏置项,得到输出向量 y .这个过程实现了从输入特征空间到输出特征空间的线性映射。

线性层在神经网络中的作用

  1. 特征整合与转换:线性层可以将前一层提取到的各种特征进行整合和转换。
             例如,在图像分类任务中,卷积层和池化层会提取出图像的各种局部特征,而线性层可以将这些局部特征进行综合,形成更高级、更抽象的特征表示,以便后续进行分类决策。
  2. 分类与回归:在线性层的输出端,通过选择合适的激活函数,实现分类或回归任务。
             如果是分类任务,通常会使用 Softmax 激活函数将输出转换为概率分布,从而确定输入样本属于各个类别的概率;如果是回归任务,则可以直接使用线性层的输出作为预测值。
  3. 模型的可解释性:线性层的权重矩阵和偏置向量具有一定的可解释性。
           通过分析权重矩阵中的元素,可以了解输入特征对输出特征的影响程度,从而为模型的决策提供一定的解释依据。

PyTorch 中的线性层实现

在 PyTorch 中,使用 nn.Linear 类可以方便地定义线性层。以下是一个简单的示例代码:

import torch
import torch.nn as nn

# 定义一个线性层,输入特征数为 100,输出特征数为 50
linear_layer = nn.Linear(100, 50)

# 随机生成一个大小为 (1, 100) 的输入张量
input_vector = torch.randn(1, 100)

# 进行线性变换
output = linear_layer(input_vector)
print(output.shape)

在上述代码中,首先创建了一个 nn.Linear 对象,指定输入特征数为 100,输出特征数为 50。然后,随机生成一个大小为 (1, 100) 的输入张量,表示一个样本具有 100 个特征。最后,将输入张量传入线性层进行线性变换,得到输出张量,其形状为 (1, 50),表示输出的样本具有 50 个特征。

线性层的参数初始化与优化

         在实际应用中,线性层的权重矩阵和偏置向量需要进行合适的初始化,以确保模型能够快速收敛。
        常见的初始化方法包括随机初始化、 Xavier 初始化和 He 初始化等。同时,为了训练线性层的参数,需要使用优化算法(如随机梯度下降、Adam 等)来最小化损失函数。通过不断调整权重矩阵和偏置向量的值,使得模型的输出尽可能接近真实标签。


5.flatten层

Flatten 层的基本概念

        Flatten 层的核心任务是改变数据的形状,将输入的多维张量转换为一维张量,且不改变数据总量。例如,对于形状为 (batch_size, channels, height, width) 的图像特征图,Flatten 层会把它重新排列成 (batch_size, channels * height * width) 的一维向量,其中 batch_size 表示数据批次大小,channels 是特征图通道数,height 和 width 分别是特征图的高度和宽度 。

Flatten 层的工作原理

        假设卷积层和池化层处理后,得到一个形状为 (2, 3, 4, 4) 的特征图张量,这里 2 代表批次中样本数量,3 是通道数,4×4 是特征图尺寸。Flatten 层会按顺序将每个样本的所有通道、所有空间位置的元素依次排列:
        先将第一个样本的 3 个通道、共 4×4×3 个元素依次展开成一维向量,接着再将第二个样本的同样 4×4×3 个元素依次排列在其后,最终得到形状为 (2, 48) 的输出张量(因为 4×4×3=48 )。

Flatten 层在神经网络中的作用

  1. 适配全连接层输入:全连接层要求输入是一维向量,Flatten 层将卷积层输出的多维特征图转换为合适的形状,让全连接层能基于整合后的特征进行计算。比如在图像分类任务中,卷积和池化操作提取图像特征后,需经 Flatten 层处理,才能输入到全连接层完成分类预测。
  2. 整合特征信息:通过将多维特征图展平,Flatten 层把不同通道、不同空间位置的特征信息整合到一维向量中,使得后续全连接层能综合利用这些信息,学习到更复杂的特征关系 。

PyTorch 中的 Flatten 层实现

在 PyTorch 中,使用nn.Flatten类可以轻松定义 Flatten 层,以下是示例代码:

import torch
import torch.nn as nn

# 定义Flatten层
flatten = nn.Flatten()

# 假设前面卷积和池化后得到的特征图大小为(1, 6, 13, 13)
# 其中1是批次大小,6是通道数,13×13是特征图尺寸
input_feature_map = torch.randn(1, 6, 13, 13)

# 进行Flatten操作
output = flatten(input_feature_map)
print(output.shape)  # 输出:torch.Size([1, 1014])

上述代码中,先实例化nn.Flatten对象,接着生成模拟的多维特征图张量,传入 Flatten 层后,输出张量形状变为(1, 1014) ,其中1是批次大小,10146×13×13计算得出,完成了从多维到一维的转换。



6.loss(cost)函数

损失函数的基本概念

        损失函数,也称为代价函数(Cost Function),是一个用于评估模型预测值与真实标签之间差异的函数。模型的训练过程,本质上就是通过不断调整参数,使得损失函数的值最小化的过程。一个合适的损失函数能够准确地反映模型的性能优劣,为模型的优化提供明确的目标。

常见的损失函数

  1. 均方误差(Mean Squared Error,MSE)
    • 公式:对于一组预测值\hat{y}_i和真实值 y_ii = 1, 2, \cdots, n)均方误差的计算公式为 MSE = \frac{1}{n} \sum_{i=1}^{n} (\hat{y}_i - y_i)^2
    • 适用场景:均方误差常用于回归任务,例如预测房价、股票价格等连续数值。它通过计算预测值与真实值之间差值的平方的平均值,来衡量模型的预测误差。差值的平方使得较大的误差得到更大的权重,从而更严格地惩罚那些偏离真实值较远的预测结果。
    • 特点:均方误差函数是一个可导的凸函数,这使得它在优化过程中可以使用梯度下降等优化算法来寻找最小值。同时,它对数据中的噪声相对敏感,因为每个样本的误差都会对最终的损失值产生影响。
  2. 交叉熵损失(Cross Entropy Loss)
    • 公式:在二分类问题中,交叉熵损失的计算公式为 L = -[y \log(\hat{y}) + (1 - y) \log(1 - \hat{y})]其中 y 是真实标签(取值为 0 或 1),\hat{y}是模型预测为正类的概率。在多分类问题中,通常使用 Softmax 函数将模型的输出转换为概率分布,然后计算交叉熵损失,公式为 L = -\sum_{i=1}^{C} y_i \log(\hat{y}_i,其中 C 是类别数,y_i真实标签中第 i 类的概率(通常为 0 或 1),\hat{y}_i是模型预测第 i 类的概率。
    • 适用场景:交叉熵损失广泛应用于分类任务,它衡量的是两个概率分布之间的差异,能够有效地反映模型在分类问题上的性能。在实际应用中,交叉熵损失常常与 Softmax 函数结合使用,将模型的输出转换为概率分布,然后计算损失值。
    • 特点:交叉熵损失对于分类问题具有很好的区分能力,当模型的预测结果与真实标签相差较大时,损失值会迅速增大,从而促使模型更快地调整参数。与均方误差不同,交叉熵损失对预测概率的变化更加敏感,能够更准确地反映模型在分类任务上的优劣。

损失函数在模型训练中的作用

        在模型训练过程中,损失函数是评估模型性能的重要指标。每次迭代时,模型根据当前的参数对输入数据进行预测,然后计算预测结果与真实标签之间的损失值。优化算法(如随机梯度下降、Adam 等)根据损失函数的梯度来调整模型的参数,使得损失函数的值逐渐减小。通过不断重复这个过程,模型的预测能力逐渐提高,最终达到较好的性能。

PyTorch 中的损失函数实现

在 PyTorch 中,实现常见的损失函数非常方便。以下是一些示例代码:

import torch
import torch.nn as nn

# 均方误差损失函数
mse_loss = nn.MSELoss()
# 随机生成预测值和真实值
predictions = torch.randn(1, 5)
targets = torch.randn(1, 5)
# 计算损失
loss_mse = mse_loss(predictions, targets)
print("均方误差损失:", loss_mse)

# 交叉熵损失函数
ce_loss = nn.CrossEntropyLoss()
# 随机生成分类预测和标签
class_predictions = torch.randn(3, 10)
class_targets = torch.tensor([1, 3, 2])
# 计算损失
loss_ce = ce_loss(class_predictions, class_targets)
print("交叉熵损失:", loss_ce)


7.反向传播

1.反向传播的基本概念

        在深度学习领域,反向传播(Backpropagation)算法是驱动神经网络模型优化的核心动力。当我们构建好一个神经网络,并确定了损失函数来衡量模型预测与真实结果的差距后,反向传播算法就负责计算如何调整模型中的参数,以最小化这个差距,从而让模型的性能不断提升。

        反向传播,简单来说,就是基于链式法则(Chain Rule)从神经网络的输出层开始,将损失函数关于输出的梯度,按照网络的连接结构反向传播,依次计算每一层参数(如权重和偏置)的梯度,进而指导参数更新的过程。其核心目的是在给定输入和目标输出的情况下,通过调整网络参数,使得损失函数的值尽可能小。

2.反向传播的工作原理

  1. 前向传播(Forward Propagation):在进行反向传播之前,首先要进行前向传播。输入数据从神经网络的输入层进入,依次经过各个隐藏层的计算,最终得到输出层的预测结果。在这个过程中,每一层的神经元根据输入和该层的权重进行线性组合,然后通过激活函数引入非线性变换,逐步将输入数据映射到输出空间。例如,对于一个包含全连接层和激活层的简单神经网络,输入数据\mathbf{x}经过第一层全连接层的线性变换 \mathbf{z}_1 = \mathbf{W}_1\mathbf{x} + \mathbf{b}_1再通过激活函数 \a_1 = f(\mathbf{z}_1)第一层的输出,以此类推,直到得到输出层的预测值 \hat{\mathbf{y}}
  2. 计算损失函数:根据输出层的预测值\hat{\mathbf{y}}和真实标签 y,计算损失函数 L(\hat{\mathbf{y}}, \mathbf{y})值,如均方误差或交叉熵损失等。这个损失值反映了模型当前预测结果与真实情况之间的差异。
  3. 反向传播计算梯度:从输出层开始,计算损失函数关于输出层神经元的梯度 \frac{\partial L}{\partial \mathbf{z}_L}其中\mathbf{z}_L 是输出层的线性组合结果)。然后,根据链式法则,将这个梯度反向传播到上一层,计算出损失函数关于上一层神经元的梯度 \frac{\partial L}{\partial \mathbf{z}_{L - 1}}。具体来说,对于第 l 层和第 \(l + 1\) 层,有\frac{\partial L}{\partial \mathbf{z}_l} = (\mathbf{W}_{l + 1}^T \frac{\partial L}{\partial \mathbf{z}_{l + 1}}) \odot f'(\mathbf{z}_l)其中 \mathbf{W}_{l + 1}是第 l + 1 层的权重矩阵,f'(\mathbf{z}_l)是第 l 层激活函数的导数,\odot 表示元素相乘。通过不断重复这个过程,依次计算出每一层的梯度。
  4. 更新参数:根据计算得到的每一层参数的梯度,使用优化算法(如随机梯度下降、Adam 等)来更新网络中的参数。例如,对于权重矩阵W 和偏置向量 b,更新公式为 \mathbf{W} \leftarrow \mathbf{W} - \eta \frac{\partial L}{\partial \mathbf{W}}和 \mathbf{b} \leftarrow \mathbf{b} - \eta \frac{\partial L}{\partial \mathbf{b}}其中 \eta是学习率,控制参数更新的步长。

3.传播在神经网络中的作用

  1. 优化模型参数:反向传播算法使得神经网络能够根据损失函数的反馈,自动调整参数,从而不断降低损失值,提高模型的预测准确性。通过多次迭代的反向传播和参数更新,模型逐渐学习到数据中的模式和规律,实现对各种任务的有效处理。
  2. 提高模型泛化能力:合理的反向传播过程有助于模型在训练数据上学习到具有代表性的特征,并且通过适当的正则化等手段,避免模型过拟合,从而提高模型对未知数据的泛化能力,使其在实际应用中能够更可靠地进行预测和决策。

4.PyTorch 中的反向传播实现

        在 PyTorch 中,反向传播的实现相对简洁。PyTorch 会自动构建计算图(Computational Graph),记录前向传播过程中的计算步骤,当调用损失函数的 backward() 方法时,就会自动沿着计算图反向传播,计算出所有参数的梯度。以下是一个简单的示例代码:

import torch
import torch.nn as nn
import torch.optim as optim

# 定义一个简单的神经网络模型
class SimpleNet(nn.Module):
    def __init__(self):
        super(SimpleNet, self).__init__()
        self.fc1 = nn.Linear(10, 5)
        self.fc2 = nn.Linear(5, 2)

    def forward(self, x):
        x = torch.relu(self.fc1(x))
        x = self.fc2(x)
        return x

model = SimpleNet()
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(model.parameters(), lr=0.01)

# 假设输入数据和标签
input_data = torch.randn(1, 10)
labels = torch.tensor([1])

# 前向传播
outputs = model(input_data)
loss = criterion(outputs, labels)

# 反向传播和参数更新
optimizer.zero_grad()  # 清空之前的梯度
loss.backward()  # 反向传播计算梯度
optimizer.step()  # 更新参数

在上述代码中,首先定义了一个简单的神经网络模型,然后选择了交叉熵损失函数和随机梯度下降优化器。在训练过程中,通过前向传播得到模型的输出并计算损失,接着调用 loss.backward() 进行反向传播计算梯度,最后使用 optimizer.step() 更新模型的参数。


8.深度学习中 PyTorch 优化器的使用

在深度学习里,优化器起着关键作用。它负责根据损失函数的梯度来更新模型的参数,从而让模型的性能得到优化。在 PyTorch 中,torch.optim 模块提供了各种优化器。

        1.优化器的基本使用流程

在 PyTorch 中使用优化器,一般遵循以下几个步骤:

  1. 定义模型:首先要定义好神经网络模型。
  2. 选择损失函数:依据具体的任务类型,挑选合适的损失函数,如分类任务常用交叉熵损失,回归任务常用均方误差损失。
  3. 选择优化器:从 torch.optim 模块里选择合适的优化器,并传入模型的参数和学习率等超参数。
  4. 训练循环:在训练循环里,进行前向传播、计算损失、反向传播计算梯度,最后使用优化器更新参数。
  5. 注意:

    在训练时,每次反向传播计算梯度后,梯度信息会被累积在对应的参数张量(tensor)中。如果不清零梯度,在下一次计算梯度时,这些梯度将会被新计算的梯度累加,导致梯度信息错误。即,先进行optimizer.zero_grad()再进行计算loss,进行反向传播loss.backward()之后再进行optim.step()

        2.常见优化器及其特点

随机梯度下降(Stochastic Gradient Descent, SGD)

这是最基础的优化器。它每次迭代只使用一个样本或一小批样本(mini - batch)来计算梯度并更新参数。

  • 特点:简单易懂,实现方便。不过,它的收敛速度可能较慢,并且容易陷入局部最优解。
  • 代码示例
import torch
import torch.nn as nn
import torch.optim as optim

# 定义一个简单的模型
class SimpleModel(nn.Module):
    def __init__(self):
        super(SimpleModel, self).__init__()
        self.fc = nn.Linear(10, 1)

    def forward(self, x):
        return self.fc(x)

model = SimpleModel()
# 定义损失函数
criterion = nn.MSELoss()
# 定义优化器,使用 SGD
optimizer = optim.SGD(model.parameters(), lr=0.01)

# 模拟输入和标签
inputs = torch.randn(100, 10)
labels = torch.randn(100, 1)

# 训练循环
for epoch in range(10):
    optimizer.zero_grad()  # 清空梯度
    outputs = model(inputs)  # 前向传播
    loss = criterion(outputs, labels)  # 计算损失
    loss.backward()  # 反向传播
    optimizer.step()  # 更新参数
    print(f'Epoch {epoch + 1}, Loss: {loss.item()}')

带动量的随机梯度下降(SGD with Momentum)

在 SGD 的基础上引入了动量的概念。动量可以帮助参数更新在同一方向上加速,减少震荡,从而加快收敛速度。

  • 特点:能够更快地跳出局部最优解,在很多情况下收敛速度比普通 SGD 快。
  • 代码示例
# 定义优化器,使用带动量的 SGD
optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.9)

Adagrad

Adagrad 会自适应地为每个参数调整学习率。对于经常更新的参数,学习率会变小;对于不常更新的参数,学习率会变大。

  • 特点:适合处理稀疏数据,能够自动调整不同参数的学习率。不过,它的学习率可能会在训练后期变得非常小,导致收敛速度变慢。
  • 代码示例
# 定义优化器,使用 Adagrad
optimizer = optim.Adagrad(model.parameters(), lr=0.01)

RMSprop

RMSprop 是对 Adagrad 的改进,它通过指数移动平均的方式来调整学习率,避免了 Adagrad 学习率过早衰减的问题。

  • 特点:能够在不同参数上自适应地调整学习率,并且在训练过程中保持学习率的相对稳定。
  • 代码示例
# 定义优化器,使用 RMSprop
optimizer = optim.RMSprop(model.parameters(), lr=0.01)

Adam

Adam 结合了动量和自适应学习率的思想,它在训练过程中同时考虑了梯度的一阶矩(均值)和二阶矩(方差),能够自适应地调整每个参数的学习率。

  • 特点:收敛速度快,在很多任务中都能取得较好的效果,是目前应用最广泛的优化器之一。
  • 代码示例
# 定义优化器,使用 Adam
optimizer = optim.Adam(model.parameters(), lr=0.01)

 

优化器的其他操作

调整学习率

在训练过程中,有时候需要根据训练的进度调整学习率。可以使用 torch.optim.lr_scheduler 模块来实现学习率的动态调整。例如,使用 StepLR 可以每隔一定的 epoch 降低学习率:

from torch.optim.lr_scheduler import StepLR

# 定义优化器
optimizer = optim.SGD(model.parameters(), lr=0.01)
# 定义学习率调度器,每 5 个 epoch 学习率乘以 0.1
scheduler = StepLR(optimizer, step_size=5, gamma=0.1)

# 训练循环
for epoch in range(20):
    optimizer.zero_grad()
    outputs = model(inputs)
    loss = criterion(outputs, labels)
    loss.backward()
    optimizer.step()
    scheduler.step()  # 更新学习率
    print(f'Epoch {epoch + 1}, Loss: {loss.item()}, LR: {optimizer.param_groups[0]["lr"]}')
保存和加载优化器状态

在训练过程中,可能需要保存和加载优化器的状态,以便在中断后继续训练。可以使用 state_dict() 方法保存优化器的状态,使用 load_state_dict() 方法加载状态:
 

# 保存优化器状态
torch.save(optimizer.state_dict(), 'optimizer_state.pth')

# 加载优化器状态
optimizer.load_state_dict(torch.load('optimizer_state.pth'))

四、相关训练流程套路:

1.训练循环:

for data in dataloader循环:
这个循环通常用于遍历数据集中的每个批次(batch)数据。
在每次迭代中,for data in dataloader会从数据加载器中获取一个批次的数据,然后你可以对这个批次的数据进行前向传播、计算损失、反向传播和参数更新等操作。
这个循环通常嵌套在训练循环中,用于处理每个训练批次的数据。


for epoch in range(X)循环:
这个循环用于控制整个训练过程的迭代次数,其中X代表训练的总轮数(epochs)。
一个epoch表示将数据集中的所有样本都用于训练一次,通常情况下,训练过程会重复多个epoch以便模型能够更好地学习数据的特征。
在每个epoch循环中,你会执行多次for data in dataloader循环,每次处理一个批次的数据,并进行前向传播、损失计算、反向传播和参数更新等训练步骤。
一般来说,训练过程会在每个epoch结束时进行模型评估,例如计算验证集上的准确率或损失,以便监控模型的训练情况和避免过拟合。

for data in dataloader循环用于处理单个批次的数据,而for epoch in range(X)循环用于控制整个训练过程的迭代次数,确保模型能够在整个数据集上进行多次学习和优化。在实际的训练过程中,这两个循环通常会结合使用,以完成模型的训练任务。

from torch import nn
from torch.nn import Module
from torch.nn import Conv2d
from torch.nn import MaxPool2d,Flatten,Linear, Sequential
import torch
import torchvision
from torch.utils.data import DataLoader
from torch.utils.tensorboard import SummaryWriter 

dataset = torchvision.datasets.CIFAR10("./dataset",train= False, transform =torchvision.transforms.ToTensor(), download=True)
dataloader = DataLoader(dataset, batch_size=1, drop_last=True)

#使用sequential
class Tudui(nn.Module):
    def __init__(self):
        super(Tudui,self).__init__()
        self.model1 = Sequential(
            Conv2d(3, 32, 5, padding=2),
            MaxPool2d(2),
            Conv2d(32, 32, 5, padding=2),
            MaxPool2d(2),
            Conv2d(32,64,5, padding=2),
            MaxPool2d(2),
            Flatten(),
            Linear(1024,64),
            Linear(64, 10)   
        )
    def forward(self, x):
        x = self.model1(x)
        return x

loss = nn.CrossEntropyLoss()
tudui = Tudui()
optim = torch.optim.SGD(tudui.parameters(), lr=0.01, )
for epoch in range(20):
    running_loss = 0.0
    for data in dataloader:
        imgs, targets = data
        outputs = tudui(imgs)
        result_loss = loss(outputs, targets)
        optim.zero_grad()
        result_loss.backward()
        optim.step()
        running_loss = running_loss +result_loss # running_loss相当于扫一遍全部数据的loss总和
    print(running_loss)
        

 


2.使用pytorch官方内置的网络模型

以 VGG16 模型为例,微调以用于自定义任务

通常情况下,我们需要对预训练模型进行微调,以适应特定的任务,如自定义的图像分类任务。以下是一个简单的微调示例,假设我们的任务是对 10 个类别的图像进行分类:

import torch
import torchvision.models as models
import torch.nn as nn

# 加载预训练的 VGG16 模型
vgg16 = models.vgg16(pretrained=True)

# 冻结所有卷积层的参数,不进行更新
for param in vgg16.parameters():
    param.requires_grad = False

# 修改全连接层,以适应我们的 10 类分类任务
num_ftrs = vgg16.classifier[6].in_features
vgg16.classifier[6] = nn.Linear(num_ftrs, 10)

# 定义损失函数和优化器
criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(vgg16.classifier.parameters(), lr=0.001)

# 假设我们有自定义的数据集和数据加载器
# 这里省略了数据集和数据加载器的具体定义
# 以下是一个简单的训练循环示例
for epoch in range(10):
    running_loss = 0.0
    for i, data in enumerate(train_loader, 0):
        inputs, labels = data
        optimizer.zero_grad()

        outputs = vgg16(inputs)
        loss = criterion(outputs, labels)
        loss.backward()
        optimizer.step()

        running_loss += loss.item()
    print(f'Epoch {epoch + 1}, Loss: {running_loss / len(train_loader)}')

在上述代码中:

  1. 首先加载预训练的 VGG16 模型。
  2. 通过设置 param.requires_grad = False 冻结了所有卷积层的参数,这样在训练过程中这些参数不会被更新,从而保留了预训练模型学习到的通用特征。
  3. 修改了全连接层的最后一层,将其输出维度调整为 10,以适应 10 类分类任务。
  4. 定义了损失函数和优化器,这里只对全连接层的参数进行优化。
  5. 最后是一个简单的训练循环,用于对模型进行微调训练。

补充:使用微调后的 VGG16 模型进行预测

在训练好模型后,我们可以使用它进行预测。以下是一个简单的预测示例

import torch
import torchvision.models as models
import torchvision.transforms as transforms
from PIL import Image

# 加载预训练的 VGG16 模型(假设已经完成微调训练)
vgg16 = models.vgg16(pretrained=True)
# 假设我们修改了全连接层以适应 10 类分类任务
num_ftrs = vgg16.classifier[6].in_features
vgg16.classifier[6] = nn.Linear(num_ftrs, 10)

# 加载训练好的模型参数
model_path ='model.pth'  # 模型参数保存的路径及文件名
vgg16.load_state_dict(torch.load(model_path))

# 定义图像预处理操作
transform = transforms.Compose([
    transforms.Resize((224, 224)),
    transforms.ToTensor(),
    transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
])

# 加载测试图像
image = Image.open('test_image.jpg').convert('RGB')
image = transform(image).unsqueeze(0)

# 进行预测
with torch.no_grad():
    outputs = vgg16(image)
    _, predicted = torch.max(outputs, 1)
    print(f'Predicted class: {predicted.item()}')

在上述预测代码中:

  1. 加载预训练并微调好的 VGG16 模型。
  2. 定义了图像预处理操作,包括调整图像大小、转换为张量以及归一化等,以符合模型输入的要求。
  3. 加载测试图像并进行预处理。
  4. 使用 torch.no_grad() 上下文管理器,在不计算梯度的情况下进行预测,提高预测效率。最后获取预测结果并输出预测的类别。

3.PyTorch 中模型的保存与读取

保存:

1. 仅保存模型参数(推荐方式)

通常情况下,我们仅保存模型的参数,因为这样保存的文件相对较小,且更具灵活性。后续可以根据需求重新定义模型结构并加载参数。

import torch
import torch.nn as nn

# 定义一个简单的模型
class SimpleModel(nn.Module):
    def __init__(self):
        super(SimpleModel, self).__init__()
        self.fc = nn.Linear(10, 1)

    def forward(self, x):
        return self.fc(x)

model = SimpleModel()

# 保存模型参数
torch.save(model.state_dict(),'model_params.pth')

在上述代码中,model.state_dict() 会返回一个包含模型所有参数的字典,torch.save() 函数将这个字典保存到指定文件 model_params.pth 中。

2. 保存整个模型

除了保存参数,还可以保存整个模型,包括模型的结构和参数。不过这种方式保存的文件较大,且在加载时可能会受模型定义代码的影响。

# 保存整个模型
torch.save(model,'model_entire.pth')

读取:

1. 读取模型参数

读取模型参数时,需要先定义好与保存时相同结构的模型,然后使用 load_state_dict() 方法加载参数。

# 重新定义模型
new_model = SimpleModel()

# 加载模型参数
new_model.load_state_dict(torch.load('model_params.pth'))
new_model.eval()  # 设置为评估模式

# 进行预测示例
input_tensor = torch.randn(1, 10)
output = new_model(input_tensor)
print(output)

在上述代码中,torch.load('model_params.pth') 会加载保存的参数,new_model.load_state_dict() 将参数赋值给新模型。new_model.eval() 用于将模型设置为评估模式,在预测时这是必要的。

2.读取整个模型

如果保存的是整个模型,读取时可以直接加载,无需重新定义模型结构。

# 加载整个模型
loaded_model = torch.load('model_entire.pth')
loaded_model.eval()  # 设置为评估模式

# 进行预测示例
input_tensor = torch.randn(1, 10)
output = loaded_model(input_tensor)
print(output)

保存和读取模型用于继续训练

在实际训练中,可能需要在训练过程中保存模型,以便后续继续训练。此时,除了保存模型参数,还需要保存优化器的状态和当前的训练轮数等信息。

import torch.optim as optim

# 定义模型和优化器
model = SimpleModel()
optimizer = optim.SGD(model.parameters(), lr=0.01)
epoch = 10

# 保存模型、优化器状态和训练轮数
checkpoint = {
    'epoch': epoch,
    'model_state_dict': model.state_dict(),
    'optimizer_state_dict': optimizer.state_dict()
}
torch.save(checkpoint, 'checkpoint.pth')

# 加载检查点并继续训练
checkpoint = torch.load('checkpoint.pth')
new_model = SimpleModel()
new_optimizer = optim.SGD(new_model.parameters(), lr=0.01)

new_model.load_state_dict(checkpoint['model_state_dict'])
new_optimizer.load_state_dict(checkpoint['optimizer_state_dict'])
start_epoch = checkpoint['epoch']

# 继续训练
for epoch in range(start_epoch, 20):
    # 训练代码
    pass

在上述代码中,通过字典 checkpoint 保存了模型参数、优化器状态和当前训练轮数。加载时,从字典中提取相应信息并赋值给新的模型和优化器,然后从保存的轮数开始继续训练。


4.torch.no_grad() 

原理

在 PyTorch 里,默认情况下,当对张量进行操作时,会构建一个计算图(Computational Graph)来记录所有的操作步骤,以便后续进行反向传播计算梯度。而 torch.no_grad() 会暂时关闭这个自动求导功能,即不会为在其上下文环境中执行的操作构建计算图,从而避免了不必要的梯度计算和内存开销。

import torch
import torch.nn as nn

# 定义一个简单的模型
class SimpleModel(nn.Module):
    def __init__(self):
        super(SimpleModel, self).__init__()
        self.fc = nn.Linear(10, 1)

    def forward(self, x):
        return self.fc(x)

# 初始化模型并加载预训练参数(这里省略加载过程)
model = SimpleModel()
model.eval()  # 设置为评估模式

# 生成输入数据
input_tensor = torch.randn(1, 10)

# 使用 torch.no_grad() 进行推理
with torch.no_grad():
    output = model(input_tensor)
    print("模型推理结果:", output)

 在上述代码中,with torch.no_grad(): 语句块内的操作不会构建计算图,因此在进行模型推理时不会计算梯度,从而提高了推理效率。

验证阶段示例

# 假设我们有一个验证数据集和数据加载器
# 这里简单模拟验证数据
val_inputs = torch.randn(100, 10)
val_labels = torch.randn(100, 1)

total_loss = 0
criterion = nn.MSELoss()

with torch.no_grad():
    for i in range(len(val_inputs)):
        input_data = val_inputs[i].unsqueeze(0)
        target = val_labels[i].unsqueeze(0)
        output = model(input_data)
        loss = criterion(output, target)
        total_loss += loss.item()

average_loss = total_loss / len(val_inputs)
print("验证集平均损失:", average_loss)

在验证阶段,使用 torch.no_grad() 可以避免在计算验证损失时计算梯度,从而节省计算资源。


5.分类问题的正确率如何计算?

单标签分类问题

单标签分类问题指的是每个样本只属于一个类别。计算正确率的基本思路是统计模型预测正确的样本数量,然后除以总样本数量。

代码示例

import torch
import torch.nn as nn

# 定义一个简单的分类模型
class SimpleClassifier(nn.Module):
    def __init__(self):
        super(SimpleClassifier, self).__init__()
        self.fc = nn.Linear(10, 5)  # 假设输入特征维度为 10,类别数为 5

    def forward(self, x):
        return self.fc(x)

# 初始化模型
model = SimpleClassifier()

# 假设我们有预测结果和真实标签
# 预测结果的形状为 (batch_size, num_classes),表示每个样本属于各个类别的得分
predictions = torch.tensor([[0.2, 0.5, 0.1, 0.1, 0.1],
                            [0.1, 0.1, 0.6, 0.1, 0.1],
                            [0.1, 0.1, 0.1, 0.6, 0.1]])
# 真实标签的形状为 (batch_size,),表示每个样本的真实类别索引
labels = torch.tensor([1, 2, 3])

# 使用 max 方法获取每个样本预测得分最高的类别索引
# max 方法返回两个值,第一个是最大值,第二个是最大值所在的索引
_, predicted = torch.max(predictions, 1)

# 使用 sum 方法统计预测正确的样本数量
# predicted == labels 会得到一个布尔型张量,True 表示预测正确,False 表示预测错误
# sum 方法会将布尔型张量中的 True 视为 1,False 视为 0,然后求和
correct = (predicted == labels).sum()

# 使用 item 方法将张量中的单个值转换为 Python 标量
correct = correct.item()

# 计算正确率
accuracy = correct / len(labels)
print(f"单标签分类问题的正确率: {accuracy * 100:.2f}%")

方法解释

  • torch.max(input, dim):该方法用于返回输入张量在指定维度上的最大值及其索引。在上述代码中,torch.max(predictions, 1) 表示在维度 1(即类别维度)上找出最大值及其索引。第一个返回值是最大值组成的张量,我们用 _ 忽略;第二个返回值是最大值所在的索引,即预测的类别索引。
  • torch.sum(input):该方法用于计算输入张量中所有元素的和。在上述代码中,(predicted == labels).sum() 会统计预测正确的样本数量。
  • tensor.item():该方法用于将只包含一个元素的张量转换为 Python 标量。在上述代码中,correct.item() 将 correct 这个只包含一个元素的张量转换为 Python 整数。

多标签分类问题

多标签分类问题是指每个样本可以属于多个类别。计算正确率的方法通常是计算每个样本预测正确的标签数占总标签数的比例,然后对所有样本求平均值。

代码示例

import torch
import torch.nn as nn

# 定义一个简单的多标签分类模型
class SimpleMultiLabelClassifier(nn.Module):
    def __init__(self):
        super(SimpleMultiLabelClassifier, self).__init__()
        self.fc = nn.Linear(10, 5)  # 假设输入特征维度为 10,标签数为 5

    def forward(self, x):
        return torch.sigmoid(self.fc(x))  # 使用 sigmoid 函数将输出转换为概率值

# 初始化模型
model = SimpleMultiLabelClassifier()

# 假设我们有预测结果和真实标签
# 预测结果的形状为 (batch_size, num_labels),表示每个样本的每个标签的预测概率
predictions = torch.tensor([[0.8, 0.2, 0.9, 0.1, 0.7],
                            [0.3, 0.7, 0.2, 0.8, 0.4],
                            [0.6, 0.4, 0.7, 0.3, 0.6]])
# 真实标签的形状为 (batch_size, num_labels),表示每个样本的每个标签的真实值(0 或 1)
labels = torch.tensor([[1, 0, 1, 0, 1],
                       [0, 1, 0, 1, 0],
                       [1, 0, 1, 0, 1]])

# 将预测概率转换为预测的标签值(大于 0.5 视为正标签)
predicted = (predictions > 0.5).float()

# 计算每个样本预测正确的标签数
correct_per_sample = (predicted == labels).sum(dim=1)

# 计算每个样本的总标签数
total_labels_per_sample = labels.sum(dim=1)

# 计算每个样本的正确率
accuracy_per_sample = correct_per_sample / total_labels_per_sample

# 计算平均正确率
average_accuracy = accuracy_per_sample.mean().item()
print(f"多标签分类问题的平均正确率: {average_accuracy * 100:.2f}%")

方法解释

  • torch.sum(input, dim):当指定 dim 参数时,sum 方法会在指定维度上进行求和。在上述代码中,(predicted == labels).sum(dim=1) 表示在维度 1(即标签维度)上求和,得到每个样本预测正确的标签数;labels.sum(dim=1) 表示在维度 1 上求和,得到每个样本的总标签数。

多类别多标签分类问题

多类别多标签分类问题是指每个样本可以属于多个不同类别的情况,且每个类别下可能有多个标签。计算正确率时,通常分别计算每个类别下的正确率,然后对所有类别求平均值。

import torch
import torch.nn as nn

# 定义一个简单的多类别多标签分类模型
class SimpleMultiClassMultiLabelClassifier(nn.Module):
    def __init__(self):
        super(SimpleMultiClassMultiLabelClassifier, self).__init__()
        self.fc = nn.Linear(10, 10)  # 假设输入特征维度为 10,5 个类别,每个类别 2 个标签

    def forward(self, x):
        return torch.sigmoid(self.fc(x))  # 使用 sigmoid 函数将输出转换为概率值

# 初始化模型
model = SimpleMultiClassMultiLabelClassifier()

# 假设我们有预测结果和真实标签
# 预测结果的形状为 (batch_size, num_classes * num_labels_per_class),表示每个样本的每个标签的预测概率
predictions = torch.tensor([[0.8, 0.2, 0.9, 0.1, 0.7, 0.3, 0.8, 0.2, 0.6, 0.4],
                            [0.3, 0.7, 0.2, 0.8, 0.4, 0.6, 0.3, 0.7, 0.5, 0.5],
                            [0.6, 0.4, 0.7, 0.3, 0.6, 0.4, 0.7, 0.3, 0.8, 0.2]])
# 真实标签的形状为 (batch_size, num_classes * num_labels_per_class),表示每个样本的每个标签的真实值(0 或 1)
labels = torch.tensor([[1, 0, 1, 0, 1, 0, 1, 0, 1, 0],
                       [0, 1, 0, 1, 0, 1, 0, 1, 0, 1],
                       [1, 0, 1, 0, 1, 0, 1, 0, 1, 0]])

# 将预测概率转换为预测的标签值(大于 0.5 视为正标签)
predicted = (predictions > 0.5).float()

# 假设每个类别有 2 个标签,将预测结果和真实标签按类别拆分
num_classes = 5
num_labels_per_class = 2
correct_per_class = []
total_labels_per_class = []

for i in range(num_classes):
    start_idx = i * num_labels_per_class
    end_idx = (i + 1) * num_labels_per_class
    class_predicted = predicted[:, start_idx:end_idx]
    class_labels = labels[:, start_idx:end_idx]

    # 计算每个类别下预测正确的标签数
    correct = (class_predicted == class_labels).sum().item()
    # 计算每个类别下的总标签数
    total = class_labels.numel()
    correct_per_class.append(correct)
    total_labels_per_class.append(total)

# 计算每个类别的正确率
accuracy_per_class = [c / t for c, t in zip(correct_per_class, total_labels_per_class)]

# 计算平均正确率
average_accuracy = sum(accuracy_per_class) / len(accuracy_per_class)
print(f"多类别多标签分类问题的平均正确率: {average_accuracy * 100:.2f}%")

方法解释

  • torch.argmax(input, dim):该方法用于返回输入张量在指定维度上最大值的索引。在单标签分类问题中,torch.argmax(predictions, 1) 和 torch.max(predictions, 1)[1] 的功能是相同的,都可以得到预测的类别索引。
  • tensor.numel():该方法用于返回张量中元素的总数。在上述代码中,class_labels.numel() 用于计算每个类别下的总标签数。

6.model.train()和model.eval()

1. model.train()

  • 作用:将模型设置为训练模式。在训练模式下,模型中的某些层(如 DropoutBatchNorm 等)会以训练时的方式工作。例如,Dropout 层会随机丢弃一些神经元,以防止过拟合;BatchNorm 层会根据当前批次的数据计算均值和方差来进行归一化。
  • 使用场景:当你在训练模型时,需要调用 model.train() 方法,确保模型处于训练模式。
  • 示例代码
import torch
import torch.nn as nn

# 定义一个包含 Dropout 和 BatchNorm 的简单模型
class SimpleModel(nn.Module):
    def __init__(self):
        super(SimpleModel, self).__init__()
        self.fc1 = nn.Linear(10, 20)
        self.dropout = nn.Dropout(0.5)
        self.bn = nn.BatchNorm1d(20)
        self.fc2 = nn.Linear(20, 2)

    def forward(self, x):
        x = self.fc1(x)
        x = self.dropout(x)
        x = self.bn(x)
        x = torch.relu(x)
        x = self.fc2(x)
        return x

model = SimpleModel()
model.train()  # 设置模型为训练模式

# 模拟训练数据
inputs = torch.randn(32, 10)
labels = torch.randint(0, 2, (32,))

criterion = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(model.parameters(), lr=0.001)

# 训练循环
for epoch in range(10):
    optimizer.zero_grad()
    outputs = model(inputs)
    loss = criterion(outputs, labels)
    loss.backward()
    optimizer.step()
    print(f'Epoch {epoch + 1}, Loss: {loss.item()}')

2. model.eval()

  • 作用:将模型设置为评估模式。在评估模式下,Dropout 层不会丢弃神经元,BatchNorm 层会使用在训练阶段计算好的全局均值和方差进行归一化。这样可以保证模型在推理时的结果是稳定和可重复的。
  • 使用场景:当你在验证模型、测试模型或者使用模型进行推理时,需要调用 model.eval() 方法,确保模型处于评估模式。通常还会结合 torch.no_grad() 上下文管理器一起使用,以避免在推理过程中计算梯度,从而节省内存和计算资源。
  • 示例代码
# 假设上面已经完成了模型的训练
model.eval()  # 设置模型为评估模式

# 模拟测试数据
test_inputs = torch.randn(16, 10)

with torch.no_grad():  # 不计算梯度
    test_outputs = model(test_inputs)
    _, predicted = torch.max(test_outputs, 1)
    print("预测结果:", predicted)

总结

  • model.train() 和 model.eval() 方法主要用于控制模型中一些特殊层(如 DropoutBatchNorm)在训练和推理阶段的不同行为。
  • 在训练模型时,使用 model.train();在验证、测试或推理时,使用 model.eval()
  • 在使用 model.eval() 进行推理时,建议结合 torch.no_grad() 上下文管理器,以提高效率。

7.利用gpu进行训练

1.对模型:

device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

2.对数据:

data = data.to(device)
target = target.to(device) 

五、完整的模型训练思路及套路

1.一般的训练思路及套路:

1.准备数据集

2.加载数据集

3.创建网络模型
4.损失函数

5.优化器

6.设置训练网络参数

7.开始训练

8.测试验证

9数据可视化与模型保存


2.小土堆完整训练代码, 以CIFAR10为数据集,进行了代码注释和一些优化

import numpy as np
from torch import dropout
from torch.nn import ReLU, BatchNorm2d, Conv2d, MaxPool2d
from torch.utils.tensorboard import SummaryWriter
import torch
import torchvision
import torch.nn as nn
from torch.utils.data import DataLoader
import cv2


# 定义训练的设备
device = torch.device("cuda")

# 准备数据集
train_transform = torchvision.transforms.Compose([
    torchvision.transforms.ToTensor(),  # 转为张量
    torchvision.transforms.RandomCrop(32, padding=4),  # 随机裁剪32*32的图片,padding=4是填充4个像素
    torchvision.transforms.RandomHorizontalFlip(),  # 随机水平翻转图片
    torchvision.transforms.RandomErasing(scale=(0.04, 0.2), ratio=(0.5, 2), value=0)  # 随机擦除
])  # 转为tensor

# 定义测试集只需要转换为张量即可,通常测试集不进行数据增强(避免改变测试样本的真实分布)
test_transform = torchvision.transforms.Compose([
    torchvision.transforms.ToTensor()
])

train_data = torchvision.datasets.CIFAR10("./dataset", train=True, transform=train_transform, download=True)
# 下载数据集,如果没有下载过,则会自动下载
test_data = torchvision.datasets.CIFAR10("./dataset", train=False, transform=test_transform, download=True)
# len()获取数据集长度
train_data_size = len(train_data)
test_data_size = len(test_data)
print("训练数据集的长度为:{}".format(train_data_size))
print("测试数据集的长度为:{}".format(test_data_size))

# 利用dataloader加载数据集
train_dataloader = DataLoader(train_data, batch_size=64, drop_last=True)
test_dataloader = DataLoader(test_data, batch_size=64, drop_last=True)


# 创建网络模型
# 创建网络模型,进行了结构优化,添加了残差连接示例
class Tudui(nn.Module):
    # 定义网络模型
    def __init__(self):
        # 初始化父类
        super(Tudui, self).__init__()
        self.model = nn.Sequential(
            Conv2d(in_channels=3, out_channels=64, kernel_size=(3, 3), padding=1),
            BatchNorm2d(64),
            Conv2d(in_channels=64, out_channels=64, kernel_size=(3, 3), padding=1),
            BatchNorm2d(64),
            ReLU(inplace=True),
            MaxPool2d(kernel_size=2, ceil_mode=False),

            Conv2d(in_channels=64, out_channels=128, kernel_size=(3, 3), padding=1),
            BatchNorm2d(128),
            Conv2d(in_channels=128, out_channels=128, kernel_size=(3, 3), padding=1),
            BatchNorm2d(128),
            ReLU(inplace=True),
            MaxPool2d(kernel_size=2, ceil_mode=False),

            Conv2d(in_channels=128, out_channels=256, kernel_size=(3, 3), padding=1),
            BatchNorm2d(256),
            Conv2d(in_channels=256, out_channels=256, kernel_size=(3, 3), padding=1),
            BatchNorm2d(256),
            ReLU(inplace=True),
            MaxPool2d(kernel_size=2, ceil_mode=False),

            Conv2d(in_channels=256, out_channels=512, kernel_size=(3, 3), padding=1),
            BatchNorm2d(512),
            Conv2d(in_channels=512, out_channels=512, kernel_size=(3, 3), padding=1),
            BatchNorm2d(512),
            ReLU(inplace=True),
            MaxPool2d(kernel_size=2, ceil_mode=False),
            nn.Flatten(),  # 展平后的序列长度为 64*4*4=1024
            nn.Linear(2048, 256),
            nn.ReLU(),
            nn.Linear(256, 64),
            nn.ReLU(),
            nn.Linear(64, 10)
        )

    def forward(self, x):
        x = self.model(x)
        return x


tudui = Tudui()
tudui = tudui.to(device)

# 损失函数,添加了L2正则化(通过weight_decay参数),惩罚模型复杂度,防止过拟合
loss_fn = nn.CrossEntropyLoss()  # 交叉熵损失函数
loss_fn = loss_fn.to(device)  # 将损失函数转移到GPU

# 优化器,这里使用Adam优化器替代SGD优化器,通常能更快收敛且性能较好
learning_rate_max = 0.05
learning_rate_min = 0.001

# 设置训练网络的一些参数
# 记录训练的次数
total_train_step = 0

# 记录测试的次数
total_test_step = 0

# 训练的轮数
epoch = 50

# 添加tensorboard
writer = SummaryWriter()  # 创建tensorboard记录器

for i in range(epoch):
    learning_rate = learning_rate_max - (learning_rate_max - learning_rate_min) * (i + 1) / epoch
    optimizer = torch.optim.SGD(tudui.parameters(), lr=learning_rate)
    print("-------------第 {} 轮训练开始------------".format(i + 1))
    # 训练步骤开始
    tudui.train()
    for data in train_dataloader:
        imgs, targets = data
        imgs = imgs.to(device)
        targets = targets.to(device)
        output = tudui(imgs)
        loss = loss_fn(output, targets)

        # 优化器优化模型
        optimizer.zero_grad()  # 清空梯度
        loss.backward()  # 反向传播
        optimizer.step()  # 更新参数
        total_train_step = total_train_step + 1  # 训练次数+1
        if total_train_step % 100 == 0:  # 每100次记录一次loss
            print("训练次数:{}, Loss:{}".format(total_train_step, loss.item()))
            writer.add_scalar("train_loss", loss.item(), total_train_step)  # 记录loss到tensorboard

    # 训练步骤结束
    # 测试步骤开始
    tudui.eval()  # 切换到测试模式
    total_test_loss = 0  # 测试集的loss
    total_accuracy = 0  # 准确率
    with torch.no_grad():  # 关闭梯度计算,节省内存
        for data in test_dataloader:  # 测试集的dataloader
            imgs, targets = data  # 获取测试集的图片和标签
            imgs = imgs.to(device)  # 将图片转移到GPU
            targets = targets.to(device)  # 将标签转移到GPU
            outputs = tudui(imgs)  # 模型输出
            # 计算测试集的loss
            loss = loss_fn(outputs, targets)
            total_test_loss = total_test_loss + loss.item()  # 累加loss
            # 计算测试集的准确率
            accuracy = (outputs.argmax(1) == targets).sum()  # 计算准确率
            total_accuracy = total_accuracy + accuracy  # 累加准确率

    # 测试步骤结束
    print("整体测试集上的Loss:{}".format(total_test_loss))
    print("整体测试集上的正确率:{}".format(total_accuracy / test_data_size))
    writer.add_scalar("test_loss", total_test_loss, total_test_step)
    writer.add_scalar("test_accuracy", total_accuracy / test_data_size, total_test_step)
    total_test_step = total_test_step + 1

    torch.save(tudui, "tudui_{}.pth".format(i))
    print("模型已保存")
writer.close()

总结:本篇着重于面向深度学习入门者,提供了一个相对完整清晰的深度学习任务的实现过程的关键部分的解析。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值