同样,记录这个的起因是源于老师的作业,所以多有不成熟的地方,大佬绕路
概念说明
引用其他博客的一段说明梯度下降
首先来看看梯度下降的一个直观的解释。比如我们在一座大山上的某处位置,由于我们不知道怎么下山,于是决定走一步算一步,也就是在每走到一个位置的时候,求解当前位置的梯度,沿着梯度的负方向,也就是当前最陡峭的位置向下走一步,然后继续求解当前位置梯度,向这一步所在位置沿着最陡峭最易下山的位置走一步。这样一步步的走下去,一直走到觉得我们已经到了山脚。当然这样走下去,有可能我们不能走到山脚,而是到了某一个局部的山峰低处。
从上面的解释可以看出,梯度下降不一定能够找到全局的最优解,有可能是一个局部最优解。当然,如果损失函数是凸函数,梯度下降法得到的解就一定是全局最优解。
梯度下降法介绍
这里我们如果要最小化一个损失函数的话,我们应该采用梯度下降法。但是在上一篇博客当中,我们最小化一个损失函数是采用的解析解的方法,即直接求出了最小化这个函数对应的参数的数学解,但是实际上很多机器学习的模型是无法直接求出这样的数学解,所以这里我们采用一种基于搜索的最优化方法来找到相应的最优解,其中梯度下降法应该是最为常见的一个方法。
我们沿着其导数逐渐减小的方向进行
其实在一维函数中,我们可以直接用导数来理解(多维函数中需要对各个方向的分量分别进行求导,最后得到的方向就是梯度)
我们之前所遇到的是二次函数,显然是有唯一的极值点,所以上一篇博客写到,我们可以直接用解析法求得解析解,但并不是所有函数都有唯一极值点
高维平面情况如下图
如果我们采用梯度下降法,很明显我们现在曲率下降的情况,会找到第一个极小值点,但很明显,这一块只是一个局部情况,从全局来看,这并不是最小值点
为了解决这一问题,其实可以随机改变初始点的位置,从而能找到更好的解
但是对目前老师布置的作业而言,我们在线性回归问题当中考虑梯度下降的话,其损失函数是具有唯一的最优解的。
代码实现
下面我相当于我自己定义了一个损失二次函数,然后针对这个二次函数去找局部最优解
代码部分
import numpy as np
import matplotlib.pyplot as plt
# 损失函数
plot_x = np.linspace(-1, 6, 141)
plot_y = (plot_x - 2.5) ** 2 - 1 # 先自己定义了一个二次函数
# 绘制
plt.plot(plot_x, plot_y)
plt.show()
# 对损失函数求导
def dEwb(theta):
return 2 * (theta -2.5)
# 损失函数
def E_function(theta):
return (theta - 2.5) ** 2 - 1
theta = 0.0
α = 0.1 # 设置阿尔法
epsilon = 1e-8 # 差值精度,确定最优解点
history_theta = [ theta ] # 记录theta值用于之后的对比
# 找梯度最小的情况
while True:
gradient = dEwb(theta) # 求精度
last_theta = theta # theta重新赋值前,记录上一场的值
theta = theta - α * gradient # 通过一定的α取得下一个点的theta
history_theta.append(theta) # 更新history_theta
# 最近两点的损失函数差值小于一定精度,退出循环
if(abs(E_function(theta) - E_function(last_theta)) < epsilon):
break
plt.plot(plot_x, E_function(plot_x))
plt.plot(np.array(history_theta), E_function(np.array(history_theta)), color = 'r', marker = '+')
plt.show()
运行结果
首先是绘制出定义的二次函数
下降过程用红色的加号标注出来
改变学习率
这里为了改变学习率,首先将上述的代码的梯度下降过程,和绘制过程的代码进行封装
import numpy as np
import matplotlib.pyplot as plt
# 损失函数
plot_x = np.linspace(-1, 6, 141)
plot_y = (plot_x - 2.5) ** 2 - 1 # 先自己定义了一个二次函数
# 绘制
plt.plot(plot_x, plot_y)
plt.show()
# 对损失函数求导
def dEwb(theta):
return 2 * (theta -2.5)
# 损失函数
def E_function(theta):
return (theta - 2.5) ** 2 - 1
# 找梯度最小的情况,梯度下降过程
def gradient_descent(initial_theta, α, epsilon = 1e-8):
theta = initial_theta
history_theta.append(theta)
while True:
gradient = dEwb(theta) # 求精度
last_theta = theta # theta重新赋值前,记录上一场的值
theta = theta - α * gradient # 通过一定的α取得下一个点的theta
history_theta.append(theta) # 更新history_theta
# 最近两点的损失函数差值小于一定精度,退出循环
if(abs(E_function(theta) - E_function(last_theta)) < epsilon):
break
def plot_theta_history():
plt.plot(plot_x, E_function(plot_x))
plt.plot(np.array(history_theta), E_function(np.array(history_theta)), color = 'r', marker = '+')
plt.show()
# 改变参数,进行调用
α = 0.1 # 设置阿尔法
# 差值精度,确定最优解点
history_theta = [] # 记录theta值用于之后的对比
gradient_descent(0., α)
plot_theta_history()
当我们改变学习率α后(现在是0.1,假设改为0.01)
可以看到我们红色加号的点变的更加密集,也就是说学习率降低了。
如果接下来增大学习率α(0.1变为0.8)
这种情况下,虽然变量theta从左半边跳到了右半边,但是损失函数的值还是在减小,最后也能找到我们的最优解的情况(那是不是theta不超过某个限度的情况下,只要损失函数在减小,最后也能找到最优解的地方呢?)
当我们继续增大学习率的时候,python出现了报错,结果过大
我们可以首先在求损失函数的方法中捕获异常
# 损失函数
def E_function(theta):
try:
return (theta - 2.5) ** 2 - 1
except:
return float('inf') # 返回浮点数的最大值
但是这样一来也仅仅是不报错了,上面的循环是采用的while true,所以这里其实造成了一个死循环(无法通过abs(E_function(theta) - E_function(last_theta)) < epsilon判断来结束循环,当无穷减去无穷的时候,这里定义的其实是NAN),所以在这里我们也要限制循环次数
def gradient_descent(initial_theta, α, n_iters = 1e8, epsilon = 1e-8):
theta = initial_theta
history_theta.append(theta)
i_iters = 0
while i_iters < n_iters:
gradient = dEwb(theta) # 求精度
last_theta = theta # theta重新赋值前,记录上一场的值
theta = theta - α * gradient # 通过一定的α取得下一个点的theta
history_theta.append(theta) # 更新history_theta
# 最近两点的损失函数差值小于一定精度,退出循环
if(abs(E_function(theta) - E_function(last_theta)) < epsilon):
break
i_iters += 1
限制后出来的结果如下图所示
显然这种情况下的学习率太大了
下面是整体的代码
import numpy as np
import matplotlib.pyplot as plt
# 损失函数
plot_x = np.linspace(-1, 6, 141)
plot_y = (plot_x - 2.5) ** 2 - 1 # 先自己定义了一个二次函数
# 绘制
plt.plot(plot_x, plot_y)
plt.show()
# 对损失函数求导
def dEwb(theta):
return 2 * (theta -2.5)
# 损失函数
def E_function(theta):
try:
return (theta - 2.5) ** 2 - 1
except:
return float('inf') # 返回浮点数的最大值
# 找梯度最小的情况,梯度下降过程
# n_iters:循环次数,不传值的时候默认限制1w次
def gradient_descent(initial_theta, α, n_iters = 1e8, epsilon = 1e-8):
theta = initial_theta
history_theta.append(theta)
i_iters = 0
while i_iters < n_iters:
gradient = dEwb(theta) # 求精度
last_theta = theta # theta重新赋值前,记录上一场的值
theta = theta - α * gradient # 通过一定的α取得下一个点的theta
history_theta.append(theta) # 更新history_theta
# 最近两点的损失函数差值小于一定精度,退出循环
if(abs(E_function(theta) - E_function(last_theta)) < epsilon):
break
i_iters += 1
def plot_theta_history():
plt.plot(plot_x, E_function(plot_x))
plt.plot(np.array(history_theta), E_function(np.array(history_theta)), color = 'r', marker = '+')
plt.show()
# 改变参数,进行调用
α = 1.1 # 设置阿尔法
# 差值精度,确定最优解点
history_theta = [] # 记录theta值用于之后的对比
gradient_descent(0, α, n_iters = 10)
plot_theta_history()