ラグランジュの未定乗数法を完全理解する

予算100万円でパソコンと周辺機器を買うとき、最も満足度を高くするにはどう配分すればよいでしょうか。これは「予算という制約のもとで、満足度(効用関数)を最大化する」問題です。制約がなければ無限に買えばよいのですが、予算の壁があるために最適な配分を探す必要が出てきます。

このような制約付き最適化問題は数学・物理・工学・経済学のあらゆる場面で現れます。そして、この問題を解くための最も基本的かつ強力な手法がラグランジュの未定乗数法(method of Lagrange multipliers)です。

ラグランジュの未定乗数法を理解すると、以下のような応用が開けます。

  • 機械学習: サポートベクトルマシン(SVM)の双対問題はラグランジュ乗数で定式化される
  • 物理学: 解析力学(ラグランジュ力学)における束縛条件の処理
  • 経済学: 予算制約下の効用最大化、企業のコスト最小化問題
  • 工学: 強度制約の下での構造重量の最小化

本記事の内容

  • ラグランジュの未定乗数法の直感的な理解
  • 数学的な定式化と証明
  • ラグランジュ乗数の経済学的解釈(影の価格)
  • 複数の制約条件の場合
  • 不等式制約とKKT条件
  • Pythonによる実装と可視化

前提知識

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

ラグランジュの未定乗数法とは

直感的な理解 — 等高線と制約曲線

ラグランジュの未定乗数法の核心は、驚くほどシンプルな幾何学的アイデアに基づいています。

2変数関数 $f(x, y)$ を制約条件 $g(x, y) = 0$ のもとで最大化したいとします。$f$ の等高線($f = c$ の曲線)と制約曲線 $g = 0$ を同じ平面に描いてみましょう。

制約条件を満たしながら $f$ を大きくするには、制約曲線 $g = 0$ の上を歩きながら、$f$ の値がより大きい等高線に移動すればよいのです。最適な点では何が起こるでしょうか。

制約曲線上のある点で、$f$ の等高線が制約曲線に接する(tangent)瞬間があります。その点が最適点です。なぜなら、等高線が制約曲線と交わっている限り、制約曲線に沿って歩けば $f$ をさらに大きくできますが、等高線が接してしまうと、どちらに歩いても $f$ は小さくなるしかないからです。

2つの曲線が接するということは、その点での法線方向(曲線に垂直な方向)が一致するということです。$f$ の等高線の法線方向は勾配 $\nabla f$ であり、$g = 0$ の法線方向は $\nabla g$ です。したがって、最適点では

$$ \nabla f = \lambda \nabla g $$

が成り立ちます。ここで $\lambda$ は比例定数であり、ラグランジュ乗数(Lagrange multiplier)と呼ばれます。

歴史的背景

ジョゼフ=ルイ・ラグランジュ(1736–1813)は、イタリア生まれのフランスの数学者で、解析力学の創始者として知られています。彼は1788年の名著『解析力学』(Mécanique analytique)において、力学的な束縛条件を処理するためにこの方法を導入しました。

ラグランジュの偉大さは、幾何学的な束縛(例:滑らかな面の上に拘束された質点)を、抗力の代わりに「乗数」という代数的な道具で処理したことにあります。これにより、幾何学的直感に頼らず、代数的・解析的な方法で力学を定式化することが可能になりました。

この歴史的な背景を踏まえて、数学的な定式化に進みましょう。

数学的定式化

問題の設定

等式制約付き最適化問題は次のように定式化されます。

$$ \begin{align} &\text{maximize (or minimize)} \quad f(\bm{x}) \\ &\text{subject to} \quad g(\bm{x}) = 0 \end{align} $$

ここで $\bm{x} = (x_1, \ldots, x_n) \in \mathbb{R}^n$、$f, g: \mathbb{R}^n \to \mathbb{R}$ は $C^1$ 級関数です。

ラグランジュの定理

定理: $f, g$ が $C^1$ 級で、$\bm{x}^*$ が制約 $g(\bm{x}) = 0$ の下での $f$ の極値点であり、$\nabla g(\bm{x}^*) \neq \bm{0}$(制約想定条件、constraint qualification)を満たすとする。このとき、ある実数 $\lambda$ が存在して

$$ \begin{equation} \nabla f(\bm{x}^*) = \lambda \nabla g(\bm{x}^*) \end{equation} $$

証明

証明は陰関数定理を使います。$\nabla g(\bm{x}^*) \neq \bm{0}$ より、ある $j$ について $\frac{\partial g}{\partial x_j}(\bm{x}^*) \neq 0$ です。簡単のため $j = n$ とします。

陰関数定理により、$g(\bm{x}) = 0$ の近傍で $x_n = h(x_1, \ldots, x_{n-1})$ と表せます。制約付き最適化問題は

$$ \text{maximize} \quad F(x_1, \ldots, x_{n-1}) = f(x_1, \ldots, x_{n-1}, h(x_1, \ldots, x_{n-1})) $$

という無制約問題に帰着します。$\bm{x}^*$ が極値点なので、$i = 1, \ldots, n-1$ について

$$ \frac{\partial F}{\partial x_i} = \frac{\partial f}{\partial x_i} + \frac{\partial f}{\partial x_n}\frac{\partial h}{\partial x_i} = 0 $$

一方、$g(x_1, \ldots, x_{n-1}, h) = 0$ を $x_i$ で微分すると

$$ \frac{\partial g}{\partial x_i} + \frac{\partial g}{\partial x_n}\frac{\partial h}{\partial x_i} = 0 \implies \frac{\partial h}{\partial x_i} = -\frac{\partial g / \partial x_i}{\partial g / \partial x_n} $$

上の2式を組み合わせると

$$ \frac{\partial f}{\partial x_i} – \frac{\partial f / \partial x_n}{\partial g / \partial x_n}\frac{\partial g}{\partial x_i} = 0 $$

$\lambda = \frac{\partial f / \partial x_n}{\partial g / \partial x_n}$ とおくと、$i = 1, \ldots, n-1$ について $\frac{\partial f}{\partial x_i} = \lambda \frac{\partial g}{\partial x_i}$ が成り立ちます。$i = n$ の場合も $\lambda$ の定義から自動的に成り立ちます。

よって $\nabla f(\bm{x}^*) = \lambda \nabla g(\bm{x}^*)$ が示されました。

ラグランジアン

実際の計算では、ラグランジアン(Lagrangian)と呼ばれる関数を導入すると便利です。

$$ \begin{equation} \mathcal{L}(\bm{x}, \lambda) = f(\bm{x}) – \lambda g(\bm{x}) \end{equation} $$

ラグランジアンの停留点条件は

$$ \frac{\partial \mathcal{L}}{\partial x_i} = \frac{\partial f}{\partial x_i} – \lambda \frac{\partial g}{\partial x_i} = 0, \quad i = 1, \ldots, n $$

$$ \frac{\partial \mathcal{L}}{\partial \lambda} = -g(\bm{x}) = 0 $$

最初の $n$ 個の方程式は $\nabla f = \lambda \nabla g$ に対応し、最後の方程式は制約条件 $g(\bm{x}) = 0$ そのものです。つまり、ラグランジアンの全変数($\bm{x}$ と $\lambda$)に関する停留点条件を解けば、制約付き最適化の候補点が全て求まるのです。

具体例

$f(x, y) = xy$ を制約条件 $g(x, y) = x^2 + y^2 – 1 = 0$(単位円上)の下で最大化しましょう。

ラグランジアンは $\mathcal{L} = xy – \lambda(x^2 + y^2 – 1)$ です。停留点条件は

$$ \frac{\partial \mathcal{L}}{\partial x} = y – 2\lambda x = 0 $$

$$ \frac{\partial \mathcal{L}}{\partial y} = x – 2\lambda y = 0 $$

$$ \frac{\partial \mathcal{L}}{\partial \lambda} = -(x^2 + y^2 – 1) = 0 $$

第1式から $y = 2\lambda x$、第2式から $x = 2\lambda y$ です。第1式を第2式に代入すると $x = 2\lambda(2\lambda x) = 4\lambda^2 x$ です。$x \neq 0$ として $4\lambda^2 = 1$、つまり $\lambda = \pm 1/2$ です。

$\lambda = 1/2$ のとき $y = x$ で、制約条件 $x^2 + y^2 = 1$ から $x = y = \pm 1/\sqrt{2}$ です。$f = xy = 1/2$ です。

$\lambda = -1/2$ のとき $y = -x$ で、$x = 1/\sqrt{2}, y = -1/\sqrt{2}$ または $x = -1/\sqrt{2}, y = 1/\sqrt{2}$ です。$f = xy = -1/2$ です。

したがって、最大値は $1/2$(点 $(1/\sqrt{2}, 1/\sqrt{2})$、$(-1/\sqrt{2}, -1/\sqrt{2})$)、最小値は $-1/2$ です。

この例は簡単に見えますが、ラグランジュの方法の威力は制約が複雑な場合に発揮されます。次に、ラグランジュ乗数自体の持つ意味を見てみましょう。

ラグランジュ乗数の意味 — 影の価格

経済学的解釈

ラグランジュ乗数 $\lambda$ には、制約条件を「少し緩めた」ときの目的関数の変化率という重要な意味があります。

制約条件を $g(\bm{x}) = c$ と一般化しましょう($c = 0$ が元の問題)。最適値 $f^*(c) = f(\bm{x}^*(c))$ の $c$ に関する微分が

$$ \begin{equation} \frac{df^*}{dc} = \lambda \end{equation} $$

つまり、制約を $\Delta c$ だけ緩めると、最適値は $\lambda \cdot \Delta c$ だけ変化します。

経済学ではこの $\lambda$ を影の価格(shadow price)と呼びます。予算制約の下で効用を最大化する問題では、$\lambda$ は「予算が1円増えたときの効用の増加分」を表します。$\lambda$ が大きいほど、その制約はきつく効いている(制約を緩める価値が大きい)ことを意味します。

証明

制約 $g(\bm{x}) = c$ の下で $f$ の最適値を $f^*(c)$ とし、最適解を $\bm{x}^*(c)$ とします。

停留点条件 $\nabla f(\bm{x}^*) = \lambda \nabla g(\bm{x}^*)$ と $g(\bm{x}^*) = c$ が成り立つとき

$$ \frac{df^*}{dc} = \nabla f^\top \frac{d\bm{x}^*}{dc} = \lambda \nabla g^\top \frac{d\bm{x}^*}{dc} $$

$g(\bm{x}^*(c)) = c$ の両辺を $c$ で微分すると $\nabla g^\top \frac{d\bm{x}^*}{dc} = 1$ なので

$$ \frac{df^*}{dc} = \lambda $$

が得られます。

ラグランジュ乗数の深い意味を理解したところで、次に複数の制約条件がある場合に拡張しましょう。

複数の等式制約

一般的な定式化

$m$ 個の等式制約がある場合

$$ \begin{align} &\text{maximize} \quad f(\bm{x}) \\ &\text{subject to} \quad g_1(\bm{x}) = 0, \quad g_2(\bm{x}) = 0, \quad \ldots, \quad g_m(\bm{x}) = 0 \end{align} $$

ラグランジアンは $m$ 個のラグランジュ乗数 $\lambda_1, \ldots, \lambda_m$ を使って

$$ \begin{equation} \mathcal{L}(\bm{x}, \bm{\lambda}) = f(\bm{x}) – \sum_{j=1}^{m} \lambda_j g_j(\bm{x}) \end{equation} $$

停留点条件は

$$ \nabla f(\bm{x}) = \sum_{j=1}^{m} \lambda_j \nabla g_j(\bm{x}), \quad g_j(\bm{x}) = 0 \quad (j = 1, \ldots, m) $$

これは $n + m$ 個の未知数($\bm{x}$ の $n$ 成分と $\lambda_1, \ldots, \lambda_m$)に対する $n + m$ 個の方程式です。

幾何学的には、最適点で $\nabla f$ が $\nabla g_1, \ldots, \nabla g_m$ の線形結合で表せることを意味します。これは $f$ の等高線の法線方向が、全ての制約曲面の法線方向が張る空間内にあるということです。

制約想定条件

定理が成り立つためには、$\nabla g_1(\bm{x}^*), \ldots, \nabla g_m(\bm{x}^*)$ が線形独立である必要があります(制約想定条件、constraint qualification)。この条件が破れると、ラグランジュの方法では最適点を見つけられないことがあります。

等式制約を理解したところで、実用上より重要な不等式制約の場合に進みましょう。

不等式制約とKKT条件

不等式制約の問題

実際の最適化問題では、不等式制約が多く現れます。

$$ \begin{align} &\text{minimize} \quad f(\bm{x}) \\ &\text{subject to} \quad g_i(\bm{x}) \leq 0, \quad i = 1, \ldots, m \\ &\phantom{\text{subject to}} \quad h_j(\bm{x}) = 0, \quad j = 1, \ldots, p \end{align} $$

KKT条件

不等式制約を含む問題の最適性の必要条件がカルーシュ・クーン・タッカー(KKT)条件です。

$\bm{x}^*$ が上の問題の局所最適解であり、制約想定条件を満たすとき、ラグランジュ乗数 $\mu_i \geq 0$($i = 1, \ldots, m$)と $\lambda_j$($j = 1, \ldots, p$)が存在して、以下の条件を全て満たします。

停留性(stationarity): $$ \nabla f(\bm{x}^*) + \sum_{i=1}^{m} \mu_i \nabla g_i(\bm{x}^*) + \sum_{j=1}^{p} \lambda_j \nabla h_j(\bm{x}^*) = \bm{0} $$

主実行可能性(primal feasibility): $$ g_i(\bm{x}^*) \leq 0, \quad h_j(\bm{x}^*) = 0 $$

双対実行可能性(dual feasibility): $$ \mu_i \geq 0 $$

相補性条件(complementary slackness): $$ \mu_i g_i(\bm{x}^*) = 0 $$

相補性条件は特に重要です。これは各不等式制約 $g_i$ について、「制約が等号で成り立つ($g_i = 0$、アクティブ)」か「乗数がゼロ($\mu_i = 0$、制約が効いていない)」のどちらかであることを意味します。

直感的に言えば、あなたが壁のある部屋で最適な位置を探しているとき、壁に押し付けられている(制約がアクティブ)なら壁からの反力($\mu_i > 0$)が働きますが、壁から離れている(制約が非アクティブ)なら反力はゼロ($\mu_i = 0$)です。

SVMとの関連

機械学習のサポートベクトルマシン(SVM)はKKT条件の代表的な応用例です。SVMのマージン最大化問題

$$ \text{minimize} \quad \frac{1}{2}\|\bm{w}\|^2 \quad \text{subject to} \quad y_i(\bm{w}^\top \bm{x}_i + b) \geq 1 $$

のKKT条件における相補性条件 $\mu_i(y_i(\bm{w}^\top \bm{x}_i + b) – 1) = 0$ が、「サポートベクトル以外のデータ点の乗数はゼロ」という性質を導きます。これがSVMの疎性(sparsity)の源です。

理論を整理したところで、Pythonで可視化してみましょう。

Pythonでの実装と可視化

等高線と制約曲線の幾何学

ラグランジュの未定乗数法の幾何学的意味を可視化します。

import numpy as np
import matplotlib.pyplot as plt

# 目的関数: f(x, y) = xy
def f(x, y):
    return x * y

# 制約: g(x, y) = x^2 + y^2 - 1 = 0
def g(x, y):
    return x**2 + y**2 - 1

x = np.linspace(-1.8, 1.8, 300)
y = np.linspace(-1.8, 1.8, 300)
X, Y = np.meshgrid(x, y)
Z = f(X, Y)
G = g(X, Y)

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

# (a) 等高線と制約曲線
ax = axes[0]
levels = np.linspace(-1, 1, 21)
contour = ax.contour(X, Y, Z, levels=levels, cmap='RdBu_r', alpha=0.7)
ax.clabel(contour, inline=True, fontsize=8)

# 制約曲線(単位円)
theta = np.linspace(0, 2 * np.pi, 200)
ax.plot(np.cos(theta), np.sin(theta), 'k-', linewidth=2.5, label='$g(x,y)=0$')

# 最適点
optimal_points = [(1/np.sqrt(2), 1/np.sqrt(2)),
                  (-1/np.sqrt(2), -1/np.sqrt(2)),
                  (1/np.sqrt(2), -1/np.sqrt(2)),
                  (-1/np.sqrt(2), 1/np.sqrt(2))]
labels = ['max', 'max', 'min', 'min']
colors_pt = ['red', 'red', 'blue', 'blue']

for (px, py), lab, col in zip(optimal_points, labels, colors_pt):
    ax.plot(px, py, 'o', color=col, markersize=10, zorder=5)
    # 勾配ベクトル
    grad_f = np.array([py, px])
    grad_g = np.array([2*px, 2*py])
    scale = 0.3
    ax.annotate('', xy=(px + scale*grad_f[0], py + scale*grad_f[1]),
                xytext=(px, py),
                arrowprops=dict(arrowstyle='->', color='green', lw=2))
    ax.annotate('', xy=(px + scale*grad_g[0], py + scale*grad_g[1]),
                xytext=(px, py),
                arrowprops=dict(arrowstyle='->', color='purple', lw=2))

# 凡例用のダミー
ax.plot([], [], 'g-', linewidth=2, label=r'$\nabla f$')
ax.plot([], [], 'purple', linewidth=2, label=r'$\nabla g$')

ax.set_xlabel('x', fontsize=12)
ax.set_ylabel('y', fontsize=12)
ax.set_title('Contours of $f=xy$ and constraint $x^2+y^2=1$', fontsize=13)
ax.set_aspect('equal')
ax.legend(fontsize=10)
ax.grid(True, alpha=0.3)

# (b) 制約曲線上での f の値
ax = axes[1]
theta_vals = np.linspace(0, 2*np.pi, 500)
f_on_circle = np.cos(theta_vals) * np.sin(theta_vals)

ax.plot(theta_vals, f_on_circle, 'b-', linewidth=2)
ax.axhline(0.5, color='red', linestyle='--', alpha=0.5, label='max = 0.5')
ax.axhline(-0.5, color='blue', linestyle='--', alpha=0.5, label='min = -0.5')

# 最適点のθを表示
for theta_opt, lab, col in [(np.pi/4, 'max', 'red'), (5*np.pi/4, 'max', 'red'),
                              (3*np.pi/4, 'min', 'blue'), (7*np.pi/4, 'min', 'blue')]:
    ax.plot(theta_opt, np.cos(theta_opt)*np.sin(theta_opt), 'o',
            color=col, markersize=10, zorder=5)

ax.set_xlabel(r'$\theta$ (parameterization of constraint)', fontsize=12)
ax.set_ylabel(r'$f(\cos\theta, \sin\theta)$', fontsize=12)
ax.set_title('Objective function along constraint curve', fontsize=13)
ax.legend(fontsize=10)
ax.grid(True, alpha=0.3)
ax.set_xlim(0, 2*np.pi)
ax.set_xticks([0, np.pi/2, np.pi, 3*np.pi/2, 2*np.pi])
ax.set_xticklabels(['0', r'$\pi/2$', r'$\pi$', r'$3\pi/2$', r'$2\pi$'])

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

このグラフから、ラグランジュの未定乗数法の幾何学的な意味が明確にわかります。

  1. 左図(等高線と制約曲線): $f = xy$ の等高線(赤〜青のカラーマップ)の中を、制約曲線(黒い単位円)が通っています。最適点(赤と青の丸)では、$f$ の勾配 $\nabla f$(緑の矢印)と $g$ の勾配 $\nabla g$(紫の矢印)が同じ方向を向いていることが確認できます。これこそ $\nabla f = \lambda \nabla g$ の条件であり、等高線が制約曲線に接する点です。

  2. 右図(制約曲線上の目的関数値): 制約曲線を $\theta$ でパラメータ化して $f$ の値をプロットすると、$\sin 2\theta / 2$ のグラフになります。最大値 $0.5$ と最小値 $-0.5$ の位置がはっきり見え、これがラグランジュの方法で求めた解に一致しています。

影の価格(ラグランジュ乗数の意味)の可視化

制約を緩めたときの最適値の変化を数値的に確認します。

import numpy as np
import matplotlib.pyplot as plt
from scipy.optimize import minimize

# 目的関数: f(x, y) = -(xy)(最小化に変換)
# 制約: x^2 + y^2 = c

c_values = np.linspace(0.5, 2.0, 50)
optimal_values = []

for c in c_values:
    # scipy.optimize で制約付き最小化
    result = minimize(lambda xy: -xy[0]*xy[1],
                      x0=[0.5, 0.5],
                      constraints={'type': 'eq', 'fun': lambda xy: xy[0]**2 + xy[1]**2 - c})
    optimal_values.append(-result.fun)  # 最大値に戻す

optimal_values = np.array(optimal_values)

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

# (a) 最適値 f*(c) vs c
ax = axes[0]
ax.plot(c_values, optimal_values, 'b-', linewidth=2, label=r'$f^*(c)$')

# c=1 での接線(ラグランジュ乗数 = 影の価格)
c0 = 1.0
f_star_c0 = 0.5  # c=1 での最適値
lambda_val = 0.5  # ラグランジュ乗数
tangent = f_star_c0 + lambda_val * (c_values - c0)
ax.plot(c_values, tangent, 'r--', linewidth=1.5,
        label=rf'Tangent at $c=1$: slope $= \lambda = {lambda_val}$')
ax.plot(c0, f_star_c0, 'ro', markersize=10)

ax.set_xlabel('Constraint parameter $c$ ($x^2+y^2=c$)', fontsize=12)
ax.set_ylabel(r'Optimal value $f^*(c)$', fontsize=12)
ax.set_title('Shadow Price Interpretation', fontsize=13)
ax.legend(fontsize=10)
ax.grid(True, alpha=0.3)

# (b) 様々な c での最適解と制約円
ax = axes[1]
c_demo = [0.5, 1.0, 1.5, 2.0]
colors_demo = ['#e74c3c', '#2ecc71', '#3498db', '#9b59b6']

for c, color in zip(c_demo, colors_demo):
    r = np.sqrt(c)
    theta = np.linspace(0, 2*np.pi, 200)
    ax.plot(r*np.cos(theta), r*np.sin(theta), '-', color=color,
            linewidth=1.5, alpha=0.7, label=f'$c={c:.1f}$')
    # 最適点
    opt = np.sqrt(c/2)
    ax.plot(opt, opt, 'o', color=color, markersize=8)

# 等高線
Z = f(X, Y)
ax.contour(X, Y, Z, levels=10, cmap='gray', alpha=0.3)

ax.set_xlabel('x', fontsize=12)
ax.set_ylabel('y', fontsize=12)
ax.set_title('Optimal points for different constraints', fontsize=13)
ax.set_aspect('equal')
ax.legend(fontsize=9)
ax.grid(True, alpha=0.3)
ax.set_xlim(-2, 2)
ax.set_ylim(-2, 2)

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

このグラフから、ラグランジュ乗数の「影の価格」としての意味が確認できます。

  1. 左図(最適値と影の価格): 制約パラメータ $c$(円の半径の二乗)を変えたときの最適値 $f^*(c) = c/2$ がプロットされています。$c = 1$ での接線の傾き(赤い破線)が $\lambda = 0.5$ に等しく、これは「制約を少し緩めると最適値がどれだけ改善するか」を正確に表しています。

  2. 右図(制約の変化と最適点): 制約 $c$ が大きくなる(円が大きくなる)ほど、最適点が原点から離れ、$f = xy$ の値が大きくなります。各制約円上の最適点(丸印)が原点を通る直線 $y = x$ 上にあることもわかります。

不等式制約のKKT条件の可視化

不等式制約を持つ問題を解き、アクティブ/非アクティブな制約を可視化します。

import numpy as np
import matplotlib.pyplot as plt
from scipy.optimize import minimize

# 問題: minimize f(x,y) = (x-2)^2 + (y-2)^2
# subject to: x + y <= 3, x >= 0, y >= 0

def objective(xy):
    return (xy[0] - 2)**2 + (xy[1] - 2)**2

# scipy.optimize で解く
constraints = [
    {'type': 'ineq', 'fun': lambda xy: 3 - xy[0] - xy[1]},  # x+y <= 3
    {'type': 'ineq', 'fun': lambda xy: xy[0]},                # x >= 0
    {'type': 'ineq', 'fun': lambda xy: xy[1]}                 # y >= 0
]

result = minimize(objective, x0=[0.5, 0.5], constraints=constraints)
x_opt, y_opt = result.x

fig, ax = plt.subplots(figsize=(8, 8))

# 実行可能領域
vertices = np.array([[0, 0], [3, 0], [0, 3], [0, 0]])
from matplotlib.patches import Polygon
feasible = Polygon(vertices[:3], alpha=0.15, color='green', label='Feasible region')
ax.add_patch(feasible)

# 制約の境界
ax.plot([0, 3], [3, 0], 'k-', linewidth=2, label='$x+y=3$')
ax.axhline(0, color='gray', linewidth=1)
ax.axvline(0, color='gray', linewidth=1)

# 等高線
x = np.linspace(-0.5, 4, 200)
y = np.linspace(-0.5, 4, 200)
X, Y = np.meshgrid(x, y)
Z = (X - 2)**2 + (Y - 2)**2
contour = ax.contour(X, Y, Z, levels=15, cmap='Blues', alpha=0.5)
ax.clabel(contour, inline=True, fontsize=8)

# 制約なしの最適点
ax.plot(2, 2, 'r^', markersize=12, label='Unconstrained min (2,2)')

# 制約付きの最適点
ax.plot(x_opt, y_opt, 'g*', markersize=15, zorder=5,
        label=f'Constrained min ({x_opt:.2f},{y_opt:.2f})')

# 勾配ベクトル
grad_f = np.array([2*(x_opt-2), 2*(y_opt-2)])
grad_g = np.array([1, 1])  # nabla g = (1, 1) for x+y=3
scale = 0.5
ax.annotate('', xy=(x_opt - scale*grad_f[0]/np.linalg.norm(grad_f),
                     y_opt - scale*grad_f[1]/np.linalg.norm(grad_f)),
            xytext=(x_opt, y_opt),
            arrowprops=dict(arrowstyle='->', color='red', lw=2.5))
ax.annotate('', xy=(x_opt + scale*grad_g[0]/np.linalg.norm(grad_g),
                     y_opt + scale*grad_g[1]/np.linalg.norm(grad_g)),
            xytext=(x_opt, y_opt),
            arrowprops=dict(arrowstyle='->', color='purple', lw=2.5))

ax.text(x_opt - 0.6, y_opt - 0.4, r'$-\nabla f$', color='red', fontsize=13,
        fontweight='bold')
ax.text(x_opt + 0.2, y_opt + 0.3, r'$\nabla g$', color='purple', fontsize=13,
        fontweight='bold')

ax.set_xlabel('x', fontsize=12)
ax.set_ylabel('y', fontsize=12)
ax.set_title('KKT Conditions: Inequality Constraints', fontsize=13)
ax.set_xlim(-0.5, 4)
ax.set_ylim(-0.5, 4)
ax.set_aspect('equal')
ax.legend(fontsize=10, loc='upper right')
ax.grid(True, alpha=0.3)

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

print(f"最適解: ({x_opt:.4f}, {y_opt:.4f})")
print(f"最適値: {objective(result.x):.4f}")
print(f"制約 x+y <= 3: {x_opt + y_opt:.4f} (アクティブ: {abs(x_opt + y_opt - 3) < 1e-6})")
print(f"制約 x >= 0:   {x_opt:.4f} (アクティブ: {abs(x_opt) < 1e-6})")
print(f"制約 y >= 0:   {y_opt:.4f} (アクティブ: {abs(y_opt) < 1e-6})")

このグラフから、KKT条件の幾何学的意味が理解できます。制約なしの最適点 $(2, 2)$(赤い三角)は実行可能領域(緑の領域)の外にあるため、制約付きの最適点(緑の星)は実行可能領域の境界、具体的には $x + y = 3$ の上に押し出されます。最適点 $(1.5, 1.5)$ では、$-\nabla f$(目的関数の減少方向、赤い矢印)と $\nabla g$(制約の勾配、紫の矢印)が同じ方向を向いており、KKT条件の停留性条件 $\nabla f + \mu \nabla g = 0$ が成り立っています。$x \geq 0$ と $y \geq 0$ の制約は非アクティブ(余裕がある)なので、対応する乗数はゼロです。

まとめ

本記事では、ラグランジュの未定乗数法の理論と応用について解説しました。

  • 制約付き最適化の最適点では、目的関数の勾配 $\nabla f$ と制約関数の勾配 $\nabla g$ が平行になる($\nabla f = \lambda \nabla g$)
  • ラグランジアン $\mathcal{L} = f – \lambda g$ の停留点条件を解くことで、制約付き最適化の候補点が全て得られる
  • ラグランジュ乗数 $\lambda$ は影の価格であり、制約を微小に緩めたときの最適値の変化率を表す
  • 不等式制約を含む問題ではKKT条件(停留性・主実行可能性・双対実行可能性・相補性)が最適性の必要条件
  • 相補性条件 $\mu_i g_i = 0$ は「制約がアクティブか、乗数がゼロか」のどちらかを意味する

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