数学の具体的な問題にPythonを使って、数学もPythonも同時に学んでしまいましょう。今回はPythonの科学技術計算ライブラリであるSciPyを使った最適化問題の例として、関数の極値を求める問題を扱います。また補助的に数値計算やグラフの描画を用いるので、NumPy、matplotlibの使い方の復習にもなるかと思います。
関数の極値:1変数の場合
まず手始めに、次の1変数関数
ですから、関数 は
において極値をとります。グラフを描いてみましょう。
import numpy as np import matplotlib.pyplot as plt %matplotlib inline x_range = [-1.5, 1.5] y_range = [-1, 2] x = np.linspace(-1.5, 1.5, 60) y = - x**2 + x**4 plt.plot(x,y) plt.grid(True) plt.xlim(x_range) plt.ylim(y_range) plt.show()
より
と表示されます。特に、極小(かつ最小)となる点が
であることがわかります。これらの点での関数の値は
です。さて、適当に設定した初期位置の直近にある極小点は、SciPyの組み込み関数 minimize を使って求めることができます。いま初期位置として ととると、直近の極小点は のはずです。実際
from scipy.optimize import minimize def f(x): y = - x**2 + x**4 return y x_init = 1.0 minimize( f, x_init )
とすると
fun: -0.24999999999984324 hess_inv: array([[0.24908418]]) jac: array([-1.08964741e-06]) message: 'Optimization terminated successfully.' nfev: 14 nit: 5 njev: 7 status: 0 success: True x: array([0.7071065])
といった出力を得ます。出力の最後(.x)と最初(.fun)がそれぞれ直近の極小点の位置と、その点での関数の値です。
minimize( f, x_init ).x
minimize( f, x_init ).fun
上で求めていた結果と合っていることがわかります。
平面上の3点からの距離和の最小点(フェルマー点):2変数の場合
問題を少し複雑にしてみましょう。最適化問題の初等的な具体例として、フェルマー点を求める問題を考えてみます。フェルマー点とは、-平面上の適当に与えられた3点(例としてここでは
とします)を考え、点 をとるとき、各点O、P、Qから点Cまでの距離の和
を最小にするような点です。フェルマー点についての詳しい解説は
など、他の方による解説記事にまかせることにします。関数 の具体形はいまの場合
で与えられます。この関数を極小にするような点 を求めるのですが、このような場合にもSciPyの minimize を使うことができます。いま初期位置として
などと適当にとって
import numpy as np from scipy.optimize import minimize def D(c): d = np.sqrt(c[0]**2+c[1]**2) + np.sqrt((c[0]-1)**2+c[1]**2) \ + np.sqrt(c[0]**2+(c[1]-1)**2) return d c_init = [.3, .3] minimize(D, c_init)
とすると
が求める点であることが出力されます。初等幾何によって示すことのできるフェルマー点の性質から
となることが知られていますが、実際、ベクトル
を使って( として)
から確認することができます。
c_min = 0.211325 cos1 = (2*c_min**2-c_min)/(np.sqrt(2*c_min**2)*np.sqrt((1-c_min)**2+c_min**2)) cos2 = (2*c_min**2-2*c_min)/(np.sqrt((1-c_min)**2+c_min**2)*np.sqrt(c_min**2+(1-c_min)**2)) cos3 = (2*c_min**2-c_min)/(np.sqrt(c_min**2+(1-c_min)**2)*np.sqrt(c_min**2+c_min**2)) [cos1, cos2, cos3]
拘束条件のある場合
関数
を曲線
の上の点に制限して、その極値問題を考えましょう。いまの場合、曲線の式を について解いてしまって
として関数 に代入すれば
から
で極値をとることがわかります。実際に関数を図示してみましょう。
import matplotlib.pyplot as plt %matplotlib inline x_range = [-3, 3] y_range = [-6, 6] x = np.linspace(-3,3, 50) y = - (x-1)*(x+1)**2 plt.plot(x,y) plt.grid(True) plt.xlim(x_range) plt.ylim(y_range) plt.show()
より
といったグラフを得ます。特に極小点は
であることがわかります。いまの場合には拘束条件が簡単だったので式変形できてしまいましたが、より一般的にはラグランジュ未定乗数 を導入して新たに関数 を
と定義し、 を独立変数とみなして偏導関数
を考えて、これらについて連立式
を課す、という方法によって極値点を求めることができます(ラグランジュの未定乗数法)。このような拘束条件付きの場合にも minimize 関数は対応していて
import numpy as np from scipy.optimize import minimize def g(x): z = x[1] + x[0] * x[1] return z def con(x): c = x[0]**2 + x[1] -1 return c cons = {'type':'eq', 'fun': con} x_init = [-1., -1.] minimize(g, x_init, constraints=cons)
によって確かに
が極小であることが出力されます。
いかがだったでしょうか。今回は、最適化問題をSciPyで扱う方法、特に組み込み関数 minimize の使い方の初歩について解説してみました。