Java学习-Day37
前言
本文代码来自 CSDN文章: 日撸 Java 三百行(81-90天,CNN 卷积神经网络)
我将借用这部分代码对 CNN 进行一个更深层次的理解.
卷积神经网络 (代码篇)
一、数据集读取与存储
1. 数据集描述
简要描述一下我们需要读取的数据集.
1 |
|
乍一看这不就是由 0 和 1组成的集合吗? 这个时候我们对这些数字想象成一个图片, 然后通过一些工具就可以呈现出下面的这样一副图片.
这张图片的大小就为 \(28 \times 28\), 那这堆数据最后不是多出了一个数字吗? 这个数字要表达什么意思呢? 这个时候仔细观察图片, 它是不是看起来像数字 '0'. 为了检验这个想法是否正确, 我们再找一行数据.
1 |
|
虽然图中的数字写法不标准, 但是隐约中还是能判别为数字 '3', 然后多出的那个数字正好是 '3'. 由此得出结论, 数据集的每一行代表一张图片, 由 '0' '1' 表示其黑白像素点, 且该行最后一个数字表示图片中数字的值.
所以对于这个数据集数据的读取就是把图片的像素点以数组方式存储, 数组的大小就是图片的大小. 然后用一个单独的值存储图片中所表示的数字, 把这个就作为图片的标签.
2. 具体代码
1 |
|
3. 运行截图
二、卷积核大小的基本操作
1. 操作
对卷积核大小进行处理, 也就是对卷积核的长和宽进行处理.
一个方法是长和宽同时除以两个整数, 要是不能被整除就抛出错误. 例如:
1
2(4, 12) / (2, 3) -> (2, 4)
(2, 2) / (4, 6) -> Error
另一个方法是长和宽同时减去两个整数, 然后再加上 1. 例如:
1
(4, 6) - (2, 2) + 1 -> (3,5)
2. 具体代码
1 |
|
3. 运行截图
三、数学工具类
1. 工具函数
定义了一个算子, 其主要目的是为了矩阵操作时对每个元素都做一遍. 有对单个矩阵进行运算, 例如用 1 减去矩阵中的值, 或者对矩阵中的值使用 \(Sigmoid\) 函数. 有对两个矩阵进行运算, 例如两个矩阵之间的加法还有减法.
矩阵旋转 180 度, 其实就是旋转两次 90 度. 旋转 90 度的公式为 \[ matrix[row][col] \overset{rotate}{=}matrix_{new}[col][n - row - 1] \]
convnValid 是卷积操作. convnFull 为其逆向操作.
scaleMatrix 是均值池化. kronecker 是池化的逆向操作.
2. 具体代码
1 |
|
这里定义了一个枚举类用来标识每一层的属性, 比如输入层, 卷积层等.
1
2
3
4
5
6
7
8
9
10package cnn;
/**
* Enumerate all layer types.
*
* @author Shi-Huai Wen Email: shihuaiwen@outlook.com.
*/
public enum LayerTypeEnum {
INPUT, CONVOLUTION, SAMPLING, OUTPUT;
} //Of enum LayerTypeEnum
四、网络结构与参数
对单层设置一些工具类的函数, 然后就是通过上面的枚举类型 LayerTypeEnum 来区别神经网络中不同的层, 例如输入层、卷积层和池化层.
1 |
|
在 CnnLayer 类上再封装一层, 用于更加简便地创建神经网络中的各层.
1 |
|
五、神经网络的搭建
1. 正向传播
正向传播的基本内容在之前已经提到了, 这里简述一下.
一张图片通过卷积核得到特征图, 然后特征图通过自己选择的池化层进行池化, 最后使用激活函数对池化层进行激活, 并把激活后的输出做为下一个卷积层的输入.
在重复卷积、池化、激活后进入全连接层. 全连接层中也有一个卷积过程, 他是把 \(m \times n\) 的特征图转换为 \(1 \times n\) 的向量, 然后这个向量通过 \(Softmax\) 函数进行处理并归一化. 这时候这个向量中最大值的下标就表示是最有可能的类别的下标.
2. 反向传播
反向传播这是一个老生常谈的问题了, 因为开始的卷积核是随机的, 所以就需要利用损失函数找到最佳的卷积核.
反向传播最开始更新的是全连接层, 它的反向传播和 ANN 网络中类似, 就是更新其中的权值.
然后就是池化层, 池化层的权值更新是最简单的. 以最大池化举例, 假设池化后的值为 6, 反向传播得到的误差为 +1, 反向传播回去得到池化前的值就是 \(6 + 1 = 7\).
最麻烦的就是卷积层, 其中的公式推导我还是没有太弄清楚. 大致理解就是从二维出发得到了一个二维的公式, 然后将二维推广到神经网络中的多维.
知乎文章: 卷积神经网络(CNN)反向传播算法推导 有详细的推导和解释.
4. 具体代码
1 |
|
5. 运行截图
总结
卷积神经网络理解起来容易, 但是实际编写一个框架对我来说就是非常痛苦且困难的事情.
首先是反向传播时数学公式的推导, 知道梯度下降和矩阵求导, 这些也仅仅是在单一的练习题中完成, 当实际运用时就找不到门路.
再者是代码的编写, 不管是数学的工具类还是矩阵的工具类, 尤其是矩阵旋转那部分刚开始看完全不理解.
要是我们只需要利用公式去编写代码, 想必这些问题就会迎刃而解. 偏偏天不遂人愿, 这些都是需要我真真实实去感受、去推导、去实现的.
路漫漫其修远兮, 吾将上下而求索.