DIY全连接神经网络识别mnist数据集

使用python和numpy写一个全连接神经网络,识别mnist数据集

一、代码思路

读取mnist数据集前990个数字训练网络,991-1000个数据进行简单的测试。
由两位博主的代码拼接而来,一部分是mnist的读取,另一部分是神经网络的搭建,但是那篇文章没有用mnist数据集。

二、mnist读取部分

1、mnist下载位置:http://yann.lecun.com/exdb/mnist/
解压后的四个文件

DIY全连接神经网络识别mnist数据集_第1张图片
2、抄自博客:https://blog.csdn.net/panrenlong/article/details/81736754
读取部分除了mnist目录和main函数其他没有改动,注意mnist文件的根目录改成和自己电脑上的位置。
这部分看懂思路就好,不用细看纠结,直接用。
3、此处将此文件命名为mnist_loader.py,代码如下,详细了解见原博客

import numpy as np
import struct
import matplotlib.pyplot as plt

# 训练集文件(根据自己mnist数据集下载位置更改)
train_images_idx3_ubyte_file = 'E:/项目/神经网络入门/mnist/train-images-idx3-ubyte'
# 训练集标签文件
train_labels_idx1_ubyte_file = 'E:/项目/神经网络入门/mnist/train-labels-idx1-ubyte'

# 测试集文件
test_images_idx3_ubyte_file = 'E:/项目/神经网络入门/mnist/test-images-idx3-ubyte'
# 测试集标签文件
test_labels_idx1_ubyte_file = 'E:/项目/神经网络入门/mnist/test-labels-idx1-ubyte'


def decode_idx3_ubyte(idx3_ubyte_file):
    """
    解析idx3文件的通用函数
    :param idx3_ubyte_file: idx3文件路径
    :return: 数据集
    """
    # 读取二进制数据
    bin_data = open(idx3_ubyte_file, 'rb').read()

    # 解析文件头信息,依次为魔数、图片数量、每张图片高、每张图片宽
    offset = 0
    fmt_header = '>iiii' #因为数据结构中前4行的数据类型都是32位整型,所以采用i格式,但我们需要读取前4行数据,所以需要4个i。我们后面会看到标签集中,只使用2个ii。
    magic_number, num_images, num_rows, num_cols = struct.unpack_from(fmt_header, bin_data, offset)
    print('魔数:%d, 图片数量: %d张, 图片大小: %d*%d' % (magic_number, num_images, num_rows, num_cols))

    # 解析数据集
    image_size = num_rows * num_cols
    offset += struct.calcsize(fmt_header)  #获得数据在缓存中的指针位置,从前面介绍的数据结构可以看出,读取了前4行之后,指针位置(即偏移位置offset)指向0016。
    print(offset)
    fmt_image = '>' + str(image_size) + 'B'  #图像数据像素值的类型为unsigned char型,对应的format格式为B。这里还有加上图像大小784,是为了读取784个B格式数据,如果没有则只会读取一个值(即一副图像中的一个像素值)
    print(fmt_image,offset,struct.calcsize(fmt_image))
    images = np.empty((num_images, num_rows, num_cols))
    #plt.figure()
    for i in range(num_images):
        if (i + 1) % 10000 == 0:
            print('已解析 %d' % (i + 1) + '张')
            print(offset)
        images[i] = np.array(struct.unpack_from(fmt_image, bin_data, offset)).reshape((num_rows, num_cols))
        #print(images[i])
        offset += struct.calcsize(fmt_image)
#        plt.imshow(images[i],'gray')
#        plt.pause(0.00001)
#        plt.show()
    #plt.show()

    return images


def decode_idx1_ubyte(idx1_ubyte_file):
    """
    解析idx1文件的通用函数
    :param idx1_ubyte_file: idx1文件路径
    :return: 数据集
    """
    # 读取二进制数据
    bin_data = open(idx1_ubyte_file, 'rb').read()

    # 解析文件头信息,依次为魔数和标签数
    offset = 0
    fmt_header = '>ii'
    magic_number, num_images = struct.unpack_from(fmt_header, bin_data, offset)
    print('魔数:%d, 图片数量: %d张' % (magic_number, num_images))

    # 解析数据集
    offset += struct.calcsize(fmt_header)
    fmt_image = '>B'
    labels = np.empty(num_images)
    for i in range(num_images):
        if (i + 1) % 10000 == 0:
            print ('已解析 %d' % (i + 1) + '张')
        labels[i] = struct.unpack_from(fmt_image, bin_data, offset)[0]
        offset += struct.calcsize(fmt_image)
    return labels


def load_train_images(idx_ubyte_file=train_images_idx3_ubyte_file):
    """
    TRAINING SET IMAGE FILE (train-images-idx3-ubyte):
    [offset] [type]          [value]          [description]
    0000     32 bit integer  0x00000803(2051) magic number
    0004     32 bit integer  60000            number of images
    0008     32 bit integer  28               number of rows
    0012     32 bit integer  28               number of columns
    0016     unsigned byte   ??               pixel
    0017     unsigned byte   ??               pixel
    ........
    xxxx     unsigned byte   ??               pixel
    Pixels are organized row-wise. Pixel values are 0 to 255. 0 means background (white), 255 means foreground (black).

    :param idx_ubyte_file: idx文件路径
    :return: n*row*col维np.array对象,n为图片数量
    """
    return decode_idx3_ubyte(idx_ubyte_file)


def load_train_labels(idx_ubyte_file=train_labels_idx1_ubyte_file):
    """
    TRAINING SET LABEL FILE (train-labels-idx1-ubyte):
    [offset] [type]          [value]          [description]
    0000     32 bit integer  0x00000801(2049) magic number (MSB first)
    0004     32 bit integer  60000            number of items
    0008     unsigned byte   ??               label
    0009     unsigned byte   ??               label
    ........
    xxxx     unsigned byte   ??               label
    The labels values are 0 to 9.

    :param idx_ubyte_file: idx文件路径
    :return: n*1维np.array对象,n为图片数量
    """
    return decode_idx1_ubyte(idx_ubyte_file)


def load_test_images(idx_ubyte_file=test_images_idx3_ubyte_file):
    """
    TEST SET IMAGE FILE (t10k-images-idx3-ubyte):
    [offset] [type]          [value]          [description]
    0000     32 bit integer  0x00000803(2051) magic number
    0004     32 bit integer  10000            number of images
    0008     32 bit integer  28               number of rows
    0012     32 bit integer  28               number of columns
    0016     unsigned byte   ??               pixel
    0017     unsigned byte   ??               pixel
    ........
    xxxx     unsigned byte   ??               pixel
    Pixels are organized row-wise. Pixel values are 0 to 255. 0 means background (white), 255 means foreground (black).

    :param idx_ubyte_file: idx文件路径
    :return: n*row*col维np.array对象,n为图片数量
    """
    return decode_idx3_ubyte(idx_ubyte_file)


def load_test_labels(idx_ubyte_file=test_labels_idx1_ubyte_file):
    """
    TEST SET LABEL FILE (t10k-labels-idx1-ubyte):
    [offset] [type]          [value]          [description]
    0000     32 bit integer  0x00000801(2049) magic number (MSB first)
    0004     32 bit integer  10000            number of items
    0008     unsigned byte   ??               label
    0009     unsigned byte   ??               label
    ........
    xxxx     unsigned byte   ??               label
    The labels values are 0 to 9.

    :param idx_ubyte_file: idx文件路径
    :return: n*1维np.array对象,n为图片数量
    """
    return decode_idx1_ubyte(idx_ubyte_file)

if __name__ == '__main__':
    train_images = load_train_images()
    train_labels = load_train_labels()
    test_images = load_test_images()
    test_labels = load_test_labels()

    #查看某一编号的图片数据和label,并显示图片
    image_num = 992
    print(train_images[image_num])      #显示编号对应28*28矩阵
    print(train_labels[image_num])      #打印显示的数字值
    plt.imshow(train_images[image_num], cmap='gray')    #通过matplotlib显示图片
    plt.pause(0.000001)
    plt.show()

示例运行结果:可以看到打印出来的矩阵,matplotlib显示的图像,和左下角label为数字5.0
DIY全连接神经网络识别mnist数据集_第2张图片

DIY全连接神经网络识别mnist数据集_第3张图片

三、神经网络的搭建

1、此部分代码优化自博客:https://blog.csdn.net/m0_46238576/article/details/106151131?spm=1001.2014.3001.5502,网络结构完全没有变,代码变动也很少,建议先看完此博主的博客,他讲得很细。
2、博主是初中生,现在可能已经高中了,看他两年没更新了,他文章部分内容来自:

DIY全连接神经网络识别mnist数据集_第4张图片
代码如下:名称为MyNet.py

import matplotlib.pyplot as plt #用来画mnist 28*28图像
import numpy as np              #numpy
import scipy.special            #引入激活函数sigmoid
import mnist_loader             #读取mnist数据,mnist_loader.py

# 三层神经网络类
class Network:
    # 类的初始化,
    def __init__(self, input_num, hidden_num, output_num, learning_rate):
        # 各层神经元的数量初始化
        self.input_node = input_num     #输入节点数量(28*28=784个输入,注意是一维数据)
        self.hidden_node = hidden_num   #隐藏层神经元数量(中间层神经元应该算可以变更数量的,按原教程暂定600)
        self.output_node = output_num   #输出层神经元数量(10个神经元输出,代表0-9数字,识别出图像后只有一个值很大)

        # 学习率(步长)初始化
        self.lr = learning_rate

        # 初始化随机 输入-隐藏,隐藏-输出 两个二维权重矩阵,np.random.rand()返回一组服从[0,1)均匀分布的随机样本值,减去0.5矩阵里初始有正有负,训练效果据说更好
        self.weight_input_hidden = (np.random.rand(self.hidden_node, self.input_node) - 0.5)    # 输入层到隐藏层的权重,减0.5是为了有小于0的初始权重
        self.weight_hidden_output = (np.random.rand(self.output_node, self.hidden_node) - 0.5)  # 隐藏层到输出层的权重

        # 激活函数,百度一下sigmoid函数
        self.sigmoid = lambda x: scipy.special.expit(x)

    #训练
    """
    input_list是训练时单个的图像数据,由784个像素值组成
    target_list是input_list中图片的数字值,已经变换成一维列表。如数字0是[1,0,0,0,0,0,0,0,0,0],数字2是[0,1,0,0,0,0,0,0,0,0],第几个数字为1就代表0-9中对应的数字
    通过不停刷新input_list和target_list,训练输入层与隐藏层之间的权重、隐藏层与输出层之间的权重
    """
    def train(self, input_list, target_list):
        # 把输入列表转换为二维矩阵(数据还是一维),不然不能转置(numpy要求),如将[0...783]变成[[0...783]],然后转置
        inputs = np.array(input_list, ndmin=2).T
        targets = np.array(target_list, ndmin=2).T

        # 前向传播(算出输出值)
        # 计算隐藏层输入
        hidden_input = np.dot(self.weight_input_hidden, inputs)
        # 计算隐藏层输出
        hidden_output = self.sigmoid(hidden_input)
        # 计算输出层输入
        output_input = np.dot(self.weight_hidden_output, hidden_output)
        # 计算输出层输出
        output = self.sigmoid(output_input)

        # 反向传播(算出总误差,分配给前层,并更新权重)
        # 求最终误差(目标值-预测值)
        output_error = targets - output
        hidden_error = np.dot(self.weight_hidden_output.T, output_error)
        # 更新隐藏层与输出层之间的权重
        self.weight_hidden_output += self.lr * np.dot((output_error * output * (1.0 - output)), np.transpose(hidden_output))
        # 更新输入层与隐藏层之间的权重
        self.weight_input_hidden += self.lr * np.dot((hidden_error * hidden_output * (1.0 - hidden_output)), np.transpose(inputs))

    # 神经网络查询函数
    def ask(self, input_list):
        #此函数用于通过输入、权重、激活函数来进行正向传播(算出输出)
        # 把输入列表转换为二维矩阵(数据还是一维)并转置,如将[0...783]变成[[0...783]],然后转置
        inputs = np.array(input_list, ndmin=2).T
        # 计算隐藏层输入
        hidden_input = np.dot(self.weight_input_hidden, inputs)
        # 计算隐藏层输出
        hidden_output = self.sigmoid(hidden_input)

        # 计算输出层输入
        output_input = np.dot(self.weight_hidden_output, hidden_output)
        # 计算输出层输出(输出结果和train函数中的target_list差不多,不过十个数为科学计数法形式,除了一个数接近1其他一般都很小)
        output = self.sigmoid(output_input)
        return output

if __name__ == "__main__":
    #神经网络初始化
    input_num = 784     #输入节点数量(28*28=784个输入,注意是一维数据)
    hidden_num = 600    #隐藏层神经元数量(中间层神经元应该算可以变更数量的,按原教程暂定600)
    output_num = 10     #输出层神经元数量(10个神经元输出,代表0-9数字,识别出图像后只有一个值很大)
    lr = 0.05           #学习率(步长)初始化
    net = Network(input_num, hidden_num, output_num, lr)    #初始化神经网络结构,但此时网络内部权重都是随机无作用的

    #将mnist文件中的数据通过mnist_loader.py写入mnist_images_list和mnist_labels_list中
    mnist_images_list = mnist_loader.load_train_images()    #图片数据28*28*45176的矩阵
    mnist_labels_list = mnist_loader.load_train_labels()    #图片中写的数字,45176个数字,保留一位小数如1.0, 8.0

    #image处理
    """
    由于数据多达4万多张,此处仅取train-images-idx3-ubyte前990张进行训练,留下10张简单测试。正常流程应该用test-images-idx3-ubyte中数据测试。
    遍历mnist_images_list中的前1000张图片,在每次循环中将图像的28*28二维矩阵变换为784个数据的一维列表,
    同时将像素值[0,255]范围变成[0,1)范围,并将此次循环的单张一维图片数据写入temp_image
    将temp_image[]数据添加到image_list[]中,如此循环1000次,temp_image就有temp_image[0]-temp_image[999]1000组一维图片数据
    """
    image_list = []
    for index_image in mnist_images_list[0: 1000]:
        temp_image = []         #在1000次循环中temp_image一维数据不断添加到image_list[]中
        for row in range(28):
            for pixel in range(28):
                temp = index_image[row][pixel]/256 #在单张28*28行列循环中,将0-255的像素值缩至0-1小数
                temp_image.append(temp)            #通过28*28次循环,将0-1的784个temp值添加到一维列表temp_image[]中
        image_list.append(temp_image)              #在每张图片数据完成一维处理后,添加到image_list中,重复1000次

    #label处理
    """
    由于label数据多达4万多条,只处理前0-989范围990个数据,990-999最终测试用,不训练
    minist提供的label数据为保留一位小数的整数,0.0,1.0,2.0,3.0----,9.0
    但训练数据时用的label是[1,0,0,0,0,0,0,0,0,0]这种形式,第几位数字是1就代表0-9哪个数字,注意是训练用数据,训练完成测试输出时都是浮点数,最大的那个就是代表的数字
    所以此处将前990个label从浮点数转换为如[1,0,0,0,0,0,0,0,0,0]这种形式,并存储在label_list[]中。和image_list[]中列表序号对应,但最后十个不训练
    """
    label_list = []   
    #labels = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
    for k in range(1000):
        num_int = int(mnist_labels_list[k])    #label中的数据转换为整数,如0.0转换为0整数
        temp_list = [0] * 10                   #生成temp_list[] [0,0,0,0,0,0,0,0,0,0]
        temp_list[num_int] = 1                 #根据num_int整数对应数字,将temp_list[]0位置1, [1,0,0,0,0,0,0,0,0,0]
        label_list.append(temp_list)         #将temp_list[]添加至label_list,如此循环1000次,label_list[]就有label_list[0]-label_list[999]1000个形似temp_list[]的数据

    #通过image_list[0:990]和label_list[0:990]训练
    #刷新990次net.train中的input_list, target_list,输入层与隐藏层之间的权重weight_input_hidden,隐藏层与输出层之间的权重weight_hidden_output由随机逐渐变为有意义的权重
    for n in range(990):
        input_list = image_list[n]
        target_list = label_list[n]
        net.train(input_list, target_list)

    while True:
        num = int(input("输入990——999号码")) #假设num为998,当然0-989也可以输入看看,不过那是训练过的数据
        out_list = image_list[num]      #假设num为998,out_list为image_list[998],784个像素一维数据列表
        out = net.ask(out_list)         #假设num为998,将image_list[998]输入,输出out为形如[1,0,0,0,0,0,0,0,0,0]的矩阵
        print(mnist_labels_list [num])  #mnist里的对应位置的label数字
        print(label_list[num])          #训练时用的label,最后10个没有训练,只是打印出来看看
        print(out)                      #神经网络实际输出的10个数(科学计数法),最大的那个就是神经网络认为识别的数字(实际输出)

        plt.imshow(mnist_images_list[num], cmap='gray') #显示对应数据图形,手动关掉plt窗口可以输下一个数
        plt.show()
        plt.close()                     #不关闭窗口程序会停止

3、代码其实不多,除去注释空行不到100行,其中搭建类Network就占了大部分。运行结果如下:
注意关掉plt窗口就可以测试下一张图片
DIY全连接神经网络识别mnist数据集_第5张图片
上图识别结果见左侧红线,图像为mnist数据集第991张图片,mnist中label为4.0,训练的预期第五位是一(990-999数据未训练),实际输出结果第五位最大8.64*10^-1即0.864,第五位代表0-9的数字4,所以识别正确。测试中991-999全部识别正确。
我也找到一个训练过但仍然识别失败的,第100张
DIY全连接神经网络识别mnist数据集_第6张图片mnist数据集label为8.0,训练时的预期结果为[0,0,0,0,0,0,0,0,1,0],实际输出第二位最大,识别为数字1,识别错误

如果哪位读者有时间兴趣可以改一下代码试试正儿八经的把整个mnist数据集跑一遍,测试看看准确率。
看到这里点个赞吧!

你可能感兴趣的:(神经网络,神经网络,python,人工智能)