编辑
🍊本文从行人下山故事引入梯度下降法,随后详细阐述其原理,并做了两个简单实验进行实战
🍊实验一使用Pytorch来求解函数的最小值
🍊实验二使用批量梯度下降算法和随机梯度下降算法来拟合函数求解最佳参数
一、Introduction
1.1 案例
梯度下降/上升算法是一种优化算法,主要用于ML中递归性逼近最小偏差模型,主要用于寻找目标函数的最值(也有可能是拟合函数参数,其本质上是求解Loss最小值),其主要思想如下图中所示,假设有一个人需要经过最短的时间爬到山脚(不考虑走陡坡比较累),怎么做呢?首先他以当前的位置为基准,随后寻找该位置最陡峭的地方,沿该方向行走,走了一段路程后重复上述操作走到山脚就会花费最小时间
编辑
1.2 抽象化
以数学的角度看待下山过程
山 | 目标函数 |
山顶 | 函数最值 |
陡峭方向 | 梯度正方向(找函数最大值) 梯度反方向(找函数最小值) |
下山步子 | 学习率α |
是否已到达山脚 | 阈值β,判断是否完成收敛 |
是否已到达山脚 | 阈值γ,判断是否达到最大迭代次数 |
因此梯度下降算法求解的过程就是对函数不断的求梯度,并使用学习率α进行约束的
学习率的设置比较灵活,设置的太小似跺小碎步,设置的太大容易扯到蛋。因为我们的最终目标是最短的时间内到达山顶,因此最理想的状态是先大步子下山,随后小碎步逼近山脚,但是这样又出现一个问题是在山脚跺小碎步要很久😕,因此还需要设置一个阈值来进行限制,阈值的设置主要有两种策略,第一种是判断是否收敛完成,第二种为判断是否到达最大迭代次数。
二、Principle
2.1 性质
- BGD可以找到局部最优解,不一定是全局最优解
- 若损失函数为凸函数,则BGD所求解一定为全局最优解
- 所求函数必须为连续可微分
2.2 单变量函数推导过程
符号 | 含义 |
θ | 待求参数 |
α | 学习率 |
∇J(θ) | 目标函数的梯度 |
其推导公式如下
=−α∗∇J()
2.3 梯度下降三种形式
形式 | 特点 |
Batch Gradient Descent 批量梯度下降算法 | 直接整个数据集来训练,若数据量大将面临内存不足,收敛速度慢问题 |
Stochastic Gradient Descent 随机梯度下降算法(在线学习) | 一个一个数据来训练,每训练一个就更新一次参数,收敛速度较快,但是可能高频的参数更新导致方差过大最终导致目标函数数值震荡 |
Mini-batch Gradient Descent 小批量梯度下降算法 | 综合BGD和SGD算法,每次取一小个batch数量的数据来计算,训练过程较稳定 |
三、Experiment
3.1 实验一:函数最小值求解
题目:求解函数y=2.1-2.5x+1.3的最小值
伪代码
1 初始化函数、学习率、随机初始值 2 计算函数梯度 3 更新参数 4 循环次数若小于阈值,则输出,若大于阈值则跳2
代码
# 求解f(x) = a*x**2 + b*x + c 的最小值 import numpy as np import torch # 1 Initialize parameters x = torch.tensor(0.0, requires_grad=True) # x需要被求导 a = torch.tensor(2.1) b = torch.tensor(-2.5) c = torch.tensor(1.3) # 2 Design optimizer optimizer = torch.optim.SGD(params=[x], lr=0.01) # SGD Stochastic Gradient Descent def f(x): result = a * torch.pow(x, 2) + b * x + c return (result) for i in range(500): optimizer.zero_grad() # Initialize grad to zero y = f(x) y.backward() # BP optimizer.step() # Update parameters print("x=", x.data, "y=", f(x))
3.2 实验二:函数拟合求参
题目:已知有如下数据集,其对应的函数为y=ω*x,问如何拟合出最优的ω?
编辑
解题思路
1 要拟合出最佳的ω,也就是让所有Loss之和最小,因此实际上还是一个求解最小值问题
2 求解Loss之和的导数
编辑
3 求解更新公式
编辑
3.2.1 BGD梯度下降法
import numpy as np import matplotlib.pyplot as plt # Initialize parameters x_data = [1.0, 2.0, 3.0] y_data = [2.0, 4.0, 6.0] w = 1.0 # Define function def forward(x): return x * w # Define the loss function def cost(xs, ys): cost = 0 for x, y in zip(xs, ys): y_pred = forward(x) cost += (y_pred - y) ** 2 return cost / len(xs) # Gradient function def gradient(xs, ys): grad = 0 for x, y in zip(xs, ys): grad += 2 * x * (x * w - y) return grad / len(xs) cost_list = [] epoch_list = [] # Trainging for epoch in range(100): epoch_list.append(epoch) cost_val = cost(x_data, y_data) cost_list.append(cost_val) grad_val = gradient(x_data, y_data) w -= 0.1 * grad_val print('Epoch:', epoch, 'w=', w, 'loss=', cost_val) # Visualize the results plt.plot(epoch_list, cost_list) plt.ylabel('Loss') plt.xlabel('Epoch') plt.show()
编辑
可以很明显的看到,随着训练的进行,Loss是平滑的收敛于0
3.2.2 SGD随机梯度下降法
SGD与BGD最大的不同在于每个epoch,SGD是一个一个数据来训练,BGD是所有数据一起训练
import numpy as np import matplotlib.pyplot as plt # Initialize parameters x_data = [1.0, 2.0, 3.0] y_data = [2.0, 4.0, 6.0] w = 1.0 # Define function def forward(x): return x * w # Define the loss function def loss(x, y): y_pred = forward(x) return (y_pred - y) ** 2 # Gradient function def gradient(x, y): return 2 * x * (x * w - y) loss_list = [] epoch_list = [] # Trainging for epoch in range(100): for x, y in zip(x_data, y_data): grad = gradient(x, y) w -= 0.01 * grad Loss = loss(x, y) loss_list.append(Loss) epoch_list.append(epoch) print('Epoch:', epoch, 'w=', w, 'loss=', Loss) # Visualize the results plt.plot(epoch_list, loss_list) plt.ylabel('Loss') plt.xlabel('Epoch') plt.show()
编辑
可以看到明显的看到,随着训练的进行,Loss上下浮动的收敛于0
参考资料
《机器学习》周志华
《深度学习与机器学习》吴恩达
《神经网络与与深度学习》邱锡鹏
《Pytorch深度学习实战》刘二大人