2015年5月31日日曜日

「ソフトウェアエンジニアならば1時間以内に解けなければいけない5つの問題」にPythonで挑んでみた

売られたケンカは買わずにいられない性分の人間です。こんにちは。
ダメ人間の鑑みたいな感じですね。

さてタイトルの通り、遅まきながら微妙に話題になっている感のある以下の問題に挑戦してみました。



Problem1〜4は昼休みに会社で20分くらい、Problem5は帰宅してから40〜50分くらいで終わったので失格かどうかギリギリのラインですね。

ということで、解答したソースコードは以下の通り。
力業ゴリ押し感があるのはご愛嬌

2015年5月27日水曜日

mecab-ipadic-neologd試してみた

ちまちま手を動かしてましたが、どうも心折れると進みが悪くなりますね。おはようございます。

先日の記事(オープンソース版SiriのSirius試してみた)で、質問応答をいろいろ試していたのですが、どうやら日本語対応(というか英語以外の言語への対応を)していない*1ようでした。
てことで、せっかくなので勉強がてらNLPにも手を出してみることにします。
直近の目標は「簡単な質問応答ができる」にしようと思います。

んで、なんにせよ日本語を扱うのだから分かち書きできないとお話にならんのですが、MeCabの辞書としてよく使われるipadicをそのまま使うと、結果が今ひとつな感じになる印象です。(特に口語や新語の取り扱い)
辞書を自前で整備しようとするとどうもコストの調整が容易ではなさそうなので、また心折れそうになってました。

が、神はネットにいました。
MeCab 用の新語辞書 mecab-ipadic-neologd を公開しました [Overlasting::Life]
mecab-ipadic-neologd は、多数のWeb上の言語資源から得た新語を追加することでカスタマイズした MeCab 用のシステム辞書です。
まさに必要としていたものが公開されていたことに感動を覚えます。
ということで今回はこちらのご紹介です。

インストール(ソースインストールのMeCabを添えて)

基本的な手順はgithubに記載のとおりですが、MeCabをソースビルドしてかつ、root権が必要なところへインストールしている場合、neologdのインストールでsudoする際にMeCabへのパスを渡す必要があるので注意が必要*2です。
例えば以下の通り。
$ sudo PATH=$PATH:/usr/local/mecab/mecab-0.996/bin ./bin/install-mecab-ipadic-neologd -n
(/usr/local/mecab/mecab-0.996 にインストールしていた場合)

使ってみる

インストールが済んだら早速使ってみます。
システム辞書として指定するので、-d オプションでインストール先のディレクトリを指定して動かします。
$ echo "備忘録とかそんな感じの" | mecab -d /usr/local/mecab/mecab-0.996/lib/mecab/dic/mecab-ipadic-neologd
備忘録 名詞,一般,*,*,*,*,備忘録,ビボウロク,ビボーロク
とか 助詞,並立助詞,*,*,*,*,とか,トカ,トカ
そんな 連体詞,*,*,*,*,*,そんな,ソンナ,ソンナ
感じ 名詞,一般,*,*,*,*,感じ,カンジ,カンジ
の 助詞,連体化,*,*,*,*,の,ノ,ノ
EOS
ひとまずエラーなく動いているようです。
ただこのままだと、毎回長々とディレクトリ指定を書く必要があり面倒なのでエイリアスを追加しておきます。
.bashrcに以下の記述を追加しておくことで、少しでも楽をしたいと思います。
alias mecab-with-neolog='mecab -d /usr/local/mecab/mecab-0.996/lib/mecab/dic/mecab-ipadic-neologd'
一応ちゃんと設定できているか確認します。
$ mecab-with-neolog -D
filename: /usr/local/mecab/mecab-0.996/lib/mecab/dic/mecab-ipadic-neologd/sys.dic
version: 102
charset: UTF8
type: 0
size: 2068937
left size: 1316
right size: 1316
どうやら大丈夫そうです。
ということで、いろいろ試してみましょう。

手始めに最近購入して積ん読している某書籍のタイトルから。
$ echo "続・わかりやすいパターン認識 教師なし学習入門" | mecab
続 接頭詞,名詞接続,*,*,*,*,続,ゾク,ゾク
・ 記号,一般,*,*,*,*,・,・,・
わかり 動詞,自立,*,*,五段・ラ行,連用形,わかる,ワカリ,ワカリ
やすい 形容詞,非自立,*,*,形容詞・アウオ段,基本形,やすい,ヤスイ,ヤスイ
パターン 名詞,一般,*,*,*,*,パターン,パターン,パターン
認識 名詞,サ変接続,*,*,*,*,認識,ニンシキ,ニンシキ
  記号,空白,*,*,*,*, , , 
教師 名詞,一般,*,*,*,*,教師,キョウシ,キョーシ
なし 形容詞,自立,*,*,形容詞・アウオ段,文語基本形,ない,ナシ,ナシ
学習 名詞,サ変接続,*,*,*,*,学習,ガクシュウ,ガクシュー
入門 名詞,サ変接続,*,*,*,*,入門,ニュウモン,ニューモン

$ echo "続・わかりやすいパターン認識 教師なし学習入門" | mecab-with-neolog 
続 接頭詞,名詞接続,*,*,*,*,続,ゾク,ゾク
・ 記号,一般,*,*,*,*,・,・,・
わかり 動詞,自立,*,*,五段・ラ行,連用形,わかる,ワカリ,ワカリ
やすい 形容詞,非自立,*,*,形容詞・アウオ段,基本形,やすい,ヤスイ,ヤスイ
パターン認識 名詞,固有名詞,一般,*,*,*,パターン認識,パターンニンシキ,パターンニンシキ
  記号,空白,*,*,*,*, , , 
教師 名詞,一般,*,*,*,*,教師,キョウシ,キョーシ
なし 形容詞,自立,*,*,形容詞・アウオ段,文語基本形,ない,ナシ,ナシ
学習 名詞,サ変接続,*,*,*,*,学習,ガクシュウ,ガクシュー
入門 名詞,サ変接続,*,*,*,*,入門,ニュウモン,ニューモン
EOS
「パターン認識」が1語として分割されるようになっています。
「教師なし学習」も1語にしてほしい気もしますが、どこまでつなげるべきかについては詳しくないのでよくわかりません。

続いて某ラノベのタイトル。
 echo "とある魔術の禁書目録" | mecab
とある 連体詞,*,*,*,*,*,とある,トアル,トアル
魔術 名詞,一般,*,*,*,*,魔術,マジュツ,マジュツ
の 助詞,連体化,*,*,*,*,の,ノ,ノ
禁書 名詞,一般,*,*,*,*,禁書,キンショ,キンショ
目録 名詞,一般,*,*,*,*,目録,モクロク,モクロク
EOS

$ echo "とある魔術の禁書目録" | mecab-with-neolog 
とある魔術の禁書目録 名詞,固有名詞,一般,*,*,*,とある魔術の禁書目録,トアルマジュツノインデックス,トアルマジュツノインデックス
EOS
1語にまとまっているのもさることながら、読みまで変わるのは素晴らしいです。

次。
$ echo "リア充やリア充爆発しろとはどういう意味なのでしょうか。" | mecab
リア 名詞,固有名詞,人名,名,*,*,リア,リア,リア
充 名詞,固有名詞,人名,名,*,*,充,タカシ,タカシ
や 助詞,並立助詞,*,*,*,*,や,ヤ,ヤ
リア 名詞,一般,*,*,*,*,リア,リア,リア
充 名詞,固有名詞,人名,名,*,*,充,タカシ,タカシ
爆発 名詞,サ変接続,*,*,*,*,爆発,バクハツ,バクハツ
しろ 動詞,自立,*,*,サ変・スル,命令ro,する,シロ,シロ
と 助詞,格助詞,引用,*,*,*,と,ト,ト
は 助詞,係助詞,*,*,*,*,は,ハ,ワ
どういう 連体詞,*,*,*,*,*,どういう,ドウイウ,ドーユウ
意味 名詞,サ変接続,*,*,*,*,意味,イミ,イミ
な 助動詞,*,*,*,特殊・ダ,体言接続,だ,ナ,ナ
の 名詞,非自立,一般,*,*,*,の,ノ,ノ
でしょ 助動詞,*,*,*,特殊・デス,未然形,です,デショ,デショ
う 助動詞,*,*,*,不変化型,基本形,う,ウ,ウ
か 助詞,副助詞/並立助詞/終助詞,*,*,*,*,か,カ,カ
。 記号,句点,*,*,*,*,。,。,。
EOS

$ echo "リア充やリア充爆発しろとはどういう意味なのでしょうか。" | mecab-with-neolog 
リア充 名詞,固有名詞,一般,*,*,*,リア充,リアジュウ,リアジュー
や 助詞,並立助詞,*,*,*,*,や,ヤ,ヤ
リア充爆発しろ 名詞,固有名詞,一般,*,*,*,リア充爆発しろ,リアジュウバクハツシロ,リアジュウバクハツシロ
と 助詞,格助詞,引用,*,*,*,と,ト,ト
は 助詞,係助詞,*,*,*,*,は,ハ,ワ
どういう 連体詞,*,*,*,*,*,どういう,ドウイウ,ドーユウ
意味 名詞,サ変接続,*,*,*,*,意味,イミ,イミ
な 助動詞,*,*,*,特殊・ダ,体言接続,だ,ナ,ナ
の 名詞,非自立,一般,*,*,*,の,ノ,ノ
でしょ 助動詞,*,*,*,特殊・デス,未然形,です,デショ,デショ
う 助動詞,*,*,*,不変化型,基本形,う,ウ,ウ
か 助詞,副助詞/並立助詞/終助詞,*,*,*,*,か,カ,カ
。 記号,句点,*,*,*,*,。,。,。
EOS
「リア充」が1語にまとまっているのも、いろいろな用途を感じられてよいですね。
が、「リア充爆発しろ」が1語にまとまり、かつ名詞なのはどういうことでしょう?
と思って調べたら、どうやら少し前に「リア充爆発しろ」というアプリが話題になっていたようなのでその影響かもしれません。

ということで、非常に有用なMeCab用辞書 mecab-ipadic-neologd のご紹介でした。
これで日本語の解析がはかどりますね!!

*1:「システム構成上、自由に入れ替えられるから勝手にやってね!」って話のようですが。。。
*2:ハマったというか、そらそうよね感ある話。

2015年4月11日土曜日

オープンソース版SiriのSirius試してみた

最近はブログのネタに事欠かなくて非常に幸せです。こんばんは。

さてある日twitterを眺めていたらこんなツイートが↓
ファッ?!ってなりますよね?
なりますよね!!!

非常に気になって記事を見てみたのですが、
「Siriusとは、音声認識、画像マッチング、自然言語処理および質疑応答システムなどのIPA(インテリジェントパーソナルアシスタント)の中心的機能を果たすものである」
と書かれています。
どうやらSiriと同等の機能を提供することを目標に開発されているようです。

もともとこのへんの分野が非常に好きな私としては試さずにはいられません。
しかも(なぜか)都合よく、VM2つ3つ起動できるスペックのマシンを発注していたので、早速試してみました。

インストールとか

プロダクトのページは http://sirius.clarity-lab.org/ で、ソースコードはgithubの https://github.com/jhauswald/sirius で公開されています。

インストール自体は書かれている手順通りにやれば基本的には問題ないのですが、いかんせん依存ライブラリが多い(http://sirius.clarity-lab.org/index.html%3Fp=9.html#install のPrerequisites参照)です。
で、たまたま最近Dockerに興味があったので、これ幸いと環境構築に使うことにします。
なお、当然ながら同じこと考えていた人がおり、プルリク投げられていた のでDockerfile作成の参考にしています。(せっかくなのでこちらはUbuntu14.04)

今回使った環境は以下の通りです。
  • ホスト
    • OS
      • Windows7 64bits
    • メモリ
      • 16GB
    • CPU
      • 8コア
  • ゲストOS
    • OS
      • Ubuntu 14.04
    • メモリ割り当て
      • 2GB
    • CPU
      • 1コア

Windows上にVMでUbuntuを起動して、その上でDockerを動かす構成にしています。

VMをWindows上のVirtualBoxで動かすので、Vagrantの力も借ります。
Vagrantfileの書き方やDockerとの連携については、以下のページを参考にしています。ありがとうございます。


本題ではないのでVagrantfileとDockerfileの中身については説明を省きますが、gistに置いて*1ますので気になる方はどうぞ。*2, *3

ということでVagrantfileとDockerfileの準備ができたので、早速VMを起動しコンテナを作成します。
$ cd ${VAGRANT_DIR}
$ vagrant up

たったこれだけ。簡単ですね。
ちなみに手元の環境だと、依存ライブラリのインストールからSirius本体のビルドなどが終わるまで数時間かかっています。(おそらくゲストOSに割り当てているリソースが少ない上に、無線で繋いでいたのでなおさら悪い)

動かしてみる

準備が整ったので早速動かしてみます。
動かし方は公式のドキュメントに記載のとおりです。

Automatic Speech Recognition (ASR) [音声認識]

サーバプロセス起動

$ cd /path/to/sirius/sirius-application/run-scripts/
$ ./start-asr-server.sh pocketsphinx &
注意:start-<service>-server.shはフォアグラウンドで動きます。

テストクエリ送信

$ ./sirius-asr-test.sh ../inputs/questions/what.is.the.speed.of.light.wav

結果

Your audio file is:
../inputs/questions/what.is.the.speed.of.light.wav
Sending request to server localhost:8081/ ...
transcript []: what is the speed of light
 what is the speed of light
***********************************************
正しく認識してくれているようです。

Image Matching (IMM) [画像認識]

サーバプロセス起動

$ cd /path/to/sirius/sirius-application/run-scripts/
$ ./start-imm-server.sh &

テストクエリ送信

$ ./sirius-imm-test.sh ../image-matching/matching/landmarks/query/query.jpg

結果

(1) Your image file is:
../image-matching/matching/landmarks/query/query.jpg
img: tower-pisa.jpg data: tower pisa
Image data: tower pisa
画像はピサの斜塔なので正解です。

Question-Answering System (QA) [質問応答]

サーバプロセス起動

$ cd /path/to/sirius/sirius-application/run-scripts/
$ ./start-qa-server.sh &

テストクエリ送信

$ ./sirius-qa-test.sh "what is the speed of light"
注意:質問文はなんでもよい様子

結果

(1) Your query text is:
what is the speed of light
(2) Sending request to server...
Query str: query=what%20is%20the%20speed%20of%20light

+++++ Analyzing question (2015-04-11 08:53:20) +++++
Normalization: what be the speed of light

Answer types:
NErate->NEspeed

Interpretations:
Property: SPEED
Target: light

Predicates:
-

+++++ Generating queries (2015-04-11 08:53:22) +++++
Query strings:
speed light
("speed of light" OR "light speed")
"light" speed light
"the speed of light is"
"is the speed of light"

+++++ Searching (2015-04-11 08:53:22) +++++

+++++ Selecting Answers (2015-04-11 08:53:34) +++++
Filter "AnswerTypeFilter" started, 64 Results (2015-04-11 08:53:34)
Filter "AnswerTypeFilter" finished, 81 Results (2015-04-11 08:53:35)
Filter "AnswerPatternFilter" started, 81 Results (2015-04-11 08:53:35)
Filter "AnswerPatternFilter" finished, 439 Results (2015-04-11 08:53:45)
Filter "PredicateExtractionFilter" started, 439 Results (2015-04-11 08:53:45)
Filter "PredicateExtractionFilter" finished, 439 Results (2015-04-11 08:53:45)
Filter "FactoidsFromPredicatesFilter" started, 439 Results (2015-04-11 08:53:45)
Filter "FactoidsFromPredicatesFilter" finished, 439 Results (2015-04-11 08:53:45)
Filter "TruncationFilter" started, 439 Results (2015-04-11 08:53:45)
Filter "TruncationFilter" finished, 408 Results (2015-04-11 08:53:46)
Filter "StopwordFilter" started, 408 Results (2015-04-11 08:53:46)
Filter "StopwordFilter" finished, 371 Results (2015-04-11 08:53:46)
Filter "QuestionKeywordsFilter" started, 371 Results (2015-04-11 08:53:46)
Filter "QuestionKeywordsFilter" finished, 341 Results (2015-04-11 08:53:46)
Filter "ScoreNormalizationFilter" started, 341 Results (2015-04-11 08:53:46)
Filter "ScoreNormalizationFilter" finished, 341 Results (2015-04-11 08:53:46)
Filter "ScoreCombinationFilter" started, 341 Results (2015-04-11 08:53:46)
Filter "ScoreCombinationFilter" finished, 341 Results (2015-04-11 08:53:46)
Filter "FactoidSubsetFilter" started, 341 Results (2015-04-11 08:53:46)
Filter "FactoidSubsetFilter" finished, 338 Results (2015-04-11 08:53:46)
Filter "DuplicateFilter" started, 338 Results (2015-04-11 08:53:46)
Filter "DuplicateFilter" finished, 172 Results (2015-04-11 08:53:46)
Filter "ScoreSorterFilter" started, 172 Results (2015-04-11 08:53:46)
Filter "ScoreSorterFilter" finished, 172 Results (2015-04-11 08:53:46)
299,792,458 meters per second
***********************************************
光速は約30万km/sなので、正しく応答してくれていそうです。

余談

出身地で動かしてみたくなったので、以下のクエリを投げ込んでみました。
$ ./sirius-qa-test.sh "where is saga?"

結果

(1) Your query text is:
where is saga?
(2) Sending request to server...
Query str: query=where%20is%20saga%3F

+++++ Analyzing question (2015-04-11 08:54:24) +++++
Normalization: where be saga

Answer types:
NElocation

Interpretations:
Property: PLACE
Target: saga

Predicates:
-

+++++ Generating queries (2015-04-11 08:54:24) +++++
Query strings:
saga
saga
"saga" saga
"saga is in"
"saga is at"

+++++ Searching (2015-04-11 08:54:24) +++++

+++++ Selecting Answers (2015-04-11 08:54:27) +++++
Filter "AnswerTypeFilter" started, 24 Results (2015-04-11 08:54:27)
Filter "AnswerTypeFilter" finished, 345 Results (2015-04-11 08:54:30)
Filter "AnswerPatternFilter" started, 345 Results (2015-04-11 08:54:30)
Filter "AnswerPatternFilter" finished, 6183 Results (2015-04-11 08:54:45)
Filter "PredicateExtractionFilter" started, 6183 Results (2015-04-11 08:54:45)
Filter "PredicateExtractionFilter" finished, 6183 Results (2015-04-11 08:54:45)
Filter "FactoidsFromPredicatesFilter" started, 6183 Results (2015-04-11 08:54:45)
Filter "FactoidsFromPredicatesFilter" finished, 6183 Results (2015-04-11 08:54:45)
Filter "TruncationFilter" started, 6183 Results (2015-04-11 08:54:45)
Filter "TruncationFilter" finished, 4301 Results (2015-04-11 08:54:46)
Filter "StopwordFilter" started, 4301 Results (2015-04-11 08:54:46)
Filter "StopwordFilter" finished, 3440 Results (2015-04-11 08:54:48)
Filter "QuestionKeywordsFilter" started, 3440 Results (2015-04-11 08:54:48)
Filter "QuestionKeywordsFilter" finished, 3174 Results (2015-04-11 08:54:48)
Filter "ScoreNormalizationFilter" started, 3174 Results (2015-04-11 08:54:48)
Filter "ScoreNormalizationFilter" finished, 3174 Results (2015-04-11 08:54:48)
Filter "ScoreCombinationFilter" started, 3174 Results (2015-04-11 08:54:48)
Filter "ScoreCombinationFilter" finished, 3096 Results (2015-04-11 08:54:48)
Filter "FactoidSubsetFilter" started, 3096 Results (2015-04-11 08:54:48)
Filter "FactoidSubsetFilter" finished, 3077 Results (2015-04-11 08:54:48)
Filter "DuplicateFilter" started, 3077 Results (2015-04-11 08:54:48)
Filter "DuplicateFilter" finished, 876 Results (2015-04-11 08:54:57)
Filter "ScoreSorterFilter" started, 876 Results (2015-04-11 08:54:57)
Filter "ScoreSorterFilter" finished, 876 Results (2015-04-11 08:54:57)
Icelandic
***********************************************
Icelandic……?
佐賀はアイスランドだった……?

Combining Services [システム連携]

(この例はASR→QAという連携のようです)

サーバプロセス起動

$ cd /path/to/sirius/sirius-application/run-scripts/
$ ./start-asr-server.sh pocketsphinx &
$ ./start-qa-server.sh &

テストクエリ送信

./sirius-asr-qa-test.sh ../inputs/real/what.is.the.capital.of.italy.wav

結果

Your voice search (text) is:
../inputs/real/what.is.the.capital.of.italy.wav
Sending request to ASR server...
transcript []: what is the capital affiliate
Sending request to QA server...
Query str: query=what%20is%20the%20capital%20affiliate

+++++ Analyzing question (2015-04-11 09:02:06) +++++
Normalization: what be the capital affiliate

Answer types:
NElocation

Interpretations:
Property: NAME
Target: capital affiliate
Property: DEFINITION
Target: capital affiliate

Predicates:
-

+++++ Generating queries (2015-04-11 09:02:06) +++++
Query strings:
capital affiliate
"the capital" affiliate
"capital affiliate" capital affiliate
"capital affiliate" capital affiliate
"the capital affiliate is"
"is the capital affiliate"

+++++ Searching (2015-04-11 09:02:06) +++++

+++++ Selecting Answers (2015-04-11 09:02:11) +++++
Filter "AnswerTypeFilter" started, 37 Results (2015-04-11 09:02:11)
Filter "AnswerTypeFilter" finished, 505 Results (2015-04-11 09:02:15)
Filter "AnswerPatternFilter" started, 505 Results (2015-04-11 09:02:15)
Filter "AnswerPatternFilter" finished, 886 Results (2015-04-11 09:02:16)
Filter "PredicateExtractionFilter" started, 886 Results (2015-04-11 09:02:16)
Filter "PredicateExtractionFilter" finished, 886 Results (2015-04-11 09:02:16)
Filter "FactoidsFromPredicatesFilter" started, 886 Results (2015-04-11 09:02:16)
Filter "FactoidsFromPredicatesFilter" finished, 886 Results (2015-04-11 09:02:16)
Filter "TruncationFilter" started, 886 Results (2015-04-11 09:02:16)
Filter "TruncationFilter" finished, 787 Results (2015-04-11 09:02:16)
Filter "StopwordFilter" started, 787 Results (2015-04-11 09:02:16)
Filter "StopwordFilter" finished, 757 Results (2015-04-11 09:02:16)
Filter "QuestionKeywordsFilter" started, 757 Results (2015-04-11 09:02:16)
Filter "QuestionKeywordsFilter" finished, 724 Results (2015-04-11 09:02:16)
Filter "ScoreNormalizationFilter" started, 724 Results (2015-04-11 09:02:16)
Filter "ScoreNormalizationFilter" finished, 724 Results (2015-04-11 09:02:16)
Filter "ScoreCombinationFilter" started, 724 Results (2015-04-11 09:02:16)
Filter "ScoreCombinationFilter" finished, 722 Results (2015-04-11 09:02:16)
Filter "FactoidSubsetFilter" started, 722 Results (2015-04-11 09:02:16)
Filter "FactoidSubsetFilter" finished, 722 Results (2015-04-11 09:02:16)
Filter "DuplicateFilter" started, 722 Results (2015-04-11 09:02:16)
Filter "DuplicateFilter" finished, 435 Results (2015-04-11 09:02:17)
Filter "ScoreSorterFilter" started, 435 Results (2015-04-11 09:02:17)
Filter "ScoreSorterFilter" finished, 435 Results (2015-04-11 09:02:17)
New York City
***********************************************
イタリアの首都(=ローマ)はなにか?が元の音声なので、ニューヨークと答えてるのはNGですね。
ただ音声認識の結果が "what is the capital affiliate" なので、"affiliate" からアメリカを連想できているのはいい感じかもしれません。

ということで、同梱されているデータだけではまだ精度的に厳しい部分もありそうですが、いわゆる音声認識での対話システムみたいなものが簡単に実現できそうな感じです。(Combining Serviceあたりが想像しやすいでしょうか)
日本語に対応しているかは未確認ですが、ぜひ対応して欲しいですね!(自分でやれ)

*1:気が向いたら本家にプルリク投げるか…?
*2:余談その1:Ubuntuは12と14で微妙に差異があったのでいろいろハマりました。
*3:余談その2:直近(2015/04/08, JST)のコミットで入った、各サーバプロセス起動時にCPUを見てスレッド数を変更する対応の影響で、プロセッサ数が4未満だとQAがうまく動かなくなるようです。(該当するissueはこれ→https://github.com/jhauswald/sirius/issues/59

2015年3月29日日曜日

Pythonで2次元配列を初期化するときのアレ

備忘録として。

Web上でよく見かける、Pythonで2次元配列を初期化する際のtipsは以下のようなものかと思います。

# N x M.
# ダメな例
ng_list = [[0]*M]*N

# 良い例
ok_list = [[0 for j in range(M)] for i in range(N)]

2次元以上にも応用が効くので全く問題ないのですが、推奨されている方も内包表記がネストしていてなんとなくモヤっとします。
てことで思いついた記法を試したら問題なさそうだったので、一応晒しておきます。
ツッコミ歓迎なので、気になる点がありましたらご指摘ください。

# N x M.
my_list = [[0] * M for i in range(N)]

2015年3月22日日曜日

別サーバにあるJenkinsジョブと連携するスクリプト書いた

タイトルの通り、別サーバにあるJenkins間でジョブを連携させるスクリプトを勉強がてら書いたので、ことのついでに晒してみます。

ソースはgist(syncbuild_remote_jenkins.py)にあります。
動作環境などは↓のような感じ。
  • python2.7で動作確認済み
    • 3.x系では動きません
  • 基本的に標準のライブラリだけで動く(はず)
    • 自由にpip install出来ない環境では重宝するかも
使い方としては、任意のホスト、ポート、ジョブ名を指定する感じです。
$ ./syncbuild_remote_jenkins.py --host remote-jenkins-server --job RemoteJobName --interval 300

ちなみに、intervalオプションでジョブの状態をポーリングする間隔を変えられるようにしています。(上記の例だと300秒)
時間のかかるジョブであれば長めに設定すると無駄な処理が走らなくて良いかと。

できること/できないこと

今のところ以下のことが可能です。
  • 指定したサーバのJenkinsジョブが完了するまで待機して、成否などの情報を返す

一方、できないことは以下の通りです。
  • 認証付きのJenkinsサーバには対応していません
  • このスクリプトでビルドしたジョブの成果物を取得するとかも特にやってません
  • その他いろいろ未対応

いやそもそもさぁ……

Q:普通に Remote access API とか Parameterized Remote Trigger Plugin とかあるよね?
A:うん。

確かに便利なプラグインやライブラリは存在していて、最初はRemote Trigger Pluginを使おうと考えていました。
が、こいつの出力があまり充実しておらず、特にビルドした別サーバのJenkinsジョブのURLが出てこないのがちょっとマズかったので自作することにしました。
(設定いじると出てくるのであれば乗り換えたいですが。)

自作するにしても、なるべく簡単に作りたかったので単にRemote access APIをシェルから叩くつもりでしたが、レスポンスのパースとかが面倒くさそうだった大変そうだったのでPythonで実装しています。

実装方針とか

そもそも複数サーバのJenkinsを連携させようと考えたきっかけが、会社で動かしてるCI環境のせいだったりします。
現状が、
  • プロダクトビルド用のJenkinsがある
  • デグレードの検出に直前のバージョンとの返却値比較をしたい
  • ただし、デグレードのチェックは最近構築したJenkinsサーバでやりたい
    • ビルド用サーバは共有していてあまりいじりたくないので
  • ビルドジョブはそのままビルド用サーバ上で動かしたい
  • チェックが失敗したら、プロダクトのビルドも失敗させたい
  • 成否によらず、プロダクトのビルド結果からチェックのビルドを参照したい
という感じなので、複数サーバの連携が必要だと考えました。

今回の要件としては、
  • python2.7で動く
  • pip installはNG
  • ビルドしたジョブが終わるのを待機できる
  • ビルドしたジョブのURLを出力する
  • できれば1ファイル
というところでしょうか。

JenkinsのREST APIを色々試していると、
  1. 対象ジョブのビルド開始
    1. ${JOBNAME}/buildWithParameters を叩く
  2. ビルドキューの状態確認
    1. ${JOBNAME}/buildWithParameters のレスポンスについてくるLocationヘッダに対象ビルドに関するキューのステータス取得用パスが入ってる(実際には、api/jsonなどを末尾に付与する必要はありますが)
    2. JSONの場合、ステータスとして取得したオブジェクトにexecutableフィールドが存在しない間はキューで待ち状態になっているようなので、返ってくるまで定期的にポーリング
    3. executableフィールドが返ってきたら、中のurlを使ってビルド自体の進捗をチェック
  3. ビルド自体の状態確認
    1. キューの状態確認で取れたexecutableフィールドのurlにアクセスすると、ビルドの状態が取れる
    2. ビルドが完了していない間は、取得したオブジェクトのresultフィールドがnullになっているので、これまた定期的にポーリング
    3. null以外になったら、入っている文字列が最終的なビルド結果
という感じで必要な情報は全部取れそうなので、この流れで実装すれば大丈夫そうです。

そして実装したソースコード全体は以下の通りです。
会社で動かすものはいろいろな事情で制約がつきやすいと思いますので、そういう時に活用していただけると良いかと思います。

2014年10月5日日曜日

HMMLearn導入メモ

どうもご無沙汰しております。気づけば一年以上ぶりですね。
忙しさを言い訳に全く何もしてない状況が続いていましたが、さすがにこれはマズいとそろそろ重い腰を上げたいところです。

最近はデータ解析のまねごとに勤しんだり、新しい機能の開発をやってみたり、新卒の子のフォローをしてみたりと色々やっているわけですが、ここに来てまた他のことにも手を出そうとしています。下手の横好きとも

ということで、タイトルのネタです。

諸々の事情により時系列データの予測をする必要に迫られそうなので、とりあえずHMM使ってみるかー、という安易な発想に基づき手頃なライブラリとして sklearn を導入することに。

が、

HMMのリファレンスいわく、
Warning

The sklearn.hmm module has now been deprecated due to it no longer matching the scope and the API of the project. It is scheduled for removal in the 0.17 release of the project.

This module has been moved to a seperate repository: https://github.com/hmmlearn/hmmlearn
とのこと。
仕方ないので、hmmlearnのリポジトリに飛んで記載されている手順通りにインストール。

  1. git clone git://github.com/hmmlearn/hmmlearn.git
  2. cd hmmlearn
  3. python setup.py install
  4. nosetests
すると、4. で以下のエラーに遭遇。

E.E
======================================================================
ERROR: Failure: ImportError (cannot import name _hmmc)

まぁ、なんやかんやあって切り離されたライブラリみたいだし、しょうがないかと思ってissuesを漁るとありました、ドンピシャのやつ。
https://github.com/hmmlearn/hmmlearn/issues/3
見るとまだ解決していないようですが、とりあえず全部コピればOKのようです。

……汚くなるからやだなぁ。

ということで色々試してみた結果、以下のようにすれば良さそうです。
$ cp -r hmmlearn/utils/ ${PYTHON_ROOT}/site-packages/hmmlearn/utils/
(${PYTHON_ROOT}はpythonのインストール先。virtualenv使ってたら、${VIRTUAL_ENV}/lib/pythonx.x/とかでしょうか)

どうもこのutils直下にいるfixes.pyを読んでいる感じですが、詳細不明なのでとりあえずの対処ということで。
ひょっとしなくてもvirtualenv使ってるとおかしくなるとかそういうアレかもしれません。

割とニッチなライブラリみたいなので困っている方はいない気もしますが、万一お困りの方がいれば参考までに。

2013年5月10日金曜日

PRML読書録(その0)

コンスタントに書く習慣をつけたい今日このごろ。

前回の記事(→WebGLでk-meansをビジュアライズ)ですが、当初の予定ではこれにDirichlet Process Mixtureによるクラスタリングを追加する予定でした。
学生の頃から気になる手法ではあったのですが、ちゃんと追えていなかったので、ここでやろうと。
が、どうにも基礎知識が足りなさすぎて実装の手がかりすらつかめない状況に陥ったので、方針を変えることに。

何をするかといえば、これまた学生の頃から(ry な、Pattern Recognition and Machine Learning(PRML)*1をちゃんと読んで、しばらくの間基礎知識の補強期間にしようと思います。
で、その過程で色々実装したものを使って遊んだり、アウトプットしていって、最終的に最近の新しい話題に関するネタをやれる状況を作ろうかなー、と。

ただ、このあたりは既に偉大な先駆者(PRML 読んでやってみた(上巻編)PRML 読んでやってみた(下巻編))がいらっしゃるので、ちょっと違う観点で実装したり出来ればなぁと考えていたり。*2

ということで、本来は順番通りに読んでいくべきでしょうけど、DPMに関係の深そうな、HMMとかその辺を中心にやっていこうと思います。
果たして最初のアウトプットはいつになるのか

  • *1:amazonだと和書が中古でしか流通していないので、手元にあるのはなぜか洋書。無駄にハードルが…
  • *2:実装に使われている言語はRがメインのようなので、numpyで書いてみるとか、実装したお試しコード全部ビジュアライズするとか。

2013年5月3日金曜日

WebGLでk-meansをビジュアライズ

久しぶりの連休!
ということで月イチどころか、週イチでの更新です。珍しい。
多分続かないですが

さて今日は少し前からちまちま作っていたものが形になったので公開してみます。

WebGLでk-meansをビジュアライズ
http://www.lazykyama.jp/ClusteringVisualizer/contents/
はい。タイトルのまんまです。

どういうものかというと、てっく煮ブログさんの以下の記事をパクったにインスパイアされたコンテンツです。
3D版!「K-Means法」ビジュアライズしてみた
http://tech.nitoyon.com/ja/blog/2009/04/13/kmeans-visualize-3d/ 

内容は機械学習やクラスタリングなどでお馴染み、k-meansの結果をWebGLで可視化する、ということで、インスパイア元とほぼ同じ*1です。
ちなみに動作確認はChromeでしかやっていないので、他のブラウザの方はご覧頂けないかもしれません。

このコンテンツですが、実際にアクセスすると、
こんな感じで表示されます。

で、nextボタンをポチポチ押していくとクラスタの割り当てが変わっていき、最終的に、
こうなります。
当然回転するので、くるくる回しているだけでも結構楽しいかもしれませんw


最後に実装について少し。
今回はWebGLを使ったと書きましたが、実際の描画などはthree.jsを使っています。
色々調べているとWebGLを直で触るのがしんどそうだったので、ライブラリの力を借りました。
OpenGLを少し触っていた事もありますが、サンプルが充実しているので、このくらいの規模なら割と簡単に作れるなー、というのが率直な感想です。
今後はWebGLを使ったサイトも増えてくるんでしょうかね。

  • *1:うちの方がアニメーションなどもろもろしょぼいので、正しくは劣化コピー


2013/05/14 追記:
「関連キーワードでググったらどのくらい上に来るかな〜」と、嬉々としてググったところ、全く同じ事をやっている方のブログ(K-means法をHTML5 canvasで可視化する)を発見して(´・ω・`)ションボリ
なんか作るときはちゃんと調べてからやりましょうねということで…

2013年4月29日月曜日

プレゼン・各種発表用タイムキーパーツール

月イチとか言いつつ週イチくらいでアウトプットできていて非常によい傾向です。

さて今日は深夜テンションでぱぱっと作ったツールを公開しようかと思います。
https://gist.github.com/lazykyama/5477836
プレゼンや発表会などで使う、タイムキーパー用のツールです。
特徴としては、

  1. HTMLファイル一つ(とjQuery)で動くので、特別なインストール作業が不要
  2. 背景色で残り時間をお知らせ

といったところでしょうか。
まぁ正直、何番煎じだかすらわかりませんが。

使い方

肝心の使い方ですが、とっても簡単!
  1. 上記gistにアクセス
  2. HTMLファイルをダウンロード
    1. 画面左側の「Download Gist」ボタンをクリックし、ダウンロードした.tar.gzファイルを解凍する
    2. または、画面右側の小さい「<>」ボタンからファイルの内容を表示し、右クリックで「ファイルに保存」など
  3. ダウンロードしたファイルをGoogle Chromeなどのブラウザにドラッグ&ドロップ
と、たったこれだけ。

ブラウザで開いた直後の画面はこんな感じで↓

右上の小さい「start/stop」ボタンを押すと、
こんな感じに。

残り1分を切ると、
背景が黄色になって、
制限時間をオーバーすると赤くなります。

最近はブラウザにフルスクリーンモードがあったりするので、発表者の前にPCなどを置いてデカデカと表示してあげるといいんではないでしょうか。


今後ですが、せっかくHTML5で書いているので、この手のツールに必須の音を出す機能を追加したいなと思っていたりします。
が、このへんの予定は未定です。
あと、一晩でぱっと書いたのでコードが汚いのも早いうちにどうにか

2013年4月22日月曜日

複数の色を均等に生成する

コンスタントに色々やる体制が整いつつあるkyamaです。こんばんは。
目指せ月イチアウトプット。

さて今日のお題は、「複数の要素に区別しやすい色を割り当てる方法」です。

いくつかの要素をカテゴリ分けして表示する際などに、カテゴリごとの色を作らないといけない、と言うことはそれなりにあると思います。
プラットフォームやフレームワークなどで提供されていれば悩む必要はないのですが、そういうものがない場合は当然自前で書くことになります。

このとき手っ取り早い方法は、以下の様なコードでランダムに色を選ぶことでしょうか。
var color = {r: 255, g: 255, b: 255};
color.r = Math.round(Math.random() * 255);
color.g = Math.round(Math.random() * 255);
color.b = Math.round(Math.random() * 255);

ただしこれは、重複した色や類似した色が生成されてしまい、実際には使いにくい場面もあります。
そのため、以下の様なコードで、色相を均等に分割して色を割り当てるのが妥当な落とし所ではないかと思っています。
// splitSize: 分割数
// index: 分割されたうちの何番目か
// return: RGBのオブジェクト。0から1までの値をとる。
function pickRGBColorOnCircle(splitSize, index) {
    var interval = 360.0 / splitSize;
    var hue = (index * interval);
    var sat = 1.0;
    var val = 1.0;
 
    // this code written in reference to the following site and Wikipedia.
    // http://www.technotype.net/tutorial/tutorial.php?fileId=%7BImage%20processing%7D&sectionId=%7B-converting-between-rgb-and-hsv-color-space%7D
    var hi = Math.floor(hue / 60.0) % 6;
    var f  = (hue / 60.0) - hi;
    var p  = val * (1.0 - sat);
    var q  = val * (1.0 - sat * f);
    var t  = val * (1.0 - sat * (1.0 - f));
 
    switch(hi) {
    case 0:
        return {
            r: val,
            g: t,
            b: p
        };
    case 1:
        return {
            r: q,
            g: val,
            b: p
        };
    case 2:
        return {
            r: p,
            g: val,
            b: t
        };
    case 3:
        return {
            r: p,
            g: q,
            b: val
        };
    case 4:
        return {
            r: t,
            g: p,
            b: val
        };
    case 5:
        return {
            r: val,
            g: p,
            b: q
        };
    default:
        throw new Error('unexpected error: args = ' + splitSize + ', ' + index);
    }
}

色相を分割する関係上、HSV表色系で色を特定した後にRGBに変換するようにしています。
現状は色相の開始位置を固定していますが、引数にoffsetなどを追加して変更できるようにすれば任意の位置から色の選択を開始できるはずです。

また上記のコードはGistにも置いているので、使いたい方はご自由にお使いください。
 https://gist.github.com/lazykyama/5429907
ライセンスは要らない気もしましたが、一応MITにしています。
ファイル名の変更などもご自由に。

しかしもう完全に備忘録ではなくなってきてますね…


2013年3月10日日曜日

Eclipseの設定デフォルト値(行番号表示・文字コード)を変えたお話

気づけば前回の更新から半年以上…*1

さて、今回はEclipseのデフォルト設定を変更するところでうまく検索できなかったので、情報を集約すべく記事を書いています。
というか備忘録なので自分用にまとめているだけのはずですが

今回変更したデフォルト設定は以下の2つです。
  1. 行番号(行数)表示
  2. 文字コード

どちらもワークスペースを切り替えると、逐一変更する必要があって面倒な項目ではないでしょうか?
ということで、以下に内容を記述します。
ほぼ参照元の内容そのままですが。

なお、今回の環境は以下の通り*2です。
  • MacOSX 10.7.5 Lion
  • Eclipse 4.2 Juno

行番号表示

行番号表示のデフォルト設定を変更するためには、eclipse.iniの書き換えとplugin_customization.iniの作成(ないし書き換え)が必要とのこと。
以下、具体的な手順です。

  1. $(eclipseインストールディレクトリ)/Eclipse.app/Contents/MacOS/直下のeclipse.iniに以下の内容を追加する。追加位置は-vmargsより前
  2. -pluginCustomization
    plugin_customization.ini
  3. plugin_customization.iniを、eclipse.iniと同じ場所に作成する。内容は以下の通り。
  4. # line number ruler (default: false)
    org.eclipse.ui.editors/lineNumberRuler=true
    
    plugin_customization.iniは、$(eclipseインストールディレクトリ)/plugins/にインストール済みの場合もあるようなので、その場合はコピペ(ないし移動)して使うとよい。

ほかにも色々と設定できるようですが、詳細については参考サイトの方をご覧ください。

参考サイト

文字コード

文字コードのデフォルト値変更は、eclipse.iniを変更するだけでよいようです。
以下のパラメータを-vmargsの直後に追加*3してください。
-Dfile.encoding=UTF-8
※ UTF-8以外の文字コードを利用される方は、Shift-JISなど、適当な文字列を使ってください

参考サイト

以上の設定が完了したら、Eclipseを起動しましょう。
おそらくエディタの左端に行番号が表示され、新規に作成するファイルの文字コードがすべて指定のものになっているはずです。

  • *1:この半年で職場が変わったりと色々ありましたが、私は元気ですw
  • *2:WindowsやLinuxでも、パスを読み替えれば同じ手順で変更できるはずです。
  • *3:意味がわかっている方は直後でなくとも良いと思います。

2012年7月25日水曜日

続・Macでのlibjpegとかlibpngとか

またしてもひと月おきの更新です。
さて。
今回はタイトルにも掲げていますが、先日書いた記事(Macでのlibjpegとかlibpngとか)の続き(というかまとめ)です。*1
あんまり追加情報はありません。

まとめ

先日の記事で書いた、「-Lオプション付ける位置次第で動いたり動かなかったりする」という問題は、どうやらNetBeansに依存する問題のようです。*2
ということで、Macでlibjpegおよびlibpngをうまいこと動かすには、

  1. libjpegはbrewなどでインストールしたモジュールを使う
    1. ライブラリパス /usr/local/lib は、デフォルトでdyldが探すので、手動で追加しない
    2. インクルードパス /usr/local/include は指定する(-I/usr/local/include)
  2. libpngは下手にbrewなどで追加しない
    1. 最初から(?) /usr/X11/lib にライブラリファイルがあるはずなので、それを使う
    2. ライブラリパスに /usr/X11/lib を追加する(-L/usr/X11/lib)
    3. インクルードパス /usr/X11/include も指定する(-I/usr/X11/include)

ということが重要なようです。
ちなみに、X11の下にlibpngがいない場合は、XCodeをインストールした後、Command Line Toolsという追加コンポーネントをダウンロードする必要があるかもしれません。

  • *1:閲覧数を見る限り割と需要がありそうだったので。
  • *2:ターミナルからgcc直接叩いたら問題なく動いた。

2012年6月28日木曜日

Cakefile書こうとして一歩目から躓いたお話

ご無沙汰しています。
最近自分のコードを書く気力がなくてどんよりしてます。

さて。

突然ですが、私、ここ最近CoffeeScriptに魅了されております。
会社の先輩にそそのかされたとかなんとか。

仕事で結構な行数のJavaScriptを書くことがあるのですけど、素のJavaScriptを書いているとどうもしんどいことが多々有りまして。
その点CoffeeScriptだと、手軽にコードが書けて非常に便利です。

んで、諸々の事情により、CoffeeScriptを自動コンパイルする必要が出てきて、Cakefileの書き方を調べていたのですが、初手から躓いたので、そこら辺をメモしておきます。

一番単純な(ハズの)Cakefile

まずはと思って、CakeFileの使い方 -CoffeeScript-を参考に一番単純なCakefileを書いてみました。
sys = require 'sys'

sys.puts 'Heloi, word.'

するとなぜかエラー。
sys.puts〜を削除してもエラー。

ここでようやく、エラーメッセージを確認するわけですが、
path.existsSync is deprecated. It is now called `fs.existsSync`.

sys.js:1
throw new Error(
      ^
Error: The "sys" module is now called "util".
    at sys.js:1:69
    at NativeModule.compile (node.js:585:5)
    at Function.require (node.js:553:18)
    at Function._load (module.js:297:25)
    at Module.require (module.js:362:17)
    at require (module.js:378:17)
    at Object. (/Users/kyama/xxx/xxx/.../xxx/Cakefile:4:9)
    at Object. (/Users/kyama/xxx/xxx/.../xxx/Cakefile:8:4)
    at Module._compile (module.js:449:26)
    at Object.run (/Users/kyama/.node/v0.7.9/lib/node_modules/coffee-script/lib/coffee-script/coffee-script.js:79:25)
とのこと。

おや?
どうやら6行目のメッセージによると "sys" は "util" に変わっているようです。

ということで、書き直して↓
util = require 'util'

util.puts 'Heloi, word.'

結果は↓
path.existsSync is deprecated. It is now called `fs.existsSync`.
Heloi, word.
Cakefile defines the following tasks:
    

結局 sys を util に変更することで事なきを得たという実にくだらないお話でした。
これで自分の環境が原因だったら更にくだらない…

2012年5月3日木曜日

アラインメントの罠

大型連休っていいですね!!

ということで日がな一日好きなコードを書き続けています。こんばんは。
ちょっと勉強がてら、OpenCVでカメラからキャプチャした画像をOpenGLで描画しようとして、またしてもハマったのでメモ。

やろうとしたこと

冒頭にも書いた通り、今回はOpenCVで読み込んだ画像をOpenGLで出力-CPPのようなことをやろうとしてました。
他にも類似の記事はたくさん引っかかったので、すぐできるだろー、と思いコードを書くことに。

方針としては、cvQueryFrame()を使ってIplImageを取ってきて、それを毎回glDrawPixels()に渡して描画する、という流れを考えてました。

結果

うまくいきませんでした……orz

cvQueryFrame()で取れた画像をglDrawPixels()に直接渡して描画*1しようとした結果が以下です。

どうしてこうなった……(´・ω:;.:...

原因

設定がおかしいのかとも思いましたが、結局、正しいデータを渡せていないことが原因でした。
cvCreateImage()で作ったIplImageにcvQueryFrame()で取れたIplImageのデータを「ちゃんと」コピーすることで、期待通りの動作をするようになります。

具体的には以下のようにコピーすることでうまく動作しました。
    // _pFrameはcvQueryFrame()で取得したIplImage
    // frameはcvCreateImage()で作成したIplImage↓
    // IplImage *frame = cvCreateImage( cvSize( _pFrame->width, _pFrame->height ), IPL_DEPTH_8U, 3);
    for (int y = 0; y < frame->height; y++) {
        for (int x = 0; x < frame->width; x++) {
            frame->imageData[frame->widthStep * y + x * 3] =
                    _pFrame->imageData[_pFrame->widthStep * y + x * 3];
            frame->imageData[frame->widthStep * y + x * 3 + 1] =
                    _pFrame->imageData[_pFrame->widthStep * y + x * 3 + 1];
            frame->imageData[frame->widthStep * y + x * 3 + 2] =
                    _pFrame->imageData[_pFrame->widthStep * y + x * 3 + 2];
                    
        }
    }

実行結果↓

どうやらcvQueryFrame()で取得できるIplImageには、各行ごとにパディング用の領域が確保されていて、その領域をOpenGLが読み飛ばせずにおかしな描画をしていたようです。
実際、cvQueryFrame()で取れたIplImageとcvCreateImage()で作ったIplImageのwidthStepを見ると、それぞれ、

  • cvQueryFrame()のIplImage->widthStep = 2560 (= 640 * 4)
  • cvCreateImage()のIplImage->widthStep = 1920 (= 640 * 3)

(※ともにwidth=640, nChannels=3)
となっており、確かに余計な領域が入っていることが確認できました。

そんなわけで、cvQueryFrame()でキャプチャした画像を直接OpenGLに渡す場合には気をつけましょうね!




……と、ここまで書いて気づきました。



cvCopy()一発でいけるんじゃ……?



ということで、forループを2周してコピーしている部分を以下のように書き変えました。
    cvCopy(_pFrame, frame);

で、結果↓


……まぁすっきりしたからよしとしましょう!!

結論

  • cvQueueFrame()で取得したIplImageをglDrawPixels()に渡す際は、一度cvCopy()でコピーしておくと良い
  • 変なコードを書く前に調べる


  • *1:cvCloneImage()してコピーした画像を渡したりもしてみた

2012年5月1日火曜日

SBJsonでの自作クラスJSON化

最近色々動けていなかった鬱憤を晴らすかのように2本目です。

何の因果か、ここのところiOSの開発もできるようにならないといけなくてお勉強したりしているわけですが、ちょっとライブラリを使うときにハマったので、軽くがっつりメモしておきます。

iOSでJSON文字列を生成する

google先生によると、どうやらiOSでJSONを使う場合、SBJsonというライブラリを使うのが一般的な様子。
昔はJson.frameworkと呼ばれていたようですが、いまはSBJsonとなっているそうです。

で、2012/05/01現在、SBJsonの最新バージョンは3.1になっていました。
トップページのAPI DocumentationにはVersion 3.1(Alpha)と書かれていますが、githubを見てみるとひと月前くらいにアップデートされているみたいですね。
ちなみに3.0と3.1の差異はARC対応しているかどうかだそうです→http://d.hatena.ne.jp/paraches/20120115/1326650073

ひとまずgithubからバージョン3.1のzipを落としてきて展開します。


インストールの方法はいくつかあるらしいのですが、他でも紹介されているようにソース一式をプロジェクトに追加しました。


これで準備は完了したので、後はコードを書いていきます。

必要なところで#import "SBJson.h"すれば、すぐにパーサが使えるようになるようです。
文字列をオブジェクトに変換するクラスはSBJsonParserで、このクラスのobjectWithStringなどを使うといい感じに変換してくれます。

んで、例えば↓
 [
  {
     "name":"google",
     "url":"www.google.com"
   },
   {
     "name":"yahoo",
     "url":"www.yahoo.com"
   }
 ]
のようなJSON文字列をパースしてオブジェクト化する場合のコードが↓↓
    SBJsonParser *parser = [[SBJsonParser alloc] init];
    NSError *error = nil;
    // jsonStrにJSON文字列が入ってます
    NSArray *resultList = [parser objectWithString:jsonStr error:&error];
    if ( error ) {
        NSLog(@"parse error: %@", jsonStr);
    } else {
        NSLog(@"%@", resultList);
    }
です。

これを実行してみると、
 (
    {
        name = google;
        url = "www.google.com";
    },
    {
        name = yahoo;
        url = "www.yahoo.com";
    }
 )
という感じの出力が得られるはずです。

逆に、配列などをJSONにしたい場合はSBJsonWriterを使います。
例えば、
    SBJsonWriter *writer = [[SBJsonWriter alloc] init];
    NSMutableDictionary *dstDict = [NSMutableDictionary dictionaryWithCapacity:100];
    [dstDict setObject:[NSNumber numberWithDouble:123.27] forKey:@"latitude"];
    [dstDict setObject:[NSNumber numberWithDouble:74.027] forKey:@"longitude"];
    [dstDict setObject:[NSNumber numberWithDouble:0.0001] forKey:@"accuracy"];
    error = nil;
    jsonStr = [writer stringWithObject:dstDict error:&error];
    if ( error ) {
        NSLog(@"parse error: %@", dstDict);
    } else {
        NSLog(@"%@", jsonStr);
    }
のようなコードを実行すると、
{"longitude":74.027000000000001,"latitude":123.27,"accuracy":0.0001}
という文字列を返してくれます。

自作クラスをSBJsonWriterに引数として直接渡す

前フリが長かったですが、ここからが本題です。
SBJsonWriterを使ってJSON文字列を作成する場合、stringWithObjectを使うことになります。
このとき、引数として渡すオブジェクトはなんでもいいのですが、基本的にはNSArrayかNSDictionaryしか渡すことができません。
自作クラスのオブジェクトも渡せますが、普通に渡すとエラーが返ってきます。
しかしどうにかして自作クラスを直接渡したい。

ということで軽くソースを眺めてみると、SBJsonStreamWriter.hの41行目以降に書いてありました。
 If you have a custom class that you want to create a JSON representation for you can implement this method in your class. It should return a representation of your object defined in terms of objects that can be translated into JSON. For example, a Person object might implement it like this: 
 @code
 - (id)proxyForJson {
        return [NSDictionary dictionaryWithObjectsAndKeys:
        name, @"name",
        phone, @"phone",
        email, @"email",
        nil];
 }
 @endcode
ということで、proxyForJsonメソッドを実装すれば良いそうです。

で、実際に実装してみた結果が以下。

MyClassSample.h
@interface MyClassSample : NSObject

@property (nonatomic, strong) NSString *name;
@property (nonatomic) uint age;

- (id)proxyForJson;  // SBJsonを使用して任意のオブジェクトをjsonにする場合,このメソッドを実装する必要がある
@end
MyClassSample.m
#import "MyClassSample.h"

@implementation MyClassSample

@synthesize name;
@synthesize age;

- (id) proxyForJson {
    return [NSDictionary dictionaryWithObjectsAndKeys:
            name, @"name",
            [NSNumber numberWithUnsignedInt:age], @"age", 
            nil];
}

@end
呼び出し側
    MyClassSample *obj1 = [[MyClassSample alloc] init];
    obj1.name = @"name1";
    obj1.age = 21;
    
    error = nil;
    jsonStr = [writer stringWithObject:obj1 error:&error];
    if ( error ) {
        NSLog(@"parse error: %@", obj1);
    } else {
        NSLog(@"%@", jsonStr);
    }
実行結果
{"age":21,"name":"name1"}

ということで、無事に自作クラスをJSON化できました。
ちなみに、自作クラスをNSDictionaryに追加してもちゃんと動作して、
呼び出し側
    MyClassSample *obj1 = [[MyClassSample alloc] init];
    MyClassSample *obj2 = [[MyClassSample alloc] init];
    obj1.name = @"name1";
    obj1.age = 21;
    obj2.name = @"name2";
    obj2.age = 42;
    NSDictionary *userMap = [NSDictionary dictionaryWithObjectsAndKeys:
                             obj1, @"user1", 
                             obj2, @"user2", 
                             nil];
    
    error = nil;
    jsonStr = [writer stringWithObject:userMap error:&error];
    if ( error ) {
        NSLog(@"parse error: %@", userMap);
    } else {
        NSLog(@"%@", jsonStr);
    }
実行結果
{"user2":{"age":42,"name":"name2"},"user1":{"age":21,"name":"name1"}}

という感じになり、こちらも無事にJSON文字列を取ってくることができました。

GLMetaseqとHandyARを使ってみる その0

3日坊主にだけはするまいと思っていたにも関わらず、約一月ぶりの更新です。
こんばんは。
まだまだ仕事のコントロールが出来ない若造でございます。

さて、前回までにHandyARで手のひらを検出するところまで行ってましたが、ちまちま作業をしていた結果、ひとまずミクさんが手のひらに乗ってくれるところまで到達↓

※3Dモデルは――innoce――さんのLat式ミクさんをお借りしました。

タイトルにもありますが、3Dモデルの読み込みにGLMetaseq(元記事)というライブラリを使用し、読み込んだデータをHandyARで検出した座標系を使って表示、という流れになっています。
どう見ても二番煎じです。本当に(ry

で、このあとどうするかが問題で、

  1. アニメーションを適用する
  2. 手の検出精度を上げる
  3. ぜんぜん違うことやる

と3つくらい考えてます。
個人的にはアニメーションが結構ややこしそうなので、手の検出精度を上げるか、ぜんぜん違うことやり始めようかなーと思ってたりします。
が、あんまり決めてなかったりするのでどうなるかは不明です。

ちなみに実装でハマったところとかそのへんは近日中に書くと思います。
GLMetaseq付属のサンプルは画面描画のときにメモリリークしてる気がしたりとか…ね…

2012年4月7日土曜日

Macでのlibjpegとかlibpngとか

凄まじくハマったので、忘れないように(AND 追加の問題発生時の対応用に)メモ。
試行錯誤しながらやったので、細かい部分は曖昧です。

(2012/07/25追記)
下記の内容をまとめました→(続・Macでのlibjpegとかlibpngとか
お急ぎの方ははまとめをご覧下さい。


何をしようとしたか

そもそもの発端は、前回までの続きとしてミクさんをプログラム的に呼び出す方法を調べていたことでした。
んで、工学ナビさんが急募してたのでMacでGLMetaseqを使ってみたとか、「ARToolKitで初音ミク」をやってみようといったエントリを参考に、環境構築をしていたわけです。
最初は順調だったんですが、工学ナビさんが急募してたのでMacでGLMetaseqを使ってみた(続き)に書かれているpng対応などを追加したあたりから雲行きが怪しく…。

最初作っていた環境はMacports経由で色々インストールしていたので、状況は全く同じはずだったんですが、なぜか実行時に"symbol not found"などと怒られ、一切実行できなくなってしまいました。

libjpeg.dylibとかlibpng.dylibのあるディレクトリにはパス通してました*1し、原因が一切思いつかないので、とりあえず最近流行りらしいし、パッケージ管理をMacportsからhomebrewに変えようと思い、一式アンインストールして入れなおすという暴挙に。

続く迷走

暴挙に出た結果としては、そもそもコンパイルすら通らなくなる有様*2…。
この辺からよく分からなくなって、brewがインストールするときに張るシンボリックリンクをImageIO.frameworkの下にいるlibjpeg.dylibとかに張り替えるとか、挙句OSのクリーンインストールまでやる始末。
また、OpenCVの中でlibjpegとか呼んでるのが悪いんじゃないかと考えて、ImageIO.frameworkの配下にいるライブラリを叩くように修正しようかと、訳のわからないことをやろうとしたりで、大迷走。

ライブラリの読み込み順

たださすがにOpenCV自体をいじるのはコスト的に美味しくないので、どうにかならないかと調べていたら、以下のエントリがまさに答えを示していました。

MacPorts のために DYLD_LIBRARY_PATH を使ってはいけない理由

曰く、
Macではランタイムライブラリの検索順序について、「man 1 dyld」 で確認することができます。
曰く、以下の順序で検索されます。
1. DYLD_LIBRARY_PATH
If it still can’t find the library, it then searches DYLD_FALLBACK_FRAMEWORK_PATH and DYLD_FALLBACK_LIBRARY_PATH in turn.
2. DYLD_FALLBACK_FRAMEWORK_PATH
By default, it is set to /Library/Frameworks:/Network/Library/Frameworks:/System/Library/Frameworks
3. DYLD_FALLBACK_LIBRARY_PATH
By default, it is set to $(HOME)/lib:/usr/local/lib:/lib:/usr/lib.
とのこと。

デフォルト状態ではDYLD_LIBRARY_PATHは設定されていないので、IDEなどで明示的に設定しない場合のライブラリ検索順序は、

  1. /Library/Frameworks:/Network/Library/Frameworks:/System/Library/Frameworks
  2. $(HOME)/lib:/usr/local/lib:/lib:/usr/lib

となるはず。
Macportsを使っていたときは、/opt/local/libの下にライブラリ一式がいたので意味がなかったのですけど、homebrewは/usr/local/libにライブラリ一式を追加してくれてます。

…ということで、これまで自力でライブラリパスを通していたのが実は裏目に出ていたようです。
これで、libjpegまわりのエラーはなくなりました。

で、libpngについてですが、homebrewの管理から外れているということは承知の上で、brew search libpngを実行したところ、こんなメッセージが。
Apple distributes libpng with OS X, you can find it in /usr/X11/lib. However not all build scripts look here, so you may need to call ENV.x11 in your formula's install function.
…おや?
そう、なにもサードパーティのlibpngを入れるまでもなく、最初から入ってたんですね。
もちろんヘッダファイルも/usr/X11/includeにしっかりいました。

で、/usr/X11/libはどこにもパス指定されていないので、コンパイル時には指定しないとダメなはずですが、今回はNetBeansがよしなに計らってくれているようで*3、うまいことできてました。
自分のために、ライブラリパスを明示的に設定しない場合と設定した場合に実行されたコマンドを貼っておきます。


  • 設定しない場合(うまく実行できる)
gcc     -framework GLUT -framework OpenGL -L/usr/X11/lib -o dist/Debug/GNU-MacOSX/glmetaseq_test build/Debug/GNU-MacOSX/_ext/2091976640/GLMetaseq.o build/Debug/GNU-MacOSX/_ext/2091976640/main.o -ljpeg -lpng
  • 設定した場合(実行できない)
gcc     -framework GLUT -framework OpenGL -L/usr/X11/lib -o dist/Debug/GNU-MacOSX/glmetaseq_test build/Debug/GNU-MacOSX/_ext/2091976640/GLMetaseq.o build/Debug/GNU-MacOSX/_ext/2091976640/main.o -L/usr/X11/lib -ljpeg -lpng
オブジェクトファイルの後ろで-Lオプションつけると何が変わるんでしたっけ…?


(2012/4/8追記)
上記/usr/X11/libへのパスですが、自分で強引に設定していたのを忘れていました…。
プロジェクトのプロパティー→構築→リンカーとたどって、普通は「追加のライブラリディレクトリ」としてパスを設定すると思いますが、今回は「追加のオプション」に -L/usr/X11/lib を追加することでうまくいっていました。(=明示的に設定している)
ただ、やっぱりオブジェクトファイルのあとで-Lオプションつけるのと何が違うのか、さっぱりです。

…動いたからよしとしましょう。

結論

今回の結論としては、

  1. デフォルトで検索されるライブラリパスはいじらないほうが吉
  2. いじる必要がある場合、他のものに出る影響を最小化するために、DYLD_FALLBACK_LIBRARY_PATHへ追加する

といったところでしょうか。
本来ならもっとスマートに解決できそうに思いますが、どうもこのへんが限界です

先のエントリの結論にも書かれてますが、こういうのは「既存ソフトウェアに影響があるので、新規ライブラリ (ports で入れたライブラリ等) はデフォルトライブラリの後で読み込むようにしましょう。」この一言につきますね。



…しかしOpenCVと組み合わせたらまた問題が出そうな気がしますね、コレ。

  • *1:これがそもそもの間違い
  • *2:こっちもシンボル見つからない系のエラーを吐いてた
  • *3:こういう姿勢がもう色々とダメ

2012年4月6日金曜日

MacBookAir(10.7 Lion)でHandyAR その3

今のところLionだからという感じの問題に遭遇していないので、タイトルが釣りっぽいなーと思ったりしながらの3日目です。

ということで今日は前回の続きということで、固まっている原因はどこか、ソースコードを確認していきたいと思います。


先に結論

とりあえず先に結論だけ書いてしまうと、固まっているように見えたのは、前処理にものすごい時間がかかってただけでしたw
ということで、無事動くまでの経緯を…。

困ったときのprintfデバッグ

まずはどこまで無事に動作するかを確認するため、ひたすらprintf()デバッグを敢行しました。
なぜかNetBeansのデバッガが使えなかったので

すると、HandRegion.cppのloadSkinColorProbTable()ってところがどうも怪しいのでよーく確認すると、

51行目〜
        printf("making a lookup table for skin color distribution ");
        _SkinColor.MakeLookUpTable();
        printf("done\n");

この部分で止まっているようです。
ということでMakeLookUpTable()を探してみると、MixGaussian.cppにあったので、中を覗くと…、

118行目〜
void MixGaussian::MakeLookUpTable()
{
    CvMat * SampleMat = cvCreateMat( 3, 1, CV_64FC1 );

    for ( int R = 0 ; R < 256 ; R ++ )
    {
        printf(".");
        for ( int G = 0 ; G < 256 ; G ++ )
        {
            for ( int B = 0 ; B < 256 ; B ++ )
            {
                cvSetReal1D( SampleMat, 0, (double)R );
                cvSetReal1D( SampleMat, 1, (double)G );
                cvSetReal1D( SampleMat, 2, (double)B );

                _Probability[R][G][B] = GetProbability( SampleMat );
            }
        }
    }
    
    cvReleaseMat( &SampleMat );
}

3重ループかー…
どうもここの計算に時間を取られていたようです。
しかもprintf(".")って、改行コード出してないからインジケータとしての意味が無いですし…。

そんな訳で、インジケータをわかりやすく(R=20ごとに改行)変更したところ、無事に動いていることが確認でき…、


てない…orz
コードを確認すると、上の処理の直後にnonskin.mgmというファイルを読んでいるのですが、追加されてませんでした。
なので、nonskin.mgmファイルを追加した後、改めて実行。

そして待つことしばし…



動いた!!!(・∀・)
でも上下反転してますね…(;´∀`)

付属のドキュメントを見ると、flipオプションというのがあり、
-  [-filp]: flip video images vertically. (Default=don’t flip)    
とのこと。

なので、実行時に -flip を渡して再度実行…、


ktkr!!!


ということで、ひとまずMacでも動くことが確認できたので、次回はミクさんに登場して頂けるように頑張りましょうw

2012年4月4日水曜日

MacBookAir(10.7 Lion)でHandyAR その2

ということで2日目です。
昨日はコンパイル通ったところまで確認できたので、今日は実際に動かしてみましょう!

実行ファイル作成

とりあえずmain書かないとお話にならないので、まずはそこからいきます。
とはいえ何も参考にしないで書ける訳がないので、デモに入っているmain.cppを覗いてみます。

…なんかそのまま使えそうなので、使ってしまいましょう。
てい。



案の定、エラー出ませんでした。
引き続き、インクルードしているヘッダ(main.h、bunny/inc/bunny.h)をすべて追加すれば準備が完了です。
実行ファイルを作りましょう。
といってもNetBeansにお任せなので、必要なパスを通して依存関係書いておけばあとは勝手に…


はいエラーw
昨日の段階でコンパイルできてたってのは幻想だったんですねーwww

…やっぱりソースの修正は必要なようです。

エラー吐いたファイルはFingertipTracker.cppだそうなので、中を見てみましょう。
エラーの箇所はこんな感じでした。

684行目〜
    if ( nPoints < NUM_FINGERTIP )
    {
        _fDetected = false;
        _nFingertips = 0;
        _nPoints = 0;
        goto Finished;
    }

772行目〜
Finished:

    //
    // Update Mode
    //
    if ( _fDetected )
    {
        _nMode = MODE_FINGERTIP_TRACKING;
    }
    else
    {
        _nMode = MODE_FINGERTIP_LOST_TRACKING;
    }

    return _fDetected;

goto文って使ってる人いたんだ、とか思いつつ、文法的には合ってるっぽいので何故にエラーが出たのかもよくわからず。

軽くぐぐってみると、どうもMac特有のエラーらしいので、goto文を使わないように修正します。
幸い、単純にエラー時の終了処理のためだけに使われているようなので、772行目以降のif文をメソッドに閉じ込めて、684行目のif文に入ったらそのメソッドを呼び出して即returnするようにしましょう。

ということで、修正後のコードは以下になります。
684行目〜
    if ( nPoints < NUM_FINGERTIP )
    {
        _fDetected = false;
        _nFingertips = 0;
        _nPoints = 0;
        updateMode();

        return _fDetected;
    }
772行目〜
    //
    // Update Mode
    //
    updateMode();

    return _fDetected;
どちらもupdateMode()というメソッドがいきなり出てきてますが、これは以下のように定義しています。

void FingertipTracker::updateMode() {
    if ( _fDetected )
    {
        _nMode = MODE_FINGERTIP_TRACKING;
    }
    else
    {
        _nMode = MODE_FINGERTIP_LOST_TRACKING;
    }
}
単にif文をメソッド化しただけ。

ということで再度ビルドしてみますが、今度はFingertipPoseEstimation.cppからエラー…
ただしこちらもgoto文関係のエラーなので、まずは関連しているラベルを確認。

209行目〜
Finished:

    return fResult;

…goto文をすべて消して、直後に全く同じreturn文を書くことで対処しました。

改めてビルドすると今度こそ…
成功!
無駄に長かったですねー…

実行してみる

はやる心を抑えつつ、早速実行してみます。


あ。


(ノ∀`)アチャー

ダメでした。
しかしエラーログ見る限り、必要なファイルが無いだけのようなので、これをプロジェクトルートに追加します。
一応、念のために必要そうなファイルはすべて追加しておきましょう。


気を取り直して再度実行します。


とりあえずエラーは出てませんし、カメラにもアクセスできている風なんですが、全然ウィンドウが表示される気配が無いです…


ということで、困ったときのアクティビティモニタ。


応答なしwww
こらダメだwwwww

中身に踏み込…もうとする

このままではどうしようもないので、頑張ってソースを読むことにします。



…が、今日は疲れたので明日の自分に期待!!

2012年4月3日火曜日

MacBookAir(10.7 Lion)でHandyAR その1

色々やろうやろうと思いつつ、気づけば数ヶ月…。
最近になってようやく仕事が一段落してきたので、いざ!ということで、やりたかったことをガシガシ進めていきます。

そんなわけでしばらくの間、タイトルの通り、LionでHandyARを動かしていきます。
当面の目標としては、このへん↓
HandyARで初音ミクが手のひらの上でウッーウッーウマウマ(゚∀゚)
を目指しておきます。
今更感が漂いすぎててアレなところはスルーで
イケそうならiPhoneでも動かしてみようと思います。

ホントはPTAMあたりを使おうかなと思ってたんですが、64bit環境だとインストールが非常に手間みたいなので、今回はHandyARを採用する方向でいきます。

HandyARの入手

HandyAR自体はいろんな方が解説されてますので、詳細は割愛しますが、カリフォルニア大のTaehee Leeという方たちが作った手法だそうです。

ソースコードの入手自体は、彼らのページから可能です。
ページ下部の"Source Code"に"HandyAR Verx.x"というリンクがあるので、そこからzipを落とすだけでOK。
おそらく"HandyAR-verx.x.zip"みたいなファイルがDLできるはずです。

とりあえず解凍してみると、なんかいろいろ入ってました。

入ってますが、今回用があるのは DemoHandyAR/DemoHandyAR/HandyAR/ 以下のファイル群になると思いますので、ここのファイルをごちゃごちゃいじっていきます。

とりあえずコンパイルしてみる

何もせずに動くならそれに越したことはないので、とりあえずコンパイルできる状態へ持っていきます。
なお、今回は個人的な趣味によりNetBeansで色々いじっていきます。

まずは HandyAR/ 以下のファイルを、ディレクトリごとプロジェクト直下にコピーしてきます。
また、上では HandyAR/ 以下だけで大丈夫そうと書きましたが、一応 ARTag/ と Capture/ もコピーしておきます。

コピーが済んだので、インクルードパスを通して、適当なファイルを開いてみると…


おや?

エラーがたくさんある上にインクルード文からも警告が…

インクルードファイル開くと…

 global.hも必要ですか…orz
ということでこれもコピー。
そして再度確認!


今度はglobal.h内部がおかしいようです。
global.hの中を覗くと…


OpenCVとglut関係でエラー吐いてます。
ただ、現時点で自分の環境にインストール済みのOpenCVはバージョン2.3なのですが、このヘッダ指定はどう見ても1.xのそれなので、激しく中身を書き換えたい衝動に…
ついでにデモ用に書かれたであろうヘッダ(cvFindExtrinsicCameraParams3.h)まであるので、うーんって感じです。

なんですが、今日はコンパイルさえ通ればよいので、バージョン古くても問題なし。
早速インクルードパスをOpenCVとglutに通し、cvFindExtrinsicCameraParams3.cpp/.hをプロジェクト直下に追加。
さらに再度確認。



ということで、無事通りました。
残りのファイルもエラー出てないな…



…と思ってたら、


エラーだらけ…orz
Capture/ 以下のFlyCap.cpp/.hがどうも他のライブラリに依存しているような感じですね。

ここで、同梱されていたドキュメント(DemoHandyAR Documentation.pdf)を読んでみると、

 Capture/
- In this directory, Capture.h and cpp files are used to capture video from a camera using OpenCV functions (and PointGrey’s SDK). In order to use PointGrey’s SDK, you need to include FlyCap.h and cpp files in the same directory into your VC++ project.
Capture/
- このディレクトリには、OpenCV(とPointGrey's SDK)を利用し、カメラからビデオキャプチャを行うために必要なCapture.h と cppファイルがあります。PointGrey's SDKを使うには、同じディレクトリのFlyCap.h と cppファイルをVC++プロジェクトに追加する必要があります。

とのこと。

ひとまずキャプチャはOpenCVだけで十分だと思いますので、上で出たエラーは無視できるようですね。
というか、調べてみるとPointGrey's SDKと言っているのは、FlyCapture SDKというものらしいんですが、どうもMacOSには対応してないみたいです。
なのでいずれにせよ上記のエラーは無視するしかないようです。南無。



なにはともあれ、これでようやく全体のコンパイルが終了(2012/4/4追記:全然コンパイルできてませんでしたw 詳細はその2へ)ということで、次回は実際に何か動かすところまで持っていこうとおもいます。
そういえばHandyARのライセンス関係ってどうなってるんだろう