超幾何分布
[mathjax] 基本的に、足りない地頭を補うために時間を使ってきた歴史がある。 理解の速度や再現に地頭が影響する。 頭の中でパンパンパンって話が進む経験は全くない。 たぶんここに書いていることを活かすには地頭が必要だと思うので今後の人生で使えないだろうな。 具体的な確率分布を理解していく。 超幾何分布(hypergeometric distribution)。 超幾何分布 2種類のグループ(A,B)があって、個数の構成はそれぞれ(M),(N-M)。 これらから(n)個を取り出したときに、(A)が(x)個、(B)が(n-x)個であるとする。 袋の中に赤い玉が(M)個、白い玉が(N-M)個あって、 赤い玉を(x)個、白い玉を(n-x)個取る確率だな。取り出して戻さないやつ。 俯瞰して書くとこういうことになる。 begin{eqnarray} f(x) = frac{ {}_M C_x cdot {}_{N-M} C_{n-x} }{ {}_N C_n } end{eqnarray} 単に確率を求めただけ、みたいに見えるけど、 この事実が確率変数(x)を使った確率分布になっている。 (x)以外は定数(つまり観測可能、設定可能な値)となっていて、 (n,M,x)から(N)を推測するのに使われたりする。 超幾何分布という確率分布を知ったのであれば、 捕獲再捕獲法という条件下で、(n,M,x)という既知の値を使って(N)を推測したいなと 式が確率分布であることの証明 そもそも(f(x))はある(x)に関する確率を求めただけではないのか、 なぜ確率分布なのか。以下のように考えるらしい。 スタートは以下の恒等式。 begin{eqnarray} (1+t)^N equiv (1+t)^M cdot (1+t)^{N-M} end{eqnarray} (N=2)のとき、左辺を展開すると(t^2+2t+1)。 (N=3)のときは(t^3+3t^2+3t+1)。 (N)を増やしていった時、一般的に(t^n)の項の係数は({}_N C_n )。(初めて考えたけどこうなるんだな...) 対して、右辺の展開式において(t^n)の項の係数を調べる。 ((1+t)^M)の展開式において次数(x)の項と、 ((1+t)^{N-M})の展開式において次数(n-x)の項の積が次数(n)となる。 (x)は複数あるが、全ての(x)について係数を足すことで(t^n)の係数を求められる。 つまり、(sum_x {}_M C_x cdot {}_{N-M} C_{n-x}) 左辺、右辺の(t^n)の項の係数は同じであるはずなので、 begin{eqnarray} {}_N C_n = sum_x {}_M C_x cdot {}_{N-M} C_{n-x} end{eqnarray} 両辺を({}_N C_n)で割ると、 begin{eqnarray} 1 &=& sum_x frac{ {}_M C_x cdot {}_{N-M} C_{n-x} }{ {}_N C_n } \\ &=& sum_x f(x) end{eqnarray} 全部足して1になるということは確率分布。 左辺がサクッと1になって、右辺がサクッと超幾何分布の式になるという、 ぷよぷよの2段消しみたいな快感。 確率分布とは関係ない恒等式からスタートして、いきなり言いたいことが出てくるという不思議。 超幾何分布と二項分布の関係 超幾何分布は2種類のグループから取り出したものを戻さずに次を取り出す際の確率分布だけども、 戻して取り出す場合と戻さずに取り出す場合では、立式自体が変わってくる。 で、戻さないで次を取り出すときは二項分布になる。 (N)の極限を取ると二項分布になると書いてある。 立式の上では、以下の通り、確かに二項分布になる。 begin{eqnarray} lim_{N rightarrow infty} f(x) &=& lim_{N rightarrow infty} frac{ {}_M C_x cdot {}_{N-M} C_{n-x} }{ {}_N C_n } \\ &=& lim_{N rightarrow infty} cdot frac{ {}_{M_1} C_x cdot {}_{M_2} C_{n-x} }{ {}_N C_n } \\ &=& lim_{N rightarrow infty} frac{M_1!}{(M_1-x)! cdot x!} cdot frac{M_2!}{(M_2-n+x)!(n-x)!} cdot frac{(N-n)!n!}{N!} \\ &=& lim_{N rightarrow infty} frac{n!}{(n-x)! x!} frac{M_1(M_1-1)cdots (M_1-x+1)cdot M_2(M_2-1)cdots (M_2-n+x+1)}{N(N-1)(N-2)cdots (N-n+1)} \\ &=& lim_{N rightarrow infty} {}_N C_x cdot frac{ frac{M_1}{N}(frac{M_1-1}{N})cdots (frac{M_1-x+1}{N}) cdot frac{M_2}{N}(frac{M_2-1}{N})cdots (frac{M_2-n+x+1}{N}) }{(1-frac{1}{N})(1-frac{2}{N})cdots (1-frac{n-1}{N})} \\ &=& lim_{N rightarrow infty} {}_N C_x cdot frac{ p cdot (p-frac{1}{N}) cdot (p-frac{x+1}{N}) cdot q cdot (q-frac{1}{N}) cdot (q-frac{n-x-1}{N}) }{ (1-frac{1}{N})(1-frac{2}{N})cdots (1-frac{n-1}{N}) } \\ &=& {}_N C_x p^x cdot q^{n-x} \\ &=& {}_N C_x p^x cdot (1-p)^{n-x} end{eqnarray} (N)が十分に大きいときはより簡単な二項分布で近似せよ、ということになる。 あぁ、ここで、(p=frac{M_1}{N}, q=frac{M_2}{N})。 アルゴリズムの計算量の話のように、どういう問題がどんな分布に収まるのか、 というのは、知っておくと便利な気がする。 単純にモデルに当てはめるというのではなくて、 世の中には、こういうデータの測り方がありますよ、というケーススタディなんですな。
procとlambda
[arst_toc tag=\"h3\"] Procとcallメソッド 以下では、get_closureメソッドにより、クロージャオブジェクトを生成している。 その際初期値を設定している。 クロージャオブジェクトのcallメソッドによりクロージャを実行する。 初期値を設定した変数initial_valueはクロージャのスコープなので、 クロージャの外からは一切存在を管理する必要がない。 def get_closure initial_value Proc.new{|up| initial_value += up} end closure = get_closure(100) closure.call(10) # 110 closure.call(20) # 130 proc内でのreturn proc内でreturnすると、procの呼び出し元のスコープを抜ける。 procの呼び出し位置で処理が止まるため、そのあとの処理は実行されない。 以下callerというメソッド内でprocを実行するが、 procがreturnすることで、proc.callの実行後callerのスコープを抜ける。 なので2は評価されない。 この動きは後のlambdaと異なる。 def caller proc = Proc.new{ return 1 } proc.call 2 end Procオブジェクトをブロックに変換 作っておいたクロージャオブジェクトをメソッド実行時にブロックとして使用することができる。 メソッドの引数として、&を付与してProcオブジェクトを渡す。 def func hoge hoge + yield end closure = Proc.new {5} func(10, &closure) # 15 ブロックをProcオブジェクトに変換 ブロックをProcオブジェクトとして受けることができる。 def func2 hoge, &proc hoge + proc.call end func2(100) do 200 end # 300 lambda lambdaもprocを返すが、Proc.newとは異なる動きをする。 procの実行時にreturnするとprocの呼び出し元のスコープを抜けていた。 lambdaは実行時にreturnしてもlambdaの呼び出し元のスコープを抜けない。 下の例でlambdaが返したprocが途中で終わっても callerはそこで抜けず、proc.callの後が実行される。 def caller proc = lambda{ return 1 } proc.call 2 end lambdaの別の書き方 以下のように格好良く書ける。他の言語の同様の構文に似せた感じ。 lmd2 = ->(hoge,fuga) { hoge + fuga } lmd2.call(100,200) # 300
ブロック(クロージャ)とスコープ
[arst_toc tag=\"h3\"] Ruby固有のスコープの扱いについて追記。 ブロックとスコープ メソッド呼び出しの直後にブロックを指定することができる。 例えば、以下においてメソッドfuncを呼び出す際に引数1と同時にdoから始まりendで終わるブロックを指定している。 ブロックの中ではx1という変数に2を代入していて、それがブロックの評価値となる。 ブロック付きで呼び出されたメソッドの中では、ブロックの評価値をyeildという特殊変数で受け取ることができる。 つまり、メソッドfuncの中では、引数y1=1と、ブロックの評価値2の和の評価が行われる。 def func y1 y1 + yield end # 3 func (1) do x1 = 2 end なお、ブロックはdo..endだけでなく、{..}でも書ける。 Rubyの掟的には、複数行のブロックはdo..end、1行のブロックは{..}とのこと。 func (1) { x1 = 2 } ちなみに、ブロックの中と外はスコープが異なる。 ブロックの中で確保した変数x1は、スコープの外からは見えない。 ブロックの中で確保した変数はブロックの評価が完了した時点で破棄される。 func (1) { x1 = 2 } p x1 # Exception 破棄されるのはブロックで初めて確保した変数であって、 ブロックの外部の変数をブロック内で使用した場合は、 ブロック内での操作がブロック外の変数に影響する。 例えば、以下のように予めブロックの外で変数x1を確保しておき、 ブロックにより加算するという状況で、ブロック内のx1は紛れもなくブロック外のx1。 x1はブロックにより束縛される(キリっ)。 ブロックの評価後に評価すると、確かにブロック内の加算が行われた後。 x1 = 10 func (1) { x1 += 2 } # 13 p x1 # 12 Rubyの特殊なスコープ事情 変数の束縛について追記。他の言語では動きそうな以下のコードはRubyでは動かない。 Rubyではオブジェクトやメソッドの壁を超えて変数を参照することはできない。 (Railsではインスタンス変数を使って超えちゃってるけど..) fuga = 20 def myfunc p fuga end ブロックを使うことで初めて実行時の環境(コンテキスト,文脈)を共有できる。 ここで一般化して良いのかわからないけども、 処理の生成時の変数を束縛するものをクロージャというらしい。 実行時の環境を動的に使えて便利だな、ぐらいの認識だったけど、 変数の束縛がポイント。 ブロックの引数 ブロック、つまりクロージャの引数の定義方法と使いかたは以下の通り。 クロージャ内の先頭で引数を|で囲む。かなり特殊に見える。慣れだろうか。 def func2 x2, y2 x2 + yield(y2,30) end p func2(10,20){|x,y| x+y} # 60 メソッド側でブロックが指定されたか調べる どんなケースでそんな使いかたをするのか.. メソッド側で呼び出し時にブロックが指定されてか否かを調べることができる。 def func3 x3,y3 return x2 + yield(y2,30) if block_given? 0 end p func3(10,20){|x,y| x+y} # 60 p func3(10,20) # 0
モーメント母関数と確率密度関数
[mathjax] 期待値、分散、歪度、尖度...、確率分布を形成する確率密度関数の特徴を表す値で、 実は、相互に変換できる値なのだという...。読んでいったら若干感動したのでまとめてみる。 40近いオッさんがはじめてテイラー展開のありがたさを味わう瞬間の記録。 モーメント母関数とモーメント モーメント母関数を以下のように定義。 begin{eqnarray} M_x(t) &=& E(e^{tX}) \\ &=& int_{-infty}^{infty} e^{tx} f(x) dx end{eqnarray} 英語で書くとmoment generating function。モーメントを作る関数。 ここで(f(x))というのが確率密度関数。 もともとこの時点で積分が存在しないかもしれない。 確率密度関数によっては、期待値、分散、歪度、尖度を直接求めるのは難しい。 しかし、モーメント母関数の(r)階導関数からモーメント(mu_r)を解析的に求められる性質から、 期待値、分散、歪度、尖度を求めることができる。 さて...、40近いオッさんは思い出す。 指数関数のテイラー展開は、マクローリン級数を使って以下の通り。 begin{eqnarray} e^x = 1+x+x^2/2!+x^3/3!+cdots end{eqnarray} (tX)だと、 begin{eqnarray} e^{tX} = 1+tX+(tX)^2/2! +(tX)^3/3! + cdots end{eqnarray} 両辺の期待値をとる。右辺全体の期待値はそれぞれの項の期待値の和にできるので、 begin{eqnarray} E(e^{tX}) &=& M_x(t)\\ &=& E(1) + E(tX) + E((tX)^2/2!) + E((tX)^3/3!)) + cdots end{eqnarray} (t)に対する定数を出すと begin{eqnarray} M_x(t) &=& E(e^{tX})\\ &=& E(1) + E(X)t + E(X^2/2!)t^2 + E(X^3/3!)t^3 + cdots \\ &=& 1 + mu_1 t + (mu_2/2!)t^2 + (mu_3/3!)t^3 + cdots end{eqnarray} キター。おわかりだろうか...。 (M_x(t))は(t)に関する展開式の係数に各次数のモーメントを含んでいる。 (t)について1回微分すると0次までの項は消える。 2次以降の項の(t)の次数が1減って残る。1次の項だけ(t)が消える。 そのとき(t=0)とすると、階数の係数(M^{(r)}_X(0)=mu_r)だけ残る! つまり、以下のようなとんでもないことになる。 begin{eqnarray} M_X\'(0) = mu_1 \\ M_X\'\'(0) = mu_2 \\ M_X\'\'\'(0) = mu_3 \\ end{eqnarray} 各次数のモーメントである期待値、分散、歪度、尖度を、 モーメント母関数の(r)階導関数から求められるということになる。 指数分布のモーメント 試しに指数分布でやってみる。 begin{eqnarray} M_x(t) &=& int_{0}^{infty} e^{tx} lambda e^{-lambda x} dx \\ &=& lambda int_{0}^{infty} e^{(t-lambda)x} dx end{eqnarray} 指数関数の積分のところでおっ、と思ったけど、以下となる。 begin{eqnarray} M_x(t) = frac{ lambda }{ lambda -t} end{eqnarray} これ、解析的に微分できるのかな...と思うんだけども高校数学で暗記するやつ。 微分と積分を行ったり来たりするとわかる。 begin{eqnarray} M_x^{(1)}(t) &=& frac{ lambda }{ (lambda -t)^2} \\ M_x^{(2)}(t) &=& frac{ 2 cdot lambda }{(lambda -t)^3} \\ M_x^{(3)}(t) &=& frac{ 2 cdot 3 cdot lambda}{(lambda -t)^4} end{eqnarray} (t=0)とおくと、 begin{eqnarray} mu_1 &=& frac{1}{lambda} \\ mu_2 &=& frac{2}{lambda^2} \\ mu_3 &=& frac{2 cdot 3}{ lambda^3} \\ mu_4 &=& frac{2 cdot 3 cdot 4}{ lambda^4} end{eqnarray} これで、微分の数値計算をしなくても解析的に(mu_1)から(mu_4)が求まった。 そして永遠に微分し続けることで指数分布を形作る指標が決まっていく。 すごいなぁ...。
歪度と尖度 (モーメント母関数導入前)
[mathjax] 確率変数(X)を使って表される確率分布の様子を表す指標について。 最も基本的なのが期待値(E(X))であって、次が分散(V(X))。 さらに、期待値(E(X))を中心として左右のどちらに歪んでいるかを表す歪度(alpha_3)、 期待値(E(X))近辺が全体と比較してどの程度尖っているかを表す尖度(alpha_4)がある。 その調子で、確率分布の特徴を表す指標は無数に作ることができる。 モーメント母関数の(r)階導関数から得られる各次数のモーメントが期待値、分散、歪度、尖度である、 という究極的にシンプルな話を読んでかなり感動したのだが、 モーメント母関数の(r)階導関数からモーメントを導出するのは次回として、 まず、歪度、尖度をそれぞれ別個に書いてあるのを読んでみる。 歪度 確率分布は常に期待値(E(X))を中心に左右対象という訳ではない。 この非対称性の指標の定義があって歪度が使われる。歪度は(alpha_3)という記号を使う。 その定義は以下の通り。 begin{eqnarray} alpha_3 = E(X-mu )^3 / sigma^3 end{eqnarray} (alpha_3 ge 0)ならば左の裾が長い。 (alpha_3 le 0)ならば右の裾が長い、と読む。 また、(|alpha_3|)は左右の歪みの程度を表す。 (alpha_3)を定義通り計算しても値が得られるが、 以下のようにしておくとより楽に得られる。 begin{eqnarray} E(X-mu)^3 &=& E(X^3) - 3mu E(X^2) + 3mu^2 E(X) - mu^3 \\ &=& E(X^3) - 3mu E(X^2) + 2mu^3 end{eqnarray} なんで(alpha_3)の式が確率分布の歪みを表すのか。 ( mu = E(X) )を中心に、(X-mu ge 0)を満たす確率よりも(X-mu le 0 )を満たす確率の方が少なければ、 (X-mu)は右に歪んでいるといえる。逆も真。 3次の式(y=x^3)は、(x ge 0)であれば正、(x le 0 )であれば負であるという特徴がある。 この特徴を使うと、( E(X-mu)^3 )の正負は、(X-mu)の正負と一致すると言える。 さらに、大きさを規格化するため、( E(X-mu)/sigma )の正負を考えることとし、( (X-mu)^3 / sigma^3 )が出てくる。 尖度 分布の頂点がどれだけ尖っているかを表すのが尖度(alpha_4)。 正規分布の尖度を3とし、 (alpha_4-3 gt 0)であれば正規分布より尖っている。 (alpha_4 lt 0)であれば正規分布より尖っていない、とする。 これも(y = x^4 ) の特徴から導かれる。 (x=0)近辺の値と、(x=0)から離れた値を比べたとき、 前者は(y = x^4 ) に対して小さな値を出力し、後者は大きな値を出力する。 (x=0)から離れると急激に値が大きくなるという特徴がある。 つまり、(E(X-mu)^4)を見たとき、 確率値が(mu)の近辺にあれば値は0に近く、(mu)から離れた値が多ければ急激に値が大きくなる。 大きさを規格化し、(E(X-mu)^4/sigma^4)を尖度として利用する。 (alpha_3)と同様に、直接計算する方法もあるが、以下のように計算することもできる。 begin{eqnarray} E(X-mu)^4 &=& E(X^4) - 4mu E(X^3) + 6 mu E(X^3) -4 mu^3 E(X) + mu ^4 \\ &=& E(X^4) - 4mu E(X^3) + 6 mu E(X^2) - 3 mu^4 end{eqnarray} モーメント母関数のr階導関数 期待値、分散、歪度、尖度は、モーメント母関数から統一的に導かれる。 これがまたすごいので次のエントリで書いてみる。
範囲リテラル、case式、while式
[arst_toc tag=\"h3\"] 範囲を表すリテラルが言語仕様として準備されている。 範囲リテラル 範囲クラスはRangeクラスのインスタンス。 \"..\"により開始、終了の両方を含む範囲を表現する。\"...\"だおt終了は含まれない。 def func for var in 1..10 do p var end end func # 1 2 3 4 5 6 7 8 9 10 def func2 for var in 1...10 do p var end end func2 # 1 2 3 4 5 6 7 8 9 包含と同値 include?により範囲に値が含まれるかどうかを調べられる。 (1..5).include?(3) # true (1..5).include?(6) # false なお、===演算子も包含関係を表す。 (1..5) === 3 # true (1..5) === 10 # false ==演算子は同値関係を表す。 (1..5) == (1..5) # true (1..5) == 3 # true 添字演算子 配列の添字演算子に範囲を指定することができる。何番目から何番目という表現はかなり直感的。 a = [ 1, 2, 3 ,4 ,5] p a[1..2] # [2..3] p a[1,3] # 文字列の添字演算子にも範囲を指定することができる。部分文字列を範囲で表現できる。直感的。 a = \"abcdef\" p a[1..2] # bc case式 phpなど他の言語のswitch文。case when.. と書く様子。 でもなぜswitchではいけなかったのだろうか。 case式自体の評価値はcase式内で最後に評価された値となる。 def hoge a case a when 10 then p \"first\" when 20 then p \"second\" else p \"third\" end end hoge 10 # \"first\" hoge 20 # \"second\" hoge 30 # \"third\" whenで何が行なわれているかというと、===演算子によって同値の判断がされている。 なので、whenにRangeを指定すると、===演算子によって包含関係が評価される。 while式 phpのwhileと同等。条件が真である間繰り返す。 i = 0 while (0..5) === i do p i i += 1 end # 1 2 3 4 5 phpなどにもあるように、ループの末尾にwhileを書くことができる。 最初の1回は必ず実行される例のやつ。 後に置くので\"後置while\"という名前がついている。 i=0 begin p i i+=1 end while (1..4) === 1 # 0 1 2 3 4 後置whileとは別に、後に置くが\"修飾詞\"として使う書き方がある。 最初の1回はwhileが評価されるところが後置ifと異なる。 i=0 p i += 1 while (0..4) === i # 1 2 3 4 5 until式 条件が真になるまで繰り返す(真になったら抜ける)、専用の条件式。 Rubyは条件式を書く時に否定文を書く必要がない。(たぶん) i=0 until i===5 do p i i+=1 end # 0 1 2 3 4
確率変数、確率密度関数
[mathjax] やりなおし統計もだいぶ頭が慣れてきた。 だいぶ赤本を読めるようになってきたぞ、という感触がある。 続けて確率密度関数の定義を読んでいく。 一線でやっているデータサイエンティスト(俗称)の方の話として、必要な数学力というのは 実はそこまで高くなく、線形代数といっても実は単に行列の掛け算ができる、とか、 解析学といっても、高校数学の数Ⅲぐらいだったりとか、そういうことを言う人が多いみたい。 (アカデミックな層とは2段も3段も劣るオッさんが基礎を学んだところで行けるラインなんて限界がある。) 機械学習と密接な関係の統計学くらいはちゃんとインプットしておきたいのだが。 もちろん一発で理解できる頭ではないので、たぶんこれから何度もやりなおすだろう。 一方で足りなかったら後から足していけば良いぐらいの位置に立てたかな、とも思う。 確率変数と確率分布 サイコロを投げてそれぞれの目が出る相対度数はおそらく(1/6)くらいだろう。 実際に出る目はランダムで相対度数は決まらなない。一方で確率の定義からそれぞれ(1/6)。 実際に出る目を(X)として表して、(P(X) = 1/6)として表現する。 つまり、 begin{eqnarray} P(X=1)=1/6, P(X=2)=1/6, P(X=3)=1/6, \\ P(X=4)=1/6, P(X=5)=1/6, P(X=6)=1/6 end{eqnarray} この(X)を確率変数という。 また(P(X=x_k)=p_k)を確率分布という。 加算集合({x_1,x_2,cdots,})の中の値を取る確率変数(X=x_k)について 確率分布は離散的であり、(sum_{k=1}^{infty} f(x_k) =1 )である。 また、確率変数が連続である場合、(P(aleq X leq b)=int_{a}^{b} f(x) dx =1 )である 確率密度関数 連続型の確率変数(X)について、(P(xleq X leq x+ Delta x)=int_{a}^{b} f(x) dx )と表す場合、 区間(a)から(b)の定積分、つまり面積が確率値となる。 (X)は連続型の確率分布をもつという。(f(x))を(X)を確率密度関数という。 ここで、全ての(X)に対して以下が成り立つ。 begin{eqnarray} f(x) geq 0 \\ int_{-infty}^{infty}f(x) dx = 1 end{eqnarray} 「密度」とは何なのか。 (P(X)=int_{a}^{b} f(x))の式において、(a=b)であるならば定積分は0である。 確率密度関数において一点の確率は0ということになる。 これだと、密度関数の大小が確率にどう影響を与えるか説明しづらい。 小さい(Delta x)を考えたとき、面積を(Delta x)を使って近似する。 微小区間に掛け与える値の大小(f(x))が確率(P(X))に影響する。 この振る舞いを「密度」と言っているらしい。 begin{eqnarray} P(xleq X leq x+ Delta x) simeq f(x)cdot Delta x end{eqnarray} 全ての確率変数(X)について均一に確率が存在している訳ではなく、 高い確率、低い確率の存在に濃淡がある。その様を表すのが確率密度関数である。 うん、特に難しくない。
ハッシュリテラル
要素の順序性がある場合に使う配列と、順序性がない場合に使うハッシュ。 Rubyのハッシュの言語仕様について。 [arst_toc tag=\"h3\"] 基本的な書き方 PHPと同じようにキーとして文字列を使う場合は以下の通り。 a = {\"hoge1\" => 100, \"hgoe2\" => 200, \"hoge3\" => 300} a[\"hoge1\"] # 100 a[\"hoge2\"] # 200 a[\"hoge4\"] # nil キーとしてシンボルを使う場合は以下の通り。Rubyっぽくなる。 Ruby1.9以降はこちらを使う。 b = {:hoge1 => 100, :hoge2 => 200, :hoge3 => 300} b[:hoge1] # 100 b[:hoge2] # 200 b[:hoge4] # nil Hash.new()による生成 Hash.new()により配列の初期値として新たに確保した変数を設定できる。 Array.new()と同様に、全ての要素として同じオブジェクトを設定する。 a = Hash.new(100) a[:hoge1] # 100 a[:hoge1].object_id # 12345 a[:hoge2] # 100 a[:hoge2].object_id # 12345 []による生成 面倒くさいことに、キーと値が交互に現れる配列からハッシュを生成できる。 その書き方は以下の通り。 a = Hash[:hoge1, 100, :hoge2, 200, :hoge3, 300] # [:hoge1=>100,:hoge2=>200,:hoge3=>300] b = [[:hoge1,100],[:hoge2,200],[:hoge3,300]].to_h # [:hoge1=>100,:hoge2=>200,:hoge3=>300] 関数の引数、波カッコの省略 関数にハッシュを引数として与える場合、ハッシュを構成する波カッコを省略できる。 というか、引数の数が合わなかったときに末尾の処理の一つである様子。 ハッシュ引数の波カッコの種略を引数の先頭にもってくると(末尾の引数でないと)、例外が発生する。 def func a, b, c c end func 100,200,:hoge1=>300,:hoge2=>400 # {:hoge1=>300,:hoge2=>400} func :hoge1=>100,:hoge2=>200,300,400 func :hoge1=>100,:hoge2=>200,300,400 # 例外 キーワード引数 Rubyっぽい書き方。キーワード引数。 キーワードにシンボルで名前をつけることができる。関数を呼び出す時の可読性が向上する。 def func hoge1:, hoge2:, hoge3 hoge1 + hoge2 + hoge3 end func 100,200,300 # 600
配列、配列演算、繰り返し
本日の素振り。 [arst_toc tag=\"h3\"] 配列 配列はArrayクラスのインスタンス。 a = Array.new(3) # [nil,nil,nil] b = Array.new(2,\"hoge\") # [\"hoge\",\"hoge\"] 配列は[]で表す。 a = [10,20,30] # [10,20,30] a[1] # 20 b = [20,true,\"hoge\"] # [10,true,\"hoge\"] b[2] # \"hoge\" b[3] # nil %記法で配列生成 配列の要素が文字列である場合に限って、%W、%wによって文字列を配列化できる。 デリミタは半角スペース、%Wでダブルクォート、%wでシングルクォート(なし)。 自動的にダブルクォート、シングルクォートで括るため、 文字列の配列を作るためにダブルクォート、シングルクォートで汚すことを防げる。 p = %W(hoge fuga) # [\"hoge\",\"fuga\"] q = %w(hoge fuga) # [\'hoge\',\'fuga\'] joinで配列結合 配列と配列を結合して新しい配列を作るのではなく、要素を文字列として結合する。 p = [100,200,300] # [100,200,300] p.join # \"100200300\" p.join(\"_\") # \"100_200_300\" 配列の生成 Array.new()を使うと同じオブジェクトを持つ配列を作れる。 要素分の変数が確保されるのではなく、指定した値を持つ変数が1つ確保されて並ぶ。 a = Array.new(2,\"hoge\") # [\"hoge\", \"hoge\"] a.object_id # 4032 b.object_id # 4032 a[0].replace(\'fuga\') # [\"fuga\", \"fuga\"] a.object_id # 4032 b.object_id # 4032 Array.new(){}だと要素数分の変数が確保されて並ぶ。 a = Array.new(2){\'hoge\'} # [\"hoge\", \"hoge\"] a[0].object_id # 4033 a[1].object_id # 4034 範囲外アクセス 配列の大きさを超えてアクセスしたとき、例外とならず、アクセスしたところまで配列が広がる。 最初の位置から作った最後の位置までnilで埋められる。 a = Array.new(3){\"hoge\"} # [\"hoge\",\"hoge\",\"hoge\"] a[5] = \"fuga\" # [\"hoge\",\"hoge\",\"hoge\",nil,nil,\"fuga\"] 負の添え字アクセス 何が何でもコード行を削減したい意思! 配列に負の添え字でアクセスすると\"末尾から数えた位置\"という意味になる。 a = [\"hoge1\", \"hoge2\", \"hoge3\"] # [\"hoge1\", \"hoge2\", \"hoge3\"] a[-1] # \"hoge3\" 配列のスライス phpのarray_slice()に相当するやつ。指定したインデックスから何個分というやつ。 a = [0, 1, 2, 3, 4, 5, 6] # [0, 1, 2, 3, 4, 5, 6] a[2,3] # [2, 3, 4] 以下のように指定したインデックスから何個分を置き換えられる。 a = [0, 1, 2, 3, 4, 5, 6] # [0, 1, 2, 3, 4, 5, 6] a[1,2] = \"hoge\" # [0, \"hoge\", 3, 4, 5, 6] b = [0, 1, 2, 3, 4, 5, 6] # [0, 1, 2, 3, 4, 5, 6] b[1,2] = \"hoge\", \"fuga\" # [0, \"hoge\",\"fuga\", 3, 4, 5, 6] c = [0, 1, 2, 3, 4, 5, 6] # [0, 1, 2, 3, 4, 5, 6] c[1,2] = \"hoge1\", \"hoge2\", \"hoge3\" # [0, \"hoge1\",\"hoge2\",\"hoge3\", 3, 4, 5, 6] 複数の戻り返却と多重代入 複数の値を返す関数を定義する。複数の値を返すように見えるが配列が返っている。 def test return \"hoge1\", \"hoge2\", \"hoge3\" end a, b, c = test # [\"hoge1\", \"hoge2\", \"hoge3\"] a # \"hoge1\" b # \"hoge2\" c $ \"hoge3\" 戻りとしてネストした配列を返すことができて、同じ構造の変数に代入できる。(多重代入) def test2 return [[\"hoge1\", \"hoge2\"], \"hoge3\"] end (x,y),z = test2 # [[\"hoge1\",\"hgoe2\"], \"hoge3\"] +、-演算子 左オペランドと右オペランドの要素の加算、左オペランドから右オペランドの減算 a = [\"hoge1\",\"hoge2\"] # [\"hoge1\",\"hoge2\"] b = [\"hoge2\",\"hoge3\"] # [\"hoge2\",\"hoge3\"] a + b # [\"hoge1\",\"hoge2\",\"hoge2\",\"hoge3\"] a - b # [\"hoge1\"] b - a # [\"hoge3\"] 減算の場合、左オペランドに重複があるときは全て削除される x = [\"hoge1\",\"hoge1\",\"hoge2\"] # [\"hoge1\",\"hoge2\",\"hoge3\"] y = [\"hoge1\"] x - y # [\"hoge2\",\"hoge3\"] *演算子 右オペランドが数値の場合は左オペランドをその回数繰り返す。 右オペランドが文字列の場合はjoinと同じ。 a = [1,2,3] a * 2 # [1,2,3,1,2,3] for式,each式 PHPのforeachに近いforとeach。forは期待と異なりループの中と外はスコープが同じ。 for内の変数をfor外でアクセスできる。 for value in [100,200,300] inner_loop = 500 end inner_loop + 100 # 600 対してeachはループ内とループ外でスコープが異なりeach内の変数はeach外で未初期化。 [100,200,300].each do inner_look2 = 500 end inner_loop # undefined variable
条件付き確率、乗法定理とベイズの定理
[mathjax] ようやくたどり着いたベイズの定理の入り口。 ここから入らないと何も理解できないと思う。 条件付き確率と事象の独立性 事象(B)が起こることがわかってる場合に事象(A)が起こる確率を (B)を条件とする(A)の条件付き確率という。 読み方は、Probability of A given B. (P_B (A))とも書く。 当たり前のように以下の定義がある。 そもそもここから引っかかる。 begin{eqnarray} P(A|B) = frac{P(Acap B)} {P(B)} end{eqnarray} 見方はこちら。 begin{eqnarray} P(Acap B) = P(B) cdot P(A|B) end{eqnarray} 最初に(B)か(B)でないかで分岐する際に(B)を選んだ確率が(P(B))。 次に(A)か(A)でないかで分岐する際に(A)を選んだ確率が(P(A|B))。 俯瞰して(A)と(B)を両方一気に選ぶ確率は(P(Acap B))。 そう、何が気持ち悪いかというと(P(Acap B))と(P(A|B))が同じでないところ。 ではどう違うのか。 (A)を選ぶ確率が(B)に依存していないならば、(P(A|B))は(P(A))と等しい。 つまり、(P(Acap B) = P(B) cdot P(A))。こちらは直感的。 依存しているならば、(P(B))と(P(A))を分離できなくなり、Aを選ぶ確率にBの影響が出る。 Aを選ぶのにBの影響が出る一般の条件がコレで、 begin{eqnarray} P(Acap B) = P(B) cdot P(A|B) end{eqnarray} そのうち、影響が全くでない特殊な条件がコレ。 begin{eqnarray} P(Acap B) = P(B) cdot P(A) end{eqnarray} すっきり。 ベイズの定理 (H_1,H_2,cdots,H_n)という原因の結果(A)が得られた、という条件。 普通は原因(H)が発生した上で結果(A)が得られる確率(P(A|H))を直接計算することができるが、 結果が得られた上で原因が得られる確率(P(H|A))は直接計算できない。 直接計算できる(P(A|H))を直接計算できない(P(H|A))に変換するのがベイズの定理。 begin{eqnarray} P(H_i|A) = frac{P(H_i)cdot P(A|H_i)}{sum P(H_j) cdot P(A|H_j)} end{eqnarray} 一番有名なたとえ。 「無料」という単語を含むメールが迷惑メールである確率を知りたいけど直接計算では求められない。 大量に迷惑メールを集めて「無料」という単語が含まれる確率は計算で得られる。 後者を前者に変換することで、直接得られない確率を推定する話。 条件付き確率の定義を変形するとベイズの定理になる。 begin{eqnarray} A &=& A cap Omega \\ &=& A cap (H_1 cup H_2 cup cdots cup H_n) \\ &=& (A cap H_1) cup (A cap H_2) cup cdots (A cap H_n) end{eqnarray} ((A cap H_1))、((A cap H_2))、(cdots)、((A cap H_n))は排反だから begin{eqnarray} P(A) &=& sum P(A cup H_j) \\ &=& sum P(H_j) cdot P(A|H_j) end{eqnarray} 条件付き確率の定義に放り込む。 begin{eqnarray} P(H_i|A) &=& frac{P(H_icup A)}{P(A)} \\ &=& frac{P(A|H_i)cdot P(H_i)}{P(A)} \\ &=& frac{P(A|H_i)cdot P(H_i)}{sum P(H_j) cdot P(A|H_j)} end{eqnarray}