見出し画像

音声データからノイズを除去したい! ~音声認識の精度向上を目指して~

ICTRAD改め、メディア研究開発センター(通称、M研)所属、入社2年目になりました、倉井です。
本エントリでは、1年目の仕事を紹介するこちらのエントリで少しだけ触れていた「ノイズ除去ライブラリ」の開発エピソードを紹介いたします。

M研において、多岐にわたる分野の研究開発に取り組んでいる様子が伝われば幸いです。

なお、あくまで筆者は既存のライブラリを組み合わせて、簡単にノイズ除去できるライブラリを作ったにすぎず、大元の信号処理レベルの開発は何もしていません。
あくまで「やってみた」レベルですので、悪しからず。

ノイズ除去機能開発の経緯

当たり前なことですが、新聞記事の執筆には「取材」が必要不可欠です。
記者のみなさんは、取材で聞き出した情報を確実に記録するために、スマホやボイスレコーダーを活用しています。それを後から聞き直し、やりとりの一言一句を書き出すことも少なくありません。

このいわゆる「文字起こし」作業には、今までたくさんの時間と労力が要されていました。この苦労を肩代わりしてくれるのが、最近多くのサービスにも搭載されるようになってきたリアルタイム文字起こし機能です。
Google DocumentやiPhone標準搭載の文字起こし機能、Wordの文字起こし機能など、有名企業から多くのサービスがリリースされていますが、取材内容によっては秘匿性の高い情報が含まれていることもあり、安易に社外のサービスを使うことははばかられます。

そこで、社内規定に準じるセキュリティが担保されたインフラやネットワークを使って、文字起こしができる社内向けサービスを作ろうということになりました。

ただし、気になるのは自動文字起こしの「精度」です。精度はファイル中の音声の明瞭度合いに左右されるので、できる限りノイズが乗らないように録音するのが望ましいです。しかし、場合によっては必ずしも静かな状況で取材ができるわけではありません。
せっかく自動化できると思って文字起こししてみたら、支離滅裂な文章が…ということも考えられます。

そこで私は、文字起こしの前処理として音声データのノイズを除去するライブラリを開発し、精度向上が見込めるのか、試してみることにしました。

どんなシステム構成にしたか

Dockerで開発することで、どんな環境においても作動するようにしました。ノイズ除去のスクリプト自体はpythonで記述し、uvicornとFastAPIを使って、サーバー内でAPI的にノイズ除去機能を呼び出せるように開発しました。

ノイズ除去のアルゴリズム

今回実装したアルゴリズムは二種類に大別でき、解析にノイズ音のみの音声データを追加で用いるか否かによって違いがあります。
それぞれの(主観での)評価は次の章で紹介していくとして、ここでは各手法のアルゴリズムについて紹介します。

①1つの音声ファイルのみから、ノイズ部分の特定・除去を行う方法
こちらのレポジトリを参考に開発しました。

上記のレポジトリでは6パターンのノイズ除去手法を使っているのですが、そのうち今回の文字起こしの技術仕様として適していそうなもの3つを試しました。

(1)Power:音の時系列データに対して各時間で周波数スペクトル重心を求め、それの時間全体での中央値を算出、その周波数fを基準にして、0.1f-1.5fまでのバンドパスフィルターを設計したもの(librosaを利用)
(2)Centroid:音の時系列データに対して各時間で周波数スペクトル重心を求め、それの時間全体での最大値と最小値を算出、その範囲のバンドパスフィルターを設計したもの(librosaを利用)
(3)MFCC:音声認識において使用されることの多い特徴量であるMFCCを利用し、ファイル中の人の声の周波数を分析してそれに適したフィルターを設計したもの(python_speech_featuresを利用)

この3パターンはそれぞれ得意な環境やノイズ音の大きさが異なっていて、主観ではどれがいいと一概に言えず、どれも利用できるようにしました。

いずれのパターンにおいても、librosaで音声の読み込みを行い、pysndfxを用いてフィルタリング処理を行っています。

▼コードの一部抜粋(Powerの処理部分)

'''------------------------------------
FILE READER:
   receives filename,
   returns audio time series (y) and sampling rate of y (sr)
------------------------------------'''
def read_file(file_name):
   sample_file = file_name
   sample_directory = '00_samples/'
   sample_path = sample_directory + sample_file

   # generating audio time series and a sampling rate (int)
   y, sr = librosa.load(sample_path)

   return y, sr

'''------------------------------------
NOISE REDUCTION USING POWER:
   receives an audio matrix,
   returns the matrix after gain reduction on noise
------------------------------------'''
def reduce_noise_power(y, sr):

   cent = librosa.feature.spectral_centroid(y=y, sr=sr)

   threshold_h = round(np.median(cent))*1.5
   threshold_l = round(np.median(cent))*0.1

   less_noise = AudioEffectsChain().lowshelf(gain=-30.0, frequency=threshold_l, slope=0.8).highshelf(gain=-12.0, frequency=threshold_h, slope=0.5)#.limiter(gain=6.0)
   y_clean = less_noise(y)

   return y_clean

②取材音声ファイルとノイズ音のみのファイルの2つを用意し、ノイズを明確に指定してその除去を行う方法
二つ目のノイズ音声を別途分析し除去する方法では、noisereduceというpythonのライブラリを利用しました。
このライブラリを使うことで、FFTを用いた周波数解析によるノイズ帯の特定から音声ファイルへのフィルタの適用までを一括で行うことができます。ただしこのライブラリでは、ノイズ除去をしたいファイルと同じ環境で収録した、ノイズ音のみのデータを準備しなくてはなりません。録音のタイミングで一手間かかるため、実際に運用する時に注意が必要なポイントです。

なお、音声ファイルの読み込みに関しては、一つ目同様librosaを用いました。

▼コードの一部抜粋

    # load data
   data, rate = librosa.load("./00_samples/" + infile)

   # select section of data that is noise
   noisy_part, _ = librosa.load("./00_samples/" + innoise)

   # perform noise reduction
   reduced_noise = nr.reduce_noise(
       audio_clip=data, noise_clip=noisy_part, verbose=True
   )

主観での評価

どちらの方法も、未処理の状態に比べノイズが低減していると感じました
一つ目の方法(ノイズ音ファイルなし)では、確かにノイズも小さくなっていますが、音声自体も若干曇った印象を受けました。
pysndfxで実装したフィルターは完全に平らな通過帯域を有しているため、その帯域に重畳しているノイズは取り除けません。一方でカットしている帯域に含まれていた音声成分は取り除かれてしまった結果、ノイズはある程度低減されつつも音声自体もくぐもってしまったのだと考えられます。

一方で二つ目の方法(ノイズ音ファイルあり)は、ノイズ部分だけが低減し、人の声の明瞭さが増したような印象を受けました
noisereduceではノイズ音のみの解析を行い、その結果に応じて周波数ごとにゲインの異なるフィルターが生成されるので、ノイズ音だけを低減する処理が行われたのだと考えられます。

一つ困ったことが、ノイズ除去をしたいファイルと同じ環境のノイズ音を適切に渡さないと、作成されるフィルタが適切なものにならず、フィルタリングされた結果が耳障りな爆音の音声ファイルになってしまうということです。
私はイヤホンをしながら検証に当たっていたので、音圧で耳がやられそうになるとともに、突然の大音量への心の準備がまったくできておらず、ホラー映画以上にびっくりしてしまいました、、(社内でやらかしたので恥ずかしかったです、、)

ということで、どちらの方法でもノイズが除去できていることは確認できましたので、いよいよ文字起こしエンジンに投入した時の、精度の向上具合を検証することにしました。

文字起こし精度は向上…しなかった

見出しの通りですが、ノイズ除去した音声ファイルはノイズ除去前に比べ、明確な文字起こし精度向上がなされていませんでした

以下に、実際に私が話した音声ファイルを用いた結果を一例として示します。

①実際に話した内容
②何も処理もせずに文字起こしした結果
③1つの音声ファイルのみからノイズ除去を行い、文字起こしした結果
④ノイズの音声ファイルを別途用意してノイズ除去を行い、文字起こしした結果

の順で列挙します。

また、文字起こしした内容がどれくらい元の文章に近いかを評価するため、①と比較した各文章の類似度(WERとCER)も併記しておきます。WERとCERの計算にはM研所属の田口さんが作成した計算スクリプトを使用しました。
▼田口さんのエントリはこちら!

①実際に話した内容

今外で、だいたい1メートルぐらい離れて、喋っています。
これぐらいの距離で、どのぐらいのノイズが乗るのか、試しています。
また、ちょっとノイズが別で音をとって、そしてそれを使って、
どのぐらいのノイズを低減できるのか、っていうのを試そうと思っています。
はい、ちょっと距離遠めで、声は普通にしゃべるぐらいの大きさです。しゃべっています。
この後、ノイズだけの収録をするのと、もうちょっと近い距離で録音します。以上です。

②何も処理もせずに文字起こしした結果(WER:18.10%、CER:14.05%)

今外de会いたいし、メートル位遅れてしゃべっています。
これぐらいの距離、deどのぐらいの能率Ga見れるのか、試しています。
これまた、ちょっとノイズが別で音をとって。そしてそれを使って、
どのぐらいノイズを低減できるのかってのを試そうと思っています。
はい。ちょっと距離投目で、声は普通にしゃべるぐらいの大きさです。食べています。
そのあと、ノイズだけの収録をするのと、もうちょっと近い距離で録音します。以上です。

1つの音声ファイルのみからノイズ除去を行い、文字起こしした結果(WER:24.76%、CER:16.22%)

一de会いたいし、メートル位遅れてしゃべっています。
これぐらいの距離、de、どのぐらいノーリツGa見れるのか、試しています。
また、ちょっとノイズが後で音をとって。そしてそれを使って、
このぐらいの五零」低減できるのか、というのを試そうと思っています。
はい。ちょっと距離投目で、声は普通にしゃべるぐらいの大きさです。喋っています。
その後、ノイズだけの収録をするのと、もうちょっと近い距離で録音します。以上です。

④ノイズの音声ファイルを別途用意してノイズ除去を行い、文字起こしした結果(WER:17.14%、CER:11.35%)

外で会いたいし、メートルぐらい離れて喋っています。
これぐらいの距離で、どのぐらいのノイズが乗るのか、ためしています。
また、ちょっとノイズが別で音をとって、それを使って
どのぐらいの人数を低減できるのか、っていうのを試そうと思っています。
ちょっと距離投目で、声は普通にしゃべるぐらいの大きさですいます。
その後、ノイズだけの収録をすること。もうちょっと近い距離で録音します。以上です。

この例においては、数値的に若干類似度が向上しているケースもありますが、主観的には一概に良くなったと言えないように思います
未処理の時に誤って文字起こしされていた言葉がノイズ除去によって正しくなっている部分もあれば、その逆の現象が起きている部分があったり、文字起こしがされなくなってしまった箇所も見受けられ、ノイズ除去処理の有無で文章全体の文字起こし精度はあまり変化しないというのが私の感想です。

また、例に挙げた音声ファイルは私がこの検証のために録音したものですが、実際の記者さんの取材音源をテストした場合も似たような結果となりました。

これらから考えられること

このような結果になった理由として、文字起こしエンジン内でもノイズ除去処理が行われている可能性があることが考えられます。
今回、文字起こしにはMicrosoft社製のエンジンを利用しました。そこに投入したデータ自体は外部に流れていかず、課金情報だけが送信される仕組みになっています。このためデータの秘匿性を担保しつつ最先端の文字起こしエンジンが使えるわけですが、その中の処理自体はブラックボックスになってしまいます。

どうやらノイズ除去的な処理が行われているようだ、という情報を得ることができましたが、具体的なアルゴリズムは社外秘のため公開されていません。そのため、今回の効果は検証してみることでしか確かめようがなかったのですが、自前のノイズ除去に製品のノイズ除去がかかっても、あまり大きな変化がなかったということになります。
今後社内で音声とその文字起こしデータが十分に蓄積されたら、社内のデータのみを用いた自社製文字起こしエンジンを開発したいと考えています。

また1点注意が必要なのは、文字起こしエンジンが「加工済み音声データ」を想定していない可能性が非常に高いことです。

先述のエンジンは、膨大な音声データとその文字起こしデータを機械学習で学習することで出来上がっていますが、その学習に用いた音声データに対して周波数ドメインをいじるような処理はされていないはずです。
そもそも論的な話にはなりますが、ノイズ除去によって音声データの性質をエンジン側の想定していないものに変化させてしまうと、文字起こしの精度は向上するどころか、むしろ低下してしまうと考えられます。

機械学習において、学習データと本質的な条件の異なるデータを入力することに意味はないので、自社文字起こしエンジンを開発する際には、①あらかじめノイズ除去を行ったデータのみ、②未処理のデータのみを利用した2つのモデルをそれぞれ作成し、それらの文字起こし精度をテストするなどして、ノイズ除去の必要性を検討する必要がありそうです。

文字起こし精度は向上しなかった...がしかし

ということで、私の開発した一連のライブラリは社内の文字起こしシステムに乗ることはありませんでしたが、Dockerの利用方法やAPIの簡単な作成方法、音声データの加工方法などいろいろと学びの多い取り組みでした。

M研では、このように直接ビジネスにつながらないような基礎研究でも、自分が興味を持てば取り組める環境にあります。
このテーマは、新卒一年目の配属直後に取り組んだ内容ですが、社内発表会で発表させていただくところまで担当することができました。

私の開発はうまい形で実用化されることはありませんでしたが、新聞社のエンジニアが実は面白いことに挑戦できる!という小噺に昇華することができたのでよしとしましょう!
最後は無理矢理宣伝で締めさせていただきました。
また他のエントリでお会いしましょう。

(メディア研究開発センター・倉井敬史)