Python手写数字识别

神经网络是手写数字识别中常用的机器学习模型。它由许多神经元组成,每个神经元接收输入并生成输出。在前向传递过程中,神经元计算一些权重和偏移量的线性组合,并将其输入到一个非线性的激活函数中,从而生成神经元的输出。输出层通常使用softmax函数,将神经网络的输出映射到每个数字类别的概率。训练神经网络通常使用反向传播算法,该算法用于计算网络中每个权重和偏移量的梯度,并用梯度下降算法调整这些参数以最小化损失函数。在训练完成后,手写数字图像可以通过前向传递神经网络来进行分类。梯度下降法是寻找损失函数最小值的常用方法,包括批量梯度下降、随机梯度下降和小批量梯度下降。选择哪种梯度下降法取决于具体的算法和任务需求

本项目的源代码已放在文章末尾,如果它对您有帮助,请点个赞或收藏支持一下哦~。

神经网络结构

一个完整的神经网络通常由多个基本的网络层堆叠而成。本项目中的三层全连接神经网络由三个全连接层构成,在每两个全连接层之间插入 ReLU 激活函数以引入非线性变换,最后使用 Softmax 层计算交叉熵损失,如图所示。因此本项目中使用的基本单元包括全连接层、ReLU 激活函数、Softmax 损失函数
Python手写数字识别_第1张图片

MINIST数据集

MNIST数据集是一个手写数字图像数据集,由60,000个训练图像和10,000个测试图像组成。每个图像都是28x28像素的灰度图像,表示0到9之间的一个数字。该数据集常用于机器学习中的图像分类任务。

获取MNIST数据集的步骤如下:

  1. 访问MNIST官方网站:http://yann.lecun.com/exdb/mnist/
  2. 点击“Download”链接,下载四个压缩文件:train-images-idx3-ubyte.gz、train-labels-idx1-ubyte.gz、t10k-images-idx3-ubyte.gz和t10k-labels-idx1-ubyte.gz。
  3. 解压缩这四个文件,得到四个二进制文件。
  4. 使用Python等编程语言读取这四个文件,将其转换为图像和标签数据。可以使用第三方库,如numpy和scikit-learn,来读取和处理数据。

代码解读

mian.py

# coding=utf-8
import numpy as np
import struct
import os
import time
import matplotlib.pyplot as plt

from layers_1 import FullyConnectedLayer, ReLULayer, SoftmaxLossLayer

# 画图时的中文支持
plt.rcParams['font.sans-serif'] = ['SimHei']  # 用来正常显示中文标签
plt.rcParams['axes.unicode_minus'] = False  # 用来正常显示负号

# 定义了 MNIST 数据集的存储路径和文件名
MNIST_DIR = "./mnist_data"
TRAIN_DATA = "train-images-idx3-ubyte"
TRAIN_LABEL = "train-labels-idx1-ubyte"
TEST_DATA = "t10k-images-idx3-ubyte"
TEST_LABEL = "t10k-labels-idx1-ubyte"


# 显示矩阵的形状和均值和标准差
def show_matrix(mat, name):
    # print(name + str(mat.shape) + ' mean %f, std %f' % (mat.mean(), mat.std()))
    pass


# 该类包括多个函数,用于加载数据集、建立模型、初始化模型、训练和评估
# 定义一个类名为 MNIST_MLP,表示一个多层感知机(Multilayer Perceptron)模型
class MNIST_MLP(object):
    # 初始化函数,传入了多个参数用于初始化神经网络
    def __init__(self, batch_size=90, input_size=784, hidden1=32, hidden2=16, out_classes=10, lr=0.01, max_epoch=2,
                 print_iter=100):
        self.batch_size = batch_size  # 每次训练迭代所使用的数据批次大小
        self.input_size = input_size  # 输入层神经元数量(即MNIST图像大小)
        self.hidden1 = hidden1  # 第一层隐藏层神经元数量
        self.hidden2 = hidden2  # 第二层隐藏层神经元数量
        self.out_classes = out_classes  # 输出层神经元数量(即分类数量)
        self.lr = lr  # 学习率,用于控制模型训练时每次参数的更新幅度
        self.max_epoch = max_epoch  # 最大训练轮数
        self.print_iter = print_iter  # 训练过程中每隔print_iter次输出一次日志信息
        self.accuracy_list = []  # 存储每次迭代的准确率
        self.loss_list = []  # 存储每次迭代损失

    # 该函数用于读取 MNIST 数据集的图像和标记,并将其存储为 numpy 数组,其中 is_images 参数为 True 表示读取图像,为 False 表示读取标记
    # 定义一个函数来读取mnist数据集
    def load_mnist(self, file_dir, is_images='True'):
        # 打开二进制文件并读取其中的数据
        bin_file = open(file_dir, 'rb')
        bin_data = bin_file.read()
        bin_file.close()
        # 解析文件头
        if is_images:
            # 如果是图像数据集,读取图像格式的文件头
            fmt_header = '>iiii'
            magic, num_images, num_rows, num_cols = struct.unpack_from(fmt_header, bin_data, 0)
        else:
            # 如果是标签数据集,读取标签格式的文件头
            fmt_header = '>ii'
            magic, num_images = struct.unpack_from(fmt_header, bin_data, 0)
            num_rows, num_cols = 1, 1
        # 计算数据的大小
        data_size = num_images * num_rows * num_cols
        # 解析数据
        mat_data = struct.unpack_from('>' + str(data_size) + 'B', bin_data, struct.calcsize(fmt_header))
        # 将数据重塑为一个矩阵
        mat_data = np.reshape(mat_data, [num_images, num_rows * num_cols])
        # 打印读取数据的相关信息
        print('Load images from %s, number: %d, data shape: %s' % (file_dir, num_images, str(mat_data.shape)))
        # 返回读取到的数据
        return mat_data

    # 调用 load_mnist 函数读取和存储 MNIST 中训练数据和测试数据的图像和标记
    def load_data(self):
        # 调用函数 load_mnist 读取和预处理 MNIST 中训练数据和测试数据的图像和标记
        # print('Loading MNIST data from files...')
        print('加载 MNIST 数据集 ...')
        train_images = self.load_mnist(os.path.join(MNIST_DIR, TRAIN_DATA), True)
        train_labels = self.load_mnist(os.path.join(MNIST_DIR, TRAIN_LABEL), False)
        test_images = self.load_mnist(os.path.join(MNIST_DIR, TEST_DATA), True)
        test_labels = self.load_mnist(os.path.join(MNIST_DIR, TEST_LABEL), False)
        self.train_data = np.append(train_images, train_labels, axis=1)
        self.test_data = np.append(test_images, test_labels, axis=1)
        # print(train_images.shape)
        # print(train_labels.shape)
        # print(test_images.shape)
        # print(test_labels.shape)

    # 将训练数据集打乱,以便更好地训练
    def shuffle_data(self):
        print('随机混洗 MNIST 数据...')
        np.random.shuffle(self.train_data)

    # 建立多层感知机神经网络,并定义各层的数量及其之间的连接关系
    def build_model(self):  # 建立网络结构
        print('构建多层感知机模型...')
        self.fc1 = FullyConnectedLayer(self.input_size, self.hidden1)
        self.relu1 = ReLULayer()
        self.fc2 = FullyConnectedLayer(self.hidden1, self.hidden2)
        self.relu2 = ReLULayer()
        self.fc3 = FullyConnectedLayer(self.hidden2, self.out_classes)
        self.softmax = SoftmaxLossLayer()
        self.update_layer_list = [self.fc1, self.fc2, self.fc3]

    # 初始化神经网络参数
    def init_model(self):
        # print('Initializing parameters of each layer in MLP...')
        print('初始化各层参数权值...')

        for layer in self.update_layer_list:
            layer.init_param()

    # 从文件加载神经网络参数(即权重和偏差)
    def load_model(self, param_dir):  # 加载神经网络权值
        print('从文件加载参数权值:' + param_dir)
        params = np.load(param_dir, allow_pickle=True).item()
        self.fc1.load_param(params['w1'], params['b1'])
        self.fc2.load_param(params['w2'], params['b2'])
        self.fc3.load_param(params['w3'], params['b3'])

    # 将神经网络的参数(即权重和偏差)保存到文件中以备后续调用
    def save_model(self, param_dir):
        print('保存权值和偏置到: ' + param_dir)
        params = {}
        params['w1'], params['b1'] = self.fc1.save_param()
        params['w2'], params['b2'] = self.fc2.save_param()
        params['w3'], params['b3'] = self.fc3.save_param()
        np.save(param_dir, params)

    # 神经网络的前向传播,即将输入数据通过神经网络传递,得出预测结果
    def forward(self, input):  # 神经网络的前向传播
        h1 = self.fc1.forward(input)
        h1 = self.relu1.forward(h1)
        h2 = self.fc2.forward(h1)
        h2 = self.relu2.forward(h2)
        h3 = self.fc3.forward(h2)
        prob = self.softmax.forward(h3)
        return prob

    # 神经网络的反向传播,即求解梯度,调整神经网络参数
    def backward(self):  # 神经网络的反向传播
        # TODO:神经网络的反向传播
        dloss = self.softmax.backward()
        dh3 = self.fc3.backward(dloss)
        dh2 = self.relu2.backward(dh3)
        dh2 = self.fc2.backward(dh2)
        dh1 = self.relu1.backward(dh2)
        dh1 = self.fc1.backward(dh1)

    # 根据梯度和学习率更新神经网络参数
    def update(self, lr):  # 神经网络的参数更新
        for layer in self.update_layer_list:
            layer.update_param(lr)

    # 使用反向传播和梯度下降训练多层感知机神经网络,使其逐渐适应数据集
    def train(self):
        # 计算每个batch中的样本数
        max_batch = self.train_data.shape[0] // self.batch_size  # [0]表示数据集的第一维,也就是数据集中样本的数量
        print('开始训练...')
        # 对于每个epoch
        for idx_epoch in range(3):
            # 将训练数据集随机打乱
            self.shuffle_data()
            # 对于每个batch
            for idx_batch in range(max_batch):
                # 从训练数据中取出当前batch的图片和标签
                batch_images = self.train_data[idx_batch * self.batch_size:(idx_batch + 1) * self.batch_size, :-1]
                batch_labels = self.train_data[idx_batch * self.batch_size:(idx_batch + 1) * self.batch_size, -1]
                # 使用前向传播算法计算预测概率
                prob = self.forward(batch_images)
                # 使用Softmax交叉熵函数计算损失
                loss = self.softmax.get_loss(batch_labels)
                # 使用反向传播算法进行梯度下降更新模型参数
                self.backward()
                self.update(self.lr)
                # 如果当前batch的序号可以整除打印迭代步数的间隔,打印该batch的损失
                if idx_batch % self.print_iter == 0:
                    print('Epoch %d, iter %d, loss: %.6f' % (idx_epoch, idx_batch, loss))
                    #  计算当前batch的预测准确率并存储下来
                    pred_results = np.zeros([batch_labels.shape[0]])
                    for idx in range(batch_labels.shape[0] // self.batch_size):
                        # 从当前batch中取出样本进行预测
                        batch_images_temp = batch_images[idx * self.batch_size:(idx + 1) * self.batch_size, :]
                        prob = self.forward(batch_images_temp)
                        pred_labels = np.argmax(prob, axis=1)
                        pred_results[idx * self.batch_size:(idx + 1) * self.batch_size] = pred_labels
                    # 计算准确率
                    acc = np.mean(pred_results == batch_labels)
                    self.accuracy_list.append(acc)  # 存储当前batch的预测准确率
                    self.loss_list.append(loss)  # 存储当前batch的预测准确率

    # 在测试数据集上对神经网络进行推断,并计算分类准确率
    def evaluate(self):  # 推断函数
        pred_results = np.zeros([self.test_data.shape[0]])
        start_time = time.time()
        for idx in range(self.test_data.shape[0] // self.batch_size):
            batch_images = self.test_data[idx * self.batch_size:(idx + 1) * self.batch_size, :-1]
            prob = self.forward(batch_images)
            end = time.time()
            # print(prob.shape)
            pred_labels = np.argmax(prob, axis=1)
            pred_results[idx * self.batch_size:(idx + 1) * self.batch_size] = pred_labels
        print("测试总时间: %f" % (time.time() - start_time))
        accuracy = np.mean(pred_results == self.test_data[:, -1])
        print('测试集准确率: %f' % accuracy)

        plt.plot(range(len(self.accuracy_list)), self.accuracy_list)
        plt.xlabel('迭代次数')
        plt.ylabel('准确率')
        plt.title('训练过程中准确率变化')
        plt.show()

        plt.plot(range(len(self.loss_list)), self.loss_list)
        plt.xlabel('迭代次数')
        plt.ylabel('准确率')
        plt.title('训练过程中损失变化')
        plt.show()


# 创建 MNIST_MLP 类的实例,并对参数进行初始化、加载数据、建立模型、初始化模型、训练、保存模型、加载模型和评估的一系列操作
if __name__ == '__main__':
    h1, h2, e = 32, 16, 1
    mlp = MNIST_MLP(hidden1=h1, hidden2=h2, max_epoch=e)
    mlp.load_data()
    mlp.build_model()
    mlp.init_model()
    start_time = time.time()
    mlp.train()
    print("训练总时间: %f" % (time.time() - start_time))
    mlp.save_model('mlp-%d-%d-%depoch.npy' % (h1, h2, e))
    mlp.load_model('mlp-%d-%d-%depoch.npy' % (h1, h2, e))
    mlp.evaluate()

这是一个使用多层感知机(MLP)进行手写数字分类的Python代码。

  1. 定义常量:定义了 MNIST 数据集的存储路径和文件名。
  2. 函数 load_mnist:该函数用于读取 MNIST 数据集的图像和标记,并将其存储为 numpy 数组,其中 is_images 参数为 True 表示读取图像,为 False 表示读取标记。
  3. 函数 show_matrix:显示矩阵的形状和均值和标准差。
  4. 类 MNIST_MLP:该类包括多个函数,用于加载数据集、建立模型、初始化模型、训练和评估。
  5. 函数 load_data:调用 load_mnist 函数读取和存储 MNIST 中训练数据和测试数据的图像和标记。
  6. 函数 shuffle_data:将训练数据集打乱,以便更好地训练。
  7. 函数 build_model:建立多层感知机神经网络,并定义各层的数量及其之间的连接关系。
  8. 函数 init_model:初始化神经网络参数。
  9. 函数 load_model:从文件加载神经网络参数(即权重和偏差)。
  10. 函数 save_model:将神经网络的参数(即权重和偏差)保存到文件中以备后续调用。
  11. 函数 forward:神经网络的前向传播,即将输入数据通过神经网络传递,得出预测结果。
  12. 函数 backward:神经网络的反向传播,即求解梯度,调整神经网络参数。
  13. 函数 update:根据梯度和学习率更新神经网络参数。
  14. 函数 train:使用反向传播和梯度下降训练多层感知机神经网络,使其逐渐适应数据集。
  15. 函数 evaluate:在测试数据集上对神经网络进行推断,并计算分类准确率。
  16. main 函数中,创建 MNIST_MLP 类的实例,并对参数进行初始化、加载数据、建立模型、初始化模型、训练、保存模型、加载模型和评估的一系列操作。
# coding=utf-8    # 代码文件编码方式

import numpy as np   # 引入numpy库
import struct        # 引入struct库
import os            # 引入os库
import time          # 引入time库

# 实现了一个全连接层类,num_input是输入维度,num_output是输出维度
class FullyConnectedLayer(object):
    def __init__(self, num_input, num_output):
        self.num_input = num_input
        self.num_output = num_output
        print('\tFully connected layer with input %d, output %d.' % (self.num_input, self.num_output))

    # 初始化参数,std是初始化参数的标准差
    def init_param(self, std=0.01):
        self.weight = np.random.normal(loc=0.0, scale=std, size=(self.num_input, self.num_output))
        self.bias = np.zeros([1, self.num_output])

    # 前向传播计算, input是输入
    def forward(self, input):
        start_time = time.time()
        self.input = input
        # 全连接层的前向传播,计算输出结果
        self.output = np.dot(self.input, self.weight) + self.bias
        return self.output

    # 反向传播计算,top_diff是损失函数对输出的导数
    def backward(self, top_diff):
        # 计算参数梯度和本层损失
        self.d_weight = np.dot(self.input.T, top_diff)
        self.d_bias = np.sum(top_diff, axis=0, keepdims=True)
        bottom_diff = np.dot(top_diff, self.weight.T)
        return bottom_diff

    # 利用参数进行参数更新,lr是学习率
    def update_param(self, lr):
        self.weight -= lr * self.d_weight
        self.bias -= lr * self.d_bias

    # 加载参数,用于重新加载模型
    def load_param(self, weight, bias):
        assert self.weight.shape == weight.shape
        assert self.bias.shape == bias.shape
        self.weight = weight
        self.bias = bias

    # 用于保存参数,方便模型重新训练
    def save_param(self):
        return self.weight, self.bias

# 实现了一个ReLU激活函数层类
class ReLULayer(object):
    def __init__(self):
        print('\tReLU layer.')

    # 计算前向传播的输出结果
    def forward(self, input):
        start_time = time.time()
        self.input = input
        # ReLU层的前向传播,计算输出结果
        output = np.maximum(0, input)
        return output

    # 计算反向传播输出的损失结果
    def backward(self, top_diff):
        # ReLU层的反向传播,计算本层损失
        bottom_diff = top_diff.copy()
        bottom_diff[self.input<0] = 0
        return bottom_diff

# 实现了一个softmax激活函数层类
class SoftmaxLossLayer(object):
    def __init__(self):
        print('\tSoftmax loss layer.')

    # 计算前向传播的输出结果
    def forward(self, input):
        # softmax 损失层的前向传播,计算输出结果
        input_max = np.max(input, axis=1, keepdims=True)
        input_exp = np.exp(input - input_max)
        self.prob = input_exp / np.sum(input_exp, axis=1, keepdims=True)
        return self.prob

    # 计算损失
    def get_loss(self, label):
        self.batch_size = self.prob.shape[0]
        self.label_onehot = np.zeros_like(self.prob)
        self.label_onehot[np.arange(self.batch_size), label] = 1.0
        loss = -np.sum(np.log(self.prob) * self.label_onehot) / self.batch_size
        return loss

    # 计算反向传播输出的损失结果
    def backward(self):
        # softmax 损失层的反向传播,计算本层损失
        bottom_diff = (self.prob - self.label_onehot) / self.batch_size
        return bottom_diff


这段代码实现了一个简单的神经网络,包括全连接层、ReLU激活函数层和softmax损失函数层。其中,全连接层用于将输入数据进行线性变换,ReLU激活函数层用于增加网络的非线性能力,softmax损失函数层用于计算分类问题中的损失函数。代码中实现了前向传播和反向传播的计算,并且提供了参数初始化、参数更新、参数加载和保存等功能,方便模型的重新训练。

项目源码: https://gitee.com/TGY1817/handwritten-digit-recognition

你可能感兴趣的:(神经网络,感知机,python,深度学习,计算机视觉)