众所周知的是,在大学课程中一般只会教授一种拟合方法(也即参数估计方法)——最小二乘法。这是一种直接求解的方法,非常的有效,不仅是损失最小解,而且是最大似然解。只不过,有一个缺点,它只能解决线性方程参数问题,对于非线性曲线,就无能为力了。大部分情况下还是将其转换成线性问题,再使用最小二乘法。
然而,并非所有的问题都能转换为线性问题,甚至并非所有目标建模公式的参数都能有解析解,其他学科如机器学习等学科如何解决这个参数估计问题?答案是——《最优化方法》,其中最常用的是梯度下降法,不去寻找解析解,而是寻找其导数/梯度。因为导数/梯度具有如下优点
我们的主要目标是
arg min θ L ( θ ) = 1 n ∑ i = 1 n ( y i − f θ ( x i ) ) 2 \argmin_\theta L(\theta)=\dfrac{1}{n}\sum_{i=1}^{n} (y_i-f_\theta(x_i))^2 θargminL(θ)=n1i=1∑n(yi−fθ(xi))2
这是最小二乘问题,也叫均方误差最小化问题。其中 θ \theta θ就是我们想要求的参数。 f θ ( x ) f_\theta(x) fθ(x)就是我们的模型,即带有参数 θ \theta θ的函数。 x i , y i x_i,y_i xi,yi分别是数据中第 i i i对自变量和因变量。 L ( θ ) L(\theta) L(θ)是损失函数,用来衡量我们拟合的效果。
而梯度下降法的主要内容是
θ ← θ − η ∇ θ L u n t i l ∇ θ L = 0 \theta \gets \theta- \eta\nabla_\theta L \quad until \quad \nabla_\theta L=0 θ←θ−η∇θLuntil∇θL=0
其中 η \eta η是学习率,目的是不要让 θ \theta θ变动幅度过大(特别是一些有限制的量,比如log函数的底数),导致不能收敛。 ∇ θ \nabla_\theta ∇θ是求对 θ \theta θ的梯度。取负号是因为梯度默认是增大函数值的方向,这里是最小化问题,所以取其相反数。由这里可知,梯度大小不是我们所需要的,我们只要梯度的方向。
关于logistics增长问题,是这样的,假设某生物种群数量记为 N N N,最开始为常数 N 0 N_0 N0。
假设有未知参数 r r r, K K K,其中 r r r是固定增长率,表明在没有任何限制的情况下种群数量的增长速度。 K K K是环境容纳量,是表明某个环境对生物的限制导致的最大容纳数量。
它们的关系如下
d N d t = r × N × ( 1 − N K ) \dfrac{dN}{dt}=r\times N\times(1-\dfrac{N}{K}) dtdN=r×N×(1−KN)
我们的数据是按照时间 t i t_i ti记录的种群数量 N i N_i Ni。我们的目标就是求出 r r r, K K K。
这是一条微分方程。关于它的原函数可以参考我写的另一篇文章
它的图像是这样的
其中 N 0 = 10 , r = 1 , K = 1000 N_0=10,r=1,K=1000 N0=10,r=1,K=1000。横坐标是时间 t t t,纵坐标是种群数量 N N N
首先,我们先明确实际输出与目标输出,按照logistic公式,我们的输出
f i = f ( t i , N i ) = r × N i × ( 1 − N i K ) f_i=f(t_i,N_i)=r\times N_i\times(1-\dfrac{N_i}{K}) fi=f(ti,Ni)=r×Ni×(1−KNi)
我们的目标
d N ( t i ) d t i ≈ N i + 1 − N i t i + 1 − t i \dfrac{dN(t_i)}{dt_i}\approx \dfrac{N_{i+1}-N_i}{t_{i+1}-t_i} dtidN(ti)≈ti+1−tiNi+1−Ni
如果我们更细究一点,有 ∃ t ξ i ∈ [ t i , t i + 1 ] , d N ( t ξ i ) d t ξ i = N i + 1 − N i t i + 1 − t i \exists t_{\xi_i}\in[t_i,t_{i+1}],\dfrac{dN(t_{\xi_i})}{dt_{\xi_i}}= \dfrac{N_{i+1}-N_i}{t_{i+1}-t_i} ∃tξi∈[ti,ti+1],dtξidN(tξi)=ti+1−tiNi+1−Ni。我们可以大约令 N ξ i = N i + 1 + N i 2 , t ξ i = t i + 1 + t i 2 N_{\xi_i}=\frac{N_{i+1}+N_i}{2},t_{\xi_i}=\frac{t_{i+1}+t_i}{2} Nξi=2Ni+1+Ni,tξi=2ti+1+ti,将点 ( t ξ i , N ξ i ) (t_{\xi_i},N_{\xi_i}) (tξi,Nξi)对应导数认为 N ξ i ′ ≈ N i + 1 − N i t i + 1 − t i N_{\xi_i}'\approx\dfrac{N_{i+1}-N_i}{t_{i+1}-t_i} Nξi′≈ti+1−tiNi+1−Ni。
那么损失函数
L ( r , K ) = 1 n − 1 ∑ i = 1 n − 1 ( N ξ i ′ − f ( t ξ i , N ξ i ) ) 2 = 1 n − 1 ∑ i = 1 n − 1 ( N ξ i ′ − f ξ i ) 2 L(r,K)=\dfrac{1}{n-1}\sum_{i=1}^{n-1} (N_{\xi_i}'-f(t_{\xi_i},N_{\xi_i}))^2=\dfrac{1}{n-1}\sum_{i=1}^{n-1} (N_{\xi_i}'-f_{\xi_i})^2 L(r,K)=n−11i=1∑n−1(Nξi′−f(tξi,Nξi))2=n−11i=1∑n−1(Nξi′−fξi)2
在logistics增长问题中,未知量是 r r r, K K K,所以我们的目标是求出 ∇ r L , ∇ K L \nabla_r L,\nabla_K L ∇rL,∇KL。
∇ r L = ∂ L ∂ r , ∇ K L = ∂ L ∂ K \nabla_r L=\dfrac{\partial L}{\partial r},\quad\nabla_K L=\dfrac{\partial L}{\partial K} ∇rL=∂r∂L,∇KL=∂K∂L
根据链式求导法则,我们有
∂ L ∂ r = ∑ i = 1 n − 1 ∂ L ∂ f ξ i ∂ f ξ i ∂ r \dfrac{\partial L}{\partial r}=\sum_{i=1}^{n-1}\dfrac{\partial L}{\partial f_{\xi_i}}\dfrac{\partial f_{\xi_i}}{\partial r} ∂r∂L=i=1∑n−1∂fξi∂L∂r∂fξi
同理 K K K也有类似形式。
而我们有
∂ L ∂ f ξ i = − 2 ( N ξ i ′ − f ξ i ) , ∂ f ξ i ∂ r = N ξ i ( 1 − N ξ i K ) , ∂ f ξ i ∂ K = r N ξ i 2 K 2 \dfrac{\partial L}{\partial f_{\xi_i}}=-2(N_{\xi_i}'-f_{\xi_i}),\quad\dfrac{\partial f_{\xi_i}}{\partial r}=N_{\xi_i}(1-\dfrac{N_{\xi_i}}{K}),\quad\dfrac{\partial f_{\xi_i}}{\partial K}=r\dfrac{N_{\xi_i}^2}{K^2} ∂fξi∂L=−2(Nξi′−fξi),∂r∂fξi=Nξi(1−KNξi),∂K∂fξi=rK2Nξi2
所以
∇ r L = ∂ L ∂ r = − 2 ∑ i = 1 n − 1 ( N ξ i ′ − f ( t ξ i , N ξ i ) ) N ξ i ( 1 − N ξ i K ) ∇ K L = ∂ L ∂ K = − 2 ∑ i = 1 n − 1 ( N ξ i ′ − f ( t ξ i , N ξ i ) ) r N ξ i 2 K 2 \nabla_r L=\dfrac{\partial L}{\partial r}=-2\sum_{i=1}^{n-1}(N_{\xi_i}'-f(t_{\xi_i},N_{\xi_i}))N_{\xi_i}(1-\dfrac{N_{\xi_i}}{K})\\ \nabla_K L=\dfrac{\partial L}{\partial K}=-2\sum_{i=1}^{n-1}(N_{\xi_i}'-f(t_{\xi_i},N_{\xi_i}))r\dfrac{N_{\xi_i}^2}{K^2}\\ ∇rL=∂r∂L=−2i=1∑n−1(Nξi′−f(tξi,Nξi))Nξi(1−KNξi)∇KL=∂K∂L=−2i=1∑n−1(Nξi′−f(tξi,Nξi))rK2Nξi2
首先是数据的生成
import numpy as np
N0=10
K_real=1000
r_real=1
c=math.log(N0/(K_real-N0))
#原函数
def ground_true(t):
return(K_real/(1+np.exp(-r_real*t-c)))
x=np.arange(0,10,0.1)
y=ground_true(x)
# dN/dt 目标输出
y_diff = (y[1:]-y[:-1])/(x[1:]-x[:-1])
# N_xi 处理后的输入
y_xi = (y[1:]+y[:-1])/2
# 这里不想用n-1,直接以n表示了
n = y_xi.shape[0]
原数据效果就是一开始给的图,而经过处理的数据则是如下效果
import matplotlib.pyplot as plt
plt.plot(x,y)
plt.plot(x[:-1],y_xi)
plt.show()
# 模型
def f(r, K, N):
return r*N*(1-N/K)
# loss函数
def loss_of_rK(r, K):
return 1/n*np.sum((y_diff-f(r, K, y_xi))**2)
# ∂L/∂r
def diff_r(r, K):
return -2.0/n*np.sum((y_diff-f(r, K, y_xi))*y_xi*(1-y_xi/K))
# ∂L/∂K
def diff_K(r, K):
return -2.0/n*np.sum((y_diff-f(r, K, y_xi))*r*(y_xi**2)/(K**2))
在选择梯度下降法的起点时,尽量靠近最终目标,收敛时间才短。
前面说过,梯度的大小不是我们所关心的内容,所以我加入了一个归一化函数 arctan \arctan arctan(归一化函数还有很多,如sigmoid等等)用来取出它的方向,用参数本身的绝对值当做步伐大小。(其实这比较像是强行把L2损失改为L1损失)
#初始化
rx = np.random.rand()
Kx = np.random.rand()*np.max(y_xi)#选接近最大值为初始值比较合理
# 学习率
eta = 1e-1
# 误差精度
epsilon= 1
while (loss_of_rK(rx, Kx) > epsilon):
rx = rx-eta*np.arctan(diff_r(rx, Kx))*np.abs(rx)
Kx = Kx-eta*np.arctan(diff_K(rx, Kx))*np.abs(Kx)
迭代了41610步,结果为
它的拟合轨迹如下,横轴为r,纵轴为K
需要注意的是,梯度下降法没有办法保证解是最优/解存在/解可达。也没有办法保证收敛。它只是一种凸优化手段。尽管如此,机器学习等学科仍然大量的使用它,俗称炼丹。