0


CNN卷积神经网络代码实现及解析(仅全连接层)

** 一、相关库的引入与解析**

  1. import matplotlib.pyplot as plt '''#2D绘图库'''
  2. from PIL import Image '''#pillow(PIL库),用于图像处理(导入Image子类)'''
  3. import torch '''#torch库,基于Tensor(张量)的GPU加速计算,创建神经网络'''
  4. from torch import nn '''#torch库中nn子模块:专用于构建神经网络(全连接层、卷积层
  5. 、循环神经网络层)'''
  6. '''
  7. PyTorch框架中有一个重要且好用的包:torchvision,该包主要由3个子包组成,
  8. 分别是:torchvision.datasets、torchvision.models、torchvision.transforms。
  9. '''
  10. from torchvision import transforms '''transforms是对图像进行预处理'''
  11. from torchvision.datasets import MNIST '''MNIST数据集:就是所谓的X→Y(问题及
  12. 答案)用来训练模型'''
  13. from torch.utils.data import DataLoader
  14. '''
  15. torch.utils.data库实现自由的数据读取,
  16. 包括三个子类:.Dataset、.sampler.Sampler(data_source)、.DataLoader
  17. '''
  18. import os '''“os库是 Python 的标准库之一,用于与操作系统进行交互,这里用于路径操作'''
  19. import numpy as np '''#numpy库,矩阵数组计算'''

#import

#import···as···更名操作

#from··(大库)··import··(子模块)··(from PIL import Image)

Image.open (打开图像文件)
Image.resize(调整图像大小)
Image.convert (图像格式转换(灰度图像))

二、数据预处理-----1.图像格式转换 2.特征缩放,参数归一化

(让各个参数的取值范围差不多,便于选择合适的步长,学习率)(对象MNIST)

  1. '''example1:'''
  2. transform = transforms.Compose([transforms.ToTensor(),
  3. transforms.Normalize((0.1307,), (0.3081,))])
  4. #注意这里的逗号两个
  5. '''example2:'''
  6. transform = transforms.Compose([transforms.ToTensor()])

**#transforms.Compose() ------ 可以组合几个变换一起操作
#transforms.ToTensor() ** ------ 将原始的PILImage格式或者numpy.array格式转为张量,并把灰度范围从0-255变换到(0,1)
之间

参数是无吗

#transform.Normalize(mean所处理数据的原始平均值, std所处理数据的原始标准偏差) -----Z-score归一化,把(0,1)变换到(-1,1),经该处理后数据会变为均值为 0,标准差为 1的标准化像素值,每个值x被转换为**(x - mean) / std**,接下来将用于处理MNIST数据集,使数据在0附近居中,使其具有指定的平均值和标准偏差,确保数据分布会更加符合标准正态分布,可改善训练过程和收敛性,更适合于神经网络的训练。

·均值为 0,标准差为 1意义: \cdot\cdot 零中心化: 将数据的均值移动到零,这样可以更好地匹配神经网络的激活函数(如 ReLU)的工作区间。 单位标准差: 将数据缩放到标准差为 1,使得数据具有一致的尺度,防止某些特征对训练过程的影响过大。

**三、1、加载出MNIST数据集 2、创建数据加载器(包括训练集和测试集) **

  1. '''example1:'''
  2. # 训练数据集
  3. '''加载 MNIST 数据集'''
  4. train_data = MNIST(root='./data',train=True,transform=
  5. transforms.ToTensor(),download=False)
  6. '''创建一个数据加载器'''
  7. train_loader = DataLoader(train_data,shuffle=True,batch_size=64)
  8. # 测试数据集(结构同上)
  9. test_data = MNIST(root='./data',train=False,transform=
  10. transforms.ToTensor(),download=False)
  11. test_loader = DataLoader(test_data,shuffle=False,batch_size=64)
  12. ----------------------------------------------------------------------
  13. '''example2:'''
  14. #整体用函数
  15. def data_loader(train):#上方对训练集测试集的代码基本一样不一样在与train=?即作为参数
  16. transform_tensor = transforms.Compose((transforms.ToTensor(),
  17. transforms.Normalize((0.1307,), (0.3081,))))#两个操作合一了,反正都是对MNIST进行操作的
  18. dataset = torchvision.datasets.MNIST('./data', train, transform =
  19. transform_tensor, download=True)#等于号左右有一个写上就行了
  20. dataloader = DataLoader(dataset, shuffle=Truebatch_size=15)
  21. return dataloader
  22. ''''''主函数中使用时
  23. train_data = data_loader(train=True)
  24. test_data = data_loader(train=False)

用于在训练神经网络时迭代数据。

\star结构:**MNIST(参数一:指定存储路径,参数二:加载的是否是训练集,参数三:对图像的转换操作,参数四:若数据集不存在是否从互联网下载) ** 参数三:应用于每个图像的转换函数。常用的转换包括将图像转换为 PyTorch 张量 (

  1. transforms.ToTensor()

)和标准化图像(

  1. transforms.Normalize(mean, std)

\star结构:**

  1. DataLoader(

参数一:

  1. 包装数据的对象,

参数二:

  1. 是否打乱(

提高模型的泛化能力**

  1. **),**

参数三:

  1. ** 批量操作数量)DataLoader**

用于包装数据集,使其可以批量加载并支持多线程加速。综上所述,这两行代码的目的是将 MNIST 训练数据集加载到内存中,并创建一个数据加载器(函数应该返回的),以便在训练神经网络时可以按批次随机抽取数据,提高训练效率。

训练集和测试集中都已经包含了一个手写数字和对应答案,一般训练集打乱测试集不打,都打乱也可以。(将训练数据随机打乱有助于提高模型的泛化能力,防过拟合。确保模型在每个** epoch** 中看到不同顺序,这样模型在训练时不会依赖于特定的输入顺序,从而学到更通用的特征。训练集优化更新参数,测试集评估性能)

四、神经网络模型创建(类)----包括全连接层(属性特征)和向前传播(行为)

  1. class Model(nn.Module):'''是Module父类的子类'''
  2. def __init__(self):
  3. super(Model,self).__init__()'''记住'''
  4. '''四个全连接层'''
  5. self.layer1 = torch.nn.Linear(28*28,64)'''torch可以不用加,因为from torch import nn '''
  6. self.layer2 = torch.nn.Linear(64, 25)
  7. self.layer3 = torch.nn.Linear(25, 15)
  8. self.layer4 = torch.nn.Linear(15, 10)
  9. def forward(self, a): #向前传播,a为输入的图像(未经任何处理)
  10. a = a.view(-1, 784)#展平
  11. a = torch.nn.functional.relu(self.layer1(a))
  12. a = torch.nn.functional.relu(self.layer2(a))
  13. a = torch.nn.functional.relu(self.layer3(a))
  14. a = torch.nn.functional.log_softmax(self.layer4(a), dim=1)
  15. return a #别忘了这里要返回一个输出

\star**

  1. nn.Module

是所有神经网络模块的基类**,自定义的会继承。

\starsuper(当前类的名称Model,当前实例self函数用于返回父类。当前类的名称(访问当前类的父类),当前实例对当前实例进行操作。函数中调用

  1. super

初始化父类确保父类中的初始化逻辑被执行,正确设置模型结构。

!注意此时的a为之前处理过一次的MNIST数据集(仍为28*28的二维图像张量(矩阵)(之前只是对数据范围进行了一些调整未改变维度))。全连接层(线性层)接收一维向量

\starself.layer1 =** torch.nn.Linear(输入大小,输出大小)**

\startorch.nn.functional.relu(self.layer1(a)) ------第一层加ReLU激活函数

\star*x=x.view(-1,2828)

  1. **(四维**展

二维

:**将输入张量

  1. x

重新变形为大小为

  1. (-1, 784)

的张量。

  1. -1

表示自动计算维度大小,

  1. 784

表示将 28x28 的图像扁平化为 784 维。**

  1. view

调整张量的形状,不改变其数据**。

  1. -1

表示自动计算维度的大小,使总元素数量保持不变。这里

  1. -1

会自动计算为

  1. batch_size

。**(batch_size, 1(通道数), 28, 28)\Rightarrow(batch_size, 784)!!!!!!!**


附:softmax()与log_softmax()

\star多分类任务最后一层

\bulletlog_softmax():计算

  1. softmax

后的对数,更具数值稳定性,常与**

  1. nll_loss

负对数似然损失函数** 一起使用。计算​​​​交叉熵损失时,使用

  1. log_softmax()

更方便。交叉熵损失函数通常包括一个

  1. log

操作,因此与

  1. log_softmax()

结合使用可以避免重复计算对数。

\bulletsoftmax():用于模型的输出层,将输出转换为概率分布。需要概率值的场景。

实际应用中,使用交叉熵损失函数时,通常直接使用

  1. torch.nn.CrossEntropyLoss

,该损失函数内部已经包含了

  1. log_softmax

  1. nll_loss

的组合

使用案例一:----------(分开)

  1. import torch.nn as nn
  2. '''最后一层直接用log_softmax()'''
  3. a = torch.nn.functional.log_softmax(self.layer4(a), dim=1)
  4. '''损失函数定义为nll_loss()'''
  5. loss = torch.nn.functional.nll_loss(output, y)

使用案例二:----------(合着)

  1. import torch.nn as nn
  2. ···
  3. x = torch.relu(self.linear3(x))
  4. '''nn.CrossEntropyLoss() 是一个类而不是函数,不能直接像函数一样调用传入参数。
  5. 需要先实例化为一个对象,然后通过这个对象调用方法来计算损失。
  6. 正确的使用方式是先实例化 nn.CrossEntropyLoss(),
  7. 然后将预测值 predictions 和真实标签 targets 作为参数传递给它的实例方法来计算损失
  8. !!!!!!!记住'''
  9. criterion = nn.CrossEntropyLoss()
  10. loss = criterion(predictions, targets)

\bullet log_softmax(二维张量为经过上一层后的数据,dim=)

输入张量,即模型的输出。通常是二维张量。第一维:batch size,第二维:类别数目(或者某个特征的维度)。

  1. dim=1

表示在第一维(即类别维度)上进行 softmax 归一化操作。

  • 在模型的前向传播过程中,当输入的特征向量经过 self.layer**4 (20,10)后,输出是一个二维张量**,其形状为 (batch_size, 10)

\bullet nll_loss(经过softmax的输出,target)

  1. ** target 是每个样本真实的标签**

五、调用神经网络,选择损失函数,优化器(min损失函数)

  1. '''调用神经网络'''
  2. model = Model()

Model()是一个通用的结构,需要进行调用产生一个实例

  1. '''损失函数的选择'''
  2. #example1
  3. loss = torch.nn.functional.nll_loss(output, y)
  4. #example2
  5. criterion = nn.CrossEntropyLoss()
  6. #结合以上理解
  1. '''优化器的选择'''
  2. #example1
  3. optimizer = torch.optim.Adam(model.parameters(), lr=0.001)
  4. #example2
  5. optimizer = torch.optim.SGD(model.parameters(), lr=0.8)

\star**

  1. torch.optim.SGD

(随机梯度下降)**

  • 基本的优化算法,每次迭代时使用当前批次数据的梯度更新模型参数。计算相对简单,适合大规模模型。可以实现更精细的控制。易陷入局部最优点,特别是对于高度非凸的损失函数。需要手动调节学习率和学习率衰减策略。

\star**

  1. torch.optim.Adam

(自适应矩估计优化器)**(默认可选)

  • Adam 是一种自适应学习率的优化算法,结合了动量梯度下降和 RMSProp 算法。可能需要调整默认参数(如学习率)以获得最佳性能。

使用结构:**

  1. torch.optim.Adam(model.parameters(),Ir=学习率)

**

  1. model.parameters()

是返回模型在训练中要更新迭代的参数

六、模型训练与保存

  1. #写成函数
  2. def train():
  3. for index,data in enumerate(train_loader):
  4. input,target = data # input为输入数据,target为标签
  5. '''模型训练的一般步骤'''
  6. optimizer.zero_grad() # 梯度清零
  7. y_predict = model(input) # 模型预测
  8. loss = criterion(y_predict,target) # 计算损失
  9. loss.backward() # 反向传播
  10. optimizer.step() # 更新参数
  11. '''模型训练的一般步骤'''
  12. ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''
  13. '''模型保存'''
  14. '''同时保存优化器(如果仅保存模型参数而不保存优化器状态,恢复训练时优化器将没有
  15. 之前的梯度和动量信息,可能导致学习策略的不一致和效率降低)'''
  16. if index % 100 == 0: # 每一百次保存一次模型,打印损失
  17. torch.save(model.state_dict(),"./model/model.pkl") # 保存模型
  18. torch.save(optimizer.state_dict(),"./model/optimizer.pkl")
  19. print("损失值为:%.2f" % loss.item())
  20. ''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''''

\star**

  1. for index, data in enumerate(train_loader):

** 迭代数据加载器

  1. train_loader

中的数据批次

enumerate: 内置函数:**使用enumerate 遍历train_loader

在遍历迭代对象时,同时获取当前元素的索引。返回的结果是一个包含索引和数据的元组**。

  1. **index**

是当前批次的索引,从 0 开始递增。

  1. **data**

是当前批次的数据,通常是一个包含输入数据和标签的元组 **

  1. (inputs, labels)

**。

\star**

  1. input, target = data

是一种常见的解构赋值(也称为拆包**),用于从

  1. data

变量中提取输入和目标张量。通常数据和标签成对地存储在数据加载器中,这种方式可以将它们分开并传递给模型进行训练和评估。

\star模型保存:torch.save(model.state_dict(), "./model/model.pkl")

**

  1. torch.save

**:是 PyTorch 中的一个函数,用于将对象序列化并保存到磁盘。可以保存各种类型的对象,如模型的

  1. state_dict

、优化器的

  1. state_dict

以及其他 Python 对象。

**

  1. model.state_dict()

**:

  1. state_dict

是一个 Python 字典对象,它将每一层的参数(如权重和偏置)映射到其名称。返回模型的所有可学习参数及其当前值,包括

  1. nn.Module

对象的所有层的参数。这个字典包含了模型的权重、偏置等所有参数,便于保存和加载。

./model/model.pkl:

  1. "./model/"

指定了保存目录,

  1. "model.pkl"

是保存的文件名。

  1. .pkl

扩展名是 Python 常用的 pickle 文件格式扩展名,表示这是一个序列化文件。

\star损失值的打印:

  1. loss.item()

用于将损失值(

  1. loss

)**从一个张量(

  1. tensor

)**转换为一个标准的 Python 数值(通常是浮点数)。

  1. %.2f

表示以浮点数形式显示,并保留两位小数

  1. #可直接写到过程中
  2. for epoch in range(2):
  3. for (x, y) in train_data:
  4. '''模型训练的一般步骤'''
  5. model.zero_grad()
  6. output = model.forward(x.view(-1, 28 * 28))
  7. loss = torch.nn.functional.nll_loss(output, y) # 计算损失
  8. loss.backward() # 反向传播
  9. optimizer.step()# 更新参数
  10. '''模型训练的一般步骤'''
  11. print("epoch", epoch, "accuracy:", evaluate(test_data, net))

\starfor epoch in range(2):一个数据集用两遍。训练循环的迭代遍历次数。确保模型能充分学习数据的特征。使得模型逐步逼近最优解。

\starfor (x, y) in train_data:由上**

  1. train_loader中包含索引和数据,所以这里直接从train_data中找,结果是一个元组。

**

**

  1. input+ target==data......data+index==loader
  1. (下面的x 等价于 上面的input)

**

-----------------模型训练的一般步骤---------------

**# 梯度清零:

  1. optimizer.zero_grad()

  1. model.zero_grad()

**这两个方法用于清零梯度。每次训练迭代中,梯度是累加的。前一次计算的梯度会影响下一次计算的结果。因此,在反向传播前,需要清零梯度。

  1. optimizer.zero_grad()(推荐),

清零所有由这个优化器管理的参数的梯度。通常在训练循环的每次迭代开始时调用,以确保优化器在进行参数更新之前,计算的是当前 batch 的梯度,而不是累积的梯度优化器可以管理多个模型或部分模型的参数,因此更灵活。

  1. model.zero_grad(),

清零模型中所有参数的梯度。直接作用于模型参数的。比较少用。

** # 模型预测: ** y_predict = model(input) 和output = model.forward(x.view(-1, 28 * 28))(这样的原因是forward对应的处理中没有展开这一个操作),y_predict与output等价

**# 计算损失: **损失函数传入预测结果与真实结果

# 反向传播: loss.backward()

# 更新参数: optimizer.step()


附:MNIST数据集的形成以及前面的处理操作小总结:
  • 创建数据集:- 使用 TensorDataset 将 **inputs(张量)**和 **labels **(标签为数字几)组合成一个数据集对象。
  • 创建数据加载器:- DataLoader将数据集分成小批次(batch),并提供并行加载、数据打乱等功能。- 若设置 batch_size=10,表示每个批次包含 10 个样本,shuffle=True 表示每个 epoch 开始时打乱数据。
  • 遍历数据加载器:- 使用 enumerate 遍历 train_loader,每次迭代返回一个**索引 index**和一个数据批次 data。- data 是一个包含当前批次输入数据和标签的元组 **(inputs, labels)**。- 在循环体内,可以访问 indexdata,对当前批次的数据进行处理(如前向传播、计算损失、反向传播等)。

七、模型的评估测试

  1. def evaluate(test_data, model):
  2. correct = 0 #初始化清零
  3. total = 0 #初始化清零
  4. with torch.no_grad():
  5. for (x, y) in test_data:
  6. outputs = model.forward(x.view(-1, 28 * 28))
  7. '''输出的是一系列概率10依次遍历查找最大的那个'''
  8. for i, output in enumerate(outputs):
  9. ''
  10. if torch.argmax(output) == y[i]:
  11. correct += 1
  12. ''
  13. total += 1
  14. return correct / total

for (x, y) in test_data:(之前是for (x, y) in train_data:)

  1. enumerate

函数从索引 0 开始遍历,所以i从零开始

torch.argmax**

  1. (output)

:**返回

  1. output

张量中最大值的索引,即模型预测的类别。

  1. def test():
  2. correct = 0
  3. total = 0
  4. with torch.no_grad():
  5. for data in test_loader:
  6. input,target = data
  7. output=model(input) # output输出10个预测取值,
  8. 最大的为预测的数
  9. --------------------------------------------------------------
  10. probability,predict=torch.max(output.data,dim=1)
  11. total += target.size(0) # target是形状为(batch_size,1)的矩阵,
  12. 使用size(0)取出该批的大小(一次训练了一批一批有几个)
  13. --------------------------------------------------------------
  14. correct += (predict == target).sum().item() # predict和target均
  15. 为(batch_size,1)的矩阵,sum()求出相等的个数
  16. --------------------------------------------------------------
  17. print("准确率为:%.2f" % (correct / total))

评估标准:correct / total

\starwith torch.no_grad():测试模型时,不需要计算梯度,使用

  1. torch.no_grad()

上下文管理器,可以临时关闭自动求导功能(PyTorch中的张量默认具有自动求导功能,当你对张量进行操作时,PyTorch会自动构建一个计算图并保存梯度用于反向传播,从而实现自动求导),可节省内存并加快计算速度,不需要存储梯度信息。


\star**torch.argmax

  1. (output)

:**返回

  1. output

张量中最大值的索引,即模型预测的类别。这里的output是十个值中的一个。

\starprobability,predict=torch.max(output.data,dim=1) : 返回一个元组,第一个为最大概率值,第二个为最大值的下标。这里的output是十个值。


target是形状为(batch_size,1)的矩阵,使用size(0)取出该批的大小

这个函数直接返回 和 这个函数输出

八、预测自己提供的数字(使用模型)

  1. def test_mydata():
  2. image = Image.open('D:\\test\\test_five.png') '''# 取自定义手写图片'''
  3. image = Image.resize((28, 28)) '''# 裁剪尺寸为28*28'''
  4. image = Image.convert('L') '''# 转换为灰度图像'''
  5. transform = transforms.ToTensor()
  6. image = transform(image) '''# 转为张量'''
  7. image = image.view(1, 1, 28, 28) '''# 调整张量的形状'''
  8. output = net(image) '''# 调整后传入模型
  9. probability, predict = torch.max(output.data, dim=1) '''#同前'''
  10. print("此手写图片值为:%d" % (predict[0]))
  11. plt.title('此手写图片值为:{}'.format((int(predict))), fontname="SimHei")
  12. plt.imshow(image.squeeze())
  13. plt.show()

** **\star**transform = transforms.ToTensor()**这句别忘了不能省以及出现过类似的多次

\star调整图像大小常用**transforms.resize

  1. ()

或者 PIL 库的Image. resize

  1. ()

方法。对PyTorch 的张量(Tensor),不能直接调用

  1. image.resize()

**改变大小,因为张量的形状是由其数据内容决定的,不是尺寸函数决定。

\star**image.resize((28, 28))**将图像调整到 28x28 像素,与 MNIST 数据集的标准图像尺寸匹配。

\starimage = image.view(1, 1, 28, 28),图像张量调整为适合输入到 CNN 模型中的形状。

  1. 1

表示批量大小(batch size),即一次输入一张图片。

  1. 1

表示通道数(channel),因为手写数字图像是灰度图像,所以通道数为 1。

  1. 28, 28

表示图像的宽和高。

\starprobability, predict = torch.max(output.data, dim=1) #同前

\starplt.title() 用于设置 Matplotlib 图的标题。标题将显示手写数字图像的预测值。标题使用的字体将为“SimHei”,显示中文字符。**.format**是 Python 字符串格式化的方法之一,用于将变量的值插入到字符串中的指定位置

( image.view() image.squeeze())!!!!!!!!!squeeze()删除一个维度

九、结构拼接

  1. 结构一:
  2. transform = transforms.Compose({})
  3. train_data =
  4. train_loader =
  5. test_data =
  6. test_loader =
  7. class Model(nn.Module):
  8. model = Model()'''这里在全局中调用了,是全局变量了,所以
  9. 后续函数中使用无需作为参数传入'''
  10. criterion =
  11. optimizer =
  12. def train():
  13. # 加载模型
  14. if os.path.exists('./model/model.pkl'):
  15. model.load_state_dict(torch.load("./model/model.pkl")) # 加载保存模型的参数
  16. def test():
  17. def test_mydata():
  18. if __name__ == '__main__':
  19. test_mydata()
  20. for i in range(5):
  21. train()
  22. test()
  23. 原代码链接:https://blog.csdn.net/IronmanJay/article/details/128434368

**if name == 'main':**的作用
一个python文件有两种使用方法,第一作为脚本直接执行,第二是 import 到其他的 python 脚本中被调用。 if name == 'main': 下的代码在第一种情况下会被执行,而 import 到其他脚本中是不会被执行。

  1. 结构二:
  2. def
  3. def
  4. ''''''
  5. def main():
  6. ''''
  7. if __name__ == "__main__":
  8. main()

本文转载自: https://blog.csdn.net/2301_80397661/article/details/140271630
版权归原作者 woneday嘿 所有, 如有侵权,请联系我们删除。

“CNN卷积神经网络代码实现及解析(仅全连接层)”的评论:

还没有评论