# 文本转语音（TTS）微调指南

微调 TTS 模型可以让它们适应您特定的数据集、用例或期望的风格与语气。目标是定制这些模型以克隆声音、调整说话风格和语气、支持新语言、处理特定任务等。我们也支持 **语音转文本 (STT)** 模型，例如 OpenAI 的 Whisper。

使用 [Unsloth](https://github.com/unslothai/unsloth)，您可以微调 **任何** TTS 模型（`兼容 transformers` ）比其他使用 Flash Attention 2 的实现快 1.5 倍并节省 50% 内存。

⭐ **Unsloth 支持任何 `兼容 transformers` 兼容的 TTS 模型。** 即使我们还没有为其提供笔记本或上传，它仍然受支持，例如可以尝试微调 Dia-TTS 或 Moshi。

{% hint style="info" %}
零样本克隆能捕捉音色但常常错过语速和表达，往往听起来机械且不自然。微调能提供更准确、更真实的语音复刻。 [在此阅读更多信息](#fine-tuning-voice-models-vs.-zero-shot-voice-cloning).
{% endhint %}

### 微调 笔记本：

我们还将 TTS 模型（原始和量化版）上传到我们的 [Hugging Face 页面](https://huggingface.co/collections/unsloth/text-to-speech-tts-models-68007ab12522e96be1e02155).

| [Sesame-CSM（1B）](https://colab.research.google.com/github/unslothai/notebooks/blob/main/nb/Sesame_CSM_\(1B\)-TTS.ipynb) | [Orpheus-TTS（3B）](https://colab.research.google.com/github/unslothai/notebooks/blob/main/nb/Orpheus_\(3B\)-TTS.ipynb) | [Whisper Large V3](https://colab.research.google.com/github/unslothai/notebooks/blob/main/nb/Whisper.ipynb) （STT） |
| ----------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------- |
| [Spark-TTS（0.5B）](https://colab.research.google.com/github/unslothai/notebooks/blob/main/nb/Spark_TTS_\(0_5B\).ipynb)   | [Llasa-TTS（1B）](https://colab.research.google.com/github/unslothai/notebooks/blob/main/nb/Llasa_TTS_\(1B\).ipynb)     | [Oute-TTS（1B）](https://colab.research.google.com/github/unslothai/notebooks/blob/main/nb/Oute_TTS_\(1B\).ipynb)   |

{% hint style="success" %}
如果您注意到输出时长最大为 10 秒，请将`max_new_tokens = 125` 提高到高于默认值 125。由于 125 个 token 对应约 10 秒音频，若需更长输出请设置更高的值。
{% endhint %}

### 选择并加载 TTS 模型

对于 TTS，通常偏好较小模型以降低延迟并为最终用户提供更快的推理速度。微调参数量低于 3B 的模型通常是理想的，我们的主要示例使用 Sesame-CSM（1B）和基于 Llama 的 Orpheus-TTS（3B）。

#### Sesame-CSM（1B） 详情

**CSM-1B** 是一个基础模型，而 **Orpheus-ft** 是在 8 位专业配音演员上微调的，使得语音一致性成为主要区别。CSM 在每个说话者需要音频上下文才能表现良好，而 Orpheus-ft 则内建了这种一致性。

从像 CSM 这样的基础模型微调通常需要更多计算，而从像 Orpheus-ft 这样的已微调模型开始则能开箱即用地获得更好结果。

为了帮助 CSM，我们增加了新的采样选项和示例，展示如何使用音频上下文以改善语音一致性。

#### Orpheus-TTS（3B） 详情

Orpheus 在大量语音语料上进行预训练，擅长生成逼真的语音并内建对笑声、叹息等情感提示的支持。其架构使其成为最容易使用和训练的 TTS 模型之一，并且可以通过 llama.cpp 导出，这意味着它在各种推理引擎中具有良好兼容性。对于不受支持的模型，您只能保存 LoRA 适配器的 safetensors。

#### 加载模型

因为语音模型通常体积较小，您可以使用 LoRA 16 位或全量微调（FFT）来训练模型，这可能会带来更高的质量。要以 LoRA 16 位加载：

```python
from unsloth import FastModel

model_name = "unsloth/orpheus-3b-0.1-pretrained"
model, tokenizer = FastModel.from_pretrained(
    model_name,
    load_in_4bit=False  # 使用 4 位精度 (QLoRA)
)
```

当这段运行时，Unsloth 会下载模型权重，如果您更喜欢 8 位，可以使用 `load_in_8bit = True`，或者若要进行完整微调设置 `full_finetuning = True` （确保您有足够的显存）。您也可以将模型名称替换为其他 TTS 模型。

{% hint style="info" %}
**注意：** Orpheus 的分词器已经包含用于音频输出的特殊标记（稍后会详细说明）。您 *不* 需要单独的声码器——Orpheus 将直接输出音频标记，这些标记可以解码为波形。
{% endhint %}

### 准备您的数据集

至少，一个 TTS 微调数据集由 **音频片段及其对应的转录文本** （文本）组成。我们来使用 [*Elise* 数据集](https://huggingface.co/datasets/MrDragonFox/Elise) 该数据集是约 3 小时的单说话者英语语音语料。有两个变体：

* [`MrDragonFox/Elise`](https://huggingface.co/datasets/MrDragonFox/Elise) ——一个增强版本，转录中嵌入了 **情感标签** （例如 \<sigh>、\<laughs>）。这些尖括号中的标签表示表情（笑声、叹息等），并被 Orpheus 的分词器视为特殊标记
* [`Jinsaryko/Elise`](https://huggingface.co/datasets/Jinsaryko/Elise) ——基础版本，转录不含特殊标签。

数据集的组织方式是每条记录包含一个音频和对应的转录。在 Hugging Face 上，这些数据集有诸如 `audio` （波形）， `text` （转录文本），以及一些元数据（说话者姓名、音高统计等）字段。我们需要向 Unsloth 提供一组音频-文本配对的数据集。

{% hint style="success" %}
与其单纯关注音色、节奏和音高，优先事项应是确保您的数据集已完全注释并适当规范化。
{% endhint %}

{% hint style="info" %}
对于某些模型，例如 **Sesame-CSM-1B**，您可能会注意到在使用说话者 ID 0 时生成结果存在语音差异，因为它是一个 **基础模型**——它没有固定的声音身份。说话者 ID 标记主要有助于保持 **对话内一致性**，而不是跨独立生成的一致性。

要获得一致的语音，请提供 **上下文示例**，例如一些参考音频片段或先前的发言。这有助于模型更可靠地模仿目标声音。没有这些，即使使用相同的说话者 ID，也会出现变化。
{% endhint %}

**选项 1：使用 Hugging Face Datasets 库** ——我们可以使用 Hugging Face 的 `datasets` 库加载 Elise 数据集：

```python
from datasets import load_dataset, Audio

# 加载 Elise 数据集（例如带情感标签的版本）
dataset = load_dataset("MrDragonFox/Elise", split="train")
print(len(dataset), "samples")  # Elise 大约有 ~1200 个样本

# 确保所有音频为 24 kHz 采样率（Orpheus 的期望采样率）
dataset = dataset.cast_column("audio", Audio(sampling_rate=24000))
```

这将下载数据集（约 \~328 MB，约 1.2k 个样本）。每个条目 `数据集` 是一个至少包含以下项的字典：

* `"audio"`：音频片段（波形数组和诸如采样率的元数据），以及
* `"text"`：转录字符串

Orpheus 支持类似于 `<laugh>`, `<chuckle>`, `<sigh>`, `<cough>`, `<sniffle>`, `<groan>`, `<yawn>`, `<gasp>`等标签。例如： `"I missed you <laugh> so much!"`。这些标签用尖括号括起，将被模型视为特殊标记（它们与 [Orpheus 期望的标签](https://github.com/canopyai/Orpheus-TTS) 如 `<laugh>` 和 `<sigh>`相匹配）。在训练期间，模型会学会将这些标签与相应的音频模式关联。带标签的 Elise 数据集中已经有许多此类标签（例如卡片中列出有 336 次 “laughs”、156 次 “sighs” 等）。如果您的数据集中缺少这些标签但您想要加入它们，您可以在音频包含这些表情时手动为转录添加注释。

**选项 2：准备自定义数据集** ——如果您有自己的音频文件和转录：

* 将音频片段（WAV/FLAC 文件）组织到一个文件夹中。
* 创建一个带有文件路径和转录列的 CSV 或 TSV 文件。例如：

  ```
  filename,text
  0001.wav,Hello there!
  0002.wav,<sigh> I am very tired.
  ```
* 使用 `load_dataset("csv", data_files="mydata.csv", split="train")` 来加载它。您可能需要告诉数据集加载器如何处理音频路径。另一种方法是使用 `datasets.Audio` 特性以按需加载音频数据：

  ```python
  from datasets import Audio
  dataset = load_dataset("csv", data_files="mydata.csv", split="train")
  dataset = dataset.cast_column("filename", Audio(sampling_rate=24000))
  ```

  然后 `dataset[i]["audio"]` 将包含音频数组。
* **确保转录文本已规范化** （没有分词器可能不认识的异常字符，若使用则除外情感标签）。同时确保所有音频具有一致的采样率（如有必要，请重新采样到模型期望的目标采样率，例如 Orpheus 的 24kHz）。

总之，对于 **数据集准备**:

* 您需要一个 **（音频，文本）** 的列表。
* 使用 HF `datasets` 库来处理加载和可选的预处理（例如重采样）。
* 在文本中包含任何您希望模型学习的 **特殊标签** （确保它们以 `<angle_brackets>` 格式出现，以便模型将其视为独立标记）。
* （可选）如果是多说话者，您可以在文本中包含说话者 ID 标记或使用单独的说话者嵌入方法，但这超出了本基础指南的范围（Elise 是单说话者）。

### 使用 Unsloth 微调 TTS

现在，让我们开始微调！我们将通过 Python 代码来说明（可在 Jupyter 笔记本、Colab 等中运行）。

**步骤 1：加载模型和数据集**

在我们所有的 TTS 笔记本中，我们启用了 LoRA（16 位）训练并禁用了 QLoRA（4 位）训练，设置为： `load_in_4bit = False`。这样模型通常能更好地学习您的数据集并获得更高的准确性。

```python
from unsloth import FastLanguageModel
import torch
dtype = None # None 表示自动检测。Tesla T4、V100 使用 Float16，Ampere+ 使用 Bfloat16
load_in_4bit = False # 使用 4 位量化以减少内存使用。可以为 False。

model, tokenizer = FastLanguageModel.from_pretrained(
    model_name = "unsloth/orpheus-3b-0.1-ft",
    max_seq_length= 2048, # 为长上下文选择任意值！
    dtype = dtype,
    load_in_4bit = load_in_4bit,
    #token = "hf_...", # 如果使用如 meta-llama/Llama-2-7b-hf 之类的受限模型则使用此项
)

from datasets import load_dataset
dataset = load_dataset("MrDragonFox/Elise", split = "train")
```

{% hint style="info" %}
如果内存非常有限或数据集很大，您可以流式加载或分块加载。这里 3 小时的音频很容易放入内存。如果使用您自己的 CSV 数据集，也以类似方式加载。
{% endhint %}

**步骤 2：高级 - 训练前对数据进行预处理（可选）**

我们需要为 Trainer 准备输入。对于文本到语音，一种方法是以因果方式训练模型：将文本和音频 token ID 连接为目标序列。然而，由于 Orpheus 是一个仅解码器的 LLM，会输出音频，我们可以将文本作为输入（上下文）并将音频 token id 作为标签。实际上，如果模型配置将其识别为文本到语音，Unsloth 的集成可能会自动完成此操作。如果没有，我们可以做类似：

```python
# 对文本转录进行分词
def preprocess_function(example):
    # 对文本进行分词（保留如 <laugh> 之类的特殊标记）
    tokens = tokenizer(example["text"], return_tensors="pt")
    # 展平为 token ID 列表
    input_ids = tokens["input_ids"].squeeze(0)
    # 模型将在这些文本 token 之后生成音频 token。
    # 在训练中，我们可以将 labels 设为 input_ids（以便学习预测下一个 token）。
    # 但那仅涵盖文本 token 预测下一个文本 token（可能是音频 token 或结束）。
    # 更复杂的方法：追加表示音频开始的特殊标记，然后让模型生成其余部分。
    # 为简单起见，使用相同的输入作为标签（模型将学习在给定自身的情况下输出序列）。
    return {"input_ids": input_ids, "labels": input_ids}

train_data = dataset.map(preprocess_function, remove_columns=dataset.column_names)
```

{% hint style="info" %}
以上是简化说明。实际上，要正确微调 Orpheus，您需要 *将音频 token 作为训练标签的一部分*。Orpheus 的预训练很可能涉及将音频转换为离散 token（通过某种音频编码器）并训练模型在给定前文文本时预测这些 token。要在新语音数据上微调，您同样需要为每个片段获取音频 token（使用 Orpheus 的音频编码器）。Orpheus 的 GitHub 提供了一个数据处理脚本——它会将音频编码为一系列 `<custom_token_x>` token。
{% endhint %}

然而， **Unsloth 可能会将此抽象化处理**：如果模型是具有相关处理器的 FastModel，且该处理器知道如何处理音频，那么它可能会自动将数据集中的音频编码为 token。如果没有，您将不得不手动将每个音频片段编码为 token ID（使用 Orpheus 的码本）。这是超出本指南范围的高级步骤，但请记住仅使用文本 token 无法教会模型真实的音频模式——它需要匹配音频模式。

假设 Unsloth 提供了一种直接传入音频的方法（例如，通过设置 `processor` 并传入音频数组）。如果 Unsloth 还不支持自动音频分词，您可能需要使用 Orpheus 仓库的 `encode_audio` 函数来获取音频的 token 序列，然后将其用作标签。（数据集条目确实包含 `phonemes` 和一些声学特征，这表明存在一条处理流水线。）

**步骤 3：设置训练参数和 Trainer**

```python
from transformers import TrainingArguments,Trainer,DataCollatorForSeq2Seq
from unsloth import is_bfloat16_supported

trainer = Trainer(
    model = model,
    train_dataset = dataset,
    args = TrainingArguments(
        per_device_train_batch_size = 1,
        gradient_accumulation_steps = 4,
        warmup_steps = 5,
        # num_train_epochs = 1, # 将其设置为 1 则为完整的一次训练。
        max_steps = 60,
        learning_rate = 2e-4,
        fp16 = not is_bfloat16_supported(),
        bf16 = is_bfloat16_supported(),
        logging_steps = 1,
        optim = "adamw_8bit",
        weight_decay = 0.01,
        lr_scheduler_type = "linear",
        seed = 3407,
        output_dir = "outputs",
        report_to = "none", # 用于 WandB 等时可配置
    ),
)
```

我们使用 60 步以加速，但您可以设置 `num_train_epochs=1` 以进行完整训练，并将 `max_steps=None`关闭。若使用 per\_device\_train\_batch\_size >1 在多 GPU 设置下可能导致错误；为避免问题，请确保将 CUDA\_VISIBLE\_DEVICES 设置为单个 GPU（例如 CUDA\_VISIBLE\_DEVICES=0）。根据需要调整。

**步骤 4：开始微调**

这将启动训练循环。您应该每 50 步看到一次损失日志（由 `logging_steps`设置）。训练可能需要一些时间，取决于 GPU——例如在 Colab 的 T4 GPU 上，对 3 小时数据训练几个 epoch 可能需 1-2 小时。Unsloth 的优化会比标准的 HF 训练更快。

**步骤 5：保存微调后的模型**

训练完成后（或在您觉得足够时中途停止），保存模型。此操作仅保存 LoRA 适配器，而不是完整模型。要保存为 16bit 或 GGUF，请向下滚动！

```python
model.save_pretrained("lora_model")  # 本地保存
tokenizer.save_pretrained("lora_model")
# model.push_to_hub("your_name/lora_model", token = "...") # 在线保存
# tokenizer.push_to_hub("your_name/lora_model", token = "...") # 在线保存
```

这会保存模型权重（对于 LoRA，若基础模型未完全微调，可能只会保存适配器权重）。如果您在 CLI 中使用了 `--push_model` 或调用了 `trainer.push_to_hub()`，则可以将其直接上传到 Hugging Face Hub。

现在您应该在目录中拥有一个微调后的 TTS 模型。下一步是测试它，如果支持，您可以使用 llama.cpp 将其转换为 GGUF 文件。

### 微调语音模型 vs. 零样本语音克隆

有人说使用像 XTTS 这样的模型只需 30 秒音频即可克隆声音，无需训练。这从技术上来说是真的，但这并未触及要点。

零样本语音克隆（在 Orpheus 和 CSM 等模型中也可用）是一种近似。它能捕捉说话者的总体 **音色与音质** ，但不会再现完整的表达范围。您会失去诸如说话速度、措辞、语音习性以及韵律的微妙之处——这些构成了语音的 **个性与独特性**.

如果您只想要一个不同的声音并且对相同的表达模式无所谓，零样本通常足够。但语音仍会遵循 **模型的风格**，而不是说话者本身的风格。

对于任何更个性化或更具表现力的需求，您需要使用诸如 LoRA 之类的方法进行训练，以真正捕捉某人的说话方式。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://unsloth.ai/docs/zh/ji-chu/text-to-speech-tts-fine-tuning.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
