MNIST 数据集分类:单隐层神经网络实现

本文使用 Python 构建单隐层神经网络,实现 MNIST 手写数字数据集的分类任务。通过调整隐层节点数量、学习率和激活函数等参数,观察分类准确率和总体均方误差的变化。

代码实现

# -*- coding: utf-8 -*-
'''
Created on Sat Apr 29 19:44:55 2023

@author: Lenovo
'''

import PIL
import numpy as np
import pandas as pd
import imageio
from PIL import Image, ImageTk # 导入图像处理函数库
import tkinter as tk
from tkinter import constants, ttk
from tkinter import filedialog   #导入文件对话框函数库


#————————————————————————神经网络构建,三层结构——————————————————#
#激活函数
def sigmoid(x):
    return 1.0 / (1.0 + np.exp(-x))

#定义神经网络函数
class neuralNetwork:
    #初始化神经网络
    def __init__(self,inputnodes,hiddennodes,outputnodes,learnrate):
        #设立每个神经网络的输入、隐藏、输出层的节点数
        self.inodes = inputnodes
        self.hnodes = hiddennodes
        self.onodes = outputnodes
        #设置学习率
        self.lrate = learnrate
        self.wi_h = (np.random.rand(self.hnodes,self.inodes)-0.5)
        self.wh_o = (np.random.rand(self.onodes,self.hnodes)-0.5)

    #训练神经网络
    def train(self,inputs_list,targets_list):
        #输入与标准结果
        inputs = np.array(inputs_list,ndmin=2).T
        targets= np.array(targets_list, ndmin=2).T
        #计算隐藏层的信号值
        hidden_inputs = np.dot(self.wi_h,inputs)
        hidden_outputs = sigmoid(hidden_inputs)
        #计算输出层的信号值
        outputs_inputs  = np.dot(self.wh_o, hidden_outputs)
        outputs_outputs = sigmoid(outputs_inputs)
        #计算误差:精确值-实际值
        output_errors = targets - outputs_outputs
        hidden_errors = np.dot(self.wh_o.T,output_errors)
        #根据公式得出的表达式,直接用
        self.wh_o += self.lrate * np.dot((output_errors*outputs_outputs*(1.0-outputs_outputs)),
                                         np.transpose(hidden_outputs))
        self.wi_h += self.lrate * np.dot((hidden_errors * hidden_outputs * (1.0 - hidden_outputs)),
                                         np.transpose(inputs))

    #接受输入,返回输出
    #将输出进行激活,归一化
    def query(self,input_list):
        inputs = np.array(input_list,ndmin=2).T
        hidden_inputs = np.dot(self.wi_h, inputs)
        hidden_outputs = sigmoid(hidden_inputs)
        outputs_inputs = np.dot(self.wh_o,hidden_outputs)
        outputs_outputs = sigmoid(outputs_inputs)
        return outputs_outputs


#——————————————初始化GUI界面——————————————--#
window = tk.Tk()
window.title('神经网络识别MNIST数据集')
window.geometry('600x500')
global img_png  # 定义全局变量 图像的
var = tk.StringVar()  # 这时文字变量储存器
text = tk.Text(window,width=20,height=17)
text.pack(fill=tk.X,side=tk.BOTTOM)
text.insert(tk.END, '请输入相关数据,构建一个网络\n')

def craet_BPNN():
    global n
    global input_nodes
    global hidden_nodes
    global output_nodes
    global learning_rate
    global epochs
    global training_data_list
#—————————————创建神经网络对象并用数据集训练网络——————————#
# 输入、隐藏、输出节点数

    input_nodes =int(var_inputs.get())
    hidden_nodes= int(var_hidden.get())
    output_nodes = int( var_outputs.get())
    # 学习率
    learning_rate = float(var_lrate.get())
    epochs = int(var_epochs.get())
    # 创建神经网络对象
    n = neuralNetwork(input_nodes, hidden_nodes, output_nodes, learning_rate)
    text.insert(tk.END, 'BP网络构建成功!\n')
    text.insert(tk.END, '输入层节点数:'+var_inputs.get()+',隐藏层节点数:'+var_hidden.get()+',输出层节点数:'+var_outputs.get()+'\n')
    text.insert(tk.END, '学习率:' + var_lrate.get() + ',训练世代:' + var_epochs.get()+ '\n')
    text.insert(tk.END, '可以开始训练了!\n')
    #加载mnist数据集
    training_data_file = open("mnist_train.csv", 'r')
    training_data_list = training_data_file.readlines()
    training_data_file.close()

#开始训练函数,训练MNist数据集
def beg_train():
     for e in range(epochs):#   训练的世代,一次训练完成表示训练一个世代
            print("训练中,第", e, "个世代")
            text.insert(tk.END, '训练中,第' + str(e) + '个世代\n')
            text.update()
            t=0
            for record in training_data_list:
                t+=1
                print("已训练",t,"个数据")
            # 用”,“来区分数据
                all_values = record.split(',')
            # 将输入缩放和转换
                inputs = (np.asfarray(all_values[1:]) / 255.0 * 0.99) + 0.01
            # 将目标的输出值的0改为0.01,1改为0.99
                targets = np.zeros(output_nodes) + 0.01
                targets[int(all_values[0])] = 0.99
                n.train(inputs, targets)
                pass


     text.insert(tk.END, '训练完毕!\n')
     text.insert(tk.END, '可以开始测试你的网络了!\n')
pass

#       打开测试数据集MNIST-test

# 开始测试函数,遍历所有测试集中的测试数据,得出准确率

#————————————————————————测试MNIst数据集————————————————————#
def beg_test():
    global test_data_list
   #数据集的文件路径由自己定义,这是我自己的路径
    test_data_file = open("mnist_test.csv", 'r')
    test_data_list = test_data_file.readlines()
    test_data_file.close()
    all_values = test_data_list[0].split(',')
    print(all_values[1])
    n.query((np.asfarray(all_values[1:])/255.0*0.99)+0.01)
    #用来存放分数,即正确率
    scorecard = []
    text.insert(tk.END, '开始测试MNIST数据集.....\n')
    for record in test_data_list:
                #用”,“号分开数据
                all_values = record.split(',')
                # 用准确值标签记录数字准确值
                correct_label = int(all_values[0])
                print("---------")
                print("正确结果",correct_label)
                # 缩放
                inputs = (np.asfarray(all_values[1:]) / 255.0 * 0.99) + 0.01
                # 计算输出
                outputs = n.query(inputs)
                # 输出的最大值即为判断值
                label = np.argmax(outputs)
                print( "神经网络判断",label)
                # 将正确和错误的判断形成一个列表
                if (label == correct_label):
                    # 正确为1
                    scorecard
                    scorecard.append(1)
                else:
                    # 错误为0
                    scorecard
                    scorecard.append(0)
    print(scorecard)
    scorecard_array = np.asarray(scorecard)
    #正确率
    right_rate = (scorecard_array.sum() / scorecard_array.size) * 100

    text.insert(tk.END, '数据测试完毕\n')
    text.insert(tk.END, '正确率='+str(right_rate)+'%\n')
    text.update()
    print("正确率= ", right_rate, "%")
    pass

#打开图片的函数,并尝试识别自己的图片
#编写GUI,让其更容易交互
def Open_Img():
        global img_png
        global path
        global label_Img
        OpenFile = tk.Tk()  # 创建新窗口
        OpenFile.withdraw()
        file_path = filedialog.askopenfilename()
        print("训练已结束,开始测试图片")
        text.insert(tk.END, '开始测试图片\n')
        path=file_path
        Img =Image.open(file_path)
        img_png = ImageTk.PhotoImage(Img)
        label_Img = tk.Label(window, image=img_png)
        Label_Show = tk.Label(window, image=img_png,
                              # 使用 textvariable 替换 text, 因为这个可以变化
                              bg='white', font=('Arial', 12), width=60, height=60)
        Label_Show.place(x=80, y=80)
        var.set('图像已打开')
        #自己图片的数据存放在这里
        our_own_dataset = []
        image_file_name = path
        print("加载中 ... ", image_file_name)
        text.insert(tk.END, '加载中....'+image_file_name+'\n')
        # 用文件名来设置准确值标签
        label = int(image_file_name[20])
        #将图片转换为数组
        img_array = imageio.imread(image_file_name, as_gray=True)
        # 将图片从28X28的数组转换成长为784的array
        img_data = img_array.reshape(784)
        # 缩放灰度值为0-1范围内
        img_data = (img_data / 255.0 * 0.99) + 0.01
        print("图像最小值为",np.min(img_data))
        print("图像最大值为",np.max(img_data))
        # 将标签值放到数组第一个
        record = np.append(label, img_data)
        our_own_dataset.append(record)
        item = 0
        correct_label = our_own_dataset[item][0]
        # 将转换值作为输入
        inputs = our_own_dataset[item][1:]
        # 计算网络的输出
        outputs = n.query(inputs)
        print("输出节点的输出为:",outputs)
        text.insert(tk.END, '输出节点的输出为\n'+str(outputs)+'\n')
        text.update()
        # 最高输出值所在的数字作为识别标签
        label = np.argmax(outputs)
        print("神经网络说:“它是", label, "”")
        text.insert(tk.END, '神经网络认为图中的数字是' + str(label) + '\n')
        text.see(tk.END)
        if (label == correct_label):
            print("恭喜你,匹配成功!")
            text.insert(tk.END, '恭喜你,识别成功了!\n')
        else:
            print("很遗憾,识别失败了")
            text.insert(tk.END, '很遗憾,识别失败了!再试一次吧\n')
        pass

#显示图片的函数
def SHOW():
    global img_png
    Label_Show = tk.Label(window, image=img_png,
                          # 使用 textvariable 替换 text, 因为这个可以变化
                          bg='white', font=('Arial', 12), width=60, height=60)
    Label_Show.place(x=80, y=80)

pass


img_frame = tk.LabelFrame(window, text='图像显示', padx=10, pady=10,
                       width=120,height=120)
img_frame.place(x=55,y=50)
# 创建文本窗口,显示当前操作8状态
in_lable=tk.Label(window,text='输入层节点数:')
in_lable.pack()
in_lable.place(x=300,y=40)

var_inputs=tk.StringVar()
var_inputs.set('784')
entry_inputs=tk.Entry(window,textvariable=var_inputs,width=10)
entry_inputs.place(x=380,y=40)

hi_lable=tk.Label(window,text='隐藏层节点数:')
hi_lable.pack()
hi_lable.place(x=300,y=70)

var_hidden=tk.StringVar()
var_hidden.set('50')
entry_hidden=tk.Entry(window,textvariable=var_hidden,width=10)
entry_hidden.place(x=380,y=70)

out_lable=tk.Label(window,text='输出层节点数:')
out_lable.pack()
out_lable.place(x=300,y=100)

var_outputs=tk.StringVar()
var_outputs.set('10')
entry_outputs=tk.Entry(window,textvariable=var_outputs,width=10)
entry_outputs.place(x=380,y=100)

rate_lable=tk.Label(window,text='学习率:')
rate_lable.pack()
rate_lable.place(x=300,y=130)

var_lrate=tk.StringVar()
var_lrate.set('0.1')
entry_lrate=tk.Entry(window,textvariable=var_lrate,width=10)
entry_lrate.place(x=380,y=130)

epochs_lable=tk.Label(window,text='训练世代:')
epochs_lable.pack()
epochs_lable.place(x=300,y=160)

var_epochs=tk.StringVar()
var_epochs.set('5')
entry_epochs=tk.Entry(window,textvariable=var_epochs,width=10)
entry_epochs.place(x=380,y=160)

#训练数据集按钮
btn_train = tk.Button(window,text='构建网络',width=15, height=2,
                         command=craet_BPNN)
btn_train.pack()
btn_train.place(x=30,y = 210)
#测试数据集按钮
btn_test = tk.Button(window,text='训练数据集',width=15, height=2,
                         command=beg_train)
btn_test.pack()
btn_test.place(x=170,y=210)
# 创建打开图像按钮
btn_Open = tk.Button(window,
                         text='测试数据集',  # 显示在按钮上的文字
                         width=15, height=2,
                         command=beg_test)  # 点击按钮式执行的命令
btn_Open.pack()
# 按钮位置
btn_Open.place(x=310,y=210)
# 创建显示图像按钮
btn_Show = tk.Button(window,
                         text='打开测试图片',  # 显示在按钮上的文字
                         width=15, height=2,
                         command=Open_Img)  # 点击按钮式执行的命令

btn_Show.pack()
# 按钮位置
btn_Show.place(x=450,y=210)
# 运行整体窗口
window.mainloop()
pass

实验结果分析

通过调整隐层节点数量、学习率和激活函数等参数,可以观察到以下规律:

  • 隐层节点数量:随着隐层节点数量的增加,网络的表达能力增强,分类准确率在一定范围内提高。但当隐层节点数量过多时,可能会导致过拟合现象,造成训练集上表现良好,测试集上表现较差。
  • 学习率:学习率决定了网络参数更新的步长。学习率过大,可能导致参数震荡,无法收敛到最优解;学习率过小,则训练速度过慢。需要根据具体情况选择合适的学习率。
  • 激活函数:不同的激活函数具有不同的特点,例如 sigmoid 函数会将输出值压缩到 0 到 1 之间,ReLU 函数可以避免梯度消失问题。选择合适的激活函数可以提高网络的训练效率和分类效果。

总结

单隐层神经网络可以用于解决 MNIST 数据集的分类任务,但其性能受网络参数的影响很大。需要通过实验选择合适的网络参数,才能获得最佳的分类效果。

其他

  • 以上代码实现仅供参考,可以使用其他深度学习框架,例如 TensorFlow 或 PyTorch,来实现更为复杂的神经网络结构。
  • 可以尝试使用其他数据集,例如 CIFAR-10 或 ImageNet,来测试神经网络的泛化能力。
  • 可以尝试使用更先进的网络结构,例如卷积神经网络 (CNN),来提高分类准确率。
MNIST 数据集分类:单隐层神经网络实现

原文地址: https://www.cveoy.top/t/topic/k4WM 著作权归作者所有。请勿转载和采集!

免费AI点我,无需注册和登录