Параметры подгонки кривой в функции нескольких ODE - PullRequest
0 голосов
/ 03 апреля 2020

Я не хочу внедрять модель SEIR Влияние задержки диагностики на передачу COVID-19 (с небольшой модификацией) с использованием Python curve_fit и odeint . Без curve_fit мой код выглядит следующим образом:

import numpy as np
from scipy.integrate import odeint
import matplotlib.pyplot as plt

def func_ode(y,t,qS,qSq,betaV1,betaV2,beta1,beta2,e1,eta1,eta2,etaH,delta2,deltaH,theta2,f1,f2,fH,d):

    S_q,S,E1,E2,H,R,D,V=y

    dSq=qS*S-qSq*S_q
    dS=qSq*S_q-(betaV1*V+betaV2*V+beta1*E1+beta2*E2)*S
    dE1=(betaV1*V+beta1*E1)*S-(e1+eta1)*E1
    dE2=(betaV2*V+beta2*E2)*S+e1*E1-(eta2+delta2+theta2)*E2
    dH=theta2*E2-(etaH+deltaH)*H # theta is for recovered
    dR=eta1*E1+eta2*E2+etaH*H # eta is for recovered
    dD=delta2*E2+deltaH*H # delta is for death
    dV=f1*E1+f2*E2+fH*H-d*V 

    dy=[dSq, dS, dE1, dE2, dH, dR, dD, dV]
    return dy

if __name__ == "__main__":
    ## Parameters (dummy)
    qS, qSq, betaV1, betaV2, beta1, beta2, e1, eta1, eta2, etaH, delta2, deltaH, theta2, f1, f2, fH, d = \
        0, 1e-4, 4e-9, 1e-9, 4e-9, 1e-9, 1/100, 1/21, 1/104, 1/10, 1/200, 1/10400, 1/3.5, 1400, 1000, 1700, 144

    ## Initial (dummy)
    y_0=[1000,100000000,10,1,0,0,0,100]

    ## Solve
    t= np.linspace(1,60,60)
    result=odeint(func_ode,y_0,t,args=(qS,qSq,betaV1,betaV2,beta1,beta2,e1,eta1,eta2,etaH,delta2,deltaH,theta2,f1,f2,fH,d))

    ## Plot
    plt.plot(t, result[:, 0], label='Sq')
    plt.plot(t, result[:, 1], label='S')
    plt.plot(t, result[:, 2], label='E1')
    plt.plot(t, result[:, 3], label='E2')
    plt.plot(t, result[:, 4], label='H')
    plt.plot(t, result[:, 5], label='R')
    plt.plot(t, result[:, 6], label='D')
    plt.plot(t, result[:, 7], label='V')
    plt.legend(loc='best')
    plt.xlabel('t')
    plt.grid()
    plt.show()
    pass

Чтобы использовать оптимизированные параметры с входными данными, это мой не работает код:

import numpy as np
from scipy.integrate import odeint
import matplotlib.pyplot as plt
from scipy.optimize import curve_fit
import os
import pandas as pd

def func_ode(y,t,qS,qSq,betaV1,betaV2,beta1,beta2,e1,eta1,eta2,etaH,delta2,deltaH,theta2,f1,f2,fH,d):

    S_q,S,E1,E2,H,R,D,V=y

    dSq=qS*S-qSq*S_q
    dS=qSq*S_q-(betaV1*V+betaV2*V+beta1*E1+beta2*E2)*S
    dE1=(betaV1*V+beta1*E1)*S-(e1+eta1)*E1
    dE2=(betaV2*V+beta2*E2)*S+e1*E1-(eta2+delta2+theta2)*E2
    dH=theta2*E2-(etaH+deltaH)*H # theta is for recovered
    dR=eta1*E1+eta2*E2+etaH*H # eta is for recovered
    dD=delta2*E2+deltaH*H # delta is for death
    dV=f1*E1+f2*E2+fH*H-d*V 

    dy=[dSq, dS, dE1, dE2, dH, dR, dD, dV]
    return dy

def func_y(t,qS,qSq,betaV1,betaV2,beta1,beta2,e1,eta1,eta2,etaH,delta2,deltaH,theta2,f1,f2,fH,d,y_0):
    """
    Solution to the ODE y'(t) = f(t,y,parameters) with initial condition y(0) = y_0
    """
    y = odeint(func_ode, y_0, t, args=(qS,qSq,betaV1,betaV2,beta1,beta2,e1,eta1,eta2,etaH,delta2,deltaH,theta2,f1,f2,fH,d))
    return y[1:,:]

if __name__ == "__main__":
    file_name='Data Dummy.xlsx'
    current_path=os.getcwd()
    file_path=os.path.join(current_path,file_name)
    sheet_name='Sheet1'

    df_raw=pd.read_excel(file_path,sheet_name=sheet_name)
    numpy_data=df_raw[[
        'Self-quarantine susceptible',
        'Susceptible',
        'E1 (OTG)',
        'E2 (ODP)',
        'H (Hospitalized: PDP + Positif)',
        'R (Sembuh)',
        'D (Meninggal)',
        'V (Virus)'
    ]].to_numpy()               

    ## Parameters (dummy)
    qS, qSq, betaV1, betaV2, beta1, beta2, e1, eta1, eta2, etaH, delta2, deltaH, theta2, f1, f2, fH, d = \
        0, 1e-4, 4e-9, 1e-9, 4e-9, 1e-9, 1/100, 1/21, 1/104, 1/10, 1/200, 1/10400, 1/3.5, 1400, 1000, 1700, 144

    # Used Data
    y_0=numpy_data[0,:].tolist()
    numpy_data=numpy_data[1:60,:]

    ## Reference Time
    number_of_reference_time,_=np.shape(numpy_data)

    ## Solve
    param = qS, qSq, betaV1, betaV2, beta1, beta2, e1, eta1, eta2, etaH, delta2, deltaH, theta2, f1, f2, fH, d, y_0
    t= np.linspace(1,number_of_reference_time,number_of_reference_time)
    popt, cov = curve_fit(func_y, t, numpy_data,p0=[param])

    qS,qSq,betaV1,betaV2,beta1,beta2,e1,eta1,eta2,etaH,delta2,deltaH,theta2,f1,f2,fH,d = popt

    ## Check Result
    result=odeint(func_ode,y_0,t,args=(qS,qSq,betaV1,betaV2,beta1,beta2,e1,eta1,eta2,etaH,delta2,deltaH,theta2,f1,f2,fH,d))

    ## Plot
    plt.plot(t, result[:, 0], label='Sq')
    plt.plot(t, result[:, 1], label='S')
    plt.plot(t, result[:, 2], label='E1')
    plt.plot(t, result[:, 3], label='E2')
    plt.plot(t, result[:, 4], label='H')
    plt.plot(t, result[:, 5], label='R')
    plt.plot(t, result[:, 6], label='D')
    plt.plot(t, result[:, 7], label='V')
    plt.legend(loc='best')
    plt.xlabel('t')
    plt.grid()
    plt.show()
    pass

Результат ошибки показывает:

File "...\Programs\Python\Python37\lib\site-packages\scipy\optimize\minpack.py", line 458, in func_wrapped
    return func(xdata, *params) - ydata
ValueError: operands could not be broadcast together with shapes (58,8) (59,8)

Кажется, что curve_fit не может вместить odeint с несколькими графиками? Или я что-то здесь упускаю?

Редактировать: я редактирую фиксированные y[1:,:] в y.flatten() и popt, cov = curve_fit(func_y, t, numpy_data,p0=[param]) в popt, cov = curve_fit(func_y, t, numpy_data.flatten(),p0=[param]). Кроме того, измените ввод на numpy.array(list) Код можно увидеть в pastebin . Теперь проблема становится:

File "....py", line 164, in <module>
    popt, cov = curve_fit(func_y, t, numpy_data.flatten(),p0=[param])
  File "...\Python\Python37\lib\site-packages\scipy\optimize\minpack.py", line 752, in curve_fit  
    res = leastsq(func, p0, Dfun=jac, full_output=1, **kwargs)
  File "...\Python\Python37\lib\site-packages\scipy\optimize\minpack.py", line 396, in leastsq    
    gtol, maxfev, epsfcn, factor, diag)
TypeError: Cannot cast array data from dtype('O') to dtype('float64') according to the rule 'safe'

1 Ответ

1 голос
/ 03 апреля 2020

Есть несколько проблем: во-первых, в сообщении об ошибке говорится, что два массива ydata и func(xdata, *params) имеют разную форму: (59, 8) и (58, 8). Это может быть связано с тем, что ваши func_y имеют:

 return y[1:,:]

Но также: вам, вероятно, нужно будет "сгладить" ваши данные y и результат от функции модели, чтобы они были одномерными (472 наблюдения ), так что у вас есть func_y do:

 return y.flatten()

и вы запускаете curve_fit с

 popt, cov = curve_fit(func_y, t, numpy_data.flatten(), p0=[param])

Но есть еще одна концептуальная проблема (AFAIK) curve_fit не может справиться. Похоже, что последний аргумент вашей функции func_y(), y_0 представляет собой массив из 8 элементов и должен был быть нижней границей интеграции ODE, а , а не - переменным параметром в Кривая примерка. curve_fit ожидает, что 1-й аргумент функции модели будет 1-м массивом «независимой переменной» (здесь t), а все аргументы будут скалярными величинами, которые станут переменными в подгонке.

Когда вы делаете

param = qS, qSq, betaV1, betaV2, beta1, beta2, e1, eta1, eta2, etaH, delta2, deltaH, theta2, f1, f2, fH, d , y_0

вы делаете кортеж, который имеет 17 переменных и 1 массив из 8 элементов y_0. curve_fit будет делать numpy.array(param), ожидая, что каждый элемент param будет скалярным. С последним элементом, являющимся списком или массивом, это дает массив объектов, который выдает сообщение об ошибке, которое вы видите.

Для лучшей организации параметров и подгонки результатов, включая именованные параметры, которые можно легко исправить или задать границы, и продвинутые методы для изучения неопределенностей в значениях параметров и прогнозах, вы можете рассмотреть возможность использования lmfit (https://lmfit.github.io/lmfit-py/). В частности, lmfit.Model - это класс для подгонки кривой, который будет идентифицировать аргументы вашей функции по имени. Для вашего примера важно, что он допускает наличие нескольких независимых переменных и позволяет этим независимым переменным иметь любой тип Python (не ограничиваясь 1d-массивами). lmfit.Model также делает выравнивание для вас. С lmfit ваш пример кода будет выглядеть следующим образом:

import numpy as np
from scipy.integrate import odeint
import matplotlib.pyplot as plt
from lmfit import Model

def func_ode(y,t,qS,qSq,betaV1,betaV2,beta1,beta2,e1,
             eta1,eta2,etaH,delta2,deltaH,theta2,f1,f2,fH,d):

    Sq,S,E1,E2,H,R,D,V=y

    dSq=qS*S-qSq*Sq
    dS=qSq*Sq-(betaV1*V+betaV2*V+beta1*E1+beta2*E2)*S
    dE1=(betaV1*V+beta1*E1)*S-(e1+eta1)*E1
    dE2=(betaV2*V+beta2*E2)*S+e1*E1-(eta2+delta2+theta2)*E2
    dH=theta2*E2-(etaH+deltaH)*H # theta is for recovered
    dR=eta1*E1+eta2*E2+etaH*H # eta is for recovered
    dD=delta2*E2+deltaH*H # delta is for death
    dV=f1*E1+f2*E2+fH*H-d*V

    dy=[dSq, dS, dE1, dE2, dH, dR, dD, dV]
    return dy

numpy_data=np.array([....  ]) # taken from your pastebin example

def func_y(t, qS, qSq, betaV1, betaV2, beta1, beta2, e1, eta1, 
           eta2, etaH, delta2, deltaH, theta2, f1, f2, fH, d, y_0):
    """
    Solution to the ODE y'(t) = f(t, y, parameters) with 
    initial condition y(0) = y_0
    """
    return  odeint(func_ode, y_0, t, args=(qS, qSq, betaV1, betaV2,
                                           beta1, beta2, e1, eta1,
                                           eta2, etaH, delta2, deltaH,
                                           theta2, f1, f2, fH, d))


y_0 = numpy_data[0,:].tolist()
numpy_data = numpy_data[1:60,:]
number_of_reference_time, _ = np.shape(numpy_data)
t = np.linspace(1, number_of_reference_time, number_of_reference_time)

# create a model from your function, identifying the names of the 
# independent variables (arguments to not treat as variables in the fit)
omodel = Model(func_y, independent_vars=['t', 'y_0'])

# make parameters for this model, using the argument names from 
# your model function
params = omodel.make_params(qS=0, qSq=1e-4, betaV1=4e-9, betaV2=1e-9,
                            beta1=4e-9, beta2=1e-9, e1=1/100,
                            eta1=1/21, eta2=1/104, etaH=1/10,
                            delta2=1/200, deltaH=1/10400, theta2=1/3.5,
                            f1=1400, f2=1000, fH=1700, d=144)

# do the fit to `data` with `parameters` and passing in the 
# independent variables explicitly
result = omodel.fit(numpy_data, params, t=t, y_0=y_0)

# print out fit statistics, best fit values, estimated uncertainties
# note: best fit parameters will be in `result.params['qS']`, etc
print(result.fit_report(min_correl=0.5))

# Plot the portions of the best fit results
plt.plot(t, result.best_fit[:, 0], label='Sq')
plt.plot(t, result.best_fit[:, 1], label='S')
plt.plot(t, result.best_fit[:, 2], label='E1')
plt.plot(t, result.best_fit[:, 3], label='E2')
plt.plot(t, result.best_fit[:, 4], label='H')
plt.plot(t, result.best_fit[:, 5], label='R')
plt.plot(t, result.best_fit[:, 6], label='D')
plt.plot(t, result.best_fit[:, 7], label='V')
plt.legend(loc='best')
plt.xlabel('t')
plt.grid()
plt.show()

Это распечатает отчет

[[Model]]
    Model(func_y)
[[Fit Statistics]]
    # fitting method   = leastsq
    # function evals   = 162
    # data points      = 472
    # variables        = 17
    chi-square         = 4.1921e+18
    reduced chi-square = 9.2134e+15
    Akaike info crit   = 17367.1373
    Bayesian info crit = 17437.8060
[[Variables]]
    qS:     -0.01661105 +/- 0.00259955 (15.65%) (init = 0)
    qSq:     1.2272e-04 +/- 2.5428e-05 (20.72%) (init = 0.0001)
    betaV1:  4.5773e-09 +/- 6.9243e-10 (15.13%) (init = 4e-09)
    betaV2:  7.6846e-10 +/- 1.7084e-10 (22.23%) (init = 1e-09)
    beta1:   1.3770e-10 +/- 8.4682e-12 (6.15%) (init = 4e-09)
    beta2:   6.0831e-10 +/- 1.1471e-10 (18.86%) (init = 1e-09)
    e1:      0.04271070 +/- 0.00378279 (8.86%) (init = 0.01)
    eta1:    0.00182043 +/- 3.7579e-04 (20.64%) (init = 0.04761905)
    eta2:   -0.01052990 +/- 5.4028e-04 (5.13%) (init = 0.009615385)
    etaH:    0.12337818 +/- 0.01710376 (13.86%) (init = 0.1)
    delta2:  0.00644283 +/- 5.9399e-04 (9.22%) (init = 0.005)
    deltaH:  9.0316e-05 +/- 4.1630e-05 (46.09%) (init = 9.615385e-05)
    theta2:  0.22640213 +/- 0.06697444 (29.58%) (init = 0.2857143)
    f1:      447.226564 +/- 88.1621816 (19.71%) (init = 1400)
    f2:     -240.442614 +/- 30.5435276 (12.70%) (init = 1000)
    fH:      3780.95590 +/- 543.897368 (14.39%) (init = 1700)
    d:       173.743295 +/- 24.3128286 (13.99%) (init = 144)
[[Correlations]] (unreported correlations are < 0.500)
    C(qS, deltaH)     = -0.889
    C(etaH, theta2)   = -0.713
    C(betaV1, f1)     = -0.692
    C(beta1, beta2)   = -0.681
    C(betaV2, etaH)   = -0.673
    C(qS, eta2)       = -0.652
    C(deltaH, d)      = -0.651
    C(betaV1, theta2) =  0.646
    C(f1, d)          =  0.586
    C(eta2, deltaH)   =  0.585
    C(betaV2, d)      =  0.582
    C(qSq, betaV1)    = -0.523
    C(betaV2, f1)     =  0.510

и создаст график, подобный этому:

enter image description here

Я не знаю, подходит ли вам это самое лучшее, но надеюсь, это поможет вам начать.

...