(認証つきの)Proxyの背後でPlayframeworkを動かすには
(認証つきの)Proxyの背後でPlayframeworkを動かしたい場合、
http://www.playframework.org/documentation/2.0.1/Installing の手順に従ってPlayframeworkをインストールした後、
$ play new hogehoge
ではなく
$ play -Dhttp.proxyHost=192.168.0.1 -Dhttp.proxyPort=8080 -Dhttp.proxyUser=hoge -Dhttp.proxyPassword=fuga new hogehoge
などのように、playの引数でProxyの設定を付け加えれば良いよ!
Garb 0.9.2が動かなくなった
2012/01/12時点で、Garb 0.9.2が動かなくなりました。
Garbのソースは少し古くてGoogle Analytics Data Export API v2.3を前提にしていますが、どうもこの数日でこのv2.3がshutdownされた模様。
2011/12/5のChangeLogをみると、
This release also announces the deprecation of Version 2.3 and the upcoming shut down of the Account Feed in the Data Export API. In 6 months:
http://code.google.com/intl/en/apis/analytics/community/export_changelog.html
…
Requests to the version 2.3 JSON output will not be supported by the version 2.4 response and will return 404 Not Found status codes. If you need a JSON response, upgrade to Version 3.0.
…
とあり、6ヶ月の猶予も無くshutdownされた感じです。
実際Garb0.9.2を動かすと、Google Analytics Data Export APIを用いてProfileリストを取得する部分で下記のようなエラーが発生しています。
/usr/local/lib/ruby/gems/1.9.1/gems/garb-0.9.2/lib/garb/request/data.rb:39:in `send_request': "{\"error\":{\"errors\":[{\"domain\":\"global\",\"reason\":\"unsupportedOutputFormat\",\"message\":\"Unsupported Output Format\",\"locationType\":\"parameter\",\"location\":\"alt\"}],\"code\":400,\"message\":\"Unsupported Output Format\"}}" (Garb::Request::Data::ClientError) from /usr/local/lib/ruby/gems/1.9.1/gems/garb-0.9.2/lib/garb/management/feed.rb:22:in `response' from /usr/local/lib/ruby/gems/1.9.1/gems/garb-0.9.2/lib/garb/management/feed.rb:13:in `parsed_response' from /usr/local/lib/ruby/gems/1.9.1/gems/garb-0.9.2/lib/garb/management/feed.rb:18:in `entries' from /usr/local/lib/ruby/gems/1.9.1/gems/garb-0.9.2/lib/garb/management/profile.rb:12:in `all'
このエラーは https://www.google.com/analytics/feeds/datasources/ga/accounts/~all/webproperties/~all/profiles?alt=json というリクエストを投げた時にraiseされているので、ChangeLogにあるようにJSONフォーマットによる出力がshutdownされたことが直接原因のようです。
うーん、v3対応を書くかなぁ・・・
Gruffで折れ線グラフを描く際のTIPS
rubyには面白いGemがたくさんありますが、その一つにグラフを描くためのライブラリ
Gruff Graphs for Ruby | Ruby on Rails for Newbies
があります。
このGemを用いれば折れ線グラフや円グラフなどを簡単に描くことができますが、少し微妙な部分があります。それを回避するMonkeyPatchを紹介します。
検証したバージョン
- OS
- Mac OS X Lion 10.7.2
- imagemagick
- 6.7.1
- ruby
- 1.9.2p290
- rmagick
- 2.13.1
- gruff
- 0.3.6
インストール
imagemagickやrmagickを事前にインストールする必要があるため、少し手間がかかります。本家サイトや下記サイトなどを参考に、インストールしてください。
noanoa 日々の日記 : Mac OS X Lion に Ruby グラフ描画用 Gruff をインストールする
Gruffでグラフを書いてみよう - まめ畑
Gruffでグラフ!格好いいグラフを簡単に生成 - OneRingToFind
とりあえず描く
とりあえず、下記のようにすれば折れ線グラフが描けます。日本語を出すためには、FONT指定が必須です。
#!/usr/bin/env ruby # -*- encoding: utf-8 -*- require 'rubygems' require 'gruff' g = Gruff::Line.new g.font = "/Library/Fonts/Osaka.ttf" g.labels = {0=>'01/01', 3=>'01/04', 6=>'01/07'} g.data("ほげ", [1,2,3,4,5,4,3,2,1]) g.data("foooooooooooooooooooooooooooooooooooooooooooooo", [9,9,9,8,8,8,7,7,7]) g.data("bar", [3,2,1,3,2,1,3,2,1]) g.write('hoge.png')
y軸の最小値・最大値・増分を指定する
マニュアル等に従えば、minimum_value, maximum_value, y_axis_incrementを指定すればy軸の最小値・最大値・増分を指定できることになっています。
が、現バージョンではエラーになります。
#!/usr/bin/env ruby # -*- encoding: utf-8 -*- require 'rubygems' require 'gruff' g = Gruff::Line.new g.font = "/Library/Fonts/Osaka.ttf" g.minimum_value = 0 g.maximum_value = 15 g.y_axis_increment = 5 g.labels = {0=>'01/01', 3=>'01/04', 6=>'01/07'} g.data("ほげ", [1,2,3,4,5,4,3,2,1]) g.data("foooooooooooooooooooooooooooooooooooooooooooooo", [9,9,9,8,8,8,7,7,7]) g.data("bar", [3,2,1,3,2,1,3,2,1]) g.write('hoge.png')
/Users/cube/.rvm/gems/ruby-1.9.2-p290@rails_3.1.1/gems/gruff-0.3.6/lib/gruff/line.rb:112:in `normalize': wrong number of arguments (1 for 0) (ArgumentError) from /Users/cube/.rvm/gems/ruby-1.9.2-p290@rails_3.1.1/gems/gruff-0.3.6/lib/gruff/base.rb:687:in `draw_line_markers' from /Users/cube/.rvm/gems/ruby-1.9.2-p290@rails_3.1.1/gems/gruff-0.3.6/lib/gruff/base.rb:536:in `setup_drawing' from /Users/cube/.rvm/gems/ruby-1.9.2-p290@rails_3.1.1/gems/gruff-0.3.6/lib/gruff/base.rb:508:in `draw' from /Users/cube/.rvm/gems/ruby-1.9.2-p290@rails_3.1.1/gems/gruff-0.3.6/lib/gruff/line.rb:53:in `draw' from /Users/cube/.rvm/gems/ruby-1.9.2-p290@rails_3.1.1/gems/gruff-0.3.6/lib/gruff/base.rb:487:in `write' from ./grufftest.rb:20:in `<main>'
エラーメッセージは「line.rbにはひとつの引数を取れるnormalizeメソッドなんか無いぜ!」って言ってますが、line.rbのソースを見ると確かに引数をひとつ取れるnormalizeメソッドは存在しません。
下記の引数のないnormalizeメソッドのみが定義されています。
def normalize @maximum_value = [@maximum_value.to_f, @baseline_value.to_f].max super @norm_baseline = (@baseline_value.to_f / @maximum_value.to_f) if @baseline_value end
Lineクラスの親クラスであるBaseクラスを確認すると、def normalize(force=false)という引数有りメソッドが定義されていますが、このメソッドはprotectedです。
つーことで、サクっとline.rbにMonkey Patchを当てることにします。GemのソースをForkして修正してローカルインストールして・・・ ってやっても良いですが、小さい修正ならばrubyのオープンクラス特性を利用してMonkey Patchを当てたほうが早かったりします。
# ./monkey_patch/gruff/line.rb class Gruff::Line def normalize(force=false) @maximum_value = [@maximum_value.to_f, @baseline_value.to_f].max super(force) @norm_baseline = (@baseline_value.to_f / @maximum_value.to_f) if @baseline_value end end
Gemをrequireした後にこのMonkey Patchをrequireし、Gruff::Lineクラスにnormalize(force=true)メソッドを追加します。
・・・ require 'rubygems' require 'gruff' require './monkey_patch/gruff/line' ・・・
凡例を左揃えにする
gruffは凡例に表示する文字列の長さと描画領域の横幅を考慮して、凡例をオーバーラップしてくれます。これはこれですばらしいのですが、文字列の長さが(この例のように)大幅に異なる場合、なんだか気持ち悪い見た目になります。ついでに、このオーバーラップ計算を強制的にキャンセルしてみます。
凡例はGruff::Baseクラスのdraw_legendメソッドで描画されます。デフォルトのこのメソッドをundefし書き換えることにします。
# ./monkey_patch/gruff/base.rb module Gruff class Base undef draw_legend def draw_legend return if @hide_legend @legend_labels = @data.collect {|item| item[DATA_LABEL_INDEX] } legend_square_width = @legend_box_size # small square with color of this item # May fix legend drawing problem at small sizes @d.font = @font if @font @d.pointsize = @legend_font_size label_widths = [[]] # Used to calculate line wrap @legend_labels.each do |label| metrics = @d.get_type_metrics(@base_image, label.to_s) label_width = metrics.width + legend_square_width * 2.7 label_widths.last.push label_width if sum(label_widths.last) > (@raw_columns * 0.9) label_widths.push [label_widths.last.pop] end end current_x_offset = 50 current_y_offset = @hide_title ? @top_margin + title_margin : @top_margin + title_margin + @title_caps_height @legend_labels.each_with_index do |legend_label, index| # Draw label @d.fill = @font_color @d.font = @font if @font @d.pointsize = scale_fontsize(@legend_font_size) @d.stroke('transparent') @d.font_weight = NormalWeight @d.gravity = WestGravity @d = @d.annotate_scaled( @base_image, @raw_columns, 1.0, current_x_offset + (legend_square_width * 1.7), current_y_offset, legend_label.to_s, @scale) # Now draw box with color of this dataset @d = @d.stroke('transparent') @d = @d.fill @data[index][DATA_COLOR_INDEX] @d = @d.rectangle(current_x_offset, current_y_offset - legend_square_width / 2.0, current_x_offset + legend_square_width, current_y_offset + legend_square_width / 2.0) @d.pointsize = @legend_font_size metrics = @d.get_type_metrics(@base_image, legend_label.to_s) current_string_offset = metrics.width + (legend_square_width * 2.7) line_height = [@legend_caps_height, legend_square_width].max + legend_margin current_y_offset += line_height @graph_top += line_height @graph_height = @graph_bottom - @graph_top end @color_index = 0 end end end
こいつをrequireすると
・・・ require 'rubygems' require 'gruff' require './monkey_patch/gruff/base' require './monkey_patch/gruff/line' ・・・
凡例が左揃えになります。
3DモデルがアニメーションするARをOpenGL ESで作るには
という記事を@ITに寄稿しました。
http://www.atmarkit.co.jp/fsmart/articles/armobile04/01.html
このシリーズは私のチームメンバーが持ち回りで書いているのですが、段々マニアックになっていくという・・・
NodeとMeCabを用いたtweet連想ゲーム
JavaScript Advent Calendar 2011 (Node.js/WebSocketsコース)の17日目です。
ちょっと目先を変えて、MeCabの形態素解析とWikipedia見出し辞書を利用したtweet連想ゲームについて書きます。
はじめに
このMeCabの形態素解析を用いたtweet連想ゲームはもともと、1日目を書かれている@hakoberaさんが主催されているNode塾の講義その3 Node.js を使ったコードをもくもく書く会で作ったものです。せっかくなのでリファクタリングして公開しますデス。
そもそもMeCabって?
MeCabは 京都大学情報学研究科−日本電信電話株式会社コミュニケーション科学基礎研究所 共同研究ユニットプロジェクトを通じて開発されたオープンソース 形態素解析エンジンです. 言語, 辞書,コーパスに依存しない汎用的な設計を 基本方針としています. パラメータの推定に Conditional Random Fields (CRF) を用 いており, ChaSenが採用している 隠れマルコフモデルに比べ性能が向上しています。また、平均的に ChaSen, Juman, KAKASIより高速に動作します. ちなみに和布蕪(めかぶ)は, 作者の好物です.
http://mecab.sourceforge.net/
だそうです。自然言語を形態素に分解して、各語の原型や品詞、活用形などを教えてくれます。
今回は「出発単語 → 単語をtwitter searchで検索 → 得られた本文を形態素解析 → 名詞を抽出し、ランダムに次の検策語を決定 → 単語をtwitter searchで検索・・・」という処理の形態素解析部分でMeCabを使います。
MeCabとIPA辞書をインストール
最新バージョンをutf-8で使いたいので、http://mecab.sourceforge.net/#installあたりを参考に、現時点の最新版0.98をソースからインストールします。
$ wget http://sourceforge.net/projects/mecab/files/mecab/0.98/mecab-0.98.tar.gz $ tar zxfv mecab-0.98.tar.gz $ cd mecab-0.98/ $ ./configure --with-charset=utf8 $ make $ sudo make install $ sudo ldconfig
$ wget http://sourceforge.net/projects/mecab/files/mecab-ipadic/2.7.0-20070801/mecab-ipadic-2.7.0-20070801.tar.gz $ tar zxfv mecab-ipadic-2.7.0-20070801.tar.gz $ cd mecab-ipadic-2.7.0-20070801/ $ make $ sudo make install
nodeのMeCabバインディングのインストール
探してみたらやっぱり、 Node用MeCabバインディング あと非同期版つくろうとして失敗した話 - mizchi logがありました。ありがとう、偉大な先達たち!
ただ紹介されているコードのままだと、いくつか問題があるため、index.jsに修正を加えました。実際のソースコードは、nmatsui/node-mecab - GitHubから取得してください。
- requireするディレクトリの問題で0.6系のnodeで動かない => 0.4系の「build/default」が存在しない場合、 0.6系の「build/Release」からrequireする
- MeCabをインスタンス化する際にオプションが指定できない => オプションをつけたMeCabインスタンスを生成できる公開メソッドを追加する
$ git diff diff --git a/index.js b/index.js index f00c7ed..639f5a9 100644 --- a/index.js +++ b/index.js @@ -1,7 +1,24 @@ -var MeCab = new require('./build/default/mecab'); +var MeCab +try { + MeCab = new require('./build/default/mecab'); +} +catch (e) { + if (e.message.match('Cannot find module')) { + MeCab = new require('./build/Release/mecab'); + } + else { + throw e; + } +} + exports.MeCab = MeCab; var nomal = new MeCab.Tagger(); + +exports.options = function(opt) { + nomal = new MeCab.Tagger(opt); +} + exports.parse = function(text) { var buf, i, row, _i, _len; row = nomal.parse(text).split("\n");
実験実験!
$ git clone git://github.com/nmatsui/node-mecab.git $ cd node-mecab/ $ npm install . $ npm link $ cd ../tweet-associate/ $ npm link mecab $ node > mecab = require('mecab'); { MeCab: { Tagger: [Function: Tagger] }, parse: [Function] } > console.log(mecab.parse('すもももももももものうち')); [ [ 'すもも', '名詞', '一般', '*', '*', '*', '*', 'すもも', 'スモモ', 'スモモ' ], [ 'も', '助詞', '係助詞', '*', '*', '*', '*', 'も', 'モ', 'モ' ], [ 'もも', '名詞', '一般', '*', '*', '*', '*', 'もも', 'モモ', 'モモ' ], [ 'も', '助詞', '係助詞', '*', '*', '*', '*', 'も', 'モ', 'モ' ], [ 'もも', '名詞', '一般', '*', '*', '*', '*', 'もも', 'モモ', 'モモ' ], [ 'の', '助詞', '連体化', '*', '*', '*', '*', 'の', 'ノ', 'ノ' ], [ 'うち', '名詞', '非自立', '副詞可能', '*', '*', '*', 'うち', 'ウチ', 'ウチ' ] ] undefined >
おけ!
tweet連想ゲーム 初版
ということでMeCabのNodeバインドを用い、とある単語を含むtweetを検索し、その本文に含まれる単語から次のtweetを探し出すシンプルなコンソールアプリを書いてみました。
ただあまりにヘンな単語で検索しても楽しくないので、次の単語を連想する際に
- 検索する単語の品詞は名詞のみ
- 前回検索した単語と同じ単語は使わない
- 1文字の単語は使わない
- アルファベット、数字、記号のみの単語は使わない
という縛りを入れています。
#!/usr/bin/env nod var http = require('http'); var qs = require('querystring'); var colors = require('colors'); var argv = require('optimist').argv; var mecab = require('mecab'); var initialWord = argv.w || "javascript"; function findNoun(text, searchWord) { var nouns = mecab.parse(text).filter(function(morpheme) { return typeof morpheme[0] != 'undefined' && morpheme[1] == '名詞' && morpheme[0].length >= 2 && morpheme[0] != searchWord && !/^[!-~]+$/.test(morpheme[0]) }).map(function(morpheme) { return morpheme[0]; }); return nouns[Math.floor(Math.random()*nouns.length)]; } function tweetAssociate(searchWord) { http.get({ host: "search.twitter.com", port: 80, path: "/search.json?" + qs.stringify({ q : searchWord, rpp : "10", lang: "ja" }) }, function(response) { var body = ""; response.on('data', function(data) { body += data; }); response.on('end', function() { var results = JSON.parse(body).results; if (results.length == 0) { console.log(searchWord + "のようなマニアックな単語はダメ!"); setTimeout(function() { tweetAssociate(initialWord) }, 1000); } else { var tweet = results[Math.floor(Math.random()*results.length)]; var nextWord = findNoun(tweet.text, searchWord); console.log(searchWord.bold.red + " => " + nextWord.bold.blue + "\n"); console.log("@" + tweet.from_user); console.log(tweet.text.replace(searchWord, searchWord.bold.red) .replace(nextWord, nextWord.bold.blue)); console.log("===="); setTimeout(function() { tweetAssociate(nextWord) }, 5000); } }); }); } tweetAssociate(initialWord);
$ node ./tweet-associate. -w 連想
って感じで起動すると、"連想"という単語から出発してtweet連想ゲームが勝手に進行します。
tweet連想ゲーム Wikipedia見出し辞書追加版
上記の初版でも動きますが、連想する単語はかなり短いものばかりです。固有名詞はほとんど抽出できませんし、複合語もうまく連想できません。そもそもMeCabの辞書に載っていないのですから、そりゃ無理ってものです。
そこでMeCab: 単語の追加方法やMeCab の辞書構造と汎用テキスト変換ツールとしての利用を参考に、Wikipediaの見出し語をMeCabユーザー辞書として追加しましょう。このテキストファイルには、12/17時点で125万語ほどの見出し語が収集されています。
$ mkdir dic $ cd dic $ wget http://dumps.wikimedia.org/jawiki/latest/jawiki-latest-all-titles-in-ns0.gz $ gunzip jawiki-latest-all-titles-in-ns0.gz
取得したWikipedia見出し語を、MeCab辞書として取り込める形式へ変換します。全て一般名詞扱いのため、活用などなく簡単です。
ただし重要なのは、語のコストです。なるべく長いキーワードを優先したいので、長いキーワードほど小さな値になるようにします。
#!/usr/bin/env ruby # -*- encoding: utf-8 -*- ARGF.each do |word| word.chomp! if word.length >= 2 && /^[!-~]+$/.match(word).nil? && /^.*(,|").*$/.match(word).nil? cost = [-36000, -400 * word.length**1.5].max.to_i puts "#{word},0,0,#{cost},名詞,一般,*,*,*,*,#{word},*,*" end end
このスクリプトでWikipedia見出し語辞書の元ネタを作り、MeCabが認識できるバイナリ形式へコンパイルします。このWikipedia見出し語辞書を有効にしてmecabを実行してみると・・・
$ ./conv.rb jawiki-latest-all-titles-in-ns0 > wikipedia.csv $ /usr/local/libexec/mecab/mecab-dict-index -d /usr/local/lib/mecab/dic/ipadic -u wikipedia.dic -f utf-8 -t utf-8 wikipedia.csv $ mecab -u wikipedia.dic すもももももももものうち すもももももも 名詞,一般,*,*,*,*,すもももももも,*,* もも 名詞,一般,*,*,*,*,もも,*,* の 助詞,連体化,*,*,*,*,の,ノ,ノ うち 名詞,非自立,副詞可能,*,*,*,うち,ウチ,ウチ EOS
と、ノーマルなIPA辞書とは違いすももももも - Wikipediaを名詞として認識するようになりました。
nodeから追加辞書を指定して実行する場合、optionsメソッドを使います。
$ node > mecab = require('mecab'); { MeCab: { Tagger: [Function: Tagger] }, options: [Function], parse: [Function] } > mecab.options("-u dic/wikipedia.dic"); undefined > console.log(mecab.parse('すもももももももものうち')); [ [ 'すもももももも', '名詞', '一般', '*', '*', '*', '*', 'すもももももも', '*', '*' ], [ 'もも', '名詞', '一般', '*', '*', '*', '*', 'もも', '*', '*' ], [ 'の', '助詞', '連体化', '*', '*', '*', '*', 'の', 'ノ', 'ノ' ], [ 'うち', '名詞', '非自立', '副詞可能', '*', '*', '*', 'うち', 'ウチ', 'ウチ' ] ] undefined >
最後に、コンソールアプリを修正します。といっても、コマンドラインからMeCab用のオプションを取れるようにするだけです。
$ diff -u tweet-associate.js tweet-associate2.js --- tweet-associate.js 2011-12-17 13:09:32.166163004 +0900 +++ tweet-associate2.js 2011-12-17 15:47:59.896162998 +0900 @@ -6,7 +6,9 @@ var mecab = require('mecab'); var initialWord = argv.w || "javascript"; - +if (argv.u) { + mecab.options("-u " + argv.u); +} function findNoun(text, searchWord) { var nouns = mecab.parse(text).filter(function(morpheme) { return typeof morpheme[0] != "undefined" &&
では、実行してみましょう。なんとなく、長い複合語もとれるようになったと思います。
$ node ./tweet-associate2.js -w ゲーム -u `pwd`/dic/wikipedia.dic
最後に
MeCab楽しいですね!
今回は次の単語を連想する際に名詞をランダムに選んでいましたが、元の検索語とのレーベンシュタイン距離や単語長を元に「イイカンジの単語」を選ぶようにするともっと面白いかもしれません。が、力尽きました・・・orz
明日は、砂波幹雄さんです!
jQuery UI Datepicker の小ネタ
jQuery UI Datepicker の小ネタは山ほど公開されていますが、とある案件用に実験したヤツを公開してます。
Kinect + Node で似非ストリーミング配信
KinectとNode.jsを用いた似非ストリーミング配信
のブログを、ウチの会社で公開している技術ブログ Tech-Sketch - http://tech-sketch.jp/ に書いてます。
- Kinect Hack 映像をNode.jsで擬似ストリーミング・・・まだまだだね(1) - Tech-Sketch
- Kinect Hack 映像をNode.jsで擬似ストリーミング・・・まだまだだね(2) - Tech-Sketch
そうか、Node 0.4.11 の時代だったのか・・・
続きを実験する時間がナカナカ取れないなぁ