BudouX カスタムモデル模索の仕方 & Siv3D で使う

# 0. Siv3D Advent Calender 25日目です
24日目 | [OpenSiv3Dでフレームレートを60fps以外に固定する方法(FrameRateLimitアドオン)](https://qiita.com/m4saka/items/5da6cd4b57bc894d35dd) by m4saka さん

こんにちは、tomolatoon です。
24日目は m4saka さんの、フレームレートを Addon 機能で任意の値に保つ、という内容の記事でした。`System::Update`関数や、Addon 機能についての仕様は知らないことが多かったので、楽しく読ませていただきました。

さて今回は、9月末にリリースされて話題になった「BudouX: 読みやすい改行のための軽量な分かち書き器」について、Siv3D からの利用についてと、BudouX のカスタムモデル構築についての記事となります。（ところで、去年の Advent Calendar を振り返ってみたら、[去年も25日目を担当していました](https://qiita.com/tomolatoon/items/d0666bb008054844b4ce)（笑））

# 1. BudouX って何？
英語は「I have a pen.」みたいな感じで、単語と単語の間をスペースで区切りますよね。一方、日本語は基本的にそのような区切りをしないまま、続けて書いてしまいます。その結果、日本語の文章は読みづらい位置で改行されてしまうことが多くあります。

それを解決するためには、何らかの手段で単語や文章の区切りを検出する必要があります。それに対して、Google は 2016年に Budou というものをリリースしていたのですが、それの後継にあたるのが BudouX です。

Google 公式による紹介はこちら

https://developers-jp.googleblog.com/2023/09/budoux-adobe.html

# 2. BudouX を Siv3D で利用する方法
BudouX のモデルは JSON ファイルとして表現されています。そのため、各利用言語では

1. モデルに基づく評価アルゴリズムを実装しておき、
1. JSON ファイルとしてのモデルを読み込み、
1. 読み込んだモデルを用い、評価アルゴリズムを利用し、
1. 改行ししてよい場所が検出できるのでそれを利用して描画等に使用する

と言った感じで利用することができます。なお、Siv3D 環境では、ラクラムシさんが評価アルゴリズムを実装を用意してくださったので、それを用いればよいです。

## 2.1. tomolatoon の実装
筆者は、ラクラムシさんの実装を拝借して、ranges に対して使用することの出来る実装を用意しました。この記事では、この実装に関して説明していきます。

こちらから入手できます:
[Header 版: `tomolatoon.BudouX.hpp`](https://github.com/tomolatoon/Siv3D_instances/blob/main/tomolatoon/include/tomolatoon/BudouX/BudouX.hpp)
[Module 版: `tomolatoon.BudouX.ixx`](https://github.com/tomolatoon/Siv3D_instances/blob/main/tomolatoon/module/BudouX/tomolatoon.BudouX.ixx)

なお、`rivet.hpp`は onihusube さんのリポジトリから最新版が入手出来ます。ヘッダファイルやモジュールファイルと同じ場所に置いてください。

https://github.com/onihusube/rivet

## 2.2.サンプルコード
こちらに掲載している例を上げておきます。

https://scrapbox.io/Siv3D-instances/BudouX_%E3%82%92_ranges_%E3%81%A8%E5%85%B1%E3%81%AB%E4%BD%BF%E3%81%86

## 2.3. `BudouXParser`の API 解説
筆者が忙しいので、割愛させていただきます。筆者の手が空いたら加筆するかもしれません。（春以降の予定。）ただ、上記 Scrapbox と同じ説明は下に書いておきます。

- `struct tomolatoon::BudouXParser`
    - BudouX で改行位置を検出するパーサです。
    - ラクラムシさんのコードを一部変更したものです。ラクラムシさんありがとうございます。
- `tomolatoon::BudouXParser::Download(URLView url)`
    - URL から JSON ファイルをダウンロードしてパースしてパーサを作成します
- `tomolatoon::BudouXParser:Load(FilePathView path)`
    - ファイルパスから JSON ファイルを読み込んでパースしてパーサを作成します
- `tomolatoon::BudouXParser::Parse(JSON json)`
    - BudouX のモデルが格納されている JSON をパースしてパーサを作成します
- `struct tomolatoon::BudouXBreakView`
    - `BudouXParser`を View としてラップしたものです。直接使うことはないでしょう。
    - Range category
        - `input_range`から構築されれば`input_range`
        - `forward_range`以上から構築されれば`forward_range`
        - 原理上`bidirectional_range`に出来るんですが、需要があるか不明なので実装していません。
- `tomolatoon::detail::BudouXBreakAdaptor BudouXBreak`
    - 使い方: `String(U"すもももももももものうち") | tomolatoon::BudouXBreak(parser)`
    - `parser`は`tomolatoon::BudouXParser`のことです。
    - 既存のを渡すときには`std::ref(parser)`などとするとパフォーマンス的に良いです。

## 2.4. `BudouXParser`のしていること
`BudouX`のアルゴリズムでは、文字ごとに改行場所になり得るかどうかを判定することが出来ます。

具体的には、

1. 判定する文字に対して、改行するかどうかのスコアを計算するために、
1. 先読み2・後読み3を行い
1. 部分文字列事毎にモデルの辞書と突き合わせ、
1. 辞書にあればそれに対応するスコアを加算

という処理を行い、`BudouXParser`に設定した閾値を超えたら改行して良い文字であると判定しています。

## 2.5. 描画のテクニック
ラクラムシさんや Ryo さんが公開していらっしゃるように、改行可能位置がわかった後は、

1. 改行可能位置毎に表示幅を取得し、
1. 表示可能領域を超えているかどうかを調べ、
    1. 超えていなければそのまま表示する
    1. 超えていたら改行してから表示する

とするのがよいでしょう。また、表示を各改行可能位置毎にやらずに溜めておいて、中央寄せや右寄せで表示するのもいいですね。

https://twitter.com/Reputeless/status/1706452398947152133?t=zlDzM6GJ886sZJ8fgan2IA&s=19

# 3. BudouX カスタムモデルの作成
BudouX のカスタムモデルの作り方ですが、基本的には公式リポジトリ Readme に書いてある通りに行えばよいですが、ここでも具体的に説明していきます。

:::note warn
Windows のコマンドプロンプトでは、次のコマンド（コマンドプロンプトの処理に使う文字コードを UTF-8 に変更）を始めに実行してください。

```cmd
chcp 65001
```
:::

## 3.1. 公式リポジトリをクローンする
https://github.com/google/budoux/

これが BudouX のリポジトリです。ここからリポジトリをクローンしてください。Git がインストールされているなら、コマンドラインから次のコマンドを実行します。

```sh
$ git clone https://github.com/google/budoux.git
```

そのあと、カレントディレクトリをクローンして出来たリポジトリのディレクトリに移動してください。

```sh
$ cd ./budoux
```

### 3.1.1. Windows の場合
公式が用意しているカスタムモデルのビルドスクリプトは、ファイル読み込み時の文字コード指定がなされていないために、「Windows 尚且つ UTF-8 が標準でない」環境では文字コードに関する Python のエラーが出てしまいます。それを（Python スクリプト中のファイル読み込み箇所に文字コード指定を付け加えることのよって）解決させます。

例えば:
```py
# before
open(outfile, 'w')

# after
open(outfile, 'w', encoding="utf-8")
```

## 3.2. KNBC Corpus をダウンロード
KNBC Corpus とは、「京都大学情報学研究科--NTTコミュニケーション科学基礎研究所 共同研究ユニット」による日本語の分かち書きに関するデータのことです。

BudouX に付属するモデルはこのデータを元に学習が行われたものです。カスタムモデルを作る際にも、このデータを用いることによって、全て1からデータを作る必要がなくなり、各々の変更したい点に注力することが出来ます。

```sh
$ curl -o knbc.tar.bz2 https://nlp.ist.i.kyoto-u.ac.jp/kuntt/KNBC_v1.0_090925_utf8.tar.bz2
$ tar -xf knbc.tar.bz2  # outputs KNBC_v1.0_090925_utf8 directory
$ python scripts/prepare_knbc.py KNBC_v1.0_090925_utf8 -o source_knbc.txt
```

## 3.3. Corpus のデータを活用してモデルの学習データを作る
KNBC Corpus に基づくファイルが`source_knbc.txt`として出力されたかと思います。`mysource.txt`というファイルを別に作成して、そこに全文をコピペしておきましょう。

そうしたら、`mysource.txt`に変更を加えてみましょう。ここでは、半角スペースも改行されるようなカスタムモデルを作るために、次のような文字列を追加しました。（私の過去記事を少し手直ししたもの。）

```
［C++ ▁談義］▁C++ ▁の▁SFINAE ▁について
もう▁説明し▁尽くされている▁気がします▁が、▁「SFINAE ▁って何？」から▁見ていきます。▁SFINAE は▁「Substitution ▁Failure ▁Is ▁Not ▁An ▁Error」の略。
日本語だと▁「（テンプレート引数の）置き換え失敗は▁（コンパイル）エラーではない」ということ▁になります。
ですから、▁SFINAE ▁は「型特性に▁応じて▁オーバーロードや▁テンプレートの実体化を▁選択する」や、▁「型制約を▁付ける」などの▁場合で▁使用されます。▁C++20 ▁なんだから ▁Concept を▁使えって？▁それは▁後に▁させて▁ください…
Wikipedia の例が▁分かりやすいので、▁Wikipedia から▁引用した▁コードを▁見てください。▁コード中の▁`typedef int foo;`は、▁エイリアス宣言の▁`using foo = int;`と▁等価です。▁なお、▁前述の▁通り▁`typedef`は ▁C++11 では▁オワコンなのです。

```

この文字列は、改行が挿入されうる場所に対して "▁"（U+2581）を挿入してある文字列です。このように書いておくだけで、モデルが出来上がってしまうのです。

## 3.4. カスタムモデルをビルドする
作った学習データをビルドして JSON ファイルとして出力する作業をしていきましょう。コマンドラインから、次のようなコマンドを実行していきます。

```sh
$ python scripts/encode_data.py mysource.txt -o encoded_data.txt
$ python scripts/train.py encoded_data.txt -o weights.txt --iter 100000
$ python scripts/build_model.py weights.txt -o mymodel.json
```

:::note
なお、`--iter 100000`は、学習の回数や精度のようなもので、回数を増やすとより大きな JSON ファイルが出力され、より細かいケースが反映されやすくなります。
:::

## 3.5. 出来上がり！
実のところ、学習の途中であっても、そこまでの学習結果はちゃんと JSON ファイルに書き出されています。ということで何時止めても良かったりはしますが、指定した`--iter`の値だけ待っておきましょう。

ちなみに筆者のノートパソコンだと、平均100秒で10000回分程度進んでいたので、100000 だと16分強程度で終了しました。

出力されたモデルは下記の Gist のようになりました。（Gist には、100000回と150000回の結果を載せておいています。）これを使うと、無事に半角スペースでも改行されるようになりました！

https://gist.github.com/tomolatoon/609cf09e15115d93cd856c19324ef8f7

# 4. おわりに
是非皆さんもカスタムモデルを作って、理想の改行位置を実現してみてくださいね～！