《深度学习入门》的cpp实现-ch03: 神经网络
激活函数、模型推理

神经网络的定义
层
我们把最左边的一列称为输入层,最右边的一列称为输出层,中间的一列称为中间层,也称为隐藏层。“隐藏”一词的意思是,隐藏层的神经元(和输入层、输出 层不同)肉眼看不见。

链接方式
在神经元的链接方式上,神经网络和感知机并没有任何差异。二者的不同在于感知机中,流动的信号是 0 或 1 的二元信号,而神经网络中流动的是连续的实数值信号。
这是由于,感知集对输入信号的总和处理是使用阶跃函数(step function),而对于神经网络,使用sigmoid、ReLU和 softmax 这种“平滑”的函数。我们把处理输入信号总和的函数称为 激活函数
激活函数
常见的激活函数分为一下几种
sigmoid 函数
sigmoid函数定义为 $$ h (x) = \frac{\mathrm{1} }{\mathrm{1} + e^{-x} } $$

值域位于(0, 1)
cpp实现
Eigen::MatrixXf sigmoid(Eigen::MatrixXf input) {
return 1.0f / (1.0f + (input.array() * (-1.0)).exp());
}
阶跃函数

ReLU函数
ReLU函数定义为 $$Relu(z) = max(0, z)$$

softmax函数
softmax函数定义为
softmax 函数的分子是输入信号的指数函数,分母是所有输入信号的指数函数的和。
可以看出和其他激活函数(只受当前神经元权重求和的影响)不同,输出层的各个神经元都受到所有输入信号的影响。
实现方式
实现softmax需要注意的是,对于e的指数是一个增长非常快的函数,exp(10) 的值 会超过 20000,exp(1000) 会变成一个后面有 40 多个 0 的超大值。 因此我们需要对softmax函数进行改进。

cpp实现
Eigen::MatrixXf softmax(Eigen::MatrixXf input) {
Eigen::Matrix<float, Eigen::Dynamic, Eigen::Dynamic> result;
result.resize(input.rows(), input.cols());
for (int i = 0; i < input.rows(); i++) {
float max = input.row(i).maxCoeff();
float sum = (input.row(i).array() - max).exp().sum();
for (int j = 0; j < input.cols(); j++) {
result(i, j) = exp(input(i, j) - max) / sum;
}
}
return result;
}
这里需要注意的是,即便使用了 softmax 函数,各个元素之间的大小关系也不会改变。这是因为指数函数(y = exp(x))是单调递增函数。
用途
推理阶段一般会省 略输出层的 softmax 函数。在输出层使用 softmax 函数是因为它和 神经网络的学习有关系。
恒等函数
$$y=x$$
输出层的激活函数
要根据求解问题的性质决定。一般地,回归问题可以使用恒等函数,二元分类问题可以使用 sigmoid函数, 多元分类问题可以使用 softmax 函数。
模型推理
mnist数据集
mnist 数据集是一个手写数字的数据集,我们这里需要加载的是测试图片和对应标签。测试数据集是一个10000*784的数据集,10000代表共有100张图片,784代表每张图片是28x28的像素的图片。
模型设计
对于手写数字识别的模型设计采用三层网络,除最后一层网络,激活函数均为sigmoid函数。
模型的参数,保存在 json文件 中。
各层的weight和bias如下
b1 rows: 1 cols: 50
b2 rows: 1 cols: 100
b3 rows: 1 cols: 10
w1 rows: 784 cols: 50
w2 rows: 50 cols: 100
w3 rows: 100 cols: 10
矩阵形状解释

对于矩阵相乘的lhs(即层上的数据),每一行可以看作一组数据输入,输入的个数就是输入矩阵的列数。对于rhs(权重数组), 每次取出一列,编号为j,和lhs的一行相乘求和,就可以得到下一层第j个神经元的权重。

可以把rhs的第j列理解为所有到下一层神经元的所有权重。
example: rhs的第2 列(2, 3),是所有到下一层神经网路第2个节点的权重。





