基于pytorch搭建CNN

先上代码

import torch
import torch.nn as nn
import torch.optim as optim
import torch.nn.functional as F
from torchvision import datasets, transforms
import matplotlib.pyplot as plt
import numpy as np
import pandas as pd
import matplotlibmatplotlib.use('tkAgg')# 设置图形配置
config = {"font.family": 'serif',"mathtext.fontset": 'stix',  # matplotlib渲染数学字体时使用的字体,和Times New Roman差别不大"font.serif": ['SimSun'],  # 宋体'axes.unicode_minus': False  # 处理负号,即-号
}
matplotlib.rcParams.update(config)# 定义超参数
input_size = 28  # 图像的尺寸为28*28*1
num_classes = 10  # 一共有10个类别的结果
num_epochs = 3
batch_size = 64  # 一个批次训练64张图片
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")train_dataset = datasets.MNIST(root='./data', train=True, transform=transforms.ToTensor(), download=True)test_dataset = datasets.MNIST(root='./data', train=False, transform=transforms.ToTensor())# 构建batch数据集
train_loader = torch.utils.data.DataLoader(dataset=train_dataset, batch_size=batch_size, shuffle=True)test_loader = torch.utils.data.DataLoader(dataset=test_dataset, batch_size=batch_size, shuffle=True)class CNN(nn.Module):def __init__(self):super().__init__()self.conv1 = nn.Sequential(  # 输入大小(1,28,28)nn.Conv2d(in_channels=1,  # 灰度图out_channels=16,  # 得到的特征图的个数(也是使用卷积核的个数)kernel_size=5,  # 卷积核的大小stride=1,  # 步长padding=2,  # 边缘填充,如果希望得到的特征图大小和原来一样,那么padding=(kernel_size-1)/2 if stride = 1),nn.ReLU(),nn.MaxPool2d(kernel_size=2),  # 池化层操作区域(2*2),输出结果为(16,14,14))self.conv2 = nn.Sequential(nn.Conv2d(16, 32, 5, 1, 2),nn.ReLU(),nn.MaxPool2d(2),  # 输出(32,7,7))self.out = nn.Linear(32 * 7 * 7, 10)  # 全连接层def forward(self, x):x = self.conv1(x)x = self.conv2(x)x = x.view(x.size(0), -1)output = self.out(x)return output# 计算准确率
def accuracy(predictions, labels):pred = torch.max(predictions.data, 1)[1]rights = pred.eq(labels.data.view_as(pred)).sum()return rights, len(labels)# 具体实例化
net = CNN().to(device)# 损失函数
criterion = nn.CrossEntropyLoss()# 优化器
optimizer = optim.Adam(net.parameters(), lr=0.001)for epoch in range(num_epochs):train_rights = []for batch_idx, (data, target) in enumerate(train_loader):data, target = data.to(device), target.to(device)  # 数据和标签移动到 GPUnet.train()output = net(data)loss = criterion(output, target)optimizer.zero_grad()loss.backward()optimizer.step()right = accuracy(output, target)train_rights.append(right)if batch_idx % 100 == 0:net.eval()val_rights = []with torch.no_grad():  # 测试时不计算梯度以节省内存for (data, target) in test_loader:data, target = data.to(device), target.to(device)  # 测试数据也要移动到 GPUoutput = net(data)right = accuracy(output, target)val_rights.append(right)train_r = (sum([tup[0] for tup in train_rights]), sum([tup[1] for tup in train_rights]))val_r = (sum([tup[0] for tup in val_rights]), sum([tup[1] for tup in val_rights]))print('当前epoch:{}[{}/{} ({:.0f}%)]\t损失:{:.6f}\t 训练集准确率:{:.2f}%\t测试集正确率:{:.2f}%'.format(epoch, batch_idx * batch_size, len(train_loader.dataset),100 * batch_idx / len(train_loader),loss.data,100 * train_r[0].item() / train_r[1],  # 使用 item() 获取标量100 * val_r[0].item() / val_r[1]))

详细解释

数据准备

train_dataset = datasets.MNIST(root='./data', train=True, transform=transforms.ToTensor(), download=True)test_dataset = datasets.MNIST(root='./data', train=False, transform=transforms.ToTensor())# 构建batch数据集
train_loader = torch.utils.data.DataLoader(dataset=train_dataset, batch_size=batch_size, shuffle=True)test_loader = torch.utils.data.DataLoader(dataset=test_dataset, batch_size=batch_size, shuffle=True)

train_dataset=datasets.MNIST(root='./data',train=True,transform=transforms.ToTensor(), download=True)

  • datasets.MNIST: 这是PyTorch提供的一个数据集类,用于加载MNIST手写数字数据集。MNIST数据集包含60,000张训练图像和10,000张测试图像,每张图像都是28x28像素的灰度图像,表示0到9之间的数字。

  • root='./data': 这是数据集下载和存储的根目录。在这个例子中,数据集将被下载到当前目录下的./data文件夹中。

  • train=True: 这个参数指定加载的是训练数据集。如果设置为False,则会加载测试数据集。

  • transform=transforms.ToTensor(): 这是数据预处理的一个步骤。transforms.ToTensor()将PIL Image或numpy数组转换为PyTorch张量(Tensor),并且将像素值从[0, 255]范围归一化到[0, 1]范围。

  • download=True: 如果数据集尚未下载到指定的root目录,这个参数会自动下载数据集。

test_dataset=datasets.MNIST(root='./data',train=False,transform=transforms.ToTensor())

  • 这行代码与第一行类似,但train=False表示加载的是MNIST的测试数据集。测试数据集包含10,000张图像,用于评估模型的性能。

  • download=True没有在这里出现,因为测试数据集通常在下载训练数据集时一同下载。

train_loader=torch.utils.data.DataLoader(dataset=train_dataset,batch_size=batch_size, shuffle=True)

  • torch.utils.data.DataLoader: 这是PyTorch中用于构建数据加载器的类。DataLoader可以将数据集打包成mini-batch的形式,便于模型训练。

  • dataset=train_dataset: 这是我们之前创建的训练数据集对象。DataLoader将从这个数据集中抽取数据。

  • batch_size=batch_size: 这是每个mini-batch的大小。batch_size是一个预定义的变量,通常在代码的其他地方定义。

  • shuffle=True: 这个参数表示在每次迭代时是否打乱数据集。打乱数据可以避免模型学习到数据顺序的偏差,从而提高训练效果。

test_loader=torch.utils.data.DataLoader(dataset=test_dataset,batch_size=batch_size, shuffle=True)

  • 这行代码与第三行类似,但使用的是测试数据集。测试数据集通常不需要在每次迭代时打乱数据,但在这个例子中,shuffle=True表示在每次测试时也会打乱数据集。这在某些情况下可能并不必要,但在代码中默认了这种设置。

数据的结构

这样讲可能只能知道一些概念的东西,不知道这个数据的具体结构,下面介绍一下数据的结构:

train_dataset结构

train_dataset 是一个 torchvision.datasets.MNIST 对象,专门用于处理 MNIST 数据集。它的主要属性和方法如下:

  • 数据结构:

    • train_dataset.data: 用于存储图像数据,是一个形状为 (N, 28, 28) 的张量,其中 N 是训练样本的数量(60,000)。每个图像都是28x28的灰度图。
    • train_dataset.targets: 用于存储对应的标签,是一个一维的张量,形状为 (N,),存放着每个图像的数字标签(0-9)。
  • 常用方法:

    • __getitem__(index): 用于获取指定索引(index)的样本和标签。返回的是一个元组 (image, label),其中 image 是转换为张量后的图像,label 是相应的数字标签。
    • __len__(): 返回数据集中样本的总数,这里是60,000。

train_loader结构

train_loader 是一个 torch.utils.data.DataLoader 对象,它为 train_dataset 提供划分和迭代的方式。主要特点包括:

  • 数据结构:

    • train_loader 本身并不存储数据。它是在遍历 train_dataset 时提供数据的工具。每次迭代都将返回一个 mini-batch 的数据
    • train_loader 将生成 mini-batch 的元组,通常每次迭代返回的是 (images, labels)
      • images: 一个形状为 (batch_size, 1, 28, 28) 的张量,其中 batch_size 是指定的每个 mini-batch 的大小。
      • labels: 一个形状为 (batch_size,) 的张量,存放当前 mini-batch 中每张图像的标签。
  • 常用方法:

    • __iter__(): 使得 train_loader 可以被用在 for 循环中,每次迭代都会返回一个新的 mini-batch。
    • __len__(): 返回 DataLoader 中的总 mini-batch 数量。这通常是数据集样本数除以 batch_size。

例如,假设 batch_size 的值为 64,那么在使用 train_loader 进行迭代时,每次迭代将得到:

  • images:

    • 形状为 (64, 1, 28, 28),表示64张28x28的灰度图像。每张图像的通道数为1,因为它是灰度图。
  • labels:

    • 形状为 (64,),表示这64张图像的标签,值在0到9之间。

DataLoader生成什么?

再来介绍一下torch.utils.data.DataLoader

DataLoader 生成的是可以迭代的 mini-batch 数据。具体来说,每次迭代时,DataLoader 会返回一个 mini-batch 的数据。这个 mini-batch 通常是一个元组 (images, labels),其中:

  • images 是一个形状为 (batch_size, C, H, W) 的张量,batch_size 是每次迭代的大小,C 是图像的通道数,H 和 W 是图像的高度和宽度。
  • labels 是一个形状为 (batch_size,) 的张量,表示相应图像的标签。

如何使用enumerate?

那么我们如何遍历这个元组呢?其实python不像C/C++,遍历需要用下标遍历,比如我们像便利一个列表我们直接是for idx in list:,这个idx得到的直接是list中的值,但是有时候我们希望知道遍历的下标(用于指示此次遍历到多少遍了)。这样就可以用"enumerate"

1. enumerate 的作用

enumerate 的主要作用是在遍历一个可迭代对象时,返回一个包含索引的元组。具体来说,它会对可迭代对象中的每一个元素配上一个索引值,从 0 开始(默认),然后逐个返回索引和元素。

2. 基本语法
enumerate(iterable, start=0)

  • iterable: 任何可迭代的对象,比如列表、元组、字符串等。
  • start: 索引的起始值,默认为 0,但你可以指定其他起始值。
3. 如何使用 enumerate
示例 1:基本用法
# 一个简单的列表
fruits = ['apple', 'banana', 'mango']# 使用 enumerate 遍历列表
for index, value in enumerate(fruits):print(f"Index: {index}, Value: {value}")
输出:
Index: 0, Value: apple
Index: 1, Value: banana
Index: 2, Value: mango

在这个例子中,enumerate 为每个列表元素提供了一个索引,从 0 开始。我们通过 for 循环同时获取了索引和元素值。

示例 2:指定起始索引

你可以通过 enumerate 的第二个参数指定索引的起始值。例如,如果你想让索引从 1 开始:

fruits = ['apple', 'banana', 'mango']# 使用 enumerate 遍历列表,索引从 1 开始
for index, value in enumerate(fruits, start=1):print(f"Index: {index}, Value: {value}")

输出:

Index: 1, Value: apple
Index: 2, Value: banana
Index: 3, Value: mango

4. 在 DataLoader 中使用 enumerate

在 PyTorch 中,我们通常使用 DataLoader 来加载数据集。在训练神经网络时,我们不仅需要遍历每个 mini-batch,还需要知道当前遍历到了第几个 batch。这时,enumerate 就非常有用。

示例:在 DataLoader 中使用 enumerate

假设我们有一个 DataLoader 加载了 MNIST 数据集,我们可以使用 enumerate 来同时获取 batch 的索引和数据。

import torch
from torchvision import datasets, transforms# 定义数据集和 DataLoader
train_dataset = datasets.MNIST(root='./data', train=True, transform=transforms.ToTensor(), download=True)
train_loader = torch.utils.data.DataLoader(dataset=train_dataset, batch_size=64, shuffle=True)# 使用 enumerate 遍历 DataLoader
for batch_idx, (images, labels) in enumerate(train_loader):print(f"Batch Index: {batch_idx}, Images shape = {images.shape}, Labels shape = {labels.shape}")# 如果需要在第一个 batch 后停止,可以加一个条件判断if batch_idx == 0:break

输出:

Batch Index: 0, Images shape = torch.Size([64, 1, 28, 28]), Labels shape = torch.Size([64])

在这个例子中:

  • batch_idx 是当前 mini-batch 的索引。
  • images 和 labels 是当前 mini-batch 的数据和标签。
  • enumerate(train_loader) 返回的 batch_idx 是从 0 开始的批次索引,而 images 和 labels 是对应的批次数据。

CNN的定义

class CNN(nn.Module):def __init__(self):super().__init__()self.conv1 = nn.Sequential(  # 输入大小(1,28,28)nn.Conv2d(in_channels=1,  # 灰度图out_channels=16,  # 得到的特征图的个数(也是使用卷积核的个数)kernel_size=5,  # 卷积核的大小stride=1,  # 步长padding=2,  # 边缘填充,如果希望得到的特征图大小和原来一样,那么padding=(kernel_size-1)/2 if stride = 1),nn.ReLU(),nn.MaxPool2d(kernel_size=2),  # 池化层操作区域(2*2),输出结果为(16,14,14))self.conv2 = nn.Sequential(nn.Conv2d(16, 32, 5, 1, 2),nn.ReLU(),nn.MaxPool2d(2),  # 输出(32,7,7))self.out = nn.Linear(32 * 7 * 7, 10)  # 全连接层def forward(self, x):x = self.conv1(x)x = self.conv2(x)x = x.view(x.size(0), -1)output = self.out(x)return output

什么是类

为了讲清楚这个结构,我们从最基本的地方讲起。

1. 类和对象

在 Python 中,类是一种用于创建对象的蓝图或模板。类定义了一组属性和方法,这些属性和方法可以被实例化后的对象所使用。

定义类
class Dog:def __init__(self, name, age):self.name = nameself.age = agedef bark(self):return "Woof!"

  • __init__ 方法: 这是类的构造函数,用于初始化对象的属性。
  • self: 表示类的实例对象本身。
创建对象
my_dog = Dog("Buddy", 3)
print(my_dog.name)  # 输出: Buddy
print(my_dog.bark())  # 输出: Woof!

2. 继承

继承是面向对象编程的一个重要特性,它允许一个类(子类)继承另一个类(父类)的属性和方法。子类可以重用父类的代码,并且可以在不修改父类的情况下添加新的功能。

定义父类
class Animal:def __init__(self, name):self.name = namedef speak(self):return "I am an animal."

定义子类
class Dog(Animal):def __init__(self, name, age):super().__init__(name)  # 调用父类的构造函数self.age = agedef speak(self):return "Woof!"

  • super(): 用于调用父类的方法。
  • super().__init__(name): 调用父类 Animal 的构造函数,初始化 name 属性。
创建子类对象
my_dog = Dog("Buddy", 3)
print(my_dog.name)  # 输出: Buddy
print(my_dog.age)  # 输出: 3
print(my_dog.speak())  # 输出: Woof!

3. super() 函数

super() 函数用于调用父类的方法。它在多重继承中特别有用,因为它可以确保正确的方法解析顺序(Method Resolution Order, MRO)。

super() 的基本用法
class Animal:def __init__(self, name):self.name = namedef speak(self):return "I am an animal."class Dog(Animal):def __init__(self, name, age):super().__init__(name)  # 调用父类的构造函数self.age = agedef speak(self):return "Woof!"

  • super().__init__(name): 调用父类 Animal 的构造函数,初始化 name 属性。
super() 的多重继承
class Animal:def speak(self):return "I am an animal."class Mammal(Animal):def speak(self):return "I am a mammal."class Dog(Mammal):def speak(self):return super().speak() + " And I bark."my_dog = Dog()
print(my_dog.speak())  # 输出: I am a mammal. And I bark.

在这个例子中,super().speak() 首先调用 Mammal 类的 speak() 方法,然后在其基础上添加新的内容。

代码整体结构解释

1. 类定义与父类初始化
class CNN(nn.Module):def __init__(self):super().__init__()

  • 类定义CNN 类继承自 nn.Module
  • 父类初始化super().__init__() 调用父类 nn.Module 的构造函数确保 nn.Module 中的所有属性和方法被正确初始化
2. 第一个卷积层 (self.conv1)
self.conv1 = nn.Sequential(  # 输入大小(1,28,28)nn.Conv2d(in_channels=1,  # 灰度图out_channels=16,  # 得到的特征图的个数(也是使用卷积核的个数)kernel_size=5,  # 卷积核的大小stride=1,  # 步长padding=2,  # 边缘填充,如果希望得到的特征图大小和原来一样,那么padding=(kernel_size-1)/2 if stride = 1),nn.ReLU(),nn.MaxPool2d(kernel_size=2),  # 池化层操作区域(2*2),输出结果为(16,14,14)
)

  • nn.Sequential: 是一个容器,允许我们将多个层按顺序组合起来。
  • nn.Conv2d: 2D 卷积层,用于提取图像特征。
    • in_channels=1: 输入通道数为 1(灰度图像)。
    • out_channels=16: 输出通道数为 16,即使用 16 个卷积核,得到的特征图有 16 个。
    • kernel_size=5: 卷积核大小为 5x5。
    • stride=1: 卷积操作的步长为 1。
    • padding=2: 边缘填充 2 个像素,确保输出特征图大小与输入一致。
  • nn.ReLU: 激活函数,引入非线性。
  • nn.MaxPool2d: 最大池化层,用于减少特征图的空间尺寸。
    • kernel_size=2: 池化窗口大小为 2x2,步长默认为 2,输出特征图大小为 (16, 14, 14)。
3. 第二个卷积层 (self.conv2)
self.conv2 = nn.Sequential(nn.Conv2d(16, 32, 5, 1, 2),nn.ReLU(),nn.MaxPool2d(2),  # 输出(32,7,7)
)

  • nn.Conv2d: 第二个卷积层,输入通道数为 16(来自第一个卷积层的输出),输出通道数为 32。
    • in_channels=16: 输入通道数为 16。
    • out_channels=32: 输出通道数为 32。
    • kernel_size=5: 卷积核大小为 5x5。
    • stride=1: 卷积操作的步长为 1。
    • padding=2: 边缘填充 2 个像素,确保输出特征图大小与输入一致。
  • nn.ReLU: 激活函数,引入非线性。
  • nn.MaxPool2d: 最大池化层,用于减少特征图的空间尺寸。
    • kernel_size=2: 池化窗口大小为 2x2,步长默认为 2,输出特征图大小为 (32, 7, 7)。
4. 全连接层 (self.out)
self.out = nn.Linear(32 * 7 * 7, 10)  # 全连接层

  • nn.Linear: 全连接层,将卷积层输出的特征图转换为最终的分类结果。
    • 32 * 7 * 7: 输入特征的大小,32 个特征图,每个大小为 7x7。
    • 10: 输出大小为 10,对应 10 个类别的分类任务。
5. 前向传播函数 (forward)
def forward(self, x):x = self.conv1(x)x = self.conv2(x)x = x.view(x.size(0), -1)output = self.out(x)return output

  • forward: 前向传播函数,定义了数据在神经网络中的流动路径。
    • self.conv1(x): 将输入数据 x 通过第一个卷积层 conv1
    • self.conv2(x): 将经过 conv1 处理后的数据通过第二个卷积层 conv2
    • x.view(x.size(0), -1): 将卷积层的输出展平为一维向量,x.size(0) 是批量大小,-1 表示自动计算展平后的长度。
    • self.out(x): 将展平后的数据通过全连接层 out,得到最终的输出结果。
    • return output: 返回输出结果。

各个方法的具体解释

1. nn.Conv2d 的参数

nn.Conv2d 用于创建二维卷积层,它的构造函数有以下参数:

nn.Conv2d(in_channels,      # 输入通道数out_channels,     # 输出通道数kernel_size,      # 卷积核的大小stride=1,         # 步长,默认为1padding=0,        # 填充,默认为0dilation=1,       # 空洞卷积的膨胀系数,默认为1groups=1,         # 分组卷积的数量,默认为1bias=True,        # 是否使用偏置
)

  • in_channels (int): 输入的通道数。对于灰度图像一般为 1,对于 RGB 图像一般为 3
  • out_channels (int): 输出的通道数,即卷积核的个数
  • kernel_size (int 或 tuple): 卷积核的尺寸。可以是一个整数(表示方形卷积核),也可以是一个二元组,表示长和宽,例如 (3, 5)
  • stride (int 或 tuple): 步长,默认为 1。可以是一个整数(表示在两个方向上的步长相同),也可以是一个二元组,例如 (1, 2)(表示在两个方向上不同)
  • padding (int 或 tuple): 在输入的边缘补充的零的数量(加几圈0)。也可以是一个整数或二元组,类似于 stride 的情况。
  • dilation (int 或 tuple): 控制卷积核元素之间的间隔,默认为 1。
  • groups (int): 控制卷积的分组。默认为 1 谷歌母公司的 MobileNet 等模型使用分组卷积时将其设为 2 或更多。
  • bias (bool): 是否使用偏置,默认为 True。
2. nn.ReLU 的参数
nn.ReLU(inplace=False)  # 'inplace' 是一个可选参数

  • inplace (bool): 是否进行原地操作。如果为 True,ReLU 会直接改变输入的值,可以节省内存,但不适合某些场景。默认为 False。
3. nn.MaxPool2d 的参数
nn.MaxPool2d(kernel_size,      # 池化窗口的大小stride=None,      # 步长,默认为 kernel_sizepadding=0,        # 填充大小,默认为0dilation=1,       # 空洞卷积的膨胀系数,默认为1return_indices=False,  # 是否返回池化 indicesceil_mode=False   # 是否向上取整
)

  • kernel_size (int 或 tuple): 池化窗口的大小,可以是一个整数或二元组
  • stride (int 或 tuple): 步长,默认为 kernel_size
  • padding (int 或 tuple): 在输入的边缘补充的零的数量。
  • dilation (int 或 tuple): 控制池化的间隔,默认为 1。
  • return_indices (bool): 如果为 True,返回每个池化区域的索引。默认为 False。
  • ceil_mode (bool): 若为 True,使用向上取整。在输入图像尺寸非常小的情况下可能有帮助。默认为 False。
4. nn.Linear 的参数
nn.Linear(in_features, out_features, bias=True)

  • in_features (int): 输入的特征数量,即上一层的输出维度。
  • out_features (int): 输出的特征数量,即本层的输出维度。
  • bias (bool): 是否使用偏置,默认为 True。
5. view 方法的参数
x.view(size)  # size 可以是一个整数或元组

  • size (int 或 tuple): 用于重新定义张量的形状,第一个维度通常是 batch size,有时你可以使用 -1 来自动推导某个维度的大小。例如,x.view(x.size(0), -1) 中的 -1 表示根据其他维度的大小自动计算它的值。

在PyTorch中,张量的 .size() 方法返回一个 torch.Size 对象,它包含了张量的形状信息。具体来说,.size(0) 是用来获取张量的第一维度的大小。

假设你有一个形状为 (batch_size, channels, height, width) 的四维张量 x,那么在不同维度下使用 .size() 会返回不同的值:

  • x.size(0):返回第一个维度的大小,即 batch_size
  • x.size(1):返回第二个维度的大小,即 channels
  • x.size(2):返回第三个维度的大小,即 height
  • x.size(3):返回第四个维度的大小,即 width
import torch# 创建一个四维张量
x = torch.randn(32, 3, 64, 64)  # 批量大小为 32,通道数为 3,高度和宽度均为 64# 获取各维度的大小
batch_size = x.size(0)  # 32
channels = x.size(1)    # 3
height = x.size(2)      # 64
width = x.size(3)       # 64print(f"Batch Size: {batch_size}")
print(f"Channels: {channels}")
print(f"Height: {height}")
print(f"Width: {width}")

输出:::Batch Size: 32 Channels: 3 Height: 64 Width: 64

训练过程

训练前准备

# 具体实例化
net = CNN().to(device)# 损失函数
criterion = nn.CrossEntropyLoss()# 优化器
optimizer = optim.Adam(net.parameters(), lr=0.001)

训练过程


for epoch in range(num_epochs):train_rights = []for batch_idx, (data, target) in enumerate(train_loader):data, target = data.to(device), target.to(device)  # 数据和标签移动到 GPUnet.train()output = net(data)loss = criterion(output, target)optimizer.zero_grad()loss.backward()optimizer.step()right = accuracy(output, target)train_rights.append(right)if batch_idx % 100 == 0:net.eval()val_rights = []with torch.no_grad():  # 测试时不计算梯度以节省内存for (data, target) in test_loader:data, target = data.to(device), target.to(device)  # 测试数据也要移动到 GPUoutput = net(data)right = accuracy(output, target)val_rights.append(right)train_r = (sum([tup[0] for tup in train_rights]), sum([tup[1] for tup in train_rights]))val_r = (sum([tup[0] for tup in val_rights]), sum([tup[1] for tup in val_rights]))print('当前epoch:{}[{}/{} ({:.0f}%)]\t损失:{:.6f}\t 训练集准确率:{:.2f}%\t测试集正确率:{:.2f}%'.format(epoch, batch_idx * batch_size, len(train_loader.dataset),100 * batch_idx / len(train_loader),loss.data,100 * train_r[0].item() / train_r[1],  # 使用 item() 获取标量100 * val_r[0].item() / val_r[1]))

1. 主要循环 for epoch in range(num_epochs)

这个循环遍历所有的训练周期(epoch)。每个 epoch 代表了一次完整的训练数据集遍历。

2. 初始化 train_rights 列表

train_rights = []

train_rights 用于存储每个批次(batch)的准确率信息。准确率信息通常以元组的形式存储,比如 (正确预测的数量, 总样本数量)

3. 训练数据加载与处理

for batch_idx, (data, target) in enumerate(train_loader):data, target = data.to(device), target.to(device)

  • train_loader: 一个 PyTorch DataLoader 对象,用于加载训练数据批次。
  • batch_idx: 当前批次的索引。
  • data: 输入数据(如图像)。
  • target: 目标标签(如分类标签)。
  • data.to(device) 和 target.to(device): 将数据和标签移动到指定的设备(如 GPU)。

4. 设置模型为训练模式

net.train()

net.train() 将模型设置为训练模式。这会影响某些层的行为,如 Dropout 和 BatchNorm。在训练模式下,Dropout 会随机丢弃神经元,BatchNorm 会更新其内部统计量。

5. 前向传播

output = net(data)

使用输入数据进行前向传播,得到模型的输出结果 output

6. 计算损失

loss = criterion(output, target)

使用预定义的损失函数 criterion 计算模型输出 output 和目标标签 target 之间的损失值。

7. 清零梯度

optimizer.zero_grad()

在每次反向传播之前,需要清零梯度,以避免梯度累积。

8. 反向传播

loss.backward()

调用 backward() 方法进行反向传播,计算损失函数对模型参数的梯度。

9. 更新模型参数

optimizer.step()

使用优化器更新模型参数,根据计算出的梯度调整参数。

10. 计算训练准确率

right = accuracy(output, target)
train_rights.append(right)

计算当前批次的准确率 right,并将其添加到 train_rights 列表中。

11. 验证模型性能

if batch_idx % 100 == 0:net.eval()val_rights = []with torch.no_grad():for (data, target) in test_loader:data, target = data.to(device), target.to(device)output = net(data)right = accuracy(output, target)val_rights.append(right)

  • net.eval(): 将模型设置为评估模式。在评估模式下,Dropout 和 BatchNorm 等层的行为会有所不同。
  • torch.no_grad(): 在评估时禁用梯度计算,以节省内存和计算资源。
  • test_loader: 用于加载验证数据批次。
  • val_rights: 用于存储验证批次(batch)的准确率信息。

12. 计算并打印训练和验证的准确率

train_r = (sum([tup[0] for tup in train_rights]), sum([tup[1] for tup in train_rights]))
val_r = (sum([tup[0] for tup in val_rights]), sum([tup[1] for tup in val_rights]))print('当前epoch:{}[{}/{} ({:.0f}%)]\t损失:{:.6f}\t 训练集准确率:{:.2f}%\t测试集正确率:{:.2f}%'.format(epoch, batch_idx * batch_size, len(train_loader.dataset),100 * batch_idx / len(train_loader),loss.data,100 * train_r[0].item() / train_r[1],  # 使用 item() 获取标量100 * val_r[0].item() / val_r[1]
))

  • train_r: 计算训练集的总正确预测数量和总样本数量。
  • val_r: 计算验证集的总正确预测数量和总样本数量。
  • print: 输出当前 epoch、批次数、损失值、训练集准确率和验证集准确率。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/pingmian/57568.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

书生营 L0G4000 玩转HF/魔搭/魔乐社区

模型下载 在codespace上给环境装包,按照教材即可 运行后下载成功 建立下载json文件 新建下载internlm2_5-chat-1_8b的json文件 运行结果 基本上没啥问题,照着教程来就行 模型上传(可选) push的时候需要先认证token 最后的…

Linux 权限的理解

内容摘要 本文内容包括shell的运行原理,包括外壳程序的原理、理解、和意义,以及从两个方面对于权限的理解(人和事物的属性)、修改文件的权限,包括修改文件的拥有者、修改文件拥有者所在的组的用户以及修改文件的三类用…

域渗透AD渗透攻击利用 MS14-068漏洞利用过程 以及域渗透中票据是什么 如何利用

目录 wmi协议远程执行 ptt票据传递使用 命令传递方式 明文口令传递 hash口令传递 票据分类 kerberos认证的简述流程 PTT攻击的过程 MS14-068 漏洞 执行过程 wmi协议远程执行 wmi服务是比smb服务高级一些的,在日志中是找不到痕迹的,但是这个主…

鸿蒙中富文本编辑与展示

富文本在鸿蒙系统如何展示和编辑的?在文章开头我们提出这个疑问,带着疑问来阅读这篇文章。 富文本用途可以展示图文混排的内容,在日常App 中非常常见,比如微博的发布与展示,朋友圈的发布与展示,都在使用富文…

【树莓派 5B】anaconda换源 更换清华源

【树莓派 5B】anaconda换源 更换清华源 前言 本文基于树莓派5B上运行的 Raspberrypi-OS-64bit (Debian 12 Bookworm)平台,更换 Anaconda 官方源为清华大学镜像源,旨在解决网络连接超时、连接官方源失败、下载速度慢的问题。 参…

spring (Aop) day 1024

ok了家人们,继续学习spring ,这段知识点有点绕,建议搭配b站的视频去学,passion!!! 八.AOP-面向切面编程 8.1 动态代理 8.1.1 概述 什么是代理?在现实生活中,代理很常见…

学习虚幻C++开发日志——TSet

TSet 官方文档:虚幻引擎中的Set容器 | 虚幻引擎 5.5 文档 | Epic Developer Community (epicgames.com) TSet 是通过对元素求值的可覆盖函数,使用数据值本身作为键,而不是将数据值与独立的键相关联。 默认情况下,TSet 不支持重…

iOS 18.2开发者预览版 Beta 1版本发布,欧盟允许卸载应用商店

苹果今天为开发人员推送了iOS 18.2开发者预览版 Beta 1版本 更新(内部版本号:22C5109p),本次更新距离上次发布 Beta / RC 间隔 2 天。该版本仅适用于支持Apple Intelligence的设备,包括iPhone 15 Pro系列和iPhone 16系…

【设计模式系列】观察者模式

一、什么是观察者模式 观察者模式(Observer Pattern)是一种行为设计模式,它定义了对象之间的一对多依赖关系,当一个对象的状态发生变化时,所有依赖于它的对象都会得到通知并自动更新。这种模式也被称为发布-订阅模式&…

「重磅」中国电信数据湖+数据中台实施方案(附60页方案)

来源:公众号-数据分析小兵 作者按 哈喽,大家好,我是数据分析小兵,今天小兵向大家分享中国电信基于数据湖的数据中台实施方案。 方案核心内容一:数据湖的搭建与实施 数据湖是一套针对海量多源异构数据,具备数据采集、数据存储、数据计算、数据访问、数据管理的技术架构…

1.CentOS安装

CentOS安装 新建虚拟机 选择安装方式 指定镜像方式 选择操作系统类型 设置虚拟机名称和位置 指定磁盘大小 点击“自定义硬件” 指定内存大小 指定镜像位置 点击“开启此虚拟机” 选择“Install CentOS 7”并回车 选择语言 选择安装“GNOME桌面”环境 配置安装位置 配置网络和…

springboot高校科研项目和课题管理平台-计算机毕业设计源码18198

摘要 随着科技的快速发展和高校科研水平的持续提高,科研项目和课题的管理逐渐变得复杂多样。传统的管理方式,如使用纸质文档或简单的电子表格进行记录,已经无法满足现代高校科研管理的需求。这不仅影响了科研工作的效率,还可能导致…

CentOS 7(Linux)详细安装教程

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 一、CentOS镜像的下载(准备工作) 我选择的是其他镜像源的下载地址: Index of /centos-vault/7.6.1810/isos/x86_64/ | 南阳理工学院开源镜…

8个方法教会你提高企业培训效率

培训成本是企业中的一个复杂问题。它完全取决于课程内容、培训方法以及成本效益。在计算培训费用时,公司会面临许多关于包括哪些内容、如何进行以及假设情景的问题。 企业员工培训的每个方面都会产生自己的成本。例如: 地点:我们专门找个培训…

冒泡排序(Python)

冒泡排序:依次比较相邻的两个数,将大数放在后面,小数放在前面。 n个数排序共需进行n-1趟,第一趟排序结束时,最后一个元素为所有元素中的最大值。 冒泡排序的原理 1)比较相邻元素:如果第一个比…

婚纱相册必须去摄影店吗?其实自己会拍照就能实现,性价比更高

一直以来,婚纱照都是新人们婚礼筹备中不可或缺的部分。然而,高昂的摄影店价格让不少新人望而却步。其实,只要掌握一些拍照技巧,自己在家就能制作出独一无二的婚纱相册,不仅性价比超高,还能留下更多珍贵的回…

Android 中的串口开发

一:背景 本文着重讲安卓下的串口。 由于开源的Android在各种智能设备上的使用越来越多,如车载系统等。在我们的认识中,Android OS的物理接口一般只有usb host接口和耳机接口,但其实安卓支持各种各样的工业接口,如HDM…

条码检测系统——基于MATLAB的一维条码识别

摘 要:条码技术是如今应用最广泛的识别和输入技术之一,由于其包含的信息量大,识别错误率低而在各个方面得到很大的重视。它发展迅速并被广泛应用于于工业、商业、图书出版、医疗卫生等各行各业。由我国目前发展现状来看,条码的正…

攻坚金融关键业务系统,OceanBase亮相2024金融科技大会

10月15-16日,第六届中新数字金融应用博览会与2024金融科技大会(简称“金博会”)在苏州工业园区联合举办。此次大会融合了国家级重要金融科技资源——“中国金融科技大会”,围绕“赋能金融高质量发展,金融科技创新前行”…

【C++指南】运算符重载详解

引言 C 提供了运算符重载这一特性,允许程序员为自定义类型(如类和结构体)定义运算符的行为。 通过运算符重载,可以使自定义类型对象像内置类型一样使用运算符,从而提高代码的可读性和易用性。 本文将详细介绍 C 中运算…