『ディープラーニングの力で結月ゆかりの声になるリポジトリ』のパラメータチューニング(第一段階編)

#1.概要
『ディープラーニングの力で結月ゆかりの声になるリポジトリ』
https://github.com/Hiroshiba/become-yukarin
を動かすために調べた内容と、第一段階の音質を上げる方法について解説します。
#2.導入方法
導入方法については、まとめてくれている方がいますのでそちらを参照ください。
ubuntu17.10の方はこちら
[「ディープラーニングの力で結月ゆかりの声になる」ためのLinux環境構築](https://qiita.com/atticatticattic/items/575d71dab4ee716e4969)
ubuntu18.04の方はこちら
[【Quick Start】ディープラーニングで結月ゆかりの声になる on Ubuntu18.04](https://nullpo24.hatenablog.com/entry/2019/02/22/225311)

#3.実行環境
こちらについても、動作環境とコマンド操作についてまとめられています。
本記事では、この環境でパラメータチューニングを行います。

[「ディープラーニングの力で結月ゆかりの声になる」ための基礎知識とコマンド操作](https://qiita.com/atticatticattic/items/f1e4c671d73d815bbad2)
動作環境：https://github.com/YoshikazuOota/become-yukarin

#4.パラメータチューニング
第一段階の音質を上げるには学習データとなる自分の声をもとにパラメータを調整します。
前提として、ここでのパラメータチューニングは音質を上げるために学習データとなる音声の解像度(?)を上げる方針で進めるため、リポジトリにあるリアルタイム音声変換をするにはさらに調整が必要になります。
第一段階の音質改善に主眼をおいていることにご留意ください。

第一段階に関する設定ファイルは以下のファイルになります。

+ param.py ⇒ WORLDに関する設定が含まれる
+ config.json ⇒ 第一段階の学習に関する設定が含まれる
 
[設定ファイルの場所]
become-yukarin
　├become-yukarin
　│　└param.py
　├dat
　│　└config.json

#5.WORLDの設定
WORLDは音声合成分析システムで、学習する音声データの分解と学習後のデータを合成するのに使用します。
ここでは学習処理を排した音声分解合成処理のパラメータ調整と確認を行います。

param.pyファイルのorderとframe_periodを調整します。


```python:param.py(抜粋)
class AcousticFeatureParam(NamedTuple):
    frame_period: int = 5
    order: int = 8
```


以下は、確認用のコードです。
world_test関数のframe_period,orderがparam.pyのframe_period,orderに対応していますので、
main関数から値を変更し元の音声と同程度の音質になるまで調整してください。
確認後は調整した値をparam.pyに反映します。

WORLDのアルゴリズムを'harvest'としていますが'dio'を使用したい方は適宜変更してください。
WORLDの説明は以下の記事が参考になります。
[音声分析合成システムWORLDなるものを使ってみる。](https://qiita.com/misumi3104/items/de49e591a72b781c0521)
'dio'の調整はこちらを参照してください。
[「ディープラーニングの力で結月ゆかりの声になる」ための第1段の高音質化](https://qiita.com/atticatticattic/items/6d9e29d88e556911d9a3)

環境ルート(become-yukarin)に学習データの自分の音声ファイルを配置して実行します。
変換後のファイルは同フォルダに作成されます。

become-yukarin
　├world.py
　├my.wav　・・・学習データの自分の音声ファイル
　├my_5_8_harvest.wav　・・・変換後の音声ファイル



```python:world.py
# -*- coding:utf-8 -*-
import glob
import numpy
import librosa
import argparse
from pathlib import Path
import pyworld
import pysptk

from become_yukarin.data_struct import AcousticFeature
from become_yukarin.dataset.dataset import AcousticFeatureLoadProcess
from become_yukarin.dataset.dataset import AcousticFeatureProcess
from become_yukarin.dataset.dataset import AcousticFeatureSaveProcess
from become_yukarin.dataset.dataset import WaveFileLoadProcess
from become_yukarin.dataset.utility import MelCepstrumAligner
from become_yukarin.param import VoiceParam
from become_yukarin.param import AcousticFeatureParam
from become_yukarin.data_struct import Wave

def world_test(fpath, outdpath, frame_period, order, f0_estimating_method) :
    parser = argparse.ArgumentParser()
    base_voice_param = VoiceParam()
    base_acoustic_feature_param = AcousticFeatureParam()
    parser.add_argument('--input1_directory', '-i1', type=Path)
    parser.add_argument('--input2_directory', '-i2', type=Path)
    parser.add_argument('--output1_directory', '-o1', type=Path)
    parser.add_argument('--output2_directory', '-o2', type=Path)
    parser.add_argument('--pre_converter1_config', type=Path)
    parser.add_argument('--pre_converter1_model', type=Path)
    parser.add_argument('--sample_rate', type=int, default=base_voice_param.sample_rate)
    parser.add_argument('--top_db', type=float, default=base_voice_param.top_db)
    parser.add_argument('--pad_second', type=float, default=base_voice_param.pad_second)
    parser.add_argument('--frame_period', type=float, default=base_acoustic_feature_param.frame_period)
    parser.add_argument('--order', type=int, default=base_acoustic_feature_param.order)
    parser.add_argument('--alpha', type=float, default=base_acoustic_feature_param.alpha)
    parser.add_argument('--f0_estimating_method', type=str, default=base_acoustic_feature_param.f0_estimating_method)
    parser.add_argument('--f0_floor1', type=float, default=71)
    parser.add_argument('--f0_ceil1', type=float, default=800)
    parser.add_argument('--f0_floor2', type=float, default=71)
    parser.add_argument('--f0_ceil2', type=float, default=800)
    parser.add_argument('--ignore_feature', nargs='+', default=['spectrogram', 'aperiodicity'])
    parser.add_argument('--disable_alignment', action='store_true')
    parser.add_argument('--enable_overwrite', action='store_true')
    arguments = parser.parse_args()
    print('sample', arguments.sample_rate)
    wave_file_load_process = WaveFileLoadProcess(
        sample_rate=arguments.sample_rate,
        top_db=arguments.top_db,
        pad_second=arguments.pad_second,
    )
    wave1 = wave_file_load_process(fpath, test=True)
    acoustic_feature_process1 = AcousticFeatureProcess(
        frame_period=frame_period,
        order=order,
        alpha=arguments.alpha,
        f0_estimating_method=f0_estimating_method,
        f0_floor=arguments.f0_floor1,
        f0_ceil=arguments.f0_ceil1,
    )
    f1 = acoustic_feature_process1(wave1, test=True).astype_only_float(numpy.float32)
    
    fftlen = pyworld.get_cheaptrick_fft_size(arguments.sample_rate)
    spectrogram = pysptk.mc2sp(
        f1.mfcc,
        alpha=base_acoustic_feature_param.alpha,
        fftlen=fftlen,
    )
    
    f2 = AcousticFeature(
        f0=f1.f0,
        spectrogram=spectrogram,
        aperiodicity=f1.aperiodicity,
        mfcc=f1.mfcc,
        voiced=f1.voiced,
    ).astype(numpy.float64)
    
    out = pyworld.synthesize(
        f0=f2.f0.ravel(),
        spectrogram=f2.spectrogram,
        aperiodicity=f2.aperiodicity,
        fs=arguments.sample_rate,
        frame_period=frame_period,
    )
    
    fname = Path(fpath).stem
    
    outpath = outdpath + '/' + fname + '_' + str(frame_period) + '_' + str(order) + '_' + f0_estimating_method + '.wav'
    librosa.output.write_wav(outpath, out, arguments.sample_rate, norm=True)

if __name__ == "__main__": 
    world_test("my.wav", "./", 5, 8, 'harvest')

```

実行例

```bash:command
$ python world.py
```

#6.第一段階の設定
WORLDのパラメータ(order)を変更すると、学習に使う中間データ(npyファイル)の要素数が変化するため、
config.jsonをorderに合わせ変更します。

config.jsonのin_channelsとout_channelsをorder+2とする。

```json:config.json(抜粋)
{
  "model": {
    "in_channels": 10,
    "out_channels": 10,
}
```

第一段階学習のパラメータを調整します。
※現在(2019/3/9)、有効なパラメータとして確認できているのはnoiseのみです。




```json:config.json(抜粋)
{
  "dataset": {
    "input_global_noise": 0.01,
    "input_local_noise": 0.01,

    "target_global_noise": 0.01,
    "target_local_noise": 0.01,
  },
}
```

学習が進むにつれノイズが混じる場合は、学習率の減衰コードをtrain.pyの最後に追加します。

```python:train.py抜粋(学習率減衰コード追加)
    # 学習率減衰 1000イテレータ毎に0.9倍にする
    trigger_sh=(1000, 'iteration')
    ext = extensions.ExponentialShift('alpha', 0.9, optimizer=trainer.updater.get_optimizer('predictor'))
    trainer.extend(ext, trigger=trigger_sh)
    ext = extensions.ExponentialShift('alpha', 0.9, optimizer=trainer.updater.get_optimizer('discriminator'))
    trainer.extend(ext, trigger=trigger_sh)
    
    save_args(arguments, arguments.output)
    trainer.run()
```

#7.まとめ
『ディープラーニングの力で結月ゆかりの声になるリポジトリ』の
第一段階目のパラメータチューニングについて説明してきました。

調整するパラメータはどの値がいいか気になるところだと思いますが、
学習データの元となる音声の声質により変わってくると考えています。
一概にどれがよいかわかりませんので筆者も現在調整している段階になります。
#0.追記まとめ

+ 【6.第一段階の設定のnoiseに関して】

2019/3/10追記：
noiseに関して作者のヒホ氏に質問したところ、入力音声に対してノイズを与えて学習データにノイズ耐性を与えるとのこと。
もう少し高くしていいかもと言ってたので、~~0.1〜0.5~~ 0.01〜0.05？
そもそもノイズがデカすぎると良くない...と言ってたので劇的によくなるものではなさそう。
2019/3/12追記：
noiseは0.01がリポジトリに上がっている数値でしたので0.1は誤りです。

+ 【6.第一段階の設定に関して】
 
2019/3/12追記：
豆知識ですが、第一段階の学習はデータサイズが小さいのでGPU使用率が30%程度くらいです。
バッチサイズを上げれば、GPU使用率が100%になるので速く学習させたい方は1024とかにすると良いかもしれません。
ただし、バッチサイズを上げると1回の処理が長くなり、並列で動かすので精度が落ちるみたいです。

```json:config.json(抜粋)
{
  "train": {
    "batchsize": 1024,
  },
}
```

+ 【5.WORLDの設定に関して】

2019/3/13追記：
WORLDのframe_periodを下げるとフレーム間隔が短くなるので分解能がよくなりますが、代わりにデータサイズが大きくなるため第二段階の処理ができなくなります。
第二段階の性能アップの記事は次回書きます。(4月頃)

+ サンプル音声

2019/3/19追記：
サンプル動画をyoutubeにアップしました。
[卓上防音壁を作ってみた(設計編)](https://youtu.be/XTZ6-YOYK8k)