pytorh学习第二站:手写数字识别
一、训练程序
1、创建脚本框架mnist_demo.py:
import torch
import torchvision.datasets as dataset# data # 定义数据# net # 定义网络# loss # 损失# optimizer # 优化# training # 训练# test # 测试# save# 保存
2、下载数据集
import torch
import torchvision.datasets as dataset
import torchvision.transforms as transforms# data # 定义数据
train_data = dataset.MNIST(root='mnist_data', # 创建数据集train=True, # 书否训练transform=transforms.ToTensor, # 转换成tensordownload=True) # 是否下载# net # 定义网络# loss # 损失# optimizer # 优化# training # 训练# test # 测试# save# 保存
运行脚本,自动下载数据集。
MNIST数据集是由60000个训练样本和10000个测试样本组成,每个样本都是一张28 * 28像素的灰度手写数字图片。 一共4个文件,训练集、训练集标签、测试集、测试集标签。
文件名称 | 大小 | 内容 |
---|---|---|
train-images-idx3-ubyte.gz | 9,681 kb | 55000张训练集,5000张验证集 |
train-labels-idx1-ubyte.gz | 29 kb | 训练集图片对应的标签 |
t10k-images-idx3-ubyte.gz | 1,611 kb | 10000张测试集 |
t10k-labels-idx1-ubyte.gz | 5 kb | 测试集图片对应的标签 |
3、定义网络
# net # 定义网络
class CNN(torch.nn.Module):def __init__(self, channel=32): # 通道数(channel)指的是卷积层中的滤波器数量。# 这个参数对于控制内存使用和训练速度非常重要。super(CNN, self).__init__()self.channel = channelself.conv = torch.nn.Sequential( # 卷积# “torch.nn.Sequential” 是 PyTorch 中的一个容器类,它允许将多个模块按照顺序依次组合起来,形成一个新的模块。这样可以方便地构建神经网络模型,使得模型的定义更加简洁和清晰。# 例如,可以使用 “torch.nn.Sequential” 来快速构建一个包含多个卷积层和全连接层的神经网络。torch.nn.Conv2d(in_channels=1, out_channels=self.channel, kernel_size=5, stride=1, padding=2), # 卷积层# torch.nn.Conv2d 是 PyTorch 深度学习框架中的一个类,它定义了一个二维卷积层。在神经网络中,卷积层是核心组成部分之一,主要用于执行卷积操作,# 这种操作可以通过一组可学习的滤波器(也称为卷积核)来提取输入数据的特征。# 具体来说,torch.nn.Conv2d 类接受几个关键参数:# in_channels:输入数据的通道数。例如,对于灰度图像,通道数为 1;对于 RGB 彩色图像,通道数为 3。# out_channels:输出数据的通道数,也就是卷积层将产生多少个特征图(feature map)。# kernel_size:卷积核的大小,通常表示为一个整数或者一个包含两个整数的元组(高度和宽度)。# stride:卷积操作的步长,即卷积核在输入数据上移动的间隔。# padding:填充大小,用于在输入数据的边缘周围添加额外的像素,以保持输出数据的大小与输入数据相同或按需调整。torch.nn.BatchNorm2d(self.channel), # 归一化# torch.nn.BatchNorm2d() 是一个在 PyTorch 框架中定义的二维批量归一化(Batch Normalization)层。# 批量归一化是一种用于加速深度神经网络训练过程的技术。它通过对每一批数据进行归一化处理,使得数据的均值为0,方差为1,# 从而减少内部协变量偏移(Internal Covariate Shift)。这样做可以使得网络对于初始化权重的敏感度降低,并且允许使用更高的学习率。# 在这个上下文中,channel=32表示每个输入样本有32个特征图(feature maps)。# 二维批量归一化层通常用在卷积神经网络(CNN)中,用于处理图像数据。由于图像数据通常是二维的(高度和宽度),因此使用 BatchNorm2d# 而不是 BatchNorm1d,后者通常用于处理一维数据,如全连接层的输出。# 在实际应用中,BatchNorm2d 层通常紧跟在卷积层之后,激活函数之前。它有助于网络更快地收敛,并且可以起到一定的正则化效果,减少过拟合。# 总结一下,torch.nn.BatchNorm2d(32) 创建了一个适用于32通道输入数据的二维批量归一化层,这个层可以被添加到神经网络的模型定义中,以提高训练效率和模型性能。torch.nn.ReLU(), # 激活函数# orch.nn.ReLU () ReLU(Rectified Linear Unit,修正线性单元)将输入值小于等于 0 的部分置为 0,输入值大于 0 的部分保持不变。# 它常用于神经网络中,能够引入非线性,帮助神经网络更好地拟合复杂的数据模式,提高模型的表达能力和性能。torch.nn.MaxPool2d(kernel_size=2)) # 池化层# torch.nn.MaxPool2d” 是 PyTorch 中的一个模块,用于对输入的二维信号(图像等)进行二维最大池化操作。最大池化通常是在卷积神经网络中用于降低特征图的空间尺寸,# 同时保留重要的特征信息,它在一定窗口范围内选取最大值作为输出。例如,对于一个图像特征图,通过 MaxPool2d 可以减少计算量,提高模型的效率和对一些局部特征变化的鲁棒性。self.fc = torch.nn.Linear(14 * 14 * 32, 10) # 全连接# 这里的 torch.nn.Linear 是 PyTorch 框架中定义全连接层的类。这个类的构造函数接受两个参数:# 第一个参数 14*14*32 表示输入特征的数量。在这个例子中,它是由前面卷积层处理后的特征图大小决定的。# 假设经过一系列卷积和池化操作后,每个样本的特征被展平(flatten)成了一个 14x14x32 的三维数组,# 那么展平后的一维特征数量就是 14*14*32。# 第二个参数 10 表示输出特征的数量,也就是全连接层的神经元数量。在这个例子中,它对应于分类任务中的类别数,# 假设这是一个用于手写数字识别的任务,共有 10 个类别(0 到 9)。def forward(self, x): # 前向传播x = self.conv(x) # 之前定义的卷积操作x = x.view(x.size()[0], -1) # reshape# view方法主要有以下应用:# 一、重塑张量(Tensor)形状# 1、基本概念# 张量是深度学习中数据的基本表示形式。view方法允许在不改变张量中数据的存储顺序和内容的情况下,改变其维度结构。# 例如,一个形状为(batch_size, channels, height, width)的 4D 图像张量可以通过view方法转换为(batch_size, channels * height * width)的 2D 张量,# 这在将图像数据展平以便输入到全连接层时非常有用。# 2、内存布局# view操作是基于张量的存储布局(contiguous memory layout)进行的。如果一个张量的存储不是连续的(例如,经过某些转置操作后),在使用view之前可能需要先调用# contiguous方法来确保正确的视图重塑。# 3、动态网络架构# 在构建动态神经网络结构时,view方法可以根据不同的输入或网络中间状态动态调整张量的形状。例如,在循环神经网络(RNN)的变长输入场景中,输入序列的长度可能是可变的。# 通过view方法,可以将输入张量调整为适合网络处理的形状,然后再将处理后的结果重新调整回合适的形状用于后续操作。# 4、模型输出调整# 在一些深度学习模型中,网络的输出可能需要调整形状以匹配特定的任务要求。例如,在生成对抗网络(GAN)中,生成器的输出可能需要通过view方法调整为与目标图像相同的形状,# 以便与真实图像进行比较和计算损失。output = self.fc(x) # 全连接return output
cnn = CNN() # 定义网络
cnn = cnn.cuda() # 放入GPU
4、训练
# loss # 损失
loss_func = torch.nn.CrossEntropyLoss() # 损失函数,使用了交叉熵损失函数。常用于分类问题中,衡量模型预测结果与真实标签之间的差异# 使模型的预测结果更接近真实标签。# optimizer # 优化
optimizer = torch.optim.Adam(cnn.parameters(), lr=0.01) # 优化器,使用了Adam优化器。Adam是一种自适应学习率方法,可以自动调整学习率,使其适应训练过程。# training # 训练
for epoch in range(10): # 训练会进行 10 次,每次代表一个 “epoch”。“epoch” 通常在机器学习中表示完整遍历一次训练数据集的过程。for i, (images, labels) in enumerate(train_loader): # 遍历训练数据集images = images.cuda() # 将数据放入GPUlabels = labels.cuda() # 将标签放入GPUoptimizer.zero_grad() # 梯度清零,这一行代码用于清零模型参数的梯度,因为在PyTorch中,# 梯度是在每次反向传播时累加的,所以每次前向传播之前都需要清零梯度。outputs = cnn(images) # 前向传播,将图像数据输入到CNN模型中,得到模型的输出outputs。loss = loss_func(outputs, labels) # 计算损失loss.backward() # 反向传播,计算损失函数对模型参数的梯度optimizer.step() # 优化器更新参数if i % 100 == 0: # 打印训练信息print('Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:.6f}'.format(epoch + 1, i * len(images), len(train_loader.dataset),100. * i / len(train_loader), loss.item()))
经过训练后输出如下:
Train Epoch: 10 [0/60000 (0%)] Loss: 0.011444
Train Epoch: 10 [6400/60000 (11%)] Loss: 0.014965
Train Epoch: 10 [12800/60000 (21%)] Loss: 0.032280
Train Epoch: 10 [19200/60000 (32%)] Loss: 0.055170
Train Epoch: 10 [25600/60000 (43%)] Loss: 0.019414
Train Epoch: 10 [32000/60000 (53%)] Loss: 0.010601
Train Epoch: 10 [38400/60000 (64%)] Loss: 0.016180
Train Epoch: 10 [44800/60000 (75%)] Loss: 0.005475
Train Epoch: 10 [51200/60000 (85%)] Loss: 0.046143
Train Epoch: 10 [57600/60000 (96%)] Loss: 0.067438
5、测试:
加入测试的代码:
# test # 测试loss_test = 0correct = 0for i, (images, labels) in enumerate(test_loader): # 遍历测试数据集images = images.cuda() # 将数据放入GPUlabels = labels.cuda() # 将标签放入GPUoutputs = cnn(images) # 前向传播,将图像数据输入到CNN模型中,得到模型的输出outputs。loss_test += loss_func(outputs, labels) # 计算损失_, predicted = outputs.max(1) # 预测结果correct += predicted.eq(labels).sum().item() # 计算正确率print('Test set: Average loss: {:.4f}, Accuracy: {}/{} ({:.0f}%)'.format(loss_test / len(test_loader.dataset), correct, len(test_loader.dataset),100. * correct / len(test_loader.dataset)))
6、保存
# save# 保存
torch.save(cnn, 'model/mnist_cnn.pkl') # 保存模型参数
完整代码:
import torch
import torchvision.datasets as dataset
import torchvision.transforms as transforms
import torch.utils.data as data_utils# data # 定义数据
per_batch = 2000 # batch_size指的是在一次迭代中用于训练模型的样本数量
# 定义训练数据集
train_data = dataset.MNIST(root='mnist_data', # 创建数据集train=True, # 是否训练transform=transforms.ToTensor(), # 转换成tensordownload=False) # 是否下载# 定义测试数据集
test_data = dataset.MNIST(root='mnist_data', # 创建数据集train=False, # 是否训练transform=transforms.ToTensor(), # 转换成tensordownload=False) # 是否下载
# 定义训练数据集的迭代器
train_loader = data_utils.DataLoader(dataset=train_data, # 定义数据集batch_size=per_batch, # 定义batch_sizeshuffle=True, ) # 是否打乱# 定义测试数据集的迭代器
test_loader = data_utils.DataLoader(dataset=test_data, # 定义数据集batch_size=per_batch, # 定义batch_sizeshuffle=True, ) # 是否打乱# net # 定义网络
class CNN(torch.nn.Module):def __init__(self, channel=32): # 通道数(channel)指的是卷积层中的滤波器数量。# 这个参数对于控制内存使用和训练速度非常重要。super(CNN, self).__init__()self.channel = channelself.conv = torch.nn.Sequential( # 卷积# “torch.nn.Sequential” 是 PyTorch 中的一个容器类,它允许将多个模块按照顺序依次组合起来,形成一个新的模块。这样可以方便地构建神经网络模型,使得模型的定义更加简洁和清晰。# 例如,可以使用 “torch.nn.Sequential” 来快速构建一个包含多个卷积层和全连接层的神经网络。torch.nn.Conv2d(in_channels=1, out_channels=self.channel, kernel_size=5, stride=1, padding=2), # 卷积层# torch.nn.Conv2d 是 PyTorch 深度学习框架中的一个类,它定义了一个二维卷积层。在神经网络中,卷积层是核心组成部分之一,主要用于执行卷积操作,# 这种操作可以通过一组可学习的滤波器(也称为卷积核)来提取输入数据的特征。# 具体来说,torch.nn.Conv2d 类接受几个关键参数:# in_channels:输入数据的通道数。例如,对于灰度图像,通道数为 1;对于 RGB 彩色图像,通道数为 3。# out_channels:输出数据的通道数,也就是卷积层将产生多少个特征图(feature map)。# kernel_size:卷积核的大小,通常表示为一个整数或者一个包含两个整数的元组(高度和宽度)。# stride:卷积操作的步长,即卷积核在输入数据上移动的间隔。# padding:填充大小,用于在输入数据的边缘周围添加额外的像素,以保持输出数据的大小与输入数据相同或按需调整。torch.nn.BatchNorm2d(self.channel), # 归一化# torch.nn.BatchNorm2d() 是一个在 PyTorch 框架中定义的二维批量归一化(Batch Normalization)层。# 批量归一化是一种用于加速深度神经网络训练过程的技术。它通过对每一批数据进行归一化处理,使得数据的均值为0,方差为1,# 从而减少内部协变量偏移(Internal Covariate Shift)。这样做可以使得网络对于初始化权重的敏感度降低,并且允许使用更高的学习率。# 在这个上下文中,channel=32表示每个输入样本有32个特征图(feature maps)。# 二维批量归一化层通常用在卷积神经网络(CNN)中,用于处理图像数据。由于图像数据通常是二维的(高度和宽度),因此使用 BatchNorm2d# 而不是 BatchNorm1d,后者通常用于处理一维数据,如全连接层的输出。# 在实际应用中,BatchNorm2d 层通常紧跟在卷积层之后,激活函数之前。它有助于网络更快地收敛,并且可以起到一定的正则化效果,减少过拟合。# 总结一下,torch.nn.BatchNorm2d(32) 创建了一个适用于32通道输入数据的二维批量归一化层,这个层可以被添加到神经网络的模型定义中,以提高训练效率和模型性能。torch.nn.ReLU(), # 激活函数# orch.nn.ReLU () ReLU(Rectified Linear Unit,修正线性单元)将输入值小于等于 0 的部分置为 0,输入值大于 0 的部分保持不变。# 它常用于神经网络中,能够引入非线性,帮助神经网络更好地拟合复杂的数据模式,提高模型的表达能力和性能。torch.nn.MaxPool2d(kernel_size=2)) # 池化层# torch.nn.MaxPool2d” 是 PyTorch 中的一个模块,用于对输入的二维信号(图像等)进行二维最大池化操作。最大池化通常是在卷积神经网络中用于降低特征图的空间尺寸,# 同时保留重要的特征信息,它在一定窗口范围内选取最大值作为输出。例如,对于一个图像特征图,通过 MaxPool2d 可以减少计算量,提高模型的效率和对一些局部特征变化的鲁棒性。self.fc = torch.nn.Linear(14 * 14 * 32, 10) # 全连接# 这里的 torch.nn.Linear 是 PyTorch 框架中定义全连接层的类。这个类的构造函数接受两个参数:# 第一个参数 14*14*32 表示输入特征的数量。在这个例子中,它是由前面卷积层处理后的特征图大小决定的。# 假设经过一系列卷积和池化操作后,每个样本的特征被展平(flatten)成了一个 14x14x32 的三维数组,# 那么展平后的一维特征数量就是 14*14*32。# 第二个参数 10 表示输出特征的数量,也就是全连接层的神经元数量。在这个例子中,它对应于分类任务中的类别数,# 假设这是一个用于手写数字识别的任务,共有 10 个类别(0 到 9)。def forward(self, x): # 前向传播x = self.conv(x) # 之前定义的卷积操作x = x.view(x.size()[0], -1) # reshape# view方法主要有以下应用:# 一、重塑张量(Tensor)形状# 1、基本概念# 张量是深度学习中数据的基本表示形式。view方法允许在不改变张量中数据的存储顺序和内容的情况下,改变其维度结构。# 例如,一个形状为(batch_size, channels, height, width)的 4D 图像张量可以通过view方法转换为(batch_size, channels * height * width)的 2D 张量,# 这在将图像数据展平以便输入到全连接层时非常有用。# 2、内存布局# view操作是基于张量的存储布局(contiguous memory layout)进行的。如果一个张量的存储不是连续的(例如,经过某些转置操作后),在使用view之前可能需要先调用# contiguous方法来确保正确的视图重塑。# 3、动态网络架构# 在构建动态神经网络结构时,view方法可以根据不同的输入或网络中间状态动态调整张量的形状。例如,在循环神经网络(RNN)的变长输入场景中,输入序列的长度可能是可变的。# 通过view方法,可以将输入张量调整为适合网络处理的形状,然后再将处理后的结果重新调整回合适的形状用于后续操作。# 4、模型输出调整# 在一些深度学习模型中,网络的输出可能需要调整形状以匹配特定的任务要求。例如,在生成对抗网络(GAN)中,生成器的输出可能需要通过view方法调整为与目标图像相同的形状,# 以便与真实图像进行比较和计算损失。output = self.fc(x) # 全连接return output
cnn = CNN() # 定义网络
cnn = cnn.cuda() # 放入GPU# loss # 损失
loss_func = torch.nn.CrossEntropyLoss() # 损失函数,使用了交叉熵损失函数。常用于分类问题中,衡量模型预测结果与真实标签之间的差异# 使模型的预测结果更接近真实标签。# optimizer # 优化
optimizer = torch.optim.Adam(cnn.parameters(), lr=0.01) # 优化器,使用了Adam优化器。Adam是一种自适应学习率方法,可以自动调整学习率,使其适应训练过程。# training # 训练
for epoch in range(10): # 训练会进行 10 次,每次代表一个 “epoch”。“epoch” 通常在机器学习中表示完整遍历一次训练数据集的过程。for i, (images, labels) in enumerate(train_loader): # 遍历训练数据集images = images.cuda() # 将数据放入GPUlabels = labels.cuda() # 将标签放入GPUoptimizer.zero_grad() # 梯度清零,这一行代码用于清零模型参数的梯度,因为在PyTorch中,# 梯度是在每次反向传播时累加的,所以每次前向传播之前都需要清零梯度。outputs = cnn(images) # 前向传播,将图像数据输入到CNN模型中,得到模型的输出outputs。loss = loss_func(outputs, labels) # 计算损失loss.backward() # 反向传播,计算损失函数对模型参数的梯度optimizer.step() # 优化器更新参数if i % 100 == 0: # 打印训练信息print('Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:.6f}'.format(epoch + 1, i * len(images), len(train_loader.dataset),100. * i / len(train_loader), loss.item()))# test # 测试loss_test = 0correct = 0for i, (images, labels) in enumerate(test_loader): # 遍历测试数据集images = images.cuda() # 将数据放入GPUlabels = labels.cuda() # 将标签放入GPUoutputs = cnn(images) # 前向传播,将图像数据输入到CNN模型中,得到模型的输出outputs。loss_test += loss_func(outputs, labels) # 计算损失_, predicted = outputs.max(1) # 预测结果correct += predicted.eq(labels).sum().item() # 计算正确率print('Test set: Average loss: {:.4f}, Accuracy: {}/{} ({:.0f}%)'.format(loss_test / len(test_loader.dataset), correct, len(test_loader.dataset),100. * correct / len(test_loader.dataset)))# save# 保存
torch.save(cnn, 'model/mnist_cnn.pkl') # 保存模型参数
二、利用保存的模型推理
新建inference.py:
import torch
import torchvision.datasets as dataset
import torchvision.transforms as transforms
import torch.utils.data as data_utils# net # 定义网络
class CNN(torch.nn.Module):def __init__(self, channel=32): # 通道数(channel)指的是卷积层中的滤波器数量。# 这个参数对于控制内存使用和训练速度非常重要。super(CNN, self).__init__()self.channel = channelself.conv = torch.nn.Sequential( # 卷积torch.nn.Conv2d(in_channels=1, out_channels=self.channel, kernel_size=5, stride=1, padding=2), # 卷积层torch.nn.BatchNorm2d(self.channel), # 归一化torch.nn.ReLU(), # 激活函数torch.nn.MaxPool2d(kernel_size=2)) # 池化层self.fc = torch.nn.Linear(14 * 14 * 32, 10) # 全连接def forward(self, x): # 前向传播x = self.conv(x) # 之前定义的卷积操作x = x.view(x.size()[0], -1) # reshapeoutput = self.fc(x) # 全连接return output# data # 定义数据
per_batch = 2000
# 定义训练数据集
train_data = dataset.MNIST(root='mnist_data', # 创建数据集train=True, # 是否训练transform=transforms.ToTensor(), # 转换成tensordownload=False) # 是否下载# 定义测试数据集
test_data = dataset.MNIST(root='mnist_data', # 创建数据集train=False, # 是否训练transform=transforms.ToTensor(), # 转换成tensordownload=False) # 是否下载# 定义测试数据集的迭代器
test_loader = data_utils.DataLoader(dataset=test_data, # 定义数据集batch_size=per_batch, # 定义batch_sizeshuffle=True, ) # 是否打乱cnn = torch.load('E:\AI_tset\mnist_demo\model\mnist_cnn.pkl') # 定义网络
cnn = cnn.cuda() # 放入GPU# inference # 预测
loss_test = 0
correct = 0for i, (images, labels) in enumerate(test_loader): # 遍历测试数据集images = images.cuda() # 将数据放入GPUlabels = labels.cuda() # 将标签放入GPUoutputs = cnn(images) # 前向传播,将图像数据输入到CNN模型中,得到模型的输出outputs。_, predicted = outputs.max(1) # 预测结果correct += predicted.eq(labels).sum().item() # 计算正确率print('Test set: Average loss: {:.4f}, Accuracy: {}/{} ({:.0f}%)'.format(loss_test / len(test_loader.dataset), correct, len(test_loader.dataset),100. * correct / len(test_loader.dataset)))
运行结果:
Test set: Average loss: 0.0000, Accuracy: 9813/10000 (98%)
三、将网络定义为单独调用的类
1、新建CNN.py ,将之前训练脚本中的网络定义拷贝:
import torch# net # 定义网络
class CNN(torch.nn.Module):def __init__(self, channel=32): # 通道数(channel)指的是卷积层中的滤波器数量。# 这个参数对于控制内存使用和训练速度非常重要。super(CNN, self).__init__()self.channel = channelself.conv = torch.nn.Sequential( # 卷积# “torch.nn.Sequential” 是 PyTorch 中的一个容器类,它允许将多个模块按照顺序依次组合起来,形成一个新的模块。这样可以方便地构建神经网络模型,使得模型的定义更加简洁和清晰。# 例如,可以使用 “torch.nn.Sequential” 来快速构建一个包含多个卷积层和全连接层的神经网络。torch.nn.Conv2d(in_channels=1, out_channels=self.channel, kernel_size=5, stride=1, padding=2), # 卷积层# torch.nn.Conv2d 是 PyTorch 深度学习框架中的一个类,它定义了一个二维卷积层。在神经网络中,卷积层是核心组成部分之一,主要用于执行卷积操作,# 这种操作可以通过一组可学习的滤波器(也称为卷积核)来提取输入数据的特征。# 具体来说,torch.nn.Conv2d 类接受几个关键参数:# in_channels:输入数据的通道数。例如,对于灰度图像,通道数为 1;对于 RGB 彩色图像,通道数为 3。# out_channels:输出数据的通道数,也就是卷积层将产生多少个特征图(feature map)。# kernel_size:卷积核的大小,通常表示为一个整数或者一个包含两个整数的元组(高度和宽度)。# stride:卷积操作的步长,即卷积核在输入数据上移动的间隔。# padding:填充大小,用于在输入数据的边缘周围添加额外的像素,以保持输出数据的大小与输入数据相同或按需调整。torch.nn.BatchNorm2d(self.channel), # 归一化# torch.nn.BatchNorm2d() 是一个在 PyTorch 框架中定义的二维批量归一化(Batch Normalization)层。# 批量归一化是一种用于加速深度神经网络训练过程的技术。它通过对每一批数据进行归一化处理,使得数据的均值为0,方差为1,# 从而减少内部协变量偏移(Internal Covariate Shift)。这样做可以使得网络对于初始化权重的敏感度降低,并且允许使用更高的学习率。# 在这个上下文中,channel=32表示每个输入样本有32个特征图(feature maps)。# 二维批量归一化层通常用在卷积神经网络(CNN)中,用于处理图像数据。由于图像数据通常是二维的(高度和宽度),因此使用 BatchNorm2d# 而不是 BatchNorm1d,后者通常用于处理一维数据,如全连接层的输出。# 在实际应用中,BatchNorm2d 层通常紧跟在卷积层之后,激活函数之前。它有助于网络更快地收敛,并且可以起到一定的正则化效果,减少过拟合。# 总结一下,torch.nn.BatchNorm2d(32) 创建了一个适用于32通道输入数据的二维批量归一化层,这个层可以被添加到神经网络的模型定义中,以提高训练效率和模型性能。torch.nn.ReLU(), # 激活函数# orch.nn.ReLU () ReLU(Rectified Linear Unit,修正线性单元)将输入值小于等于 0 的部分置为 0,输入值大于 0 的部分保持不变。# 它常用于神经网络中,能够引入非线性,帮助神经网络更好地拟合复杂的数据模式,提高模型的表达能力和性能。torch.nn.MaxPool2d(kernel_size=2)) # 池化层# torch.nn.MaxPool2d” 是 PyTorch 中的一个模块,用于对输入的二维信号(图像等)进行二维最大池化操作。最大池化通常是在卷积神经网络中用于降低特征图的空间尺寸,# 同时保留重要的特征信息,它在一定窗口范围内选取最大值作为输出。例如,对于一个图像特征图,通过 MaxPool2d 可以减少计算量,提高模型的效率和对一些局部特征变化的鲁棒性。self.fc = torch.nn.Linear(14 * 14 * 32, 10) # 全连接# 这里的 torch.nn.Linear 是 PyTorch 框架中定义全连接层的类。这个类的构造函数接受两个参数:# 第一个参数 14*14*32 表示输入特征的数量。在这个例子中,它是由前面卷积层处理后的特征图大小决定的。# 假设经过一系列卷积和池化操作后,每个样本的特征被展平(flatten)成了一个 14x14x32 的三维数组,# 那么展平后的一维特征数量就是 14*14*32。# 第二个参数 10 表示输出特征的数量,也就是全连接层的神经元数量。在这个例子中,它对应于分类任务中的类别数,# 假设这是一个用于手写数字识别的任务,共有 10 个类别(0 到 9)。def forward(self, x): # 前向传播x = self.conv(x) # 之前定义的卷积操作x = x.view(x.size()[0], -1) # reshape# view方法主要有以下应用: 一、重塑张量(Tensor)形状 1、基本概念 张量是深度学习中数据的基本表示形式。view方法允许在不改变张量中数据的存储顺序和内容的情况下,改变其维度结构。 例如,一个形状为(# batch_size, channels, height, width)的 4D 图像张量可以通过view方法转换为(batch_size, channels * height * width)的 2D 张量,# 这在将图像数据展平以便输入到全连接层时非常有用。 2、内存布局 view操作是基于张量的存储布局(contiguous memory# layout)进行的。如果一个张量的存储不是连续的(例如,经过某些转置操作后),在使用view之前可能需要先调用 contiguous方法来确保正确的视图重塑。 3、动态网络架构# 在构建动态神经网络结构时,view方法可以根据不同的输入或网络中间状态动态调整张量的形状。例如,在循环神经网络(RNN)的变长输入场景中,输入序列的长度可能是可变的。# 通过view方法,可以将输入张量调整为适合网络处理的形状,然后再将处理后的结果重新调整回合适的形状用于后续操作。 4、模型输出调整# 在一些深度学习模型中,网络的输出可能需要调整形状以匹配特定的任务要求。例如,在生成对抗网络(GAN)中,生成器的输出可能需要通过view方法调整为与目标图像相同的形状, 以便与真实图像进行比较和计算损失。output = self.fc(x) # 全连接return output
2、在训练脚本中调用定义好的网络:
import torch
import torchvision.datasets as dataset
import torchvision.transforms as transforms
import torch.utils.data as data_utils
from CNN import CNN# “torch.utils.data”提供了一些用于数据处理和加载的实用工具,比如可以用来创建数据加载器(DataLoader)、对数据集进行分割等操作。
# “torch.utils.data” 中的 “data_utils” 子模块除了可以分割数据集外,还提供了以下一些实用工具:
# 一、数据加载器相关
# 创建数据加载器(DataLoader)时可以设置各种参数,如批次大小(batch_size)、是否随机打乱数据(shuffle)等,方便高效地加载数据进行训练和推理。
# 可以处理不同类型的数据,包括图像、文本、表格数据等,将其转换为适合模型输入的格式。
# 二、数据预处理
# 提供一些常见的数据预处理函数,例如归一化、标准化等操作,以便使数据更适合模型训练。
# 可以对数据进行随机裁剪、旋转等数据增强操作,增加数据的多样性,提高模型的泛化能力。
# 三、数据集管理
# 帮助管理数据集的迭代,确保在训练过程中能够正确地遍历数据集,避免重复或遗漏数据。
# 可以方便地与不同类型的数据集进行交互,包括自定义数据集和常见的公开数据集。# data # 定义数据
one_batch = 2000 # batch_size指的是在一次迭代中用于训练模型的样本数量
# 定义训练数据集
train_data = dataset.MNIST(root='mnist_data', # 创建数据集train=True, # 是否训练transform=transforms.ToTensor(), # 转换成tensordownload=False) # 是否下载# 定义测试数据集
test_data = dataset.MNIST(root='mnist_data', # 创建数据集train=False, # 是否训练transform=transforms.ToTensor(), # 转换成tensordownload=False) # 是否下载
# 定义训练数据集的迭代器
train_loader = data_utils.DataLoader(dataset=train_data, # 定义数据集batch_size=one_batch, # 定义batch_sizeshuffle=True, ) # 是否打乱# 定义测试数据集的迭代器
test_loader = data_utils.DataLoader(dataset=test_data, # 定义数据集batch_size=one_batch, # 定义batch_sizeshuffle=True, ) # 是否打乱cnn = CNN() # 定义网络
cnn = cnn.cuda() # 放入GPU# loss # 损失
loss_func = torch.nn.CrossEntropyLoss() # 损失函数,使用了交叉熵损失函数。常用于分类问题中,衡量模型预测结果与真实标签之间的差异# 使模型的预测结果更接近真实标签。# optimizer # 优化
optimizer = torch.optim.Adam(cnn.parameters(), lr=0.01) # 优化器,使用了Adam优化器。Adam是一种自适应学习率方法,可以自动调整学习率,使其适应训练过程。# training # 训练
for epoch in range(10): # 训练会进行 10 次,每次代表一个 “epoch”。“epoch” 通常在机器学习中表示完整遍历一次训练数据集的过程。for i, (images, labels) in enumerate(train_loader): # 遍历训练数据集images = images.cuda() # 将数据放入GPUlabels = labels.cuda() # 将标签放入GPUoptimizer.zero_grad() # 梯度清零,这一行代码用于清零模型参数的梯度,因为在PyTorch中,# 梯度是在每次反向传播时累加的,所以每次前向传播之前都需要清零梯度。outputs = cnn(images) # 前向传播,将图像数据输入到CNN模型中,得到模型的输出outputs。loss = loss_func(outputs, labels) # 计算损失loss.backward() # 反向传播,计算损失函数对模型参数的梯度optimizer.step() # 优化器更新参数if i % 100 == 0: # 打印训练信息print('Train Epoch: {} [{}/{} ({:.0f}%)]\tLoss: {:.6f}'.format(epoch + 1, i * len(images), len(train_loader.dataset),100. * i / len(train_loader), loss.item()))# test # 测试loss_test = 0correct = 0for i, (images, labels) in enumerate(test_loader): # 遍历测试数据集images = images.cuda() # 将数据放入GPUlabels = labels.cuda() # 将标签放入GPUoutputs = cnn(images) # 前向传播,将图像数据输入到CNN模型中,得到模型的输出outputs。loss_test += loss_func(outputs, labels) # 计算损失_, predicted = outputs.max(1) # 预测结果correct += predicted.eq(labels).sum().item() # 计算正确率print('Test set: Average loss: {:.4f}, Accuracy: {}/{} ({:.0f}%)'.format(loss_test / len(test_loader.dataset), correct, len(test_loader.dataset),100. * correct / len(test_loader.dataset)))# save# 保存
torch.save(cnn, 'model/mnist_cnn.pkl') # 保存模型参数
四、在推理脚本中加入图片显示的功能:
import torch
import torchvision.datasets as dataset
import torchvision.transforms as transforms
import torch.utils.data as data_utils
import cv2# data # 定义数据
per_batch = 32
# 定义训练数据集
train_data = dataset.MNIST(root='mnist_data', # 创建数据集train=True, # 是否训练transform=transforms.ToTensor(), # 转换成tensordownload=False) # 是否下载# 定义测试数据集
test_data = dataset.MNIST(root='mnist_data', # 创建数据集train=False, # 是否训练transform=transforms.ToTensor(), # 转换成tensordownload=False) # 是否下载# 定义测试数据集的迭代器
test_loader = data_utils.DataLoader(dataset=test_data, # 定义数据集batch_size=per_batch, # 定义batch_sizeshuffle=True, ) # 是否打乱cnn = torch.load('E:\AI_tset\mnist_demo\model\mnist_cnn.pkl') # 定义网络
cnn = cnn.cuda() # 放入GPU# inference # 预测
loss_test = 0
correct = 0for i, (images, labels) in enumerate(test_loader): # 遍历测试数据集images = images.cuda() # 将数据放入GPUlabels = labels.cuda() # 将标签放入GPUoutputs = cnn(images) # 前向传播,将图像数据输入到CNN模型中,得到模型的输出outputs。_, predicted = outputs.max(1) # 预测结果correct += predicted.eq(labels).sum().item() # 计算正确率images = images.cpu().numpy() # 将数据从GPU中取出labels = labels.cpu().numpy() # 将标签从GPU中取出predicted = predicted.cpu().numpy() # 将预测结果从GPU中取出for idx in range(per_batch): # 遍历每一批数据image = images[idx] # 取出图像label = labels[idx] # 取出标签predict = predicted[idx] # 取出预测结果print(image.shape) # 打印图像的shape (1, 28, 28)# 由于image的维度是(1, 28, 28),所以需要将维度变成(28, 28, 1),下面两种方法都可以# image = image.reshape(28, 28) # 将图像reshape成28*28的矩阵image = image.transpose(1, 2, 0) # 将图像转置 # (1, 28, 28) -> (28, 28, 1) cv2.imshow('image', image) # 显示图像print('label:{},predict:{}'.format(label, predict)) # 打印标签和预测结果cv2.waitKey(0) # 等待按键
运行结果: