Rでラグランジュの未定乗数法

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

解きたい問題

等式制約のもとで関数の最大値・最小値を求める問題を考えます。

\[
\begin{aligned}
&\text{最大化(または最小化)} && f(\boldsymbol{x}) \\
&\text{制約条件} && g(\boldsymbol{x}) = c
\end{aligned}
\]

ここで、

  • \(\boldsymbol{x} = (x_1, x_2, \dots, x_n)\)

    • 変数のベクトル
  • \(f(\boldsymbol{x})\)

    • 最大化(または最小化)したい目的関数
  • \(g(\boldsymbol{x})\)

    • 制約を表す関数
  • \(c\)

    • 制約の値を表す定数

です。

制約が1本で変数が2つの場合、制約を \(y = h(x)\) の形に解いて \(f(x, h(x))\) を1変数関数として微分する方法(代入法)が使えます。

しかし、制約が複雑で解けない場合や、変数・制約が多い場合には代入法は現実的ではありません。

ラグランジュの未定乗数法は、制約を解かずに扱える一般的な方法になります。

幾何学的な考え方

\(f\) の等高線(値が一定の曲線)と、制約曲線 \(g(\boldsymbol{x}) = c\) を同じ平面に描いて考えます。

制約曲線の上を動きながら \(f\) を大きくしていくと、\(f\) の等高線が制約曲線に接する点で、それ以上 \(f\) を大きくできなくなります。

もし等高線が制約曲線を横切っていれば、制約曲線に沿って動くことで \(f\) をさらに増減できるためです。

等高線と制約曲線が接するとは、両者の法線ベクトル(曲線(曲面)の接線に直交する向きのベクトル)が平行になることです。

関数の勾配ベクトルは等高線に直交しますので、

\[
\nabla f(\boldsymbol{x}) = \lambda \, \nabla g(\boldsymbol{x})
\]

となる実数 \(\lambda\) が存在します。ここで、

  • \(\nabla f\)

    • \(f\) の勾配ベクトル \(\left(\dfrac{\partial f}{\partial x_1}, \dots, \dfrac{\partial f}{\partial x_n}\right)\)
  • \(\nabla g\)

    • \(g\) の勾配ベクトル \(\left(\dfrac{\partial g}{\partial x_1}, \dots, \dfrac{\partial g}{\partial x_n}\right)\)
  • \(\lambda\)

    • ラグランジュ乗数(2本の勾配ベクトルの比例定数)

です。

ラグランジュ関数と停留条件

上の条件と制約条件をまとめて1つの式で表すために、ラグランジュ関数を次のように定義します。

\[
L(\boldsymbol{x}, \lambda) = f(\boldsymbol{x}) - \lambda \, \bigl( g(\boldsymbol{x}) - c \bigr)
\]

\(L\) を \(\boldsymbol{x}\) と \(\lambda\) のすべてで偏微分して 0 とおくと、

\[
\begin{aligned}
\frac{\partial L}{\partial x_i} &= \frac{\partial f}{\partial x_i} - \lambda \frac{\partial g}{\partial x_i} = 0 \quad (i = 1, \dots, n) \\
\frac{\partial L}{\partial \lambda} &= -\bigl( g(\boldsymbol{x}) - c \bigr) = 0
\end{aligned}
\]

となります。

上段の \(n\) 本の式が「勾配が平行」という条件 \(\nabla f = \lambda \nabla g\) であり、下段の式がもとの制約条件 \(g(\boldsymbol{x}) = c\) です。

つまり、制約付き問題を、\(n + 1\) 個の未知数 \((\boldsymbol{x}, \lambda)\) に関する \(n + 1\) 本の連立方程式に置き換えることができます。

その連立方程式を解いて得られる点を停留点(候補点)と呼びます。

制約が複数ある場合

制約が \(m\) 本ある場合は、制約ごとに乗数を用意します。

\[
L(\boldsymbol{x}, \boldsymbol{\lambda}) = f(\boldsymbol{x}) - \sum_{j=1}^{m} \lambda_j \bigl( g_j(\boldsymbol{x}) - c_j \bigr)
\]

ここで、

  • \(m\)

    • 制約の本数
  • \(g_j(\boldsymbol{x}) = c_j\)

    • \(j\) 番目の制約条件
  • \(\lambda_j\)

    • \(j\) 番目の制約に対応するラグランジュ乗数
  • \(\boldsymbol{\lambda} = (\lambda_1, \dots, \lambda_m)\)

    • 乗数のベクトル

です。未知数は \(n + m\) 個、方程式も \(n + m\) 本になります。

ラグランジュ乗数の意味

最適値を制約の値 \(c\) の関数として \(f^{*}(c)\) と書くと、

\[
\frac{d f^{*}(c)}{d c} = \lambda
\]

が成り立ちます(包絡線定理)。ここで、

  • \(f^{*}(c)\)

    • 制約の値が \(c\) のときの \(f\) の最適値
  • \(\lambda\)

    • 最適解におけるラグランジュ乗数

です。

すなわち \(\lambda\) は、制約を少し緩めたとき(たとえば予算 \(c\) を1単位増やしたとき)に最適値がどれだけ変化するかを表す感度になります。

注意点

  • 必要条件であって十分条件ではない

    • 連立方程式の解は極値の候補です。最大か最小か、あるいはどちらでもないかは、候補点での \(f\) の値の比較や、2階の条件(縁付きヘッセ行列)で判断します。
  • 制約の勾配が 0 でないこと

    • 最適解で \(\nabla g(\boldsymbol{x}) \neq \boldsymbol{0}\) でなければ、上の方法で最適解を見つけられないことがあります(制約想定)。
  • 等式制約が対象

    • 不等式制約 \(g(\boldsymbol{x}) \leq c\) には、ラグランジュの未定乗数法を拡張したKKT条件を使います。

Rコード

取り上げる例題

次の問題を扱います。

\[
\begin{aligned}
&\text{最大化・最小化} && f(x, y) = x y \\
&\text{制約条件} && g(x, y) = x^2 + y^2 = 1
\end{aligned}
\]

ここで、

  • \(x, y\)

    • 変数
  • \(f(x, y) = xy\)

    • 目的関数
  • \(g(x, y) = x^2 + y^2\)

    • 制約を表す関数(単位円)
  • 制約の値
    • \(c = 1\)

です。単位円の上で積 \(xy\) の最大値・最小値を求める問題です。

手計算による解

ラグランジュ関数は

\[
L(x, y, \lambda) = x y - \lambda \, (x^2 + y^2 - 1)
\]

です。ここで \(\lambda\) はラグランジュ乗数です。停留条件は

\[
\begin{aligned}
&\frac{\partial L}{\partial x} = y - 2 \lambda x = 0 \\
&\frac{\partial L}{\partial y} = x - 2 \lambda y = 0 \\
&x^2 + y^2 = 1
\end{aligned}
\]

となります。

第1式から \(y = 2\lambda x\) となりますので、第2式に代入すると \(x = 4 \lambda^2 x\) です。

\(x = 0\) の場合 \(y = 0\) となり制約を満たしませんので \(x \neq 0\) であり、\(\lambda^2 = 1/4\)、すなわち \(\lambda = \pm 1/2\) を得ます。

  • \(\lambda = +1/2\) のとき \(y = x\) となり、\(x^2 + y^2 = 1\) から \((x, y) = \pm\left(\tfrac{1}{\sqrt{2}}, \tfrac{1}{\sqrt{2}}\right)\)、\(f = \tfrac{1}{2}\) (最大値)
  • \(\lambda = -1/2\) のとき \(y = -x\) となり、\((x, y) = \pm\left(\tfrac{1}{\sqrt{2}}, -\tfrac{1}{\sqrt{2}}\right)\)、\(f = -\tfrac{1}{2}\) (最小値)

以降のRコードで、この結果を数値計算と図で確かめます。

問題の定義

# 目的関数
f <- function(x, y) x * y

# 制約を表す関数
g <- function(x, y) x^2 + y^2

# 制約の値
c0 <- 1

# 目的関数の勾配ベクトル
grad_f <- function(x, y) c(y, x)

# 制約を表す関数の勾配ベクトル
grad_g <- function(x, y) c(2 * x, 2 * y)

等高線と制約曲線を描く

まず、\(f\) の等高線と制約曲線(単位円)を重ねて描きます。

円の上を動いたときに \(f\) が最大・最小になる点は、等高線が円に接する点になります。

library(ggplot2)

# 等高線を描くための格子データ
grid_df <- expand.grid(
  x = seq(-1.5, 1.5, length.out = 301),
  y = seq(-1.5, 1.5, length.out = 301)
) |>
  transform(z = f(x, y))

# 制約曲線(単位円)のデータ
theta_seq <- seq(0, 2 * pi, length.out = 401)
circle_df <- data.frame(
  x = sqrt(c0) * cos(theta_seq),
  y = sqrt(c0) * sin(theta_seq)
)

p_base <- ggplot() +
  geom_contour(
    data = grid_df,
    aes(x = x, y = y, z = z, colour = after_stat(level)),
    breaks = seq(-1.2, 1.2, by = 0.2)
  ) +
  geom_path(data = circle_df, aes(x = x, y = y), linewidth = 1.2) +
  scale_colour_gradient2(
    low = "blue", mid = "grey70", high = "red", midpoint = 0
  ) +
  coord_equal() +
  labs(
    title = "f(x, y) = xy の等高線と制約曲線 x² + y² = 1",
    x = "x",
    y = "y",
    colour = "f の値"
  ) +
  theme_bw()

p_base
Figure 1

黒い太線が制約曲線、色付きの細線が \(f\) の等高線です。

等高線と制約曲線が接すること無く横切っている場合、制約曲線に沿って動くことで \(f\) をさらに増減できてしまうことが確認できます。

停留条件の連立方程式をニュートン法で解く

停留条件は、未知数 \((x, y, \lambda)\) についての3本の連立方程式

\[
\boldsymbol{F}(x, y, \lambda) =
\begin{pmatrix}
y - 2 \lambda x \\
x - 2 \lambda y \\
x^2 + y^2 - 1
\end{pmatrix}
= \boldsymbol{0}
\]

です。

ここで \(\boldsymbol{F}\) は3つの停留条件を縦に並べたベクトル値関数です(第3成分は \(\partial L / \partial \lambda\) の符号を反転したもので、解は変わりません)。

Note

例題のラグランジュ関数

\[
L(x, y, \lambda) = xy - \lambda \, (x^2 + y^2 - 1)
\]

の括弧を展開すると

\[
L = xy - \lambda x^2 - \lambda y^2 + \lambda
\]

となります。\(\lambda\) で偏微分した

\[
\frac{\partial L}{\partial \lambda} = -x^2 - y^2 + 1 = -(x^2 + y^2 - 1)
\]

を 0 とおくと \(x^2 + y^2 = 1\) となり、制約条件そのものが得られます。

したがって、第3成分 \(x^2 + y^2 - 1\) は、\(\partial L / \partial \lambda\) の符号を反転したものであり、0 とおいた方程式の解は変わりません。

ニュートン法では、現在の値 \(\boldsymbol{v}_k = (x_k, y_k, \lambda_k)\) から次の値を

\[
\boldsymbol{v}_{k+1} = \boldsymbol{v}_k - J(\boldsymbol{v}_k)^{-1} \, \boldsymbol{F}(\boldsymbol{v}_k)
\]

で更新します。ここで \(J\) は \(\boldsymbol{F}\) のヤコビ行列

\[
J(x, y, \lambda) =
\begin{pmatrix}
-2\lambda & 1 & -2x \\
1 & -2\lambda & -2y \\
2x & 2y & 0
\end{pmatrix}
\]

です。

Note

ヤコビ行列は、ベクトル値関数 \(\boldsymbol{F}\) の各成分を、各変数で偏微分して並べたものです。

\(\boldsymbol{F}\) の第 \(i\) 成分を \(F_i\)、変数ベクトルを \(\boldsymbol{v} = (v_1, v_2, v_3) = (x, y, \lambda)\) とすると、ヤコビ行列の \((i, j)\) 成分は

\[
J_{ij} = \frac{\partial F_i}{\partial v_j}
\]

です。ここで、

\[
F_1 = y - 2\lambda x, \qquad F_2 = x - 2\lambda y, \qquad F_3 = x^2 + y^2 - 1
\]

です。各行を計算します。他の変数は定数として扱います。

第1行(\(F_1 = y - 2\lambda x\))

\[
\frac{\partial F_1}{\partial x} = -2\lambda, \qquad
\frac{\partial F_1}{\partial y} = 1, \qquad
\frac{\partial F_1}{\partial \lambda} = -2x
\]

第2行(\(F_2 = x - 2\lambda y\))

\[
\frac{\partial F_2}{\partial x} = 1, \qquad
\frac{\partial F_2}{\partial y} = -2\lambda, \qquad
\frac{\partial F_2}{\partial \lambda} = -2y
\]

第3行(\(F_3 = x^2 + y^2 - 1\))

\[
\frac{\partial F_3}{\partial x} = 2x, \qquad
\frac{\partial F_3}{\partial y} = 2y, \qquad
\frac{\partial F_3}{\partial \lambda} = 0
\]

\(F_3\) には \(\lambda\) が含まれていないので、\(\lambda\) での偏微分は 0 になります。

これらを行ごとに並べると、

\[
J(x, y, \lambda) =
\begin{pmatrix}
-2\lambda & 1 & -2x \\
1 & -2\lambda & -2y \\
2x & 2y & 0
\end{pmatrix}
\]

となります。

# 停留条件を並べたベクトル値関数
stationary_fn <- function(v) {
  x <- v[1]
  y <- v[2]
  lambda <- v[3]
  c(
    y - 2 * lambda * x,
    x - 2 * lambda * y,
    g(x, y) - c0
  )
}

# ヤコビ行列
stationary_jac <- function(v) {
  x <- v[1]
  y <- v[2]
  lambda <- v[3]
  matrix(
    c(
      -2 * lambda, 1, -2 * x,
      1, -2 * lambda, -2 * y,
      2 * x, 2 * y, 0
    ),
    nrow = 3,
    byrow = TRUE
  )
}

# ニュートン法(収束しなければ NA を返す)
newton_solve <- function(v, tol = 1e-12, max_iter = 100) {
  for (k in seq_len(max_iter)) {
    fv <- stationary_fn(v)
    if (max(abs(fv)) < tol) {
      return(v)
    }
    v <- tryCatch(
      v - solve(stationary_jac(v), fv),
      error = function(e) rep(NA_real_, 3)
    )
    if (anyNA(v)) {
      break
    }
  }
  rep(NA_real_, 3)
}

# 乱数で初期値を50組つくり、それぞれからニュートン法を実行する
set.seed(20261021)
start_list <- lapply(seq_len(50), function(i) runif(3, min = -1.5, max = 1.5))
sol_mat <- do.call(rbind, lapply(start_list, newton_solve))
colnames(sol_mat) <- c("x", "y", "lambda")

# 収束した解を丸めて重複を除く
stat_df <- sol_mat |>
  as.data.frame() |>
  na.omit() |>
  round(6) |>
  unique()

# 目的関数の値と、最大・最小の区別を加える
stat_df$f_value <- f(stat_df$x, stat_df$y)
stat_df$type <- ifelse(
  stat_df$f_value == max(stat_df$f_value), "最大",
  ifelse(stat_df$f_value == min(stat_df$f_value), "最小", "")
)

# 表示用に並べ替える
stat_df <- stat_df[order(-stat_df$f_value, stat_df$x), ]
rownames(stat_df) <- NULL

knitr::kable(
  stat_df,
  col.names = c("x", "y", "λ", "f の値", "判定"),
  digits = 4
)
xyλf の値判定
-0.7071-0.70710.50.5最大
0.70710.70710.50.5最大
-0.70710.7071-0.5-0.5最小
0.7071-0.7071-0.5-0.5最小
# 1/√2の確認
1 / sqrt(2)
[1] 0.7071068

乱数で選んだ初期値から出発しても、停留点は4個に集まります。

得られた \(x, y, \lambda, f\) の値は、手計算の結果(\(\lambda = \pm 1/2\)、\(f = \pm 1/2\))と一致しています。

勾配ベクトルが平行になっていることを確かめる

ラグランジュの未定乗数法の要点は、最適解で \(\nabla f = \lambda \nabla g\) が成り立つことでした。

各停留点で \(\nabla f\) と \(\nabla g\) を計算し、確かめます。

# 各停留点で勾配ベクトルを計算する
grad_df <- do.call(rbind, lapply(seq_len(nrow(stat_df)), function(i) {
  x <- stat_df$x[i]
  y <- stat_df$y[i]
  lambda <- stat_df$lambda[i]
  gf <- grad_f(x, y)
  gg <- grad_g(x, y)
  data.frame(
    x = x,
    y = y,
    fx = gf[1],
    fy = gf[2],
    gx = gg[1],
    gy = gg[2],
    # lambda 倍した制約の勾配との差(0 になれば平行)
    diff_norm = sqrt(sum((gf - lambda * gg)^2))
  )
}))

knitr::kable(
  grad_df,
  col.names = c("x", "y", "∂f/∂x", "∂f/∂y", "∂g/∂x", "∂g/∂y", "|∇f - λ∇g|"),
  digits = 6
)
xy∂f/∂x∂f/∂y∂g/∂x∂g/∂y|∇f - λ∇g|
-0.707107-0.707107-0.707107-0.707107-1.414214-1.4142140
0.7071070.7071070.7071070.7071071.4142141.4142140
-0.7071070.7071070.707107-0.707107-1.4142141.4142140
0.707107-0.707107-0.7071070.7071071.414214-1.4142140

最後の列 \(|\nabla f - \lambda \nabla g|\) は、4つの停留点のすべてで 0 になります。

等高線の図に、各停留点での勾配ベクトルを重ねます。

赤の矢印が \(\nabla f\)、青の矢印が \(\nabla g\) です(見やすいように長さを 0.3 倍にしています)。

# 矢印の長さを調整する倍率
arrow_scale <- 0.3

p_base +
  geom_segment(
    data = grad_df,
    aes(x = x, y = y, xend = x + arrow_scale * fx, yend = y + arrow_scale * fy),
    colour = "red",
    linewidth = 0.9,
    arrow = arrow(length = unit(0.15, "cm"))
  ) +
  geom_segment(
    data = grad_df,
    aes(x = x, y = y, xend = x + arrow_scale * gx, yend = y + arrow_scale * gy),
    colour = "blue",
    linewidth = 0.9,
    linetype = "dashed",
    arrow = arrow(length = unit(0.15, "cm"))
  ) +
  geom_point(data = grad_df, aes(x = x, y = y), size = 2.5) +
  labs(
    title = "停留点での勾配ベクトル(赤: ∇f、青の破線: ∇g)",
    colour = "f の値"
  )
Figure 2

4つの停留点のどこでも、赤と青の矢印が同じ直線上に並んでいます。

\(\lambda > 0\) の点(最大点)では同じ向き、\(\lambda < 0\) の点(最小点)では逆向きです。

こちらが「\(\nabla f = \lambda \nabla g\)」の幾何学的な意味になります。

制約曲線上で f を1変数関数として見る

制約曲線は \(x = \cos\theta,\ y = \sin\theta\) (\(\theta\) は角度)とパラメータ表示できます。

このとき \(f = \cos\theta \sin\theta = \tfrac{1}{2}\sin 2\theta\) となり、制約付きの問題が \(\theta\) の1変数関数の極値問題になります。

# 制約曲線上の f を角度 theta の関数として計算する
theta_df <- data.frame(theta = theta_seq) |>
  transform(f_value = f(cos(theta), sin(theta)))

# 停留点の角度(0 から 2π の範囲にそろえる)
stat_df$theta <- atan2(stat_df$y, stat_df$x) %% (2 * pi)

ggplot(theta_df, aes(x = theta, y = f_value)) +
  geom_hline(yintercept = 0, colour = "grey60") +
  geom_line(linewidth = 1) +
  geom_point(
    data = stat_df,
    aes(x = theta, y = f_value, colour = type),
    size = 3
  ) +
  scale_x_continuous(
    breaks = seq(0, 2 * pi, by = pi / 2),
    labels = c("0", "π/2", "π", "3π/2", "2π")
  ) +
  labs(
    title = "制約曲線上での f の値(θ は角度)",
    x = "θ",
    y = "f の値",
    colour = "判定"
  ) +
  theme_bw()
Figure 3

ニュートン法で求めた4つの停留点が、この曲線の山の頂点・谷の底とちょうど重なっています。

ラグランジュの未定乗数法は、制約を解かなくても、この1変数化と同じ候補点を探索できます。

ラグランジュ乗数が表す感度を確かめる

最後に、\(\lambda\) が「制約の値 \(c\) を変えたときの最適値の変化率」であることを数値で確かめます。

制約を \(x^2 + y^2 = c\) に変えたときの最大値を \(f^{*}(c)\) とします。

円の半径は \(\sqrt{c}\) ですので、\(x = \sqrt{c}\cos\theta,\ y = \sqrt{c}\sin\theta\) として \(\theta\) について optimize() で最大化します。

# 制約の値が cc のときの最大値を数値的に求める
f_star <- function(cc) {
  radius <- sqrt(cc)
  optimize(
    function(theta) f(radius * cos(theta), radius * sin(theta)),
    interval = c(0, pi / 2),
    maximum = TRUE,
    tol = 1e-10
  )$objective
}

# 差分近似で f*(c) の c に関する微分を求める
step_h <- 1e-4
df_dc <- (f_star(c0 + step_h) - f_star(c0 - step_h)) / (2 * step_h)

# 最大点に対応するラグランジュ乗数
lambda_max <- stat_df$lambda[stat_df$type == "最大"][1]

data.frame(
  項目 = c("df*/dc の数値微分", "最大点のラグランジュ乗数 λ"),
  値 = c(df_dc, lambda_max)
) |>
  knitr::kable(digits = 6)
項目値
df*/dc の数値微分0.5
最大点のラグランジュ乗数 λ0.5

数値微分で求めた \(d f^{*}/dc\) は、最大点の \(\lambda = 1/2\) と一致します。

手計算でも、\(x^2 + y^2 = c\) のときの最大値は \(f^{*}(c) = c/2\) ですので、\(d f^{*}/dc = 1/2 = \lambda\) です。

Note

ラグランジュ関数は

\[
L(x, y, \lambda) = xy - \lambda \, (x^2 + y^2 - c)
\]

です。また、停留条件は

\[
\begin{aligned}
\frac{\partial L}{\partial x} &= y - 2\lambda x = 0 \\
\frac{\partial L}{\partial y} &= x - 2\lambda y = 0 \\
\frac{\partial L}{\partial \lambda} &= -(x^2 + y^2 - c) = 0
\end{aligned}
\]

です。

第1式から \(y = 2\lambda x\) となり、第2式に代入すると \(x = 4\lambda^2 x\) です。

\(x = 0\) なら \(y = 0\) となり、\(c > 0\) の制約を満たしませんので \(x \neq 0\) であり、\(\lambda = \pm 1/2\) が得られます。

最大値に対応するのは \(\lambda = 1/2\) の場合です。このとき \(y = x\) ですので、制約条件は

\[
x^2 + y^2 = 2x^2 = c \quad \Longrightarrow \quad x^2 = \frac{c}{2}
\]

となります。したがって、このときの \(f\) の値は

\[
f = xy = x^2 = \frac{c}{2}
\]

です(\(\lambda = -1/2\) のときは \(y = -x\) で、\(f = -x^2 = -c/2\) となり最小値です)。

以上より \(f^{*}(c) = c/2\) です。

\(c\) で微分すると \(d f^{*}/dc = 1/2\) となり、最大点の \(\lambda = 1/2\) と一致します。

まとめ

  • 制約付きの最適化は、ラグランジュ関数 \(L = f - \lambda (g - c)\) の停留条件(連立方程式)に置き換えられる。
  • 最適解では、等高線と制約曲線が接し、\(\nabla f\) と \(\nabla g\) が平行になる。
  • 得られた停留点は候補であり、最大・最小の判定は \(f\) の値の比較などで別に行う。
  • \(\lambda\) は、制約を緩めたときの最適値の変化率を表す。

以上です。