赞
踩
神经网络是手写数字识别中常用的机器学习模型。它由许多神经元组成,每个神经元接收输入并生成输出。在前向传递过程中,神经元计算一些权重和偏移量的线性组合,并将其输入到一个非线性的激活函数中,从而生成神经元的输出。输出层通常使用softmax函数,将神经网络的输出映射到每个数字类别的概率。训练神经网络通常使用反向传播算法,该算法用于计算网络中每个权重和偏移量的梯度,并用梯度下降算法调整这些参数以最小化损失函数。在训练完成后,手写数字图像可以通过前向传递神经网络来进行分类。梯度下降法是寻找损失函数最小值的常用方法,包括批量梯度下降、随机梯度下降和小批量梯度下降。选择哪种梯度下降法取决于具体的算法和任务需求
本项目的源代码已放在文章末尾,如果它对您有帮助,请点个赞或收藏支持一下哦~。
一个完整的神经网络通常由多个基本的网络层堆叠而成。本项目中的三层全连接神经网络由三个全连接层构成,在每两个全连接层之间插入 ReLU 激活函数以引入非线性变换,最后使用 Softmax 层计算交叉熵损失,如图所示。因此本项目中使用的基本单元包括全连接层、ReLU 激活函数、Softmax 损失函数
MNIST数据集是一个手写数字图像数据集,由60,000个训练图像和10,000个测试图像组成。每个图像都是28x28像素的灰度图像,表示0到9之间的一个数字。该数据集常用于机器学习中的图像分类任务。
获取MNIST数据集的步骤如下:
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代码。
# 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
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。