LoginSignup
6
6

More than 5 years have passed since last update.

SentencePieceを形態素解析のように使えるAPIを公開した

Last updated at Posted at 2017-04-12

2017/4/13追記:taku910さん本人から助言をいただきました。詳細はコメント欄を見てください。本記事は追って修正します。
2017/4/18追記:公開しているAPIの方は修正しました。Unigramの方はまだモデル構築中です(5日間回しても終わらず)。
2017/4/26追記:Unigramの方もできました(12日間回してやっと終わった)。本件の詳細は別の記事で投稿しました。

はじめに

※無理やりWebAPIにしたので、間違ってたら指摘してください。
GoogleがSentencePieceを公開しました。NMT (Neural Machine Translation/ニューラル機械翻訳) で有効性が確認されているアプローチです。今回はそれを形態素解析のように使えるWebAPIにしてみました。無料で使えるので使ってみてください。

API

サンプルコード

関連記事

実装内容

簡単に解説すると、

  1. 日本語Wikipediaの記事にSentencePieceをかける
  2. SentencePieceの出力であるvocabファイルをmecab-ipadicの辞書形式に整形する
  3. kuromojiで辞書ごとコンパイルする

もう少し詳細に説明します。

1. 日本語Wikipediaの記事にSentencePieceをかける

手元にあった日本語Wikipediaのデータが20160915のdumpで少し古いですが、こいつを元データにしました。いくつか手を加えて整形しています。やったことを羅列すると

  1. dumpはwp2txtでテキスト化して、全部を一つのファイルにガッシャンコ
  2. 40文字以下の行は削除
  3. 行先頭が「Image」「File」「イメージ」「ファイル」は削除
  4. ファイルの行をランダムに入れ替える
  5. 先頭から1,700,000行(約600MB)を使ってSentencePiece実行

なんでこんなことをしているかと言うと、SentencePieceはメモリを大量に必要とするようで、私の手元のPC(メモリ16GB)では全文(約2GB)を入力できませんでした。色々と試行錯誤したところ、16GBのPCでは約600MBが限界みたいです。その代わり、メモリにデータが乗りさえすれば処理は早いです。

SentencePieceは以下のコマンドで実行しています。今更ですが、実行環境はWindows10にCygwinを入れて実行しました。本家のREADMEにあった必要ライブラリはCygwinでそれっぽいのを入れました(バージョンが一部合っていないですが、動いたことは動いた)。

$ ./spm_train --input=input.txt --model_prefix=output --vocab_size=8000 --model_type=bpe

出力はvocabファイルとmodelファイルです。vocabファイルは8000行の単語欠片です。

2. SentencePieceの出力であるvocabファイルをmecab-ipadicの辞書形式に整形する

さて、WebAPIで公開するための準備をします。WebAPIのレスポンスは「単語欠片」と「単語欠片ID」の組を配列で返すことにします。「単語欠片ID」は例えば機械学習でOne-Hotベクトルを作るときに使ってください。もちろん、使わなくても良いです。

さて、実装検討です。私が運営するWebAPIのマーケットプレイスApitoreは完全なJavaで実装していますが、SentencePieceはC++で書かれています。「ラッパー書くのは面倒だし、C++でWebAPIとかよくわからんし・・・ということで、ここは無理やり実現するしかない!SentencePieceも形態素解析みたいなもんでしょ」ってことで、普段からお世話になっているJavaの形態素解析器kuromojiを流用することにしました。kuromojiは有名なmecabのJava版です。そしてmecabはSentencePieceを作った工藤さんの研究技術です。つながってますね!

というわけで今回は、SentencePieceの出力を新しい辞書として既存のkuromojiに追加する形を取りました。その代わりに、いくつか工夫をしておきます。辞書の形式はこんな感じです。

#表層形,左文脈ID,右文脈ID,コスト,品詞,品詞細分類1,品詞細分類2,品詞細分類3,活用形,活用型,原形,読み,発音
され,1,1,1,SPWORD,1,*,*,*,*,*,*,*

「表層形」がSentencePieceの出力である『単語欠片』です。「コスト」を『1』にする、ここがポイントです。「コスト」を『1』にすれば、ほぼ間違いなく形態素解析時にSentencePieceの単語欠片が選択されます。念のため、文脈の連接コストを定義するmatrix.defで品詞の接続コストもすべて1に変更しておきます。こうすることで「文脈を考慮せず、ひたすらSentencePieceの単語欠片をつないでいく」ことが出来ます。文脈を気にする必要がなくなったので「文脈ID」は何でも良いです。今回は「文脈ID」を『1』としました。

「品詞細分類1」には『単語欠片ID』を振りました。『単語欠片ID』はSentencePieceの出力8000語に対して私がふったユニークなIDです(つまり全部で8000個のID、番号は1~8000までを使う)。SentencePieceの「品詞」は『SPWORD』としました。この品詞は、SentencePieceの対象外の語を見つけるために使います。少し説明すると、SentencePieceの単語欠片は学習データが元になっています。当然ながら学習データで一度も現れていない文字は扱いようがありません。その未知の文字を従来のkuromojiで検出することにしました。(未知の文字はほぼ間違いなく『未知語』に分類されると思いますが)品詞が『SPWORD』じゃないときは、単語欠片IDを『0』としました。これで未知文字も扱えます。

3. kuromojiで辞書ごとコンパイルする

あとはコンパイルするだけです。kuromojiを通常通りにコンパイルします。kuromojiに内包されるテストコードは絶対に通らないので、テストは削除してしまいましょう。

実際に使ってみる

APIはこちらで公開しています。APIコールまでの準備(API登録、アクセストークン発行、サンプル実行)はこちらを参考にしてください。

APIの入出力などの仕様はこちらで公開しています。一応ここにも書くと、APIレスポンスの仕様はこんな感じです。入力はテキストです。

{
  "endTime": "string",
  "log": "string",
  "processTime": "string",
  "startTime": "string",
  "tokens": [
    {
      "token": "string",
      "wid": 0
    }
  ]
}

実際の使用例を見てみます。「吾輩は猫である。名前はまだない。」を入力してみました。たしかに、通常の形態素解析とは若干異なりますね。

"tokens": [
  {
    "wid": 5578,
    "token": "吾"
  },
  {
    "wid": 5386,
    "token": "輩"
  },
  {
    "wid": 472,
    "token": "は"
  },
  {
    "wid": 5643,
    "token": "猫"
  },
  {
    "wid": 11,
    "token": "である"
  },
  {
    "wid": 3796,
    "token": "。"
  },
  {
    "wid": 2002,
    "token": "名前"
  },
  {
    "wid": 472,
    "token": "は"
  },
  {
    "wid": 1914,
    "token": "まだ"
  },
  {
    "wid": 26,
    "token": "ない"
  },
  {
    "wid": 3796,
    "token": "。"
  }
]

続いて「WRYYYYYYYYYY!最高にハイってやつだアアア」。見事にバラッバラに分解されています。

"tokens": [
  {
    "wid": 829,
    "token": "W"
  },
  {
    "wid": 589,
    "token": "R"
  },
  {
    "wid": 3032,
    "token": "Y"
  },
  {
    "wid": 3032,
    "token": "Y"
  },
  {
    "wid": 3032,
    "token": "Y"
  },
  {
    "wid": 3032,
    "token": "Y"
  },
  {
    "wid": 3032,
    "token": "Y"
  },
  {
    "wid": 3032,
    "token": "Y"
  },
  {
    "wid": 3032,
    "token": "Y"
  },
  {
    "wid": 3032,
    "token": "Y"
  },
  {
    "wid": 3032,
    "token": "Y"
  },
  {
    "wid": 3032,
    "token": "Y"
  },
  {
    "wid": 0,
    "token": "!"
  },
  {
    "wid": 799,
    "token": "最高"
  },
  {
    "wid": 2689,
    "token": "に"
  },
  {
    "wid": 646,
    "token": "ハイ"
  },
  {
    "wid": 9,
    "token": "って"
  },
  {
    "wid": 3880,
    "token": "や"
  },
  {
    "wid": 3888,
    "token": "つ"
  },
  {
    "wid": 3914,
    "token": "だ"
  },
  {
    "wid": 1726,
    "token": "ア"
  },
  {
    "wid": 1726,
    "token": "ア"
  },
  {
    "wid": 1726,
    "token": "ア"
  }
]

最後に「「恐怖」を克服することが「生きる」こと」を入力してみます。なかなか特徴的なセグメントしますね。

"tokens": [
  {
    "wid": 648,
    "token": "「"
  },
  {
    "wid": 5092,
    "token": "恐"
  },
  {
    "wid": 5725,
    "token": "怖"
  },
  {
    "wid": 3846,
    "token": "」"
  },
  {
    "wid": 2163,
    "token": "を"
  },
  {
    "wid": 5711,
    "token": "克"
  },
  {
    "wid": 4840,
    "token": "服"
  },
  {
    "wid": 543,
    "token": "することが"
  },
  {
    "wid": 648,
    "token": "「"
  },
  {
    "wid": 2859,
    "token": "生き"
  },
  {
    "wid": 3798,
    "token": "る"
  },
  {
    "wid": 3846,
    "token": "」"
  },
  {
    "wid": 12,
    "token": "こと"
  }
]

おわりに

SentencePieceをWebAPIにしてみました。翻訳で使うのもモチロンそうですし、sec2secに使えるってことなので標準語-方言変換とかもできそうです。私は極性判定に使ってみようと思っています。今はWord2Vec結果をRNN+LSTMしていますが、SentencePieceの単語欠片でone-hotベクトル作ってRNN+LSTMって何か良さげじゃないですか?

6
6
5

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
6