Java (Scala) で言語判定
やりたい事
- Java (Scala) で、ある文字列が何語(日本語、英語、など)なのかを判別する
- 入力文字列は以下の2通り
- ユーザーから入力された検索文字列(1単語、数文字〜数単語、数十文字)
- 検索対象となる文章(数十単語〜数十ページ程度)
- 対応する言語
- 当初は日本語と英語
- 今後は5言語程度
なぜこれをやりたいかは、以下のエントリーを参照。
英語か日本語かを判別
これは、単純にひらがな・カタカナ・漢字が使われているかどうかで判別可能。ちょっと検索した限りではいくつか方法があったが、以下の2つが素直な方法。
- Java の UnicodeBlock を使う: 参考サイト → Unicodeの文字のブロックに触れてみる | mwSoft
- 正規表現を使う: 参考サイト → Javaで入力チェックに使える正規表現まとめ
同じ文字種を使う複数の言語から判別
文字種による判定は困難なのでライブラリを使用
同じような漢字を使う日本語と繁体中国語や、同じアルファベットを使う英語とオランダ語の場合、単純な文字種による判別は困難、あるいは precision が低くなる可能性がある。従って、ライブラリを使うことにした。
ライブラリ選定:サイボウズの方が作ったライブラリのフォーク
ライブラリは、サイボウズの方が作った以下のものがあるが、しばらく更新されていないので、
Google Code Archive – Long-term storage for Google Code Project Hosting.
今回は、これのフォークのさらにフォークである以下のライブラリを使用した。
optimaize/language-detector: Language Detection Library for Java
Language Detection Library
内部的な仕組み
同ライブラリは、内部的な仕組みは元のサイボウズの方のライブラリが基盤となっているようなので、以下のスライドが参考になった。
Language Detection Library for Java
大雑把には以下の通り。
- Wikipedia の文章で学習したプロファイルを元に判別
- n-gram
- ナイーブベイズ
使い方
Scala コードだが、Java な人たちもまぁ分かるかと思う。
val languageProfiles = new LanguageProfileReader().readAllBuiltIn() val languageDetector = LanguageDetectorBuilder.create(NgramExtractors.standard()) .withProfiles(languageProfiles) .build() // 短いテキスト用。長いテキスト用の forDetectingOnLargeText メソッドなどもある。 val textObjectFactory = CommonTextObjectFactories.forDetectingShortCleanText() val textObject = textObjectFactory.forText("こんにちは") // Optional[LdLocale] を返す。閾値が高めに設定されてるので、 // 空の値(=判別出来なかった)が返ることが多い。 languageDetector.detect(textObject)
コメントに記載した通り、上のコードだと短い入力文字列だと判定されずに空の値が返ってくることが多い。適当に文字列を入れてみたが、以下のような結果になった。
- slack → 判別不能
- 連携 → 判別不能
detect -> getProbabilities
detect メソッドのコメントには、
Note: you may want to use getProbabilities() instead. This here is very strict, and sometimes returns absent even though the first choice in getProbabilities() is correct.
という事が書かれていた。detect だと、「入力文字列がある言語である確率」が決められた閾値以上のもののうち最大のものが返されるようだが、閾値を超えたものがなければ空が返る。それに対して、getProbabilities を使うと、候補の言語と確率が返される。
getProbabilities を使うようにして、上述のコードの最終行を以下のように変更してみた。
import scala.collection.JavaConversions._ languageDetector.getProbabilities(textObject).headOption.map(_.getLocale)
そうすると、同じ入力値でも以下のように出力が変わった。
- slack → スウェーデン語
- 連携 → 韓国語
いずれにしても短い入力文字列だと結果は良くない。slack がスウェーデン語となるのは理解できなくもないが、韓国語では基本的には漢字を使わないはずなのに漢字と判定されるのはちょっと良くわからない。どういうテキストを使って学習してるのだろうか。
対象言語を絞る
同ライブラリが対応している数十言語の中から判定させると、上のような散々な結果になったため、対象の言語を絞れないかちょっと調べてみた。ライブラリのソースを見たところ、元のコードの先頭を以下のように変更すれば良いことが分かった。
import scala.collection.JavaConversions._ // 使う言語のプロファイルのみを読み込む val languages = List("ja", "en", "br", "fr", "de", "es", "th") val languageProfiles = new LanguageProfileReader().readBuiltIn(languages)
これで試した所
- slack → 英語
- 連携 → 日本語
というように判定された。もう少し色んな文字列で試してみるが、前よりは使える状態になったっぽい。
まとめ
文字列の言語判定に関しては、一般的には以下のことが言えそう。
- 日英程度であれば、使っている文字種で判別するのが一番簡単そう
- 複数言語からの判定の場合はライブラリを使用したほうが良さそう
- 短い入力文字列の場合は精度が低くなりがち
今回使ったライブラリに関してわかったことは以下の通り。
- 入力文字列が短い時用、長い時用で、異なるオブジェクトが用意されている
- 使う言語を絞ることが出来るのであれば、入力文字列が短くてもある程度は精度があげられる
- (本文中には書いてないが、)com.google.common.base.Optional ってクラスを使っているが、Java 8 の Optional にしてほしい