😸

信号処理による話者性変換を用いた音声データ拡張

2023/04/06に公開

こんにちは!Fusic 機械学習チームの鷲崎です。機械学習モデルの開発から運用までなんでもしています。もし、機械学習で困っていることがあれば、気軽にお問い合わせください。

最近、so-vits-svc(SoftVC VITS Singing Voice Conversion)という、性能の良いVoice Conversion (VC)が流行っている気がします。VCでは、入力音声から話者性を除去したコンテンツ情報の抽出が重要になります。そのため、コンテンツ情報に一貫性を持たせて、話者性を変更するデータ拡張が使用されることがあります。例えば、ピッチを変更するデータ拡張やイコライザーを持ちいたデータ拡張です。

イコライザーを用いた音声データ拡張に関しては、前回記事にしました。スペクトログラム上では、あまり変化していないように見えましたが、実際聞いた際に、若干、声を張り上げているよう聞こえたりし、環境が変化したような音声に拡張されていました。

本記事では、ピッチを変更するデータ拡張について解説します。コンテンツ情報を抽出するように学習されたContentVecでは、フォルマント周波数と基本周波数をランダムにスケールさせて話者性を変化させた結果に対して対照学習を行うことで、抽出コンテンツ情報の一貫性を保たせるという工夫をしています。実際、ピッチを変更するデータ拡張を行うと、男性の声を女性っぽく変更したり、さらに声を低くしたりといった拡張が達成できていました。

ContentVecの論文では、フォルマント周波数と基本周波数をスケールと書いていますが、実際の実装では、ピッチを遷移させることで、話者性の遷移を達成しています。(これは、等価なのでしょうか?)

Change Gender

ContentVecでは、以下のような関数でピッチのデータ拡張を行っています。

import parselmouth
def change_gender(x, fs, lo, hi, ratio_fs, ratio_ps, ratio_pr):
    s = parselmouth.Sound(x, sampling_frequency=fs)
    f0 = s.to_pitch_ac(pitch_floor=lo, pitch_ceiling=hi, time_step=0.8/lo)
    f0_np = f0.selected_array['frequency']
    f0_med = np.median(f0_np[f0_np!=0]).item()
    ss = parselmouth.praat.call([s, f0], "Change gender", ratio_fs, f0_med*ratio_ps, ratio_pr, 1.0)
    return ss.values.squeeze(0)

ここで重要な部分は、praat.callChange gendeを実行している部分です。引数の詳細などは、Sound: Change gender...を参考にしてください。ここで、ピッチを遷移させており、以下の式で計算されます。

finalPitch = newPitchMedian + (pitch * newPitchMedian / oldPitchMedian - newPitchMedian) * pitchRangeScaleFactor

ここの、oldPitchMedianは、関数中でto_pitch_acf0を抽出し、medianを取っている部分です。

Change gendeの引数は、以下になります。

  • ratio_fsは、周波数の遷移割合を表しており、1.1にした場合、声が高くなり女性っぽい声になり、1/1.1に設定した場合は、声が低くなり男性っぽい声になります。
  • f0_med*ratio_psは、新しいピッチの中央値(newPitchMedian)で、元の音声のF0の中央値をratio_psでスケールさせており、0Hzで、オリジナルの音声と同じピッチになるとのことです。
  • ratio_prは、上式のpitchRangeScaleFactorで、最終的なピッチを決定する際に、どの程度newPtichMedianを重要視するかというスケール値です。
  • 最後の引数1は、Duration factorで、時間方向のスケールを行うと時間ごとのコンテンツ情報が変化してしまうため、固定で1にしています。

データ拡張の実装

実際にchange genderを用いてデータ拡張を行う実装は、以下になります。詳細は、auspicious3000/contentvec/blob/main/contentvec/data/audio/contentvec_dataset.pyを参考にしてください。

音声波形wavと、その周波数、そして、spk2infoから情報を抽出するための話者indexspkが引数となっています。

最小pitchloと最大pitchhiの設定ですが、話者ごとに設定するのがベストです。しかし、作者にきいたところ、基本的には、男性、女性の属性ごとに設定しており、男性の場合、lo=50, hi=250, 女性の場合、lo=100, hi=400としているとのことです。メソッドの中では、一部修正しており、男性の場合lo=5075に変更しています。

change_genderの他の引数に関しては、ランダムに生成しています。

contentvec/blob/main/contentvec/data/audio/contentvec_dataset.py
...
    def random_formant_f0(self, wav, sr, spk):
        #s = parselmouth.Sound(wav, sampling_frequency=sr)
        _, (lo, hi, _) = self.spk2info[spk]
        
        if lo==50:
            lo=75
        if spk=="1447":
            lo, hi = 60, 400
        
        ratio_fs = self.rng.uniform(1, 1.4)
        coin = (self.rng.random() > 0.5)
        ratio_fs = coin*ratio_fs + (1-coin)*(1/ratio_fs)
        
        ratio_ps = self.rng.uniform(1, 2)
        coin = (self.rng.random() > 0.5)
        ratio_ps = coin*ratio_ps + (1-coin)*(1/ratio_ps)
        
        ratio_pr = self.rng.uniform(1, 1.5)
        coin = (self.rng.random() > 0.5)
        ratio_pr = coin*ratio_pr + (1-coin)*(1/ratio_pr)
        
        ss = change_gender(wav, sr, lo, hi, ratio_fs, ratio_ps, ratio_pr)
        
        return ss

このメソッドを用いることで、ランダムにピッチを変更し話者性を信号処理的にですが、変更できていました。

まとめ

簡単にですが、ピッチに関するデータ拡張について解説しました。思った以上に、簡単に実装できるので、音声処理のデータ拡張において、もっと一般的になればと思います。

最後に宣伝になりますが、機械学習でビジネスの成長を加速するために、Fusicの機械学習チームがお手伝いしています。機械学習のPoCから運用まで、すべての場面でサポートした実績があります。もし、困っている方がいましたら、ぜひFusicにご相談ください。お問い合わせから気軽にご連絡いただけますが、TwitterのDMからでも大歓迎です!

GitHubで編集を提案
Fusic 技術ブログ

Discussion