Automata Learning とは、未知の (ブラックボックス) システムに対する入力とその出力から、システムの振舞いを有限状態オートマトンとして再現する手法です。
これは、仕様が形式化されていないシステムに対して形式的な手法を適用するための足掛りになるなど、近年重要な技術となっています。
Angluin によるL ∗ L\ast L ∗ は Automata Learning のアルゴリズムの中でも最も代表的な存在です。
L ∗ L\ast L ∗ は未知の正規言語を教師を使って学習するアルゴリズムで、多くの Automata Learning の基礎となっています。
この記事ではL ∗ L\ast L ∗ の、その原理やアルゴリズムの詳細について解説します。
L ∗ L\ast L ∗ について
L ∗ L\ast L ∗ はDana Angluin によって提案された Automata Learning のアルゴリズムです[1] 。
教師を用いて、未知の正規言語のオートマトン (DFA) による表現を学習できます。
ここで言う「未知」というのは、オートマトンや正規表現でどのように表すのかは定かではない、という状況を表します。
L = { w ∣ w は長さが3の倍数 } L = \{ w\ |\ w\text{は長さが3の倍数} \} L = { w ∣ w は長さが 3 の倍数 } やL = { w ∣ w は a b a と b a b を含む } L = \{ w\ |\ w\text{は}aba\text{と}bab\text{を含む} \} L = { w ∣ w は aba と bab を含む } のように文章で言語が定義されている場合や、もっと現実的な例では、学習したい言語は既にコンパイルされたプログラムで受理される文字列として定義されるような状況をイメージしてください。
L ∗ L\ast L ∗ について、まずはじめに動作の大雑把なイメージを説明します。
L ∗ L\ast L ∗ の動作のイメージ
L ∗ L\ast L ∗ の登場人物は「教師 (Teacher)」と「学習者 (Learner)」の二人です。
この例では、L = { w ∣ w は長さが3の倍数 } L = \{ w\ |\ w\text{は長さが3の倍数} \} L = { w ∣ w は長さが 3 の倍数 } をL ∗ L\ast L ∗ で学習する様子を説明していきます。
学習者は学習対象のシステムを予想して仮説 (Hypothesis) となるオートマトンを構築し、それが正しいかどうかを教師に問いかけます。
教師はその仮説が正しいかどうかを答えます。間違っている場合、反例となる入力も同時に返します。
学習者は反例を元に、仮説のオートマトンを修正します。
修正した仮説のオートマトンが正しいかを、再度教師に問いかけます。
仮説のオートマトンが正しい場合、L ∗ L\ast L ∗ が終了します。
このように学習者が教師に問いかけを繰り返して、仮説を学習対象のオートマトンに近付けていくのがL ∗ L\ast L ∗ となります。
L ∗ L\ast L ∗ の詳細
それでは、学習者はどのようにして仮説のオートマトンを構築・修正していくのでしょうか?
ここからは、L ∗ L\ast L ∗ の詳細について説明していきます。
なお、この説明は Hames Worrell の講義ノート[2] を元にしています。
教師
ここまで教師と言ってきましたが、具体的には次の 2 つのクエリ (関数) の組として定義されます。
M E M B E R ( w ) \mathsf{MEMBER}(w) MEMBER ( w ) : 文字列w w w が学習対象のオートマトンで受理されるかどうかを判定するクエリ。
E Q U I V ( H ) \mathsf{EQUIV}(\mathcal{H}) EQUIV ( H ) : 仮説のオートマトンH \mathcal{H} H が学習対象のオートマトンと等しいかどうかを判定するクエリ。違う場合は反例となる文字列を返します。
Q Q Q とT T T
L ∗ L\ast L ∗ ではQ Q Q とT T T という 2 つの文字列の集合を、空文字列のみの集合{ ε } \{ \varepsilon \} { ε } から更新していき、この文字列の集合によって仮説のオートマトンを構築します。
それぞれ、次のような役割になります。
Q Q Q : 仮説のオートマトンの状態集合となる文字列の集合。
T T T : Q Q Q の各文字列の末尾に連結したときに、受理されるかどうかで区別できる文字列の集合。
これだけだとよく分からないので、さらなる説明のためにT T T による等価性≡ T \equiv_T ≡ T を次のように定義します。
q 1 ≡ T q 2 ⟺ ∀ t ∈ T . M E M B E R ( q 1 ⋅ t ) = M E M B E R ( q 2 ⋅ t ) q_1 \equiv_T q_2 \iff \forall t \in T.\: \mathsf{MEMBER}(q_1 \cdot t) = \mathsf{MEMBER}(q_2 \cdot t) q 1 ≡ T q 2 ⟺ ∀ t ∈ T . MEMBER ( q 1 ⋅ t ) = MEMBER ( q 2 ⋅ t )
q 1 ≡ T q 2 q_1 \equiv_T q_2 q 1 ≡ T q 2 のとき「q 1 q_1 q 1 とq 2 q_2 q 2 はT T T によって等価」と呼び、そうでない場合 (q 1 ̸ ≡ T q 2 q_1 \not\equiv_T q_2 q 1 ≡ T q 2 ) 「q 1 q_1 q 1 とq 2 q_2 q 2 はT T T によって区別される」と呼ぶことにします。
このとき、Q Q Q はT T T によって区別される文字列の集合と説明できます。
つまり、Q Q Q は次の分離性 (Separability) 条件を満たすものとします。
∀ q 1 , q 2 ∈ Q . ( q 1 = q 2 ⟺ q 1 ≡ T q 2 ) \forall q_1, q_2 \in Q.\ (q_1 = q_2 \iff q_1 \equiv_T q_2) ∀ q 1 , q 2 ∈ Q . ( q 1 = q 2 ⟺ q 1 ≡ T q 2 )
さらに、Q Q Q とT T T からオートマトンを構築するために、次の閉鎖性 (Closedness) 条件を考えます。
∀ q 1 ∈ Q , σ ∈ Σ . ∃ q 2 ∈ Q . q 1 ⋅ σ ≡ T q 2 \forall q_1 \in Q, \sigma \in \Sigma.\: \exists q_2 \in Q.\: q_1 \cdot \sigma \equiv_T q_2 ∀ q 1 ∈ Q , σ ∈ Σ. ∃ q 2 ∈ Q . q 1 ⋅ σ ≡ T q 2
Q Q Q とT T T が分離性条件と閉鎖性条件を満たしているとき、次のようにして仮説の DFA H ( Q , T ) \mathcal{H}(Q, T) H ( Q , T ) を構築できます。
H ( Q , T ) = ( Σ , Q , ε , F , δ ) where F = { q ∣ q ∈ Q ∧ M E M B E R ( q ) } δ ( q , σ ) = q ′ s.t. q ′ ≡ T q ⋅ σ \begin{array}{l}
\mathcal{H}(Q, T) = (\Sigma, Q, \varepsilon, F, \delta) \\
\text{where} \\
\quad\begin{array}{rcl}
F &=& \{ q\ |\ q \in Q \land \mathsf{MEMBER}(q) \} \\
\delta(q, \sigma) &=& q'\ \text{s.t.}\ q' \equiv_T q \cdot \sigma
\end{array}
\end{array} H ( Q , T ) = ( Σ , Q , ε , F , δ ) where F δ ( q , σ ) = = { q ∣ q ∈ Q ∧ MEMBER ( q )} q ′ s.t. q ′ ≡ T q ⋅ σ
分離性条件によりδ \delta δ は決定的に定義されて、閉鎖性条件によりδ \delta δ が正しく定義されることに注意してください。
Q Q Q とT T T の更新方法
L ∗ L\ast L ∗ は次のような手順のアルゴリズムになります。
Q Q Q とT T T を{ ε } \{ \varepsilon \} { ε } で初期化
Q Q Q を閉鎖性を満たすように更新する
仮説の DFA H = H ( Q , T ) \mathcal{H} = \mathcal{H}(Q, T) H = H ( Q , T ) を構築し、E Q U I V ( H ) \mathsf{EQUIV}(\mathcal{H}) EQUIV ( H ) を呼ぶ
E Q U I V ( H ) \mathsf{EQUIV}(\mathcal{H}) EQUIV ( H ) が成功した場合は、H \mathcal{H} H を返す
E Q U I V ( H ) \mathsf{EQUIV}(\mathcal{H}) EQUIV ( H ) が失敗した場合は、反例を分割して必要なq ′ , t ′ q', t' q ′ , t ′ を求めQ , T Q, T Q , T に追加する
2 から 6 をE Q U I V ( H ) \mathsf{EQUIV}(\mathcal{H}) EQUIV ( H ) が成功するまで繰り返す
ここで問題となるのは、手順 2 や 5 でQ Q Q やT T T をどのようにして更新していくかです。
これらの更新方法について説明していきます。
まず、手順 2 のQ Q Q を閉鎖性を満たすように更新していく方法について説明します。
手順 1 でQ = { ε } Q = \{ \varepsilon \} Q = { ε } としたときや、手順 5 でQ Q Q にq ′ q' q ′ を追加したあと、Q Q Q とT T T は閉鎖性を満たしていない可能性があります。
そこで、閉鎖性を満たすように足りない文字列をQ Q Q に追加する必要があります。
これを行う関数は次のようになります。
Algorithm 1 Correct Q Q Q to satisfy closedness.
function Close (Q , T Q, T Q , T )
for q ∈ Q q \in Q q ∈ Q do
for σ ∈ Σ \sigma \in \Sigma σ ∈ Σ do
if ¬ ∃ q ′ ∈ Q . q ⋅ σ ≡ T q ′ \lnot \exists q' \in Q.\: q \cdot \sigma \equiv_T q' ¬∃ q ′ ∈ Q . q ⋅ σ ≡ T q ′ then
Q ← Q ∪ { q ⋅ σ } Q \gets Q \cup \{ q \cdot \sigma \} Q ← Q ∪ { q ⋅ σ }
end if
end for
end for
return Q Q Q
end function
内容はシンプルで、各状態q q q とアルファベットσ \sigma σ について、q ⋅ σ q \cdot \sigma q ⋅ σ とT T T によって等価な状態が存在するかを調べて、無い場合はq ⋅ σ q \cdot \sigma q ⋅ σ を追加する、といったものになっています。
次に、手順 5 の反例を分割する方法について説明します。
これは、次のような関数で実現できます。
ただし、w w w はE Q U I V ( H ) \mathsf{EQUIV}(\mathcal{H}) EQUIV ( H ) の返した反例の文字列で、H \mathcal{H} H は仮定の DFA です。
Algorithm 2 Split the given counterexample.
function Split (w , H w, \mathcal{H} w , H )
H = ( Σ , Q , q 0 , F , δ ) \mathcal{H} = (\Sigma, Q, q_0, F, \delta) H = ( Σ , Q , q 0 , F , δ )
q ← q 0 , i ← 1 q \gets q_0,\ i \gets 1 q ← q 0 , i ← 1
while t r u e \mathbf{true} true do
q ← δ ( q , w i ) , i ← i + 1 q \gets \delta(q, w_i),\ i \gets i + 1 q ← δ ( q , w i ) , i ← i + 1
if M E M B E R ( q ⋅ w i … w ∣ w ∣ ) ≠ M E M B E R ( w ) \mathsf{MEMBER}(q \cdot w_i \dots w_{|w|}) \ne \mathsf{MEMBER}(w) MEMBER ( q ⋅ w i … w ∣ w ∣ ) = MEMBER ( w ) then
return ( w 1 … w i − 1 , w i … w ∣ w ∣ ) (w_1 \dots w_{i-1}, w_i \dots w_{|w|}) ( w 1 … w i − 1 , w i … w ∣ w ∣ )
end if
end while
end function
w w w は反例の文字列のため、どこかしらにはM E M B E R ( q ⋅ w i … w ∣ w ∣ ) ≠ M E M B E R ( w ) \mathsf{MEMBER}(q \cdot w_i \dots w_{|w|}) \ne \mathsf{MEMBER}(w) MEMBER ( q ⋅ w i … w ∣ w ∣ ) = MEMBER ( w ) となる状態q q q があり、アルゴリズムは正しく分割結果を返します。
L ∗ L\ast L ∗ アルゴリズム
最後に、上で定義した関数を使ったL ∗ L\ast L ∗ のアルゴリズムを疑似コードで示します。
Algorithm 3 The L ∗ L\ast L ∗ algorithm.
function Learn ()
Q ← { ε } , T ← { ε } Q \gets \{ \varepsilon \},\ T \gets \{ \varepsilon \} Q ← { ε } , T ← { ε }
while t r u e \mathbf{true} true do
Q ← Q \gets Q ← Close (Q , T Q, T Q , T )
H = H ( Q , T ) \mathcal{H} = \mathcal{H}(Q, T) H = H ( Q , T )
r e s u l t = E Q U I V ( H ) \mathnormal{result} = \mathsf{EQUIV}(\mathcal{H}) res u lt = EQUIV ( H )
if r e s u l t \mathnormal{result} res u lt is success then
end if
Now, r e s u l t \mathnormal{result} res u lt is a counterexample w w w .
( q ′ , t ′ ) = (q', t') = ( q ′ , t ′ ) = Split (w , H w, \mathcal{H} w , H )
Q ← Q ∪ { q ′ } , T ← T ∪ { t ′ } Q \gets Q \cup \{ q' \},\ T \gets T \cup \{ t' \} Q ← Q ∪ { q ′ } , T ← T ∪ { t ′ }
end while
end function
大体前の説明の通りになっているのが分かるのではないかと思います。
アルゴリズムの分析
L ∗ L\ast L ∗ の正確性や停止性、計算量について少し分析します。
正確性・停止性
L ∗ L\ast L ∗ はE Q U I V ( H ) \mathsf{EQUIV}(\mathcal{H}) EQUIV ( H ) に成功した場合に停止するため、教師が正しければ結果の DFA は学習対象のオートマトンと等価なものになるはずです。
一方、L ∗ L\ast L ∗ がいずれ停止するという保証はどこにあるのでしょうか?
これは、次のMyhill-Nerode の定理 から説明できます。
言語L L L の同値関係≡ L \equiv_L ≡ L をw 1 ≡ L w 2 ⟺ ∀ w ∈ Σ ∗ . ( w 1 w ∈ L ⟺ w 2 w ∈ L ) w_1 \equiv_L w_2 \iff \forall w \in \Sigma^\ast.\: (w_1 w \in L \iff w_2 w \in L) w 1 ≡ L w 2 ⟺ ∀ w ∈ Σ ∗ . ( w 1 w ∈ L ⟺ w 2 w ∈ L ) で定義し、≡ L \equiv_L ≡ L による同値類[ w ] L = { w ′ ∈ Σ ∗ ∣ w ≡ L w ′ } [w]_L = \{ w' \in \Sigma^\ast | w \equiv_L w' \} [ w ] L = { w ′ ∈ Σ ∗ ∣ w ≡ L w ′ } で定義する。
このとき同値類[ w ] L [w]_L [ w ] L の数が有限である、かつそのときに限りL L L は正規言語である。
関数Learn のループでは、一周毎にQ Q Q の要素が少なくとも 1 つは増加してきます。
Q Q Q の各要素はT T T によって区別できるのですが、これはつまり学習対象の言語ではQ Q Q の各要素はそれぞれ別の同値類に入ることを意味します。
よって、Q Q Q が無尽蔵に増え続けるということは起こり得ず、同値類の数程度 (つまり最小オートマトンの状態数) のループ回数で停止するはずだと分かります。
さらに、同値類が異なるように状態を分けていることから、L ∗ L\ast L ∗ の結果の DFA は最小 DFA となることも分かります。
計算量
計算量として、E Q U I V ( H ) \mathsf{EQUIV}(\mathcal{H}) EQUIV ( H ) やM E M B E R ( w ) \mathsf{MEMBER}(w) MEMBER ( w ) の呼び出される回数について考察します。
ここでのQ Q Q とT T T はアルゴリズムが結果を返す直前のQ Q Q とT T T とします。
まず、E Q U I V ( H ) \mathsf{EQUIV}(\mathcal{H}) EQUIV ( H ) の呼び出される回数は、単純に∣ T ∣ − 1 |T| - 1 ∣ T ∣ − 1 回のはずです。
次に、M E M B E R ( w ) \mathsf{MEMBER}(w) MEMBER ( w ) の呼び出される回数を考えます。
≡ T \equiv_T ≡ T の中でもO ( ∣ T ∣ ) O(|T|) O ( ∣ T ∣ ) 回呼び出されることに注意すると、関数Close で呼び出される回数はO ( ∣ Q ∣ ∣ Σ ∣ ∣ Q ∣ ∣ T ∣ ) O(|Q||\Sigma||Q||T|) O ( ∣ Q ∣∣Σ∣∣ Q ∣∣ T ∣ ) 回程度だと分かります。
ただし、q ∈ Q , t ∈ T q \in Q, t \in T q ∈ Q , t ∈ T に対するM E M B E R ( q ⋅ t ) \mathsf{MEMBER}(q \cdot t) MEMBER ( q ⋅ t ) の呼び出しは≡ T \equiv_T ≡ T の中で何度も行なわれるので、結果をキャッシュすることで、呼び出し回数をO ( ( ∣ Q ∣ ∣ Σ ∣ + ∣ Q ∣ ) ∣ T ∣ ) O((|Q||\Sigma| + |Q|)|T|) O (( ∣ Q ∣∣Σ∣ + ∣ Q ∣ ) ∣ T ∣ ) 回程度にできます。
また、関数Split の中で呼び出される回数は、反例の文字列さをw w w としてO ( ∣ w ∣ ) O(|w|) O ( ∣ w ∣ ) 回程度のはずです。
∣ w ∣ |w| ∣ w ∣ は無尽蔵に大きくなりそうに思えますが、もしE Q U I V ( H ) \mathsf{EQUIV}(\mathcal{H}) EQUIV ( H ) が常に最小の反例を返すのであれば∣ w ∣ |w| ∣ w ∣ は最小 NFA の大きさ∣ Q ∣ |Q| ∣ Q ∣ 以下となるはずなので、実際にはそれほど大きくなりません。
さらに、分割点の探索をM E M B E R ( q i − 1 ⋅ w i … w ∣ w ∣ ) ≠ M E M B E R ( q i ⋅ w i + 1 … w ∣ w ∣ ) \mathsf{MEMBER}(q_{i-1} \cdot w_i \dots w_{|w|}) \ne \mathsf{MEMBER}(q_i \cdot w_{i+1} \dots w_{|w|}) MEMBER ( q i − 1 ⋅ w i … w ∣ w ∣ ) = MEMBER ( q i ⋅ w i + 1 … w ∣ w ∣ ) となる位置i i i を 2 分探索で探すことでO ( log ∣ w ∣ ) O(\log |w|) O ( log ∣ w ∣ ) で実現できます。
∣ T ∣ ≤ ∣ Q ∣ |T| \le |Q| ∣ T ∣ ≤ ∣ Q ∣ であることを踏まえてまとめると、O ( ( ( ∣ Q ∣ ∣ Σ ∣ + ∣ Q ∣ ) ∣ T ∣ + log ∣ w ∣ ) ∣ T ∣ ) = O ( ∣ Q ∣ ( ∣ Q ∣ 2 ∣ Σ ∣ + log ∣ w ∣ ) ) O(((|Q||\Sigma| + |Q|)|T| + \log |w|)|T|) = O(|Q| (|Q|^2 |\Sigma| + \log |w|)) O ((( ∣ Q ∣∣Σ∣ + ∣ Q ∣ ) ∣ T ∣ + log ∣ w ∣ ) ∣ T ∣ ) = O ( ∣ Q ∣ ( ∣ Q ∣ 2 ∣Σ∣ + log ∣ w ∣ )) 程度だと分かります。
今一効率が良いと言えるのか分かりませんが、多項式時間で未知の正規言語の DFA が求められるというのは意外なことだと思います。
実装
ここまで理解すれば実装は難しくないのではないかと思います。
参考までに Ruby による実装を残しておきます。
class DFA
def initialize (state_set, initial, accept_set, delta)
@state_set = state_set
@initial = initial
@accept_set = accept_set
@delta = delta
end
def run (w)
q, i = @initial, 0
while i < w.size && q
q, i = @delta[q][w[i]], i + 1
end
i == w.size && @accept_set.include?(q)
end
end
class LStar
def initialize (alphabet, teacher)
@alphabet = alphabet
@teacher = teacher
@state_set = [ '' ]
@tests = [ '' ]
end
def learn
loop do
delta = close
h = make_hypothesis(delta)
result = @teacher.find_counterexample(h)
return h unless result
new_state, new_test = split(delta, result)
@state_set << new_state unless @state_set.include?(new_state)
@tests << new_test unless @tests.include?(new_test)
end
end
def distinguish? (w1, w2)
@tests.any? { |t| @teacher.member?(w1 + t) != @teacher.member?(w2 + t) }
end
def close
delta = {}
@state_set.each do |state|
delta[state] = {}
@alphabet.each do |a|
next_state = @state_set.find { |q| ! distinguish?(state + a, q) }
unless next_state
next_state = state + a
@state_set << next_state
end
delta[state][a] = next_state
end
end
delta
end
def split (delta, w)
q, i = '' , 0
expected = @teacher.member?(w)
loop do
unless @teacher.member?(delta[q][w[i]] + w[i + 1 ..]) == expected
return [q + w[i], w[i + 1 ..]]
end
q, i = delta[q][w[i]], i + 1
end
end
def make_hypothesis (delta)
DFA . new (
@state_set,
'' ,
@state_set.filter { |q| @teacher.member?(q) },
delta
)
end
end
使い方は次のようにmember?
とfind_counterexample
メソッドを持ったクラスを用意して、それをteacher
としてLStar
を作り、learn
メソッドを呼び出します。
下の例ではL = { w ∣ w は長さが3の倍数 } L = \{ w\ |\ w\text{は長さが3の倍数} \} L = { w ∣ w は長さが 3 の倍数 } の場合の学習を行なっています。
class SampleTeacher
def member? (w)
w.size % 3 == 0
end
def find_counterexample (h)
10 .times.map { 'a' * _1 }.find { |w| h.run(w) != member?(w) }
end
end
teacher = SampleTeacher . new
learner = LStar . new ([ 'a' ], teacher)
dfa = learner.learn
pp dfa
ちなみに、あまり効率の良い実装ではないことには注意してください。
工夫できるところは色々あるので、より効率的な実装を追求してみてもいいでしょう。
反例の見つけ方
実際に実装をしてみると悩むのが、どうやって反例を見つけるか、という点だと思います。
上の例はアルファベットが 1 種類の単純なものなので、適当な回数繰り返して試せばいいのですが、アルファベットが複数あるとこの方法も使えません。
代表的な方法は、DFA をランダムに遷移して文字列を生成する方法です。
初期状態からランダムに何度か遷移して、その遷移に使った文字列で反例を探します。
この方法は効率的ですが、ランダム性があるため場合によっては学習が不完全に打ち切られてしまう可能性があります。
他にも W method や WP method と呼ばれる決定的な方法も存在します[3] 。
LearnLib であれば、これらの方法を試すことができるので、使ってみるといいかもしれません。
発展
Mealy 機械への応用 :
少し工夫することでMealy 機械 を学習することもできます。
具体的にはM E M B E R ( w ) \mathsf{MEMBER}(w) MEMBER ( w ) の結果を受理したかどうかではなく、最後の遷移の出力とすることで Mealy 機械の学習が可能となります。
Compisitional Automata Learning :
冒頭で書いた通り、近年 Automata Learning は形式検証への応用が盛んに行なわれています。
しかし、対象のシステムのオートマトンでの状態数が大きい場合、現実的な時間で学習が行なえない場合があります。
そういった問題を解決するために、現実のシステムは複数のコンポーネントから構成されていることに注目して、複数コンポーネントからなるオートマトンを学習する Compisitional Automata Learning の手法が研究されています[4] [5] 。
あとがき
永らく書こうと思って放置していたL ∗ L\ast L ∗ についての記事をまとめることができました。
L ∗ L\ast L ∗ は有限状態オートマトンの有限性が如実に現れた興味深いアルゴリズムだと思います。
さらに、実用上の重要性も高まっており、研究対象としても興味深いのではないかと考えています。
あと本文とは直接関係ないのですが、この記事のために Markdown 中にpseudocode
を書けるようにしてみました。
pseudocode.js を使っています。
どうだったでしょうか。
終わりに、結構長い記事になってしまいましたが、最後まで目を通していただきありがとうございました。
参考文献
Angluin, Dana. "Learning regular sets from queries and
counterexamples." Information and computation 75.2 (1987): 87-106.
James Worrell. "Exactly Learning Regular Languages Using
Membership and Equivalence Queries." Lecture notes by James Worrell,
University of Oxford.
https://www.cs.ox.ac.uk/people/james.worrell/DFA-learning.pdf
Khendek, Fujiwara Bochmann, et al. "Test selection based
on finite state models." IEEE Transactions on software engineering 17.591-603
(1991): 10-1109.
Labbaf, Faezeh, et al. "Compositional Learning for
Interleaving Parallel Automata." Foundations of Software Science and
Computation Structures LNCS 13992 (2023): 413.
Neele, Thomas, and Matteo Sammartino. "Compositional
Automata Learning of Synchronous Systems." International Conference on
Fundamental Approaches to Software Engineering. Cham: Springer Nature
Switzerland, 2023.