メインコンテンツへスキップ

準ニュートン法による最適化とPythonによる実装

·2104 文字·5 分
目次

はじめに
#

本記事では、BFGS公式の準ニュートン法について簡単に解説し、Pythonで実装した例を示す。実装は、数理工学社の「工学基礎 最適化とその応用」の4.8節「準ニュートン法」を参考にさせていただいた。

準ニュートン法は、再急降下法とニュートン法の両者の長所を持つような手法である。再急降下法は、収束速度が1次でしかない替わりに、初期点が解から離れていても収束する(大域的収束性)。一方、ニュートン法は、解近傍の収束速度は2次であるが、探索方向が評価関数の勾配方向と一致するとは限らないため、直線探索をそのまま利用できない。 両者の実装については、それぞれ以下の記事を参考。

本記事で使用したPythonとライブラリのバージョンは以下の通り。

バージョン
Python 3.8.5
NumPy 1.19.2
matplotlib 3.3.2

実装では、以下のようにライブラリをインポートしていることを前提とする。

import numpy as np
import matplotlib.pyplot as plt

準ニュートン法
#

概要
#

最小化したい関数を\(f(\bm{x})\)とする。 ニュートン法の\(k\)回目の更新において、解の更新方向\(\bm{d}_k\)は次の方程式の解として与えられる。

$$ \nabla ^2 f(\bm{x}_k) \bm{d}_k = - \nabla f(\bm{x}_k) $$

しかし、一般にヘッセ行列\(\nabla ^2 f(\bm{x}_k)\)が正定値である保証はない。したがって、ヘッセ行列は逆行列を持たない可能性があり、上記の方程式を\(\bm{d}_k\)について解けるとは限らない。

そこで、準ニュートン法ではヘッセ行列を適当な正定値行列で近似する。準ニュートン法には、ヘッセ行列を近似する方法と、ヘッセ行列の逆行列を近似する方法の2つがある。この記事では前者について述べる。

ヘッセ行列を近似する正定値行列を\(B_k\)として、解の更新方向\(\bm{d}_k\)を次の方程式の解として求める。

$$ B_k \bm{d} = - \nabla f(\bm{x}_k) $$

\(B_k\)を求めるには、勾配\(\nabla f(\bm{x})\)のテイラー展開(次式)を利用する。

$$ \nabla f(\bm{x}_k) = \nabla f(\bm{x}\_{k+1}) + \nabla^2 f(\bm{x}\_{k+1}) (\bm{x}_k - \bm{x}\_{k+1}) + ... $$

すなわち、以下の近似式が成り立つ。

$$ \nabla^2 f(\bm{x}\_{k+1}) (\bm{x}\_{k+1} - \bm{x}_k) \approx \nabla f(\bm{x}\_{k+1}) - \nabla f(\bm{x}_k) $$

さらに、

$$ \bm{s}_k = \bm{x}\_{k+1} - \bm{x}_k, \\ \bm{y}_k = \nabla f(\bm{x}\_{k+1}) - \nabla f(\bm{x}_k) $$

と置き、以下を満たすように近似行列\(B_{k+1}\)を求める。

$$ B_{k+1} \bm{s}_k = \bm{y}_k $$

この式をセカント条件と呼ぶ。

ヘッセ行列を近似する方法の1つとして、BFGS (Broyden–Fletcher–Goldfarb–Shanno) 法がある。この方法では、以下の公式を用いて近似行列を更新する。

$$ B_{k+1} = B_k - \frac{B_k \bm{s} (B_k \bm{s}_k)^\top}{\bm{s}_k^\top B_k \bm{s}_k} + \frac{\bm{y}_k \bm{y}_k^\top}{\bm{s}_k^\top \bm{y}_k } $$

初期行列\(B_0\)には通常は単位行列が用いられてる。

アルゴリズム
#

準ニュートン法のアルゴリズムは以下のようになる。

  1. 解の初期値\(x_0\), 初期行列\(B_0\)を用意
  2. \(B_k \bm{d}_k = - \nabla f(\bm{x}_k)\)を解いて解の更新方向\(\bm{d}_k\)を求める
  3. 直線探索によりステップ幅\(\alpha_k\)を求める
  4. \(\bm{x}\_{k+1} = \bm{x}_k + \alpha_k \bm{d}_k\)で解を更新
  5. BFGS公式で近似行列\(B\)を更新

解が収束するか、反復回数の上限に達するまで、上記のアルゴリズム2~5を繰り返す。

Pythonによる実装
#

BFGS公式を用いた準ニュートン法をPythonで実装した。 直線探索にはArmijo条件を用いた。

参考:直線探索を使った最急降下法をPythonで実装

目的関数
#

以下の評価関数を考える。

$$ f(\bm{x}) = 2x_0^2 + x_0 x_1 + x_1^2 $$

この関数は\((x_1, x_2)=(0, 0)\)で最小値0をとる。また、勾配ベクトルは次式となる。

$$ \nabla f(\bm{x}) = \begin{bmatrix} 4x_0 + x_1 \\ x_0 + 2x_1 \end{bmatrix} $$

評価関数と勾配ベクトルを返す関数を以下のように定義する。

def obj_func(x):
    """評価関数"""
    return 2*x[0]**2 + x[1]**2 + x[0]*x[1]

def obj_grad(x):
    """評価関数の勾配"""
    return np.array([4*x[0] + x[1], x[0] + 2*x[1]])

評価関数のプロットを示す。

x0 = np.arange(-4, 4, 0.1)
x1 = np.arange(-4, 4, 0.1)
X0, X1 = np.meshgrid(x0, x1)

Y = obj_func(np.dstack((X0,X1)).T)

fig, ax = plt.subplots()
contourSet = ax.contour(X0, X1, Y)
ax.clabel(contourSet, fontsize=12, fmt="%.1f")
ax.set_xlabel("x0")
ax.set_ylabel("x1")
ax.set_aspect('equal')
ax.grid()
plt.show()

評価関数
評価関数

準ニュートン法
#

準ニュートン法のアルゴリズムを以下に示す。 解の初期値を\((3, 3)\), 反復回数を10回とした。

x = [3, 3]
B = np.eye(len(x)) # ヘッセ行列の近似行列

tau = 0.9 # 直線探索のパラメータ
xi = 0.3 # 直線探索のパラメータ

grad = obj_grad(x)

for i in range(10):
    f = obj_func(x)
    d = - np.dot(np.linalg.inv(B), grad) # 探索方向
    
    # Armijo条件を用いた直線探索
    alpha = 1
    temp = xi*np.dot(grad, d)
    while obj_func(x+alpha*d) > f+alpha*temp:
        alpha *= tau
    
    x += alpha*d # 解の更新
    
    old_grad = grad
    grad = obj_grad(x)
    
    s = (alpha*d).reshape([-1,1]) # 解の変化量
    y = (grad - old_grad).reshape([-1,1]) # 勾配の変化量
    Bs = np.dot(B, s)
    
    B -= (Bs*Bs.T)/np.dot(s.T, Bs) - (y*y.T)/np.dot(s.T, y)
    # ヘッセ行列の近似行列を更新
    print(x)
#    print(B)

実行結果
#

解の推移を以下に示す。最適解\((0, 0)\)に収束していることが分かる。

[-1.70715894  0.17570464]
[ 0.27118335 -0.56701972]
[-0.01360106  0.19786906]
[-0.00424114 -0.0012749 ]
[ 0.00027077 -0.00011258]
[-7.12942645e-07  2.00328906e-06]
[ 7.26783781e-10 -1.66812385e-08]
[8.90466726e-12 2.23457180e-12]
[-1.38353745e-14  5.85295723e-15]
[ 8.89368462e-19 -2.52015868e-18]

また、近似行列Bの反復1, 5, 10回目の値はそれぞれ以下の通り。

1回目

[[3.83903021 1.26828299]
 [1.26828299 1.55286169]]

5回目

[[3.99881539 1.00459841]
 [1.00459841 1.98214989]]

10回目

[[3.9999154  0.9998001 ]
 [0.9998001  1.99952763]]

実際に評価関数のヘッセ行列を計算すると、以下のようになる。

$$ \nabla^2 f(\bm{x}) = \begin{bmatrix} 4 & 1 \\\ 1 & 2 \end{bmatrix} $$

反復回数が増えるにつれて、実際のヘッセ行列の値に近づいている。

参考
#

準ニュートン法の詳細なアルゴリスムについては、以下の書籍を参考とした。

Helve
著者
Helve
関西在住、電機メーカ勤務のエンジニア。X(旧Twitter)で新着記事を配信中です

関連記事

ニュートン法による最適化とPythonによる実装
·1714 文字·4 分
ニュートン法による最適化アルゴリズムへの理解を深めるため、Pythonで実装した。
直線探索を使った最急降下法をPythonで実装
·2648 文字·6 分
最急降下法と直線探索手法を解説し、Pythonで実装する。
ナップサック問題と分枝限定法
·3230 文字·7 分
分枝限定法は、組合せ最適化問題の解を効率的に求める手法である。組合せ最適化問題の1つであるナップサック問題を対象に、分枝限定法のアルゴリズムを示す。
線形計画問題の主双対内点法
·2267 文字·5 分
線形計画問題に対する主双対内点法 (primal-dual interior point method) についてまとめた。
線形計画問題と双対問題
·1164 文字·3 分
最適化でよく用いられる双対問題についてまとめた。
等式制約付き最適化問題とラグランジュの未定乗数法 後編
·826 文字·2 分
等式制約付き最適化問題に対する、ラグランジュの未定乗数法についてまとめた。簡単な例題に対して、最適解が満たす幾何学的な意味を示す。