# Audio-Course

## Docs

- [语音识别的评价指标](https://huggingface.co/learn/audio-course/zh-CN/chapter5/evaluation.md)
- [单元简介](https://huggingface.co/learn/audio-course/zh-CN/chapter5/introduction.md)
- [Supplemental reading and resources](https://huggingface.co/learn/audio-course/zh-CN/chapter5/supplemental_reading.md)
- [语音识别的预训练模型](https://huggingface.co/learn/audio-course/zh-CN/chapter5/asr_models.md)
- [选择数据集](https://huggingface.co/learn/audio-course/zh-CN/chapter5/choosing_dataset.md)
- [用 Gradio 创建 demo](https://huggingface.co/learn/audio-course/zh-CN/chapter5/demo.md)
- [微调语音识别模型](https://huggingface.co/learn/audio-course/zh-CN/chapter5/fine-tuning.md)
- [实战练习](https://huggingface.co/learn/audio-course/zh-CN/chapter5/hands_on.md)
- [CTC结构](https://huggingface.co/learn/audio-course/zh-CN/chapter3/ctc.md)
- [测测你对课程内容的理解](https://huggingface.co/learn/audio-course/zh-CN/chapter3/quiz.md)
- [第3单元：音频Transformer架构](https://huggingface.co/learn/audio-course/zh-CN/chapter3/introduction.md)
- [补充阅读和资源](https://huggingface.co/learn/audio-course/zh-CN/chapter3/supplemental_reading.md)
- [音频分类结构](https://huggingface.co/learn/audio-course/zh-CN/chapter3/classification.md)
- [Seq2Seq结构](https://huggingface.co/learn/audio-course/zh-CN/chapter3/seq2seq.md)
- [欢迎来到Hugging Face 音频课程！](https://huggingface.co/learn/audio-course/zh-CN/chapter0/introduction.md)
- [加入社区！](https://huggingface.co/learn/audio-course/zh-CN/chapter0/community.md)
- [课程准备工作](https://huggingface.co/learn/audio-course/zh-CN/chapter0/get_ready.md)
- [祝贺！](https://huggingface.co/learn/audio-course/zh-CN/chapter8/introduction.md)
- [获取您的完成证书](https://huggingface.co/learn/audio-course/zh-CN/chapter8/certification.md)
- [Check your understanding of the course material](https://huggingface.co/learn/audio-course/zh-CN/chapter1/quiz.md)
- [第1单元：音频数据处理](https://huggingface.co/learn/audio-course/zh-CN/chapter1/introduction.md)
- [附加内容](https://huggingface.co/learn/audio-course/zh-CN/chapter1/supplemental_reading.md)
- [加载音频数据集](https://huggingface.co/learn/audio-course/zh-CN/chapter1/load_and_explore.md)
- [音频数据的流式加载](https://huggingface.co/learn/audio-course/zh-CN/chapter1/streaming.md)
- [音频数据集的预处理](https://huggingface.co/learn/audio-course/zh-CN/chapter1/preprocessing.md)
- [音频数据处理入门](https://huggingface.co/learn/audio-course/zh-CN/chapter1/audio_data.md)
- [第2单元：音频应用的入门介绍](https://huggingface.co/learn/audio-course/zh-CN/chapter2/introduction.md)
- [利用pipeline进行音频分类](https://huggingface.co/learn/audio-course/zh-CN/chapter2/audio_classification_pipeline.md)
- [利用pipeline进行自动语音识别](https://huggingface.co/learn/audio-course/zh-CN/chapter2/asr_pipeline.md)
- [实战练习](https://huggingface.co/learn/audio-course/zh-CN/chapter2/hands_on.md)
- [评估语音合成模型](https://huggingface.co/learn/audio-course/zh-CN/chapter6/evaluation.md)
- [第六单元：从文本到语音](https://huggingface.co/learn/audio-course/zh-CN/chapter6/introduction.md)
- [补充阅读](https://huggingface.co/learn/audio-course/zh-CN/chapter6/supplemental_reading.md)
- [语音合成数据集](https://huggingface.co/learn/audio-course/zh-CN/chapter6/tts_datasets.md)
- [微调 SpeechT5](https://huggingface.co/learn/audio-course/zh-CN/chapter6/fine-tuning.md)
- [实战练习](https://huggingface.co/learn/audio-course/zh-CN/chapter6/hands_on.md)
- [语音合成的预训练模型](https://huggingface.co/learn/audio-course/zh-CN/chapter6/pre-trained_models.md)

### 语音识别的评价指标
https://huggingface.co/learn/audio-course/zh-CN/chapter5/evaluation.md

# 语音识别的评价指标

如果您熟悉自然语言处理（NLP）中的[莱文斯坦距离（Levenshtein distance）](https://en.wikipedia.org/wiki/Levenshtein_distance)，
那么您会发现评估语音识别系统的指标颇为相似！如果您不熟悉，也不用担心，我们会从头到尾解释这些指标，确保您了解它们的不同之处以及它们的含义。

在评估语音识别系统时，我们将系统的预测与目标文本的转写进行比较，并标注出现的任何错误。我们将这些错误归类为以下三种类别：

1. 替换错误（Substitution, S）：在预测中转写了**错误的词**（例如，将 "sat" 转写为 "sit"）
2. 插入错误（Insertion, I）：在预测中**多加了一个词**
3. 删除错误（Deletion, D）：在预测中**漏掉了一个词**

这些错误类别在所有语音识别指标中都是相同的，不同的是我们计算这些错误的粒度：我们可以在 _词级别_ 或者 _字符级别_ 上进行计算。

我们将使用一个示例来解释每个指标的定义。这里，我们有一个 _基准_ 或 _参考_ 文本序列：

```python
reference = "the cat sat on the mat"
```

以及一个来自我们尝试评估的语音识别系统的预测序列：

```python
prediction = "the cat sit on the"
```

我们可以看到，预测相当接近，但有些词不太对。我们将用三种最流行的语音识别指标评估此预测，看看会算出什么样的结果。

## 词错误率

*词错误率（WER）* 是语音识别的“事实上的”指标。它在*词级别*上计算替换、插入和删除错误，即错误是逐词标注的。以我们的示例为例：

| 参考 | the | cat | sat     | on  | the | mat |
|----- |-----|-----|---------|-----|-----|-----|
| 预测 | the | cat | **sit** | on  | the |     |
| 标签 | ✅ | ✅   | S      | ✅ | ✅  | D   |

在这里，我们有：

* 1 个替换错误（"sit" 而不是 "sat"）
* 0 个插入错误
* 1 个删除错误（缺少 "mat"）

这样总共有 2 个错误。要得到错误率，我们将错误数除以参考文本中的总词数（N），在这个例子中为 6：

$$
\begin{aligned}
WER &= \frac{S + I + D}{N} \\
&= \frac{1 + 0 + 1}{6} \\
&= 0.333
\end{aligned}
$$

好的！所以我们得到的 WER 是 0.333，或 33.3%。注意，“sit”这个词只有一个字符是错误的，
但整个词都被标记为不正确。这是 WER 的一个特点：无论错误多么轻微，拼写错误都会受到重罚。

WER *越低越好*：较低的 WER 意味着我们的预测中错误较少，因此完美的语音识别系统的 WER 应为零（无错误）。

我们来看看如何使用 🤗 Evaluate 来计算 WER。我们需要两个库来计算我们的 WER 指标：🤗 Evaluate 用于 API 接口，JIWER 用于执行计算：

```
pip install --upgrade evaluate jiwer
```

太好了！我们现在可以加载 WER 指标，并为我们的示例计算出结果：

```python
from evaluate import load

wer_metric = load("wer")

wer = wer_metric.compute(references=[reference], predictions=[prediction])

print(wer)
```

**打印输出：**

```
0.3333333333333333
```

0.33，或 33.3%，与预期一致！我们现在知道了这个 WER 计算的幕后情况。

现在，这里有一个坑……您认为 WER 的上限是多少？您可能会认为是 1 或 100% 对吧？并不是！由于 WER 是错误与单词数（N）的比率，因此 WER 没有上限！
让我们以一个预测了 10 个词而目标只有 2 个词的示例为例。如果我们的所有预测都是错误的（10 个错误），我们将得到 WER 为 10 / 2 = 5，或 500%！
这是在训练 ASR 系统并看到超过 100% 的 WER 时需要牢记的一点。如果您看到这种情况，可能意味着什么地方出了问题……😅

## 词准确率

我们可以将 WER 反过来，得到一个*越高越好*的指标。与其衡量词错误率，不如衡量我们系统的*词准确率（WAcc）*：

$$
\begin{equation}
WAcc = 1 - WER \nonumber
\end{equation}
$$

WAcc 也是在词级别上测量的，它只是将 WER 重新表述为准确率指标，而不是错误率指标。
WAcc 在语音文献中很少被引用——我们倾向于以词错误为中心思考系统的预测，因此更喜欢与这些错误类型标注更相关的错误率指标。

## 字符错误率

我们将整个词 “sit” 标记为错误似乎有点不公平，事实上只有一个字母是不正确的。这是因为我们是在词的级别上评估我们的系统，只能逐词标注错误。
*字符错误率（CER）* 则在*字符级别*上评估系统。这意味着我们将单词分解为它们的各个字符，并在字符级别上标注错误：

| 参考 | t   | h   | e   |     | c   | a   | t   |     | s   | a     | t   |     | o   | n   |     | t   | h   | e   |     | m   | a   | t   |
|------|-----|-----|-----|-----|-----|-----|-----|-----|-----|-------|-----|-----|-----|-----|-----|-----|-----|-----|-----|-----|-----|-----|
| 预测 | t   | h   | e   |     | c   | a   | t   |     | s   | **i** | t   |     | o   | n   |     | t   | h   | e   |     |     |     |     |
| 标签 | ✅   | ✅   | ✅   |     | ✅   | ✅   | ✅   |     | ✅   | S     | ✅   |     | ✅   | ✅   |     | ✅   | ✅   | ✅   |     | D   | D   | D   |

我们现在可以看到，对于词 “sit”，“s” 和 “t” 被标记为正确。只有 “i” 被标记为替换错误（S）。因此，我们奖励了我们的系统部分正确的预测 🤝

在我们的例子中，我们有 1 个字符替换，0 个插入和 3 个删除。总共有 14 个字符。所以，我们的 CER 是：

$$
\begin{aligned}
CER &= \frac{S + I + D}{N} \\
&= \frac{1 + 0 + 3}{14} \\
&= 0.286
\end{aligned}
$$

好的！我们得到了一个 CER 为 0.286，或 28.6%。注意这比我们的 WER 低——现在我们对拼写错误的惩罚要少得多了。

## 我应该使用哪个指标？

一般来说，WER 比 CER 更多地用于评估语音系统。这是因为 WER 要求系统对预测的上下文有更深入的理解。在我们的例子中，“sit” 使用了错误的时态。
一个理解句子中动词和时态关系的系统会预测出正确的动词时态 “sat”。我们希望训练我们的语音系统达到这种理解水平。
所以，尽管 WER 比 CER 更不宽容，但它也更有助于我们开发出我们想要的智能系统。因此，我们通常使用 WER，并鼓励您也这样做！
然而，在某些情况下，使用 WER 是不可能的。某些语言，如汉语和日语，没有“词”的概念，因此 WER 没有意义。在这里，我们转而使用 CER。

在我们的例子中，我们只在计算 WER 时使用了一句话。但真正在评估一个系统时，我们通常会使用包含几千句话的整个测试集。
在评估多个句子时，我们会将所有句子的 S、I、D 和 N 求和，然后根据上面定义的公式计算 WER。这样可以更好地估计未见数据的 WER。

## 规范化

如果我们在带有标点和大小写的数据上训练 ASR 模型，它将学会在其转写中预测大小写和标点。当我们想将我们的模型用于实际的语音识别应用，
如记录会议或口述时，这很有用，因为预测的转写将完全格式化，带有大小写和标点，这种风格被称为*正字法*（orthographic）。

然而，我们可以选择*规范化*（normalise）数据集以消除任何大小写和标点。规范化数据集使语音识别任务变得更容易：模型不再需要区分大写和小写字符，
也不需要单独从音频数据中预测标点（例如，分号发出什么声音？）。因此，词错误率自然更低（结果更好）。Whisper 论文演示了规范化转写对 WER 结果的巨大影响（参见 [Whisper 论文](https://cdn.openai.com/papers/whisper.pdf) 的第 4.4 节）。
虽然我们获得了更低的 WER，但模型并不一定更适合生产。缺乏大小写和标点使模型预测的文本难以阅读。以 [前一节](asr_models) 中的示例为例，
我们在 LibriSpeech 数据集的同一音频样本上运行了 Wav2Vec2 和 Whisper。Wav2Vec2 模型既不预测标点也不预测大小写，而 Whisper 则预测了这两者。
对比两种转写，我们看到 Whisper 的转写更易于阅读：

```
Wav2Vec2:  HE TELLS US THAT AT THIS FESTIVE SEASON OF THE YEAR WITH CHRISTMAUS AND ROSE BEEF LOOMING BEFORE US SIMALYIS DRAWN FROM EATING AND ITS RESULTS OCCUR MOST READILY TO THE MIND
Whisper:   He tells us that at this festive season of the year, with Christmas and roast beef looming before us, similarly is drawn from eating and its results occur most readily to the mind.
```

Whisper 的转写是符合正字法的，因此可以直接使用——它写成了我们期望的会议记录或口述记录的格式，带有标点和大小写。
相反，如果我们想将 Wav2Vec2 的预测用于下游应用，我们需要使用额外的后处理来恢复标点和大小写。

在规范化和不规范化之间有一个折中方案：我们可以在正字法转写上训练我们的系统，然后在计算 WER 之前对预测和目标进行规范化。
这样，我们既能够训练系统预测带有完整格式的文本，又通过规范化转写提高了 WER。

Whisper 模型发布时带有一个能有效处理大小写、标点和数字格式化等规范化任务的规范器（normaliser）。让我们将规范器应用于 Whisper 转写，以演示如何对它们进行规范化：

```python
from transformers.models.whisper.english_normalizer import BasicTextNormalizer

normalizer = BasicTextNormalizer()

prediction = " He tells us that at this festive season of the year, with Christmas and roast beef looming before us, similarly is drawn from eating and its results occur most readily to the mind."
normalized_prediction = normalizer(prediction)

normalized_prediction
```

**输出：**

```
' he tells us that at this festive season of the year with christmas and roast beef looming before us similarly is drawn from eating and its results occur most readily to the mind '
```

太好了！我们可以看到文本全部小写，并且没有标点。现在让我们定义参考转写，然后计算参考和预测之间的规范化 WER：

```python
reference = "HE TELLS US THAT AT THIS FESTIVE SEASON OF THE YEAR WITH CHRISTMAS AND ROAST BEEF LOOMING BEFORE US SIMILES DRAWN FROM EATING AND ITS RESULTS OCCUR MOST READILY TO THE MIND"
normalized_referece = normalizer(reference)

wer = wer_metric.compute(
    references=[normalized_referece], predictions=[normalized_prediction]
)
wer
```

**输出：**

```
0.0625
```

6.25%——这大概就是我们能预期 Whisper 基础模型在 LibriSpeech 验证集上达到的效果。正如我们在这里看到的，我们预测了一个符合正字法的转写，但通过在计算 WER 之前规范化参考和预测提升了 WER。

您如何规范化转写最终取决于您的需求。我们建议在正字法文本上进行训练，并在规范化文本上进行评估，以获得两全其美的效果。

## 汇总

好的！到目前为止，我们在本单元已经介绍了三个主题：预训练模型、数据集选择和评估。让我们来做点有趣的，将它们结合在一起做一个端到端的样例 🚀 
我们将评估预训练的 Whisper 模型在 Common Voice 13 Dhivehi 测试集上的表现，为下一节关于微调的内容做铺垫。我们会将算出的 WER 作为微调的 _基准_，并尝试超越这个目标 🥊

首先，我们将使用 `pipeline()` 类加载预训练的 Whisper 模型。我们现在对这个过程应该非常熟悉了！我们唯一的新步骤是在 GPU 上使用半精度（float16）加载模型——这样会加速推理且几乎不影响 WER。

```python
from transformers import pipeline
import torch

if torch.cuda.is_available():
    device = "cuda:0"
    torch_dtype = torch.float16
else:
    device = "cpu"
    torch_dtype = torch.float32

pipe = pipeline(
    "automatic-speech-recognition",
    model="openai/whisper-small",
    torch_dtype=torch_dtype,
    device=device,
)
```

接下来，我们将加载 Common Voice 13 的迪维希语测试子集。您可能还记得，从上一节中我们了解到 Common Voice 13 是*受限的*，
这意味着我们必须同意数据集的使用条款才能访问数据集。我们现在可以将我们的 Hugging Face 账户链接到我们的笔记本，以便我们可以从当前使用的机器访问数据集。

将笔记本链接到 Hub 非常简单——只需要依照提示输入您的 Hub 身份验证令牌即可。在 [这里](https://huggingface.co/settings/tokens) 找到您的 Hub 身份验证令牌：

```python
from huggingface_hub import notebook_login

notebook_login()
```

太好了！我们将笔记本链接到我们的 Hugging Face 账户之后，就可以下载 Common Voice 数据集了。
下载和预处理需要几分钟，以下代码会从 Hugging Face Hub 获取数据并在您的笔记本上自动准备：

```python
from datasets import load_dataset

common_voice_test = load_dataset(
    "mozilla-foundation/common_voice_13_0", "dv", split="test"
)
```

<Tip>
    如果在加载数据集时遇到身份验证问题，请确保您已经通过以下链接在 Hugging Face Hub 上接受了数据集的使用条款：https://huggingface.co/datasets/mozilla-foundation/common_voice_13_0
</Tip>

用与推理单个样本相同的方式评估整个数据集——我们所要做的就是**循环**处理输入的所有音频，而不是仅推理单个样本。为此，我们首先将我们的数据集转换为 `KeyDataset`。
我们要做的只是挑选出我们要传递给模型的特定的数据列（在我们的案例中，那是 `"audio"` 列），忽略其余的（如目标转写，在推理时用不上）。
然后我们遍历这个转换后的数据集，将模型输出的预测结果保存到列表中。以下代码单元将在 GPU 上以半精度运行时大约需要五分钟，内存峰值为 12GB：

```python
from tqdm import tqdm
from transformers.pipelines.pt_utils import KeyDataset

all_predictions = []

# 运行流式推理
for prediction in tqdm(
    pipe(
        KeyDataset(common_voice_test, "audio"),
        max_new_tokens=128,
        generate_kwargs={"task": "transcribe"},
        batch_size=32,
    ),
    total=len(common_voice_test),
):
    all_predictions.append(prediction["text"])
```

<Tip>
    如果在运行上述单元时遇到 CUDA 内存不足（out-of-memory, OOM）问题，请将 `batch_size` 逐渐减小 2 的倍数，直到找到适合您设备的批次大小。
</Tip>

最后，我们可以计算 WER。让我们首先计算正字法 WER，即没有任何后处理时的 WER：

```python
from evaluate import load

wer_metric = load("wer")

wer_ortho = 100 * wer_metric.compute(
    references=common_voice_test["sentence"], predictions=all_predictions
)
wer_ortho
```

**输出：**

```
167.29577268612022
```

好吧……167% 意味着我们的模型输出的是垃圾 😜 别担心，我们的目标是通过在迪维希语训练集上微调模型来改善这一点！

接下来，我们将评估规范化 WER，即规范化后处理过再得到的 WER。我们必须过滤掉规范化后为空的样本，否则我们参考中的总词数（N）将为零，这将导致我们的计算中出现除以零的错误：

```python
from transformers.models.whisper.english_normalizer import BasicTextNormalizer

normalizer = BasicTextNormalizer()

# 计算规范化 WER
all_predictions_norm = [normalizer(pred) for pred in all_predictions]
all_references_norm = [normalizer(label) for label in common_voice_test["sentence"]]

# 过滤掉参考文本被规范化后为零值的样本
all_predictions_norm = [
    all_predictions_norm[i]
    for i in range(len(all_predictions_norm))
    if len(all_references_norm[i]) > 0
]
all_references_norm = [
    all_references_norm[i]
    for i in range(len(all_references_norm))
    if len(all_references_norm[i]) > 0
]

wer = 100 * wer_metric.compute(
    references=all_references_norm, predictions=all_predictions_norm
)

wer
```

**输出：**

```
125.69809089960707
```

我们再次看到通过规范化我们的参考和预测，我们显著降低了 WER：基线模型在正字法的测试中的 WER 为 168%，而规范化 WER 为 126%。

好了！这些是我们在通过微调提高 Whisper 模型语音识别迪维希语的能力时需要超越的目标。继续阅读，开始动手尝试微调示例吧 🚀


<EditOnGithub source="https://github.com/huggingface/audio-transformers-course/blob/main/chapters/zh-CN/chapter5/evaluation.mdx" />

### 单元简介
https://huggingface.co/learn/audio-course/zh-CN/chapter5/introduction.md

# 单元简介

在本节中，我们将探讨如何使用 Transformers 将语音转换为文本，这一任务被称为 _语音识别_。

<div class="flex justify-center">
    <img src="https://huggingface.co/datasets/huggingface-course/audio-course-images/resolve/main/asr_diagram.png" alt="语音到文本的示意图">
</div>

语音识别，也称为自动语音识别（ASR）或语音转文本（STT），是最受欢迎和令人兴奋的语音处理任务之一。
它广泛应用于包括口述、语音助手、视频字幕和会议记录在内的多种应用中。

您可能在不知不觉中多次使用过语音识别系统，比如说您智能手机中的数字助手（Siri、Google Assistant、Alexa）！
当您使用这些助手时，它们首先要做的就是将您的语音转写为书面文本，准备用于各种下游任务（比如为您查询天气预报 🌤️）。

试试下面的语音识别 demo。您可以使用麦克风录制自己的声音，或拖放音频样本文件进行转写：

<iframe src="https://course-demos-whisper-small.hf.space" frameborder="0" width="850" height="450"> </iframe>

语音识别是一项具有挑战性的任务，它需要对音频和文本都有所了解。输入的音频可能有很多背景噪音，并且可能由具有各种口音的说话人发出，这使得从中识别出语音变得困难。
书面文本可能包含无声音的字符，如标点符号，这些信息仅从音频中推断很困难。这些都是我们在构建有效的语音识别系统时必须克服的障碍！

现在我们已经定义了我们的任务，我们可以开始更详细地研究语音识别。通过本单元的学习，您将对各种可用的预训练语音识别模型有一个良好的基本理解，并了解如何通过 🤗 Transformers 库使用它们。
您还将了解对一个领域或某种特定语言微调 ASR 模型的过程，使您能够为遇到的任何任务构建一个高效的系统。您将能够向您的亲朋好友现场演示您的模型，一个能够将任何语音转换为文本的模型！

具体而言，我们将介绍：

* [语音识别的预训练模型](asr_models)
* [选择数据集](choosing_dataset)
* [语音识别的评价指标](evaluation)
* [微调语音识别模型](fine-tuning)
* [创建 demo](demo)
* [实战练习](hands_on)


<EditOnGithub source="https://github.com/huggingface/audio-transformers-course/blob/main/chapters/zh-CN/chapter5/introduction.mdx" />

### Supplemental reading and resources
https://huggingface.co/learn/audio-course/zh-CN/chapter5/supplemental_reading.md

# Supplemental reading and resources

本单元介绍了语音识别的实践，语音识别是音频领域最受欢迎的任务之一。
想了解更多吗？在这里，您可以找到更多资源，加深您对这个主题的理解，提升学习体验。

* [Whisper Talk](https://www.youtube.com/live/fZMiD8sDzzg?feature=share) by Jong Wook Kim: a presentation on the Whisper model, explaining the motivation, architecture, training and results, delivered by Whisper author Jong Wook Kim
* [End-to-End Speech Benchmark (ESB)](https://arxiv.org/abs/2210.13352): a paper that comprehensively argues for using the orthographic WER as opposed to the normalised WER for evaluating ASR systems and presents an accompanying benchmark
* [Fine-Tuning Whisper for Multilingual ASR](https://huggingface.co/blog/fine-tune-whisper): an in-depth blog post that explains how the Whisper model works in more detail, and the pre- and post-processing steps involved with the feature extractor and tokenizer
* [Fine-tuning MMS Adapter Models for Multi-Lingual ASR](https://huggingface.co/blog/mms_adapters): an end-to-end guide for fine-tuning Meta AI's new [MMS](https://ai.facebook.com/blog/multilingual-model-speech-recognition/) speech recognition models, freezing the base model weights and only fine-tuning a small number of *adapter* layers
* [Boosting Wav2Vec2 with n-grams in 🤗 Transformers](https://huggingface.co/blog/wav2vec2-with-ngram): a blog post for combining CTC models with external language models (LMs) to combat spelling and punctuation errors


<EditOnGithub source="https://github.com/huggingface/audio-transformers-course/blob/main/chapters/zh-CN/chapter5/supplemental_reading.mdx" />

### 语音识别的预训练模型
https://huggingface.co/learn/audio-course/zh-CN/chapter5/asr_models.md

# 语音识别的预训练模型

在本节中，我们将介绍如何使用 `pipeline()` 来使用预训练的语音识别模型。
在[第 2 单元](../chapter2/asr_pipeline)中，我们介绍了 `pipeline()` 这种执行语音识别任务的简便方法，
所有的预处理和后处理都在幕后进行，而且它还可以灵活地快速实验 Hugging Face Hub 上的任何预训练检查点。
在本单元中，我们将更深入地探索语音识别模型的不同属性，以及利用它们来处理不同的任务的方法。

如第 3 单元详细介绍的那样，语音识别模型大致分为两类：

1. 连接时序分类（Connectionist Temporal Classification, CTC）：仅编码器（encoder-only）的模型，顶部带有线性分类（CTC）头
2. 序列到序列（Sequence-to-sequence, Seq2Seq）：编码器-解码器（encoder-decoder）模型，编码器和解码器之间带有交叉注意力机制

在 2022 年之前，CTC 是这两种架构中更受欢迎的一种，以 encoder-only 模型为主，例如 Wav2Vec2、HuBERT 和 XLSR 在语音的预训练/微调范式中取得了突破。
大公司如 Meta 和 Microsoft 在大量无标签音频数据上对编码器进行了多天甚至数周的预训练。
用户采用一个预训练的检查点，并在少至 **10 分钟** 的有标注的语音数据上进行微调，就可以在下游语音识别任务中取得强大的性能。

然而，CTC 模型也有其缺点。在编码器上附加一个简单的线性层可以得到一个小巧、快速的完整模型，但可能容易出现语音拼写错误。我们将用 Wav2Vec2 模型演示这一点。

## 探索 CTC 模型

让我们加载 [LibriSpeech ASR](hf-internal-testing/librispeech_asr_dummy) 数据集的一小部分，以展示 Wav2Vec2 的语音转写能力：

```python
from datasets import load_dataset

dataset = load_dataset(
    "hf-internal-testing/librispeech_asr_dummy", "clean", split="validation"
)
dataset
```

**输出：**

```
Dataset({
    features: ['file', 'audio', 'text', 'speaker_id', 'chapter_id', 'id'],
    num_rows: 73
})
```

我们可以选择 73 个音频样本中的一个，检查音频样本及其转写：

```python
from IPython.display import Audio

sample = dataset[2]

print(sample["text"])
Audio(sample["audio"]["array"], rate=sample["audio"]["sampling_rate"])
```

**输出：**

```
HE TELLS US THAT AT THIS FESTIVE SEASON OF THE YEAR WITH CHRISTMAS AND ROAST BEEF LOOMING BEFORE US SIMILES DRAWN FROM EATING AND ITS RESULTS OCCUR MOST READILY TO THE MIND
```

好的！圣诞节和烤牛肉，听起来很棒！🎄 选择了一个数据样本后，我们现在将一个微调过的检查点加载到 `pipeline()` 中。
为此，我们将使用官方在 100 小时 LibriSpeech 数据上微调的 [Wav2Vec2 base](facebook/wav2vec2-base-100h) 检查点：

```python
from transformers import pipeline

pipe = pipeline("automatic-speech-recognition", model="facebook/wav2vec2-base-100h")
```

接下来，我们将从数据集中取一个示例，并将其数据传递给 pipeline。由于 `pipeline` 会 *消耗* 我们传入的字典（意味着它无法被多次读取），
我们将传递数据的副本。这样，我们可以安全地在以下示例中重复使用同一个音频样本：

```python
pipe(sample["audio"].copy())
```

**输出：**

```
{"text": "HE TELLS US THAT AT THIS FESTIVE SEASON OF THE YEAR WITH CHRISTMAUS AND ROSE BEEF LOOMING BEFORE US SIMALYIS DRAWN FROM EATING AND ITS RESULTS OCCUR MOST READILY TO THE MIND"}
```

我们可以看到 Wav2Vec2 模型在转写这个样本方面做得相当好——乍一看似乎是正确的。我们将目标和预测放在一起，突出两者的区别：


```
Target:      HE TELLS US THAT AT THIS FESTIVE SEASON OF THE YEAR WITH CHRISTMAS AND ROAST BEEF LOOMING BEFORE US SIMILES DRAWN FROM EATING AND ITS RESULTS OCCUR MOST READILY TO THE MIND
Prediction:  HE TELLS US THAT AT THIS FESTIVE SEASON OF THE YEAR WITH **CHRISTMAUS** AND **ROSE** BEEF LOOMING BEFORE US **SIMALYIS** DRAWN FROM EATING AND ITS RESULTS OCCUR MOST READILY TO THE MIND
```

将目标文本与预测出的转写进行比较，我们可以看到所有单词 _听起来_ 都是正确的，但有些拼写不准确。例如：

* _CHRISTMAUS_ vs. _CHRISTMAS_
* _ROSE_ vs. _ROAST_
* _SIMALYIS_ vs. _SIMILES_

这突显出了 CTC 模型的缺点。CTC 模型本质上是一个“仅声学”的模型：它由一个编码器组成，该编码器通过输入的音频计算出隐藏状态，并且由一个线性层将隐藏状态映射到字符：

<!--- Need U3 to be merged before this figure is available:

<div class="flex justify-center">
     <img src="https://huggingface.co/datasets/huggingface-course/audio-course-images/resolve/main/wav2vec2-ctc.png" alt="Transformer encoder with a CTC head on top">
</div>

--->

这意味着该系统几乎完全基于它的声学输入（音频中的语音部分）进行预测，因此倾向于以表音的方式进行转写（例如 _CHRISTMAUS_）。
它对上下文的语言建模重视程度较低，因此容易出现语音拼写错误。一个更智能的模型应该发现 _CHRISTMAUS_ 不是英语中的有效单词，并在进行预测时将其纠正为 _CHRISTMAS_。
我们的预测还缺少两个重要特征——大小写和标点符号。这限制了该模型的转写的现实应用。

## 过渡到 Seq2Seq

Seq2Seq 模型来了！正如第 3 单元所概述的，Seq2Seq 模型由编码器和解码器通过交叉注意力机制相连构成。
编码器的作用与以前相同，负责计算音频输入的隐藏状态表示，而解码器的作用则是 **语言模型**。
解码器处理编码器计算出的整个隐藏状态表示序列，并生成相应的文本转写。它会通过语言建模，在推理时通过输入音频的全局上下文实时纠正拼写错误，避免语音预测的问题。

Seq2Seq 模型有两个缺点：

1. 解码较慢，因为解码过程是逐步进行的，而不是一次性完成的
2. 需要更多的训练数据才能收敛

实际上，大量的训练数据一直是语音领域 Seq2Seq 架构进步的瓶颈。带标注的语音数据很难找到，目前最大的带标注的数据集仅有 10,000 小时。
这一切都在 2022 年 **Whisper** 发布时发生了改变。Whisper 是由 OpenAI 的 Alec Radford 等作者在 [2022 年 9 月](https://openai.com/blog/whisper/) 发布的用于语音识别的预训练模型。
与之前完全在 **无标注** 音频数据上预训练的 CTC 前辈不同，Whisper 是在大量 **有标注** 的音频-转写数据上预训练的，整整有 680,000 小时。

这是比用于训练 Wav2Vec 2.0 的无标签音频数据（60,000 小时）多一个数量级的数据。更重要的是，这些预训练数据中有 117,000 小时是多语言（或“非英语”）数据。
这导致了预训练出的检查点可以应用于 96 种以上语言，其中包括许多 _低资源_ 的语言，即这些语言缺乏适合训练的大型数据集。

当扩展到 680,000 小时有标注的预训练数据时，Whisper 模型显示出强大的泛化能力，适用于许多数据集和领域。
预训练检查点取得了能与最先进的语音识别系统相媲美的结果，在 LibriSpeech 的 test-clean 子集上实现了接近 3% 的词错误率（WER），
并在 TED-LIUM 上创造了新的最佳记录，WER 为 4.7%（参考 [Whisper 论文](https://cdn.openai.com/papers/whisper.pdf) 中的表 8）。

最重要的是 Whisper 能处理长音频样本、对输入噪声有鲁棒性，且能预测带大小写和标点符号的转写。这使其成为实际应用中可选的语音识别系统之一。

本节的剩余部分将向您展示如何使用 🤗 Transformers 中的预训练 Whisper 模型进行语音识别。
在许多情况下，预训练的 Whisper 检查点表现出色，我们建议在解决任何语音识别问题时第一步先考虑用它。
通过微调，预训练检查点可以针对特定数据集和语言进行调整，以进一步优化效果，我们将在接下来的 [微调](fine-tuning) 小节中演示如何做到这一点。

Whisper 检查点有五种配置，每种配置的模型大小不同。最小的四个分别在仅英语和多语言数据集上训练，而最大的检查点仅有多语言。
一共九个预训练检查点都可以在 [Hugging Face Hub](https://huggingface.co/models?search=openai/whisper) 上找到。
以下表格总结了这些检查点，并附有 Hub 上模型的链接。“VRAM”表示以最小批次大小（batch size）为 1 时运行模型所需的 GPU 内存。
“Rel Speed”是与最大型号相比的相对速度。根据这些信息，您可以选择最适合您硬件的检查点。

| 大小   | 参数量 | VRAM / GB |  相对速度  | 仅英语                                              | 多语言                                            |
|--------|--------|----------|------------|----------------------------------------------------|---------------------------------------------------|
| tiny   | 39 M   | 1.4       | 32        | [✓](https://huggingface.co/openai/whisper-tiny.en)   | [✓](https://huggingface.co/openai/whisper-tiny)   |
| base   | 74 M   | 1.5       | 16        | [✓](https://huggingface.co/openai/whisper-base.en)   | [✓](https://huggingface.co/openai/whisper-base)   |
| small  | 244 M  | 2.3       | 6         | [✓](https://huggingface.co/openai/whisper-small.en)  | [✓](https://huggingface.co/openai/whisper-small)  |
| mdeium | 769 M  | 4.2       | 2         | [✓](https://huggingface.co/openai/whisper-medium.en) | [✓](https://huggingface.co/openai/whisper-medium) |
| large  | 1550 M | 7.5       | 1         | x                                                    | [✓](https://huggingface.co/openai/whisper-large-v2)|

让我们加载 [Whisper Base](https://huggingface.co/openai/whisper-base) 检查点，它的大小与我们之前使用的 Wav2Vec2 检查点相当。
考虑到我们接下来会做多语言语音识别，我们将加载基础检查点的多语言变体。我们还将模型加载到 GPU（如果有）或 CPU 上，如有必要的话 `pipeline()` 随后会负责将所有输入/输出从 CPU 移动到 GPU：

```python
import torch
from transformers import pipeline

device = "cuda:0" if torch.cuda.is_available() else "cpu"
pipe = pipeline(
    "automatic-speech-recognition", model="openai/whisper-base", device=device
)
```

好！现在让我们像之前一样转写音频。我们唯一改变的是传递一个额外的参数 `max_new_tokens`，它告诉模型在进行预测时生成的最大词元数：

```python
pipe(sample["audio"], max_new_tokens=256)
```

**输出：**

```
{'text': ' He tells us that at this festive season of the year, with Christmas and roast beef looming before us, similarly is drawn from eating and its results occur most readily to the mind.'}
```

轻松搞定！您首先注意到的是大小写和标点符号，这显然比 Wav2Vec2 不区分大小写也不含标点的转写更易读。让我们对比一下转写与目标输出：

```
Target:     HE TELLS US THAT AT THIS FESTIVE SEASON OF THE YEAR WITH CHRISTMAS AND ROAST BEEF LOOMING BEFORE US SIMILES DRAWN FROM EATING AND ITS RESULTS OCCUR MOST READILY TO THE MIND
Prediction: He tells us that at this festive season of the year, with **Christmas** and **roast** beef looming before us, **similarly** is drawn from eating and its results occur most readily to the mind.
```

Whisper 很好地纠正了我们在 Wav2Vec2 那里看到的语音错误—— _Christmas_ 和 _roast_ 都拼写正确。我们看到模型在转写 _SIMILES_ 时出了错，
误写为 _similarly_，但这次预测是英语中的有效单词。使用更大的 Whisper 检查点可以帮助进一步减少转写错误，但代价是需要更多的计算和更长的转写时间。

该模型应当有能力处理 96 种语言，所以现在让我们暂时离开英语语音识别，走向全球 🌎！
[Multilingual LibriSpeech](https://huggingface.co/datasets/facebook/multilingual_librispeech) (MLS) 数据集是 LibriSpeech 数据集的多语言版本，
包含六种语言的带标注音频数据。我们将从 MLS 数据集的西班牙语子集中加载一个样本，使用 _流式_（streaming）模式，这样我们就不必下载整个数据集：

```python
dataset = load_dataset(
    "facebook/multilingual_librispeech", "spanish", split="validation", streaming=True
)
sample = next(iter(dataset))
```

我们再次检查文本转写并试听音频片段：

```python
print(sample["text"])
Audio(sample["audio"]["array"], rate=sample["audio"]["sampling_rate"])
```

**输出：**

```
entonces te delelitarás en jehová y yo te haré subir sobre las alturas de la tierra y te daré á comer la heredad de jacob tu padre porque la boca de jehová lo ha hablado
```

这是我们希望通过 Whisper 转写出的目标文本，虽然我们现在知道我们预测的结果可能会比这更好，因为我们的模型还会预测标点和大小写，而参考中没有这些。
让我们将音频样本输入 pipeline 以获得文本预测。需要注意的是，pipeline 会 _消耗_ 我们输入的音频输入字典，即字典不能被重复读取。
为了解决这个问题，我们将传递一个音频样本的 _副本_，这样我们就可以在后续代码示例中重复使用同一个音频样本：

```python
pipe(sample["audio"].copy(), max_new_tokens=256, generate_kwargs={"task": "transcribe"})
```

**输出：**

```
{'text': ' Entonces te deleitarás en Jehová y yo te haré subir sobre las alturas de la tierra y te daré a comer la heredad de Jacob tu padre porque la boca de Jehová lo ha hablado.'}
```

太好了——这看起来与我们的参考文本非常相似（可以说更好，因为它有标点和大小写！）。您会注意到，我们以 _生成关键字参数_（generate kwarg）的形式传递了 `"task"`。
将 `"task"` 设置为 `"transcribe"` 会迫使 Whisper 执行 _语音识别_ 任务，即转写的语言就是音频中使用的语音。
Whisper 还能够执行与之密切相关的 _语音翻译_ 任务，西班牙语音频可以被翻译成英语文本。要实现这一点，我们将 `"task"` 设置为 `"translate"`：

```python
pipe(sample["audio"], max_new_tokens=256, generate_kwargs={"task": "translate"})
```

**输出：**

```
{'text': ' So you will choose in Jehovah and I will raise you on the heights of the earth and I will give you the honor of Jacob to your father because the voice of Jehovah has spoken to you.'}
```

现在我们知道可以在语音识别和语音翻译之间切换，我们可以根据需要选择任务。
要么我们从 X 语言的音频识别到同一语言 X 的文本（例如，西班牙语音频到西班牙语文本），要么我们从任何语言 X 的音频翻译到英文文本（例如，西班牙语音频到英文文本）。

要了解更多关于如何使用 `"task"` 参数来控制生成文本的属性，请参阅 Whisper base 的 [模型卡片](https://huggingface.co/openai/whisper-base#usage)。

## 长篇转写和时间戳

到目前为止，我们一直专注于转写不到 30 秒的短音频样本。但我们也提到过 Whisper 的一个优势是可以处理长音频样本，我们将在这里处理这个任务！

让我们通过拼接 MLS 数据集中连续几条样本来创建一个长音频文件。因为 MLS 数据集是通过将长有声读物录音分割成较短片段来整理的，
所以拼接样本是重构较长有声读物段落的一种方法，拼接出的音频在各个样本间应该是连贯的。

我们将目标音频长度设为 5 分钟，并在达到这个值时停止拼接样本：

```python
import numpy as np

target_length_in_m = 5

# 将分钟数转为秒数（* 60），再转为采用点数（* sampling rate）
sampling_rate = pipe.feature_extractor.sampling_rate
target_length_in_samples = target_length_in_m * 60 * sampling_rate

# 遍历数据集，拼接样本直到长度达到目标
long_audio = []
for sample in dataset:
    long_audio.extend(sample["audio"]["array"])
    if len(long_audio) > target_length_in_samples:
        break

long_audio = np.asarray(long_audio)

# 结果如何？
seconds = len(long_audio) / 16000
minutes, seconds = divmod(seconds, 60)
print(f"Length of audio sample is {minutes} minutes {seconds:.2f} seconds")
```

**输出：**

```
Length of audio sample is 5.0 minutes 17.22 seconds
```

好的！有 5 分钟 17 秒的音频要转写。直接将这个长音频样本输入给模型有两个问题：

1. Whisper 被设计用于处理 30 秒的样本：少于 30 秒的任何内容都会用静音填充至 30 秒，超过 30 秒的任何内容都会通过切掉多余音频缩短至 30 秒，所以如果我们直接输入我们的音频，我们只会得到前 30 秒的转写
2. Transformer 网络中的内存随序列长度平方增长：输入长度加倍会使内存需求增加四倍，所以传递超长音频文件会导致内存不足（out-of-memory, OOM）错误

在 🤗 Transformers 中进行长篇转写的方式是将输入音频 _分块_（chunking）为更小、更易管理的段落，每个段落与前一个段落有少量重叠。
这样，我们可以在段落边界处准确地将段落拼接在一起，因为我们可以找到段落之间的重叠并相应地合并转写：

<div class="flex justify-center">
     <img src="https://huggingface.co/blog/assets/49_asr_chunking/Striding.png" alt="🤗 Transformers 分块算法。来源：https://huggingface.co/blog/asr-chunking。">
</div>

给样本分块的优势在于，转写后续的块 \\( i + 1 \\) 不需要使用之前的块 \\( i \\) 的结果。拼接是在我们已经转写了所有块之后在块边界处进行的，所以转写的顺序并不重要。
该算法完全是 **无状态的**，所以我们甚至可以同时转写块 \\( i + 1 \\) 和块 \\( i \\)！这允许我们 _批量_ 处理块，并通过并行运行模型，与顺序转写相比大大提升计算速度。
要了解更多关于 🤗 Transformers 中分块的知识，请参阅这篇 [博客](https://huggingface.co/blog/asr-chunking)。

要使用长篇转写，我们在调用 pipeline 时需要添加一个额外参数。这个参数，`chunk_length_s`，控制分块段落的长度（以秒为单位）。对于 Whisper，30 秒的块是最佳的，因为这符合 Whisper 期望的输入长度。

要使用批处理，我们需要将 `batch_size` 参数传递给 pipeline。将所有这些放在一起，我们可以像下面这样转写长音频样本，并进行分块和批处理：

```python
pipe(
    long_audio,
    max_new_tokens=256,
    generate_kwargs={"task": "transcribe"},
    chunk_length_s=30,
    batch_size=8,
)
```

**输出：**

```
{'text': ' Entonces te deleitarás en Jehová, y yo te haré subir sobre las alturas de la tierra, y te daré a comer la
heredad de Jacob tu padre, porque la boca de Jehová lo ha hablado. nosotros curados. Todos nosotros nos descarriamos
como bejas, cada cual se apartó por su camino, mas Jehová cargó en él el pecado de todos nosotros...
```

由于输出相当长（总共 312 个词），我们在这里不会打印整个输出！在 16GB V100 GPU 上，预计上面的代码要运行 3.45 秒，对于 317 秒的音频样本来说相当不错。在 CPU 上，预期大约要跑 30 秒。

Whisper 还能够预测音频数据的片段级 _时间戳_。这些时间戳指示音频的短片段的开始和结束时间，对于将转写与输入音频对齐特别有用。
比方说我们想为视频提供字幕（closed caption）——我们需要这些时间戳来知道转写的哪一部分对应于视频的某个段落，以便在那个时间显示正确的转写。

启用时间戳预测很简单，我们只需设置参数 `return_timestamps=True`。时间戳与我们之前使用的分块和批处理方法兼容，所以我们可以直接将时间戳参数附加到我们之前的代码上：

```python
pipe(
    long_audio,
    max_new_tokens=256,
    generate_kwargs={"task": "transcribe"},
    chunk_length_s=30,
    batch_size=8,
    return_timestamps=True,
)["chunks"]
```

**输出：**

```
[{'timestamp': (0.0, 26.4),
  'text': ' Entonces te deleitarás en Jehová, y yo te haré subir sobre las alturas de la tierra, y te daré a comer la heredad de Jacob tu padre, porque la boca de Jehová lo ha hablado. nosotros curados. Todos nosotros nos descarriamos como bejas, cada cual se apartó por su camino,'},
 {'timestamp': (26.4, 32.48),
  'text': ' mas Jehová cargó en él el pecado de todos nosotros. No es que partas tu pan con el'},
 {'timestamp': (32.48, 38.4),
  'text': ' hambriento y a los hombres herrantes metas en casa, que cuando vieres al desnudo lo cubras y no'},
 ...
```

瞧！我们有了预测的文本以及相应的时间戳。

## 总结

Whisper 是一个强大的用于语音识别和翻译的预训练模型。与 Wav2Vec2 相比，它具有更高的转写准确性，输出包含标点和大小写。
它可用于转写英语和其他 96 种语言的语音，既能处理短音频片段又能通过 _分块_ 处理的较长片段。这些属性使其可以直接用于许多语音识别和翻译任务，无需微调。
`pipeline()` 方法提供了一种使用一行 API 调用轻松运行推理的方法，还能对生成的预测进行控制。

尽管 Whisper 模型在许多高资源语言上表现极佳，但它对于低资源语言，即那些缺乏容易获得的训练数据的语言，转写和翻译准确性较低。
在某些语言的不同口音和方言，包括不同性别、种族、年龄或其他人口统计标准的发言者身上的表现也存在差异（参见 [Whisper 论文](https://arxiv.org/pdf/2212.04356.pdf)）。

为了提高在低资源语言、口音或方言上的性能，我们可以将预训练的 Whisper 模型在一小部分适当选择的数据上继续训练，这个过程称为 _微调_。
我们将展示如何仅使用十小时的额外数据，就能将 Whisper 模型在低资源语言上的性能提高 100% 以上。在下一节中，我们将介绍选择微调数据集的过程。


<EditOnGithub source="https://github.com/huggingface/audio-transformers-course/blob/main/chapters/zh-CN/chapter5/asr_models.mdx" />

### 选择数据集
https://huggingface.co/learn/audio-course/zh-CN/chapter5/choosing_dataset.md

# 选择数据集

就像在其他机器学习问题中一样，我们的模型最多只能表现得与我们用来训练它的数据一样好。
语音识别数据集在组织方式和覆盖的领域方面有很大的不同，我们需要查看它们提供的特征，选出最符合我们标准的数据集。

所以在选择数据集之前，我们首先需要了解特征的定义。

## 语音数据集的特征

### 1. 小时数

简单来说，训练小时数体现了数据集的大小，类似 NLP 数据集中的训练样例数量。然而，更大的数据集不一定更好。
如果我们想要一个泛化能力强的模型，就需要一个具有许多不同发言者、领域和发言风格的**多样化**数据集。

### 2. 领域

领域（domain）包括数据的来源，比如有声读物、播客、YouTube 还是金融会议。每个领域的数据分布都不同，
例如有声读物在高质量的录音室条件下录制（没有背景噪音），并且文本取自书面文学。而对于 YouTube，音频可能包含更多背景噪音和更非正式的语音风格。

我们需要保证选择的领域与推理时的条件相匹配。例如，如果我们在有声读物上训练我们的模型，就不能期望它在嘈杂的环境中表现良好。

### 3. 说话风格

说话风格（speaking style）分为两类：

* 叙述性（Narrated）：按照给定的文本朗读
* 自发性（Spontaneous）：没有固定剧本的对话

音频和文本数据反映了说话的风格。由于叙述文本是有剧本的，它表达得会比较清晰，没有任何错误：

```
“Consider the task of training a model on a speech recognition dataset”
```

而自发性言语使用更口语化的说话风格，包括重复、犹豫和错误：

```
“Let's uhh let's take a look at how you'd go about training a model on uhm a sp- speech recognition dataset”
```

### 4. 转写风格

转写风格指的是目标文本是否有标点、区分大小写等。如果我们希望系统生成可用于出版物或会议转写的完全格式化文本，我们需要带有标点和大小写的训练数据。
如果我们只需要未格式化结构的口头言语，那么标点和大小写都不是必需的。在这种情况下，我们可以选择一个没有标点或大小写的数据集，或者选择一个有标点和大小写的数据集，然后通过预处理从目标文本中去除它们。

## Hub 上数据集的汇总

以下是 Hugging Face Hub 上最受欢迎的英语语音识别数据集，作为您根据自己的标准选择数据集的参考：

| 数据集                                                                                    | 训练小时数 | 领域                        | 说话风格      | 大小写 | 标点 | 许可证            | 推荐用途              |
|---------------------------------------------------------------------------------------------|---------|--------------------------|----------------|--------|------|------------------|----------------------|
| [LibriSpeech](https://huggingface.co/datasets/librispeech_asr)                              | 960     | 有声读物                    | 叙述性           | ❌    | ❌   | CC-BY-4.0       | 学术基准测试        |
| [Common Voice 11](https://huggingface.co/datasets/mozilla-foundation/common_voice_11_0)     | 3000    | 维基百科                    | 叙述性           | ✅    | ✅   | CC0-1.0         | 非母语发言者        |
| [VoxPopuli](https://huggingface.co/datasets/facebook/voxpopuli)                             | 540     | 欧洲议会                    | 演讲式           | ❌    | ✅   | CC0             | 非母语发言者        |
| [TED-LIUM](https://huggingface.co/datasets/LIUM/tedlium)                                    | 450     | TED 演讲                    | 演讲式           | ❌    | ❌   | CC-BY-NC-ND 3.0 | 技术主题           |
| [GigaSpeech](https://huggingface.co/datasets/speechcolab/gigaspeech)                        | 10000   | 有声读物、播客、YouTube      | 叙述性、自发性    | ❌    | ✅   | apache-2.0      | 多领域鲁棒性       |
| [SPGISpeech](https://huggingface.co/datasets/kensho/spgispeech)                             | 5000    | 金融会议                    | 演讲式、自发性    | ✅    | ✅   | User Agreement  | 完全格式化的转写    |
| [Earnings-22](https://huggingface.co/datasets/revdotcom/earnings22)                         | 119     | 金融会议                    | 演讲式、自发性    | ✅    | ✅   | CC-BY-SA-4.0    | 口音多样性         |
| [AMI](https://huggingface.co/datasets/edinburghcstr/ami)                                    | 100     | 会议                        | 自发性           | ✅    | ✅   | CC-BY-4.0       | 嘈杂语音环境        |

以下是多语言语音识别数据集的表格，请注意，我们省略了训练小时数列（因为这取决于每个数据集有哪些语言，占比多少），改成了每个数据集的语言数量：

| 数据集                                                                                         | 语言数量 | 领域                      | 说话风格 | 大小写 | 标点  | 许可证    | 推荐用途         |
|------------------------------------------------------------------------------------------------|--------|---------------------------|---------|-------|-------|-----------|------------------|
| [Multilingual LibriSpeech](https://huggingface.co/datasets/facebook/multilingual_librispeech)  | 6      | 有声读物                   | 叙述性   | ❌    | ❌   | CC-BY-4.0 | 学术基准测试      |
| [Common Voice 13](https://huggingface.co/datasets/mozilla-foundation/common_voice_13_0)        | 108    | 维基百科文本和众筹的言语    | 叙述性   | ✅    | ✅   | CC0-1.0   | 多样化的发言者集合 |
| [VoxPopuli](https://huggingface.co/datasets/facebook/voxpopuli)                                | 15     | 欧洲议会                   | 自发性   | ❌    | ✅   | CC0       | 欧洲语言          |
| [FLEURS](https://huggingface.co/datasets/google/fleurs)                                        | 101    | 欧洲议会                   | 自发性   | ❌    | ❌   | CC-BY-4.0 | 多语言评估        |

有关两个表中涵盖的音频数据集的详细分析，请参阅博文 [音频数据集完全指南](https://huggingface.co/blog/audio-datasets#a-tour-of-audio-datasets-on-the-hub)。
虽然 Hub 上有超过 180 个语音识别数据集，但可能没有一个数据集符合您的需求。在这种情况下，也可以通过 🤗 Datasets 库来使用您自己的音频数据。要创建自定义音频数据集，请参考指南 [创建音频数据集](https://huggingface.co/docs/datasets/audio_dataset)。
自定义音频数据集时，请考虑在 Hub 上分享最终构建出的数据集，以便社区中的其他人可以从您的成果中受益——音频社区包容且广大，您感谢其他人的贡献，其他人会像一样感谢您的贡献。

好了！既然我们已经了解了选择 ASR 数据集的所有标准，那么我们为这个教程选择一个吧。我们知道 Whisper 已经在高资源语言（如英语和西班牙语）上做得很好了，
所以我们将专注于低资源的多语言转写。我们想保留 Whisper 预测标点和大小写的能力，所以从第二张表来看，Common Voice 13 是一个很好的候选数据集！

## Common Voice 13

Common Voice 13 是一个众筹的数据集，发言者用不同语言朗读并录制维基百科的文本。它是由 Mozilla 基金会发布的一系列 Common Voice 数据集之一。
在撰写本文时，Common Voice 13 是最新版的数据集，拥有迄今为止任何版本中最多的语言和每种语言最多的小时数。

我们可以通过查看 Hub 上的数据集页面来获取 Common Voice 13 数据集的全部语言列表：[mozilla-foundation/common_voice_13_0](https://huggingface.co/datasets/mozilla-foundation/common_voice_13_0)。
您第一次查看此页面时会被要求接受使用条款，之后您将获得完全访问数据集的权限。

一旦我们提交了使用数据集的认证信息，我们将看到数据集预览。数据集预览向我们展示了每种语言数据集的前 100 个样本。更重要的是，它已加载了可播放的音频样本。
对于这个单元，我们将选择 [_Dhivehi_](https://en.wikipedia.org/wiki/Maldivian_language)（或 _迪维希语_），一种在南亚岛国马尔代夫使用的印度-雅利安语。
虽然我们在本教程中只示范了迪维希语，但这里介绍的步骤适用于 Common Voice 13 数据集中 108 种语言的任何一种，以及 Hugging Face Hub 上 180 多个音频数据集的任何一个，没有语言或方言的限制。

我们可以通过使用下拉菜单将子集设置为 `dv` 来选择 Common Voice 13 的 Dhivehi 子集（`dv` 是迪维希语的语言代码）：

<div class="flex justify-center">
    <img src="https://huggingface.co/datasets/huggingface-course/audio-course-images/resolve/main/cv_13_dv_selection.png" alt="从数据集预览中选择 Dhivehi 集">
</div>

如果我们点击第一个样本上的播放按钮，我们可以听到音频并查看相应的文本。浏览训练和测试集的样本，以更好地了解我们正在处理的音频和文本数据。
从语调和风格上可以看出，录音来自叙述性言语。您可能会注意到每条数据的发言者和录音质量的有很大不同，这是众筹数据的一个共同特征。

数据集预览是在开始大规模使用之前感性认知音频数据集的绝佳方式。您可以选择 Hub 上的任何数据集，浏览样本并播放不同子集的音频，评估它是否适合您的需求。选择了一个数据集后，加载数据并开始使用就非常简单了。

目前我个人不会说迪维希语，而且估计绝大多数读者也不会！为了知道我们微调后的模型是否有效，我们需要一种严格的方法来**评估**它对未见过的数据的转写准确性。我们将在下一节中详细介绍这一点！


<EditOnGithub source="https://github.com/huggingface/audio-transformers-course/blob/main/chapters/zh-CN/chapter5/choosing_dataset.mdx" />

### 用 Gradio 创建 demo
https://huggingface.co/learn/audio-course/zh-CN/chapter5/demo.md

# 用 Gradio 创建 demo

现在我们已经对迪维希语语音识别微调了 Whisper 模型，让我们继续前进，构建一个 [Gradio](https://gradio.app) demo 来向社区展示它！

首先，我们需要使用 `pipeline()` 类加载微调后的检查点——这一点我们从 [预训练模型](asr_models) 章节开始就非常熟悉了。
您可以更改 `model_id` 为您在 Hugging Face Hub 上微调模型的命名空间，或者使用预训练的 [Whisper 模型](https://huggingface.co/models?sort=downloads&search=openai%2Fwhisper-) 来进行零样本语音识别：

```python
from transformers import pipeline

model_id = "sanchit-gandhi/whisper-small-dv"  # 改为您的模型 id
pipe = pipeline("automatic-speech-recognition", model=model_id)
```

其次，我们将定义一个函数，该函数接收输入音频的文件路径并将其输入 pipeline。在这里，pipeline 会自动加载音频文件，
将其重采样到正确的采样率，并使用模型运行推理。然后我们可以简单地返回转写文本作为函数的输出。
为了确保我们的模型可以处理任意长度的音频输入，我们将启用 *分块*，正如 [预训练模型](asr_models) 章节所描述的：

```python
def transcribe_speech(filepath):
    output = pipe(
        filepath,
        max_new_tokens=256,
        generate_kwargs={
            "task": "transcribe",
            "language": "sinhalese",
        },  # 修改成您微调过的语言
        chunk_length_s=30,
        batch_size=8,
    )
    return output["text"]
```

我们将使用 Gradio 的 [blocks](https://gradio.app/docs/#blocks) 功能在我们的 demo 中启动两个标签页：一个用于麦克风转写，另一个用于文件上传。

```python
import gradio as gr

demo = gr.Blocks()

mic_transcribe = gr.Interface(
    fn=transcribe_speech,
    inputs=gr.Audio(sources="microphone", type="filepath"),
    outputs=gr.outputs.Textbox(),
)

file_transcribe = gr.Interface(
    fn=transcribe_speech,
    inputs=gr.Audio(sources="upload", type="filepath"),
    outputs=gr.outputs.Textbox(),
)
```

最后，我们使用刚刚定义的两个 blocks 启动 Gradio demo：

```python
with demo:
    gr.TabbedInterface(
        [mic_transcribe, file_transcribe],
        ["Transcribe Microphone", "Transcribe Audio File"],
    )

demo.launch(debug=True)
```

这将启动一个类似于在 Hugging Face Space 上运行的 Gradio demo：

<iframe src="https://course-demos-whisper-small.hf.space" frameBorder="0" height="450" title="Gradio app" class="container p-0 flex-grow space-iframe" allow="accelerometer; ambient-light-sensor; autoplay; battery; camera; document-domain; encrypted-media; fullscreen; geolocation; gyroscope; layout-animations; legacy-image-formats; magnetometer; microphone; midi; oversized-images; payment; picture-in-picture; publickey-credentials-get; sync-xhr; usb; vr ; wake-lock; xr-spatial-tracking" sandbox="allow-forms allow-modals allow-popups allow-popups-to-escape-sandbox allow-same-origin allow-scripts allow-downloads"></iframe>

如果您希望在 Hugging Face Hub 上托管您的 demo，您可以使用这个 Space 作为您微调的模型的模板。

点击链接将模板 demo 复制到您的账户：https://huggingface.co/spaces/course-demos/whisper-small?duplicate=true

我们建议将您的 Space 命名为与您的微调模型类似的名称（例如 whisper-small-dv-demo）并将可见性设置为“公开”。

将 Space 复制到您的账户后，请点击“Files and versions”->“app.py”->“edit”，然后更改模型标识符为您的微调模型（第6行）。滚动到页面底部并点击
“Commit changes to main”，demo 将重启，这次使用的是您的微调模型。您可以与您的朋友和家人分享这个 demo，以便他们可以使用您训练的模型！

查看我们的视频教程，以更好地了解如何复制 Space 👉️ [YouTube 视频](https://www.youtube.com/watch?v=VQYuvl6-9VE)

我们期待在 Hub 上看到您的 demos！


<EditOnGithub source="https://github.com/huggingface/audio-transformers-course/blob/main/chapters/zh-CN/chapter5/demo.mdx" />

### 微调语音识别模型
https://huggingface.co/learn/audio-course/zh-CN/chapter5/fine-tuning.md

# 微调语音识别模型

在本节中，我们将手把手指导如何在 Common Voice 13 数据集上微调 Whisper 以进行语音识别。
我们将使用模型的“small”版本和一个相对轻量级的数据集，使您能够在任何 16GB 以上的 GPU 上用少量磁盘空间相对快速地微调，
例如 Google Colab 免费提供的 16GB T4 GPU。

如果您的 GPU 太小，或在训练过程中遇到内存问题，可以遵循我们之后提供的建议来减少内存使用量。
相反，如果您可以访问更大的 GPU，您可以修改训练参数以最大化您的吞吐量。因此，无论您的 GPU 规格如何，都可以参考本指南！

同样，本指南简要介绍了如何针对迪维希语微调 Whisper 模型。然而，这里介绍的步骤可以推广到 Common Voice 数据集中的任何语言，
更一般地，可以推广到 Hugging Face Hub 上的任何 ASR 数据集。您可以更改代码以快速切换到您选择的语言，并在您的母语上微调 Whisper 模型 🌍

好了，废话少说，让我们开始我们的微调流程吧！

## 配置环境

我们强烈建议您在训练时直接将模型检查点上传至 [Hugging Face Hub](https://huggingface.co/)。Hub 提供：

- 集成的版本控制：您可以确保训练过程中不会丢失任何模型检查点。
- Tensorboard 日志：跟踪训练过程中的重要指标。
- 模型卡片：记录模型的功能及其预期用例。
- 社区：与社区共享和协作的简便方式！🤗

将 notebook 链接到 Hub 非常简单——只需要输入您的 Hub 认证令牌。在 [这里](https://huggingface.co/settings/tokens) 找到您的 Hub 认证令牌并根据提示输入它：

```python
from huggingface_hub import notebook_login

notebook_login()
```

**输出：**

```bash
Login successful
Your token has been saved to /root/.huggingface/token
```

## 加载数据集

[Common Voice 13](https://huggingface.co/datasets/mozilla-foundation/common_voice_13_0) 包含大约十小时的标注过的迪维希语数据，
这对于微调来说是极少量的数据，因此我们将依赖在预训练期间 Whisper 获得的广泛的多语言 ASR 知识来弥补迪维希语的低资源量。

使用 🤗 Datasets 下载和准备数据非常简单。我们可以用一行代码下载并准备好 Common Voice 13 的数据。由于迪维希语资源非常少，
我们将结合 `train` 和 `validation` 子集，提供大约七小时的训练数据，并使用三小时的 `test` 数据作为我们保留的测试集：

```python
from datasets import load_dataset, DatasetDict

common_voice = DatasetDict()

common_voice["train"] = load_dataset(
    "mozilla-foundation/common_voice_13_0", "dv", split="train+validation"
)
common_voice["test"] = load_dataset(
    "mozilla-foundation/common_voice_13_0", "dv", split="test"
)

print(common_voice)
```

**输出：**

```
DatasetDict({
    train: Dataset({
        features: ['client_id', 'path', 'audio', 'sentence', 'up_votes', 'down_votes', 'age', 'gender', 'accent', 'locale', 'segment', 'variant'],
        num_rows: 4904
    })
    test: Dataset({
        features: ['client_id', 'path', 'audio', 'sentence', 'up_votes', 'down_votes', 'age', 'gender', 'accent', 'locale', 'segment', 'variant'],
        num_rows: 2212
    })
})
```

<Tip>
    您可以将语言标识符从 `"dv"` 更改为您选择的语言标识符。要查看 Common Voice 13 中所有可选的语言，
    请查看 Hugging Face Hub 上的数据集卡片：https://huggingface.co/datasets/mozilla-foundation/common_voice_13_0
</Tip>

大多数 ASR 数据集只提供输入音频样本（`audio`）和相应的转写文本（`sentence`）。Common Voice 包含额外的元数据，如 `accent` 和 `locale`，
我们做 ASR 时可以忽略这些信息。为了让代码尽可能通用，我们只用输入音频和转写文本来进行微调，丢弃额外的元数据信息：

```python
common_voice = common_voice.select_columns(["audio", "sentence"])
```

## 特征提取器、分词器和处理器

ASR 流程可以分解为三个阶段：

1. 特征提取器将原始音频输入预处理为 log-mel 频谱图
2. 模型执行序列到序列的映射
3. 分词器将预测的词元后处理为文本

在 🤗 Transformers 中，Whisper 模型有一个关联的特征提取器和分词器，分别称为 [WhisperFeatureExtractor](https://huggingface.co/docs/transformers/main/model_doc/whisper#transformers.WhisperFeatureExtractor) 和 [WhisperTokenizer](https://huggingface.co/docs/transformers/main/model_doc/whisper#transformers.WhisperTokenizer)。
为了简化我们的工作，这两个类的对象又可以被封装在一个类中，称为 [WhisperProcessor](https://huggingface.co/docs/transformers/model_doc/whisper#transformers.WhisperProcessor)。
我们可以调用 WhisperProcessor 来执行音频预处理和文本词元后处理。这样我们在训练期间只需要跟踪两个对象：处理器和模型。

当执行多语言微调时，我们需要在实例化处理器时设置 `"language"` 和 `"task"`。`"language"` 应设置为源音频语言，
任务设置为 `"transcribe"` 以进行语音识别或 `"translate"` 以进行语音翻译。这些参数会影响分词器的行为，应正确设置以确保目标标签被正确编码。

我们可以通过导入语言列表来查看 Whisper 支持的所有可能语言：

```python
from transformers.models.whisper.tokenization_whisper import TO_LANGUAGE_CODE

TO_LANGUAGE_CODE
```

如果您浏览此列表，您会注意到许多语言都存在，但迪维希语是少数不存在的之一！这意味着 Whisper 没有在迪维希语上进行预训练。
然而，这并不意味着我们不能在其上微调 Whisper。这样做，我们将教会 Whisper 一种新的语言，一种预训练检查点不支持的语言。这很酷，对吧！

当您在一种新语言上微调时，Whisper 可以利用它在其他 96 种语言上的预训练知识。总的来说，所有现代语言在语言学上至少与 Whisper 已经
知道的 96 种语言中的一种类似，所以我们可以利用起这种跨语言的知识。

我们需要做的是找到 Whisper 在预训练期间学习过**最相似**的语言。维基百科的迪维希语词条指出，迪维希语与斯里兰卡的僧伽罗语密切相关。
如果我们再次检查语言代码，我们可以看到僧伽罗语存在于 Whisper 语言集中，因此我们可以安全地将我们的语言参数设置为 `"sinhalese"`。

好！我们将从预训练检查点加载我们的处理器，将语言设置为 `"sinhalese"`，任务设置为 `"transcribe"`，如上所述：

```python
from transformers import WhisperProcessor

processor = WhisperProcessor.from_pretrained(
    "openai/whisper-small", language="sinhalese", task="transcribe"
)
```

值得重申的是，在大多数情况下，您会发现您想要微调的语言在预训练语言集中，这种情况下，您可以直接将语言设置为您的源音频语言！
请注意，对于仅英语的微调，这两个参数应省略，这种情况下语言（`"English"`）和任务（`"transcribe"`）都是唯一默认的选项。

## 预处理数据

让我们看看数据集特征。特别注意 `"audio"` 列——这详细说明了我们输入音频的采样率：

```python
common_voice["train"].features
```

**输出：**

```
{'audio': Audio(sampling_rate=48000, mono=True, decode=True, id=None),
 'sentence': Value(dtype='string', id=None)}
```

由于我们的输入音频采样率为 48kHz，我们需要在将其传递给 Whisper 特征提取器之前将其 _下采样_ 到 16kHz，16kHz 是 Whisper 模型期望的采样率。

我们将使用数据集的 [`cast_column`](https://huggingface.co/docs/datasets/main/en/package_reference/main_classes#datasets.Dataset.cast_column) 方法
将音频输入设置为正确的采样率。此操作不会就地更改音频，而是指示数据集在加载音频样本时即时地重采样：

```python
from datasets import Audio

sampling_rate = processor.feature_extractor.sampling_rate
common_voice = common_voice.cast_column("audio", Audio(sampling_rate=sampling_rate))
```

现在我们可以编写一个函数来准备我们的数据以供模型使用：

1. 调用 `sample["audio"]` 在逐个样本上加载和重采样音频数据。如上所述，🤗 Datasets 在加载时即时执行任何必要的重采样操作。
2. 用特征提取器从我们的一维音频数组计算 log-mel 频谱图输入特征。
3. 用分词器将转写文本编码为标签 id。

```python
def prepare_dataset(example):
    audio = example["audio"]

    example = processor(
        audio=audio["array"],
        sampling_rate=audio["sampling_rate"],
        text=example["sentence"],
    )

    # 计算输入音频样本的长度，以秒计
    example["input_length"] = len(audio["array"]) / audio["sampling_rate"]

    return example
```

我们可以使用 🤗 Datasets 的 `.map` 方法将数据预处理函数应用于我们所有的训练样本。
我们将从原始训练数据中移除原有的列（音频和文本），只留下 `prepare_dataset` 函数返回的列：

```python
common_voice = common_voice.map(
    prepare_dataset, remove_columns=common_voice.column_names["train"], num_proc=1
)
```
最后，我们定义一个函数来过滤掉音频样本长度超过 30s 的训练数据。这些样本否则会被 Whisper 特征提取器截断，这可能影响训练的稳定性。
我们定义一个函数，对于小于 30s 的样本返回 `True`，对于更长的样本返回 `False`：

```python
max_input_length = 30.0


def is_audio_in_length_range(length):
    return length < max_input_length
```

我们通过 🤗 Datasets 的 `.filter` 方法将我们的过滤函数应用于我们的训练数据集的所有样本：

```python
common_voice["train"] = common_voice["train"].filter(
    is_audio_in_length_range,
    input_columns=["input_length"],
)
```

让我们检查一下通过这个过滤步骤我们移除了多少训练数据：

```python
common_voice["train"]
```

**输出：**

```
Dataset({
    features: ['input_features', 'labels', 'input_length'],
    num_rows: 4904
})
```

好的！在过滤前后我们有相同数量的样本，所以原数据集中没有超过 30s 的样本。如果您切换语言，情况可能不同，因此为了稳健性最好保留这个过滤步骤。
有了这些，我们的数据已经完全准备好进行训练了！让我们继续看看如何使用这些数据来微调 Whisper。

## 训练和评估

现在我们已经准备好了我们的数据，我们准备好深入训练流程了。
[🤗 Trainer](https://huggingface.co/transformers/master/main_classes/trainer.html?highlight=trainer) 将为我们完成大部分繁重的工作。我们所要做的就是：

- 定义一个数据整理器：数据整理器接受我们预处理的数据并准备好 PyTorch 张量以供模型使用。
- 评估指标：我们希望使用词错误率（WER）指标来评估模型，需要定义一个处理这种计算的 `compute_metrics` 函数。
- 加载预训练检查点：我们需要加载一个预训练检查点并为训练正确配置它。
- 定义训练参数：这将被 🤗 Trainer 用来构建训练计划。

一旦我们完成了模型的微调，我们将在测试数据上评估它，以验证我们是否正确地训练了它转写迪维希语语音。

### 定义数据整理器

序列到序列语音模型的数据整理器很独特，它能独立地处理 `input_features` 和 `labels`：`input_features` 必须由特征提取器处理，而 `labels` 由分词器处理。

`input_features` 已经填充到 30s 并转换为固定维度的 log-Mel 频谱图，所以我们所要做的就是使用特征提取器的 `.pad` 方法，
并设置参数 `return_tensors=pt`，将它们转换为批量的 PyTorch 张量。请注意，这里没有应用额外的填充，因为输入是固定维度的，
`input_features` 仅被转换为 PyTorch 张量。

另一方面，`labels` 是未填充的。我们首先使用分词器的 `.pad` 方法将序列填充到批次中的最大长度。然后用 `-100` 替换填充词元，
以便在计算损失函数时**不**考虑这些词元。我们还要去掉标签序列开头的转写起始（start of transcript）词元，因为我们稍后在训练期间会添加它。

我们可以利用我们之前定义的 `WhisperProcessor` 来执行特征提取器和分词器操作：

```python
import torch

from dataclasses import dataclass
from typing import Any, Dict, List, Union


@dataclass
class DataCollatorSpeechSeq2SeqWithPadding:
    processor: Any

    def __call__(
        self, features: List[Dict[str, Union[List[int], torch.Tensor]]]
    ) -> Dict[str, torch.Tensor]:
        # 分离输入特征和标签，它们长度不同，填充方式也不同
        # 首先以 PyTorch 张量格式返回音频
        input_features = [
            {"input_features": feature["input_features"][0]} for feature in features
        ]
        batch = self.processor.feature_extractor.pad(input_features, return_tensors="pt")

        # 获取分词后得到的标签序列
        label_features = [{"input_ids": feature["labels"]} for feature in features]
        # 把标签序列填充到最大长度
        labels_batch = self.processor.tokenizer.pad(label_features, return_tensors="pt")

        # 用 -100 来代替填充进的标签，从而不影响损失函数的计算
        labels = labels_batch["input_ids"].masked_fill(
            labels_batch.attention_mask.ne(1), -100
        )

        # 如果在之前分词时添加了 bos 词元，那就剪切掉，因为之后还会加上的
        if (labels[:, 0] == self.processor.tokenizer.bos_token_id).all().cpu().item():
            labels = labels[:, 1:]

        batch["labels"] = labels

        return batch
```

我们现在可以初始化我们刚刚定义的数据整理器：

```python
data_collator = DataCollatorSpeechSeq2SeqWithPadding(processor=processor)
```

继续！

### 评估指标

接下来，我们定义我们在测试集上将使用的评估指标。我们将使用 [评价指标](evaluation) 章节中介绍的词错误率（WER）指标，这是评估 ASR 系统的“事实上”的指标。

我们从 🤗 Evaluate 加载 WER 指标：

```python
import evaluate

metric = evaluate.load("wer")
```

然后我们只需定义一个函数，它接受我们模型的预测并返回 WER 指标。这个函数称为 `compute_metrics`，首先用 `pad_token_id` 替换 `label_ids` 中的 `-100`
（撤销我们在数据整理器中为了在损失函数中正确忽略填充词元做的步骤）。然后，它将预测的标签 id 解码为字符串。最后，它计算预测结果和参考标签之间的 WER。
在这里，我们可以选择评估“标准化”的转录和预测，这些转录和预测都移除了标点和大小写。我们建议您遵循这个步骤，通过标准化转录改进 WER。

```python
from transformers.models.whisper.english_normalizer import BasicTextNormalizer

normalizer = BasicTextNormalizer()


def compute_metrics(pred):
    pred_ids = pred.predictions
    label_ids = pred.label_ids

    # 用 pad_token_id 替换 -100
    label_ids[label_ids == -100] = processor.tokenizer.pad_token_id

    # 我们希望在计算指标时不要组合起词元
    pred_str = processor.batch_decode(pred_ids, skip_special_tokens=True)
    label_str = processor.batch_decode(label_ids, skip_special_tokens=True)

    # 计算普通的 WER
    wer_ortho = 100 * metric.compute(predictions=pred_str, references=label_str)

    # 计算标准化的 WER
    pred_str_norm = [normalizer(pred) for pred in pred_str]
    label_str_norm = [normalizer(label) for label in label_str]
    # 过滤，从而在评估时只计算 reference 非空的样本
    pred_str_norm = [
        pred_str_norm[i] for i in range(len(pred_str_norm)) if len(label_str_norm[i]) > 0
    ]
    label_str_norm = [
        label_str_norm[i]
        for i in range(len(label_str_norm))
        if len(label_str_norm[i]) > 0
    ]

    wer = 100 * metric.compute(predictions=pred_str_norm, references=label_str_norm)

    return {"wer_ortho": wer_ortho, "wer": wer}
```

### 加载预训练检查点

现在让我们加载预训练的 Whisper small 检查点。用 🤗 Transformers 这是小菜一碟！

```python
from transformers import WhisperForConditionalGeneration

model = WhisperForConditionalGeneration.from_pretrained("openai/whisper-small")
```

我们将 `use_cache` 设置为 `False` 以进行训练，因为我们正在使用 [梯度检查点](https://huggingface.co/docs/transformers/v4.18.0/en/performance#gradient-checkpointing)，
两者不兼容。我们还将覆盖两个生成参数以控制模型在推理期间的行为：我们将通过设置 `language` 和 `task` 参数在生成期间强制规定语言和任务标签，
并重新启用缓存以加速推理：

```python
from functools import partial

# 在训练期间不使用缓存，因为它和梯度检查点不兼容
model.config.use_cache = False

# 为生成设置语言和任务，并重新启用缓存
model.generate = partial(
    model.generate, language="sinhalese", task="transcribe", use_cache=True
)
```

## 定义训练配置

在最后一步，我们定义了所有与训练相关的参数。在这里，我们将训练步骤数设置为 500。这足以看到与预训练 Whisper 模型相比 WER 大幅提升，
同时确保微调可以在大约 45 分钟内在 Google Colab 免费版上运行完。有关训练参数的更多详情，请参阅 Seq2SeqTrainingArguments [文档](https://huggingface.co/docs/transformers/main_classes/trainer#transformers.Seq2SeqTrainingArguments)。

```python
from transformers import Seq2SeqTrainingArguments

training_args = Seq2SeqTrainingArguments(
    output_dir="./whisper-small-dv",  # 在 HF Hub 上的输出目录的名字
    per_device_train_batch_size=16,
    gradient_accumulation_steps=1,  # 每次 batch size 下调到一半就把这个参数上调到两倍
    learning_rate=1e-5,
    lr_scheduler_type="constant_with_warmup",
    warmup_steps=50,
    max_steps=500,  # 如果您有自己的 GPU 或者 Colab 付费计划，上调到 4000
    gradient_checkpointing=True,
    fp16=True,
    fp16_full_eval=True,
    evaluation_strategy="steps",
    per_device_eval_batch_size=16,
    predict_with_generate=True,
    generation_max_length=225,
    save_steps=500,
    eval_steps=500,
    logging_steps=25,
    report_to=["tensorboard"],
    load_best_model_at_end=True,
    metric_for_best_model="wer",
    greater_is_better=False,
    push_to_hub=True,
)
```

<Tip>
    如果您不想将模型检查点上传到 Hub，请设置 `push_to_hub=False`。
</Tip>

我们可以将训练参数传递给 🤗 Trainer，连同我们的模型、数据集、数据整理器和 `compute_metrics` 函数一起：

```python
from transformers import Seq2SeqTrainer

trainer = Seq2SeqTrainer(
    args=training_args,
    model=model,
    train_dataset=common_voice["train"],
    eval_dataset=common_voice["test"],
    data_collator=data_collator,
    compute_metrics=compute_metrics,
    tokenizer=processor,
)
```

有了这个，我们就准备好开始训练了！

### 训练

要启动训练，只需执行：

```python
trainer.train()
```

训练大约需要 45 分钟，取决于您的 GPU 或 Google Colab 给分配的 GPU 。由于 GPU 不同，当您开始训练时，可能会遇到 CUDA `"out-of-memory"` 错误。
在这种情况下，您可以两倍两倍地逐渐减少 `per_device_train_batch_size` 并使用 [`gradient_accumulation_steps`](https://huggingface.co/docs/transformers/main_classes/trainer#transformers.Seq2SeqTrainingArguments.gradient_accumulation_steps) 来补偿。

**输出：**

| Training Loss | Epoch | Step | Validation Loss | Wer Ortho | Wer     |
|:-------------:|:-----:|:----:|:---------------:|:---------:|:-------:|
| 0.136         | 1.63  | 500  | 0.1727          | 63.8972   | 14.0661 |

我们最终的 WER 是 14.1%——对于七小时的训练数据和仅 500 训练步骤来说不错，这相当于与预训练模型相比提高了 112%！
这意味着我们已经将一个之前不了解迪维希语的模型微调为在不到一小时内以足够的准确性识别迪维希语语音 🤯

最重要的是，这与其他 ASR 系统相比如何。为此，我们可以查看 autoevaluate [排行榜](https://huggingface.co/spaces/autoevaluate/leaderboards?dataset=mozilla-foundation%2Fcommon_voice_13_0&only_verified=0&task=automatic-speech-recognition&config=dv&split=test&metric=wer)，
一个根据语言和数据集分类模型并根据它们的 WER 排名的排行榜。

查看排行榜，我们看到我们训练了 500 步的模型令人信服地击败了我们在前一节评估的预训练 [Whisper Small](https://huggingface.co/openai/whisper-small) 检查点。干得好 👏

我们看到有一些检查点比我们训练的表现更好。Hugging Face Hub 的美妙之处在于它是一个*协作*平台——如果我们没有时间或资源自己进行更长时间的训练运行，
我们可以加载社区中其他人已经训练并慷慨分享的检查点（记得对他们心怀感谢！）。您将能够以与预训练检查点相同的方式使用 `pipeline` 类加载这些检查点，
正如我们之前所做的！所以没有什么能阻止您挑选排行榜上最佳模型来用于您的任务！

我们可以在将训练结果推送到 Hub 时自动将我们的检查点提交到排行榜——我们只需设置适当的关键字参数 (kwargs)。您可以根据您的数据集、语言和模型名称相应地更改这些值：

```python
kwargs = {
    "dataset_tags": "mozilla-foundation/common_voice_13_0",
    "dataset": "Common Voice 13",  # 训练数据集
    "language": "dv",
    "model_name": "Whisper Small Dv - Sanchit Gandhi",  # 给模型起个“漂亮”的名字
    "finetuned_from": "openai/whisper-small",
    "tasks": "automatic-speech-recognition",
}
```

现在可以将训练结果上传到 Hub，请执行 `push_to_hub` 命令：

```python
trainer.push_to_hub(**kwargs)
```

这将在 `"您的用户名/您给模型起的名字"` 下保存训练日志和模型权重。作为示例，请查看 `sanchit-gandhi/whisper-small-dv` 上的上传文件。

虽然在 Common Voice 13 迪维希语测试数据上微调的模型提供了令人满意的结果，但这绝非最佳。本指南的目的是演示如何使用 🤗 Trainer 微调 ASR 模型以进行多语言语音识别。

如果您有自己的 GPU 或订阅了 Google Colab 付费计划，您可以将 `max_steps` 增加到 4000 步，通过更多步骤的训练进一步降低大约 3% 的 WER。
如果您决定进行 4000 步的训练，我们还建议将学习率调度器更改为*线性*计划（设置 `lr_scheduler_type="linear"`），因为这将在长时间的训练中提升更多性能。

通过优化训练超参数，如 _学习率_ 和 _dropout_，并使用更大的预训练检查点（`medium` 或 `large`），可能进一步改善结果。我们将这留作读者的练习。

## 分享您的模型

您现在可以使用 Hub 上的链接与任何人共享此模型。大家可以直接将标识符 `"您的用户名/您给模型起的名字"` 加载到 `pipeline()` 对象中。
例如，要加载微调的检查点 ["sanchit-gandhi/whisper-small-dv"](https://huggingface.co/sanchit-gandhi/whisper-small-dv)：

```python
from transformers import pipeline

pipe = pipeline("automatic-speech-recognition", model="sanchit-gandhi/whisper-small-dv")
```

## 结论

在本节中，我们逐步介绍了如何使用 🤗 Datasets、Transformers 和 Hugging Face Hub 微调 Whisper 模型以进行语音识别。
我们首先加载了 Common Voice 13 数据集的迪维希语子集，并通过计算 log-mel 频谱图和分词文本对其进行了预处理。然后我们定义了数据整理器、评估指标和训练参数，
使用 🤗 Trainer 来训练和评估我们的模型。我们将微调的模型上传到 Hugging Face Hub，并展示了如何使用 `pipeline()` 类共享和使用它。

如果您一直学到了这里，您现在应该有了一个用于语音识别的微调检查点，干得好！🥳 更重要的是，您现在掌握了所有必要的工具，可以在任何语音识别数据集或领域上微调 Whisper 模型。
那么，您还在等什么呢！从 [选择数据集](choosing_dataset) 章节中介绍的数据集里挑一个，或选择您自己的数据集，看看您是否可以获得 SOTA！排行榜在等着您……


<EditOnGithub source="https://github.com/huggingface/audio-transformers-course/blob/main/chapters/zh-CN/chapter5/fine-tuning.mdx" />

### 实战练习
https://huggingface.co/learn/audio-course/zh-CN/chapter5/hands_on.md

# 实战练习

在本单元中，我们探讨了微调 ASR 模型的挑战，验证了在新语言上微调像 Whisper（即使是小型检查点）模型需要不少时间和资源。
为了提供实践经验，我们设计了一个练习，让您能够在使用较小数据集的情况下，体验微调 ASR 模型的过程。
这个练习的主要目的是让您熟悉过程，而不是取得生产级别的结果。我们故意设置了一个低指标，以确保即使在资源有限的情况下，您也应该能够达到它。

以下是指导说明：

* 使用 `"PolyAI/minds14"` 数据集的美式英语（"en-US"）子集，微调 `"openai/whisper-tiny"` 模型。
* 使用 **前450 个样本**进行训练，其余的用于评估。确保在使用 `.map` 方法预处理数据集时设置 `num_proc=1`（这将确保您的模型正确提交以进行评估）。
* 评估模型时，使用本单元描述的 `wer` 和 `wer_ortho` 指标。但是，*不要* 通过乘以 100 将指标转换为百分比（例如，如果 WER 是 42%，我们将期望在此练习中看到 0.42 的值）。

您微调了一个模型后，请使用以下 `kwargs` 将其上传到 🤗 Hub：

```
kwargs = {
    "dataset_tags": "PolyAI/minds14",
    "finetuned_from": "openai/whisper-tiny",
    "tasks": "automatic-speech-recognition",
}
```

如果您的模型的标准化 WER（`wer`）低于 **0.37**，您将通过这个任务。

请随意构建您的模型 demo，并在 Discord 上分享！如果您有任何问题，欢迎在 #audio-study-group 频道发布。


<EditOnGithub source="https://github.com/huggingface/audio-transformers-course/blob/main/chapters/zh-CN/chapter5/hands_on.mdx" />

### CTC结构
https://huggingface.co/learn/audio-course/zh-CN/chapter3/ctc.md

# CTC结构

CTC结构（Connectionist Temporal Classification）是一种仅使用Transformer编码器（encoder）结构的语音识别（ASR）模型。使用该架构的模型包括Wav2Vec2、HuBERT、M-CTC-T等等。

仅含编码器的Transformer（encoder-only Transformer）是最简单的Transformer，因为它只使用模型的编码器部分。编码器读取输入序列（音频波形）并将其映射到隐藏状态序列（sequence of hidden-states），也称为输出嵌入（output embedding）。

使用CTC模型时，我们对隐藏状态序列进行额外的线性映射以获得类标签预测。类标签为**字母表中的字母**（a、b、c，...）。这样，我们就能够使用一个很小的分类头来预测目标语言中的任何单词，因为词汇表（vocabulary）只需要包含26个字符加上一些特殊的标记。

<div class="flex justify-center">
    <img src="https://huggingface.co/datasets/huggingface-course/audio-course-images/resolve/main/wav2vec2-ctc.png" alt="Transformer encoder with a CTC head on top">
</div>

到目前为止，这与我们在NLP中使用BERT模型的做法非常相似：仅编码器的Transformer模型将文本标记映射到一系列编码器隐藏状态，然后我们应用线性映射，为每个隐藏状态预测一个类标签。

棘手的问题在于：在语音识别中，我们不知道音频输入和文本输出的**对齐方式**（alignment）。我们知道语音的顺序与文本的转录顺序相同（对齐是单调的），但我们不知道转录文本中的字符如何与音频对齐。这就是CTC算法的用武之地。


<Tip>
💡 在NLP模型中，词汇表（vocabulary，也称字典）通常由数千个标记组成，这些标记不仅描述单个字符，还描述单词的部分甚至整个单词。然而对于CTC模型，较小的词汇表效果最好，我们一般会将其保持在50个字符以下。我们不关心字母的大小写，因此仅使用大写（或仅使用小写）就足够了。数字则被拼写为单词，例如`"20"`变成`"twenty"`。除了字母，我们还需要至少一个单词分隔符标记（空格，space）和一个填充标记（padding token）。与NLP模型一样，填充标记允许我们将多个示例组合成批次，但它也是模型将预测的沉默标记。在英语中，保留`'`字符也很有用——毕竟，`"it's"`和`"its"`的含义完全不同。
</Tip>

## 我们到底该怎么对齐呢？

自动语音识别（ASR）涉及将音频作为输入并产生文本作为输出。我们有几种选择来预测文本：

- 预测单个字母
- 预测音素（phoneme）
- 预测单词标记（word token）

ASR模型会使用含有`(音频, 文字)`对的数据集进行训练，这些数据集的文字信息通常是人工转录的。通常，数据集不包含任何时间信息，也就是说，我们不知道输入和输出序列应该如何对齐。

假设我们有一个长度为一秒钟的音频文件。在**Wav2Vec2**中，模型首先使用CNN特征编码器对音频输入进行下采样，将其映射到较短的隐藏状态序列，其中每20毫秒的音频会对应一个隐藏状态向量（hidden-states vector）。对于一秒的音频，我们将生成的隐藏状态序列传递给Transformer编码器。 （从输入序列中提取的音频片段会有部分重叠，因此即使每20毫秒会生成一个隐藏状态向量，但每个向量实际上包含了25毫秒的音频信息。）

Transformer的编码器会对每个隐藏状态向量产生一个输出预测，因此我们从Transformer中得到一个长度同样为50的输出向量序列。该序列中的每个向量的维度为768. 因此，该示例中Transformer编码器的输出序列的形状为`(768, 50)`。每个预测值包含了25毫秒的音频信息，而音素（phoneme）通常会持续超过25毫秒。因此，我们这里预测音素的效果会好于预测整个单词。CTC在小词汇表上的效果更好，因此我们将预测字母。

<div class="flex justify-center">
    <img src="https://huggingface.co/datasets/huggingface-course/audio-course-images/resolve/main/cnn-feature-encoder.png" alt="The audio waveform gets mapped to a shorter sequence of hidden-states">
</div>

为了进行文本预测，我们使用一个线性层（"CTC头"）将768维的编码器输出映射到我们的字符标签。然后，模型会预测一个`(50, 32)`的张量，其中32是词汇表中的标记数。由于我们对序列中的每个特征都进行了一次预测，因此每秒音频会有50个字符预测。


然而，如果我们简单地每20毫秒预测一个字母，我们的预测结果可能会是这样的：


```text
BRIIONSAWWSOMEETHINGCLOSETOPANICONHHISOPPONENT'SSFAACEWHENTHEMANNFINALLLYRREECOGGNNIIZEDHHISSERRRRORR ...
```

仔细观察这个输出，我们可以发现它看起来有一点像英语，但有很多重复的字母。这是因为我们的模型*必须*每20毫秒都输出一些东西，而如果某个字母的持续时间超过了20毫秒，模型就会输出重复的字母。我们无法避免这种情况，特别是在训练时我们不知道转录文本的时间信息。而CTC就是一个帮助我们过滤重复字母的方法。

（在实际操作中，预测的序列还有可能包含很多填充标记（padding token），用于表示模型不太确定音频表示的是什么，或者用于表示字符之间的空白。为了清晰起见，我们从示例中删除了这些填充标记。音频片段之间的部分重叠也是字符重复的另一个原因。）


## CTC算法

CTC算法的关键在于使用一个特殊的标记，通常称为**空白标记**（blank token）。这是一个我们人为加入词汇表的额外标记。在这个例子中，空白标记被表示为`_`。我们用这个特殊的标记来表示字母组之间的硬边界。

CTC模型的完整输出类似于如下的序列：


```text
B_R_II_O_N_||_S_AWW_|||||_S_OMEE_TH_ING_||_C_L_O_S_E||TO|_P_A_N_I_C_||_ON||HHI_S||_OP_P_O_N_EN_T_'SS||_F_AA_C_E||_W_H_EN||THE||M_A_NN_||||_F_I_N_AL_LL_Y||||_RREE_C_O_GG_NN_II_Z_ED|||HHISS|||_ER_RRR_ORR||||
```

该序列中的`|`标记是单词分隔符。在这个例子中，我们使用`|`而不是空格作为单词分隔符，这样可以更容易地看出单词的分隔位置，但它们的作用是一样的。

CTC空白标记使我们能够过滤掉重复的字母。例如预测序列中的最后一个单词，`_ER_RRR_ORR`。如果没有CTC空白标记，这个单词看起来是这样的：

```text
ERRRRORR
```

如果我们简单地去掉非CTC结果中的重复字符，那么它就变成了`EROR`。显然这不是正确的拼写。但是有了CTC空白标记，我们就可以在每个字母组中去掉重复的字母：


```text
_ER_RRR_ORR
```

变为：

```text
_ER_R_OR
```

最后我们去掉空白标记`_`，得到最终的单词：

```text
ERROR
```

如果我们将这种逻辑应用到整个文本，包括`|`，并将剩余的`|`字符替换为空格，那么最终的CTC解码输出会变成：


```text
BRION SAW SOMETHING CLOSE TO PANIC ON HIS OPPONENT'S FACE WHEN THE MAN FINALLY RECOGNIZED HIS ERROR
```

总结一下，CTC模型对应每20毫秒的输入音频（包括部分重叠）会生成一个预测标记。这样的预测规则会生成很多重复的字母。利用CTC空白标记，我们可以轻松地移除这些重复的字母，而不会破坏单词的正确拼写。这是一种非常简单和方便的方法，可以解决输出文本与输入音频的对齐问题。

<Tip>
💡 在实际的Wav2Vec2模型中，CTC空白标记与填充标记`<pad>`是相同的。模型会预测很多这样的`<pad>`标记，例如当当前20毫秒的音频没有明确的字符可以预测时。使用相同的标记作为填充和CTC空白标记可以简化解码算法，并有助于保持词汇表的小规模。
</Tip>

我们可以在Transomer编码模型简单地加入CTC：将编码器的输出序列进入一个线性层，该线性层将音频特征映射到词汇表。模型使用特殊的CTC损失进行训练。

CTC的一个缺点在于，它可能会输出*听起来*正确但*拼写*不正确的单词。毕竟，CTC分类头只考虑了单个字符，而没有处理整个单词。我们可以使用额外的语言模型来提高音频的转录质量。这个语言模型实际上是作为了CTC输出的拼写检查器。


## Wav2Vec2, HuBERT, M-CTC-T等模型有什么区别？

所有基于Transformer的CTC模型的架构都非常相似：它们都使用Transformer编码器（但不使用解码器），并在最后使用一个CTC分类头。从架构上来说，它们的相似程度大于他们的不同程度。

Wav2Vec2和M-CTC-T之间的一个区别在于，前者使用原始音频波形，而后者使用梅尔时频谱（mel spectrogram）作为输入。这些模型还是为不同的目的而训练的。例如，M-CTC-T是为多语言语音识别而训练的，因此它的CTC头相对较大，包含了中文字符以及其他字母。

Wav2Vec2和HuBERT使用了完全相同的架构，但训练方式有很大的区别。Wav2Vec2的训练方式类似于BERT的掩码语言模型（masked language modeling），通过预测音频中被遮盖（mask）的音素来进行预训练。HuBERT则更进一步，学习预测“离散语音单元”（discrete speech unit），这类似于文本句子中的标记，因此可以使用已有的NLP技术来处理语音。

最后，我们在这里介绍的模型并非全部的Transformer CTC模型。还有很多其他的模型，但现在我们知道他们都使用了类似的原理。


<EditOnGithub source="https://github.com/huggingface/audio-transformers-course/blob/main/chapters/zh-CN/chapter3/ctc.mdx" />

### 测测你对课程内容的理解
https://huggingface.co/learn/audio-course/zh-CN/chapter3/quiz.md

# 测测你对课程内容的理解

### 1. 什么是声码器（vocoder）？

<Question
	choices={[
		{
			text: "将Transformer输出的时频谱转化为声音波形的外挂神经网络。",
			explain: "正确！",
			correct: true
		},
		{
			text: "生成音频嵌入序列的一种Transformer层。",
			explain: ""
		},
		{
			text: "对声音进行降噪预处理的外挂神经网络。",
			explain: "",
		}
	]}
/>

### 2. Wav2Vec2模型采用了什么结构？

<Question
	choices={[
		{
			text: "Seq2Seq结构",
			explain: ""
		},
		{
			text: "CNN结构",
			explain: ""
		},
		{
			text: "CTC结构",
			explain: "正确！",
			correct: true
		}
	]}
/>

### 3. CTC算法中的空白标记（blank token）有什么作用？

<Question
	choices={[
		{
			text: "空白标记可以表示句子中不同单词之间的分隔。",
			explain: ""
		},
		{
			text: "空白标记表示的是字母组之间的硬边界的预测标记。它可以帮助我们对重复字母进行过滤。",
			explain: "正确！",
			correct: true
		},
		{
			text: "空白标记表示不对应字母表中任一词素的声音，类似于“未知”标记<UNK>。",
			explain: ""
		}
	]}
/>

### 4. 下列关于CTC模型的选项中哪一项是**错误**的？ 

<Question
	choices={[
		{
			text: "CTC模型仅使用的Transformer结构中的编码器（encoder）。",
			explain: ""
		},
		{
			text: "Wav2Vec2和HuBERT使用了完全相同的结构，但训练方法不同。",
			explain: ""
		},
		{
			text: "CTC模型在语音识别任务上的准确率高于其他任何模型。",
			explain: "正确！",
			correct: true
		}
	]}
/>

### 5. Whisper模型使用了什么结构？

<Question
	choices={[
		{
			text: "Seq2Seq结构",
			explain: "正确！",
			correct: true
		},
		{
			text: "CNN结构",
			explain: ""
		},
		{
			text: "CTC结构",
			explain: ""
		}
	]}
/>

### 6. 解决音频分类任务的最简单方法是？

<Question
	choices={[
		{
			text: "在音频波形上使用编码器-解码器结构。",
			explain: ""
		},
		{
			text: "使用时频谱并将其视为图像分类任务。",
			explain: "正确！",
			correct: true
		},
		{
			text: "将CTC模型改造为一个音频分类器，改变其分类标签并使用常规的交叉熵损失函数（regular cross-entropy loss function）进行训练。",
			explain: ""
		}
	]}
/>

### 7. 以下陈述是否正确：当我们在时频谱上应用图像分类技巧时，我们可以使用常见的图像数据增强（Image data augmentation）方法，例如图像平移、裁切或改变大小，来提高模型的性能。

<Question
	choices={[
		{
			text: "正确",
			explain: ""
		},
		{
			text: "错误",
			explain: "正确！",
			correct: true
		}
	]}
/>


<EditOnGithub source="https://github.com/huggingface/audio-transformers-course/blob/main/chapters/zh-CN/chapter3/quiz.mdx" />

### 第3单元：音频Transformer架构
https://huggingface.co/learn/audio-course/zh-CN/chapter3/introduction.md

# 第3单元：音频Transformer架构

本课程中，我们主要关注Transformer模型以及它们如何应用于音频任务。虽然您不需要了解这些模型的内部细节，但了解使它们工作的主要概念很有用，因此我们在本小节中回顾一下关于Transformer的知识。有关transformer的深入了解，请查看我们的[NLP课程](https://huggingface.co/course/chapter1/1)。

## Transformer的原理是什么？

Transformer架构最早是为文本翻译的任务而设计的。它的结构如下：

<div class="flex justify-center">
    <img src="https://huggingface.co/datasets/huggingface-course/documentation-images/resolve/main/en/chapter1/transformers.svg" alt="Original transformer architecture">
</div>

左边是**编码器**（encoder），右边是**解码器**（decoder）。

- 编码器负责接收模型的输入，本例中是一系列文本标记(toen)，并构建其表征（representation，或称特征，feature）。Transformer模型的编码器在经过训练后能够从输入中提取并理解信息。

- 解码器负责使用编码器生成的表征（特征向量）以及其他输入（先前预测的标记）来生成目标序列。Transformer模型的解码器在经过训练能够根据表征中蕴含的信息生成输出序列。在原始设计中，输出序列由文本标记组成。

有的Transformer模型只使用编码器（适用于需要理解输入的任务，例如分类），或者只使用解码器（适用于生成文本的任务）。BERT就是一个只使用编码器的例子；GPT2则是一个只使用解码器的例子。

Transformer模型的一个关键特征是它们使用特殊的层，称为**注意力层**（attention layers）。这些层告诉模型在计算特征表示时，特别关注输入序列中的某些元素，而忽略其他元素。

## 使用Transformer进行音频处理

本课程中，我们将介绍的音频模型通常具有上图所示的标准Transformer架构，但会在输入或输出端稍作修改来适应音频而非文本序列。由于所有这些模型本质上都是Transformer，因此它们的大部分架构都是相同的，主要区别在于它们的训练和使用方式。

<div class="flex justify-center">
    <img src="https://huggingface.co/datasets/huggingface-course/audio-course-images/resolve/main/transformers_blocks.png" alt="The transformer with audio input and output">
</div>

对于音频任务，输入和/或输出序列可以是音频而非文本：

- 自动语音识别（ASR）：输入为语音，输出为文本。
- 文本到语音（TTS）：输入为文本，输出为语音。
- 语音分类：输入为语音，输出为类别概率——对于序列中的每个元素，或者对于整个序列的单个类别概率。
- 语音转换或语音增强：输入和输出均为语音。

有几种不同的方法可以将音频数据转换为Transformer能够处理的格式。主要的区别在于是使用音频的原始波形作为模型的输入序列，还是使用其时频谱作为输入序列。

## 模型的输入格式

音频模型的输入可以是文本或声音。我们的目标是将此输入转换为一个嵌入向量（embedding vector），以便Transformer架构可以处理。

### 文本输入

文本到语音模型（TTS）接受文本作为输入。这与原始Transformer或任何其他NLP模型的工作方式相同：首先对文本进行标记化（tokenization），得到一系列文本标记。然后将此序列通过输入嵌入层，将标记转换为512维向量。然后将这些嵌入向量传递到Transformer编码器中。

### 波形输入

自动语音识别模型（ASR）接受语音作为输入。为了使用Transformer解决ASR任务，我们首先需要以某种方式将音频转换为嵌入向量序列。

**Wav2Vec2**和**HuBERT**一类的模型直接使用音频波形作为模型的输入。如您在[音频数据章节](chapter1/introduction)中所见，波形是一个浮点数序列，其中的每个数表示某个时间点的幅值。我们首先将原始波形标准化为零均值和单位方差的序列，这有助于标准化不同音量（振幅）的音频样本。

<div class="flex justify-center">
    <img src="https://huggingface.co/datasets/huggingface-course/audio-course-images/resolve/main/wav2vec2-input.png" alt="Wav2Vec2 uses a CNN to create embeddings from the input waveform">
</div>

完成标准化后，我们将该波形序列被转换为嵌入向量序列。这是通过一个小型卷积神经网络（CNN）完成的，称为**特征编码器**（feature encoder）。该网络的每个卷积层都处理输入序列，对音频进行下采样（subsampling）以减少序列长度，直到最后一个卷积层输出512维向量，即每25毫秒音频的嵌入向量（embedding vector）。当输入序列被转换为这样的嵌入向量序列（embedding vector sequence）后，Transformer便可以像往常一样处理数据了。

### 时频谱输入

使用原始波形作为输入的一个缺点是它们往往具有较长的序列长度。例如，采样率为16 kHz的30秒音频输入的长度为`30 * 16000 = 480000`。长的序列需要在Transformer模型中进行更多的计算，因此会增加内存使用量。

因此，我们认为在大多数情况下原始音频并不是最有效率的表示音频输入的方式。通过使用**时频谱**（spectrogram），我们可以以更紧凑的形式表示相同数量的信息。

<div class="flex justify-center">
    <img src="https://huggingface.co/datasets/huggingface-course/audio-course-images/resolve/main/whisper-input.png" alt="Whisper uses a CNN to create embeddings from the input spectrogram">
</div>

**Whisper**等模型首先将波形转换为一个对数梅尔频谱图（log-mel spectrogram）。Whisper会将音频分成数个30秒的片段，每个片段的对数梅尔频谱图的形状为`(80, 3000)`，其中80是梅尔频率的数量，3000是序列的长度。通过将音频转换为对数梅尔频谱图，我们不仅减少了输入数据的数量，而且更重要的是，转换后的序列长度远远小于波形的长度。。最后，对数梅尔频谱图由一个小型CNN处理，转换为嵌入向量序列，然后像往常一样输入Transformer。

无论是波形还是时频谱输入，我们都会先使用一个小型的神经网络在Transformer之前将输入转换为嵌入向量序列，然后由Transformer接管并完成其工作。

## 模型的输出格式

Transformer架构会输出一个隐藏状态向量（hidden-state vectors）的序列，也称为输出嵌入向量（output embedding）。我们的目标是将这些向量转换为文本或音频输出。

### 文本输出

自动语音识别模型的目标是预测一个文本标记（text token）的序列。这是通过在Transformer的输出上添加一个语言建模（language modeling）头——通常是一个单独的线性层———然后在transformer的输出上添加一个softmax来完成的。该输出预测的是词汇表中文本标记的概率。

### 时频谱输出

对于生成音频的模型，例如文本到语音（TTS）模型，我们需要添加能够生成音频序列的层。通常会生成一个频谱图，然后使用另一个神经网络（称为**声码器**（vocoder））将该频谱图转换为波形。

举个例子，**SpeechT5** TTS模型的输出是一个768元素向量的序列。线性层将该序列投影到一个对数梅尔频谱图上。然后，一个由线性层和卷积层组成的后处理网络（post-net）会对频谱图进行降噪处理。最后，我们使用声码器生成输出的音频波形。

<div class="flex justify-center">
    <img src="https://huggingface.co/datasets/huggingface-course/audio-course-images/resolve/main/speecht5.png" alt="SpeechT5 outputs a spectrogram and uses a vocoder to create the waveform">
</div>

<Tip>
💡如果我们对波形进行短时傅里叶变换（STFT）得到其时频谱，我们可以通过其逆向过程（ISTFT）重新得到原始的波形。这是因为STFT生成的时频谱包含了幅值和相位两部分的信息，而这两部分信息都是重建波形所必需的。然而，音频深度学习模型所生成的时频谱往往仅包含了幅值信息。为了将这样的时频谱转化为波形，我们需要通过某种方式估计其相位信息，这便是声码器的功能。
</Tip>

### 波形输出

除了生成时频谱作为中间输出的方法外，有些模型也可以直接生成波形输出。但是，目前🤗 Transformers中没有包含能够直接生成波形的模型。

## 小节

总结一下：大多数音频Transformer模型都是相似的——它们都是基于相同的Transformer架构和注意力层构建的。不过，有些模型只使用Transformer的编码器部分，而有些模型则同时使用编码器和解码器。

我们也学习了如何在Transformer模型中将音频作为输入或者输出。在ASR、TTS等音频任务中，我们可以改变模型中的部分输入层来将数据转换为嵌入向量序列，也可以改变一部分输出层来将生成的嵌入向量序列转化为输出格式，而Transformer的主体部分保持不变。

接下来，我们将学习几种不同的训练方法来训练一个自动语音识别模型。

<EditOnGithub source="https://github.com/huggingface/audio-transformers-course/blob/main/chapters/zh-CN/chapter3/introduction.mdx" />

### 补充阅读和资源
https://huggingface.co/learn/audio-course/zh-CN/chapter3/supplemental_reading.md

# 补充阅读和资源

如果你想进一步了解不同的Transformer结构并学习它们在语音处理中的各种应用，请查看这篇最近的论文：

### Transformers in Speech Processing: A Survey 

作者：Siddique Latif, Aun Zaidi, Heriberto Cuayahuitl, Fahad Shamshad, Moazzam Shoukat, Junaid Qadir

"Transformer在自然语言处理领域的显著成功引起了语音处理界的兴趣，从而探索了它们在语音序列中建模长距离依赖性的潜力。最近，Transformer在各种与语音相关的领域中获得了突出的地位，包括自动语音识别、语音合成、语音翻译、语音语言学、语音增强、口语对话系统和众多多模态应用。在本文中，我们提出了一份综合调查报告，旨在桥接语音技术中不同子领域的研究成果。通过整合语音技术领域的研究成果，我们为那些有兴趣利用Transformer的力量推动该领域发展的研究人员提供了宝贵的资源。我们指出了Transformer在语音处理中遇到的挑战，同时也提供了解决这些问题的潜在解决方案。"

[arxiv.org/abs/2303.11607](https://arxiv.org/abs/2303.11607)


<EditOnGithub source="https://github.com/huggingface/audio-transformers-course/blob/main/chapters/zh-CN/chapter3/supplemental_reading.mdx" />

### 音频分类结构
https://huggingface.co/learn/audio-course/zh-CN/chapter3/classification.md

# 音频分类结构

音频分类任务的目标是预测音频输出的分类标签。分类模型可以为整个输入序列预测一个标签，也可以为每一帧预测一个不同的标签。在分帧预测时，模型通常为每20毫秒的输入音频预测一个标签，并生成一个由分类标签概率分布组成的序列。预测单一标签的例子如预测音频中发出声音的鸟的种类；预测分帧标签的例子如说话人识别，每帧都可能由不同的说话人在发出声音。

## 使用时频谱进行分类

最简单的音频分类方法之一是将其视为一个图像分类问题！

回想一下，时频谱是一个形状为`（频率，序列长度）`的二维张量。在[音频数据章节](chapter1/audio_data)中，我们学习过将这些时频谱绘制为图像。没错，我们可以直接将时频谱视为图像，并将其传递给一个常见的CNN分类器模型，如ResNet，并获得非常好的预测结果。更好的是，也可以使用图像Transformer模型，如ViT。

这就是**AST**（Audio Spectrogram Transformer）的作用。它使用ViT（即Vision Transformer）模型，并使用时频谱而非常规图像作为输入。由于Transformer的自注意层，该模型能够比CNN更好地捕获全局上下文。

与ViT一样，AST模型将音频时频谱分成16×16像素的部分重叠的图像块（image patch）序列。然后，我们将此序列中的块投影到嵌入序列中，并像往常一样将其作为输入传递给Transformer编码器。AST是一个仅含编码器的Transformer模型，因此输出是一个隐藏状态序列，每个16×16输入块对应一个隐藏状态。最后是一个含有sigmoid激活函数的简单分类层，将隐藏状态映射为分类概率。

<div class="flex justify-center">
    <img src="https://huggingface.co/datasets/huggingface-course/audio-course-images/resolve/main/ast.png" alt="The audio spectrogram transformer works on a sequence of patches taken from the spectrogram">
</div>

图像来自论文[AST: Audio Spectrogram Transformer](https://arxiv.org/pdf/2104.01778.pdf)

<Tip>
💡 尽管在这里我们假装时频谱与图像相同，但它们之间其实有重要的区别。例如，将图像的内容上下平移通常不会改变图像中的内容的含义。然而，将时频谱上下平移将改变声音中的频率，并完全改变其特性。图像具有一定的上下和左右平移不变性（shift-invariance），但时频谱仅具有一定的左右平移不变性，而几乎完全不具有上下平移不变性。尽管在实践中，将时频谱视为图像可以得到很好的效果，但请记住它们在原理上完全不同。
</Tip>

## 任何Transformer都可以是分类器

在[CTC](ctc)一节中，我们了解到CTC是一种使用仅含编码器的Transformer来执行自动语音识别的有效技术。CTC模型实际上已经可以被视为分类器，因为其预测的是词汇表中每个词素的概率。我们可以通过更改标签并使用常规的交叉熵损失函数代替特殊的CTC损失来将CTC模型转换为通用的音频分类器。

举个例子，HF Transformers库中包含了`Wav2Vec2ForCTC`模型，但提供了`Wav2Vec2ForSequenceClassification`和`Wav2Vec2ForAudioFrameClassification`。这些模型之间的唯一区别是分类层的大小和所使用的损失函数。

实际上，任何仅含编码器的音频Transformer模型都可以通过在隐藏状态序列之上添加分类层来转换为音频分类器。分类任务通常不需要Transformer解码器。

如果需要对整个序列输出一个单独的分类标签（`Wav2Vec2ForSequenceClassification`），模型会对隐藏状态序列沿其长度取平均值，并将其输送到分类层。这样我们就可以对整个序列输出一个概率分布，而不是输出序列中每个元素的概率分布。

如果需要对每一帧输出一个分类标签（`Wav2Vec2ForAudioFrameClassification`），我们就让分类器对隐藏状态序列中的每个元素都输出一个概率分布，这样我们的输出会成为一个具有同样长度的概率分布序列。

<EditOnGithub source="https://github.com/huggingface/audio-transformers-course/blob/main/chapters/zh-CN/chapter3/classification.mdx" />

### Seq2Seq结构
https://huggingface.co/learn/audio-course/zh-CN/chapter3/seq2seq.md

# Seq2Seq结构

上一小节中介绍的CTC结构仅适用于Tranformer结构的编码器（encoder）部分。如果我们给模型添加一个解码器（decoder）构建一个自编码模型（autoencoder，也称编码-解码模型，encoder-decoder），这个模型也可以被称为**序列到序列**模型（sequence-to-sequence，简称seq2seq）。Seq2Seq模型将一种数据序列映射到另一种数据序列。

在仅使用编码器的Transformer模型中，编码器为输入序列中的每个元素进行预测。因此，输入和输出序列的长度总是相同的。在CTC模型（如Wav2Vec2）的情况下，输入波形会被下采样，但我们仍然对下采样后的每个元素（对应20毫秒的音频）输出一个对应的预测标记，因此输入标记和输出的预测标记仍然是等长的。

而在seq2seq模型中，输入和输出序列的长度可以不同。这使得seq2seq模型更适合文本摘要或翻译等的NLP任务，也同时适用于语音识别等音频任务。

Seq2Seq结构中的解码器（decoder）和编码器（encoder）非常类似，两者都使用自注意力（self-attention）作为主要的运算单元。但是解码器的任务与编码器不同。为了理解这一点，让我们看看seq2seq模型如何进行自动语音识别。

## 自动语音识别

下图展示了**Whisper**模型的结构（图片来自[OpenAI Whisper博客](https://openai.com/blog/whisper/)）：


<div class="flex justify-center">
    <img src="https://huggingface.co/blog/assets/111_fine_tune_whisper/whisper_architecture.svg" alt="Whisper is a transformer encoder-decoder model">
</div>

看起来十分熟悉。左边是**Transformer编码器**。它以对数梅尔时频谱（log-mel spectrogram）作为输入，并对其进行编码，输出一个编码器隐藏状态序列（sequence of encoder hidden states），从中提取出输入语音的重要特征。这个隐藏状态张量代表了整个输入序列，并高效地编码了输入语音的“含义”。

<Tip>
💡 这些seq2seq模型通常使用时频谱作为输入。不过，seq2seq模型也可以直接使用音频波形作为输入。
</Tip>

编码器的输出被传递到右边的**Transformer解码器**，使用一种称为**交叉注意力**（cross-attention）的机制。交叉注意力与自注意力类似，不过此处注意力的对象是编码器的输出。在这之后，我们就不再需要编码器了。

解码器会使用**自回归**（autoregressive）的方式来预测一个文本标记序列，每次只预测一个标记，从一个仅含有“起始”标记（Whisper使用`SOT`作为起始标记）的序列开始。在每个时间步，前一个时间步的输出序列会作为新的输入序列被传递给解码器。这样，解码器每次只会输出一个新的标记，逐步增长输出序列，直到预测出“结束”标记，或者达到最大时间步数。

虽然解码器的结构与编码器类似，但是解码器和编码器有两个主要的区别：

1. 解码器会使用交叉注意力机制，允许它查看编码器生成的对于输入序列的隐藏状态向量。
2. 解码器的注意力是**因果**（causal）的——解码器不允许查看未来的信息。

在这个架构中，解码器的功能类似于**语言模型**（language model），它处理编码器的隐藏状态表示，并生成相应的文本转录。这种方法比CTC更强大，甚至CTC结合外部语言模型也无法相比。Seq2Seq结构可以使用相同的训练数据和损失函数进行端到端（end-to-end）训练，从而提供更好的灵活性和更好的性能。

<Tip>
💡 与CTC模型输出单个字母的序列不同，Whisper模型的输出是完整的单词或词素。它使用GPT-2的分词器，有50k+个不同的标记。因此，seq2seq模型可以输出比CTC模型更短的序列。
</Tip>

Seq2Seq语音识别模型最常使用的损失函数为交叉熵损失（cross-entropy loss），因为我们通常把模型最终层的输出视为一个标记的概率分布。交叉熵损失通常与[束搜索（beam search）](https://huggingface.co/blog/how-to-generate)等技术结合使用，生成最终的序列。语音识别的度量标准是WER（word error rate），它衡量将预测文本转换为目标文本所需的替换、插入和删除的数量。所需的操作越少说明预测结果越好。

## 文本到语音（Text-to-speech，TTS）

可能不会让你感到惊讶的是， Seq2Seq的TTS模型基本上和ASR模型相同，仅仅是将输入和输出的数据种类互换！Transformer编码器接收文本标记序列，并提取表示输入文本的隐藏状态序列。Transformer解码器使用交叉注意力机制，预测输出的时频谱序列。

<Tip>
💡 时频谱图是通过对音频波形的连续时间切片进行频谱分析得到的。换句话说，时频谱图是一个序列，其中的元素是（对数梅尔）频谱，每个时间步一个。
</Tip>

在ASR模型里，解码器的初始输出序列是一个仅包含“起始”标记的序列。而在TTS模型里，我们可以使用一个长度为1，值全为0的时频谱序列来代替“起始”标记。有了这个初始时频谱序列和编码器隐藏状态的交叉注意力，解码器就可以预测下一个时间步的时频谱，逐步增长时频谱序列。


<div class="flex justify-center">
    <img src="https://huggingface.co/datasets/huggingface-course/audio-course-images/resolve/main/speecht5_decoding.png" alt="The audio waveform gets mapped to a shorter sequence of hidden-states">
</div>

但我们的解码器怎样知道需要在什么时候停止呢？在**SpeechT5**模型中，解码器会预测第二个序列。这个序列包含当前时间步是否是最后一个时间步的概率。在推理时，如果这个概率超过某个阈值（比如0.5），我们就认为解码器在指示时频谱序列已经生成完毕，应该结束生成循环。

在解码器生成完整的时频谱序列后，SpeechT5会使用一个称为**后处理网络**（post-net）的模型对时频谱序列进行微调。后处理网络由几个卷积层组成，用于微调时频谱序列。

在训练TTS模型时，我们的生成目标是训练数据的时频谱图，损失函数为L1损失或最小二乘损失（mean square error，MSE）。在推理时，我们希望将输出的时频谱图转换为音频波形，这样我们就可以听到它。为此，我们需要使用一个外部模型，称为**声码器**（vocoder）。这个声码器不是seq2seq架构的一部分，而是单独训练的。

TTS任务的难点在于其一对多的映射。对于语音到文本的任务，每个输入语音只对应一个正确的输出文本，但是对于文本到语音的任务，输入文本可以映射到多个可能的语音。例如，不同的说话人可能会强调同一个句子中的不同部分。这使得TTS模型很难评估。因为这个原因，L1或MSE损失实际上并不是理想的损失函数，因为同一个文本可以映射到多种不同的时频谱序列。这就是为什么TTS模型通常会由人类听众进行评估的原因，使用的指标称为MOS（mean opinion score，平均意见分数）。

## 总结

Seq2Seq结构是一种强大的模型，其能力往往优于仅含编码器的模型。通过将输入序列的编码与输出序列的解码分离，我们可以很好地处理音频和文本的对齐问题。<!-- 模型中的交叉注意力单元会解决输入和输出间的对齐问题。 -->

然而，编码器-解码器模型也存在一些缺点，比较重要的一点是其推理速度较慢。这是由于其解码过程在每一步仅生成一个输出元素，因此完整的预测需要进行数次推理。输出序列越长，推理的速度也就越慢。自回归模型也可能会陷入循环或者跳过一些元素。束搜索（beam search）等技术可以提高预测结果的质量，但也会进一步降低推理速度。

<EditOnGithub source="https://github.com/huggingface/audio-transformers-course/blob/main/chapters/zh-CN/chapter3/seq2seq.mdx" />

### 欢迎来到Hugging Face 音频课程！
https://huggingface.co/learn/audio-course/zh-CN/chapter0/introduction.md

# 欢迎来到Hugging Face 音频课程！

屏幕前的同学，

欢迎来到本课！我们将为你介绍Transformer模型在音频领域的应用。Transformer模型已经多次证明了自己是最强大、多功能的深度学习模型之一，并在自然语言处理（Natural Language Processing, NLP）、机器视觉（Computer Vision, CV）、和最近的音频处理等等任务上取得了最佳成绩。

在本课中，我们会学习如何将Trasnfromer结构应用到音频数据上。你会学到如何使用Transformer解决各种各样与音频有关的任务。无论你是对自动语音识别（Automatic Speech Recognition, ASR）、音频分类（Audio CLassification）、或是由文字生成语音（Text-to-Speech synthesis， TTS）感兴趣，本课和Transformer都会成为你的得力助手。

让我们直观地感受一下这些模型的威力吧！在下面的demo里随便说些什么，然后看看我们的模型是如何实时地将语音记录成文字的（仅支持英语）：

<iframe
	src="https://openai-whisper.hf.space"
	frameborder="0"
	width="850"
	height="450">
</iframe>

在本课中，你会学习到针对音频数据的特殊处理方法、各种不同的Transformer架构、以及使用强大的预训练模型来训练你自己的音频Transformer。

本课是为有一定深度学习基础，并对Transformer有一些初步了解的同学而设计。你不需要对音频数据处理有任何经验。如果你需要加强自己对Transformer结构的基础了解，请参考我们的[自然语言处理课程](https://huggingface.co/learn/nlp-course/zh-CN/chapter1/1)。这门课程包含了Transformer结构的基础知识和更多细节。

## 关于作者

**Sanchit Gandhi, Hugging Face机器学习科研工程师**

嗨! 我是Sanchit，来自Hugging Face🤗开源组的的音频机器学习研发工程师。我的主要方向是自动语音识别和（automatic speech recognition,ASR）和自动语音翻译。我现在的工作方向是构建更快、更轻、更易用的语音模型。

**Matthijs Hollemans, Hugging Face机器学习工程师**

我是Matthijs, Hugging Face开源组的音频机器学习工程师。我也是《how to write sound synthesizers》（如何编写音频合成器）一书的作者。我喜欢在业余时间编写音频插件。

**Maria Khalusova, Hugging Face文档和课程经理**

我是Maria，我的主要工作是制作教学内容和文档，使我们的Transformers库和其他开源工具更加易用。我会解构复杂的技术概念，并且帮助大家从零开始理解最前沿的技术。

**Vaibhav Srivastav, Hugging Face机器学习开发者大使**

我是Vaibhav (VB)，Hugging Face开源组的开发者大使（Developer Advocate）。我的研究方向是低资源TTS以及尖端语音研究的大众化推进。

## 课程结构

本课程被分为多个单元，涵盖了众多方向的深度议题：

* 第1单元：学习音频数据的特殊性，包括音频信号处理技巧和数据预处理。
* 第2单元：了解音频机器学习的应用，使用🤗Transformers pipelines实现不同任务，包括音频分类（Audio classification）和语音识别（speech recognition）。
* 第3单元：探索各种音频Transformer架构，了解他们的区别和各自的优势任务。
* 第4单元：学习如何构建你自己的音乐风格分类器（music genre classifier）
* 第5单元：深入学习语音识别（speech recognition），搭建一个会议转录模型。
* 第6单元：学习如何从文字生成语音。
* 第7单元：学习利用Transformer进行音频到音频转换（audio to audio）。

每个单元都包含了理论部分，对模型背后的概念和技巧做了深入的极少。本课程的各个章节都包含了课后习题，用来测试和强化你的学习成果。部分单元还包含了实战练习。

完成本课程的学习后，你会在Transformer和音频数据上拥有一个扎实的基础，并且会学习到针对多种音频相关任务的工具，帮助你快速开发应用。

本课程的各个单元会按照如下的时间表发布：（译注：此表为英文版课程发布时间）

| 单元   | 发布日期 |
|---|-----------------|
| 第0、1、2单元  | 2023年6月14日   |
| 第3、4单元  | 2023年6月21日   |
| 第5单元  | 2023年6月28日   |
| 第6单元  | 2023年7月5日    |
| 第7、8单元  | 2023年7月12日   |

[//]: # (| 额外单元  | 未定             |)

## 学习方法和证书

你可以用任何方式来利用这门课程。本课程的所有内容均为100%免费、公开且开源。你可以按照自己喜欢的节奏来学习本课程，不过我们建议按照顺序来阅读各个单元。

如果你想要获取结课证明，我们提供两种不同的选项：

| 证书类别   | 要求                                                                                   |
|---|------------------------------------------------------------------------------------------------|
| 结课证书  | 在2023年7月底前，根据指引完成80%的实战练习内容。 |
| 荣誉证书  | 在2023年7月底前，根据指引完成100%的实战练习内容。 |

每个单元的实战练习内容均有各自的完成标准。当你完成了任意类别的证书要求后，请按照最后一个单元的指引来获取你的结课证明。祝你好运！

## 注册本课程

本课程的不同单元会在数周内逐渐更新。我们建议你注册课程更新通知，以防错过新内容的更新。注册课程的同学还能第一时间了解到我们举办的特殊活动。

[注册链接（仅英文）](http://eepurl.com/insvcI)

祝你学习愉快！

<EditOnGithub source="https://github.com/huggingface/audio-transformers-course/blob/main/chapters/zh-CN/chapter0/introduction.mdx" />

### 加入社区！
https://huggingface.co/learn/audio-course/zh-CN/chapter0/community.md

# 加入社区！

我们邀请你 [加入我们充满活力和乐于助人的Discord社区（仅英文）](http://hf.co/join/discord)。在这里，你将有机会认识同温层的同学、与他们交流想法，并且获得关于实战练习的宝贵反馈。你可以在这里提问、分享资源、与他人合作。

我们的团队也会在Discord中和大家交流，并且为大家提供支持和指引。加入社区可以帮助你保持学习动力、增加参与感、加强练习。我们欢迎你成为社区的一员！

## Discord是什么?

Discord是一个免费的聊天平台。如果你使用过Slack，那么Discord也与之类似。我们的Hugging Face Discord频道已经有超过18000名用户，包括了AI专家、AI学习者和AI爱好者。我们邀请你参与到讨论中来!

## 使用Discord

在你注册并加入我们的Discord服务器之后，你可以在界面左侧点击`#role-assignment`来添加你感兴趣的话题。你可以选择任意数量的话题。想要加入本课程的讨论组，请点击"ML for Audio and Speech"标签。我们也欢迎你在`#introduce-yourself`标签中分享关于你自己的内容。

## 音频课程频道

我们的Discord服务器中有针对许多不同方向的频道。大家可以在不同的频道中讨论论文、组织活动、分享自己的项目的调子、头脑风暴、等等。作为音频课程的同学，你可以关注以下频道的相关内容：

* `#audio-announcements`: 课程内容的更新、Hugging Face所有音频相关内容的新闻、活动通知等。
* `#audio-study-group`: 交流想法、提问和交流课程相关内容的频道。
* `#audio-discuss`: 任何音频相关内容的讨论。

除了加入`#audio-study-group`频道之外，你也可以创建自己的学习小组。一起学习总是更加轻松！


<EditOnGithub source="https://github.com/huggingface/audio-transformers-course/blob/main/chapters/zh-CN/chapter0/community.mdx" />

### 课程准备工作
https://huggingface.co/learn/audio-course/zh-CN/chapter0/get_ready.md

# 课程准备工作

希望你已经做好准备开始学习了！这一节中我们会确保你做好了所有开始学习前的准备工作。

## 第1步：注册课程

注册课程以获取内容更新和特殊活动的通知：

[👉 注册课程（仅英文）](http://eepurl.com/insvcI)

## 第2步：创建Hugging Face 账号

如果你还没有Hugging Face 账号，我们推荐你现在进行免费注册。实战练习需要使用你的个人Hugging Face 账号来探索预训练模型、获取数据集和更多资源。

[👉 创建Hugging Face 账号（仅英文）](https://huggingface.co/join)

## 第3步：复习基础内容（如需要）

本课程针对熟悉深度学习基础知识并对Transformer架构有一定了解的同学而设计。如果你需要复习Transformer架构的基础内容，可以参考我们的[自然语言处理课程](https://huggingface.co/learn/nlp-course/chapter0/1)。

## 第4步：检查你的环境

学习本课程需要以下资源：
- 一台联网的电脑
- 使用[Google Colab](https://colab.research.google.com)进行实战练习。免费版本即可。

如果你没有使用过Google Colab，可以参考这个[官方介绍笔记本（仅英文）](https://colab.research.google.com/notebooks/intro.ipynb).

## 第5步：加入社区

加入我们的Discord频道，与同学们和Hugging Face 团队交流你的想法。

[👉 加入DISCORD社区（仅英文）](http://hf.co/join/discord)

想了解更多关于社区的内容并更好地利用它，请参考[下一小节](community).



<EditOnGithub source="https://github.com/huggingface/audio-transformers-course/blob/main/chapters/zh-CN/chapter0/get_ready.mdx" />

### 祝贺！
https://huggingface.co/learn/audio-course/zh-CN/chapter8/introduction.md

# 祝贺！

您的不懈努力让您抵达了这里，我们想祝贺您完成了这个音频课程！

回顾整个课程，您获得了音频数据的基础理解，探索了一些新概念，并掌握了与音频 `Transformers` 相关的实用技能。

从通过 `pipeline` 处理音频数据和预训练 `checkpoints` 的基础知识，到构建真实世界的音频应用程序，您学会了如何构建不仅能理解声音，还能创造声音的系统。

由于这一领域充满活力、不断发展更新，因此我们鼓励您保持好奇心，不断探索更新的模型、最新的研究进展和应用。在开发您自己的新颖音频应用程序时，请确保始终牢记道德准则，并仔细考虑您的应用对个人和整个社会的潜在影响。

感谢您参加本次音频课程，希望您能像我们当时乐此不疲地制作课程时一样，充分享受这一学习经历。欢迎您在[课程的 GitHub 仓库](https://github.com/huggingface/audio-transformers-course)中提供反馈和贡献。

如果您顺利完成了实践作业，并想要了解如何获得课程完成证书，请查看[下一页](certification)。

最后，您可以在 `X` 上关注我们的音频课程团队并和我们保持联系：

- Maria Khalusova：[@mariakhalusova](https://twitter.com/mariaKhalusova)
- Sanchit Gandhi：[@sanchitgandhi99](https://twitter.com/sanchitgandhi99)
- Matthijs Hollemans：[@mhollemans](https://twitter.com/mhollemans)
- Vaibhav (VB) Srivastav：[@reach_vb](https://twitter.com/reach_vb)

保持好奇心，训练 `Transformers` ！ :)



<EditOnGithub source="https://github.com/huggingface/audio-transformers-course/blob/main/chapters/zh-CN/chapter8/introduction.mdx" />

### 获取您的完成证书
https://huggingface.co/learn/audio-course/zh-CN/chapter8/certification.md

# 获取您的完成证书

认证的过程是完全免费的。

- 要获得完成证书：您需要通过4项实践作业的其中3项。
- 要获得卓越证书：您需要通过全部的4项实践作业。

每项实践作业的要求都在其对应的单元中列出：

- [第4单元实践](../chapter4/hands_on)
- [第5单元实践](../chapter5/hands_on)
- [第6单元实践](../chapter6/hands_on)
- [第7单元实践](../chapter7/hands_on)

对于涉及到训练模型的作业，请确保您将符合要求的模型推送到 `Hub` 并附带相关的 `kwargs`。 

对于第7单元的演示作业，请确保您的演示 `demo` 被设置为公开可见。

如果要以自我评估的形式查看您通过或未通过的单元，您可以使用以下 `HuggingFace` 空间：
[查看我的进度 - 音频课程](https://huggingface.co/spaces/MariaK/Check-my-progress-Audio-Course)

一旦您达到了获得证书的资格，请前往 [音频课程认证](https://huggingface.co/spaces/MariaK/Audio-Course-Certification) 空间。 

该空间中存在额外的检查，以确保您的提交符合评估标准。

在文本字段中输入您的 `HuggingFace` 用户名、名字和姓氏，然后单击“检查我是否通过并获得证书”按钮。

如果您通过了4个实践作业的其中3个，您将获得完成证书。 

如果您通过了4个实践作业，您将获得卓越证书。

您可以通过 `pdf` 和 `png` 格式下载您的证书。请自豪地在 `X` 和 `LinkedIn ` 上分享您的证书（可以加上 `tag` 我 @mariakhalusova 和 `HuggingFace` @huggingface）。

如果您不符合认证标准，也请不要气馁！

返回 [查看我的进度 - 音频课程](https://huggingface.co/spaces/MariaK/Check-my-progress-Audio-Course) 空间，查看您需要重新完成哪些单元才能获得您的证书。如果您在这些认证相关的空间中遇到任何问题，请联系我们！

<EditOnGithub source="https://github.com/huggingface/audio-transformers-course/blob/main/chapters/zh-CN/chapter8/certification.mdx" />

### Check your understanding of the course material
https://huggingface.co/learn/audio-course/zh-CN/chapter1/quiz.md

# Check your understanding of the course material

### 1. 采样率使用的单位是？

<Question
	choices={[
		{
			text: "dB",
			explain: "错误，使用dB标度的是幅值。"
		},
		{
			text: "Hz",
			explain: "采样率是指每秒内采样点的个数，使用赫兹（Hz）为单位。",
			correct: true
		},
		{
			text: "bit",
			explain: "比特（bit）是描述位深度的单位，位深度指的是单个样本点需要用多少位的二进制数（即比特）来表示。",
		}
	]}
/>

### 2. 使用流式加载模式加载大规模数据集时，何时才能开始使用该数据集？

<Question
	choices={[
		{
			text: "在完整的数据集下载完毕时",
			explain: "使用流式加载的目的就是让我们不需要下载完整的数据集也可以开始使用数据集。"
		},
		{
			text: "在前16个样本下载完成时",
			explain: "再试一次！"
		},
		{
			text: "在第一个样本被下载完成是",
			explain: "",
			correct: true
		}
	]}
/>

### 3. 什么是时频谱？

<Question
	choices={[
		{
			text: "将麦克风的电信号转化为数字信号的设备",
			explain: "将麦克风的电信号转化为数字信号的设备称为模拟-数字转换器（模数转换器，Analog-to-Digital Converter，ADC）。再试一次！"
		},
		{
			text: "表示音频信号的幅值随时间变化的图像，也被称为声音信号的*时域*表示",
			explain: "这种图像称为波形图，而非时频谱图。"
		},
		{
			text: "表示音频信号的各个频率成分随时间变化的可视化表示",
			explain: "",
			correct: true
		}
	]}
/>

### 4. 下列方法中，最简单的可以将原始音频转化为Whisper模型接受的对数梅尔谱的方法是？

A.
```python
librosa.feature.melspectrogram(audio["array"])
```

B.
```python
feature_extractor = WhisperFeatureExtractor.from_pretrained("openai/whisper-small")
feature_extractor(audio["array"])
```

C.
```python
dataset.feature(audio["array"], model="whisper")
```

<Question
	choices={[
		{
			text: "A",
			explain: "`librosa.feature.melspectrogram()`生成的是能量频谱。"
		},
		{
			text: "B",
			explain: "",
			correct: true
		},
		{
			text: "C",
			explain: "Dataset并不会对数据进行预处理或转换。模型的特征提取器才会进行转换。"
		}
	]}
/>

### 5. 如何从🤗 Hub加载数据集？

A.
```python
from datasets import load_dataset

dataset = load_dataset(DATASET_NAME_ON_HUB)
```

B.
```python
import librosa

dataset = librosa.load(PATH_TO_DATASET)
```

C.
```python
from transformers import load_dataset

dataset = load_dataset(DATASET_NAME_ON_HUB)
```

<Question
	choices={[
		{
			text: "A",
			explain: "最佳方案是使用🤗 Datasets库。",
			correct: true
		},
		{
			text: "B",
			explain: "Librosa.load 在加载单个的音频文件时十分好用，但在加载多样本和多特征的数据库时并非最佳方案。"
		},
		{
			text: "C",
			explain: "load_dataset 方法属于🤗 Datasets库，而非🤗 Transformers。"
		}
	]}
/>

### 6. 你的自定义数据集包含了32千赫兹采样率的高清音频。你现在想要训练一个使用16千赫兹采样率的语音识别模型。你应该怎么做？

<Question
	choices={[
		{
			text: "直接使用这些数据，模型可以对不同采样率的数据有泛化能力",
			explain: "由于依靠注意力机制，模型很难对不同采样率的数据进行泛化。"
		},
		{
			text: "使用🤗 Datasets库的音频模组对自定义数据集进行降采样",
			explain: "",
			correct: true
		},
		{
			text: "使用每隔一个样本点丢弃一个样本点的方法进行降采样",
			explain: "这种方法会产生混叠失真。重采样操作往往十分棘手，因此我们推荐经过测试的工具库，例如librosa和🤗 Datasets。"
		}
	]}
/>

### 7. 如何将机器学习模型生成的时频谱转化为波形？

<Question
	choices={[
		{
			text: "我们可以使用一种叫做声码器（vocoder）的神经网络从时频谱重构波形",
			explain: "由于生成的时频谱缺乏相位信息，我们需要使用声码器或者经典的Griffin-Lim算法来重构波形。",
			correct: true
		},
		{
			text: "我们可以用逆短时傅里叶变换（inverse STFT）将生成的时频谱转化为波形",
			explain: "使用逆短时傅里叶变化需要时频谱的相位信息，但生成的时频谱一般仅具有幅值信息。"
		},
		{
			text: "无法将机器学习模型生成的时频谱转化为波形。",
			explain: "再试一次！"
		}
	]}
/>


<EditOnGithub source="https://github.com/huggingface/audio-transformers-course/blob/main/chapters/zh-CN/chapter1/quiz.mdx" />

### 第1单元：音频数据处理
https://huggingface.co/learn/audio-course/zh-CN/chapter1/introduction.md

# 第1单元：音频数据处理

## 单元简介

所有音频或语音相关的任务都需要使用音频文件。在我们深入了解这些任务之前，我们需要了解音频文件的实际内容以及如何利用音频文件。

本单元将为你介绍与音频数据相关的基本概念，包括波形、采样率和频谱图。你会学习到如何使用音频数据集，包括音频数据加载、音频数据预处理，以及高效加载大规模音频数据集的流式加载方法。

完成本单元的学习后，你会掌握基础的音频相关术语，并且掌握针对不同应用的音频数据处理工具。本单元的知识会成为后面章节的基础。

<EditOnGithub source="https://github.com/huggingface/audio-transformers-course/blob/main/chapters/zh-CN/chapter1/introduction.mdx" />

### 附加内容
https://huggingface.co/learn/audio-course/zh-CN/chapter1/supplemental_reading.md

# 附加内容

本小节涵盖了许多处理音频数据的基础概念。想学习更多？我们在这里提供了额外的内容，帮助你加深理解、增强学习体验。

在接下来的这段视频中，来自xiph.org的Monty Montgomery为我们实时演示了采样、量化、位深度的概念，并且展示了现代的数字分析方法和复古的模拟测量仪器：

<Youtube id="cIQ9IXSUzuM"/>

如果你想了解更多关于数字信号处理的知识，可以参考免费的["Digital Signals Theory"一书](https://brianmcfee.net/dstbook-site/content/intro.html)（仅英语）。本书的作者是Brian McFee，来自New York University的音乐技术和数据科学副教授，同时也是`librosa`库的主要维护者。



<EditOnGithub source="https://github.com/huggingface/audio-transformers-course/blob/main/chapters/zh-CN/chapter1/supplemental_reading.mdx" />

### 加载音频数据集
https://huggingface.co/learn/audio-course/zh-CN/chapter1/load_and_explore.md

# 加载音频数据集

本节中我们将会使用🤗 Datasets来获取音频数据集。🤗 Datasets是一个下载和准备数据集的开源工具，包含了音频在内的各种模态数据。该工具集为Hugging Face Hub上公开的机器学习数据集提供了易用的接口。此外，🤗 Datasets还提供了专门为音频数据集而设的多种特性，帮助研究者和机器学习实践者更轻松地使用这些数据集。

首先，我们要确认已经安装了🤗 Datasets库：

```bash
pip install datasets[audio]
```

🤗 Datasets的其中一个重磅功能是可以使用`load_dataset()`函数达到仅用一行代码下载和准备数据集。

这里我们来加载和探索[MINDS-14](https://huggingface.co/datasets/PolyAI/minds14)这一音频数据集。该数据集的内容是人们向某个网银系统提问的录音，包含了多种语言和方言。

为了加载MINDS-14数据集，我们需要复制该数据集在Hugging Face Hub上的identifier（`PolyAI/minds14`），并向`load_dataset()`函数传入该参数。这里我们只选取该数据集的澳大利亚子集（`en-AU`）的训练分集：

```py
from datasets import load_dataset

minds = load_dataset("PolyAI/minds14", name="en-AU", split="train")
minds
```

**输出：**
```out
Dataset(
    {
        features: [
            "path",
            "audio",
            "transcription",
            "english_transcription",
            "intent_class",
            "lang_id",
        ],
        num_rows: 654,
    }
)
```

该数据集包含了654个音频文件，每个都有对应的转录文字和其英语翻译，以及一个代表询问人目的的标签。"audio"列则包含了原始的音频数据。我们来仔细看看其中的一个样本：

```py
example = minds[0]
example
```

**输出**
```out
{
    "path": "/root/.cache/huggingface/datasets/downloads/extracted/f14948e0e84be638dd7943ac36518a4cf3324e8b7aa331c5ab11541518e9368c/en-AU~PAY_BILL/response_4.wav",
    "audio": {
        "path": "/root/.cache/huggingface/datasets/downloads/extracted/f14948e0e84be638dd7943ac36518a4cf3324e8b7aa331c5ab11541518e9368c/en-AU~PAY_BILL/response_4.wav",
        "array": array(
            [0.0, 0.00024414, -0.00024414, ..., -0.00024414, 0.00024414, 0.0012207],
            dtype=float32,
        ),
        "sampling_rate": 8000,
    },
    "transcription": "I would like to pay my electricity bill using my card can you please assist",
    "english_transcription": "I would like to pay my electricity bill using my card can you please assist",
    "intent_class": 13,
    "lang_id": 2,
}
```

你可能注意到了"audio"列包含了好几个特征，它们分别是：
* `path`：音频文件的路径（这里为`*.wav`）。
* `array`：解码后的音频文件，以1维NumPy数组表示。
* `sampling_rate`：音频文件的采样率（该样本为8000赫兹）。

`intent_class`则是分类的具体类别。我们可以使用`int2str()`方法将该数字转换为有意义的字符串：

```py
id2label = minds.features["intent_class"].int2str
id2label(example["intent_class"])
```

**输出：**
```out
"pay_bill"
```

在该样本的转录文字中，我们可以看到该音频的内容确实是某人在提一个关于账单的问题。

如果你只是想用该子集训练一个音频分类器，你可能不需要使用所有的特征。举个例子，`lang_id`标签在该子集中全部为同样的值；`english_transcription`标签和`transcription`几乎完全含有相同的内容，因此我们也可以舍弃该标签。

你可以使用🤗 Datasets的`remove_columns()`方法轻松地移除所有不相关的标签：

```py
columns_to_remove = ["lang_id", "english_transcription"]
minds = minds.remove_columns(columns_to_remove)
minds
```

**输出：**
```out
Dataset({features: ["path", "audio", "transcription", "intent_class"], num_rows: 654})
```

现在我们已经加载并检验了数据集的原始内容，让我们来听几个例子吧！我们可以使用`Gradio`中的`Blocks`功能和`Audio`功能从数据集中解码几个样本：

```py
import gradio as gr


def generate_audio():
    example = minds.shuffle()[0]
    audio = example["audio"]
    return (
        audio["sampling_rate"],
        audio["array"],
    ), id2label(example["intent_class"])


with gr.Blocks() as demo:
    with gr.Column():
        for _ in range(4):
            audio, label = generate_audio()
            output = gr.Audio(audio, label=label)

demo.launch(debug=True)
```

你也可以可视化你想要的样本。这里我们试着绘制第一个样本的波形图：

```py   
import librosa
import matplotlib.pyplot as plt
import librosa.display

array = example["audio"]["array"]
sampling_rate = example["audio"]["sampling_rate"]

plt.figure().set_figwidth(12)
librosa.display.waveshow(array, sr=sampling_rate)
```

<div class="flex justify-center">
    <img src="https://huggingface.co/datasets/huggingface-course/audio-course-images/resolve/main/waveform_unit1.png" alt="Waveform plot">
</div>

动手试试吧！试着下载MINDS-14数据集中其他语言或方言的子集，聆听并可视化其中的一些样本，感受整个数据集的多样性。你可以在[这里](https://huggingface.co/datasets/PolyAI/minds14)找到语言和方言的全部列表（仅英文）。

<EditOnGithub source="https://github.com/huggingface/audio-transformers-course/blob/main/chapters/zh-CN/chapter1/load_and_explore.mdx" />

### 音频数据的流式加载
https://huggingface.co/learn/audio-course/zh-CN/chapter1/streaming.md

# 音频数据的流式加载

使用音频数据集的最大挑战之一是它们庞大的规模。仅仅一分钟的CD音质音频（44.1千赫兹，16比特）就需要占用约5MB的存储。一般的音频数据集会含有数个小时的音频录音。

在上一小节中，我们使用了MINDS-14的一个非常小的子集，但常见的音频数据集往往要大得多。举个例子[GigaSpeech from SpeechColab](https://huggingface.co/datasets/speechcolab/gigaspeech)的`xs`（最小）子集仅有10小时的训练数据，但需要占用超过13GB的存储空间。当我们选择更大的子集时会发生什么呢？其最大的`xl`子集含有10000小时的训练数据，需要占用超过1TB的存储空间。对大多数人来说，这已经远远超过了我们的硬盘所能容纳的容量。我们需要剁手购买额外的硬盘吗？还是说有其他方法可以让我们绕过存储空间的限制呢？

🤗 Datasets为你提供了解决方法：流式处理模式。流式处理可以让我们在遍历数据集的过程中渐进地加载数据。与其一次性地下载完整数据集，不如将样本一个个单独加载。在遍历数据集时，我们实时地在有需要时加载和准备好每个样本。通过这样的方式，我们永远只加载我们正在使用的数据，而不加载我们暂时不需要的数据。当我们使用完一个样本时，就继续遍历数据集，加载下一个样本。

相较于全量加载，流式处理模式主要有三个优点：

* 硬盘空间：样本是在遍历数据集的过程中被依次加载到内存中的。由于我们不需要将数据下载到本地，因此也没有任何对硬盘空间的要求，你可以使用任意大小的数据集。
* 下载和处理时间：音频数据集极其庞大并且需要大量的数据下载和处理时间。在流式处理模式下，数据会进行实时的加载和处理，因此当第一个样本准备完成时我们就可以开始训练。
* 测试简单：无需下载完整的数据集，仅用少量的样本就可以测试你的训练脚本。

在使用流式处理模式时，有一点需要警惕：在全量下载数据集时，所有的原始数据和处理后的数据都会在硬盘内本地存储。如果我们想要重复利用这些数据，我们可以跳过下载和处理过程，直接从硬盘中加载数据。这意味着我们在重复利用数据时，仅在第一次时需要下载和处理数据。

但在流式处理模式中，数据不会被下载到硬盘内。因此，我们的下载和预处理过程都不会被保存。在重复利用数据集时，我们必须要重复进行下载和预处理才做。因此，如果你计划重复使用数据集，我们还是推荐将数据集下载到本地硬盘。

如何启用流式处理模式呢？很简单！只需要在加载数据集时传入`streaming=True`参数即可。我们会帮你做好其他工作：

```py
gigaspeech = load_dataset("speechcolab/gigaspeech", "xs", streaming=True)
```

与我们在全量下载MINDS-14子集时进行的预处理操作相同，你也可以在流式数据集上使用同样的预处理操作。

唯一的区别是我们现在无法使用Python的索引功能访问单个的样本（例如`gigaspeech["train"][sample_idx]`）。相反地，我们需要遍历整个数据集。你可以在流式数据集这这样访问单个样本：

```py
next(iter(gigaspeech["train"]))
```

**输出：**
```out
{
    "segment_id": "YOU0000000315_S0000660",
    "speaker": "N/A",
    "text": "AS THEY'RE LEAVING <COMMA> CAN KASH PULL ZAHRA ASIDE REALLY QUICKLY <QUESTIONMARK>",
    "audio": {
        "path": "xs_chunks_0000/YOU0000000315_S0000660.wav",
        "array": array(
            [0.0005188, 0.00085449, 0.00012207, ..., 0.00125122, 0.00076294, 0.00036621]
        ),
        "sampling_rate": 16000,
    },
    "begin_time": 2941.89,
    "end_time": 2945.07,
    "audio_id": "YOU0000000315",
    "title": "Return to Vasselheim | Critical Role: VOX MACHINA | Episode 43",
    "url": "https://www.youtube.com/watch?v=zr2n1fLVasU",
    "source": 2,
    "category": 24,
    "original_full_path": "audio/youtube/P0004/YOU0000000315.opus",
}
```

你可以使用`take()`函数来预览数据集的几个样本。让我们看看gigaspeech数据集的前两个样本：

```py
gigaspeech_head = gigaspeech["train"].take(2)
list(gigaspeech_head)
```

**输出：**
```out
[
    {
        "segment_id": "YOU0000000315_S0000660",
        "speaker": "N/A",
        "text": "AS THEY'RE LEAVING <COMMA> CAN KASH PULL ZAHRA ASIDE REALLY QUICKLY <QUESTIONMARK>",
        "audio": {
            "path": "xs_chunks_0000/YOU0000000315_S0000660.wav",
            "array": array(
                [
                    0.0005188,
                    0.00085449,
                    0.00012207,
                    ...,
                    0.00125122,
                    0.00076294,
                    0.00036621,
                ]
            ),
            "sampling_rate": 16000,
        },
        "begin_time": 2941.89,
        "end_time": 2945.07,
        "audio_id": "YOU0000000315",
        "title": "Return to Vasselheim | Critical Role: VOX MACHINA | Episode 43",
        "url": "https://www.youtube.com/watch?v=zr2n1fLVasU",
        "source": 2,
        "category": 24,
        "original_full_path": "audio/youtube/P0004/YOU0000000315.opus",
    },
    {
        "segment_id": "AUD0000001043_S0000775",
        "speaker": "N/A",
        "text": "SIX TOMATOES <PERIOD>",
        "audio": {
            "path": "xs_chunks_0000/AUD0000001043_S0000775.wav",
            "array": array(
                [
                    1.43432617e-03,
                    1.37329102e-03,
                    1.31225586e-03,
                    ...,
                    -6.10351562e-05,
                    -1.22070312e-04,
                    -1.83105469e-04,
                ]
            ),
            "sampling_rate": 16000,
        },
        "begin_time": 3673.96,
        "end_time": 3675.26,
        "audio_id": "AUD0000001043",
        "title": "Asteroid of Fear",
        "url": "http//www.archive.org/download/asteroid_of_fear_1012_librivox/asteroid_of_fear_1012_librivox_64kb_mp3.zip",
        "source": 0,
        "category": 28,
        "original_full_path": "audio/audiobook/P0011/AUD0000001043.opus",
    },
]
```

流式处理模式可以助力你的研究进入更高的阶段：现在你不仅可以利用庞大的数据集，而且可以在无需担忧硬盘空间的情况下使用多个数据集来评估你的系统。与使用单个数据集进行评估相比，多数据集评估可以更好地体现系统的泛化性能。

<EditOnGithub source="https://github.com/huggingface/audio-transformers-course/blob/main/chapters/zh-CN/chapter1/streaming.mdx" />

### 音频数据集的预处理
https://huggingface.co/learn/audio-course/zh-CN/chapter1/preprocessing.md

# 音频数据集的预处理

使用🤗 Datasets加载数据集只是乐趣的一半。如果你计划用数据集训练模型或者运行推理，那么你还需要对数据进行预处理（pre-processing）。数据预处理通常包括以下几步：

* 音频重采样
* 对数据集进行过滤
* 将音频数据转换为模型要求的输入形式

## 音频重采样

`load_dataset()`函数在下载数据集时会保留数据集发布时的原始采样率。当你使用其他模型进行训练或推理时，该采样率也许会不符合要求。当采样率不同时，你可以进行重采样来将数据调整到模型所期望的采样率。

目前大多数的预训练模型采用了16千赫兹采样率的数据集进行预训练。在上一张探索MINDS-14数据集时，你可能注意到了该数据集采用了8千赫兹的采样率，这意味着我们需要对其进行上采样。

我们可以使用🤗 Datasets的`cast_column()`方法进行上采样。该方法不会在原位改动数据，而是在数据加载时进行实时的重采样。下面的代码可以将样本重采样到16千赫兹：

```py
from datasets import Audio

minds = minds.cast_column("audio", Audio(sampling_rate=16_000))
```

现在我们重新加载MINDS-14的第一个样本，并检查其是否按照我们提供的`sampling_rate`参数进行了重采样：

```py
minds[0]
```

**输出**
```out
{
    "path": "/root/.cache/huggingface/datasets/downloads/extracted/f14948e0e84be638dd7943ac36518a4cf3324e8b7aa331c5ab11541518e9368c/en-AU~PAY_BILL/response_4.wav",
    "audio": {
        "path": "/root/.cache/huggingface/datasets/downloads/extracted/f14948e0e84be638dd7943ac36518a4cf3324e8b7aa331c5ab11541518e9368c/en-AU~PAY_BILL/response_4.wav",
        "array": array(
            [
                2.0634243e-05,
                1.9437837e-04,
                2.2419340e-04,
                ...,
                9.3852862e-04,
                1.1302452e-03,
                7.1531429e-04,
            ],
            dtype=float32,
        ),
        "sampling_rate": 16000,
    },
    "transcription": "I would like to pay my electricity bill using my card can you please assist",
    "intent_class": 13,
}
```

你可能注意到数列的值也有所变化。这是因为我们在重采样后会获得两倍于原来采样点数量的数值。

<Tip>
💡 关于重采样的背景知识：当信号的原始采样率为8千赫兹时，信号每秒钟会包含8000个采样点，并且我们知道该信号不会包含高于4千赫兹的频率成分。奈奎斯特采样定理保证了这一点。也因此，我们可以确保在两个采样点中间的原始连续信号呈一条平滑的曲线。在这样的条件下，上采样所要做的就只是根据对曲线的估计而计算出两个采样点中间的额外数值。与之相反的是，下采样过程需要我们首先过滤掉所有高于奈奎斯特极限的频率成分，之后才能重新计算采样点。也就是说，我们不能通过简单的每隔一个采样点丢弃一个采样点来进行2倍的下采样：这会造成信号的失真，我们称之为混叠失真。重采样过程十分棘手，因此我们推荐使用经过测试的工具库，例如librosa或🤗 Datasets。
</Tip>

## 过滤数据集

我们可能会需要用一些指标来过滤掉数据集中的一些数据。一种常见情况是限制音频文件的时长。举个例子，我们可能需要过滤掉所有长度超过20秒的音频来防止模型训练过程中的内存不足错误。

我们可以使用🤗 Datasets的`filter`方法并传入过滤逻辑的函数来进行过滤。首先我们需要编写一个函数来指示哪些样本需要保留，哪些样本需要舍弃。这里我们编写了`is_audio_length_in_range()`函数，在样本长度小于20秒时会返回`True`，否则返回`False`。

```py
MAX_DURATION_IN_SECONDS = 20.0


def is_audio_length_in_range(input_length):
    return input_length < MAX_DURATION_IN_SECONDS
```

该过滤函数可以直接应用在数据集的列上，但我们的数据集并没有一个单独的记录音频长度的列。不过我们可以自己创建一列，然后进行过滤，最后在过滤完成之后删除该列。

```py
# 使用librosa从音频文件里获取该音频的长度
new_column = [librosa.get_duration(filename=x) for x in minds["path"]]
minds = minds.add_column("duration", new_column)

# 使用🤗 Datasets的`filter`方法来进行过滤
minds = minds.filter(is_audio_length_in_range, input_columns=["duration"])

# 移除临时列
minds = minds.remove_columns(["duration"])
minds
```

**输出**
```out
Dataset({features: ["path", "audio", "transcription", "intent_class"], num_rows: 624})
```

我们可以看到数据集的样本数量从654个被过滤到了624个。

## 音频数据的预处理

在利用音频数据集时，最具挑战的工作之一便是给模型训练提供正确格式的数据。如你所见，原始的音频数据是一个单列的采样点数组。然而，无论是推理还是根据任务进行微调，大部分的预训练模型都需要将音频数据转化成其对应的输入特征。每个模型的输入特征都有不同的要求，往往根据模型的架构和其预训练的数据集所决定。好消息是，🤗 Transformers所支持的所有音频模型都提供了一个特征提取类，负责将原始的音频数据转化为该模型所需的输入特征格式。

特征提取器具体会对音频文件做些什么呢？我们可以参考[Whisper](https://cdn.openai.com/papers/whisper.pdf)的特征提取器来理解一些常用的特征提取变换。Whisper是一个自动语音识别（automatic specch recognition, ASR）的预训练模型，由OpenAI的Alec Radford等人于2022年9月发布。

首先，Whisper的特征提取器会加长/截短某一批次中的所有音频样本，确保他们均为长30秒的音频。短于30秒的样本会采用末尾补零的方式加长至30秒，因为零在音频信号中代表无信号或静音。长于30秒的音频会被截取至30秒。由于一批次的所有音频都被加长/截短至同一长度，我们不再需要使用attention mask。这是Whisper模型的特性之一，因为大多数其他的音频模型都需要使用attention mask来告诉模型输入的哪些部分进行了补值，来让这些被补值的位置在self-attention过程中被忽略。Whisper模型在训练时就在无需attention mask的情况下运行，并且能直接从输入的音频信号中推理出哪些部分需要被忽略。

Whisper的特征提取器所进行的第二个操作是将定长的音频数组转化为对数梅尔时频谱（log-mel spectrogram）。如前文所述，对数梅尔谱描述了各个频率成分是如何随时间变化的，并且在频率上使用了梅尔标度，在幅值上使用的分贝（对数）标度，使得频率和幅值的关系更接近于人耳的感知。

上述的所有变换都可以用简短的几行代码应用到原始的音频数据上。现在，我们从预训练的Whisper模型检查点（checkpoint）加载特征提取器，为音频数据做好准备：

```py
from transformers import WhisperFeatureExtractor

feature_extractor = WhisperFeatureExtractor.from_pretrained("openai/whisper-small")
```

然后，我们编写一个处理单个样本的函数，将该样本中的音频文件输入特征提取器`feature_extractor`。

```py
def prepare_dataset(example):
    audio = example["audio"]
    features = feature_extractor(
        audio["array"], sampling_rate=audio["sampling_rate"], padding=True
    )
    return features
```

我们可以使用🤗 Datasets的`map`方法将这个数据处理函数应用到数据集中的所有训练样本上：

```py
minds = minds.map(prepare_dataset)
minds
```

**输出：**
```out
Dataset(
    {
        features: ["path", "audio", "transcription", "intent_class", "input_features"],
        num_rows: 624,
    }
)
```

简单几步后，我们就获得了对数梅尔谱并存储在数据集的`input_features`列中。

我们来试着可视化`minds`数据集中的一个样本：

```py
import numpy as np

example = minds[0]
input_features = example["input_features"]

plt.figure().set_figwidth(12)
librosa.display.specshow(
    np.asarray(input_features[0]),
    x_axis="time",
    y_axis="mel",
    sr=feature_extractor.sampling_rate,
    hop_length=feature_extractor.hop_length,
)
plt.colorbar()
```

<div class="flex justify-center">
    <img src="https://huggingface.co/datasets/huggingface-course/audio-course-images/resolve/main/log_mel_whisper.png" alt="Log mel spectrogram plot">
</div>

现在你可以看到经过预处理后的Whisper模型的输入了。

模型的特征提取器会保证将原始音频转化为模型所需要的输入格式。然而，许多音频相关的任务，比如语音识别，往往也是多模态的任务。🤗 Transformers库提供了针对各种文字模型的分词器（Tokenizer）。请参考我们的[自然语言处理课程](https://huggingface.co/learn/nlp-course/zh-CN/chapter2/4)中对分词器的详细介绍。

我们可以分别加载Whisper中的特征提取器和其他多模态模型中的分词器，也可以通过processor将他们同时加载。如果想要更加简单的用法，你可以使用`AutoProcessor`从模型的检查点中直接加载特征提取器和processor：

```py
from transformers import AutoProcessor

processor = AutoProcessor.from_pretrained("openai/whisper-small")
```

在本小节中，我们介绍了数据准备的基本流程。但要注意的是，您的自定义数据集往往会需要一些更复杂的数据预处理。针对这些情况，你可以扩展`prepare_dataset()`函数来实现各种自定义的数据转换流程。在🤗 Datasets的帮助下，只要您能将数据处理流程编写为Python函数，就可以将其应用在您的数据集上！

<EditOnGithub source="https://github.com/huggingface/audio-transformers-course/blob/main/chapters/zh-CN/chapter1/preprocessing.mdx" />

### 音频数据处理入门
https://huggingface.co/learn/audio-course/zh-CN/chapter1/audio_data.md

# 音频数据处理入门

声波在本质上是一种连续信号，这意味着在一段给定时间内的声音信号有无数个取值。对于只能读取有限长数组的数字计算机来说，这是一个重要的问题。为了使得数字设备能够处理、储存和传送声波，我们需要将连续的声音信号转换为一个离散的序列。我们称之为数字化表示。

音频数据集里包含了许多音频段落的数字化文件，例如一段旁白或者一段音乐。你可能见过不同的文件格式，例如`.wav` (Waveform Audio File，音频波形文件)、 `.flac` (Free Lossless Audio Codec，免费无损音频编解码)
和 `.mp3` (MPEG-1 音频格式 3)。这些格式的主要区别在于他们的压缩方法不同。

下面我们来了解一下如何将连续的声音信号转换为这些数字化表示。原始的模拟信号首先被麦克风捕捉，并由声音信号转化为电信号。接下来，电信号会由模拟-数字转换器（模数转换器，Analog-to-Digital Converter, ADC）经由采样过程转换为数字化表示。

## 采样过程和采样率

采样是一种在固定的时间间隔上测量连续信号的数值的过程。采样过后的信号被称为_离散信号_，因为这些信号是在固定间隔上记录的有限长度信号。

<div class="flex justify-center">
    <img src="https://huggingface.co/datasets/huggingface-course/audio-course-images/resolve/main/Signal_Sampling.png" alt="Signal sampling illustration">
</div>

*示意图取自维基百科词条: [Sampling (signal processing)](https://en.wikipedia.org/wiki/Sampling_(signal_processing)) [采样](https://zh.wikipedia.org/zh-cn/%E5%8F%96%E6%A8%A3)*

**采样率**（sampling rate，也叫采样频率，sampling frequency）指的是每一秒钟内测量信号数值的次数，单位为赫兹（Hz）。作为参照，CD音质的音频一般采用44100赫兹的采样率，意味着每秒钟测量了44100次信号的数值。作为对比，高清（High-resolution）音频的采样率一般为192000赫兹，即192千赫兹。语音模型常用的采样率为16,000赫兹，即16千赫兹。

采样率决定了能够被捕捉的最高频率。采样率的二分之一被称为奈奎斯特极限，这是该采样率能够捕捉的最高频率。人耳可辨认的语音信号往往在8千赫兹以下，因此16千赫兹的采样率足够捕捉所有可听到的语音内容。使用更高的采样率并不能采集到更多的信息，并且往往会导致计算成本的增加。另一方面，过低的采样率会导致信息丢失。使用8千赫兹采样的音频会听起来很闷，因为该采样率无法捕捉更高频率的声音。

在处理音频任务时，切记要保证数据集中的所有数据都使用了相同的采样率。如果你计划使用自己的数据来对预训练模型进行微调，你自己的音频数据和预训练模型所使用的音频数据需要保持相同的采样率。采样率决定了相邻的音频采样点的间隔时间，同时也影响着音频数据的时间分辨率。设想这样一个例子：一段5秒长度，16000赫兹采样率的音频等效于一个40000个数据点的序列。Transformer模型会使用注意力机制来学习音频或多模态表征。由于序列的长度会根据音频采样率而变化，我们的模型很难对不同的采样率进行泛化学习。**重采样**过程可以匹配不同音频文件的采样率，是音频数据[预处理](preprocessing#resampling-the-audio-data)过程的一部分。

## 幅值和位深度

采样率告诉了我们每个采样点之间的时间间隔，那么采样点的数值具体又是如何确定的呢？

声音本质上是人类可察觉范围内的气压的周期性波动。声音的**幅值**描述的是任意瞬间的气压大小，使用分贝（dB）作为单位。人类感知到的幅值强度称为响度。举个例子，正常的说话声音响度在60分贝以下；一场摇滚演出的响度大概在125分贝，几乎是人耳的极限。

在数字音频中，每个采样点都记录了某个时间点上的声波的幅值。采样点的**位深度**决定了采样点的数值可以有多少种变化，即采样的精度。位深度越大，数字化表示就可以越准确地记录下原始的连续声波。

最常见的音频位深度为16比特或24比特。比特是一个二进制单位，表示了声波的连续幅值被数字化后可以取值的范围：16比特有65,536种可能的取值，而24比特有16,777,216种可能的取值。在这一量化过程中，原始的连续幅值被约减到最近的离散值上，因此量化过程会引入噪声。位深度越大，量化噪声则越小。在实际应用中，16比特音频的量化噪声已达到了几乎不可辨别的程度，因此我们通常不会使用更大的位深度。

你也许听说过32比特音频。在这种设置下，采样点会被当作浮点数储存，而16比特或24比特则是将采样点作为整数储存。32比特的浮点数所拥有的精度实际上也是24比特，与24比特音频相同。浮点数采样点的数值变化范围是[-1.0, 1.0]。由于机器学习模型在设计上也采用浮点数据，因此事实上任何音频文件在被输入进模型进行训练之前都需要转换为浮点数。在下一个章节[音频数据预处理](preprocessing)中我们会详细介绍这一过程。

与连续声音信号相同，数字音频信号的响度也通常使用分贝（dB）表示。这是由于人耳对于声音响度的感知是遵循对数关系的：我们的耳朵对于细微声音的微小扰动的敏感度大于对吵闹声音的微小扰动的敏感度。分贝也遵循这样的对数关系。现实世界中声音的分贝值是从0分贝开始计算的，0分贝代表着人耳所能感知到的最小的声音，更大的声音则拥有更高的分贝值。然而在数字音频中，0分贝代表着最大的幅值，并且任何更小的声音都有着负数的分贝值。一个简单的规则是，每-6分贝会让幅值减半，而-60分贝以下的声音基本是不可感知的，除非音量被调到很大。

## 音频的波形表示

你可能见过被可视化为**波形**的声音信号。在这种图表中，采样点随着时间变化的数值被标记在直角坐标系中。这也被称为声音的*时域*表示。

这种可视化表示方法可以很好地帮助我们辨别声音信号中的某些特征，例如某个声音时间发生的时间、音频的整体响度、以及音频中的非正常部分或者噪声部分。

我们可以使用`librosa`这一Python库来绘制音频信号的波形图：

```bash
pip install librosa
```

我们可以使用库中自带的音频文件"trumpet"绘制示例图：

```py
import librosa

array, sampling_rate = librosa.load(librosa.ex("trumpet"))
```

这一示例音频文件以元组的形式被加载，第一个元素为音频的时间序列（我们命名为`array`），第二个元素为采样率（`sampling_rate`）。我们使用librosa的`waveshow()`函数来绘制该音频的波形图：

```py
import matplotlib.pyplot as plt
import librosa.display

plt.figure().set_figwidth(12)
librosa.display.waveshow(array, sr=sampling_rate)
```

<div class="flex justify-center">
    <img src="https://huggingface.co/datasets/huggingface-course/audio-course-images/resolve/main/waveform_plot.png" alt="Waveform plot">
</div>

该图中的y轴表示的是信号的幅值，x轴则表示时间。换句话说，图中的每个点都代表着该音频对应的原始信号被采样的某一瞬间的取值。同时我们也注意到librosa返回的音频序列已经是浮点数的，并且幅值的范围在[-1.0, 1.0]之间。

除了直接聆听音频外，音频的可视化也可以帮助我们更好地理解我们的数据。你可以观察信号的形状、总结信号的规律、学习如何找出信号中的噪音和失真。如果你使用了归一化、重采样或者滤波等的信号预处理方法，你可以用可视化的方法来确认预处理后的信号是否符合你的预期。在完成模型训练之后，你也可以可视化模型出错的数据（例如在音频分类任务中被分到错误类别的样本）来找到模型中的错误。

## 频谱图

另一种音频可视化的方法则是绘制出音频信号的**频谱**（spectrum），也称为信号的**频域**（frequency domain）表示。频谱可以通过离散傅里叶变换（Discrete Fourier Transform， DFT）求得，它描述了音频信号中每个频率成分的强度。

我们可以使用numpy的`rfft()`函数来绘制前文提到的小号声音的频谱图。虽然我们也可以绘制整个音频文件的频谱，但绘制一小段音频片段的频谱会更加有用。这里我们使用整段音频的前4096个采样点计算DFT，这差不多是第一个音符的长度：

```py
import numpy as np

dft_input = array[:4096]

# 计算 DFT
window = np.hanning(len(dft_input))
windowed_input = dft_input * window
dft = np.fft.rfft(windowed_input)

# 计算频谱的幅值，转换为分贝标度
amplitude = np.abs(dft)
amplitude_db = librosa.amplitude_to_db(amplitude, ref=np.max)

# 计算每个DFT分量对应的频率值
frequency = librosa.fft_frequencies(sr=sampling_rate, n_fft=len(dft_input))

plt.figure().set_figwidth(12)
plt.plot(frequency, amplitude_db)
plt.xlabel("Frequency (Hz)")
plt.ylabel("Amplitude (dB)")
plt.xscale("log")
```

<div class="flex justify-center">
    <img src="https://huggingface.co/datasets/huggingface-course/audio-course-images/resolve/main/spectrum_plot.png" alt="Spectrum plot">
</div>

这张图向我们展示了截取的音频片段中各个频率成分的强度。图中的x轴是频率的值，一般采用对数表示；y轴则对于频率的幅值。

可以看到这张频谱图中有几个峰值。这些峰值对应着当前音符的泛音频率，且更高的泛音声音更小。可以看到首个峰对应的频率在620赫兹左右，这说明当前演奏的音符的音高是E♭。

计算DFT所得到的频谱是由复数组成的序列，每个复数都包含了实部和虚部。我们可以使用`np.abs(dft)`来计算频谱的绝对值（又称模、幅值）。实部和虚部的夹角组成的序列也成为相位谱，但在机器学习应用中我们通常不关注这一部分。

我们使用了`librosa.amplitude_to_db()`函数将幅值转换为了分贝标度，方便我们观察频谱的细节。有时人们也使用测量能量而非幅值的**能量谱**（power spectrogram），其值为幅值的平方。

<Tip>
💡 在实践中，人们往往将快速傅里叶变换（Fast Fourier Transform, FFT）和离散傅里叶变换（Discrete Fourier Transform, DFT）这两个名词等价使用，这是因为FFT是在计算机中可以高效计算DFT的唯一方法。
</Tip>

音频信号的频谱和其波形所包含的信息其实完全相同，他们只是相同数据的不同表示方法（这里均表示该小号音频的前4096个样本）。两者的区别在于波形表示的是幅值随着时间的变化，而频谱表示的是各个频率成分在该时间段内的强度。

## 时频谱

我们能否用某种方法表示出频率成分随着时间的变化呢？在这段小号音频中，演奏者实际上吹奏了几个不同频率的音符。频谱的问题在于其只能表示一个短暂时间段内各个频率成分的总体幅值。这里的解决方法是我们可以进行多次的DFT，每次DFT都覆盖一小段不同的时间段，然后再把所有的频谱堆叠起来，这样就构成了**时频谱**（spectrogram）。

时频谱表示了音频信号中各个频率成分随时间变化的过程。它可以让你在一张图中看到时间、频率和幅值的所有信息。计算时频谱的算法被成为短时傅里叶变换（Short Time Fourier Transform, STFT）。

时频谱是信息量最大的音频工具之一。举个例子，在分析音乐文件时，时频谱可以清晰地展示出各个乐器和人声在音乐整体中所占的部分。在语音文件中，你可以在时频谱里看到每个元音音节以及它们频率成分的差异。

我们使用librosa的`stft()`函数和`specshow()`函数来绘制同一段小号音频的时频谱图：

```py
import numpy as np

D = librosa.stft(array)
S_db = librosa.amplitude_to_db(np.abs(D), ref=np.max)

plt.figure().set_figwidth(12)
librosa.display.specshow(S_db, x_axis="time", y_axis="hz")
plt.colorbar()
```

<div class="flex justify-center">
    <img src="https://huggingface.co/datasets/huggingface-course/audio-course-images/resolve/main/spectrogram_plot.png" alt="Spectrogram plot">
</div>

该图中，x轴表示的是和波形图中相同的时间，但y轴现在表示着不同的频率，以赫兹为单位。颜色的强度表示着当前时间点和频率的幅值强度，使用分贝（dB）标度。

时频谱的计算大概经过以下几个步骤：首先截取很短的音频片段（通常只有几毫秒），然后对每个片段计算其离散傅里叶变换（DFT）；获得所有片段的频谱之后，我们再将频谱延时间轴堆叠起来，这样就得到了我们的时频谱。时频谱图像的每个垂直切片都是一个单独的频谱图。`librosa.stft()`函数在默认条件下会把音频信号分割为2048个样本的许多切片，这一数字是在权衡了时频谱的频域分辨率和时域分辨率之后设置的。

由于时频谱和波形是同一信号的不同表示方法，我们也可以利用反向短时傅里叶变换（inverse STFT）将时频谱转换回原始的波形。然而，这一操作除了需要时频谱的强度谱之外，也需要时频谱的相位谱。目前的机器学习模型大多只能生成强度谱。这时我们可以使用一些相位重建（phase reconstruction）方法，包括传统的Griffin-Lim算法，或者使用一种被称为声码器（vocoder）的神经网络来从时频谱还原其波形。

时频谱的作用不仅在于音频的可视化。许多机器学习模型也会使用时频谱作为模型的输入和输出而不直接使用音频的波形。

现在我们了解了时频谱的原理和计算方法，我们来进一步学习一下在语音处理中常见的一种时频谱变体：梅尔时频谱。

## 梅尔时频谱

梅尔时频谱（简称梅尔谱）是一种在语音处理和机器学习中常用的时频谱变体。梅尔谱也和时频谱一样表示了频率成分随时间的变化，只是频率所在的轴不同。

在标准的时频谱中，频率所在的轴是赫兹的线性变化轴。然而，人类的听觉系统对于低频率声音的变化更敏感，对于高频率声音的变化则较不敏感。这一敏感度的变化是随频率的上升呈对数关系下降的。梅尔标度作为一种感知标度模拟了人耳对于频率的非线性感知。

为了生成信号的梅尔谱，我们首先使用和标准时频谱相同的短时傅里叶变换（STFT）将音频分割为许多短时片段，并计算每个片段的频谱。然后，我们将每个片段的频谱输入进梅尔滤波器组（mel filterbank），来将频率成分转换到梅尔标度。 

下面我们使用librosa的`melspectrogram()`函数绘制梅尔谱图，该函数帮我们执行了上述的所有步骤：

```py
S = librosa.feature.melspectrogram(y=array, sr=sampling_rate, n_mels=128, fmax=8000)
S_dB = librosa.power_to_db(S, ref=np.max)

plt.figure().set_figwidth(12)
librosa.display.specshow(S_dB, x_axis="time", y_axis="mel", sr=sampling_rate, fmax=8000)
plt.colorbar()
```

<div class="flex justify-center">
    <img src="https://huggingface.co/datasets/huggingface-course/audio-course-images/resolve/main/mel-spectrogram.png" alt="Mel spectrogram plot">
</div>


在这段例子中，`n_mels`代表梅尔滤波器组中的滤波器个数。梅尔滤波器组会计算一组频率范围，这些频率范围会将整个频谱分割成许多部分。每个频率范围都对应滤波器组中的一个滤波器，滤波器的形状和间隔是模拟人耳对不同频率的感知差异而计算得出。常用的`n_mels`取值为40或80。`fmax`则代表我们想选取的最大频率（以赫兹为单位）。

和标准频谱一样，我们也会将梅尔频率成分的强度转化为分贝标度。由于分贝的转化过程涉及到对数运算，转化后的梅尔谱通常被称为**对数梅尔时频谱**（log-mel spectrum）。在上面示例中，我们使用`librosa.power_to_db()`函数和`librosa.feature.melspectrogram()`来生成能量对数梅尔时频谱。

<Tip>
💡 梅尔视频谱间也有各种区别！有两种常用的mel计算标度（"htk" 和 "slaney"），此外还有能量谱和幅度谱的区别。对数梅尔谱的转换有时仅仅是简单计算`对数`而不会完整转化为分贝标度。因此，在使用以梅尔谱作为输入的机器学习模型时，我们建议你检查梅尔谱的计算过程是否完全一致。
</Tip>

由于梅尔谱的计算过程中需要对信号进行滤波，梅尔谱的计算是一个有损过程。将梅尔谱转化回波形比将标准时频谱转化回波形更加困难，因为我们需要估计在滤波过程中丢失的频率成分。这就是为何我们需要HiFiGAN声码器等机器学习模型来将梅尔谱转化回波形。

与标准时频谱相比，梅尔谱可以捕捉更多人类可感知的音频特征，因此梅尔谱也成为了在语音识别、说话人识别、音乐风格分类等任务中更常用的选择。

现在你已经学会如何可视化音频数据了，试着可视化看看你最喜欢的声音吧:)

<EditOnGithub source="https://github.com/huggingface/audio-transformers-course/blob/main/chapters/zh-CN/chapter1/audio_data.mdx" />

### 第2单元：音频应用的入门介绍
https://huggingface.co/learn/audio-course/zh-CN/chapter2/introduction.md

# 第2单元：音频应用的入门介绍

欢迎来到Hugging Face音频课程的第二单元！我们已经掌握了音频数据的基础，以及使用🤗 Datasets和 🤗 Transformers加载音频数据的方法。我们已经了解了许多音频相关的概念，例如采样率、位深度、波形、时频谱等等。我们也已经了解到如何对数据进行预处理，并为模型的预训练准备数据。

现在，你也许会想了解🤗 Transformers能处理怎样的任务，并且你已经掌握了所有相关的基础知识！我们来看看这些令人震惊的音频应用吧：

* **声音分类**（Audio classification）：将音频片段分入不同的类别。我们可以认出该音频是汪汪的狗叫声、喵喵的猫叫声，或者某个特定的音乐风格。
* **自动语音识别**（Automatic speech recognition, ASR）：将语音片段自动转录成文字。你可以将一段对话自动转化为文字稿，例如一段“你今天怎么样？”的录音。对于做笔记会很有用！
* **说话人识别**（Speaker diarization）：想知道录音里在说话的人是谁吗？我们可以利用🤗 Transformers来识别出一段录音中正在说话的人的身份，例如在一段对话的录音中识别出Alice和Bob各自说话的部分。
* **由文字生成语音**（Text to speech, TTS）：由一段文字合成其对应的语音，例如生成一本有声书，语音助手，或者给游戏中的NPC配音。利用🤗 Transformers，你可以轻松地做到！

在本单元中，你会学到如何使用🤗 Transformers的`pipeline()`函数加载预训练模型来完成上述的一些任务。我们将重点介绍如何使用预训练模型完成声音分类和自动语音识别任务。让我们开始吧！

<EditOnGithub source="https://github.com/huggingface/audio-transformers-course/blob/main/chapters/zh-CN/chapter2/introduction.mdx" />

### 利用pipeline进行音频分类
https://huggingface.co/learn/audio-course/zh-CN/chapter2/audio_classification_pipeline.md

# 利用pipeline进行音频分类

音频分类任务需要根据音频的内容对其进行单个和多个标签的识别。这些标签可以代表不同的声音类别，如音乐、语音或噪音，也可以代表更细分的分类，如鸟叫声和引擎声等。

在深入了解音频Transformer的细节和微调（fine-tuning）之前，我们先来看看如何使用🤗 Transformers和现成的预训练模型，在几行代码中构建一个音频分类器。

我们使用和前几个单元相同的[MINDS-14](https://huggingface.co/datasets/PolyAI/minds14) 数据集。回忆一下，MINDS-14数据集的内容是许多不同语言和方言的使用者向一个网银系统提问的录音。其标签包含了`intent_class`，记录了该次对话的目的。dataset that you have explored

和之前一样，我们先加载`en-AU`子集来测试我们的pipeline，将其上采样至音频模型常见的16千赫兹采样率。

```py
from datasets import load_dataset
from datasets import Audio

minds = load_dataset("PolyAI/minds14", name="en-AU", split="train")
minds = minds.cast_column("audio", Audio(sampling_rate=16_000))
```

我们可以使用🤗 Transformers中的`audio-classification` pipeline来构建一个将音频分入一个类别集的任务。在我们的设定下，我们需要一个已经针对音频的目的分类在MINDS-14数据集上微调过的模型。运气好的是，在🤗 Hub中已经有了一个这样的模型！让我们使用`pipeline()`函数来加载该模型：

```py
from transformers import pipeline

classifier = pipeline(
    "audio-classification",
    model="anton-l/xtreme_s_xlsr_300m_minds14",
)
```

这个pipeline需要以NumPy数组形式输入的音频数据。对于原始音频数据的预处理操作则已经包含在了该pipeline中。让我们拿一个样本来测试一下：

```py
example = minds[0]
```

回忆一下数据集的结构，我们的原始音频数据是以NumPy数组的形式存储在`["audio"]["array"]`中的。我们将其直接传递给`classifier`:

```py
classifier(example["audio"]["array"])
```

**输出：**
```out
[
    {"score": 0.9631525278091431, "label": "pay_bill"},
    {"score": 0.02819698303937912, "label": "freeze"},
    {"score": 0.0032787492964416742, "label": "card_issues"},
    {"score": 0.0019414445850998163, "label": "abroad"},
    {"score": 0.0008378693601116538, "label": "high_value_payment"},
]
```

模型非常确信，在这个样本中，顾客的目的是了解关于账单的信息。让我们来看看该样本的真实标签：

```py
id2label = minds.features["intent_class"].int2str
id2label(example["intent_class"])
```

**输出**
```out
"pay_bill"
```

好耶！模型预测正确！我们很幸运地找到了一个预测标签和我们需要的标签完全相同的预训练模型。很多时候，我们所找的预训练模型可以预测的标签集和我们所需要的标签集不会完全相同。这种情况下，我们需要对该预训练模型进行微调（fine-tuning），来将其“校准”到我们需要的标签集上。我们在接下来的单元中会学到微调相关的知识。现在，我们来看看另一个语音处理中常见的任务，_自动语音识别_。

<EditOnGithub source="https://github.com/huggingface/audio-transformers-course/blob/main/chapters/zh-CN/chapter2/audio_classification_pipeline.mdx" />

### 利用pipeline进行自动语音识别
https://huggingface.co/learn/audio-course/zh-CN/chapter2/asr_pipeline.md

# 利用pipeline进行自动语音识别

自动语音识别（Automatic Speech Recognition, ASR）是一个将录音转录为文字的任务。该任务有许多非常实用的应用，包括生成视频字幕、语音助手（Siri、Alexa等）的语音指令功能等等。

本小节中，我们将使用`automatic-speech-recognition` pipeline来转录MINDS-14数据集中一段用户询问账单信息的录音。

首先，我们根据[利用pipeline进行音频分类](audio_classification_pipeline.mdx)一节的方法加载数据并上采样至16千赫兹。

接下来，我们利用🤗 Transformers中的`automatic-speech-recognition` pipeline来构建自动语音识别的任务：

```py
from transformers import pipeline

asr = pipeline("automatic-speech-recognition")
```

下面，我们从数据集中选取一个样本，并将该样本的原始音频数据传递给pipeline：

```py
example = minds[0]
asr(example["audio"]["array"])
```

**输出**
```out
{"text": "I WOULD LIKE TO PAY MY ELECTRICITY BILL USING MY COD CAN YOU PLEASE ASSIST"}
```

我们可以将模型转录的结果和该样本标签中真实的转录文字进行对比：

```py
example["english_transcription"]
```

**输出：**
```out
"I would like to pay my electricity bill using my card can you please assist"
```

看起来模型在文字转录上的效果还不错！和原始转录相比，仅有一个词（card）转录错误。考虑到用户使用的澳大利亚方言中“r”通常不发音，这个结果已经很不错了。 不过我并不推荐用一条鱼来支付你的下一张电费账单就是了！（译注：cod为鳕鱼）

在默认条件下，该pipeline使用的是一个针对英语语言的自动语音识别模型，这在本例中是可以的。如果你想尝试转录MINDS-14数据集中其他语言的子集，你可以在[🤗 Hub](https://huggingface.co/models?pipeline_tag=automatic-speech-recognition&language=fr&sort=downloads)上找到一个预训练的ASR模型。你可以首先按任务过滤模型列表，然后再按语言过滤。一旦你找到了喜欢的模型，将其名称作为`model`参数传递给pipeline即可。

让我们尝试一下MINDS-14数据集中德语子集的转录。首先加载`de-DE`子集：

```py
from datasets import load_dataset
from datasets import Audio

minds = load_dataset("PolyAI/minds14", name="de-DE", split="train")
minds = minds.cast_column("audio", Audio(sampling_rate=16_000))
```

接下来，我们获取一个样本并查看其转录：

```py
example = minds[0]
example["transcription"]
```

**输出**
```out
"ich möchte gerne Geld auf mein Konto einzahlen"
```

接下来，我们在🤗 Hub上找到一个针对德语语言的预训练ASR模型，然后将其名称传递给pipeline，并对样本进行转录：

```py
from transformers import pipeline

asr = pipeline("automatic-speech-recognition", model="maxidl/wav2vec2-large-xlsr-german")
asr(example["audio"]["array"])
```

**输出**
```out
{"text": "ich möchte gerne geld auf mein konto einzallen"}
```

德语也没问题！

当你在解决自己的任务时，从本单元中展示的简单pipeline开始是一个非常有价值的工具，它有以下几个好处：
- 预训练模型可能已经能够很好地解决你的任务，从而节省了大量时间
- `pipeline()`会为你进行所有的预处理和后处理操作，因此你不必再花时间将数据转换为模型正确的格式
- 如果结果不理想，pipeline仍然可以为你提供一个快速实现的基线，以便在未来对其进行微调
- 如果你在自己的数据集上微调了一个模型并将其分享到Hub上，整个社区都可以通过`pipeline()`方法快速轻松地使用它，从而使AI更加易于使用。


<EditOnGithub source="https://github.com/huggingface/audio-transformers-course/blob/main/chapters/zh-CN/chapter2/asr_pipeline.mdx" />

### 实战练习
https://huggingface.co/learn/audio-course/zh-CN/chapter2/hands_on.md

# 实战练习

本节的练习不计入课程成绩，旨在帮助你熟悉本课程中将要使用的工具和库。如果你已经熟悉使用 Google Colab、🤗 Datasets、librosa 和 🤗 Transformers，你可以选择跳过本节练习。

1. 创建一个 [Google Colab](https://colab.research.google.com) 笔记本。
2. 使用 🤗 Datasets 加载 [`facebook/voxpopuli` 数据集](https://huggingface.co/datasets/facebook/voxpopuli) 的训练集，语言任选，使用流式加载模式。
3. 获取数据集`train`部分的第三个样本并探索它。根据这个样本所拥有的特征，你可以用这个数据集做哪些音频任务？
4. 绘制这个样本的波形图和频谱图。
5. 前往 [🤗 Hub](https://huggingface.co/models)，探索预训练模型并找到一个可以用于你之前选择的语言的自动语音识别的模型。使用你找到的模型实例化一个对应的 pipeline，并转录这个样本。
6. 将你从 pipeline 得到的转录与样本的真实转录文字进行比较。

如果你在这个练习中遇到了问题，可以参考[示例解决方案](https://colab.research.google.com/drive/1NGyo5wFpRj8TMfZOIuPaJHqyyXCITftc?usp=sharing)。你发现了什么有趣的东西？找到了一个很酷的模型？得到了一个漂亮的频谱图？欢迎在 Twitter 上分享你的工作和发现！

在接下来的章节中，你会学习到更多关于各种音频Transformer架构的知识，并训练你自己的模型！


<EditOnGithub source="https://github.com/huggingface/audio-transformers-course/blob/main/chapters/zh-CN/chapter2/hands_on.mdx" />

### 评估语音合成模型
https://huggingface.co/learn/audio-course/zh-CN/chapter6/evaluation.md

# 评估语音合成模型

在训练期间，语音合成模型旨在优化平均平方误差损失（mean-square error loss，简称 MSE）或平均绝对误差（mean absolute error，简称 MAE），
这两者衡量的是预测的频谱图和真正的频谱图之间的差异。MSE 和 MAE 都鼓励模型最小化预测和目标频谱图之间的差异。然而，由于 TTS 是一种一对多映射问题，
即给定文本的输出频谱图有多种可能性，所以评估语音合成模型要比预想的困难得多。

与许多其他可以使用定量指标（如准确率、精确度）客观衡量的计算任务不同，评估 TTS 主要依赖于主观的人类分析。

评估 TTS 系统最常用方法之一是通过平均意见得分（Mean Opinion Scores，简称 MOS）进行定性评估。MOS 是一种主观评分系统，
允许人类评估者对合成语音的感知质量进行评分，评分范围从 1 到 5。这些得分通常通过听力测试收集，参与者听取并评价合成语音样本。

对于 TTS 评估来说，设置客观指标困难的主要原因之一是语音感知的主观性。不同的人类听众对语音的不同方面，包括发音、语调、自然度和清晰度，
有着各自的偏好和敏感度。用单一的数值捕捉这些感知细微差别是一项艰巨的任务。同时，人类评估的主观性使得比较和基准测试不同的 TTS 系统变得困难。

此外，这种评估可能会忽视语音合成的某些重要方面，如自然度、表达力和情感影响。这些质量难以客观量化，但在需要合成语音达到类人的品质和引发适当情感反应的应用中至关重要。

总之，由于缺乏一个真正客观的指标，评估语音合成模型是一项复杂的任务。最常用的评估方法，即平均意见得分（MOS），依赖于主观的人类分析。
尽管 MOS 提供了对合成语音质量的宝贵意见，但它也引入了不确定性和主观性。


<EditOnGithub source="https://github.com/huggingface/audio-transformers-course/blob/main/chapters/zh-CN/chapter6/evaluation.mdx" />

### 第六单元：从文本到语音
https://huggingface.co/learn/audio-course/zh-CN/chapter6/introduction.md

# 第六单元：从文本到语音

在上一个单元中，您学习了如何使用 Transformers 将语音转换成文本。现在，让我们换一个方向，看看该如何将输入的文本转换成听起来像人类语音的音频输出。

我们在这个单元将学习的任务称为“语音合成”（Text-to-Speech，简称 TTS），要将文本转换为可听的人类语音。这样的模型具有广泛的潜在应用：

* 辅助 app：可以利用这些模型帮助视觉障碍人士通过声音媒介访问数字内容。
* 有声读物朗读：将文本的书籍转换成音频形式，使比起读喜欢听的或阅读有困难的人们能更容易地欣赏文学作品。
* 虚拟助手：TTS 模型是 Siri、Google Assistant 或 Amazon Alexa 等虚拟助手的基本组成部分。它们使用分类模型捕捉到唤醒词，并使用 ASR（语音识别）模型处理了您的请求之后，就可以使用 TTS 模型来回应您的问题。
* 娱乐、游戏和语言学习：为您的 NPC（非玩家角色）赋予声音，叙述游戏事件，或帮助语言学习者了解单词和短语的正确发音和语调。

这些只是一些例子，我相信您还可以想象出更多！然而，能力越大责任越大，需要强调 TTS 模型有可能被用于恶意目的。例如，有了足够的声音样本，
不法分子可能会合成出足以以假乱真的假语音，未经授权使用他人的声音，甚至用于诈骗。如果想要收集数据以微调自己的系统，请仔细考虑隐私和知情同意。
获取声音数据应获得个人的明确同意，确保他们理解声音在 TTS 系统中使用的目的、范围和潜在风险。请负责任地使用语音合成技术。

在这一章中，我们将介绍：

* [适合训练语音合成模型的数据集](tts_datasets)
* [语音合成的预训练模型](pre-trained_models)
* [在一门新语言上微调 SpeechT5](fine-tuning)
* [评估语音识别模型的性能](evaluation)


<EditOnGithub source="https://github.com/huggingface/audio-transformers-course/blob/main/chapters/zh-CN/chapter6/introduction.mdx" />

### 补充阅读
https://huggingface.co/learn/audio-course/zh-CN/chapter6/supplemental_reading.md

# 补充阅读

本单元介绍了语音合成任务，包含了很多内容。想要了解更多吗？在这里，您将找到额外的资源，帮助您深入理解这些主题并提升您的学习体验。

* [HiFi-GAN: 用于高效和高保真语音合成的生成对抗网络](https://arxiv.org/pdf/2010.05646.pdf)：介绍语音合成中的声码器 HiFi-GAN 的论文。
* [X-Vectors: 用于说话人识别的鲁棒 DNN 嵌入](https://www.danielpovey.com/files/2018_icassp_xvectors.pdf)：介绍说话人嵌入的 X-Vector 方法的论文。
* [FastSpeech 2: 快速且高质量的端到端语音合成](https://arxiv.org/pdf/2006.04558.pdf)：介绍 FastSpeech 2 的论文，这是另一个流行的语音合成模型，它使用了一种非自回归的 TTS 方法。
* [文本到语音合成的一种基于真实自发语音的向量量化方法](https://arxiv.org/pdf/2302.04215v1.pdf)：介绍 MQTTS 的论文，这是一个自回归的 TTS 系统，它用量化的离散表示替换了梅尔谱。


<EditOnGithub source="https://github.com/huggingface/audio-transformers-course/blob/main/chapters/zh-CN/chapter6/supplemental_reading.mdx" />

### 语音合成数据集
https://huggingface.co/learn/audio-course/zh-CN/chapter6/tts_datasets.md

# 语音合成数据集

语音合成任务（也称为 _文本转语音_，Text-to-Speech）面临许多挑战。

首先，就像之前讨论的语音识别（ASR）一样，文本和语音之间的对齐可能很棘手。然而，与 ASR 不同的是，TTS 是一个**一对多**映射问题，
即同一文本可以以多种不同方式合成。想想您每天听到的语音中声音和说话风格的多样性——每个人说同一句话的方式都不同，但它们都是有效且正确的！
TTS 模型的不同输出，频谱图或音频波形，可能对应同一个真实结果（ground truth）。模型必须学会为每个音素、单词或句子生成正确的持续时间和时序，
这可能会很有挑战性，特别是对于长且复杂的句子。

其次，TTS 中存在长距离依赖问题：语言具有时间维度，理解句子的意义通常需要考虑包含了周围词汇的上下文。确保 TTS 模型能捕获并保留长序列中的上下文信息对于生成连贯自然的语音至关重要。

最后，训练 TTS 模型通常需要文本和相应的语音录音配对，且为保证模型能够为不同说话人和说话风格生成自然的语音，数据应包含来自多个说话人的多样、有代表性的语音样本。
收集此类数据既昂贵又耗时，并且对于某些语言来说并不可行。您可能会想，为什么不直接采用为 ASR 设计的数据集来训练 TTS 模型呢？不幸的是，
ASR 数据集并不是最佳选择。它们对于 ASR 有益的特性，如过多的背景噪音，在 TTS 中通常是不好的。能够在嘈杂街道的录音中辨别出语音是很棒的，
但如果您的语音助手回答您时背景有汽车喇叭声和建筑施工声就不那么理想了。尽管如此，有时可以用一些 ASR 数据集来微调，因为寻找高质量、多语言、多说话人的 TTS 数据集可能相当困难。

让我们来探索一些 🤗 Hub 上适用于 TTS 的数据集吧。

## LJSpeech

[LJSpeech](https://huggingface.co/datasets/lj_speech) 是一个包含 13,100 个英语音频片段及其对应转写的大型数据集，
内容为一位说话人朗读 7 本纪实类英语书籍的录音。由于其音质高，语言内容多样，LJSpeech 经常被用作评估 TTS 模型的基准。

## Multilingual LibriSpeech

[Multilingual LibriSpeech](https://huggingface.co/datasets/facebook/multilingual_librispeech) 是 LibriSpeech 数据集的多语言扩展版。
后者只包含英语有声读物，而 Multilingual LibriSpeech 还包括了额外的语言，如德语、荷兰语、西班牙语、法语、意大利语、葡萄牙语和波兰语。
它提供了每种语言的音频录音以及与之对齐的转写。这个数据集为开发多语言 TTS 系统和探索跨语言语音合成技术提供了宝贵的资源。

## VCTK (Voice Cloning Toolkit)

[VCTK](https://huggingface.co/datasets/vctk) 是专为语音合成研究和开发设计的数据集。它包含了 110 位不同口音的人说英语的录音，每位说话人读约 400 个句子，
这些句子选自报纸、the rainbow passage 和旨在识别说话者口音的引出段落（elicitation paragraph）。VCTK 为训练具有多样化声音和口音的 TTS 模型提供了宝贵资源，使语音合成更加自然和多样化。

## Libri-TTS / LibriTTS-R

[Libri-TTS / LibriTTS-R](https://huggingface.co/datasets/cdminix/libritts-r-aligned) 是一个由约 585 小时的英语朗读语音组成的多说话人英语语料库，
采样率 24kHz，由 Heiga Zen 在 Google Speech 和 Google Brain 团队成员的协助下搭建。LibriTTS 语料库专为 TTS 研究而设计，它源自 LibriSpeech 语料库的原始材料
（LibriVox 的 mp3 音频文件和 Project Gutenberg 的文本文件）。它与 LibriSpeech 语料库的主要区别如下：

* 音频文件是 24kHz 采样率。
* 每条语音数据在句子与句子间断开。
* 包括原始和规范化的文本。
* 可以提取上下文信息（例如相邻句子）。
* 排除了具有显著背景噪音的语音数据。

组建一个适用于 TTS 的优秀数据集并非易事，因为这样的数据集需要具备几个关键特性：

* 高质量和多样化的录音，涵盖广泛的语音模式、口音、语言和情感。录音应清晰、无背景噪音，并展现自然的语音特征。
* 转写：每个音频录音应有其相应的文本转写。
* 语言内容的多样性：数据集应包含语言多样化的内容，包括不同类型的句子、短语和单词。它应涵盖各种主题、体裁和领域，以确保模型能够处理不同的语言环境。

好消息是，您很可能不需要从头开始训练一个 TTS 模型。在下一节中，我们将探讨在 🤗 Hub 上可用的预训练模型。



<EditOnGithub source="https://github.com/huggingface/audio-transformers-course/blob/main/chapters/zh-CN/chapter6/tts_datasets.mdx" />

### 微调 SpeechT5
https://huggingface.co/learn/audio-course/zh-CN/chapter6/fine-tuning.md

# 微调 SpeechT5

现在您已经熟悉了语音合成任务和 SpeechT5 模型的内部工作原理，该模型是在英语数据上预训练的，让我们看看如何将其微调到另一种语言。

## 基础准备

如果您想复现这个示例，请确保您有一个 GPU。在笔记本中，您可以使用以下命令检查：

```bash
nvidia-smi
```

<Tip warning={true}>

在我们的示例中，我们将使用大约 40 小时的训练数据。如果您想使用 Google Colab 免费版的 GPU 复现，需要将训练数据量减少到大约 10-15 小时，并减少训练步骤的数量。

</Tip>

您还需要一些额外的依赖：

```bash
pip install transformers datasets soundfile speechbrain accelerate
```

最后，不要忘记登录您的 Hugging Face 账户，以便您能够上传并与社区共享您的模型：

```py
from huggingface_hub import notebook_login

notebook_login()
```

## 数据集

在这个示例中，我们将使用 [VoxPopuli](https://huggingface.co/datasets/facebook/voxpopuli) 数据集的荷兰语（`nl`）子集。
[VoxPopuli](https://huggingface.co/datasets/facebook/voxpopuli) 是一个大规模的多语言语音语料库，包含了 2009-2020 年欧洲议会事件的录音数据。
它包含 15 种欧洲语言的带标签的音频-转写数据。虽然我们将使用荷兰语子集，但您可以自由选择其他子集。

这是一个语音识别（ASR）数据集，所以，如前所述，它不是训练 TTS 模型的最佳选择。然而，对于这个练习来说，它已经足够好了。

让我们加载数据：

```python
from datasets import load_dataset, Audio

dataset = load_dataset("facebook/voxpopuli", "nl", split="train")
len(dataset)
```

**输出：**

```out
20968
```

20968 条数据应该足以进行微调。输入 SpeechT5 的音频数据应具有 16 kHz 的采样率，所以要确保我们的数据集满足这一要求：

```python
dataset = dataset.cast_column("audio", Audio(sampling_rate=16000))
```

## 数据预处理

处理器包含了分词器和特征提取器，我们需要用它们来预处理训练数据。所以我们先定义要使用的模型检查点，并加载对应的处理器：

```py
from transformers import SpeechT5Processor

checkpoint = "microsoft/speecht5_tts"
processor = SpeechT5Processor.from_pretrained(checkpoint)
```

### 为 SpeechT5 分词进行文本清理

首先，为了处理文本，我们需要处理器的分词器部分，所以让我们来获取它：

```py
tokenizer = processor.tokenizer
```

让我们看一个示例：

```python
dataset[0]
```

**输出：**

```out
{'audio_id': '20100210-0900-PLENARY-3-nl_20100210-09:06:43_4',
 'language': 9,
 'audio': {'path': '/root/.cache/huggingface/datasets/downloads/extracted/02ec6a19d5b97c03e1379250378454dbf3fa2972943504a91c7da5045aa26a89/train_part_0/20100210-0900-PLENARY-3-nl_20100210-09:06:43_4.wav',
  'array': array([ 4.27246094e-04,  1.31225586e-03,  1.03759766e-03, ...,
         -9.15527344e-05,  7.62939453e-04, -2.44140625e-04]),
  'sampling_rate': 16000},
 'raw_text': 'Dat kan naar mijn gevoel alleen met een brede meerderheid die wij samen zoeken.',
 'normalized_text': 'dat kan naar mijn gevoel alleen met een brede meerderheid die wij samen zoeken.',
 'gender': 'female',
 'speaker_id': '1122',
 'is_gold_transcript': True,
 'accent': 'None'}
```

您可能会注意到数据包含 `raw_text` 和 `normalized_text` 特征。在决定使用哪个特征作为文本输入时，需要注意的是 SpeechT5 分词器没有任何数字的词元。
在 `normalized_text` 中，数字被写成文本。因此，它更合适，我们应该使用 `normalized_text` 作为输入文本。

因为 SpeechT5 是在英语上训练的，它可能无法识别荷兰语数据集中的某些字符。如果保持原样，这些字符将被转换为 `<unk>` 词元。
然而，在荷兰语中，某些字符如 `à` 用于强调音节。为了保留文本的含义，我们可以将此字符替换为普通的 `a`。

要识别不支持的词元，使用 `SpeechT5Tokenizer` 提取数据集中所有独特字符，该分词器将字符视为词元。为此，我们将编写 `extract_all_chars` 映射函数，
该函数将所有数据样例的转写连接成一个字符串，然后转换为字符集。确保在 `dataset.map()` 中设置 `batched=True` 和 `batch_size=-1`，以便一次性获取所有转写并输入映射函数。

```py
def extract_all_chars(batch):
    all_text = " ".join(batch["normalized_text"])
    vocab = list(set(all_text))
    return {"vocab": [vocab], "all_text": [all_text]}


vocabs = dataset.map(
    extract_all_chars,
    batched=True,
    batch_size=-1,
    keep_in_memory=True,
    remove_columns=dataset.column_names,
)

dataset_vocab = set(vocabs["vocab"][0])
tokenizer_vocab = {k for k, _ in tokenizer.get_vocab().items()}
```

现在您有两组字符：一个来自数据集，另一个来自分词器。要识别数据集中任何不支持的字符，您可以取这两组的差集，结果将包含在数据集中而不在分词器中的字符。

```py
dataset_vocab - tokenizer_vocab
```

**输出：**

```out
{' ', 'à', 'ç', 'è', 'ë', 'í', 'ï', 'ö', 'ü'}
```

为了处理上一步骤中识别的不支持字符，我们可以定义一个将这些字符映射到有效词元的函数。注意，分词器中的空格已经被替换为 `▁`，因此不需要单独处理。

```py
replacements = [
    ("à", "a"),
    ("ç", "c"),
    ("è", "e"),
    ("ë", "e"),
    ("í", "i"),
    ("ï", "i"),
    ("ö", "o"),
    ("ü", "u"),
]


def cleanup_text(inputs):
    for src, dst in replacements:
        inputs["normalized_text"] = inputs["normalized_text"].replace(src, dst)
    return inputs


dataset = dataset.map(cleanup_text)
```

现在我们处理好了文本中的特殊字符，是时候将注意力转移到音频数据上了。

### 说话人

VoxPopuli 数据集包含多个说话人的语音，但到底有多少呢？我们可以计算一下数据集中说话人的数量以及每个说话人贡献的数据量。
数据集总共有 20,968 条数据，这些信息将帮助我们更好地了解数据中的说话人和数据样例的分布。

```py
from collections import defaultdict

speaker_counts = defaultdict(int)

for speaker_id in dataset["speaker_id"]:
    speaker_counts[speaker_id] += 1
```

通过绘制直方图，您可以了解每个说话人的数据量。

```py
import matplotlib.pyplot as plt

plt.figure()
plt.hist(speaker_counts.values(), bins=20)
plt.ylabel("Speakers")
plt.xlabel("Examples")
plt.show()
```

<div class="flex justify-center">
    <img src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/transformers/tasks/tts_speakers_histogram.png" alt="Speakers histogram"/>
</div>

直方图显示，数据集中大约三分之一的说话人的数据少于 100 条，而大约十个说话人的数据超过 500 条。为了提高训练效率并平衡数据集，我们可以将数据限制在有 100 到 400 条数据的说话人之间。

```py
def select_speaker(speaker_id):
    return 100 <= speaker_counts[speaker_id] <= 400


dataset = dataset.filter(select_speaker, input_columns=["speaker_id"])
```

让我们检查还剩多少个说话人：

```py
len(set(dataset["speaker_id"]))
```

**输出：**

```out
42
```

让我们看看还剩多少条数据：

```py
len(dataset)
```

**输出：**

```out
9973
```

您留下了不到 10,000 条数据，来自大约 40 个独特的说话人，这应该足够用了。

请注意，如果某些数据很长，一些看似数据样例量较少的说话人可能有比预想的更多的音频数据。然而，确定每个说话人的总音频量需要扫描整个数据集，
这是一个耗时的过程，涉及加载和解码每个音频文件。因此，我们在这里选择跳过这一步。

### 说话人嵌入

为了使 TTS 模型能够区分多个说话人，您需要为每条数据创建一个说话人嵌入。说话人嵌入是模型的一个额外输入，用于描述特定说话人的声音特征。
要生成这些说话人嵌入，可以使用来自 SpeechBrain 的预训练模型 [spkrec-xvect-voxceleb](https://huggingface.co/speechbrain/spkrec-xvect-voxceleb)。

创建一个 `create_speaker_embedding()` 函数，该函数接受音频波形作为输入，并输出包含相应说话人嵌入的 512 维向量。

```py
import os
import torch
from speechbrain.pretrained import EncoderClassifier

spk_model_name = "speechbrain/spkrec-xvect-voxceleb"

device = "cuda" if torch.cuda.is_available() else "cpu"
speaker_model = EncoderClassifier.from_hparams(
    source=spk_model_name,
    run_opts={"device": device},
    savedir=os.path.join("/tmp", spk_model_name),
)


def create_speaker_embedding(waveform):
    with torch.no_grad():
        speaker_embeddings = speaker_model.encode_batch(torch.tensor(waveform))
        speaker_embeddings = torch.nn.functional.normalize(speaker_embeddings, dim=2)
        speaker_embeddings = speaker_embeddings.squeeze().cpu().numpy()
    return speaker_embeddings
```

注意，`speechbrain/spkrec-xvect-voxceleb` 模型是在 VoxCeleb 数据集的英语语音上训练的，而这个示例训练的是荷兰语。
虽然我们相信这个模型仍然可以为我们的荷兰语数据集生成合理的说话人嵌入，但这个假设可能不总是成立。

为了获得最佳结果，我们需要首先在目标语音上训练 X-Vector 模型。这将确保模型能够更好地捕捉荷兰语中存在的独特声音特征。如果您想训练自己的 X-向量模型，
可以参考 [此脚本](https://huggingface.co/mechanicalsea/speecht5-vc/blob/main/manifest/utils/prep_cmu_arctic_spkemb.py)。

### 处理数据集

最后，让我们将数据处理成模型能够读入的格式。创建一个 `prepare_dataset` 函数，输入单个示例并使用 `SpeechT5Processor` 对象来对输入文本进行分词，并将目标音频加载成对数梅尔谱。它还应该额外输入说话人嵌入。

```py
def prepare_dataset(example):
    audio = example["audio"]

    example = processor(
        text=example["normalized_text"],
        audio_target=audio["array"],
        sampling_rate=audio["sampling_rate"],
        return_attention_mask=False,
    )

    # 去掉批量处理的维度
    example["labels"] = example["labels"][0]

    # 用 SpeechBrain 获取 X-Vector
    example["speaker_embeddings"] = create_speaker_embedding(audio["array"])

    return example
```

查看单个示例来验证处理是否正确：

```py
processed_example = prepare_dataset(dataset[0])
list(processed_example.keys())
```

**输出：**

```out
['input_ids', 'labels', 'stop_labels', 'speaker_embeddings']
```

说话人嵌入应该是一个 512 维向量：

```py
processed_example["speaker_embeddings"].shape
```

**输出：**

```out
(512,)
```

标签应该是一个有 80 个 mel 频段的对数梅尔谱。

```py
import matplotlib.pyplot as plt

plt.figure()
plt.imshow(processed_example["labels"].T)
plt.show()
```

<div class="flex justify-center">
    <img src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/transformers/tasks/tts_logmelspectrogram_1.png" alt="Log-mel spectrogram with 80 mel bins"/>
</div>

注：如果您看不明白这个频谱图，可能是因为您习惯将低频放在底部，高频放在顶部。然而，在使用 matplotlib 库将频谱图作为图像绘制时，y 轴是反过来的，频谱图看起来是倒置的。

现在我们需要将处理函数应用于整个数据集。这将花费 5 到 10 分钟的时间。

```py
dataset = dataset.map(prepare_dataset, remove_columns=dataset.column_names)
```

您会看到一个警告说数据集中的某些数据长于模型能够处理的最大输入长度（600 词元），得从数据集中删除这些数据。在这里，我们更进一步，为了允许更大的批量大小，删除任何超过 200 词元的内容。

```py
def is_not_too_long(input_ids):
    input_length = len(input_ids)
    return input_length < 200


dataset = dataset.filter(is_not_too_long, input_columns=["input_ids"])
len(dataset)
```

**输出：**

```out
8259
```

接下来，把数据集分成基本的训练/测试子集：

```py
dataset = dataset.train_test_split(test_size=0.1)
```

### 数据整理器

为了将多条数据组合成一个批次，您需要定义一个自定义数据整理器。这个整理器将使用填充词元填充较短的序列，确保所有示例都具有相同的长度。
对于频谱图标签，填充部分将被特殊值 `-100` 替换。这个特殊值指示模型在计算频谱图的损失函数时忽略那部分频谱图。

```py
from dataclasses import dataclass
from typing import Any, Dict, List, Union


@dataclass
class TTSDataCollatorWithPadding:
    processor: Any

    def __call__(
        self, features: List[Dict[str, Union[List[int], torch.Tensor]]]
    ) -> Dict[str, torch.Tensor]:
        input_ids = [{"input_ids": feature["input_ids"]} for feature in features]
        label_features = [{"input_values": feature["labels"]} for feature in features]
        speaker_features = [feature["speaker_embeddings"] for feature in features]

        # 把输入数据和生成目标整合进一个批次
        batch = processor.pad(
            input_ids=input_ids, labels=label_features, return_tensors="pt"
        )

        # 把填充词元换成 -100 来正确地忽略这一部分的损失函数
        batch["labels"] = batch["labels"].masked_fill(
            batch.decoder_attention_mask.unsqueeze(-1).ne(1), -100
        )

        # 在微调时用不上，删了
        del batch["decoder_attention_mask"]

        # 把目标长度下调到 reduction factor 的整数倍
        if model.config.reduction_factor > 1:
            target_lengths = torch.tensor(
                [len(feature["input_values"]) for feature in label_features]
            )
            target_lengths = target_lengths.new(
                [
                    length - length % model.config.reduction_factor
                    for length in target_lengths
                ]
            )
            max_length = max(target_lengths)
            batch["labels"] = batch["labels"][:, :max_length]

        # 加上说话人嵌入
        batch["speaker_embeddings"] = torch.tensor(speaker_features)

        return batch
```

在 SpeechT5 中，模型的解码器部分的输入减少了 2 倍（reduction factor）。换句话说，它抛弃了目标序列中每两步中的一步。然后，解码器预测一个两倍长度的序列。
由于原来的目标序列长度可能是奇数，数据整理器会确保将批次的最大长度调整为 2 的倍数。

```py 
data_collator = TTSDataCollatorWithPadding(processor=processor)
```

## 训练模型

从与处理器相同的检查点加载预训练模型：

```py
from transformers import SpeechT5ForTextToSpeech

model = SpeechT5ForTextToSpeech.from_pretrained(checkpoint)
```

`use_cache=True` 选项与梯度检查点不兼容。我们在训练时禁用这个选项，并在生成时重新启用缓存以加快推理：

```py 
from functools import partial

# 在训练时禁用缓存
model.config.use_cache = False

# 设置语言和任务准备推理，并重新启用缓存
model.generate = partial(model.generate, use_cache=True)
``` 

定义训练参数。这里我们在训练过程中不计算任何评估指标，我们将在本章稍后讨论评估。这里，我们先只关注损失函数：

```python
from transformers import Seq2SeqTrainingArguments

training_args = Seq2SeqTrainingArguments(
    output_dir="speecht5_finetuned_voxpopuli_nl",  # 改成您选择的仓库名
    per_device_train_batch_size=4,
    gradient_accumulation_steps=8,
    learning_rate=1e-5,
    warmup_steps=500,
    max_steps=4000,
    gradient_checkpointing=True,
    fp16=True,
    evaluation_strategy="steps",
    per_device_eval_batch_size=2,
    save_steps=1000,
    eval_steps=1000,
    logging_steps=25,
    report_to=["tensorboard"],
    load_best_model_at_end=True,
    greater_is_better=False,
    label_names=["labels"],
    push_to_hub=True,
)
```

实例化 `Trainer` 对象并将模型、数据集和数据整理器传递给它。

```py
from transformers import Seq2SeqTrainer

trainer = Seq2SeqTrainer(
    args=training_args,
    model=model,
    train_dataset=dataset["train"],
    eval_dataset=dataset["test"],
    data_collator=data_collator,
    tokenizer=processor,
)
```

有了这个，我们就准备开始训练了！训练将花费几个小时。由于 GPU 不同，当您开始训练时，可能会遇到 CUDA 报“out-of-memory”（显存不足）的错误。这时，您可以尝试将 `per_device_train_batch_size` 两倍两倍地减少，并将 `gradient_accumulation_steps` 增加到两倍以补偿。

```py
trainer.train()
```

将最终的模型上传到 🤗 Hub：

```py
trainer.push_to_hub()
```

## 推理

一旦您微调了一个模型，您就可以使用它进行推理！从 🤗 Hub 加载模型（记得在以下代码片段中使用您的账号名）：

```py
model = SpeechT5ForTextToSpeech.from_pretrained(
    "您的账号/speecht5_finetuned_voxpopuli_nl"
)
```

选择一个示例，这里我们将从测试数据集中取一个。获取说话人嵌入。

```py 
example = dataset["test"][304]
speaker_embeddings = torch.tensor(example["speaker_embeddings"]).unsqueeze(0)
```

定义一些输入文本并对它进行分词。

```py 
text = "hallo allemaal, ik praat nederlands. groetjes aan iedereen!"
```

预处理输入文本：

```py
inputs = processor(text=text, return_tensors="pt")
```

实例化一个声码器并生成语音：

```py
from transformers import SpeechT5HifiGan

vocoder = SpeechT5HifiGan.from_pretrained("microsoft/speecht5_hifigan")
speech = model.generate_speech(inputs["input_ids"], speaker_embeddings, vocoder=vocoder)
```

准备好听结果了吗？

```py
from IPython.display import Audio

Audio(speech.numpy(), rate=16000)
```

用这个模型在新语言上获得的满意结果可能很有挑战性。说话人嵌入的质量可能是一个重要因素。由于 SpeechT5 是使用英语 X-Vector 预训练的，它在使用英语说话人嵌入时表现最佳。如果合成的语音听起来效果不好，尝试使用不同的说话人嵌入。

增加训练时长也可能提高结果的质量。但即便不继续训练，语音也显然是荷兰语而不是英语，并且它确实学到了说话人的声音特征（与示例中的原始音频相比较）。另一个可以试验的是模型的配置。例如，尝试使用 `config.reduction_factor = 1` 来看是否能改善结果。

在下一节中，我们将讨论如何评估语音合成模型。


<EditOnGithub source="https://github.com/huggingface/audio-transformers-course/blob/main/chapters/zh-CN/chapter6/fine-tuning.mdx" />

### 实战练习
https://huggingface.co/learn/audio-course/zh-CN/chapter6/hands_on.md

# 实战练习

在这个单元中，我们探讨了语音合成的音频任务，讨论了现有的 Datasets、预训练模型以及为新语言微调 SpeechT5 的细节。

正如您所见，为语音合成任务微调模型在资源较少的情况下可能比较有挑战性。同时，评估语音合成模型也不是一件容易的事。

因此，这个实践练习将侧重于练习技能，而不是达到特定的指标。

您在这个任务中的目标是在您选择的数据集上微调 SpeechT5。您可以从相同的 `voxpopuli` 数据集中选择另一种语言，或者选择本单元中列出的任何其他数据集。

注意训练数据的大小！如果在 Google Colab 免费版的 GPU 上训练，我们推荐将训练数据限制在大约 10-15 小时。

微调完成后，请上传到 Hub 来分享您的模型，并加上 `text-to-speech` 模型标签，可以通过 kwargs 或者在 Hub UI 中设置。

请记得，这个练习的主要目的是为您提供充分的实践，让您锻炼学到的技能并深入理解语音合成的音频任务。


<EditOnGithub source="https://github.com/huggingface/audio-transformers-course/blob/main/chapters/zh-CN/chapter6/hands_on.mdx" />

### 语音合成的预训练模型
https://huggingface.co/learn/audio-course/zh-CN/chapter6/pre-trained_models.md

# 语音合成的预训练模型

与 ASR（语音识别）和音频分类任务相比，语音合成的预训练模型检查点明显较少。在 🤗 Hub 上，您可以找到近 300 个适合的检查点。
在这些预训练模型中，我们将重点关注两种在 🤗 Transformers 库中开箱即用的架构——SpeechT5 和 Massive Multilingual Speech（MMS）。
在本节中，我们将探索如何在 Transformers 库中使用这些预训练模型进行 TTS（语音合成）。

## SpeechT5

[SpeechT5](https://arxiv.org/abs/2110.07205) 是由 Microsoft 的 Junyi Ao 等人发布的模型，它能够处理一系列语音任务。虽然本单元中我们关注的是文本转语音，
但这个模型还可以用于语音转文本的任务（语音识别或说话人识别），以及语音转语音的任务（例如语音增强或变声器）。这是模型设计和预训练的方式所决定的。

SpeechT5 的核心是一个常规的 Transformer 编码器-解码器模型。就像任何其他 Transformer 一样，编码器-解码器网络使用隐藏表示来模拟序列到序列的转换。这个 Transformer 骨架对 SpeechT5 支持的所有任务都是相同的。

除此之外，SpeechT5 还有六个模态特定（语音/文本）的预处理网络（_pre-nets_）和后处理网络（_post-nets_）。输入的语音或文本（取决于任务）通过相应的预处理网络被预处理，
以获得 Transformer 可以使用的隐藏表示。然后，Transformer 的输出被送进后处理网络，转化为目标模态的输出。

以下是该模型的架构（图片来源于原始论文）：

<div class="flex justify-center">
    <img src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/blog/speecht5/architecture.jpg" alt="SpeechT5 architecture from the original paper">
</div>

SpeechT5 首先使用大规模的未标注语音和文本数据进行预训练，以获得不同模态的统一表示。在预训练阶段，所有的预处理网络和后处理网络同时使用。

预训练之后，将整个编解码器骨架针对每个单独任务进行微调。在这个步骤中，只有与特定任务相关的预处理和后处理网络被采用。
例如，要使用 SpeechT5 进行语音合成，您需要选择文本编码器预处理网络来处理文本输入，以及语音解码器的预处理和后处理网络来处理语音输出。

这样就可以获得多个针对不同语音任务微调的模型，它们都受益于最开始在未标注数据上预训练时学到的知识。

<Tip>

尽管模型开始时使用的是同一个预训练模型的相同权重集，但微调后的最终版本会大不相同。所以，您不能仅仅通过更换预处理和后处理网络，
就将一个微调后的 ASR 模型转换为一个可用的 TTS 模型。SpeechT5 很灵活，但还没有灵活到这个程度 QwQ

</Tip>

让我们具体看看 SpeechT5 在 TTS（文本到语音）任务中使用的预/后处理网络是什么：

* 文本编码器预处理网络（Text encoder pre-net）：一个文本嵌入层，将文本词元映射到编码器可以读入的隐藏表示。这与 NLP 模型（如 BERT）中的做法类似。
* 语音解码器预处理网络（Speech decoder pre-net）：这个网络以对数梅尔谱（log mel spectrogram）为输入，并使用一连串的线性层来将频谱图压缩成隐藏表示。
* 语音解码器后处理网络（Speech decoder post-net）：这个网络预测一个残差来添加到输出的频谱图中，用于优化模型输出的结果。

结合起来，这就是 SpeechT5 在语音合成中使用的架构：

<div class="flex justify-center">
    <img src="https://huggingface.co/datasets/huggingface/documentation-images/resolve/main/blog/speecht5/tts.jpg" alt="SpeechT5 architecture for TTS">
</div>

正如您所看到的，输出是一个对数梅尔谱，而不是最终的波形。如果您记得，我们在 [第 3 单元](../chapter3/introduction#spectrogram-output) 简要讨论过这个话题。
生成语音的模型的输出常常是对数梅尔谱，还需要通过一个额外的神经网络，即声码器（vocoder），才能转换成波形。

让我们看看具体应该怎么做。

首先，让我们从 🤗 Hub 加载微调过的 TTS SpeechT5 模型，以及用于分词和特征提取的处理器对象：

```python
from transformers import SpeechT5Processor, SpeechT5ForTextToSpeech

processor = SpeechT5Processor.from_pretrained("microsoft/speecht5_tts")
model = SpeechT5ForTextToSpeech.from_pretrained("microsoft/speecht5_tts")
```

接下来，给输入的文本分词。

```python
inputs = processor(text="Don't count the days, make the days count.", return_tensors="pt")
```

SpeechT5 TTS 模型不仅限于生成一种音色的语音。相反，它能够使用所谓的“说话人嵌入”（speaker embeddings）来模仿各种特定说话人的声音特征。

<Tip>

说话人嵌入（Speaker embeddings）是一个固定大小的向量，不论音频的长度如何都能用一种紧凑的方式表示说话人的身份。这些向量捕获了关于说话人的音色、口音、
语调以及其他独特特征的关键信息，这些特征区分了不同的说话人。这样的嵌入有说话人验证（speaker verification）、说话人分离（speaker diarization）、
说话人识别（speaker identification）等多种应用。生成说话人嵌入时最常用的技术包括：

* I-Vectors（身份向量）：I-Vectors 基于高斯混合模型（Gaussian mixture model，GMM）。它属于无监督学习，用一个特定说话人的 GMM 的统计数据得出一个低维度、固定大小的向量来代表说话人。
* X-Vectors：X-Vectors 利用了深度神经网络（DNN），通过整合时间维度的上下文来捕获帧级的说话人信息。

[X-Vectors](https://www.danielpovey.com/files/2018_icassp_xvectors.pdf) 是 SOTA（state-of-the-art）的方法，在测试集上的表现优于 I-Vectors。
X-Vectors 是深度神经网络产生的：它被训练以区分不同说话人，并将长度不一的语音音频映射到固定维度的嵌入。您还可以加载提前计算好的 X-Vector 说话人嵌入，它封装好了某个特定说话人的说话特征。

</Tip>

让我们从 Hub 上的数据集中加载一个说话人嵌入。这些嵌入是使用 [CMU ARCTIC 数据集](http://www.festvox.org/cmu_arctic/) 和 [这个脚本](https://huggingface.co/mechanicalsea/speecht5-vc/blob/main/manifest/utils/prep_cmu_arctic_spkemb.py) 获得的，这里使用任何 X-Vector 嵌入都可以。

```python
from datasets import load_dataset

embeddings_dataset = load_dataset("Matthijs/cmu-arctic-xvectors", split="validation")

import torch

speaker_embeddings = torch.tensor(embeddings_dataset[7306]["xvector"]).unsqueeze(0)
```

说话人嵌入是形状为 (1, 512) 的张量，我们选的这个说话人嵌入描述的是一个女声。

此时，我们已经有足够的输入来生成对数梅尔谱作为输出，您可以这样做：

```python
spectrogram = model.generate_speech(inputs["input_ids"], speaker_embeddings)
```

这会输出一个形状为 (140, 80) 的张量，包含对数梅尔谱。第一维是序列长度，可能每次运行代码它的取值都会不一样，
因为语音解码器预处理网络（pre-net）总是对输入序列用 dropout。这给生成的语音添加了一些随机变化。

如果我们想生成语音波形，我们还需要指定一个用于把频谱图转换为波形的声码器（vocoder）。理论上，您可以使用任何适用于 80-bin 梅尔谱的声码器。
🤗 Transformers 提供了一个基于 HiFi-GAN 的声码器，可以很方便地使用。其权重由 SpeechT5 的原作者友情提供。

<Tip>

[HiFi-GAN](https://arxiv.org/pdf/2010.05646v2.pdf) 是一种用于高保真语音合成的 SOTA 生成对抗网络（GAN）。它能够根据输入的频谱图生成高品质且逼真的音频波形。

总的来说，HiFi-GAN 由一个生成器和两个鉴别器组成。生成器是一个全卷积神经网络，它以梅尔谱作为输入并学习产生原始音频波形。
鉴别器负责区分真实音频和生成音频。这两个鉴别器关注音频的不同方面。

HiFi-GAN 在大量高品质音频数据上进行训练。它采用所谓的<em>对抗训练</em>，其中生成器和鉴别器网络相互竞争。最初，生成器产生的音频质量较低，
鉴别器可以轻松地将其与真实音频区分开。随着训练的进行，生成器改进其输出，目标是欺骗鉴别器。反过来，鉴别器也学会更准确地区分真实和生成的音频。
这种对抗性反馈循环帮助两个网络随时间推移提升性能。最终，HiFi-GAN 学会生成高保真音频，精密地模仿训练数据的特征。

</Tip>

加载声码器就像加载任何其他 🤗 Transformers 模型一样简单。

```python
from transformers import SpeechT5HifiGan

vocoder = SpeechT5HifiGan.from_pretrained("microsoft/speecht5_hifigan")
```

现在您需要做的就是在生成语音时将其作为参数传递，输出将自动转换为语音波形。

```python
speech = model.generate_speech(inputs["input_ids"], speaker_embeddings, vocoder=vocoder)
```

让我们来听听输出的结果。SpeechT5 使用的采样率始终是 16 kHz。

```python
from IPython.display import Audio

Audio(speech, rate=16000)
```

真不错！

请随意使用 SpeechT5 语音合成 demo 探索其他声音，尝试各种各样的输入。请注意，该预训练检查点仅支持英语：

<iframe
	src="https://matthijs-speecht5-tts-demo.hf.space"
	frameborder="0"
	width="850"
	height="450">
</iframe>

## Bark

Bark 是 Suno AI 提出的基于 transformer 的语音合成模型，见 [suno-ai/bark](https://github.com/suno-ai/bark)。

与 SpeechT5 不同，Bark 直接生成语音波形，无需在推理过程中使用单独的声码器——它已经集成了这一功能。这种功能是通过使用 [`Encodec`](https://huggingface.co/docs/transformers/main/en/model_doc/encodec) 实现的，它既是编解码器也是压缩工具。

借助 `Encodec`，您可以将音频压缩成轻量级格式以减少内存使用，然后再解压缩以恢复原始音频。这个压缩过程由 8 个 codebook 帮助完成，
每个 codebook 都由整数向量组成。可以将这些 codebook 视为音频的整数形式表示或嵌入，并且每个后续的 codebook 都能在前一个的基础上提高音频重建的质量。
由于 codebook 是整数向量，它们可以被 transformer 模型学习，而 transformer 在这项任务上非常高效。这正是 Bark 被训练去做的任务。

更具体地说，Bark 由 4 个主要的模型组成：

- `BarkSemanticModel`（也称为“文本”模型）：一个因果自回归的 transformer 模型，它接收分词后的文本作为输入，并预测表示文本含义的语义文本词元。
- `BarkCoarseModel`（也称为“粗粒度声学”模型）：一个因果自回归的 transformer，它接收 `BarkSemanticModel` 模型的结果作为输入，预测 EnCodec 所需的前两个音频 codebook 。
- `BarkFineModel`（“细粒度声学”模型），这次是一个非因果自编码 transformer，它基于之前生成的所有 codebook 嵌入，迭代地预测下一个 codebook 。
- 在 `EncodecModel` 预测了所有 codebook 通道后，Bark 使用它来解码输出音频数组。

需要注意的是，前三个模块中的每一个都支持说话人嵌入作为条件参数，以根据预特定的预设调整输出的声音。

Bark 是一个高度可控的语音合成模型，意味着您可以调整各种各样的设置，下面我们将见证这一功能。

在一切开始之前，我们需要加载模型及其处理器。

处理器在这里有两方面的作用：

1. 它用于对输入文本进行分词，即将其切割成模型可以理解的小片段。
2. 它存储说话人嵌入，即可以调节生成结果的声音预设。

```python
from transformers import BarkModel, BarkProcessor

model = BarkModel.from_pretrained("suno/bark-small")
processor = BarkProcessor.from_pretrained("suno/bark-small")
```

Bark 非常多功能，可以通过处理器加载一个 [说话人嵌入库](https://suno-ai.notion.site/8b8e8749ed514b0cbf3f699013548683?v=bc67cff786b04b50b3ceb756fd05f68c) 中的说话人嵌入来调节它产生的声音。

```python
# 设置说话人嵌入
inputs = processor("This is a test!", voice_preset="v2/en_speaker_3")

speech_output = model.generate(**inputs).cpu().numpy()
```

<audio controls> 
  <source src="https://huggingface.co/datasets/ylacombe/hf-course-audio-files/resolve/main/first_sample.wav" type="audio/wav"> 
Your browser does not support the audio element. 
</audio> 

它还可以生成随时可用的多语言语音，例如法语和中文。您可以在 [这里](https://huggingface.co/suno/bark) 找到支持的语言列表。与下面讨论的 MMS 不同，它不需要指定所使用的语言，只需将输入文本调整为相应的语言即可。

```python
# 试试法语，同时我们也来用一个法语说话人嵌入
inputs = processor("C'est un test!", voice_preset="v2/fr_speaker_1")

speech_output = model.generate(**inputs).cpu().numpy()
```
<audio controls> 
  <source src="https://huggingface.co/datasets/ylacombe/hf-course-audio-files/resolve/main/second_sample.wav" type="audio/wav"> 
Your browser does not support the audio element. 
</audio> 

该模型还可以生成**非语言交流**，例如笑、叹息和哭泣。 您只需使用相应的提示修改输入文本，例如 `[clears throat]`、`[laughter]` 或 `...`。

```python
inputs = processor(
    "[clears throat] This is a test ... and I just took a long pause.",
    voice_preset="v2/fr_speaker_1",
)

speech_output = model.generate(**inputs).cpu().numpy()
```

<audio controls> 
  <source src="https://huggingface.co/datasets/ylacombe/hf-course-audio-files/resolve/main/third_sample.wav" type="audio/wav"> 
Your browser does not support the audio element. 
</audio> 

Bark 甚至可以产生音乐。您可以通过在单词周围添加 ♪ 音符 ♪ 来做到这一点。

```python
inputs = processor(
    "♪ In the mighty jungle, I'm trying to generate barks.",
)

speech_output = model.generate(**inputs).cpu().numpy()
```

<audio controls> 
  <source src="https://huggingface.co/datasets/ylacombe/hf-course-audio-files/resolve/main/fourth_sample.wav" type="audio/wav"> 
Your browser does not support the audio element. 
</audio> 

除了所有这些功能之外，Bark 还支持批处理，这意味着您可以同时处理多个文本条目，但代价是更密集的计算。在某些硬件（例如 GPU）上，批处理可以加快整体生成速度，这意味着一次性生成所有样本比逐个生成样本更快。

让我们尝试生成一些示例：

```python
input_list = [
    "[clears throat] Hello uh ..., my dog is cute [laughter]",
    "Let's try generating speech, with Bark, a text-to-speech model",
    "♪ In the jungle, the mighty jungle, the lion barks tonight ♪",
]

# 设置一个说话人嵌入
inputs = processor(input_list, voice_preset="v2/en_speaker_3")

speech_output = model.generate(**inputs).cpu().numpy()
```

让我们来一个一个听这些输出。

第一个：

```python
from IPython.display import Audio

sampling_rate = model.generation_config.sample_rate
Audio(speech_output[0], rate=sampling_rate)
```

<audio controls> 
  <source src="https://huggingface.co/datasets/ylacombe/hf-course-audio-files/resolve/main/batch_1.wav" type="audio/wav"> 
Your browser does not support the audio element. 
</audio> 

第二个：

```python
Audio(speech_output[1], rate=sampling_rate)
```

<audio controls> 
  <source src="https://huggingface.co/datasets/ylacombe/hf-course-audio-files/resolve/main/batch_2.wav" type="audio/wav"> 
Your browser does not support the audio element. 
</audio> 

第三个：

```python
Audio(speech_output[2], rate=sampling_rate)
```

<audio controls> 
  <source src="https://huggingface.co/datasets/ylacombe/hf-course-audio-files/resolve/main/batch_3.wav" type="audio/wav"> 
Your browser does not support the audio element. 
</audio> 

<Tip>

Bark 与其他 🤗 Transformers 模型一样，只需几行代码即可针对速度和内存影响进行优化。要了解具体操作方法，请单击 [此 colab 演示笔记本](https://colab.research.google.com/github/ylacombe/notebooks/blob/main/Benchmark_Bark_HuggingFace.ipynb)。

</Tip>

## Massive Multilingual Speech (MMS)

如果您需要一个非英语的预训练模型怎么办？Massive Multilingual Speech（MMS，大规模多语种语音）是另一个涵盖多种语音任务的模型，并且，它支持大量的语言。比方说，它可以合成超过 1,100 种语言的语音。

MMS 的语音合成是基于 [VITS Kim et al., 2021](https://arxiv.org/pdf/2106.06103.pdf) 的，它是最先进的 TTS 方法之一。

VITS 是一个语音生成网络，可以将文本转换成语音波形。它的工作方式类似于条件变分自编码器（conditional variational auto-encoder），从输入文本估计音频特征。首先，生成用频谱图表示的声学特征。
然后，使用改编自 HiFi-GAN 的转置卷积层解码出波形。在推理过程中，文本编码被上采样并使用流模块（flow module）和 HiFi-GAN 解码器转换成波形。像 Bark 一样，这里不需要声码器（vocoder），因为已经直接生成波形了。

<Tip warning={true}>

MMS 模型最近才被添加到 🤗 Transformers 中，所以您需要从源代码安装该库：

```bash
pip install git+https://github.com/huggingface/transformers.git
```

</Tip>

让我们试用一下 MMS，看看如何合成非英语的语音，例如德语。首先，我们将加载特定语言的检查点和分词器：

```python
from transformers import VitsModel, VitsTokenizer

model = VitsModel.from_pretrained("facebook/mms-tts-deu")
tokenizer = VitsTokenizer.from_pretrained("facebook/mms-tts-deu")
```

您可能会注意到，加载 MMS 模型需要使用 `VitsModel` 和 `VitsTokenizer`。这是因为如前所述，MMS 的 TTS 是基于 VITS 模型的。

让我们选择一首德语儿歌的前两句作为示例文本：

```python
text_example = (
    "Ich bin Schnappi das kleine Krokodil, komm aus Ägypten das liegt direkt am Nil."
)
```

要生成波形输出，使用分词器预处理文本，并将其传递给模型：

```python
import torch

inputs = tokenizer(text_example, return_tensors="pt")
input_ids = inputs["input_ids"]


with torch.no_grad():
    outputs = model(input_ids)

speech = outputs["waveform"]
```

让我们来听听：

```python
from IPython.display import Audio

Audio(speech, rate=16000)
```

Wunderbar！如果您想尝试其他语言的 MMS，可以 [在 🤗 Hub 上](https://huggingface.co/models?filter=vits) 寻找其他合适的 `vits` 检查点。

现在，让我们看看如何自己微调一个 TTS 模型！


<EditOnGithub source="https://github.com/huggingface/audio-transformers-course/blob/main/chapters/zh-CN/chapter6/pre-trained_models.mdx" />
