からっぽのしょこ

読んだら書く!書いたら読む!同じ事は二度調べ(たく)ない

【R】Lpノルムの作図【PRMLのノート】

はじめに

 『パターン認識と機械学習』の独学時のまとめです。一連の記事は「数式の行間埋め」または「R・Pythonでのスクラッチ実装」からアルゴリズムの理解を補助することを目的としています。本とあわせて読んでください。

 この記事は、3.1.4項「正則化最小二乗法」を補足する内容です。LpノルムをR言語で作図します。

【前節の内容】

www.anarchive-beta.com

【他の節一覧】

www.anarchive-beta.com

【この節の内容】

・Lpノルム

 3.1.4項の正則化で利用するLpノルム($L^p$ノルム)をグラフで確認します。

 利用するパッケージを読み込みます。

# 利用するパッケージ
library(tidyverse)
library(gganimate)


・定義式の確認

 $M$次元ベクトル$\mathbf{w} = (w_1, w_2, \cdots, w_M)$のLpノルム$\|\mathbf{w}\|_p$は、次の式で定義されます。

$$ \|\mathbf{w}\|_q = \sqrt[q]{\sum_{j=1}^M |w_j|^q} = \Bigl( \sum_{j=1}^M |w_j|^q \Bigr)^{\frac{1}{q}} $$

 ベクトルを$\|$で挟んでそのベクトルのノルムを表します。$\sqrt[n]{x} = x^{\frac{1}{n}}$であり、$(\sqrt[n]{x})^n = (x^{\frac{1}{n}})^n = x$です。
 (Lpノルムと呼ぶくらいなので)$p$を使うことが多いようですが、本に合わせて$q$を使うことにします。

・L1ノルム

 $q = 1$のときL1ノルム($L^1$ノルム)と呼びます。L1ノルムのグラフを確認します。

 $q = 1$のとき、$x^{\frac{1}{1}} = x$なので累乗根$\sqrt{}$が外れ、次の式になります。

$$ \|\mathbf{w}\|_1 = \sum_{j=1}^M |w_j| $$

 つまり、L1ノルム$\|\mathbf{w}\|_1$は、$\mathbf{w}$の各要素の絶対値$|w_j|$の総和です。

 L1ノルムを計算します。2次元のグラフで描画するため$M = 2$とします。

# 作図用のwの範囲を指定
w_vec <- seq(-10, 10, by = 0.1)

# 値を指定
q <- 1

# Lpノルムを計算
norm_df <- tidyr::tibble(
  w_1 = rep(w_vec, times = length(w_vec)), # w1の値
  w_2 = rep(w_vec, each = length(w_vec)), # w2の値
  Lq_norm = (abs(w_1)^q + abs(w_2)^q)^(1 / q) # ノルム
)

# 確認
head(norm_df)
## # A tibble: 6 x 3
##     w_1   w_2 Lq_norm
##   <dbl> <dbl>   <dbl>
## 1 -10     -10    20  
## 2  -9.9   -10    19.9
## 3  -9.8   -10    19.8
## 4  -9.7   -10    19.7
## 5  -9.6   -10    19.6
## 6  -9.5   -10    19.5

 作図用に、$\mathbf{w} = (w_1, w_2)$の値とL1ノルムの値をデータフレームに格納します。

 グラフとして描画する$w_j$の値をw_vecとしてseq()で作成します。処理が重い場合は、この値を調整してください。
 w_vecとして作成した値に対して、全ての組み合わせを持つように$w_1, w_2$の値を作成します。w_1, w_2列の各行が、1つの点$\mathbf{w}$に対応します。

 絶対値はabs()で計算できます。

 L1ノルムの等高線グラフを作成します。

# ノルムの等高線図を作成
ggplot(norm_df, aes(x = w_1, y = w_2, z = Lq_norm, color = ..level..)) + 
  geom_contour() + # 等高線グラフ
  #geom_contour(breaks = 1.0) + # 等高線グラフ:(値を指定)
  coord_fixed(ratio = 1) + # アスペクト比
  labs(title = expression(group("|", group("|", w, "|"), "|")[q] == sqrt(sum(group("|", w[j], "|")^q, j==1, M), q)), 
       subtitle = paste0("q=", q), 
       x = expression(w[1]), y = expression(w[2]), color = paste0("L", q, " norm"))

 geom_contour()で等高線を描画できます。breaks引数に等高線を描く値を指定できます。
 L1ノルムの値(z軸の値)が1の線だけ描画してみます。

f:id:anemptyarchive:20211117152420p:plainf:id:anemptyarchive:20211117152456p:plain
L1ノルムのグラフ

 L1ノルムは、$M = 2$のとき3Dグラフを水平に切断した断面を上から見ると、正方形になります。

・L2ノルム

 $q = 2$のときL2ノルム($L^2$ノルム)と呼びます。L2ノルムのグラフを確認します。

 $q = 2$のとき、$|x|^2 = x^2$なので絶対値が外れ、次の式になります。

$$ \|\mathbf{w}\|_2 = \sqrt{\sum_{j=1}^M w_j^2} $$

 つまり、L2ノルム$\|\mathbf{w}\|_2$は、$\mathbf{w}$の各要素の二乗和の平方根です。

 $q = 2$のグラフを確認します。q2を代入すると、先ほどのコードで処理できます。

f:id:anemptyarchive:20211117152526p:plainf:id:anemptyarchive:20211117152528p:plain
L2ノルムのグラフ

 L2ノルムは、$M = 2$のとき3Dグラフを水平に切断した断面を上から見ると、円形になります。

 正則化では累乗根の計算を行わないので、その場合(式(3.25))のグラフも確認しましょう。

# 正則化項を計算
E_df <- tidyr::tibble(
  w_1 = rep(w_vec, times = length(w_vec)), # w1の値
  w_2 = rep(w_vec, each = length(w_vec)), # w2の値
  E_w = (abs(w_1)^q + abs(w_2)^q) / q # 正則化項
)

# 正則化項の等高線図を作成
ggplot(E_df, aes(x = w_1, y = w_2, z = E_w, color = ..level..)) + 
  geom_contour() + # 等高線グラフ
  #geom_contour(breaks = 1.0) + # 等高線グラフ:(値を指定)
  coord_fixed(ratio = 1) + # アスペクト比
  labs(title = expression(E[w](w) == sum(group("|", w[j], "|")^q, j==1, M)), 
       subtitle = paste0("q=", q), 
       x = expression(w[1]), y = expression(w[2]), color = expression(E[w](w)))

f:id:anemptyarchive:20211117152553p:plain
L2正則化項のグラフ

 値は変わりますが、形状は変化していません。

・おまけ:qとグラフの形状の関係

 最後に、$q$の値とグラフの形状の関係をアニメーションで確認します。

・作図コード(クリックで展開)

 forループでqの値を変更して繰り返しノルムを計算して、計算結果をデータフレームに追加していきます。

# qの最大値を指定
q_max <- 7.5

# 使用するqの値を作成
q_vec <- seq(0.5, q_max, by = 0.1)

# qごとにノルムを計算
anime_df <- tidyr::tibble()
for(q in q_vec) {
  # Lpノルムを計算
  tmp_norm_df <- tidyr::tibble(
    q = q, 
    w_1 = rep(w_vec, times = length(w_vec)), # w1の値
    w_2 = rep(w_vec, each = length(w_vec)), # w2の値
    Lq_norm = (abs(w_1)^q + abs(w_2)^q)^(1 / q) # ノルム
  )
  
  # 結果を結合
  anime_df <- rbind(anime_df, tmp_norm_df)
}

# 確認
head(anime_df)
## # A tibble: 6 x 4
##       q   w_1   w_2 Lq_norm
##   <dbl> <dbl> <dbl>   <dbl>
## 1   0.5 -10     -10    40. 
## 2   0.5  -9.9   -10    39.8
## 3   0.5  -9.8   -10    39.6
## 4   0.5  -9.7   -10    39.4
## 5   0.5  -9.6   -10    39.2
## 6   0.5  -9.5   -10    39.0


 gganimateパッケージを利用してアニメーション(gif画像)を作成します。

# ノルムの等高線図を作成
anime_graph <- ggplot(anime_df, aes(x = w_1, y = w_2, z = Lq_norm, color = ..level..)) + 
  geom_contour() + # 等高線グラフ
  gganimate::transition_manual(q) + # フレーム
  coord_fixed(ratio = 1) + # アスペクト比
  labs(title = expression(group("|", group("|", w, "|"), "|")[q] == sqrt(sum(group("|", w[j], "|")^q, j==1, M), q)), 
       subtitle = paste0("q={current_frame}"), 
       x = expression(w[1]), y = expression(w[2]), color = paste0("Lp norm"))

# gif画像に変換
gganimate::animate(anime_graph, nframes = length(q_vec), fps = 10)

f:id:anemptyarchive:20211117152629g:plain
Lpノルムのグラフ


 この項では、Lpノルムを確認しました。次項では、L1ノルムとL2ノルムを利用して正則化を行います。

参考文献

  • C.M.ビショップ著,元田 浩・他訳『パターン認識と機械学習 上下』,丸善出版,2012年.

おわりに

 Lpノルムを検索すると正方形や円形のグラフが出てくるけど、これはあくまで等高線の1本または断面図なんだな。
 接線の図も微妙にアレで、グラフの端と端が接しているわけではなく、更に言うとz軸の値は異なるので接してもいない(と思う?)。こっちは次の記事でやります。

【次節の内容】

www.anarchive-beta.com