摘要
服務器都有了,這不得練練丹,有點說不過去啊。所以嘗試了從頭開始訓練一個模型,結果由于推理頁面好像有bug,不知道是不是失敗了,然后又嘗試微調一下模型。本篇文章主要記錄了三流調包俠嘗試煉丹過程中學習到的一些知識。
為什么要自己訓練或者微調模型
F5-TTS 的設計目標不是生成一個固定的、默認的(例如女聲普通話)語音。傳統的 TTS 模型,比如一些早期的端到端模型,確實可以直接輸入文本,然后生成一個預設音色的語音。它是一個“Text-to-Cloned-Voice”模型: F5-TTS 專注于從一小段參考音頻中提取說話人的音色、語調、說話風格等特征,然后將這些特征應用到你提供的文本上,生成帶有該音色的語音。
雖然 F5-TTS 強調零樣本語音克隆,需要參考音頻來工作,但訓練自己的模型(無論是從頭開始訓練還是在預訓練模型上進行微調)的意義在于:
提升特定音色的克隆質量和穩定性
- 預訓練的 F5-TTS 模型可能在通用語音克隆方面表現良好,但對于特定目標音色(例如你想要克隆的某個人的聲音),其克隆質量和穩定性可能不是最佳。
- 通過在包含目標音色的大量數據上進行微調 (fine-tuning),模型能夠更好地學習該音色的細微特征、語調模式和發音習慣,從而生成更自然、更像目標音色的語音。
- 這對于需要高保真度語音克隆的應用(如虛擬主播、個人語音助手等)至關重要。
適應特定語言或口音
- F5-TTS 原始模型可能主要在英文或中英文混合數據集上訓練。如果你需要為其他語言(例如粵語、日語、德語等)或特定口音(例如英式英語、美式英語的不同口音)生成高質量語音,那么在相應語言/口音的數據集上訓練或微調模型是必不可少的。
適應特定應用場景和數據特點
- 不同的應用場景可能對語音有不同的要求。例如,有聲讀物可能需要更平穩的語速,而對話系統可能需要更自然的停頓和交互感。
- 如果你的數據有特定的噪聲、錄音條件或說話風格,通過在類似數據上訓練,可以使模型對這些特點有更好的魯棒性或生成效果。
- F5-TTS 論文提到,其模型訓練使用了 “in-the-wild multilingual speech dataset Emilia”,這意味著它處理的是真實世界中帶噪的數據。如果你希望在更干凈的數據上得到更清晰的語音,或者在更復雜的場景下有更好的表現,訓練可以幫助模型適應。
控制生成語音的特性(如情感、語速等):
- 雖然 F5-TTS 自身就支持一定的情感和語速控制,但通過訓練自己的模型,并可能在數據中包含更多情感標簽或不同語速的樣本,可以進一步增強模型在這方面的表現和可控性。
官方訓練模型文檔如下
https://github.com/SWivid/F5-TTS/tree/main/src/f5_tts/train
接下來使用 F5-TTS來訓練個人的語音模型
環境準備
請先按照官方文檔部署并能夠正常文本轉語音,依賴應該就沒問題了
數據準備
數據集格式
官方文檔
https://github.com/SWivid/F5-TTS/discussions/57#discussioncomment-10959029
F5-TTS 采用的格式
datasets/
└── your-dataset/├── wavs/│ ├── 00001.wav│ ├── 00002.wav│ └── ...└── metadata.csv
metadata.csv
格式
wavs/00001.wav|這是第一條語音的文字
wavs/00002.wav|這是第二條語音的文字
網上的數據集
我本來想自己標注的,但好像好廢時間,就搜了搜看有沒有標注好的數據集,然后找到了
https://www.bilibili.com/opus/961351523556655128?spm_id_from=333.1387.0.0
B站up主 紅血球AE3803 分享的
解壓后文件中包含的文件如下
- .wav是語音文件
- .lab是其對應的文本
編寫腳本處理數據集
建議使用項目自帶的 web ui的方式處理數據集,會對音頻切段,可能會訓練效果更好一些
import os
import csv
import wave
import contextlibdef process_files(directory):"""處理指定目錄中的wav和lab文件,生成metadata.csv文件,并統計語音文件總時長"""# 檢查目錄是否存在if not os.path.isdir(directory):print(f"目錄 {directory} 不存在!")return# 創建輸出目錄(如果需要)wavs_dir = os.path.join(os.path.dirname(directory), "wavs")if not os.path.exists(wavs_dir):os.makedirs(wavs_dir)print(f"創建目錄: {wavs_dir}")# 收集所有匹配的文件file_pairs = []lab_files = {}wav_files = []# 首先收集所有.lab文件for filename in os.listdir(directory):if filename.endswith('.lab'):base_name = os.path.splitext(filename)[0]lab_files[base_name] = filename# 然后檢查每個.lab文件是否有對應的.wav文件for base_name, lab_file in lab_files.items():wav_file = f"{base_name}.wav"if os.path.exists(os.path.join(directory, wav_file)):file_pairs.append((wav_file, lab_file))# 處理匹配的文件對metadata = []total_duration = 0 # 添加總時長統計變量for wav_file, lab_file in file_pairs:# 讀取文本內容with open(os.path.join(directory, lab_file), 'r', encoding='utf-8') as f:text_content = f.read().strip()# 構建目標wav路徑(如 wavs/audio_xxxx.wav)target_wav_path = f"wavs/{wav_file}"# 添加到元數據metadata.append((target_wav_path, text_content))# 獲取wav文件時長wav_path = os.path.join(directory, wav_file)try:with contextlib.closing(wave.open(wav_path, 'r')) as f:frames = f.getnframes()rate = f.getframerate()duration = frames / float(rate)total_duration += durationprint(f"文件 {wav_file} 時長: {duration:.2f}秒")except Exception as e:print(f"無法讀取文件 {wav_file} 的時長: {str(e)}")# 復制wav文件到wavs目錄(如果需要)source_wav_path = os.path.join(directory, wav_file)target_wav_full_path = os.path.join(os.path.dirname(directory), target_wav_path)if not os.path.exists(os.path.dirname(target_wav_full_path)):os.makedirs(os.path.dirname(target_wav_full_path))# 如果文件不在目標位置,可以復制它# import shutil# if not os.path.exists(target_wav_full_path):# shutil.copy2(source_wav_path, target_wav_full_path)# 寫入metadata.csvoutput_file = os.path.join(os.path.dirname(directory), "metadata.csv")with open(output_file, 'w', encoding='utf-8', newline='') as f:for wav_path, text in metadata:f.write(f"{wav_path}|{text}\n")# 格式化總時長輸出hours, remainder = divmod(total_duration, 3600)minutes, seconds = divmod(remainder, 60)time_str = f"{int(hours)}小時{int(minutes)}分{seconds:.2f}秒"print(f"處理完成! 共處理了 {len(metadata)} 對文件,輸出到 {output_file}")print(f"語音文件總時長: {time_str} ({total_duration:.2f}秒)")if __name__ == "__main__":import sys# if len(sys.argv) > 1:# directory = sys.argv[1]# else:# # 默認目錄,可以根據需要修改# directory = input("請輸入包含wav和lab文件的目錄路徑: ")directory = "/Users/wy/wy/workspace/ios_app/黑天鵝"process_files(directory)
該腳本只是讀取文本然后拼接文件名,輸出metadata.csv
文件, 文件是手動挪到指定目錄的。輸出內容如下:
最后的數據集目錄結構如下
txt目錄是多余的,沒有也沒所謂,我只是將剩余的lab文件放到里面好看一點而已.
數據集預處理
這里除了手動處理外,還可以使用web的方式來處理數據集,具體請看后面訓練模型章節里面的web ui訓練小節,沒有辦法,誰叫寫完前面這堆內容才發現有web可以用…
腳本位置src/f5_tts/train/datasets/prepare_csv_wavs.py
prepare_csv_wavs.py腳本源碼
查看腳本可以看到正確的命令如下
入口函數是prepare_and_save_set
入口函數就調用了兩個函數
sub_result, durations, vocab_set = prepare_csv_wavs_dir(inp_dir, num_workers=num_workers)
調用 prepare_csv_wavs_dir
函數處理輸入目錄
- 使用多線程并行處理音頻文件(獲取音頻時長)
- 將文本轉換為拼音表示
- 返回處理后的數據、音頻時長列表和詞匯集合
save_prepped_dataset(out_dir, sub_result, durations, vocab_set, is_finetune)
調用 save_prepped_dataset
函數保存處理結果
- 將數據保存為Arrow格式(高效讀取)
- 保存音頻時長信息為JSON
- 根據
is_finetune
決定是復制現有詞匯表還是創建新詞匯表
其中跟音頻技術相關的是中文拼音轉換這步的處理,下面詳細看看拼音轉換的代碼。
中文拼音轉換函數詳解
convert_char_to_pinyin
函數是 F5-TTS 模型中的一個關鍵組件,它負責將中文文本轉換為拼音表示。這對于中文語音合成至關重要,因為模型需要處理拼音以正確發音。下面是詳細解析:
這個函數接收文本列表并將其中的中文字符轉換為帶聲調的拼音表示,同時保留非中文字符。這對于生成自然的中文語音至關重要。
def convert_char_to_pinyin(text_list, polyphone=True):if jieba.dt.initialized is False:jieba.default_logger.setLevel(50) # CRITICALjieba.initialize()final_text_list = []custom_trans = str.maketrans({";": ",", """: '"', """: '"', "'": "'", "'": "'"}) # add custom trans here, to address oovdef is_chinese(c):return ("\u3100" <= c <= "\u9fff" # common chinese characters)for text in text_list:char_list = []text = text.translate(custom_trans)for seg in jieba.cut(text):seg_byte_len = len(bytes(seg, "UTF-8"))if seg_byte_len == len(seg): # if pure alphabets and symbolsif char_list and seg_byte_len > 1 and char_list[-1] not in " :'\"":char_list.append(" ")char_list.extend(seg)elif polyphone and seg_byte_len == 3 * len(seg): # if pure east asian charactersseg_ = lazy_pinyin(seg, style=Style.TONE3, tone_sandhi=True)for i, c in enumerate(seg):if is_chinese(c):char_list.append(" ")char_list.append(seg_[i])else: # if mixed characters, alphabets and symbolsfor c in seg:if ord(c) < 256:char_list.extend(c)elif is_chinese(c):char_list.append(" ")char_list.extend(lazy_pinyin(c, style=Style.TONE3, tone_sandhi=True))else:char_list.append(c)final_text_list.append(char_list)return final_text_list
為什么要轉換成拼音?
消除歧義
- 中文是多音字密集的語言,一個漢字可能有多個讀音(如 “行” 可讀作 xíng 或 háng)。
- 在沒有上下文的情況下,TTS 系統很難判斷正確讀音。拼音提供了明確的發音方式,便于后續處理。
與語音模型對接
- 很多中文 TTS 系統的底層語音模型(如 Tacotron、FastSpeech、GPT-SoVITS)并不是直接以漢字為輸入,而是以拼音、聲調甚至音素(phoneme)為輸入。
- 拼音是一個中間表示(intermediate representation),便于模型學習發音規律。
提升語音自然度
- 精準的拼音/音素輸入能幫助模型合成更自然、連貫的語音,尤其是在聲調和語調變化方面。
1. 結巴分詞初始化
if jieba.dt.initialized is False:jieba.default_logger.setLevel(50) # CRITICALjieba.initialize()
- 初始化結巴分詞庫
- 將日志級別設為 CRITICAL 以抑制不必要的輸出
2. 自定義轉換規則
custom_trans = str.maketrans({";": ",", """: '"', """: '"', "'": "'", "'": "'"})
- 定義特殊字符的轉換規則,將一些特殊標點符號統一為標準形式
- 這有助于減少詞匯表外(OOV)字符的出現
3. 中文字符識別
def is_chinese(c):return "\u3100" <= c <= "\u9fff" # common chinese characters
- 定義內部函數檢測字符是否為中文
- 使用 Unicode 范圍 U+3100 至 U+9FFF,涵蓋常見中文字符
4.漢字轉為拼音
pypinyin
是 Python 中最主流的中文轉拼音庫,示例代碼如下
from pypinyin import lazy_pinyin, Style
text = "你好,世界!"
# 默認風格,不帶聲調
print(lazy_pinyin(text))
# 輸出: ['ni', 'hao', 'shi', 'jie']
# 帶聲調
print(lazy_pinyin(text, style=Style.TONE3))
# 輸出: ['ni3', 'hao3', 'shi4', 'jie4']
# 首字母
print(lazy_pinyin(text, style=Style.FIRST_LETTER))
# 輸出: ['n', 'h', 's', 'j']
5. 文本處理流程
對于每個輸入文本,函數執行以下步驟
- 應用自定義字符轉換規則
- 使用結巴分詞將文本分割為詞語片段
- 根據不同情況分別處理每個片段
a) 純字母和符號
if seg_byte_len == len(seg): # if pure alphabets and symbolsif char_list and seg_byte_len > 1 and char_list[-1] not in " :'\"":char_list.append(" ")char_list.extend(seg)
- 檢測片段是否只包含 ASCII 字符
- 在必要時添加空格以確保正確分隔
- 原樣保留字母和符號
b) 純中文
elif polyphone and seg_byte_len == 3 * len(seg): # if pure east asian charactersseg_ = lazy_pinyin(seg, style=Style.TONE3, tone_sandhi=True)for i, c in enumerate(seg):if is_chinese(c):char_list.append(" ")char_list.append(seg_[i])
- 檢測是否為純中文片段
- 使用
lazy_pinyin
將整個片段轉換為拼音 - 添加空格以確保拼音正確分隔
style=Style.TONE3
表示使用數字表示聲調(如 “ni3” 而非 “nǐ”)tone_sandhi=True
啟用聲調變化規則(如"一"的變調)
c) 混合字符
else: # if mixed characters, alphabets and symbolsfor c in seg:if ord(c) < 256:char_list.extend(c)elif is_chinese(c):char_list.append(" ")char_list.extend(lazy_pinyin(c, style=Style.TONE3, tone_sandhi=True))else:char_list.append(c)
- 逐字符處理混合內容
- ASCII 字符原樣保留
- 中文字符轉換為拼音并添加空格
- 其他字符(如日文、韓文等)原樣保留
開始預處理數據集
python src/f5_tts/train/datasets/prepare_csv_wavs.py /home/ubuntu/data-set /home/ubuntu/F5-TTS/data/heitiane --pretrain
- /home/ubuntu/data-set 數據集所在目錄
- /home/ubuntu/F5-TTS/data/heitiane 處理好的數據集存放位置,
F5-tts
項目所在的data目錄, 是固定的,heitiane
是你數據集的名稱,隨便填
還需要修改訓練用的配置文件中的數據集名稱為heitiane
具體位置請看訓練腳本詳解章節中的分詞器設置小節。
數據集預處理完成后,就可以開始訓練模型了
訓練模型
官方文檔給出的命令如下:
# setup accelerate config, e.g. use multi-gpu ddp, fp16
# will be to: ~/.cache/huggingface/accelerate/default_config.yaml
accelerate config# .yaml files are under src/f5_tts/configs directory
accelerate launch src/f5_tts/train/train.py --config-name F5TTS_v1_Base.yaml# possible to overwrite accelerate and hydra config
accelerate launch --mixed_precision=fp16 src/f5_tts/train/train.py --config-name F5TTS_v1_Base.yaml ++datasets.batch_size_per_gpu=19200
了解accelerate命令
accelerate
是 Hugging Face 提供的一個非常強大的工具,旨在幫助 PyTorch 用戶在不同的分布式訓練環境中(如多 GPU、多節點、TPU、混合精度等)輕松運行他們的訓練腳本,而無需編寫大量的樣板代碼。它讓你能夠專注于編寫 PyTorch 訓練循環本身,而把分布式訓練的復雜性交給 accelerate
來處理。
accelerate config命令
用于交互式地配置你的訓練環境。它會引導你通過一系列問題,根據你的硬件設置(CPU、單 GPU、多 GPU、TPU 等)和訓練需求(混合精度、DeepSpeed 等)生成一個配置文件。
下面是引導中的一些問題
- 問題1
它讓你選擇:你的訓練是在一臺機器上,還是多臺機器上?如果是在一臺機器上,是只用 CPU 跑,還是用多個 GPU、TPU 或其他專用硬件來跑?
- 問題2
Do you wish to optimize your script with torch dynamo?[yes/NO]:
PyTorch Dynamo 是 PyTorch 2.0 引入的一個核心功能,旨在顯著提高 PyTorch 模型的運行速度。
Dynamo 會在運行時動態地分析你的 PyTorch 代碼,將 Python 級別的操作圖轉換為底層的、更高效的、優化的計算圖。
- 問題3
當你使用 PyTorch Dynamo 進行代碼編譯時,你希望使用哪種“后端(backend)”來實際執行編譯優化?你可以把 Dynamo 理解為一個“編譯器前端”,它負責分析你的 PyTorch 代碼,把它翻譯成一個中間表示。而這些后端,就是真正把這個中間表示編譯成高效機器代碼并運行在特定硬件上的“編譯器后端”。
inductor
(通常為默認和推薦) 這是 PyTorch 2.0 默認和推薦的編譯后端,也是最常用的。它能夠將 PyTorch 代碼編譯成高度優化的 C++/CUDA 內核。inductor
是 PyTorch 團隊為提高性能而設計的,它能針對 GPU(特別是 NVIDIA GPU)生成非常高效的代碼。
- 問題4
Do you want to customize the defaults sent to torch.compile? [yes/NO]:
當你在 accelerate config
中選擇啟用 torch dynamo
優化后,accelerate
會在內部調用 PyTorch 的 torch.compile()
函數來對你的模型和訓練循環進行編譯。accelerate
默認會為 torch.compile()
使用一組合理的默認參數。但是,通過這個問題,它給你一個機會去修改這些默認值,以更精細地控制編譯行為。只有在你了解 torch.compile
參數的作用,并且有特定需求(如追求極致性能或解決特定兼容性問題)時,才考慮選擇 yes
并進行自定義配置。
- 問題5
Do you want to use DeepSpeed? [yes/NO]:
DeepSpeed 是微軟開發的一個深度學習優化庫,旨在讓訓練超大型模型(如大型語言模型)變得更容易、更高效、更節省資源。它提供了多種高級優化技術. 選擇 NO
(默認和常見選擇)
- 問題6
What GPU(s) (by id) should be used for training on this machine as a comma-seperated list? [all]:
在這臺機器上,您希望使用哪些 GPU 進行訓練?請以逗號分隔的列表形式提供 GPU 的 ID。默認是使用所有 GPU,回車即可。
- 問題7
Would you like to enable numa efficiency? (Currently only supported on NVIDIA hardware). [yes/NO]:
是否希望啟用 NUMA 效率優化? 并且它明確指出,這項功能目前僅支持 NVIDIA 硬件。如果您使用的是 NVIDIA GPU,并且您的機器是較新的、用于深度學習訓練的服務器(通常是雙路或多路 CPU),那么選擇 yes
可能會帶來性能提升。如果是一般的家用或開發用臺式機,通常選擇 NO
即可,性能提升不明顯。
- 問題8
你是否希望使用混合精度(Mixed Precision)訓練,以及如果使用,要選擇哪種浮點精度?
在訓練過程中,同時使用 32 位浮點數 (FP32) 和 16 位浮點數 (FP16 或 BF16) 兩種精度。現代 GPU(尤其是 NVIDIA 的 Tensor Core)對 FP16/BF16 計算有專門的硬件加速,這可以顯著提高訓練速度。
accelerate lunch命令
無論你是在單 GPU、多 GPU、多節點服務器(多臺機器)、還是 TPU 上訓練模型,你都可以使用 accelerate launch
命令來運行你的 Python 訓練腳本,而不用去處理底層的分布式通信、設備管理等復雜細節。
具體怎么實現的,超綱了!忽視,我就一臺機子,沒有分布式環境。
了解F5-TTS 訓練腳本
這是 F5-TTS 的訓練入口腳本 train.py
,用于配置和啟動文本到語音模型的訓練。下面是模型訓練中涉及的部分代碼的閱讀筆記
1. Hydra 管理配置文件
@hydra.main(version_base="1.3", config_path=str(files("f5_tts").joinpath("configs")), config_name=None)
def main(model_cfg):# ...訓練流程代碼
- 使用 Hydra 管理配置文件
config_path
指向配置文件目錄config_name=None
允許通過命令行指定配置文件model_cfg
參數自動加載配置內容
配置文件目錄是src/f5_tts/configs
config_name
是F5TTS_v1_Base.yaml
,是在運行命令時提供的
2. 模型配置和初始化
model_cls = hydra.utils.get_class(f"f5_tts.model.{model_cfg.model.backbone}")
model_arc = model_cfg.model.arch
tokenizer = model_cfg.model.tokenizer
mel_spec_type = model_cfg.model.mel_spec.mel_spec_typeexp_name = f"{model_cfg.model.name}_{mel_spec_type}_{model_cfg.model.tokenizer}_{model_cfg.datasets.name}"
具體的配置參數如下
下面是上圖中出現的參數筆記,需要的時候再看
Transformer相關參數
arch
這一部分定義了模型內部的核心“語音處理器”——Transformer 的工作方式。Transformer 是一種非常強大的神經網絡結構,擅長處理序列數據,在這里就是把文字序列變成語音特征序列。
-
dim: 1024
(維度/尺寸)
決定了模型內部處理信息的“通道”數量,影響模型的容量和表達能力。 想象這是工廠里傳送帶的“寬度”。寬度越大,每次能處理的信息量就越多,模型可能學到更豐富的特征,但也會更耗資源。 -
depth: 22
(深度)
Transformer 模型的層數,層數越多模型越深,理論上學習能力越強。想象這是傳送帶的“層數”。層數越多,信息被處理和提煉的次數就越多,模型能進行更復雜的轉換和學習。 -
heads: 16
(注意力頭數)
Transformer 中的多頭注意力機制,每個頭可以關注輸入序列的不同方面,提高模型捕捉復雜關系的能力。想象這是工廠里有 16 雙“眼睛”,每雙眼睛都從不同的角度去看輸入的信息,然后把各自的發現整合起來。這樣能更全面地理解信息的不同部分之間的關系。 -
ff_mult: 2
(前饋網絡乘數)
決定了 Transformer 中前饋神經網絡的維度,通常是dim
的倍數,影響模型的復雜度和學習能力。想象在每層傳送帶上,信息經過處理后會送入一個小“加工車間”,這個數字就是這個車間的“放大倍數”。放大倍數越大,車間能處理的細節就越多。 -
text_dim: 512
(文本維度)
定義了模型在處理文本輸入時,每個字符或詞語被轉換成的向量表示的維度。想象這是文字信息進入工廠時的“初始規格”或者“編碼長度”。 -
text_mask_padding: True
(文本填充掩碼)
在處理變長文本序列時,對填充(padding)部分應用掩碼,防止模型關注到無意義的填充信息。想象文字輸入會有長短不一的情況,為了讓所有輸入都一樣長,我們會在短的后面“補齊”一些空白。這個設定就是告訴機器,在處理時要“忽略”這些補齊的空白,不要把它們當作真正的文字信息。 -
qk_norm: null
(查詢鍵歸一化)
控制是否對 Transformer 中的查詢(query)和鍵(key)向量進行歸一化操作,這可能會影響訓練的穩定性和性能。 -
conv_layers: 4
(卷積層數)
指的是在 Transformer 模塊中包含的卷積層數量,用于提取局部特征。在 Transformer 的核心處理之前或之后,信息還會經過 4 道“精細篩選”的工序,這些工序能捕捉到一些局部的、細粒度的特征。 -
pe_attn_head: null
(位置編碼注意力頭)
如果不為null
,它會為注意力機制提供額外的位置編碼信息,幫助模型更好地理解序列中元素的順序。 -
checkpoint_activations: False
(檢查點激活)
一種節省顯存(內存)的優化策略,通過在反向傳播時重新計算激活值而非存儲所有激活值來減少內存消耗,但會增加計算量。想象工廠里信息處理的每一步都會產生中間結果。如果設置為True
,就是說每處理完一步,我們都把這一步的中間結果“暫時存起來”,而不是直接丟棄。這樣雖然處理速度會慢一點,但是能省下很多用來記錄的“工作臺空間”(內存)。False
就是不存。
mel_spec
(梅爾頻譜設定)
mel_spec
這一部分定義了語音最終輸出的梅爾頻譜的特性。梅爾頻譜是一種模擬人類聽覺感知的語音特征表示,是語音合成中非常關鍵的中間產物。
-
target_sample_rate: 24000
(目標采樣率)
決定了生成語音的采樣率,影響語音的質量和文件大小。24000 Hz(24 kHz)是高保真語音常用的采樣率。 想象這是最終語音的“清晰度”或“細膩度”。每秒鐘取 24000 個聲音樣本,數字越大,聲音就越細膩、逼真。 -
n_mel_channels: 100
(梅爾通道數)
梅爾頻譜的維度,表示梅爾濾波器組的數量。更多的梅爾通道可以捕捉更豐富的語音細節。 想象這是把聲音分解成不同頻率成分時,“過濾器”的數量。過濾器越多,對聲音頻率的分析就越細致、越準確。 -
hop_length: 256
(跳躍長度)
計算梅爾頻譜時,相鄰幀之間采樣的跳躍步長。每隔 256 個聲音樣本就“截取”一段聲音來分析。這個值越小,分析就越密集,時間分辨率越高。 -
win_length: 1024
(窗口長度)
計算梅爾頻譜時,每個分析窗口的長度。想象每次“截取”聲音來分析時,我們截取多長的一段。截取 1024 個樣本長度。 -
n_fft: 1024
(FFT 點數)
這是聲音從時間域轉換到頻率域時的一種數學計算方式的參數。快速傅里葉變換 (FFT) 的點數,通常等于或大于win_length
,影響頻譜分辨率。 -
mel_spec_type: vocos
(梅爾頻譜類型)
指定用于生成梅爾頻譜的特定聲碼器或梅爾頻譜提取方法。不同的類型可能有不同的實現細節和音質特點。粗略理解這是生成梅爾頻譜的“方式”或“算法標準”。這里可以選擇使用vocos
還是bigvgan
這種算法。
3. 分詞器設置
if tokenizer != "custom":tokenizer_path = model_cfg.datasets.name
else:tokenizer_path = model_cfg.model.tokenizer_path
vocab_char_map, vocab_size = get_tokenizer(tokenizer_path, tokenizer)
- 設置文本分詞器路徑
- 加載詞匯表映射和大小
- 支持默認和自定義分詞器選項
由于是默認的分詞器是"pinyin", 取的是配置文件中配置的datasets中的name
Emilia_ZH_EN
是默認的數據集,此處要修改為你數據集預處理生成的數據集名稱heitiane(黑天鵝)
datasets:name: heitiane
4. 數據集加載和訓練啟動
train_dataset = load_dataset(model_cfg.datasets.name, tokenizer, mel_spec_kwargs=model_cfg.model.mel_spec)
trainer.train(train_dataset,num_workers=model_cfg.datasets.num_workers,resumable_with_seed=666, # 數據集隨機打亂的種子
)
- 加載處理過的訓練數據集
- 調用訓練器的
train
方法開始訓練 - 配置數據加載并行度
- 設置固定種子確保可復現性
訓練代碼就不看了,超綱了!
開始訓練模型
命令行訓練模型
訓練命令如下
# .yaml files are under src/f5_tts/configs directory
accelerate launch src/f5_tts/train/train.py --config-name F5TTS_v1_Base.yaml
或者指定某個配置項的值
# possible to overwrite accelerate and hydra config
accelerate launch --mixed_precision=fp16 src/f5_tts/train/train.py --config-name F5TTS_v1_Base.yaml ++datasets.batch_size_per_gpu=19200
由于訓練時間較長,所以需要放在后臺訓練,下面是訓練命令示例,請根據自己需要調整參數,其實所有的參數都能在命令行中指定,其他參數可以參考web ui章節給出的命令
nohup accelerate launch --mixed_precision=fp16 src/f5_tts/train/train.py --config-name F5TTS_v1_Base.yaml ++datasets.batch_size_per_gpu=4800 ++optim.epochs=30 ++optim.earning_rate=9.375e-6 > train_log.log 2>&1 &
web ui訓練模型
f5-tts_finetune-gradio --port 7860 --host 0.0.0.0
啟動的時候最好加上nohup放在后臺跑
nohup f5-tts_finetune-gradio --port 7860 --host 0.0.0.0 2>&1 &
經過前面訓練腳本的學習,再看web界面會清晰很多,下面是官方的web使用教程視頻,就是教如何用web完成所有操作(數據集處理、模型訓練/微調、模型測試等)
https://github.com/SWivid/F5-TTS/discussions/143
web訓練模型的截圖,能夠看到訓練進度、以及很方便調整參數和查看當前訓練的效果。值得注意的是,web對文件的位置是有要求的,具體請根據后臺報錯信息查看。
在使用的過程中,各種因素可能會導致訓練中斷,web中可以指定checkpoint功能,就算掛了,也能續上繼續訓練。當然命令行也支持,可以參考下面的命令。
web實際上用的微調命令大致如下
accelerate launch --mixed_precision=fp16 /home/ubuntu/F5-TTS/src/f5_tts/train/finetune_cli.py --exp_name F5TTS_v1_Base --learning_rate 1e-05 --batch_size_per_gpu 4898 --batch_size_type frame --max_samples 64 --grad_accumulation_steps 1 --max_grad_norm 1 --epochs 100 --num_warmup_updates 100 --save_per_updates 500 --keep_last_n_checkpoints 5 --last_per_updates 100 --dataset_name heitiane --finetune --tokenizer pinyin --log_samples
訓練模型遇到的問題
顯存不足
這里有幾個概念需要弄清楚
batch_size
它指的是每次模型參數更新(一次反向傳播)所使用的訓練樣本數。相當于背單詞,讓你一次背完單詞書,腦子不夠用(顯存不夠多),當然搞不了。
epochs
它的含義是整個訓練集被模型完整“看”一遍的次數,相當于背多少次單詞書。
假設你有 1000 個訓練樣本,設置 batch_size = 100
,每一步訓練處理 100 個樣本,每個epoch
需要重復 1000 / 100 = 10次(步數)。
默認的batch_size_per_gpu=38400太大了,顯存放不下,改小就夠了。我的顯存是16G,調到4800才能夠正常跑下去。將 batch_size_per_gpu
從 38400 降低到 4800 時,這是一個非常顯著的減少(減少到原來的 4800/38400=1/8)。理論上,如果其他因素不變,顯存占用會下降到原來的 1/8。這應該能有效解決遇到的 CUDA out of memory
錯誤。
但是這種大幅度減小 batch_size
對訓練過程和模型質量有重要的影響,其他的參數也需要相應地進行調整。
學習率 (Learning Rate) 調整
batch_size
越小,每次梯度更新的噪聲越大。如果學習率保持不變,模型在損失函數空間中的跳動會非常劇烈,可能導致:
- 無法收斂: 模型在最優解附近震蕩,無法穩定下來。
- 梯度爆炸: 極端情況下,損失值變成
NaN
。 - 收斂到次優解: 即使收斂,也可能不如使用大批量時找到的最優解。
如何調整
遵循學習率線性縮放規則,如果原始 batch_size
是 38400,對應學習率是 7.5×10?5。
新的 batch_size
是 4800,是原來的 1/8。那么,新的學習率應該調整為:
7.5×10?5×(4800/38400)=7.5×10?5×(1/8)=0.9375×10?5=9.375×10?6
訓練命令加上 ++optim.earning_rate=9.375e-6
即可
總訓練步數與 Epochs
如果您保持 epochs
不變,由于 batch_size
減小到 1/8,那么每個 epoch
內的訓練步數會增加 8 倍。這意味著總訓練步數會大幅增加。
如何權衡
-
增加
epochs
: 盡管總步數增加了,但由于每次更新的梯度估計質量下降,模型可能需要更多的epochs
才能達到與大batch_size
相似的收斂程度。您可以嘗試增加epochs
,例如增加 2-4 倍,但需要觀察驗證損失,防止過擬合。 -
梯度累積 (Gradient Accumulation): 強烈推薦。這是一種更優雅的解決方案,可以在保持小
batch_size
的同時,模擬更大的有效批量大小。如果您將batch_size_per_gpu
從 38400 降到了 4800 (1/8),您可以設置gradient_accumulation_steps = 8
。這意味著模型每 8×4800=38400 幀才執行一次權重更新。這相當于在每次權重更新時,使用了與原來 38400 幀相同的有效批量大小。
什么是梯度累積
首先要理解兩個概念,
- 物理批次大小 (Physical Batch Size): 這是您實際能夠裝入 GPU 內存的批次大小。在本例中,您將其設為 4800 幀/GPU。
- 有效批次大小 (Effective Batch Size): 這是模型在執行一次權重更新時,所“看到”的數據總量。它等于 物理批次大小 × 梯度累積步數。
沒有梯度累積時(原始設置):
batch_size_per_gpu = 38400
幀。每次處理 38400 幀的數據,就執行一次梯度計算和一次權重更新。假設總訓練數據是 Total_Frames
。總的權重更新步數 = Total_Frames / 38400
使用梯度累積后:
batch_size_per_gpu = 4800
幀。gradient_accumulation_steps = 8
。
模型會處理第一個 4800 幀的批次,計算梯度,但不更新權重,而是累積梯度。接著處理第二個 4800 幀的批次,計算梯度,將其累積到之前的梯度上。這個過程重復 8 次。當處理完第 8 個 4800 幀的批次后,累積的梯度就相當于一個包含了 8×4800=38400 幀的超級批次所計算出的梯度。此時,模型才執行一次權重更新。
所以,雖然您每次 GPU 處理的物理數據量變小了(4800 幀),但是模型進行一次實際的權重更新,依然是基于 38400 幀的數據(有效批次大小)。因此,總的權重更新步數又變回了:Total_Frames / 38400。
梯度累積的好處是,可以保持梯度估計的“平滑性”與大批量接近,同時又降低了單次前向/反向傳播的顯存占用。如果使用梯度累積,那么 epochs
和學習率的調整就更接近于原始大批量的情況。
生成的全是噪音(??)
最初訓練了4個小時左右,從頭訓練的一個模型,用推理頁面測試,結果出來全是噪音。懷疑是訓練時間不夠或者自己哪里操作、參數有問題。改微調模型,在微調的web ui上測試是正常的,但是在推理的web ui上就又全是噪音了。可能是推理web ui上代碼有bug。由于訓練了4個小時的模型已經刪了,已經無從考證了。本來想仔細讀讀代碼看是不是bug的,但是時間不夠了,gpu服務器時間到期了。。。
數據集的語音質量是可以的,但是這個數據集是否適合用來訓練。這得去看看論文了解模型算法,還有對應的訓練數據集。而我本意是想得到一個穩定的某個音色,所以還是選擇微調吧。
F5-TTS 作為一個零樣本語音克隆模型,其核心能力在于無需特定說話人的訓練數據,即可通過短時參考音頻克隆音色。微調的意義在于將這種通用克隆能力進一步特化和優化,以達到更高水平的質量、穩定性、適應性。
查看gpu使用情況
watch -n 1 nvidia-smi
-
推理單個句子時候,顯存使用情況
-
使用web ui微調時候顯存使用情況
web實際調用的微調命令如下:
accelerate launch --mixed_precision=fp16 /home/ubuntu/F5-TTS/src/f5_tts/train/finetune_cli.py --exp_name F5TTS_v1_Base --learning_rate 5e-06 --batch_size_per_gpu 4800 --batch_size_type frame --max_samples 64 --grad_accumulation_steps 8 --max_grad_norm 1 --epochs 100 --num_warmup_updates 100 --save_per_updates 500 --keep_last_n_checkpoints 5 --last_per_updates 100 --dataset_name test-demo --finetune --tokenizer pinyin --log_samples
微調結果
損失曲線(Loss Curve)是深度學習訓練過程中,損失函數值隨訓練迭代次數(或 Epochs)變化的圖形表示。它是評估模型訓練狀態和診斷問題(如欠擬合、過擬合、學習率不當)的最重要工具之一。
在 TensorBoard 或其他監控工具中觀察訓練損失的變化。在web ui中可以看到,訓練模型的頁面有個日志選項是可以選擇TensorBoard的,所以代碼是已經集成了TensorBoard了的。
安裝相關的庫
pip install tensorboard
然后在F5-tts項目根目錄執行下面的命令然后訪問頁面即可
tensorboard --host 0.0.0.0 --logdir runs/ --port 8000
下圖是測試微調時候的截圖
第一張圖可以看到損失曲線整體呈現下降趨勢。雖然有波動,但平滑后的曲線(淺色線)從大約 0.75
附近下降到了 0.6787
。這表明模型正在學習!損失的下降是積極的信號,意味著模型正在改進其預測能力。但是由于時間太短了,得加長訓練時間!
第二張圖是典型的余弦退火(Cosine Annealing)學習率調度器的曲線。余弦退火是一種非常有效的學習率調度策略。它在訓練初期保持較高的學習率以快速探索損失空間,然后在后期平滑地降低學習率,使模型能夠更精細地收斂到最優解,并且有助于跳出局部最優。至于圖上能看出什么。。。沒研究
命令行去使用微調得到的模型
f5-tts_infer-cli --model F5TTS_v1_Base \
--ckpt_file /home/ubuntu/F5-TTS/ckpts/test-demo/my.safetensors \
--vocab_file /home/ubuntu/F5-TTS/data/test-demo_pinyin/vocab.txt \
--ref_audio /home/ubuntu/tests/chapter3_5_blackswan_154.wav \
--ref_text "這個方向,請跟我來." \
--output_file /home/ubuntu/tests/tests/infer_cli_basic.wav \
--gen_text "可惡,這狗bug浪費了我不少時間"