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と表示されるが問題なく動作はする。

In [1]:
%load_ext ipybind

openfstがインストールされているかの確認

condaで入れた場合conda/binに格納されている模様

In [2]:
!which fstprint
/opt/conda/bin/fstprint

ipybind11での実行

  • -Wl="-lfst"でlibfst.soをリンクする必要がある
In [3]:
%%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.");
}
running build_ext
setting C++ standard: -std=c++14
building 'pybind11_41f0e60' extension
creating /home/jovyan/.cache/ipython/pybind11/pybind11_41f0e60/home
creating /home/jovyan/.cache/ipython/pybind11/pybind11_41f0e60/home/jovyan
creating /home/jovyan/.cache/ipython/pybind11/pybind11_41f0e60/home/jovyan/.cache
creating /home/jovyan/.cache/ipython/pybind11/pybind11_41f0e60/home/jovyan/.cache/ipython
creating /home/jovyan/.cache/ipython/pybind11/pybind11_41f0e60/home/jovyan/.cache/ipython/pybind11
/usr/bin/gcc -pthread -B /opt/conda/compiler_compat -Wl,--sysroot=/ -Wsign-compare -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -D_IPYBIND_MODULE_NAME=pybind11_41f0e60 -I/opt/conda/include/python3.7m -c /home/jovyan/.cache/ipython/pybind11/pybind11_41f0e60.cpp -o /home/jovyan/.cache/ipython/pybind11/pybind11_41f0e60/home/jovyan/.cache/ipython/pybind11/pybind11_41f0e60.o -std=c++14 -fvisibility=hidden -flto -isystem /opt/conda/lib/python3.7/site-packages/ipybind-0.1.0-py3.7.egg/ipybind/include -isystem /opt/conda/include -isystem /opt/conda/include -isystem /opt/conda/include
/usr/bin/g++ -pthread -shared -B /opt/conda/compiler_compat -L/opt/conda/lib -Wl,-rpath=/opt/conda/lib -Wl,--no-as-needed -Wl,--sysroot=/ /home/jovyan/.cache/ipython/pybind11/pybind11_41f0e60/home/jovyan/.cache/ipython/pybind11/pybind11_41f0e60.o -L/opt/conda/lib -o build/lib.linux-x86_64-3.7/pybind11_41f0e60.cpython-37m-x86_64-linux-gnu.so -flto -lfst
copying build/lib.linux-x86_64-3.7/pybind11_41f0e60.cpython-37m-x86_64-linux-gnu.so -> /home/jovyan/.cache/ipython/pybind11

ipybind経由で上記の関数をpythonから実行

In [4]:
openfst_basic_operation()

作成したbinary形式のfstはfstprintで表示が可能

In [5]:
!fstprint work/binary.fst
0	1	1	1	0.5
0	1	2	2	1.5
1	2	3	3	2.5
2	3.5

fstinfoコマンドでfstの情報を表示可能

In [6]:
!fstinfo work/binary.fst
fst type                                          vector
arc type                                          standard
input symbol table                                none
output symbol table                               none
# of states                                       3
# of arcs                                         3
initial state                                     0
# of final states                                 1
# of input/output epsilons                        0
# of input epsilons                               0
# of output epsilons                              0
input label multiplicity                          1
output label multiplicity                         1
# of accessible states                            3
# of coaccessible states                          3
# of connected states                             3
# of connected components                         1
# of strongly conn components                     3
input matcher                                     y
output matcher                                    y
input lookahead                                   n
output lookahead                                  n
expanded                                          y
mutable                                           y
error                                             n
acceptor                                          y
input deterministic                               y
output deterministic                              y
input/output epsilons                             n
input epsilons                                    n
output epsilons                                   n
input label sorted                                y
output label sorted                               y
weighted                                          y
cyclic                                            n
cyclic at initial state                           n
top sorted                                        y
accessible                                        y
coaccessible                                      y
string                                            n
weighted cycles                                   n

fstdrawコマンドでbinary形式のfstをgraphvizのdot形式へと変換が可能

In [7]:
!fstdraw work/binary.fst work/binary.dot

dot形式はテキストなのでcatで中身を表示

In [8]:
!cat work/binary.dot
digraph FST {
rankdir = LR;
size = "8.5,11";
center = 1;
orientation = Landscape;
ranksep = "0.4";
nodesep = "0.25";
0 [label = "0", shape = circle, style = bold, fontsize = 14]
	0 -> 1 [label = "1:1/0.5", fontsize = 14];
	0 -> 1 [label = "2:2/1.5", fontsize = 14];
1 [label = "1", shape = circle, style = solid, fontsize = 14]
	1 -> 2 [label = "3:3/2.5", fontsize = 14];
2 [label = "2/3.5", shape = doublecircle, style = solid, fontsize = 14]
}

上記のままだとorientationがLandscapeになっており縦に表示されるのでsedでPortraitに変換した上でgraphvizのdotコマンドでpngファイルに変換する。

In [9]:
!sed -e "s/Landscape/Portrait/g" work/binary.dot | dot -Tpng > work/binary.png
In [10]:
from IPython.display import Image
Image("work/binary.png")
Out[10]:

シェルからの実行

上記のfstはシェルからテキストで定義したFSTの構造ファイルと入力シンボル、出力シンボルを組み合わせることでも作成可能である。

fstのフォーマットは AT&T FSMフォーマットである。 fstの各行のフォーマットは 遷移元状態ID 遷移先状態ID 入力ラベル 出力ラベル [重み] 最終状態はのフォーマットは 状態ID [重み] である。 最初の状態が最初の行でなければならないことを除いて他の行についてはどんな順番で書いても良い。 重みが指定されない場合は0.0(ライブラリのデフォルトであるWeight型に対する値)となる。

In [11]:
%%writefile work/text.fst
0 1 a x .5
0 1 b y 1.5
1 2 c z 2.5
2 3.5
Overwriting work/text.fst

上記定義で入力ラベルに指定されていたa,b,cを1,2,3へと変換するためのファイルとしてisyms.txtを作成する。

については後述する。

In [12]:
%%writefile work/isyms.txt
<eps> 0
a 1
b 2
c 3
Overwriting work/isyms.txt

同様に上記定義で出力ラベルに指定されていたx,y,zを1,2,3へと変換するためのファイルとしてosyms.txtを作成する。

In [13]:
%%writefile work/osyms.txt
<eps> 0
x 1
y 2
z 3
Overwriting work/osyms.txt

これらのファイルを組み合わせることでfstcompileコマンドによってbinary形式のfstを作成可能である。

In [14]:
!fstcompile --isymbols=work/isyms.txt --osymbols=work/osyms.txt work/text.fst work/binary2.fst

念の為再度画像に変換して確認してみると同一のWFSTが作成できていることが分かる。

In [15]:
!fstdraw work/binary2.fst work/binary2.dot
In [16]:
!sed -e "s/Landscape/Portrait/g" work/binary2.dot | dot -Tpng > work/binary2.png
In [17]:
Image("work/binary2.png")
Out[17]:

ラベルにはどんな文字列を用いても良く、ラベルIDには非負整数であれば何を用いても良い。 ラベルID 0はepsilonラベルとして予約されている、これは空文字列を表す。 先の例ではテーブルに0を含めたがFSTの作成には用いていない。 後続するFSTの操作ではepsilonを追加するかもしれないので、シンボルファイルに含めておくのは良い心がけである。

このテキスト形式のFSTはOpenFstライブラリにで使用される前にbinary形式へとコンバートしなければならない。

FSTにシンボルテーブルを含める場合は--keep_isymbols--keep_osymbolsのオプションを付けて実行する。

In [36]:
!fstcompile --isymbols=work/isyms.txt --osymbols=work/osyms.txt --keep_isymbols --keep_osymbols work/text.fst work/binary2symkeep.fst
In [34]:
!fstinfo work/binary2.fst
fst type                                          vector
arc type                                          standard
input symbol table                                none
output symbol table                               none
# of states                                       3
# of arcs                                         3
initial state                                     0
# of final states                                 1
# of input/output epsilons                        0
# of input epsilons                               0
# of output epsilons                              0
input label multiplicity                          1
output label multiplicity                         1
# of accessible states                            3
# of coaccessible states                          3
# of connected states                             3
# of connected components                         1
# of strongly conn components                     3
input matcher                                     y
output matcher                                    y
input lookahead                                   n
output lookahead                                  n
expanded                                          y
mutable                                           y
error                                             n
acceptor                                          y
input deterministic                               y
output deterministic                              y
input/output epsilons                             n
input epsilons                                    n
output epsilons                                   n
input label sorted                                y
output label sorted                               y
weighted                                          y
cyclic                                            n
cyclic at initial state                           n
top sorted                                        y
accessible                                        y
coaccessible                                      y
string                                            n
weighted cycles                                   n
In [37]:
!fstinfo work/binary2symkeep.fst
fst type                                          vector
arc type                                          standard
input symbol table                                work/isyms.txt
output symbol table                               work/osyms.txt
# of states                                       3
# of arcs                                         3
initial state                                     0
# of final states                                 1
# of input/output epsilons                        0
# of input epsilons                               0
# of output epsilons                              0
input label multiplicity                          1
output label multiplicity                         1
# of accessible states                            3
# of coaccessible states                          3
# of connected states                             3
# of connected components                         1
# of strongly conn components                     3
input matcher                                     y
output matcher                                    y
input lookahead                                   n
output lookahead                                  n
expanded                                          y
mutable                                           y
error                                             n
acceptor                                          y
input deterministic                               y
output deterministic                              y
input/output epsilons                             n
input epsilons                                    n
output epsilons                                   n
input label sorted                                y
output label sorted                               y
weighted                                          y
cyclic                                            n
cyclic at initial state                           n
top sorted                                        y
accessible                                        y
coaccessible                                      y
string                                            n
weighted cycles                                   n

input/output symbol tableに元のテーブルの情報が残っていることが分かる。この場合、fstprintで表示するとラベル文字列が表示される。

In [41]:
!fstprint work/binary2.fst
0	1	1	1	0.5
0	1	2	2	1.5
1	2	3	3	2.5
2	3.5
In [40]:
!fstprint work/binary2symkeep.fst
0	1	a	x	0.5
0	1	b	y	1.5
1	2	c	z	2.5
2	3.5

シンボルテーブルファイルを指定せずにFSTを作った場合を試してみる。

In [38]:
!fstcompile work/text.fst work/binary2wosym.fst
FATAL: FstCompiler: Bad arc ilabel integer = "a", source = work/text.fst, line = 1

このようにtext.fstで入出力ラベルに非負整数以外が使用されている場合は警告が出るようである。

一方でテキスト形式のFST内においてもしラベルが非負整数で表現されているのであればシンボルテーブルのファイルは不要となる。 この場合はのFSTの内部表現は先に描画した図と同じようになるはずである。 これを確かめるためにラベルを保持していない場合のバイナリ形式FSTであるbinary2.fstをfstprintし、その結果をfstcompileした上で再度fstprintしてみる。

In [44]:
!fstprint work/binary2.fst | fstcompile - | fstprint -
0	1	1	1	0.5
0	1	2	2	1.5
1	2	3	3	2.5
2	3.5

このようにラベルが非負整数の場合は問題なくfstcompileが可能であることが分かる。

なお、ラベルが非負整数の場合に先のと同じシンボルテーブルを与えてもシンボルテーブル内に非負整数のラベルに対応したエントリが存在していないためエラーが生じる。

In [47]:
!fstprint work/binary2.fst | fstcompile --isymbols=work/isyms.txt --osymbols=work/osyms.txt - | fstprint -
FATAL: FstCompiler: Symbol "1" is not mapped to any integer arc ilabel, symbol table = work/isyms.txt, source = standard input, line = 1
ERROR: FstHeader::Read: Bad FST header: standard input. Magic number not matched. Got: 0

一度バイナリ形式のFSTが作成されると、(同じアーキテクチャのマシン上であれば)他のシェルレベルのプログラムと組み合わせて使うことも可能である。 またC++コードでは下記コードでコード中でロードすることも可能である。

StdFst *fst = StdFst::Read("binary.fst");

pythonバインディングでの実行

openfstには公式に提供されているpywrapfstというラッパーがあるが、 openfst自体は別にインストールしておく必要があるため新規環境に導入しにくいため別途openfst本体を導入する必要を無くした openfst-pythonというライブラリが公開されている。 ここではこのopenfst-pythonを使って先程までと同様の内容を実施する。

In [18]:
!pip install openfst-python
Requirement already satisfied: openfst-python in /opt/conda/lib/python3.7/site-packages (1.7.3)
In [19]:
import openfst_python as fst

c++のStdVectorFSTfst.Fstという名前で生成できる模様である(要確認)。 またc++のStdArcfst.Arcで生成している。

fst.add_arcメソッドは"遷移元状態ID、Arcオブジェクト、遷移先状態ID"を取る模様であり 第2引数のArcオブジェクトは"入力ラベル、出力ラベル、Weightオブジェクト"を引数に取る。 このWeightオブジェクトは第一引数にFSTのweight_typeを取り、第2引数に重みの値を取る。 ここでは0->1への2つ目の遷移の重みだけはfst.Weight.Oneメソッドで与えているが、この第一引数もFSTのweight_typeである。

In [20]:
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))
Out[20]:
FST 0 01 10->1 1:2/0.50->1 1:32 2/3.51->2 3:3/2.5
In [21]:
f
Out[21]:
FST 0 01 10->1 1:2/0.50->1 1:32 2/3.51->2 3:3/2.5

上記のようにpythonラッパーはfstオブジェクトを評価すると自動的に画像に変換して描画してくれるようである。 使い方の詳細はドキュメント http://www.openfst.org/twiki/bin/view/FST/PythonExtension を確認すること。

(おまけ)自作pythonバインディングでの実行

この記事を書き始めた当初はpybind11でラッパーを作ろうかと思ったのですが分量が結構膨大なのと途中で公式でpythonラッパーを提供していることに気づいたので下記に微妙な痕跡を残しておきます。

In [22]:
%%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));
}
running build_ext
setting C++ standard: -std=c++14
building 'pybind11_32de930' extension
creating /home/jovyan/.cache/ipython/pybind11/pybind11_32de930/home
creating /home/jovyan/.cache/ipython/pybind11/pybind11_32de930/home/jovyan
creating /home/jovyan/.cache/ipython/pybind11/pybind11_32de930/home/jovyan/.cache
creating /home/jovyan/.cache/ipython/pybind11/pybind11_32de930/home/jovyan/.cache/ipython
creating /home/jovyan/.cache/ipython/pybind11/pybind11_32de930/home/jovyan/.cache/ipython/pybind11
/usr/bin/gcc -pthread -B /opt/conda/compiler_compat -Wl,--sysroot=/ -Wsign-compare -DNDEBUG -g -fwrapv -O3 -Wall -fPIC -D_IPYBIND_MODULE_NAME=pybind11_32de930 -I/opt/conda/include/python3.7m -c /home/jovyan/.cache/ipython/pybind11/pybind11_32de930.cpp -o /home/jovyan/.cache/ipython/pybind11/pybind11_32de930/home/jovyan/.cache/ipython/pybind11/pybind11_32de930.o -std=c++14 -fvisibility=hidden -flto -isystem /opt/conda/lib/python3.7/site-packages/ipybind-0.1.0-py3.7.egg/ipybind/include -isystem /opt/conda/include -isystem /opt/conda/include -isystem /opt/conda/include
/usr/bin/g++ -pthread -shared -B /opt/conda/compiler_compat -L/opt/conda/lib -Wl,-rpath=/opt/conda/lib -Wl,--no-as-needed -Wl,--sysroot=/ /home/jovyan/.cache/ipython/pybind11/pybind11_32de930/home/jovyan/.cache/ipython/pybind11/pybind11_32de930.o -L/opt/conda/lib -o build/lib.linux-x86_64-3.7/pybind11_32de930.cpython-37m-x86_64-linux-gnu.so -flto -lfst
copying build/lib.linux-x86_64-3.7/pybind11_32de930.cpython-37m-x86_64-linux-gnu.so -> /home/jovyan/.cache/ipython/pybind11
In [23]:
# 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");
In [24]:
!fstdraw work/binary_py.fst work/binary_py.dot
In [28]:
!cat work/binary_py.dot
digraph FST {
rankdir = LR;
size = "8.5,11";
center = 1;
orientation = Landscape;
ranksep = "0.4";
nodesep = "0.25";
0 [label = "0", shape = circle, style = bold, fontsize = 14]
	0 -> 1 [label = "1:1/0.8", fontsize = 14];
	0 -> 1 [label = "2:2/1.3", fontsize = 14];
1 [label = "1", shape = circle, style = solid, fontsize = 14]
	1 -> 2 [label = "3:3/2.2", fontsize = 14];
2 [label = "2/3.5", shape = doublecircle, style = solid, fontsize = 14]
}
In [29]:
!sed -e "s/Landscape/Portrait/g" work/binary_py.dot | dot -Tpng > work/binary_py.png
In [30]:
Image("work/binary_py.png")
Out[30]:

コメント

Comments powered by Disqus