0

opencv-haartrainingの進行状況をtwitterに流すbot

OpenCVをソースからビルドするとhaarlike分類器(顔認識などに使われているやつ)の学習ツールが手に入るんだけど、たくさんのマシンでたくさん学習させているとそれぞれの進行状況をチェックするのが面倒になってくる。

でも、入力した画像ファイルが壊れていると学習が強制終了してしまったり、データがばらつきすぎてて収束しなくてあきらめて終了されたりするので、プロセスが死んでいたらパラメータを直してすぐやり直しをさせたい。学習中は予断を許さない状況が続く。

なので、進行状況を監視してtwitterアカウントshokai_logにpostするbotを作った。
5分間隔でopencv-haartrainingの作業ディレクトリとプロセスが生きているかをチェックする。
学習stageが進む毎に適当に通知し、プロセスが強制終了していた場合は激しくreplyしてくれる。これで安心して寝れる。OpenCV1.0/2.0両方対応。

プロセスが落ちていると教えてくれたり、段階が進む毎にtwitterに投稿したりする。
一見何言ってるのかわかりにくいpostもあるが、「ドドドド」だったらstage4が終わったという意味。
tweet-haartraining.rb
辞書はコード内にある。


第2引数にopencv-haartrainingの-dataオプションで渡した「結果の書き出し先ディレクトリ名」を指定する。第3引数は無しでもいいが、twitter投稿の末尾にメモを付けられる。複数のマシンで実行していてどれの進行状況かわからなくなる時は、マシンの名前を入れておけばいい。
ruby tweet-haartraining.rb /Users/sho/path/to/training/dir/ "Macbook黒"

tweet-haartraining.rb
#!/usr/bin/env ruby
# -*- coding: utf-8 -*-
require 'rubygems'
require 'twitter'

# setting
USER = 'your-account'
PASS = 'your-password'
INTERVAL = 300 # sleep sec
YOU = 'shokai' # 時々replyしてくる nilでreplyなし
NOPOST = false # debug用

def post(message)
  return if !message
  message = "@#{YOU} #{message}" if rand(3)<1 if !(message =~ /@#{YOU}/) && rand(2)<1
  puts message + "\t" + Time.now.to_s
  return if NOPOST
  httpAuth = Twitter::HTTPAuth.new(USER, PASS)
  tw = Twitter::Base.new(httpAuth)
  tw.update(message)
end

if ARGV.size < 1
  puts '結果が出力されるディレクトリへのパスが必要です。メモも付けられます(オプション)'
  puts 'e.g. ruby tweet-haartraining.rb /path/to/haar/training/dir/ "研究室の学習用パソコン"'
  exit 1
end

puts path = ARGV.shift
memo = ARGV.shift || ""
dir_path = path
if path =~ /\/$/
  dir_path = path
  xml_path = path[0...path.size-1]+'.xml'
else
  dir_path = path+'/'
  xml_path = path+'.xml'
end


if stage_p = Dir.glob(dir_path+'*').delete_if{|i| File::ftype(i) != 'directory'}.map{|i| i.split(/\//).last.to_i}.max
  post "ステージ#{stage_p}から開始" + " " + memo
else
  messages = ["開始。",
              "はじめ",
              "起床",
              "おきた",
              "start",
              "スタートしました",
              "hello world",
              "hello work",
              "はじめますわっ",
              "スタンバイレディ セタップ"]
  post messages[rand(messages.size)] + " " + memo
end


while true do
  sleep INTERVAL
  stage = Dir.glob(dir_path+'*').delete_if{|i| File::ftype(i) != 'directory'}.map{|i| i.split(/\//).last.to_i}.max
  if File.exists? xml_path
    messages = ["全行程完了(ステージ#{stage})。お疲れ様でした。",
                "全部オワタ(#{stage})",
                "修了しました",
                "寝る。#{stage}時に起きる。",
                "終わったので、#{stage}時に帰ります",
                "全段階完了しました。データを回収し、電源を落としてください(#{xml_path.split(/\//).last})",
                "全ステージ完了しました(#{xml_path.split(/\//).last})",
                "ⓢⓤⓨⓐⓡⓘ"]
    post "@#{YOU} " + messages[rand(messages.size)] + " " + memo
    exit 0
  end
  if nil == `ps aux | grep opencv-haartraining`.split(/[\r\n]/).delete_if{|m|m=~/grep opencv-haartraining/}.first
    messages = ["#{stage}段階目まで来たけど異常終了したかも",
                "落ちてる",
                "ERROR! haartraining is not working. please restart \(^o^)/",
                "異常終了",
                "異常です",
                "動いてないっぽい・・・",
                "死んだかも",
                "だめっぽい・・",
                "おい、異常終了してるぞ",
                "冒 険 の 書 (#{stage}) は 消 え ま し た",
                "おお、死んでしまうとは情けない",
                "\(^o^)/"*stage,
                "ピッコロの気が消えた",
                "なん・・だと・・",
                "#{stage}面でピチュった"]
    post "@#{YOU} " + messages[rand(messages.size)] + " " + memo
    sleep INTERVAL*2
    next
  end
  next if stage == stage_p or stage == nil
  stage_p = stage
  messages = ["#{stage}段階目まで進みました",
              "バリバリです(stage#{stage})",
              "ばっちりですわっ",
              "------ここまで読んだ(#{stage})------",
              "がんばってます(#{stage})",
              "stage #{stage}",
              "ステージ#{stage}なう",
              "now finished stage#{stage}.",
              "よし!ステージ#{stage}まで終わった!!!",
              "うわ"+"あ"*stage,
              "ド"*stage,
              "ゴ"*stage,
              "ゴ"+"ー"*stage,
              "もりもり",
              "ふむふむなるほど"+"・"*stage,
              "頭が"*stage+"おかしくなりそうだ",
              "もういや",
              "無理"*stage,
              "ズザ"+"ー"*stage,
              "帰りたい",
              "まだ#{stage}段階目だ",
              "もうstage#{stage}まで終わった。超はやい",
              "もうstage#{stage}まで終わった",
              "stage#{stage}まで終わった",
              "stage#{stage}まで終わったし",
              "ククク・・遂に#{stage}界までまで昇ってきたか・・・",
              "ⓢⓤⓨⓐ"*stage]
  
  post messages[rand(messages.size)] + " " + memo
end

1

apache2のサブディレクトリをthinで起動してるsinatraにプロキシする

SinatraやRailsは開発は楽だけどデプロイ方法がいろいろあって悩む。
最近はpassengerを使う方法が流行ってるけど、あいにく学校に置いたサーバーでサブドメインが使えないので同一ホスト名でapacheと共存させるしかない。

passengerでもRailsBaseURIだったか?を設定すればサブディレクトリでRack対応アプリを動かせるが、
今回は1年前からRails+mongrelのデプロイに使っているapache2のサブディレクトリをmongrelで起動してるrailsにプロキシする – 橋本詳解と同じ方法でSinatra+Thinをデプロイした。
Railsではmongrel_clusterで複数起動させたrailsにapache2のmod_proxy_balancerでアクセスを割り振ったが、同じ設定でsinatra+thinにも割り振れる。

結果、橋本商会 Twitterの地名なうbotを全blockするOAuthアプリで作ったアプリのURLをport番号むきだし状態から http://shokai.mag.keio.ac.jp/block_nowbots/ という良いURLに変更できた。ちゃんと他のrailsアプリとも共存できてる
今は10個起動したthinにapacheがアクセスを割り振っている。


■thinでsinatraを動かす
まず単純にthinを1プロセスだけ起動してsinatraが動くかどうかチェックしておく

sudo gem install thin

起動にはアプリ本体と、config.ruが必要

main.rb
#!/usr/bin/env ruby
require 'rubygems'
require 'sinatra'
require 'rack'

get '/' do
end
thinから起動するためにrackをrequireしておく。


config.ru
require 'rubygems'
require 'sinatra'

require 'main.rb'
run Sinatra::Application


logやpid、静的ファイルを置く場所を作る
mkdir log
mkdir public
mkdir tmp
mkdir tmp/pids


まず普通に起動させてみる。とくにpublicやtmpディレクトリの位置を指定する必要ない。ディレクトリがあればそれぞれの機能は有効化されるし無くても特にエラーは出ないみたい
thin start -R config.ru
ctrl+Cで終了。



■apache2のmod_proxy_balancerでアクセスをthinに振り分ける

mod_proxy有効化
sudo a2enmod proxy_balancer 
sudo a2enmod proxy
sudo a2enmod proxy_http


/etc/apache2/mods-available/proxy.conf
<IfModule mod_proxy.c>
ProxyRequests Off

<Proxy *>
Order deny,allow
Allow from all
</Proxy>

</IfModule>


/etc/apache2/conf.d/your-app-name.conf
ProxyPreserveHost Off
ProxyRequests Off

<Proxy *>
Order deny,allow
Allow from all
</Proxy>

ProxyPass /appname balancer://appname
ProxyPassReverse /appname balancer://appname

<Proxy balancer://appname>
BalancerMember http://127.0.0.1:4040 loadfactor=10
BalancerMember http://127.0.0.1:4041 loadfactor=10
BalancerMember http://127.0.0.1:4042 loadfactor=10
BalancerMember http://127.0.0.1:4043 loadfactor=10
BalancerMember http://127.0.0.1:4044 loadfactor=10
BalancerMember http://127.0.0.1:4045 loadfactor=10
BalancerMember http://127.0.0.1:4046 loadfactor=10
BalancerMember http://127.0.0.1:4047 loadfactor=10
BalancerMember http://127.0.0.1:4048 loadfactor=10
BalancerMember http://127.0.0.1:4049 loadfactor=10
</Proxy>
こうすると
http://hostname:4040/ から http://hostname:4049/までで動いているsinatraアプリが、
http://hostname/appname/ として外からは見えるようになる。

apache2再起動
sudo /etc/init.d/apache2 restart

今後は新しいアプリを作る毎にこの your-app-name.conf を雛形にして conf.d の中に配置してapache2再起動するだけでいい。




■thinを複数起動させる
thin+sinatraを複数起動させて管理しやすくする設定ファイルを作り、mongrel_clusterみたいにする。
main.rb, config.ruと同じディレクトリにthin.ymlを置く

thin.yml
timeout: 30
log: log/thin.log
environment: production
servers: 10
daemonize: true
rackup: config.ru
port: 4040


起動
thin start -C thin.yml
これでport 4040から10(つまり4049まで)まとめてdaemonとして起動する。

停止、再起動コマンドもある。
thin stop -C thin.yml
thin restart -C thin.yml



最終的なファイルの配置こうなる。 *.logと*.pidはthinが自動生成したもの。jsや画像はpublicの下に置く。
|-- config.ru
|-- log
| |-- thin.4040.log
| |-- thin.4041.log
| |-- thin.4042.log
| |-- thin.4043.log
| |-- thin.4044.log
| |-- thin.4045.log
| |-- thin.4046.log
| |-- thin.4047.log
| |-- thin.4048.log
| `-- thin.4049.log
|-- main.rb
|-- public
| `-- shokai.jpg
|-- thin.yml
`-- tmp
`-- pids
|-- thin.4040.pid
|-- thin.4041.pid
|-- thin.4042.pid
|-- thin.4043.pid
|-- thin.4044.pid
|-- thin.4045.pid
|-- thin.4046.pid
|-- thin.4047.pid
|-- thin.4048.pid
`-- thin.4049.pid

0

Twitterの地名なうbotを全blockするOAuthアプリ

http://shokai.mag.keio.ac.jp/block_nowbots/から使える。
(8月15日:URL変更しました

とりあえずSinatraとOAuthの組み合わせを試してみたかったので、DBは使わないものを作りたかった。取得したOAuthのtokenなどはその場で捨てている。blockコマンドを送る権限だけを一時的に委譲してもらう。


以下技術的なことのメモ。
■OAuthアプリの登録
まずhttp://twitter.com/oauth_clientsでアプリを登録し、consumer keyとconsumer secretを取得する。
で、下のコード中のCONSUMER_KEY, CONSUMER_SECRETを書き換える。


■必要なライブラリ
必要なgemをインストールする。最新版にした。

sudo gem install oauth twitter sinatra
それぞれ0.3.5, 0.6.13, 0.9.4がインストールされた。
twitterは内部でoauthに依存していて、oauthはバージョン毎に関数がけっこう変わっている。このバージョンの組み合わせなら動く。


■動かす
そして起動。
ruby block-nowbots.rb -p 2692 -s mongrel
thinだと複数クライアントから同時にアクセスした時1クライアントずつしか対応してくれなかったんだけど、mongrelは全クライアントに同時に応答してくれた。mongrelにいつのまにかそういう機能がついたのか、sinatraのバグでthinがthread処理されないのかはよくわからない。
sinatraもrailsと同じくデプロイまわりを工夫した方がよさそう。passenger使うのがいいのかな?


参考:


block-nowbots.rb
#!/usr/bin/env ruby
require 'rubygems'
require 'sinatra'
require 'oauth'
require 'twitter'

HOST_AND_PORT = "localhost:2692"

CONSUMER_KEY = "your-consumer-key"
CONSUMER_SECRET = "your-consumer-secret"

BOTS = 'nishinipporinow,nippori_now,kanda_now,tabata_now,sugamo_now,komagome_now,shinokubo_now,mejiro_now,takadanobabanow,okachimachi_now,uguisudani_now,ikebukuro_now,otsuka_now,akiba_now,tokyo_now,harajuku_now,shibuya_now,shibuya_now,yoyogi_now,shinjuku_now,ebisu_now'

set :sessions, true
def consumer
  OAuth::Consumer.new(CONSUMER_KEY, CONSUMER_SECRET,
                      :site => "http://twitter.com")
end

template :layout do
  '<html>
     <body>
       <h1>地名なうbotを全blockするOAuthアプリ</h1>
       <%= yield %>
       <hr style="margin-top: 100px" />
       <a href="http://shokai.org">http://shokai.org</a><br /><a href="http://shokai.org/blog/archives/4698">解説</a>
     </body>
   </html>'
end

get '/' do
  @request_token = consumer.get_request_token(:oauth_callback => "http://#{HOST_AND_PORT}/auth")
  session[:request_token] = @request_token.token
  session[:request_token_secret] = @request_token.secret
  erb %{
    <p><%= BOTS %><br />をblock設定します</p>
    <p>OAuth認証してください</p>
    <a href="<%= @request_token.authorize_url %>">認証する!</a>
  }
end

get '/auth' do

  @request_token = OAuth::RequestToken.new(consumer,
                                          session[:request_token],
                                          session[:request_token_secret])
  @access_token = @request_token.get_access_token({},
                                                  :oauth_token => params[:oauth_token],
                                                  :oauth_verifier => params[:oauth_verifier])
  session[:access_token] = @access_token.token
  session[:access_token_secret] = @access_token.secret

  erb %{
    <p>認証成功</p>
    <p><a href="/block_now_bots">地名なうbotをblockする</a>(全部で<%= BOTS.split(",").size*3 %>秒くらいかかる)</p>
    <a href='/'>戻る</a>
  }
end

get '/block_now_bots' do
  oauth = Twitter::OAuth.new(CONSUMER_KEY, CONSUMER_SECRET)
  oauth.authorize_from_access(session[:access_token], session[:access_token_secret])
  twit = Twitter::Base.new(oauth)
  BOTS.split(",").each{|bot|
    twit.block(bot)
    puts "block #{bot}"
    sleep 3
  }
  erb %{
    <p>たぶんblockしました</p>
    <p><a href="http://twitter.com/yamanote_now/following">確認</a></p>
  }
end

0

Flickrダウンローダを作った

Rubyで。ダウンロード部分はwgetに投げているのでUnix系でしか動かせないかもしれない。REXML::XPath使うのの勉強になった。

shokai / Flickr downloader / overview — bitbucket.org

タグ検索してユーザ指定したりもして(権限があれば)オリジナル画像を一括ダウンロードできる。


実行例

flickr-download beer,food 51753258@N00

で俺がbeerとfoodをタグを付けた写真が全部取ってこれて空腹を紛らわせれる。51753258@N00のところはユーザIDで、各ユーザぺージのRSSのURLを見ればわかる。


オリジナル画像を手に入れるためにFlickr APIのphotos.searchとphotos.getInfoのラッパーを実装した

0

ahokaiに適当にtimelineの空気を読んで発言する機能、NGワード機能を追加、gemのアップデートに対応など


ahokaiという、俺の発言を収集・再構築して喋るtwitter botに

  • ruby twitter gem0.6あたりから認証方法が変わったので、現最新版(0.6.6)に対応
  • タイムラインに含まれる名詞の出現回数をチェックしてその単語から文章を作る
  • post前に発言を検閲し、NGワードが含まれていたら発言を再度作り直す
という機能追加を行った。
あとは、replyとNGユーザ機能を気の向いた時に付ける予定。


ahokaiについては橋本商会 スーパーボット大戦およびymrl.net – 裏Twitterへ。
勝手にbot化しても許してくれそうな人の発言を収集し、賛同者?の方々と共にすこしずつbotが増やしています。ありがとうございます。


■既にahokaiを動かしていて、バージョンアップしたい場合
http://bitbucket.org/shokai/bot-ahokai/ からhg pullしてhg upするか、*.rbファイルを最新版のソースコードに置き換えてください。
DBやconfig.yaml等は書き換えなくても動作します。
ruby twitter gemはアップデートして最新のものを使わなければ動きません

sudo gem update twitter –remote

windowsの場合はsudoはいりません。


—–(ここから下の機能は、設定しなくても今まで通り動くので面倒ならやらなくて良い)—–


■ruby twitter gemの認証方法の変更への対応
コンストラクタに渡す引数が変わってた → twitter gemのHTTPAuth – 橋本詳解


■タイムラインに含まれる名詞の出現回数をチェックしてその単語から文章を作る
Buzzwords.rbに実装した。
timelineをMeCabで名詞だけ抽出して、出現回数をチェックして適当に名詞を選びそこから左右にマルコフ連鎖を伸ばす。
以前実装したbuzztterのRSSから発言を作る機能を拡張したので一瞬で完成した。

config.yamlのbuzzratioという項目で頻度が設定できます。sample.config.yamlを参考にしてください。

# twitter user/pass to post
usernum : “3631571” # see your Feed of twitter
user : “username”
pass : “password”

## post with buzztter ratio
buzzratio : 0.3 # 30%

## search words for refav
#searchwords : [“ahokai”, “あほか”]

## block NG words
#blockngwords : “true”

## for debug
#nopost : “true”

0.0にすれば動作しなくなり、1.0にすると確実に空気を読みます。
なお、バズワードの取得元は自分のtimelineだけではなく、buzztterからもランダムに取得します。


■post前に発言を検閲し、NGワードが含まれていたら発言を再度作り直す
bot元にしたらbotが確実に犯罪予告しそうな人がいたので、安全な言葉を出すまで発言を再構築させて犯罪防止をする機能を追加。
ngwordsに正規表現を書いておく。

.*殺.*
.*死.*
.*糞.*
.*うんこ.*
.*爆破.*
.*爆発.*

安全な言葉を出すまで10回再提出させて、無理ならあきらめる仕様なのであまり多くすると何もしゃべれなくなる。

config.yamlで

## block NG words
#blockngwords : “true”


## block NG words
blockngwords : “true”

とコメントアウトを外せば検閲機能が有効になります。

検閲はpost3gram.rbの中で実装してます