Haskellで蟻本を解く1
最近Haskellを勉強していて、練習がてら、蟻本こと「プログラミングコンテストチャレンジブック」の問題をできるだけ解いてみようと思います。
最初は練習問題の
n本の棒から周長が最大となる三角形を作る
という問題。これをO(nlogn)で解くにはどうしたらいいかというものです。(しらみつぶしでO(n^3)の方法については本に書いてあります)
qsort :: [Int] -> [Int] qsort [] = [] qsort (x:xs) = qsort [x' | x' <- xs, x'>x] ++ [x] ++ qsort [x' | x' <- xs, x'<=x] maxTriangleSorted :: [Int] -> [Int] maxTriangleSorted (x:xs) | length xs < 2 = [] | x < sum xss = [x] ++ xss | otherwise = maxTriangleSorted xs where xss = take 2 xs maxTriangle :: [Int] -> [Int] maxTriangle = maxTriangleSorted.qsort
maxTriangle [15,2,7,3,8,6] [8,7,6]
qsortはO(nlogn)で比較は高々n回なので計算時間はO(nlogn)です。
あまりHaskell的にはよろしくないコードだと思いますが…。
- 作者: 秋葉拓哉,岩田陽一,北川宜稔
- 出版社/メーカー: 毎日コミュニケーションズ
- 発売日: 2010/09/11
- メディア: 単行本(ソフトカバー)
- 購入: 52人 クリック: 1,538回
- この商品を含むブログ (83件) を見る
homebrewからopencvの最新版(2.3.1)をインストールする
そのうち必要なくなるとは思いますが、現在のhomebrewのデフォルトではopencvのformulaは2.2をインストールするように記述されているため、最新版を入れるためには少々作業が必要でした。
2.3以降を入れる大きなメリットとして、新しいpythonバインディングcv2が使えるようになるというものがあります。pyhtonからopencvを使いたい場合は、なるべく2.3以上にしておきたいところだと思います。
インストールのための作業
インストール作業は以下のようにしました。
opencvのFormulaを書き換える
https://github.com/wjwwood/homebrew/commit/9317f433927a3a7ef9dc40d147830c366ec9b0a7
この通りにopencv.rbを修正するか、あるいはgithubにcommitされた最新版を落としてきます。
(brew updateで反映されれば良いのですが、僕の環境では反映されませんでした)
ローカル環境のFormulaはおそらく/usr/local/Library/Formula以下にあります。
monoが悪さをするので(インストールされている場合は)アンインストールする
この状態でいきなりbrew upgradeをすると失敗することがあると思います。試行錯誤した結果、monoがインストールされていることが原因であるらしいことがわかりました。とりあえずアンインストールして続行します(opencvのインストール後にmonoを入れ直せばおそらく大丈夫だと思います)。
monoのアンインストール方法は、
http://www.mono-project.com/Mono:OSX#Uninstalling_Mono_on_Mac_OS_X
にあるUninstalling Mono on Mac OS Xに従ってシェルスクリプトを実行すればOKです。
pythonでパーティクルフィルタによる物体トラッキング
Tokyo.SciPyという勉強会に参加するので、pythonの練習に最近少し勉強しているコンピュータビジョン関連のアルゴリズムを実装してみました。
パーティクルフィルタによる物体トラッキングです。OpenCVを用いてカメラからキャプチャした画像の色情報を使い、追跡したい物体にパーティクルが集まるように尤度関数を設定しています。よく例題として出てくるものです。一応、システムモデル(パーティクルの時間発展を記述)、尤度モデル(パーティクルの重みを記述)を表す関数オブジェクトを入れ替えることで色々な問題に対応できるようにはなっています。練習ということであまりこだわらずにアドホックに書いてしまったところもあり、特にOpenCV周りはいい加減です…。
# -*- coding:utf-8 -*- import random import numpy as np from scipy import stats import cv #キャプチャを管理 class Image: def __init__(self): self.capture = cv.CreateCameraCapture(0) self.image = cv.QueryFrame(self.capture) cv.ShowImage("Capture",self.image) self.size = (self.image.width,self.image.height) def create(self): self.image = cv.QueryFrame(self.capture) def getCol(self,sv): x = sv[0] y = sv[1] #パーティクルが画面外にはみ出した場合は黒として返す if((x<0 or x>self.size[0]) or (y<0 or y>self.size[1])): return((0,0,0,0)) else: #OpenCVでは座標指定が(y,x)の順 return(cv.Get2D(self.image,int(sv[1]),int(sv[0]))) #システムモデルを管理 class SystemModel: def __init__(self,model): self.model = model def generate(self,sv,w): return(self.model(sv,w)) #尤度モデルを管理 class Likelihood: def __init__(self,model): self.model = model def generate(self,sv,mv): return(self.model(sv,mv)) def normalization(self,svs,mv): return(sum([self.generate(sv,mv) for sv in svs])) #システムモデルの関数オブジェクト def model_s(sv,w): #等速直線運動モデル #状態ベクトルは(x,y,vx,vy)を仮定 F = np.matrix([[1,0,1,0], [0,1,0,1], [0,0,1,0], [0,0,0,1]]) return(np.array(np.dot(F,sv))[0]+w) #尤度モデルの関数オブジェクト def model_l(sv,mv): #mv(cvイメージ)からsvの座標値(第1,2成分)に #対応する点の色情報を取得する mv_col = img.getCol(sv) mv_col = mv_col[0:3] target_col = (43,22,55) #追跡したい物体の色(BGR) #尤度は色情報と指定する色の差に対するガウスモデル delta = np.array(mv_col)-np.array(target_col) dist_sqr = sum(delta*delta) sigma = 10000.0 gauss = np.exp(-0.5*dist_sqr/(sigma*sigma)) / (np.sqrt(2*np.pi)*sigma) return(gauss) #パーティクルのリサンプリング def resampling(svs,weights): N = len(svs) #重みの大きい順にパーティクルをソート sorted_particle = sorted([list(x) for x in zip(svs,weights)],key=lambda x:x[1],reverse=True) #重みに従ってパーティクルをリサンプリング resampled_particle = [] while(len(resampled_particle)<N): for sp in sorted_particle: resampled_particle += [sp[0]]*(sp[1]*N) resampled_particle = resampled_particle[0:N] return(resampled_particle) #1ステップ分のフィルタ def filtration(svs,mv,systemModel,likelihood): #システムモデルに用いる乱数を生成 dim = len(svs[1]) N = len(svs) sigma = 5.0 rnorm = stats.norm.rvs(0,sigma,size=N*dim) ranges = zip([N*i for i in range(dim)],[N*i for i in (range(dim+1)[1:])]) ws = np.array([rnorm[p:q] for p,q in ranges]) ws = ws.transpose() #予測サンプルを生成 svs_predict = [systemModel.generate(sv,w) for sv,w in zip(svs,ws)] #尤度重みを計算 normalization_factor = likelihood.normalization(svs_predict,mv) likelihood_weights = [likelihood.generate(sv,mv)/normalization_factor for sv in svs_predict] #重みによってリサンプリング svs_resampled = resampling(svs_predict,likelihood_weights) return(svs_resampled) #初期パーティクルを生成する。モデルが(x,y,vx,vy)の4次元モデルであることを仮定 def initStateVectors(imageSize,sampleSize): xs = [random.uniform(0,imageSize[0]) for i in range(sampleSize)] ys = [random.uniform(0,imageSize[1]) for i in range(sampleSize)] vxs = [random.uniform(0,5) for i in range(sampleSize)] vys = [random.uniform(0,5) for i in range(sampleSize)] return([list(s) for s in zip(xs,ys,vxs,vys)]) #パーティクル付きの画像を表示する def showImage(svs,img): #パーティクル描画用のコピー dst = cv.CloneImage(img.image) #パーティクルを書き込み for sv in svs: #パーティクル位置をintに変換 cv.Circle(dst,(int(sv[0]),int(sv[1])),3,cv.CV_RGB(0,0,255)) #表示 cv.ShowImage("Capture",dst) if(__name__=="__main__"): #イメージソースを指定 img = Image() #パーティクル数を指定 sampleSize = 100 #モデルオブジェクトを生成 systemModel = SystemModel(model_s) likelihood = Likelihood(model_l) #初期パーティクルを生成 svs = initStateVectors(img.size,sampleSize) while(True): #描画 showImage(svs,img) #観測 img.create() #フィルタ svs = filtration(svs,img,systemModel,likelihood)
せっかくなので第一回Tokyo.ScipyでこれについてLTさせてもらいました。
そのときの資料は以下。
多変量対数正規分布の計算 (basic statistics of multivariate lognormal distribution)
多変量正規分布については色々な解説がありますが、多変量対数正規分布についての結果が知りたいと思ったら意外と(英語でも)解説が見つからなかったので、仕方なく自分で計算しました。メモ代わりにまとめておきます。
Basic statistics(means and (co)variances) of multivariate lognormal distribution are calculated below. Although explanations are in Japanese, you might see mathematical expressions and get information you want. Comments are welcome if you have any questions.
多変量対数正規分布の確率密度関数
多変量対数正規分布の確率密度関数は
という形になります。ベクトルや行列は多変量正規分布の平均ベクトル、分散共分散行列に対応しますが、多変量対数正規分布の平均や分散共分散にはなっていないことに注意が必要です。これらのパラメータを用いて、多変量対数正規分布の平均ベクトルと分散共分散行列を求めることが目的になります。
変数変換
1変数の対数正規分布は、と変数変換をするとについての標準正規分布になります。これに対応する変数変換のを多変量対数正規分布に対しても行うことで、各種統計量の計算を行います。
今、の固有値と固有ベクトルが分かったとします。このときは
およびは
と書けます(証明略)。ここで、はk番目の固有ベクトルの第i成分を表します。これを利用して、変数変換を
とします。多変量の積分の変数変換にはヤコビアンを計算する必要があり、
となります。行列式の性質を使って計算を進めると
となります()。Uは直交行列なので、行列式は1となり、結局ヤコビアンは
となります。yからtへ変換する際には逆変換となり、その場合、ヤコビアンは逆数となります。
平均
の平均値は
となります。先ほどの変数変換を行うと、確率密度関数の部分は無相関な標準正規分布となり、は
となります。ここでは直交行列であり、逆行列がであるという性質を使っています。従って、
となります。積分を計算するためには各について平方完成すればよく、eの肩に乗っている項のうち関連するもののみを見ると、
とすることができます。の項はで積分すると規格化定数とキャンセルして消えるので、結局残るのは
になります。ここで、
という式を思い出すと、
であることが分かります。結局、多変量対数正規分布の平均値は
と計算できることが分かりました。
分散共分散
平均の次は分散(共分散)を計算します。
共分散の計算はやや面倒ですが、考え方は平均のときと同じものを使うことができます。
平均値は既に求まっているのでそれを代入し、平均の計算と同様に標準正規分布への変数変換を行うと
ただし、ここでスペース節約のため、アインシュタインの縮約記法(単一の項内で重複する添字については和を取る)を用いています。すなわち、eの肩に載っている項のkについては和を取るという意味になります。
これらの項のうち、積分後に生き残る(ゼロにならない)のは(eの肩に)tの1乗が含まれる項です。また生き残る項についての積分の結果は、平均の計算のときと同様に、元々の確率密度関数から来る項と合わせた平方完成による「おつり」の項です。この計算を地道に行うと
となり、平均値のときと同様、、となるので、結局
となります。
Tokyo.R #14で発表しました
気がついたら結構参加回数を重ねていたTokyo.Rにて、プレッシャーに耐え切れなくなり発表することになりました。
isomapという多様体学習の手法が使いたくて実装したので、それの周りについての発表としました。先日の多次元尺度構成法もこの手法の一部として使われています。
ただ実装についてはまだ少々バグがあるっぽいのと、isomapだけならveganというパッケージで使えることがわかったため、公開はもう少しブラッシュアップしてからにしようかなと思っています。
少しパンチが弱いなあと思っていますが、思ったより好評だったようでよかったです。
ちなみに、slideshareには別の資料も少し置いてあって、統計学入門は「統計とか興味あるけどよくわからないなあ」という人におすすめです。
多次元尺度構成法イントロダクション
ちょっと多次元尺度構成法について理解する必要があったので簡単なまとめです。
多次元尺度構成法とは、多数の多次元点間の距離データのみが与えられたときに、その距離を再現するような空間(座標系)を逆算する手法です。(多次元)座標値が与えられれば距離は自由に計算できますが、距離だけが与えられた場合に座標値を計算するのは直感的には中々できません。
今、n個の点がありそれぞれの点間の個の距離がデータとして与えられたとします(はゼロです)。空間の次元をqとし、未知のn個の座標ベクトルを、それらを縦に並べた行列を
とします(tは転地記号)。また、点間の距離はユークリッド距離
とします。
ここで
という行列を考えます。もし何らかの形で距離データからBを求めることができれば、Bを対角化し固有値の平方根をとることで、を求めることができます。の成分は
であり、距離行列の成分と比べて次のような関係にあることがわかります。
(少々ややこしいですが、ここではi,jについては和を取りません。左辺にも現れるもので、ダミー変数ではないとわかります。)
この関係式によって、距離行列(手元にあるデータ)とを結びつけることができます。
ところが、実はこの関係式だけでは距離行列からを(すなわち座標値を)決めることができません。考えてみれば当然で、座標軸は原点をずらしたり回転したりしても距離は変わりません。逆に、距離だけがわかっている場合は原点や回転の自由度があるため座標軸を一意に決めることができないのです。
回転については直交変換の自由度が残ることになりますが、原点の位置を決めればまずは一つの座標系を書き下すことができるようになります。普通、原点は計算したデータ座標値の平均がゼロとなるようにとられます。
この関係式を(a)式に適用すると、もまたiまたはjの一方について和をとるとゼロとなることがわかります。これにより、(b)についてiで和をとると
となります。ここではの対角成分の和(トレース)で、nはの次元です。同様に、jについての和、i,j両方についての和をとると
という関係式も得られ、これら3つをについて解くことで、(b)式よりをを用いて書き下すことができます。
これでがわかったので、ここからを解いてを求めます。そのためにはの固有値・固有ベクトルを求めればよいというのが線形代数の常套手段です。実際
となる固有値、固有ベクトルが求まったとすると、
と書くことができ、これは、固有ベクトルを並べた行列
と、固有値を対角成分に並べた行列を用いて、
と書けます。ただしです。
従って、求めるは
であることがわかります。
ここで注意が必要です。固有値の平方根を要素とする行列を作りましたが、固有値は一般に負の数や複素数に成り得るので、この議論が成り立つためには固有値は0または正でなければなりません。これは行列が半正定値でなければならないということを意味します。実際には必ずしも半正定値ではなく、また欲しいのはなるべくズレの少ない低次元表現ですので、固有値を降順にソートし、大きいものからp個持ってきた行列でを近似します。これによってp次元で表現された座標値が求まります。
長々と解説しましたが、Rでこれを実行するのは簡単です。statsパッケージにcmdscaleという関数があり、デフォルトでは2次元の多次元尺度構成法を行ってくれます。
> library(stats) > iris.dist <- dist(iris[,-5]) #距離行列を作成 > iris.cmd <- cmdscale(iris.dist,k=2) #多次元尺度構成法 > plot(iris.cmd,col=iris$Species) #結果を色つきでプロット
以下のようなグラフが得られます。
irisは元々4次元のデータですが、多次元尺度構成法によって得られた2次元表現でも、そこそこきちんと種別を反映してくれていることがわかると思います。irisは元データが座標値に対応するため、低次元表現を求めるという意味では主成分分析と同等ですが、データによってはそもそも距離データしか得られないということもあり、そういう場合には得にデータの可視化に役立ちます。
今回紹介した多次元尺度構成法は古典的多次元尺度構成法と呼ばれるもので、実際には距離がユークリッドでない場合や非線形な場合などを主眼に置いた様々な改良手法が存在します。いずれその辺りも書くかもしれません。
参考文献
- 作者: B.エヴェリット,石田基広
- 出版社/メーカー: シュプリンガー・ジャパン株式会社
- 発売日: 2007/06/28
- メディア: 単行本
- クリック: 40回
- この商品を含むブログ (9件) を見る
Rcppを使うときのメモ
Rcppを使おうとして苦戦したのでメモ
Rcppのインストール
R上で
install.packages("Rcpp")
すればOK
C++側から読み込むヘッダファイル(Rcpp.h)の設定
C:\Program Files\R\R-2.13.0\include
などにあるので、パスを張るなりg++等のインクルードディレクトリに追加するなりする必要がある。
コンパイル環境の整備
RからGSL(WinXP用)
この辺に書いてあるようなMinGW,MSYS,(Perl?)のインストールとパス設定が必要(GSLは必須ではない)。
C++ソースコードのコンパイル
R用関数を書いたソースをコンパイルする際、色々なサイトに書いてあるのは
R CMD SHLIB hogehoge.cpp
というコマンド。これは単純にg++のコマンドをラップしたものを実行するよう。ところが、Makevarsファイルで
PKG_CPPFLAGS = -I"C:\Program Files\hoge"
とか書いても、ダブルクォーテーションが勝手に省略されてしまうよう。この場合は仕方が無いのでR CMD SHLIB hogehoge.cppでコンソールに出たコマンドを適宜修正して直接打ち込むことでコンパイルできる。
ググって出てくる内容にこのあたりを追加すれば、大体使えるのではないかと思います。