深度学习基础技术分析4:CNN(含代码分析)

1. 模型图示

图1 CNN 示例

2. 相关技术

技术4.1 卷积

在图像分析中,将每个像素点作为独立属性来处理是不合适的。卷积具有两个优点:

  1. 通过对一片区域的操作,获取局部特性。例如,使用 5*5 的卷积核,可以将这片区域的矩阵转换为一个实数值。
  2. 不同的区域共享卷积核。这样,所提取的属性具有相同的物理涵义,如边缘。同时,卷积比全连接的参数少很多。例如,一个 100 × 100 100 \times 100 100×100 的图像,如果用基础的神经网络模型进行全连接,两层之间的连线有 1 0 8 10^8 108 条,相应数量的参数;如果用单个 5 × 5 5 \times 5 5×5 的卷积核,只需要 25 个参数;即使用 100 个卷积核,参数的数量也只有 2500.

需要注意如下两点:

  1. 所有的卷积核,都是通过训练获得的。举例用那些卷积核,旨在对其物理意义进行强行解释,而不会直接应用于 CNN,否则效果很差.
  2. 需要一定量的卷积核,提取不同的特征。至于它们之间的任务调节,是神经网络自己的事情,这和基础模型的道理相同。

技术4.2 采样

.

3. 代码分析

源代码地址为: https://github.com/DeepCompute/cnn/. 这里仅描述我自己的理解.

3.1 数据

数据存放于 mnist/train.format 和 mnist/test.format. 每一行存储一个 28*28 图片, 其像素点为二值化 (0 或 1). 最后一列为决策,即该图片代表的是哪个数字 (0 到 9).

3.2 main 包

main包里面有两个类, 用于测试实际数据.

3.2.1 CNNMnist.java

用于测试 Mnist 数据.

runtrain()方法
// 构建网络层次结构
LayerBuilder builder = new LayerBuilder();
builder.addLayer(Layer.buildInputLayer(new Size(28, 28))); 
builder.addLayer(Layer.buildConvLayer(6, new Size(5, 5))); 
builder.addLayer(Layer.buildSampLayer(new Size(2, 2)));
builder.addLayer(Layer.buildConvLayer(12, new Size(5, 5))); 
builder.addLayer(Layer.buildSampLayer(new Size(2, 2))); 
builder.addLayer(Layer.buildOutputLayer(10));
CNN cnn = new CNN(builder, 10);

从第 3 行开始分析:
// 第 1 层: 输入层. 输入层输出map大小为 28 × 28 28 \times 28 28×28, 实际为图片点阵.
// 第 2 层: 卷积层. 单个卷积核大小为 5 × 5 5 \times 5 5×5, 共有6个卷积核, 导致6个map输出. 输出map大小为 24 × 24 24 \times 24 24×24, 24 = 28 + 1 − 5 24=28+1-5 24=28+15
// 第 3 层: 采样层. 每次处理 2 × 2 2 \times 2 2×2 大小的区域, 输出map大小为 12 × 12 12 \times 12 12×12, 12 = 24 / 2 12=24/2 12=24/2. 输出 map 个数与输入一致.
// 第 4 层: 卷积层. 单个卷积核大小为 5 × 5 5 \times 5 5×5, 6 6 6 个输入与 12 12 12 个输出之间用 6 × 12 = 72 6 \times 12 = 72 6×12=72 个卷积核连接. 使用了简单叠加了激活函数, 导致 12 12 12 个 map 输出. 输出 map 大小为 8 × 8 8 \times 8 8×8, 8 = 12 + 1 − 5 8=12+1-5 8=12+15
// 第 5 层: 采样层. 输出map大小为 4 × 4 4 \times 4 4×4, 4 = 8 / 2 4=8/2 4=8/2
// 第 6 层: 输出层. 输出端口 10 个,对应于 0 到 9 数字.

3.2.2 CNNVoice.java

用于测试声音数据,

3.3 core 包

用于实现核心代码.

3.3.1 Layer.java

该类主要用于定义一层的数据结构.

3.3.1.1 class Size

定义宽度和高度. 可用于 map, kernel, scale 等.

3.3.1.2 enum LayerType
enum LayerType {
    input, output, conv, samp
}

层类型. 为一个枚举类型.

3.3.1.3 kernel 变量
double[][][][] kernel;

第一维表示前一层 (输入) 的图编号;
第二维表示当前层 (输出) 的图编号;
后两维表示单个的kernel.
例如, 前一层的 outMapNum = 6, 当前层的 outMapNum = 12, kernel 的大小为 5 × 5 5 \times 5 5×5, 则本层 kernel 涉及 6 × 12 × 5 × 5 = 1800 6 \times 12 \times 5 \times 5 = 1800 6×12×5×5=1800 个参数. 这仅为平常神经网络的全连接 28 × 28 × 28 × 28 = 614656 28 \times 28 \times 28 \times 28 = 614656 28×28×28×28=614656 的1/340.

3.3.1.4 bias 变量
double[] bias;

与平常神经网络的偏移一致, 其个数与下一层的图数相等.

3.3.1.5 outmaps 变量
double[][][][] outmaps;

第一维表示批次, 与批量训练相关;
第二维表示 (输出) 图编号;
后两维表示单个的map, 如 24 × 24 24 \times 24 24×24.
批量训练的优点包括:

  1. 减缓抖动;
  2. 一批仅调整一次参数;
  3. 支持并行;
3.3.1.6 errors 变量

维度与 outmaps 变量一致. 即需要为每一个 (像素、属性) 点计算error.

3.3.1.7 buildInputLayer(Size mapSize)

输入层, 输出 map 数为 1.

3.3.1.8 buildConvLayer(int outMapNum, Size kernelSize)

第 1 个参数为输出图的个数, Size 指定的是卷积核大小.

3.3.1.9 buildSampLayer(Size scaleSize)

采样层指定了采样的Size.

3.3.1.10 buildOutputLayer(int classNum)

输出层获得的图个数为决策类的个数, 大小为 1 × 1 1 \times 1 1×1, 因此预测的时候,只需要获得其 [0][0] 位置的元素.

3.3.2 CNN.java

3.3.2.1 forward(Record)

前向运算. 根据各层相应方法.

3.3.2.2 setInLayerOutput(Record)

输入层操作. 将图片数据拷贝到输入层的图中. 对于单色图, 仅拷贝一个矩阵 (如 28 × 28 28 \times 28 28×28).

3.3.2.3 setConvOutput()

卷积计算.
包括:

  1. 对各个输入求卷积;
  2. 将各卷积累加;
  3. 使用激活函数 (需要先加上偏移 bias).
3.3.2.4 setSampOutput()

采样操作.
参见MathUtils.scaleMatrix()

3.2.2 backPropagation(Record)

反向传播.

3.2.2.1 setOutLayerErrors(Record)

根据当前记录的标签, 设置输出层的错误. 使用了 sigmoid 函数的偏导数. 与全连接网络中求激活函数的偏导一致.

3.2.2.2 setHiddenLayerErrors()

根据当前层的类型计算误差.

3.2.2.3 setSampErrors(Layer layer, Layer nextLayer)

将下一层各节点 (map) 的误差求全卷积 (180度旋转), 然后再相加,获得当前节点的误差.

3.2.2.4 setConvErrors(Layer layer, Layer nextLayer)

根据卷积的偏导数, 计算 m(1-m) 然后与扩展后的 error 点乘.

3.2.3 train(DataSet trainset, int repeat)

训练整个数据集, repeat 指重复次数.
每批随机抽样 batchSize 个元素进行训练, 训练完毕后进行参数调整.

3.2.3.1 updateParas()

仅对卷积层和输出层更新. 采样层没有参数可更新.

3.2.3.2 updateKernels(final Layer layer, final Layer lastLayer)

更新核. 对于同一批次的数据, 求得其更新值后平均.
并非原 kernel 与改变量相加, 而是 λ k 1 + α k 2 \lambda k_1 + \alpha k_2 λk1+αk2, 其中 k 1 k_1 k1 为原核, k 2 k_2 k2 为校正量.

3.4 data包

仅一个类.

3.4.1 DataSet.java

问题: 直接用 double 矩阵存储数据不好吗?
回答: 可能是为了可扩展性. 这个代码虽然不多, 但作者考虑的技术多.

class DataSet

load() 方法从文件中逐行读入记录, 每个记录用 Record 对象存放. 同时设置决策属性.

class Record

它是DataSet的内嵌类, 为public.
– 能直接使用 DataSet 类的 labelIndex 成员变量.
– 将条件属性值存为 double 数组, 而决策属性值存为 Double 对象.

3.5 utils 包

关于常用方法.

3.5.1 ConcurentRunner.java

用于多线程管理并行计算.

abstract static class TaskManager

具体的任务管理, process(start, end) 方法指定了输出图编号的下界、上界.

3.5.2 MathUtils.java

数学计算, 特别是矩阵计算.

3.5.2.1 convnValid(final double[][], double[][])

有效卷积. 第一个参数为图, 其大小如 28 × 28 28 \times 28 28×28, 第二个参数为核, 如 5 × 5 5 \times 5 5×5, 如果不扩展, 卷积后大小为 24 × 24 24 \times 24 24×24.

3.5.2.2 convnFull(final double[][], double[][])

全卷积. 令卷积核大小为 k × k k \times k k×k, 在四周都填充 k − 1 k - 1 k1 个 0, 卷积后大小为 m + k m + k m+k.

3.5.2.3 scaleMatrix(double[][], Size)

均值采样. 区域不重叠. 如 12 × 24 12 \times 24 12×24 采样 3 × 2 3 \times 2 3×2, 则结果矩阵大小为 4 × 12 4 \times 12 4×12.

3.5.2.4 interface Operator

自定义操作, 这样就可以把操作符写成对象了.

3.5.2.5 matrixOp(double[][], Operator)

支持自定义操作的矩阵运算.
由于 Operator 可以实例化, 对于不同的运算符, 就可以避免重载本方法.
在这个代码里常用到.

3.5.2.6 matrixOp(double[][], double[][], Operator, Operator, OperatorOnTwo)

先对两个矩阵作单变量的操作, 再对它俩做双变量操作.

3.5.2.7 kronecker(double[][], Size)

把矩阵每个元素按 Size 指定大小复制.

4. 小结

CNN的操作也不复杂。

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值