要在GitHub上执行或查看/下载此笔记本
数据加载
处理数据消耗了许多机器学习项目中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 数据输入输出遵循并扩展了 PyTorch 数据加载。本前言部分回顾了 PyTorch 数据加载,尚未考虑 SpeechBrain 数据加载的扩展。
概述
PyTorch 数据加载可以在多种配置下运行, 但典型的方法包含以下基本元素:
一个Dataset,它一次加载一个数据点。
一个排序函数,或简称
collate_fn
,它接收一个数据点列表并形成一个批次。一个Sampler,它决定了数据集的迭代顺序。
一个 DataLoader,它结合了上述元素(并为
collate_fn
和 Sampler 提供了默认值),并协调整个流程。
数据集
Dataset 的作用是生成单个数据点。通常它们是从磁盘加载的,但它们也可能来自一些更复杂的来源,或者在某些情况下仅来自 RAM。您可以编写自己的 Dataset 子类,有时也可以使用标准化的类。训练、验证和测试子集各自拥有自己的 Dataset 实例。
Dataset 接口很简单;它实现了
__getitem__
并且通常也实现了 __len__
。通常使用“映射风格”的 Dataset,但值得注意的是 PyTorch 也有一个 IterableDataset 的概念。
__getitem__
可以返回任何内容,因为数据可以看起来像任何东西。不过,通常一个数据点由多个相关的事物组成(例如,一张图片和一个标签,或者一个语音波形及其转录)。数据集应该返回所有这些相关的事物。
在CPU上动态转换数据也是相对常见的。
排序函数
collate_fn
只是将一系列示例转换为一个 PyTorch 张量批次。如果数据包含可变长度的序列,collate_fn
通常需要实现填充。
采样器
通常用户不需要创建自己的采样器;两个默认选项是按原始数据集顺序迭代或按随机顺序迭代。
数据加载器
DataLoader 接受上述其他元素以及许多其他参数,例如批量大小。DataLoader 对所有参数都有基本的默认值(当然,除了 Dataset),但理解 args 是值得的。
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)
请注意,还可以实现更高级/灵活的管道,用户可以集成他们自己的自定义数据集、数据加载器和采样器。这些在dataIO高级教程中有所说明。
基本的数据IO管道围绕三个“关键”块组织:DynamicItemDataset、Dynamic Items Pipelines (DIPs) 和 CategoricalEncoder,它们紧密相连。
DynamicItemDataset 继承自 torch.utils.data.Dataset
,并且设计用于与 Dynamic Items Pipelines 一起工作,以提供一种直接且灵活的方式来从存储在磁盘上的原始数据集中获取和转换数据和标签。
DIPs 由用户定义的函数组成,用户在这些函数中指定应用于数据集中包含的元数据和数据的操作。例如,读取和增强音频文件或使用SentencePiece分词器对一系列单词进行编码。
这些函数在DynamicItemDataset的__getitem__
方法内部调用,并在CPU上并行运行。
CategoricalEncoder 是我们为多类分类问题提供的一个方便的抽象,它被细分为 TextEncoder 和 CTCTextEncoder,这些可以用于与文本相关的序列到序列应用,例如 ASR。
多亏了这些抽象,设置数据输入输出管道所需的大部分工作是将数据集解析为DynamicItemDataset支持的合适注释(SpeechBrain支持CSV和JSON格式)。
一旦这个注释准备就绪,就可以用几行代码创建一个灵活高效的管道,因为SpeechBrain会在后台处理填充和其他操作。
在以下教程中,我们将详细解释这些模块的工作原理。 我们将从所需的CSV或JSON注释开始,其目的是表示和描述数据集中包含的信息。 例如:
音频文件的路径,预提取的特征等。
元数据,如音频文件中说出的词语、信噪比、声音事件标签、说话者身份等。
基本上训练算法所需的任何信息。
数据集注释
SpeechBrain 提供了对 JSON 和 CSV 格式的原生支持,用于描述数据集,事实上,在官方配方(如 LibriSpeech ASR 配方)中,我们提供了解析脚本来获取这些格式。
我们可以通过下载的Mini-LibriSpeech示例来一窥这些文件的结构。
Mini-LibriSpeech中的每个文件都是来自单个说话者的话语,因此可以使用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)、说话人日志(diarization)、语音活动检测(VAD)等。
这是因为在SpeechBrain中,我们致力于许多不同的任务和数据集。
每个数据集都是独一无二的,它可以是单通道或多通道的,可以提供不同的元数据,如说话者ID、说话者位置,甚至是多模态数据,如音频和视频。
每个任务都是独一无二的,本示例中使用的注释适用于ASR和说话人识别等应用。但对于例如说话人分割,用户可能还希望获得每个话语的开始和停止时间(以秒、帧等为单位)。
这也使得注释保持非常简单,并专注于特定任务,仅包含当前应用程序所需的信息,而不是拥有一个繁琐的全能注释。
提示
在构建解析脚本时,为每个示例设置一个length
或duration
非常有用,这些示例包含以秒、样本或帧为单位的长度。这允许进行后续操作,例如过滤过长的示例(以避免OOM问题)或对它们进行排序以加快训练速度。关于CSV文件,如果指定了duration
列,则在从CSV构建DynamicItemDataset时,它会自动转换为浮点数。
如前所述,这些文件必须由合适的解析脚本生成,该脚本在我们的配方中提供,并依赖于数据集和任务。 然而,如果希望使用自己的自定义数据集,则必须编写解析脚本以生成描述数据的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 是 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]
分类编码器
SpeechBrain dataio
提供了一个 CategoricalEncoder
类,用于编码属于离散集的标签:例如用于说话人识别或任何其他多类分类问题。
给定一组可哈希的对象(例如字符串),它将每个唯一的项目编码为一个整数值:["spk0", "spk1"]
–> [0, 1]
在内部,每个标签与其索引之间的对应关系由两个字典处理:lab2ind
和 ind2lab
。
它是为了与DynamicItemDataset
和dataIO pipelines
紧密集成而构建的。
例如,可以通过创建CategoricalEncoder的实例并将其拟合到数据集对象,从我们的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 提供了一种方便的方法,可以在多个维度上自动对不同长度的张量进行右填充。
这是通过使用在 speechbrain.dataio.batch
中定义的 PaddedBatch
类来实现的。
PaddedBatch
既是一个 collate_fn
也是一个批处理对象。
当一个torch.utils.data.Dataset
(因此也是一个DynamicItemDataset
)被传递给Brain
类时,PaddedBatch
被用作默认的整理函数collate_fn
,并且示例会自动填充在一起。
作为默认的DataLoader,Brain类实例化了一个SpeechBrain自定义的DataLoader:speechbrain.dataio.dataloader.SaveableDataLoader
。
这个DataLoader与普通的DataLoader相同,只是它允许在epoch内保存。因此,如果由于某种原因训练在epoch中间停止,可以从该步骤精确地恢复。请参阅Checkpointing Tutorial。
此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
对象(spkid_encoded
)编码的说话者ID和示例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"]
由于它是一个namedtuple,因此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
)移动到正确的设备上,使用以下方法:
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")
我们添加了DIP,它编码了spkID
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,并为您处理填充。
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}
}