June 2019
Volume 34 Number 6
[音声]
.NET での音声合成
Ilia Smirnov Smirnov | 2019 年 6 月
私は、頻繁に、母に会いに飛行機でフィンランドに行きます。飛行機がヴァンター空港に降り立つたびに、空港の出口に向かう乗客の少なさに驚かされます。大部分の乗客は、中央ヨーロッパや東ヨーロッパの全土に広がる行き先への接続便に乗り換えます。飛行機が降下を始めると接続便に関するアナウンスが大量に流れるのも不思議ではありません。「タリン行きは 123 ゲートをご利用ください」、「サンクトペテルブルク行きの XYZ 便のお客様は、234 ゲートにお進みください」といった具合です。もちろんフライト アテンダントはたくさんの言語を話せないのが普通なので英語を使用しますが、これは大部分の乗客にとってネイティブ言語ではありません。飛行機の機内放送 (PA) システムの音質に加えて、エンジン音、赤ちゃんの泣き声、その他の雑音を考慮すると、情報を効率的に伝えるにはどうしたらいいでしょうか。
各シートにはヘッドホンが装備されています。今日では、全部ではありませんが、多くの長距離飛行機が個別のスクリーンを備えています (ローカル飛行機は 2 つ以上の音声チャネルを備えています)。乗客がアナウンスの言語を選び、機内のコンピューター システムでフライト アテンダントが動的な (事前録音でない) 音声メッセージを作成および送信できたらどうでしょうか。ここで主な課題はメッセージの動的な性質です。安全指示やケータリング オプションなどは更新されることがほとんどないため、事前録音が容易です。それでも、文字どおり飛行中にメッセージを作成することは必要です。
幸い、これに役立つ成熟した技術があります。音声合成 (TTS) です。気付くことはまれですが、このようなシステムはどこにでも存在します。公共アナウンス、コール センターの音声ガイダンス、ナビゲーション デバイス、ゲーム、スマート デバイス、その他のアプリケーションはすべて、事前に録音された音声ガイダンスでは不十分であるか、メモリ制限のために波形のデジタル化ができない例です (TTS エンジンが読み上げるテキストはデジタル化した波形よりずっと小さいサイズで保存できます)。
コンピューターベースの音声合成は昔からあります。通信会社は事前に録音されたメッセージの限界を克服するために TTS に投資してきましたし、軍事研究者は複雑な制御インターフェイスを簡素化するために音声ガイダンスとアラートを試してきました。同様に、ポータブル シンセサイザーが障害者のために開発されてきました。このようなデバイスで 25 年前に何ができたかを理解するには、Pink Floyd の 1994 年のアルバム「The Division Bell」から「Keep Talking」というトラックをお聞きください。有名な次の一節を Stephen Hawking 氏が読み上げています。「All we need to do is to make sure we keep talking (必要なのは話し続けることだけです)」。
TTS API がその「逆」に相当する音声認識と一緒に提供されることは少なくありません。人間とコンピューターの効果的な相互作用にはこの両方が必要ですが、この調査では音声合成に特に焦点を当てます。Microsoft .NET TTS API を使用して、飛行機の PA システムのプロトタイプを構築します。TTS の「単位選択」アプローチの基礎を理解するため、中身も具体的に調べます。デスクトップ アプリケーションの構築手順をたどりますが、ここで示す原理はクラウド ベースのソリューションに直接当てはまります。
独自の音声システムに着手する
機内アナウンス システムのプロトタイプを作成する前に、単純なプログラムを使って API を調べてみましょう。Visual Studio を起動して、コンソール アプリケーションを作成します。System.Speech への参照を追加して、図 1 のメソッドを実装します。
図 1 System.Speech.Synthesis メソッド
using System.Speech.Synthesis;
namespace KeepTalking
{
class Program
{
static void Main(string[] args)
{
var synthesizer = new SpeechSynthesizer();
synthesizer.SetOutputToDefaultAudioDevice();
synthesizer.Speak("All we need to do is to make sure we keep talking");
}
}
}
今度はコンパイルして実行します。ほんの数行のコードで、Hawking 氏の有名なフレーズが複製されました。
このコードを入力するとき、IntelliSense によって、SpeechSynthesizer クラスのすべてのパブリック メソッドとプロパティを含むウィンドウが開きました。見逃した場合は、「Control-Space」または「ドット」キーボード ショートカットをご使用ください (または bit.ly/2PCWpat をご確認ください)。ここで興味深い点は何でしょうか。
まず、さまざまな出力ターゲットを設定できます。音声ファイルでもストリームでもかまわず、null にすることさえできます。2 番目に、同期出力 (前の例の場合) と、非同期出力の両方があります。音量や話す速度の調整、一時停止と再開、イベントの受信もできます。声を選択することもできます。この機能はここで重要なものです。さまざまな言語で出力を生成するために使用するからです。しかし、どんな声を使用できるのでしょうか。図 2 のコードを使用して確認してみましょう。
図 2 音声情報コード
using System;
using System.Speech.Synthesis;
namespace KeepTalking
{
class Program
{
static void Main(string[] args)
{
var synthesizer = new SpeechSynthesizer();
foreach (var voice in synthesizer.GetInstalledVoices())
{
var info = voice.VoiceInfo;
Console.WriteLine($"Id: {info.Id} | Name: {info.Name} |
Age: {info.Age} | Gender: {info.Gender} | Culture: {info.Culture}");
}
Console.ReadKey();
}
}
}
Windows 10 Home が搭載されたマシンで図 2 から生成された出力は次のとおりです。
Id: TTS_MS_EN-US_DAVID_11.0 | Name: Microsoft David Desktop |
Age: Adult | Gender: Male | Culture: en-US
Id: TTS_MS_EN-US_ZIRA_11.0 | Name: Microsoft Zira Desktop |
Age: Adult | Gender: Female | Culture: en-US
使用できるのは 2 つの英語の音声だけです。他の言語はどうしたのでしょうか。音声ごとにディスク領域が占有されるため、既定ではインストールされていません。それらを追加するには、[スタート] | [設定] | [時刻と言語] | [地域と言語] に移動して、[言語の追加] をクリックします。オプション機能で [音声] が選択されていることを確認します。Windows は 100 を超える言語をサポートしていますが、TTS に対応しているのは約 50 の言語だけです。サポートされている言語の一覧は bit.ly/2UNNvba で確認できます。
コンピューターを再起動すると、新しい言語パックが使用可能になるはずです。ここでは、ロシア語を追加してから、その新しい音声をインストールしました。
Id: TTS_MS_RU-RU_IRINA_11.0 | Name: Microsoft Irina Desktop |
Age: Adult | Gender: Female | Culture: ru-RU
これで、最初のプログラムに戻って、synthesizer.Speak 呼び出しの代わりに、次の 2 行を追加できます。
synthesizer.SelectVoice("Microsoft Irina Desktop");
synthesizer.Speak("Всё, что нам нужно сделать, это продолжать говорить");
言語を切り替えたい場合は、適当な場所に SelectVoice 呼び出しを挿入できます。しかし、より良い方法は、構造体を音声に追加することです。そのために、図 3 に示すように、PromptBuilder クラスを使用してみましょう。
図 3 PromptBuilder クラス
using System.Globalization;
using System.Speech.Synthesis;
namespace KeepTalking
{
class Program
{
static void Main(string[] args)
{
var synthesizer = new SpeechSynthesizer();
synthesizer.SetOutputToDefaultAudioDevice();
var builder = new PromptBuilder();
builder.StartVoice(new CultureInfo("en-US"));
builder.AppendText("All we need to do is to keep talking.");
builder.EndVoice();
builder.StartVoice(new CultureInfo("ru-RU"));
builder.AppendText("Всё, что нам нужно сделать, это продолжать говорить");
builder.EndVoice();
synthesizer.Speak(builder);
}
}
}
EndVoice を呼び出す必要があることにご注意ください。そうしなかった場合は、ランタイム エラーが発生します。また、言語を指定するための別の方法として CultureInfo を使用しました。PromptBuilder には便利なメソッドがたくさんありますが、ここでは AppendTextWithHint に注目してみたいと思います。次のコードをお試しください。
var builder = new PromptBuilder();
builder.AppendTextWithHint("3rd", SayAs.NumberOrdinal);
builder.AppendBreak();
builder.AppendTextWithHint("3rd", SayAs.NumberCardinal);
synthesizer.Speak(builder);
入力を構築してその読み上げ方を指定するのに、音声合成マークアップ言語 (SSML) を使用する方法もあります。これは、国際的な Voice Browser Working Group (w3.org/TR/speech-synthesis) によって開発されたクロス プラットフォームの勧告です。Microsoft の TTS エンジンは、SSML の包括的なサポートを提供しています。この使用方法を以下に示します。
string phrase = @"<speak version=""1.0""
http://www.w3.org/2001/10/synthesis""
xml:lang=""en-US"">";
phrase += @"<say-as interpret-as=""ordinal"">3rd</say-as>";
phrase += @"<break time=""1s""/>";
phrase += @"<say-as interpret-as=""cardinal"">3rd</say-as>";
phrase += @"</speak>";
synthesizer.SpeakSsml(phrase);
SpeechSynthesizer クラスに対する別の呼び出しが使用されていることにご注意ください。
これで、プロトタイプで作業する準備が整いました。今度は新しい Windows Presentation Foundation (WPF) プロジェクトを作成します。1 つのフォームと、2 種類の言語の音声ガイダンス用の 1 組のボタンを追加します。その後で、図 4 の XAML に示すように、クリック ハンドラーを追加します。
図 4 XAML コード
using System.Collections.Generic;
using System.Globalization;
using System.Speech.Synthesis;
using System.Windows;
namespace GuiTTS
{
public partial class MainWindow : Window
{
private const string en = "en-US";
private const string ru = "ru-RU";
private readonly IDictionary<string, string> _messagesByCulture =
new Dictionary<string, string>();
public MainWindow()
{
InitializeComponent();
PopulateMessages();
}
private void PromptInEnglish(object sender, RoutedEventArgs e)
{
DoPrompt(en);
}
private void PromptInRussian(object sender, RoutedEventArgs e)
{
DoPrompt(ru);
}
private void DoPrompt(string culture)
{
var synthesizer = new SpeechSynthesizer();
synthesizer.SetOutputToDefaultAudioDevice();
var builder = new PromptBuilder();
builder.StartVoice(new CultureInfo(culture));
builder.AppendText(_messagesByCulture[culture]);
builder.EndVoice();
synthesizer.Speak(builder);
}
private void PopulateMessages()
{
_messagesByCulture[en] = "For the connection flight 123 to
Saint Petersburg, please, proceed to gate A1";
_messagesByCulture[ru] =
"Для пересадки на рейс 123 в Санкт-Петербург, пожалуйста, пройдите к выходу A1";
}
}
}
言うまでもなく、これは本当に小さなプロトタイプです。現実の世界では、PopulateMessages は外部リソースから読み取ることになります。たとえば、フライト アテンダントは、Bing 翻訳ツール (bing.com/translator) などのサービスを呼び出すアプリケーションを使用して、複数の言語のメッセージを含むファイルを生成できます。フォームは、はるかに洗練され、使用可能な言語に基づいて動的に生成されるようになります。エラー処理なども用意されます。しかし、ここでポイントは中核的機能について説明することです。
音声の分解
これまでは、驚くほど小さなコードベースを使用して目標を達成してきました。次は、中身を確認して、TTS エンジンのしくみをより深く理解するチャンスを利用しましょう。
TTS システムを構築する方法はたくさんあります。昔から、研究者たちは、アルゴリズムを構築するための一連の発音規則の特定を試みてきました。外国語を勉強したことがあれば、「’city’ のように ’e’、’i’、’y’ の前の ’c’ は ’s’ として発音するが、’cat’ のように ’a’、’o’、’u’ の前の ’c’ は ’k’ として発音する」などの規則に精通しているはずです。残念ながら、例外や特殊なケース (単語が続いたときの発音の変化など) が多すぎて包括的な一連の規則を構築するのは困難です。さらに、このようなシステムのほとんどが、“機械” であることがはっきりわかる音声を生成する傾向があります。外国語の初心者が単語を 1 文字ずつ発音するところを想像してみてください。
音声がより自然に聞こえるようにするため、研究は録音された音声の断片から成る大きなデータベースに基づくシステムへと移っており、今ではそのようなエンジンが市場を独占しています。一般に連結単位選択 TTS と呼ばれるこれらのエンジンは、入力テキストに基づいて音声標本 (単位) を選択し、それらを連結して語句にします。通常、エンジンではコンパイラによく似た 2 段階の処理を使用します。まず入力を分析し、発音表記と追加のメタデータから成る、リスト状またはツリー状の内部的な構造体にします。次いで、この構造体に基づいて音を合成します。
自然言語を扱っているため、パーサーはプログラミング言語のものより複雑です。したがって、パーサーはトークン化 (文同士や単語同士の境界を見つけること) を行うにとどまらず、入力ミスの修正、品詞の特定、句読点の分析、省略形、短縮形、特殊記号のデコードを行う必要があります。パーサーの出力は、句または文ごとに分割されて、語を説明するコレクションを形成するのが普通です。このコレクションでは、品詞、発音、アクセントなどのメタデータがグループ化および保持されます。
パーサーには、入力の曖昧さを解消する役目があります。たとえば、「Dr.」とは何でしょうか。「Dr. Smith」のように「doctor」のことなのか、「Privet Drive」のように「drive」のことなのでしょうか。 それとも、「Dr.」は大文字で始まってピリオドで終わっているから文なのでしょうか。「project」は名詞なのでしょうか、動詞なのでしょうか。アクセントを置く音節が変わるため、これを知ることは重要です。
これらの問いは必ずしも簡単には答えられませんし、多くの TTS システムが特定の領域 (数字、日付、省略形、頭字語、地名、URL などの特殊なテキストの形式など) 向けの別々のパーサーを搭載しています。これらは、言語固有であり、地域固有でもあります。幸い、このような問題は長期にわたって研究されてきましたし、十分に開発されたフレームワークとライブラリに頼ることができます。
次のステップは、発音形式を生成することです。たとえば、音声記号でツリーにタグを付けます ("school" を "s k uh l" に変形するなど)。この処理は、特殊な書記素音素変換アルゴリズムによって行われます。スペイン語のような言語の場合は、いくつかの比較的単純な規則を適用できます。しかし、それ以外の言語 (英語など) の場合は、発音と筆記形式が大きく異なります。その場合は、既知の単語のデータベースと一緒に統計的手法が使用されます。その後で、追加の後語彙処理が必要になります。これは、単語が文の中で組み合わされたときに発音が変わる可能性があるためです。
パーサーはテキストから可能なすべての情報を抽出しようとしますが、見つけにくいために抽出が不可能なものもあります。プロソディやイントネーションです。話すときは、プロソディを使用して、特定の単語を強調したり、感情を伝えたり、肯定文、命令、質問を区別したりします。ただし、記述されたテキストにはプロソディを示すシンボルが含まれていません。確かに、句読点はコンテキストを提供します。コンマは短い一時停止を意味しますが、ピリオドはより長い一時停止を意味し、疑問符は文の最後でイントネーションを上げることを意味します。ただし、子供におとぎ話を読み聞かせたことがあれば、これらの規則が実際の読み上げとは程遠いことがわかります。
さらに、同じテキストでも読む人が違えば読み方が変わることがあります (あなたとあなたの配偶者のどちらがおとぎ話を読むのが上手か、お子さんに聞いてみてください)。このため、統計的手法を信頼して使用することはできません。さまざまな専門家が教師あり学習に対して異なるラベルを作成することになるためです。この問題は複雑であり、徹底的な調査にもかかわらず、解決には至っていません。プログラマにできる最善のことは SSML を使用することです。これにはプロソディ用のタグがあります。
TTS のニューラル ネットワーク
統計手法や機械学習手法は、何年にもわたって、TTS 処理のすべての段階に適用されてきました。たとえば、隠れマルコフ モデルは、最もありそうな解析を生成するパーサーを作成したり、音声標本データベースのラベル付けを実行したりするために使用されています。デシジョン ツリーは単位選択や書記素音素変換アルゴリズムで使用され、その一方でニューラル ネットワークとディープ ラーニングが TTS 研究の最前線に躍り出ました。
音声標本は、時系列の波形標本化と見なすことができます。自己回帰モデルを作成すれば、次の標本を予測できます。その結果、このモデルは、音を真似て話すことを学ぶ赤ちゃんのように、話し言葉のようなバブリングを生成します。音声テキストまたは既存の TTS システムからの前処理出力でこのモデルをさらに調整すると、パラメーター化された音声のモデルが出来上がります。モデルの出力には、実際の波形を生成するボコーダーのスペクトログラムが記載されます。このプロセスは、録音された標本を含むデータベースに依存せず、むしろ生成に頼るため、モデルはメモリ占有領域が小さく、パラメーターの調整が可能です。
モデルは自然な音声に基づいてトレーニングされるため、出力では、呼吸、アクセント、イントネーションなどの特性のすべてが保持されます (したがって、ニューラル ネットワークによってプロソディの問題が解決される可能性があります)。また、ピッチを調整したり、まったく違う声を作成したり、歌唱を模倣したりすることもできます。
この記事の執筆時点で、Microsoft は、ニューラル ネットワーク TTS のプレビュー バージョン (bit.ly/2PAYXWN) を提供しています。高い品質とほぼ瞬時の性能を持つ 4 つの音声が付属しています。
音声生成
これで、メタデータを持つツリーができたため、音声生成に進みます。オリジナルの TTS システムは、正弦曲線を組み合わせて信号を合成しようとしました。もう 1 つの興味深いアプローチは、直径と長さが異なる複数のチューブを連結したものとして人間の声道を記述する連立微分方程式を構築することでした。このようなソリューションは、非常にコンパクトですが、残念なことに、音はかなり機械的です。そのため、楽器のシンセサイザーと同様、標本に基づくソリューションへ焦点が徐々にシフトして行きました。これには、大きなスペースが必要ですが、基本的に音は自然です。
このようなシステムを構築するためには、特別に構成したテキストを読み上げるプロの俳優の何時間にも及ぶ高品質な録音が必要です。このテキストは、単位に分割されて、ラベルが付けられ、データベースに保存されます。音声生成は、適切な単位を選択して連結する作業になります。
音声を合成するわけではないため、実行時にパラメーターを調整することはあまりできません。男性と女性の両方の声が必要な場合や地方訛り (スコットランド英語やアイルランド英語など) を提供する必要がある場合は、別々に録音する必要があります。テキストは、必要なすべての音声単位をカバーするように構成する必要があります。また、俳優は、連結が容易になるように自然な調子で読み上げる必要があります。
分割とラベル付けも重要な作業です。これは手動で行う必要があったので、退屈な作業が何週間も続いていました。幸いなことに、今はこれに機械学習が適用されています。
おそらく、TTS システムにとって最も重要なパラメーターが単位サイズです。言うまでもなく、文全体を使用すれば正しいプロソディで最も自然な音を作ることができますが、それほど大量のデータの録音と保存は不可能です。単語に分割できるでしょうか。たぶんできますが、俳優が辞書全体を読み終えるまでどのくらいの時間がかかるでしょうか。また、データベース サイズの制限はどのくらいでしょうか。一方で、アルファベットだけを録音することはできません。そうしたとしても、スペリング コンテストにしか使用できません。そのため、通常は、2 つの 3 文字グループとして単位が選択されます。これらは必ずしも音節ではありません。音節境界をまたぐグループはかなりうまく連結することができます。
ここからは最後のステップです。音声単位のデータベースがある場合は、連結を処理する必要があります。残念ながら、オリジナルの録音でイントネーションがどんなに中立であっても、単位の連結時に、音量、周波数、および位相の変化を回避するように調整する必要があります。これは、デジタル信号処理 (DSP) を使って実行されます。これは、語句にイントネーションを追加する (主張や質問の場合に、生成された声を上げ下げするなど) ために使用することもできます。
まとめ
この記事では、.NET API のみを取り上げました。他のプラットフォームでも同様の機能が提供されます。macOS では同等の機能を持つ NSSpeechSynthesizer が Cocoa にあり、ほとんどの Linux ディストリビューションには eSpeak エンジンが付属しています。これらの API はどれもネイティブ コード経由でアクセスできるため、C#、C++、Swift を使用する必要があります。Python のようなクロス プラットフォーム エコシステムの場合は、Pyttsx などの一部のブリッジが存在しますが、特定の制限があります。
一方、クラウド ベンダーは、幅広い対象をターゲットにして、最も普及している言語とプラットフォームにサービスを提供しています。機能はベンダー間で似たり寄ったりですが、SSML タグに対するサポートは異なる可能性があるため、ソリューションを選択する前にドキュメントをご確認ください。
Microsoft は、Cognitive Services の一部として、音声合成サービスを提供しています (bit.ly/2XWorku)。45 の言語の 75 の音声が提供されているだけでなく、独自の音声を作成することもできます。そのため、このサービスには、対応するトランスクリプトを伴う音声ファイルが必要です。先に、テキストを記述してから、それを誰かに読んでもらうことも、既存の録音を入手してそのトランスクリプトを記述することもできます。これらのデータセットを Azure にアップロードすると、機械学習アルゴリズムが独自のユニークな “音声フォント” に対してモデルをトレーニングします。 優れたステップ バイ ステップ ガイドが bit.ly/2VE8th4 に用意されています。
Cognitive Speech Services にアクセスするための非常に便利な方法は、Speech Software Development Kit (bit.ly/2DDTh9I) を使用することです。このキットは、音声認識と音声合成の両方をサポートしており、すべての主要なデスクトップおよびモバイル プラットフォームと最も普及している言語に使用できます。十分文書化されており、GitHub 上に多数のコード サンプルがあります。
TTS は、今後も、特殊なニーズを持つ人々に大いに役立ちます。たとえば、linka.su をご確認ください。これは、脳性麻痺がある優秀なプログラマが、発話障害や筋骨格障害がある人、自閉症の人、麻痺から回復中の人を支援するために作成した Web サイトです。作者は個人的な経験から彼らがどのような制限に直面しているか知っており、通常のキーボードで入力できない人、一度に 1 文字ずつしか選択できない人、タブレット上の画像をタッチするだけの人のためのさまざまなアプリケーションを作成しました。TTS のお陰で、彼は、文字どおり、声を持っていない人たちに声を届けています。私たち全員がプログラマとしてそのように誰かの役に立てることを願っています。
Ilia Smirnov 氏は、20 年以上、Java や .NET などの主要なプラットフォーム上でエンタープライズ アプリケーションを開発した経験があります。ここ 10 年は、金融リスクのシミュレーションに専念していました。3 つの修士号、FRM、その他の専門的な認定資格を持っています。
この記事のレビューに協力してくれたマイクロソフト技術スタッフのSheng Zhao 氏 (Sheng.Zhao@microsoft.com) に心より感謝いたします。
Sheng Zhao 氏は、北京にある STCA Speech でプリンシパル グループ ソフトウェア エンジニアを務めています。