梯度下降是迭代法的一种,可以用于求解最小二乘问题(线性和非线性都可以)。在求解机器学习算法的模型参数,即无约束优化问题时,梯度下降(Gradient Descent)是最常采用的方法之一,另一种常用的方法是最小二乘法。在求解损失函数的最小值时,可以通过梯度下降法来一步步的迭代求解,得到最小化的损失函数和模型参数值。反过来,如果我们需要求解损失函数的最大值,这时就需要用梯度上升法来迭代了。在机器学习中,基于基本的梯度下降法发展了两种梯度下降方法,分别为随机梯度下降法和批量梯度下降法。顾名思义,梯度下降法的计算过程就是沿梯度下降的方向求解极小值(也可以沿梯度上升方向求解极大值)。其迭代公式为,
其中代表梯负方向,表示梯度方向上的搜索步长。梯度方向我们可以通过对函数求导得到,步长的确定比较麻烦,太大了的话可能会发散,太小收敛速度又太慢。一般确定步长的方法是由线性搜索算法来确定,即把下一个点的坐标看做是ak+1的函数,然后求满足f(ak+1)的最小值的ak+1即可。因为一般情况下,梯度向量为0的话说明是到了一个极值点,此时梯度的幅值也为0.而采用梯度下降算法进行最优化求解时,算法迭代的终止条件是梯度向量的幅值接近0即可,可以设置个非常小的常数阈值。
牛顿迭代法是以微分为基础的,微分就是用直线来代替曲线,由于曲线不规则,那么我们来研究直线代替曲线后,剩下的差值是不是高阶无穷小,如果是高阶无穷小,那么这个差值就可以扔到不管了,只用直线就可以了,这就是微分的意义。
牛顿法是牛顿在17世纪提出的一种求解方程f(x)=0.多数方程不存在求根公式,从而求精确根非常困难,甚至不可能,从而寻找方程的近似根就显得特别重要。
牛顿迭代法是取x0之后,在这个基础上,找到比x0更接近的方程的跟,一步一步迭代,从而找到更接近方程根的近似跟。方法使用函数f(x)的泰勒级数的前面几项来寻找方程f(x) = 0的根。牛顿迭代法是求方程根的重要方法之一,其最大优点是在方程f(x) = 0的单根附近具有平方收敛,而且该法还可以用来求方程的重根、复根。另外该方法广泛用于计算机编程中。
设r是f(x)=0的根,选取x0作为r初始近似值,过点(x0,f(x0))做曲线y=f(x)的切线L,L的方程为y=f(x0)+f’(x0)(x-x0),求出L与x轴交点的横坐标 x1=x0-f(x0)/f’(x0),称x1为r的一次近似值,过点(x1,f(x1))做曲线y=f(x)的切线,并求该切线与x轴的横坐标 x2=x1-f(x1)/f’(x1)称x2为r的二次近似值,重复以上过程,得r的近似值序列{Xn},其中Xn+1=Xn-f(Xn)/f’(Xn),称为r的n+1次近似值。上式称为牛顿迭代公式。
import math
#使用梯度下降法求函数的最小值
# f = x1^2+2x2^2-4x1-2x1x2 初始点为(1,1)
#设计函数
def function_one(x1,x2): # 函数的输入 x,y
f = x1*x1+2*x2*x2-4*x1-2*x1*x2 # 算出f 的值
dx = 2*x1-4-2*x2 # 算出 一阶x导数的值
dy = 4*x2-2*x1 # 算出 一阶y导数的值
return f, dx, dy # 返回三个值
def main():
x = 1 #初始点
y = 1 #初始点
a = 0.3 # 设置步长
error = 0.000001 # 设置误差函数
f_old, dx, dy = function_one(x,y) # 算出初始的值
for i in range(100): # 开始循环 梯度下降 设置循环的次数
x -= a*dx
y -= a*dy
f_result, dx, dy = function_one(x,y) # 获得第一次计算的初始值
if abs(f_result - f_old) < error: # 下降的结果绝对值与误差进行比较 如果再允许范围内则终止
f_final = f_result
print("极值点为: %.5f ,最小值为: %.5f, 循环次数: %d, 误差:%8f" % (x,f_final, i , abs(f_result - f_old)))
break
print("第 %d 次循环, 函数值为 %f" % (i, f_result))
f_old = f_result
if __name__ == '__main__':
main()
第 0 次循环, 函数值为 -5.400000
第 1 次循环, 函数值为 -6.576000
第 2 次循环, 函数值为 -7.193280
第 3 次循环, 函数值为 -7.533504
第 4 次循环, 函数值为 -7.727005
第 5 次循环, 函数值为 -7.839158
第 6 次循环, 函数值为 -7.904877
第 7 次循环, 函数值为 -7.943626
第 8 次循环, 函数值为 -7.966552
第 9 次循环, 函数值为 -7.980142
第 10 次循环, 函数值为 -7.988206
第 11 次循环, 函数值为 -7.992994
第 12 次循环, 函数值为 -7.995838
第 13 次循环, 函数值为 -7.997527
第 14 次循环, 函数值为 -7.998531
第 15 次循环, 函数值为 -7.999127
第 16 次循环, 函数值为 -7.999481
第 17 次循环, 函数值为 -7.999692
第 18 次循环, 函数值为 -7.999817
第 19 次循环, 函数值为 -7.999891
第 20 次循环, 函数值为 -7.999935
第 21 次循环, 函数值为 -7.999962
第 22 次循环, 函数值为 -7.999977
第 23 次循环, 函数值为 -7.999986
第 24 次循环, 函数值为 -7.999992
第 25 次循环, 函数值为 -7.999995
第 26 次循环, 函数值为 -7.999997
第 27 次循环, 函数值为 -7.999998
极值点为: 3.99862 ,最小值为: -8.00000, 循环次数: 28, 误差:0.000001
由此可知,该函数的极值点为4.000,最小值为-8.000
#求第一组数据的
from sympy import *
a1,a2,b=symbols('a1 a2 b')
S=(469-10*a1-80*a2-b)*(469-10*a1-80*a2-b)
print(diff(S,a1))
print(diff(S,a2))
print(diff(S,b))
200*a1 + 1600*a2 + 20*b - 9380
1600*a1 + 12800*a2 + 160*b - 75040
20*a1 + 160*a2 + 2*b - 938
##求第2组数据的
from sympy import *
a1,a2,b=symbols('a1 a2 b')
S=(366-8*a1-0*a2-b)*(366-8*a1-0*a2-b)
print(diff(S,a1))
print(diff(S,a2))
print(diff(S,b))
128*a1 + 16*b - 5856
0
16*a1 + 2*b - 732
##求第3组数据的
from sympy import *
a1,a2,b=symbols('a1 a2 b')
S=(371-8*a1-200*a2-b)*(371-8*a1-200*a2-b)
print(diff(S,a1))
print(diff(S,a2))
print(diff(S,b))
128*a1 + 3200*a2 + 16*b - 5936
3200*a1 + 80000*a2 + 400*b - 148400
16*a1 + 400*a2 + 2*b - 742
##求第4组数据的
from sympy import *
a1,a2,b=symbols('a1 a2 b')
S=(208-5*a1-200*a2-b)*(208-5*a1-200*a2-b)
print(diff(S,a1))
print(diff(S,a2))
print(diff(S,b))
50*a1 + 2000*a2 + 10*b - 2080
2000*a1 + 80000*a2 + 400*b - 83200
10*a1 + 400*a2 + 2*b - 416
##求第5组数据的
from sympy import *
a1,a2,b=symbols('a1 a2 b')
S=(246-7*a1-300*a2-b)*(246-7*a1-300*a2-b)
print(diff(S,a1))
print(diff(S,a2))
print(diff(S,b))
98*a1 + 4200*a2 + 14*b - 3444
4200*a1 + 180000*a2 + 600*b - 147600
14*a1 + 600*a2 + 2*b - 492
##求第6组数据的
from sympy import *
a1,a2,b=symbols('a1 a2 b')
S=(297-8*a1-230*a2-b)*(297-8*a1-230*a2-b)
print(diff(S,a1))
print(diff(S,a2))
print(diff(S,b))
128*a1 + 3680*a2 + 16*b - 4752
3680*a1 + 105800*a2 + 460*b - 136620
16*a1 + 460*a2 + 2*b - 594
##求第7组数据的
from sympy import *
a1,a2,b=symbols('a1 a2 b')
S=(363-7*a1-40*a2-b)*(363-7*a1-40*a2-b)
print(diff(S,a1))
print(diff(S,a2))
print(diff(S,b))
98*a1 + 560*a2 + 14*b - 5082
560*a1 + 3200*a2 + 80*b - 29040
14*a1 + 80*a2 + 2*b - 726
##求第8组数据的
from sympy import *
a1,a2,b=symbols('a1 a2 b')
S=(436-9*a1-0*a2-b)*(436-9*a1-0*a2-b)
print(diff(S,a1))
print(diff(S,a2))
print(diff(S,b))
162*a1 + 18*b - 7848
0
18*a1 + 2*b - 872
##求第9组数据的
from sympy import *
a1,a2,b=symbols('a1 a2 b')
S=(198-6*a1-330*a2-b)*(198-6*a1-330*a2-b)
print(diff(S,a1))
print(diff(S,a2))
print(diff(S,b))
72*a1 + 3960*a2 + 12*b - 2376
3960*a1 + 217800*a2 + 660*b - 130680
12*a1 + 660*a2 + 2*b - 396
##求第10组数据的
from sympy import *
a1,a2,b=symbols('a1 a2 b')
S=(364-9*a1-180*a2-b)*(364-9*a1-180*a2-b)
print(diff(S,a1))
print(diff(S,a2))
print(diff(S,b))
162*a1 + 3240*a2 + 18*b - 6552
3240*a1 + 64800*a2 + 360*b - 131040
18*a1 + 360*a2 + 2*b - 728
由此可知:当a1=44.944,a2=-0.4475,b=69.9925时Se有极小值。
所以y=44.944x1-0.4475x2+69.9925
漫画书中所得结果y=41.5x1-0.3x2+65.3
两者有一定的误差,不过用梯度下降法得到的结果没有用最小二乘法得到的结果准确。
import math
#使用梯度下降法求函数的最小值
#设计函数
def function_one(x1,x2,b): # 函数的输入 x,y
f = (469-10*x1-80*x2-b)*(469-10*x1-80*x2-b)+(366-8*x1-0*x2-b)*(366-8*x1-0*x2-b)+(371-8*x1-200*x2-b)*(371-8*x1-200*x2-b)+(208-5*x1-200*x2-b)*(208-5*x1-200*x2-b)+(246-7*x1-300*x2-b)*(246-7*x1-300*x2-b)+(297-8*x1-230*x2-b)*(297-8*x1-230*x2-b)+(363-7*x1-40*x2-b)*(363-7*x1-40*x2-b)+(436-9*x1-0*x2-b)*(436-9*x1-0*x2-b)+(198-6*x1-330*x2-b)*(198-6*x1-330*x2-b)+(364-9*x1-180*x2-b)*(364-9*x1-180*x2-b) # 算出f 的值
dx1 = 1226*x1+22440*x2+154*b-53306 # 算出 一阶x1导数的值
dx2 = 22440*x1+744400*x2+3120*b-881620 # 算出 一阶x2导数的值
db = 154*x1+3120*x2+20*b-6636
return f, dx1, dx2 ,db # 返回4个值
def main():
x1 = 45 #初始点
x2 = 1 #初始点
b = 70
a = 0.0000001 # 设置步长
error = 0.8 # 设置误差函数
f_old, dx1, dx2,db = function_one(x1,x2,b) # 算出初始的值
for i in range(100): # 开始循环 梯度下降 设置循环的次数
x1 -= a*dx1
x2 -= a*dx2
b -=b*db
f_result, dx1, dx2,db = function_one(x1,x2,b) # 获得第一次计算的初始值
if abs(f_result-f_old)<error:
print("a1=",x1)
print("a2=",x2)
print("b=",b)
f_old=f_result
if __name__ == '__main__':
main()