Witten-Bell smoothingのおぼえ書き
witten-bell smoothingを実装することがあったのだが、「えっ、何するんだっけ?」となってしまったので、忘れないうちに書いておく。
そもそもスムージングとは?
確率的言語モデルを使うときに役に立つ便利道具。
もっともベーシックな言語モデルは学習コーパスから、モデルを構築する。
で、N-gram系列の確率を計算して、実際に利用する。
でも、実際は、実際に利用するN-gram系列が学習コーパスに出現しないことは、普通にありえる。
じゃあ、学習コーパスにほとんど出現しないN-gram系列の確率はどうすんの?たまたま学習コーパスに出現しなかっただけじゃないの?
「それは違うよね。学習コーパスに出現しなかっただけで、真の確率ではないよね」という思想で、学習コーパスにほとんど出現しなかったN-gram系列にも確率を割り当てる作業のことをスムージングという。
例えば(bi-gramの例)
Tokyo city is big city in Japan
と Nara city is big city in Japan
というテスト文を考えてみる。
学習コーパスが東京に偏ったコーパスだったとしたら、学習コーパスの中にTokyo city
はよく出現するが、Nara city
はそんなに出現しない。
でも、確率は学習コーパスに依存するので、このままだとNara city
が低確率になってしまう。(もしかしたら0になってしまうかもしれない。)
で、Nara city
に確率をもうちょい割り当ててやりましょう。
って感じ。
どうやって確率を割り当てるの?
線形補完っていう黒魔術を使う。
黒魔術の正体はただの式なのだが、bi-gramの場合、式は
P(w_i | w_{i-1}) = lambda_2 * P-ML(w_i | w_{i-1}) + lambda_1 * P(w_i)
[1]
ただし、
P(w_i) = lambda_1 * P-ML(w_i) + (1 - lambda_1) * (1 / N)
P-ML(w_i | w_{i-1})
が学習コーパスでのbi-gram確率
P-ML(w_i)
が学習コーパスでのunigram確率
N
がテストコーパスの総語数
で、lambda_1
とlambda_2
は何?っていう話になるのだが、このパラメタを調整できるのが、線形補完のミソ。
で、lambdaを推定するために、witten-bell smoothingが出現する。
つまり、witen-bell smoothingはlambdaを計算するアレ
witten-bell smoothingの基本的な考え方は、「後に続く単語の種類数も考えてやろうよ」という考え方。で、種類数が大きほど、lambdaは低くなるはずだよね。と考える。
式は
lambda = 1 - (unique_after(w_i) / (unique_after(w_i) + freq(w_i))
[2]
※ 1 - lambda = unique_after(w_i) / (unique_after(w_i) + freq(w_i))
と紹介しているスライドもあるけど、要は同じ
ただし、
unique_after(w_i)
はw_i
の後(+1インデックス)に出現する単語種類数を返す関数
freq(w_i)
はw_i
が出現する回数を返す関数
天下りになるが、ここにめっちゃわかりやすいpdfの資料があったので、この例に従う。
split
とconstant
は両方ともEuroparlコーパスで993回出現する。
でも後に続く単語も考えてみると,split
の後(+1インデックス)には9種類の単語しかなかった。対して、constant
の後には268種類の単語があった。
後につづく(+1インデックス)に出現する種類が多い方ほど、lambdaも小さくすべきだろう。(lambdaを小さくすると、確率が低くなるので)
witten-bell smoothingの式では、後につづく単語種類数が増えると、分子が大きくなって、1 - (unique_after(w_i) / (unique_after(w_i) + freq(w_i))
の数値が小さくなる仕組みになっている。
で、計算されたlambdaを線形補完式[1]に代入して、bi-gramの確率を計算する。
まとめ
これで、黒づくめの組織に追い詰められたときも「へっ・・・バーロォ・・・言語モデルはスムージングされてるんだよ・・・」ってセリフは吐けると思います。
movable typeのmarkdownでコード表記する
やりたいこと
movable typeで記事を書くときにmarkdownを使うが、コードを表記したい。
でも、毎行4つのスペース挿入はかったるいし、複数行コードを記載する表記(バッククオート3つ)は機能しない。
解決策
<pre><code>
タグを使用する。
こんな感じ。
``
print('hoge')
`
`
よく見たら、ちゃんとここに書いてあった。
メソッドパラメータの型指定でHigher Kinded Typesを使う
やりたいこと
メソッドのパラメタで、Mapを使いたい。
で、Mapのキー(または値)の型を明示的にしておきたくない。
いくつかの種類の型をキー(または値に持てるようにしておきたい)
解決法
Higher Kinded Typesを使う。
日本語にすると、「高階型変数」というらしい。。。(絶対に、一発で変換できない漢字だ)
def func(map_arg: Map[String,Float]): Unit = println(Map.keys())
val map_input = Map[String, Int]
func(map_input)
とかすると、「定義されたパラメタと入力のパラメタが違うで」と怒られる。
パラメタを抽象化(?)するには
def func(map_arg: Map[String,_]): Unit = println(Map.keys())
val map_input = Map[String, Int]
func(map_input)
とすると通った。
こういう風に(どういう風なんだ。。)パラメタを抽象化(?)してしまうことをHigher Kinded Typesと呼ぶのだとか。
Javaでも同じ表記でいけるらしい。
Javaをほとんど書いたことないので、知らなかった。。。
男のための機械学習〜RBMでA◯女優さんの共通特徴量を得よう〜
いま、巷で話題(3年くらい前からだいぶ話題だけど)のDeep learningをア◯でも使えるpylearn2
を使って見る。
Deep learningとは?
一言で言うと「教師なしのニューラルネットをいっぱいつなげて多層ネットワーク化したもの。」
いままでは
前処理職人の丹精を込めた特徴量作り → 分類器(SVMとか、ロジスティック回帰とか、なんでもいいので関数)の学習
だったのが、
黒魔術で特徴量作り → 分類器(SVMとか、ロジスティック回帰とか、なんでもいいので関数)の学習
にできる。
黒魔術をもうちょっと紐解く
「黒魔術」って言ってるところでやっていることは「ベストな写像関数の学習」
もう少し、言葉を厳密に表現すると、「入力/ノイズ付き出力の変換をうまく表現できる関数の学習」
この関数はできるだけ良い[1]条件で別の空間に写像する。
なので、この関数が学習できると、特徴量空間に射影する関数を学習できたことになる。
[1] 「何が、どういいのか?」はやはり黒魔術である
え、そんなのわかんねえし、という人向け
「そんなのわかんねえよksが」っていう人は「画像をいっぱい入力したら、似たところを自動でとってきてくれる便利なツール」って認識しておけばいいんじゃないっすかね(鼻くそほじほじ)
お手軽Deep learningをやってみよう
口でいうのは簡単だが、それをコードを落とすとなると難しい。
しかし、No problemだ。どっかモントリオール大学の優秀な学生達が、Deep learning用汎化ライブラリ(Theano)を作成してくれた。
しかし、これでもやや敷居が高い。無能な私にはチュートリアルで終わるのが精一杯だった。
という状況の中、さらに便利なツールが登場した!それがPylearn2である。
Theanoのラッパーとして、アホでも使える程度の簡単さにしてくれた。まだ、開発版ではあるが、十分に遊べるレベルになっている。
今回は、Pylearn2のテスト使用ということで、ちょっと遊んでみた。
顔の特徴量抽出をしてみよう
Deep Learningは元々は画像認識分野から出てきた(Hintonという偉いおっさんが初めて使った)。
その時は、画像認識(画像を入力して、写っている物の名前を当てるタスク)で使われていた。
そこで、脅威の性能を出してしまったばかりに、例によってGoogleが興味をもって、ここにGoogleネコが誕生した
この時のGoogleのアイディアは「いっぱいネコ画像を入力に与えたら、ネコの顔だけ抽出してくれる関数を学習してくれんだろ。バーロ」みたいな感じだった。
ここに歪んだ欲望と男のロマンをちょっとだけ加えると次のようになる。
「いっぱい好みの女優さんの顔画像を与えたら、自分の好みの顔を学習してくれんだろ。バーロ」
と、いうことでやってみた。きっとHintonも喜んでくれるだろう。
pylearn2で顔パーツを学習してみよう
ごくごく簡単にpylearn2を使うまでの手順を書くと、こうなる
- (前提) python, git, pipを使える状態。numpyとscipyがインストールされている。
- 1 theanoをインストールする
- 2 pylearn2をインストールする。(とはいってもgit cloneをする)
python, git, pip, numpy, scipyの使用準備の説明は今回、省く。
theanoのインストール
pip install theano
でおしまいなはず(設定がうまくいっていれば)
pylearn2のインストール
インストールと準備の段階では以下のサイトも参考になる
必要なライブラリ
- PIL
- PyYAML
- IPython
- Cython
全部、pip install
コマンドで楽勝である。
ただし、PILはpip install PIL
ではダメでpip install pillow
と打たないとダメらしい。参考
pylearn2のインストール
次の2つのコマンドを実行すれば、うまくいく(はず)
git clone git://github.com/lisa-lab/pylearn2.git
python setup.py develop
pylearn2のテスト
pylearn2の使い方は、簡単にまとめると、
このサイトの下の方に、CIFAR10とMNISTという画像データセットでのサンプル実行例がある。
- http://deeplearning.jp/?p=196
- http://shower.human.waseda.ac.jp/~asaitaku/toolsManual/_build/html/python/pylearn2/main.html
pylearn2で、女優さんの顔から特徴量を抽出してみよう
画像の学習のために、pylearn2を更に簡単に使えるコードが用意されてる。ここ
このコードがあれば、画像から色情報csvファイルへの変換、レシピyamlを使った学習までとっても簡単。
やってみること
- DMM女優名鑑から好み(主観)の女優さんの画像を集めてくる。
- 女優さんの画像を入力にして、Deep learningでモデルを構築してみる。
- 共通する特徴量を抽出する
顔画像の収集
入力画像はDMM女優名鑑から集めてくる。顔画像は155*155で保存されているので、好都合。
wgetコマンドで簡単に画像を落とせる。
URLの構成規則はhttp://pics.dmm.co.jp/mono/actjpgs/姓_名.jpg
になっているのと、割りと簡単。
マシンは型落ちMacBookなので、学習量を少なくするため、10枚ぐらいに
集めてきた顔画像はこんな感じ。(あくまで私の好みです)
画像のグレースケール化とcsvへの変換
画像のグレースケール変換は、PILライブラリを使って簡単にできる。
こんなコードを書いた。
#! /usr/bin/python
from PIL import Image
from PIL import ImageOps
import os
import sys
if __name__ == "__main__":
if len(sys.argv)==3:
input_path = sys.argv[1]
output_path = sys.argv[2]
else:
sys.exit("Need 2 args. Input ans Output path")
input_image = Image.open(input_path)
output_image = ImageOps.grayscale(input_image)
output_image.save(output_path)
python img2csv.py グレースケール変換した.jpgのリスト train.csv
を実行する。
クラスの用意とパス設定
クラスファイルをpylearn2で呼び出せるようにしておく。
ここからクラスが記述されたファイルを落としてきて、pylearn2/datasets/
に配置する
環境変数は
export PYLEARN2_DATA_PATH=train.csvとgrbm.yamlが置いてあるパス
export PYLEARN2_VIEWER_COMMAND="opne -Wn"
[2]
[2]はmac専用のコマンド。windowsの場合は・・・・知らね
モデルの学習
今回はグレースケール化して、サイズも同だし、いろいろ考えるのも面倒なので、「制約付きボルツマンマシン(RBM)」で片付ける。(コード中のアルゴリズムではgrbm)
カラー化していれば、grbm_smd、サイズが違えばmultimodalを使った方がいいだろう(って、公式マニュアルに書いてあったお。)
実行するコマンドは、grbm.yamlが置いてある場所で、
train.py grbm.yaml
を実行するだけ。
1イテレーションはこんな画面
学習結果の確認
学習した関数の出力を画像で表示するには
show_weights.py grbm.pkl
を実行する。
結果はこんな感じだった
夜に見たら、おしっこちびりそう・・・・
椎名りくが強すぎワロタ
いくつかの特徴量では、うまく人間共通の顔パーツを学習できている様子。
あくまで考察だけど、椎名りくの画像だけ他と違って、セーラー服で、ラインが入っていたため、外れ値が多かったのでは・・・とかそんなことを思いました。
ちなみに私はブレザー派です
まとめ
Deep learningって楽しいね!
theanoライブラリを使えるようになるまでにがんばったこと
やろうとしたこと
theanoライブラリを使って、deep learningの勉強してみようとした。
しかし、theanoのインストールがなかなかうまくいかず、いろいろ作業をしたので、その時のログを残しておく。
theanoのインストールがうまくいかなかった原因
pip install theano
でインストールを試すと、通信に関するエラー発生する。(エラーコードは忘れた)
原因はopensslのバージョンが古くて、pipコマンドが失敗することだった。
古いopenssl(脆弱性があるとわかったやつ)だと、通信が拒絶されるらしい。
そこで、opensslを新しいバージョンにして、再度、pip installを試す。
手順
1 homebrewの環境を整備して、opensslを最新版にする
やったこと ### 1.1 homebrewの環境整備。macportとfinkの削除。
### 1.2 brew install openssl の実行
### 1.3 以前から入っていたopensslが参照されているので、新しいopensslにリンク張替え。 brew link openssl --force を実行
### 1.4 3がうまくいかなかったので、/usr/local/binにパスを通して、ターミナルの再起動
2 最新版のopensslに対応したpythonのインストールをやりなおす。
brew install python --with-brewed-openssl
brew linkapps
下の書き込みのとおりにコマンドを実行
さらにbrewでインストールしたpipにパスが通るようにする。
PATH=$(brew --prefix)/share/python:$(brew --prefix)/share/python/bin:$(brew --prefix)/share/python/sbin:$PATH
PYTHONPATH=$(brew --prefix)/lib/python2.7/site-packages:$PYTHONPATH
pip install --upgrade distribute
pip install --upgrade pip
この一連の作業はこのBBSにあった。
3 新しくしたpipでpip install theanoを実行する
4 ついでなので、ipythonの再インストールも行う。
古いpipでインストールしたipythonを一度、削除し、pip install ipython を実行。うまくいった。ipythonでnumpyとscipyとtheanoをimportできることを確認すれば、もう使える準備がととのった。
グループ化集計して、上位N件の取得
やりたいこと
データフレームをグループ化して集計(平均)し、その上で、各グループについて上位N件を取得したい。
つまり、やることを分解すると、
- 1 グループ化して数値集計(今回は平均)
- 2 集計した数値で、各グループごとに上位N件取得
1 グループ化して集計
pandasの場合、groupby
メソッドが用意されており、これで解決できる。
dfをデータフレームとした時、
df.groupby([列番号]).集計関数
と記述する。
戻り値は、集計済みのデータフレームである。
2 集計した上で、各グループごとに上位N件
これが頭を悩ませた。
と、いうのも、データフレームのままでやろうとすると、上位N件だけ残す。ということができないからだ。
各グループで、違う項目が上位N件に来るので、表という形式をもっている限り、どうしてもソートはできない。 (各グループごとに、数値の順位を記載した行を用意する。という解決索はあるが、スマートでない気がした。)
そこで、一度、データフレームをバラして、dictにしてから、グループごとにソートする。ということを行なった。
で、面倒なのだが、ソートしてから、またデータフレームにして、出力をした。 (こんな面倒なことやらんでも、jsonで渡せたら最良だったのだが、OUTPUTにexcelで使えるデータを求められていたので、どうしてもcsvで保存する必要があった)
まずmelt
元のデータは行がキー(グループID)、列が値のインデックス。という巨大なデータフレームなので、まずは、これを変形する。
pandas.melt(df, id_vars=['列名'])
でmeltができる
dictにする
さて、これがちょっと面倒くさい。
と、いうのも、一発で簡単にdictに変換できなかったからだ。
そこで、forループを回して、行ごとに処理をすることにした。
my_dict = {}
for x in range(len(df)):
row_info = df.iloc[x]
line_info = row_info.tolist()
c_key = line_info[0] # キーになる要素
currentvalue1 = line_info[1] # 値の要素1
currentvalue2 = line_info[2] # 値の要素2
if c_key in my_dict: my_dict[c_key].append( [currentvalue1, currentvalue2] )
else: my_dict[c_key] = [[currentvalue1, currentvalue2]]
ソートして上位N件取得
上の処理でつくったdictの各キーで、ソートをし、上位N件だけ残したdict(キー:インデックス名、値:数値)を値にして、新しくdictを作ることにした。(キーは同じ)
わざわざ2重のdict構造にするのは、この後の処理でpandasのメソッドを使って、一発で目的の表に変換できるからだ。
top_n_map = {}
for key in df_map:
values_list = df_map[key]
values_list.sort(key=lambda x:(x[1]), reverse = True)
value_map = {}
for item in values_list[0:9]:
value_map[item[0]] = item[1] # インデックス名をキー、数値を値にして、2次元目のdictを保存
top_n_map[key] = value_map
データフレームにもどして、csvに保存
{キー1:{キー2:値} }
という形のdictなら、一発で下の表に変換してくれる。
_____| キー2 | キー2 | .....
キー1 | 値 | 値 | .....
キー1 | 値 | 値 | .....
キー1 | 値 | 値 | .....
これをする命令は
pandas.DataFrame.from_dict(dict)
まとめ
グループ化集計して、上位N件だけ残す処理をした。
が、ふと思ったのは、別にmeltはしなくてもよかったような気がする。
別にmeltしなくともdict {キー:list [ list [インデックス名, 数値] ] }
の形は構築できる。
無駄足を踏んだ気がするが、meltをどこかで使う機会があるかもしれないので、記録に残しておく。