基于BP神经网络对MNIST数据集检测识别(Pytorch,Tensorflow版本)

1.作者介绍

任陇刚,男,西安工程大学电子信息学院,2022级研究生
研究方向:机器视觉与人工智能
电子邮件:[email protected]

张思怡,女,西安工程大学电子信息学院,2022级研究生,张宏伟人工智能课题组
研究方向:机器视觉与人工智能
电子邮件:[email protected]

2.基于BP神经网络对MNIST数据集检测识别

2.1 BP神经网络介绍

首先从名称中可以看出,Bp神经网络可以分为两个部分,bp和神经网络。bp是 Back Propagation 的简写 ,意思是反向传播。BP网络能学习和存贮大量的输入-输出模式映射关系,而无需事前揭示描述这种映射关系的数学方程。它的学习规则是使用最速下降法,通过反向传播来不断调整网络的权值和阈值,使网络的误差平方和最小。其主要的特点是:信号是正向传播的,而误差是反向传播的。算法流程图如下:
在这里插入图片描述

2.2 神经元模型

每个神经元都接受来自其它神经元的输入信号,每个信号都通过一个带有权重的连接传递,神经元把这些信号加起来得到一个总输入值,然后将总输入值与神经元的阈值进行对比(模拟阈值电位),然后通过一个“激活函数”处理得到最终的输出(模拟细胞的激活),这个输出又会作为之后神经元的输入一层一层传递下去。神经元模型如下:
在这里插入图片描述

2.3 激活函数

引入激活函数的目的是在模型中引入非线性。如果没有激活函数(其实相当于激励函数是f(x) = x),那么无论你的神经网络有多少层,最终都是一个线性映射,那么网络的逼近能力就相当有限,单纯的线性映射无法解决线性不可分问题。正因为上面的原因,我们决定引入非线性函数作为激励函数,这样深层神经网络表达能力就更加强大
BP神经网络算法常用的激活函数:
1)Sigmoid(logistic),也称为S型生长曲线,函数在用于分类器时,效果更好。
在这里插入图片描述
在这里插入图片描述
2)Tanh函数(双曲正切函数),解决了logistic中心不为0的缺点,但依旧有梯度易消失的缺点。
在这里插入图片描述
在这里插入图片描述
3)relu函数是一个通用的激活函数,针对Sigmoid函数和tanh的缺点进行改进的,目前在大多数情况下使用
在这里插入图片描述
在这里插入图片描述

2.4 BP神经网络基础架构

BP网络由输入层、隐藏层、输出层组成
在这里插入图片描述
输入层:信息的输入端,是读入你输入的数据的
隐藏层:信息的处理端,可以设置这个隐藏层的层数(在这里一层隐藏层,q个神经元)
输出层:信息的输出端,也就是我们要的结果
对于上图的只含一个隐藏层的神经网络模型:BP神经网络的过程主要分为两个阶段,
第一阶段是信号的正向传播,从输入层经过隐含层,最后到达输出层;
第二阶段是误差的反向传播,从输出层到隐含层,最后到输入层,依次调节隐含层到输出层的权重和偏置,输入层到隐含层的权重和偏置

2.5 BP神经网络正向传播反向传播

正向传播过程
正向传播就是让信息从输入层进入网络,依次经过每一层的计算,得到最终输出层结果的过程
从输入层到隐藏层:
从隐藏层到输出层:
以y1举例。y1里的输出自然有来自b1,b2,…bq的。那么分别按照权重去乘。

类似的我们可以求解出y2……yn
反向传播过程
基本思想就是通过计算输出层与期望值之间的误差来调整网络参数,从而使得误差变小。
计算误差公式如下:(差值的平方)
在这里插入图片描述
如何调整权重的大小,才能使损失函数不断地变小呢?
1.梯度下降法
2.(随机梯度下降)SGD
3.(自适应优化)Adam

3.基于BP神经网络对MNIST数据集检测识别实验

3.1 MNIST数据集介绍

MNIST 数据集可在 http://yann.lecun.com/exdb/mnist/ 获取,它包含了四个部分:
•Training set images: train-images-idx3-ubyte.gz (9.9 MB, 解压后 47 MB, 包含 60,000 个样本)
•Training set labels: train-labels-idx1-ubyte.gz (29 KB, 解压后 60 KB, 包含 60,000 个标签)
•Test set images: t10k-images-idx3-ubyte.gz (1.6 MB, 解压后 7.8 MB, 包含 10,000 个样本)
•Test set labels: t10k-labels-idx1-ubyte.gz (5KB, 解压后 10 KB, 包含 10,000 个标签)
Mnist数据集中的标签是介于0~9的数字,Mnist中的标签是用独热编码(one-hot-vectors)表示的,一个one-hot向量除了某一位数字是1以外,其余维度的数组都是0,比如标签0用独热编码表示为([1, 0, 0, 0, 0, 0, 0, 0, 0, 0]),标签3用独热编码表示为([0, 0, 0, 1, 0, 0, 0, 0, 0, 0])。所以,Mnist数据集中所有的标签mnist.train.labels是一个[60000, 10]的数字矩阵。
在这里插入图片描述
训练图片和测试图片:
每一张图片包含2828个像素。Mnist数据集把代表一张图片的二维数据转开成一个向量,长度为2828=784。因此在Mnist的训练数据集中mnist.train.images是一个形状为[60000, 784]的张量,第一个维度数字用来索引图片,第二个维度数字用来索引每张图片中的像素点,图片里的某个像素的强度值介于0-1之间。(图8部分数据可视化)
在这里插入图片描述
在这里插入图片描述

3.2代码实现(Pytorch版本)

定义transform对象,其定义了数据集中的图片应该做怎样的处理:

transform = transforms.Compose([transforms.ToTensor(),
                                transforms.Normalize((0.5,), (0.5,)), ])

加载和下载训练测试数据集,这里使用pytorch提供的API进行下载:

train_set = datasets.MNIST('data',  # 下载到该文件夹下
                           download=not os.path.exists('train_set'),  # 是否下载,如果下载过,则不重复下载
                           train=True,  # 是否为训练集
                           transform=transform  # 要对图片做的transform
                           )
print(train_set)
test_set = datasets.MNIST('data',
                          download=not os.path.exists('test_set'),
                          train=False,
                          transform=transform
                          )
print(test_set)

构建训练数据集和测试数据集的DataLoader对象:

train_loader = torch.utils.data.DataLoader(train_set, batch_size=128, shuffle=True)
test_loader = torch.utils.data.DataLoader(test_set, batch_size=128, shuffle=True)

dataiter = iter(train_loader)
images, labels = dataiter.next()

print(images.shape)
print(labels.shape)

在上面,batch_size=64,每个batch送进64张图片每个图片只有一个通道(灰度图),大小为28x28。抽一张绘制一下:

plt.imshow(images[0].numpy().squeeze(), cmap='gray_r');

定义神经网络:

class NerualNetwork(nn.Module):
    def __init__(self):
        super().__init__()
        """
        定义第一个线性层,
        输入为图片(28x28),
        输出为第一个隐层的输入,大小为128。
        """
        self.linear1 = nn.Linear(28 * 28, 128)
        # 在第一个隐层使用ReLU激活函数
        self.relu1 = nn.ReLU()
        """
        定义第二个线性层,
        输入是第一个隐层的输出,
        输出为第二个隐层的输入,大小为64。
        """
        self.linear2 = nn.Linear(128, 64)
        # 在第二个隐层使用ReLU激活函数
        self.relu2 = nn.ReLU()
        """
        定义第三个线性层,
        输入是第二个隐层的输出,
        输出为输出层,大小为10
        """
        self.linear3 = nn.Linear(64, 10)
        # 最终的输出经过softmax进行归一化
        self.softmax = nn.LogSoftmax(dim=1)

        # 上述操作可以直接使用nn.Sequential写成如下形式:
        self.model = nn.Sequential(nn.Linear(28 * 28, 128),
                                   nn.ReLU(),
                                   nn.Linear(128, 64),
                                   nn.ReLU(),
                                   nn.Linear(64, 10),
                                   nn.LogSoftmax(dim=1)
                                   )

网络前向传播

def forward(self, x):
    """
    定义神经网络的前向传播
    x: 图片数据, shape为(64, 1, 28, 28)
    """
    # 首先将x的shape转为(64, 784)
    x = x.view(x.shape[0], -1)
    # 接下来进行前向传播
    x = self.linear1(x)
    x = self.relu1(x)
    x = self.linear2(x)
    x = self.relu2(x)
    x = self.linear3(x)
    x = self.softmax(x)
    # 上述一串,可以直接使用 x = self.model(x) 代替。
    return x

模型实例化:
定义损失函数,这里选用负对数似然损失函数(NLLLoss, negative log likelihood loss),其常用于分类任务
定义优化器,这里使用随机梯度下降法,学习率设置为0.003,momentum取默认的0.9(用于防止过拟合)

model = NerualNetwork()
criterion = nn.NLLLoss()
optimizer = optim.SGD(model.parameters(), lr=0.003, momentum=0.9)

模型训练过程:

time0 = time()  # 记录下当前时间
epochs = 15  # 一共训练15轮
for e in range(epochs):
    running_loss = 0  # 本轮的损失值
    for images, labels in train_loader:
        # 前向传播获取预测值
        output = model(images)
        # 计算损失
        loss = criterion(output, labels)
        # 进行反向传播
        loss.backward()
        # 更新权重
        optimizer.step()
        # 清空梯度
        optimizer.zero_grad()
        # 累加损失
        running_loss += loss.item()
    else:
        # 一轮循环结束后打印本轮的损失函数
        print("Epoch {} - Training loss: {}".format(e+1, running_loss / len(train_loader)))
# 打印总的训练时间
print("\nTraining Time (in minutes) =", (time() - time0) / 60)


correct_count, all_count = 0, 0
model.eval()  # 将模型设置为评估模式
# 从test_loader中一批一批加载图片
for images, labels in test_loader:
    # 循环检测这一批图片
    for i in range(len(labels)):
        logps = model(images[i])  # 进行前向传播,获取预测值
        probab = list(logps.detach().numpy()[0])                        # 将预测结果转为概率列表。[0]是取第一张照片的10个数字的概率列表(因为一次只预测一张照片)
        pred_label = probab.index(max(probab))  # 取最大的index作为预测结果
        true_label = labels.numpy()[i]
        if (true_label == pred_label):  # 判断是否预测正确
            correct_count += 1
        all_count += 1

print("Number Of Images Tested =", all_count)
print("\nModel Accuracy ={}%".format((correct_count / all_count)*100))

完整代码:

import os
import numpy as np
import torch
import torchvision
import matplotlib.pyplot as plt
from time import time
from torchvision import datasets, transforms
from torch import nn, optim

transform = transforms.Compose([transforms.ToTensor(),
                                transforms.Normalize((0.5,), (0.5,)), ])

train_set = datasets.MNIST('data',  # 下载到该文件夹下
                           download=not os.path.exists('train_set'),  # 是否下载,如果下载过,则不重复下载
                           train=True,  # 是否为训练集
                           transform=transform  # 要对图片做的transform
                           )
print(train_set)
test_set = datasets.MNIST('data',
                          download=not os.path.exists('test_set'),
                          train=False,
                          transform=transform
                          )
print(test_set)
train_loader = torch.utils.data.DataLoader(train_set, batch_size=128, shuffle=True)
test_loader = torch.utils.data.DataLoader(test_set, batch_size=128, shuffle=True)

dataiter = iter(train_loader)
images, labels = dataiter.next()

print(images.shape)
print(labels.shape)

plt.imshow(images[0].numpy().squeeze(), cmap='gray_r');


class NerualNetwork(nn.Module):
    def __init__(self):
        super().__init__()
        """
        定义第一个线性层,
        输入为图片(28x28),
        输出为第一个隐层的输入,大小为128。
        """
        self.linear1 = nn.Linear(28 * 28, 128)
        # 在第一个隐层使用ReLU激活函数
        self.relu1 = nn.ReLU()
        """
        定义第二个线性层,
        输入是第一个隐层的输出,
        输出为第二个隐层的输入,大小为64。
        """
        self.linear2 = nn.Linear(128, 64)
        # 在第二个隐层使用ReLU激活函数
        self.relu2 = nn.ReLU()
        """
        定义第三个线性层,
        输入是第二个隐层的输出,
        输出为输出层,大小为10
        """
        self.linear3 = nn.Linear(64, 10)
        # 最终的输出经过softmax进行归一化
        self.softmax = nn.LogSoftmax(dim=1)

        # 上述操作可以直接使用nn.Sequential写成如下形式:
        self.model = nn.Sequential(nn.Linear(28 * 28, 128),
                                   nn.ReLU(),
                                   nn.Linear(128, 64),
                                   nn.ReLU(),
                                   nn.Linear(64, 10),
                                   nn.LogSoftmax(dim=1)
                                   )

    def forward(self, x):
        """
        定义神经网络的前向传播
        x: 图片数据, shape为(64, 1, 28, 28)
        """
        # 首先将x的shape转为(64, 784)
        x = x.view(x.shape[0], -1)
        # 接下来进行前向传播
        x = self.linear1(x)
        x = self.relu1(x)
        x = self.linear2(x)
        x = self.relu2(x)
        x = self.linear3(x)
        x = self.softmax(x)
        # 上述一串,可以直接使用 x = self.model(x) 代替。
        return x


model = NerualNetwork()
criterion = nn.NLLLoss()
optimizer = optim.SGD(model.parameters(), lr=0.003, momentum=0.9)

time0 = time()  # 记录下当前时间
epochs = 15  # 一共训练15轮
for e in range(epochs):
    running_loss = 0  # 本轮的损失值
    for images, labels in train_loader:
        # 前向传播获取预测值
        output = model(images)
        # 计算损失
        loss = criterion(output, labels)
        # 进行反向传播
        loss.backward()
        # 更新权重
        optimizer.step()
        # 清空梯度
        optimizer.zero_grad()
        # 累加损失
        running_loss += loss.item()
    else:
        # 一轮循环结束后打印本轮的损失函数
        print("Epoch {} - Training loss: {}".format(e+1, running_loss / len(train_loader)))
# 打印总的训练时间
print("\nTraining Time (in minutes) =", (time() - time0) / 60)


correct_count, all_count = 0, 0
model.eval()  # 将模型设置为评估模式
# 从test_loader中一批一批加载图片
for images, labels in test_loader:
    # 循环检测这一批图片
    for i in range(len(labels)):
        logps = model(images[i])  # 进行前向传播,获取预测值
        probab = list(logps.detach().numpy()[0])                        # 将预测结果转为概率列表。[0]是取第一张照片的10个数字的概率列表(因为一次只预测一张照片)
        pred_label = probab.index(max(probab))  # 取最大的index作为预测结果
        true_label = labels.numpy()[i]
        if (true_label == pred_label):  # 判断是否预测正确
            correct_count += 1
        all_count += 1

print("Number Of Images Tested =", all_count)
print("\nModel Accuracy ={}%".format((correct_count / all_count)*100))

3.3代码实现(TensorFlow版本)

加载数据

#加载数据
mnist = tf.keras.datasets.mnist
(train_x,train_y),(test_x,test_y) = mnist.load_data()
print('\n train_x:%s, train_y:%s, test_x:%s, test_y:%s'%(train_x.shape,train_y.shape,test_x.shape,test_y.shape))

数据预处理

#归一化、并转换为tensor张量,数据类型为float32.
X_train,X_test = tf.cast(train_x/255.0,tf.float32),tf.cast(test_x/255.0,tf.float32)
y_train,y_test = tf.cast(train_y,tf.int16),tf.cast(test_y,tf.int16)

建立模型(单层隐含层的神经网络)

#建立模型
model = tf.keras.Sequential()
model.add(tf.keras.layers.Flatten(input_shape=(28,28)))     #添加Flatten层说明输入数据的形状
model.add(tf.keras.layers.Dense(128,activation='relu'))     #添加隐含层,为全连接层,128个节点,relu激活函数
model.add(tf.keras.layers.Dense(10,activation='softmax'))   #添加输出层,为全连接层,10个节点,softmax激活函数
print('\n',model.summary())     #查看网络结构和参数信息

配置模型训练方法

#配置模型训练方法
#adam算法参数采用keras默认的公开参数,损失函数采用稀疏交叉熵损失函数,准确率采用稀疏分类准确率函数
model.compile(optimizer='adam',loss='sparse_categorical_crossentropy',metrics=['sparse_categorical_accuracy'])

评估模型

#评估模型
model.evaluate(X_test,y_test,verbose=2)    #每次迭代输出一条记录,来评价该模型是否有比较好的泛化能力

保存模型

#保存整个模型
model.save('mnist_weights.h5')

结果可视化

#结果可视化
print(history.history)
loss = history.history['loss']          #训练集损失
val_loss = history.history['val_loss']  #测试集损失
acc = history.history['sparse_categorical_accuracy']            #训练集准确率
val_acc = history.history['val_sparse_categorical_accuracy']    #测试集准确率

运行结果
在这里插入图片描述
测试结果
在这里插入图片描述
在这里插入图片描述
完整代码:

########手写数字数据集##########
###########保存模型############
########1层隐含层(全连接层)##########
#60000条训练数据和10000条测试数据,28x28像素的灰度图像
#隐含层激活函数:ReLU函数
#输出层激活函数:softmax函数(实现多分类)
#损失函数:稀疏交叉熵损失函数
#输入层有784个节点,隐含层有128个神经元,输出层有10个节点
import tensorflow as tf
import matplotlib.pyplot as plt
import numpy as np

import time
print('--------------')
nowtime = time.strftime('%Y-%m-%d %H:%M:%S')
print(nowtime)

#指定GPU
import os
os.environ["CUDA_VISIBLE_DEVICES"] = "0"
# gpus = tf.config.experimental.list_physical_devices('GPU')
# tf.config.experimental.set_memory_growth(gpus[0],True)
#初始化
plt.rcParams['font.sans-serif'] = ['SimHei']

#加载数据
mnist = tf.keras.datasets.mnist
(train_x,train_y),(test_x,test_y) = mnist.load_data()
print('\n train_x:%s, train_y:%s, test_x:%s, test_y:%s'%(train_x.shape,train_y.shape,test_x.shape,test_y.shape))

#数据预处理
#X_train = train_x.reshape((60000,28*28))
#Y_train = train_y.reshape((60000,28*28))       #后面采用tf.keras.layers.Flatten()改变数组形状

#归一化、并转换为tensor张量,数据类型为float32.
X_train,X_test = tf.cast(train_x/255.0,tf.float32),tf.cast(test_x/255.0,tf.float32)
y_train,y_test = tf.cast(train_y,tf.int16),tf.cast(test_y,tf.int16)

#建立模型
model = tf.keras.Sequential()
model.add(tf.keras.layers.Flatten(input_shape=(28,28)))     #添加Flatten层说明输入数据的形状
model.add(tf.keras.layers.Dense(128,activation='relu'))     #添加隐含层,为全连接层,128个节点,relu激活函数
model.add(tf.keras.layers.Dense(10,activation='softmax'))   #添加输出层,为全连接层,10个节点,softmax激活函数
print('\n',model.summary())     #查看网络结构和参数信息

#配置模型训练方法
#adam算法参数采用keras默认的公开参数,损失函数采用稀疏交叉熵损失函数,准确率采用稀疏分类准确率函数
model.compile(optimizer='adam',loss='sparse_categorical_crossentropy',metrics=['sparse_categorical_accuracy'])

#训练模型
#批量训练大小为64,迭代5次,测试集比例0.2(48000条训练集数据,12000条测试集数据)
print('--------------')
nowtime = time.strftime('%Y-%m-%d %H:%M:%S')
print('训练前时刻:'+str(nowtime))

history = model.fit(X_train,y_train,batch_size=64,epochs=30,validation_split=0.2)

print('--------------')
nowtime = time.strftime('%Y-%m-%d %H:%M:%S')
print('训练后时刻:'+str(nowtime))
#评估模型
model.evaluate(X_test,y_test,verbose=2)    #每次迭代输出一条记录,来评价该模型是否有比较好的泛化能力

#保存模型参数
#model.save_weights('C:\\Users\\xuyansong\\Desktop\\深度学习\\python\\MNIST\\模型参数\\mnist_weights.h5')
#保存整个模型
model.save('mnist_weights.h5')


#结果可视化
print(history.history)
loss = history.history['loss']          #训练集损失
val_loss = history.history['val_loss']  #测试集损失
acc = history.history['sparse_categorical_accuracy']            #训练集准确率
val_acc = history.history['val_sparse_categorical_accuracy']    #测试集准确率

plt.figure(figsize=(10,3))

plt.subplot(121)
plt.plot(loss,color='b',label='train')
plt.plot(val_loss,color='r',label='test')
plt.ylabel('loss')
plt.legend()

plt.subplot(122)
plt.plot(acc,color='b',label='train')
plt.plot(val_acc,color='r',label='test')
plt.ylabel('Accuracy')
plt.legend()

#暂停5秒关闭画布,否则画布一直打开的同时,会持续占用GPU内存
#根据需要自行选择
#plt.ion()       #打开交互式操作模式
#plt.show()
#plt.pause(5)
#plt.close()

#使用模型
plt.figure()
for i in range(10):
    num = np.random.randint(1,10000)

    plt.subplot(2,5,i+1)
    plt.axis('off')
    plt.imshow(test_x[num],cmap='gray')
    demo = tf.reshape(X_test[num],(1,28,28))
    y_pred = np.argmax(model.predict(demo))
    plt.title('标签值:'+str(test_y[num])+'\n预测值:'+str(y_pred))
y_pred = np.argmax(model.predict(X_test[0:5]),axis=1)
print('X_test[0:5]: %s'%(X_test[0:5].shape))
print('y_pred: %s'%(y_pred))

plt.ion()       #打开交互式操作模式
plt.show()
plt.pause(5)
plt.close()

4. BP神经网络注意问题

a)参数选取很重要。 用最基本的BP 算法来训练BP 神经网络时,学习率、均方误差、权值、阈值的设置都对网络的训练均有影响。综合选取合理的值,将有利于网络的训练。在最基本的BP 算法中,学习率在整个训练过程是保持不变的,学习率过大,算法可能振荡而不稳定;学习率过小,则收敛速度慢,训练时间长。
b)存在麻痹现象。由于优化的目标函数很复杂,它必然会在神经元输出接近0或1的情况下,出现一些平坦区,在这些区域内,权值误差改变很小,使训练过程几乎停顿;
c) 搜索步长提前确定。 为了使网络执行BP算法,不能用传统的一维搜索法求每次迭代的步长,而必须把步长的更新规则预先赋予网络,这种方法将引起算法低效。
不同epoch下的Accurary可视化结果:

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

4.1 代码相关问题

Dataloader中num_worker设置
在这里插入图片描述
在这里插入图片描述
•num_workers=0表示只有主进程去加载batch数据,这个可能会是一个瓶颈。
•num_workers = 1表示只有一个worker进程用来加载batch数据,而主进程是不参与数据加载的。这样速度也会很慢。
•num_workers>0 表示只有指定数量的worker进程去加载数据,主进程不参与。增加num_works也同时会增加cpu内存的消耗。所以num_workers的值依赖于 batch size和机器性能。
•一般开始是将num_workers设置为等于计算机上的CPU数量
•最好的办法是缓慢增加num_workers,直到训练速度不再提高,就停止增加num_workers的值。

猜你喜欢

转载自blog.csdn.net/m0_37758063/article/details/131074950