数据集文档

创建音频数据集

Hugging Face's logo
加入 Hugging Face 社区

并获得增强的文档体验

开始使用

创建音频数据集

你可以通过在 Hugging Face Hub 上创建一个数据集仓库,与你的团队或社区中的任何人分享数据集

from datasets import load_dataset

dataset = load_dataset("<username>/my_dataset")

有几种方法可以创建和分享音频数据集

  • 使用 Python 和 Dataset.push_to_hub() 从本地文件创建音频数据集。这是一种简单的方法,只需要几个 Python 步骤。

  • 使用 AudioFolder 构建器创建音频数据集仓库。这是一种无需代码的解决方案,可以快速创建包含数千个音频文件的数据集。

你可以通过要求用户先分享他们的联系信息来控制对数据集的访问。查看门控数据集指南,了解更多关于如何在 Hub 上启用此功能的信息。

本地文件

你可以使用音频文件的路径加载你自己的数据集。使用 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 是一个数据集构建器,旨在快速加载包含数千个音频文件的数据集,而无需编写任何代码。

💡 查看拆分模式层级结构,了解更多关于 AudioFolder 如何根据你的数据集仓库结构创建数据集拆分的信息。

AudioFolder 会根据目录名称自动推断数据集的类别标签。将你的数据集存储在如下目录结构中:

folder/train/dog/golden_retriever.mp3
folder/train/dog/german_shepherd.mp3
folder/train/dog/chihuahua.mp3

folder/train/cat/maine_coon.mp3
folder/train/cat/bengal.mp3
folder/train/cat/birman.mp3

如果数据集遵循 AudioFolder 结构,那么你可以使用 load_dataset() 直接加载它

>>> from datasets import load_dataset

>>> dataset = load_dataset("username/dataset_name")

这等同于在 load_dataset() 中手动传递 audiofolder,并在 data_dir 中传递目录

>>> dataset = load_dataset("audiofolder", data_dir="/path/to/folder")

你也可以使用 audiofolder 加载涉及多个拆分的数据集。为此,你的数据集目录应具有以下结构:

folder/train/dog/golden_retriever.mp3
folder/train/cat/maine_coon.mp3
folder/test/dog/german_shepherd.mp3
folder/test/cat/bengal.mp3

如果所有音频文件都包含在单个目录中,或者它们不在同一目录结构级别,则不会自动添加 label 列。如果需要它,请显式设置 drop_labels=False

如果想包含关于数据集的额外信息,例如文本字幕或边界框,请将其作为 metadata.csv 文件添加到文件夹中。这使你可以快速为不同的计算机视觉任务(如文本字幕或物体检测)创建数据集。你也可以使用 JSONL 文件 metadata.jsonl 或 Parquet 文件 metadata.parquet

folder/train/metadata.csv
folder/train/0001.mp3
folder/train/0002.mp3
folder/train/0003.mp3

你也可以压缩音频文件,在这种情况下,每个 zip 文件应同时包含音频文件和元数据

folder/train.zip
folder/test.zip
folder/validation.zip

你的 metadata.csv 文件必须具有 file_name*_file_name 字段,用于将音频文件与其元数据链接起来

file_name,additional_feature
0001.mp3,This is a first value of a text feature you added to your audio files
0002.mp3,This is a second value of a text feature you added to your audio files
0003.mp3,This is a third value of a text feature you added to your audio files

或者使用 metadata.jsonl

{"file_name": "0001.mp3", "additional_feature": "This is a first value of a text feature you added to your audio files"}
{"file_name": "0002.mp3", "additional_feature": "This is a second value of a text feature you added to your audio files"}
{"file_name": "0003.mp3", "additional_feature": "This is a third value of a text feature you added to your audio files"}

这里的 file_name 必须是元数据文件旁边音频文件的名称。更一般地说,它必须是从包含元数据的目录到音频文件的相对路径。

可以在数据集的每一行中指向多个音频,例如,如果你的输入和输出都是音频文件

{"input_file_name": "0001.mp3", "output_file_name": "0001_output.mp3"}
{"input_file_name": "0002.mp3", "output_file_name": "0002_output.mp3"}
{"input_file_name": "0003.mp3", "output_file_name": "0003_output.mp3"}

你还可以定义音频文件列表。在这种情况下,你需要将字段命名为 file_names*_file_names。这是一个例子

{"recordings_file_names": ["0001_r0.mp3", "0001_r1.mp3"], label: "same_person"}
{"recordings_file_names": ["0002_r0.mp3", "0002_r1.mp3"], label: "same_person"}
{"recordings_file_names": ["0003_r0.mp3", "0003_r1.mp3"], label: "different_person"}

WebDataset

WebDataset 格式基于 TAR 归档文件,适用于大型音频数据集。实际上,你可以将音频文件分组到 TAR 归档文件中(例如,每个 TAR 归档文件 1GB 的音频文件),并拥有数千个 TAR 归档文件

folder/train/00000.tar
folder/train/00001.tar
folder/train/00002.tar
...

在归档文件中,每个示例都由共享相同前缀的文件组成

e39871fd9fd74f55.mp3
e39871fd9fd74f55.json
f18b91585c4d3f3e.mp3
f18b91585c4d3f3e.json
ede6e66b2fb59aab.mp3
ede6e66b2fb59aab.json
ed600d57fcee4f94.mp3
ed600d57fcee4f94.json
...

你可以使用 JSON 或文本文件等来放置音频文件标签/字幕/边界框。

加载你的 WebDataset,它将为每个文件后缀创建一个列(这里是“mp3”和“json”)

>>> from datasets import load_dataset

>>> dataset = load_dataset("webdataset", data_dir="/path/to/folder", split="train")
>>> dataset[0]["json"]
{"transcript": "Hello there !", "speaker": "Obi-Wan Kenobi"}

每个示例也可以有多个音频文件,如下所示

e39871fd9fd74f55.input.mp3
e39871fd9fd74f55.output.mp3
e39871fd9fd74f55.json
f18b91585c4d3f3e.input.mp3
f18b91585c4d3f3e.output.mp3
f18b91585c4d3f3e.json
...

有关 WebDataset 格式和 Python 库的更多详细信息,请查看 WebDataset 文档

(旧版)加载脚本

编写数据集加载脚本以手动创建数据集。它定义了数据集的拆分和配置,并处理下载和生成数据集示例。脚本应与你的数据集文件夹或仓库同名

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 归档文件中,这需要一种特定的方法来支持流式处理模式。虽然不是必需的,但我们强烈建议在你的音频数据集中实现流式处理支持,因为磁盘空间不足的用户可以在不下载数据集的情况下使用它。在流式处理指南中了解更多关于流式处理的信息!

这是一个使用 TAR 归档文件的例子

my_dataset/
├── README.md
├── my_dataset.py
└── data/
    ├── train.tar.gz
    ├── test.tar.gz
    └── metadata.csv

除了学习如何创建可流式处理的数据集外,你还将学习如何:

  • 创建数据集构建器类。
  • 创建数据集配置。
  • 添加数据集元数据。
  • 下载并定义数据集拆分。
  • 生成数据集。
  • 将数据集上传到 Hub。

最好的学习方法是打开现有的音频数据集加载脚本,例如 Vivos,并跟着学习!

本指南展示了如何处理存储在 TAR 归档文件中的音频数据 - 这是音频数据集最常见的情况。查看 minds14 数据集,了解使用 ZIP 归档文件的音频脚本示例。

为了帮助你入门,我们创建了一个加载脚本模板,你可以复制并用作起点!

创建数据集构建器类

GeneratorBasedBuilder 是从字典生成器生成的数据集的基础类。在这个类中,有三种方法可以帮助你创建数据集

  • _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

你可以包含关于数据集的很多信息,但一些重要的信息是:

  1. description 提供了数据集的简洁描述。
  2. features 指定数据集列类型。由于你要创建音频加载脚本,因此你需要包含 Audio 特性和数据集的 sampling_rate
  3. homepage 提供了数据集主页的链接。
  4. license 指定使用数据集的权限,如许可证类型所定义。
  5. 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,
    )

下载并定义数据集拆分

现在你已经添加了一些关于数据集的信息,下一步是下载数据集并定义拆分。

  1. 使用 download() 方法下载 _PROMPTS_URLS 中的元数据文件和 _DATA_URL 中的音频 TAR 归档文件。此方法返回本地文件/归档文件的路径。在流式处理模式下,它不会下载文件,而只会返回一个 URL 以从中流式传输数据。此方法接受:

    • Hub 数据集仓库内文件的相对路径(例如,在 data/ 文件夹中)
    • 托管在其他位置的文件的 URL
    • 文件名或 URL 的(嵌套)列表或字典
  2. 下载数据集后,使用 SplitGenerator 来组织每个拆分中的音频文件和句子提示。使用标准名称命名每个拆分,例如:Split.TRAINSplit.TESTSPLIT.Validation

    gen_kwargs 参数中,指定 prompts_pathpath_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_pathpath_to_clipsaudio_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

恭喜你,现在你可以从 Hub 加载你的数据集了!🥳

>>> from datasets import load_dataset
>>> load_dataset("<username>/my_dataset")

(高级)在本地解压 TAR 压缩包

在上面的示例中,下载的压缩包未被解压,因此示例不包含有关它们在本地存储位置的信息。为了解释如何在也支持流式传输的方式下进行解压,我们将简要介绍 LibriVox Indonesia 加载脚本。

下载并定义数据集拆分

  1. 使用 download() 方法下载 _AUDIO_URL 处的音频数据。

  2. 要在本地解压音频 TAR 压缩包,请使用 extract()。你只能在非流式模式下使用此方法(当 dl_manager.is_streaming=False 时)。这将返回解压后的压缩包目录的本地路径

    local_extracted_archive = dl_manager.extract(audio_path) if not dl_manager.is_streaming else None
  3. 使用 iter_archive() 方法遍历 audio_path 处的压缩包,就像上面的 Vivos 示例一样。iter_archive() 不提供有关压缩包中文件完整路径的任何信息,即使它已被解压。因此,你需要将 local_extracted_archive 路径传递到 gen_kwargs 中的下一步,以便保留有关压缩包解压到何处的信息。这是在生成示例时构建到本地文件的正确路径所必需的。

你需要结合使用 download()iter_archive() 的原因是 TAR 压缩包中的文件无法通过其路径直接访问。相反,你需要遍历压缩包中的文件!你只能在非流式模式下将 download_and_extract()extract() 与 TAR 压缩包一起使用,否则会抛出错误。

  1. 使用 download_and_extract() 方法下载 _METADATA_URL 中指定的元数据文件。此方法在非流式模式下返回本地文件的路径。在流式模式下,它不会在本地下载文件,而是返回相同的 URL。

  2. 现在使用 SplitGenerator 来组织每个拆分中的音频文件和元数据。使用标准名称命名每个拆分,例如:Split.TRAINSplit.TESTSPLIT.Validation

    gen_kwargs 参数中,指定 local_extracted_archiveaudio_filesmetadata_pathpath_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_archiveaudio_filesmetadata_pathpath_to_clips 作为参数。

  1. 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
  2. 现在你可以生成 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
< > 在 GitHub 上 更新