SudachiPyを使ってみる

[SudachiPy](https://github.com/WorksApplications/sudachi.rs/tree/develop/python)はワークス徳島人工知能NLP研究所が開発している形態素解析器[Sudachi](https://github.com/WorksApplications/Sudachi)のPython版です。
バージョン0.6.0からは実装をCythonからRustに変更し、以前のバージョンより20倍ほど実行速度が速くなっています。

# 導入

最初はPythonの仮想環境を作ります。

```bash
python -m venv supy-test
```

`bash`や`zsh`を使う場合は環境を有効にするために以下のようにします。

```bash
source supy-test/bin/activate
```

Windows上のPowershellの場合はPowershell版の有効化スクリプトを使います。

```powershell
.\supy-test\Scripts\Activate.ps1
```

<details><summary>ARMのMac (Apple Silicon) の場合は追加のステップが必要です。</summary>

ARMのMacの場合はバイナリーのパッケージは[まだ存在していない](https://github.com/WorksApplications/sudachi.rs/issues/126)ので、ビルドのために[RustのToolchain](https://rustup.rs/)とPythonのパッケージも必要になります。
```pip install setuptools_rust```

</details>

### インストール

準備が終わったら、SudachiPyをインストールします。

```bash
pip install sudachipy sudachidict_core
```

:::note 
`sudachidict_core` の代わりに、`sudachidict_small`, `sudachidict_full`もインストールできます。
:::

# 試してみる

使い方は二通りあります。

まずは、コマンドラインツール`sudachipy`から起動確認をします。

```bash
$ echo "吾輩は猫である" | sudachipy
吾輩    代名詞,*,*,*,*,*        我が輩
は      助詞,係助詞,*,*,*,*     は
猫      名詞,普通名詞,一般,*,*,*        猫
で      助動詞,*,*,*,助動詞-ダ,連用形-一般      だ
ある    動詞,非自立可能,*,*,五段-ラ行,終止形-一般       有る
EOS
```

## ライブラリとしての使い方

コマンドラインでの利用は簡単な試用にはいいですが、醍醐味があるのはライブラリとしての使い方です。

基本的な使い方は以下の3つのステップです：

```py
>>> dict = sudachipy.Dictionary() # まずは辞書を作る
>>> tokenizer = dict.create() # 辞書から分割器を作る
>>> tokenizer.tokenize("吾輩は猫である") # 分割自体を行う
<MorphemeList[
  <Morpheme(吾輩, 0:2, (0, 350242))>,
  <Morpheme(は, 2:3, (0, 122101))>,
  <Morpheme(猫, 3:4, (0, 571106))>,
  <Morpheme(で, 4:5, (0, 101816))>,
  <Morpheme(ある, 5:7, (0, 12492))>,
]>
```

:::note
`sudachipy.Dictionary(dict="full")` では `sudachidict_full` を使えます。`sudachidict_small`の場合は`sudachipy.Dictionary(dict="small")`を使ってください。
:::

分かち書きの出力：

```py
>>> morphemes = tokenizer.tokenize("吾輩は猫である")
>>> print(*[m.surface() for m in morphemes])
吾輩 は 猫 で ある
```

:::note warn
今後、SudachiPyは解析中に[GILを使わないようにする予定](https://github.com/WorksApplications/sudachi.rs/issues/205)です。スレッドごとに別のTokenizerのインスタンスを使うようにしてください。
:::

## コンポーネントの説明

[Dictionary](https://worksapplications.github.io/sudachi.rs/python/api/sudachipy.html#sudachipy.Dictionary)は辞書データを持つ部品です。辞書データはimmutableなので、基本的に複数のDictionaryを作る必要がなく、スレッド間の共有もできます。

[Tokenizer](https://worksapplications.github.io/sudachi.rs/python/api/sudachipy.html#sudachipy.Tokenizer)は解析中に可変な状態をもつコンポーネントです。中身は可変なので、スレッド間の共有は不可能です。

[MorphemeList](https://worksapplications.github.io/sudachi.rs/python/api/sudachipy.html#sudachipy.MorphemeList)は解析の結果で、形態素列を表す型です。追加の状態を持つ必要がありPythonのListではなく独自実装ですが、Listと同じように使えます。

[Morpheme](https://worksapplications.github.io/sudachi.rs/python/api/sudachipy.html#sudachipy.Morpheme)は形態素です。様々な情報を持っています。大事なのは以下のものです。

* `begin()` \ `end()`：入力の文字列にたいして形態素の始まりと終わりの文字（正確に言えばUnicodeのCodepoint）のオフセット
* `surface()`：形態素の見出し、入力の文字列中の文字列
* `part_of_speech()`：UniDic体系の品詞
* `dictionary_form()`：活用可能な形態素の辞書形
* `normalized_form()`：正規化情報、例）あんまり→余り
* `reading_form()`：形態素の読み

# 分割の単位

Sudachiは複数の単位に分割できます。

* C：固有表現レベル（既定値）
* B：人間に自然なレベル
* A：Unidicの短単位に相当するレベル

分割のレベルの指定の仕方は3通りあります。

## すべての解析を1つのレベルで行う

この場合は`Tokenizer`を作る際に`Dictionary.create()`に`mode`の引数として渡します。

```py
>>> tokenizer = dict.create(mode=sudachipy.SplitMode.C) # 既定値はC
>>> tokenizer.tokenize("関西国際空港")
<MorphemeList[
  <Morpheme(関西国際空港, 0:6, (0, 1564531))>,
]>
>>> tokenizer = dict.create(mode=sudachipy.SplitMode.A)
>>> tokenizer.tokenize("関西国際空港")
<MorphemeList[
  <Morpheme(関西, 0:2, (0, 735344))>,
  <Morpheme(国際, 2:4, (0, 365535))>,
  <Morpheme(空港, 4:6, (0, 602797))>,
]>
```

:::note info
この使い方は一番おすすめです。
:::

## 解析ごとにレベルを変える

この場合は`Tokenizer.tokenize()`に`mode`の引数として渡します。

```py
>>> tokenizer.tokenize("関西国際空港", mode=sudachipy.SplitMode.C)
<MorphemeList[
  <Morpheme(関西国際空港, 0:6, (0, 1564531))>,
]>
>>> tokenizer.tokenize("関西国際空港", mode=sudachipy.SplitMode.B)
<MorphemeList[
  <Morpheme(関西, 0:2, (0, 735344))>,
  <Morpheme(国際, 2:4, (0, 365535))>,
  <Morpheme(空港, 4:6, (0, 602797))>,
]>
>>> tokenizer.tokenize("関西国際空港", mode=sudachipy.SplitMode.A)
<MorphemeList[
  <Morpheme(関西, 0:2, (0, 735344))>,
  <Morpheme(国際, 2:4, (0, 365535))>,
  <Morpheme(空港, 4:6, (0, 602797))>,
]>
```

:::note info
解析ごとに一時的にレベルが変更されるので、Tokenizerのデフォルトの分割モードは変更されません。
:::

## 同時に複数のレベルを使いたい場合

この場合は`Morpheme.split()`を使います。

```py
>>> tokenizer = dict.create(mode=sudachipy.SplitMode.C)
>>> morphemes = tokenizer.tokenize("関西国際空港")
>>> morphemes
<MorphemeList[
  <Morpheme(関西国際空港, 0:6, (0, 1564531))>,
]>
>>> morphemes[0].split(sudachipy.SplitMode.A)
<MorphemeList[
  <Morpheme(関西, 0:2, (0, 735344))>,
  <Morpheme(国際, 2:4, (0, 365535))>,
  <Morpheme(空港, 4:6, (0, 602797))>,
]>
```

良いSudachi Lifeを。