之前我们使用Bert-VITS2V2.0.2版本对现有的原神数据集进行了本地训练,但如果克隆对象脱离了原神角色,我们就需要自己构建数据集了,事实上,深度学习模型的性能和泛化能力都依托于所使用的数据集的质量和多样性,本次我们在本地利用Bert-VITS2V2.0.2对霉霉讲中文的音色进行克隆实践。
霉霉讲中文的原始音视频地址:
https://www.bilibili.com/video/BV1bB4y1R7Nu/
这一段是基于HeyGen项目的AI音色克隆以及唇形合成技术,全片1分钟左右,中文和英文各30秒,因为我们只克隆中文音色部分,那么将英文部分截去,留下30秒的中文音频素材。
拿到视频后,首先需要做音画分离的操作,即将视频和音频拆开,因为数据集并不需要视频,运行命令安装相关库:
pip3 install moviepy
moviepy可以帮我们把音频部分提取出来,编写代码:
from moviepy.editor import AudioFileClip
my_audio_clip = AudioFileClip("e:/meimei.mp4")
my_audio_clip.write_audiofile("e:/meimei.wav")
音频就被提取了出来。
随后针对原始音频素材进行分析:
import librosa
import numpy as np
audio, freq = librosa.load("e:\meimei.wav")
time = np.arange(0, len(audio)) / freq
print(len(audio), type(audio), freq, sep="\t")
程序返回:
python3 -u "test.py"
848384 22050
可以看到读取到了采样频率和每个采样点的信号强度,采样点共 848384,频率为 22050,音频长度约38秒。
至此,我们就完成了原始数据集文件的准备。
深度学习训练过程中,计算机会把训练数据读入显卡的缓存中,但如果训练集数据过大,会导致内存溢出问题,也就是常说的“爆显存”现象。
将数据集分成多个部分,每次只载入一个部分的数据进行训练。这种方法可以减少内存使用,同时也可以实现并行处理,提高训练效率。
虽然38秒的原始数据并不大,我们依然需要对其切分,这里首先克隆Bert-VITS2V2.0.2本地训练项目:
https://github.com/v3ucn/Bert-VITS2_V202_Train.git
安装依赖:
pip install -r requirements.txt
随后运行项目内的切分脚本:
python3 audio_slicer.py
该脚本原理就是利用slicer2库将大文件切分为小份:
import librosa # Optional. Use any library you like to read audio files.
import soundfile # Optional. Use any library you like to write audio files.
import shutil
import gradio as gr
import os
import webbrowser
import subprocess
import datetime
import json
import requests
import soundfile as sf
import numpy as np
import yaml
from config import config
import os
with open('config.yml', mode="r", encoding="utf-8") as f:
configyml=yaml.load(f,Loader=yaml.FullLoader)
model_name = configyml["dataset_path"].replace("Data\\","")
from slicer2 import Slicer
audio, sr = librosa.load(f'./Data/{model_name}/raw/{model_name}/{model_name}.wav', sr=None, mono=False) # Load an audio file with librosa.
slicer = Slicer(
sr=sr,
threshold=-40,
min_length=2000,
min_interval=300,
hop_size=10,
max_sil_kept=500
)
chunks = slicer.slice(audio)
for i, chunk in enumerate(chunks):
if len(chunk.shape) > 1:
chunk = chunk.T # Swap axes if the audio is stereo.
soundfile.write(f'./Data/{model_name}/raw/{model_name}/{model_name}_{i}.wav', chunk, sr) # Save sliced audio files with soundfile.
if os.path.exists(f'./Data/{model_name}/raw/{model_name}/{model_name}.wav'): # 如果文件存在
os.remove(f'./Data/{model_name}/raw/{model_name}/{model_name}.wav')
需要注意的是min_length参数非常重要,分片文件时长绝对不能低于2秒,这里单位是毫秒,所以数值为2000,因为梅尔频谱本身需要有一个加窗的过程,音频文件必须要至少达到1帧长+窗口时长才能有结果,否则就会返回空。所以在数据切分时不能有超过2秒的音频,同时本来短时样本的质量就普遍偏低。
切分后效果:
E:\work\Bert-VITS2-v202_demo\Data\meimei\raw\meimei>tree /f
Folder PATH listing for volume myssd
Volume serial number is 7CE3-15AE
E:.
meimei_0.wav
meimei_1.wav
meimei_2.wav
meimei_3.wav
meimei_4.wav
meimei_5.wav
meimei_6.wav
meimei_7.wav
meimei_8.wav
可以看到38秒音频被切成了九份。
切分好数据集后,需要对音频进行重新采样并生成标注文件,较高的采样率会导致更大的数据量和更高的计算成本。
运行脚本:
python3 short_audio_transcribe.py --languages "CJE" --whisper_size medium
这里语言使用medium模型进行推理,解决方案采用whisper,关于whisper,请移步:持续进化,快速转录,Faster-Whisper对视频进行双语字幕转录实践(Python3.10),这里不再赘述。
程序返回:
E:\work\Bert-VITS2-v202_demo\venv\lib\site-packages\whisper\timing.py:58: NumbaDeprecationWarning: The 'nopython' keyword argument was not supplied to the 'numba.jit' decorator. The implicit default value for this argument is currently False, but it will be changed to True in Numba 0.59.0. See https://numba.readthedocs.io/en/stable/reference/deprecation.html#deprecation-of-object-mode-fall-back-behaviour-when-using-jit for details.
def backtrace(trace: np.ndarray):
Data\meimei\raw
Detected language: zh
但这些歌曲没进入专辑因为想留着他们下一张专辑用
Processed: 1/31
Detected language: zh
然後下一張專輯完全不同所以他們被拋在了後面
Processed: 2/31
Detected language: zh
你總是會想起這些歌曲你會想
Processed: 3/31
Detected language: zh
会发生什么因为我希望人们能听到这个但它属于那个时刻
Processed: 4/31
Detected language: zh
所以现在我可以回去重新审视我的旧作品
Processed: 5/31
Detected language: zh
我從他們所在的地方挖掘出那些歌曲
Processed: 6/31
Detected language: zh
並聯繫了我喜歡的藝術家
Processed: 7/31
Detected language: zh
問他們是否願意和我一起演唱這首歌
Processed: 8/31
Detected language: zh
你知道Phoebe Bridgers是我最喜欢的艺术家之一
Processed: 9/31
可以看到文本已经被whisper转录了出来。
随后对文本进行预处理以及生成bert模型可读文件:
python3 preprocess_text.py
python3 bert_gen.py
执行后会产生训练集和验证集文件:
E:\work\Bert-VITS2-v202\Data\meimei\filelists>tree /f
Folder PATH listing for volume myssd
Volume serial number is 7CE3-15AE
E:.
cleaned.list
short_character_anno.list
train.list
val.list
检查无误后,数据预处理就完成了。
打开Data/meimei/config.json训练配置文件:
{
"train": {
"log_interval": 50,
"eval_interval": 50,
"seed": 42,
"epochs": 200,
"learning_rate": 0.0001,
"betas": [
0.8,
0.99
],
"eps": 1e-09,
"batch_size": 8,
"fp16_run": false,
"lr_decay": 0.99995,
"segment_size": 16384,
"init_lr_ratio": 1,
"warmup_epochs": 0,
"c_mel": 45,
"c_kl": 1.0,
"skip_optimizer": false
},
"data": {
"training_files": "Data/meimei/filelists/train.list",
"validation_files": "Data/meimei/filelists/val.list",
"max_wav_value": 32768.0,
"sampling_rate": 44100,
"filter_length": 2048,
"hop_length": 512,
"win_length": 2048,
"n_mel_channels": 128,
"mel_fmin": 0.0,
"mel_fmax": null,
"add_blank": true,
"n_speakers": 1,
"cleaned_text": true,
"spk2id": {
"keqing": 0
}
},
"model": {
"use_spk_conditioned_encoder": true,
"use_noise_scaled_mas": true,
"use_mel_posterior_encoder": false,
"use_duration_discriminator": true,
"inter_channels": 192,
"hidden_channels": 192,
"filter_channels": 768,
"n_heads": 2,
"n_layers": 6,
"kernel_size": 3,
"p_dropout": 0.1,
"resblock": "1",
"resblock_kernel_sizes": [
3,
7,
11
],
"resblock_dilation_sizes": [
[
1,
3,
5
],
[
1,
3,
5
],
[
1,
3,
5
]
],
"upsample_rates": [
8,
8,
2,
2,
2
],
"upsample_initial_channel": 512,
"upsample_kernel_sizes": [
16,
16,
8,
2,
2
],
"n_layers_q": 3,
"use_spectral_norm": false,
"gin_channels": 256
},
"version": "2.0"
}
训练的保存间隔调小一点,方便训练过程中随时进行推理验证。
随后输入命令,开始训练:
python3 train_ms.py
至此,训练环节和之前的基于已有数据集的本地训练流程已经一致,更多训练步骤请移步:本地训练,开箱可用,Bert-VITS2 V2.0.2版本本地基于现有数据集训练(原神刻晴),囿于篇幅,这里不再赘述。
按照刻板印象,训练步数应该越多越好,但其实不然,训练步数(或称为迭代次数)并不是越多越好,而是需要在一定范围内找到一个合适的平衡点,如果模型的训练步数过多,模型可能会过度拟合训练数据,导致在新数据上的泛化能力下降。过拟合指的是模型过度记忆了训练数据中的细节和噪声,而无法很好地适应新的、未见过的数据。
类比的话,有些类似生活中的语义饱和现象,又称字形饱和、完形崩坏,是一种心理学现象,指的是人在重复盯着一个字或者一个单词长时间后,会发生突然不认识该字或者单词的情况。此过程仅为暂时,心理学上认为其原因是人的大脑神经如果短时间内接收到太多重复的刺激,就会引起神经活动的抑制,造成对常用字突然不认识的现象。
一般情况下,较大的数据集通常可以提供更多的样本和更丰富的数据分布,有助于模型学习更准确和泛化能力更好的特征。大数据集可以降低过拟合的风险,使模型更能够捕捉数据中的普遍模式而不是噪声。因此,如果数据集足够大,模型可能需要更多的训练步数才能充分利用数据集的信息。
但我们的数据集只有30秒,所以并不需要迭代过多次数,50步足矣。
最后,运行命令对刚训练的模型进行推理即可:
python3 server_fastapi.py
需要注意的是,本次30秒小数据集训练很容易导致过拟合,因为模型可能会过度记忆数据中的细节和噪声。过多的训练次数可能会加剧过拟合问题。另一方面,如果训练次数太少,模型可能无法充分学习数据中的模式和特征,导致欠拟合。因此,需要在过拟合和欠拟合之间找到一个平衡点。
最后奉上本地整合包,与君共觞:
https://pan.baidu.com/s/1KtNb4wb4UbsHrwVKyTlT0g?pwd=v3uc