音声AIの評価指標CER(文字誤り率)を解説!——意外と知らない“マクロCER/マイクロCER”の違いまで——
はじめに
Parakeet株式会社リサーチャーの榎本 (X: @henomoto1025)です。純粋数学で博士号を取りポスドクをしていましたが、音声の分野に興味が移り、現在は音声界隈の研究のキャッチアップをしながら研究開発をしています。
さて、今回は、日本語の音声AIの評価に使われる CER (Character Error Rate, 文字誤り率) について初見で分かるように解説し、また(私自身が知らなかった)複数発話のCERを集約する際の注意事項について説明します。
この記事で伝えたいこと
まず最初に、この記事で伝えたいことについて簡単にまとめます。
- CERは主に日本語の音声認識の評価に用いられる指標。だが、音声変換や音声合成の評価にも用いられる。
- CERは「正しい文字列から比べて、生成結果の文字列がどれだけの割合間違っているか」を表す定量的な指標。編集距離 という概念を用いて定式化すると分かりやすい。
- 複数サンプルのCER結果をまとめて一つの値を出すとき、 CERの単純平均(マクロCER) と、 編集距離の和を使って計算するマイクロCER がある。
- CERの単純平均(マクロCER)では、サンプルの文章長に由来して予期せぬ結果になってしまう可能性があり、集約計算時は注意が必要。
これらについて、以下では基礎から詳しく解説していきます。
CERのモチベーション
音声を扱うAIで代表的なものは、次のようなものがあります。
- 音声認識: 音声を受け取り、それが何を喋っているかの文字列を出力する。
- 音声変換: 誰かの音声を受け取り、内容はそのまま、その声音を別の人の声に変換した音声を出力する。
- 音声合成: 日本語文字列を受け取り、それを喋っている音声を出力する。
音声認識モデルの評価として
さて、CER(文字誤り率)は、この中で直接的には音声認識AIの精度を評価する数値指標です。
ある音声認識AIのモデルを作ったとき、この精度を測るにはどうしたらよいでしょうか?
音声認識モデルは、参照音声
を受け取ると、そこから 予測テキスト
を返す関数だとみなせます。
なので、参照音声の 正解テキスト
がある場合は、 正解テキスト
と比べて 予測テキスト
がどれほどズレてしまっているかを計測することで、その音声に対する評価を出すことができます。
この正解からのズレを測る指標がまさにCERにより与えられるというのがCERのモチベです。
具体的な定義や計算方法は後で与えるとして、CERは、次のようなある関数だと考えてください。
- 入力として、 正解テキスト
ref_text
と 予測テキストhyp_text
の2つの文字列を受け取る。 - 出力として、ある0以上の実数
CER(ref_text, hyp_text)
を返す。値が小さいほど良い。よく100倍して%表記される。
例えば、正解テキストが「おはようございます」だったとします。このとき、いくつかの予測テキストに対するCERの実例が次の表のようになります(編集距離やCERの計算方法については後述)。
正解テキスト | 予測テキスト | ケース | 編集距離 | CER |
---|---|---|---|---|
おはようございます | おはようございます | 完全一致 | 0 | 0% |
おはようございます | おはようございまし | 「す」→「し」 | 1 | 11% |
おはようございます | おようございま | 2文字欠落(「は」「す」抜け) | 2 | 22% |
おはようございます | 今日はいい天気 | 全く別の文 | 8 | 89% |
これを見ると、CERは正解テキストに対する予測テキストのズレ具合を測っていることが観察できます。
他の音声AIの評価として
音声認識と違い、音声変換やテキスト音声合成では、出力が文字ではなく音声です。これに対して、「テキストを入力として受け取るCERがどう役に立つの?」という疑問があるかもしれません。
実は、CERは 音声認識と組み合わせることで、生成音声の滑舌・内容がはっきり伝わるかの指標 として用いることができます。
例えば音声変換の場合を考えます。音声変換は、ある 元音声
を受け取ると、別話者の声音に変換した 変換音声
を出力します。
このとき、
- もしかしたら音声変換の質が悪くて、
変換音声
は元音声
と比べて全く何を言っているか分からなかったり・一部分が違和感がある音声になっていたりするかもしれませんし、 - 逆に質が高くて、
元音声
とほぼ変わらない聞き取りやすさで内容が伝わる かもしれません。
これを評価するには、音声が何を言っているかを取り出して、そのテキストに対してCERを計算すればよいと考えられます。ここで、音声が何を言っているかについては音声認識モデルを固定すればテキストとして取り出すことができます。
よって、下図のようなプロセスで、音声認識結果に対してのCERを測ることで、音声変換モデルの滑舌の良さ(元音声と同程度に発話内容が伝わるかどうか)を評価できます。
テキスト音声合成の場合も同じで、生成された音声から音声認識でテキストを取り出し、それを「入力テキスト」もしくは「評価データで同じ内容を発話している音声の音声認識結果」と比べたCERを測ることにより、生成された音声の聞き取りやすさ・不自然さを評価することができます。
例えば、テキスト音声合成で「わたし雨が好き」という音声を生成させたとき、「雨(ア↘️メ)」のアクセントが間違って「ア↗️メ」となっていたら、その音声を音声認識モデルへ入れると「わたし飴が好き」と認識します(すると期待されます)。ですので、CERの計算の際にそこの雨と飴の違いが反映され、CERではこのようなアクセントミスも反映していると考えられることになります。
以上により、CERは音声認識以外にも用いられる指標ですが、この記事では説明の便宜上、以下全てCERを音声認識の評価に対して用いる前提で記述します。
編集距離(Levenshtein距離)
CERを理解するためには、まず 編集距離 (edit distance)、その中でも特にLevenshtein距離という概念を理解する必要があります。
編集距離とは、端的に言うと「1つの文字列を何回の基本操作(編集)でもう1つの文字列へ変形できるか」を表します。
用語
この節では、文字を0個以上自由に横に並べたものを文字列と呼ぶことにします。簡単のため、この節では文字として小文字アルファベットのみを考えます。
数学的には
数学的に言うと、ある文字集合を固定し、その元のことを文字、文字集合上の自由モノイドを文字列の集合、その元のことを文字列と呼んでいます。また、この説で紹介する編集距離・Levenshtein距離は、その名の通り、この文字列の集合上の距離関数となっています(詳細は後述)。
文字列上の基本操作
ある与えられた文字列に対して、次の3つの操作で新しい文字列を作ることができます。
-
挿入 (insertion): 文字列のある場所に1文字を挿入する。
例: 文字列abc
に対して、1回の挿入操作により、例えばzabc
aybc
abcz
等ができる。 -
削除 (deletion): 文字列のある場所の1文字を削除する。
例: 文字列abc
に対して、1回の削除操作により、ab
ac
bc
ができる。 -
置換 (replacement): 文字列のある場所の1文字を別の1文字に置き換える。
例: 文字列abc
に対して、1回の置換操作によりzbc
abx
ayc
等ができる。
この操作を、文字列上の基本操作と便宜上呼ぶことにします(一般的な用語ではありません)。
例えば、次のように複数回の操作を繰り返して、ある文字列から別の文字列へ変形していくことができます。
hello
—(hをyに置換)—> yello
—(末尾にwを追加)—> yellow
基本操作についての性質
上記の基本操作操作について、次のことが成り立ちます。
命題(連結性): 任意の2つの文字列
これは、文字列xから削除操作を繰り返して空文字列を作り、次に挿入操作を繰り返して文字列yを作れることから明らかです。
つまり数学的には
この主張を数学的に言うと、文字列の集合を頂点集合とし、基本操作を辺とするような(多重辺を許す)グラフが構成できますが、そのグラフが連結であるということを主張しています。
また、その(自明すぎる)証明から明らかなことですが、一般に ある文字列を別の文字列にするのに必要な操作の手順や回数は複数ありうる ことに注意してください。例えば、abc
から aaa
への変形を考えると、次のような経路がありえます。
-
abc
—(bをaに置換)—>aac
—(cをaに置換)—>aaa
-
abc
—(cを削除)—>ab
—(bをaに置換)—>aa
—(先頭にaを追加)—>aaa
-
abc
—(aを削除)—>bc
—(bを削除)—>c
—(cを削除)—>空
—(aを追加)—>a
—(aを追加)—>aa
—(aを追加)—>aaa
また、同じ文字の削除挿入等を繰り返すことなどによって、操作の回数はいくらでも長くできます。なので、単に「何回で変形できるか」を数えることにあまり意味はなく、最短で何回の基本操作で変形できるのか、が大事だと推測されます。
Levenshtein距離の定義
以上の観察をもとに、自然と次のような距離を定義することができます。
定義: 2つの文字列
これはちょうど 2つの文字列が基本操作の観点でどれほど離れているか を定式化している定義です。
もっと直感的に言うと、Levenshtein距離は 2つの文字列が何文字分ズレているか を測っていると考えて良いでしょう。
数学的にも距離関数になっている
Levenshtein距離は、前述の通り、文字列の集合上の距離関数を与えることが証明できます。すなわち、任意の文字列
-
で、等号が成立する必要十分条件はd(x, y) \geq 0 .x = y -
.d(x, y) = d(y, x) -
.d(x, y) + d(y, z) \geq d(x, z)
証明は省略します(グラフの言葉で述べると、2つの頂点の間の距離を、2つを結ぶ最短のwalkの長さで定義していることになるので、一般にその状況で距離になることが証明できます)。
また、距離が非負整数しか取らないことから、この距離で位相を入れても離散位相になってしまうことが分かるので、位相空間としては意味はあまりありません。
例
例えば、hello
とyellow
の編集距離は、上記例で説明した「先頭のhをyに置換」「末尾にwを追加」による2回の操作で移り合え、また1回では移り合えないことから、2と分かります。
また、編集距離を与える最短の編集方法や編集種別の回数は一つとは限らないことに注意します。例えば ab
と ba
の編集距離を考えると、例えば次の2つの異なる最短経路が存在します(他にもあります)。
-
ab
—(aをbに置換)—>bb
—(右のbをaに置換)—>ba
-
ab
—(末尾にaを挿入)—>aba
—(先頭のaを削除)—>ba
ab
と ba
の編集距離は2なことは容易に確かめられますが、そこに使われた「挿入」「削除」「置換」の回数は、1では「挿入0回、削除0回、置換2回」ですが、2では「挿入1回、削除1回、置換0回」です。
編集距離からCERへ
編集距離は 2つの文字列の違い度合い を測る数値的な指標となることがわかりました。しかし、これをそのまま音声認識の精度指標としてしまうと問題があります。
具体的に、次のような状況を考えましょう:
正解テキスト | 予測テキスト | 編集距離 | |
---|---|---|---|
例1 | バカ | アホ | 2 |
例2 | あなたはひょっとしてバカですか? | あなたはひょっとしてアホですか? | 2 |
上記の例1と例2では、共に編集距離は2です。しかし、例1では正解テキストと比べて完璧に全部間違った予測なのに対して、例2では正解テキストのほんの一部分だけ間違っているだけです。なので、2つに対してそのまま編集距離を指標としてしまうのは望ましくなく、例1の場合のほうがスコアが悪く出るのが望ましいと思えます。
CERの定義
では、その 正解テキストに比べて予測テキストが間違っている割合 を出すにはどうすればよいでしょうか?単純に 全体のうち何文字ほどが間違っていたか を測れば良さそうで、編集距離は 何文字分離れているか を測っているとみなせるので、次の定義を考えるのが自然となります。
定義: 空でない正解テキスト
ここで
実際は、分かりやすさのために100倍して%形式でCERを掲載することが多く、本記事でもそれに従います。
例
先程の例
この定義をもとに、先ほどの例文でCERを計算してみると次のようになります。
正解テキスト | 予測テキスト | 編集距離 | 正解テキストの文字数 | CER | |
---|---|---|---|---|---|
例1 | バカ | アホ | 2 | 2 | 2 / 2 = 100% |
例2 | あなたはひょっとしてバカですか? | あなたはひょっとしてアホですか? | 2 | 16 | 2 / 16 = 12.5% |
こうすると、先ほどの「例1では全部が間違ってるのに例2では一部分しか間違っていない」という具合をきちんと数値的に定式化できていることがわかります。
CERが大きくなる例
また、CERは100%を超えていくらでも大きくなりうることに注意してください。たとえば、次のような例を考えればすぐに分かります。
正解テキスト | 予測テキスト | 編集距離 | 正解テキストの文字数 | CER | |
---|---|---|---|---|---|
例1 | バカ | アホ | 2 | 2 | 2 / 2 = 100% |
例2 | バカ | アホアホ | 4 | 2 | 4 / 2 = 200% |
例3 | バカ | アホマヌケタコ | 7 | 2 | 7 / 2 = 350% |
例えばWhisperのような自己回帰型の音声認識モデルに対して、無音や感情の強い音声を入力すると、正解テキストは短いにも関わらず、予測テキストは同じフレーズが繰り返し出てきて予測文字列が長くなる等のエラーが起こることがあり、そのような場合にCERが100%を超えることはよくあります。
CERは対称でないという例
編集距離は、2つの引数を入れ替えても同じ値が出てきました(つまり
いくらでも例は作れますが、例えば次を見ると良いでしょう。
正解テキスト | 予測テキスト | 編集距離 | 正解テキストの文字数 | CER | |
---|---|---|---|---|---|
例1 | バカ | バカアホ | 2 | 2 | 2 / 2 = 100% |
例2 | バカアホ | バカ | 2 | 4 | 2 / 4 = 50% |
上の例1では、実際は「バカ」の2文字だけにも関わらず同じ文字数だけの「アホ」が予測に加わっているのでCERが高く出ていますが、例2では、正解「バカアホ」という4文字のなかの最後の2文字が抜けているだけなので、それよりもCERが低く出ている、というニュアンスです。
Pythonでの計算について
PythonでCERを計算する時は、よく jiwer というライブラリが用いられます。編集距離の計算も最適化されて高速であり、pip install
ですぐに使え、また本記事の最後に解説するマイクロ平均がデフォルトなことから、特段の理由がない限りjiwerを用いることをお勧めします。
使い方は
pip install jiwer
でインストールしたあと、
from jiwer import cer
ref_text = "あなたはひょっとしてバカですか?"
hyp_text = "あなたはひょっとしてアホですか?"
print(cer(ref_text, hyp_text)) # Output: 0.125
のようにすぐ使えます。
jiwerを使って複数の音声認識結果をまとめて評価する際の使い方は、その集約方法について後の説で解説するので、そのあとに記載します。
補足:WERについて
日本語の音声認識の評価では主にCERが用いられますが、英語等の他言語では、CERの代わりに 単語誤り率WER (Word Error Rate) を用いることもあるので、以下のブロックで軽く触れておきます。
WERについて
CERでは、最初の編集距離を計算するときは「文字レベルでの編集」を行い、またCERを求める時も、「正解テキストの文字数」で割っていました。しかし、英語等の場合、参照テキスト・正解テキストは空白による 単語区切り が自然に存在します。この場合、文字単位の代わりに単語を一つの単位と思って編集距離を求めたり、単語数で割ることが自然に考えられ、それがWERです。
例として、例えば
- 正解テキスト:
I am a knight
- 予測テキスト:
I am a night
だった場合を考えます。このとき、
- CERの計算
- 文字単位の編集距離 = 1
- 正解テキストの文字数 = (空白を含めると) 13
- よってCER = 1 / 13 ≒ 7.7%
- WERの計算
-
単語レベルの編集距離 = 1 (
knight
→night
の置換1回) - 正解テキストの単語数 = 4
- よってWER = 1 / 4 = 25%
-
単語レベルの編集距離 = 1 (
のように、二つが計算できます。
音声認識という観点からだと、「発話音声の意味が正しく書き起こしで反映され伝わっているか?」をきちんと捉えることが重要なので、「単語が間違っている割合」を示すWERのほうが、「アルファベットがどれほど間違っているか」を示すCERよりも、より元の意味や文脈を考慮できており、その点では望ましい指標だと言えます。なので、英語等の空白区切りでの表記を行う言語では、CERよりもWERを使う方が一般的です。
日本語の場合に同様にWERを計算することもありますが、日本語は英語等と違い単語が空白区切りされていないので、そもそもまず文を単語列に分割する必要があります。その操作は形態素解析で実現できますが、形態素解析器の違いや、形態素解析のミスだったり、そもそも単語とみなす区切りの粒度の違いの影響を受けることもあり、CERの方が多く見られます(ただしMeCab等の形態素解析器を明示した上でWERを報告する例も多いです)。
複数サンプルのCERを集約する方法
ここまででは、正解テキストと予測テキストのペアが1つ与えられた時に、その精度を表す指標としてCERを計算してきました。
では、「音声認識モデル自体の精度を評価したい」ときは実際どうすればよいでしょうか。そのためには、もちろん適当な音声サンプル1つ(とその正解テキスト)に対する精度だけでは音声認識モデルの精度は測れないので、一定量の音声サンプルに対して精度を評価することが重要です。
このとき、1つ1つの音声に対してはCERが計算できますが、それをまとめて一つの数値指標を出す操作が必要です。
方法1: マクロCER
ナイーブな考え方では、1文ごとにCERを計算したあと、最後に全ての平均を取る方法が考えられます。この方法で求めた指標のことをCERのマクロ平均 (macro average)、あるいは単純に マクロCER (Macro CER) と呼ぶことにします。
先ほどみた2サンプルでの例を考えましょう。
正解テキスト | 予測テキスト | CER | |
---|---|---|---|
例1 | あなたはひょっとしてバカですか? | あなたはひょっとしてアホですか? | 12.5% |
例2 | バカ | アホ | 100% |
ここから、単純に2つのCERの平均をとることで、マクロCERは (12.5 + 100) / 2 = 56.25% と計算できます。
この結果を見ると、(おそらくこの音声認識モデルは、「バカ」という単語を「アホ」と誤認識してしまう特性があるようですが、)かなり例2のCERの高さに引っ張られていることがわかります。
きちんと数式で定義をして観察してみましょう。正解テキスト、予測テキストのペア
これは、編集距離
つまり上記例では、例1は16文字、例2は2文字なので、例2のほうが例1よりも8倍、編集距離が重視されて集約されていることになります。
このマクロCERも、きちんと「1つのセリフのCERが一般的にどれくらいか」を測る指標とみなせるので、決して悪いわけではありません。
また、CERのマクロ平均を指標として使っているコードも実際によく散見されます。
方法2: マイクロCER
いつもの例で、もう一つの集約方法を見ていきます。
マクロCERの場合は、サンプルの正解テキストの文字数によって、そのサンプルの編集距離の重みが変わっていました。これを変えずに全てのサンプルでの編集距離を等価に扱うのがマイクロCERです。
考えは単純で、CERを計算してから平均を取る代わりに、全てのサンプルでの編集距離と正解テキストの文字数を合計して、その後で割るだけです。この方法をCERのマイクロ平均 (micro average)、あるいは単に マイクロCER (Micro CER) と呼ぶことにします。
正解テキスト | 予測テキスト | 編集距離 | 正解テキストの文字数 | |
---|---|---|---|---|
例1 | あなたはひょっとしてバカですか? | あなたはひょっとしてアホですか? | 2 | 16 |
例2 | バカ | アホ | 2 | 2 |
合計 | 2 + 2 = 4 | 16 + 2 = 18 |
この例では、つまり「合計18文字の正解テキスト長に対して、編集距離が4文字分ずれていた」ことがわかるので、マイクロCERは 4 / 18 ≒ 22.2% と計算できます。
先ほどのマクロCER 56.25% と比べると数値が低く出ています。マクロCERでは例2の文字数が少ないことにより例2の編集距離が大きく重み付けられて計算されていましたが、マイクロCERではどのセリフでも編集距離の重み付けは対等なので、発話長の長さによる不平等性が起きていません。
数式で書くと、CERのマイクロ平均とは以下のように計算される量です。
これを式変形すれば、無理やり個別CERの重みつき和と見ることもできます:
ここで
jiwerではデフォルトはマイクロCER
先ほど紹介した、PythonでCERを計算できるjiwerでは、次のように複数文章のCERを計算できます。
from jiwer import cer
ref_texts = ["あなたはひょっとしてバカですか?", "バカ"]
hyp_texts = ["あなたはひょっとしてアホですか?", "アホ"]
print(cer(ref_texts, hyp_texts)) # Output: 0.22…
ここで、ref_texts
は正解テキストのリスト、hyp_texts
は対応する順に予測テキストのリストを与えます。
この数値と、先ほど計算したマクロ・マイクロCERとを比べると、jiwerではマイクロCERを用いてCERを集約していることが分かります。
上記のようにjiwerを使う場合は、意識せずともマイクロ平均を使っていることが分かります。
参考までに、ミクロ平均を無理やり使う場合は次のように書けます。
from jiwer import cer
ref_texts = ["あなたはひょっとしてバカですか?", "バカ"]
hyp_texts = ["あなたはひょっとしてアホですか?", "アホ"]
cer_values = []
for ref_text, hyp_text in zip(ref_texts, hyp_texts):
cer_values.append(cer(ref_text, hyp_text))
print(f"Macro CER: {sum(cer_values) / len(cer_values)}") # Output: Macro CER: 0.5625
このように、各サンプルごとにCERを計算して記録し、最後にその値を平均する、という計算スクリプトを書いていた場合、それはマイクロ平均でなくマクロ平均であることに注意してください。
どっちがいいの?→基本的にはjiwerでマイクロ平均がオススメ
先ほども述べましたが、おそらく使い所の問題です。しかし、jiwerを含め多くのCERやWER計算の際は、マイクロ平均を用いているものが多いようです。
しかし、サンプル量が多い場合などに、いちいち全ての予測を行ってからそれをjiwerへ入れるよりも、サンプルごとにCERを記録しておいて、後からそれを平均するというマクロCERのほうが単純で分かりやすい、ということもあり、マクロCERを用いているコードも多く見られます (私も以前は何も知らずにマクロCERを使っていました)。
もちろん、先ほど述べた通り、マクロCERも決して悪い指標と言うことはできませんが、今まで見てきた通り正解テキストの長さによって、1つのサンプルの編集距離の寄与が変わることは、少なくとも使う際には注意しておきたい点だと思います。
終わりに
この記事で、編集距離やCERの計算の仕組みや、マクロ・マイクロCERの違いについてみなさまの理解が深まれば幸いです。
以下に、冒頭で述べたまとめを少し詳しくまとめ直して、この記事を終わりとさせていただきます。
- CERは主に日本語の音声認識の評価に用いられる指標。だが、音声変換や音声合成の評価にも用いられる。
- CERは「正しい文字列から比べて、生成結果の文字列がどれだけの割合間違っているか」を表す定量的な指標。正確には、 Levenshtein距離(編集距離の1つ) という文字列間の距離を、正解テキストの文字数で割ったもの。
- 複数サンプルのCER結果をまとめて一つの値を出すとき、 CERの単純平均(マクロCER) と、 編集距離の和を使って計算するマイクロCER がある。
- CERの単純平均(マクロCER)では、サンプルの文章長に由来して予期せぬ結果になってしまう可能性があり、集約計算時は注意が必要、基本的にjiwerでマイクロCERを出せば安心。
Discussion