手写数字识别--神经网络实验

news2024/12/28 19:43:49

实验源码自取:

神经网络实验报告源码.zip - 蓝奏云

上深度学习的课程,老师布置了一个经典的实验报告,我做了好久才搞懂,所以把实验报告放到CSDN保存,自己忘了方便查阅,也为其他人提供借鉴

由于本人是小白,刚入门炼丹,有写地方搞不懂,实验报告有错误的在所难免,请及时指出错误的地方

前馈神经网络的设计

一、实验目标及要求

1.掌握python编程

2.掌握神经网络原理

3. 掌握numpy库的基本使用方法

4. 掌握pytorch的基本使用

前馈神经网络是学习神经网络的基础。本实验针对MNIST手写数字识别数据集,设计实现一个基本的前馈神经网络模型,要求如下:

1. 在PyCharm平台上,分别基于numpy库和PyTorch实现两个版本的模型。

2. 网络包含一个输入层、一个输出层,以及k个隐藏层(1≤k≤3 )。

3. 每个版本的项目文件夹里面有一个文件夹以及三个文件,文件夹名为data,存放MNIST数据集,三个文件为: main.py、network.py、 data_loader.py。main为主文件,通过运行main启动手写数字识别程序;network.py存放神经网络类定义及相关函数;data_loader.py存放负责读入数据的相关方法。

4. 原训练集重新划分为训练集(5万样本)、验证集(1万样本),原测试集(1万样本)作为测试集。

5. 模型中使用交叉熵代价函数和L2正则化项。

6. 在每一个epoch(假设为第i个epoch)中,用训练数据训练网络后,首先用验证集数据进行评估,假设验证集的历史最佳准确率为p ,本次epoch得到的准确率为pi ,如果pi>p ,则用测试集评估模型性能,并把p更新为pi ;否则不评估并且p不更新 ,进行第i+1个epoch的训练。

7. 假设学习率固定为0.01,通过实验,评估不同的隐藏层个数k,以及隐藏层神经元个数m对模型性能的影响,找到你认为最好的k和m。

二、实验过程(含错误调试)

1.基于numpy库的模型

打开并统计数据集,发现训练集有50000个,验证集有10000个,测试集有10000个

对训练集输入的28x28图像矩阵转成784x1的列形状,把目标转成长度为10的列向量

对测试集和验证集输入的28x28图像矩阵转成784x1的列形状,目标保持不变

把数据传进模型,进行训练和测试

根据实验要求,定义Network类,实现初始化网络、前向传播、反向传播,更新参数,验证模型,测试模型,继续迭代,寻找最优参数。

import random
import numpy as np

# https://zhuanlan.zhihu.com/p/148102828

class Network(object):
    def __init__(self, sizes):
        self.num_layers = len(sizes)
        self.sizes = sizes
        self.biases = [np.random.randn(y, 1) for y in sizes[1:]]  # randn,随机正态分布
        self.weights = [np.random.randn(y, x) for x, y in zip(sizes[:-1], sizes[1:])]
        self.lmbda = 0.1  # L2正则化参数

    def feedforward(self, a):
        for b, w in zip(self.biases, self.weights):
            a = sigmoid(np.dot(w, a) + b)
        return a

        # 重复训练epochs次,训练集mini_batch_size个一包,学习率步长
    def SGD(self, training_data, epochs, mini_batch_size, eta, val_data,test_data=None):
        n_test = len(test_data)
        n = len(training_data)
        n_val = len(val_data)
        best_accuracy = 0
        for j in range(epochs):  # 重复训练的次数
            random.shuffle(training_data)  # 随机打乱训练集顺序
            mini_batches = [
                training_data[k:k + mini_batch_size]  # 切分成每批10个一组
                for k in range(0, n, mini_batch_size)]

            for mini_batch in mini_batches:
                self.update_mini_batch(mini_batch, eta)  # TODO

            # 一次训练完毕
            val_accuracy = self.evaluate(val_data) / n_val  # 进行验证
            # if j==1:
            #     print(f"参数m={m}训练第二次时,验证集精度{val_accuracy * 100}% ")

            if val_accuracy > best_accuracy:
                best_accuracy = val_accuracy
                test_num = self.evaluate(test_data) # 进行测试

                print(f"迭代次数: {j + 1},验证集精度{best_accuracy * 100}% ,测试集预测准确率: {(test_num / n_test) * 100}%")

            else:
                print(f'迭代次数:{j + 1},验证精度比前面那个小了,不进行测试')
        # return val_accuracy

    def update_mini_batch(self, mini_batch, eta):
        m = len(mini_batch)
        x_matrix = np.zeros((784, m))
        y_matrix = np.zeros((10, m))
        for i in range(m):  # 初始化矩阵为输入的10个x,一次计算
            x_matrix[:, i] = mini_batch[i][0].flatten()  # 将多维数组转换为一维数组
            y_matrix[:, i] = mini_batch[i][1].flatten()
        self.backprop_matrix(x_matrix, y_matrix, m, eta)

    def backprop_matrix(self, x, y, m, eta):
        # 生成梯度矩阵,初始为全零
        nabla_b = [np.zeros(b.shape) for b in self.biases]
        nabla_w = [np.zeros(w.shape) for w in self.weights]
        # 前向传播
        activation = x
        activations = [x]  # 各层的激活值矩阵
        zs = []  # 各层的带权输入矩阵
        for w, b in zip(self.weights, self.biases):
            z = np.dot(w, activation) + b
            activation = sigmoid(z)
            zs.append(z)
            activations.append(activation)

        # 后向传播
        # 计算输出层误差, # 加上L2正则化
        delta = (self.cross_entropy_cost_derivative(activations[-1], y) +
                 self.L2_regularization(self.weights[-1], m)) * sigmoid_prime(zs[-1])
        # 计算输出层的偏置、权重梯度
        nabla_b[-1] = np.array([np.mean(delta, axis=1)]).transpose()
        nabla_w[-1] = (np.dot(delta, activations[-2].transpose()) / m)
        # 反向传播误差,并计算梯度
        for l in range(2, self.num_layers):
            z = zs[-l]
            sp = sigmoid_prime(z)
            delta = np.dot(self.weights[-l + 1].transpose(), delta) * sp
            nabla_b[-l] = np.array([np.mean(delta, axis=1)]).transpose()
            nabla_w[-l] = np.dot(delta, activations[-l - 1].transpose()) / m
        for l in range(1, self.num_layers):
            self.biases[-l] = self.biases[-l] - eta * nabla_b[-l]
            self.weights[-l] = self.weights[-l] - eta * nabla_w[-l]

    def evaluate(self, test_data):
        test_results = [(np.argmax(self.feedforward(x)), y)  # argmax()找到数组中最大值的索引
                        for (x, y) in test_data]
        return sum(int(x == y) for (x, y) in test_results)

    def L2_regularization(self, weights, m):
        return (self.lmbda / (m * 2)) * np.sum(np.square(weights))

    def cross_entropy_cost_derivative(self, a, y):
        # a是预测值矩阵,y是真实值矩阵
        epsilon = 1e-7
        a = a + epsilon  # 防止除0错误
        dc = -y / a  # 交叉熵代价函数的导数
        return dc


def sigmoid(z):
    if np.all(z >= 0):  # 对sigmoid函数优化,避免出现极大的数据溢出
        return 1.0 / (1.0 + np.exp(-z))
    else:
        return np.exp(z) / (1 + np.exp(z))

# 求导sigmoid函数
def sigmoid_prime(z):
    return sigmoid(z) * (1 - sigmoid(z))


# def cross_entropy_cost(a, y):
#     # a是预测值矩阵,y是真实值矩阵
#     n = a.shape[1]  # 样本数量
#     return -np.sum(y * np.log(a)) / n  # 交叉熵代价函数
#
#
# def relu(z):
#     return np.maximum(0, z)
#
#
# def relu_prime(z):
#     # return np.array(x > 0, dtype=x.dtype)
#     return (z > 0).astype(int)  # relu函数的导数

# # 交叉熵代价函数和L2正则化项  -(self.lmbda / m) * self.weights[-1]  # 加入L2正则化项
#
# def cost_function(output_activations, y):
#     return np.sum(np.nan_to_num(-y * np.log(output_activations) - (1 - y) * np.log(1 - output_activations)))
# def L2_regularization(self, lmbda, weights):
#     return lmbda * np.sum(np.square(weights)) / 2.0
#
# def cost_function_with_regularization(output_activations, y, weights, lmbda):
#     return cost_function(output_activations, y) + L2_regularization(lmbda, weights)

在交叉熵代价函数求导后加上L2正则化,先衰减偏置,再衰减权重,防止过拟合

# 后向传播
# 计算输出层误差, # 加上L2正则化
delta = (self.cross_entropy_cost_derivative(activations[-1], y) + 
         self.L2_regularization(self.weights[-1], m)) * sigmoid_prime(zs[-1])
# 计算输出层的偏置、权重梯度
nabla_b[-1] = np.array([np.mean(delta, axis=1)]).transpose()
nabla_w[-1] = (np.dot(delta, activations[-2].transpose()) / m)

在大范围的改变学习率时,运行报错,

RuntimeWarning: overflow encountered in exp return 1.0 / (1 + np.exp(-x))

参照网上的做法,对sigmoid函数的x做判断,if np.all(x>=0): #对sigmoid函数优化,避免出现极大的数据溢出

        return 1.0 / (1 + np.exp(-x))

    else:

        return np.exp(x)/(1+np.exp(x))

写交叉熵代价函数的导数时出现除0错误,于是对a加上很小的数

def cross_entropy_cost_derivative(self, a, y):
    # a是预测值矩阵,y是真实值矩阵
    epsilon = 1e-7
    a = a + epsilon  # 防止除0错误
    dc = -y / a  # 交叉熵代价函数的导数
    return dc

2.基于PyTorch模型

打开并统计数据集,发现训练集有50000个,验证集有10000个,测试集有10000个

对数据集的图像矩阵转成浮点型的张量,对目标转成长整型的张量

把图像张量和目标张量一一对应放到 TensorDataset()函数转成数据对象,然后调用DataLoader()函数分批打包数据,生成迭代器对象

把数据传进模型,进行训练和测试

定义Net类,继承Model类,实现初始化网络、定义网络每一层的对象放入列表,前向传播、把输入传进每一层对象、最后一层调用log_softmax()函数进行归一化; 输入训练数据进行前向传播,把结果放进交叉熵损失函数、后向传播计算梯度,更新参数,验证模型,测试模型,继续迭代,寻找最优参数

处理数据集的时候把图像和目标都转成浮点张量torch.Tensor(),然后报错,网上找原因,需要把目标转成 长整型的张量,因为使用交叉熵损失函数进行训练时,需要将标签转换为整数编码。y=LongTensor(y),以便进行后续的计算,而模型中并没有使用y=LongTensor(y)函数,则需要提前将目标转成长整型张量

train_images, train_labels = torch.tensor(tr_d[0], dtype=torch.float32), torch.tensor(tr_d[1], dtype=torch.long)

在处理数据集的时候,直接把张量放到DataLoader()里,然后报错了,查了书本后发现DataLoader()要传入dataset对象,需要把张量对应传入TensorDataset()函数生成dataset对象

train_dataset = TensorDataset(train_images, train_labels)

 

  • 三、对实验中参数和结果的分析

1.基于numpy库的模型

当使用默认参数:sizes=[784,30,10] ,epochs=30,mini_batch_size=10,

Lmbda=0.1 ,eta=1.3

测试最高精度是94.3%

运行时间79s

根据实验要求,分析不同隐藏层个数k和隐藏层神经元个数m对模型性能的影响:

由于我的电脑不太行,训练不同隐藏层个数k和隐藏层神经元个数m对模型性能的影响的时候需要很多时间,因此只训练2次出结果,结果可能会有偶然性。

当epochs=2,mini_batch_size=10,

Lmbda=0.1 ,eta=0.01,当只用一层隐藏层时,测试不同的神经元m对模型精度的影响,代码和结果如下

# 当隐藏层k=1时
accuracy_list=[]
for m in range(10,784):
    net = Network([784,m, 10])
    accuracy=net.SGD(training_data, 2, 10, 0.01, validation_data,m,test_data=test_data)
    accuracy_list.append(accuracy)

x = range(10, 784)
max_acc=max(accuracy_list)
max_index = accuracy_list.index(max_acc)
print(f"当神经元m={10+max_index} 时,验证精度最大值为:{max_acc}")
plt.plot(x, accuracy_list)
plt.xlabel('m')
plt.ylabel('val_accuracy')
plt.show()

当运行到m=297 时,就卡住了,因此只讨论10到297个神经元的结果:

当只用一层隐藏层,神经元个数为138时,验证集精度最高,为77.92%

当用两层隐藏层时,测试不同的神经元m对模型精度的影响,由于组合次数太多,不可能每个神经元都训练,因此固定第一个隐藏层为138,神经元个数逐层递减进行训练,代码和结果如下

......

当有两层隐藏层,[784,138,m,10],神经元个数为124时,验证集精度最高,为77.38%

当用三层隐藏层时,测试不同的神经元m对模型精度的影响,由于组合次数太多,不可能每个神经元都训练,因此固定第一个隐藏层为138,神经元个数逐层递减进行训练,代码和结果如下

........

当有三层隐藏层,[784,138,124,m,10],神经元个数为118时,验证集精度最高,为76.18%

根据以上结果,当学习率固定为0.01时,我认为模型最好的k是1 ,m是138,精度77.92%,因为增加层数后验证集精度并没有多大提升,反而浪费了时间, 有点奇怪,也可能是模型有点问题

            

学习率固定为0.01,可能学习率小,步长小,迭代30次精度才77%左右,当学习率为默认的1.3时,精度才快速到94%

2.基于PyTorch模型

当使用默认参数:layers=[784,30,10] ,epochs=30,mini_batch_size=10,

,lr=0.01weight_decay=0.0001时

测试最高精度是93.17%

运行时间171s

根据实验要求,分析不同隐藏层个数k和隐藏层神经元个数m对模型性能的影响:

由于我的电脑不太行,训练不同隐藏层个数k和隐藏层神经元个数m对模型性能的影响的时候需要很多时间,因此只训练2次出结果,结果可能会有偶然性。

当epochs=2,mini_batch_size=10,

lr=0.01,weight_decay=0.0001时,当只用一层隐藏层时,测试不同的神经元m对模型精度的影响,代码和结果如下

# 当隐藏层k=1时
accuracy_list=[]
for m in range(10,298):
    net = Net([784,m, 10])
    accuracy=train_and_test_net(net,training_data,validation_data,test_data,2,0.01,0.0001,m)
    accuracy_list.append(accuracy)

x = range(10,298)
max_acc=max(accuracy_list)
max_index = accuracy_list.index(max_acc)
print(f"当神经元m={10+max_index} 时,验证精度最大值为:{max_acc}")
plt.plot(x, accuracy_list)
plt.xlabel('m')
plt.ylabel('val_accuracy')
plt.show()

结果:当只用一层隐藏层,神经元个数为247时,验证集精度最高,为93.51%

当用两层隐藏层时,测试不同的神经元m对模型精度的影响,由于组合次数太多,不可能每个神经元都训练,因此固定第一个隐藏层为138,神经元个数逐层递减进行训练,代码和结果如下

......

结果: 当有两层隐藏层,[784,247,m,10],神经元个数为19时,验证集精度最高,为93.34%

当用三层隐藏层时,测试不同的神经元m对模型精度的影响,由于组合次数太多,不可能每个神经元都训练,因此固定第一个隐藏层为138,神经元个数逐层递减进行训练,代码和结果如下

.......

结果:当有三层隐藏层,[784,247,19,m,10],神经元个数为14时,验证集精度最高,为83.91%

根据以上结果,当学习率固定为0.01时,我认为模型最好的k是1 ,m是247,精度93.51%,因为增加层数后验证集精度并没有多大提升,反而浪费了时间和内存

            

四、两个模型的对比

基于numpy库的模型:

当使用默认参数:sizes=[784,30,10] ,epochs=30,mini_batch_size=10,

Lmbda=0.1 ,eta=1.3时

迭代30次后测试集最高精度是94.3%

运行时间79s

基于PyTorch模型

当使用默认参数:layers=[784,30,10] ,epochs=30,mini_batch_size=10,

,lr=0.01,weight_decay=0.0001时

测试最高精度是93.17%

运行时间171s

由此可见基于numpy库训练的模型比基于PyTorch的模型好,测试精度高,用的时间也少

基于numpy库的模型比较偏向底层,实现难,代码复杂,但运行速度快;基于PyTorch模型实现方式比较简单,代码比较简洁,但底层比较复杂,运行速度慢

训练神经网络模型首选PyTorch框架

五、总结

学会了很多,收获了很多,普通电脑只能训练小模型,以后遇到中模型肯定要用各大云平台的算力进行训练。

........................

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1161756.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

2023年11月1日,Google全新域名来袭:.ing域名现已问世!

2023年11月1日(Oct31,2023美国与中国时差)Google宣布,正式推出.ing域名,这是一种新的顶级域名,旨在为用户提供更多的选择和创意。.ing域名是由Google和国际互联网名称与数字地址分配机构(ICANN)合作开发的,…

安全与HTTP协议:为何明文传输数据成为争议焦点?

🎬 江城开朗的豌豆:个人主页 🔥 个人专栏 :《 VUE 》 《 javaScript 》 📝 个人网站 :《 江城开朗的豌豆🫛 》 ⛺️ 生活的理想,就是为了理想的生活 ! 目录 ⭐ 专栏简介 📘 文章引言 一、H…

自动驾驶算法(二):A*算法讲解与代码实现

目录 1 A* 算法提出的动机 2 A*算法代码详解 3 A*算法完整代码 1 A* 算法提出的动机 减少收录的珊格树目,增加搜索速度。在Dijkstra算法中,我们考虑收录栅格时我们考虑的是到起点的距离,我们会考虑收录距离起点较近的珊格进行收录。在A*算法…

关于打包css找不到报错

背景&#xff1a;最近公司产品升级为v3项目&#xff0c;打包总是报css缺失 问题&#xff1a;报错提示如下&#xff0c;找不到css 出现原因分析&#xff1a;由于项目比较老&#xff0c;很多人写的&#xff0c;代码风格不统一导致的&#xff0c;比如父组件A使用<style><…

日志数据库写入数据频繁内存占用越来越大

一直写数据&#xff0c;内存越来越高。查看内存占用命令&#xff1a;htop 解决方式&#xff1a;日志数据库的模式改为【简单】 完整会把你的所有操作记录早日志文件&#xff0c;简单就不会

虾总管:16年磨一剑,引领餐饮新现象级潮流

在繁忙的都市生活中&#xff0c;我们常常追求一种简单而美好的生活。美食&#xff0c;则是让我们忘却烦恼&#xff0c;回归初心的一种方式。在河北承德&#xff0c;就有这样一家美食品牌虾总管&#xff0c;用一份鸭虾锅带给无数人美好和喜悦。 16年始终如一&#xff0c;初心不改…

teambition迁移云效

由于TB(行云)停止运营了&#xff0c;可惜了&#xff0c;非常好用的一个工具&#xff0c;项目管理&#xff0c;代码管理&#xff0c;自动化构建等&#xff0c;都支持。现需要切换到云效(https://codeup.aliyun.com/)。这个工作量确实挺大的&#xff0c;像我有N个公司*N个项目的&…

win10 vs2015 构建xp适配

整理中&#xff1a; LIB_CPPFLAGS-D"_WIN32_WINNT0x502"

跨模块边界分配和释放内存

我想&#xff0c;有一条编程铁律已经深深的刻入到你的头脑中了&#xff1a;使用成套的函数来分配和释放内存&#xff0c;例如&#xff0c;如果使用 LocalAlloc 分配内存&#xff0c;则应该使用 LocalFree&#xff0c;类似的例子还有&#xff1a;GlobalAlloc 对应 GlobalFree&am…

手把手教你使用Vue2.0实现动态路由展示

文章目录 VUE2相关组件版本原始菜单数据数据库数据树形数据 前端项目配置静态路由配置路由守卫左侧路由回显代码 使用Vue2实现动态路由展示 思路&#xff1a; 后端返回树形数据根据数据渲染左侧菜单根据数据组装路由数据 注意&#xff1a;本文主要目的讲解是一种思路&#xff0…

47GB水经微图从入门到精通视频教程

本视频教程共47GB&#xff0c;为了方便大家观看&#xff0c;同时录制了横版视频教程和竖版视频教程。 本视频教程的内容主要包括快速入门、地图标注、影像下载、高程下载和矢量下载几部分。 本文将列出所有视频教程所有内容。 快速入门&#xff08;23.1GB&#xff09; 如何…

串口实用解说

我们学习单片机&#xff0c;首先接触的可能是点灯&#xff08;GPIO&#xff09;&#xff0c;再次就是串口&#xff08;UART&#xff09;。 串口是常用的一种通信接口&#xff0c;也是学嵌入式必备掌握的一项知识&#xff0c;但我发现有很多小伙伴只知道用串口输出或者打印一些数…

《windows核心编程》第4章 进程

一、进程基本概念 1、进程&#xff1a;一个进程就是一个正在运行的程序&#xff0c;一个程序可以产生多个进程。进程包含下面两个东西 ● 进程内核对象&#xff1a;一个内核对象被系统用来管理某个进程&#xff0c;内核对象就是代表这个进程。这个内核对象中&#xff0c;还包…

CTS分析思路

目录 原理简介&#xff1a; Cts测试原理&#xff1a; CTS报告与日志目录 CTS报告目录如下​编辑 log查看 举例 原理简介&#xff1a; Cts环境搭建和测试方法&#xff0c;大家可以自行查询网上资料。 Cts测试原理&#xff1a; 输入命令后&#xff0c;会安装一系列的测试…

短视频账号矩阵系统/剪辑/矩阵/无人直播/文案引流爆款

一、 短视频账号矩阵源码开发包含哪几方面&#xff1f; 1. 界面设计&#xff1a;需要根据用户需求&#xff0c;设计出优美简洁的UI界面&#xff0c;使用户可以方便快捷地管理自己的短视频账号。 2. 数据存储&#xff1a;需要将用户的账号信息、数据统计等信息存储在数据库中&a…

pytorch dropout 置零 + 补偿性放缩

一句话概括&#xff1a;&#xff08;训练过程中&#xff09;Dropout 操作 随机置零 非置零元素进行后补偿性放缩。以保证dropout前后数据scale不变。 详细解释(来自chatgpt): 在 PyTorch 中&#xff0c;dropout 的操作不仅仅是将某些元素置零。为了确保期望输出在训练和测试…

WoShop跨境电商商城源码(多语言多货币多商户进出口电商平台)

一、跨境电商商城系统源码包括以下几个部分 前端框架&#xff1a;uni-app,vue 后端框架&#xff1a;ThinkPHP5.wokerman 支付系统&#xff1a;PayPal、USDT等主流支付平台 语言包&#xff1a;跨境电商支持15种语言&#xff0c;后续会增加 前端&#xff1a;包含APP端、小程序端、…

无需数据搬迁,10倍性能提升!携程的统一分析之旅

作者&#xff1a;携程技术中心大数据总监 许鹏 携程自 2022 年起引入了 StarRocks&#xff0c;目前已经成为了集团内部的主要技术栈&#xff0c;应用到酒店、机票、商旅、度假、市场、火车票等多个关键业务线。目前&#xff0c;携程内部已经拥有超过 10 个 StarRocks 集群&…

c语言函数宏的几种封装方式

c语言函数宏的几种封装方式 在c语言开发中&#xff0c;除了使用函数封装代码之外&#xff0c;也经常使用宏来封装一些重要或简洁的代码。 宏在c开发有三种&#xff1a;预定义宏&#xff0c;不带参宏&#xff0c;和带参数宏&#xff0c;通常&#xff0c;带参数宏也叫函数宏&am…

HR应用人才测评来提升人才价值

对于企业而言&#xff0c;产出价值是最重要的&#xff0c;但企业拥有人才&#xff0c;才能产出更多的价值&#xff0c;做HR这么久发现很多企业&#xff0c;都欠缺人才管理的测评技术&#xff0c;导致很多人才被埋没或者浪费&#xff0c;这也说明一个很大的问题&#xff0c;一定…