The One with ...

思いついたことや作業メモなどを公開しています

Dynamic Models of Segregation

Dynamic Models of Segregation (Schelling 1971)をリアルタイムに表現するコードを更新したのでそのメモを残しておきます.

今回はNetLogo風に,エージェントの幸福度をリアルタイムに計算してプロットする部分を追加してみました.

Mathematica ノートブックは以下のサイト内で公開しているseg.nbというファイルをDLしてください.

www.sal.tohoku.ac.jp

使い方は,ノートブック内にも書いていますが,

  1. Module セルを読み込む
  2. 評価 [RightArrow] 動的更新の実行にチェックを入れる
  3. Manipulate セルを読み込む
  4. resetボタンを押す
  5. run simulationのチェックボックスをon
  6. 終了すると END!! が出て自動的に停止する
  7. 条件を変えて4.から繰り返し

です.最終的なアウトプットはこんな感じです.

  • 赤と青の個体がトーラス上をランダムに移動します
  • 各個体は自分の近傍内に同色の仲間が35%以上存在すると,その位置に定着します
  • 左のバーは平均的な幸福度(周りの同色エージェント割合)です
  • このシミュレーションは,強い排外意識がなくても,分居が自然に生じる様子を表しています

youtu.be

ノートブックを読み込む段階で動的更新をonにしていると,このUIが表示されるはずですが,エラーが出る場合もあるので 最初はオフにしておき,上記の手順で動的更新を許可すれば問題ないはずです.

このnbはWolfram Demonstrations Projectで公開されているコードを参考にして作りました.

demonstrations.wolfram.com

特に,GarbageCollectionByAntsというコードを参考にさせていただきました.

使用関数の定義

まずエージェントの初期配置を決める関数.size×sizeの行列上の番地でエージェントの位置を指定する. 赤エージェントの数は,rednで,赤エージェントの数は,bluenで指定する.

(*赤agent 青agentの初期配置*)
initialset[size_, redn_, bluen_] :=
  Module[{a, b, n = redn + bluen},
   Developer`ToPackedArray[(* Integer型指定で高速化 *)
    a = RandomSample[Range[size*size], n];(* 総エージェント数だけ1次元配列から非復元抽出 *)
    b = Flatten[Table[{i, j}, {i, 1, size}, {j, 1, size}], 1];(*  2次元座標 *)
    (* 抽出した1次元配列番号を座標に変換する. 前半は赤,後半は青用 *)
    {Table[b[[a[[i]]]], {i, 1, redn}], 
     Table[b[[a[[i]]]], {i, redn + 1, n}]}, Integer]
   ];
(* 非復元抽出で初期配置を与える.
output: {{赤の位置情報},{青の位置情報}} の順で出力する
initialset[size,redn,bluen][[1]] が 赤初期配置  
initialset[size,redn,bluen][[2]] が  青初期配置  *)

初期グリッドに0を並べる手続きです.動的表現を使わない場合は,わざわざ定義する必要はありません.

(*初期グリッド*)
zerogrid[size_] := Table[0, {size}, {size}]

エージェントの位置情報を色に変換する関数です.この関数の引数である位置情報がDynamicで更新されるのでエージェントが動いているように見える,という仕組みです.

(*  agentの位置情報を検索して,可視化する関数 *)
visualize[{redposi_, blueposi_}, size_] := 
  Module[{zero1 = zerogrid[size](*Mnipulate内で指定*)},
   (* Embedding agents into zero1 for visualization 
   引数のredposi_,blueposi_はそれぞれ「赤」と「青」の現在位置 *)
   Do[zero1[[ Sequence @@ redposi[[i, {1, 2}]]]] = 1, {i, 
     Length[redposi]}];
   Do[zero1[[ Sequence @@ blueposi[[i, {1, 2}]]]] = 2, {i, 
     Length[blueposi]}];
   ArrayPlot[zero1, 
    ColorFunction -> (If[# == 0, White, 
        If[# == 1, Darker[Red], Darker[Blue]]] &),
    ColorFunctionScaling -> False, ImageSize -> {300, 300}, 
    AspectRatio -> Automatic, Frame -> False, Mesh -> All, 
    Axes -> False]
   ];

全セル(トーラス空間)のムーア近傍の座標を出力する関数です.エージェントの位置毎に計算するよりも,グローバルに一回計算して,必要があるやつだけ呼び出したほうが早いんじゃないかと思って定義した関数です.計算は一回きりなので,メモリの節約にはなっていると思うのですが,実際に比較はしてません.ただ,コードの構造上はこの方法が理解しやすいと思います.

(* トーラス上に配置されたエゴのムーア近傍を出力する関数 *)
moore[t_(* agent position *), size_] := 
  Module[{m = size, p1, p2, p3, p6, p4, p7, p8, p9, network},
   p6 = Table[
     If[t[[i]][[2]] != m, t[[i]] + {0, 1}, 
      t[[i]] + {0, -(m - 1)}], {i, Length[t]}];
   p9 = Table[Which[(* +-している1を一般化すれば近傍の距離を自由に定義できる*)
      (t[[i]][[1]] != 1) && (t[[i]][[2]] != m), t[[i]] + {-1, 1},
      (t[[i]][[1]] == 1) && (t[[i]][[2]] == m), 
      t[[i]] + {m - 1, -(m - 1)},
      (t[[i]][[1]] == 1) && (t[[i]][[2]] != m), t[[i]] + {m - 1, 1},
      (t[[i]][[1]] != 1) && (t[[i]][[2]] == m), 
      t[[i]] + {-1, -(m - 1)}], {i, Length[t]}];
   p7 = Table[Which[
      (t[[i]][[1]] != 1) && (t[[i]][[2]] != 1), t[[i]] + {-1, -1},
      (t[[i]][[1]] == 1) && (t[[i]][[2]] == 1), 
      t[[i]] + {m - 1, (m - 1)},
      (t[[i]][[1]] == 1) && (t[[i]][[2]] != 1), t[[i]] + {m - 1, -1},
      (t[[i]][[1]] != 1) && (t[[i]][[2]] == 1), 
      t[[i]] + {-1, (m - 1)}], {i, Length[t]}];
   p3 = Table[Which[
      (t[[i]][[1]] != m) && (t[[i]][[2]] != m), t[[i]] + {1, 1},
      (t[[i]][[1]] == m) && (t[[i]][[2]] == m), 
      t[[i]] + {-(m - 1), -(m - 1)},
      (t[[i]][[1]] == m) && (t[[i]][[2]] != m), t[[i]] + {-(m - 1), 1},
      (t[[i]][[1]] != m) && (t[[i]][[2]] == m), 
      t[[i]] + {1, -(m - 1)}], {i, Length[t]}];
   p1 = Table[Which[
      (t[[i]][[1]] != m) && (t[[i]][[2]] != 1), t[[i]] + {1, -1},
      (t[[i]][[1]] == m) && (t[[i]][[2]] == 1), 
      t[[i]] + {-(m - 1), (m - 1)},
      (t[[i]][[1]] == m) && (t[[i]][[2]] != 1), 
      t[[i]] + {-(m - 1), -1},
      (t[[i]][[1]] != m) && (t[[i]][[2]] == 1), 
      t[[i]] + {1, (m - 1)}], {i, Length[t]}];
   p4 = Table[
     If[t[[i]][[2]] != 1, t[[i]] + {0, -1}, 
      t[[i]] + {0, (m - 1)}], {i, Length[t]}];
   p2 = Table[
     If[t[[i]][[1]] != m, t[[i]] + {1, 0}, 
      t[[i]] + {-(m - 1), 0}], {i, Length[t]}];
   p8 = Table[
     If[t[[i]][[1]] != 1, t[[i]] + {-1, 0}, 
      t[[i]] + {(m - 1), 0}], {i, Length[t]}];
   network = Developer`ToPackedArray[
     Table[{p6[[i]], p4[[i]], p2[[i]], p8[[i]], p9[[i]], p7[[i]], 
       p3[[i]], p1[[i]]}, {i, Length[t]}], Integer]
   ];

エージェントの幸福度を計算する関数です.最小値0,最大値1です.位置情報と幸福度をセットにして出力します.

(* ムーア近傍情報に基づき幸福度(ムーア近傍内の同色エージェント割合)を計算, 位置情報に幸福度を追加  
引数 agentposiは単色エージェント *)
happy[agentposi_, size_] := Module[{neloca, happy3},
   neloca = moore[agentposi, size];
   happy3 = 
    Table[Plus @@ 
      Table[If[MemberQ[agentposi, neloca[[i]][[j]]], 1, 0], {j, 8}],
     {i, 1, Length[neloca]}];
   happy3 = happy3/8;(* 同色エージェント数を *)
   Table[Append[agentposi[[i]], happy3[[i]]], {i, Length[agentposi]}]
   ];

(*入力は  {{1,1},{2,1},{3,1}} のような単色エージェントの位置情報.出力は
{{1,1,0.2},{2,1,0.3},{3,1,0.8}} のような 位置情報+幸福度
*)

最後にエージェントの移動を管理する関数です.設定した同色エージェント割合(閾値)に達していない場合に移動します.

(* 幸福情報に基づき,移動する visualizeの引数を生成する *)
move[agents_, size_, redth_, blueth_, movern_] := 
  Module[{preloca, zerolocate, red0, blue0, red1, blue1, k1, k2, 
    AllocationforRed, l1, l2, AllocationforBlue},
   red0 = agents[[1]]; blue0 = agents[[2]];
   red1 = happy[red0, size]; blue1 = happy[blue0, size];
   preloca = Flatten[{red0, blue0}, 1];(* 埋まっているセル *)
   zerolocate = 
    Complement[Tuples[Range[size], 2], preloca];(*ゼロセルの番地リスト*)
   
   (*red agentからmovern人を選び動かす*非復元抽出* *)
   k1 = RandomSample[Range[Length[red0]], movern];
   k2 = Select[k1, red1[[#, 3]] < redth &];
   AllocationforRed = RandomSample[zerolocate, Length[k2]];
   red[[k2]] = AllocationforRed;
   
   (*Blue agentからmovern人を選び動かす*非復元抽出* *)
   l1 = RandomSample[Range[Length[blue0]], movern];
   l2 = Select[l1, blue1[[#, 3]] < blueth &];
   AllocationforBlue = 
    RandomSample[Complement[zerolocate, AllocationforRed], Length[l2]];
   blue[[l2]] = AllocationforBlue;
   {red, blue}
   ];

さて,以上の関数をforで回すのではなく,Manipulateの中でRefreshします. するとリアルタイムでエージェントがピコピコ動きます.このような動的表現が,MathematicaのDynamic関数によって実行できます.

以下Manipulateの中身です.

(*  ver110:幸福度をlistplotでリアルタイムに更新
自動停止機能を追加,happy level モニタリングをDynamic追加 *)
Manipulate[
 Refresh[
  If[moving, {red, blue} = 
    move[{red, blue}, s, redth, blueth, nomove]](* 更新している部分 *);
  If[(Min[happy[red, s][[All, 3]]] >= 
      redth) && (Min[happy[blue, s][[All, 3]]] >= blueth), 
   moving = False;
   state = Text[Style["END !", Red, 20]], moving = moving];
  state2 = Mean[happy[red, s][[All, 3]]] // N;
  state3 = Mean[happy[blue, s][[All, 3]]] // N;
  (*  agentのhappy を 監視して,全員が満足したらmoving をFalseに切り替え,
 Dynamic[state]表示をendに変える.
  moving の値がFalseになると {red,blue}の更新が終わり,
  "run simulation"チェックボックスがoffになる*)
  visualize[{red, blue}, s], 
  UpdateInterval -> If[moving, 0.01, Infinity]](* reflesh end *),
 
 {{s, 20, "length of a side"}, 10, 30, 1, ImageSize -> Tiny, 
  Appearance -> "Labeled"},
 {{redn, Floor[2*s], "number of red"}, 1, Floor[(s*s)/2], 1, 
  ImageSize -> Tiny, Appearance -> "Labeled"},
 {{bluen, Floor[2*s], "number of blue"}, 1, Floor[(s*s)/2], 1, 
  ImageSize -> Tiny, Appearance -> "Labeled"},
 {{redth, 0.35, "red threshold"}, 0, 1, ImageSize -> Tiny, 
  Appearance -> "Labeled"},
 {{blueth, 0.35, "blue threshold"}, 0, 1, ImageSize -> Tiny, 
  Appearance -> "Labeled"}, {{nomove, Floor[(s)/5], 
   "number of mover"}, 1, Min[{redn, bluen}], 1, ImageSize -> Tiny, 
  Appearance -> "Labeled"},
 {{moving, False, "run simulation"}, {True, False}},
 Button["reset", moving = False; state = "going..."; 
  x = initialset[s, redn, bluen]; red = x[[1]]; blue = x[[2]],
  ImageSize -> Medium],
 Dynamic[{"state=", state}],
 Dynamic[ListPlot[{{{1, state2}}, {{2, state3}}}, Filling -> Axis, 
   PlotRange -> {{0, 3}, {0, 1}}, PlotMarkers -> {Red, Blue}]],
 SaveDefinitions -> True, 
 TrackedSymbols :> {moving, red, blue, redn, bluen, s},
 SynchronousUpdating -> True, ControlPlacement -> Left
 ]

なにゆえ,このManipulateでエージェントが動くのかを,ざっと解説します.

Manipulateの直接の実行対象となっている関数は Refresh[ ]です.Refresh[ ]内で,上で定義した一連の関数をループさせています. ループさせている関数の中には,visualizeがあるので,Refreshされる度に,グラフィック出力が変化します.このRefreshするタイミングを UpdateInterval ->で指定します.intervalが短い(上記コードでは0.01)ので,CPUさえ高性能なら,ヌルヌル動くというわけです*1

実験としてスライダの下部にDynamic objectを2つ埋め込みました.一つはシミュレーションが停止したかどうかを示すstateです. 停止条件を満たすとここがEND!と表示されます. もう一つが,今回修正したエージェントの幸福度(リアルタイム)です.色別の幸福度の平均値を計算してListPlotする命令をDynamicに放り込むだけでうまくいきました.

Dynamicは,誤って使用するとPCに負担がかかりすぎてカーネルが落ちるため,なかなか使いこなすのが難しい関数ですが,うまくいけば動的な表現の幅が広がるので是非 使ってみてください.

(今みなおすと,まだ無駄な計算が残っており,もっと効率化できそうです.どのあたりが修正できそうか,気づいた学生さんは授業時間内に教えてください)

*1:厳密に言えば,このオブジェクトは最速で更新されるために,インターバルの指定は無視されます.遅延評価する実行文の場合,インターバルは指定通りになります.これはRefreshの仕様です

SSD換装メモ

ノートPC SSD換装メモ

PCがモッサリしてきたけど,買い換えると再設定が面倒.そんなときOSごとクローンしてSSD換装するわけですが, 数年に1回しかやらないので,手順をすぐに忘れてしまう.そこで,次に作業するとき参照するために記録を残しておきます.

換装手順
  1. BIOSでCDドライブを起動ドライブに設定
  2. 移動先SSDSATA/USB変換ケーブルで接続
  3. acronis true imageをCDドライブに入れて起動.OSごと内蔵SSDの中身を移動先SSDにクローン
  4. 電源オフ
  5. 内蔵SSDを引っ張り出して,移動先SSDSATA接続.このとき移動先SSDに,取り出し用のテープを貼る.
  6. BIOSの起動設定を戻す→起動
  7. ウマー(゚Д゚)
使った道具
気づいたポイント
  • クローンする際に,新SSDをUSB接続する.
  • pc背面の開封シールは開けなくてよい.よって保証対象のまま.
  • 内蔵SSDは,基板むき出しの薄いタイプで7mmより薄い.大容量型が買えるなら基板むき出しタイプの方が確実に入る.SSDの厚みは7mm程度が限界.
  • acronis true imageを使ってSSDクローンを 5 回ほどやったが,これまで失敗は無かった.
  • デスクトップの場合,空きSATAポートを使うので変換ケーブルは必要なし
  • ノートPCの場合,SSDだけ背面から取り出せる

JAGSによる対数正規分布のパラメータ推定

Kruschke氏のblogでは,対数正規分布のデータを生成するのに,一度正規乱数作ってから基準化して指数化するというやや煩雑な手順を用いている.

doingbayesiandataanalysis.blogspot.jp

最初にコードを見たとき,BUGSのクセとあいまって,何をやっているのか分からなかった. 単純化のため,対数正規分布を使って,直接データを生成してみる.

set.seed(47405)
y = rlnorm( n=500, 5.0, 0.5)  

# Package the data for shipping to JAGS:
dataList = list(
  y = y ,
  N = length(y) ,
  meanOfX = mean(log(y)) ,#事前分布のパラメータに使う
  sdOfX = sd(log(y)))#事前分布のパラメータに使う

JAGSに渡すコードは以下の通り

modelstring = "
  model {
    for( i in 1 : N ) {
      y[i] ~ dlnorm( mu, 1/sigma^2)#JAGS codeの定義より
    }
  sigma ~ dunif( 0.001*sdOfX , 1000*sdOfX )
  mu ~ dunif( 0.001*meanOfX , 1000*meanOfX )
  meanY <- exp(mu+(sigma^2)/2)
  modeOfY <- exp(mu-sigma^2)
  sigmaOfY <- sqrt(exp(2*mu+sigma^2)*(exp(sigma^2)-1))
}
" # close quote for modelstring
writeLines(modelstring,con="model.txt")

一応これでもMCMC推定はうまくいく.

(今思いついたが,事前分布のパラメータの範囲を指定するのに正規分布を1回経由したほうが便利だから,ああいうコードになっていたのかもしれん)

理論モデルから生成したデータに基づく推定

次の手順として,対数正規分布のパラメータを y_0, b, p, nの関数とみなして,ダイレクトに対数正規分布確率密度関数を使って推定してみる.

要するに理論モデル(GMI)に基づいてデータを人工的に発生させて,理論上のパラメータ(潜在変数)をMCMC推定できるのか? という実験である.

まずデータは次のように生成する

#直接対数正規分布のパラメータを指定する
set.seed(47406)

y0=20; b=0.2; p=0.65; n=10;
mu=log(y0)+n*log(1-b)+log((1+b)/(1-b))*n*p
sigma= sqrt(n*p*(1-p)*(log((1+b)/(1-b)))^2) 

y = rlnorm( n=100, mu, sigma)  

JAGS用モデルは次の通り(これをモデルと呼ぶことに依然として抵抗を感じるが今は無視する).

#modelstring0はパラメータをn,y0,p,b,を全て推定
modelstring0 = "
model{
for( i in 1 : N ) {
y[i] ~ dlnorm(m, 1/(s^2))
}
m <- log(y0)+n*log(1-b)+log((1+b)/(1-b))*n*p
s <- sqrt(n*p*(1-p)*(log((1+b)/(1-b)))^2)
n ~ dpois(a)#nをポアソン分布で近似
a ~ dunif(13,18)
y0 ~ dpois(c)#
c ~ dunif(10,18) 
p ~ dbeta(1,1)#p,bの事前分布にβ分布
b ~ dbeta(1,1)}"
# close quote for modelstring

この場合真値をHDIに含む推定をしてくれる.ただ関数gmiは人工データとしては,あまりうまく機能しないようだ.

(自由度に問題がないのか,若干気になる.直感的に言って自由度が高すぎてMCMCが収束しない場合はp以外のパラメータを決定論的に 指定する必要がある.)

再現可能な図

校正をしてて気づいた点をメモしておきます.

図は完全にreproducibleなコードを残しておくべきである.

例えば,Mathematicaで独自関数をplotする場合, パラメータの数値なども図と一緒に出力するようなコードを書いておく.

Module[{plot1, point = 18, t1, t2, \[Rho] = 0.5},
 t1 = Graphics[
   Text[Style["G of Income", FontSize -> point], {0.6, 0.3}]];
 t2 = Graphics[
   Text[Style["G of Human Capital", FontSize -> point], {0.6, 0.5}]];
 plot1 = Plot[{
    giniincome[0.5, s1, 1, \[Rho]],
    ginihuman[s1]
    }, {s1, 0, 1}, 
   PlotStyle -> {{Black, Thick, Dashed}, {Gray, Thick}}];
 Show[plot1, t1, t2, PlotRange -> {0, 1},
  Frame -> True, 
  FrameLabel -> {TableForm[{{"\!\(\*SubscriptBox[\(\[Sigma]\), \(1\)]\
\) of Human Capital"}, {"\[Alpha]=0.5, \
\!\(\*SubscriptBox[\(\[Sigma]\), \(2\)]\)=1, \[Rho]=0.5"}}], 
    "Inequality (Gini index)"},
  ImageSize -> Large, LabelStyle -> {FontSize -> point}, 
  AxesStyle -> Directive[point], GridLines -> Automatic, 
  ImageSize -> Medium]
 ]

ここでは,凡例をplot内に書き込み,パラメータ値をフレーム外に表示させている.ここでTeXやWordなどで 編集している本文のキャプションではなく,計算用ソフト上のグラフィックオブジェクトとして, テキスト文字を埋め込んでしまうのが,ポイントだ.

同じことはggplot2を使えばRでもできる.

こうすれば図を変更した後にキャプションをいちいち修正しなくて済む.なぜなら図を変更したとき コードも修正されているからだ(本当は関数の引数とキャプションの値が同期しているのが望ましい).

実行例はこんな感じだ.

f:id:hamada7418:20161003112440j:plain

複数の線が異なるパラメータに対応しているなら,こんな描き方でも分かりやすいと思う.

f:id:hamada7418:20161003112617j:plain

Mathematicaには出力した図に,オブジェクトをGUI処理で重ね書きする機能があるが, 多少面倒でもグラフィックオブジェクトをコードで書いて,最後にShowでまとめて表示 したほうがよい.

急がば回れ,というやつだろう.要は「未来の自分」が内容を忘れていても, Enterキー一発で,再現できるようなコードを残しておくと,後でラクなのだ.

前回の続き.

そもそも分布を導出する理論モデルのパラメータを階層ベイズMCMCで推定できるのか,という確認をやってみる.

まずデータとして,理論モデルからのサンプルをつくる.

今回はJAGSRから呼び出すので, コードは全部Rで済むように書き換える.

data生成用コードはざっと以下のような感じ

# Generate a person's income under the given condition: 
set.seed(47406)

gmi <- function(y1, b0, p0, n0){
  y = y1
for(i in 1:n0){
  if(runif(1) < p0){
  y <- y + (y*b0)}
else{y <- y - (y*b0)} }#for end
y
}

#generate income data for m people
m=2000; y1=10; b0=0.2; p0=0.5; n0=15
dat0 <- c()
for(i in 1:m){ dat0[i] <- gmi(y1, b0, p0, n0)}

ここで定義しているパラメータが真の値に相当する

次にJAGSに渡すモデルの記述

modelstring0 = "
model{
for( i in 1 : N ) {
y[i] ~ dlnorm(m, 1/(s^2))
}
m <- log(y0)+n*log(1-b)+log((1+b)/(1-b))*n*p
s <- sqrt(n*p*(1-p)*(log((1+b)/(1-b)))^2)
n ~ dpois(a)#nをポアソン分布で近似
a ~ dunif(1,20)
y0 ~ dpois(c)#
c ~ dunif(1,20) 
p ~ dbeta(1,1)#p,bの事前分布にβ分布
b ~ dbeta(1,1)}"

実行には例によってJohn K. Kruschke.さんのラッパー関数DBDA2E-utilities.Rをお借りする. (MCMCの設定はデフォルト)

さて,実行してみると,chainは収束してるみたいだけど,

なんかうまく推定できていない気がする.

よくよく考えたら,nだのy_0だの理論的に定数であるべきパラメータを(非負だからといって) ポアソン分布に従うとか雑に仮定したのがよくないのでは?

というわけで修正した統計モデルがこちら

modelstring1 = "
model{
for( i in 1 : N ) {
y[i] ~ dlnorm(m, 1/(s^2)) }
m <- log(y1)+n0*log(1-b)+log((1+b)/(1-b))*n0*p
s <- sqrt(n0*p*(1-p)*(log((1+b)/(1-b)))^2)
#prior for hyper parameters
p ~ dbeta(1,1)
b ~ dbeta(1,1)}"

p, b以外は定数扱いとして,事前に定義しておく.推定しているのは実質的には p, bのみである.具体的にはpが投資成功確率でbが投資する資本割合である.

で,結果は

f:id:hamada7418:20161002163636p:plain:w600

こんな感じでpの事後分布のモードは真値に近いけど,bの方はあまりよくない.

中心極限定理の観点から考えるとpの分散は0でなくてもいいけど(リアプノフの条件から直感的にそう思う),bのほうが理論モデルにあっていないのかもしれない.つまり,そもそもbが確率分布していると,対数正規分布をGMIから導出できないのかもしれない.これでは元も子もない.

ここまでの疑問を整理すると

  • ハイパーパラメータはそもそも何個指定できるのか?
  • 実際に所得データからパラメータを推定する際に「足りない情報」をどの変数で補うのか?
  • 分布Aのパラメータが階層ベイズの仮定として(Aと共役でない)確率分布Bに従う場合,データがAに従うという仮定は妥当か?

といったところ.

続く

ずっと気になっている(しかしマイナな)問題が解決しないので,そのメモを記録しておきます.


所得分布の生成モデルは,投資行動の反復から対数正規分布を導出する数理モデルである.仮定は次のとおりである(Hamada 2003; 2004; 2016).

  1. 人々が最初に初期資産y_0を持っている.
  2. 資産のうち割合bを投資して,確率pで成功すると,資産が増える.失敗すると投資した資産を失う.
  3. 資産の一定割合を所得として得る.人々が投資行動をn回繰り返すと,資産と所得の分布は対数正規分布にしたがう.

このモデルの結論の一つは,導出された対数正規分布のパラメータがモデルの外生的パラメータであるy_0, b, p, nによって内生的に決まる,というものである. 具体的には,資産の分布は

{\displaystyle
 Y \sim \Lambda( \log y_{0} + n \log ( 1-b ) + \log \frac{1 + b }{ 1 - b } np, npq  ( \log \frac{ 1 + b }{ 1 - b })^{2})  }

となる.証明はHamada (2003; 2004; 2016)をみよ.

これは2項分布{B(n, p)}正規分布{N(np,np(1-p))}で近似できる,という性質を利用した命題である(確率変数を基準化した場合にはド・モアブル=ラプラスの定理となる).


さて

このモデルから得た命題をシミュレーションによって再現できない,というのが問題だった.

どういうことか説明しよう.

まず次の関数によって,確率pで資産y_0 bを増やし,1-pで資産y_0 bを失う仮定を表現する.コードは例によって一番楽に書けるMathematica

gmi[y0_, b_, p_, n_] := Module[{y = y0},
   Do[(* repeat n time  *)
    If[Random[] < p, y = y + (y*b), y = y - (y*b)], {n}];
   y];

一方で,理論的に導出される定まる確率分布のパラメータを使って,対数正規分布に従う疑似乱数をコンピュータに作らせる.

(* generate samples from a pdf with parameters derived from Hamada model  *)
lognsamples[y0_, b_, p_, n_, pop_] := Module[{m, s, a, B },
   a = Log[(1 + b)/(1 - b)]; B = Log[y0] + n Log[1 - b];
   m = B + a n p;
   s = Sqrt[n p (1 - p) a^2];
   RandomVariate[LogNormalDistribution[m, s], {pop}]];

モデルが正しく対数正規分布を導出できているのであれば,ミクロレベルでの資産生成プロセスを集約した結果と,分布からサンプリングした結果は,ほぼ等しくなるはずである.

modelfit[y0_, b_, \[Gamma]_, n_, pop_] := Module[{dat0, dat1},
   (*  generate samples from a model *)
   dat0 =  Table[gmi[y0(*ini.c*), b(*interest*), p(*success*), n(*repeat*)], {pop(* population *)}];
   dat1 =  lognsamples[y0(*ini.c*), b(*interest*), p(*success*), n(*repeat*), pop];
   Histogram[{dat0, dat1}, Automatic, "Probability", ChartStyle -> {Red, Blue}]];

ところが,この関数を実行すると,「理論値から得たサンプルデータ」と「疑似乱数で作ったサンプルデータ」がズレる.

ズレの解消の仕方として,疑似乱数でサンプルデータを作るのではなく,確率密度関数を理論値で得た離散的所得階級値間のレンジで積分して,確率密度を少し「まとめて」やれば,うまくいくことは分かっている.

だが,いちいち積分させるのもコードが煩わしい.

先日,ふと気づいた.疑似乱数から生成したデータベクトルの中には同じ値がdouble精度で1つもないのに,理論に基づいて生成した方は,double精度で同じ値がいくつも含まれているのだ.ようするに,理論値のベクトルの方が疑似乱数のベクトルよりも分散が小さいのである.

これは一体,どういうことなのか?

理論値は理論値で間違っていない.疑似乱数ベクトルも指定したパラメータを持つ分布からランダムにサンプルを抽出している.

分布を導出しているはずの数理モデルから生成したサンプルが,なぜPCで発生させた確率分布疑似乱数と分布がずれてしまうのか?

そこで理論値は厳密すぎて,疑似乱数のランダムさを失っていると考えてみる.

確かに関数gmi[y0_, b_, p_, n_]の中で一様乱数を使っているので投資結果はランダムに変化するのだが,獲得した資本額がある決まった値しかとらない.だから理論値にノイズを足してやればいいのでは? というわけだ. 

別の言い方をすれば疑似乱数の方は連続確率変数の実現値を表現しているのに,理論モデルは離散的確率変数の実現値しか表現できていない.だから確率密度関数区間毎に積分する操作が,実現値の離散化に対応していたのである.よって修正はただノイズ

RandomVariate[NormalDistribution[0, 0.1]] 

を加えるだけでよいのでは,と考えた.

修正したコードはこうなる

lognsamples[y0_, b_, p_, n_, pop_] := Module[{m, s, a, B},
   a = Log[(1 + b)/(1 - b)];
   B = Log[y0] + n Log[1 - b];
   m = B + a n p;
   s = Sqrt[n p (1 - p) a^2];
   RandomVariate[LogNormalDistribution[m, s], {pop}]];
gmi[y0_, b_, p_, n_] := Module[{y = y0},
   Do[(* repeat n time  *)
    If[Random[] < p, y = y + (y*b), y = y - (y*b)], {n}];
   y];
modelfit[y0_, b_, p_, n_, pop_] := Module[{dat0, dat1},
   dat0 = 
    Table[gmi[y0(*ini.c*), b(*interest*), p(*success*), n(*repeat*)] +
      RandomVariate[NormalDistribution[0, 1/Sqrt[n]]], {pop(* 
      population *)}];
   (* add randome error since the model is too rigorous *)
   dat1 = 
    lognsamples[y0(*ini.c*), b(*interest*), p(*success*), n(*repeat*),
      pop];
   Histogram[{dat0, dat1}, ChartStyle -> {Red, Blue}]];

これで,だいぶマシにはなる.だがパラメータの値によってノイズの分散に微妙に調整が必要なので 完璧ではない.

多分,記録しておかないと数日したら忘れちゃうので,書き留めておきます.

順番を決めるコード

新学期がはじまるので,演習報告担当者の順番決め用のあみだくじコード(Mathematica)を再掲しておきます.

(* 動的表現 サイズ固定*)
Manipulate[
 Dynamic[Refresh[RandomSample[Range[n]], UpdateInterval -> x]],
 {{x, Infinity, "シャッフル"}, {0 -> "Go", Infinity -> "Stop"}},
 {{n, 20, "人数"}, 2, 100, 1}, ContentSize -> {600, 150}]

ダイナミックを使ってるので,動的に更新します. この短さでGUIまで自動的に生成するとは驚きである. やっぱりMathematicaで書くとコードがシンプルでええなあ.