音声分類や音声認識の分野で個人開発をしている方は、学習に使う音声データの前処理や加工に苦労されているんじゃないかと思います。そこで、今回は私が試行錯誤した結果、データ加工に有効だと思った手法を網羅的に紹介したいと思います。また、その手法を実現するためのPythonソースコードと海外の機械学習プロジェクトの事例も併せて公開します。個人的な感想多めです、専門家の方がいらっしゃったらコメントで間違いを指摘していただけるとめちゃくちゃ嬉しいです!(周りに機械学習勉強してる人がいないので寂しい・・・)
目次
- 使用ライブラリ解説
- librosa
- SoX(Sound eXchange)
- librosa
- データオーギュメンテーション手法解説
- 音量上げ/下げ
- ノーマライズ(正規化)
- ホワイトノイズ付加
- タイムシフト(音声の始まる時間をずらす)
- タイムストレッチ(音声の伸縮、早送り・ゆっくり)
- ピッチシフト(音の高さを変える)
- リバーブ(響きをつける)
- イコライザー(特定の周波数を中心に強調・減少させる)
- その他のエフェクト
- 音量上げ/下げ
- 海外プロジェクトの音声データ加工事例
使用ライブラリ解説
librosa
公式ドキュメント:
https://librosa.org/doc/latest/index.html
解説:
Pythonにおける音声ライブラリではメジャーなので詳しい説明は割愛。音声の加工や、メルスペクトログラムのような変換機能がとても充実しています。基本的なデータ加工はこれ1つで大丈夫なのでまずはこれを使うとよいです。処理も早いです。
インストール方法:
# pipの場合
pip install librosa
# condaの場合
conda install -c conda-forge librosa
・SoX(Sound eXchange)
公式ドキュメント:
http://sox.sourceforge.net/ ・・・公式サイト
https://pysox.readthedocs.io/en/latest/・・・Pythonライブラリ
解説:
クロスプラットフォーム対応の音声変換ユーティリティです。日本語ではあまりドキュメントがありませんが、海外の機械学習関連のソースコードを漁ってるとデータの前処理や加工でよく見かけます。ラッパーがPythonライブラリで用意されているので、そちらからSoXのバイナリへアクセスして音声の加工や変換をすることになります。
外部の実行ファイルを呼び出す関係上、少し処理が遅くなってしまいますが、出来る音声加工の種類がとても豊富なので採用を検討するのをおすすめします。リバーブ、イコライザー、コンプレッサーが手軽に使えるのはめちゃくちゃアツいです。
インストール方法:
Windows環境でのインストール方法は以下の通り(MacやLinuxは公式ドキュメント参照)
1.公式ページにて、Downloadの「Sourceforge’s File Release System.」をクリック
2.「Download Latest Version」をクリックしてインストーラーをダウンロード
3.ダウンロードしたインストーラーを実行してインストール
4.インストールしたフォルダを環境変数(PATH)に追加
5.コマンドプロンプトから「sox」コマンドのパスが通っているか確認する
6.ラッパーライブラリをPythonにインストール
#pipの場合
pip install sox
データオーギュメンテーション手法解説
librosaで音声データの読み込みを行っている前提で、ソースコードの紹介とざっくりとした解説をしていきます。
import librosa
filename = "test.wav"
data, _ = librosa.load(filename, sr=44100)
・音量上げ/下げ
data_vol = data * 2.0
音量を上げたい分だけ掛け算をすれば大丈夫です。これを学習データ毎にランダムでかけてあげると音量の大小に対して強くなれる気がします。ただ、明らかに2.0倍はやりすぎです笑
・ノーマライズ(正規化)
data_norm = librosa.util.normalize(data)
音声データの最大音量をもとに、限界まで音量アップしてくれる処理になると思います。学習させる音声データが比較的短時間の場合(私は3秒くらいまでを目安にしています)は、音量の大小に対してロバスト性が高くなる気がしてます。データの時間が長かったり、1つのデータの中で音量の振れ幅が大きい場合はあまりうまくいかないかも。その場合はコンプレッサーかけた方がいいかもしれないです。
・ホワイトノイズ付加
def white_noise(x, rate=0.002):
return x + rate*np.random.randn(len(x))
data_whitenoise = white_noise(data)
雑音を入れることで認識精度をあげようというものです。脳死で使ってます。
ほとんどの音声分類系のタスクで使われているイメージですが、経験上確かに精度良くなります。タスクの内容にもよりますが、基本必須でいいと思います。
・タイムシフト(音声の始まる時間をずらす)
def time_shift(data, shift):
data_roll = np.roll(data, shift)
return data_roll
data_timeshift = time_shift(data,2**10)
音声の開始時間をずらします。ずらした分、後ろの方の音が前に移動します。音の開始タイミングの違いに強くなれそうなオーギュメンテーションだと思います。とりあえず使うことが多いです。
・タイムストレッチ(音声の伸縮、早送り・ゆっくり)
def stretch(data, rate=1):
input_length = len(data)
data = librosa.effects.time_stretch(data, rate)
if len(data)>input_length:
data = data[:input_length]
else:
data = np.pad(data, (0, max(0, input_length - len(data))), "constant")
return data
data_stretch = stretch(data, rate=1.5)
早送りにしたり、ゆっくりにしたりします。音程は変わらないはずです。これ使うと精度上がるかどうか、使ってみた感想教えてくれると嬉しいです。学習データのサイズが大きくなりすぎた時は省略しちゃうことが多いかな…
・ピッチシフト(音の高さを変える)
# librosaパターン
def pitch_shift(data, sample_rate, shift):
ret = librosa.effects.pitch_shift(data, sample_rate, shift, bins_per_octave=12, res_type='kaiser_best')
return ret
data_pitch = pitch_shift(data, 44100, 12)
# SoXパターン
def pitch_shift(data, sample_rate, shift)
tfm = sox.Transformer()
tfm.pitch(shift)
return tfm.build_array(input_array=data, sample_rate_in=sample_rate)
data_pitch = pitch_shift(data, 44100, 12)
shiftのパラメータですが、半音が1で1オクターブが12となります。音程を変えちゃうので、ピッチ情報が結構大切なデータの場合は薄く使います。個人的には、人の声や楽器の音の場合はプラスマイナス0.1くらいがちょうどいい気がします。
・リバーブ(響きをつける)
def reverb(data, sample_rate, rate)
tfm = sox.Transformer()
tfm.reverb(rate)
return tfm.build_array(input_array=data, sample_rate_in=sample_rate)
data_reverb = reverb(data, 44100, 35)
音声データの録音環境がバラバラな場合とかには効果を発揮する気がしています。VTuberの音声を学習させてた時のことですが、配信中にリバーブをアクセントに使ったりする配信者さんの場合は誤検知が多かったんですよね…その時に使ってみて結構うまくいった覚えがあります。
・イコライザー(特定の周波数を中心に強調・減少させる)
def eq(data, sample_rate)
tfm = sox.Transformer()
tfm.equalizer(2000, 2.0, 5.0)
return tfm.build_array(input_array=data, sample_rate_in=sample_rate)
data_eq = eq(data, 44100, 35)
これも音声データの録音環境の違いを吸収してくれそうな気がするので、学習データのサイズに余裕があったら使ってます。マイクからの距離だったり、機材のセッティング等で録音される音の周波数の強弱は影響を受けますからね…
ちなみにパラメータですが、公式ドキュメントによると以下の通りです。
equalizer(frequency: float, width_q: float, gain_db: float)
frequency : float
The filter’s central frequency in Hz.
width_q : float
The filter’s width as a Q-factor.
gain_db : float
The filter’s gain in dB.
・その他のエフェクト
SoXでは他にも「コンプレサー」や「バンドパスフィルタ」などミキサーやイコライザーの基本的な機能は網羅しています。まさかの「フランジャー」などモジュレーション系のエフェクトもあるのはなぜ??笑
海外プロジェクトの音声データ加工事例
ドラム音声分類でお世話になっている、Googleの「magenta」プロジェクトのソースコードを分析して、soxラッパーから呼び出した形に改造しました。こちらが元のコードです。
def liner_uniform(min, max):
return np.random.uniform(min, max)
def log_uniform(min, max):
log_min_value = math.log(min)
log_max_value = math.log(max)
return math.exp(np.random.uniform(log_min_value, log_max_value))
def eq_sound(x):
tfm = sox.Transformer()
tfm.pitch(liner_uniform(-0.1, 0.1))
tfm.contrast(liner_uniform(0.0, 100.0))
tfm.equalizer(log_uniform(32.0, 4096.0), liner_uniform(2.0, 2.0), liner_uniform(-20.0, 5.0))
tfm.equalizer(log_uniform(32.0, 4096.0), liner_uniform(2.0, 2.0), liner_uniform(-20.0, 5.0))
tfm.reverb(liner_uniform(0.0, 70.0))
return tfm.build_array(input_array=x, sample_rate_in=S_R)
data_eq = eq_sound(data)
音声データに対して、下記の要領でオーギュメンテーションしています。
- 音程(ピッチ)の変更を適用
- コントラスト(公式ドキュメントによるとコンプレッサーに相当)を適用
- イコライザでランダムな周波数帯を中心として増減を適用×2回
- リバーブを適用
エフェクトをかける度合いも効率よくランダムになっています。ドラム音や楽器の演奏の分類タスクに用いられているものなので、同様のタスクには効果があるのではないでしょうか。
最後に
現在取り組んでいるアプリ開発はこちらです。プロトタイプはTwitterで紹介しています。この記事で紹介したオーギュメンテーションを使ったりしながら、少しずつ精度を上げているところです。いいねしてくださるとうれしいです、励みになります!!