gensimのLdaModelでtop_topic()を使おうとしたらいろいろバグが残っていたので、とりあえず手元で動くようにしてみました。詳細はMLに報告しましたが、これが正しい解法かは自信がありません。 問題は複数ありますが、まずは名前空間に不備があります。”np”と”ifilter”という2つの関数をglobalで利用しようとして失敗します。これは以下のように無理やり定義してやれば問題を回避できます。 これ以外に、LdaModel.top_topics()に与えたcorpusによってはKeyErrorが発生する問題があります。これはMLにも書いたようなパッチで回避できました。 私の環境では、これでgensim 0.11.1においてtop_topicsが機能するようになりました。
gensimでLDA(Latent Dirichlet Allocation)
トピックモデルを試そうとgensimのLdaModelを使ってみたのですが、参考にした記事「LSIやLDAを手軽に試せるGensimを使った自然言語処理入門」は対象とするgensimのバージョンが古いようだったので、現状にあうようアレンジして試してみました。この記事で使ったgensimのバージョンは0.11.1です。 過去のgensimと今のものとでは、メソッド名等の命名規則が変わっています。旧来はcamel caseでしたが、今は小文字とアンダースコアの組み合わせになっています。たとえばnumTopicsという名前付き引数はnum_topicsに変わっています。 それ以外にも多少の違いはあるのですが、yuku_tさんのgistのコードを今のバージョンのgensimで動くように修正したのが以下になります。 WikiCorpusクラスにはsaveAsTextと等価なメソッドが今は存在しないので、個別にDictionaryとCorpusをファイルに出力しています。 生成されたモデルをもとにLSI, LDAモデルを作成する流れは、メソッド名等を(たとえばPrintTopic→print_topic)書き換える点以外は元記事とほぼ変わりません。 また、LDAモデルの生成をマルチコアで実施するgensim.corpura.LdaMulticoreというクラスも用意されています。worker=2等とワーカースレッド数を指定できる点以外はLdaModelクラスと同じです。しかし、あまりよくスケールするわけではないようで、CPU論理コア数8の環境でworker=7としてみても対して速度が上がることはないようです(参考:Multicore LDA in Python: from over-night to over-lunch)。一応workerプロセスは指定しただけ起動はするのですけど。加えてマルチコアで処理した場合、出力されるログが少なくなって進捗状況がわかりづらくなるようです。 ところで以前書いたword2vecの記事でgensimのword2vecでのマルチスレッド動作について書いていなかったので、同様にworkerスレッド数を指定できることを追記しておきました。 また、Google C言語実装のword2vecのマルチスレッド処理について一つだけ気になるところがありました。それは、単純にファイルをバイト単位でスレッド数に分割し、処理している点です。単語の区切りを一切考慮しないので、その箇所がたまたま単語の途中であってもお構いなしに処理を進めてしまいます。 もっとも、出現頻度の少ない単語は捨てられるので、実用上問題になることはほとんどないはずではあります。運悪くUTF-8の途中で切られてしまい、なおかつ語彙として記録されてしまうと、gensimで読み込もうとするときにInvalidUnicodeErrorが発生してしまうので、その点だけは気を付ける必要があります。
LanguageToolで長音記号チェック
漢字変換ミスで、たまに長音記号がかな文字以外の後に置かれる事例を見かけたので、それをLanguageToolのルールにして取り込んでもらいました。 現状ある日本語のルールをざっと見たのですが、うまい具合に表現する方法が思い浮かばなかったので、思い切ってメーリングリストで聞いてみました。 メイン開発者のDanielさんは親切で反応も早い方で、「正規表現でUnicodeの範囲を使えばできる」という方法を示してくれました。また、過去にも日本語のルールを書いているSilvanさんからは、JavaにおけるUnicodeのクラス表現(\p{IsXxx})を紹介してくれました。 これらを踏まえ、以下のようなルールをpull requestとして書き、無事masterにマージしてもらえました。 カタカナとひらがな以外が長音記号の前にあるとき、LanguageToolは警告を出します。 この機会に、Doc-ja Wikiの”LanguageTool使い方メモ“も若干修正しました。自前のgrammar.xmlを指定して起動する方法と、ソースコード上で変更をしたときにルールのテストをする方法について新た説明を加えています。 LanguageToolのリリースバージョンは現在2.9ですが、次期バージョン3.0ではgrammar.xmlの書式も変わっているため、いずれWiki内の説明もそちらに合わせて修正したいところです。
pyroongaの挙動
GroongaをPythonから扱うのにpyroongaを使ってみたのですが、いくつかハマリポイントがありました。 Groonga serverが必要 rroongaは単独で動作するのに対して、pyroongaはGroongaがserverとして起動している必要があります。 カラム名が予約語だとハマる pyroongaはテーブル名をクラス名に、カラム名をアトリビュートにマッピングして動作します。したがって、”from”などの予約語がカラム名だとうまく動きません。あまりPythonには詳しくないのですが、これを回避する方法は思いつきませんでした。 複数語のクエリで期待した結果が返ってこないことがあある いまいち原因がわからないのですが、”foo bar”というようなクエリをコマンドから与えた時に得られる結果と、queryメソッドで同じクエリを与えた時の結果が異なります。ちょっとよくわかりません。 対処: subprocess呼び出し 非常にダサい方法ではありますが、pythonからgroongaコマンドを直接呼び出すことで期待する結果が得られるようになりました。 これで期待通りの結果が得られるようになりました。 追記(2015/0512) ロケールが設定されてない環境(CGIなど)でqueryがunicode文字列の場合、引数をきちんとUTF-8でencodeしてやる必要があります。でないと、UnicodeEncodeErrorが発生します。私はこれで数時間悩みました。
gensimとword2vec
何かと話題のword2vecですが、自分も使ってみようと思って試行錯誤したことを書いてみます。間違いなどあればぜひコメントください。 GoogleのC実装 https://code.google.com/p/word2vec/にある、C言語による実装は独立したアプリケーションで、Apache 2.0ライセンスで提供されています。 ビルドした状態でword2vecコマンドを使って学習、モデルを作成し、distance等のコマンドからモデルをもとにデータを出力します。以下はword2vecを引数なしで実行したときの実行例です。 各オプションの意味は以下になります。 train: 学習対象のテキストデータ(コーパス) size: 次元数(デフォルトは100) window: skip-gramの対象とする単語の最大数を指定(多分)デフォルトは5 sample: 単語を無視する際の頻度の閾値(デフォルトは1e-3) threads: 処理するスレッド数 hs: Hierarchical Softmax(階層的Softmax)を使う(1)/使わない(0) negative: ネガティブサンプリングに用いる単語数 binary: 出力結果をバイナリ形式にする(1)/テキスト形式(0) cbow: Continuous Bag-of-Words 連続単語集合モデルを使う(1)/使わない(0) iter: 学習する回数(イテレーション)の指定。デフォルトは5 CBOWとSkip-gramについてはFiS Projectの記事がわかりやすいです。階層的SoftmaxとネガティブサンプリングについてはnishioさんによるQiitaの記事が詳しいです。 gensim(Python)実装 gensimはPythonとCを組み合わせて記述された、文書解析ソフトウェアパッケージです。ライセンスはLGPL3+で、ソースはhttps://github.com/piskvorky/gensimで公開されています。 もともと複数の計算モデルを扱えるようになっていたようなのですが、2013年9月ごろにword2vecが追加されたようです。webで紹介される実装はこちらの方が多いようです。 Google実装はサンプル実装という面が強く、コマンドからできることに限りがあります。それに比べるとgensimはできることが多い(任意の数の単語のpositive/negativeな演算ができる)のですが、モデル構築にメモリをより使用します。 1GBのテキストをコーパスとしてgensimのword2vecに与えたところ、メインメモリ8GBのマシンでスラッシングを起こしてしまいました。Google実装であれば、1GB程度のテキストはオンメモリで処理できるようです。また、POSIX threadによるマルチスレッド処理にも対応しています。 幸いなことに、gensimのWord2VecではGoogle実装のモデルを読み込ませることができます。大きなデータを扱う際には、これらを組み合わせるとよい感じです。 pythonで処理したモデルもファイルに保存することができます。 gensimでは、trainメソッドで後から追加学習させることもできるのですが、既に構築したモデルに存在しない単語は扱えない(語彙が増やせない)ようです。これに気付かず、分割したテキストデータをちょっとづつ学習させるというようなことをやらせようとしてはまりました。 結局、大きなコーパスを取り扱う際にはC実装でモデルを構築して、それをgensimで読み込むという使い方をするのが良いようです。 その他参考にした資料 ディープラーニングチュートリアル 応用編 追記(2015/5/20) gensimでword2vecの処理がシングルスレッドでしかできないような表現でしたが、Word2Vecの引数にworker=2等とすることでマルチコア動作することに気付きました。それでも処理速度はGoogle実装の方が早いとは思いますが、記録として残しておきます。
LanguageToolのgrammer.xml
先日の東京エリアDebian勉強会ハックタイムで、もう少しLanguageToolをみて行きました。いくつか誤解があったのと、gitのコードを見ていて気づいたことを記録します。 私が参照していたJapanese.javaがgrammar.xmlでカバーできないケースをソースコードレベルで対応するものだと勘違いしていたのですが、ここは単にテキストの分割と品詞情報の付加を行うための処理を記述するもののようでした。 grammar.xmlはもっと柔軟性が高いようで、LanguageTool Wikiに高度な処理を行う方法も記述されています。一方でやはりgrammar.xmlだけで対応しきれないケースはJavaで書けることも記述されています。 今回追加しようと考えていたのは、長音符「-」の直前の文字がひらがな、カタカナ以外のケースです。基本的にそれはありえないので、LanguageToolが指摘すべき問題とすべきでしょう。 Doc-ja Wikiに書いた方法で-tオプションを利用すれば、そのようなケースの時に「-」はどのような品詞情報が付加されるかを調べることができます。 単独の長音符「ー」は「未知語」と解釈されることがわかりました。単純にtokenがーでPOSが未知語である情報が検出されたら警告すればよさそうです。 しかし現状のLanguageToolはmasterのバージョンが3.0となっており、grammar.xml自体の文法も変わっていることに気付きました。したがって、Doc-ja Wikiの説明も今後は修正が必要になります。 ということを把握したところで先日は時間がなくなってしまいました。grammar.xmlの解説だけでも結構ボリュームがあるので、これを翻訳できると自分を含めうれしい人はいるかなあと思いました。
日本語も扱える文章チェッカーLanguageTool(実用レベルとはいっていない)
以前から注目しているソフトウェアとして、LanguageToolがあります。Javaで書かれた文法チェッカーで、コマンドラインやFirefoxアドオン、Libre/OpenOfficeのプラグインとして動作します。 以前簡単な紹介をおこなったり、使い方をDoc-ja Wikiに書いたりしてみました。このころはversion 2.6でしたが、現在の最新版は2.9です。 残念ながら、日本語の対応状況については今も芳しくありません。日本語のルールの数はslideshareの資料では23種類でしたが、version 2.9の段階では43種類と増えてはいるものの、まだまだ不足している状態です。 単語ベースでのルールも少ないのですが、コードベースで対応すべきケースについては現状皆無です(https://github.com/languagetool-org/languagetool/blob/master/languagetool-language-modules/ja/src/main/java/org/languagetool/language/Japanese.java)。 先日、こちらにコードを追加しないと対応できなさそうな実例が出てきたので、それを考えてみようと思います。 以前grammer.xmlのプルリクエストを送った時は、パッケージ全体をビルドする必要がなかったので、ビルド環境がそろっていなかったことに気付いていませんでした。 ビルドにはmavenが必要なので、Debian wheezy上でapt-get install mavenを行ってbuild.shをたたいてみたのですが… JVMが古すぎると怒られてしまいました。ということでsid環境で試したところ、無事ビルドできました。続きは明日の東京エリアDebian勉強会でもやってみようと思います。
KAKASIの写経とバグ修正
ここのところKAKASIのコードをいじっています。昔から再実装したいと考えていたのですが、コードを十分理解していない部分もあったので、目視でソースを見ながらエディタで書き写す「写経」的なことをやってみました。 写経の過程でいくつかの潜在的なバグが発見でき、本体にコミットすることもできました。内容は以下です。 ee2.c:E2alphabetのn[1].c1をゼロクリア g2.c:g2jにおける出力先の変数の間違い それ以外にもいくつかコミットがありますが、直接的なバグではないもの(即値の代入、不適切なマクロ定数の利用、実害のないロジックミス)ばかりです。 それ以外にも問題はありましたが、それらは修正に関して検討が必要なものだったので、Tracにチケットを作る程度にとどめています。 写経したKAKASIのコードも一応gitにおいてあります。ただし動作検証は行っていません。あくまで実験として行ったものです。環境変数で辞書のディレクトリを指定すればおそらく動作はするはずです。 作業に関して、ソースの可読性を高めるため以下のことを念頭に行いました。 K&Rスタイルの廃止 単純なマクロ定義だった定数をいくつかenum化 配列定数として埋め込んでいたいくつかのテーブルを外部ファイル化 文字エンコーディング定数でマジックナンバーだったものをマクロ化(dict.cのみ) CMakeの採用 特に文字エンコーディングがらみはかなりわかりやすくなったのではないかと思います。旧来のコードの一部を引用してみます。 ESC $ BというのはISO-2022-JPでG0をJIS X 0208に指定するためのエスケープシーケンスですが、これをマクロで以下のように書き換えました。 コメントがなくても意味が理解しやすくなったと思っています。まだ道半ばではありますが、ゆくゆくはfrom scratchなコードを起こしたいと思っています。
tty emacsで表示できない絵文字をゲタで代替表示
普段emacsをputtyからUTF-8で使うことが多いのですが、最近よくとある絵文字の表示に遭遇すると表示が崩れるという事態にあったので、なんとかできないかと対処してみました。 .@knok izonmoji-mode.elが参考にならないでしょうか。Emacsで独自 coding systemを定義する方法は私も知りたい。cp5022x.elが参考になるのだろうか? Wanderlustで iso-2022-jpを cp50220に置き変えてる。 — ふみやす%シェルまおう的なにか@通販生活 (@satoh_fumiyasu) 2015, 4月 2 前提として、https://github.com/kachie/emacs.d/blob/master/init.d/w32-init.elと同様の設定をしています。この設定では、いくつかの文字をputtyのUTF-8設定で正しく表示できるようなutf-8-for-puttyというエンコーディングを定義しています。その中でcp5022x.elという機種依存文字を表示させるためのelispも使われているのですが、その中身を見ながら見よう見まねで書いてみました。 変換テーブルの定義 置き換えの対象となる絵文字のテーブルを自作します。 #x1F440は絵文字の目玉で、これをゲタ(〓 #x3013)に置き換えるテーブルです。 エンコーディングの定義 utf-8-for-puttyエンコーディングの定義部分を修正します。 表示幅の定義 オリジナルのset-east-asian-ambiguous-widthテーブルに目玉のコードポイントを追加します。 これで自分が望む出力が得られるようになりました。 非tty Emacsの場合 今回の目的には合いませんでしたが、画像が表示できる環境ではemoji.elが良いようです。こちらを使うと、絵文字がイメージとして表示されます。キャリア別の絵文字も扱えるようで、なかなか高機能です。 @knok emoji.el はどうでしょうか? https://t.co/XwaX0Ta2wo — Shinichiro HIDA (@_shinichiro_) 2015, 4月 2
将軍の名前を補完するshogun-completion
かつて、どこかで「プログラマが徳川将軍の名前を覚えられないのはshellで補完できないからだ」という言説を見かけ、それを解消するべくbashで徳川幕府の将軍名を補完できるshogun-completionというものを作成しました。 当時課題だった問題2点に対応すべく、このたび改良版を公開しました。 鎌倉幕府対応 以前の実装では徳川幕府のみにしか対応していなかったので、オプションにkamakuraを入力した上で補完をすると、鎌倉幕府の歴代将軍を補完できるように修正しました。 shogunコマンドの実装 以前の実装は実在しないshogunコマンドに対する補完、という実装だったのですが、このたびshogunコマンドも実装しました。開発言語にはgolangを用いています。ごーしょーぐんとでも呼んでください。 shogunコマンドの引数に将軍名を入力すると、何代目であるかの情報を出力します。これで以前の実装では対応できなかった、「当該将軍が何代目であるかわからない」という問題にも対応できました。 bashにおける補完の限界 実のところ、zshの補完であればもっと高度なことができます。将軍名を補完する際にそれが何代目であるかの情報を表示するといったことも可能です。 じゃあbashでできないかというと、頑張ればできそうではあります。”bash autocompletion: add description for possible completions“というStackOverflowのやり取りに一例がありました。以下はそのコードの転載です。 ローカル変数completionsに行区切りで補完したい情報を代入しています。その結果を、IFSに改行のみを設定したうえで、compgenに処理させています。これで、入力にマッチする補完候補のみが配列としてCOMPREPLYに代入されます。さらに配列の数が1つだった場合、文字列” – “以下の部分を切り取って返します。その結果、この例では入力したいIPアドレスのみが実際には補完される、というわけです。 このアプローチを自分も試してみたのですが、bashのバージョンのせいか、IFS=”\n”とした状態でもcompgenの出力全体を代入された1個の配列にしかなりませんでした。IFSを使わない方法も試してみたのですが、思うように複数個の配列にできなかったのでそこで断念しました。あと、当該記事にも書かれていますが、IFSの変更は影響が大きいのでむやみにやらない方がいいようです。