まずは蝋の翼から。

学んだことを書きながら確認・整理するためのメモブログ。こういうことなのかな?といったことをふわっと書いたりしていますが、理解が浅いゆえに的はずれなことも多々あると思うのでツッコミ歓迎

成長するためには手癖でやらないためのシステムが必要

前回に引き続き怪文書ポエム。

記事に書いたが、最近手癖で行動してしまっている。

knknkn.hatenablog.com

手癖、つまり慣れたやり方は楽。しかし成長の観点で言えば、同じことをなんとなくやっているだけなので成長は皆無は言いすぎかもしれないが非常に緩やかになるし、飛躍はない。

生産性と成長

話は変わるが、会社の上長と話していて生産性の話になった。そこで、以下の本を勧められた。

生産性

生産性

その中に、「生産性 = 得られた成果 ÷ 投入した資源 = アウトプット ÷ インプット」という記載があります。生産性を上げるには、得られた成果を増やす or 投入した資源を減らす の2つしかない。
つまり、「時間を増やさずにどうやったらより良い成果物になるか」「時間をかけないにはどうすればよいか」を考えていくことで、「短時間でより良いアウトプットを出す」ことができ生産性を上げることができる。

成長とは何か考えると、「より良い成果物が出せるようになること」だと考えていたし正しい側面ではあるが、より良い成果物を出すために今まで以上に時間がかかっていたらそれは成長といえるのか。時間は有限なので、例えばあるアウトプットを出すのに8時間使っていたので類似案件はよりよいものを出すために10時間使いました。そのあと、さらに類似案件をよくするために12時間使いました...となるとキリがないし24時間を超えたところで「成果物のより良さ」は頭打ちになる。

一方で、生産性を意識しながら行動をすると頭打ちにならずに、成果物をより良くし続けることができる。また、使う時間は同じあるいは減るので他のことに時間を回すことができるので、さらなる学習機会を得ることができる。これがまさに成長かと。

ちなみに、上記書籍の作者(と言われている別名)で生産性と成長というそのものズバリな内容について書いている。

「成長=昨日より今日の生産性を上げること」

「インプットの最小化がその答え」

脱手癖

成長のためには手癖でやらない。
では手癖でやらないためには?

以前の記事でも書いたが、「xxしない!」は根性論なので長続きはしない。そのため、システム化が必要になる。

一番簡単なのは、「どうすれば同じことを短時間でできたか」「どこを改善すべきだったか」といったPDCAをまわす作業を自動化することだろう。日報とかはその一環かな、と思う。

では、どのように自動化するか。他者に宣言して半強制力を生むことでやり忘れを回避、グーグルカレンダーにリマインド登録をする、あたりがよくある方法だと思うのでとりあえずそれを試してみる。

データ分析プロジェクトを主体的にこなすためには

ただの雑記怪文書です。

近況と問題意識

最近、分析プロジェクトの全フローを担当するようになった。

前期では表面上は「先輩データサイエンティストと共におこなっていた」ことになっているが、実質的には「先輩データサイエンティストの考えたモデルに沿ったコードを書いて数値を報告する」ことをおこなっていた。

つまり、以下のようなことは先輩データサイエンティストに無意識に丸投げしていた。

  • 何が目的か考える(&そのために、クライアントとのやりとりをメールなど漁ったり営業に詳細を聞きにいく)
  • 目的に沿った手法/モデルは何か考える(&類似目的の論文など調べる)
  • 出た結果に関して解釈を考える(&ドメイン知識がある人と結果について考察し合う)
  • 結果をまとめる

問題点

ありていに、一言で言うと「考えることは先輩に丸投げした脳死で作業していた」ことになる。

何故そんな状態になったか考えたのだが、理由として「自分で考えないでも先輩が勝手に考えて進めてくれる」から。手法や解釈を考える場は先輩と議論していた。一人より複数人で議論しながらの方が良いアイデアが思い浮かぶので、都度議論するのは当たり前だが、このとき私は何も考えてなくてただただ先輩の壁打ち役になっていただけのように思う。

何故壁役になっていたか考えると「先輩が強い」から。正確には「先輩が強いと思い込んで萎縮しているから」「今まで考えてきていないから壁以外できないから」。

また、要件定義のために「何を目的とするか」自体をちゃんと考えていないし、そのためのドメイン知識もあまり持っていない。故に、そもそも考えが出てこない。

このままではいつまでたっても先輩がいないと何もできないゴミカスである。また、市場価値はゼロとなる。

さらにいえば、営業などのステークホルダーとのコミュニケーション、例えば「全体のワークフロー/スケジューリング/結果イメージを考えて、営業などと結果イメージ/スケジュールに合意を得る」といったようなこともしていなかった。

つまり、自分以外のステークホルダーからすると全体感と進捗が読めないことになる。そうなると、「どういう内容ならはどれくらいの作業工数になりそうか」という見積もりがステークホルダーができないし、「進捗どおりなのか」もわからない。更にいえば、「何を作ろうとしているかわからないので、完成したものが筋違いな可能性がある」。

これも要は何も考えずに来たものを処理することしか考えていない脳死行動を取っているのが原因だと思う。

対策

脳死にならないためには。

一言で言えば、手癖が「脳死で目の前のことをただ処理する」なので、「手癖でやらない」が対策となる。

「xxしない!」は根性目標なので、はじめは続いても続くわけがない。つまり、システム化する必要がある。

分析プロジェクトが立ち上がったら、プロジェクトテンプレートを用意しその項目を自分で埋める形で行動していく。営業や先輩データサイエンティストにその旨を伝えることで常にそのテンプレートを使わざるを得ない環境を作る。

また、要件定義の議論は気づいたら何も考えていないで先輩が決めた要件定義になってしまうのでプロジェクトテンプレートのtoDoは「要件定義を先輩と考える」ではなく「自分なりの要件定義を一人でx分考える」を挟んだ上で「要件定義を先輩と考える」とすべき(x分、としているのは考える行為はキリがないので時間で区切るため)。

もちろん、結果的には先輩発案の要件定義内容になることが大半だとは思うが、自分で考えることを繰り返していくことで要件定義の精度が上がったり、先輩とは別の視点での定義が生まれると思う。とにかく、「自分の頭で考える」トレーニングを積むべき。

また、考えるのに必要な素材としてドメイン知識が必要なので、営業と目的についてすり合わせたり、考えたアウトプットイメージをすり合わせる、といったコミュニケーション系もテンプレートに入れておくことで手癖でやってしまうと漏れそうな必要な行為を防ぐ。

2019年3Q OKR

2019年2Q振り返りを踏まえて、3QのOKR作成。

knknkn.hatenablog.com

OKRについては以下にざっくりまとめてる。

knknkn.hatenablog.com

Objective

「独立したDSに!」

まぁ前期OKRとほぼ同じだがちょっと意図変えた。

下流はまぁそれなりにできるようになったので今期は上流(要件定義)ができるDSになる。

要件定義は
ドメイン知識に沿った需要を満たす目的
②目的を達成するために道具をどう使うか

この2つに分解できるかと。それぞれに対応するKRを立てる。

KR

KR1

「案件において、自分で要件定義をしたものが8割」

他の人の意見でよりよくはなると思うので、正確には「ベースとなる定義は自分で考える」。

そのための週の行動指針は

  • 案件xxについてコンサル部門/営業部門の人と話す
  • 営業/コンサルとの接点を増やしてドメイン知識を得る

とかか

KR2

「案件を3つ作る」

需要考えて自分で案件を考え提案する。

そのための週の行動指針は - (ビジネスフローを分解したときの)領域xxについて、需要がありそうなこと考える - 上記について、営業/コンサルと議論する - 営業/コンサルが言っていた○○について解決策をx個考える

とかか?

KR3

**「仕事関連の論文を10本読む」

道具を増やす、道具のよりよい使い方を学ぶ

そのための週の行動指針は

  • 調べたい領域を考える
  • 論文何件か見つける
  • 読む
  • そのために必要な理論学ぶ
  • ブログ書く

あたりか

その他

OKRちゃんと振り返るの難しいから、どうするかですね。。。問題意識のもと、OKRに沿った行動は無意識にやってはいるけど定期振り返りはしてないからOKRではないよね。。。

てか自転車通勤だから自転車乗りながら振り返ればいいのか。。。

勉強する時間はある程度決まっているので、グーグルカレンダーかなんかに時間割みたいにやることを曜日毎に決めてスケジュール先に入れたらいいんかなー。

あとKRを達成するために今週はなにをするか?が毎回難しい。というか同じになる。だからやらなくなるんのかな。

解決策

  • めんどいし土日は不確定要素多いので振り返りと翌週の目標(P)設定は週初めにまとめてする
  • KR考える/振り返る/見直す系は出勤中(雨天時以外は自転車)にする
  • 知人巻き込んで、週振り返り/週目標設定を一方的に報告する会設立→半強制力でちゃんとする&リマインドになる
  • 内容はTrelloで管理

まぁこんなところか。

2019年2Q振り返り

今期やったこと

仕事

  • 階層ベイズ
  • シミュレーション
  • ログ解析(Rでくそめんどい集計して傾向見る)
  • 簡単な集計多数

自学

  • 使った技術と、それをベースに横展開した技術を学んだ
  • ブログでちゃんとアウトプットをした(6月は案件炎上したから1ヶ月くらい書けなかったけど)

OKR振り返り

一応OKR立ててたけど相変わらずほぼ見てない。このダメさは運用の仕方に問題あるなこれは。。。 knknkn.hatenablog.com

ちなみにKR1「過去のDSプロジェクトを把握し使いこなす」のと、3「ドキュメント(英語)をちゃんと読む」はまぁそれなりにできたかな、と。

KR2「自分でモデルを考える」はゴミ。というか、モデルというより要件定義自体だな。

環境

禁煙しました(2ヶ月目)。

今まで10年近く、喫煙しながら勉強していたのでそのあたりの勉強とセットになる習慣が禁じられたため慣れるまで苦労した。

反省

Objective「ある程度自立したDSになる」だったが、ある意味で達成、ある意味でゴミ。

というのも、手を動かすことに関しては、必要になると超速で学習しながらやることで割とできるようになった。一方で、要件定義が完全思考停止で言われた定義に沿って手を動かすだけマンと化していた。

言い訳をするならば、上流の人の要件定義力が強いためこっちが考える間もなく(あるいは、考えなくても)勝手にそれ以上の要件定義が思い浮かばないなぁとなって決まるので自然とそうなっていた。

ただ、そうなると「なんのためにこの分析をしているか」という目的がなんとなくでしか理解していなかったりすることが多いので、結果の解釈がしづらかったり、細部の要件が漏れていることに気づけなかったりとムダが多い。

なによりも、自分で分析設計ができないDSの市場価値は低いためキャリア的に死ぬ。

今期を踏まえて

アウトプット

学習したらアウトプットするのはこのまま続ける。正直、自分用メモに近いので「自分が思い出せてわかればいいや」程度の書き方をしていて読者を想定した書き方にあまりなっていない。ここは改善の余地はあるのだが、そのことで書くハードルが上がることが懸念される。

もちろん、読者を想定するとちゃんと調べるためより学習が深まるという効果はあるが、今もまぁそこそこ調べているので個人的にはこれくらいのバランスでいいと思う。

学習対象

割と事前にやっといた方がよい基本的なハードスキルはやり終えた感。

あとは、仕事で必要になったら(横展開含む)学習していく方向で。

ただ、論文を調べる能力や解釈すること、それ以前に数式(特に行列)がへちょいのでここをどうするか。分野の論文把握は、使える道具を増やすことになるのでマスト。ただし、細かい理論となると俺にその部分は必要か?となる。

まぁ例えば、「機械学習の細かい理論までは知らなくていいけどざっくり把握をしとくとパラメータチューニングや使い分けができるようになる」といったような効能はあるので、ざっくり把握ができる程度の解釈能力は必要そう。

要件定義力

これはもう慣れかな、と。慣れだが、前述のような理由で自然状態ではいつまでも慣れることはない。ゆえに自分から取りに行くスタイルが必要。

案件がくると、いつのまにか要件定義をされているのでいったん他の人の意見を聞かないでいったん考える時間をもらうようにする。そうしないと結局何も考えれない。

あと、そもそもどういうことをすれば会社的に需要があるか、という部分が弱い気がする。実際、入社1年目はワンオペで手を動かすことで手一杯だったし、人が増えてからはスキルを得ることにていっぱいだったしで、ドメイン知識よりも他のことをまず得よう!という意識でいたのでわざとだけど。

とりあえず手を動かせるようになったのでドメイン知識を得に行こう。それがないとそもそも、いい感じの分析設計はできないかと。

資源の最適な配分を、ディリクレ分布を用いたシミュレーションから求める

やりたいこと

なにかしらの資源をどう分配したら効用を最大化できるか、ということを解析ではなくシミュレーションによって求める。

例題

今回、例として「総予算上限10万円分で各時間帯にCMを打つ。そのとき、3回以上CMに接触した人数が多いCM投下パターンを求めたい。」という例題で考える

条件

CMを打つ時間によって広告費が変わる

設定

テキトーに以下のような値段設定にする(なんとなく人が多そうな時間ほど高くした)。

時間 広告費
0時 1000
1時 800
2時 600
3時 400
4時 200
5時 600
6時 900
7時 1500
8時 2000
9時 1500
10時 1500
11時 1500
12時 2000
13時 1500
14時 1500
15時 1500
16時 1500
17時 1500
18時 3000
19時 5000
20時 4500
21時 4500
22時 3000
23時 2000

実装

CM投下パターン

各時間帯にどれくらいの金額を使うか、ランダムに配分をおこなう。
その際、ディリクレ分布を用いる。ディリクレ分布とは簡単にいうと「各面の出やすさをパラメータで設定した多面サイコロを振ったときの各面の出る確率」を出すことができる。「各面の出やすさが同じなら確率は等しいのでは?」と思うし、実際無限回行えばそうなるが乱数生成する場合は回数に制限があるため各面の確率はゆらぎがある。

以下に5種類の3面サイコロ結果を示す。

rdirichlet(5, rep(1, 3))
           [,1]      [,2]      [,3]
[1,] 0.44650941 0.3393241 0.2141665
[2,] 0.23953497 0.5266908 0.2337743
[3,] 0.05629639 0.4216316 0.5220720
[4,] 0.03596511 0.1777634 0.7862715
[5,] 0.40526176 0.3653088 0.2294295

確率なので各面を足し合わせると1.0になる。

ディリクレ分布のパラメータをサイコロに例える | コード7区

今回、(サイコロで例えると)どの面も出る確率が等しい24面サイコロを振ることで各面、つまり各時間帯が選ばれる確率を生成できる。
前述のように、確率なので各面を足し合わせると1.0になる。考え方を変えると、これは1.0という数字を24つに対してランダムに配分していることになる。そのため、この確率に今回使用する予定の10万円をかけると各時間に10万円をランダムに配分した結果が得られる。

このランダム配分を1000回シミュレーションをする。

library(gtools)

all_money = 10^5 # 使用する金額
n_simulation = 10^3 # シミュレーション回数

# 各シミュレーションでの、時間毎使用料金
use_simulation = tibble(simulation_no = rep(seq_len(n_simulation), each = 24), # id=セット数
                       hour = rep(0:23, n_simulation),
                       money_rate = rdirichlet(n_simulation, rep(1, 24)) %>% t() %>% matrix(ncol = 1, byrow = T) %>% c()) %>% 
  mutate(use_money = money_rate * all_money)

# # A tibble: 240 x 4
# simulation_no  hour money_rate use_money
# <int> <int>          <dbl>         <dbl>
#   1             1     0       0.0432          4322. 
# 2             1     1       0.103          10302. 
# 3             1     2       0.0165          1646. 
# 4             1     3       0.0251          2508. 
# 5             1     4       0.0472          4724. 
# 6             1     5       0.00851          851. 
# 7             1     6       0.0316          3157. 
# 8             1     7       0.156          15562. 
# 9             1     8       0.0373          3728. 
# 10             1     9       0.171          17112. 
# 11             1    10       0.0531          5306. 
# 12             1    11       0.00363          363. 
# 13             1    12       0.0112          1121. 
# 14             1    13       0.0884          8845. 
# 15             1    14       0.0184          1844. 
# 16             1    15       0.00720          720. 
# 17             1    16       0.00863          863. 
# 18             1    17       0.00893          893. 
# 19             1    18       0.000429          42.9
# 20             1    19       0.0668          6685. 
# 21             1    20       0.0152          1515. 
# 22             1    21       0.00644          644. 
# 23             1    22       0.0546          5456. 
# 24             1    23       0.0179          1790. 
# 25             2     0       0.0563          5627. 
# # … with 215 more rows

各時間のCM放送回数

時間毎CMの料金を先程求めた、時間毎使用料金にJOINする。各時間に使用できる料金(予算上限)は先程求めたので1回あたりのCM料金で割ることで、各時間で何回CMを放送するか決まる。

# 時間毎CM料金
hourly_price <- read_csv('./cm_price.csv')

# # A tibble: 24 x 2
# hour price
# <dbl> <dbl>
#   1     0  1000
# 2     1   800
# 3     2   600
# 4     3   400
# 5     4   200
# 6     5   600
# 7     6   900
# 8     7  1500
# 9     8  2000
# 10     9  1500
# # … with 14 more rows

simulation_hourly_cm_cnt = use_simulation %>% 
  inner_join(hourly_price) %>% # 各時間の1回あたりCM料金
  rename(hourly_price = price) %>% 
  mutate(cm_cnt = floor(use_money/hourly_price)) # 放送可能回数

# # A tibble: 240 x 6
# simulation_no  hour money_rate use_money hourly_price cm_cnt
# <int> <dbl>      <dbl>     <dbl>        <dbl>  <dbl>
#   1             1     0     0.0442     4423.         1000      4
# 2             1     1     0.111     11096.          800     13
# 3             1     2     0.0881     8806.          600     14
# 4             1     3     0.0497     4968.          400     12
# 5             1     4     0.0252     2524.          200     12
# 6             1     5     0.0932     9319.          600     15
# 7             1     6     0.0253     2525.          900      2
# 8             1     7     0.0113     1127.         1500      0
# 9             1     8     0.0463     4627.         2000      2
# 10             1     9     0.0238     2383.         1500      1
# # … with 230 more rows

時間毎の人の有無

今回はテキトーに乱数で作成するが、実務においては実際のユーザー行動からユーザー毎に各時間帯の滞在確率を求める。

観察できるユーザー人数は1000として、各ユーザーの各時間帯の滞在率を乱数で作成。

library(tidyverse)

n_user = 1000 # 対象ユーザー数

# ユーザー毎の、各時間の滞在確率
user_prob = tibble(user_id = rep(seq_len(n_user), each = 24), # ユーザーIDを24時間分
                       hour = rep(0:23, n_user), #時間
                       prob = rep(sample(0:100, 24 * n_user, replace=TRUE)/100) # 滞在確率
)


# # A tibble: 24,000 x 3
# user_id  hour  prob
# <int> <int> <dbl>
#   1       1     0  0.84
# 2       1     1  0.1 
# 3       1     2  0.82
# 4       1     3  0   
# 5       1     4  0.14
# 6       1     5  0.51
# 7       1     6  0.69
# 8       1     7  0.55
# 9       1     8  0   
# 10       1     9  0.75
# # … with 23,990 more rows

接触回数

各時間毎のCM放送回数と、各時間毎のユーザー毎滞在確率が求まった。そのため、これらをかけ合わせると各時間毎のユーザー毎滞在期待値が求まる。
このとき、期待値が1以上の人は1回以上接触するユーザーと考えることができるし、期待値が3以上の人は3回以上接触するユーザーと考えることができる。

simulation_resulut = 
  simulation_hourly_cm_cnt %>% 
  inner_join(user_prob) %>% 
  mutate(exp_reach = cm_cnt * prob) %>% 
  mutate(reach_1over = if_else(exp_reach >= 1.0,1,0),
         reach_3over = if_else(exp_reach >= 3.0,1,0)) %>% 
  group_by(simulation_no) %>% 
  summarise(reach_1over_cnt = sum(reach_1over),
            reach_3over_cnt = sum(reach_3over)) %>% 
  arrange(-reach_3over_cnt)


# # A tibble: 1,000 x 3
# simulation_no reach_1over_cnt reach_3over_cnt
# <int>           <dbl>           <dbl>
#   1           226            9106            5781
# 2           721           10618            5549
# 3           882            9026            5535
# 4           290            9649            5502
# 5           491            9694            5472
# 6           229           10332            5438
# 7           470            9940            5376
# 8           640            8784            5356
# 9           180            8813            5332
# 10            78            8286            5322
# # … with 990 more rows

3回以上接触者が多い投下パターン

上記結果より、simulation_no 226において3回以上接触者が5781を獲得していて、1000回シミュレーションをした結果では一番多い。

このときの10万円の使用配分(CM投下パターン)は以下のようになる。

simulation_hourly_cm_cnt %>% 
  filter(simulation_no == 226) %>% 
  print(n=24)

# # A tibble: 24 x 6
# simulation_no  hour money_rate use_money hourly_price cm_cnt
# <int> <dbl>      <dbl>     <dbl>        <dbl>  <dbl>
#   1           226     0    0.107      10677.         1000     10
# 2           226     1    0.0695      6947.          800      8
# 3           226     2    0.0597      5966.          600      9
# 4           226     3    0.0270      2698.          400      6
# 5           226     4    0.0410      4096.          200     20
# 6           226     5    0.152      15150.          600     25
# 7           226     6    0.0595      5951.          900      6
# 8           226     7    0.00883      883.         1500      0
# 9           226     8    0.00989      989.         2000      0
# 10           226     9    0.0182      1816.         1500      1
# 11           226    10    0.0163      1630.         1500      1
# 12           226    11    0.0534      5337.         1500      3
# 13           226    12    0.0289      2895.         2000      1
# 14           226    13    0.00924      924.         1500      0
# 15           226    14    0.00603      603.         1500      0
# 16           226    15    0.130      12969.         1500      8
# 17           226    16    0.0312      3125.         1500      2
# 18           226    17    0.0810      8097.         1500      5
# 19           226    18    0.0257      2566.         3000      0
# 20           226    19    0.00753      753.         5000      0
# 21           226    20    0.0184      1842.         4500      0
# 22           226    21    0.0295      2946.         4500      0
# 23           226    22    0.00714      714.         3000      0
# 24           226    23    0.00427      427.         2000      0

おまけ

実際のCM投下はここまで単純ではないです。

また、時間帯ごとCM回数は切り捨てにしているので各シミュレーションで全使用料金が変わる問題があります。

さらに言えば、そもそも投下回数が1回の時間帯では、接触期待値部分は1 * 確率(1以下) ≦ 1 となるので100%じゃない限り誰も接触していないことになるのでそれは直感とは反していることになるので少し不適切な気もします。

Stanのmodelブロックの意味

Stanを書くとき、モデル式を書いてそれをコードに落とし込むという流れで書いている。感覚的にコードを書いているのだが、冷静に振り返ると何故ここにこれを書くのか?ということがわからなくなってきたのでメモ。

というか、前の記事みたいにエラーが出た際に「データが複雑すぎるから初期値で条件を縛る必要性が出た」という判断をしましたが、「そもそもこの書き方であっているのか? = 意図しない意味わからんモデルになっているから初期値問題が出るのでは?」という疑問もあってちゃんと理解しないとな、と思いました。
具体的には、「modelパラメータにif文である条件のときは0・・・というのできないからnormal(0,0)で実質的に0にするかー」とかいう処理をしたけど明らか問題あるよなこれ。って思ったからです。

サンプルコードおよび解釈として、Logic of Blueさんの以下の記事を参考に使用させてもらいます。
(どうでもいいのですが、Logic of Blue管理人さんのサイトや書籍はいつも勉強になっているのですが、私の出身高校の1つ下の学年ということを最近知って驚いた)

Stanによるベイズ推定の基礎 | Logics of Blue

  data {
    int n;
    vector[n] Nile;
  }

  parameters {
    real mu;                # 確定的レベル(データの平均値)
    real<lower=0> sigmaV;   # 観測誤差の大きさ
  }

  model {
    for(i in 1:n) {
      Nile[i] ~ normal(mu, sqrt(sigmaV));
    }
  }

このStanコードのうちModelブロックで書いているNile[i] ~ normal(mu, sqrt(sigmaV))が何なのかが今回の疑問点です。

まずベイズの定義を考えます。

 f(\theta | X) = \frac{f(X | \theta) f{\theta}}{f(X)}

それぞれ分解すると、

 f(\theta | X) は、データXが得られたときにパラメータθが従う確率分布。いわゆる 事後分布MCMCサンプリングによって得られる確率分布。・・・(1)
 f(X | \theta)は、パラメータθのある値が得られたときにデータXが観測される確率。要するにパラメータθの尤度L(θ)
 f(\theta)は、データXが従う確率分布。いわゆる 事前分布 f(X)は、Xが得られる確率で定数。

つまり、以下のように書き換えることができる。

 事後分布 = \frac{尤度 * 事前分布}{データが得られる確率} ∝ 尤度 * 事前分布

また、コード中の表記に合わせると以下のようになる。

 f(mu, sigmaV | ナイル川データ) = \frac{f(ナイル川データ | mu, sigmaV) f{mu, sigmaV}}{f(ナイル川データ)}

このときのf(ナイル川データ | mu, sigmaV)、「あるmu, sigmaV(未知)のときのナイル川のデータ(実データ)」というのがコードのNile[i] ~ normal(mu, sqrt(sigmaV))に相当する。
ナイル川データは実データなので、「mu,sigmaVを動かしたときにどういう値ならば実データのナイル川データが得られるか」という尤度関数を得ることができる。このときの尤度を求める計算としてnormal(正規分布)を用いる、というのがこのModelブロックに記載されているNile[i] ~ normal(mu, sqrt(sigmaV))の意味。

f:id:chito_ng:20190622154835p:plain

そして、Modelブロックで尤度の指定がされ、Parametersブロックで事前分布の指定がされたことでベイズの定理よりパラメータの事後分布を推定することができる。

言い方を換えると、「Modelブロックで確率モデルを記載するとその確率モデルに従う尤度関数を通すことで、確率モデルに用いたパラメータがMCMCによりベイズ推定される」「Nile[i] ~ normal(mu, sqrt(sigmaV))に従うmu, sigmaVをMCMCで推定する」

階層ベイズなどで例えばNile[i] ~ normal(mu[i], sqrt(sigmaV))mu[i] ~ normal(mu_avg, sqrt(sigmaV))のようになっている場合、「mu,sigmaVを動かしたときにどういう値ならば 実データ のNileデータが得られるか」となるが、ここのmuはベイズ推定なので一意な値ではないためmu[i] ~ normal(mu_avg, sqrt(sigmaV))の「mu_avg,sigmaVを動かしたときにどういう値ならば ベイズ推定値(一意でない) のmuが得られるか」となる気がするが全組み合わせについてやってるのだろうか。謎だ。

はじめの話

はじめに書いた「modelパラメータにif文である条件のときは0(Sales ~ ... ではなく、Sales = 0)・・・というのできないからnormal(0,0)で実質的に0にするかー」とかいう処理の話。
modelブロックの意味を考えると、そもそもnormal(0,0)ってなんも推定してないやんけということがわかるし、尤度関数もそもそも1点に定まるっていう。それが原因でエラーになっているのかどうかはわからんが、少なくともModelブロックに書くのはおかしい気がする。

ただ、そうなると閉店フラグが0のとき値が一意(0)になるようなモデルはどう作れば。。。欠損値処理のように、そこだけ値を除去してあとで0で埋めるとかか?そもそもある条件のときだけ別のモデルに従うような場合ってどう書けばいいのだろう。

参考

http://rtokei.tech/stan/bayesian-inference-with-stan-005/

Stanで統計モデリングを学ぶ(2): そもそもMCMCって何だったっけ? - 渋谷駅前で働くデータサイエンティストのブログ

P.48あたりから。

www.slideshare.net

状態空間モデルをstanでやりたかった② パネルデータに対する状態空間モデル(階層ベイズ)

前回では以下のようなModelを考えた。

tが閉店日の場合、  SalesObserve_t = 0
それ以外の場合、
 SalesObserve_t = SalesState_t + Week_t + PromotionEffect_t + \sigma_{SalesObserve}
 SalesState_t = 2\mu_{t-1} - \mu_{t-2} + \sigma_{SalesState}
 Week_t = s_t + d_{1,t} b_1 (s_{日,t} - s_t) + d_{2,t} (b_2 (s_{金,t} - s_t) + b_3 (s_{土,t} - s_t))
 PromotionEffect_t = C_{promo} * PromotionFlg_t

knknkn.hatenablog.com

だが、結果としてsamplingがおこなわなかった。そのため、今回もプロットは無しで理屈のみ。。。

前回のモデルと今回のモデルの差

前回のモデルは「ある1店舗」の時系列を考えた。今回は、「全店舗」について考えたい。

店舗についての情報として、

  • StoreType(店舗タイプ)
  • Assortment(装飾のレベル)

などがある。
StoreTypeとAssortmentをかけ合わせたStoreTypeAssortmentを用いて、「StoreTypeAssortmentが同じ店舗の動きは似ているだろう」という仮設を入れた階層ベイズモデルを作成する。

このとき、以下のようなモデルとなる(id=店舗id、SA = StoreTypeAssortment) 。

 \mu_{id, SA, t} = SalesState_{id, SA, t} + Week_{id, SA, t} + PromotionEffect_{id, SA, t}
 SalesObserve_{id, SA, t} =  \mu_{id, SA, t} + \sigma_{SalesObserve_{SA}}
  \mu_{id, SA, t} = \mu_{共通, t} + \sigma_{SalesState_{共通}}
 \sigma_{SalesObserve_{SA}} = \sigma_{SalesObserve_{共通}} + \sigma_{SalesState_{共通}}

参考

階層ベイズと状態空間モデルで広告効果を推定したい - 統計コンサルの議事メモ

状態空間モデルのれんしゅう(レストランの来客予測) - rmizutaの日記