From Neural Network to AI

From Neural Network to AI

神经网络的基本结构

普通的函数的映射: x->y;

人类可以完成的映射但是机器无法很快完成的映射: 猫的图片->猫

有一组数据 (1, 8), (2, 13), (3, 18), (4, 23), 可以轻易得到映射关系: y = 5x + 3; 这是严格相等的很好找的, 然而有一些数据无法找到精确的映射, 比如:

(0.7, 8.1), (2.3, 12.9), (3.1, 18.4), (3.9, 23.5) , 这时, 可以找到一个近似相等的映射 y = 5x + 3;虽然不是完全相等, 但是可以看成近似相等.

上面讲的是线性函数f(x) = wx + b, 然而实际上的函数大多数是非线性的, 如何得到非线性的函数? 很简单, 在线性函数外层套一层非线性运算即可. 比如平方, sin, 取指函数… 这些函数统称为激活函数, 目的是将线性函数变换为非线性函数f(x) = g(wx + b).

上面讲的是单变量的函数, 多变量函数对应的结构是:

f(x) = g(w1x1 + w2x2 + ... + wnxn + b)

很多情况下, 只套一层激活函数是没有办法得到我们想要的曲线的, 这个时候, 我们可以把上面的非线性函数再进行一次线性变换, 再套一层激活函数

f(x) = g2(w3g1(w1x1 + w2x2 +b1) + b2)

f(x) = g4(w3(g2(w3g1(w1x1 + w2x2 +b1) + b2) + b3))

…可以无限变化下去, 理论上可以逼近任意的连续函数

当变换的层数太多的时候, 看起来很复杂, 我们抽象出输入层输出层

比如 f(x) = g(w1x1 + w2x2 + b), 他的输入层可以看作有两个输入神经元x1, x2; 输出层一个输出神经元y.

graph LR
    subgraph Input_Layer [Input Layer]
        direction TB
        x1((x1))
        x2((x2))
    end

    subgraph Output_Layer [Output Layer]
        y((y))
    end

    x1 ==> y
    x2 ==> y

    style Input_Layer fill:#f9f9f9,stroke:#333,stroke-width:2px
    style Output_Layer fill:#e1f5fe,stroke:#0277bd,stroke-width:2px
    style x1 fill:#fff,stroke:#333,stroke-width:2px
    style x2 fill:#fff,stroke:#333,stroke-width:2px
    style y fill:#fff,stroke:#0277bd,stroke-width:3px

如果再叠加一层变换: f(x) = g2(w3(g1(w1x1 + w2x2) + b1) + b2)

graph LR
    subgraph Input_Layer [Input Layer]
        direction TB
        x1((x1))
        x2((x2))
    end

    subgraph Hidden_Layer [Hidden Layer]
        y1((y1))
    end
    
    subgraph Output_Layer [Output Layer]
        y2((y2))
    end

    x1 ==> y1
    x2 ==> y1
    y1 ==> y2

    style Input_Layer fill:#f9f9f9,stroke:#333,stroke-width:2px
    style Hidden_Layer fill:#f9f9f9,stroke:#333,stroke-width:2px
    style Output_Layer fill:#e1f5fe,stroke:#0277bd,stroke-width:2px
    style x1 fill:#fff,stroke:#333,stroke-width:2px
    style x2 fill:#fff,stroke:#333,stroke-width:2px
    style y1 fill:#fff,stroke:#0277bd,stroke-width:3px
    style y2 fill:#fff,stroke:#0277bd,stroke-width:3px

这时, 我们可以把第一次变换看成隐藏层.

从神经网络的输入层->隐藏层->输出层的过程, 就叫前向传播.

求w和b

线性回归

在我们知道所有的输入和输出之后, 求出w和b是根本的问题, 因为正确的w和b可以让我们得到正确的映射, 从而得到其他函数值.

f(x) = wx + b 为例:

  • Q1.什么样的w和b是好的?

    因为是非线性函数, w 和 b一般无法求出真正的准确值, 只能求近似值. 好的w和b是指使函数输出值接近真实值的取值. 越拟合真实数据越好.

  • Q2. 数学上怎样判断拟合的好?

    我们取了w和b后有一个准确的函数, f(x) = wx + b, 假如我们有:

    三组真实数据(x1, y1), (x2, y2), (x3, y3). 将自变量x1, x2, x3带进函数会有我们的

    预测值: 1, 2, 3

    那么对应的误差值分别是 |y1 -1|, |y2 - 2|, |y3 - 3|

    整体的拟合效果用所有误差值的绝对值之和来表示:

    $\sum_{i=1}^{n}|y_i - \hat{y}_i|$

  • 常见的损失函数: MSE

    上面这个表示真实值与预测值误差的函数, 叫做损失函数, 对上面的函数做处理, 得到现在常用的损失函数表示:

    1. 去除绝对值, 改用平方, 解决绝对值的问题, 并放大误差较大的问题

    2. 取一个平均值, 消除样本数量大小的影响.

    3. 这个损失函数可以叫做均方误差MSE(mean square error):

      $\text{L} = \frac{1}{n}\sum_{i=1}^{n}(y_i - \hat{y}_i)^2$

从参数的视角来看, 损失函数的值也就是误差值的均方值, 取决于我们的w和b, 不同取值对应的L不同.

$\text{L(w, b)} = \frac{1}{n}\sum_{i=1}^{n}(y_i - \hat{y}_i)^2$

一元函数求最小值/最大值点, 很明显求导, 找到导数为0的点即可找到极值点

比如一个最简单的(1, 1), (2, 2), (3, 3)

我们取最简单的y = wx, 忽略b值

此时的

$$ \begin{aligned} \text{L} &= \frac{1}{n}\sum_{i=1}^{n}(y_i - \hat{y}_i)^2 \\ &= \frac{1}{3}\sum_{i=1}^{3}(y_i - \hat{y}_i)^2 \\ &= \frac{1}{3}[(y_1 - \hat{y}_1)^2 + (y_2 - \hat{y}_2)^2 + (y_3 - \hat{y}_3)^2] \\ &= \frac{1}{3}[(1 - w)^2 + (2 - 2w)^2 + (3 - 3w)^2] \\ &= \frac{14}{3}w^2 - \frac{28}{3}w + \frac{14}{3} \end{aligned} $$

这是一个开口向上的抛物线, 求最小值要求导数为0点

$L' = \frac{28}{3}w - \frac{28}{3}$

当w = 1时, 取得L最小值为0.

如果保留b 则这是一个多元函数: L = f(w, b)

此时L的图像是一个开口向上的三维碗装图. 多元函数求最小值需要分别求偏导为0的点.

即我们要求: $\frac{\partial L(w, b)}{\partial w} = 0$$\frac{\partial L(w, b)}{\partial b} = 0$ 的点

上述的通过寻找一个线性函数, 来拟合x和y关系的方法, 叫做线性回归

梯度下降

当我们的函数在叠加了很多层线性变换和非线性变换后, 用求导的方法无法求出合适的w和b了, 这时我们应该怎么做?

暴力尝试:

1
2
3
4
round 1:
w = 5
b = 5
L(w, b) = 10

尝试增加w, 保持b不变

1
2
3
4
round 2:
w = 6
b = 5
L(w, b) = 9

L变小了, 说明调整对了.

尝试w不变, b增加:

1
2
3
4
round 3:
w = 6
b = 6
L(w, b) = 11

L变大了, 说明不合适.

尝试w不变, b减小:

1
2
3
4
round 4:
w = 6
b = 4
L(w, b) = 7

L变小了, 说明调整对了.

…循环调整

回到最初始的状态, L(w, b)在w = 5, b = 5的情况下, 改变了一个增量w = 6, 此时L’也有一个改变量从10 到了 9, 这就算损失函数L对w的偏导数 $\frac{\partial L(w, b)}{\partial w}$

偏导数为正时, 代表w增大L增大, 此时我们应该减小w

偏导数为负时, 代表w增大L减小, 此时我们应该增大w

所以对于变量w和b, 我们应该让他们向着自己偏导数的反方向变化, 即偏导数为正就减小, 偏导数为负就增大

$w = w -\frac{\partial L(w, b)}{\partial w}$

$b = b - \frac{\partial L(w, b)}{\partial b}$

这里变化的快慢用一个系数η来表示, 这个系数也叫做学习率

$w = w - η\frac{\partial L(w, b)}{\partial w}$

$b = b - η\frac{\partial L(w, b)}{\partial b}$

用梯度的思想总结一下上面的例子:

我们有一个初始的w和b值, 最终目的是找到偏导为0的点.

  1. 找到当前点的导数$\frac{\partial L(w, b)}{\partial w}$

  2. 沿着导数反方向变化, 使我们的导数贴近0, 即往$- \frac{\partial L(w, b)}{\partial w}$变化, 导数是一个点的变化率, 正式的变化值需要乘上一个系数η, 所以我们变化值是$- η \frac{\partial L(w, b)}{\partial w}$

  3. 所以我们迭代完一次后的新w, 和b的值为

    $w = w - η\frac{\partial L(w, b)}{\partial w}$

    $b = b - η\frac{\partial L(w, b)}{\partial b}$

  4. 每一次迭代我们都需要得到两个偏导数, $(\frac{\partial L(w, b)}{\partial w}, \frac{\partial L(w, b)}{\partial b})$, 这两个数组成了一个向量, 叫做梯度, 他的正负指明了w 和 b当前的变化方向. 梯度下降指的是我们要逆着梯度的方向走, 使L的值下降. 而顺着梯度走, 函数值会增加

反向传播

在复杂的神经网络中, 函数本身就非常复杂, 更不用说其损失函数了,但是如果抽象成层与层的关系就好多了.

比如:

graph LR
    subgraph Input_Layer [Input Layer]
        direction TB
        x((x))
    end

    subgraph Hidden_Layer [Hidden Layer]
        a((a))
    end
    
    subgraph Output_Layer [Output Layer]
        y((y))
    end

    x ==> a
    a ==> y

    style Input_Layer fill:#f9f9f9,stroke:#333,stroke-width:2px
    style Hidden_Layer fill:#f9f9f9,stroke:#333,stroke-width:2px
    style Output_Layer fill:#e1f5fe,stroke:#0277bd,stroke-width:2px
    style x fill:#fff,stroke:#333,stroke-width:2px
    style a fill:#fff,stroke:#0277bd,stroke-width:3px
    style y fill:#fff,stroke:#0277bd,stroke-width:3px

我们的计算流程是这样的:

$x \xrightarrow{g_1(w_1 x + b_1)} a \xrightarrow{g_2(w_2 a + b_2)} \hat{y} \xrightarrow{\frac{1}{n}\sum_{i=1}^{n}(y_i - \hat{y}_i)^2} L$

我们最终的问题是求合适的w1, w2, b1, b2这四个值, 使Loss最小

前面说过, 我们可以通过求偏导的方式来求这这几个值, 那么我们需要求

$\frac{\partial L}{\partial w_1}$

实际上的意思是看w1变化一点点的情况下L的变化情况:

  1. 可以看作w1变化了一点情况下a变化了多少, 即$\frac{\partial a}{\partial w_1}$
  2. a变化了一点的情况下变化了多少, 即$\frac{\partial \hat{y}}{\partial a}$
  3. 变化了一点的情况下L变化了多少, 即$\frac{\partial L}{\partial \hat{y}}$

这也是高数中学过的链式求导法则(复合函数求导):

$\frac{\partial L}{\partial w_1}$ = $\frac{\partial L}{\partial \hat{y}} \frac{\partial \hat{y}}{\partial a}\frac{\partial a}{\partial w_1}$

这里注意顺序, 先求后层对前层的导数, 也就是从右往左求导, 先求出来的导数可以继续用在后续求导, 这个过程就叫反向传播

神经网络的一次训练
graph LR
    subgraph Input_Layer [Input Layer]
        direction TB
        x((x))
    end

    subgraph Hidden_Layer [Hidden Layer]
        a((a))
    end
    
    subgraph Output_Layer [Output Layer]
        y((y))
    end

    x ==> a
    a ==> y

    style Input_Layer fill:#f9f9f9,stroke:#333,stroke-width:2px
    style Hidden_Layer fill:#f9f9f9,stroke:#333,stroke-width:2px
    style Output_Layer fill:#e1f5fe,stroke:#0277bd,stroke-width:2px
    style x fill:#fff,stroke:#333,stroke-width:2px
    style a fill:#fff,stroke:#0277bd,stroke-width:3px
    style y fill:#fff,stroke:#0277bd,stroke-width:3px

对于这样一个结构的神经网络

  1. 通过前向传播, 根据输入x得到输出y
  2. 通过反向传播, 计算出损失函数关于每个参数的梯度
  3. 每个参数向着梯度的反方向变化一点点

这就是神经网络的一次训练

调节神经网络的参数

过拟合问题

图中的折线是实际函数的值, 直线是我们拟合的函数, 这时我们的预测值与真实值会有误差.

xychart-beta
    title "Linear Regression(Underfitting Model)"
    x-axis "Feature X" [1, 2, 3, 4, 5, 6, 7, 8]
    y-axis "Target Y" 0 --> 20
    
    %% 真实数据点 (用虚线连接以突出位置)
    line "Training Data" [3, 5, 8, 9, 11, 14, 13, 17]
    
    %% 线性模型预测线 (平滑直线)
    line "Linear Model" [4, 6, 8, 10, 12, 14, 16, 18]

下面这张图表达的是, 我们预测的函数和实际的函数完全重叠了, Loss = 0.

看起来拟合的非常好, 实际上这种预测函数, 只对训练数据效果好, 对新数据的预测反而不如上面的函数.

这种只在训练数据上表现好, 在未见过数据上表现(泛化能力)差的情况, 叫做过拟合(Overfitting)

xychart-beta
    title "Overfitting Model"
    x-axis "X" [1, 2, 3, 4, 5, 6, 7, 8]
    y-axis "Y" 0 --> 20
    
    %% 实际函数: 连续实线
    line "Actual Data" [3, 5, 8, 9, 11, 14, 13, 17]
    
    %% 过拟合模型: 剧烈震荡连线
    %% 即使数值相同,Mermaid也可能因渲染引擎差异显示为不同线型
    %% 此处通过微小扰动确保其被识别为独立系列
    line "Overfit Model" [3.1, 4.9, 8.1, 8.9, 11.1, 13.9, 13.1, 16.9]

过拟合的情况是因为, 原本的函数并不复杂, 但是训练的时候把噪声和随机波动学会了.

解决过拟合方法:

  1. 简化模型复杂度
  2. 增加数据训练量(可以在原有数据中创造更多数据, 比如将同一副图旋转, 翻转, 裁剪, 加噪声…)
  3. 提前终止训练过程: 假如训练模型训练到最后会导致过拟合, 我们提前终止得到的模型就不会完全拟合.
L1,L2正则化

正则化的作用就是增加函数的泛化能力, 即减弱过拟合.

前文说过了, 训练参数的过程就是让损失函数不断减小的过程(梯度下降). 每次迭代更新中L都会不断减少, 这时, 如果我们让减少的量稍微小一点, 迭代完成后的L就会偏大一点.

我们通过给原来的损失函数增加一个惩罚项得到新的损失函数

惩罚项1: $\sum_{i=1}^{N}|w_i|$, 叫做L1范数, 所以对应的方法叫L1正则化

惩罚项2: $\sum_{i=1}^{N}w_i^2$, 叫做L2范数, 所以对应的方法叫L2正则化

范数是向量空间中的概念, 记得去补一下…

$\text{L} = \frac{1}{n}\sum_{i=1}^{n}(y_i - \hat{y}_i)^2$

$L_1 = \frac{1}{n}\sum_{i=1}^{n}(y_i - \hat{y}_i)^2 + \sum_{i=1}^{N}|w_i|$

$L_2 = \frac{1}{n}\sum_{i=1}^{n}(y_i - \hat{y}_i)^2 + \sum_{i=1}^{N}w_i^2$

同样的, 我们给范数前面加一个正则化系数(也叫超参数)λ

$L_1 = \frac{1}{n}\sum_{i=1}^{n}(y_i - \hat{y}_i)^2 + λ\sum_{i=1}^{N}|w_i|$

$L_2 = \frac{1}{n}\sum_{i=1}^{n}(y_i - \hat{y}_i)^2 + λ\sum_{i=1}^{N}w_i^2$

Dropout

Dropout是丢弃一些参数来解决过拟合问题的方法, 后面补充

卷积神经网络(Convolutional Neural Network)

神经网络的矩阵表达形式

一个简单的神经网络:

y = g(wx + b)

如果有多个输入就是

y = g(w1x1 + w2x2 + w3x3 + b)

如果输出有多个就是

y1 = g(w11x1 + w12x2 + w13x3 + b1)

y2 = g(w21x1 + w22x2 + w23x3 + b2)

graph LR
    subgraph Input_Layer [Input Layer]
        direction TB
        x1((x1))
        x2((x2))
        x3((x3))
    end

    subgraph Output_Layer [Output Layer]
        y1((y1))
        y2((y2))
    end

    x1 ==> y1
    x2 ==> y1
    x3 ==> y1
    x1 ==> y2
    x2 ==> y2
    x3 ==> y2

    style Input_Layer fill:#f9f9f9,stroke:#333,stroke-width:2px
    style Output_Layer fill:#e1f5fe,stroke:#0277bd,stroke-width:2px
    style x1 fill:#fff,stroke:#333,stroke-width:2px
    style x2 fill:#fff,stroke:#333,stroke-width:2px
    style x3 fill:#fff,stroke:#333,stroke-width:2px
    style y1 fill:#fff,stroke:#333,stroke-width:2px
    style y2 fill:#fff,stroke:#333,stroke-width:2px

很明显的, 可以写成矩阵乘法形式:

$$ \begin{aligned} &\begin{bmatrix} \color{green}{b_1} \\ b_2 \end{bmatrix} + \begin{bmatrix} \color{green}{w_{11}} & \color{green}{w_{12}} & \color{green}{w_{13}} \\ w_{21} & w_{22} & w_{23} \end{bmatrix} \begin{bmatrix} \color{blue}{x_1} \\ \color{blue}{x_2} \\ \color{blue}{x_3} \end{bmatrix} = \begin{bmatrix} \color{blue}{y_1} \\ \color{blue}{y_2} \end{bmatrix} \end{aligned} $$

写成矩阵的形式:

WX + b = Y

Y = g(WX + b)

如果再多几层神经网络:

graph LR
    subgraph Input_Layer [Input Layer]
        direction TB
        x1((x1))
        x2((x2))
        x3((x3))
    end
    subgraph Middle_Layer_1 [Middle_Layer_1]
        direction TB
        u1((u1))
        u2((u2))
    end
    subgraph Middle_Layer_2 [Middle_Layer_2]
        direction TB
        v1((v1))
        v2((v2))
    end
    subgraph Output_Layer [Output Layer]
        y1((y1))
        y2((y2))
    end

    x1 ==> u1
    x2 ==> u1
    x3 ==> u1
    x1 ==> u2
    x2 ==> u2
    x3 ==> u2

    u1 ==> v1
    u2 ==> v1
    u1 ==> v2
    u2 ==> v2
    
    v1 ==> y1
    v2 ==> y1
    v1 ==> y2
    v2 ==> y2

    style Input_Layer fill:#f9f9f9,stroke:#333,stroke-width:2px
    style Middle_Layer_1 fill:#e1f5fe,stroke:#0277bd,stroke-width:2px
    style Middle_Layer_2 fill:#e1f5fe,stroke:#0277bd,stroke-width:2px
    style Output_Layer fill:#e1f5fe,stroke:#0277bd,stroke-width:2px
    style x1 fill:#fff,stroke:#333,stroke-width:2px
    style x2 fill:#fff,stroke:#333,stroke-width:2px
    style x3 fill:#fff,stroke:#333,stroke-width:2px
    style y1 fill:#fff,stroke:#333,stroke-width:2px
    style y2 fill:#fff,stroke:#333,stroke-width:2px
    style v1 fill:#fff,stroke:#333,stroke-width:2px
    style v2 fill:#fff,stroke:#333,stroke-width:2px
    style u1 fill:#fff,stroke:#333,stroke-width:2px
    style u2 fill:#fff,stroke:#333,stroke-width:2px
  1. 第一次变换:

    u1 = g(w11x1 + w12x2 + w13x3 + b1)

    u1 = g(w21x1 + w22x2 + w23x3 + b2)

    矩阵形式: U = W0X + b0

  2. 第二次变换:

    v1 = g(w11u1 + w12u2 + b1)

    v2 = g(w21u1 + w22u2 + b2)

    矩阵形式: V = W1U + b1

  3. 第三次变换:

    y1 = g(w11v1 + w12v2 + b1)

    y2 = g(w21v1 + w22v2 + b2)

    矩阵形式: Y = W2V + b2

总结一下:

$$ \begin{aligned} U &= W_0X + b_0 \\ V &= W_1U + b_1 \\ Y &= W_2V + b_2 \end{aligned} $$

再抽象一下, 把所有中间层都看作输入的一部分, 最开始的层为第0层, 后面的依次增加:

graph LR
    subgraph Layer_0 ["a[0]"]
        direction TB
        a0_1(("a1[0]"))
        a0_2(("a2[0]"))
        a0_3(("a3[0]"))
    end

    subgraph Layer_1 ["a[1]"]
        direction TB
        a1_1(("a1[1]"))
        a1_2(("a2[1]"))
    end

    subgraph Layer_2 ["a[2]"]
        direction TB
        a2_1(("a1[2]"))
        a2_2(("a2[2]"))
    end

    subgraph Layer_3 ["a[3]"]
        direction TB
        a3_1(("a1[3]"))
        a3_2(("a2[3]"))
    end

    %% 连接 Layer 0 -> Layer 1 (全连接)
    a0_1 ==> a1_1
    a0_2 ==> a1_1
    a0_3 ==> a1_1
    a0_1 ==> a1_2
    a0_2 ==> a1_2
    a0_3 ==> a1_2

    %% 连接 Layer 1 -> Layer 2 (全连接)
    a1_1 ==> a2_1
    a1_2 ==> a2_1
    a1_1 ==> a2_2
    a1_2 ==> a2_2

    %% 连接 Layer 2 -> Layer 3 (全连接)
    a2_1 ==> a3_1
    a2_2 ==> a3_1
    a2_1 ==> a3_2
    a2_2 ==> a3_2

    %% 样式设置 (仿照原图颜色)
    style Layer_0 fill:#f9f9f9,stroke:none,stroke-width:0px
    style Layer_1 fill:#f9f9f9,stroke:none,stroke-width:0px
    style Layer_2 fill:#f9f9f9,stroke:none,stroke-width:0px
    style Layer_3 fill:#f9f9f9,stroke:none,stroke-width:0px
    
    style a0_1 fill:#1E90FF,stroke:#fff,stroke-width:2px,color:#fff
    style a0_2 fill:#1E90FF,stroke:#fff,stroke-width:2px,color:#fff
    style a0_3 fill:#1E90FF,stroke:#fff,stroke-width:2px,color:#fff
    
    style a1_1 fill:#1E90FF,stroke:#fff,stroke-width:2px,color:#fff
    style a1_2 fill:#1E90FF,stroke:#fff,stroke-width:2px,color:#fff
    
    style a2_1 fill:#1E90FF,stroke:#fff,stroke-width:2px,color:#fff
    style a2_2 fill:#1E90FF,stroke:#fff,stroke-width:2px,color:#fff
    
    style a3_1 fill:#1E90FF,stroke:#fff,stroke-width:2px,color:#fff
    style a3_2 fill:#1E90FF,stroke:#fff,stroke-width:2px,color:#fff

则有:

$$ \begin{aligned} A^{[1]} &= g(W^{[1]} A^{[0]} + b^{[1]}) \\ A^{[2]} &= g(W^{[2]} A^{[1]} + b^{[2]}) \\ A^{[3]} &= g(W^{[3]} A^{[2]} + b^{[3]}) \end{aligned} $$

用L表示层级, 则有:

A[L] = g(W[L]A[L − 1] + b[L])

为什么要转换成矩阵形式?(GPU和CPU)

转换成矩阵乘法, 可以用GPU进行并行运算, 效率高很多, 比用CPU快多了

待补充…

卷积(Convolution)

全连接层(Full Connect) 是指所有神经元都与前一层所有神经元相连的层, 比如上面的例子.

全连接层有一个问题就是参数量太大了, 比如, 一张图片的像素规模为30 * 30, 则第一层有900个输入, 假如第二层为全连接层且有1000个神经元, 则一共有90000个参数量:

W[][] = W[1000][900]

这个参数量太大了, 而且仅仅是平铺展开, 如果图片仅仅是微调一下, 比如平移, 调暗等等, 这些参数得重新训练, 非常麻烦. 解决方法是:

  1. 减小参数量的规模
  2. 想办法抽取图像的关键特征

卷积就是这样的一种运算, 核心思想是, 把某一块的输入, 提取出一个特征.

$$ \begin{bmatrix} \color{green}{w_{11}} & \color{green}{w_{12}} \\ \color{green}{w_{21}} & \color{green}{w_{22}} \end{bmatrix} \times \begin{bmatrix} \color{white}{b_{11}} & \color{white}{b_{12}} \\ \color{white}{b_{21}} & \color{white}{b_{22}} \end{bmatrix} = \color{green}{w_{11}}\color{white}{b_{11}} + \color{green}{w_{12}}\color{white}{b_{12}} + \color{green}{w_{21}}\color{white}{b_{21}} + \color{green}{w_{22}}\color{white}{b_{22}} $$

  • 卷积运算的定义: 两个规格相同的矩阵进行卷积运算, 每个元素与两一个矩阵对应元素相乘, 最后相加
  • 对一个大矩阵进行卷积提取特征值的过程: 用小尺寸卷积核(滤波器)在大矩阵上滑动,在每个位置将卷积核与对应区域进行逐元素相乘后求和,生成输出矩阵的一个值

拿一个规格为3 * 3的矩阵为例, 我们首先取一个卷积核矩阵(Convolution kernel), 假设规模是2 * 2的. 其卷积的过程如下:

$$ \begin{bmatrix} \color{green}{0} & \color{green}{1} & \color{white}{2} \\ \color{green}{3} & \color{green}{4} & \color{white}{5} \\ \color{white}{6} & \color{white}{7} & \color{white}{8} \end{bmatrix} \times \begin{bmatrix} \color{green}{0} & \color{green}{1} \\ \color{green}{2} & \color{green}{3} \end{bmatrix} = \begin{bmatrix} \color{green}{12} & \color{green}{} \\ \color{green}{} & \color{green}{} \end{bmatrix} $$

$$ \begin{bmatrix} \color{white}{0} & \color{green}{1} & \color{green}{2} \\ \color{white}{3} & \color{green}{4} & \color{green}{5} \\ \color{white}{6} & \color{white}{7} & \color{white}{8} \end{bmatrix} \times \begin{bmatrix} \color{green}{0} & \color{green}{1} \\ \color{green}{2} & \color{green}{3} \end{bmatrix} = \begin{bmatrix} \color{white}{12} & \color{green}{25} \\ \color{green}{} & \color{green}{} \end{bmatrix} $$

$$ \begin{bmatrix} \color{white}{0} & \color{white}{1} & \color{white}{2} \\ \color{green}{3} & \color{green}{4} & \color{white}{5} \\ \color{green}{6} & \color{green}{7} & \color{white}{8} \end{bmatrix} \times \begin{bmatrix} \color{green}{0} & \color{green}{1} \\ \color{green}{2} & \color{green}{3} \end{bmatrix} = \begin{bmatrix} \color{white}{12} & \color{white}{25} \\ \color{green}{37} & \color{white}{} \end{bmatrix} $$

$$ \begin{bmatrix} \color{white}{0} & \color{white}{1} & \color{white}{2} \\ \color{white}{3} & \color{green}{4} & \color{green}{5} \\ \color{white}{6} & \color{green}{7} & \color{green}{8} \end{bmatrix} \times \begin{bmatrix} \color{green}{0} & \color{green}{1} \\ \color{green}{2} & \color{green}{3} \end{bmatrix} = \begin{bmatrix} \color{white}{12} & \color{white}{25} \\ \color{white}{37} & \color{green}{43} \end{bmatrix} $$

通过设置不同的卷积核, 我们可以获得不同的图像效果, 如模糊, 轮廓, 锐化…

在Deep Learning 领域, 卷积核是一组待训练的值.

回到刚刚的经典神经网络结构中, 我们将一个全连接层替换成一个卷积层, 这就大大减少了我们的参数数量

池化(待补充)
CNN的局限

适用于静态数据, 比如图片. 动态数据如时间序列, 文本, 语音等不适用

循环神经网络(Recurrent Neural Network)

经典RNN

在已经有了普通的神经网络的情况下, 为什么还有RNN?

  1. 普通的神经网络的输入要求是固定的, 比如CNN可能要求一张图共 30 * 30 = 900个输入变量. 但是RNN的输入可以是不变的
  2. RNN在时序问题上表现非常好, 比如, 在NLP中的一个词性标注任务:我吃苹果, 通过大量学习后的CNN可能会标注出: 我(nn)吃(v)苹果(nn), 但是如果是RNN的话, 在时序方面表现非常好, 输入的顺序是我->吃->苹果, 输入第一个是名词, 他后面的第二个输入大概率是动词, 第二个输入吃(v)是动词, 则第三个输入苹果(nn)大概率是动词. 这种特性也会用在大模型(LLM)中, 后面会说到.

假如现在有四个词输入, 传统的神经网络做法是:

$$ \begin{aligned} Y^{\langle 1 \rangle} &= g(WX^{\langle 1 \rangle} + b) \\ Y^{\langle 2 \rangle} &= g(WX^{\langle 2 \rangle} + b) \\ Y^{\langle 3 \rangle} &= g(WX^{\langle 3 \rangle} + b) \\ Y^{\langle 4 \rangle} &= g(WX^{\langle 4 \rangle} + b) \end{aligned} $$

这样他们的输出都是独立的没有关联, 但是我们实际的输入是有顺序关联的, 怎么做才能关联起来?

我们将第一个词非线性变换后, 不直接输出, 而是先记录为一个隐藏状态, 再经过一次非线性变换得到输出, 将这个隐藏状态用来和下一个输入一起运算.

时间步 隐藏状态 输出
t=1 h⟨1⟩ = g(WxhX⟨1⟩ + bh) Y⟨1⟩ = g(Whyh⟨1⟩ + by)
t=2 h⟨2⟩ = g(WxhX⟨2⟩ + Whhh⟨1⟩ + bh) Y⟨2⟩ = g(Whyh⟨2⟩ + by)

注意这里用了的W矩阵不同, xh下标表示这是由x得到h的W矩阵:

$$ \begin{array}{c c c c c} X^{\langle 1 \rangle} & \xrightarrow{W_{xh}} & h^{\langle 1 \rangle} & \xrightarrow{W_{hy}} & Y^{\langle 1 \rangle} \\ & & \downarrow \scriptstyle W_{hh} & & \\ X^{\langle 2 \rangle} & \xrightarrow{W_{xh}} & h^{\langle 2 \rangle} & \xrightarrow{W_{hy}} & Y^{\langle 2 \rangle} \end{array} $$

RNN公式:

$$ \begin{aligned} h^{\langle t \rangle} &= g_1(W_{xh}X^{\langle t \rangle} + b_h) \\ Y^{\langle t \rangle} &= g_2(W_{hy}h^{\langle t \rangle} + b_y) \end{aligned} $$

Note:微信收藏里面存了一张图

Problems:

  1. 信息会随着时间步增多而逐渐丢失, 无法捕捉长期依赖
  2. 无法并行运算, 下一时间的输入依赖上一时间的隐藏状态

GRULSTM可以改进, 但是, transformer 碾压一切.

Transformer

Transformer的核心思想, 让每个token都包含其它token的信息, 让他们互相有关联.