openfst quick tourをjupyterlab上で実行
openfst quick tourをjupyterlab上で実行¶
Open Fst Quick Tourをpython上からbinding経由で実行してみたりシェルコマンドを実行したりして試してみます。 若干内容が中途半端なので後日追記するかもしれません。
前提¶
下記を実行済の状態で実行しています。
$ pip install pybind11
$ git clone https://github.com/aldanor/ipybind.git && cd ipybind & python setup build && python setup.py install
$ conda install -c conda-forge openfst
$ apt install graphviz
$ pip install openfst-python
ビルド済のopenfstがcondaでインストールできるようになってて驚きました。
ipybindのロード¶
jupyter labではJavascript Error: require is not defined
と表示されるが問題なく動作はする。
%load_ext ipybind
openfstがインストールされているかの確認¶
condaで入れた場合conda/bin
に格納されている模様
!which fstprint
ipybind11での実行¶
-
-Wl="-lfst"
でlibfst.soをリンクする必要がある
%%pybind11 -fv -Wl="-lfst"
#include <fst/fst-decl.h>
#include <fst/fstlib.h>
namespace fst {
void openfst_basic_operation(void){
// Vector FSTは一般的にmutableなFST
StdVectorFst fst;
// 状態0を最初に空のFSTに追加しstart stateとする
fst.AddState(); // 最初の状態は状態0となる(AddStateの返り値になる)
fst.SetStart(0); // 引数は状態ID
// 状態0から分岐する2つのarcを追加
// arcのコンストラクタの引数は入力ラベル、出力ラベル、重み、宛先となる状態ID
fst.AddArc(0, StdArc(1, 1, 0.5, 1)); // 最初の引数は分岐元の状態ID
fst.AddArc(0, StdArc(2, 2, 1.5, 1));
// 状態1を追加し状態1から2に至るarcを追加
fst.AddState();
fst.AddArc(1, StdArc(3, 3, 2.5, 2));
// 状態2を追加し状態2を最終状態とする(最終状態の重みも追加)
fst.AddState();
fst.SetFinal(2, 3.5); // 最初の引数は状態IDであり、2番目の引数は重みである
// FSTをWriteでファイルに保存
fst.Write("work/binary.fst");
}
}
PYBIND11_MODULE(myadd, m){
m.def("openfst_basic_operation", &fst::openfst_basic_operation, "Tutorial method.");
}
ipybind経由で上記の関数をpythonから実行
openfst_basic_operation()
作成したbinary形式のfstはfstprintで表示が可能
!fstprint work/binary.fst
fstinfoコマンドでfstの情報を表示可能
!fstinfo work/binary.fst
fstdrawコマンドでbinary形式のfstをgraphvizのdot形式へと変換が可能
!fstdraw work/binary.fst work/binary.dot
dot形式はテキストなのでcatで中身を表示
!cat work/binary.dot
上記のままだとorientationがLandscapeになっており縦に表示されるのでsedでPortraitに変換した上でgraphvizのdot
コマンドでpngファイルに変換する。
!sed -e "s/Landscape/Portrait/g" work/binary.dot | dot -Tpng > work/binary.png
from IPython.display import Image
Image("work/binary.png")
シェルからの実行¶
上記のfstはシェルからテキストで定義したFSTの構造ファイルと入力シンボル、出力シンボルを組み合わせることでも作成可能である。
fstのフォーマットは AT&T FSMフォーマットである。
fstの各行のフォーマットは
遷移元状態ID
遷移先状態ID
入力ラベル
出力ラベル
[重み]
最終状態はのフォーマットは
状態ID
[重み]
である。
最初の状態が最初の行でなければならないことを除いて他の行についてはどんな順番で書いても良い。
重みが指定されない場合は0.0(ライブラリのデフォルトであるWeight型に対する値)となる。
%%writefile work/text.fst
0 1 a x .5
0 1 b y 1.5
1 2 c z 2.5
2 3.5
上記定義で入力ラベルに指定されていたa,b,cを1,2,3へと変換するためのファイルとしてisyms.txtを作成する。
%%writefile work/isyms.txt
<eps> 0
a 1
b 2
c 3
同様に上記定義で出力ラベルに指定されていたx,y,zを1,2,3へと変換するためのファイルとしてosyms.txtを作成する。
%%writefile work/osyms.txt
<eps> 0
x 1
y 2
z 3
これらのファイルを組み合わせることでfstcompile
コマンドによってbinary形式のfstを作成可能である。
!fstcompile --isymbols=work/isyms.txt --osymbols=work/osyms.txt work/text.fst work/binary2.fst
念の為再度画像に変換して確認してみると同一のWFSTが作成できていることが分かる。
!fstdraw work/binary2.fst work/binary2.dot
!sed -e "s/Landscape/Portrait/g" work/binary2.dot | dot -Tpng > work/binary2.png
Image("work/binary2.png")
ラベルにはどんな文字列を用いても良く、ラベルIDには非負整数であれば何を用いても良い。 ラベルID 0はepsilonラベルとして予約されている、これは空文字列を表す。 先の例ではテーブルに0を含めたがFSTの作成には用いていない。 後続するFSTの操作ではepsilonを追加するかもしれないので、シンボルファイルに含めておくのは良い心がけである。
このテキスト形式のFSTはOpenFstライブラリにで使用される前にbinary形式へとコンバートしなければならない。
FSTにシンボルテーブルを含める場合は--keep_isymbols
、--keep_osymbols
のオプションを付けて実行する。
!fstcompile --isymbols=work/isyms.txt --osymbols=work/osyms.txt --keep_isymbols --keep_osymbols work/text.fst work/binary2symkeep.fst
!fstinfo work/binary2.fst
!fstinfo work/binary2symkeep.fst
input/output symbol tableに元のテーブルの情報が残っていることが分かる。この場合、fstprintで表示するとラベル文字列が表示される。
!fstprint work/binary2.fst
!fstprint work/binary2symkeep.fst
シンボルテーブルファイルを指定せずにFSTを作った場合を試してみる。
!fstcompile work/text.fst work/binary2wosym.fst
このようにtext.fstで入出力ラベルに非負整数以外が使用されている場合は警告が出るようである。
一方でテキスト形式のFST内においてもしラベルが非負整数で表現されているのであればシンボルテーブルのファイルは不要となる。
この場合はのFSTの内部表現は先に描画した図と同じようになるはずである。
これを確かめるためにラベルを保持していない場合のバイナリ形式FSTであるbinary2.fst
をfstprintし、その結果をfstcompileした上で再度fstprintしてみる。
!fstprint work/binary2.fst | fstcompile - | fstprint -
このようにラベルが非負整数の場合は問題なくfstcompile
が可能であることが分かる。
なお、ラベルが非負整数の場合に先のと同じシンボルテーブルを与えてもシンボルテーブル内に非負整数のラベルに対応したエントリが存在していないためエラーが生じる。
!fstprint work/binary2.fst | fstcompile --isymbols=work/isyms.txt --osymbols=work/osyms.txt - | fstprint -
一度バイナリ形式のFSTが作成されると、(同じアーキテクチャのマシン上であれば)他のシェルレベルのプログラムと組み合わせて使うことも可能である。 またC++コードでは下記コードでコード中でロードすることも可能である。
StdFst *fst = StdFst::Read("binary.fst");
pythonバインディングでの実行¶
openfstには公式に提供されているpywrapfst
というラッパーがあるが、
openfst自体は別にインストールしておく必要があるため新規環境に導入しにくいため別途openfst本体を導入する必要を無くした
openfst-pythonというライブラリが公開されている。
ここではこのopenfst-pythonを使って先程までと同様の内容を実施する。
!pip install openfst-python
import openfst_python as fst
c++のStdVectorFST
はfst.Fst
という名前で生成できる模様である(要確認)。
またc++のStdArc
はfst.Arc
で生成している。
fst.add_arc
メソッドは"遷移元状態ID、Arcオブジェクト、遷移先状態ID"を取る模様であり
第2引数のArcオブジェクトは"入力ラベル、出力ラベル、Weightオブジェクト"を引数に取る。
このWeightオブジェクトは第一引数にFSTのweight_typeを取り、第2引数に重みの値を取る。
ここでは0->1への2つ目の遷移の重みだけはfst.Weight.One
メソッドで与えているが、この第一引数もFSTのweight_typeである。
f = fst.Fst()
s0 = f.add_state()
s1 = f.add_state()
s2 = f.add_state()
f.set_start(s0)
f.add_arc(s0, fst.Arc(1, 2, fst.Weight(f.weight_type(), 0.5), s1))
f.add_arc(s0, fst.Arc(1, 3, fst.Weight.One(f.weight_type()), s1))
f.add_arc(s1, fst.Arc(3, 3, fst.Weight(f.weight_type(), 2.5), s2))
f.set_final(s2, fst.Weight(f.weight_type(), 3.5))
f
上記のようにpythonラッパーはfstオブジェクトを評価すると自動的に画像に変換して描画してくれるようである。 使い方の詳細はドキュメント http://www.openfst.org/twiki/bin/view/FST/PythonExtension を確認すること。
(おまけ)自作pythonバインディングでの実行¶
この記事を書き始めた当初はpybind11でラッパーを作ろうかと思ったのですが分量が結構膨大なのと途中で公式でpythonラッパーを提供していることに気づいたので下記に微妙な痕跡を残しておきます。
%%pybind11 -fv -Wl="-lfst"
#include <fst/fst-decl.h>
#include <fst/fstlib.h>
namespace py = pybind11;
PYBIND11_MODULE(myadd, m){
py::class_<fst::StdArc>(m, "StdArc")
.def(py::init<int, int, double, int>());
py::class_<fst::StdVectorFst>(m, "StdVectorFst")
.def(py::init<>())
.def("add_state", &fst::StdVectorFst::AddState)
.def("add_arc" , py::overload_cast<int, const fst::StdArc&>(&fst::StdVectorFst::AddArc))
.def("set_start", &fst::StdVectorFst::SetStart)
.def("set_final", [](fst::StdVectorFst& self, int state, double weight){ self.SetFinal(state, weight); })
.def("write" , [](fst::StdVectorFst& self, const char* filepath){ self.Write(filepath); });
//.def("set_final", py::overload_cast<int, double>(&fst::StdVectorFst::SetFinal))
//.def("write" , py::overload_cast<const std::string&>(&fst::StdVectorFst::Write));
}
# python binding test
fst = StdVectorFst()
fst.add_state()
fst.set_start(0)
fst.add_arc(0, StdArc(1, 1, 0.8, 1))
fst.add_arc(0, StdArc(2, 2, 1.3, 1))
fst.add_state()
fst.add_arc(1, StdArc(3, 3, 2.2, 2))
fst.add_state()
fst.set_final(2, 3.5)
fst.write("work/binary_py.fst");
!fstdraw work/binary_py.fst work/binary_py.dot
!cat work/binary_py.dot
!sed -e "s/Landscape/Portrait/g" work/binary_py.dot | dot -Tpng > work/binary_py.png
Image("work/binary_py.png")