深度学习基础:梯度下降算法手撕
深度学习基础:梯度下降算法手撕
在深度学习中,梯度下降算法发挥着重要作用。由于深度学习模型通常具有大量的参数,因此需要一个高效的优化算法来更新这些参数。梯度下降算法通过计算损失函数关于每个参数的梯度,沿着梯度的反方向更新参数,从而实现对模型的训练。
这个步骤可以通过调用已封装好的模块自动实现,但是为了加深对于模型原理的理解,本文以线性拟合为例介绍梯度下降算法的数学原理和代码实现。
一、数学推导
模型构建
在回归问题中,我们想利用已知的自变量X建立回归模型,来推测因变量Y的值:
Y = b1X1 + b2X2 + ⋯ + bnXn + b0
可以发现,其中关键问题就在于各个系数
B = [b0, b1, b2, …, bn]T
的求解,于是我们进行了大量的观测,并得到了n组实验数据
{y1 = b0 + b1x11 + ⋯ + bp x1p + ε,
⋮
yi = b0 + b1xi1 + ⋯ + bp xip + ε,
⋮
yn = b0 + b1xn1 + ⋯ + bpxnp + ε.
其中ε表示每一次观测时产生的随机误差,这一点可以联系误差理论与测量平差去理解,因此实际上我们给出的是考虑平差后的观测方程。写成线性代数的形式就是
Y = XB + E
E ∼ N(0, Σ)
求解方法
为了求解B,我们常采用两用算法,一种是最小二乘法(这个方法咱们以后讲),还有便是梯度下降算法。
梯度下降算法的主要思路是这样的:
- 初始化参数(这里就是预设B)
- 计算损失函数,即当前参数值下的预测值和真实值的损失
- 沿着梯度方向更新参数并重新计算损失
- 重复迭代直至损失值收敛到极小值处
在线性回归中,我们常用均方误差(MSE)作为损失函数,其公式为:
J(θ) = 1/2m ∑i=1m (hθ(x(i)) − y(i))2.
其中
hθ(x(i)) = b1x1 + b2x2 + ⋯ + bnxn + b0
耐心观察可以发现,MSE的实质就是对预测值和真值之间的误差求平均。
因此为了找到最合适的系数B,我们要让损失函数的值最小。在第一步我们预设了系数的初始值,求出损失后需要对初始值进行修正,怎么实现呢?
在这里损失函数实质上是一个(n+1)元的函数,在高等数学中我们知道函数中一点沿梯度方向存在上升/下降的最大速率,我们可以求出当前值的梯度,从而对每一个参数进行更新使得最后的总体损失逐渐下降。
梯度求解
梯度下降算法的关键在于其更新过程,公式表达如下:
θj := θj − α ∂/∂θj J(θ).
其中α控制梯度下降算法的速率(也就是深度学习中的学习率),该算法是一个非常直观的算法,即每一次θ值的更新都沿着J减小最快的方向。
当然,这里有一个必要的注意点:保证梯度下降算法达到全局最优解的条件是J(θ)必须是凹函数。
下面求解每一步θ的改正值:
∂/∂θj J(θ) = ∂/∂θj 1/2m (hθ(x) − y)2
= 1/m (hθ(x) − y) ∂/∂θj (hθ(x) − y)
= 1/m (hθ(x) − y) ∂/∂θj (∑i=0n θixi − y)
= 1/m (hθ(x) − y)xj
所以梯度下降算法的更新准则是:
θj := θj − α/m (hθ(x(i)) − y(i)) xj(i).
其中m表示观测方程个数。
以上就是全部理论推导,我们只需利用更新准则不断对参数进行调整最终求得最优解。
二、代码编写
在写代码时我们需要注意循环终止条件的设置,可以采取人为规定循环次数的方式(相当于深度学习中epoch)来终止循环。
下面是Python代码展示,此处我们只设置了b0和b1作为演示
import numpy as np
import pandas as pd
from tqdm import tqdm
import matplotlib.pyplot as plt
def DataLoader(path):
# 加载数据
df = pd.read_csv(path, header=None)
x = df.iloc[:, 0].values
y = df.iloc[:, 1].values
return x, y
def get_J(x, y, b1, b0):
# 计算损失函数
h = b1 * x + b0
delta2 = np.square(y - h)
J = np.mean(delta2) / 2
return J
def get_gradients(x, y, b1, b0):
# 计算梯度
m = len(x)
h = b1 * x + b0
grad_k = np.dot(h - y, x) / m
grad_b = np.mean(h - y)
return grad_k, grad_b
def update(x, y, b1, b0, alpha):
# 更新参数
grad_k, grad_b = get_gradients(x, y, b1, b0)
b1 -= alpha * grad_k
b0 -= alpha * grad_b
return b1, b0
def main():
# 超参设置
alpha = 0.0008
epoch = 4000
path = './Data_Test2/Data/data.csv'
x, y = DataLoader(path)
# 初始化参数
b1 = 1
b0 = 0.3
# 列表存放每轮的损失,以便绘图
l = []
e = []
loss = get_J(x,y,b1,b0)
l.append(loss)
ep = 0
e.append(ep)
# 梯度下降
for i in tqdm(range(epoch)):
e.append(i+1)
b1, b0 = update(x, y, b1, b0, alpha)
loss = get_J(x, y, b1, b0)
l.append(loss)
plt.plot(e, l)
plt.title('Loss vs. Epoch')
plt.xlabel('Epoch')
plt.ylabel('Loss')
plt.show()
if __name__ == '__main__':
main()
运行结果展示:
可以发现该模型于500轮左右便已经收敛,因此可以适当减少训练轮数。