web-dev-qa-db-ja.com

scipyは最適化せず、「精度の低下により必ずしも望ましいエラーは達成されない」を返します

対数尤度関数を最小化しようとする次のコードがあります。

#!/usr/bin/python
import math
import random
import numpy as np
from scipy.optimize import minimize

def loglikelihood(params, data):
    (mu, alpha, beta) = params
    tlist = np.array(data)
    r = np.zeros(len(tlist))
    for i in xrange(1,len(tlist)):
        r[i] = math.exp(-beta*(tlist[i]-tlist[i-1]))*(1+r[i-1])
    loglik  = -tlist[-1]*mu
    loglik = loglik+alpha/beta*sum(np.exp(-beta*(tlist[-1]-tlist))-1)
    loglik = loglik+np.sum(np.log(mu+alpha*r))
    return -loglik

atimes = [ 148.98894201,  149.70253172,  151.13717804,  160.35968355,
        160.98322609,  161.21331798,  163.60755544,  163.68994973,
        164.26131871,  228.79436067]
a= 0.01
alpha = 0.5
beta = 0.6
print loglikelihood((a, alpha, beta), atimes)

res = minimize(loglikelihood, (0.01, 0.1,0.1), method = 'BFGS',args = (atimes,))
print res

それは私に与えます

28.3136498357
./test.py:17: RuntimeWarning: invalid value encountered in log
  loglik = loglik+np.sum(np.log(mu+alpha*r))
   status: 2
  success: False
     njev: 14
     nfev: 72
 hess_inv: array([[1, 0, 0],
       [0, 1, 0],
       [0, 0, 1]])
      fun: 32.131359359964378
        x: array([ 0.01,  0.1 ,  0.1 ])
  message: 'Desired error not necessarily achieved due to precision loss.'
      jac: array([ -2.8051672 ,  13.06962156, -48.97879982])

パラメータがまったく最適化されておらず、最小化された値32が28よりも大きいことに注意してください。これは、a = 0.01、alpha = 0.5、beta = 0.6で得られる値です。この問題は、より適切な初期推測を選択することで回避できる可能性がありますが、そうであれば、どうすればこれを自動的に実行できますか?

12
felix

私はあなたの例をコピーして少し試しました。 BFGSソルバーを使い続けると、数回の反復の後、mu+ alpha * rはいくつかの負の数を持ち、それがRuntimeWarningを取得する方法です。

私が考えることができる最も簡単な修正は、ネルダーミードソルバーに切り替えることです。

res = minimize(loglikelihood, (0.01, 0.1,0.1), method = 'Nelder-Mead',args = (atimes,))

そしてそれはあなたにこの結果を与えるでしょう:

28.3136498357
  status: 0
    nfev: 159
 success: True
     fun: 27.982451280648817
       x: array([ 0.01410906,  0.68346023,  0.90837568])
 message: 'Optimization terminated successfully.'
     nit: 92
14
JimmyK

Log()関数の負の値に注意し、それらを解決して、ペナルティを追加することにより、それらが悪いことをオプティマイザに伝えます。

#!/usr/bin/python
import math
import random
import numpy as np
from scipy.optimize import minimize

def loglikelihood(params, data):
    (mu, alpha, beta) = params
    tlist = np.array(data)
    r = np.zeros(len(tlist))
    for i in xrange(1,len(tlist)):
        r[i] = math.exp(-beta*(tlist[i]-tlist[i-1]))*(1+r[i-1])
    loglik = -tlist[-1]*mu
    loglik += alpha/beta*sum(np.exp(-beta*(tlist[-1]-tlist))-1)
    argument = mu + alpha * r
    limit = 1e-6
    if np.min(argument) < limit:
        # add a penalty for too small argument of log
        loglik += np.sum(np.minimum(0.0, argument - limit)) / limit
        # keep argument of log above the limit
        argument = np.maximum(argument, limit)
    loglik += np.sum(np.log(argument))
    return -loglik

atimes = [ 148.98894201,  149.70253172,  151.13717804,  160.35968355,
        160.98322609,  161.21331798,  163.60755544,  163.68994973,
        164.26131871,  228.79436067]
a= 0.01
alpha = 0.5
beta = 0.6
print loglikelihood((a, alpha, beta), atimes)

res = minimize(loglikelihood, (0.01, 0.1,0.1), method = 'BFGS',args = (atimes,))
print res
3
optimizer

別の解決策(私にとってはうまくいった)は、関数(および勾配)を0に近い値にスケーリングすることです。たとえば、60kポイントの対数尤度を評価しなければならないときに問題が発生しました。これは、私の対数尤度が非常に大きいことを意味しました。概念的には、対数尤度は非常にスパイキーな関数でした。

勾配は最初から大きくなり(このスパイキーマウンテンを登るため)、次に適度に小さくなりましたが、BGFSルーチンのデフォルトのgtolパラメーター(これは、すべての勾配が終了のために下でなければならないしきい値)を下回ることはありません。 。また、この時点で基本的に正しい値に到達していました(生成されたデータを使用していたため、真の値がわかりました)。

起こっていたのは、私の勾配が約だったということでした。 60k * average individual gradient value、およびaverage individual gradient valueが小さい場合でも、1e-8未満と言う、60k * 1e-8> gtol。そのため、解決策にたどり着いても、しきい値を満足することはありませんでした。

概念的には、この非常にスパイキーな山のため、アルゴリズムは小さなステップを作成していましたが、ステッピングover真の最小値であり、決して達成されませんaverage individual gradient << 1e-8これは、私の勾配がgtol

2つのソリューション:

1)対数尤度と勾配を1/nのような係数でスケーリングします。ここで、nはサンプル数です。

2)gtolをスケーリングします。例:"gtol": 1e-7 * n

3

同じ警告が表示されたので、引数やデータではなくlog(params)およびlog(data)を引数として取得するように対数尤度関数を書き換えることで解決しました。

したがって、可能であれば、尤度関数またはヤコビアンでnp.log()を使用しないようにします。

3
Sahar