“自然搞懂”深度学习系列(基于Pytorch架构)——01初入茅庐
文章目录
- “自然搞懂”深度学习(基于Pytorch架构)
- 第Ⅰ章 初入茅庐
- 一、 数学的奥妙
- 二、梯度下降
- 2.1 自然引出
- 2.2 梯度下降三种方法
- 2.2.1 All(Gradient Descent)
- 2.2.2 One-By-One(Stochastic Gradient Descent)
- 2.2.3 Mini-Batch(小批量梯度下降法)
- 三、前向传播与反向传播
- 3.1 定义
- 3.2 实例
- 3.2.1 单样本点实例
- 3.2.2 多样本点实例
- 3.3 Think-Help
我的一个学习座右铭:一切烦恼来源于定义不清。我认为是有些道理的。
今天我又感悟到了其中很重要的一点——隐含定义(或者说默认规则),一个人理解知识的链路基于严密逻辑,这个链路是否被打通并且正确决定了他是否真正理解了所学知识,但教授知识的人真正做到这点是很难的,由于他自身对知识已了然于胸往往无法感同身受,从而容易忽略链路中潜在隐含的一些定义规则,导致学习者学完模棱两可,在偶然间学生了解到链路中的堵点或纠正了错点,我们便常说这个学生“开窍了”。
因此,我会在讲解中穿插加入Think-Help,同时希望读者指出错误并提出宝贵意见,万分感谢!
“自然搞懂”深度学习(基于Pytorch架构)
第Ⅰ章 初入茅庐
一、 数学的奥妙
【世上所有事物运行规律皆可用函数表达】
深度学习本质思想:使用 “线性函数与非线性激活函数相互嵌套形成的一个函数”,例如式1:
g ( w 3 g ( w 1 x 1 + w 2 x 2 + b 1 ) + b 2 ) ) g(w_3g(w_1x_1+w_2x_2+b_1)+b_2)) g(w3g(w1x1+w2x2+b1)+b2))
来拟合任何函数,解决任何复杂问题。
而这样的函数代数形式过于复杂,将其化为 “神经网络” 的形式形象表达:
对应式1:输入层两个节点x1与x2,隐藏层一个节点:
g ( w 1 x 1 + w 2 x 2 + b 1 ) g(w_1x_1+w_2x_2+b_1) g(w1x1+w2x2+b1)
其中需要求解的参数有w1、w2、b1;输出层一个节点:
g ( w 3 g ( w 1 x 1 + w 2 x 2 + b 1 ) + b 2 ) ) g(w_3g(w_1x_1+w_2x_2+b_1)+b_2)) g(w3g(w1x1+w2x2+b1)+b2))
其中参数w3、b2。
如何求解w和b呢?根据已知输入输出值去“猜测”。
先随机给定一组参数值,计算损失函数L(以均方误差MSE为例,后面一说损失函数即指该式)
L ( w , b ) = 1 N ∑ i = 1 N ( y i − y ^ i ) 2 L(w,b) = \frac{1}{N} \sum_{i=1}^{N} (y_i - \hat{y}_i)^2 L(w,b)=N1i=1∑N(yi−y^i)2
的值,“调整”参数使L最小。
如何调整?尝试直接求偏导以找到损失函数最低点。
使L对每个参数的偏导值为0,求解此时的参数。例如,对多自变量函数,每个自变量偏导为0,此时为函数极小值。联想xyz坐标系的一个三维曲面,如下图,x轴横截面与y轴横截面最低点组成的坐标(x,y,z)为曲面最低点。
然而将函数式带入损失函数L后,L变得极为复杂,直接令偏导为0求解变得不现实。
可以举一个简单例子:拟合式为y=wx+b,L仍为MSE,给定三个点A(1,1) B(2,2) C(3,3)带入得:
L = 1 3 [ ( w + b − 1 ) 2 + ( 2 w + b − 2 ) 2 + ( 3 w + b − 3 ) 2 ] L=\frac{1}{3} \left[ (w + b - 1)^2 + (2w + b - 2)^2 + (3w + b - 3)^2 \right] L=31[(w+b−1)2+(2w+b−2)2+(3w+b−3)2]
分别使L对w,b偏导为0联立得w=1,b=0,故y=x,完美拟合三点。
Think-Help
这里的拟合式省去了激活函数,实际不可省略,因为这是神经网络拟合复杂问题的基础。
这是一个最简单的例子,试想成千上万的参数和数据集,分别将样本点代入L,然后分别将上万个偏导式联立,求解对应上万个参数的解,基本不可能,而且很多时候无法直接求出偏导为零的解析解。
那怎么办?
二、梯度下降
2.1 自然引出
没办法直接求出来那就一点点试,仍以式2为例,梯度下降公式3如下:
w = w − η ⋅ ∂ L ( w , b ) ∂ w b = b − η ⋅ ∂ L ( w , b ) ∂ b w = w - \eta \cdot \frac{\partial L(w,b)}{\partial w}\\ b = b - \eta \cdot \frac{\partial L(w,b)}{\partial b} w=w−η⋅∂w∂L(w,b)b=b−η⋅∂b∂L(w,b)
偏导大于0,即参数与L的变化方向一致(如式3中w若“偏导L/偏导w”大于0,表示固定b,w增大L就增大),反之变化相反。
这样,式3就实现了永远朝着L减小的方向前进。(方向一致,减小参数;方向相反,增大参数)
每次都代入全部样本点进行参数更新吗?
在每一次epoch中,我们都有一组样本点(训练集)作为信息可以使用。比如在公式3中,其损失函数就是用全部样本点作为信息进行更新:在一次epoch中,把所有样本点的
损失平方求和再平均(即MSE)作为损失函数代入损失函数L对参数的求导式中,进行一次参数更新。
但是,在神经网络中比局部最优解更棘手的问题是——鞍点问题——即在迭代过程中往往会出现梯度接近0的情况,这就会导致参数更新几乎停滞(Why?Think a Think)。
于是,**随机梯度下降(SGD)**成为一种解决方法。
SGD在每一次epoch中依次对每个样本点进行梯度计算并更新参数,这样的话,一个epoch中,参数会像一位中风的老爷爷般哆嗦着向前移动,而不至于陷入鞍点无法移动。
Think-Help
使用随机梯度下降每一个epoch中,每个样本点都会被用于单独更新一次参数,而不是随机选取部分样本用于更新参数,随机性主要体现在随机打乱(shuffle)训练数据的顺序。
多说一句:为什么要继续移动?因为鞍点确实是一个或几个维度上的最优点,也就是部分参数达到了最优,但我们最终的目标是使目标函数达到最小,继续移动仍极大可能因整体参数变化而使目标函数最小。
前面说到:SGD会使参数像一位中风的老爷爷般哆嗦着向前移动,也就是参数更新极小极慢;而GD又像个疯狂老奶奶动不动就劈个叉,再也站不起来;那能不能正常点,小步快走呢?当然可以,在一个epoch中,将全部样本点分组打包,依次利用每一组样本进行梯度计算及参数更新即可,这就叫Mini-Batch。
2.2 梯度下降三种方法
接下来,我们具体来看一个epoch中【我特意强调了这点:希望读者以epoch作为单位】,参数更新的三种方式:
2.2.1 All(Gradient Descent)
疯狂的老奶奶——在一个epoch中一次选取全部样本点为“信息”进行一次参数更新。
公式表现:所有样本点损失进行MSE作为损失函数进行BP。
θ : = θ − η ⋅ 1 N ∑ i = 1 N ∇ θ L ( f ( x i ; θ ) , y i ) \theta := \theta - \eta \cdot \frac{1}{N} \sum_{i=1}^{N} \nabla_\theta L(f(x_i;\theta), y_i) θ:=θ−η⋅N1i=1∑N∇θL(f(xi;θ),yi)
其中,θ:模型参数;η:学习率(learning rate);N:样本总数;L(⋅):损失函数;∇θL:对参数的梯度。
代码表现:
# Gradient Descent
for epoch in range(num_epochs):y_pred = model(X) # 所有样本一次前向传播loss = loss_fn(y_pred, y_true) # 计算整体损失grad = compute_gradients(loss, model) # 计算所有样本平均梯度model.parameters -= lr * grad # 一次更新参数
2.2.2 One-By-One(Stochastic Gradient Descent)
蹒跚的老爷爷——在一个epoch中依次选取每个样本点为“信息”进行N(指样本点数量)次参数更新。
公式表现:每个样本点单独计算损失作为损失函数进行BP。
θ : = θ − η ⋅ ∇ θ L ( f ( x i ; θ ) , y i ) , i = 1 , 2 , … , N \theta := \theta - \eta \cdot \nabla_\theta L(f(x_i;\theta), y_i), \quad i = 1,2,\ldots,N θ:=θ−η⋅∇θL(f(xi;θ),yi),i=1,2,…,N
代码表现:
# Stochastic Gradient Descent
for epoch in range(num_epochs):for i in range(N): # N为样本数x_i, y_i = X[i], y_true[i]y_pred = model(x_i) # 单样本前向传播loss = loss_fn(y_pred, y_i)grad = compute_gradients(loss, model)model.parameters -= lr * grad # 每个样本都更新一次
2.2.3 Mini-Batch(小批量梯度下降法)
稳健的你——在一个epoch中分批次选取样本点组合为“信息”进行X(指批次数量)次参数更新。
公式表现:
θ : = θ − η ⋅ 1 m ∑ j = 1 m ∇ θ L ( f ( x j ; θ ) , y j ) , ( x j , y j ) ∈ Batch k \theta := \theta - \eta \cdot \frac{1}{m} \sum_{j=1}^{m} \nabla_\theta L(f(x_j;\theta), y_j), \quad (x_j, y_j) \in \text{Batch}_k θ:=θ−η⋅m1j=1∑m∇θL(f(xj;θ),yj),(xj,yj)∈Batchk
代码表现:
# Mini-Batch Gradient Descent
for epoch in range(num_epochs):for batch_X, batch_y in DataLoader(X, y_true, batch_size):y_pred = model(batch_X) # 小批量前向传播loss = loss_fn(y_pred, batch_y)grad = compute_gradients(loss, model)model.parameters -= lr * grad # 每个批次更新一次
自然地,Mini-Batch是目前训练中最常用的梯度下降方法,所以在其它场景一说梯度下降,即指Mini-Batch方法。
接下来,我们只需要计算损失函数对各参数的梯度即可完成更新。
如何计算?
三、前向传播与反向传播
前向传播(Forward Propagation)与反向传播(Back Propagation,简称“BP”)
许多学习资料中常把这节仅称作“反向传播”,我认为还是不应该落下前向传播,这才是一个整体参数更新过程。
3.1 定义
前向传播:输入样本进入神经网络,计算得到所有中间及最终结果。
Think-Help
已知样本点或样本矩阵(矩阵由多个样本点组成)及标注y值,参数集θ(如果初步迭代,初始设定;否则,就是上一次epoch计算得到的)及激活函数,那么所有中间结果、最终预测结果及Loss值都可得到。换句话说,该神经网络的所有值你都已知道。
反向传播:在神经网络中,反向计算每两层间导数,通过链式法则相乘,得到损失函数对各参数的梯度。
Think_Help
链式法则的应用:要求”偏导L/偏导w“和”偏导L/偏导W“,W是包含w的多项表达式,自然地想到先求后者,然后
∂ L ∂ w = ∂ L ∂ W ⋅ ∂ W ∂ w \frac{\partial L}{\partial w} = \frac{\partial L}{\partial W} \cdot \frac{\partial W}{\partial w} ∂w∂L=∂W∂L⋅∂w∂W
可不可以直接把L写成w的表达式然后再直接求”偏导L/偏导w“,可以,但在神经网络复杂的体系中不现实。
3.2 实例
3.2.1 单样本点实例
建议先看一遍以下链接的例子(该例完整但过于简单):一文弄懂神经网络中的反向传播法——BackPropagation - Charlotte77 - 博客园
这是输入的样本点,相当于三种参数更新方法中的One-by-One,只通过这个例子学习的话会产生一个疑问:多个样本点(样本矩阵)同时输入是如何计算的呢?会不会需要一些特殊处理?
3.2.2 多样本点实例
我以Mini-Batch Size=2的情况手写了另一个例子:
采用学习率为0.01更新后:
W 2 new = [ 1 , 2 ] − 0.01 ⋅ [ 0 , − 30 ] = [ 1.00 , 2.30 ] W_2^{\text{new}} = [1, 2] - 0.01 \cdot [0, -30] = [1.00, 2.30] W2new=[1,2]−0.01⋅[0,−30]=[1.00,2.30]
b 2 new = 0 − 0.01 ⋅ ( − 8 ) = 0.08 b_2^{\text{new}} = 0 - 0.01 \cdot (-8) = 0.08 b2new=0−0.01⋅(−8)=0.08
W 1 new = [ 1 − 1 2 0 ] − 0.01 [ 0 0 − 34 − 50 ] = [ 1.00 − 1.00 2.34 0.50 ] W_1^{\text{new}} = \begin{bmatrix} 1 & -1 \\ 2 & 0 \end{bmatrix} - 0.01 \begin{bmatrix} 0 & 0 \\ -34 & -50 \end{bmatrix} =\begin{bmatrix} 1.00 & -1.00 \\ 2.34 & 0.50 \end{bmatrix} W1new=[12−10]−0.01[0−340−50]=[1.002.34−1.000.50]
b 1 new = [ 0.5 , − 0.5 ] − 0.01 [ 0 , − 16 ] = [ 0.50 , − 0.34 ] b_1^{\text{new}} = [0.5, -0.5] - 0.01[0, -16] = [0.50, -0.34] b1new=[0.5,−0.5]−0.01[0,−16]=[0.50,−0.34]
3.3 Think-Help
既然用了多个样本,为什么梯度计算过程中矩阵变大,但参数 w、b 的形状却没变?
参数w、b的形状只取决于网络结构,不取决于样本数量。
Think-Help
也就是说,无论你用 1 个样本还是 1 万个样本,只要输入维度、输出维度、每层的神经元数量没变,则参数矩阵的维度始终固定。
这里关键的魔法在于:矩阵乘法自动帮你把所有样本的梯度累加/求和,结果仍是固定形状的矩阵。
个人认为这是一个理解的堵点,我又整理了一个更为复杂(6样本 × 3特征)的例子以供理解,已上传至:Xueyouing/Study-Naturally
至于为何在神经网络中比局部最优解更棘手的问题是鞍点问题?
在一个具有高维空间的损失函数中,如果一个维度上(对应一个参数w_i)的梯度为0,这个时候这个参数的值为0,那么在的某个邻域内,要么是凸函数,要么是凹函数,在一个维度上,凸函数对应着极小值,凹函数对应着极大值,如果训练到了所有维度的参数的梯度均为0时,如果真的达到了极小值,那么就要求所有维度上在此点的一个邻域内都是凸函数,很显然,这样的概率是很小的,我们更有可能遇到的是鞍点(或者是平坦区域)。
该题解采自鞍点问题 - Hexo。
到这里,恭喜你!已经为神经网络运算提供了基本思路(函数形式)和可行方法(梯度下降+前反向传播)。
- 本文由Forester.原创撰写,无偿分享,若发现侵犯版权、转卖倒卖等行为,追究其责任。
- 欢迎关注,未完待续!