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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
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)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
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 带进函数会有我们的

    预测值: y^1 , y^2 , y^3

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

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

    i=1n|yiy^i|

  • 常见的损失函数: MSE

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

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

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

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

      L=1ni=1n(yiy^i)2

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

L(w, b)=1ni=1n(yiy^i)2

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

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

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

此时的

L=1ni=1n(yiy^i)2=13i=13(yiy^i)2=13[(y1y^1)2+(y2y^2)2+(y3y^3)2]=13[(1w)2+(22w)2+(33w)2]=143w2283w+143

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

L=283w283

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

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

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

即我们要求: L(w,b)w=0 L(w,b)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的偏导数 L(w,b)w

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

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

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

w=wL(w,b)w

b=bL(w,b)b

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

w=wηL(w,b)w

b=bηL(w,b)b

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

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

  1. 找到当前点的导数 L(w,b)w

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

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

    w=wηL(w,b)w

    b=bηL(w,b)b

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

反向传播

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

比如:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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

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

xg1(w1x+b1)ag2(w2a+b2)y^1ni=1n(yiy^i)2L

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

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

Lw1

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

  1. 可以看作 w1 变化了一点情况下 a 变化了多少, 即 aw1
  2. a 变化了一点的情况下 y^ 变化了多少, 即 y^a
  3. y^ 变化了一点的情况下 L 变化了多少, 即 Ly^

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

Lw1 = Ly^y^aaw1

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

神经网络的一次训练
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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. 每个参数向着梯度的反方向变化一点点

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

调节神经网络的参数

过拟合问题

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

1
2
3
4
5
6
7
8
9
10
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)

1
2
3
4
5
6
7
8
9
10
11
12
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: i=1N|wi| , 叫做L1范数, 所以对应的方法叫L1正则化

惩罚项2: i=1Nwi2 , 叫做L2范数, 所以对应的方法叫L2正则化

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

L=1ni=1n(yiy^i)2

L1=1ni=1n(yiy^i)2+i=1N|wi|

L2=1ni=1n(yiy^i)2+i=1Nwi2

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

L1=1ni=1n(yiy^i)2+λi=1N|wi|

L2=1ni=1n(yiy^i)2+λi=1Nwi2

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)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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

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

[b1b2]+[w11w12w13w21w22w23][x1x2x3]=[y1y2]

写成矩阵的形式:

WX+b=Y

Y=g(WX+b)

如果再多几层神经网络:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
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

总结一下:

U=W0X+b0V=W1U+b1Y=W2V+b2

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
graph LR
subgraph Layer_0 ["a<sup>[0]</sup>"]
direction TB
a0_1(("a<sub>1</sub><sup>[0]</sup>"))
a0_2(("a<sub>2</sub><sup>[0]</sup>"))
a0_3(("a<sub>3</sub><sup>[0]</sup>"))
end

subgraph Layer_1 ["a<sup>[1]</sup>"]
direction TB
a1_1(("a<sub>1</sub><sup>[1]</sup>"))
a1_2(("a<sub>2</sub><sup>[1]</sup>"))
end

subgraph Layer_2 ["a<sup>[2]</sup>"]
direction TB
a2_1(("a<sub>1</sub><sup>[2]</sup>"))
a2_2(("a<sub>2</sub><sup>[2]</sup>"))
end

subgraph Layer_3 ["a<sup>[3]</sup>"]
direction TB
a3_1(("a<sub>1</sub><sup>[3]</sup>"))
a3_2(("a<sub>2</sub><sup>[3]</sup>"))
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

则有:

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])

用L表示层级, 则有:

A[L]=g(W[L]A[L1]+b[L])
为什么要转换成矩阵形式?(GPU和CPU)

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

待补充…

卷积(Convolution)

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

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

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

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

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

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

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

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

[012345678]×[0123]=[12] [012345678]×[0123]=[1225] [012345678]×[0123]=[122537] [012345678]×[0123]=[12253743]

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

在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)中, 后面会说到.

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

Y1=g(WX1+b)Y2=g(WX2+b)Y3=g(WX3+b)Y4=g(WX4+b)

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

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

时间步 隐藏状态 输出
t=1 h1=g(WxhX1+bh) Y1=g(Whyh1+by)
t=2 h2=g(WxhX2+Whhh1+bh) Y2=g(Whyh2+by)

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

X1Wxhh1WhyY1WhhX2Wxhh2WhyY2

RNN公式:

ht=g1(WxhXt+bh)Yt=g2(Whyht+by)

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

Problems:

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

用GRU和LSTM可以改进, 但是, transformer 碾压一切

本文链接:https://wangyier.top/From-Neural-Network-to-AI/

版权声明:本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 The Great Library