Google謹製Compact Language Detector v3を使ってChromeで言語判定をする

# Electronで言語判定をしたい
趣味で開発しているElectronアプリで、入力された文字列が英語か日本語かはたまたそれ以外の言語かを判別して処理を切り替える機能を設けることにしました。

少し調べると、2010年ごろにサイボウズの方が作られた [language-detection](https://blog.cybozu.io/entry/2158) というライブラリがあることがわかりました。
しかし、これはJavaで作られており、Electronで使うことは難しそうです。

そこで、さらに色々と調べたところ、Chromeで動くGoogle謹製のツールがオープンソースで公開されていることがわかりました。
その名を**Compact Language Detector v3**と言います。
Chromeで動くならElectronでも、それ以外でも非常に幅広く使えますね。

そんな便利なライブラリですが、どうも日本語での情報は皆無のようです。
そこで、本記事ではこの概要と利用方法を説明します。

# Compact Language Detector v3（CLD3）とは
Compact Language Detector v3は、Google製の言語判定ライブラリで、ニューラルネットワークを利用して入力された文字列が何語かを推測します。
107か国語という膨大な数の言語に対応しています。
以下のリポジトリに技術的な説明や詳しい対応言語が載っています。

https://github.com/google/cld3

このツールで「これは言語判定のテストです。」を判定させると、以下のような結果が得られます。

```
language: "ja"
probability: 1
is_reliable: true
proportion: 1
```
`probability`は信頼度で、0~1の範囲を取ります。ちゃんと日本語だと判定されています。

こんどは、英語と中国語を混ぜて「Hello, World! 你好世界」を判定させてみます。

```
language: "zh"
probability: 0.4412570893764496
is_reliable: false
proportion: 1
```

中国語と判定されましたが、信頼度が0.5以下に下がり、`is_reliable`が`false`になっています。

`CLD3`には、多言語が混ざっている文章について、どれくらいの割合でどの言語が含まれているかを判別することもできます。「This is a pen. これはペンです。C'est un livre.」という文章で試してみます。

```
0:
    language: "ja"
    probability: 1
    is_reliable: true
    proportion: 0.42592594027519226
1:
    language: "fr"
    probability: 0.9999618530273438
    is_reliable: true
    proportion: 0.29629629850387573
2:
    language: "en"
    probability: 0.9999408721923828
    is_reliable: true
    proportion: 0.2777777910232544
```

このように、日本語、英語、フランス語と判定されたうえで、`proportion`の値でそれぞれの割合が示されています。

以上でこのライブラリの機能がお分かりいただけたと思います。

# 利用方法

利用方法は二つあります。簡単な方法と、正攻法です。

簡単な方法ではライブラリをラップしてくれるモジュールを利用します。正攻法は、ライブラリ本体をビルドします。

## 簡単な方法
Googleとは別の方が開発したcld3-asmというモジュールを使います。

https://github.com/kwonoj/cld3-asm

まずnpm installします。

```
npm install cld3-asm
```

そのうえでモジュールを読み込みます。

```Javascript:App.vue
import { loadModule } from 'cld3-asm'

const cldFactory = await loadModule();
const langIdentifier = cldFactory.create()
const result = langIdentifier.findLanguage("判定させたい文字列")

const num = 3 //複数の候補を出させる場合の数
const result2 = langIdentifier.findMostFrequentLanguages("判定させたい文字列", num)

console.log(result, result2)
```

`create()`は`create(minBytes, maxBytes)`と二つ引数を取れます。これで判定に使う文字列の長さを指定できます。短文を判定させたいときは`minBytes`に小さな数字を指定してください。

たったこれだけで、上で挙げたような結果が得られます！

## 正攻法
このライブラリはChromiumベースで作られています。なので、ビルドするにはまずChromiumの開発環境を用意する必要があります。

### 注意点
先に断っておきますが、私自身この方法を試し、起動できる実行ファイルの作成には成功したものの、まともに動かせていません。
「私はここまでできた」という報告として紹介します。

なお、Windows 10での環境を想定しています。

### Chromiumの用意
Chromiumの用意方法は以下のページに記載されています（英語）

https://chromium.googlesource.com/chromium/src/+/main/docs/windows_build_instructions.md

このページではChromium本体をビルドする手順まで説明されています（めちゃくちゃ時間かかるらしい）。ですが、CLDを使うだけならそこまで必要はありません。
私は"Get the code"のセクションまでやりました。

かなり複雑で環境変数を特定の仕方でいじるような手順を要求されますが、CLDをビルドするためにどれが必要でどれは必要でないのか判断がつかないので、ここでの説明は控えます。やりたい方はご自分で解読してください。

ともかく、無事Chromiumのリポジトリのフェッチに成功したら、`cd src`として`//リポジトリのディレクトリ/src/`に移動します。

### CLD3のビルド
CLD3のgithubページの説明には、コードを`//third_party/cld_3/`にコピーしろとありますが、フェッチしてきたChromiumの中に最初から入っているので必要ありません。

`//third_party/cld_3/src/src/BUILD.gn`を開き、`executable("language_identifier_main")`の部分のコメントを解除します。

そして、コマンドプロンプトで以下を実行します。

```
gn gen out/Default
ninja -C out/Default third_party/cld_3/src/src:language_identifier_main
```

そうすると、`//out/`ディレクトリに実行ファイルが生成されます。
これを実行すると、以下のようなデモが表示されます！
（文字化けする方は`chcp 65001`として文字コードをUTF-8に変えてください）

```
text: This text is written in English.
  language: en
  probability: 0.999636
  reliable: 1
  proportion: 1

text: Text in deutscher Sprache verfasst.
  language: de
  probability: 0.999535
  reliable: 1
  proportion: 1

text: This piece of text is in English. Този текст е на Български.
  language: bg
  probability: 0.917389
  reliable: 1
  proportion: 0.585366

  language: en
  probability: 0.999979
  reliable: 1
  proportion: 0.414634

  language: und
  probability: 0
  reliable: 0
  proportion: 0
```

同じフォルダの中にある`libc++.dll`と`protobuf_lite.dll`と一緒にコピーすれば他の場所でも動きます。

しかし・・・

これはあくまでデモで、この実行ファイルは標準入力からの入力を受け付けてくれません！

それそそのはず、ソースを見ると以下のようになっています。


```C++:language_identifier_main.cc
int main(int argc, char **argv) {
  NNetLanguageIdentifier lang_id(/*min_num_bytes=*/0,
                                 /*max_num_bytes=*/1000);

  const std::vector<std::string> texts{"This text is written in English.",
                                       "Text in deutscher Sprache verfasst."};
  for (const std::string &text : texts) {
    const NNetLanguageIdentifier::Result result = lang_id.FindLanguage(text);
    std::cout << "text: " << text << std::endl
              << "  language: " << result.language << std::endl
              << "  probability: " << result.probability << std::endl
              << "  reliable: " << result.is_reliable << std::endl
              << "  proportion: " << result.proportion << std::endl
              << std::endl;
  }

  const std::string &text =
      "This piece of text is in English. Този текст е на Български.";
  std::cout << "text: " << text << std::endl;
  const std::vector<NNetLanguageIdentifier::Result> results =
      lang_id.FindTopNMostFreqLangs(text, /*num_langs*/ 3);
  for (const NNetLanguageIdentifier::Result &result : results) {
    std::cout << "  language: " << result.language << std::endl
              << "  probability: " << result.probability << std::endl
              << "  reliable: " << result.is_reliable << std::endl
              << "  proportion: " << result.proportion << std::endl
              << std::endl;
  }
  return 0;
}
```

なぜか、入力文字列がハードコーディングされています・・・

私はこのコードを書き換えて標準入力から受け付けるように改造しようとしましたが、C++素人の自分では無理でした。
それが、「まともに動かせていない」と書いた理由です。

ここまで読ませておいてそれかよ！と思った方、申し訳ありません。
C++やChromiumに詳しい方がこの記事を見ていたらどうやったら使えるようになるのか教えていただけると嬉しいです。

# まとめ
Google謹製の**Compact Language Detector v3**を使えばChromeでニューラルネットワークによる言語判定ができる。
`cld3-asm`モジュールを使えば簡単に利用できる。
本格的にビルドして使う方法もあるけどまだできていない。