web-dev-qa-db-ja.com

scipy盆地ホッピング最適化関数を理解するための例

Scipyで ベイスンホッピングアルゴリズム に遭遇し、その使用方法を理解するための簡単な問題を作成しましたが、その問題では正しく機能していないようです。私は完全に間違ったことをしているのかもしれません。

コードは次のとおりです。

_import scipy.optimize as spo
import numpy as np
minimizer_kwargs = {"method":"BFGS"}    
f1=lambda x: (x-4)
def mybounds(**kwargs):
    x = kwargs["x_new"]
    tmax = bool(np.all(x <= 1.0))
    tmin = bool(np.all(x >= 0.0))
    print x
    print tmin and tmax
    return tmax and tmin


def print_fun(x, f, accepted):
      print("at minima %.4f accepted %d" % (f, int(accepted)))
x0=[1.]     
spo.basinhopping(f1,x0,accept_test=mybounds,callback=print_fun,niter=200,minimizer_kwargs=minimizer_kwargs)
_

それが与える解決策はx: array([ -1.80746874e+08])です

13
akhil

テストしている関数は、メトロポリス・ヘイスティングスと呼ばれるアプローチを利用します。これは、確率論的な方法で関数を最適化できるシミュレーテッドアニーリングと呼ばれる手順に変更できます。

これが機能する方法は次のとおりです。まず、ポイントx0のようにポイントを選択します。その時点から、ランダムな摂動を生成します(これは「提案」と呼ばれます)。提案された摂動があると、摂動を現在のアウトに適用することにより、新しいポイントの候補を取得します。したがって、x1 = x0 + perturbationのように考えることができます。

通常の古い勾配降下法では、perturbation項は、勾配の方向へのステップのように、決定論的に計算された量にすぎません。しかし、メトロポリス・ヘイスティングスでは、perturbationがランダムに生成されます(ランダムに移動する場所についての手がかりとして勾配を使用することもありますが、手がかりがないままランダムに生成されることもあります)。

x1を取得したこの時点で、「x0をランダムに混乱させて良いことをしたのか、それともすべてを台無しにしたのか」と自問する必要があります。その一部は、mybounds関数などのいくつかの境界の内側に固執することに関係しています。それの別の部分は、目的関数の値が新しい時点でどれだけ良く/悪くなったかに関係しています。

したがって、twox1の提案を拒否する方法があります。まず、設定した範囲に違反し、問題の定義によって実行不可能なポイントになる可能性があります。第二に、メトロポリス・ヘイスティングスの承認/拒否評価ステップから、拒否されるべき非常に悪い点である可能性があります。いずれの場合も、x1を拒否し、代わりにx1 = x0を設定して、同じ場所にとどまって再試行したかのように見せかけます。

グラデーションタイプの方法とは対照的に、間違いなく、何があっても、常に少なくともいくつかの種類の動きをします(グラデーションの方向)。

ふぅ、大丈夫。それはさておき、これがbasinhopping関数でどのように機能するかを考えてみましょう。ドキュメントから、典型的な受け入れ条件はtake_step引数によってアクセスされ、ドキュメントには次のように記載されていることがわかります。「デフォルトのステップ実行ルーチンは座標のランダムな変位ですが、他のステップ実行アルゴリズムの方が優れている場合があります。一部のシステムでは。」したがって、mybounds境界チェッカーとは別に、関数は座標をランダムに変位させて、試行する新しいポイントを生成します。また、この関数の勾配は定数1であるため、(最小化のために)負の勾配の方向に常に同じ大きなステップを実行します。

実用的なレベルでは、これは、x1の提案されたポイントが常に間隔[0,1]のかなり外側になり、境界チェッカーが常にそれらを拒否することを意味します。

私があなたのコードを実行するとき、私はこれがいつも起こっているのを見ます:

In [5]: spo.basinhopping(f1,x0,accept_test=mybounds,callback=print_fun,niter=200,minimizer_kwargs=minimizer_kwargs)
at minima -180750994.1924 accepted 0
[ -1.80746874e+08]
False
at minima -180746877.5530 accepted 0
[ -1.80746873e+08]
False
at minima -180746877.3896 accepted 0
[ -1.80750991e+08]
False
at minima -180750994.7281 accepted 0
[ -1.80746874e+08]
False
at minima -180746878.2433 accepted 0
[ -1.80746874e+08]
False
at minima -180746877.5774 accepted 0
[ -1.80746874e+08]
False
at minima -180746878.3173 accepted 0
[ -1.80750990e+08]
False
at minima -180750994.3509 accepted 0
[ -1.80750991e+08]
False
at minima -180750994.6605 accepted 0
[ -1.80746874e+08]
False
at minima -180746877.6966 accepted 0
[ -1.80746874e+08]
False
at minima -180746877.6900 accepted 0
[ -1.80750990e+08]
False
at minima -180750993.9707 accepted 0
[ -1.80750990e+08]
False
at minima -180750994.0494 accepted 0
[ -1.80750991e+08]
False
at minima -180750994.5824 accepted 0
[ -1.80746874e+08]
False
at minima -180746877.5459 accepted 0
[ -1.80750991e+08]
False
at minima -180750994.6679 accepted 0
[ -1.80750991e+08]
False
at minima -180750994.5823 accepted 0
[ -1.80750990e+08]
False
at minima -180750993.9308 accepted 0
[ -1.80746874e+08]
False
at minima -180746878.0395 accepted 0
[ -1.80750991e+08]
False

# ... etc.

したがって、決してポポジションポイントを受け入れません。出力は、解決策が見つかったことを示していません。考えられる解決策を探求するためのランダムな摂動よりも、オプティマイザーにはますます見栄えのするポイントが得られますが、基準を満たさないままになっていることがわかります。 domyboundsを満たすポイントを取得するために[0,1]に戻る方法を見つけることができません。

34
ely

コード化した盆地ホッピングの動作は、摂動と局所最小化を組み合わせることです。

ローカル最適化ピースのために、ルーチンは許容できないものを生成し続けます。基本的に、使用しているBFGSルーチンは完全に制約がないため、負の無限大への勾配に従います。この結果は、チェッカーにフィードバックされます。

したがって、流域ホッピングポイントがどこにあるかは関係ありませんx1は、BFGS部分は常に大きな負の値になります。

ベンチマーク関数x - 4使用しているものは、ここでは理想的なターゲットではありません。例を確認してください。 Rastrigin関数 。実際に線形関数を最適化する必要がある場合は、それを行うためのアルゴリズムのクラス全体があります(Wikipediaの 線形計画法 を参照)。

6
Yike Lu

Yike Luはすでに問題を指摘しています。境界はトップレベルでのみ適用されますが、ローカルオプティマイザーBFGSはそれらについて何も知らないことを知っています。

一般に、最適化に「ハード」境界を使用することは、多くの場合、悪い戦略です。ほとんどのアルゴリズムでは、許可されたスペースの境界でアルゴリズムを直接最適化するパスがないため、境界に触れることが許可されます。終了します。上記のケース(x = 0)で、x = -0.000001を試してみて、少し行き過ぎて少し戻ってみないと、最適なものを見つけるのがいかに難しいかがわかります。現在、入力データを変換することでこれを実行できるアルゴリズムがあります(scipy.optimize、それらは引数として境界を受け入れるものです)、しかし一般的な解決策はこれです:

入力が許可された範囲外に移動した場合に非常に迅速に増加するようにコスト関数を更新します:

def f1(x):
    cost_raw = (x-4)
    if   x >= 1.0: cost_overrun = (1000*(x-1))**8
    Elif x <= 0.0: cost_overrun = (1000*(-x))**8
    else: cost_overrun = 0.0

    return(cost_raw + cost_overrun)

そうすれば、オプティマイザーはコスト関数が増加するのを確認し、境界を超えるとすぐに、許可されたスペースに戻ります。これは厳密な強制ではありませんが、オプティマイザはとにかく繰り返し近似しているため、必要な厳密さに応じて、ペナルティ関数を調整して、増加を多少あいまいにすることができます。オプティマイザーの中には、連続導関数(したがってべき関数)を好むものもあれば、固定ステップを喜んで処理するものもあります。その場合、範囲外のときはいつでも10000を追加できます。

2
Zak