深度学习入门(一)手写数字识别
本次手写数字识别参考的是Michael Nielsen大佬写的 Neural Networks and Deep Learning 来实现的,由于本书的代码都是由 Python 2.0 编写而成,因此这次我打算使用 Python 3.9 来进行源代码的修改,之后再去实现手写数字识别
期望实现的目标
通过Python3.9实现手写数字识别的基本代码基于以上代码实现出一个较为精确的模型并产生可视化函数- 实现在每次返回后随机抽取9个图片显示,并在上方显示出识别出的数字结果
- 基于模型的基础上,做出一套可以通过识别自己笔迹,实时输出的手写数字识别系统
准备环境
MNIST数据库
MNIST 数据集可在 获取, 它包含了四个部分:
- Training set images: train-images-idx3-ubyte.gz (9.9 MB, 解压后 47 MB, 包含 60,000 个样本)
- Training set labels: train-labels-idx1-ubyte.gz (29 KB, 解压后 60 KB, 包含 60,000 个标签)
- Test set images: t10k-images-idx3-ubyte.gz (1.6 MB, 解压后 7.8 MB, 包含 10,000 个样本)
- Test set labels: t10k-labels-idx1-ubyte.gz (5KB, 解压后 10 KB, 包含 10,000 个标签)
和其他的手写数字识别一样,都是用 Training 中的 60000 个样本进行训练,再用 Test 中的 10000 个样本进行验证
Numpy 的 Python 库
如果你没有安装过 Anaconda ,那我强烈建议你去安装,因为真的是太方便了。
如果没有的话也可以在这里下载
神经网络搭建
神经网络代码的核心特征
1 | class Network(object): |
代码解释:
size列表
包含各层神经元。假如想要创建一个3层的神经网络,每层分别有2,3,1个神经元,则使用方法如下:
1 | net = Network([2,3,1]) |
np.random.randn()
是Numpy中的一员来⽣成均值为 0,标准差为 1 的高斯分布。这样就可以将我们所设置的神经网络对象,进行 w (权重),b (偏置) 进行随机初始化。
值得注意的是
- 这个核心代码,默认情况为,第一层神经元均为输入层,并且不对这些神经元进行任何的偏置处理。
net.weight[1]
是一个存储着链接二、三层神经元的权重。
定义S型函数
1 | def sigmoid(z): |
PS:注意,当输入z 是⼀个向量或者 Numpy 数组时,Numpy 自动地按元素应用sigmoid()
函数。
np.exp()
执行e^x^运算。
添加向前传播的行为方式
1 | def feedforward(self,a): |
目的:为每一层都应用
这个公式
np.dot()
函数是为了执行,线性代数中的矩阵乘法。
实现梯度下降学习的SGD方法
1 | def sgd(self, training_data, epochs, mini_batch_size, eta, test_data=None): |
定义参数:
- self:指代类实例本身。
- training_data:包含了训练数据及其对应标签的列表。
- epochs:表示要遍历整个训练数据集的次数。
- mini_batch_size:表示每次迭代中使用的小批量样本的大小。
- eta:学习效率,用于控制梯度下降的步长。
- test_data:可选参数,包含了测试数据及其对应标签的列表。
实现步骤:
- 如果提供了测试数据,则计算测试数据的数量
n_test
。 - 对于每个
epoch
,将训练数据随机打乱。 - 将打乱后的训练数据分割成小批量样本。
- 对于每个小批量样本,使用
self.update_mini_batch
方法更新神经网络的权重和偏置。 - 如果提供了测试数据,则在每个epoch结束时,使用
self.evaluate
方法计算神经网络在测试数据上的准确率,并打印输出。 - 如果没有提供测试数据,则在每个epoch结束时,输出”Epoch {j} complete”。
实现小批量样本的更新
1 | def update_mini_batch(self, mini_batch, eta): |
具体来说,该函数接收一个mini_batch
(一个由训练样本和对应的标签组成的元组列表)、一个学习率eta作为输入,然后计算出该小批次样本的梯度并更新权重和偏置。
该函数首先初始化了两个空列表nabla_b
和nabla_w
,用于存储偏置和权重的梯度。然后使用backprop
方法计算每个样本的偏导数,并将它们累加到nabla_b
和nabla_w
中。
接下来,该函数使用更新后的权重和偏置的公式,具体来说,对于每个权重和偏置,函数将其减去一个学习率(eta)乘以其对应的梯度(nabla_w和nabla_b),并除以mini_batch
的大小
反向传播 定义backprop()
是一种反向传播的算法,一种快速计算代价函数梯度的方法。在本函数中具体用来计算每个样本的偏置与权重的偏导数。
1 | def backprop(self, x, y): |
输入$x$ 和 $y$,并返回一个元组 (nabla_b, nabla_w)
,表示代价函数 C_x
的梯度。其中 nabla_b
和 nabla_w
是逐层存储的 Numpy 数组列表,与 self.biases
和 self.weights
类似。
首先,该函数进行前向传播计算,计算出每层的激活值(activations[])
和加权输入值(zs[])
,然后,函数计算输出层的误差(即 $\delta$),并使用误差反向传播算法来计算每层的误差。应用如上公式
对于每一层,误差计算需要使用前一层的误差来计算,并且在计算时还需要使用该层的加权输入值和激活函数的导数。最后,函数返回 nabla_b
和 nabla_w
,表示代价函数相对于每个偏置和权重的梯度。
神经网络评估
1 | def evaluate(self, test_data): |
np.argmax()
numpy.argmax(a, axis=None, out=None)
函数功能,返回最大值的索引。
若axis=1,表明按行比较,输出每行中最大值的索引,若axis=0,则输出每列中最大值的索引。
test_results = [(np.argmax(self.feedforward(x)), y) for (x, y) in test_data]
:将测试数据中的每个样本 x
通过神经网络前向传播得到预测值,使用 np.argmax()
函数获取预测值中最大值所对应的类别标签,并将其与真实标签 y
组成元组,存入列表 test_results
中。
return sum(int(x == y) for (x, y) in test_results)
:遍历 test_results
列表,如果预测值 x
与真实标签 y
相同,则将其转换为整数 1
,否则为整数 0
,最后将所有转换后的整数相加,得到正确分类的样本数,作为模型在测试数据上的准确率进行返回。
计算输出激活的偏导数向量
1 | def cost_derivative(self, output_activations, y): |
实现可视化函数
1 | def plot_accuracy(self, accuracy_list): |
导入MNIST数据库
将下列代码单独存放在一个.py文件中
1 | import pickle |
结果展示
小结
本次实现的手写识别有几点不足:
- 没能实现显示特征图和预测结果将其输出。(这点会在之后的学习中,进行增加)
- 没能实现手写及时输出。(是未来可以学习去实现的一个目标,会在之后的学习当中去完善)
出现以上几点问题的原因主要还是,学习的东西知之甚少,不能将一些功能写出,并为自己所用。
个人感觉现在的已有代码身上,对增加测试数据和保存有些麻烦,之后会在空闲时间,通过使用pytorch,重新再写一份手写数字识别,并争取能够实现本次没能实现的两个目标。