ヘッセ行列と多変数の極値問題

2変数関数のグラフを山の地形図に例えると、山頂や谷底、峠のような特別な点があります。山頂は「どの方向に歩いても下がる」点であり、谷底は「どの方向に歩いても上がる」点です。峠は「ある方向に歩くと上がるが、別の方向に歩くと下がる」という不思議な点です。

1変数関数では、極値の判定は $f”(a)$ の符号を見るだけで済みました。$f”(a) > 0$ なら極小、$f”(a) < 0$ なら極大です。しかし、多変数関数では方向が無限にあるため、1つの数値では「全方向の曲がり具合」を表せません。全方向の2階微分情報をまとめた行列がヘッセ行列(Hessian matrix)です。

ヘッセ行列を理解すると、以下のような応用が開けます。

  • 最適化: ニュートン法はヘッセ行列の逆行列を使って2次収束を実現する
  • 機械学習: 損失関数の曲率(ヘッセ行列の固有値)が学習の速度と安定性を左右する
  • 統計学: 最尤推定量の漸近分散は、対数尤度のヘッセ行列(フィッシャー情報行列)の逆行列で与えられる
  • 構造力学: 弾性エネルギーのヘッセ行列は剛性行列に対応し、構造の安定性を決定する

本記事の内容

  • ヘッセ行列の直感的な理解と定義
  • 多変数関数の2次テイラー展開との関係
  • 極値の判定条件(正定値性)
  • 鞍点の意味と判定
  • ニュートン法への応用
  • Pythonによる可視化と実装

前提知識

この記事を読む前に、以下の記事を読んでおくと理解が深まります。

ヘッセ行列とは — 曲面の「曲がり具合」の行列

直感的な理解

1変数関数 $f(x)$ の2次導関数 $f”(x)$ は、グラフの曲がり具合(曲率)を表します。$f”(x) > 0$ ならグラフは上に凸(お皿の形)、$f”(x) < 0$ なら下に凸(山の形)です。

2変数関数 $f(x, y)$ では、曲がり具合は方向によって異なります。ある地点から東に歩くと上に凸でも、北に歩くと下に凸かもしれません。ヘッセ行列は、全ての方向の曲がり具合をまとめて1つの行列に詰め込んだものです。

具体的には、ヘッセ行列の固有値が各主軸方向の曲率を表し、固有ベクトルがその主軸方向を表します。全ての固有値が正なら「全方向に上に凸」(お皿の形、極小)、全て負なら「全方向に下に凸」(山頂、極大)、正と負が混在すれば「方向によって凸凹が異なる」(峠=鞍点)です。

鞍点のイメージ

鞍点(saddle point)の名前は、馬の鞍の形に由来します。鞍の上に座ると、前後方向には谷(上に凸)ですが、左右方向には山(下に凸)です。数学的には、ある方向への曲率が正、別の方向への曲率が負である点が鞍点です。

機械学習の大規模な最適化では、損失関数の極小点よりも鞍点のほうがはるかに多いことが知られています。パラメータが数百万次元の場合、全方向で曲率が正(極小点)になる確率は非常に低く、一部の方向で負(鞍点)である状態がほとんどです。

こうした直感を踏まえて、ヘッセ行列の数学的な定義を見ていきましょう。

ヘッセ行列の数学的定義

定義

$n$ 変数関数 $f: \mathbb{R}^n \to \mathbb{R}$ が2回連続微分可能であるとき、ヘッセ行列 $\bm{H}$ は

$$ \begin{equation} \bm{H}(f) = \begin{pmatrix} \frac{\partial^2 f}{\partial x_1^2} & \frac{\partial^2 f}{\partial x_1 \partial x_2} & \cdots & \frac{\partial^2 f}{\partial x_1 \partial x_n} \\ \frac{\partial^2 f}{\partial x_2 \partial x_1} & \frac{\partial^2 f}{\partial x_2^2} & \cdots & \frac{\partial^2 f}{\partial x_2 \partial x_n} \\ \vdots & \vdots & \ddots & \vdots \\ \frac{\partial^2 f}{\partial x_n \partial x_1} & \frac{\partial^2 f}{\partial x_n \partial x_2} & \cdots & \frac{\partial^2 f}{\partial x_n^2} \end{pmatrix} \end{equation} $$

$(i, j)$ 成分は $H_{ij} = \frac{\partial^2 f}{\partial x_i \partial x_j}$ です。シュワルツの定理により $\frac{\partial^2 f}{\partial x_i \partial x_j} = \frac{\partial^2 f}{\partial x_j \partial x_i}$ なので、ヘッセ行列は対称行列です。

対称行列であることは非常に重要で、全ての固有値が実数であり、固有ベクトルが直交基底をなすことが保証されます。

2次テイラー展開との関係

ヘッセ行列は、関数の2次テイラー展開に自然に現れます。点 $\bm{a}$ の近傍で

$$ \begin{equation} f(\bm{a} + \bm{h}) = f(\bm{a}) + \nabla f(\bm{a})^\top \bm{h} + \frac{1}{2}\bm{h}^\top \bm{H}(\bm{a}) \bm{h} + O(\|\bm{h}\|^3) \end{equation} $$

ここで $\nabla f(\bm{a})$ は勾配ベクトル、$\bm{H}(\bm{a})$ はヘッセ行列です。

この式の各項の意味を確認しましょう。

意味
$f(\bm{a})$ 展開点での関数値
$\nabla f(\bm{a})^\top \bm{h}$ 1次の項(接平面による近似)
$\frac{1}{2}\bm{h}^\top \bm{H}(\bm{a}) \bm{h}$ 2次の項(曲率による補正)

2次の項 $\frac{1}{2}\bm{h}^\top \bm{H} \bm{h}$ は2次形式と呼ばれ、方向 $\bm{h}$ に依存する量です。$\bm{h}$ がヘッセ行列の固有ベクトル $\bm{v}_i$(固有値 $\lambda_i$)の方向を向いているとき、$\bm{h}^\top \bm{H} \bm{h} = \lambda_i \|\bm{h}\|^2$ となり、その方向の曲率が $\lambda_i$ であることがわかります。

計算例

$f(x, y) = x^3 – 3xy^2 + y^4$ のヘッセ行列を求めましょう。

まず、1次偏微分を計算します。

$$ \frac{\partial f}{\partial x} = 3x^2 – 3y^2, \quad \frac{\partial f}{\partial y} = -6xy + 4y^3 $$

次に、2次偏微分を計算します。

$$ \frac{\partial^2 f}{\partial x^2} = 6x, \quad \frac{\partial^2 f}{\partial x \partial y} = -6y, \quad \frac{\partial^2 f}{\partial y^2} = -6x + 12y^2 $$

したがって

$$ \bm{H} = \begin{pmatrix} 6x & -6y \\ -6y & -6x + 12y^2 \end{pmatrix} $$

原点 $(0, 0)$ では $\bm{H}(0,0) = \begin{pmatrix} 0 & 0 \\ 0 & 0 \end{pmatrix}$ であり、ヘッセ行列が零行列になるため、2次のテイラー展開では判定できません。

点 $(1, 0)$ では $\bm{H}(1,0) = \begin{pmatrix} 6 & 0 \\ 0 & -6 \end{pmatrix}$ であり、固有値は $6$ と $-6$(正と負が混在)なので鞍点です。

ヘッセ行列の計算方法を理解したところで、次にこれを使った極値の判定条件を導出しましょう。

極値の判定条件

停留点の分類

多変数関数 $f$ の停留点(stationary point、臨界点)とは、勾配ベクトルがゼロになる点 $\nabla f(\bm{a}) = \bm{0}$ のことです。1変数の $f'(a) = 0$ の拡張です。

停留点では2次テイラー展開の1次の項が消えて

$$ f(\bm{a} + \bm{h}) – f(\bm{a}) \approx \frac{1}{2}\bm{h}^\top \bm{H}(\bm{a}) \bm{h} $$

となります。この2次形式の符号が極値の種類を決定します。

正定値性と極値

定義: 対称行列 $\bm{A}$ が正定値(positive definite)であるとは、全ての非零ベクトル $\bm{h} \neq \bm{0}$ に対して $\bm{h}^\top \bm{A} \bm{h} > 0$ が成り立つことです。同様に、負定値は全て $< 0$、不定値は正にも負にもなるものです。

定理(多変数の極値判定): $f$ が $C^2$ 級で、$\nabla f(\bm{a}) = \bm{0}$ とする。

ヘッセ行列の性質 結論
$\bm{H}(\bm{a})$ が正定値 $\bm{a}$ は極小点
$\bm{H}(\bm{a})$ が負定値 $\bm{a}$ は極大点
$\bm{H}(\bm{a})$ が不定値 $\bm{a}$ は鞍点
$\bm{H}(\bm{a})$ が半正定値/半負定値 判定不能(高次の項が必要)

この判定の直感的な理由は明確です。正定値なら $f(\bm{a}+\bm{h}) – f(\bm{a}) \approx \frac{1}{2}\bm{h}^\top \bm{H}\bm{h} > 0$(全方向に上がる=極小)、負定値なら $< 0$(全方向に下がる=極大)、不定値なら方向によって正にも負にもなる(鞍点)ためです。

正定値性の判定方法

対称行列が正定値かどうかを判定する方法がいくつかあります。

方法1: 固有値

全ての固有値が正なら正定値、全て負なら負定値、正と負が混在すれば不定値です。これは最も直接的な方法です。

方法2: シルベスターの判定法(首座小行列式)

$n \times n$ 対称行列 $\bm{A}$ に対して、左上 $k \times k$ の小行列式(首座小行列式、leading principal minor)$D_k$ を順に計算します。

$$ D_1 = a_{11}, \quad D_2 = \det\begin{pmatrix} a_{11} & a_{12} \\ a_{21} & a_{22} \end{pmatrix}, \quad \ldots, \quad D_n = \det(\bm{A}) $$

  • 正定値: $D_1 > 0, D_2 > 0, \ldots, D_n > 0$(全て正)
  • 負定値: $D_1 < 0, D_2 > 0, D_3 < 0, \ldots$(符号が交互)

2変数の場合の判定

2変数関数 $f(x, y)$ の場合、ヘッセ行列は $2 \times 2$ なので、判定は特に簡単です。

$$ \bm{H} = \begin{pmatrix} f_{xx} & f_{xy} \\ f_{xy} & f_{yy} \end{pmatrix} $$

$D = \det(\bm{H}) = f_{xx}f_{yy} – f_{xy}^2$ として

条件 結論
$D > 0$ かつ $f_{xx} > 0$ 極小
$D > 0$ かつ $f_{xx} < 0$ 極大
$D < 0$ 鞍点
$D = 0$ 判定不能

$D > 0$ は2つの固有値が同符号であることを意味し(積=行列式が正)、$f_{xx}$ の符号がどちら向きの同符号かを教えてくれます。$D < 0$ は固有値が異符号であることを意味します。

具体例

$f(x, y) = x^4 + y^4 – 2x^2 + 4xy – 2y^2$ の停留点と極値を求めます。

偏微分を計算します。

$$ f_x = 4x^3 – 4x + 4y = 0, \quad f_y = 4y^3 + 4x – 4y = 0 $$

2つの方程式を足すと $4x^3 + 4y^3 = 0$、つまり $y = -x$ です。これを第1式に代入すると

$$ 4x^3 – 4x – 4x = 0 \implies 4x^3 – 8x = 0 \implies 4x(x^2 – 2) = 0 $$

$x = 0, \pm\sqrt{2}$ なので、停留点は $(0, 0)$, $(\sqrt{2}, -\sqrt{2})$, $(-\sqrt{2}, \sqrt{2})$ の3つです。

ヘッセ行列は

$$ \bm{H} = \begin{pmatrix} 12x^2 – 4 & 4 \\ 4 & 12y^2 – 4 \end{pmatrix} $$

各停留点での判定を行います。

$(0, 0)$: $\bm{H} = \begin{pmatrix} -4 & 4 \\ 4 & -4 \end{pmatrix}$, $D = 16 – 16 = 0$ → 判定不能

$(\sqrt{2}, -\sqrt{2})$: $\bm{H} = \begin{pmatrix} 20 & 4 \\ 4 & 20 \end{pmatrix}$, $D = 400 – 16 = 384 > 0$, $f_{xx} = 20 > 0$ → 極小

$(-\sqrt{2}, \sqrt{2})$: 同様に $D = 384 > 0$, $f_{xx} = 20 > 0$ → 極小

極値の判定条件を理解したところで、次にヘッセ行列の最も重要な応用の一つであるニュートン法を見てみましょう。

ニュートン法への応用

ニュートン法の導出

多変数の最適化で、勾配降下法よりも高速な収束を実現するのがニュートン法です。ニュートン法は関数を2次のテイラー展開で近似し、その近似の最小点に向かってステップします。

停留点 $\nabla f(\bm{x}^*) = \bm{0}$ を求めるために、現在の点 $\bm{x}_k$ での2次テイラー展開を考えます。

$$ f(\bm{x}_k + \bm{d}) \approx f(\bm{x}_k) + \nabla f(\bm{x}_k)^\top \bm{d} + \frac{1}{2}\bm{d}^\top \bm{H}(\bm{x}_k) \bm{d} $$

右辺を $\bm{d}$ で微分してゼロに設定すると

$$ \nabla f(\bm{x}_k) + \bm{H}(\bm{x}_k)\bm{d} = \bm{0} $$

$\bm{H}(\bm{x}_k)$ が正則であれば

$$ \begin{equation} \bm{d}_k = -\bm{H}(\bm{x}_k)^{-1}\nabla f(\bm{x}_k) \end{equation} $$

更新則は $\bm{x}_{k+1} = \bm{x}_k + \bm{d}_k = \bm{x}_k – \bm{H}(\bm{x}_k)^{-1}\nabla f(\bm{x}_k)$ です。

勾配降下法との比較

勾配降下法の更新則 $\bm{x}_{k+1} = \bm{x}_k – \alpha \nabla f(\bm{x}_k)$ と比較すると、ニュートン法はステップサイズ $\alpha$ の代わりにヘッセ行列の逆行列 $\bm{H}^{-1}$ を使っています。

勾配降下法は全方向に同じスケーリングで下りますが、ニュートン法はヘッセ行列の情報を使って、曲率の大きい方向には小さく、曲率の小さい方向には大きくステップします。これにより、楕円的な等高線を持つ関数でも効率的に最小点に到達できます。

ニュートン法は停留点近傍で2次収束($\|\bm{x}_{k+1} – \bm{x}^*\| \leq C\|\bm{x}_k – \bm{x}^*\|^2$)を達成しますが、ヘッセ行列の計算と逆行列の計算にコストがかかります。大規模な問題では、ヘッセ行列を近似的に構築する準ニュートン法(BFGS, L-BFGS)が実用的です。

ニュートン法の注意点

ニュートン法にはいくつかの注意点があります。

  • ヘッセ行列が正定値でない場合(鞍点の近傍など)、ニュートン法のステップが最小化方向ではなく最大化方向に向かう可能性があります。これを防ぐために、ヘッセ行列を修正する手法(修正ニュートン法)が使われます。
  • 初期点が最小点から遠い場合、2次近似が不正確でステップが大きすぎることがあります。直線探索(line search)を組み合わせることで安定性を改善できます。

理論を理解したところで、Pythonで具体的に可視化してみましょう。

Pythonでの実装と可視化

停留点の分類の可視化

鞍点を持つ関数を3Dで可視化し、ヘッセ行列の固有値との関係を確認します。

import numpy as np
import matplotlib.pyplot as plt
from mpl_toolkits.mplot3d import Axes3D

# f(x, y) = x^2 - y^2(鞍点を持つ典型的な関数)
def f_saddle(x, y):
    return x**2 - y**2

# f(x, y) = x^2 + y^2(極小点を持つ関数)
def f_min(x, y):
    return x**2 + y**2

# f(x, y) = -x^2 - y^2(極大点を持つ関数)
def f_max(x, y):
    return -x**2 - y**2

x = np.linspace(-2, 2, 100)
y = np.linspace(-2, 2, 100)
X, Y = np.meshgrid(x, y)

fig = plt.figure(figsize=(16, 5))

# (a) 極小点
ax1 = fig.add_subplot(131, projection='3d')
Z = f_min(X, Y)
ax1.plot_surface(X, Y, Z, cmap='Blues', alpha=0.8, edgecolor='none')
ax1.set_title('Local minimum\n$H$ positive definite\n$\\lambda_1=2, \\lambda_2=2$',
              fontsize=11)
ax1.set_xlabel('x'); ax1.set_ylabel('y'); ax1.set_zlabel('z')
ax1.view_init(elev=25, azim=135)

# (b) 極大点
ax2 = fig.add_subplot(132, projection='3d')
Z = f_max(X, Y)
ax2.plot_surface(X, Y, Z, cmap='Reds', alpha=0.8, edgecolor='none')
ax2.set_title('Local maximum\n$H$ negative definite\n$\\lambda_1=-2, \\lambda_2=-2$',
              fontsize=11)
ax2.set_xlabel('x'); ax2.set_ylabel('y'); ax2.set_zlabel('z')
ax2.view_init(elev=25, azim=135)

# (c) 鞍点
ax3 = fig.add_subplot(133, projection='3d')
Z = f_saddle(X, Y)
ax3.plot_surface(X, Y, Z, cmap='coolwarm', alpha=0.8, edgecolor='none')
ax3.set_title('Saddle point\n$H$ indefinite\n$\\lambda_1=2, \\lambda_2=-2$',
              fontsize=11)
ax3.set_xlabel('x'); ax3.set_ylabel('y'); ax3.set_zlabel('z')
ax3.view_init(elev=25, azim=135)

plt.tight_layout()
plt.savefig('hessian_classification.png', dpi=150, bbox_inches='tight')
plt.show()

このグラフから、ヘッセ行列の固有値と曲面の形状の対応が一目瞭然です。

  1. 左図(極小点): ヘッセ行列 $\bm{H} = \begin{pmatrix} 2 & 0 \\ 0 & 2 \end{pmatrix}$ は正定値($\lambda_1 = \lambda_2 = 2$)であり、原点はお椀型の底(極小点)です。どの方向に歩いても関数値が増加します。

  2. 中央図(極大点): ヘッセ行列 $\bm{H} = \begin{pmatrix} -2 & 0 \\ 0 & -2 \end{pmatrix}$ は負定値($\lambda_1 = \lambda_2 = -2$)であり、原点は山頂(極大点)です。どの方向に歩いても関数値が減少します。

  3. 右図(鞍点): ヘッセ行列 $\bm{H} = \begin{pmatrix} 2 & 0 \\ 0 & -2 \end{pmatrix}$ は不定値($\lambda_1 = 2, \lambda_2 = -2$)であり、原点は馬の鞍の形をしています。$x$ 方向には極小的(上に凸)、$y$ 方向には極大的(下に凸)です。

ヘッセ行列の固有値と主軸方向

ヘッセ行列の固有値と固有ベクトルが曲面の形状をどう決めるかを可視化します。

import numpy as np
import matplotlib.pyplot as plt

# f(x, y) = 3x^2 + 2xy + 2y^2(非対角成分ありの2次形式)
def f_quad(x, y):
    return 3 * x**2 + 2 * x * y + 2 * y**2

H = np.array([[6, 2],
              [2, 4]])

# 固有値分解
eigenvalues, eigenvectors = np.linalg.eigh(H)

print(f"ヘッセ行列:\n{H}")
print(f"固有値: {eigenvalues}")
print(f"固有ベクトル:\n{eigenvectors}")

x = np.linspace(-2, 2, 200)
y = np.linspace(-2, 2, 200)
X, Y = np.meshgrid(x, y)
Z = f_quad(X, Y)

fig, axes = plt.subplots(1, 2, figsize=(14, 6))

# (a) 等高線と固有ベクトル方向
ax = axes[0]
contour = ax.contour(X, Y, Z, levels=20, cmap='viridis')
ax.clabel(contour, inline=True, fontsize=8)

# 固有ベクトルを描画
origin = np.array([0, 0])
scale = 1.5
for i, (val, vec) in enumerate(zip(eigenvalues, eigenvectors.T)):
    color = 'red' if i == 0 else 'blue'
    ax.annotate('', xy=origin + scale * vec, xytext=origin,
                arrowprops=dict(arrowstyle='->', color=color, lw=2.5))
    ax.text(*(origin + scale * vec * 1.15),
            f'$\\lambda_{i+1}={val:.2f}$', fontsize=11,
            color=color, fontweight='bold', ha='center')

ax.set_xlabel('x', fontsize=12)
ax.set_ylabel('y', fontsize=12)
ax.set_title('Contours + Eigenvectors of Hessian', fontsize=13)
ax.set_aspect('equal')
ax.grid(True, alpha=0.3)
ax.set_xlim(-2, 2)
ax.set_ylim(-2, 2)

# (b) 方向ごとの曲率
ax = axes[1]
angles = np.linspace(0, 2 * np.pi, 360)
curvatures = []
for angle in angles:
    u = np.array([np.cos(angle), np.sin(angle)])
    curvature = u @ H @ u
    curvatures.append(curvature)

curvatures = np.array(curvatures)

# 極座標プロット
ax_polar = fig.add_subplot(122, projection='polar')
axes[1].set_visible(False)
ax_polar.plot(angles, curvatures, 'b-', linewidth=2)
ax_polar.fill(angles, curvatures, alpha=0.2, color='blue')

# 固有ベクトル方向を表示
for i, (val, vec) in enumerate(zip(eigenvalues, eigenvectors.T)):
    angle_ev = np.arctan2(vec[1], vec[0])
    color = 'red' if i == 0 else 'blue'
    ax_polar.plot([angle_ev, angle_ev], [0, val], color=color,
                  linewidth=2.5, linestyle='--')
    ax_polar.plot(angle_ev, val, 'o', color=color, markersize=8)

ax_polar.set_title('Curvature $u^T H u$ by direction', fontsize=13, pad=15)
ax_polar.set_rticks([2, 4, 6, 8])

plt.tight_layout()
plt.savefig('hessian_eigenvalues.png', dpi=150, bbox_inches='tight')
plt.show()

このグラフから、ヘッセ行列の固有値と等高線の関係が明確にわかります。

  1. 左図(等高線と固有ベクトル): 等高線は楕円形をしており、楕円の長軸・短軸の方向がヘッセ行列の固有ベクトル方向に一致しています。固有値が小さい方向($\lambda_1 \approx 3.17$、赤い矢印)が楕円の長軸方向であり、固有値が大きい方向($\lambda_2 \approx 6.83$、青い矢印)が短軸方向です。曲率が大きい(固有値が大きい)方向ほど等高線の間隔が狭い(急速に変化する)ことがわかります。

  2. 右図(方向ごとの曲率): 極座標で各方向の曲率 $\bm{u}^\top \bm{H} \bm{u}$ をプロットしています。固有ベクトル方向でちょうど固有値の値になり、最大曲率と最小曲率がそれぞれの固有値に対応しています。

ニュートン法 vs 勾配降下法の比較

実際にニュートン法と勾配降下法の収束の違いを可視化します。

import numpy as np
import matplotlib.pyplot as plt

# Rosenbrock関数に近い関数: f(x,y) = (1-x)^2 + 10(y-x^2)^2
def rosenbrock(xy):
    x, y = xy
    return (1 - x)**2 + 10 * (y - x**2)**2

def grad_rosenbrock(xy):
    x, y = xy
    gx = -2 * (1 - x) - 40 * x * (y - x**2)
    gy = 20 * (y - x**2)
    return np.array([gx, gy])

def hessian_rosenbrock(xy):
    x, y = xy
    hxx = 2 - 40 * (y - x**2) + 80 * x**2
    hxy = -40 * x
    hyy = 20.0
    return np.array([[hxx, hxy], [hxy, hyy]])

# 勾配降下法
def gradient_descent(f, grad, x0, lr=0.002, n_iter=5000):
    path = [x0.copy()]
    x = x0.copy()
    for _ in range(n_iter):
        g = grad(x)
        x = x - lr * g
        path.append(x.copy())
        if np.linalg.norm(g) < 1e-8:
            break
    return np.array(path)

# ニュートン法
def newton_method(f, grad, hess, x0, n_iter=100):
    path = [x0.copy()]
    x = x0.copy()
    for _ in range(n_iter):
        g = grad(x)
        H = hess(x)
        try:
            d = np.linalg.solve(H, -g)
        except np.linalg.LinAlgError:
            break
        x = x + d
        path.append(x.copy())
        if np.linalg.norm(g) < 1e-10:
            break
    return np.array(path)

x0 = np.array([-0.5, 2.0])

path_gd = gradient_descent(rosenbrock, grad_rosenbrock, x0)
path_newton = newton_method(rosenbrock, grad_rosenbrock, hessian_rosenbrock, x0)

fig, axes = plt.subplots(1, 2, figsize=(14, 6))

# 等高線
x = np.linspace(-1.5, 2, 300)
y = np.linspace(-1, 3, 300)
X, Y = np.meshgrid(x, y)
Z = (1 - X)**2 + 10 * (Y - X**2)**2

for idx, (ax, path, title, color) in enumerate([
    (axes[0], path_gd, f'Gradient Descent ({len(path_gd)} steps)', 'red'),
    (axes[1], path_newton, f'Newton Method ({len(path_newton)} steps)', 'blue')
]):
    ax.contour(X, Y, Z, levels=np.logspace(-1, 3.5, 30), cmap='viridis', alpha=0.5)
    ax.plot(path[:, 0], path[:, 1], f'{color[0]}.-', markersize=3,
            linewidth=0.8, alpha=0.8)
    ax.plot(path[0, 0], path[0, 1], 'ks', markersize=10, label='Start')
    ax.plot(1, 1, 'g*', markersize=15, label='Minimum (1,1)')
    ax.set_xlabel('x', fontsize=12)
    ax.set_ylabel('y', fontsize=12)
    ax.set_title(title, fontsize=13)
    ax.legend(fontsize=10)
    ax.grid(True, alpha=0.3)
    ax.set_xlim(-1.5, 2)
    ax.set_ylim(-1, 3)

plt.tight_layout()
plt.savefig('hessian_newton.png', dpi=150, bbox_inches='tight')
plt.show()

print(f"勾配降下法: {len(path_gd)} ステップ, 最終点 = {path_gd[-1]}")
print(f"ニュートン法: {len(path_newton)} ステップ, 最終点 = {path_newton[-1]}")

このグラフから、ニュートン法と勾配降下法の収束性の違いが劇的にわかります。

  1. 左図(勾配降下法): 細長い谷(Rosenbrock関数の特徴)に沿ってジグザグに進み、数千ステップかかっても最小点 $(1, 1)$ に到達するのが困難です。等高線が楕円的に引き伸ばされているため、勾配方向が最適方向と大きくずれてしまうのです。

  2. 右図(ニュートン法): ヘッセ行列の情報を使って曲率を考慮するため、わずか数ステップで最小点に収束しています。ヘッセ行列の逆行列が「引き伸ばされた等高線」を等方的に補正するため、効率的に最小点に向かえるのです。

ただし、ニュートン法は各ステップでヘッセ行列の計算($O(n^2)$)と連立方程式の求解($O(n^3)$)が必要なため、$n$ が大きいと計算コストが高くなります。

まとめ

本記事では、ヘッセ行列と多変数の極値問題について解説しました。

  • ヘッセ行列は2階偏微分を行列にまとめたもので、対称行列である。多変数関数の2次テイラー展開に $\frac{1}{2}\bm{h}^\top \bm{H}\bm{h}$ として現れる
  • 停留点でのヘッセ行列が正定値なら極小負定値なら極大不定値なら鞍点
  • 正定値性は固有値の符号またはシルベスターの判定法で確認できる
  • 2変数の場合は $D = f_{xx}f_{yy} – f_{xy}^2$ の符号と $f_{xx}$ の符号で判定
  • ニュートン法はヘッセ行列の逆行列を使って2次収束を実現するが、計算コストが高い

次のステップとして、以下の記事も参考にしてください。