PyTorch升级之旅——主要组成模块

本文仅作为个人学习记录使用

文章目录

前言

一、深度学习的简单流程

二、基本配置

三、数据读入

四、模型构建

五、模型初始化

六、损失函数

七、训练和评估

八、可视化

九、PyTorch优化器

总结


前言

学习链接:第三章:PyTorch的主要组成模块 — 深入浅出PyTorch (datawhalechina.github.io)


一、深度学习的简单流程

  • 数据预处理

    • 这是机器学习和深度学习中不可或缺的步骤,包含数据格式的统一、异常数据的处理、数据变换等。
    • 数据集划分方面,常见方法有按比例随机划分、KFold交叉验证等。你提到的train_test_splitKFold方法可以很好地实现这些功能。
  • 模型选择与超参数设定

    • 机器学习模型的选择通常会基于数据的性质和任务的要求,比如选择决策树、SVM、线性回归等。
    • 设定损失函数和优化方法是关键,这些通常可以使用库中自带的实现(如scikit-learnTensorFlowPyTorch等)。
  • 模型训练与评估

    • 在机器学习中,模型在训练集上进行拟合,然后在验证集或测试集上进行评估,以衡量模型的表现。
  • 深度学习的特殊性

    • 深度学习需要处理大量数据,因此要特别设计数据加载机制,如批量加载(batch loading)。
    • 模型的搭建通常是“逐层”构建的,涉及卷积层、池化层、LSTM层等特殊层的设计。这种模块化构建方式使得模型更具灵活性。
    • 损失函数和优化器需要支持自定义模型结构上的反向传播。
  • GPU加速

    • 深度学习中,由于计算量大,通常会利用GPU进行加速。
    • 代码中需要明确将数据和模型放到GPU上执行,同时还需要确保在多GPU训练时,模型和数据的合理分配。
  • 训练与验证过程

    • 深度学习训练时,数据是按批次读入的,每次处理一个批次的数据并进行模型更新。
    • 训练和验证后,还需要计算模型在验证集上的表现指标。

深度学习中训练和验证过程最大的特点在于读入数据是按批的,每次读入一个批次的数据,放入GPU中训练,然后将损失函数反向传播回网络最前面的层,同时使用优化器调整网络参数。这里会涉及到各个模块配合的问题。训练/验证后还需要根据设定好的指标计算模型表现。

PyTorch作为一个广泛使用的深度学习框架,其模块化设计(如torch.nn模块、数据加载模块、GPU支持等)可以方便地实现上述功能。

二、基本配置

在使用PyTorch进行深度学习项目时,确实需要导入一些常用的Python包以及PyTorch自身的一些模块。以下是可能会用到的一些包和模块:

  1. 标准Python包:

    • os:用于操作系统功能,如文件路径管理。
    • numpy:用于数值计算和数组操作,深度学习中常用来处理数据。
    • matplotlib:用于数据可视化,如绘制训练过程中的损失曲线。
    • pandas:用于数据处理,特别是加载和处理结构化数据。
  2. PyTorch模块:

    • torch:PyTorch的核心模块,包含张量操作、自动微分、GPU加速等功能。
    • torch.nn:用于构建神经网络模型,包括定义层和损失函数。
    • torch.optim:包含优化算法,如SGD、Adam,用于更新模型参数。
    • torch.utils.data.Dataset:用于自定义数据集的加载方式。
    • torch.utils.data.DataLoader:用于批量加载数据,可以并行加速数据加载。
    • torch.nn.functional:包含一些常用的函数,如激活函数、卷积操作等。
    • torch.cuda:用于将模型和数据移动到GPU上进行加速。

这些包和模块结合使用,可以帮助你快速搭建和训练深度学习模型。以下是一个常见的导入语句示例:

import os
import numpy as np
import torch
import torch.nn as nn
import torch.optim as optim
from torch.utils.data import Dataset, DataLoader
import torch.nn.functional as F

对于不同的项目可能还需要导入一些更上层的包如cv2等。如果涉及可视化还会用到matplotlib、seaborn等。涉及到下游分析和指标计算也常用到sklearn。

在深度学习项目中,统一设置超参数有助于后续的调试和优化。之前提到的几个关键超参数确实是影响模型性能的核心要素。以下是如何设置和组织这些超参数的建议:

# 超参数设置
batch_size = 64            # 每个批次的样本数量
learning_rate = 0.001      # 初始学习率
max_epochs = 100           # 最大训练次数

# GPU配置
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

解释:

  1. batch_size:

    • 决定了每次模型训练时处理的数据量。较大的batch_size可以加快训练速度,但需要更多内存。
  2. learning_rate:

    • 决定了优化器每次更新模型参数的步长。初始学习率的设置直接影响模型收敛的速度和效果。
  3. max_epochs:

    • 决定了模型在整个训练集上迭代的次数。训练次数过少可能导致欠拟合,过多则可能导致过拟合。
  4. device:

    • 配置模型运行的设备(CPU或GPU)。如果设备支持GPU,则优先使用GPU进行加速,否则使用CPU。

将这些超参数统一设置后,可以方便在代码其他部分调用并进行调试。例如,在定义数据加载器、优化器或训练循环时可以直接使用这些超参数。

此外,可以将这些超参数放在配置文件中(如YAML、JSON格式)或通过命令行参数传递,以便在不同实验之间快速调整。

在使用PyTorch进行深度学习时,将数据和模型显式地移动到GPU上可以显著加速训练。

# 方案一:使用os.environ,这种情况如果使用GPU不需要设置
import os
os.environ['CUDA_VISIBLE_DEVICES'] = '0,1' # 指明调用的GPU为0,1号

# 方案二:使用“device”,后续对要使用GPU的变量用.to(device)即可
device = torch.device("cuda:1" if torch.cuda.is_available() else "cpu") # 指明调用的GPU为1号

通常有两种常见的方式来设置GPU:

1. 在模型和数据初始化时指定设备:

在模型和数据初始化后,可以直接将它们移动到指定的设备上(如GPU)。这种方式在代码的任何地方都可以使用,并且易于理解和管理。

# 设置设备
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")

# 模型初始化并移动到GPU
model = MyModel().to(device)

# 数据张量移动到GPU
input_data = input_data.to(device)
target_data = target_data.to(device)

说明

  • torch.device("cuda")表示使用GPU。如果没有GPU,则会回退到torch.device("cpu"),确保代码在不同环境下都能运行。
  • .to(device)方法可以将模型、数据、损失函数等移到指定设备上。

2. 在每次使用数据时将其移动到GPU:

另一种方法是在每次进行训练或推理时动态地将数据移动到GPU。这种方式适用于数据在不同地方初始化的情况,有利于在不同的场景下灵活使用。

# 在训练循环中移动数据到GPU
for inputs, targets in dataloader:
    inputs, targets = inputs.to(device), targets.to(device)
    outputs = model(inputs)
    loss = loss_function(outputs, targets)
    # 反向传播和优化步骤...

说明

  • 在训练循环中,每次加载的数据批次都需要显式地调用.to(device)将其移动到GPU上。
  • 这种方式的优点是更加灵活,适合处理数据来源不固定的情况,缺点是代码较为冗长。

这两种方式各有优缺点,选择哪种方式取决于你的代码结构和需求。第一种方式较为简洁,但要求所有数据和模型在初始化时就能够确定设备;第二种方式更灵活,适合处理复杂数据流。

三、数据读入

PyTorch数据读入是通过Dataset+DataLoader的方式完成的,Dataset定义好数据的格式和数据变换形式,DataLoader用iterative的方式不断读入批次数据。

1. Dataset:

Dataset是一个抽象类,用于定义数据集的格式、数据变换和如何读取每一个样本。你需要继承torch.utils.data.Dataset并实现以下两个方法:

  • __len__(self):返回数据集的大小,即总共有多少样本。
  • __getitem__(self, idx):根据索引idx返回数据集中第idx个样本。这里通常会返回一个样本数据及其对应的标签。
from torch.utils.data import Dataset

class MyDataset(Dataset):
    def __init__(self, data, labels, transform=None):
        self.data = data
        self.labels = labels
        self.transform = transform

    def __len__(self):
        return len(self.data)

    def __getitem__(self, idx):
        sample = self.data[idx]
        label = self.labels[idx]

        # 进行数据变换
        if self.transform:
            sample = self.transform(sample)

        return sample, label

2. DataLoader:

DataLoader是一个数据加载器,负责从Dataset中按批次(batch)读取数据。DataLoader通过迭代的方式加载数据,支持多线程并行处理数据,可以大幅提高数据加载速度。

from torch.utils.data import DataLoader

# 初始化数据集
dataset = MyDataset(data, labels, transform=your_transform)

# 初始化DataLoader
dataloader = DataLoader(dataset, batch_size=64, shuffle=True, num_workers=4)

# 迭代读取批次数据
for inputs, targets in dataloader:
    inputs, targets = inputs.to(device), targets.to(device)
    # 接下来进行模型训练...

关键参数说明:

  • batch_size:每个批次的样本数量。在训练过程中,数据将按批次送入模型进行训练。
  • shuffle:是否对数据进行随机打乱。通常在训练时设置为True,在验证或测试时设置为False
  • num_workers:加载数据时使用的子进程数量。设置为大于1的值可以利用多线程加速数据加载,但需要考虑系统的硬件资源。

数据变换:

Dataset中,你可以使用transform参数进行数据变换。PyTorch提供了torchvision.transforms模块,特别适合图像数据的预处理,如随机裁剪、旋转、归一化等。

通过结合DatasetDataLoader,你可以灵活地定义数据的读取方式和变换操作,同时确保高效地加载和处理数据。

可以定义自己的Dataset类来实现灵活的数据读取,定义的类需要继承PyTorch自身的Dataset类。主要包含三个函数:

  • __init__: 用于向类中传入外部参数,同时定义样本集

  • __getitem__: 用于逐个读取样本集合中的元素,可以进行一定的变换,并将返回训练/验证所需的数据

  • __len__: 用于返回数据集的样本数

import torch
from torch.utils.data import Dataset

class MyDataset(Dataset):
    def __init__(self, data, labels, transform=None):
        """
        初始化函数,定义数据集和变换操作
        :param data: 输入的数据,例如图像、文本等
        :param labels: 对应的标签
        :param transform: 数据变换操作
        """
        self.data = data
        self.labels = labels
        self.transform = transform

    def __len__(self):
        """
        返回数据集的样本数量
        :return: 样本数量
        """
        return len(self.data)

    def __getitem__(self, idx):
        """
        根据索引返回一个样本数据及其对应的标签
        :param idx: 样本索引
        :return: 返回处理后的样本及标签
        """
        sample = self.data[idx]
        label = self.labels[idx]

        # 如果定义了变换操作,应用到样本数据上
        if self.transform:
            sample = self.transform(sample)

        return sample, label

解释:

  1. __init__:

    • 作用:初始化数据集,定义输入数据和标签,以及可能的变换操作。
    • 参数
      • data:包含所有样本的数据集,可以是图像、文本或其他格式的数据。
      • labels:对应每个样本的数据标签。
      • transform:一个可选参数,用于定义数据预处理和变换(如图像的裁剪、归一化等)。
  2. __len__:

    • 作用:返回数据集中样本的数量,这个值会被DataLoader用来确定在每个epoch中需要迭代多少次。
    • 返回:样本数量,通常为len(self.data)
  3. __getitem__:

    • 作用:根据提供的索引idx,从数据集中获取对应的样本和标签。
    • 过程
      • 根据idxself.data中获取样本。
      • self.labels中获取相应的标签。
      • 如果指定了transform,对样本进行变换处理。
    • 返回:一个样本及其对应的标签(通常返回一个元组(sample, label))。

示例用法

假设你有一个图像数据集,可以如下使用自定义的Dataset类:

from torchvision import transforms

# 假设data和labels是事先准备好的数据和标签
data = [...]  # 替换为实际数据
labels = [...]  # 替换为实际标签

# 定义数据变换操作(如果需要)
data_transform = transforms.Compose([
    transforms.Resize((128, 128)),
    transforms.ToTensor()
])

# 初始化自定义Dataset
my_dataset = MyDataset(data, labels, transform=data_transform)

# 使用DataLoader加载数据
dataloader = torch.utils.data.DataLoader(my_dataset, batch_size=32, shuffle=True, num_workers=4)

# 迭代读取数据
for inputs, targets in dataloader:
    inputs, targets = inputs.to(device), targets.to(device)
    # 训练模型...

典型的深度学习数据处理流程,尤其是对于图像分类任务:

使用PyTorch自带的ImageFolder类加载CIFAR-10数据集

  • ImageFolder是PyTorch提供的一个便捷类,适用于加载按文件夹结构存储的图像数据。
  • 图像文件夹结构通常是这样的:根目录下有多个子目录,每个子目录对应一个类别,子目录中存放属于该类别的图片。
import torch
from torchvision import datasets

train_data = datasets.ImageFolder(train_path, transform=data_transform)
val_data = datasets.ImageFolder(val_path, transform=data_transform)
  • 其中transform定义了图像的预处理操作,比如数据增强、归一化等。

自定义Dataset类加载图像数据

  • 这个自定义的Dataset类用于从一个CSV文件中读取图像文件路径和标签。通过这种方式,你可以更加灵活地处理数据,特别是当图像数据和标签存储在不同的文件或格式中时。
import os
import pandas as pd
from torchvision.io import read_image
from torch.utils.data import Dataset

class MyDataset(Dataset):
    def __init__(self, annotations_file, img_dir, transform=None, target_transform=None):
        self.img_labels = pd.read_csv(annotations_file)
        self.img_dir = img_dir
        self.transform = transform
        self.target_transform = target_transform

    def __len__(self):
        return len(self.img_labels)

    def __getitem__(self, idx):
        img_path = os.path.join(self.img_dir, self.img_labels.iloc[idx, 0])
        image = read_image(img_path)
        label = self.img_labels.iloc[idx, 1]
        if self.transform:
            image = self.transform(image)
        if self.target_transform:
            label = self.target_transform(label)
        return image, label

  • annotations_file:CSV文件路径,包含图像文件名和对应的标签。
  • img_dir:图像所在的目录。
  • transform:对输入图像应用的变换操作。
  • target_transform:对标签应用的变换操作。

使用DataLoader批量加载数据

  • DataLoader负责将Dataset中的数据按批次读取出来,用于训练和验证。
from torch.utils.data import DataLoader

train_loader = torch.utils.data.DataLoader(train_data, batch_size=batch_size, num_workers=4, shuffle=True, drop_last=True)
val_loader = torch.utils.data.DataLoader(val_data, batch_size=batch_size, num_workers=4, shuffle=False)
  • batch_size:每个批次加载的样本数量。
  • num_workers:用于加载数据的并行子进程数量,适当增加可以加快数据加载速度。
  • shuffle:是否在每个epoch开始时打乱数据。训练集一般设置为True,验证集通常设置为False
  • drop_last:如果最后一个批次的样本数少于batch_size,是否丢弃该批次。

可视化加载的数据

  • 通过nextiterDataLoader中提取一个批次的数据,并使用Matplotlib进行可视化。
import matplotlib.pyplot as plt
images, labels = next(iter(val_loader))
print(images.shape)
plt.imshow(images[0].permute(1, 2, 0))  # 调整维度顺序以适应Matplotlib的显示格式
plt.show()
  • images.shape输出图像张量的形状,通常为[batch_size, channels, height, width]
  • plt.imshow用于显示图像,由于PyTorch图像张量的通道顺序为[C, H, W],而Matplotlib要求为[H, W, C],所以需要使用permute调整维度顺序。

这个流程涵盖了数据加载、预处理、批量读取、以及数据的简单可视化,是构建深度学习模型前的数据准备工作的重要步骤。

四、模型构建

人工智能的第三次浪潮受益于卷积神经网络的出现和BP反向传播算法的实现,随着深度学习的发展,研究人员研究出了许许多多的模型,PyTorch中神经网络构造一般是基于nn.Module类的模型来完成的,它让模型构造更加灵活。

1. nn.Module 类的概念

nn.Module 是 PyTorch 中所有神经网络模块的基类。无论是简单的线性层、卷积层,还是复杂的自定义网络结构,都可以通过继承 nn.Module 来构建。这个类为模型的构造、前向传播、以及参数管理提供了便利。

2. 构造模型的基本步骤

构造一个神经网络模型通常包括以下步骤:

  1. 定义模型结构: 在 __init__ 方法中定义网络的层次结构。可以使用 nn.Linearnn.Conv2dnn.ReLU 等 PyTorch 提供的模块,也可以嵌套使用其他的 nn.Module

  2. 实现前向传播: 在 forward 方法中定义前向传播的过程,即数据如何流经定义的各个层。前向传播是自动进行梯度计算的关键步骤。

  3. 创建模型实例并训练: 定义好模型后,通过实例化这个类创建模型对象,然后将数据传入模型进行训练和验证。

import torch
import torch.nn as nn
import torch.nn.functional as F

class SimpleCNN(nn.Module):
    def __init__(self):
        super(SimpleCNN, self).__init__()
        # 定义网络的各层
        self.conv1 = nn.Conv2d(in_channels=3, out_channels=16, kernel_size=3, padding=1)
        self.conv2 = nn.Conv2d(in_channels=16, out_channels=32, kernel_size=3, padding=1)
        self.fc1 = nn.Linear(in_features=32*8*8, out_features=128)
        self.fc2 = nn.Linear(in_features=128, out_features=10)

    def forward(self, x):
        # 定义前向传播过程
        x = F.relu(self.conv1(x))
        x = F.max_pool2d(x, 2)
        x = F.relu(self.conv2(x))
        x = F.max_pool2d(x, 2)
        x = x.view(-1, 32*8*8)  # 展平操作
        x = F.relu(self.fc1(x))
        x = self.fc2(x)
        return x

# 实例化模型
model = SimpleCNN()

# 打印模型结构
print(model)

3. 解释

  • __init__ 方法:定义了两个卷积层 conv1conv2,以及两个全连接层 fc1fc2
  • forward 方法:定义了输入数据如何通过各个层级,最终输出预测结果的过程。这里使用了 ReLU 激活函数和最大池化操作。

4. 模型实例化和使用

一旦模型定义好,你可以创建模型实例,并将数据传入模型进行训练:

input_data = torch.randn(1, 3, 32, 32)  # 模拟一张32x32的RGB图像
output = model(input_data)
print(output)

神经网络的构造

Module 类是 torch.nn 模块里提供的一个模型构造类,是所有神经网络模块的基类,我们可以继承它来定义我们想要的模型。下面继承 Module 类构造多层感知机。这里定义的 MLP 类重载了 Module 类的 __init__ 函数和 forward 函数。它们分别用于创建模型参数和定义前向计算(正向传播)。下面的 MLP 类定义了一个具有两个隐藏层的多层感知机。

多层感知机(MLP)是神经网络中最基础的模型之一,它通常由输入层、多个隐藏层和输出层组成。通过继承 torch.nn.Module 类,可以非常方便地定义和构建多层感知机模型。

1. 定义多层感知机 (MLP) 的步骤

  • 继承 torch.nn.Module:通过继承 torch.nn.Module 类来定义模型。
  • 重载 __init__ 方法:在 __init__ 方法中定义网络的层次结构,包括输入层、隐藏层和输出层。
  • 重载 forward 方法:在 forward 方法中定义数据如何在网络中进行前向传播。

2. MLP 类的定义

以下是一个简单的具有两个隐藏层的多层感知机(MLP)的实现示例:

import torch
import torch.nn as nn
import torch.nn.functional as F

# 定义一个具有两个隐藏层的多层感知机类
class MLP(nn.Module):
    def __init__(self, input_size, hidden1_size, hidden2_size, output_size):
        super(MLP, self).__init__()
        # 定义第一层隐藏层
        self.hidden1 = nn.Linear(input_size, hidden1_size)
        # 定义第二层隐藏层
        self.hidden2 = nn.Linear(hidden1_size, hidden2_size)
        # 定义输出层
        self.output = nn.Linear(hidden2_size, output_size)

    def forward(self, x):
        # 定义前向传播过程
        x = F.relu(self.hidden1(x))  # 使用ReLU激活函数
        x = F.relu(self.hidden2(x))  # 使用ReLU激活函数
        x = self.output(x)           # 输出层不加激活函数
        return x

# 实例化模型
input_size = 784  # 例如用于处理28x28的图像(如MNIST数据集)
hidden1_size = 256
hidden2_size = 128
output_size = 10   # 例如用于10分类任务

model = MLP(input_size, hidden1_size, hidden2_size, output_size)

# 打印模型结构
print(model)

3. 代码解释

  • __init__ 方法
    • self.hidden1self.hidden2 分别定义了第一层和第二层隐藏层的线性变换(全连接层)。
    • self.output 定义了输出层的线性变换。
  • forward 方法
    • 在前向传播中,输入 x 先经过第一层隐藏层,应用 ReLU 激活函数,然后经过第二层隐藏层,再次应用 ReLU,最后通过输出层得到最终的输出结果。

4. 模型实例化

  • 输入大小 (input_size):例如用于处理 28x28 像素的图像,则输入大小为 784
  • 隐藏层大小 (hidden1_size, hidden2_size):根据模型复杂度需求设置。
  • 输出大小 (output_size):例如在一个 10 类分类任务中,输出大小设置为 10

5. 使用模型

  • 实例化模型后,可以将数据传入模型进行前向传播,得到预测结果。
input_data = torch.randn(1, 784)  # 模拟一个大小为784的输入向量
output = model(input_data)
print(output)

6. 总结

这个 MLP 类通过继承 torch.nn.Module,实现了一个简单而有效的多层感知机模型。这个结构可以通过增加隐藏层的数量或改变每层的节点数,来调整模型的复杂度,适应不同任务的需求

神经网络中常见的层

在深度学习中,神经网络的各式各样的层,如全连接层、卷积层、池化层、循环层等,构成了神经网络的核心。虽然 PyTorch 提供了许多常用的层,但有时我们仍然需要自定义层,以满足特定的需求。通过继承 torch.nn.Module 类,我们可以创建不含模型参数的层和含有模型参数的层。

不含模型参数的自定义层

不含模型参数的层通常用于执行简单的操作,例如数据变换。以下是一个简单的自定义层的示例,该层将输入减去其均值:

import torch
from torch import nn

class MyLayer(nn.Module):
    def __init__(self, **kwargs):
        super(MyLayer, self).__init__(**kwargs)

    def forward(self, x):
        return x - x.mean()

# 测试自定义层
layer = MyLayer()
output = layer(torch.tensor([1, 2, 3, 4, 5], dtype=torch.float))
print(output)

在这个例子中,MyLayer 类继承了 nn.Module,并在 forward 方法中定义了将输入减去均值的操作。实例化后,调用该层时,它会对输入数据执行定义的操作。

含模型参数的自定义层

含模型参数的层通常用于学习和更新参数。我们可以使用 nn.Parameternn.ParameterListnn.ParameterDict 来定义这些参数。以下是两个示例:

使用 ParameterList 自定义层
import torch
from torch import nn

class MyListDense(nn.Module):
    def __init__(self):
        super(MyListDense, self).__init__()
        self.params = nn.ParameterList([nn.Parameter(torch.randn(4, 4)) for i in range(3)])
        self.params.append(nn.Parameter(torch.randn(4, 1)))

    def forward(self, x):
        for i in range(len(self.params)):
            x = torch.mm(x, self.params[i])
        return x

# 测试自定义层
net = MyListDense()
print(net)

在这个例子中,MyListDense 类包含了一个 ParameterList,其中存储了多个参数矩阵。前向传播时,输入 x 会依次通过这些参数矩阵进行线性变换。

使用 ParameterDict 自定义层
import torch
from torch import nn

class MyDictDense(nn.Module):
    def __init__(self):
        super(MyDictDense, self).__init__()
        self.params = nn.ParameterDict({
            'linear1': nn.Parameter(torch.randn(4, 4)),
            'linear2': nn.Parameter(torch.randn(4, 1))
        })
        self.params.update({'linear3': nn.Parameter(torch.randn(4, 2))})  # 新增参数

    def forward(self, x, choice='linear1'):
        return torch.mm(x, self.params[choice])

# 测试自定义层
net = MyDictDense()
print(net)

MyDictDense 类使用了 ParameterDict,允许通过键来访问不同的参数矩阵。在前向传播时,可以根据输入选择使用哪个参数矩阵。

常见神经网络层

二维卷积层

二维卷积层是卷积神经网络中的基础层。下面是一个自定义的二维卷积层示例:

import torch
from torch import nn

def corr2d(X, K):
    h, w = K.shape
    Y = torch.zeros((X.shape[0] - h + 1, X.shape[1] - w + 1))
    for i in range(Y.shape[0]):
        for j in range(Y.shape[1]):
            Y[i, j] = (X[i: i + h, j: j + w] * K).sum()
    return Y

class Conv2D(nn.Module):
    def __init__(self, kernel_size):
        super(Conv2D, self).__init__()
        self.weight = nn.Parameter(torch.randn(kernel_size))
        self.bias = nn.Parameter(torch.randn(1))

    def forward(self, x):
        return corr2d(x, self.weight) + self.bias

# 测试二维卷积层
X = torch.rand(8, 8)
conv2d = Conv2D(kernel_size=(3, 3))
output = conv2d(X)
print(output.shape)

在这个例子中,Conv2D 类实现了一个简单的二维卷积操作。卷积核和偏差是模型参数,通过训练可以学习得到。

池化层

池化层通过在固定窗口内计算最大值或均值来减少数据的尺寸。以下是一个自定义的二维池化层的实现:

import torch
from torch import nn

def pool2d(X, pool_size, mode='max'):
    p_h, p_w = pool_size
    Y = torch.zeros((X.shape[0] - p_h + 1, X.shape[1] - p_w + 1))
    for i in range(Y.shape[0]):
        for j in range(Y.shape[1]):
            if mode == 'max':
                Y[i, j] = X[i: i + p_h, j: j + p_w].max()
            elif mode == 'avg':
                Y[i, j] = X[i: i + p_h, j: j + p_w].mean()
    return Y

# 测试池化层
X = torch.tensor([[0, 1, 2], [3, 4, 5], [6, 7, 8]], dtype=torch.float)
print(pool2d(X, (2, 2)))
print(pool2d(X, (2, 2), 'avg'))

这个 pool2d 函数实现了最大池化和平均池化操作。

总结

通过继承 torch.nn.Module,我们可以灵活地自定义各种层,无论是含有模型参数的层还是不含模型参数的层。这使得 PyTorch 成为一个功能强大且易于扩展的深度学习框架。

模型示例

这是一个简单的前馈神经网络 (feed-forward network)(LeNet)。它接受一个输入,然后将它送入下一层,一层接一层的传递,最后给出输出。

一个神经网络的典型训练过程如下:

  1. 定义包含一些可学习参数(或者叫权重)的神经网络

  2. 在输入数据集上迭代

  3. 通过网络处理输入

  4. 计算 loss (输出和正确答案的距离)

  5. 将梯度反向传播给网络的参数

  6. 更新网络的权重,一般使用一个简单的规则:weight = weight - learning_rate * gradient

一个简单的卷积神经网络(CNN)模型的定义和使用。这个模型使用 PyTorch 中的 torch.nn.Module 类,通过自定义 Net 类来实现:

import torch
import torch.nn as nn
import torch.nn.functional as F


class Net(nn.Module):

    def __init__(self):
        super(Net, self).__init__()
        # 输入图像channel:1;输出channel:6;5x5卷积核
        self.conv1 = nn.Conv2d(1, 6, 5)
        self.conv2 = nn.Conv2d(6, 16, 5)
        # an affine operation: y = Wx + b
        self.fc1 = nn.Linear(16 * 5 * 5, 120)
        self.fc2 = nn.Linear(120, 84)
        self.fc3 = nn.Linear(84, 10)

    def forward(self, x):
        # 2x2 Max pooling
        x = F.max_pool2d(F.relu(self.conv1(x)), (2, 2))
        # 如果是方阵,则可以只使用一个数字进行定义
        x = F.max_pool2d(F.relu(self.conv2(x)), 2)
        x = x.view(-1, self.num_flat_features(x))
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = self.fc3(x)
        return x

    def num_flat_features(self, x):
        size = x.size()[1:]  # 除去批处理维度的其他所有维度
        num_features = 1
        for s in size:
            num_features *= s
        return num_features


net = Net()
print(net)

下面是对代码的详细解释:

import torch
import torch.nn as nn
import torch.nn.functional as F

这些导入语句引入了 PyTorch 的核心模块、神经网络模块(nn),以及常用的函数接口模块(F)。

定义网络类

class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        # 输入图像channel:1;输出channel:6;5x5卷积核
        self.conv1 = nn.Conv2d(1, 6, 5)
        self.conv2 = nn.Conv2d(6, 16, 5)
        # 全连接层:y = Wx + b
        self.fc1 = nn.Linear(16 * 5 * 5, 120)
        self.fc2 = nn.Linear(120, 84)
        self.fc3 = nn.Linear(84, 10)
  • Net 类继承自 nn.Module,这是 PyTorch 中所有神经网络模块的基类。
  • __init__ 方法初始化网络的各层组件:
    • self.conv1self.conv2 是两个二维卷积层。第一个卷积层从单通道输入(如灰度图像)开始,输出6个特征图。第二个卷积层从6个输入特征图开始,输出16个特征图。
    • self.fc1self.fc2self.fc3 是三个全连接层(线性层),它们逐步减少特征的维度,最终输出一个具有10个单元的向量(通常用于分类问题)。

前向传播

    def forward(self, x):
        # 2x2 Max pooling
        x = F.max_pool2d(F.relu(self.conv1(x)), (2, 2))
        # 如果是方阵,则可以只使用一个数字进行定义
        x = F.max_pool2d(F.relu(self.conv2(x)), 2)
        x = x.view(-1, self.num_flat_features(x))
        x = F.relu(self.fc1(x))
        x = F.relu(self.fc2(x))
        x = self.fc3(x)
        return x
  • forward 方法定义了前向传播的计算步骤。
    • F.relu(self.conv1(x)) 对输入 x 应用卷积层 conv1,然后使用 ReLU 激活函数。
    • F.max_pool2d(..., (2, 2)) 对卷积层的输出应用 2x2 的最大池化。
    • 这个过程在第二个卷积层 conv2 之后重复。
    • x = x.view(-1, self.num_flat_features(x)) 将多维的特征图展平成一维向量,以便输入到全连接层。
    • 最后,特征向量通过两个全连接层,并经过 ReLU 激活后输出。

计算特征数

    def num_flat_features(self, x):
        size = x.size()[1:]  # 除去批处理维度的其他所有维度
        num_features = 1
        for s in size:
            num_features *= s
        return num_features
  • num_flat_features 方法用于计算输入 x 中所有非批处理维度的特征数。这是为了确定在进入第一个全连接层前,需要展平的特征数。

实例化模型并打印

net = Net()
print(net)
  • net = Net() 实例化了这个网络。
  • print(net) 打印网络的结构,这样你可以查看各层的配置。

运行输出

运行这段代码会打印出模型的结构信息,类似于以下内容:

Net(
  (conv1): Conv2d(1, 6, kernel_size=(5, 5), stride=(1, 1))
  (conv2): Conv2d(6, 16, kernel_size=(5, 5), stride=(1, 1))
  (fc1): Linear(in_features=400, out_features=120, bias=True)
  (fc2): Linear(in_features=120, out_features=84, bias=True)
  (fc3): Linear(in_features=84, out_features=10, bias=True)
)

这个输出展示了网络的层次结构和每一层的参数配置。

典型的 PyTorch 模型训练与推理的基本步骤

1. 定义 forward 函数

在 PyTorch 中,只需要定义模型的 forward 函数。backward 函数不需要手动定义,PyTorch 的自动求导机制(autograd)会根据 forward 函数中的计算图,自动生成 backward 函数来计算梯度。

2. 获取模型的可学习参数

通过 net.parameters() 可以获得模型中所有可学习的参数,如卷积层的权重、全连接层的权重等。

params = list(net.parameters())
print(len(params))  # 打印模型参数的数量
print(params[0].size())  # 打印第一个参数(conv1 的权重)的尺寸

这会输出类似:

10
torch.Size([6, 1, 5, 5])

这意味着模型中有10个参数张量,第一个参数的尺寸为 [6, 1, 5, 5],对应 conv1 层的权重。

3. 使用随机输入进行前向传播

LeNet 中,输入图像尺寸为 32x32。如果要使用此网络进行推理或训练,可以使用随机生成的 32x32 张量进行前向传播:

input = torch.randn(1, 1, 32, 32)
out = net(input)
print(out)

这段代码创建了一个随机的 32x32 输入图像,并通过网络进行前向传播,最终输出的结果可能是一个大小为 [1, 10] 的张量,表示 10 个类别的得分。

4. 清除梯度缓存并进行反向传播

在每次反向传播前,清空之前的梯度缓存,以免梯度累加:

net.zero_grad()

然后,进行反向传播,计算梯度:

out.backward(torch.randn(1, 10))

这里 out.backward() 的参数是形状为 [1, 10] 的张量,用于模拟从输出层传递回来的随机梯度。这个操作会沿着计算图反向传播,计算并存储每个参数的梯度。

5. 注意事项

  • 小批量处理:PyTorch 的 nn 模块支持小批量处理,所有输入张量必须是四维的,形状为 [batch_size, n_channels, height, width]。如果只有一个样本,可以通过 unsqueeze(0) 方法在第一个维度上添加一个批大小维度,使其形状变为 [1, n_channels, height, width]

  • torch.Tensor:这是 PyTorch 中最基本的数据结构,支持自动求导操作,能够保存梯度信息。

  • nn.Module:所有神经网络的基类,封装了可学习的参数,并提供了在 GPU 上运行、保存和加载模型等功能。

  • nn.Parameter:当张量被赋值为 nn.Module 的属性时,它会被自动注册为可学习的参数。

  • autograd.Function:这个类实现了自动求导的逻辑,每个张量都会至少有一个 Function 节点来记录它的创建历史,供反向传播时使用。

五、模型初始化

在深度学习中,权重初始化对于模型的收敛速度和最终性能至关重要。以下是一些常见的初始化方法及其使用方式:

  1. 均匀分布初始化

    • torch.nn.init.uniform_(tensor, a=0.0, b=1.0): 将 tensor 的元素初始化为均匀分布 [a, b) 之间的值。
  2. 正态分布初始化

    • torch.nn.init.normal_(tensor, mean=0.0, std=1.0): 将 tensor 的元素初始化为均值为 mean、标准差为 std 的正态分布。
  3. 常量初始化

    • torch.nn.init.constant_(tensor, val): 将 tensor 的元素初始化为 val
  4. 全一初始化

    • torch.nn.init.ones_(tensor): 将 tensor 的元素初始化为 1
  5. 全零初始化

    • torch.nn.init.zeros_(tensor): 将 tensor 的元素初始化为 0
  6. 单位矩阵初始化

    • torch.nn.init.eye_(tensor): 将二维 tensor 初始化为单位矩阵形式。
  7. Dirac初始化

    • torch.nn.init.dirac_(tensor, groups=1): 初始化卷积核,使其具有Dirac分布,适用于保持输入特征图和输出特征图的一致性。
  8. Xavier均匀初始化

    • torch.nn.init.xavier_uniform_(tensor, gain=1.0): 使用均匀分布的Xavier初始化方法。
  9. Xavier正态初始化

    • torch.nn.init.xavier_normal_(tensor, gain=1.0): 使用正态分布的Xavier初始化方法。
  10. Kaiming均匀初始化

    • torch.nn.init.kaiming_uniform_(tensor, a=0, mode='fan_in', nonlinearity='leaky_relu'): 适用于ReLU激活函数的权重初始化。
  11. Kaiming正态初始化

    • torch.nn.init.kaiming_normal_(tensor, a=0, mode='fan_in', nonlinearity='leaky_relu'): 适用于ReLU激活函数的正态分布初始化。
  12. 正交初始化

    • torch.nn.init.orthogonal_(tensor, gain=1): 将 tensor 初始化为正交矩阵。
  13. 稀疏初始化

    • torch.nn.init.sparse_(tensor, sparsity, std=0.01): 以给定的稀疏度 sparsity 和标准差 std 初始化张量。
  14. 计算增益

    • torch.nn.init.calculate_gain(nonlinearity, param=None): 根据给定的激活函数计算增益因子,用于初始化。

这些初始化方法能帮助你根据网络结构和激活函数的不同,选择适合的权重初始化方式,从而提高模型的训练效率和性能。

这些函数除了calculate_gain,所有函数的后缀都带有下划线,意味着这些函数将会直接原地更改输入张量的值

torch.nn.init使用

通常会根据实际模型来使用torch.nn.init进行初始化,通常使用isinstance()来进行判断模块(回顾3.4模型构建)属于什么类型。

import torch
import torch.nn as nn

conv = nn.Conv2d(1, 3, 3)  # 创建一个二维卷积层
linear = nn.Linear(10, 1)  # 创建一个线性层

# 判断conv是否是nn.Conv2d类型
is_conv_type = isinstance(conv, nn.Conv2d) 
print(is_conv_type)  # 输出: True

# 判断linear是否是nn.Conv2d类型
is_linear_type_conv = isinstance(linear, nn.Conv2d) 
print(is_linear_type_conv)  # 输出: False

在这段代码中:

  1. conv 是一个二维卷积层的实例 (nn.Conv2d),所以 isinstance(conv, nn.Conv2d) 会返回 True
  2. linear 是一个线性层的实例 (nn.Linear),而不是 nn.Conv2d 的实例,因此 isinstance(linear, nn.Conv2d) 会返回 False

将看到的输出是:

True
False

通过不同的初始化方法对 convlinear 两个层的权重进行初始化。可以根据层的类型选择合适的初始化方法,从而提高模型的收敛速度:

import torch
import torch.nn as nn

conv = nn.Conv2d(1, 3, 3)  # 创建一个二维卷积层
linear = nn.Linear(10, 1)  # 创建一个线性层

# 查看随机初始化的conv参数
print(conv.weight.data)

# 查看随机初始化的linear参数
print(linear.weight.data)

# 对conv进行Kaiming初始化
torch.nn.init.kaiming_normal_(conv.weight.data)
print(conv.weight.data)

# 对linear进行常数初始化
torch.nn.init.constant_(linear.weight.data, 0.3)
print(linear.weight.data)

输出解释:

  1. 随机初始化的参数

    • conv.weight.datalinear.weight.data 会展示 convlinear 层在随机初始化后的权重值。一般情况下,这些值是根据PyTorch默认的初始化方法产生的。
  2. Kaiming初始化

    • torch.nn.init.kaiming_normal_ 用于Kaiming(He)初始化,它特别适合ReLU激活函数的深度网络。这个方法会使权重遵循一个均值为零、方差与输入特征图数量相关的正态分布,适合保持信号的方差在前向传播过程中相对稳定。
    • conv.weight.data 展示了经过Kaiming初始化后的权重值。
  3. 常数初始化

    • torch.nn.init.constant_ 将权重初始化为一个指定的常数值。在这个例子中,所有的权重都被初始化为0.3。
    • linear.weight.data 展示了经过常数初始化后的权重值。

代码运行的过程总结:

  • 在初始化之前,convlinear 层的权重是随机生成的。
  • 通过Kaiming初始化,conv 层的权重被重新赋值为符合Kaiming标准的随机值。
  • 通过常数初始化,linear 层的权重被全部设置为 0.3

初始化函数的封装

通过定义 initialize_weights() 函数,掌握如何为不同类型的层设置合适的权重初始化方法。这种方法可以帮助提高模型的训练效果,避免梯度消失或梯度爆炸的问题。协议意识到,初始化时避免将模型参数全部设为0,因为这可能会导致梯度消失,影响模型的学习能力。

定义了一个 initialize_weights() 函数,它可以根据模型层的类型,对每一层的权重和偏置进行不同的初始化操作:

  • nn.Conv2d:

    • 对于卷积层 Conv2d,权重使用 torch.nn.init.zeros_() 初始化为全零。
    • 如果该层有偏置,则偏置使用常数0.3初始化。
  • nn.Linear:

    • 对于全连接层 Linear,权重使用正态分布 (mean=0.1, std=1.0) 初始化。
    • 如果该层有偏置,则偏置初始化为零。
  • nn.BatchNorm2d:

    • 对于批归一化层 BatchNorm2d,权重初始化为全1。
    • 偏置初始化为零。
model = MyModel()  # 这是你定义的模型
model.apply(initialize_weights)

或者直接调用:

initialize_weights(model)

这个初始化函数会遍历模型的所有层,并根据层的类型为它们的权重和偏置进行初始化。

注意事项

  1. 全零初始化: 对 Conv2d 层使用全零初始化不是最佳选择。全零初始化会导致神经网络在训练过程中没有梯度更新的效果,建议使用其他更合适的初始化方法,如 KaimingXavier

  2. 偏置初始化: 偏置通常会被初始化为零,因为偏置的初始化对网络性能的影响较小。

  3. 权重初始化的选择: 根据层的激活函数、网络深度等选择合适的初始化方法能够加速模型的收敛。

可以将 Conv2d 层的权重初始化方法改为 KaimingXavier,例如:

torch.nn.init.kaiming_normal_(m.weight.data, nonlinearity='relu')

这样可以有效避免梯度消失或爆炸的问题,尤其是在深度神经网络中。

def initialize_weights(model):
    for m in model.modules():
        if isinstance(m, nn.Conv2d):
            torch.nn.init.kaiming_normal_(m.weight.data, nonlinearity='relu')
            if m.bias is not None:
                torch.nn.init.constant_(m.bias.data, 0.3)
        elif isinstance(m, nn.Linear):
            torch.nn.init.normal_(m.weight.data, 0.1)
            if m.bias is not None:
                torch.nn.init.zeros_(m.bias.data)
        elif isinstance(m, nn.BatchNorm2d):
            m.weight.data.fill_(1)
            m.bias.data.zero_()

定义一个简单的多层感知机(MLP)模型,并演示如何使用自定义的 initialize_weights() 函数来初始化模型的权重:

  1. 定义模型类 (MLP):

    • MLP 继承自 nn.Module,是一个包含卷积层 (Conv2d)、激活函数 (ReLU) 和全连接层 (Linear) 的简单模型。
    • __init__ 方法用于声明模型的各层,并调用父类的构造函数。
    • forward 方法定义了前向传播的计算逻辑,即模型如何根据输入计算输出。
  2. 模型实例化:

    • 创建 MLP 类的实例 mlp
    • 打印 mlp.hidden.weight.data,查看卷积层 self.hidden 的权重矩阵在初始化前的值。
  3. 初始化权重:

    • 使用 mlp.apply(initialize_weights) 或直接调用 initialize_weights(mlp),对模型的所有层进行权重初始化。 apply() 函数会递归地遍历模型的每一层,并对每一层调用 initialize_weights
    • 再次打印 mlp.hidden.weight.data,查看卷积层的权重矩阵在初始化后的值。
# 模型的定义
class MLP(nn.Module):
  # 声明带有模型参数的层,这里声明了两个全连接层
  def __init__(self, **kwargs):
    # 调用MLP父类Block的构造函数来进行必要的初始化。这样在构造实例时还可以指定其他函数
    super(MLP, self).__init__(**kwargs)
    self.hidden = nn.Conv2d(1,1,3)
    self.act = nn.ReLU()
    self.output = nn.Linear(10,1)
    
   # 定义模型的前向计算,即如何根据输入x计算返回所需要的模型输出
  def forward(self, x):
    o = self.act(self.hidden(x))
    return self.output(o)

mlp = MLP()
print(mlp.hidden.weight.data)
print("-------初始化-------")

mlp.apply(initialize_weights)
# 或者initialize_weights(mlp)
print(mlp.hidden.weight.data)

预期结果

  • 初始化前: 打印卷积层的权重矩阵,显示为一些随机值,因为PyTorch默认对权重进行随机初始化。

  • 初始化后: 使用 initialize_weights() 函数将卷积层的权重初始化为全零(因为代码中 initialize_weights 函数为 Conv2d 层使用了 torch.nn.init.zeros_()),因此再次打印时,你会看到卷积层的权重变为全零。

示例输出

tensor([[[[ 0.3069, -0.1865,  0.0182],
          [ 0.2475,  0.3330,  0.1352],
          [-0.0247, -0.0786,  0.1278]]]])
"-------初始化-------"
tensor([[[[0., 0., 0.],
          [0., 0., 0.],
          [0., 0., 0.]]]])

注意: 在初始化时,最好不要将模型的参数初始化为0,因为这样会导致梯度消失,从而影响模型的训练效果。因此,我们在初始化时,可以使用其他初始化方法或者将模型初始化为一个很小的值,如0.01,0.1等。

六、损失函数

损失函数(Loss Function)在深度学习中扮演着至关重要的角色。它用于衡量模型预测与真实标签之间的差距,并指导模型在训练过程中进行调整。PyTorch提供了多种损失函数,以满足不同任务的需求。

二分类交叉熵损失函数

二分类交叉熵损失函数(Binary Cross-Entropy Loss)用于二分类任务,衡量预测概率与真实标签之间的差异。它可以处理每个样本的预测概率和真实标签,并计算每个样本的损失值,最后取平均值作为最终的损失。

功能

BCELoss 计算的是二分类问题中预测概率与实际标签之间的交叉熵损失。该损失函数可以帮助模型通过优化参数来提高预测的准确性。

主要参数

  • weight: 可以为每个类别设置不同的权重。对于二分类问题,通常不需要使用此参数。
  • size_average: 是否对每个样本的损失取平均值。当为 True 时,返回的损失是所有样本损失的平均值;为 False 时,返回的是损失的总和。
  • reduce: 是否对损失进行缩减。当为 True 时,返回的是标量(损失的平均值或总和);为 False 时,返回的是每个样本的损失值。

数学公式

对于二分类任务,交叉熵损失函数的数学公式为:

其中:

  • x 是预测的概率(模型的输出),通常是一个 sigmoid 函数的结果。
  • y是真实标签(0 或 1)。

特点

  • 数值稳定性:在计算时,为了避免对数函数的数值不稳定,通常会使用 torch.nn.BCEWithLogitsLoss,它将 logits 直接传入,并在内部使用了数值稳定的计算方法。

使用方法

在 PyTorch 中,可以使用 torch.nn.BCEWithLogitsLoss 计算二分类交叉熵损失。这个损失函数将模型的 raw logits(未经 sigmoid 激活的输出)作为输入,然后在计算损失前自动应用 sigmoid 函数,从而提高数值稳定性。

import torch
import torch.nn as nn

# 定义损失函数
criterion = nn.BCEWithLogitsLoss()

# 模拟模型输出 (logits) 和真实标签
output = torch.tensor([0.2, 0.7, -1.0])  # logits
target = torch.tensor([0.0, 1.0, 0.0])  # 真实标签

# 计算损失
loss = criterion(output, target)

print(f"Loss: {loss.item()}")

在上面的示例中,output 是模型的原始输出(logits),target 是真实的二分类标签。nn.BCEWithLogitsLoss 会首先对 output 应用 sigmoid 函数,然后计算交叉熵损失。最终返回的 loss 是一个标量,表示模型在这些样本上的平均损失。

注意事项

  • 标签要求target 的值应该是 0 或 1。
  • 模型输出output 应该是 logits,而不是经过 sigmoid 函数处理的概率。如果你的模型已经输出了概率值(在 0 到 1 之间),可以使用 torch.nn.BCELoss,它期望输入是经过 sigmoid 激活的概率值。

交叉熵损失函数

交叉熵损失函数(Cross Entropy Loss)在分类问题中广泛使用,尤其是在多分类任务中。PyTorch提供了nn.CrossEntropyLoss来计算这个损失函数,它结合了nn.LogSoftmaxnn.NLLLoss(负对数似然损失),通常用于多分类问题。

功能

交叉熵损失函数用于衡量模型的预测概率分布与真实标签分布之间的差异。该损失函数的目标是最小化预测概率分布与真实标签分布之间的交叉熵,从而提高分类准确性。

主要参数

  • weight: 可以为每个类别设置不同的权重,用于处理类别不平衡问题。
  • size_average: 是否对每个样本的损失取平均值。当为 True 时,返回的损失是所有样本损失的平均值;为 False 时,返回的是损失的总和。
  • ignore_index: 指定一个类别索引,该索引对应的标签将被忽略。通常用于处理一些特定的标签,比如填充标签。
  • reduce: 是否对损失进行缩减。当为 True 时,返回的是标量(损失的平均值或总和);为 False 时,返回的是每个样本的损失值。

计算公式

交叉熵损失函数的计算公式如下:

其中:

  • C是类别数。
  • y真实标签的独热编码(one-hot encoded)向量。
  • y^​ 是模型预测的概率分布(softmax激活后的输出)。
import torch
import torch.nn as nn

# 定义CrossEntropyLoss损失函数
criterion = nn.CrossEntropyLoss()

# 模拟模型的原始输出(logits)和真实标签
logits = torch.randn(3, 5, requires_grad=True)  # 模型的原始输出 (未经过softmax)
targets = torch.tensor([1, 0, 4])  # 真实标签(类别索引)

# 计算损失
loss = criterion(logits, targets)

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

# 打印损失值
print('CrossEntropyLoss损失函数的计算结果为', loss)

代码解释

  1. nn.CrossEntropyLoss(): 定义交叉熵损失函数。它会自动将模型输出通过 softmax 激活函数处理。
  2. logits: 模型的原始输出(未经过 softmax),形状为 (batch_size, num_classes)。在这个例子中,我们有3个样本,每个样本有5个类别的logits。
  3. targets: 真实标签,类别的索引。每个值表示真实的类别(0到4之间的整数)。
  4. criterion(logits, targets): 计算损失值。
  5. loss.backward(): 反向传播计算梯度。

注意事项

  • 输入数据: logits 应该是未经 softmax 激活的原始输出。nn.CrossEntropyLoss 会在内部自动应用 softmax 函数。
  • 目标数据: targets 是真实标签的类别索引,而不是独热编码向量。

L1损失函数

L1损失函数(L1 Loss),也称为绝对误差损失函数(Absolute Error Loss),用于回归任务中。它是预测值与真实值之间差异的绝对值的平均。

功能

L1损失函数计算预测值与真实值之间的绝对误差。与L2损失函数(均方误差)不同,L1损失对异常值不那么敏感,因为它不平方误差。这使得L1损失在处理具有异常值的数据时更加稳健。

计算公式

L1损失的计算公式如下:

其中:

  • N 是样本的数量。
  • yi​ 是真实值。
  • y​i​ ^是预测值。

主要参数

  • size_average: 是否对每个样本的损失取平均值。当为 True 时,返回的损失是所有样本损失的平均值;为 False 时,返回的是损失的总和。
  • reduce: 是否对损失进行缩减。当为 True 时,返回的是标量(损失的平均值或总和);为 False 时,返回的是每个样本的损失值。

使用方法

import torch
import torch.nn as nn

# 定义L1Loss损失函数
criterion = nn.L1Loss()

# 模拟模型的预测值和真实值
predictions = torch.tensor([0.5, 2.0, 3.0], requires_grad=True)  # 模型的预测值
targets = torch.tensor([0.0, 2.5, 2.5])  # 真实值

# 计算损失
loss = criterion(predictions, targets)

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

# 打印损失值
print('L1Loss损失函数的计算结果为', loss.item())

代码解释

  1. nn.L1Loss(): 定义L1损失函数。
  2. predictions: 模型的预测值,形状与targets一致。
  3. targets: 真实值,形状与predictions一致。
  4. criterion(predictions, targets): 计算L1损失。
  5. loss.backward(): 反向传播计算梯度。
  6. loss.item(): 获取损失值的Python标量。

注意事项

  • 输入数据: predictionstargets 的形状必须相同。
  • 目标数据: targets 应该是模型预测值的同形状的真实值。

MSE损失函数

均方误差损失函数(Mean Squared Error Loss,MSE Loss)是一种用于回归任务的常用损失函数。它测量预测值与真实值之间的平均平方差。

功能

MSE损失函数用于量化模型预测值与真实值之间的误差。它对大误差更为敏感,因为它将误差平方后进行平均。这使得MSE适用于误差分布比较均匀的场景。

计算公式

MSE损失的计算公式如下:

其中:

  • N 是样本的数量。
  • yi​ 是真实值。
  • y^​i​ 是预测值。

主要参数

  • size_average: 是否对每个样本的损失取平均值。当为 True 时,返回的损失是所有样本损失的平均值;为 False 时,返回的是损失的总和。
  • reduce: 是否对损失进行缩减。当为 True 时,返回的是标量(损失的平均值或总和);为 False 时,返回的是每个样本的损失值。
import torch
import torch.nn as nn

# 定义MSELoss损失函数
criterion = nn.MSELoss()

# 模拟模型的预测值和真实值
predictions = torch.tensor([0.5, 2.0, 3.0], requires_grad=True)  # 模型的预测值
targets = torch.tensor([0.0, 2.5, 2.5])  # 真实值

# 计算损失
loss = criterion(predictions, targets)

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

# 打印损失值
print('MSELoss损失函数的计算结果为', loss.item())

代码解释

  1. nn.MSELoss(): 定义MSE损失函数。
  2. predictions: 模型的预测值,形状与targets一致。
  3. targets: 真实值,形状与predictions一致。
  4. criterion(predictions, targets): 计算MSE损失。
  5. loss.backward(): 反向传播计算梯度。
  6. loss.item(): 获取损失值的Python标量。

注意事项

  • 输入数据: predictionstargets 的形状必须相同。
  • 目标数据: targets 应该是模型预测值的同形状的真实值。

平滑L1 (Smooth L1)损失函数

平滑L1损失函数(Smooth L1 Loss),也被称为Huber损失函数,是一种在回归问题中常用的损失函数。它结合了L1损失(绝对误差)和L2损失(平方误差)的优点,能够在小误差时更平滑,减少大误差的影响,通常用于处理异常值问题。

功能

Smooth L1损失函数用于回归任务,结合了L1损失和L2损失的特点。它在小误差时使用L2损失,平滑且更稳定;在大误差时使用L1损失,减少异常值对损失的影响。

计算公式

Smooth L1损失函数的计算公式如下:

其中:

  • β 是平滑参数,通常设为一个小值,如1.0或0.5。
  • y 是真实值。
  • y^​ 是预测值。

主要参数

  • beta: 平滑参数,决定了在何种误差范围内使用L2损失,超出该范围后使用L1损失。

使用方法

在PyTorch中,nn.SmoothL1Loss是实现平滑L1损失函数的类。

import torch
import torch.nn as nn

# 定义SmoothL1Loss损失函数
criterion = nn.SmoothL1Loss()

# 模拟模型的预测值和真实值
predictions = torch.tensor([0.5, 2.0, 3.0], requires_grad=True)  # 模型的预测值
targets = torch.tensor([0.0, 2.5, 2.5])  # 真实值

# 计算损失
loss = criterion(predictions, targets)

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

# 打印损失值
print('SmoothL1Loss损失函数的计算结果为', loss.item())

代码解释

  1. nn.SmoothL1Loss(): 定义Smooth L1损失函数。
  2. predictions: 模型的预测值,形状与targets一致。
  3. targets: 真实值,形状与predictions一致。
  4. criterion(predictions, targets): 计算Smooth L1损失。
  5. loss.backward(): 反向传播计算梯度。
  6. loss.item(): 获取损失值的Python标量。

注意事项

  • 平滑参数: 默认情况下,beta 参数设为1.0,如果需要其他值,可以在创建 SmoothL1Loss 对象时传入。
  • 输入数据: predictionstargets 的形状必须相同。
  • 目标数据: targets 应该是模型预测值的同形状的真实值。

目标泊松分布的负对数似然损失

目标泊松分布的负对数似然损失(Negative Log-Likelihood Loss for Poisson Distribution)是用于处理泊松回归任务中的损失函数。泊松分布常用于计数数据的建模,例如事件的发生次数。负对数似然损失用于最小化模型预测值和真实值之间的差距。

功能

泊松分布的负对数似然损失函数用来衡量模型的预测值与真实值之间的差异。对于给定的真实计数 yyy 和模型的预测值 λ\lambdaλ(通常是泊松分布的参数),损失函数计算模型预测的负对数似然。

计算公式

泊松分布的负对数似然损失函数的公式为:

其中:

  • y 是真实值(计数)。
  • λ 是模型的预测值(泊松分布的参数)。

主要参数

  • full: 布尔值。如果为 True,返回完整的负对数似然。如果为 False,则返回缩放的负对数似然。

使用方法

import torch
import torch.nn as nn

# 定义PoissonNLLLoss损失函数
criterion = nn.PoissonNLLLoss()

# 模拟模型的预测值和真实值
predictions = torch.tensor([2.5, 3.0, 1.0], dtype=torch.float)  # 模型的预测值(λ)
targets = torch.tensor([2, 3, 1], dtype=torch.float)  # 真实值(y)

# 计算损失
loss = criterion(predictions, targets)

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

# 打印损失值
print('PoissonNLLLoss损失函数的计算结果为', loss.item())

代码解释

  1. nn.PoissonNLLLoss(): 定义泊松负对数似然损失函数。
  2. predictions: 模型的预测值,泊松分布的参数(λ)。必须为正值。
  3. targets: 真实的计数值(y)。必须为非负整数。
  4. criterion(predictions, targets): 计算负对数似然损失。
  5. loss.backward(): 反向传播计算梯度。
  6. loss.item(): 获取损失值的Python标量。

注意事项

  • 预测值: predictions 应该为正值,因为泊松分布的参数 λ 必须为正。
  • 目标值: targets 应为非负整数。
  • 数值稳定性: 使用 torch.nn.PoissonNLLLoss 时,通常会处理数值稳定性问题,因此无需额外处理。

KL散度

KL散度(Kullback-Leibler Divergence)是一种衡量两个概率分布之间差异的度量。它通常用于生成模型的训练中,如变分自编码器(VAE)。在PyTorch中,KL散度的计算通常使用nn.KLDivLoss,其计算的是两个概率分布之间的相对熵。

KL散度的计算公式

PyTorch中的KL散度损失

nn.KLDivLoss用于计算KL散度的负对数似然。它的输入通常是对数形式的概率分布(log input),因为它计算的是对数概率的差异。

主要参数:
  • reduction: 计算损失的方式。'batchmean'(默认)表示计算批次均值,'sum'表示计算总和,'none'表示不进行任何缩减。
import torch
import torch.nn as nn

# 创建KL散度损失函数
kl_loss = nn.KLDivLoss(reduction='batchmean')

# 模拟预测概率(对数概率形式)和目标概率
input = torch.tensor([[0.1, 0.9]], dtype=torch.float)
target = torch.tensor([[0.2, 0.8]], dtype=torch.float)

# 对input进行对数变换
input_log = torch.log(input)

# 计算KL散度损失
loss = kl_loss(input_log, target)
print('KL散度损失函数的计算结果为', loss.item())

注意事项

  • 对数概率nn.KLDivLoss要求输入是对数形式的概率分布。因此,需要对输入数据使用torch.log()进行变换。
  • 数值稳定性:确保输入概率分布中没有零值,以避免对数运算中的无穷大问题。在实际使用中,可以使用torch.clamp()来避免数值不稳定。

MarginRankingLoss

MarginRankingLoss 是一种用于衡量排名的损失函数,主要用于排序或排名任务。它通常用于有序或排序数据的模型中,如学习排序模型(Learning to Rank)或其他需要比较两者之间的差异的任务。

功能

MarginRankingLoss 通过衡量模型对一对样本的相对排序(即一个样本应当被排在另一个样本之前)的正确性来进行训练。常见的应用包括推荐系统中的排序任务。

主要参数

  • margin: 用于定义正样本和负样本之间的最小差距。默认值为1。
  • reduction: 指定损失的计算方式。'none' 表示不缩减(返回每个样本的损失),'mean' 表示计算损失的均值,'sum' 表示计算损失的总和。

计算公式

import torch
import torch.nn as nn

# 创建MarginRankingLoss损失函数
margin_ranking_loss = nn.MarginRankingLoss(margin=1.0, reduction='mean')

# 模拟模型的预测分数
input1 = torch.tensor([0.5, 1.0, 1.5], dtype=torch.float)
input2 = torch.tensor([0.2, 1.2, 1.0], dtype=torch.float)

# 目标标签,+1表示input1应大于input2,-1表示input2应大于input1
target = torch.tensor([1, -1, 1], dtype=torch.float)

# 计算MarginRankingLoss损失
loss = margin_ranking_loss(input1, input2, target)
print('MarginRankingLoss损失函数的计算结果为', loss.item())

解释

  • input1input2 是模型预测的分数或相似度。
  • target 是相对标签,指示期望的排序关系。
  • margin 是一个设定的阈值,用于控制正负样本之间的差距。

通过使用 MarginRankingLoss,模型可以学习到如何根据输入的对来调整其输出,从而改进排序或排名的质量。

多标签边界损失函数

多标签边界损失函数(Multi-label Margin Loss)是针对多标签分类任务的一种损失函数。在多标签分类中,每个样本可以同时属于多个类别,因此需要一个能够处理这种情况的损失函数。

功能

MultiLabelMarginLoss 用于训练多标签分类模型,其中每个样本的目标是一个二进制标签向量(例如 [0, 1, 0] 表示样本只属于第二个标签)。损失函数的目标是最小化模型的输出与目标标签之间的差距,具体来说是通过一个边界来衡量分类结果的准确性。

主要参数

  • margin: 用于定义正负样本之间的最小差距。默认值为1。
  • reduction: 指定损失的计算方式。'none' 表示不缩减(返回每个样本的损失),'mean' 表示计算损失的均值,'sum' 表示计算损失的总和。

计算公式

import torch
import torch.nn as nn

# 创建MultiLabelMarginLoss损失函数
multi_label_margin_loss = nn.MultiLabelMarginLoss(margin=1.0, reduction='mean')

# 模拟模型的预测分数,假设有三个标签
input = torch.tensor([[0.2, 0.8, 0.5],
                      [0.9, 0.1, 0.4],
                      [0.3, 0.7, 0.2]], dtype=torch.float)

# 目标标签,表示每个样本的标签
# 标签为0, 1, 2 表示第一个样本属于第二个标签,第二个样本属于第一个标签,第三个样本属于第二个标签
target = torch.tensor([[1, 0, 0],
                       [1, 0, 0],
                       [0, 1, 0]], dtype=torch.long)

# 计算MultiLabelMarginLoss损失
loss = multi_label_margin_loss(input, target)
print('MultiLabelMarginLoss损失函数的计算结果为', loss.item())

解释

  • input 是模型预测的分数,表示每个标签的得分。
  • target 是目标标签矩阵,其中每行代表一个样本的标签。
  • margin 是一个设定的阈值,用于控制正负样本之间的差距。

通过使用 MultiLabelMarginLoss,模型可以在多标签分类任务中学习到如何为每个样本分配正确的标签,从而改进分类性能。

二分类损失函数

Soft Margin Loss

功能:计算二分类任务中的 Logistic 损失。Soft Margin Loss 是一种适用于二分类问题的损失函数,其计算基于每个输入的 sigmoid 输出与目标标签之间的误差。

主要参数

  • reduction: 计算模式。可选值为 'none'(不进行缩减)、'sum'(返回所有样本损失的总和)、'mean'(返回所有样本损失的均值)。

import torch
import torch.nn as nn

# 定义 Soft Margin Loss
loss_f = nn.SoftMarginLoss()

# 模拟模型的原始输出 (logits)
inputs = torch.tensor([[0.3, 0.7], [0.5, 0.5]])

# 目标标签(-1 或 1)
target = torch.tensor([[-1, 1], [1, -1]], dtype=torch.float)

# 计算损失
output = loss_f(inputs, target)

print('Soft Margin Loss 损失函数的计算结果为', output.item())

解释

  • inputs 是模型的原始输出(logits),它们没有经过 Sigmoid 激活。
  • target 是目标标签,通常是 -1 或 1,用于标识类别。
  • reduction 参数控制损失的返回方式:'none' 返回每个样本的损失,'sum' 返回所有样本损失的总和,'mean' 返回所有样本损失的均值。

多分类的折页损失

功能:多分类的折页损失(Focal Loss)是一种用于处理类别不平衡问题的损失函数。它在标准的交叉熵损失函数的基础上进行了修改,以降低易分类样本的权重,增加难分类样本的权重。这种方式可以帮助模型更好地关注那些较难分类的样本。

计算公式: Focal Loss 的公式为:

 其中:

  • pt​ 是模型预测的概率分布中属于真实类别的概率。
  • αt​ 是平衡因子,用于调整正负样本的影响。
  • γ 是焦点因子,用于调节难易样本的权重。通常 γ>0\gamma > 0γ>0 使得损失函数对难分类样本的权重更高。

主要参数

  • alpha: 平衡因子,用于调整正负样本的影响。
  • gamma: 焦点因子,用于调整难分类样本的权重。
  • reduction: 计算模式。可选值为 'none'(不进行缩减)、'sum'(返回所有样本损失的总和)、'mean'(返回所有样本损失的均值)。
import torch
import torch.nn as nn
import torch.nn.functional as F

class FocalLoss(nn.Module):
    def __init__(self, alpha=1.0, gamma=2.0, reduction='mean'):
        super(FocalLoss, self).__init__()
        self.alpha = alpha
        self.gamma = gamma
        self.reduction = reduction
    
    def forward(self, inputs, targets):
        # 使用 softmax 计算概率
        probs = F.softmax(inputs, dim=1)
        # 计算每个样本的 Focal Loss
        targets_one_hot = F.one_hot(targets, num_classes=inputs.size(1)).float()
        cross_entropy_loss = F.cross_entropy(inputs, targets, reduction='none')
        focal_loss = self.alpha * ((1 - probs) ** self.gamma) * cross_entropy_loss
        
        if self.reduction == 'mean':
            return focal_loss.mean()
        elif self.reduction == 'sum':
            return focal_loss.sum()
        else:
            return focal_loss

# 模型的原始输出 (logits)
inputs = torch.tensor([[0.2, 0.5, 0.3], [0.1, 0.7, 0.2]], dtype=torch.float)

# 目标标签
target = torch.tensor([1, 2], dtype=torch.long)

# 定义 Focal Loss
focal_loss = FocalLoss(alpha=1.0, gamma=2.0)

# 计算损失
output = focal_loss(inputs, target)

print('Focal Loss 损失函数的计算结果为', output.item())

解释

  • inputs 是模型的原始输出(logits),未经 softmax 激活。
  • target 是目标标签,表示真实类别。
  • alphagamma 是调节参数,用于调整损失函数的行为。
  • reduction 参数控制损失的返回方式:'none' 返回每个样本的损失,'sum' 返回所有样本损失的总和,'mean' 返回所有样本损失的均值。

三元组损失

功能:三元组损失用于训练模型以学习特征空间中样本的嵌入(embedding),使得相同类别的样本距离更近,而不同类别的样本距离更远。这种损失函数特别适用于人脸识别、图像检索等任务。

计算公式

主要参数

  • margin: 边距超参数,用于控制正负样本之间的距离差。
  • reduction: 计算模式。可选值为 'none'(不进行缩减)、'sum'(返回所有样本损失的总和)、'mean'(返回所有样本损失的均值)。
import torch
import torch.nn as nn
import torch.nn.functional as F

class TripletLoss(nn.Module):
    def __init__(self, margin=1.0, reduction='mean'):
        super(TripletLoss, self).__init__()
        self.margin = margin
        self.reduction = reduction

    def forward(self, anchor, positive, negative):
        # 计算锚点与正样本之间的距离
        pos_dist = F.pairwise_distance(anchor, positive, p=2)
        # 计算锚点与负样本之间的距离
        neg_dist = F.pairwise_distance(anchor, negative, p=2)
        # 计算三元组损失
        loss = F.relu(pos_dist - neg_dist + self.margin)

        if self.reduction == 'mean':
            return loss.mean()
        elif self.reduction == 'sum':
            return loss.sum()
        else:
            return loss

# 模型的锚点、正样本和负样本
anchor = torch.tensor([[0.5, 0.5], [0.6, 0.6]], dtype=torch.float)
positive = torch.tensor([[0.5, 0.7], [0.6, 0.7]], dtype=torch.float)
negative = torch.tensor([[0.9, 0.9], [0.8, 0.8]], dtype=torch.float)

# 定义三元组损失
triplet_loss = TripletLoss(margin=1.0)

# 计算损失
output = triplet_loss(anchor, positive, negative)

print('Triplet Loss 损失函数的计算结果为', output.item())

解释

  • anchor 是锚点样本的特征向量。
  • positive 是与锚点同一类别的正样本的特征向量。
  • negative 是与锚点不同类别的负样本的特征向量。
  • margin 是边距超参数,用来确保正样本和负样本之间的距离差异。

HingEmbeddingLoss

功能HingeEmbeddingLoss 是一种损失函数,主要用于二分类任务中的嵌入学习。它旨在最小化相似样本的距离,并最大化不相似样本的距离,从而将样本嵌入到一个有意义的空间中。特别适用于对具有标签的嵌入空间进行训练,例如在人脸识别等任务中。

主要参数:

reduction:计算模式,可为 none/sum/mean。

margin:边界值

计算公式

loss_f = nn.HingeEmbeddingLoss()
inputs = torch.tensor([[1., 0.8, 0.5]])
target = torch.tensor([[1, 1, -1]])
output = loss_f(inputs,target)

print('HingEmbeddingLoss损失函数的计算结果为',output)

余弦相似度

torch.nn.CosineEmbeddingLoss 是 PyTorch 提供的一个损失函数,用于计算两个向量之间的余弦相似度,并根据目标标签计算损失。这里是对你提供的内容的详细说明:

功能

CosineEmbeddingLoss 通过计算两个向量的余弦相似度,并与目标值(1 或 -1)进行比较,来计算损失。它可以用来训练模型,使得相似的向量对的余弦相似度接近1,而不相似的向量对的余弦相似度接近-1。

参数

  • margin: 这是一个浮点数,通常取值范围在 [−1,1][-1, 1][−1,1] 之间。推荐的取值范围是 [0,0.5][0, 0.5][0,0.5]。这个参数在计算中用来调整距离的阈值。
  • reduction: 计算损失的方式,可能的取值有:
    • 'none':不对损失进行任何归约,返回每个样本的损失。
    • 'sum':对所有样本的损失进行求和。
    • 'mean':对所有样本的损失取平均值。

计算公式

import torch
import torch.nn as nn

loss_f = nn.CosineEmbeddingLoss()
inputs_1 = torch.tensor([[0.3, 0.5, 0.7], [0.3, 0.5, 0.7]])
inputs_2 = torch.tensor([[0.1, 0.3, 0.5], [0.1, 0.3, 0.5]])
target = torch.tensor([1, -1], dtype=torch.float)
output = loss_f(inputs_1, inputs_2, target)

print('CosineEmbeddingLoss损失函数的计算结果为', output)

CTC损失函数

torch.nn.CTCLoss 是一个用于解决时序数据(如语音识别或手写识别)中分类问题的损失函数。CTC(Connectionist Temporal Classification)损失函数允许模型在预测时不需要对齐的标签序列,这对于处理输入长度与目标长度不匹配的任务尤其重要。

功能

CTC 损失函数计算输入序列和目标序列之间的损失,考虑了目标序列的所有可能排列。它通过对所有可能的对齐方式计算概率的和来实现,允许在训练过程中进行目标对齐的推断。

主要参数

  • blank: 空白标签的索引,通常设置为0。空白标签表示模型在当前位置不输出任何字符。
  • reduction: 损失的计算模式,可选值为:
    • 'none':不对损失进行归约,返回每个样本的损失。
    • 'sum':对所有样本的损失进行求和。
    • 'mean':对所有样本的损失取平均值。
  • zero_infinity: 是否将无穷大的损失值设置为0。默认值为 False

计算公式

CTC 损失函数通过以下步骤计算:

  1. 计算概率: 输入序列的每个时间步对每个标签(包括空白标签)的概率。
  2. 对齐所有可能的标签序列: 计算所有可能的对齐方式(包括重复标签的排列)。
  3. 计算损失: 通过对所有可能对齐的概率进行求和,然后取对数来计算损失。
import torch
import torch.nn as nn

T = 50      # 输入序列长度
C = 20      # 类别数(包括空白标签)
N = 16      # 批量大小
S = 30      # 批量中最长目标序列的长度(填充长度)
S_min = 10  # 最小目标长度

# 初始化随机输入向量,大小为 (T, N, C)
input = torch.randn(T, N, C).log_softmax(2).detach().requires_grad_()

# 初始化随机目标序列 (0 = 空白, 1:C = 类别)
target = torch.randint(low=1, high=C, size=(N, S), dtype=torch.long)

input_lengths = torch.full(size=(N,), fill_value=T, dtype=torch.long)
target_lengths = torch.randint(low=S_min, high=S, size=(N,), dtype=torch.long)

ctc_loss = nn.CTCLoss()
loss = ctc_loss(input, target, input_lengths, target_lengths)
loss.backward()

print('CTCLoss损失函数的计算结果为', loss)

七、训练和评估

在完成了模型的训练后,需要在测试集/验证集上完成模型的验证,以确保我们的模型具有泛化能力、不会出现过拟合等问题。在PyTorch中,训练和评估的流程是一致的,只是在训练过程中需要将模型的参数进行更新,而在评估过程中则不需要更新参数。

验证/测试的流程基本与训练过程一致,不同点在于:

  • 需要预先设置torch.no_grad,以及将model调至eval模式

  • 不需要将优化器的梯度置零

  • 不需要将loss反向回传到网络

  • 不需要更新optimizer

八、可视化

可视化是深度学习工作流中非常重要的一部分,可以帮助理解模型的行为和性能。以下是一些常见的可视化方法和工具:

1. 训练/验证过程的损失函数曲线

训练过程中,可以绘制损失函数和准确率的变化曲线,以监控模型的训练和验证表现:

import matplotlib.pyplot as plt

# 假设 train_losses 和 val_losses 是存储损失的列表
plt.figure()
plt.plot(train_losses, label='Train Loss')
plt.plot(val_losses, label='Validation Loss')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.legend()
plt.show()

2. ROC曲线

ROC(Receiver Operating Characteristic)曲线是评估分类模型性能的常用方法。你可以使用 sklearn.metrics 来绘制 ROC 曲线:

from sklearn.metrics import roc_curve, auc
import matplotlib.pyplot as plt

# 假设 y_true 是真实标签,y_score 是模型预测的概率分数
fpr, tpr, _ = roc_curve(y_true, y_score)
roc_auc = auc(fpr, tpr)

plt.figure()
plt.plot(fpr, tpr, color='darkorange', lw=2, label='ROC curve (area = %0.2f)' % roc_auc)
plt.plot([0, 1], [0, 1], color='navy', lw=2, linestyle='--')
plt.xlim([0.0, 1.0])
plt.ylim([0.0, 1.05])
plt.xlabel('False Positive Rate')
plt.ylabel('True Positive Rate')
plt.title('Receiver Operating Characteristic')
plt.legend(loc='lower right')
plt.show()

3. 卷积核可视化

对于卷积神经网络,可以可视化卷积核以理解模型学到的特征。以下是一个简单的卷积核可视化示例:

import matplotlib.pyplot as plt

# 假设 model 是你的卷积神经网络,conv1 是第一层卷积层
conv1_weights = model.conv1.weight.data.cpu().numpy()

# 绘制前 10 个卷积核
fig, axes = plt.subplots(1, 10, figsize=(20, 5))
for i, ax in enumerate(axes):
    ax.imshow(conv1_weights[i, 0, :, :], cmap='gray')
    ax.axis('off')
plt.show()

4. 梯度分布可视化

可以可视化梯度分布,以监控训练过程中的梯度情况:

import matplotlib.pyplot as plt

def plot_grad_flow(named_parameters):
    # 使用 named_parameters 提取梯度信息
    ave_grads = []
    layers = []
    for n, p in named_parameters:
        if p.requires_grad and "bias" not in n:
            layers.append(n)
            ave_grads.append(p.grad.abs().mean())
    
    plt.figure(figsize=(10, 5))
    plt.plot(ave_grads, alpha=0.5, color='b', label='average gradient')
    plt.xticks(range(0, len(ave_grads)), layers, rotation=90)
    plt.xlabel('Layers')
    plt.ylabel('Average gradient')
    plt.title('Gradient flow')
    plt.legend(loc='upper right')
    plt.show()

plot_grad_flow(model.named_parameters())

5. 特征图可视化

特征图可视化可以帮助理解网络对输入的反应:

import matplotlib.pyplot as plt

def visualize_feature_maps(model, input_image):
    # 假设 model 是你的卷积网络,input_image 是输入图像
    layers = [layer for layer in model.children() if isinstance(layer, nn.Conv2d)]
    x = input_image.unsqueeze(0)
    
    for layer in layers:
        x = layer(x)
        feature_maps = x[0].detach().cpu().numpy()
        for i in range(feature_maps.shape[0]):
            plt.subplot(1, feature_maps.shape[0], i + 1)
            plt.imshow(feature_maps[i, :, :], cmap='gray')
            plt.axis('off')
        plt.show()

visualize_feature_maps(model, input_image)

九、PyTorch优化器

优化器在深度学习中扮演了关键角色,它们通过调整网络参数来最小化损失函数,使模型的输出更接近真实标签。

优化器概述

在深度学习中,优化器的主要任务是根据网络的梯度信息更新参数,以最小化损失函数。PyTorch 提供了多种优化器,每种优化器都有其独特的更新策略和优缺点。

1. 常见优化器

SGD(Stochastic Gradient Descent)

最基础的优化器,通过计算梯度并更新参数。可以加上动量(momentum)来加速收敛。

import torch.optim as optim

optimizer = optim.SGD(model.parameters(), lr=0.01, momentum=0.9)
Adam(Adaptive Moment Estimation)

一种自适应学习率优化器,通过计算一阶矩(均值)和二阶矩(方差)来调整学习率。

optimizer = optim.Adam(model.parameters(), lr=0.001)
RMSprop

适用于处理非平稳目标的优化器,计算每个参数的自适应学习率。

optimizer = optim.RMSprop(model.parameters(), lr=0.01, alpha=0.99)
Adagrad

为每个参数自适应调整学习率,适合稀疏数据。

optimizer = optim.Adagrad(model.parameters(), lr=0.01)

2. 优化器的属性和构造

params:需要优化的参数列表,一般是通过 model.parameters() 获得。
lr:学习率,决定每次更新的步长。
momentum:用于 SGD 优化器,控制动量的影响。
weight_decay:权重衰减,防止过拟合。

3. 使用优化器

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

# 定义模型
model = nn.Sequential(
    nn.Linear(10, 10),
    nn.ReLU(),
    nn.Linear(10, 1)
)

# 定义损失函数
criterion = nn.MSELoss()

# 定义优化器
optimizer = optim.Adam(model.parameters(), lr=0.001)

# 训练过程
for epoch in range(num_epochs):
    for data, target in train_loader:
        optimizer.zero_grad()  # 清除旧的梯度
        output = model(data)  # 前向传播
        loss = criterion(output, target)  # 计算损失
        loss.backward()  # 反向传播
        optimizer.step()  # 更新参数

4. 优化器的对比

  • SGD:简单有效,适用于大多数任务。引入动量可以加速收敛。
  • Adam:结合了动量和自适应学习率,通常表现良好,适用于复杂任务。
  • RMSprop:适用于处理非平稳目标的数据,如 RNN。
  • Adagrad:适合处理稀疏数据,但学习率会迅速下降。

总结

通过选择合适的优化器和调整其参数,可以有效提高模型的训练效率和最终性能。理解不同优化器的特性,并在具体任务中进行实验,可以帮助找到最佳的优化方案。


总结

以上就是今天的内容,本文仅仅简单介绍了pytorch的主要组成模块。

  • 12
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值