はじめまして、Xです!私は普段アメリカのカーネギーメロン大学に在学していて、機械学習関連の研究をしています。ハローでは主に音声と関係する機械学習の開発をしています。
この記事では、AutoReserveの中で機械学習を使っている自動機械判定というコンポーネントについて紹介します。
自動機械判定とは
ご存知の方も多いと思いますが、電話をかけるときに相手が電話に出られないときは留守番につながることがよくあります。その際に自動音声が流れることがよくあります。AutoReserveのサービスでも、自動音声で予約する際によく店側の電話の自動音声につながることが多くあります。そのときに店側に予約を取ることが難しいため、再び時間をおいてから自動音声をかけ直す必要があります。そのため、電話をかけた際に店側が自動音声で出ているのか、店員が実際に出ているのかを知りたいことがたくさんあります。自動機械判定はこれを分類する機械学習のモデルを指します。
先行研究
意外かもしれませんが、音声合成をする研究はかなりメジャーな研究分野である一方で、自動で機械音声かどうかを判別する研究は多くありません。その原因は色々考えられますが、主な原因はこの判定タスクの定義自体がかなり曖昧で、具体的なシナリオによっては難易度が大きく変わるからだと思います。
たとえば、オーディオブックをアナウンサーが読んだ音声と機械が読んだ音声を区別するシナリオであれば、このタスクはかなり難しいと思います。その理由はそもそも音声合成は人間の声を忠実に再現することを目指しているため、よいモデルであればあるほど区別することが難しいです。一例として、発音の韻律を考慮した最近のこの研究では、人間の主観でも機械かどうかを統計的に有意に判定することが難しいです。一方で、私達は店側に電話をかけたときに留守番の自動音声かどうかを判定するシナリオについてですが、幸いこのタスクはかなり簡単な部類に入ります。
機械学習モデル
まずどんなタスクであってもある程度データを集める必要があります。このタスクは前述のようにあまり研究がなされていないもので、既存のデータはありません。そのために私達自身で、AutoReserveのサービスで集めた音声のうち、ランダムに数百個のサンプルを選んで自動かどうかを手でラベリングしました。
次に実際にどうモデリングできるかを考えてみましょう。このタスクは二値分類の問題で、入力された音声に対して、自動音声かどうかを判定します。ここ10年の音声技術分野ではさまざまな深層学習の手法が開発されていますが、どれもある程度の規模なデータセット(数百時間レベル)を想定しているが、われわれのデータセットは数時間程度しかありませんので、大半の深層学習の手法は使えません。最近流行りのself-supervised learningの深層学習の手法では、データセットは小規模で良いケースもありますが、モデルが大きいため、デプロイする際のコスト問題が発生します。
そこで、私達は深層学習ではなく、もっとシンプルな線形モデル(ロジスティック回帰)を使いました。古典的な線形モデルはシンプルでデータ量が比較的に必要としないかわりに、特徴量の選択が重要になってきます。私達は色々な特徴量をつくって実験してみましたが、そのうちから、重要かつ面白い特徴量を3つ取り上げて紹介します。
- Callee Overlap Feature
- Noise Feature
- Duration Feature
Callee Overlap Feature
自動留守番の機械音声の場合、相手を無視して音声が流れます。一方で私たちも自動で予約音声を話しているため、電話の両方で自動音声が同時に流れます。この状況は実際に人が電話に出ている場合では、なかなか起きません、っていうのはこちらの音声を聞いている間はとくになにも話さないことが多いからです。そのため、通話時間のうち、どれだけ音声がかぶっているかを調べることで、相手が機械かどうかのヒントを与えてくれます。私達の実験では、この特徴量だけでもかなりの精度(75%)を達成することができました。
Noise Feature
音声生成に詳しい方なら知っているかもしれませんが、生成された音声は基本的にノイズが少なく、かなりきれいな声で話されています。これは音声のスペクトル図を見てもはっきりわかります。そのため留守番の機械音声ですと、ノイズがほとんど乗りません。一方で店側で人が出ている場合は、店側の環境音がかなり混じっていることが多く、ノイズが多く乗ります。そのため、店側の音声にノイズがどれだけ混じっているかを調べることで、機械音声かどうかがわかるケースも多くあります。
Duration Feature
通話時間の長さからもかなり多くのことがわかります。留守番電話のケースですと、いくつかの決まった留守音声のテンプレートがあって、そのテンプレートが流れ終わると自動的にその秒数で電話が切られるか、あるいはその後ずっとこちらの留守メッセージを聞き続けて、タイムアウトがくるまでにずっと通話するかのパターンが多いです。そのため留守番の通話時間は特徴的な秒数をとっていることが多くあります。この特徴的な秒数をとっているかどうかを調べること(たとえば混合ガウスモデル)で、機械音声かどうかがわかります。
これらの特徴量に加えて、いくつかほかの典型的な特徴量(MFCC特徴量やクラスタリング特徴量など)を組み込んで線形モデルをつくりました。機械学習のライブラリは最近はやりのpytorchではなく、scikit-learnを採用しています。音声の特徴量抽出はpython_speech_featuresというライブラリを使っています。callee overlapの有無はpy-webrtcvadというモデルを改造して判別しています。すべての実装はnumpyベースであるため、軽量に動かすことができます。
下がおおよそのメインクラスの実装になります。モデルを予め訓練してpickle形式に保存しておき、デプロイ時はそれをロードして使います。毎回新しいリクエストに対して判別を行うときに、まず上にあげた特徴量などを計算しておき、線形モデルに入れて判別します。
class AutomaticMachineDetection: def __init__(self): # sklearnのロジスティック回帰を使います self.model = LogisticRegression(C=100) # 音声から典型的なMFCC特徴量を抽出するモデルを準備します self.pm = create_pm_model("mfcc_hires") # クラスタリング特徴量 self.cluster = BowCluster(100) def feature(self, audio): """ 音声から特徴量を抽出します。 :param audio: :return: """ # クラスタリング特徴量など cluster_feat = self.get_cluster_feature(audio) # 上のcallee overlapの特徴量など stat_feat = self.get_stats_feature(audio) feat = np.concatenate([cluster_feat, stat_feat]) return feat def predict(self, audio_or_wav_path): # only use the first channel which is corresponding to the callee audio (The caller side is ignored for now) if isinstance(audio_or_wav_path, str) or isinstance(audio_or_wav_path, Path): audio = read_wav(audio_or_wav_path, channel=0) else: audio = audio_or_wav_path # 特徴量抽出 feat = self.feature(audio) # 推論 return self.model.predict([feat])[0]
さまざまなパラメーターや特徴量をチューニングして実験した結果、98%ぐらいの精度を達成することができました。
このタスクのように、機械学習モデルは必ずしも深層学習を使う必要がなく、簡単なモデルで物足りるケースも多々あります。実際に深層学習を使おうとすると、大量のデータを集める必要があるのに加えて、訓練やデプロイする際に多くの計算資源を必要とします。しかし、この簡単なモデルは個人のノートPCで数分程度で訓練することができて、簡単に開発することができます。ただ、シンプルなモデルの性能を最大限に引き出すために、特徴量を注意深く作る必要があります。
まとめ
今回のモデルは線形モデルであるため非常に軽量なインスタンスで動かすことができて、最終的にこのモデルをFlask に乗せて Google Cloud Functions のサーバーレス環境にデプロイしています。自動音声予約の数は増え続けていますが、ほぼメンテなしで安定してスケールしています。ハローの電話料金コストを削減するのに役立っています。
ハローではこのように音声に関する知見を深めて、フロントエンド、バックエンドの開発にとどまらず、これからも音声の機械学習分野で技術開発を続けていきます。