skk is …

mecab-skkserv のテスト中、キー押下から反応が返ってくるまでやたら時間がかかる現象に陥ったことが一度あると前の記事に書いた。

調べてみると、この原因はまさに mecab-skkserv だった。発生する条件は、見出し語の編集中に Ctrl+I もしくは Tab を押す、つまり補完を開始するとそういう状態になる。

この補完機能は skkserv のプロトコルで言うと 4 に当たるのだけど、mecab-skkserv.cpp のメインループでそれに対応する応答を行っていないため、fcitx 側(正確には libskk)が応答を延々待ち続けることになるのだろう。知らないコマンドを無視する mecab-skkserv の問題とも言えるし、レスポンスを読む際にタイムアウトを考慮しない呼び出し側の問題とも言える。

--- mecab-skkserv-original.cpp	2020-01-17 17:37:19.897237779 +0900
+++ mecab-skkserv.cpp	2020-01-17 17:38:26.822432515 +0900
@@ -33,20 +33,10 @@
 #include <stdexcept>
 #include <set>
 
-#ifdef HAVE_GETOPT_H
-#include <getopt.h>
-#endif
-
 #ifdef HAVE_UNISTD_H
 #include <unistd.h>
 #endif
 
-#if defined HAVE_GETOPT_H && defined HAVE_GETOPT_LONG
-#include <getopt.h>
-#else
-#include "getopt.h"
-#endif
-
 #define STDIN (fileno(stdin))
 #define BUFSIZE 8192
 #define DEFAULT_CAND_SIZE 20
@@ -155,6 +145,10 @@
       case '3':
 	std::cout << "localhost:127.0.0.1: " << std::flush;
 	break;
+       
+      case '4':
+	std::cout << '4' << std::endl;
+	break;
       }
     }
 

というわけで修正自体は数行付け足すだけでいいのだけど、このパッチを誰にどう投げればいいのかよく分からない。助けて偉い人。

mozc is … #4

ということで mecab-skkserv をビルドする。

mecab 本体のビルド

まず mecab 本体を落としてきて普通にビルドする。特に記すことはない。このページで提供されている辞書は mecab-skkserv には不要なので作っても作らなくてもいい。
https://taku910.github.io/mecab/

mecab-skkserv のビルド

http://chasen.org/~taku/software/mecab-skkserv/
おそらく幾つかの点でコンパイルエラーになるので

  • mecab-skkserv.cpp で getopt.h を #include している箇所を削除
  • dicrc に cost-factor = 700 とかを追記する(値は700〜800で任意とのこと)

等々を施す必要があるだろう。ちなみにビルドの際に辞書も構築されるが、特に ./configure で指定しなければ euc-jp エンコーディングになる。

systemd ユニットの作成

mecab-skkserv 本体のインストールまで済んだら次は xinetd か tcpserver に登録することになっているが、2020年の現在はそれらではなく systemd の話になる。以下のファイルを新規作成する。

/etc/systemd/system/mecab-skkserv.socket ファイル

[Unit]
Description=mecab-skkserv socket

[Socket]
ListenStream=1178
Accept=yes

[Install]
WantedBy=sockets.target

/etc/systemd/system/mecab-skkserv@.service ファイル

[Unit]
Description=mecab-skkserv service
Requires=mecab-skkserv.socket

[Service]
Type=simple
ExecStart=/usr/local/bin/mecab-skkserv
StandardInput=socket
StandardError=journal

[Install]
WantedBy=multi-user.target

systemd に対して有効にする。この有効化はマシン再起動をまたいでも永続的である。

$ systemctl enable mecab-skkserv.socket
$ systemctl start mecab-skkserv.socket
$ systemctl status mecab-skkserv.socket

この設定だと外のマシンからの接続も受け付けるので、拒否する必要があるなら iptables とかのレベルでうまいことする。

この状態でちゃんと動いてるかどうかは例えば

$ echo "2 " | nc localhost 1178

などとして確認できる。mecab-skkserv のバージョン情報が返ってくればとりあえず接続の受付と起動は正常。

fcitx-skk に対して mecab-skkserv を辞書として登録する

~/.config/fcitx/skk/dictionary_list に以下の行を追加

type=server,host=localhost,port=1178

このファイルでは辞書の指定の順番が重要な気がするが、SKK_JISYO.* の上に置く場合と下に置く場合でどう違うのかは試してない。

また、上記の通り普通に mecab-skkserv をビルドすると euc-jp エンコーディングの辞書が構築される。skkserv プロトコル自体が euc-jp で動くことになっているのでそれで正しいのだが、絵文字など unicode でしか表現できない文字を辞書に含めることができなくなる。その場合辞書を utf-8 で構築し、dictionary_list には

type=server,host=localhost,port=1178,encoding=UTF-8

などと指定すれば良い。

あとは fcitx を再起動すれば普通に使えるようになる。素の SKK だと Watashi<spc>noNamae<spc>haNakano<spc>desu. などと打っていたものが Watashino<spc>Namaeha<spc>Nakanodesu. くらいまでシンプルになる。いやこれはあんまり良い例じゃないな。とにかく送り仮名を気にする必要がほとんどの場合なくなる。

困った点としては

  • skkserv の向う側の辞書は一切変換の学習をしない。学習を skkserv 辞書にフィードバックする仕様自体がプロトコルにない?
  • たまにキー押下からレスポンスが返ってくるまで数十秒かかる状態に陥ることがある。たまにというか今まで1回だけそうなった。原因不明
  • 素の SKK に比べると、変換のレスポンスはほんのわずかに遅れる感じはする。ほんの0.1〜0.2秒ほど。慣れの範囲だろう

とはいえ普通に快適なので、当分 mozc を使うことはないだろう。

mozc is … #3

もう少し SKK で遊んでみよう。SKK といえば辞書をネットワーク越しに持てるのが特徴のひとつである。そのへんを追いかけてみる。skkserv なんて15年くらい前の話題なので2周くらい遅れてるわけだが気にしてはいけない。

さてコンピュータ上で日本語を入力する現代的かつ一般的な方式というのは、文字入力できるフィールドにおける挿入ポイントに対してフロントエンドとなるインターフェースが用意されていて、そのプリエディットバッファ内での入力と文節単位の(漢字)変換結果の選択をおこなったのち、最終的な変換結果全体をフィールドに送出するというものだ。

しかし、この方式が本当に日本語入力の完成された到達点なのか? と言うとそうは思えない。なんか日本語変換してるときって言い様のないストレスを感じません? この原因はプリエディットというものの存在にあると思う。正確に言えばプリエディットという非常にモーダル性の高いインターフェースに役割を持たせすぎて、そのライフタイムを長くさせる設計が良くない。

思うに、プリエディットがアクティブで、そこにいろんなものが溜まってる状態というのは、いわば便秘に苛まれている状態そのものなんだろう。その状態のストレスがどんなものか、説明するまでもない。食べたものも入力したものも体内での役目を終えたら即出力されるのがストレスのない生き方なのは自明の理なのに、プリエディットに頼りすぎるという全く正反対の食生活を押し付けてくる現代の日本語入力インターフェースは健康的にも邪悪というより他はない。

そういう観点で SKK を見てみると、SKK の本質からは若干ずれている気がするが、漢字変換の必要がない文字種についてはプリエディットを経ずに直接入力できるという仕様が、SKK を通して入力する際の妙な気楽さ、ストレスの低さに貢献している要素のひとつであるように思える。これは非常に重要。

一方で、SKK の本質である漢字変換の際に送り仮名の開始位置を明示しなければならない仕様は、それが手書きに通じて良いと肯定する意見もあるが、やはりさすがに使いやすさをスポイルしている要素だと言わざるを得ないと思う。ここがなんとかなるとかなり嬉しい。

そういうことで最初に戻るのだが、SKK の場合辞書の部分が分離されていて後から追加できたり、見出し語から対応する語を返す仕様さえ満たしていれば他はなんでもありだったりするので、それを利用してもう少し楽に変換したい。具体的には mecab-skkserv を入れたい。mecab-skkserv はその名の通り mecab をバックエンドに持つ辞書で、これにより送り仮名を意識することなく変換することができるはずだ。ただし、mecab-skkserv の説明で「疑似的に連文節変換ができる」と言われることがあるが、SKK にも mecab-skkserv にも連文節を編集する機能はないのでそれはあまり正しくはない。素の SKK が漢字列とそれに付随する送り仮名の頭という風変わりな単位で変換するのに比較してより自然に文節単位で変換できるようにはなる。

Enable mecab on xrea server

ぼちぼちあべ☆アニに検索機能を追加したい。この機能は:

  • 当日から1週間先までの範囲内で
  • タイトル、説明に含まれる任意の文字列を全文検索する

という仕様にしておこう。まずサーバ側で必要なものを考えるととりあえずは全文検索機能を備えたデータベースだ。以前タテログを作った時は MySQL を使った。しかし xrea サーバ上の MySQL というのは、当然だがルートユーザの権限が必要なチューニングは一切できないのがネックだ(実は裏技がなくもないのだが、それをここでは公開できない)。タテログの場合も全文検索用のメモリの割り当てをもうちょっと増やしたいのだが、いかんともしがたい。そういう訳で今回は SQLite を使ってみたい。

次に必要なのは、日本語を形態素解析するライブラリだ。タテログの場合は大掛かりな機構を使わず、2-gram で済ませたのだがやはりこのへんはきっちりやりたい。フリーの形態素解析器といえばなんといっても Mecab とか Chasen とかだ。これらを xrea 上で使えると嬉しい。さらに欲を言えば、これらを子プロセスとして呼び出すのではなく PHP エクステンションとして扱えるとなお良い。その場合のバインディングは php-mecab を使うことになる。

さて、実は xrea サーバにはすでに mecab はインストールされている。ただし、バージョンは 0.93 と若干古い。また、PHP バインディングは用意されていない。なので今回はこれは使わず、自前で mecab と php-mecab を xrea サーバ上でビルドすることにしよう。幸い xrea サーバには ssh でログインでき、また gcc 等々のビルド環境及び PHP エクステンションをビルドするための phpize ツールなどもあらかじめインストールされている。

Mecab と辞書のビルド

  • 作業用ディレクトリを予め掘っておく。今回は ~/devel (/virtual/akahuku/devel) とした
  • Mecab 公式からソースを落としてくる

  • $ tar zxf mecab-0.996.tar.gz
    $ cd mecab-0.996
    $ ./configure --prefix=$HOME --enable-utf8-only --with-charset=utf8
    $ make
    $ make check
    $ make install
  • 同様に辞書を落としてくる: とりあえず今回は IPA 辞書を落とした

  • $ tar zxf mecab-ipadic-2.7.0-20070801.tar.gz
    $ cd mecab-ipadic-2.7.0-20070801
    $ ./configure --prefix=$HOME --with-mecab-config=/virtual/akahuku/bin/mecab-config --with-charset=utf8
    $ make
    $ make install

php-mecab のビルド
PHP エクステンションをビルドするには、前述の phpize を使う。これ自体はちょっとしたシェルスクリプトで、php のインクルードファイル等々を走査しつつ configure スクリプトを生成する。ここで困ったことが 1 つある。xrea のサーバは複数のバージョンの PHP が同時にインストールされていて、コントロールパネルからどれを使用するか選択できるのだが、どれを選択しても /usr/local/include/php 以下の内容は最も古い php5.3 のもののままなのである。従って他のバージョンの PHP 向けに普通に make すると php 本体とエクステンションのバージョン不整合が起こり認識されない。ちなみに appsweets.net で使用している PHP は 5.5.35 だ。そこで:

  • PHP 公式から該当 PHP のソースを落として、展開して、configure *だけ* やっておく
  • php-mecab のソースディレクトリ(今回は /virtual/akahuku/devel/php-mecab/mecab/)で

    $ php55ize
    $ ./configure --prefix=$HOME --php-config=/usr/bin/php55-config --with-mecab=/virtual/akahuku/bin/mecab-config
  • 生成された Makefile を vi で開き、/usr/local/include/php を含んでいるマクロの該当部分をすべて自前の PHP のソースディレクトリ(今回は /virtual/akahuku/devel/php-5.5.35)に置き換える

  • $ make
    $ make test
  • make install はしない(というか権限の関係上できない)。modules ディレクトリに出力された mecab.so をそのまま使う

PHP への組み込み
xrea の場合、現在は PHP を各ユーザの ~/.fast-cgi-bin 以下の php.ini 及び起動スクリプトによって起動させている。php55.ini を vi で開き

extension="/virtual/akahuku/devel/php-mecab/mecab/modules/mecab.so"
mecab.default_dicdir="/virtual/akahuku/lib/mecab/dic/ipadic"

を追記。

あとは fcgi による php が再生成された時に phpinfo(); して mecab が組み込まれていることを確認。

* * *

という感じになる。これにより、

$mecab = new MeCab_Tagger("すもももももももものうち");
echo $mecab->parse($str);

というように PHP から直接 Mecab の機能を使えるようになる。ここで更にもうひとひねりある。PHP の SQLite には面白い機能がある。SQLite3#createFunction() で SQL のクエリ内で使用できる関数を PHP の関数で自由に定義できるのである。つまり、PHP 側で

function php_tokenize ($arg) {
return (new Mecab_Tagger(["-O" => "wakati"]))->parse($arg);
}
$db = new SQLite3("foo.sqlite");
$db->createFunction("tokenize", "php_tokenize");

などとすると、クエリで直接

insert into table (content, tokens) values ("すもももももももものうち", tokenize("すもももももももものうち"));

などと書ける。これは面白い。データベースが PHP と同じプロセスで動いているからできる芸当だ。文字列のマーシャリングとか問題ないのか若干心配がないこともないが…。

ところで createFunction()、createAggregate()、createCollation() を使う際には第一に気をつけるべきことがある。もしその PHP 側のコールバック関数内で例外が発生して PHP の実行が中断された場合、SQLite 側のトランザクションは正しくロールバックされず、ジャーナルファイルが作られたまま、そしてデータベースファイル自身はロックされたまんま(ロック元は、mod_php 環境であれば http サーバだ。fcgi 環境では知らない)になってしまう。つまりコールバック関数内では出来うる限りガチガチにエラーチェックする必要があるということだ。

などとさっきまでやっていたら、いつの間にかふたばが全滅していた。
serverstat
なにこの…なに?