読者です 読者をやめる 読者になる 読者になる

室村日記

日々、試行錯誤したことを備忘録的にまとめていきます

中心極限定理

1 はじめに

中心極限定理について直感ではいまいち納得できなかったのでちょっとまとめようとおもったのですが,その前にまず大数の法則についてまとめてみます.

wikipedia先生に中心極限定理について聞いてみます.

大数の法則によると、ある母集団から無作為抽出された標本平均はサンプルのサイズを大きくすると真の平均に近づく。これに対し中心極限定理は標本平均と真の平均との誤差を論ずるものである。多くの場合、母集団の分布がどんな分布であっても、その誤差はサンプルのサイズを大きくしたとき近似的に正規分布に従う。

wikipedia:中心極限定理
さて,中心極限定理について聞いたのに大数の法則というのが出てきました.

2 大数の法則とは?

大数の法則が言っているのはあたりまえのことでした.
世の中のたいていの事象は無限に存在するので,その全てを調べ尽くすことは不可能に等しい行為です.
例えば卵にはたまに黄味が2つ入っているのがあります.
しかし黄味が2つ入っている確率を求めたいとして,そのためにこの世の全ての卵を調べようなんてのは無理な相談です.(こうしている間にも鶏は卵を産み続けます)
そこでほとんどの人が思いつく方法が,だいたい1,000個とか頑張って10,000個とかの卵を調べて,その中の黄味が2つある卵を数え,それでとりあえず確率を出して満足しておこうというものでしょう.
これがまさに大数の法則の考え方となります.
自分が調べることのできる量(=1000個)なんて全体の量(=世界中の卵の数)からみたら本当に微々たるものにすぎませんが,それでも結構信頼の高い確率を得ることができるということです.
これを指して,大数の法則は「経験的確率と理論的確率が一致する」ことを示すものだと言ったりもします.
また,ここで信頼性という面で大事なのは自分が”何個”調べたかであって,”何%”調べたかではないというところに注意が必要です.

大数の法則が現実に使われている例としては,選挙速報やTVの視聴率調査があります.
これらはほとんど現実の値から外れることはありませんが,現実にすべての投票者.すべてのTVのある家庭に対して調査を行っているわけではありません.
一部の家庭や有権者を調べるだけで,全体の動向もだいたいわかってしまうものなのです.

Pythonで麻雀の戦績を集計してみた

1 初めに

前回の投稿に引き続き,記録をとっていた最近の戦績を集計してみました.
基本的にはpythonのpandasしか使ってないのですが,集計してみたり,for文回してみたり,DataFrame 結合してみたり,ブロードキャストについて思い出したりと結構よい勉強になりました.
それではいよいよ実際に行った作業に移ります.


2 やったこと

こんな感じのデータを読み込みます.
f:id:muromura:20170510223606p:plain
列が各対局者,行が半荘ごとの成績(得点)となっています.
対局に参加していない人の欄は空白になっており,pythonではNaNとして読み込まれます.

3行目から6行目はエクセルで順位つけようとして途中で面倒になってやめた名残です.
不要行削除の例示に都合良いのでそのままにしておきます.



1行を削除するときには,配列のスライスと同じように指定すれば良いです.

#不要部分削除
hoge[:5]

列を削除するときにはdrop()を使います.

#不要部分削除
data = data.drop("削除したい列名", axis=1)

対局数をカウントするのには,NaNの数を利用することにしました.
各列ごとにis.null()でNaNの数を調べるとTrue/Falseで結果が帰ってきます.
pythonにおいてはTrue/Falseは数値計算すると1/0に等しくなるので,結果を1から引いてやることで各列で対局に参加した部分(=得点が入力されている部分)を1,それ以外を0とすることができました.
あとはそれを合計するだけです.

#対局数
(1-data.isnull()).sum()

順位の集計にはちょっと手間取りました.
rank()を使えば良いのですが,このとき行ごとに順位を出すには,引数でaxis=1を指定します.
ちなみにこのときNaNはNaNのまま無視してくれました.便利!

rank()を使うと数が少ない順に1から数字が振られるので,本来の順位とは逆になってしまいます.
それをひっくり返すためには5から順位を引けば良いことになります.
これはpythonのブロードキャストを使って簡単に書けました.

#順位づけ
rank = data.rank(axis=1)
rank= 5 - rank

次に,1位から4位までそれぞれの順位を何回とったか数えます.
たぶん他にもっと良い方法があると思いますが,思いつかなかったので,各列ごとに抜き出してvalue_counts()で集計し,後でそれを全部くっつけるという方法をとることにしました,
最初に空のDataFrameを用意し,colmuns()でとってきた列名でfor文を回して集計結果をくっつけています.
DataFrameの結合にはconcat()を使い,列方向にくっつけたいときにはaxis=1を指定します.

#各順位を何回とったかの集計
#列名を取得
players = rank.columns
#空のデータフレームを用意
ranks = pd.DataFrame(index=[1,2,3,4], columns=[])
#for文で回してranksに突っ込んでいく.
for player in players:
    r = rank[player].value_counts()
    ranks = pd.concat([ranks, r], axis=1)

3 サンプルコード

というわけで長くなってしまいましたが,サンプルコードは以下.

import numpy as np
import pandas as pd

#データ読み込み
raw_data = pd.read_csv("sample.csv", encoding="sjis")

#不要な部分を切り捨てる
data = pd.DataFrame(raw_data[5:])
data = data.drop("day", axis=1)
#型をチェック
#data.dtypes

#順位づけ
rank = data.rank(axis=1)
rank= 5 - rank

#対局数
(1-data.isnull()).sum()

#平均順位算
rank.mean()

#合計計算
data.sum()

#平均得点計算
data.mean()

#順位合計
players = rank.columns
players
ranks = pd.DataFrame(index=[1,2,3,4], columns=[])
for player in players:
    r = rank[player].value_counts()
    ranks = pd.concat([ranks, r], axis=1)

#集計
result = pd.concat([(1-data.isnull()).sum(),
                    ranks.T,
                    rank.mean(),
                    data.sum(), 
                    data.mean()],
                   axis=1, join='inner').T

result.index=['対局数',"1位","2位","3位","4位",'平均順位','合計得点','平均得点']
result = result.round(2)

結果はこんな感じになります.
f:id:muromura:20170510224213p:plain


また時間のあるときに,次は各日ごとの集計をしたり,player同士の相性を計算したりなどやっていきたいと思います.
適当ですが,得点の相関係数とかでも相性の指標になりうるかもしれませんね.

それにしてもplayer1勝ちすぎです.
それでは.

pythonでcsvファイルを読み込む

1 はじめに

いつか集計しようと思って,趣味の麻雀の記録をexcelでとっていたのですが,この度ついに実行に移すことになりました.
とりあえず最初はRでごちゃごちゃとやっていたのですが,せっかくなら勉強中のpythonでやったほうがいいのではないかと思いたち,まずはデータを読み込むところから始めようと思ったわけです.

2 やったこと

exelで集計していたデータをpythonで読み込みます.
使用環境は3系(python3.6.0)でJupyter Notebookを利用しています.

というわけでpandasのread_csv()を利用します.
名前的にもcsvしか読み込んでくれなさそなので,エクセルで作成したファイルを[cmd+shift+s]でファイル形式をcsvに変えて別名保存します.
早速以下のスクリプトを動かすと

pd.read_csv("../Data/mahjong_log.csv")

怒られました.

UnicodeDecodeError: 'utf-8' codec can't decode byte 0x8c in position 1: invalid start byte

初めから蹴つまずきますね.
google恩師に尋ねながら,read_table()を試してみたりしたけど同じエラーを吐きよります.

Rのときと同じように,エディタにコピペしてencoding変えて保存したらうまくいくんでしょうけど,この先データを更新するたびにその作業をするのも面倒ですよね.
そもそもエクセルをcsvに保存し直すのすらめんどいのに.
と考えたところでRのように読み込み時にencoding="sjis"で指定してやったらうまくいくんじゃないかと思ったら一発で読み込んでくれました.

3 サンプルコード

というわけでサンプルコードは以下.

import pandas as pd
pd.read_csv("hoge.csv", encoding="sjis")

次はとりあえず点数を順位に変換して平均順位などを出したいと思います.

それでは.

ファイルを一括で検索して移動

1.はじめに

 ダウンロードしてきたフリーペーパーがPDFなのはいいんだけど,各章ごとに別フォルダに分割されちゃっていました.
f:id:muromura:20170426013039p:plain
 この中に1つずつバラバラに各章のPDFが入っている感じです.

 印刷したり読んだりするのに,いちいちフォルダを開いたり閉じたりして一箇所にまとめなくちゃいけないのが面倒だなーと思っていたところ,むかしbashでそういうファイル管理を触ったことがあるのを思い出したのでメモ.

*ちなみにMacならディレクトリだろ?とかbashLinux?とかよくわかんないのでスルー


2.相対パス

 相対パスの書き方って言語ごとに微妙に違うようですね.困りますね.
 とりあえず,bash書くときは ../ で親ディレクトリが取れます.
 さらに遡りたいときは ../../ のように続けていけば大丈夫そうです.


3.mv

mv [現在のpath] [移動したいpath]

ディレクトリやファイルが移動できます.このときに例えば

mv [現在のpath] ~/hoge/piyo.pdf

とすれば,hogeというディレクトリの下のpiyo.pdfと名前を変更しつつ保存できるし,

mv [現在のpath] ~/hoge/

とすれば,hogeというディレクトリの下に名前はそのままで保存してくれます.

基本的にbashのmv(移動)やcp(コピー)は,移動先に指定された名前のディレクトリが存在すればそこに,なければ勝手に作ってそこに移動してくれるようです.


4.正規表現

~/hoge/*

のように * を使うと,hogeディレクトリにある全てのファイル,ディレクトリのことを指してくれます.これはちなみに,

~/hoge/*.pdf

のように部分指定をかけることも可能です.

5.スクリプトbashとして動かす

 vimでもsublimeでもatomでもいいのですが,sample.shのように.shという拡張子のファイルを作ります.拡張子もべつに何でもいいのですが,シェルスクリプトであるということを表すために.shを用いるのが普通です.bashというのはシェルスクリプトの中の一つです.

 中身は以下のようにします.

#!/bin/sh

[ここに命令を書く]

 スクリプトが完成したら,ターミナルに

bash [.shファイルのパス]

 と打ち込むと実行できます.
 例えばカレントディレクトリにsample.shがあれば以下です.

bash sample.sh

6.サンプルスクリプト

ということで,以上全ての知識を生かして今回使ったスクリプトが下です.

#!/bin/sh

pdfs="../paper/*/*"
for pdf in $pdfs; do
    echo "Moving: " $pdf
    mv $pdf ../pdf/
done

ここでは,paperというディレクトリの下に
f:id:muromura:20170426013039p:plain
このようにディレクトリが並んでいて,その中にさらにpdfファイルが入っている状態から,paperのとなりのpdfというディレクトリに,一括でpdfファイルを移動する操作を行いました.

ちなみにbashでは,変数は頭に$をつけないと呼び出せません.
echoは標準出力してくれる関数です.他の言語のprintのようなものですかね.


5.まとめ

何を簡単なことをだらだら書いてんねんという話ですが,こういう簡単なことも,やらないと半年ぐらいですぐに忘れるのでメモしておきました.
初心者はありとあらゆるところにつまづきます.

それでは.

ニューラルネットワークにおける勾配法と学習率

1.はじめに

 初めに注意.
 この文章は「ゼロからつくるDeep Learning」の第5章「誤差逆伝搬法」までを学習した者により,備忘録の意を込めて書かれています.しかもそいつはプログラミングすらほとんど真面目に勉強したことがありません.よって,抜け漏れや勘違いは非常に多いと思われます.

 さて.1週間で終わらせると息巻いていたものの,予定は未定とはよく言ったもので,結局1週間経ったというのにまだ終わる見込みがありません.
 とりあえず現在初めて,ニューラルネットによる文字認識の学習を回しているのですが,それがあまりにも遅いので,その間にいくつかまとめておこうと思います.例のごとく間違った認識かもしれませんが,あくまで自分用ということで……
 
 今回は勾配法と学習率について簡単にまとめようと思います.
 文字(手書き数字)認識というのは,要はコンピュータに人間が書いた手書きの,つまり時として汚かったり癖があったりする数字を認識してもらい,果たしてその文字が1~9のどれなのかを判別してもらうというものです.
f:id:muromura:20170424230236j:plain
 そのときにコンピュータの内部では様々なパラメータが動いていて,例えば
「これは丸があって上に棒が飛び出ているから”6”かな」
「これは丸が二つあるから8かな」
「これは二回折れ曲がっている棒だから7かな」
のような判断をしているようです.
例えばこの「二回折れ曲がっている」などを判別しするために大量のパラメータが用意されています.
このパラメータをいい感じに調整して,1は1,7は7として認識してくれるようにするのが,学習ということになります.
 今回はその学習の方法として勾配法を用いるようです.
 
*正確には丸とか棒ではなくもっと抽象的に捉えているのだと思いますが,わかりやすい例としてあげました.さらに正確に言えば,今回の手法では文字の画像を個別のピクセルレベルに分解して捉えています.



2.勾配法

 学習のためには,どちらに向かって進めばいいのか,つまり道しるべが必要になってきます.
 その道しるべとして,訓練用データと正解データの損失関数(ここでは交差エントロピー誤差)を用いるのが勾配法です.

 なぜ,正解/不正解の数という認識精度をそのまま道しるべとして用いてはいけないのかという疑問が生じます.こちらの方が筋道が通っているようにみえます.
 
 しかし,これだと学習がうまくいかないのです.なぜなら認識精度は非連続だからです.
 つまり,100枚中90枚正解したとするとその値は90%となります.90%の次は91%です.この場合には90.7%正解しました,などということはありません.

 その場合,どのような問題が起きるか?
 例えば,少し精度がよくなる方にパラメータが調整できたとします.しかしそれが,確かに向上はしているのですが,1枚分正解を増やすほどの向上ではなかったとします.そのとき,認識精度を目的とした学習だと,今回の調整はうまくいかなかったとして破棄してしまうのです.
 つまり,実際には少しずつでももっと精度を上げていくことができるのに,精度を評価する道しるべが荒いために,そこで立ち止まるしかなくなってしまうのです.

 ここで登場するのが交差エントロピー誤差などの損失関数です.これらの損失関数は連続です.
 連続ということは,パラメータ調整によるわずかな向上も勘定に入れることができるということです.
 さっき0.98424...だった値が0.98512...になっていれば,それは今回の調整がうまくいったということを表しているので,それを元に次なる調整に進むことができます.

*交差エントロピー誤差とは結局のところどうやって生まれた何なのかとか,なぜそれを用いるのかとかについては詳しくわかっていないので,別の機会に調べます.ごめんなさい.


3.学習率

 勾配法では,勾配,すなわち微分の情報を用いて進むべき方向を決定します.
 この場合,損失関数を最小化するのが目的なので,道しるべの例になぞらえれば,高低差のある地形を探索してとにかく窪地=一番低い場所を探して進んでいくのです.
 その進むべき方向の決定は,一歩ごとに行われます.すなわち,一歩分進んだら周りを見渡して一番低い場所に続きそうな方向に足を向け,また一歩進んで周りを見渡してということを繰り返すわけです.

 このときの一歩の大きさにあたるのが学習率です.
 容易に想像できることですが,一歩の幅があまりに小さければ,遅々として進まず,東京から大阪を目指す蟻のように,いつまでたっても目的地に辿りつけなさそうです.逆に一歩の幅があまりに大きければ,巨人のように,目的地を飛び越えてしまうことになって,これまたうまくいかなさそうです.
 
 というわけで,この学習率の大きさを適切に定めることも重要な仕事となってきます.
 (この学習率のような,パラメータを決める学習のためのパラメータのことをハイパーパラメータというようです)
 じゃあ学習率を決めるのにもうまい方法があるのかと勢い込んで読んだのですが,どうも人の手によってあれこれ試しながら探すしかないとのことです.
 ちょっとがっくりきましたが,ざっと調べてみたところ,よくわからないながらも,これを自動的に決める学習方法もあるようです.しかし何せ本元のニューラルネットワークについてもよくわかっていない身で手を出すのはあまりにもアレなので今回はスルーしました.


4.まとめ

 学問的にあっているんだか間違っているんだか自信がありませんが,少なくとも将来自分が見返したときに役立つ資料とはなりました.
 ひとまず満足です.

 問題点があるとすれば,これを書いたり夜ご飯の二郎系ラーメンを食べに行ったりしていてなおニューラルネットワークによる学習が終わらないということです.Rのパッケージで試した時から線形回帰に比べ大幅に遅いことには気づいていましたが,まさかこれほどとは.どうなることやら.

*追記:あまりに遅いので中身のぞいてみたらエラー吐いてました.こういうのに気づくのも経験ですかね……

 それでは.

Pythonの改行と手書き数字

1.はじめに

今回も途中で詰まった部分を書き散らかしていきます.

2.Pythonの改行

なんでかよくわかんないけど,Pythonでは1行に書いていい文字数が決まっていて,それは79文字であるらしいです.
これを超えてしまうと,E501 line too longというエラーが出たり出なかったりします.
これをさけるために文章の途中で改行を行うのだが,サンプルコードを見たときに最初改行してあることに気づかず混乱したのでメモしておきます.

具体的には

(var_1, var_2), (var_3, var_4) = function(condition1 = hogehoge, condition2 = fugafuga, condition3 = piyopiyo)

みたいに一度に宣言したり,引数指定が長すぎたりするとあっさり79文字を超えてしまうため,適当なところでバックスラッシュで区切ってあげると良いようです.
こんな感じ

(var_1, var_2), (var_3, var_4) = \
    function(condition1 = hogehoge, condition2 = fugafuga, condition3 = piyopiyo)

3.手書き文字認識

MNIST.松尾先生の「人工知能は人間を超えるか」にも出てきていた手書き文字認識.
まったくの門外漢なのでこういう専門の事例でよく使われているデータを扱えるだけで気分が盛り上がる.
(とはいえRのirisとかは使ったことなかったなあ)

4.勾配法と学習率

学習効率を高め,かつある程度正しい解にたどり着くためには学習率というハイパーパラメーターが大切であるとのことでした.
この学習率の決め方については,確か前に覗いたGPS論文の中で,grid adjacentという名前で紹介されていた気がする.
後で確認しないと.

Pythonで実行ファイルから相対パスでデータを取得する

1.はじめに

Pythonさわってて相対パスの取得まわりで手間取ったのでメモ.
環境はMacOS,Python3.6.0です.

2.相対パスの取得のしかた

手順としては,
1.実行ファイルの名前を取得する
2.相対パスとくっつける
3.正規化する

3.サンプルスクリプト

import os
name = os.path.dirname(os.path.abspath(__name__))
path = os.path.join(name, '../dataset/data')
data = os.path.normpath(path)

ちなみに,hoge/scripts/sample.pyからhoge/dataset/dataを引っ張るためです.