numpy实现等步长4阶Runge Kutta算法并利用中国疫情峰值后数据拟合SEIR模型参数

numpy实现等步长4阶Runge Kutta算法用来求解SEIR模型

1. Runge Kutta数值方法

《数值计算方法》(第2版)清华大学出版社[1]给出了一个未知数的4阶Runge Kutta公式:
{ y n + 1 = y n + h 6 ( k 1 + 2 k 2 + 2 k 3 + k 4 ) , k 1 = h f ( x n , y n ) , k 2 = h f ( x n + h 2 , y n + k 1 2 ) , k 3 = h f ( x n + h 2 , y n + k 2 2 ) , k 4 = h f ( x n + h 2 , y n + k 3 2 ) , \begin{cases} y_{n+1} = y_n + \frac{h}{6}(k_1+2k_2+2k_3+k_4),\\k_1=hf(x_n,y_n),\\ k_2=hf(x_n+\frac{h}{2},y_n+\frac{k_1}{2}),\\ k_3=hf(x_n+\frac{h}{2},y_n+\frac{k_2}{2}),\\ k_4=hf(x_n+\frac{h}{2},y_n+\frac{k_3}{2}), \end{cases} yn+1=yn+6h(k1+2k2+2k3+k4),k1=hf(xn,yn),k2=hf(xn+2h,yn+2k1),k3=hf(xn+2h,yn+2k2),k4=hf(xn+2h,yn+2k3),
对于两个未知数的微分方程组
{ d y d x = f ( x , y , z ) , y ( x 0 ) = y 0 , d z d x = g ( x , y , z ) , z ( x 0 ) = z 0 \begin{cases} \frac{\mathrm dy}{\mathrm dx}=f(x,y,z),\quad y(x_0)=y_0,\\ \frac{\mathrm dz}{\mathrm dx}=g(x,y,z),\quad z(x_0)=z_0\\ \end{cases} {dxdy=f(x,y,z),y(x0)=y0,dxdz=g(x,y,z),z(x0)=z0
Runge Kutta公式变为:
{ y n + 1 = y n + h 6 ( k 1 + 2 k 2 + 2 k 3 + k 4 ) , z n + 1 = z n + h 6 ( m 1 + 2 m 2 + 2 m 3 + m 4 ) , k 1 = h f ( x n , y n , z n ) , k 2 = h f ( x n + h 2 , y n + k 1 2 , z n + m 1 2 ) , k 3 = h f ( x n + h 2 , y n + k 2 2 , z n + m 2 2 ) , k 4 = h f ( x n + h , y n + k 3 , z n + m 3 ) , m 1 = h g ( x n , y n , z n ) , m 2 = h g ( x n + h 2 , y n + k 1 2 , z n + m 1 2 ) , m 3 = h g ( x n + h 2 , y n + k 2 2 , z n + m 2 2 ) , m 4 = h g ( x n + h , y n + k 3 , z n + m 3 ) , n = 0 , 1 , 2 , ⋯ \begin{cases} y_{n+1} = y_n + \frac{h}{6}(k_1+2k_2+2k_3+k_4),\\ z_{n+1} = z_n + \frac{h}{6}(m_1+2m_2+2m_3+m_4),\\ k_1=hf(x_n,y_n,z_n),\\ k_2=hf(x_n+\frac{h}{2},y_n+\frac{k_1}{2},z_n+\frac{m_1}{2}),\\ k_3=hf(x_n+\frac{h}{2},y_n+\frac{k_2}{2},z_n+\frac{m_2}{2}),\\ k_4=hf(x_n+h,y_n+k_3,z_n+m_3),\\ m_1=hg(x_n,y_n,z_n),\\ m_2=hg(x_n+\frac{h}{2},y_n+\frac{k_1}{2},z_n+\frac{m_1}{2}),\\ m_3=hg(x_n+\frac{h}{2},y_n+\frac{k_2}{2},z_n+\frac{m_2}{2}),\\ m_4=hg(x_n+h,y_n+k_3, z_n+m_3), \end{cases} n=0,1,2,\cdots yn+1=yn+6h(k1+2k2+2k3+k4),zn+1=zn+6h(m1+2m2+2m3+m4),k1=hf(xn,yn,zn),k2=hf(xn+2h,yn+2k1,zn+2m1),k3=hf(xn+2h,yn+2k2,zn+2m2),k4=hf(xn+h,yn+k3,zn+m3),m1=hg(xn,yn,zn),m2=hg(xn+2h,yn+2k1,zn+2m1),m3=hg(xn+2h,yn+2k2,zn+2m2),m4=hg(xn+h,yn+k3,zn+m3),n=0,1,2,

1.1 MATLAB实现

以下是4阶Runge Kutta算法的MATLAB实现(对《数值计算方法》322页给出的MATLAB脚本略微修改得到):

function [T, Z] = rks4(F, a, b, Za, M)
  % f 是目标函数,[a b] 是左右端点, Za = [f1(a), ..., fn(a)] 是初始值
  % M 是步长(步数),T 是步长矢量,Z = [f1(t), ..., fn(t)] 是近似值

  h = (b -a)/ M;
  T = zeros(1, M+1);
  Z = zeros(M+1, length(Za));
  T = a: h: b;
  Z(1,:) = Za;
  for i = 1: M
    k1 = h* feval(F, T(i), Z(i,:))';
    k2 = h* feval(F, T(i)+h/2, Z(i,:)+k1/2)';
    k3 = h* feval(F, T(i)+h/2, Z(i,:)+k2/2)';
    k4 = h* feval(F, T(i)+h,   Z(i,:)+k3)';
    Z(i+1,:) = Z(i,:) + (k1 + 2*k2 + 2*k3 + k4)/6;
  end
  % T要和Z的维度一致,返回结果按行为每一步迭代结果、按列为各函数取值
  T = T';
end

1.2 python实现

把上面MATLAB脚本用numpy实现,编写成自己的包并在main()函数测试求解微分方程组
{ d x d t = x , x ( 0 ) = 1 , d y d t = y , y ( 0 ) = 1 \begin{cases} \frac{\mathrm dx}{\mathrm dt}=x,\quad x(0)=1,\\ \frac{\mathrm dy}{\mathrm dt}=y,\quad y(0)=1\\ \end{cases} {dtdx=x,x(0)=1,dtdy=y,y(0)=1
他的解为 x = y = e t x=y=e^t x=y=et

# -*-coding: utf-8-*-
# 四阶Runge Kutta方法
__all__ = ['rks4']
import numpy as np
# 包含在'''''' 或 """""" 之间且不属于任何语句的内容将被解释器认为是注释
"""
    Author: Guo
    Step identical 等步长 Runge Kutta 4
    Usage: tval, yval = rks4(dYdt, t_start, t_end, Y_t0, steps -1)
    Parameters: 
    f -- 要求函数f返回导数值的列向量(与MATLAB风格一致)
    Za -- 初始条件的numpy array. (np.array([f1_0, f2_0, f3_0, ...]))
    Return:
    t -- A column vector of t values at each step
    Z -- A matrix containing column vectors of all function values  at each step
"""

def rks4(f, a, b, Za, M):
    
    # M + 1 steps in total
    h = (b - a)/ M
    t = np.linspace(a, b, M + 1).reshape(M+1,1)
    Z = np.zeros((M+1, Za.size))
    Z[0,:] = Za
    # print(Z.shape,Z[:,0].shape, Z[0,:].shape)
    for i in range(1,M+1):
        k1 = h * f(t[i-1], np.transpose(Z[i-1,:]))
        k2 = h * f(t[i-1] +h/2, np.transpose(Z[i-1,:] +k1/2))
        k3 = h * f(t[i-1] +h/2, np.transpose(Z[i-1,:] +k2/2))
        k4 = h * f(t[i-1] +h, np.transpose(Z[i-1,:] +k3))
        # print(k4.shape)
        Z[i,:] = Z[i-1,:] + (k1 + 2*k2 + 2*k3 + k4)/6
#     print(t.shape, Z.shape, sep='\n')
    return (t, Z)

def main():
    # 微分方程 dxdt = x; dydt = y 
    # 返回dxdt、dydt的列向量
    ode = lambda t, y: np.array([y[0], y[1]]).T
    tval, yval = rks4(ode, 0 ,1, np.array([1,1]) ,5)
    # np.diff(ans[::1000,0])
    y = np.exp(np.linspace(0,1,yval.shape[0]))
    # print(yval.shape[0] == y.shape[0])
    print(np.hstack((tval, yval)))
    s = np.std(yval[:,0] - y)/np.mean(y) *100
    print(f"Raletive Error: {s}%")
    
if __name__ == '__main__':
    # main()
    # 运行main()函数 请注释掉此语句
    exit('Please use rks4 as module')
[[0.         1.         1.        ]
 [0.2        1.2214     1.2214    ]
 [0.4        1.49181796 1.49181796]
 [0.6        1.82210646 1.82210646]
 [0.8        2.22552083 2.22552083]
 [1.         2.71825114 2.71825114]]
Raletive Error: 0.00060603304842948%

4阶Runge Kutta公式的截断误差为 O ( h 5 ) O(h^5) O(h5),可以看到结果与书上介绍基本一致。

2. 用scipy.optimize least_sqares方法拟合SEIR模型参数并求解

2.1 scipy.optimize.least_squares(fun, x0)

  • 功能:给定计算残差的函数和超参数初始值,用最小二乘方法给出超参数的最优值(局部)。

  • fun:callable
    fun应当具备这样的基本形式: f u n ( x , ∗ a r g s , ∗ ∗ k w a r g s ) fun(x, *args, **kwargs) fun(x,args,kwargs)(就是说 l e a s t _ s q u a r e least\_square least_square函数的最小化过程对fun的第一个参数x进行),x必须是np.array类型。返回值必须也是一维数组np.array。
    官方文档还解释了fun的返回值为复数的处理方法: If the argument x is complex or the function fun returns complex residuals, it must be wrapped in a real function of real arguments, as shown at the end of the Examples section.

  • x0: np.array类型。给出参数初始值。

  • 返回值:

变量
含义
status: intThe reason for algorithm termination:
-1 : improper input parameters status returned from MINPACK.
0 : the maximum number of function evaluations is exceeded.
1 : gtol termination condition is satisfied.
2 : ftol termination condition is satisfied.
3 : xtol termination condition is satisfied.
4 : Both ftol and xtol termination conditions are satisfied.
message: strVerbal description of the termination reason.
success: boolTrue if one of the convergence criteria is satisfied (status > 0)

2.2 拟合SEIR模型参数的python脚本

# -*-coding: utf-8-*-
# 使用scipy.optimize.least_squares进行最小二乘法拟合参数
import sys
"""对于自己编写的模块可以通过sys.path.append()把对应路径加入python搜索模块路径"""
sys.path.append(r"package mcmutils所在路径名")

import pandas as pd
import numpy as np
import seaborn as sns
import matplotlib.pyplot as plt
import seaborn as sns
from mcmutils import odes

data = pd.read_csv(r"./china.csv")
# data.head()
N = 140005e4
I = np.array(data.existing)
R = np.array(data.recovered[:-6])
E = np.array(I[6:] - I[:-6])
I = I[:-6]
S = N*np.ones_like(E) - E - I - R
y = np.array([S, E, I, R])
# 注意numpy的索引是多维数组风格
st = np.argmax(y[2,:])
# st = np.argmax(np.diff(y[2,:]))
# # st = 0
y = np.transpose(y[:,st:])

def seir(t, y, arg, N):
    (beta, lamda, sigma, mu, alpha) = (arg[0], arg[1], arg[2], arg[3], arg[4])
    dS = -beta*lamda*(y[1]+y[2])*y[0]/N
    dE = beta*lamda*(y[1]+y[2])*y[0]/N - y[1]/sigma
    dI = y[1]/sigma - (mu+alpha)*y[2]
    dR = (mu+alpha)*y[2]
    # 按照MATLAB风格 返回导数值的列向量
    return np.array([dS, dE, dI, dR]).T

# 残差函数
def rfun(args, ydata, N):
    len = ydata.shape[0]
    y0 = ydata[0,:]
#     print(y0, len)

    _, yval = odes.rks4(lambda t,y: seir(t,y, args, N), 1, len, y0, len-1)
#     print(yval.shape)
#     return np.log(np.sum((yval[:,1:] - ydata)**2))
    weights = np.array([0.1,0.1,0.6,0.4])
    return np.sum(np.abs(yval - ydata)*weights, axis=1)

# print(y.shape)
# rfun(np.random.rand(5,1), y)
from scipy.optimize import least_squares
arg0 = np.array([0.1, 3, 7, 0.1, 1e-3])
optiargs = least_squares(lambda arg: rfun(arg, y, N), arg0)
print(optiargs)
len = y.shape[0]
y0 = y[0,:]
# print(y0)
tval, yval = odes.rks4(lambda t,y: seir(t,y, optiargs.x, N), 1, len, y0, len -1)

sns.set()
fig, ax = plt.subplots()
plt.plot(tval, yval[:,2], 'r--', linewidth=1.5, label='Fit')
plt.plot(tval, y[:,2], 'k--', linewidth=1.5, label='True')
plt.legend(loc='best')
plt.xlabel('days')
plt.ylabel('cases')
plt.title('SEIR model fitting')
err = np.std(yval[:,2]- y[:,2])/np.mean(y[:,2])*100
ax.text(0.4, 0.9, f'Relative error:\n{err:.{3}}%', transform=ax.transAxes)
plt.show()

2.3 求解结果

message: '`xtol` termination condition is satisfied.'
        nfev: 66
        njev: 43
  optimality: 261400320.2678833
      status: 3
     success: True
           x: array([0.11668877, 3.00386719, 2.3342517 , 1.39832263, 1.2959752 ])

在这里插入图片描述

参考资料

[1]. 《数值计算方法》(第2版)清华大学出版社293-322

  • 4
    点赞
  • 47
    收藏
    觉得还不错? 一键收藏
  • 12
    评论
好的,我了解了你的要求。对于你的问题,我会分两个部分来回答。 首先,关于随机森林算法的编写,我们可以使用Python中的NumPy库来实现。以下是一个简单的示例代码: ```python import numpy as np # 导入数据 data = fetch_california_housing() X = data.data y = data.target # 划分训练集和测试集 train_size = int(X.shape[0] * 0.7) X_train, y_train = X[:train_size], y[:train_size] X_test, y_test = X[train_size:], y[train_size:] # 构建随机森林回归模型 n_estimators = 100 max_depth = 10 min_samples_split = 2 min_samples_leaf = 1 max_features = int(np.sqrt(X.shape[1])) models = [] for i in range(n_estimators): # 随机采样训练集 idx = np.random.choice(train_size, train_size, replace=True) X_train_i, y_train_i = X_train[idx], y_train[idx] # 构建决策树 tree = DecisionTreeRegressor(max_depth=max_depth, min_samples_split=min_samples_split, min_samples_leaf=min_samples_leaf, max_features=max_features) # 拟合决策树 tree.fit(X_train_i, y_train_i) # 将决策树添加到模型列表中 models.append(tree) # 预测结果 y_pred = np.zeros(X_test.shape[0]) for tree in models: y_pred += tree.predict(X_test) y_pred /= n_estimators # 模型评分 score = r2_score(y_test, y_pred) print(f"模型评分:{score}") ``` 在这个示例中,我们使用了NumPy库来构建随机森林回归模型。我们首先将数据集划分为训练集和测试集,然后在训练集上随机采样,构建多个决策树。我们使用了scikit-learn库中的`DecisionTreeRegressor`类来构建决策树。在预测时,我们将所有决策树的预测结果取平均值作为最终预测结果。我们还使用了scikit-learn库中的`r2_score`函数来计算模型的评分。 接下来,我们来展示模型评分,并对比scikit-learn自带的评估器的建模结果。 对于加利福尼亚房价数据集,我们使用scikit-learn库中自带的随机森林回归器进行建模和预测: ```python from sklearn.ensemble import RandomForestRegressor from sklearn.datasets import fetch_california_housing from sklearn.model_selection import train_test_split from sklearn.metrics import r2_score # 导入数据 data = fetch_california_housing() X = data.data y = data.target # 划分训练集和测试集 X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.3, random_state=42) # 构建随机森林回归模型 rf = RandomForestRegressor(n_estimators=100, random_state=42) # 拟合模型 rf.fit(X_train, y_train) # 预测结果 y_pred = rf.predict(X_test) # 模型评分 score = r2_score(y_test, y_pred) print(f"模型评分:{score}") ``` 注意,在这个示例中,我们使用了scikit-learn库中的`train_test_split`函数将数据集划分为训练集和测试集。我们还使用了scikit-learn库中的`r2_score`函数来计算模型的评分。 总结一下,我们展示了如何使用NumPy库来编写随机森林算法,并在加利福尼亚房价数据集上进行了模型训练和预测。我们还展示了如何计算模型的评分,并与scikit-learn自带的评估器的建模结果进行了对比。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 12
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值