作业
给定函数f(x) = x^2 + 3*x - 10
- 理解方程求根中的二分法,使用numpy库而非scipy库实现算法
- 使用上述算法求函数f(x)的根
- 理解梯度下降法(gradient descent),并使用基本的numpy库而非scipy库实现算法
- 使用上述算法来得到函数f(x)的最小值
import numpy as np
import scipy as sp
import scipy.optimize as opt
import matplotlib.pyplot as plt
%matplotlib inline
from sympy import *
x = symbols('x')
fsym = x**2 + 3*x - 10
# 先获取准确值
solve(fsym)
[-5, 2]
plotting.plot(fsym);
二分法
理论依据非常简单,就是若果函数f(x)在区间(a,b)上连续,且f(a)*f(b) < 0,也就是异号,则f(x)=0在区间(a,b)上必有根。进行二分,逐渐缩小区间,直到区间小于设置的误差,取区间均值为根。
二分法是比较简单、但速度较慢的一种求根方法。它的主要思想是:如果 f(a) 和 f(b) 异号,且函数方程连续,那么函数的根一定处在 a 和 b 之间。我们通过中间点 c 来估测方程的根所在的位置。如果 f(c) 为 0, c 即为 函数的根。 如果 f(c) 不为 0, 取 f(a) 和 f(b) 当中与 f(c) 异号的那一个值构建新的求根区间,继续计算中间点求根。
f = lambda x: x**2 + 3*x -10
scipy.optimize.bisect
直接求解
f参数: python function returning a number. f
must be continuous, and f(a) and f(b) must have opposite signs.
print (opt.bisect(f,-7,0), opt.bisect(f,0,5))
(-4.999999999999659, 1.9999999999998863)
手动实现
def my_bisect(f,a,b,tol=1e-10):
'''
Interval [a, b]
'''
if f(a)*f(b) > 0:
raise ValueError('f(a) and f(b) must have different signs!')
if a > b:
a, b = b, a
c = (a + b)/2.
if f(c) == 0 or (b - a)/2 < tol:
return c
while (b - a)/2. > tol:
if f(a)*f(c) > 0:
a = c
else:
b = c
c = (a + b)/2
return c
结果精度还可以
print (my_bisect(f,-7,0), my_bisect(f,0,5))
(-4.999999999978172, 2.0000000000436557)
牛顿迭代法
这里用牛顿迭代法实现函数求根。牛顿迭代法的基本思想就是用局部的线性近似,用切线代替曲线,其实是泰勒展开,只去线性项:$f(x+h)\approx f(x)+f'(x)h$
如果$x+h$是$f(x)=0$的一个根,即$f(x+h)=0$,则:$h\approx -\frac{f(x)}{f'(x)}$ 以及$x+h\approx x - \frac{f(x)}{f'(x)}$
参考用Python学微积分.
可以看到从$x0$到$x_1$到$x_2$的收敛。最后的迭代公式是$x{n+1}=xn-\frac{f(x_n)}{f'(x_n)}$,当$x{n+1}$与$x_n$足够接近的时候,表示结果收敛,可以停止迭代。
def my_newton(f,x0=0,tol=1e-8):
# x0 initial guess
while True:
x1 = x0 - f.subs(x,x0)/f.diff().subs(x,x0)
if np.abs(x1-x0) < tol:
break
x0 = x1
return float(x1)
x = symbols('x')
fsym = x**2 + 3*x - 10
# 需要画图猜出一个靠谱的初始值
for x0 in [-5,2]:
print my_newton(fsym,x0=x0)
-5.0
2.0
可以看出牛顿迭代法,精确度很高的。但是initial guess在-1.5的时候my_newton(f,x0=-1.5)
,导数值为0,还是会出现错误,出现分母为0,还需要进一步使方法更健壮,实现的有点简陋。
梯度下降法
梯度下降法是一个最优化算法,通常也称为最速下降法。最速下降法是求解无约束优化问题最简单和最古老的方法之一,虽然现在已经不具有实用性,但是许多有效算法都是以它为基础进行改进和修正而得到的。最速下降法是用负梯度方向为搜索方向的,最速下降法越接近目标值,步长越小,前进越慢。
Wiki上的解释为如果目标函数$F(x)$在点$a$处可微且有定义,那么函数$F(x)$在点$a$沿着梯度相反的方向$-\nabla F(a)$下降最快。其中,$\nabla$为梯度算子,$\nabla = (\frac{\partial}{\partial x_1}, \frac{\partial}{\partial x_2}, \ldots, \frac{\partial}{\partial x_n})^T$
什么是梯度下降法?
- 梯度下降法,可作为一种求解最小二乘法的方式,它是最优化中比较古老的一种方法
- 梯度下降,设定起始点负梯度方向 (即数值减小的方向) 为搜索方向,寻找最小值。梯度下降法越接近目标值,步长越小,前进越慢.
# 数学知识可得在对称轴取最小值
a = -1.5
print a**2 + 3*a - 10
-12.25
opt.minimize(f,-1)
fun: -12.25
hess_inv: array([[ 0.50000001]])
jac: array([ 0.])
message: 'Optimization terminated successfully.'
nfev: 12
nit: 2
njev: 4
status: 0
success: True
x: array([-1.50000001])
# 极值点
opt.minimize(f,-1).x
array([-1.50000001])
opt.minimize(f,-1).fun
-12.25
from scipy.optimize import basinhopping
basinhopping(f,x0,stepsize = 5).x
array([-1.49999998])
一维梯度下降算法
# local minimum occurs at x=1.5
# fsym = x**2 + 3*x - 10
def gd(fsym,x0,step=0.01,tol=1e-5):
x1 = x0
while True:
x0 = x1
x1 = x0 - step * fsym.diff().subs(x,x0)
if np.abs(x1-x0) < tol:
break
return x1
print gd(fsym,-1)
-1.49951076900151
二维算例
实现二维梯度下降法的实现,当然可以更也可以写general的方法,思路都是一样的。这里给出一个例子,并首先画图看看函数的性质。
def g(X):
x, y = X
return (x-1)**4 + 5 * (y-1)**2 - 2*x*y
X_opt = opt.minimize(g,(8, 3))
print X_opt
fun: -3.8672228877127943
hess_inv: array([[ 0.11159896, 0.02232105],
[ 0.02232105, 0.10446406]])
jac: array([ -8.94069672e-08, -2.98023224e-08])
message: 'Optimization terminated successfully.'
nfev: 72
nit: 12
njev: 18
status: 0
success: True
x: array([ 1.88292611, 1.37658521])
3D plot
from mpl_toolkits.mplot3d import Axes3D
from matplotlib import cm
fig = plt.figure()
ax = fig.gca(projection='3d')
X = np.linspace(-2,4,100)
Y = np.linspace(-2,4,100)
X, Y = np.meshgrid(X,Y)
Z = g((X,Y))
surf = ax.plot_surface(X, Y, Z, rstride=1, cstride=1, cmap=cm.coolwarm,
linewidth=0, antialiased=False)
cset = ax.contour(X, Y, Z, zdir='z',offset=-5, cmap=cm.coolwarm)
fig.colorbar(surf, shrink=0.5, aspect=5)
plt.show()
Contour
fig, ax = plt.subplots(figsize=(6, 4))
# get X, Y from above
c = ax.contour(X, Y, g((X, Y)), 50)
ax.plot(X_opt.x[0], X_opt.x[1], 'r*', markersize=15)
ax.set_xlabel(r"$x_1$", fontsize=18)
ax.set_ylabel(r"$x_2$", fontsize=18)
plt.colorbar(c, ax=ax)
fig.tight_layout()
用symbol,方便求导
x, y = symbols('x'), symbols('y')
def gsym(x,y):
return (x-1)**4 + 5 * (y-1)**2 - 2*x*y
# a = gsym().diff
gsym(x,y).diff(x)
-2*y + 4*(x - 1)**3
gsym(x,y).subs({x:1, y:0})
5
二维梯度下降算法
def gd_2d(f,X,step=0.01,tol=1e-3):
X0 = X
def gradient(f,X):
dx = float(f.diff(x).subs({x:X[0], y:X[1]}))
dy = float(f.diff(y).subs({x:X[0], y:X[1]}))
norm = (1./np.sqrt(dx**2+dy**2))
return norm*np.array([dx,dy])
while True:
z0 = float(f.subs({x:X0[0], y:X0[1]}))
X1 = X0 - step*gradient(f,X0)
z1 = float(f.subs({x:X1[0], y:X1[1]}))
if np.abs(z1 - z0) < tol:
break
X0 = X1
return X1
print gd_2d(gsym(x,y),[0,0])
[ 1.88360394 1.37674131]
X_opt.x
array([ 1.88292611, 1.37658521])