# 《深度学习入门》的cpp实现-ch03: 神经网络

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

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1660143565963/hahzd0040.png align="left")

### 链接方式

在神经元的链接方式上，神经网络和感知机并没有任何差异。二者的不同在于感知机中，流动的信号是 0 或 1 的二元信号，而神经网络中流动的是连续的实数值信号。

这是由于，感知集对输入信号的总和处理是使用阶跃函数(step function)，而对于神经网络，使用`sigmoid`、`ReLU`和 `softmax` 这种“平滑”的函数。我们把处理输入信号总和的函数称为 **激活函数**

## 激活函数
常见的激活函数分为一下几种
### sigmoid 函数
sigmoid函数定义为
$$ h (x) =  \frac{\mathrm{1} }{\mathrm{1} + e^{-x} }  $$ 

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1660133702749/UJTrH8refr.png align="left")

值域位于(0, 1)

cpp实现
```cpp
Eigen::MatrixXf sigmoid(Eigen::MatrixXf input) {
    return 1.0f / (1.0f + (input.array() * (-1.0)).exp());
}
```

### 阶跃函数

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1660133805852/ulYxuFvde.png align="left")

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

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1660134068432/Q4bzTJiKU.png align="left")

### softmax函数
softmax函数定义为

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1660134495870/hop5ohYK1.png align="left")
softmax 函数的分子是输入信号的指数函数，分母是所有输入信号的指数函数的和。
可以看出和其他激活函数（只受**当前**神经元权重求和的影响）不同，输出层的各个神经元都受到**所有**输入信号的影响。

#### 实现方式
实现softmax需要注意的是，对于e的指数是一个增长非常快的函数，exp(10) 的值 会超过 20000，exp(1000) 会变成一个后面有 40 多个 0 的超大值。 因此我们需要对softmax函数进行改进。

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1660143113816/UEM3BY0Wp.png align="left")

cpp实现
```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文件](https://github.com/qilingzhao/dlfs_cpp/blob/master/data/inference_param/ch03_mnist_weights.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
```

### 矩阵形状解释

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1660144412823/PNOH2BavL.png align="left")

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

![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1660144977120/FbXSb6w1T.png align="left")

可以把`rhs`的第`j`列理解为所有到下一层神经元的所有权重。

example: `rhs`的第`2` 列(2, 3)，是所有到下一层神经网路第`2`个节点的权重。


![image.png](https://cdn.hashnode.com/res/hashnode/image/upload/v1660145478928/GXZt5U9w2.png align="left")


## 代码实现

[这里](https://github.com/qilingzhao/dlfs_cpp/blob/master/src/ch03/inference.cpp)
