🎵

RVCを軽量化したv3を作ってみた

2023/06/18に公開

追記: 2023/06/19
ここで私がv3モデルと称しているのはあくまで自称であり、公式とは関係がないです。
v2を改造したものを便宜上v3としただけで、現在進行形で開発中のため、マージの予定はまだ考えていません。

はじめに

こんにちは、nadareです。
機械学習エンジニアで、普段はレコメンド・検索関連のお仕事をしています。いろんな競技プログラミングが好きです。

Retrieval-based-Voice-Conversion(以下RVC)という技術に関心を持ち、本家Retrieval-based-Voice-Conversion-WebUIddPn08さん版RVC-WebUIVC ClientにPR投げつつ勉強しています。

RVCは優れたGUI、ランタイムの整備、事前学習済みのモデルの用意など素晴らしい点が多いのですが、モデルの構造についてはやや古いように見えました。
そこでConvNextBlazeFaceを参考にモデルを軽量化し、かつ様々な工夫を追加することでより日本語のリアルタイム変換がうまくいくように改造を進めました。

これをちゃんと整備して使えるようにするかはまだ考えていませんが、今後の音声変換の他の研究に役立てば幸いです。

コード+重み

学習用コード(ddPn08さんのRVC-WebUIをフォーク)

https://github.com/nadare881/rvc-webui/tree/model_v2/lib/rvc_v3

weight + デモモデル

https://huggingface.co/datasets/nadare/rvc-v3j/tree/main
あみたろの声素材工房さん(https://amitaro.net/)の音声を使って学習したモデルを置いております。

推論用コード(VCClientのフォーク)

下記のフォーク版を開発者用モードで動かすとv3を試せます。
https://github.com/nadare881/voice-changer

改造ポイント

学習

  • 不要と思われるモジュールの除去
  • Convの近代化改修
    • depthwiseとpointwiseに分ける軽量化
      • Depthwiseには未来の情報を参照しないCausal Convolutionの導入
      • PointWiseにはLoRA亜種の導入
  • アップサンプリング+NSFのダウンサンプリングの高速化
  • generator自身を用いたaugmentationの導入
  • SegmentSizeの増加
  • bfloat16での学習

推論

  • kmeansによるindexの圧縮
  • faissのindexのパラメータ調整

不要と思われるモジュールの除去

generator

VITSは元々TTS用の構造で、その名残と思われるモジュールがいくつかありました。
WaveNetのenc_qを教師に、HuBERT+Transformerのenc_pを学習させた後、WaveNetのflowで話者の特徴量を与えていましたが、HuBERTと後段のdecoder内のResNetのみで十分と考えたのでこれらを除いてHuBERTの特徴量をそのままdecoder(synthesizer)に入れるようにしました。
また、従来はtransformerに入れる時点でHuBERTの特徴量を756次元から192次元にしてしまうのですが、ここで情報の損失が大きそうなのでdecoderには756次元のまま特徴量を入れるようにしました。

discriminator

DiscriminatorSは損失をみたところ0.25付近で固まっており、本物の音声と合成の音声の区別がついていなさそうなので削り、代わりにDiscriminatorPのperiodに1を追加しました。

Convの近代化改修

畳み込み層は空間方向とチャンネル方向に分割することでパラメータと計算量が抑えられることが知られていて、MobileNet以降の主流になっています。Generatorでは同じ次元でのresidual接続を繰り返すので、BlazeFaceを参考にdepthwiseとpointwiseを交互に繰り返すようにしました。Discriminatorでは畳み込み一回ごとにstrideを入れるのでConvNextを元にdepthwise->pointwise->pointwiseに分割しました。また、kernelsizeは大きく、層は少なくするのがレイテンシを抑えるコツなのでそれを意識したパラメータに変えました。これにより大幅に計算時間と学習パラメータ数を減らすことができました。

畳み込み層でのCausal Convolutionの導入

RVCの実行によく使われるVCClientでは入力の音声に過去の音声を繋げることで品質を確保しています。この使い方を考えた際、通常の畳み込みを用いると入力に用いた端にある音声部分の変換の質が悪くなりそうと思いました。そこで、未来の情報を参照しないCausal Convolutionをdepthwise畳み込みに入れることで、リアルタイム音声変換に特化させました。

LoRA亜種の導入

LoRAはLinear層と合わせてin_channels x r と r x out_channelsのLinear層を学習することで軽量にファインチューニングを行う技術です。このLoRAに用いる二つの行列をspeakerを表すembeddingから作成することで、話者ごとにLinear層をファインチューニングした状態を作れると考え、PointWise畳み込みにLoRAを導入しました。また、Discriminatorにも話者に紐づいたEmbeddingを与えることで、Discriminatorの精度を上げました。
ただ、LoRAがちゃんと効いているかどうかは比較できていないです。

アップサンプリング+NSFのダウンサンプリングの高速化

段階的にupsamplingを行うupsample_rateについて、先に大きく割合を上げてしまうと計算量が増えてしまいます。これを[10, 6, 2, 2, 2]から[5, 6, 4, 4]に変えることで高速化を行いました。
またSinGenで作るノイズ付きSin波を層ごとにnoise_convで毎回変換するのはかなりの計算時間をとっていることが分かりました。そこで、upsample_rateの逆に段階的にdownsamplingすることで高速化を行いました。また、singenは倍音を考慮するharmonic_numが0になっていましたが、17倍音まで考慮するようharmonic_num=16に設定することで音声合成のヒントを与えました。

generatorを用いたAugmentationの追加

音声変換の学習の問題点として、入力音声を用いて入力音声を予測している点です。ContentVecのような話者性を除いたHuBERTを用いていれば問題ないのですが、日本語版HuBERTを用いた場合HuBERTの特徴から話者性を学習してしまい話者性の変換度が落ちてしまいます。そこで、generatorで話者idとピッチを入れ替え音声を変換したものをHuBERTに入れ、そのembeddingを混ぜて学習を行うことで話者の変換がよりうまくいくようになりました。

SegmentSizeの増加

RVCでは学習時音声のサイズをsegment_sizeでトリミングしてからsynthesizerに入れ、discriminatorで比較しています。このパラメータは短くしても効率的に学習できるとVITSの論文には書いてあるのですが、RVCの設定値は秒になおすと0.24秒でした。日本語は1秒に5字喋れるらしいですが、これはあまりに短かったので、Discriminatorを軽くした分このパラメータを1.5秒分にまで増加させました。これについても変化は実感できていないのですが、効いてそうな気はします。

bfloat16での学習

RVCでは混合精度を用いて学習していますが、これにはfloat16を用いています。float16はfloat32と比較して表現できる桁数が異なるので学習が不安定になりやすいのですが、これをbfloat16に置き換えることで安定して学習できるようにしました。

kmeansによるindexの圧縮

RVCで特徴量を検索する際、検索対象は学習に用いたembeddingのすべてを対象にしていました。
これだと推論に時間がかかってしまうので、MiniBatchKmeansを用い、検索対象をkmeansのクラスタ中心に置き換えることでindexの軽量化と高速化を実現しました。これはddPn08さんのRVC-WebUIに実装済みです。(一方でこれにより学習データ中には少ない音素への変換は悪くなってしまうのでオプションにしています。)
VCClientのアップデートにより検索対象のembeddingをindexに復元するようになったので没にしたのですが、PCA+RandomRotationによる次元方向の圧縮も検討していました。ただ、これも需要が出てきたら復活させるかもしれないです。

faissのindexのパラメータ調整

faissのindexについてデータ数が多い時を想定してFastScanを提案していたのですが、データ数が少ない時は逆にスループットが増えてしまいました。そこで、パラメータ数に応じてFastScanとIVFのみを切り替えるようにしました。これはddPn08さんのRVC-WebUIに実装済みです。
また、初期にn_probeを増やすより検索結果の加重平均をとる方がより高速に良い結果を得られると提案していたのですが、実は近似近傍探索の精度はあまりいらず加重平均すら不要であるとわかりました。そこで加重平均をとる部分をVCClientから除くことでVCClientのCPU負荷を下げました。
(ついでに音声のresampleをtorchaudioのresampleに置き換えることで、ここの部分も高品質化かつ高層化を行いました。)多分最新版では反映されていると思います。

今後の開発予定

VITSのアップサンプリングベースモデルでの改良はある程度うまくいき、十分な高速化ができました。

ただ最近、NSF-HiFiGANに代わるVocosという音声合成のモジュールを見つけ、これを用いると段違いの高速化ができそうにみえました。音声変換を行うにはまだいろいろ工夫が必要なのですが、こちらを用いた音声変換の開発に移ろうと思います。今回作成したv3は本家の人に投げて改良に組み込んでもらえたら嬉しいなぁくらいでいます。上手い感じにだれか組み込んでください。応援しています。

AI声づくり技術研究会

Discussion