翻译自:https://huggingface.co/docs/datasets/audio_dataset


一、本地文件

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 repo,包含你的音频数据集

my_dataset/
├── README.md
└── data/
    └── train-00000-of-00001.parquet  

二、音频文件夹

AudioFolder是一个数据集生成器,旨在快速加载包含数千个音频文件的音频数据集,而无需编写任何代码。
只要您将有关数据集的任何其他信息 包含在 元数据文件(metadata.csv/metadata.jsonl)中,AudioFolder就会自动加载这些信息,例如转录、说话者口音或说话者意图。

在Hugging Face Hub上创建一个数据集存储库,并按照 AudioFolder 结构上传数据集目录:

my_dataset/
├── README.md
├── metadata.csv
└── data/

最好使用 jsonl 格式文件,以免你的数据 columns 格式复杂,导致出错。

元数据文件应包括一个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)创建一个标签列:

>>> 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"
}


如果所有音频文件都包含在一个目录中,或者它们不在同一级别的目录结构中,则不会自动添加标签列。

如果需要,请显式设置 drop_labels=False


一些音频数据集,比如在Kaggle比赛中发现的数据集,对于每个分割都有单独的元数据文件。

如果每个 split 的元数据功能相同,则可以使用audiofolder一次加载所有拆分。

如果每个split 的元数据特性不同,则应该使用单独的 load_dataset() 调用来加载它们。



加载脚本

  • 编写数据集加载脚本,以手动创建数据集。
  • 它定义了数据集的拆分和配置,并处理下载和生成数据集示例。
  • 脚本应与数据集文件夹或存储库具有相同的名称:
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 压缩文件中,这需要一种特殊的方法来支持流模式。
  • 虽然不需要流,但我们强烈鼓励在您的音频数据集中支持流,因为:
  • 没有大量磁盘空间的用户,可以在不下载数据集的情况下使用数据集。请参阅流媒体指南了解有关流媒体的更多信息:https://huggingface.co/docs/datasets/stream
  • 用户可以在数据集查看器中预览数据集。

这是使用 TAR 压缩包的示例:

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

除了学习如何创建可流化数据集外,您还将学习如何:

  • 创建一个数据集生成器类。
  • 创建数据集配置。
  • 添加数据集元数据。
  • 下载并定义数据集 splits。
  • 生成数据集。
  • 将数据集上载到 Hub。

最好的学习方法是打开一个现有的音频数据集加载脚本,比如 Vivos,然后继续学习!

https://huggingface.co/datasets/vivos/blob/main/vivos.py


本指南展示了如何处理存储在TAR档案中的音频数据,这是音频数据集最常见的情况。

查看注意事项14使用ZIP档案的音频脚本示例的数据集。

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


三、加载脚本


1、创建一个 dataset builder 类

GeneratorBasedBuilder is the base class for datasets generated from a dictionary generator.

Within this class, there are three methods to help create your dataset:

  • _info 存储你的数据集信息,如 description, license, and features.
  • _split_generators 下载数据集,定义 splits
  • _generate_examples generates the dataset’s samples containing the audio data and other features specified in info for each split. 产生数据样例,包含 audio 数据,和每个 split 其他在 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):

Multiple configurations

在某些情况下,一个数据集可能具有多个配置。

比如, 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 文件单独的导入语言,然后循环使用每种语言来创建配置。

release_stats.py : https://huggingface.co/datasets/indonesian-nlp/librivox-indonesia/blob/main/release_stats.py


class LibriVoxIndonesiaConfig(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")

2、添加数据集元数据

添加有关数据集的信息,有助于用户了解更多信息。

DatasetInfo : https://huggingface.co/docs/datasets/v2.11.0/en/package_reference/main_classes#datasets.DatasetInfo

此信息存储在 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 , 所以一定要查看完整的列表 和 features guide 的更多信息;


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,
    )

3、下载和定义数据集拆分

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

1、使用download() 方法来在 _PROMPTS_URLS 下载元数据文件,在 _DATA_URL 下载语音压缩包。

download() : https://huggingface.co/docs/datasets/v2.11.0/en/package_reference/builder_classes#datasets.DownloadManager.download

此方法返回本地文件/归档文件的路径。

在流模式下,它不下载文件,只返回一个URL来流式传输数据。此方法接受:

  • Hub数据集存储库中文件的相对路径(例如,data/ 文件夹中)
  • 托管在其他地方的文件的URL
  • 文件名或URL的(嵌套)列表或字典

2、你下载数据集后,使用 SplitGenerator 来组织每个split的音频文件和句子prompts。

SplitGenerator : https://huggingface.co/docs/datasets/v2.11.0/en/package_reference/builder_classes#datasets.SplitGenerator

命名每个 split 一个标准的名字,如 Split.TRAIN, Split.TEST, 和 SPLIT.Validation.

gen_kwargs 参数中, 用 prompts_path and path_to_clips 说明文件地址。

对于 audio_files, 你需要使用 iter_archive() 来遍历压缩文件中的语音文件。

这将为您的数据集启用流式传输。

所有这些文件路径都被传递到下一步,在那里您将实际生成数据集。


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() 方法,查看 (Advanced) Extract TAR archives 部分 :

https://huggingface.co/docs/datasets/audio_dataset#advanced-extract-tar-archives-locally



4、生成数据集

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()生成一个 (pathf) 元组,其中path是TAR归档中文件的相对路径,f是文件对象本身。

  • iter_archive() : https://huggingface.co/docs/datasets/v2.11.0/en/package_reference/builder_classes#datasets.DownloadManager.iter_archive
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

5、将数据集上载到 Hub

脚本准备好后, 创建数据集卡 并提交到 Hub

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

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

6、(Advanced) 本地提取压缩文件

在上面的示例中,下载的压缩文件没有被提取,因此示例不包含关于它们的本地存储信息。

为了解释如何以同样支持流的方式进行提取,我们将通过 LibriVox Indonesia 的加载脚本进行简要介绍。

LibriVox Indonesia : https://huggingface.co/datasets/indonesian-nlp/librivox-indonesia/blob/main/librivox-indonesia.py


1)下载并定义数据集拆分

1、使用 download() 方法来下载 _AUDIO_URL 的音频数据

2、要在本地提取音频要所文件,请使用 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_textract()extract()TAR存档,否则会引发错误。


4、使用 download_and_extract() 方法下载定义在_METADATA_URL 中的元数据文件。

此方法在非流模式下返回本地文件的路径。在流模式下,它不会在本地下载文件,而是返回相同的URL。

5、现在使用 SplitGenerator 来组织每个split 中的音频文件和元数据。

使用标准名称命名每个split ,如 : Split.TRAIN, Split.TEST, and SPLIT.Validation.

gen_kwargs 参数中, 用 prompts_path and path_to_clips 说明文件地址。

对于 audio_files, 你需要使用 iter_archive() 来遍历压缩文件中的语音文件。

这将为您的数据集启用流式传输。

所有这些文件路径都被传递到下一步,在那里您将实际生成数据集。


def _split_generators(self, dl_manager):
    """Returns SplitGenerators."""
    dl_manager.download_config.ignore_url_params = True

    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,
            },
        ),
    ]

2)生成数据集

这里_generate_examples 从之前的方法中接收local_extracted_archive, audio_files, metadata_path, 和path_to_clips 作为参数。

1、TAR文件是按顺序访问和生成的。

这意味着您需要首先掌握与TAR文件中的音频文件相关联的 metadata_path的元数据,以便您可以将其与相应的音频文件一起生成。

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() 方法, 将 yielded 一个 (path, f) 元组,当 path 被设置为包文件的相对路径, f 是文件本身。


3、获取本地解压文件的全路径,请连接存档提取到的目录的路径(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

2023-04-23(一)

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐