创建音频数据集
您可以通过在 Hugging Face 中心创建数据集仓库来与团队或社区中的任何人分享数据集
from datasets import load_dataset
dataset = load_dataset("<username>/my_dataset")
有多种方法可以创建和分享音频数据集
使用 Dataset.push_to_hub() 在 Python 中从本地文件创建音频数据集。这是一种简单的方法,只需在 Python 中执行几个步骤。
使用
AudioFolder
构建器创建音频数据集仓库。这是一种无代码解决方案,可用于快速创建包含数千个音频文件的音频数据集。
您可以通过要求用户首先共享他们的联系信息来控制对数据集的访问权限。有关如何在中心启用此功能的更多信息,请查看 门控数据集 指南。
本地文件
您可以使用音频文件的路径加载自己的数据集。使用 cast_column() 函数获取音频文件路径的列,并将其转换为 Audio 特征
>>> audio_dataset = Dataset.from_dict({"audio": ["path/to/audio_1", "path/to/audio_2", ..., "path/to/audio_n"]}).cast_column("audio", Audio())
>>> audio_dataset[0]["audio"]
{'array': array([ 0. , 0.00024414, -0.00024414, ..., -0.00024414,
0. , 0. ], dtype=float32),
'path': 'path/to/audio_1',
'sampling_rate': 16000}
然后使用 Dataset.push_to_hub() 将数据集上传到 Hugging Face Hub
audio_dataset.push_to_hub("<username>/my_dataset")
这将创建一个包含您的音频数据集的数据集存储库
my_dataset/
├── README.md
└── data/
└── train-00000-of-00001.parquet
AudioFolder
AudioFolder
是一个数据集构建器,旨在快速加载包含数千个音频文件的数据集,无需编写任何代码。只要您在元数据文件中 (metadata.csv
/metadata.jsonl
) 中包含此信息,AudioFolder
就会自动加载有关数据集的任何其他信息,例如转录、说话人口音或说话人意图。
💡 查看 Split pattern hierarchy 了解有关 AudioFolder
如何根据您的数据集存储库结构创建数据集拆分的更多信息。
在 Hugging Face Hub 上创建一个数据集存储库,并按照 AudioFolder
结构上传您的数据集目录
my_dataset/
├── README.md
├── metadata.csv
└── data/
data
文件夹可以是您想要的任何名称。
如果数据列包含更复杂的格式(例如浮点数列表),则将元数据存储为 jsonl
文件可能会有所帮助,以避免解析错误或将复杂值读取为字符串。
元数据文件应包含一个 file_name
列,以将音频文件与其元数据关联起来
file_name,transcription
data/first_audio_file.mp3,znowu się duch z ciałem zrośnie w młodocianej wstaniesz wiosnie i możesz skutkiem tych leków umierać wstawać wiek wieków dalej tam były przestrogi jak siekać głowę jak nogi
data/second_audio_file.mp3,już u źwierzyńca podwojów król zasiada przy nim książęta i panowie rada a gdzie wzniosły krążył ganek rycerze obok kochanek król skinął palcem zaczęto igrzysko
data/third_audio_file.mp3,pewnie kędyś w obłędzie ubite minęły szlaki zaczekajmy dzień jaki poślemy szukać wszędzie dziś jutro pewnie będzie posłali wszędzie sługi czekali dzień i drugi gdy nic nie doczekali z płaczem chcą jechać dali
然后,您可以将数据集存储在类似于以下的目录结构中
metadata.csv
data/first_audio_file.mp3
data/second_audio_file.mp3
data/third_audio_file.mp3
现在,用户可以通过在 load_dataset() 中指定 audiofolder
以及 data_dir
中的数据集目录来加载您的数据集和相关元数据
>>> from datasets import load_dataset
>>> dataset = load_dataset("audiofolder", data_dir="/path/to/data")
>>> dataset["train"][0]
{'audio':
{'path': '/path/to/extracted/audio/first_audio_file.mp3',
'array': array([ 0.00088501, 0.0012207 , 0.00131226, ..., -0.00045776, -0.00054932, -0.00054932], dtype=float32),
'sampling_rate': 16000},
'transcription': 'znowu się duch z ciałem zrośnie w młodocianej wstaniesz wiosnie i możesz skutkiem tych leków umierać wstawać wiek wieków dalej tam były przestrogi jak siekać głowę jak nogi'
}
您还可以使用 audiofolder
来加载涉及多个拆分的数据集。为此,您的数据集目录可能具有以下结构
data/train/first_train_audio_file.mp3
data/train/second_train_audio_file.mp3
data/test/first_test_audio_file.mp3
data/test/second_test_audio_file.mp3
请注意,如果音频文件不位于元数据文件的正旁边,则 file_name
列应该是音频文件的完整相对路径,而不仅仅是其文件名。
对于没有关联元数据的音频数据集,AudioFolder
会根据目录名称自动推断数据集的类别标签。这可能对音频分类任务有用。您的数据集目录可能如下所示
data/train/electronic/01.mp3
data/train/punk/01.mp3
data/test/electronic/09.mp3
data/test/punk/09.mp3
使用 AudioFolder
加载数据集,它将从目录名称(语言 ID)创建 label
列
>>> from datasets import load_dataset
>>> dataset = load_dataset("audiofolder", data_dir="/path/to/data")
>>> dataset["train"][0]
{'audio':
{'path': '/path/to/electronic/01.mp3',
'array': array([ 3.9714024e-07, 7.3031038e-07, 7.5640685e-07, ...,
-1.1963668e-01, -1.1681189e-01, -1.1244172e-01], dtype=float32),
'sampling_rate': 44100},
'label': 0 # "electronic"
}
>>> dataset["train"][-1]
{'audio':
{'path': '/path/to/punk/01.mp3',
'array': array([0.15237972, 0.13222949, 0.10627693, ..., 0.41940814, 0.37578005,
0.33717662], dtype=float32),
'sampling_rate': 44100},
'label': 1 # "punk"
}
如果所有音频文件都包含在一个目录中,或者它们不在同一目录结构级别,则不会自动添加 label
列。如果您需要它,请显式设置 drop_labels=False
。
某些音频数据集,例如在 Kaggle 竞赛 中找到的数据集,每个拆分都有单独的元数据文件。只要元数据特征对每个拆分都是相同的,就可以使用 audiofolder
一次加载所有拆分。如果元数据特征在每个拆分之间有所不同,则应使用单独的 load_dataset()
调用来加载它们。
(Legacy) 加载脚本
编写一个数据集加载脚本以手动创建数据集。它定义了数据集的拆分和配置,并处理数据集示例的下载和生成。该脚本应与您的数据集文件夹或存储库同名
my_dataset/
├── README.md
├── my_dataset.py
└── data/
data
文件夹可以是您想要的任何名称,不必是 data
。除非您将数据集托管在 Hub 上,否则此文件夹是可选的。
这种目录结构允许您在一行中加载数据集
>>> from datasets import load_dataset
>>> dataset = load_dataset("path/to/my_dataset")
本指南将向您展示如何为音频数据集创建数据集加载脚本,这与 为文本数据集创建加载脚本 有些不同。音频数据集通常存储在 tar.gz
档案文件中,这需要特定方法来支持流模式。虽然流不是必需的,但我们强烈建议在您的音频数据集中实现流支持,因为磁盘空间不多的用户可以在不下载数据集的情况下使用它。在 Stream 指南中了解更多有关流的信息!
以下是用 TAR 档案的示例
my_dataset/
├── README.md
├── my_dataset.py
└── data/
├── train.tar.gz
├── test.tar.gz
└── metadata.csv
除了学习如何创建可流式传输的数据集之外,您还将学习如何
- 创建数据集构建器类。
- 创建数据集配置。
- 添加数据集元数据。
- 下载并定义数据集拆分。
- 生成数据集。
- 将数据集上传到 Hub。
最好的学习方法是打开现有的音频数据集加载脚本,例如 Vivos,并按照说明进行操作!
本指南展示了如何处理存储在 TAR 档案中的音频数据,这在音频数据集中最为常见。查看 minds14 数据集,这是一个使用 ZIP 档案的音频脚本示例。
为了帮助您入门,我们创建了一个加载脚本 模板,您可以复制它并将其用作起点!
创建数据集构建器类
GeneratorBasedBuilder 是从字典生成器生成的 datasets 的基类。在此类中,有三种方法可以帮助您创建数据集
_info
存储有关数据集的信息,例如其描述、许可证和特征。_split_generators
下载数据集并定义其拆分。_generate_examples
为每个拆分生成数据集的样本,其中包含音频数据和info
中指定的其他特征。
首先,将数据集类创建为 GeneratorBasedBuilder 的子类,并添加这三种方法。不用担心现在就填写这些方法,您将在接下来的几节中开发它们
class VivosDataset(datasets.GeneratorBasedBuilder):
"""VIVOS is a free Vietnamese speech corpus consisting of 15 hours of recording speech prepared for
Vietnamese Automatic Speech Recognition task."""
def _info(self):
def _split_generators(self, dl_manager):
def _generate_examples(self, prompts_path, path_to_clips, audio_files):
多个配置
在某些情况下,数据集可能具有不止一种配置。例如,LibriVox Indonesia 数据集具有与不同语言相对应的几种配置。
要创建不同的配置,请使用 BuilderConfig 类创建数据集的子类。唯一必需的参数是配置的 name
,必须将其传递给配置的超类 __init__()
。否则,您可以在配置类中指定您想要的任何自定义参数。
class LibriVoxIndonesiaConfig(datasets.BuilderConfig):
"""BuilderConfig for LibriVoxIndonesia."""
def __init__(self, name, version, **kwargs):
self.language = kwargs.pop("language", None)
self.release_date = kwargs.pop("release_date", None)
self.num_clips = kwargs.pop("num_clips", None)
self.num_speakers = kwargs.pop("num_speakers", None)
self.validated_hr = kwargs.pop("validated_hr", None)
self.total_hr = kwargs.pop("total_hr", None)
self.size_bytes = kwargs.pop("size_bytes", None)
self.size_human = size_str(self.size_bytes)
description = (
f"LibriVox-Indonesia speech to text dataset in {self.language} released on {self.release_date}. "
f"The dataset comprises {self.validated_hr} hours of transcribed speech data"
)
super(LibriVoxIndonesiaConfig, self).__init__(
name=name,
version=datasets.Version(version),
description=description,
**kwargs,
)
在 GeneratorBasedBuilder 中的 BUILDER_CONFIGS
类变量中定义您的配置。在这个例子中,作者从其存储库中的另一个 release_stats.py
文件 中导入语言,然后遍历每种语言来创建一个配置
class LibriVoxIndonesia(datasets.GeneratorBasedBuilder):
DEFAULT_CONFIG_NAME = "all"
BUILDER_CONFIGS = [
LibriVoxIndonesiaConfig(
name=lang,
version=STATS["version"],
language=LANGUAGES[lang],
release_date=STATS["date"],
num_clips=lang_stats["clips"],
num_speakers=lang_stats["users"],
total_hr=float(lang_stats["totalHrs"]) if lang_stats["totalHrs"] else None,
size_bytes=int(lang_stats["size"]) if lang_stats["size"] else None,
)
for lang, lang_stats in STATS["locales"].items()
]
通常,用户需要在 load_dataset() 中指定一个配置来加载,否则会引发 ValueError
。您可以通过在 DEFAULT_CONFIG_NAME
中设置一个默认数据集配置来加载来避免这种情况。
现在,如果用户想要加载巴厘语 (bal
) 配置,他们可以使用配置名称
>>> from datasets import load_dataset
>>> dataset = load_dataset("indonesian-nlp/librivox-indonesia", "bal", split="train")
添加数据集元数据
添加有关数据集的信息有助于用户了解更多信息。此信息存储在 DatasetInfo 类中,该类由 info
方法返回。用户可以通过以下方式访问此信息
>>> from datasets import load_dataset_builder
>>> ds_builder = load_dataset_builder("vivos")
>>> ds_builder.info
您可以包含许多关于数据集的信息,但一些重要的信息是
description
提供数据集的简洁描述。features
指定数据集列类型。由于您正在创建音频加载脚本,因此需要包含 Audio 特征和数据集的sampling_rate
。homepage
提供数据集主页的链接。license
指定根据许可证类型定义的数据集使用权限。citation
是数据集的 BibTeX 引用。
您会注意到许多数据集信息是在加载脚本的前面定义的,这使得它更容易阅读。还有其他 ~Dataset.Features
可以输入,因此请务必查看完整列表和 特征指南 以获取更多详细信息。
def _info(self):
return datasets.DatasetInfo(
description=_DESCRIPTION,
features=datasets.Features(
{
"speaker_id": datasets.Value("string"),
"path": datasets.Value("string"),
"audio": datasets.Audio(sampling_rate=16_000),
"sentence": datasets.Value("string"),
}
),
supervised_keys=None,
homepage=_HOMEPAGE,
license=_LICENSE,
citation=_CITATION,
)
下载并定义数据集拆分
现在您已经添加了一些关于数据集的信息,下一步是下载数据集并定义拆分。
使用 download() 方法下载
_PROMPTS_URLS
的元数据文件和_DATA_URL
的音频 TAR 存档。此方法返回本地文件/存档的路径。在流模式下,它不会下载文件,而是返回一个 URL 用于从流中获取数据。此方法接受- Hub 数据集存储库中文件的相对路径(例如,在
data/
文件夹中) - 托管在其他地方的文件的 URL
- 文件名称或 URL 的(嵌套)列表或字典
- Hub 数据集存储库中文件的相对路径(例如,在
下载完数据集后,使用 SplitGenerator 来组织每个拆分中的音频文件和句子提示。使用标准名称命名每个拆分,例如:
Split.TRAIN
、Split.TEST
和SPLIT.Validation
。在
gen_kwargs
参数中,指定prompts_path
和path_to_clips
的文件路径。对于audio_files
,您需要使用 iter_archive() 来迭代 TAR 存档中的音频文件。这将为您的数据集启用流式传输。所有这些文件路径都将传递到下一步,您将在那里实际生成数据集。
def _split_generators(self, dl_manager):
"""Returns SplitGenerators."""
prompts_paths = dl_manager.download(_PROMPTS_URLS)
archive = dl_manager.download(_DATA_URL)
train_dir = "vivos/train"
test_dir = "vivos/test"
return [
datasets.SplitGenerator(
name=datasets.Split.TRAIN,
gen_kwargs={
"prompts_path": prompts_paths["train"],
"path_to_clips": train_dir + "/waves",
"audio_files": dl_manager.iter_archive(archive),
},
),
datasets.SplitGenerator(
name=datasets.Split.TEST,
gen_kwargs={
"prompts_path": prompts_paths["test"],
"path_to_clips": test_dir + "/waves",
"audio_files": dl_manager.iter_archive(archive),
},
),
]
此实现不会提取下载的存档。如果您想在下载后提取文件,则需要另外使用 extract(),请参阅 (高级) 提取 TAR 存档 部分。
生成数据集
在 GeneratorBasedBuilder 类中的最后一个方法实际上是在生成数据集中的样本。它根据 info
方法中指定的 features
结构生成数据集。如您所见,generate_examples
接受来自先前方法的 prompts_path
、path_to_clips
和 audio_files
作为参数。
TAR 存档中的文件是按顺序访问和生成的。这意味着您需要首先掌握与 TAR 文件中的音频文件相关的元数据,这样您就可以与相应的音频文件一起生成它。
examples = {}
with open(prompts_path, encoding="utf-8") as f:
for row in f:
data = row.strip().split(" ", 1)
speaker_id = data[0].split("_")[0]
audio_path = "/".join([path_to_clips, speaker_id, data[0] + ".wav"])
examples[audio_path] = {
"speaker_id": speaker_id,
"path": audio_path,
"sentence": data[1],
}
最后,迭代 audio_files
中的文件,并与相应的元数据一起生成它们。 iter_archive() 生成一个 (path
, f
) 元组,其中 path
是 TAR 存档中文件的**相对路径**,而 f
是文件对象本身。
inside_clips_dir = False
id_ = 0
for path, f in audio_files:
if path.startswith(path_to_clips):
inside_clips_dir = True
if path in examples:
audio = {"path": path, "bytes": f.read()}
yield id_, {**examples[path], "audio": audio}
id_ += 1
elif inside_clips_dir:
break
将这两个步骤放在一起,整个 _generate_examples
方法看起来像
def _generate_examples(self, prompts_path, path_to_clips, audio_files):
"""Yields examples as (key, example) tuples."""
examples = {}
with open(prompts_path, encoding="utf-8") as f:
for row in f:
data = row.strip().split(" ", 1)
speaker_id = data[0].split("_")[0]
audio_path = "/".join([path_to_clips, speaker_id, data[0] + ".wav"])
examples[audio_path] = {
"speaker_id": speaker_id,
"path": audio_path,
"sentence": data[1],
}
inside_clips_dir = False
id_ = 0
for path, f in audio_files:
if path.startswith(path_to_clips):
inside_clips_dir = True
if path in examples:
audio = {"path": path, "bytes": f.read()}
yield id_, {**examples[path], "audio": audio}
id_ += 1
elif inside_clips_dir:
break
将数据集上传到 Hub
恭喜,您现在可以从 Hub 加载您的数据集!🥳
>>> from datasets import load_dataset
>>> load_dataset("<username>/my_dataset")
(高级) 在本地提取 TAR 存档
在上面的示例中,下载的存档不会被提取,因此示例不包含有关它们存储在本地位置的信息。为了解释如何在支持流式传输的方式下进行提取,我们将简要介绍 LibriVox Indonesia 加载脚本。
下载并定义数据集拆分
使用 download() 方法下载
_AUDIO_URL
的音频数据。要在本地提取音频 TAR 存档,请使用 extract()。您只能在非流模式下(当
dl_manager.is_streaming=False
时)使用此方法。这将返回提取的存档目录的本地路径local_extracted_archive = dl_manager.extract(audio_path) if not dl_manager.is_streaming else None
使用 iter_archive() 方法迭代
audio_path
的存档,就像上面 Vivos 示例中一样。 iter_archive() 不会提供有关来自存档的文件的完整路径的任何信息,即使它已被提取。因此,您需要将local_extracted_archive
路径传递到gen_kwargs
中的下一步,以便保留有关存档提取到的位置的信息。这是在生成示例时构建本地文件的正确路径所必需的。
您需要使用 download() 和 iter_archive() 的组合的原因是,TAR 存档中的文件无法通过其路径直接访问。相反,您需要迭代存档中的文件!您只能在非流模式下使用 download_and_extract() 和 extract() 与 TAR 存档一起使用,否则它将引发错误。
使用 download_and_extract() 方法下载
_METADATA_URL
中指定的文件元数据。此方法在非流模式下返回本地文件的路径。在流模式下,它不会在本地下载文件,并返回相同的 URL。现在使用 SplitGenerator 来组织每个拆分中的音频文件和元数据。使用标准名称命名每个拆分,例如:
Split.TRAIN
、Split.TEST
和SPLIT.Validation
。在
gen_kwargs
参数中,指定local_extracted_archive
、audio_files
、metadata_path
和path_to_clips
的文件路径。请记住,对于audio_files
,您需要使用 iter_archive() 来迭代 TAR 存档中的音频文件。这将为您的数据集启用流式传输!所有这些文件路径都将传递到下一步,您将在那里生成数据集样本。
def _split_generators(self, dl_manager):
"""Returns SplitGenerators."""
audio_path = dl_manager.download(_AUDIO_URL)
local_extracted_archive = dl_manager.extract(audio_path) if not dl_manager.is_streaming else None
path_to_clips = "librivox-indonesia"
return [
datasets.SplitGenerator(
name=datasets.Split.TRAIN,
gen_kwargs={
"local_extracted_archive": local_extracted_archive,
"audio_files": dl_manager.iter_archive(audio_path),
"metadata_path": dl_manager.download_and_extract(_METADATA_URL + "/metadata_train.csv.gz"),
"path_to_clips": path_to_clips,
},
),
datasets.SplitGenerator(
name=datasets.Split.TEST,
gen_kwargs={
"local_extracted_archive": local_extracted_archive,
"audio_files": dl_manager.iter_archive(audio_path),
"metadata_path": dl_manager.download_and_extract(_METADATA_URL + "/metadata_test.csv.gz"),
"path_to_clips": path_to_clips,
},
),
]
生成数据集
这里 _generate_examples
接受来自先前方法的 local_extracted_archive
、audio_files
、metadata_path
和 path_to_clips
作为参数。
TAR 文件是按顺序访问和生成的。这意味着您需要首先掌握
metadata_path
中与 TAR 文件中的音频文件相关的元数据,这样您就可以与相应的音频文件一起生成它。with open(metadata_path, "r", encoding="utf-8") as f: reader = csv.DictReader(f) for row in reader: if self.config.name == "all" or self.config.name == row["language"]: row["path"] = os.path.join(path_to_clips, row["path"]) # if data is incomplete, fill with empty values for field in data_fields: if field not in row: row[field] = "" metadata[row["path"]] = row
现在您可以生成
audio_files
存档中的文件。当您使用 iter_archive() 时,它会生成一个 (path
,f
) 元组,其中path
是存档中文件的**相对路径**,而f
是文件对象本身。要获取本地提取文件的**完整路径**,请将目录的路径 (local_extracted_path
) 与提取存档的相对音频文件路径 (path
) 连接起来for path, f in audio_files: if path in metadata: result = dict(metadata[path]) # set the audio feature and the path to the extracted file path = os.path.join(local_extracted_archive, path) if local_extracted_archive else path result["audio"] = {"path": path, "bytes": f.read()} result["path"] = path yield id_, result id_ += 1
将这两个步骤放在一起,整个 _generate_examples
方法应该看起来像
def _generate_examples(
self,
local_extracted_archive,
audio_files,
metadata_path,
path_to_clips,
):
"""Yields examples."""
data_fields = list(self._info().features.keys())
metadata = {}
with open(metadata_path, "r", encoding="utf-8") as f:
reader = csv.DictReader(f)
for row in reader:
if self.config.name == "all" or self.config.name == row["language"]:
row["path"] = os.path.join(path_to_clips, row["path"])
# if data is incomplete, fill with empty values
for field in data_fields:
if field not in row:
row[field] = ""
metadata[row["path"]] = row
id_ = 0
for path, f in audio_files:
if path in metadata:
result = dict(metadata[path])
# set the audio feature and the path to the extracted file
path = os.path.join(local_extracted_archive, path) if local_extracted_archive else path
result["audio"] = {"path": path, "bytes": f.read()}
result["path"] = path
yield id_, result
id_ += 1