はじめに

ニューラルネットの具体例における学習係数の算出では特異学習理論に基づいて学習係数 \(\lambda = 3/4\) を求めた。

一方、Remark 7.1 (2) (p.219)では、完全な特異点解消が難しい場合には定理 7.1 (4) を使って数値計算により学習係数を求めることができると書かれている。

本記事では上記記事と同じ学習モデルに対して数値計算により学習係数を求めてみる。

学習モデル

真の分布は標準正規分布

\[ q(x) = \mathrm{Normal}(0, 1) = \frac{1}{\sqrt{2\pi}} \exp\left(-\frac{x^2}{2}\right) \]

学習モデルは3層ニューラルネットワーク

\[ p(x|w) = \mathrm{Normal}(a\sigma(bx) + c\sigma(dx), 1) = \frac{1}{\sqrt{2\pi}} \exp\left(-\frac{(x - (a\sigma(bx) + c\sigma(dx)))^2}{2}\right) \]

カルバック・ライブラ距離は次のようになる。

\[ \begin{align} K(w) &= \int q(x) \log \frac{q(x)}{p(x|w)} dx \\ &= \int q(x)\log \frac{\frac{1}{\sqrt{2\pi}} \exp(-\frac{x^2}{2})}{\frac{1}{\sqrt{2\pi}} \exp\big(-\frac{(x - (a\sigma(bx) + c\sigma(dx)))^2}{2}\big)} dx\\ &= \int q(x)\log \frac{\exp(-\frac{x^2}{2})}{\exp\big(-\frac{x^2 + (a\sigma(bx) + c\sigma(dx))^2}{2}\big)} dx \\ &= \int q(x)\log \exp\big(-\frac{x^2}{2} + \frac{x^2 + (a\sigma(bx) + c\sigma(dx))^2}{2} \big) dx \\ &= \int q(x) \big(\frac{(a\sigma(bx) + c\sigma(dx))^2}{2} \big) dx \\ &= \frac{1}{2} \int (a\sigma(bx) + c\sigma(dx))^2 q(x) dx \\ \end{align} \]

これらをRで実装する。

# 真の分布
true_dist <- dnorm

# 活性化関数
sigma <- function(x) exp(x) - 1

# カルバック・ライブラ距離
K <- function(a, b, c, d) {
  f <- function(x) {  
    mean <- a * sigma(b * x) + c * sigma(d * x)
    mean^2 * true_dist(x)
  }
  0.5 * integrate(f, lower = -10, upper = 10)$value
}

定理 7.1 (4)

\(V(t)\) を体積関数

\[ V(t) = \int_{K(w) < t} \varphi(w) dw \]

とする。

任意の \(a > 0 \ \ (a \neq 1)\) に対して次が成り立つ。

\[ \lambda = \lim_{t\rightarrow 0} \frac{\log \left\{ V(at)/V(t) \right\} }{\log a} \]

\(\lambda\) が求めたい学習係数である。

これをRで実装する。

事前分布 \(\varphi(w)\) はなんでもいいらしいので一様分布を仮定する。

# パラメータ空間のカルバック・ライブラ距離を計算する
set.seed(314)
N <- 1000000
min <- -1
max <- 1
a <- runif(N, min, max)
b <- runif(N, min, max)
c <- runif(N, min, max)
d <- runif(N, min, max)
KL_values <- mapply(K, a = a, b = b, c = c, d= d)
# 体積関数
V <- function(t) {
  sum(KL_values < t)
}
# t を次第に小さくする
t <- 10 ^ seq(-2, -7, length.out = 100)

# a > 0 (a != 1) ならばなんでもいい
a <- 10
f <- function(t) log( V(a*t) / V(t) ) / log(a)

# 学習係数の計算
lambda <- Vectorize(f)(t)
# グラフ描画の準備
library(scales)
reverselog_trans <- function(base = exp(1)) {
  trans <- function(x) -log(x, base)
  inv <- function(x) base^(-x)
  trans_new(paste0("reverselog-", format(base)), trans, inv, 
            log_breaks(base = base), 
            domain = c(1e-100, Inf))
}

# グラフの描画
library(ggplot2)
df <- data.frame(t = t, lambda = lambda)
breaks <- 10^seq(-1, -10, by=-1)
ggplot(df, aes(t, lambda)) + geom_line() + 
  scale_x_continuous(trans = reverselog_trans(), breaks = breaks) +
  geom_hline(yintercept = 3/4, color = "red") +
  geom_text(aes(0, 3/4, label = 3/4, vjust = -0.5, hjust = 1.3), color = "red")

\(t \rightarrow 0\) とすることで \(\lambda = 3/4 = 0.75\) に収束することが分かった。

しかし、数値計算の精度の問題で 0 に近づきすぎると 0.75 から離れる。

おまけ

\(a\) の大きさによって \(\lambda\) の収束に違いが出るのか実験。

t <- 10 ^ seq(-2, -7, length.out = 100)

a <- 10
f <- function(t) log( V(a*t) / V(t) ) / log(a)
lambda <- Vectorize(f)(t)
df <- data.frame(a = a, t = t, lambda = lambda)

a <- 100
f <- function(t) log( V(a*t) / V(t) ) / log(a)
lambda <- Vectorize(f)(t)
df <- rbind(df, data.frame(a = a, t = t, lambda = lambda))

a <- 1000
f <- function(t) log( V(a*t) / V(t) ) / log(a)
lambda <- Vectorize(f)(t)
df <- rbind(df, data.frame(a = a, t = t, lambda = lambda))

a <- 10000
f <- function(t) log( V(a*t) / V(t) ) / log(a)
lambda <- Vectorize(f)(t)
df <- rbind(df, data.frame(a = a, t = t, lambda = lambda))

library(ggplot2)
breaks <- 10^seq(-1, -10, by=-1)
ggplot(df, aes(t, lambda)) + geom_line(aes(color = factor(a))) + 
  scale_x_continuous(trans = reverselog_trans(), breaks = breaks) +
  geom_hline(yintercept = 3/4, color = "red") +
  geom_text(aes(0, 3/4, label = 3/4, vjust = -0.5, hjust = 1.3), color = "red")

\(a\) が大きいと分子の精度は上がるが分母の精度は変わらないため \(\lambda\) の精度は分母の精度に依存するようになる。