深度学习
深度学习是机器学习的一个分支,它通过一种极其强大的方式(自动特征学习+深度堆叠)来解决非线性问题。因为它在图像、语音、自然语言等领域"太好用了",以至于它已经发展成一个拥有自己生态系统、硬件、研究范式的巨大领域,因此在日常讨论中经常被单独提及。
1. 基本构成
-
神经网络:模仿人脑神经元网络结构的计算模型,是深度学习的基础。
-
层:神经网络的基本处理单元。数据会按顺序流过多个层,每一层都对数据进行一次变换。
-
神经元:层的基本组成单元,负责接收输入、进行计算并输出。
-
激活函数:给神经元增加"非线性"能力的开关。没有它,神经网络只能解决线性问题,有了它,才能处理复杂现实世界的问题。
通常的线性变换,无论如何叠加仍是线性变换。假设我们有一个两层网络,没有激活函数:
- 第一层输出:
h = W₁x + b₁ - 第二层输出:
y = W₂h + b₂ = W₂(W₁x + b₁) + b₂ - 展开后:
y = (W₂W₁)x + (W₂b₁ + b₂)
我们令
W' = W₂W₁和b' = W₂b₁ + b₂,那么y = W'x + b'。因此仍然是线性变换。现在,我们在每一层之间加入一个激活函数
f(比如 ReLU)。- 第一层输出:
h = f(W₁x + b₁) - 第二层输出:
y = f(W₂h + b₂) = f(W₂ * f(W₁x + b₁) + b₂)
现在,我们还能把这两层合并成一个
W'x + b'的形式吗?明显不可以。因为激活函数f本身是一个非线性函数 。我们无法把f从括号里"拆"出来,也无法把两个f合并成一个。常用的激活函数,例如ReLU,就是f(x) = max(0, x),它会把所有负数变成0,所有正数保持不变。这是一个典型的非线性函数。激活函数是给神经元加入非线性能力的。
- 第一层输出:
2.具体流程
每一层是怎么设计的?
每一层都由若干神经元组成,而每一个神经元有自己的权重W和偏置b,也就是y=wx+b,输出也就是y = ReLU(wx+b)。其中x是上一层输出,这一层接收的向量,一般是多维的。
因此,层与层之间都是根据前一层输入的值,处理得到一个向量,这个向量的维数表示这一层要学习的特征个数(也就是这一层神经元的个数,一个神经元代表要学习的一个特征。例如128个神经元得到的就是一个128维的向量,代表128个特征),下一层就可以依据这一层处理出的特征继续深化。这里的深化,是特征的深化。例如第一层只是低级的特征,越往后越深入。
层与层之间怎么交互?
那么每一层都需要学什么,确定什么特征,是由谁来决定的?我们不会直接设计一个流程,而是只告诉这个神经网络最终目标,让他自己想办法调整参数,达到最终的目标。这里的最终目标就是损失函数,也就是一个评分机制,用来衡量网络当前的预测结果"有多错"。因此网络的目标就是找到一组参数,让损失函数的值最小。
整个过程和参数不能完全的随机猜测,这样的效率太低。因此需要使用反向传播 :根据神经网络做出的预测,计算损失函数,然后将这个结果从后向前传播,一层一层的计算每层的参数对总损失的贡献值。这个贡献值在数学上,也就是算梯度,利用微积分中的链式法则,能够非常高效地计算出网络中每一个权重对最终损失的"贡献度"(即梯度)。如果某个权重的梯度是正数,也就是增大它会导致损失增大,那么就可以减小它;反之,梯度为负数,就可以增大这个参数的值。
这里可以用数学解释一下:
第一步:正向传播
这是数据从输入流向输出的过程,完全是计算。
- 隐藏层的输入加权和 :
z₁ = w₁ * x + b₁ - 隐藏层的输出(经过激活函数) :
我们使用 ReLU 激活函数f(z) = max(0, z)。
a₁ = f(z₁) = max(0, w₁ * x + b₁) - 输出层的输入加权和(也是最终预测值) :
z₂ = w₂ * a₁ + b₂
我们的最终预测值ŷ(y-hat) 就是z₂。
ŷ = z₂ = w₂ * a₁ + b₂
至此,正向传播完成。我们得到了一个预测值 ŷ。
第二步:计算损失函数
损失函数 L 衡量了我们的预测 ŷ 和真实值 y 之间的差距。我们使用最简单的均方误差:L = (ŷ - y)²
现在,我们把 ŷ 的表达式代入,损失就变成了所有参数 (w₁, b₁, w₂, b₂) 的函数:
L(w₁, b₁, w₂, b₂) = (w₂ * max(0, w₁ * x + b₁) + b₂ - y)²
我们的目标是最小化这个 L 。怎么最小化?就要调整参数。往哪个方向调整?梯度的反方向。这里为什么要根据梯度来调整?梯度就是在这个L(w₁, b₁, w₂, b₂)里,每个参数的偏导数,这个偏导数为正也就意味着参数越大,损失值越大,这不是想要的,因此我们要减小这个参数值;偏导数为负数,也就意味着参数越大损失值越小,那就可以增大这个参数值。同时,梯度还表达了这个坡有多陡峭,也就是梯度值的大小。
也就是说,梯度为我们提供了最优的下降方向 和调整步长的参考。因此我们要计算每个参数的偏导数,并基于此来更新参数。
第三步:反向传播
像上一步所说的,我们需要计算损失 L 对每一个参数的偏导数(梯度),然后用它们来更新参数。
核心数学工具:链式法则。链式法则用于计算复合函数的导数。例如,如果 f(u) 和 g(x) 都是可导函数,那么复合函数 f(g(x)) 的导数是:
d/dx [f(g(x))] = f'(g(x)) * g'(x)
在我们的网络中,L 是 ŷ 的函数,ŷ 是 z₂ 的函数,z₂ 是 a₁ 的函数... 这就是一个长长的复合函数,非常适合用链式法则。这个过程是从后向前的,逐层计算的。
首先分别计算输出层参数(w2,b2)偏导数,然后分别计算隐藏层参数 (w₁, b₁) 的偏导数。最终我们会得到各个参数的偏导数,就可以开始更新参数。
第四步:更新参数
这里就需要使用超参数,也就是人为提前设置好的,在训练中十分重要的参数。例如学习率η,控制了我们每次根据梯度来调整参数的"步长"有多大。如果学习率太小,收敛的时间就很长;如果太大,可能直接就跳过了最值点,无法收敛。基于设定好的学习率,可以得到:
w₁_new = w₁_old - η * ∂L/∂w₁b₁_new = b₁_old - η * ∂L/∂b₁w₂_new = w₂_old - η * ∂L/∂w₂b₂_new = b₂_old - η * ∂L/∂b₂
使用这些新参数,我们就可以开始下一次的训练(下一个样本,或下一批样本),也就是重复正向传播、计算损失值、反向传播和更新参数这四步,成千上万次后,就会得到一组不错的参数,使得L达到很小的值。
如何设计每一层?
每一层的神经元数量不是固定的,也没有一个放之四海而皆准的具体范式,只有相关的经验。
如果神经元数量太少,可能会导致欠拟合,也就无法提取到想要的全部特征。反之数量太多,可能会导致过拟合:这是因为不止提取到了特征,也可能将噪声纳入其中。工程上往往是2的幂次,这主要是为了计算效率。