2019年4月28日日曜日

影が重なる時に伸びるように見えるやつ


 影と影が重なる時に一方の影が伸びるように見えたり縞模様ができたりするのがすごい好きで暇つぶしによく使っていたのだが、これってプログラムで再現できるかな?とふと思ったのでやってみた。
 感覚一辺倒で「はっきりした影の外側のぼや~っとした影が重なることからなんやかんやでいろんな風に見えるんやろ」と思っていたことから、(1)光源に大きさがあること (2)光源が複数存在すること が影響していると予想し、色々考えた結果次の4パターンの再現を試みた。

(1)大きさ無限大の連続した光源
(2)大きさ有限の連続した光源
(3)一定間隔で無限に並ぶ点光源
(4)一定間隔で有限に並ぶ点光源

 なお3次元空間で考えるのは大変なので、2次元平面で考えた。

[設定]
 床と高さHの天井がある。-∞~0に高さh1で板P1が、a~∞に高さh2で板P2があり、いずれも水平で厚さはない。またh1>h2とする。
 天井のある点yが発する光が点xを照らす強さf(x, y)は、入射角θとした時のsinθに比例し、その点間の距離で求められる球の表面積Sに反比例するものとする。つまり定数lを用いて、f(x, y)=(l*sinθ)/Sとする。
 光源が連続の場合、天井の-d~dは発光している(蛍光灯がある)とする。光源が点の場合、-d~dの範囲の整数の点が発光している(電球がある)とする。無限に光源が続いていることを仮定する場合、d=∞とする。
 ごちゃごちゃ書いたけど図にすればこれだけのことです。


[実装にあたり]
・点xを照らす点yの条件を計算すると-x*(H-h1)/h1<y<x+(a-x)*H/h2なので、f(x, y)をこの範囲でyについて積分すると、点xの明るさI(x)が求まる。
・I(x)を計算するxの範囲は-1<x<h1*max(a)/(h1-h2)とした。上限は絶対に光が差さない領域との境界。下限は特に理由はないが、感覚としてx<0では特に面白いことは起きなさそうだと思ったため適当に決めた値である。
・yを与えられたときに|y|<dならlを、そうでなければ0を返す関数light_existをf(x, y)の定数lに突っ込むことで、汎用性をあげた。

[結果確認]
 結果はヒートマップ形式で見る。マス目の値の弄り方をググるのが面倒だったので、対応させていない点に注意。最上段はa=0であり、下に行くにつれてaが大きくなる(つまり板P2を右へ移動している)。左右はxの値であり、左端がx=-1、右端がx=h1*max(a)/(h1-h2)である。

(1)大きさ無限大の連続した光源


 上右端より上中ちょい左の方が暗いことになっているのさすがに解せないし、しかもその領域は明るさ0を下回っているあたりダメそう。その部分を華麗に無視して明るいところのパターンを見ても、そこまで面白い部分は見つからない。

(2)大きさ有限の連続した光源







 やはり無限の場合と同じく、0未満の領域が気になる。ダメそう。

(3)一定間隔で無限に並ぶ点光源


 直感的に縞模様はこれで納得できますな。また、この図の一部に具体的な数値を入れてみると、


となり、aが小さくなるとそれまで明るかった部分が一気に暗い側に近い値になることで、影が伸びるように見えると考えることもできそうである。

(4)一定間隔で有限に並ぶ点光源







 点光源が増え影の境界が増えることにより、シマシマも増えるってな具合ですな。

 と、こんなんで一応の解決を得たっぽいのだが、連続光源の場合の結果が気持ち悪い。今デスクスタンド(≒有限の連続光源)でマクロに実験してみたところ影は伸びるように見えたので、どうも再現に失敗しているようである。
 さらには、(2)の場合でdのオーダーを下げてみたところ次のようになった。





 この結果は、aを大きくしていくと、明るい部分にぼつぼつと影が出現し始める、ということを意味する。波動の干渉とかならともかく、今回の例でこれはナンセンス。このようにおかしな結果が得られたことに加え、明るさが負になったりしたあたりなども含めて、integrate.quad関数あたりが影響しているような気がする。ぼつぼつの影については、積分区間に対してdが小さすぎることで、ステップ幅的に光源の領域をスキップし、積分値がゼロになってしまったのではないか。その他なんやかんやで反意図的に負の値が出てきたのではないかなぁ~と思ったあたりで疲れたので今日はここまで。

2019年4月21日日曜日

英文をコピペでgoogle翻訳に突っ込む時、改行コードとかが邪魔なので一括で消したい

 この記事のソースコードはこちら。

 改行コードはtext.replace('\n', '')で消せた。
 改行コードの他に消したいものを考えたら、文章中でreferenceがその都度括弧で記載されていて読みにくい時があるのでそれも消したい。どうやらreモジュールで正規表現を使えるようなので使います。

def fixer(text):
    #改行コード削除
    text=re.sub('^\n', '', text)
    text=re.sub('\n$', '', text)
    text=text.replace(' \n', ' ')
    text=text.replace('\n', ' ')
 
    #括弧削除
    text=re.sub(' \(.*?\)', ' ', text)
    text=re.sub('\(.*?\)', '', text)
 
    #カンマ・ピリオド前の空白削除
    text=text.replace(' ,', ',')
    text=text.replace(' .', '.')
 
    return text


 正規表現で最短一致列が欲しい時には?マークを使うのは知らなかった。
 なにはともあれ正規表現で削除したいものを削除できるようになった。これで他にもいろいろ消したいものが出てきたときは消せそうです。

 上の関数を使って、コピペしたい文章をその都度jupyterに貼り付けてRun、でもいいのだが、もうちょっと使い勝手良くしたいなと思ったので、tkinterでGUIにした。

 paster.pyを実行すると、ただの2枠と中央のボタン1個のウインドウが出てくる。左に元の文章コピペして矢印ボタン押すと、右枠に処理済のテキスト出てくるようになっている。


 なおpyperclipをインストールしてあれば、paster_clip.pyが使える。こっちだと矢印ボタンを押した時点でクリップボードに処理済みテキストがコピーされる。

 今後の課題は以下。
・括弧は一括で削除しているが、本当はreferenceっぽいものだけを選択的に削除したい。正規表現の中に年号を含むようにすればいけそうではある。
・ボタン押さなくても変換してほしい。
・googletransというモジュールがある。これ使えば自動で翻訳してくれるような気もするが、インターネットで翻訳した方が精度が良さそうな気もする。そのあたりの検証も含めてgoogletransは近日中に触りたい。

2019年3月24日日曜日

FEPチュートリアルを読んだ

 前にツイッターで見かけたfree-energy principleのチュートリアル(リンク)、過去に2回読んだがいずれも斜め読みして演習問題をやっただけで、ちゃんと内容を読んでいなかった。先日、計算論的精神医学を一通り読み終わり、ちゃんとFEP理解したいなーと思ったので、この機会にチュートリアルをちゃんと読んでみた。
 読んでいる中で、解釈があってるか微妙なところが何点かあったので、それをインターネッツに投げておいて、通りがかりの人に間違いを指摘してもらったり、逆に誰かの助けになったらいいなと思った次第です。コメントでもツイッター(@8_deg_50)でもどっちでも大丈夫ですので、ぜひよろしくお願いします。

・p7右中、式35の下 "there is a twist."
 この表現はyahoo知恵袋()によると「予期しなかったことが起きる」という意味合いを持つらしい。個人的にはその後の"on each trial we need to modify the model parameters just a little bit"がそんなに予期しないことでもないと思ったので、Howeverとまとめて「しかし」として読み進めた。しかしおそらく何かしらの意味があって入っているフレーズだと思うので、何かの解釈もしくはニュアンスを間違えているような気がする。事実、末尾の"(rather than~)"の一節の意味がよくわからん。
 現在の自分の解釈としては「Fを最大化するパラメータも近似分布も求まるけど、現実では観測ごとに解が異なるため、長いスパン(多くの試行)で最善の予測となる分布・パラメータとなるように、少しずつパラメータを調整する必要がある」といったところです。

・p8左上、式(36)
 昔々に多変量正規分布の式を一度勉強した気がするがまったく意味わからなくなってたので、ググって勉強しなおし。HEELO CYBERNETICSさんの記事がわかりやすかった。

・4.2節
 脳内の処理が低次から高次へとなされていくのを模して、i層がi+1層に依存するようにシステムを組むので、つまりiが大きいほど低次。なので式(52)の下の文章の"the mean value of features in one level depends on the next"の"next"は「次のインデックスの層」であり、「情報伝達的に次の層」ではない。(今これ書いてて自分でもそんな読み違いするかよと思うんだけど、実際読み違えてごちゃごちゃしたんだよなぁ…)

・p11左上
 式(66)の話はストロガッツの5.2節あたりと対応。トレースと行列式から収束することはわかるが、相図を書くことで収束することを確認した。
 定数項が鬱陶しいので定数項C=Φi-gi(Φi+1)とした上で進める。
 まずΣi=1, C=0の時、τ=-1<0、Δ=1>0、τ^2-4Δ<0により安定スパイラル。ベクトル場と、試しに[ε, e]=[±0.5, ±0.75]の4点を始点として軌道を見てみる。


 Σi=3にした場合、τ=-1<0、Δ=3>0、τ^2-4Δ<0により、同様に安定スパイラルとなる。


 イメージとしては、分散が大きくなり収束に時間がかかるのを反映して、スパイラルが大きくなったという理解でよさそう。
 Σiを小さい場合をみる。Σi=0.2にした場合、τ=-1<0、Δ=0.2>0、τ^2-4Δ>0により安定ノード。


 定数項Cを変えてみる。局所の傾きが[0, 0]となる点を計算すると[ε, e]=[C/Σi, C]である。行列式が変わるわけではないので、結局のところベクトル場そのものが平行移動したものになりそうだと考えられますね。
 Σi=1, C=0.5の時、局所の傾きが[0, 0]となるのは[0.5, 0.5]であり、そこを中心にした安定スパイラルになる。


 いずれにせよ安定ノードか安定スパイラルになるので、目的の値に収束してくれる。

・Appendix B 式(84)
 転置マークは誤植ですかね。

---------------------------------------------------------------
 この記事書いてる時にすごいアホなミスや勘違いに気付き4つくらい解決したので、アウトプットも捨てたものではないな。
 なおこのチュートリアルだが、求められる数学知識は「行列が含まれてた頃の高校数学」まででほぼ事足りる程度でした。説明もめちゃ丁寧にどの式をどうすればいいかとか書いてあるのでよかったです。

 さて、FEPの面白い点は「Fが小さくなるように行動する」と考えられるところにあると思っているので、行動の要素を追加したモデルを考えてみたい。さすがにここまで初学者にやさしいチュートリアルはないと思うが……。

2019年2月4日月曜日

あれやろうこれやろう

 卒業にあたって「あれを学んでおけばよかった」というのを色々実感したこのタイミングで無職になったら、それはそれで充実したものになると思うんすよね。一方で、大学入学前に「入学後は数学と物理を独学するぞ」と意気込んでいたが結局ダレ朽ちた過去があり、二の舞は踏みたくない。なので先にマニフェストを掲げておくことでこれを避けようというのがこの記事の目的です。

合否によらず2-3月でやること
 これは合否に関わらず2-3月で読み切る。

 これも合否に関わらず2-3月で読み切りたいが、上の進捗次第。

なんだかんだでフリーな時間は2週間くらいしかなさそうなので、これで精一杯かな。

無職のターン(4月以降)
・1年間の記録をつける
 国試浪人生の1年間の生活って結構貴重だし記録に残していきたい。その場合ブログの場所は変えようかな。ただし「医学界隈の内輪でなんか偉くなった気になっているアルファ」に対する謎の敵対心があるので、あまり国試浪人を前面に出して活動はしたくない。難しいところである。

・Kagglerになる
 タイタニックで終わらず、ちゃんとしたコンペに取り組む。成績ノルマを決める自信はないので、とりあえず「Kaggleアカウント持ってます」くらい言える程度にはなる。またこれに伴ってデータサイエンス周辺の領域の勉強したい。

・ニコニコに動画をあげる
 過去におこちゃまのような曲を音雲にあげていたが、あの頃より音楽の聞こえ方がだいぶ良くなっているので、もっと練ってちゃんと動画もつけようと思います。

・技術書を継続的に消化吸収する
 読むものについてはその場で必要なものを読むので、上のKaggle関連だったり下の精神医学系だったりすると思う。ペースについては読む本次第なので、そこにこだわりはしない。とりあえず「リーダブルコード」「みどりぼん」あたりからかな。

・来年の精神神経あたりに計算論的精神医学関連で何か出す
 締め切りがだいたい年末ごろなので目標とするにはちょうどいいんすよね。

・1日10分英語リスニング
 スコアでノルマを作ると正直きついので、継続すること自体をノルマとします。

・国 試 に 受 か る
 大 本 営 。

2018年11月12日月曜日

ブロックごとの分布から合計点の分布

 notebookはこちら

 次のような試験があるとする。
・試験はT(0)~T(n_tests - 1)の(n_tests)個のブロックで構成されている。
・すべての問題は、易しめのA問題と、難しめのB問題に分けられる。
・配点はすべて1点である。
・T(0)はすべてA問題のみで構成されている。
・T(i) (i≧0)は n_questions(i)個の問題で構成され、A問題 n_a(i)個とB問題 n_b(i)個で構成されている。(n_quiestions(i)=n_a(i)+n_b(i))
・受験者数は n_subjects人である。
・j番目の受験者はA問題に正答率 p_a(j)で、B問題に正答率 p_b(j)で正答する。
・正答率 p_a(j)、p_b(j)の間には、全受験者に共通のkを用いて p_b(j)=k*p_a(j) の関係が成り立つ。(0<k<1)
・試験後に受験者に返却される成績表には、各ブロックの自分の得点率(1%刻み)と、各ブロックで得点率区間(5%刻み)に何人の受験者がいるかのヒストグラムが掲載されている。

 この設定下で、合計点の分布および自分の推定順位を求めたい。

 そこで次のような処理を考えた。
(1) T(0)の成績は受験者の正答率 p_a(j) に忠実だと仮定し、p_a(j)を求める。この時、T(0)の得点率ヒストグラムにおいて各得点率区間には一様に受験者が分布するものと仮定する。(つまり55-60%の区間に4人いた場合、その4人のp_aは[55.00, 56.25, 57.50, 58.75]ということにする。)
(2) 適当なkについて、p_b(j) を求める。
(3) n_a(i)とn_b(i)を求める。
  上で仮定したp_a(j)、p_b(j)をもつ受験者がT(i)を受けた場合の得点率の確率変数を、二項分布 B(n_a(i), p_a(j)) と B(n_b(i), p_b(j)) から求める。これを全受験者について求め合計し、実際の結果のヒストグラムとのKL情報量を求める。この作業を考えられるすべてのn_a(i), n_b(i)の組み合わせで行い、その中でKL情報量が最も小さくなるn_a(i)、n_b(i)を採用する。
(4) (2)と(3)を繰り返し、納得のいくkを求める。
(5) (4)で決定したkを用いて、受験者のp_a(j)、p_b(j)を決定する。
(6) (5)で決定したp_a(j)、p_b(j)を用いて、各受験者の合計点の確率変数を求める。
(7) 全受験者の合計点の確率変数の総和をとる。

 上記を実装する。使用するデータは、n_tests=5、n_questions=[100, 50, 30, 30, 40]、n_subjects=100としてテキトーに作成したsample_distribution.csvである。sample_distribution.csvの (x, y) には、T(x)の5*y~5*(y+1)%にいる人数が入っている。

 まずはk=0.4, 0.5, ... , 0.8 の各場合について、ブロックごとのA, B問題の配分およびKL情報量を表示する。

------------------------------------------------------------

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import math

def Combination(n, r):
    return math.factorial(n)//math.factorial(n-r)//math.factorial(r)

def Binomial(param):
    N, p=param
    return [Combination(N, k)*pow(p, k)*pow(1-p, N-k) for k in range(N+1)]

def Binomials(list_n_p):
    #list_n_p: 2-dimensioin vector (shape=(n, 2))
    #list_n_p[i]: i-th setting for Binomial distribution.
    #list_n_p[i][0]: n of i-th Binomial dist.
    #list_n_p[i][1]: p of i-th Binomial dist.
   
    list_n_p=[list_n_p[i] for i in range(len(list_n_p)) if list_n_p[i][0]!=0]
    hist=Binomial(list_n_p[0])
    for i in range(len(list_n_p)-1):
        pre_prob=hist
        add_prob=Binomial(list_n_p[i+1])
        l=np.max([len(pre_prob), len(add_prob)])
        mx=len(pre_prob)+len(add_prob)-2

        #probability map
        prob_map=np.zeros((l, l))
        prob_map[:len(pre_prob), :len(add_prob)]=np.dot(np.array(pre_prob).reshape(-1, 1), np.array(add_prob).reshape(1, -1))

        #upper-left part of matrix
        hist_former=[np.sum([prob_map[i, s-i] for i in range(s+1)]) for s in range(l)]

        #lower-right part of matrix
        hist_latter=[np.sum([prob_map[-1-i, l-(minus-i)-1] for i in range(minus+1)]) for minus in range(l-1)]
        hist=hist_former+hist_latter[::-1]
        hist=hist[:mx+1]
       
    return hist

def Sum_Prob(list_A, list_B):
    l=np.max([len(list_A), len(list_B)])
    mx=len(list_A)+len(list_B)
   
    prob_map=np.zeros((l, l))
    prob_map[:len(list_A), :len(list_B)]=np.dot(np.array(list_A).reshape(-1, 1), np.array(list_B).reshape(1, -1))
   
    hist_former=[np.sum([prob_map[i, s-i] for i in range(s+1)]) for s in range(l)]
    hist_latter=[np.sum([prob_map[-1-i, l-(minus-i)-1] for i in range(minus+1)]) for minus in range(l-1)]
   
    hist=hist_former+hist_latter[::-1]
    hist=hist[:mx+1]
   
    return hist

class test:
    def __init__(self, n_total, distribution):
        self.n_total=n_total
        self.distribution=distribution
        self.n_a=np.nan
        self.n_b=np.nan
        self.KLd=np.nan
        self.edit_distribution()
        return
   
    def edit_distribution(self):
        total=np.sum(self.distribution)
        self.distribution=[self.distribution[i]/total for i in range(len(self.distribution))]
   
    def calc_n(self, p_a, p_b, detail=False):
        temp_n_a=0
        while temp_n_a<=self.n_total:
            temp_n_b=round(self.n_total-temp_n_a)
            temp_dist=np.zeros(self.n_total+1)
            for i in range(p_a.shape[0]):
                temp_dist+=np.array(Binomials([[temp_n_a, p_a[i]], [temp_n_b, p_b[i]]]))/p_a.shape[0]
               
            temp_dist=self.convert_dist2percentage(temp_dist)
            if np.abs(mean_dist(np.arange(101), temp_dist)-mean_dist(np.arange(0, 100, 5), self.distribution))<20:
                temp_dist_5=[np.sum(temp_dist[:6])]+[np.sum(temp_dist[5*(i+1)+1:5*(i+2)+1]) for i in range(19)]
                temp_KLd=KLdivergence(temp_dist_5, self.distribution)
                if detail==True:
                    plt.plot(np.arange(len(temp_dist_5))*5+5, temp_dist_5, label='temp_dist')
                    plt.plot(np.arange(len(temp_dist_5))*5+5, self.distribution, label='self.distribution')
                    plt.title('n_a='+str(temp_n_a)+', n_b='+str(temp_n_b)+', KLd='+str(temp_KLd))
                    plt.show()

                if self.KLd>temp_KLd or np.isnan(self.KLd):
                    self.KLd=temp_KLd
                    self.n_a=temp_n_a
                   
            else:
                if detail==True:
                    print('Skip: n_a='+str(temp_n_a)+', n_b='+str(temp_n_b))
               
            temp_n_a=round(temp_n_a+1)
               
        self.n_b=self.n_total-self.n_a
        return
   
    def convert_dist2percentage(self, dist):
        percentage=[0 for i in range(101)]
        for i in range(len(dist)):
            percentage[round(100*i/(len(dist)-1))]+=dist[i]
           
        return percentage
   
def KLdivergence(p, q):
    p_range=[p[i]>1e-12 for i in range(len(p))]
    q_range=[q[i]>1e-12 for i in range(len(q))]
   
    double_true=np.sum([1 if p_range[i]==True and q_range[i]==True else 0 for i in range(len(p))])
    if double_true>0:
        out=np.sum([p[i]*np.log(p[i]/q[i]) if p_range[i]==True and q_range[i]==True else 0 for i in range(len(p))])
    else:
        out=np.inf
       
    return out

def mean_dist(value, dist):
    return np.dot(value, dist)

#data preparation
rawdata=pd.read_csv('data/sample_distribution.csv', index_col=0)
thrs=[5*i for i in range(20)]
n_questions=[100, 90, 80, 60, 70]
n_tests=len(n_questions)
n_subjects=100
subjects=np.zeros((2, n_subjects))

def dist2data(dist, thresholds):
    output=np.array([])
    i=0
    while i<len(thresholds)-1:
        if dist[i]!=0:
            interval=(thresholds[i+1]-thresholds[i])/dist[i]
            output=np.r_[output, np.arange(thresholds[i]+interval, thresholds[i+1]+interval, interval)]
           
        i+=1
    return output

subjects[0, :]=dist2data(np.array(rawdata.loc['exam0']), thrs)
subjects[0, :]/=100

for k in [0.4, 0.5, 0.6, 0.7, 0.8]:
    print('Calculation: k='+str(k))
    tests=[test(n_questions[i], np.array(rawdata.loc['exam'+str(i)])) for i in range(len(n_questions))]
    tests[0].n_a=n_questions[0]
    tests[0].n_b=0
   
    for i in range(n_tests-1):
        subjects[1]=k*subjects[0]
        tests[i+1].calc_n(subjects[0], subjects[1])
        print('tests['+str(i+1)+']: n_a='+str(tests[i+1].n_a)+', n_b='+str(tests[i+1].n_b)+', KLd='+str(tests[i+1].KLd))
       
    print()

------------------------------------------------------------

Calculation: k=0.4
tests[1]: n_a=19, n_b=31, KLd=0.07051862322854846
tests[2]: n_a=18, n_b=12, KLd=0.09610677830528232
tests[3]: n_a=19, n_b=11, KLd=0.16822053964137743
tests[4]: n_a=19, n_b=21, KLd=0.15326233787382407

Calculation: k=0.5
tests[1]: n_a=13, n_b=37, KLd=0.06463637859617347
tests[2]: n_a=15, n_b=15, KLd=0.09679698263389093
tests[3]: n_a=16, n_b=14, KLd=0.16493123276583527
tests[4]: n_a=15, n_b=25, KLd=0.1427783581892589

Calculation: k=0.6
tests[1]: n_a=4, n_b=46, KLd=0.06086984932650339
tests[2]: n_a=12, n_b=18, KLd=0.0953198995596042
tests[3]: n_a=13, n_b=17, KLd=0.16350196731623778
tests[4]: n_a=8, n_b=32, KLd=0.13279440803859335

Calculation: k=0.7
tests[1]: n_a=0, n_b=50, KLd=0.2184121139905782
tests[2]: n_a=6, n_b=24, KLd=0.09600257982634174
tests[3]: n_a=7, n_b=23, KLd=0.16232317488955234
tests[4]: n_a=0, n_b=40, KLd=0.13275637941572044

Calculation: k=0.8
tests[1]: n_a=0, n_b=50, KLd=0.8637338663088325
tests[2]: n_a=0, n_b=30, KLd=0.13691841637960955
tests[3]: n_a=0, n_b=30, KLd=0.18728352476626814
tests[4]: n_a=0, n_b=40, KLd=0.45602704138127004

------------------------------------------------------------

 KLd的にはk=0.6 or 0.7が良さそう。またkが0.7以上の時、tests[1]とtests[4]の問題がすべてB問題となり不自然なので、 k=0.6 を採用する。
 続き。
------------------------------------------------------------

k=0.6

tests=[test(n_questions[i], np.array(rawdata.loc['exam'+str(i)])) for i in range(len(n_questions))]
tests[0].n_a=n_questions[0]
tests[0].n_b=0

for i in range(n_tests-1):
    subjects[1]=k*subjects[0]
    tests[i+1].calc_n(subjects[0], subjects[1])
    print('tests['+str(i+1)+']: n_a='+str(tests[i+1].n_a)+', n_b='+str(tests[i+1].n_b)+', KLd='+str(tests[i+1].KLd))

result=np.zeros(np.sum(n_questions)+1)

for i in range(n_subjects):
    As=[[tests[j].n_a, subjects[0, i]] for j in range(n_tests)]
    Bs=[[tests[j].n_b, subjects[1, i]] for j in range(n_tests)]
    params=As+Bs
   
    subj_dist=Binomials(params)
    result+=np.array(subj_dist)

result=n_subjects*result/np.sum(result)

plt.plot(np.arange(result.shape[0]), result)
plt.xlabel('score')
plt.ylabel('subject')
plt.title('score distribution')
plt.show()

------------------------------------------------------------

------------------------------------------------------------

 この分布の線下面積は n_subjectsになるようにしている。つまり自分の点数が X点だった場合に、順位は score>X の部分の線下面積 S(score>X)を求めればよい。今回の場合 X=150だと面積 S(score>150) =63.4となるため、推定順位は64位となる。

 と、ここまで書いたところで、使用するサンプルデータをテキトーに作るのではなく、正解となる全受験者の正答率p_a, p_bと、各ブロックのA問題とB問題の配分と、合計点の分布を設定した上で作っていれば、上記の処理で得られる結果とどの程度一致するかを確かめられたなァと気づいた。しかしこの先暇つぶしする時間はないので、これ以上いじるつもりはない。

2018年11月8日木曜日

二項分布の和の分布

 notebookはこちら

 確率変数の和の分布は単純な足し算ではできない、というのを実感するために、二項分布の和の分布を求めた。要は暇つぶしです。
 まず2つの場合。内積を使って各事象の確率を求めて、斜めに和をとった。
 正しくできているかどうかは、数学的に求められる確率変数の和の平均・分散が、入手した確率変数の和の分布から計算する平均・分散と一致するかどうかで確かめた。

--------------------------------------------------------------
import numpy as np
import matplotlib.pyplot as plt
import math

def Combination(n, r):
    return math.factorial(n)//math.factorial(n-r)//math.factorial(r)

def Binomial(param):
    N=param[0]
    p=param[1]
    return [Combination(N, k)*pow(p, k)*pow(1-p, N-k) for k in range(N+1)]

def Sum_Binomial(na, pa, nb, pb):
    A=Binomial([na, pa])
    B=Binomial([nb, pb])
    l=np.max([na, nb])
   
    #probability map
    prob_map=np.zeros((l+1, l+1))
    prob_map[:na+1, :nb+1]=np.dot(np.array(A).reshape(-1, 1), np.array(B).reshape(1, -1))
   
    #upper-left part of matrix
    hist_former=[np.sum([prob_map[i, s-i] for i in range(s+1)]) for s in range(l+1)]
   
    #lower-right part of matrix
    hist_latter=[np.sum([prob_map[-1-i, l-(minus-i)] for i in range(minus+1)]) for minus in range(l)]
   
    hist=hist_former+hist_latter[::-1]
    return hist

#set parameters
na, pa=10, 0.8
nb, pb=8, 0.7

#get Binomial distribution
A=Binomial([na, pa])
B=Binomial([nb, pb])

#get E(A+B) and V(A+B) by mathmatical method
print('mathmatical method: E(A+B)=E(A)+E(B)='+str(na*pa+nb*pb))
print('mathmatical method: V(A+B)=V(A)+V(B)='+str(na*pa*(1-pa)+nb*pb*(1-pb)))

#get A+B distribution
add_dist=Sum_Binomial(na, pa, nb, pb)

#get E(A+B)
mu=np.dot(np.arange(len(add_dist)), add_dist)
print('computational method: E(A+B)='+str(mu))

#get V(A+B)
rho=np.dot(np.arange(len(add_dist))**2, add_dist)-mu**2
print('computational method: V(A+B)='+str(rho))

#plot
plt.plot(np.arange(len(add_dist)), add_dist)
plt.show()
--------------------------------------------------------------
mathmatical method: E(A+B)=E(A)+E(B)=13.6
mathmatical method: V(A+B)=V(A)+V(B)=3.28
computational method: E(A+B)=13.600000000000001
computational method: V(A+B)=3.2799999999999727


--------------------------------------------------------------


 大丈夫そう。
 調子に乗って、確率変数が3つ以上になった場合もやった。

--------------------------------------------------------------
def Binomials(list_n_p):
    #list_n_p: 2-dimensioin vector (shape=(n, 2))
    #list_n_p[i]: i-th setting for Binomial distribution.
    #list_n_p[i][0]: n of i-th Binomial dist.
    #list_n_p[i][1]: p of i-th Binomial dist.

    hist=Binomial(list_n_p[0])
    for i in range(len(list_n_p)-1):
        pre_prob=hist
        add_prob=Binomial(list_n_p[i+1])
l=np.max([len(pre_prob), len(add_prob)])
        mx=len(pre_prob)+len(add_prob)-2
        #probability map
        prob_map=np.zeros((l, l))
        prob_map[:len(pre_prob), :len(add_prob)]=np.dot(np.array(pre_prob).reshape(-1, 1), np.array(add_prob).reshape(1, -1))
        #upper-left part of matrix
        hist_former=[np.sum([prob_map[i, s-i] for i in range(s+1)]) for s in range(l)]
        #lower-right part of matrix
        hist_latter=[np.sum([prob_map[-1-i, l-(minus-i)-1] for i in range(minus+1)]) for minus in range(l-1)]
        hist=hist_former+hist_latter[::-1]
        hist=hist[:mx+1]
       
    return hist

#set parameters
na, pa=10, 0.8
nb, pb=8, 0.7
nc, pc=6, 0.5
paramlist=[
    [na, pa],
    [nb, pb],
    [nc, pc]
]

#get Binomial distribution
A=Binomial([na, pa])
B=Binomial([nb, pb])
C=Binomial([nc, pc])

#get E(A+B+C) and V(A+B+C) by mathmatical method
print('mathmatical method: E(A+B+C)=E(A)+E(B)+E(C)='+str(na*pa+nb*pb+nc*pc))
print('mathmatical method: V(A+B+C)=V(A)+V(B)+V(C)='+str(na*pa*(1-pa)+nb*pb*(1-pb)+nc*pc*(1-pc)))

#get A+B+C distribution
add_dist=Binomials(paramlist)

#get E(A+B+C)
mu=np.dot(np.arange(len(add_dist)), add_dist)
print('computational method: E(A+B+C)='+str(mu))

#get V(A+B+C)
rho=np.dot(np.arange(len(add_dist))**2, add_dist)-mu**2
print('computational method: V(A+B+C)='+str(rho))

#plot
plt.plot(np.arange(len(add_dist)), add_dist)
plt.show()

--------------------------------------------------------------
mathmatical method: E(A+B+C)=E(A)+E(B)+E(C)=16.6
mathmatical method: V(A+B+C)=V(A)+V(B)+V(C)=4.779999999999999
computational method: E(A+B+C)=16.6
computational method: V(A+B+C)=4.779999999999916



--------------------------------------------------------------

 大丈夫そう。
 正直1時間もあればできると思ってたらだいぶ時間かかってしまった。一番引っかかったのが、はじめCombination()関数をC()と名付けていて、名前が分布Cと競合したところなのが完全に天然なんだが、そうはいってもなぁ。
 とはいえつい先日までリスト内包表記を全く知らなかったことを考えれば、だいぶ成長したか。

2018年10月25日木曜日

Fitzhugh-Nagumoモデルのパラメータ変化


 Fitzhugh-Nagumoモデルのパラメータa, b, c, τを変化させた時の挙動の変化を調べた。

 まずaを変化させた。


 aが大きくなるにつれて波の振幅が狭くなる。主に上行脚よりも下降脚が急になっている印象を受ける。またわずかながらy軸方向にも平行移動しているように見えるが、振幅に対する効果に比べると無視していいレベルだと感じる。

 次にbを変化させた。


 大きくなるにつれて立ち上がりの閾値が上がり、それによって波と波の間隔が広がっているようにみえる。その考えならば、b=1で発火活動がみられないのは閾値を超えなくなったからとすると納得がいく。
 またその変化幅がbが大きいほど大きいように見えるので、分岐点に近い値で再度調べた。


 分岐点の近くだと少しの変化でかなり効果が出る。

 次にcを変化させた。


 cを大きくするに従って(1)閾値が下がり、(2)波の幅が短くなった ようにみえる。

 ここでbとcがいずれも閾値に影響したことから、二つの合わせ技を考えてみる。つまり先の結果では、b=1では発火活動がみられなかった。そこで、b=1のままcを大きくした場合に、発火活動がみられるようになるかを調べた。


 いけた。

 次にτを変化させた。


 τの変化によって時間軸方向に伸び縮みしているように見える。ただしある値より小さいと発火しなくなるようで、τ=0.4では発火活動がみられない。
 τの変化によって閾値が変化したのかはこの結果からはっきりとはわからないが、先ほどのように他の値との合わせ技を考えてみる。つまりτ=0.4としたまま、b, cを変化させて発火するようにすることはできるかを試す。
 まずはτ=0.4のままbを変化させる。


いけた。
 同様にτ=0.4のままcを変化させる。


これもいけた。
 これらの結果から一昨日の記事に対するアンサーは、「bを小さく」「cを大きく」すると閾値が下がり発火するようになり、「bを大きく」「cを小さく」すると閾値が上がって発火しにくくなりそうである。τに関しては、元の式みるとdw/dt全体にかかる係数だし、時間軸の伸び縮みくらいしか役割がない気がしているので一旦固定でいいかななどと思っている。