ほとんど感想みたいなものですが,ひととおり実装したのでまとめ.
https://github.com/ikawaha/kagome
前回までのあらすじ
- Pure Go な形態素解析器で,辞書同梱のバイナリを作ろうと MeCab辞書を go ソースに変換してコンパイルしたが,
- コンパイルが非常に遅くなるので,go-bindata で埋め込む形式に変更.
- 未知語処理にも対応しました.
今回の実装点
- Go っぽいコードを目指して再実装 / テスト追加
- kuromoji 相当の検索用分割モードを実装 / ユーザー辞書対応
- REST API, Web App のデモ追加
Go っぽいコードを目指す
最初に書いたコードは,不慣れな部分もあってあんまりGoっぽいコードじゃなかったので,golint で指摘される部分を直してみました.eclipse とか使ったらGUIでリファクタリングできるかなと思ってたんですが,どうやら出来なそうだったので,gofmt を利用してリファクタリングしました.IDE とか使わなくても十分これでいけそうな気もします.
前のコードは割とモジュール化しようと細かめにパッケージを切っていたのですが,細かくパッケージ切るとコードが書きにくくなることがわかってきたので,必要以上にパッケージには切らないようにしました.
パフォーマンスに関しては,[]byteとstringを相互にキャストしたりすると,コピーが走って遅くなることがわかったので,不用意に[]byte と string のキャストしないように修正しました.
あと,Go ではテストとカバレッジが取れるようにツールが用意されているので,テストを追加して github に push するとtravis でテストが走り coveralls でテストカバレッジが計測できるように連携して,継続的に開発できるように環境整えました.
kuromoji 相当の検索用分割モードを実装
pure java 形態素解析器の kuromoji では,検索のインデキシング用に細かめに分割するモードが用意されています.これ相当の分割を行えるようにしました.
- 標準 標準の分割
- 検索 ヒューリスティックの適用によって検索に役立つよう細分割
- 拡張 検索モードに加えて未知語を unigram に分割します(実装してみたけど,これ形態素解析器の仕事なのかちょっと悩む)
入力内容 | 標準モード | 検索モード | 拡張モード |
---|---|---|---|
関西国際空港 | 関西国際空港 | 関西 国際 空港 | 関西 国際 空港 |
日本経済新聞 | 日本経済新聞 | 日本 経済 新聞 | 日本 経済 新聞 |
シニアソフトウェアエンジニア | シニアソフトウェアエンジニア | シニア ソフトウェア エンジニア | シニア ソフトウェア エンジニア |
デジカメを買った | デジカメ を 買っ た | デジカメ を 買っ た | デ ジ カ メ を 買っ た |
kuromoji のコードを読んでみましたが,ヒューリスティックな処理で,漢字のみで構成されている形態素が閾値(2文字)以上なら閾値超えた文だけペナルティを付与するという割と単純な処理でした.kagome での該当コードはこんな感じです.
// ペナルティの計算
// searchModeKanjiLength は 2, searchModeKanjiPenalty は 1700 です.
func additionalCost(n *node) int {
l := utf8.RuneCountInString(n.surface)
if l > searchModeKanjiLength && kanjiOnly(n.surface) {
return (l - searchModeKanjiLength) * searchModeKanjiPenalty
}
if l > searchModeOtherLength {
return (l - searchModeOtherLength) * searchModeOtherPenalty
}
return 0
}
func (la *lattice) forward(mode tokenizeMode) {
for i, size := 1, len(la.list); i < size; i++ {
currentList := la.list[i]
for index, target := range currentList {
prevList := la.list[target.start]
if len(prevList) == 0 {
la.list[i][index].cost = maximumCost
continue
}
for j, n := range prevList {
var c int16
if n.class != USER && target.class != USER {
c = la.dic.Connection.At(int(n.right), int(target.left))
}
totalCost := int64(c) + int64(target.weight) + int64(n.cost)
if mode != normalModeTokenize {
totalCost += int64(additionalCost(n)) // ←★ここでペナルティ加えるだけ
}
if totalCost > maximumCost {
totalCost = maximumCost
}
if j == 0 || int32(totalCost) < la.list[i][index].cost {
la.list[i][index].cost = int32(totalCost)
la.list[i][index].prev = la.list[target.start][j]
}
}
}
}
return
}
REST API, Web App のデモ追加
Go には Web App がすぐ作れるという特徴があると思うんですが,形態素解析器がそのままサーバとしてたってくれたらちょっと何かするときに便利かなと思い,コマンドにサーバ機能を追加してみました.
REST API
-http
オプションを指定するとWebサーバが立ち上がります.
localhost
にポート8080
でサーバを立ち上げた場合,'http://localhost:8080/' に REST でアクセスできます.こんな感じ.
$ kagome -http=":8080" &
$ curl -XPUT localhost:8080 -d'{"sentence":"すもももももももものうち"}'
{{
"status": true,
"tokens": [
{
"id": 36163,
"start": 0,
"end": 3,
"surface": "すもも",
"class": "KNOWN",
"features": [
"名詞",
"一般",
"*",
"*",
"*",
"*",
"すもも",
"スモモ",
"スモモ"
]
},
{
"id": 73244,
"start": 3,
"end": 4,
"surface": "も",
"class": "KNOWN",
"features": [
"助詞",
"係助詞",
"*",
"*",
"*",
"*",
"も",
"モ",
"モ"
]
},
{
"id": 74989,
"start": 4,
"end": 6,
"surface": "もも",
"class": "KNOWN",
"features": [
"名詞",
"一般",
"*",
"*",
"*",
"*",
"もも",
"モモ",
"モモ"
]
},
{
"id": 73244,
"start": 6,
"end": 7,
"surface": "も",
"class": "KNOWN",
"features": [
"助詞",
"係助詞",
"*",
"*",
"*",
"*",
"も",
"モ",
"モ"
]
},
{
"id": 74989,
"start": 7,
"end": 9,
"surface": "もも",
"class": "KNOWN",
"features": [
"名詞",
"一般",
"*",
"*",
"*",
"*",
"もも",
"モモ",
"モモ"
]
},
{
"id": 55829,
"start": 9,
"end": 10,
"surface": "の",
"class": "KNOWN",
"features": [
"助詞",
"連体化",
"*",
"*",
"*",
"*",
"の",
"ノ",
"ノ"
]
},
{
"id": 8024,
"start": 10,
"end": 12,
"surface": "うち",
"class": "KNOWN",
"features": [
"名詞",
"非自立",
"副詞可能",
"*",
"*",
"*",
"うち",
"ウチ",
"ウチ"
]
}
]
}
Web App デモ
Web サーバを立ち上げた状態で,ブラウザで /_demo
にアクセスすると,形態素解析のデモ利用できます.
-http=:8080
を指定した場合,http://localhost:8080/_demo
になります.Lattice の表示には graphviz が必要です.(デモでは Lattice が大きすぎて表示に時間がかかりすぎる場合は Timeout します.そのような場合には lattice ツールの方を試してみてください.)
あとがきというか感想というか
このプログラム,Go を勉強しようと思って始めたんですが,とりあえずの機能はなんとか用意できたかなと云うところでひと段落です.MeCab の辞書ファイルとか,kuromoji のソースとか読んで,ここの処理こうなってたのかとか気づけることも多く,結構勉強になりました.
Go はコードフォーマッタ,テスト,カバレジ,プロファイラと開発に必要なものが一通りそろってて,ストレスなく作業できました.言語的には最近流行の機能とか特にないですが,というか,むしろ機能減ってる気もしますが,「ほー,いいじゃないか,こういうのでいいんだよ」と孤独のグルメ五郎になった気分でつぶやきたくなる嬉しさがあります.とはいえ,こないだ DSIRNLP 勉強会で若い学生さんに聞いたら Go は完全にイロモノあつかいだったのでちょっと寂しかったです.やはりおっさんホイホイな言語なのかもしれません.
ちなみに,なんで kagome(籠目)なのか何人かに聞かれたのですが,由来はカゴの目が Lattice に見えるかなというところです.名前に Go も入ってますしね!