PhantomJSでスクレイピングするのがいいのかわるいのか?

PhantomJSを触ってみたけど、スクレイピングというか自動化するならWWW::Mechanize::Firefoxのが使いやすいなぁと思った。

CasperJSを使うのがよさそうなんだけど、node.jsから使えないしなぁ。

例えばデータベースからurlリストを引っ張ってきて次々にアクセスしたいといった用途の場合、PhantomJSだったらphantomjs-nodeを使うらしいんだけどCasperJSはそういうことができるんだろうか?

と思って調べたら、SpookyJSというものを見つけたんだが、PhantomJSが1.8からwebdriverをサポートするようになったからそっち使えみたいな感じになっている。

結局pythonでselenium使うのが正解なのか?

ていうかseleniumでいいじゃんみたいな気分になっている。

Pythonで真偽値

Flaskのsessions.pyを読んでいたら真偽値を入れるところでboolを使っていて(32行目くらい)なるほどと思った。

def _set_permanent(self, value):
    self['_permanent'] = bool(value)

0,1は思った通り

>>> bool(0)
False
>>> bool(1)
True

文字列も直感通り

>>> bool("somthing")
True
>>> bool("")
False

でもこれはダメ

>>> bool("false")
True
>>> bool("true")
True

A/Bテストを超え、学習しながらウェブを最適化させる手法 (Bandit Algorithms for Website Optimization)

ふと気になったので読んでみたら、当たりをひいた。

強化学習をウェブサイトの最適化に利用する方法に関しての本で、A/Bテストの何が問題かを説明してそれを克服するためのアルゴリズムを3つ紹介している

  • Epsilon-greedy
  • SoftMax
  • UCB1

コードはPythonで書かれているので読みやすい。

ProductName Bandit Algorithms for Website Optimization
John Myles White
Oreilly & Associates Inc / 2000円 ( 2012-12-31 )


実際のビジネスでは、A/Bテストで等確率でAB振り分けるために劣っている方のテストの分だけ収益が減ってしまうし、かといってテストをしないと、よりよいサイトを見出す機会がなくなってしまう。つまりexploreを最大化するか、exploitを最大化するかというようなジレンマを抱えることになる。

求められているのは、劣っているサイトデザインに対するテスト(損失)を最小にしつつベストなサイトデザインに収斂していく手法である。そういう問題をMultiarmed Bandit Probremと呼ぶらしく、上の3つはそういった問題を解くためのよく知られたアルゴリズムだそうだ。

最初の2つはランダム性を使う(Softmaxは焼きなまし法だ)のに対し、UCBはつかわない。UCB1は明らかに期待を持てないオプション以外のものを選択しつつ、期待値を更新していくという方法で、駄目なオプションをドンドン脱落させていくイメージに近い。

UCBはちょっと前までコンピューター囲碁にも使われていたらしい。コメントで触れられているpdfがわかりやすい。

局面を上手にスコア化して期待値の高い手を選択していくという手法は結構面白いかもしれない。例えば創薬のプロジェクトを将棋とか囲碁のようなものと捉えて、これから合成しようとしている化合物群が、求めるプロファイルにどんだけ近づいているかとかそんな期待値をスコア化して、exploreとexploitをバランスよく取り入れるとかはやれるんじゃないかなぁ。

chembl_15をサポートしてバージョンもあげた(pychembldb)

先週バージョンが上がってスキーマもかなり変更されたchembl_15に対応させました。

ChEMBLはバージョンアップでおもに結合サイトまわりが強化されているみたいです。binding_siteとかpredicted_binding_domainみたいなデータが入っているのはドッキングシミュレーションを強く意識した作りになっているんでしょう。とはいえ、いい感じのOSSなドッキングシミュレーションソフトウェアがないから、遊びにくいかも。

スキーマに関して言えば、多対多はすっきりとした感じに変更されてたのでmapper書きやすくなっててよかったが、ちょいとハマったところをメモっておく

  • target_dictionaryとpredicted_binding_domainsがone-to-manyになってなくてactivityに紐付いてる
  • information_source doesn't exist in products table
  • USAN_STEMがautoでマップできないのでとりあえずコメントしてある

あとはテストケース書くのがめんどくさかったので自動生成するようにした。今回はクラスもある程度自動生成した(utils)。

doctest書かないといけないなぁと思った。

ChEMBLデータベースのPythonインターフェースを書いた

ちょっと欲しくなったので探してみたらpychemblを見つけた。

しかしメンテされてないようなのでフォークして更新しようかなと思い、ソースを眺めたら面倒くさそうな感じだったので、昨日一日使って新しいのを作ってみた。

declarativeでautoloadしているのでスキーマが変わってもメンテしやすいかなと思う。コーディングのほとんどの時間をテスト書くのに使ったので結構疲れた。それから、メタプログラミングはどうかなと思ったのでdbのuriは環境変数CHEMBL_URIで設定するようにしておいた(デフォルトはchemblのmysqlのインストールそのまま)。

chembl = ChEMBLDB(uri)とかやりたい場合にはFlask-SQLAlchemyのソースが参考になると思う。

ターゲットから活性値とInChiKeyをフェッチする

ターゲット名に含まれるアッセイ系から活性と対応する化合物の構造をとってくる。

from pychembldb import *
for target in chembldb.query(Target).filter_by(pref_name="Tyrosine-protein kinase ABL"):
    for assay in target.assays:
        for activity in assay.activities:
            print activity.published_value, activity.molecule.structure.standard_inchi_key

ジャーナルから活性値とInChiKeyをフェッチする

ジャーナル(doi)に含まれるアッセイ系から活性と対応する化合物の構造をとってくる。

from pychembldb import *
for journal in chembldb.query(Doc).filter_by(doi="10.1016/S0960-894X(01)80693-4"):
    for assay in journal.assays:
        for activity in assay.activities:
            print activity.published_value, activity.molecule.structure.standard_inchi_key

その他

ChEMBLのスキーマみれば大体わかるだろうし、その通りに動くでしょう。このスライドも参考になると思う。

余談ですが、Rubyだとbio-chemblがあるそうです。

今後やる予定のこと

  • とりあえずテーブル名をそのままクラス名にしたせいでlookupとかDictionaryとかついててダサいのでそこら辺の名前は変える(なのでクラス名とかメソッド名はまだ変わります)
  • relationが不完全なので追加していく
  • one-to-oneみたいなのもone-to-manyとしてrelation張っているせいでリストになっている部分は修正する(上だとmolecule.structures[0]とかださい)
  • pypiに登録する(pipでインストール出来ないと面倒くさい)

130128 追記

  • one-to-oneにしたのでmolecules[0]がmolecule
  • Dictionaryを除いたのでTarget,Moleculeになった

FlaskでRESTful

ちょっと色々あってSpine.jsからBackbone.jsに乗り換えることにした。で、REST-APIが使いたかったので、Flask-RESTfulで書いてみた。

from flask import Flask, request
from flask.ext.restful import Resource, Api

app = Flask(__name__)
api = Api(app)

todos = {}
i = 0

class TodoSimple(Resource):

    def get(self, tid):
        return {"tid": tid, "todo": todos[tid]}

    def put(self):
        global i
        tid = i
        i = i + 1
        todos[tid] = request.form['data']
        return {"tid": tid, "todo": todos[tid]}

    def post(self, tid):
        todos[tid] = request.form['data']
        return {"todo": todos[tid]}

    def delete(self, tid):
        deldata = todos[tid]
        del(todos[tid])
        return {"tid": tid, "todo": deldata}

api.add_resource(TodoSimple, '/', '/<int:tid>')

if __name__ == '__main__':
    app.run(debug=True)

curlで確かめた。

$ curl http://localhost:5000/ -d "data=Remember the Flask" -X PUT
{"tid": 0, "todo": "Remember the Flask"}
$ curl http://localhost:5000/ -d "data=Remember the Python" -X PUT
{"tid": 1, "todo": "Remember the Python"}
$ curl http://localhost:5000/0
{"tid": 0, "todo": "Remember the Flask"}
$ curl http://localhost:5000/0 -d "data=Remember the Sphinx" -X POST
{"todo": "Remember the Sphinx"}
$ curl http://localhost:5000/0
{"tid": 0, "todo": "Remember the Sphinx"}
$ curl http://localhost:5000/0 -X DELETE
{"tid": 0, "todo": "Remember the Sphinx"}
$ curl http://localhost:5000/0
{"status": 500, "message": "Internal Server Error"}

今回はMongoDBを使いたかったので Flask-RESTfulを検討しているのだけど、SQLAlchemyでいいんだったらFlask-RESTlessでいいかもしれない。メソッド自分で書かなくていいし。

Graph-indexing wavelet tree (gWT)のグラフ検索が超速い

高速文字列解析の流れで、タイミングよく統数研チャンネルの資料が公開されていて、最後のほうのスライド(65枚目)にグラフの類似度検索の実装としてgwtが紹介されていたので遊んでみた。

データベースは、ChEMBLから取ってきた。なぜpubchemじゃないかというと面倒なので略。

入力のフォーマットがgSpanとかGASTONと一緒だったので、昔書いたのを流用しようとしたんだが、昔書いたpythonスクリプトはどうもOBMolAtomIterあたりがバグっていて動かないようだ(OBMolBondIterも動かなかった。)

File "sdf2gsp", line 17, in convert
  for i,atom in enumerate(ob.OBMolAtomIter(mol)): 
File "/usr/local/Cellar/python/2.7.3/lib/python2.7/site-packages/openbabel.py", line 4499, in __init__
  if not self.iter.__bool__():
AttributeError: '_OBMolAtomIter' object has no attribute '__bool__'

しょうがないのでiterateしない版で書き換えた。

#!/usr/bin/env python
import sys
import pybel
import openbabel as ob

def convert(sdf):
    mols = pybel.readfile('sdf',sdf)
    for n, mol in enumerate(mols, 1):
        print "t # %d" % n
        for i in range(mol.OBMol.NumAtoms()):
            print "v %d %d " % (i+1, mol.OBMol.GetAtom(i+1).GetAtomicNum())
        for j in range(mol.OBMol.NumBonds()):
            b = mol.OBMol.GetBond(j)
            print "e %d %d %d" % (b.GetBeginAtomIdx(),
                                  b.GetEndAtomIdx(),
                                  b.GetBondOrder())

if __name__ == "__main__":
    sdffile = sys.argv[1]
    convert(sdffile)

ちなみにこの形式に名前ついているんだろうか?babelとかでサポートされないと使い勝手が悪いんだけどなぁ。

これで、ChEMBLの137万件をgspにコンバートしてみたけど遅すぎるので後でHaskellで書きなおす。sdf単に舐めるだけだからOBMolに変換する必要もないし。

できたchembl_14.gspからindexファイルをつくる。600M(137万化合物)で10分ぐらいかかった@MBA。

./gwt-build -iteration 2 chembl_14.gsp chembl14

検索はみんな大好きImatinibをgspにしたものを使った。

$ ./gwt-search -kthreshold 0.8 chembl14 query.gsp
reading index file:chembl14
end reading
start building rank dictionary
end building rank dictionary
start transferring labels
end transferring labels
id:0 1080597:0.809449 712949:0.838758 413038:0.847826 \
394513:0.826087 391369:0.819028 179393:0.815124 \
175534:0.899647 48523:0.804348 47164:1.000000 16405:0.802727 
cpu time (sec):0.712119
mean cpu time (sec):0.712119

と5,6秒程度で計算終了。ウリィィィィって言いたくなるほど圧倒的に速い。

bayonも感激したけど、それ以上にgwtはすごいなぁと思った。

OS10.8.2にopenbabel2.3.2をインストール

2.3.1のままだったので、昔のログを見ながらインストール。

が、古いswig(/usr/binにあるやつ)を見ていてエラーになるので、SWIGの場所をcmakeに知らせたいのだがオプションがわからない。

調べたトコロ

cmake -L

で一覧が出るらしい。必要なのはSWIG_EXECUTABLEなので

cmake ../openbabel-2.3.1 -DPYTHON_BINDINGS=ON \
-DPYTHON_LIBRARY=/usr/local/lib/libpython2.7.dylib\
-DPYTHON_EXECUTABLE=/usr/local/bin/python \
-DEIGEN2_INCLUDE_DIR=/Users/kzfm/openbabel/eigen-eigen-2.0.17 \
-DRUN_SWIG=ON \
-DSWIG_EXECUTABLE=/usr/loca/bin/swig

でOK

Shimehariというwaf

pypiのパッケージチェックをしていたらShimehariというwafを見つけてもしや!!と思ってクリックしたら予想通り〆張鶴インスパイアだった。

もうちょい調べたらFlaskとDjangoの中間あたりの規模を目指しているらしい

それからDocに使われているsphinx-bootstrapっていうテーマがいいなと思った。

巳年

ひねりなし

ary = [32, 95, 95, 95, 95, 95, 95, 95, 95,
       95, 95, 95, 95, 95, 95, 95, 95, 95,
       95, 95, 95, 95, 95, 95, 95, 47, 32,
       32, 79, 32, 92, 95, 95, 95, 47, 10,
       60, 95, 47, 95, 92, 95, 47, 95, 92,
       95, 47, 95, 92, 95, 47, 95, 92, 95,
       47, 95, 92, 95, 47, 95, 95, 95, 95,
       95, 95, 95, 47, 32, 32, 32, 92, 10]

print "".join([chr(c) for c in ary])

python