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
上面这个表示真实值与预测值误差的函数, 叫做损失函数, 对上面的函数做处理, 得到现在常用的损失函数表示:
去除绝对值, 改用平方, 解决绝对值的问题, 并放大误差较大的问题
取一个平均值, 消除样本数量大小的影响.
这个损失函数可以叫做均方误差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 | round 1: |
尝试增加w, 保持b不变
1 | round 2: |
L变小了, 说明调整对了.
尝试w不变, b增加:
1 | round 3: |
L变大了, 说明不合适.
尝试w不变, b减小:
1 | round 4: |
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的点.
找到当前点的导数$\frac{\partial L(w, b)}{\partial w}$
沿着导数反方向变化, 使我们的导数贴近0, 即往$- \frac{\partial L(w, b)}{\partial w}$变化, 导数是一个点的变化率, 正式的变化值需要乘上一个系数η, 所以我们变化值是$- η \frac{\partial L(w, b)}{\partial w}$
所以我们迭代完一次后的新w, 和b的值为
$w = w - η\frac{\partial L(w, b)}{\partial w}$
$b = b - η\frac{\partial L(w, b)}{\partial b}$
每一次迭代我们都需要得到两个偏导数, $(\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的变化情况:
- 可以看作w1变化了一点情况下a变化了多少, 即$\frac{\partial a}{\partial w_1}$
- a变化了一点的情况下ŷ变化了多少, 即$\frac{\partial \hat{y}}{\partial a}$
- ŷ变化了一点的情况下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
对于这样一个结构的神经网络
- 通过前向传播, 根据输入x得到输出y
- 通过反向传播, 计算出损失函数关于每个参数的梯度
- 每个参数向着梯度的反方向变化一点点
这就是神经网络的一次训练
调节神经网络的参数
过拟合问题
图中的折线是实际函数的值, 直线是我们拟合的函数, 这时我们的预测值与真实值会有误差.
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]
过拟合的情况是因为, 原本的函数并不复杂, 但是训练的时候把噪声和随机波动学会了.
解决过拟合方法:
- 简化模型复杂度
- 增加数据训练量(可以在原有数据中创造更多数据, 比如将同一副图旋转, 翻转, 裁剪, 加噪声…)
- 提前终止训练过程: 假如训练模型训练到最后会导致过拟合, 我们提前终止得到的模型就不会完全拟合.
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
第一次变换:
u1 = g(w11x1 + w12x2 + w13x3 + b1)
u1 = g(w21x1 + w22x2 + w23x3 + b2)
矩阵形式: U = W0X + b0
第二次变换:
v1 = g(w11′u1 + w12′u2 + b1′)
v2 = g(w21′u1 + w22′u2 + b2′)
矩阵形式: V = W1U + b1
第三次变换:
y1 = g(w11″v1 + w12″v2 + b1″)
y2 = g(w21″v1 + w22″v2 + 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]
这个参数量太大了, 而且仅仅是平铺展开, 如果图片仅仅是微调一下, 比如平移, 调暗等等, 这些参数得重新训练, 非常麻烦. 解决方法是:
- 减小参数量的规模
- 想办法抽取图像的关键特征
卷积就是这样的一种运算, 核心思想是, 把某一块的输入, 提取出一个特征.
$$ \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?
- 普通的神经网络的输入要求是固定的, 比如CNN可能要求一张图共 30 * 30 = 900个输入变量. 但是RNN的输入可以是不变的
- 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:
- 信息会随着时间步增多而逐渐丢失, 无法捕捉长期依赖
- 无法并行运算, 下一时间的输入依赖上一时间的隐藏状态
用GRU和 LSTM可以改进, 但是,
transformer 碾压一切.
Transformer
Transformer的核心思想, 让每个token都包含其它token的信息, 让他们互相有关联.