Bootstrap

人工智能应用-实验5-BP 神经网络分类手写数据集

🧡🧡实验内容🧡🧡

编写 BP 神经网络分类, 实现对 MNIST 数据集分类的操作。


🧡🧡代码🧡🧡

需要配置torch。由于是小demo。为了提高效率,我采用的是google的colab进行实验编码,省去配环境的烦恼。

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

#@title 加载
transform = transforms.Compose([
                transforms.ToTensor(), # 转为张量,同时如果是图片(uint8)类型,会自动进行归一化到(0,1)
                transforms.Normalize( (0.5, ) , (0.5, ) ) # 转为std=0.5、mean=0.5的分布, 灰色图像,通道只有一个  将值域(0,1)再次转为(-1,1)
                ])
train_set = datasets.MNIST('train_set', # 下载到该文件夹下
              download=not os.path.exists('train_set'), # 是否下载,如果下载过,则不重复下载
              train=True, # 是否为训练集
              transform=transform # 要对图片做的transform
              )
test_set = datasets.MNIST('test_set',
              download=not os.path.exists('test_set'),
              train=False,
              transform=transform
              )
test_set
# train_set[0][0]
train_loader = torch.utils.data.DataLoader(train_set, batch_size=64, shuffle=True)
test_loader = torch.utils.data.DataLoader(test_set, batch_size=64, shuffle=True)

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


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

    def forward(self, x):
        """
        定义神经网络的前向传播
        x: 输入的图片数据, shape为(64, 1, 28, 28)
        """
        x = x.view(x.shape[0], -1) # 首先将x的shape转为(64, 784)

        # 进行前向传播
        x = self.linear1(x)
        x = self.relu1(x)
        x = self.linear2(x)
        x = self.relu2(x)
        x = self.linear3(x)
        x = self.softmax(x)

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

#@title 评估
from sklearn.metrics import confusion_matrix, roc_auc_score, roc_curve
model.eval() # 将模型设置为评估模式

correct_count, all_count = 0, 0
predictions = [] # 预测结果列表
true_labels = [] # 真实标签列表

for images,labels in test_loader: # 从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
        predictions.append(pred_label)
        true_labels.append(true_label)

# 准确率
print("Number Of Images Tested =", all_count)
print("Model Accuracy =", (correct_count/all_count))

# 混淆矩阵
def plot_confusion_matrix(cm, classes):
    plt.imshow(cm, interpolation='nearest', cmap=plt.cm.Blues)
    plt.title("Confusion Matrix")
    plt.colorbar()
    tick_marks = np.arange(len(classes))
    plt.xticks(tick_marks, classes)
    plt.yticks(tick_marks, classes)
    thresh = cm.max() / 2
    for i in range(cm.shape[0]):
        for j in range(cm.shape[1]):
            plt.text(j, i, format(cm[i, j], 'd'), ha="center", va="center",
                     color="white" if cm[i, j] > thresh else "black")
    plt.ylabel('True Label')
    plt.xlabel('Predicted Label')
    plt.tight_layout()
    plt.show()

cm = confusion_matrix(true_labels, predictions)
classes = [str(i) for i in range(10)]
plot_confusion_matrix(cm, classes)

#@title 验证
model.train() # 切回训练模式

## 验证本地图片
import cv2
from PIL import Image
for num in range(0,10):
    img = cv2.imread('./myImg/{}.jpg'.format(num), 0)  # 以灰度图的方式读取要预测的图片
    img = cv2.resize(img, (28, 28))
    height, width = img.shape
    dst = np.zeros((height, width), np.uint8)
    for i in range(height):
        for j in range(width):
            dst[i, j] = 255 - img[i, j]
    dst= dst / 255.0 #归一化
    dst = (dst - 0.5) / 0.5  # 标准化到[-1, 1]
    img = dst
    # print(img)
    img = np.array(img).astype(np.float32)
    img = np.expand_dims(img, 0)  # 扩展后,为[1,28,28]
    img = np.expand_dims(img, 0)  # 扩展后,为[1,1,28,28]
    img = torch.from_numpy(img)
    # print(img.shape)
    with torch.no_grad():
        output=model(img)
    # print(output.data)
    print(output.data.max(1)[1])


🧡🧡分析结果🧡🧡

数据预处理

  • 加载数据集:
    加载torch自带的minst数据集
  • 转换数据:
    先转为tensor变量(相当于直接除255归一化到值域为(0,1))
    在这里插入图片描述
    然后根据std=0.5,mean=0.5,再将值域标准化到(-1,1)
    在这里插入图片描述

设置基本参数:
在这里插入图片描述

构建BP神经网络:
如下,输入为一张2828图片,拆解成2828=784个特征,最终经过三个线性层(784,128)、(128、64)、(64,10),输出为10个特征(对应10个类),归一化这10个特征,它们的大小即认为它属于哪张图片的概率值,取出概率最大的特征对应的类别作为最终预测类别。
在这里插入图片描述

模型训练:
在这里插入图片描述
在这里插入图片描述

模型评估:
准确率:达到97.69%
在这里插入图片描述
混淆矩阵
在这里插入图片描述

接下来,分析网络层数对分类准确率的影响。
被对照试验:隐藏层数目改为2,神经元数目分别为128、64
准确率为:97.69%
对照实验1:隐藏层数目改为3,神经元数目分别为256、128、64
在这里插入图片描述
Loss图:
在这里插入图片描述
准确率和混淆矩阵如下:97.55%
在这里插入图片描述
对照实验2:隐藏层数目改为5,神经元数目分别为512、256、128、64、32
在这里插入图片描述
Loss图:
在这里插入图片描述
准确率和混淆矩阵:97.85%
在这里插入图片描述
总结结果如下表:
在这里插入图片描述
分析可知:

  • 运行时间:从实验结果来看,在增加隐藏层数的情况下,运行时间明显增加。
  • 准确率:实验结果显示,在增加隐藏层数的情况下,准确率大体上有所提升,但是总体变化幅度并不大,可能是因为epochs或者随机梯度下降等参数已经设为较优值,使得准确率已经接近最优效果,从而导致增加网络层数的提优空间并不明显。
    综合来看,增加隐藏层数对于提高分类准确率有一定的帮助,但是也会明显增加运行时间。其次,需要注意的是,若增加隐藏层数并非一定能够带来准确率的提升,过多的隐藏层可能会导致过拟合等问题。

🧡🧡实验总结🧡🧡

在完成基础实验上,我自己画了几张数字图,以对模型进行验证
在这里插入图片描述
结果如下,可以看到,对数字1和数字5分类错误(分布预测成了5和8),其余均分类正确,大体上效果良好。考虑原因,可能是因为minst的数据集是“黑底白字”,而我手画的图片则为“黑字白底”,导致了一些误差。
在这里插入图片描述
理论理解:
通过本次实验,大体上掌握了BP神经网络的定义和结构,总的来说,BP神经网络可以理解为一个黑盒子,通过不断根据loss进行反向传播,最终目的就是得到线性参数w和b,从而根据Y=wx+b 对输入的新x进行预测分类。
代码实践:
一开始想用纯numpy进行BP网络的编写,但是在编写后向传播时,可能是线代和高数知识有些遗忘,求导数时琢磨了很久。后面还是选择直接使用pytorch进行编写,也容易调参,方便进行实验。对我而言,代码中比较纠结的是shape的转换和传入,因此最好多查看中间过程的shape,以便更好理解。

;