ねらい
正規表現で文字列処理をしていて「もっとパワフルなツールはないのか」と思ったことはないだろうか。あるいは、WeTextProcessingやNeMoのインストールで「pyniniのビルドに失敗しました」というエラーに遭遇したことは?
この記事では、そのPyniniの正体と、なぜ音声処理や自然言語処理の世界で重宝されているのかを解説する。
対象
- 正規表現に限界を感じている人
- WeTextProcessingやNeMoを使おうとしてPyniniで詰まった人
- 有限状態トランスデューサ(FST)って何?という人
ゴール
- 有限状態トランスデューサの基本概念を理解する
- Pyniniでできることを把握する
- インストール方法と基本的な使い方を知る
TL;DR
PyniniはGoogleが開発した有限状態トランスデューサ(FST)のPythonライブラリ。正規表現より表現力が高く、テキスト正規化、形態素解析、音声認識の言語モデルなど幅広い用途で使われている。
Pyniniとは
公式の説明によると:
This is a Python extension module for compiling, optimizing and applying grammar rules. Rules can be compiled into weighted finite state transducers, pushdown transducers, or multi-pushdown transducers.
(これはPython拡張モジュールで、文法規則のコンパイル、最適化、適用を行います。規則は重み付き有限状態トランスデューサ、プッシュダウントランスデューサ、またはマルチプッシュダウントランスデューサにコンパイルできます。)
引用元: https://pypi.org/project/pynini/
Googleの研究チームが開発し、Apache 2.0ライセンスで公開されている。Kyle Gorman氏が主要な開発者で、2016年にACLワークショップで発表された。
有限状態トランスデューサ(FST)って何?
まず有限状態オートマトン(FSA)から
有限状態オートマトンは、状態とその間の遷移で文字列を認識する仕組み。正規表現は基本的にこれと等価だ。
例えば「cat」「car」「card」を認識するFSAは、c → a → t/r という感じで分岐する木構造で表現できる。
FST = FSAの強化版
FST(Finite State Transducer)は、FSAに「出力」の概念を追加したもの。入力文字列を受け取って、別の文字列に変換できる。
例えば:
- 入力:
2:00 - 出力:
two o'clockまたはtwo(確率付き)
こういう「入力→出力」のマッピングを効率的に表現・処理できるのがFSTの強み。
正規表現との違い
O'Reillyの記事によると:
However, the resulting regular expression will have an unpleasant property—non-determinism—leading to inefficient matching. As a result, the re module is forced to fall back to a "back-tracking" strategy with catastrophic worst-case behavior.
(しかし、結果の正規表現は非決定性という不快な特性を持ち、非効率なマッチングにつながります。その結果、reモジュールはバックトラッキング戦略に頼らざるを得なくなり、最悪ケースで壊滅的な動作をします。)
引用元: https://www.oreilly.com/content/how-to-get-superior-text-processing-in-python-with-pynini/
Pyniniで決定性FSAを構築すれば、この問題を回避できる。
インストール
conda(推奨)
conda install -c conda-forge pynini
conda-forgeからインストールすると、依存関係のOpenFstやGraphvizも一緒に入る。一番楽。
pip
pip install pynini
manylinux wheelが用意されているので、Linux環境なら比較的スムーズにインストールできる。ただしmacOSやWindowsでは苦労することがある。
なぜインストールが難しいのか
PyniniはC++で書かれたOpenFstライブラリに依存している。つまり、Pythonのパッケージだけど中身はC++のバインディング。macOSでOpenFstをビルドするにはいくつかのシステム依存ライブラリが必要で、ここでつまずく人が多い。
Windowsの場合は、WSL(Windows Subsystem for Linux)を使うのが現実的だ。
基本的な使い方
文字列からFSTを作る
import pynini
# acceptor: 単一の文字列を受理するFST
fst = pynini.accep("hello")
# transducer: 入力→出力のマッピング
fst = pynini.cross("2", "two") # "2" を "two" に変換
FSTの操作
# 連結
fst = pynini.accep("hello") + pynini.accep(" ") + pynini.accep("world")
# 和集合(OR)
fst = pynini.accep("cat") | pynini.accep("dog")
# クリーネ閉包(0回以上の繰り返し)
fst = pynini.accep("a").closure()
文字列の変換
# 入力文字列にFSTを適用
input_fst = pynini.accep("hello")
output = pynini.shortestpath(input_fst @ fst).string()
実践例:チーズ名のタグ付け
O'Reillyの記事から引用した例を紹介する。テキスト中のチーズ名を見つけて <cheese> タグで囲むタスクだ。
import pynini
# チーズ名のリスト
cheeses = ("Boursin", "Camembert", "Cheddar", "Edam",
"Gruyere", "Ilchester", "Jarlsberg",
"Red Leicester", "Stilton")
# チーズ名を認識するFSA(決定性、効率的)
cheese_fsa = pynini.string_map(cheeses)
# タグを挿入するトランスデューサ
ltag = pynini.cross("", "<cheese>")
rtag = pynini.cross("", "</cheese>")
# 置換ルール
substitution = ltag + cheese_fsa + rtag
# 文脈依存書き換え規則でラップ
# (マッチしない部分はそのまま通過)
sigma = pynini.union(
*[pynini.accep(chr(i)) for i in range(256)]
)
tagger = pynini.cdrewrite(substitution, "", "", sigma.closure())
このFSTを適用すると、「I love Cheddar and Brie」が「I love Cheddar and Brie」に変換される(Brieはリストにないのでそのまま)。
なぜ音声処理で使われるのか
テキスト正規化
音声合成(TTS)では「123」を「百二十三」に変換する必要がある。これをルールベースで書くとき、FSTは最適な選択肢だ。
NVIDIAのNeMoやWeTextProcessingは、まさにこの用途でPyniniを使っている。
言語モデル
音声認識では、FSTを使ってn-gram言語モデルを効率的に表現・適用できる。OpenFstのngramツールと組み合わせて使うことが多い。
形態素解析
複雑な活用パターンを持つ言語(フィンランド語やトルコ語など)の形態素解析にもFSTは有効。ルールを階層的に組み合わせて複雑な変換を表現できる。
Pyniniが関わるプロジェクト
WeTextProcessing
前回の記事で紹介したテキスト正規化ライブラリ。中国語・英語のTN/ITNにPyniniを使用。
NeMo
NVIDIAの音声・言語処理ツールキット。テキスト正規化モジュールでPyniniを使用。
SpeechBrain
音声処理のPyTorchツールキット。一部の前処理でPyniniが関わる。
espeak-ng
オープンソースの音声合成エンジン。発音辞書の処理にFSTを活用。
Pyniniの設計思想
公式ドキュメントより:
Pynini supports much of the functionality of Thrax, but whereas Thrax is essentially a compiler for a domain-specific language, Pynini is implemented as a Python extension module.
(PyniniはThraxの機能の多くをサポートしていますが、Thraxがドメイン固有言語のコンパイラであるのに対し、PyniniはPython拡張モジュールとして実装されています。)
引用元: https://www.openfst.org/twiki/bin/view/GRM/PyniniDocs
つまり、専用言語を学ぶ必要がなく、Pythonのエコシステム(ログ、テスト、デバッグツールなど)をそのまま活用できる。これが大きなメリットだ。
重み付きFST
Pyniniでは「重み」をFSTに付けられる。これは確率的な変換を表現するのに使う。
例えば「2:00」を読み上げるとき:
- 「two」(確率20%)
- 「two o'clock」(確率80%)
こういう分布をFSTで自然に表現できる。音声認識の曖昧性解消や、複数の変換候補の中から最適なものを選ぶときに役立つ。
まとめ
Pyniniは「正規表現の上位互換」と言っても過言ではないツールだ。
主な特徴:
- 有限状態トランスデューサ(FST)をPythonで扱える
- 正規表現より表現力が高く、効率的
- テキスト正規化、形態素解析、言語モデルなど幅広い用途
- Googleが開発・メンテナンス
インストールの敷居が若干高いのが難点だが、音声処理や自然言語処理を深くやるなら避けて通れないライブラリだ。
参考リンク
- PyPI: https://pypi.org/project/pynini/
- 公式ドキュメント: https://www.openfst.org/twiki/bin/view/GRM/PyniniDocs
- GitHub: https://github.com/kylebgorman/pynini
- Google Research: https://research.google/pubs/pynini-a-python-library-for-weighted-finite-state-grammar-compilation/
- O'Reilly記事: https://www.oreilly.com/content/how-to-get-superior-text-processing-in-python-with-pynini/