之前我們使用Bert-VITS2V2.0.2版本對現有的原神資料集進行了本地訓練,但如果克隆物件脫離了原神角色,我們就需要自己構建資料集了,事實上,深度學習模型的效能和泛化能力都依託於所使用的資料集的質量和多樣性,本次我們在本地利用Bert-VITS2V2.0.2對黴黴講中文的音色進行克隆實踐。
黴黴講中文的原始音影片地址:
https://www.bilibili.com/video/BV1bB4y1R7Nu/
這一段是基於HeyGen專案的AI音色克隆以及唇形合成技術,全片1分鐘左右,中文和英文各30秒,因為我們只克隆中文音色部分,那麼將英文部分截去,留下30秒的中文音訊素材。
Bert-VITS2V2.0.2構建資料集
拿到影片後,首先需要做音畫分離的操作,即將影片和音訊拆開,因為資料集並不需要影片,執行命令安裝相關庫:
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 <class 'numpy.ndarray'> 22050
可以看到讀取到了取樣頻率和每個取樣點的訊號強度,取樣點共 848384,頻率為 22050,音訊長度約38秒。
至此,我們就完成了原始資料集檔案的準備。
Bert-VITS2V2.0.2資料集切分
深度學習訓練過程中,計算機會把訓練資料讀入顯示卡的快取中,但如果訓練集資料過大,會導致記憶體溢位問題,也就是常說的“爆視訊記憶體”現象。
將資料集分成多個部分,每次只載入一個部分的資料進行訓練。這種方法可以減少記憶體使用,同時也可以實現並行處理,提高訓練效率。
雖然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秒音訊被切成了九份。
Bert-VITS2V2.0.2資料集重取樣和標註
切分好資料集後,需要對音訊進行重新取樣並生成標註檔案,較高的取樣率會導致更大的資料量和更高的計算成本。
執行指令碼:
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
檢查無誤後,資料預處理就完成了。
Bert-VITS2 V2.0.2開始訓練
開啟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版本本地基於現有資料集訓練(原神刻晴),囿於篇幅,這裡不再贅述。
Bert-VITS2 V2.0.2過擬合問題
按照刻板印象,訓練步數應該越多越好,但其實不然,訓練步數(或稱為迭代次數)並不是越多越好,而是需要在一定範圍內找到一個合適的平衡點,如果模型的訓練步數過多,模型可能會過度擬合訓練資料,導致在新資料上的泛化能力下降。過擬合指的是模型過度記憶了訓練資料中的細節和噪聲,而無法很好地適應新的、未見過的資料。
類比的話,有些類似生活中的語義飽和現象,又稱字形飽和、完形崩壞,是一種心理學現象,指的是人在重複盯著一個字或者一個單詞長時間後,會發生突然不認識該字或者單詞的情況。此過程僅為暫時,心理學上認為其原因是人的大腦神經如果短時間內接收到太多重複的刺激,就會引起神經活動的抑制,造成對常用字突然不認識的現象。
一般情況下,較大的資料集通常可以提供更多的樣本和更豐富的資料分佈,有助於模型學習更準確和泛化能力更好的特徵。大資料集可以降低過擬合的風險,使模型更能夠捕捉資料中的普遍模式而不是噪聲。因此,如果資料集足夠大,模型可能需要更多的訓練步數才能充分利用資料集的資訊。
但我們的資料集只有30秒,所以並不需要迭代過多次數,50步足矣。
最後,執行命令對剛訓練的模型進行推理即可:
python3 server_fastapi.py
結語
需要注意的是,本次30秒小資料集訓練很容易導致過擬合,因為模型可能會過度記憶資料中的細節和噪聲。過多的訓練次數可能會加劇過擬合問題。另一方面,如果訓練次數太少,模型可能無法充分學習資料中的模式和特徵,導致欠擬合。因此,需要在過擬合和欠擬合之間找到一個平衡點。
最後奉上本地整合包,與君共觴:
https://pan.baidu.com/s/1KtNb4wb4UbsHrwVKyTlT0g?pwd=v3uc