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以外になったら、入っている文字列が最終的なビルド結果
という感じで必要な情報は全部取れそうなので、この流れで実装すれば大丈夫そうです。

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