からっぽのしょこ

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

【R】3.1.0:基底関数【PRMLのノート】

はじめに

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

 この記事は、3.1節の内容です。R言語で基底関数を実装してグラフを作成します。

【他の節一覧】

www.anarchive-beta.com

【この節の内容】

3.1.0 基底関数

 線形回帰モデルにおいて、入力変数に非線形な変換を行う基底関数をグラフで確認します。

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

# 3.1.0項で利用するパッケージ
library(tidyverse)


 線形回帰モデルを簡単に確認しておきます。

 入力$x$に非線形な変換を与えるを関数を基底関数と呼びます。ここでは、基底関数を$\phi_j(x)$で表します。
 $M$個の基底関数をまとめて次のように表記します。

$$ \boldsymbol{\phi}(x) = (\phi_0(x), \phi_1(x), \cdots, \phi_{M-1}(x))^{\top} $$

 ただし、0番目については$\phi_0(x) = 1$とします。
 また、$M$個のパラメータをまとめて次のように表記します。

$$ \mathbf{w} = (w_0, w_1, \cdots, w_{M-1})^{\top} $$

 ただし、0番目の要素$w_0$はバイアスです。
 基底関数によって変換を行った入力$\boldsymbol{\phi}(x)$とパラメータ$\mathbf{w}$との内積(積の和)を出力$y(x, \mathbf{w})$とします。

$$ y(x, \mathbf{w}) = \sum_{j=0}^{M-1} w_j \phi_j(x) = \mathbf{w}^{\top} \boldsymbol{\phi}(x) \tag{3.3} $$

 0番目については、$w_0 \phi_0(x) = w_0$になります。
 この例では入力をスカラ$x$としますが、ベクトル$\mathbf{x} = (x_1, x_2, \cdots, x_n)^{\top}$の場合もあります。

 この記事では、基底関数$\phi_j(x)$として用いられる3つの関数を確認します。

・多項式基底関数

 1つ目は、多項式基底関数です。

・定義の確認

 多項式$\sum_{j=0}^{M-1} w_j \phi_j(x)$について、$j$番目の基底関数を次の式とします。

$$ \phi_j(x) = x^j $$


 この式を関数として定義します。

# 多項式基底関数を作成
phi <- function(x, j) {
  y <- x^j
  return(y)
}


 $j$を指定して、$\phi_j(x)$を計算します。

# 値を指定
j <- 3

# x軸の値を作成
x_vals <- seq(-1, 1, by = 0.001)

# 出力を計算
res_df <- tibble::tibble(
  x = x_vals, 
  y = phi(x, j), 
  label = factor(paste0("j=", j))
)

 この例では、グラフのx軸の表示範囲x_vals-1から1とします。
 作図用に計算結果をデータフレームに格納しておきます。作成したデータフレームは次のようになります。

# 確認
head(res_df)
## # A tibble: 6 x 3
##        x      y label
##    <dbl>  <dbl> <fct>
## 1 -1     -1     j=3  
## 2 -0.999 -0.997 j=3  
## 3 -0.998 -0.994 j=3  
## 4 -0.997 -0.991 j=3  
## 5 -0.996 -0.988 j=3  
## 6 -0.995 -0.985 j=3


 多項式基底関数のグラフを作成します。

# 作図
ggplot(res_df, aes(x = x, y = y, color = label)) + 
  geom_line() + 
  labs(title = "Polynomial Basis Function", color = "params", 
       y = expression(phi(x)))

f:id:anemptyarchive:20210816233755p:plain
多項式基底関数のグラフ

 作図コードは(タイトルを変更しますが)全てのグラフで共通の処理です。

・パラメータの設定

 $j$の値とグラフの形状の関係を確認します。

# 値を指定
j_vec <- seq(0, 10)

# 出力を計算
res_df <- tibble::tibble(
  j = rep(j_vec, each = length(x_vals)), 
  x = rep(x_vals, times = length(j_vec)), 
  y = phi(x, j), 
  label = factor(paste0("j=", j), levels = paste0("j=", j_vec))
)

 この例では、$j$を0から10の整数とします。
 j_vecの要素数に対応するように、x_valsを複製します。
 また、複製したx_valsに対応するように、j_vecを要素ごとにx_vals個分複製します。

 作成したデータフレームは次のようになります。

# 確認
head(res_df)
## # A tibble: 6 x 4
##       j      x     y label
##   <int>  <dbl> <dbl> <fct>
## 1     0 -1         1 j=0  
## 2     0 -0.999     1 j=0  
## 3     0 -0.998     1 j=0  
## 4     0 -0.997     1 j=0  
## 5     0 -0.996     1 j=0  
## 6     0 -0.995     1 j=0


 $j$ごとのグラフを重ねて描画します。

f:id:anemptyarchive:20210817021638p:plain
多項式基底関数のグラフ

 $j$が偶数のときはマイナスが打ち消されるので、全ての範囲で$y \geq 0$となります。(いい感じにy軸の値が-1から1の値になっているのは、x軸の範囲が-1から1の値なためです。)

・ガウス基底関数

 2つ目は、ガウス基底関数です。

・定義の確認

 ガウス基底関数は次の式で定義されます。

$$ \phi_j(x) = \exp \left\{ - \frac{(x - \mu_j)^2}{2 s^2} \right\} \tag{3.4} $$

 指数関数$\exp(x)$について、$x < 0$のとき$0 < \exp(x) < 1$で、$\exp(0) = 1$です。また、分母分子がどちらも2乗しているので、分数部分は常に0以上の値になります。よって、$\exp(\cdot)$の中は常に0以下の値になり、ガウス基底関数の出力は$0 < \phi_j(x) \leq 1$になります。

 この式を関数として定義します。

# ガウス基底関数を作成
phi <- function(x, mu, s) {
  y <- exp(-(x - mu)^2 / (2 * s^2))
  return(y)
}


 $\mu_j$と$s$を指定して、$\phi_j(x)$を計算します。

# 値を指定
mu <- 0
s <- 0.2

# x軸の値を作成
x_vals <- seq(-1, 1, by = 0.001)

# 出力を計算
res_df <- tibble::tibble(
  x = x_vals, 
  y = phi(x, mu, s), 
  label = factor(paste0("mu=", mu, ", s=", s))
)


 ガウス基底関数のグラフを作成します。

# 作図
ggplot(res_df, aes(x = x, y = y, color = label)) + 
  geom_line() + 
  labs(title = "Gaussian Basis Function", color = "params", 
       y = expression(phi(x)))

f:id:anemptyarchive:20210816233849p:plain
ガウス基底関数のグラフ

 釣鐘型のグラフになります。

・パラメータの設定

 $\mu_j$の値とグラフの形状の関係を確認します。

# 値を指定
mu_vec <- seq(-1, 1, by = 0.2)
s <- 0.2

# 出力を計算
res_df <- tibble::tibble(
  mu = rep(mu_vec, each = length(x_vals)), 
  x = rep(x_vals, times = length(mu_vec)), 
  y = phi(x, mu, s), 
  label = factor(
    paste0("mu=", mu, ", s=", s), 
    levels = paste0("mu=", mu_vec, ", s=", s)
  )
)

 この例では、$s$を固定して、$\mu_j$を-1から10.2刻みの値とします。

 $\mu_j$ごとのグラフを重ねて描画します。

f:id:anemptyarchive:20210817021921p:plain
ガウス基底関数のグラフ

 $\mu_j$は、形状には影響せず、山が横に移動します。

 続いて、$s$の値とグラフの形状の関係を確認します。

# 値を指定
mu <- 0
s_vec <- seq(0.1, 1, by = 0.1)

# 出力を計算
res_df <- tibble::tibble(
  s = rep(s_vec, each = length(x_vals)), 
  x = rep(x_vals, times = length(s_vec)), 
  y = phi(x, mu, s), 
  label = factor(
    paste0("mu=", mu, ", s=", s), 
    levels = paste0("mu=", mu, ", s=", s_vec)
  )
)

 この例では、$\mu$を固定して、$s_j$を0.1から10.1刻みの値とします。

 $s_j$ごとのグラフを重ねて描画します。

f:id:anemptyarchive:20210817022213p:plain
ガウス基底関数のグラフ

 $s$は山の広がり具合に影響します。

・シグモイド基底関数

 3つ目は、シグモイド基底関数です。

・定義の確認

 シグモイド基底関数は、ロジスティックシグモイド関数

$$ \sigma(a) = \frac{1}{1 + \exp(-a)} \tag{3.6} $$

を用いて、次の式で定義されます。

$$ \phi_j(x) = \sigma \left( \frac{x - \mu_j}{s} \right) \tag{3.5} $$

 ロジスティックシグモイド関数によって、シグモイド基底関数の出力は$0 \leq \phi_j(x) \leq 1$になります。

 この2つの式を関数として定義します。

# ロジスティックシグモイド関数を作成
sigma <- function(x) {
  y = 1 / (1 + exp(-x))
}

# シグモイド基底関数を作成
phi <- function(x, mu, s) {
  a <- (x - mu) / s
  y <- sigma(a)
  return(y)
}


 $\mu_j,\ s$を指定して、$\phi_j(x)$を計算します。

# 値を指定
mu <- 0
s <- 0.1

# x軸の値を作成
x_vals <- seq(-1, 1, by = 0.001)

# 出力を計算
res_df <- tibble::tibble(
  x = x_vals, 
  y = phi(x, mu, s), 
  label = factor(paste0("mu=", mu, ", s=", s))
)


 シグモイド基底関数のグラフを作成します。

# 作図
ggplot(res_df, aes(x = x, y = y, color = label)) + 
  geom_line() + 
  labs(title = "Sigmoid Basis Function", color = "params", 
       y = expression(phi(x)))

f:id:anemptyarchive:20210817022301p:plain
シグモイド基底関数のグラフ

 S字型のグラフになります。

・パラメータの設定

 $\mu_j$の値とグラフの形状の関係を確認します。

# 値を指定
mu_vec <- seq(-1, 1, by = 0.2)
s <- 0.1

# 出力を計算
res_df <- tibble::tibble(
  mu = rep(mu_vec, each = length(x_vals)), 
  x = rep(x_vals, times = length(mu_vec)), 
  y = phi(x, mu, s), 
  label = factor(
    paste0("mu=", mu, ", s=", s), 
    levels = paste0("mu=", mu_vec, ", s=", s)
  )
)

 この例では、$s$を固定して、$\mu_j$を-1から10.2刻みの値とします。

 $\mu_j$ごとのグラフを重ねて描画します。

f:id:anemptyarchive:20210817022422p:plain
シグモイド基底関数のグラフ

 ガウス基底関数のときと同様に、形状には影響せず、山が横に移動します。

 続いて、$s$の値とグラフの形状の関係を確認します。

# 値を指定
mu <- 0
s_vec <- seq(0.1, 1, by = 0.1)

# 出力を計算
res_df <- tibble::tibble(
  s = rep(s_vec, each = length(x_vals)), 
  x = rep(x_vals, times = length(s_vec)), 
  y = phi(x, mu, s), 
  label = factor(
    paste0("mu=", mu, ", s=", s), 
    levels = paste0("mu=", mu, ", s=", s_vec)
  )
)

 この例では、$\mu$を固定して、$s_j$を0.1から10.1刻みの値とします。

 $s_j$ごとのグラフを重ねて描画します。

f:id:anemptyarchive:20210817022453p:plain
シグモイド基底関数のグラフ

 $s$はS字の曲がり具合に影響します。

参考文献

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

おわりに

 これまでの記事で一番早く書き終わった気がします。何か間違ってたら教えて下さい。
 つまみ食いしているPRMLですが、次は色んな節に散らばっている線形回帰モデル関連を進めようと思ってます。その次はロジスティック回帰をやって・・・

 2021年8月16日は、BEYOOOOONDSの小林萌花さんの21歳のお誕生日です!!!

 とりあえず新曲を1つと

ピアノ曲をもう1つどーぞ!

 ほのぴあの大好き♪

【次節の内容】

つづく?