在 GitHub 上执行或查看/下载此 notebook
数据加载
在许多机器学习项目中,处理数据占用了 90% 的工作时间。
SpeechBrain 补充了标准的 PyTorch 数据加载功能,用于处理变长序列、大型数据集和复杂的数据转换流程。这些是处理语音时典型的挑战,但 SpeechBrain 尽量不对您的数据做任何假设。
安装依赖项
%%capture
# Installing SpeechBrain via pip
BRANCH = 'develop'
!python -m pip install git+https://github.com/speechbrain/speechbrain.git@$BRANCH
import speechbrain
import torch
在本教程中,我们将使用来自 https://www.openslr.org/resources/31 的 MiniLibriSpeech:我们将在接下来的两个单元格中下载验证集以及图像和脚本。
%%capture
# here we download the material needed for this tutorial: images and an example based on mini-librispeech
!wget https://www.dropbox.com/s/b61lo6gkpuplanq/MiniLibriSpeechTutorial.tar.gz?dl=0
!tar -xvzf MiniLibriSpeechTutorial.tar.gz?dl=0
# downloading mini_librispeech dev data
!wget https://www.openslr.org/resources/31/dev-clean-2.tar.gz
!tar -xvzf dev-clean-2.tar.gz
前言:PyTorch 数据加载流程
SpeechBrain 数据 IO 遵循并扩展了PyTorch 数据加载。本前言部分回顾了 PyTorch 数据加载,但尚未考虑 SpeechBrain 数据加载的扩展。
概述
PyTorch 数据加载可以在多种配置下运行,但典型方法包含这些基本元素:
一个Dataset(数据集),逐一加载数据点。
一个整理函数(collation function),简称
collate_fn
,它接收数据点列表并形成一个批次(batch)。一个Sampler(采样器),它决定了 Dataset 被迭代的顺序。
一个DataLoader(数据加载器),它结合了上述元素(并为
collate_fn
和 Sampler 提供默认值),并协调整个流程(pipeline)。
Dataset
Dataset 的作用是生成单个数据点。通常它们从磁盘加载,但也可以来自一些更复杂的来源,或者在某些情况下仅来自 RAM。您可以编写自己的 Dataset 子类,或者有时可以使用标准化类。训练集、验证集和测试集都有自己的 Dataset 实例。
Dataset 接口很简单;它实现了 __getitem__
,通常还实现了 __len__
。通常使用“map-style”数据集,但值得注意的是,PyTorch 也有IterableDataset的概念。
__getitem__
可以返回任何东西,因为数据可以是任何形式。然而,数据点通常包含多个相关联的项目(例如,图像和标签,或语音波形及其转录)。Dataset 应该返回所有这些相关联的项目。
Dataset 在 CPU 上即时转换数据也相对常见。
整理函数
collate_fn
只是将示例列表转换为 PyTorch 张量批次(batch)。如果数据具有变长序列,collate_fn
通常需要实现填充(padding)。
Sampler(采样器)
通常用户不需要创建自己的采样器;两个默认选项是按原始 Dataset 顺序迭代或按随机顺序迭代。
DataLoader(数据加载器)
DataLoader 接收上述其他元素以及许多其他参数,例如批次大小(batch size)。DataLoader 为所有参数(当然除了 Dataset)都有基本默认值,但了解这些参数是值得的。
DataLoader 对象在训练循环中迭代,每个 Dataset 实例(例如训练集、验证集、测试集)都有自己的 DataLoader。
train_loader = DataLoader(train_data, collate_fn=PaddedBatch, batch_size=32, num_workers=2)
for batch in train_loader:
pred = model(batch.signal)
...
DataLoader 返回的迭代器可以在创建它的同一进程中加载批次(num_workers=0
),或者它可以启动一个新进程(num_workers=1
)或多个新进程(num_workers>1
)。由于全局解释器锁(Global Interpreter Lock),Python 在单个进程中不能同时处理两个任务。使用至少一个后台工作进程在同时运行训练时加载数据,对于充分利用 GPU 计算资源通常是必不可少的。
SpeechBrain 基本数据 IO
SpeechBrain 基本数据加载流程如下图所示。
from IPython.display import Image
Image('sbdataio.png', width=1000)
请注意,也可能实现更高级/灵活的流程,用户可以在其中集成自己的自定义 Dataset、DataLoader 和 Sampler。这些将在数据 IO 高级教程中说明。
基本数据 IO 流程围绕三个“关键”块组织:DynamicItemDataset、动态项流程 (DIPs) 和 CategoricalEncoder,它们紧密连接。
DynamicItemDataset 继承自 torch.utils.data.Dataset
,并与 动态项流程 协同工作,提供一种直接且灵活的方式来获取和转换存储在磁盘上的原始数据集中的数据和标签。
DIPs 由用户定义的函数组成,用户在其中指定应用于数据集中包含的元数据和数据的操作。例如,读取和增强音频文件,或使用 SentencePiece 分词器对单词序列进行编码。这些函数在 DynamicItemDataset 的 __getitem__
方法内部被调用,并在 CPU 上并行运行。
CategoricalEncoder 是我们为多类分类问题提供的一个方便的抽象,它被细分为 TextEncoder 和 CTCTextEncoder,后者可用于与文本相关的序列到序列应用,例如 ASR。
得益于这些抽象,设置数据 IO 流程所需的大部分工作是将数据集解析为 DynamicItemDataset 支持的合适标注格式(SpeechBrain 支持 CSV 和 JSON 格式)。
一旦此标注准备就绪,就可以用少量代码创建灵活高效的流程,因为 SpeechBrain 将在底层处理填充(padding)及其他操作。
在接下来的教程中,我们将详细解释这些块的工作原理。我们将从所需的 CSV 或 JSON 标注开始,其目的是表示和描述数据集中包含的信息。例如:
音频文件路径、预提取的特征等等。
元数据,例如音频文件中 spoken 的词、信噪比(Signal-to-Noise-Ratio)、声音事件标签、说话人身份等等。
基本上是训练您的算法所需的任何信息。
数据集标注
SpeechBrain 对 JSON 和 CSV 格式提供原生支持,用于描述数据集。实际上,在官方配方(例如 LibriSpeech ASR 配方)中,我们提供了解析脚本来获取这些格式。
我们可以使用下载的 Mini-LibriSpeech 示例来了解这些文件的结构。
Mini-LibriSpeech 中的每个文件都是来自单个说话人的一段话(utterance),因此可以使用 JSON 和 CSV 格式来包含该文件的绝对路径、说话人身份以及说话人说出的词。这足以构建一个自动语音识别 (ASR) 系统。
下面我们可以看看之前演示的简单说话人识别示例中使用的 JSON 文件结构:
from parse_data import parse_to_json # parse_data is a local library downloaded at the step before
parse_to_json("./LibriSpeech/dev-clean-2") # how to build this function will be shown later
!head data.json
因此,JSON 文件是一个字典,其中每个键都是唯一的,对应一个示例(键即示例 ID)。每个示例本身又是一个字典,包含到该段话的路径 file_path
、说的 words
、说话人的身份 spkID
以及 file_path
中音频的采样点 length
。
CSV 文件也有类似的结构
# csv file
from parse_data import parse_to_csv
parse_to_csv("./LibriSpeech/dev-clean-2")
!head data.csv
与其他对数据集指定方式有严格要求的工具包不同,我们对 JSON 和 CSV 语法没有任何限制,除了每个示例必须有一个不同的唯一 ID 字符串这一要求。
这意味着 JSON 文件必须包含一个字典,其键是示例 id,字典的每个条目包含该示例的元数据。而 CSV 文件必须至少有一个名为 id 的列。
这些是保证 JSON 和 CSV 数据集描述文件与 SpeechBrain 数据 IO 流程协同工作的唯一严格要求。
用户在 JSON 和 CSV 文件中表示其数据集的方式具有很大灵活性,因为他们的目标和应用可能不同:语音分离、增强、ASR、分割、VAD 等等。
这是因为 SpeechBrain 的目标是支持许多不同的任务和数据集。
每个数据集都是独一无二的,它可以是单通道或多通道,可以提供不同的元数据,例如说话人 ID、说话人位置,甚至可以是多模态数据,例如音频和视频。
每个任务都是独一无二的,本例中使用的标注适用于 ASR 和说话人识别等应用。但对于分割(diarization)等任务,用户可能还需要每段话的开始和停止时间(秒、帧或其他!)。
这也有助于保持标注非常简单,专注于特定任务,只包含当前应用所需的必要信息,而不是使用繁琐的万能标注。
提示
在构建解析脚本时,为每个示例提供一个包含示例长度(以秒、采样点甚至帧为单位)的 length
或 duration
是很有用的。这有助于后续操作,例如过滤过长的示例(以避免 OOM 问题)或对它们进行排序以加快训练。对于 CSV 文件,如果指定了 duration
列,则当从 CSV 构建 DynamicItemDataset 时,它会自动转换为 float 类型。
如前所述,这些文件必须由合适的解析脚本生成,这些脚本在我们提供的配方(recipes)中提供,并且依赖于数据集和任务。但是,如果希望使用自己的自定义数据集,则必须编写一个解析脚本来生成描述数据的 JSON 或 CSV 文件。
注意
必须为每个分割(训练、验证/开发、测试)提供一个单独的文件。
另一方面,对于大多数数据集来说,创建这些文件应该相当容易,因为 Python 提供了许多处理 CSV 和 JSON 文件的工具(例如 pandas)。实际上,例如将数据解析为 JSON,只需几行代码即可完成:
import glob
import json
import os
import torchaudio
from pathlib import Path
dev_clean_root = "./LibriSpeech/dev-clean-2"
flac_files = glob.glob(os.path.join(dev_clean_root, "**/*.flac"), recursive=True)
print("tot flac audio files {}".format(len(flac_files)))
flac_files[0]
# in this dataset files names are spk_id-chapter_id-utterance_id.flac
text_files = glob.glob(os.path.join(dev_clean_root, "**/*.txt"), recursive=True)
# we build a dictionary with words for each utterance
words_dict = {}
for txtf in text_files:
with open(txtf, "r") as f:
lines = f.readlines()
for l in lines:
l = l.strip("\n")
utt_id = l.split(" ")[0]
words = " ".join(l.split(" ")[1:])
words_dict[utt_id] = words
# we now build JSON examples
examples = {}
for utterance in flac_files:
utt_id = Path(utterance).stem
examples[utt_id] = {"file_path": utterance,
"words": words_dict[utt_id],
"spkID": utt_id.split("-")[0],
"length": torchaudio.info(utterance).num_frames}
with open("data.json", "w") as f:
json.dump(examples, f, indent=4)
print(examples[list(examples.keys())[0]])
DynamicItemDataset
DynamicItemDataset 是 SpeechBrain 数据流程的核心,构建在 torch.utils.data.Dataset
之上。
顾名思义,它允许从 JSON (或 CSV) 数据集标注中指定的条目动态创建新的“对象”。
#`creating a DynamiItemDataset instance from JSON or CSV annotation is immediate
from speechbrain.dataio.dataset import DynamicItemDataset
dataset = DynamicItemDataset.from_json("data.json") # or equivalently, DynamicItemDataset.from_csv("data.csv")
从 data.json 标注中指定的条目动态创建“对象”是什么意思?
dataset[0]
就像现在这样,这个 Dataset
对象不返回任何东西。
动态创建正是这个意思,用户想要返回的项目必须由用户以某种方式指定。这些项目可以依赖于 data.json 示例中指定的条目:
print(examples[list(examples.keys())[0]].keys())
即来自 ['file_path', 'words', 'spkID', 'length']
。
例如,一个“动态项”可以是音频信号,它将依赖于 'file_path'
键。另一个可以是编码为整数值的 spkID
,如果希望执行说话人识别的话,或者对于 ASR,它可以是由分词器编码的词。
要获取这些“项目”,应以某种方式指定一个函数,该函数应用于相应的键时将提供新项目。
例如,一个应用于 'file_path'
键时读取音频的函数,以便使 Dataset
类为每个示例返回音频信号。
动态项流程 (DIPs)
此任务通过为用户希望从数据集获取的每个动态项指定动态项流程来处理。用户可以指定任意数量的流程。
例如,关于音频信号:
@speechbrain.utils.data_pipeline.takes("file_path")
@speechbrain.utils.data_pipeline.provides("signal")
def audio_pipeline(file_path):
sig = speechbrain.dataio.dataio.read_audio(file_path)
return sig
我们指定一个函数,它接收每个示例的 file_path
,并提供一个名为 sig
的新项目,它是一个包含音频的张量。
我们在此使用 speechbrain.dataio.dataio
中的一些内置函数来读取音频。但用户也可以使用自己的函数。
指定后,必须将流程添加到 DynamicItemDataset
对象中,然后应使用 set_output_keys
方法指定用户请求的输出。我们在输出中请求两个项目:一个新项目 sig
以及 JSON 标注中的 file_path
。
dataset.add_dynamic_item(audio_pipeline)
dataset.set_output_keys(["signal", "file_path"],
)
dataset[0]
请注意,对于应用诸如读取音频文件之类的简单函数,可以使用更简洁的语法。
dataset.add_dynamic_item(speechbrain.dataio.dataio.read_audio, takes="file_path", provides="signal")
dataset.set_output_keys(["id", "signal", "words"])
dataset[0]
现在,数据集对象将返回这个新指定的项目“sig”以及 JSON 中指定的 file_path
print(dataset[0])
import matplotlib.pyplot as plt
plt.figure(1)
plt.title("Sig item")
plt.plot(dataset[0]["signal"])
plt.show()
可以看出,DynamicItemDataset
对象返回了 JSON 标注中指定的两个项目。
file_path
项目直接从 JSON 标注中原样获取,没有进一步处理。另一个则是一个从 file_path
项目通过我们之前定义的流程派生的新项目。
流程中可以做什么没有限制:如前所述,用户也可以使用自己的函数
!pip install soundfile
import soundfile as sf
@speechbrain.utils.data_pipeline.takes("file_path")
@speechbrain.utils.data_pipeline.provides("sig_numpy")
def audio_pipeline_numpy(file_path):
sig, _ = sf.read(file_path, dtype="float32")
return sig
speechbrain.dataio.dataset.add_dynamic_item([dataset], audio_pipeline_numpy)
speechbrain.dataio.dataset.set_output_keys(
[dataset], ["signal", "file_path", "sig_numpy"],
)
dataset[0]
现在,数据集对象也返回使用 soundfile 库读取的信号,而不仅仅是使用基于 torchaudio 的内置 speechbrain 函数读取的信号。
一个流程可以通过使用 python 生成器语法指定多个输出.
在下面的示例中,指定了三个输出,后两个直接依赖于第一个 (sig),并且是后者的转换版本:具有随机增益因子 rand_gain_sig
和恒定偏移 offset_sig
。
import random
@speechbrain.utils.data_pipeline.takes("file_path")
@speechbrain.utils.data_pipeline.provides("sig", "rand_gain_sig", "offset_sig")
def audio_pipeline(file_path):
sig = speechbrain.dataio.dataio.read_audio(file_path)
yield sig
rand_gain_sig = random.random()*sig
yield rand_gain_sig
offset_sig = sig + 1
yield offset_sig
speechbrain.dataio.dataset.add_dynamic_item([dataset], audio_pipeline)
speechbrain.dataio.dataset.set_output_keys(
[dataset], ["sig", "rand_gain_sig", "offset_sig"],
)
print(dataset[0])
import matplotlib.pyplot as plt
plt.figure(1)
plt.title("Sig item")
plt.plot(dataset[0]["sig"])
plt.title("Sig item with random gain")
plt.plot(dataset[0]["rand_gain_sig"])
plt.title("Sig item offset")
plt.plot(dataset[0]["offset_sig"])
plt.legend(["sig", "rand_gain_sig", "offset_sig"])
plt.show()
这个玩具示例演示了可以从同一个流程中获取多个项目,并且动态创建的项目可以依赖于其他动态创建的项目(offset_sig
依赖于 sig
)。
但动态项目也可以依赖于另一个预先指定的流程中动态创建的项目
@speechbrain.utils.data_pipeline.takes("sig")
@speechbrain.utils.data_pipeline.provides("sig_as_python_list")
def to_list_pipeline(sig):
yield sig.numpy().tolist()
speechbrain.dataio.dataset.add_dynamic_item([dataset], to_list_pipeline)
speechbrain.dataio.dataset.set_output_keys(
[dataset], ["sig_as_python_list"],
)
dataset[0]["sig_as_python_list"][:10]
在此示例中,我们定义了一个新流程,它接收 sig
并将其从 torch.tensor
转换为 python 列表,从而获得一个新的动态项目 sig_as_python_list
。
注意
由于我们在输出中只请求依赖于 sig
本身的 sig_as_python_list
,动态项目 offset_sig
和 rand_gain_sig
完全没有计算。只有 sig
被隐式计算,因为它对于获取 sig_as_python_list
是必需的。
事实上,在底层 DynamicItemDataset
通过构建由流程定义的计算图来找到请求项目的合适评估顺序。
如果流程之间存在任何循环依赖,则会返回错误.
一个 DIP 也可以接收多个项目/标注键作为输入,语法与输出项目相同
@speechbrain.utils.data_pipeline.takes("file_path", "spkID")
@speechbrain.utils.data_pipeline.provides("sig", "spkidstring")
def multiple_dip(file_path, spkID):
sig = speechbrain.dataio.dataio.read_audio(file_path)
yield sig
yield spkID
speechbrain.dataio.dataset.add_dynamic_item([dataset], multiple_dip)
speechbrain.dataio.dataset.set_output_keys(
[dataset], ["sig", "spkidstring"],
)
dataset[0]
@speechbrain.utils.data_pipeline.takes("file_path", "spkID")
@speechbrain.utils.data_pipeline.provides("sig")
def multiple_dip(file_path, spkID):
sig = speechbrain.dataio.dataio.read_audio(file_path)
yield sig, spkID
speechbrain.dataio.dataset.add_dynamic_item([dataset], multiple_dip)
speechbrain.dataio.dataset.set_output_keys(
[dataset], ["sig"],
)
dataset[0] # sig now is a tuple
同一个 DIP 也可以在多个数据集(例如:验证、训练和测试集)中使用。例如,您通常希望读取音频的 DIP 在验证、训练和测试集中是相同的。
validation = DynamicItemDataset.from_json("data.json")
train = DynamicItemDataset.from_json("data.json")
speechbrain.dataio.dataset.add_dynamic_item([validation, train], speechbrain.dataio.dataio.read_audio, takes="file_path", provides="signal")
speechbrain.dataio.dataset.set_output_keys([validation, train], ["id", "signal", "words"])
validation[0]
train[0]
CategoricalEncoder
SpeechBrain dataio
提供了一个 CategoricalEncoder
类,用于编码属于离散集合的标签:例如,用于说话人识别或任何其他多类分类问题。
给定一个可哈希对象集合(例如字符串),它将每个唯一的项目编码为一个整数值:["spk0", "spk1"]
–> [0, 1]
在内部,每个标签与其索引之间的对应关系由两个字典处理:lab2ind
和 ind2lab
。
它的构建旨在与 DynamicItemDataset
和 dataIO pipelines
紧密集成。
例如,可以通过创建 CategoricalEncoder 实例并将其适配(fitting)到数据集对象,从而从 Mini-LibriSpeech 数据集中获取说话人身份(JSON 中的 spkID
)的编码。
from speechbrain.dataio.encoder import CategoricalEncoder
spk_id_encoder = CategoricalEncoder()
由于 DynamicItemDataset
当前不返回 spkID,我们首先必须设置其输出来返回该动态项
speechbrain.dataio.dataset.set_output_keys(
[dataset], ["spkID"],
)
# sig is a torch.tensor with audio signal as specified before.
# REMEMBER: no need to specify the pipeline for spkID as we can read directly the value from the JSON.
dataset[0]
说话人身份 spkID
是一个字符串。
请注意,在 librispeech 中,它是一个包含唯一整数的字符串,因此有人可能会认为在这里执行编码是毫无意义的,因为转换为整数就足够了。
然而,它可能不是唯一的整数,而是像 spk1
, spk2
等等这样的唯一字符串。
spk_id_encoder
可用于此目的。我们将编码器适配到数据集,并指定要编码的动态项。
spk_id_encoder.update_from_didataset(dataset, "spkID")
注意
这将迭代数据集,获取每个示例的 spkID,并构建内部字典 lab2ind 和 ind2lab。
因此,在适配编码器之前,调用 dataset.set_output_keys
以避免计算成本高的动态项(例如,如果流程进行数据增强可能会发生)是很重要的。
只设置编码器将适配的键是一个好方法。
我们现在可以使用 __len__
来查看数据集中有多少个唯一的说话人 id
len(spk_id_encoder)
我们还可以看看编码器内部字典 lab2ind
和 ind2lab
,它们包含标签(在本例中是说话人 id)和相应的整数编码之间的映射。
spk_id_encoder.lab2ind
# contains label --> integer encoding
spk_id_encoder.ind2lab # contains integer encoding --> label
适配后,CategoricalEncoder
对象可以在一个适当定义的流程中使用,以编码 spkID 键并返回编码值
@speechbrain.utils.data_pipeline.takes("spkID")
@speechbrain.utils.data_pipeline.provides("spkid_encoded")
def spk_id_encoding(spkid):
return torch.LongTensor([spk_id_encoder.encode_label(spkid)])
speechbrain.dataio.dataset.add_dynamic_item([dataset], spk_id_encoding)
speechbrain.dataio.dataset.set_output_keys(
[dataset], ["spkid_encoded"],
)
dataset[100]
PaddedBatch 和 SaveableDataLoader
SpeechBrain 提供了一种方便的方式,可以自动在多个维度上对不同长度的张量进行右侧填充(pad right)。这通过使用 speechbrain.dataio.batch
中定义的 PaddedBatch
类实现。
PaddedBatch
既是一个 collate_fn
,也是一个批次对象。
当 torch.utils.data.Dataset
(以及 DynamicItemDataset
)传递给 Brain
类时,PaddedBatch
用作默认的整理函数 collate_fn
,并且示例会自动填充在一起。
作为 Brain 类实例化的默认 DataLoader,是一个 SpeechBrain 自定义 DataLoader:speechbrain.dataio.dataloader.SaveableDataLoader
。
这个 DataLoader 与普通 DataLoader 相同,除了它允许进行 epoch 内保存。因此,如果由于某种原因训练在一个 epoch 的中间停止,可以从精确的那个步骤恢复。参见检查点教程。这个 DataLoader 的默认 collate_fn
是 PaddedBatch
。
from speechbrain.dataio.dataloader import SaveableDataLoader
from speechbrain.dataio.batch import PaddedBatch
speechbrain.dataio.dataset.set_output_keys(
[dataset], ["id", "spkid_encoded", "signal"],
)
我们将请求的动态项设置为音频张量 sig
,以及之前定义的 CategoricalEncoder
对象编码的说话人 id (spkid_encoded
) 和示例 id
。
dataloader = SaveableDataLoader(dataset, batch_size=2, collate_fn=PaddedBatch)
batch_obj = next(iter(dataloader)) # let's look at the batch obj
batch_obj # the dataloader returns an PaddedBatch obj now
type(batch_obj)
动态项可以在批次对象中使用 dict
语法访问
batch_obj["spkid_encoded"]
batch_obj["signal"]
batch_obj["id"] # example ids in this batch useful for debugging
如前所述,PaddedBatch 中所有是 torch.Tensors
的元素都会通过在右侧添加零进行填充。当访问这些元素时,会返回一个命名元组(namedtuple):实际的填充张量和一个 length
张量。
wav_data, length = batch_obj["signal"]
由于它是一个命名元组,length 和 data 这两个项目也可以作为属性访问
lengths = batch_obj["signal"].lengths
wav_data = batch_obj["signal"].data
lengths
这个长度张量包含每个序列的相对真实长度。在这个例子中,这意味着批次中的第二个示例没有被填充(相对长度 == 1),而第一个示例则被填充到其长度的两倍多。
使用相对长度而不是绝对索引保证这些值即使在特征提取后也不会改变:无论窗长是多少,相对真实长度都保持不变,即使在 STFT 之后也是如此。
绝对索引很容易获得
abs_lens = (lengths*wav_data.shape[1]).long()
abs_lens
wav_data[1][:abs_lens[1]] # no zeros
wav_data[1][abs_lens[1]:] # zeros begins at abs_lens[0]
PaddedBatch 对象可以方便地将所有是 torch.Tensor
的动态项目使用 to 移动到正确的设备
batch_obj = batch_obj.to("cpu")
当然,不是张量的项目(例如 id
)不会被移动,也不会被填充。它们只是简单地作为一个列表返回。
batch_obj["id"]
也可以迭代 PaddedBatch
的示例
for ex in batch_obj:
print(ex)
并通过其位置访问单个示例
batch_obj.at_position(1)
这些方法可以方便地在 Brain
类的 compute_forward
和 compute_objectives
方法中使用。正如我们在本教程的第一个示例中所示,该示例说明了一个完整的数据 IO 示例。
def compute_forward(self, batch, stage):
audio, audio_len = batch["sig"]
# the examples are automatically padded, audio_len contains the relative
# length of the original sequence.
return self.modules.model(audio.unsqueeze(1)).mean(-1).unsqueeze(-1)
def compute_objectives(self, logits, batch, stage):
spk_ids, _ = batch["spkid_encoded"]
return torch.nn.functional.cross_entropy(logits, spk_ids)
完整示例:训练一个简单的说话人识别系统。
下面我们展示如何使用 DynamicItemDataset、DIPs 和 CategoricalEncoder 为说话人识别构建数据流程。
特别是我们必须:
读取音频
从标注中读取说话人 ID 并将其编码为整数
我们首先从该 JSON 标注实例化数据集
dataset = DynamicItemDataset.from_json("data.json")
然后将 CategoricalEncoder 适配到标注中的说话人 ID (spkID
)。
spk_id_encoder = CategoricalEncoder()
spk_id_encoder.update_from_didataset(dataset, "spkID")
我们添加编码 spkID
的 DIP
dataset.add_dynamic_item(spk_id_encoder.encode_label_torch, takes="spkID", provides="spk_encoded")
我们添加读取音频的 DIP
dataset.add_dynamic_item(speechbrain.dataio.dataio.read_audio, takes="file_path", provides="signal")
并设置在训练循环中想要访问的数据集输出
dataset.set_output_keys(["id", "signal", "spk_encoded"])
我们根据长度对数据集进行排序以加快训练,从而在批次中最小化填充元素的数量。
sorted_data = dataset.filtered_sorted(sort_key="length")
现在我们可以训练一个简单的分类器,只需将数据集对象直接传递给 Brain 类。Brain 类会自动创建一个带有指定 train_loader_kwargs
的 SaveableDataLoader,并将为您处理填充(padding)。
from speechbrain.lobes.features import MFCC, Fbank
from speechbrain.nnet.losses import nll_loss
class SimpleBrain(speechbrain.Brain):
def compute_forward(self, batch, stage):
example_batch = batch
x = self.modules.features(batch.signal.data)
x = self.modules.encoder(x)
x = self.modules.pooling(x, batch.signal.lengths)
x = self.modules.to_output(x)
return self.modules.softmax(x)
def compute_objectives(self, logits, batch, stage):
return nll_loss(logits, batch.spk_encoded.data)
modules = {"features": Fbank(left_frames=1, right_frames=1),
"encoder": torch.nn.Sequential(torch.nn.Linear(40, 256),
torch.nn.ReLU()),
"pooling": speechbrain.nnet.pooling.StatisticsPooling(),
"to_output": torch.nn.Linear(512, len(spk_id_encoder)),
"softmax": speechbrain.nnet.activations.Softmax(apply_log=True)}
brain = SimpleBrain(modules, opt_class=lambda x: torch.optim.SGD(x, 0.01))
brain.fit(range(1), train_set=sorted_data,
train_loader_kwargs={"batch_size": 16, "drop_last":True})
致谢
非常感谢 Nasser Benabderrazik (lenassero) 帮助改进本教程。
引用 SpeechBrain
如果您在研究或业务中使用 SpeechBrain,请使用以下 BibTeX 条目引用它
@misc{speechbrainV1,
title={Open-Source Conversational AI with {SpeechBrain} 1.0},
author={Mirco Ravanelli and Titouan Parcollet and Adel Moumen and Sylvain de Langen and Cem Subakan and Peter Plantinga and Yingzhi Wang and Pooneh Mousavi and Luca Della Libera and Artem Ploujnikov and Francesco Paissan and Davide Borra and Salah Zaiem and Zeyu Zhao and Shucong Zhang and Georgios Karakasidis and Sung-Lin Yeh and Pierre Champion and Aku Rouhe and Rudolf Braun and Florian Mai and Juan Zuluaga-Gomez and Seyed Mahed Mousavi and Andreas Nautsch and Xuechen Liu and Sangeet Sagar and Jarod Duret and Salima Mdhaffar and Gaelle Laperriere and Mickael Rouvier and Renato De Mori and Yannick Esteve},
year={2024},
eprint={2407.00463},
archivePrefix={arXiv},
primaryClass={cs.LG},
url={https://arxiv.org/abs/2407.00463},
}
@misc{speechbrain,
title={{SpeechBrain}: A General-Purpose Speech Toolkit},
author={Mirco Ravanelli and Titouan Parcollet and Peter Plantinga and Aku Rouhe and Samuele Cornell and Loren Lugosch and Cem Subakan and Nauman Dawalatabad and Abdelwahab Heba and Jianyuan Zhong and Ju-Chieh Chou and Sung-Lin Yeh and Szu-Wei Fu and Chien-Feng Liao and Elena Rastorgueva and François Grondin and William Aris and Hwidong Na and Yan Gao and Renato De Mori and Yoshua Bengio},
year={2021},
eprint={2106.04624},
archivePrefix={arXiv},
primaryClass={eess.AS},
note={arXiv:2106.04624}
}