最近Blogで継続的に記事を書くようになって,自分の書く文章について特に意識するようになった.書評なり読書ノートを1つの記事をしてまとめるにあたり,とにかく文章を書いては読みを繰り返し,記事を公開したあとも電車の中で読み返すなどして誤字脱字を訂正したり推敲を重ねている.自分のBlogの一番の読者は,まぎれもなく自分だ.いったい誰のために/何のために書いているかという問題は自分の中でもハッキリしていないが,色んなことを差し置いても,結局まわりまわって自分のために書き貯めている部分は大きい.

文章の書き方は今でもよくわからない.ある程度まとまった記事を書くのに慣れてきた面もあり,逆にあまり代わり映えしない表現力に萎えることもある.色々と本を読んで参考にしたり,尊敬する人物の書いた文章を写経したこともあったけれども,結局血肉となったかはよくわからない.本来ならば人に添削してもらうのが一番良いのだが,特に独創性であったり主義主張があるわけではないただのBlog記事にそこまでできるはずもなく,今のところ自分でなんとか改善を繰り返している.

ということで,今月のSoftware Designの特集はエンジニアのための文章力養成講座だ.製品の紹介文やマニュアルなどの文章の書き方であったり,情報をいかに整理して文章を構成していくかといったテクニックが載っている.エンジニア出身のブロガーが文章力について書いているコラムでは,文章を書く人間が普段どういうことを思いながら書いているのかということが色々と書かれていて,なかなかに興味深かった.何かを主張したり情報を広めたり布教をしたりと,アウトプットの質を高めていくにはとにかく書き続けるしかないということで,これからも引き続きblogを続けていこうというモチベーションに繋がる記事だった.



練習問題 6.1 自己回帰モデルAR(1)が定常分布を持つことを実験的に示す

この問題はいわゆるAR(1)というもので,本書の1章 P.34に同様のコードおよび作図が載っている.なお,本問題の詳細はMCMC-UseR.pdfのP.130を参照.

X^{(t+1)} = \rho X^{(t)}+\varepsilon_t \varepsilon_t \sim \mathcal{N}(0,1) で定義されるマルコフ連鎖において,系列{X^{(t)}} の定常分布は\mathcal{N}(0, \frac{\sigma^2}{1-\rho^2}) で表される.今回はt=10^4 までのシミュレーション結果をヒストグラムとしてプロットし,定常分布を赤い曲線で示した.

1
2
3
4
5
6
7
8
rho <- 0.9
x <- rnorm(1)
for(t in 2:10^4){
  x[t] <- x[t-1]*rho + rnorm(1)
}

hist(x, nclass=150, freq=F)
curve(dnorm(x, mean=0, sd=(1/sqrt(1-rho^2))), add=T, col="red")

練習問題 6.2 ランダム・ウォークが定常分布を持たないことを実験的に示す

X^{(t+1)}=X^{(t)}+\varepsilon_t で表されるランダム・ウォークが,シミュレーション回数を増やしても定常分布に収束しないことを実験的に確かめる.今回は初期値X^{(0)}=0 として,t=10^4 t=10^6 までマルコフ連鎖をシミュレートする. この問題は練習問題6.1における\rho = 1の場合と同じになるので,上で書いたコードのパラメータを変更するだけで再現できる.ただ,今回は別の書き方としてcumsum()関数を用いてランダム・ウォークの系列を計算している.

1
2
3
4
5
6
7
8
9
init <- 0
x1 <- cumsum(rnorm(10^4-1))
x2 <- cumsum(rnorm(10^6-1))

par(mfrow=c(2,2))
hist(x1, nclass=150, main="t=10^4")
plot(1:10^4, c(init,x1), type="l", xlab="t", main="t=10^4")
hist(x2, nclass=150, main="t=10^6")
plot(1:10^6, c(init,x2), type="l", xlab="t", main="t=10^6")

シミュレーション回数をt=10^4 t=10^6 としてランダム・ウォークを実行した結果が上図である.左図に系列のヒストグラム,右図に試行回数を横軸に取った系列をプロットしている.上のように図示すると,たとえ106 回試行したとしても何らかの分布に収束しないことがわかる.

雑感

練習問題 6.1と練習問題6.2を比較すると,ランダム・ウォークにおいて先行する状態に0 \leq \rho < 1 のような値を掛け合わせるだけで定常分布を取るというのは,なんとも面白い結果となっている.

補足:MCMC-UseR.pdfのP.132

  • 練習問題の\rho と以下図の\theta は同じ
  • pdfにも書いてあるがスケール(x軸とy軸の描写範囲)に注意.いかに\theta が効いているかがわかる
  • 左上はマルコフ連鎖ではない
1
2
3
4
5
6
7
8
9
10
par(mfrow=c(2,2))
for(rho in c(0, 0.4, 0.8, 0.95)){
  x <- rnorm(1)
  y <- rnorm(1)
  for(t in 2:1000){
    x[t] <- x[t-1]*rho + rnorm(1)
    y[t] <- y[t-1]*rho + rnorm(1)
  }
  plot(x,y, type="l", main=paste("theta=",rho))
}



元 Apple Inc.のシニアマネージャーの松井博氏による, Appleという会社を通して語られる働き方の指南書である.主にスティーブ・ジョブズやジョナサン・アイヴ,ティム・クックといったトップの人間を中心にして語られることが多いAppleだが,実際の会社内部の体制について語られることは意外と少ない.本書では,実際にアップルで働いた著者の体験談を元に,その企業の姿勢であったり価値観が語られる.そして本書での一番ポイントとなる部分は,松井氏がスティーブ・ジョブズが戻る以前の迷走期から復帰後の社内改革,そしてめざましい躍進ぶりを見せる黄金期にまたがって,Appleという会社を内側から眺め続けてきたところにある.めまぐるしく変わりゆく環境の中で,ジョブズの復帰により会社の環境はどのように変わったのか,そしてAppleが革新的な製品やサービスを作り続けられてきた理由はいったいどこにあるのかを,内部の視点からまとめ上げた一冊となっている.

本書は大きく分けて,職場環境の変革について書かれた前半部と,Apple社員の異常なほどまでの社内政治について書かれた後半部から構成される.前半部の職場環境については,著者が実際に肌で体感してきたアップル社内の変貌を織り交ぜつつ,どうやったら社員がパフォーマンスを発揮し組織として成果を上げていくことができるかといった方法論が語られる.その中では,昨今のApple評で良く耳にするシンプル・コンセプト・デザインなどの基本的な要素から,著者が自分の部署内で実施してきた試行錯誤,そしてスティーブ・ジョブズが打ち出した方向性によってどのように会社としての方向性が明確になったかが,臨場感溢れる語り口で述べられる.一方で,後半部の社内政治においては,一見エンジニアの天国として華やかに見えるApple社内での,外部のイメージとは全く違った熾烈な競争から生じる社内政治について,著者のアメリカ勤務時代の経験が語られる.著者自身は社内政治について良い面も悪い面もあるとしながらも,社内での立ち回りの重要性であったり如何にして成果を自分のものにし失敗を被らずに上司にアピールするかといった,すこし敬遠されがちな内容にまで踏み込んでいる.

本書で語られる経営哲学や仕事内容,上司部下の関係,職場の机のレイアウトに至るまでのエピソードはすべて,いい意味で個人の枠を飛び出していない.あくまで著者自身が社員としてとして会社の中で直面し,感じ,そして考えたかが率直に表現されている.そのため読者としては「Appleだから出来ることでしょ」「外資ベンチャーだから」「シリコンバレーだから」といった壁を作らずに済む.そもそもこの本の構成自体が自分でも何かできることはないだろうかという気持ちにさせるような書き方をしているので,Appleについて全く知らない人にとっても,エンジニアの楽園といった現実離れした妄想を抱いている人たちにとっても,数ある大企業の一つとしてのAppleとして,その会社のことを深く知ることができると思う.お酒の席で知り合いの先輩の人生哲学を聞くような感覚で気軽に読めて,それでいて内容も簡潔にまとまっており,なかなかに面白い本だった.



8章の図を再現する

今回は,本書の8章「マルコフ連鎖モンテカルロ(MCMC)法とベイズ統計モデル」の解析を実際に自分でやってみて,図を再現することを目的とする.基本的に本書の作図を真似するということを目標にして調整しているが,細部で少し違いがあるかもしれないので注意していただきたい.

問題設定とサンプルデータ

問題設定は6章と同じく,上限があるカウントデータを用いる.植物個体から8個種子を取ってきた時の種子の生存数が二項分布に従うとした上で,そのときの尤度を最大化し,生存確率であるパラメータqを推定することを目標とする.

まずはサンプルデータと二項分布の対数尤度関数の設定.

1
2
data <- c(4,3,4,5,5,2,3,1,4,0,1,5,5,6,5,4,4,5,3,4)
loglik <- function(q){data*log(q)+(8-data)*log(1-q)+log(choose(8,data))}

図 8.2・8.3

生存確率qと対数尤度の関係を図示する.8章の解析の大きな特徴としては,この後に出てくるメトロポリス法の説明のために,本来は連続変数である生存確率qを0から1までの0.01刻みの離散値として扱っている点にある.以下の図では,それらの生存確率に対応する対数尤度がマル印で示されてる.

また,解析的に求まる最大対数尤度とその時の生存確率q=0.45625を縦の赤線とバツ印で表示してある.今回は生存確率を離散値として表現しているため,最大対数尤度に対応する対数尤度の値は図示されていない.この図の中では,対数尤度の最大値を取るときの生存確率はq=0.46となっている.

1
2
3
4
5
a <- seq(0.01, 0.99, by=0.01)
plot(a, apply(matrix(a),1,function(z){sum(loglik(z))}),
     xlim=c(0.2,0.7), ylim=c(-60,-35), xlab="q", ylab="log L(q)")
abline(v=0.45625, lwd=2, col="red", lty=2)
points(0.45625, sum(loglik(0.45625)), pch=4, ,cex=2, col="red")

図 8.8

MCMCのアルゴリズムの一つであるメトロポリス法を実際に動かしてサンプリングをしてみる.

メトロポリス法の実装は本書にコード例が無かったので自作している.このコードで使っているアルゴリズムに関しては,本書P.176で述べられている手順を使っているわけではなく,スタンダードなメトロポリス法の手法を用いて先に対数尤度比を求めて条件分岐を作っている.細かい部分で違いはあれど基本的にやっていることは同じで作図もうまくいっているので,たぶん問題はないだろう.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# Metropolis method
niter <- 100000
x <- 0.30
for(i in 2:niter){
  z <- sample(c(-0.01,0.01),1)
  r <- min(1, exp(loglik(x[i-1]+z)-loglik(x[i-1])))

  if(runif(1) < r){
    x[i] <- x[i-1] + z
  }else{
    x[i] <- x[i-1]
  }
}

# Fig. 8.8
par(mfrow=c(3,2))
plot(1:100,x[1:100], type="l", xlab="Iterations", main="100 MC steps")
plot(density(x[1:100]), xlim=c(0,1), main="")
plot(1:1000,x[1:1000], type="l", xlab="Iterations", main="1000 MC steps")
plot(density(x[1:1000]), xlim=c(0,1), main="")
plot(1:niter,x, type="l", xlab="Iterations", main="100000 MC steps")
plot(density(x), xlim=c(0,1),  main="")

図 8.9

メトロポリス法では,ステップ数を大きくしていくとサンプリングの値は定常分布に収束する.しかし,ステップ数が小さい段階では初期値に影響されるため,Burn-inとしてある程度のステップまでの結果を捨てるということが行われる.このように初期値が影響する様子を見るために,以下の図では初期値を0.1から0.9までの0.1刻みとした上でメトロポリス法を実行し,その結果を重ねあわせている.

これを見ると,ステップスを重ねる毎に初期値の影響が少なくなっていくことがわかるが,では実際にどのあたりまでを捨てるべきかという疑問については図を見ても判断できない.Burn-inの範囲設定は色々と手法があるものの,ある程度は実験者の裁量に任されているようだ.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
palette <- c("#000000","#999999", "#E69F00", "#56B4E9", "#009E73", "#F0E442", "#0072B2", "#D55E00", "#CC79A7")
# http://wiki.stdout.org/rcookbook/Graphs/Colors%20(ggplot2)/

plot(1:500, rep(0,500), ylim=c(0,1), type="n", xlab="Iterations", ylab="q", main="500 MC steps")
for(init in seq(1,9)){
  niter <- 500
  x <- init*0.1
  for(i in 2:niter){
    z <- sample(c(-0.01,0.01),1)
    r <- min(1, exp(loglik(x[i-1]+z)-loglik(x[i-1])))

    if(runif(1) < r){
      x[i] <- x[i-1] + z
    }else{
      x[i] <- x[i-1]
    }
  }
  lines(x, col=palette[init], ylim=c(0,1), lwd=2)
}

参考



マルコフ連鎖モンテカルロ法 (統計ライブラリー)」PP.11-14のメトロポリス・ヘイスティング・アルゴリズムによる二重指数分布からの乱数生成(サンプリング)をやってみた.

この問題は本書にも実験結果が掲載されているので,自分が行った実験結果と見比べることができる.結論から言うと,一応大まかな結果としては同じ傾向を示したが,乱数を用いた実験ということもあって多少の値のズレがでている.また,パラメータによっては結果の値/分布が安定しないものもあるので,何度か実験を行なって結果を吟味している部分もあるが,結果を曲解するためのものではないということを付け加えておく.

なお,この文章ではメトロポリス・ヘイスティング・アルゴリズムを以降MHと表記する.

問題

ガンベル分布(二重指数分布)からの乱数生成を考える.今回はガンベル分布からの直接的な乱数生成ができないとしたうえで,MHによる乱数生成を行う.ガンベル分布は\frac{1}{2b}\exp(\frac{-|x-\mu|}{b}) で表され,今回は\mu=0 b= 3 の分布を考える.なお,ガンベル分布の平均と分散は解析的に得られ,それぞれ\mu 2b^2 である.

実験条件

今回は,MHの中でも酔歩過程/ランダムウォーク(Random walk)と独立連鎖(Independent chain)の2種類の方法を試す.それぞれのMHにおける推移カーネルK

  • 酔歩過程:K(x^{(t)},x^{(t+1)}) = \mathcal{N}(x^{(t)},c_{ran}^2)
  • 独立連鎖:K(x^{(t)},x^{(t+1)}) = \mathcal{N}(0,c_{ind}^2)

となる.ここで,c_{ran}^2 およびc_{ind}^2 は調整母数/チューニングパラメータであり,今回はそれぞれ{0.25, 1, 5, 15, 150}の5パターンで実験を行った.

また,生成した乱数の系列において,系列初期のt=1\dots 1000 までの結果は初期値に依存した値となっているため,今回は結果として用いていない.

結果:以下の図の見方

以下に示す図は,各行にチューニングパラメータの値,各列に系列のプロット,系列の分布,系列の平均の推移,系列の自己相関プロットを示したものである.図のタイトルに,チューニングパラメータの値,生成した乱数の平均と分散,そして採択率を示している(便宜的にタイトルの部分に情報を書き加えているだけで,タイトルと図は直接的には関連していない).

結果:酔歩過程/ランダムウォーク

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
par(mfrow=c(5,3))
for(i in c(0.25,1,5,15,150)){
  c.ran <- i^2
  alpha <- numeric(0)
  Niter <- 10000
  x <- rnorm(1, mean=0, sd=c.ran)
  for(t in 2:Niter){
    z <- rnorm(1, mean=0, sd=c.ran)
    alpha[t-1] <- min(1, exp(-(1/3)*(abs(x[t-1]+z)-abs(x[t-1]))))
    x[t] <- x[t-1] + z*(runif(1)<alpha[t-1])
  }
  X <- x[1001:Niter]
  stat <- table(alpha==1)
  plot(1001:Niter, X, type="l", main=paste("c_ran=",i), xlab="Iterations", ylab="")
  plot(density(X), main=paste("mean=",round(mean(X),digit=2)," var=",round(var(X),digit=2)))
  plot(cumsum(X)/1:length(X), type="l", main=paste("accept ratio=",round(100*stat[2]/stat[1]),"%"))
  abline(h=0, lwd=2, col="red")
}

c_{ran}^2=0.25 の場合

c_{ran}^2=1 の場合

c_{ran}^2=5 の場合

c_{ran}^2=15 の場合

c_{ran}^2=150 の場合

結果:独立連鎖の場合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
par(mfrow=c(5,4))
for(i in c(0.25,1,5,15,150)){
  c.ind <- i
  alpha <- numeric(0)
  Niter <- 10000
  x <- rnorm(1, mean=0, sd=c.ind)
  for(t in 2:Niter){
    z <- rnorm(1, mean=0, sd=c.ind)
    alpha[t-1] <- min(1, exp(-(1/3)*(abs(z)-abs(x[t-1]))
                             -(1/2)*((x[t-1]/c.ind)^2 - (z/c.ind)^2)
                             )
                      )
    if(runif(1)<alpha[t-1]){
      x[t] <- z
    }else{
      x[t] <- x[t-1]
    }
  }
  X <- x[1001:Niter]
  stat <- table(alpha==1)
  plot(1001:Niter, X, type="l", main=paste("c_ind=",i), xlab="Iterations", ylab="")
  plot(density(X), main=paste("mean=",round(mean(X),digit=2)," var=",round(var(X),digit=2)))
  plot(cumsum(X)/1:length(X), type="l", main=paste("accept ratio=",round(100*stat[2]/stat[1]),"%"), ylab="",xlab="Iterations")
  abline(h=0, lwd=2, col="red")
  acf(X)
}

c_{ind}^2=0.25 の場合

c_{ind}^2=1 の場合

c_{ind}^2=5 の場合

c_{ind}^2=15 の場合

c_{ind}^2=150 の場合

解釈

さて,このように色々とプロットしてみたわけだが,酔歩過程の場合も独立連鎖の場合もチューニングパラメータの値が0.25や150といった非常に小さい/大きい場合においては,乱数の分布や収束速度などに問題があることがわかる.

酔歩過程においては,マルコフ連鎖の候補点の生成は少しずつ値を移動させていくという点で,分布の表面をなぞる形となる.そのため,チューニングパラメータの値である分散が小さい場合では候補点の移動範囲が狭いため,分布全体を探索するのに非常に時間がかかる.c_{ran}^2=0.25 の図を見ると,系列のプロットではあまり大幅な移動(縦のぶれ)が少なく,生成した乱数の分布もかなりいびつである.また,値があまり変動しないという点で,自己相関係数も非常に大きな値を取っていることがわかる.一方チューニングパラメータの値が大きい場合においては,採択率が非常に小さな値を取り,系列のプロットからも値の変動しない部分が他と比べて多い.

一方,独立連鎖においては,マルコフ連鎖の候補点の生成は現在の状態に依存しない.そのため,酔歩過程のように値を移動させていくということは見られないが,一方で採択確率の部分でチューニングパラメータの値が影響していることがわかる.

参考