从零开始的语音分类

SpeechBrain训练话语级分类器

SpeechBrain:作为一个基于 PyTorch 的开源一体化语音工具包,SpeechBrain 可用于开发最新的语音技术,包括语音识别、说话者识别、语音增强、多麦克风信号处理和语音识别系统等,且拥有相当出色的性能。

Models:TDNN分类器,ECAPA-TDNN的最新模型

Data:mini-librispeech小型开源数据集

Code:参考speech_siqin/templates/speaker_id

一.准备数据

数据准备的目标是创建数据清单文件(.JSON,.CSV)。这些文件告诉SpeechBrain在哪里可以找到音频数据以及相应的话语级别分类。

打开jupyter的终端:

从零开始的语音分类

数据库导入:利用cp命令将文件复制到目标文件中

eg.

从零开始的语音分类

从零开始的语音分类

cd .. 返回上层目录

1.数据清单文件

从零开始的语音分类

python train.py train.yaml 生成数据清单文件(.JSON)

从零开始的语音分类

实际上,您可以在此处指定您需要的所有条目(语言 ID、情感注释等)。但是,这些条目的名称与实验脚本(例如 train.py)期望的名称之间必须匹配。

从零开始的语音分类

从零开始的语音分类

从零开始的语音分类

上图通过迭代次数的增加,培训损失逐渐减少。

2.准备脚本

每个数据集的格式都不同,后序操作需要我们更改train.py,train.yaml,mini_librispeech_prepare.py文件来解析自己的数据集并创建JSON或CSV文件(我们创建了三个独立的数据清单文件来管理培训、验证和测试阶段,train.json,test.json,valid.json)

二.训练分类器

上步我们已经准备好训练分类器。要基于TDNN+统计池(xvectors)训练说话人id分类器。

稍后将插入另一个称为强调通道注意力、传播和聚合模型 (ECAPA) 的模型,该模型在说话人识别任务中提供了令人印象深刻的性能。

建议的配方执行特征计算/规范化,使用编码器处理特征,并在此基础上应用分类器。数据扩充也被用来提高系统性能。

1.训练speaker-id模型

我们将训练用于x向量的基于TDNN的模型。在卷积层的顶部使用统计池将可变长度的句子转换为固定长度的嵌入。在嵌入的顶部,使用一个简单的全连接分类器来预测N个说话人中哪一个在给定的句子中是活跃的。

训练此模型,运行以下代码:

%cd templates/speaker_id

!python train.py train.yaml --number_of_epochs=15 #--device='cpu'

如果使用jupyter运行代码则须加!,执行命令用!,换路径用%,但如果直接终端运行则不需要,并且从终端打印中看出在第一个epochs验证和培训损失都在快速减少。然后,我们基本上看到了一些小的改进和性能波动。在培训结束时,验证误差应为零(或非常接近零)。

从零开始的语音分类

神经网络中的epoch,iteration,batchsize:

神经网络的神奇之处就在于权重是通过 训练自动得出的 所谓训练,就是让神经网络在 训练数据集上跑一遍,看看 损失函数的值怎么样。如果损失函数的值足够小,小到符合我们的需求,那就说明神经网络 拟合得很好了,训练大功告成了(实际上并非如此,因为仅仅知道拟合得很好并不够,我们还要知道神经网络是不是拟合得太好了,也就是 过拟合。所以还需要用 测试数据集来测试是否过拟合。不过这里就姑且就当训练完成了吧)。反之,如果损失函数的值不够小,那就还需要继续训练。

从零开始的语音分类

epoch:当一个完整的数据集通过了神经网络一次并且返回了一次,这个过程称为一次epoch。然而,当一个epoch对于计算机而言太庞大的时候,就需要把它分成多个小块。

从零开始的语音分类

Batchsize:决定我们一次训练的样本数目。在不能将数据一次性通过神经网络的时候,就需要将数据集分成几个batch。

iteration(迭代):是batch需要完成一个epoch的次数。

eg.有一个2000个训练样本的数据集。将2000个样本分成大小为500的batch,那么完成一个epoch需要4个iteration。定义10000次迭代为1个epoch,若每次迭代的batch_size设为256,那么1个epoch相当于过了2560000个训练样本。再举个栗子,训练集有1000个样本,batchsize=10,那么训练完整个样本集需要100次iteration,1次epoch。

因为我们只需对mini librispeech数据集中的28位发言者进行分类。以本教程为例,说明如何设置开发语音分类器所需的所有组件。

在深入代码之前,让我们看看在指定的output_folder:

从零开始的语音分类

从零开始的语音分类

检查点用最旧的准确率高

从零开始的语音分类

从零开始的语音分类

2. Hyperparameters 超参数

yaml文件包含实现所需分类器所需的所有模块和超参数。

train.yaml:

在第一部分中,我们指定了一些基本设置,例如seed和输出文件夹的路径:

Seed needs to be set at top of yaml, before objects with parameters are made
在创建带有参数的对象之前,需要在yaml的顶部设置种子
seed: 1986
__set_seed: !!python/object/apply:torch.manual_seed [!ref <seed>]

If you plan to train a system on an HPC cluster with a big dataset,
we strongly suggest doing the following:
1- Compress the dataset in a single tar or zip file.

2- Copy your dataset locally (i.e., the local disk of the computing node).

3- Uncompress the dataset in the local folder.

4- Set data_folder with the local path.

Reading data from the local disk of the compute node (e.g. $SLURM_TMPDIR with SLURM-based clusters) is very important.

It allows you to read the data much faster without slowing down the shared filesystem.

data_folder: ./data
output_folder: !ref ./results/speaker_id/<seed>
save_folder: !ref <output_folder>/save
train_log: !ref <output_folder>/train_log.txt</output_folder></output_folder></seed></seed>

然后,我们指定用于培训、验证和测试的数据清单文件的路径:

Path where data manifest files will be stored&#x5B58;&#x50A8;&#x6570;&#x636E;&#x6E05;&#x5355;&#x6587;&#x4EF6;&#x7684;&#x8DEF;&#x5F84;
The data manifest files are created by the data preparation script.

#&#x6570;&#x636E;&#x6E05;&#x5355;&#x6587;&#x4EF6;&#x7531;&#x6570;&#x636E;&#x51C6;&#x5907;&#x811A;&#x672C;&#x521B;&#x5EFA;&#x3002;
train_annotation: train.json
valid_annotation: valid.json
test_annotation: test.json

从实验文件(train.py)调用数据准备脚本(mini_librispeech_prepare.py)时,将自动创建这些文件。 接下来,我们设置train_logger并声明将收集分类错误率统计信息的error_stats对象:

The train logger writes training statistics to a file, as well as stdout.

#&#x8BAD;&#x7EC3;&#x8BB0;&#x5F55;&#x5668;&#x5C06;&#x8BAD;&#x7EC3;&#x7EDF;&#x8BA1;&#x6570;&#x636E;&#x4EE5;&#x53CA;&#x6807;&#x51C6;&#x8F93;&#x51FA;&#x5199;&#x5165;&#x6587;&#x4EF6;&#x3002;
train_logger: !new:speechbrain.utils.train_logger.FileTrainLogger
    save_file: !ref <train_log>

error_stats: !name:speechbrain.utils.metric_stats.MetricStats
    metric: !name:speechbrain.nnet.losses.classification_error
        reduction: batch</train_log>

现在,我们可以指定一些训练超参数,如epoch数、批量大小、学习率、epoch数和嵌入维数。

ckpt_interval_minutes: 15 # save checkpoint every N min

Feature parameters
n_mels: 23

Training Parameters
sample_rate: 16000
number_of_epochs: 35
batch_size: 16
lr_start: 0.001
lr_final: 0.0001
n_classes: 28 # In this case, we have 28 speakers
emb_dim: 512 # dimensionality of the embeddings
dataloader_options:
    batch_size: !ref <batch_size></batch_size>

变量ckpt_interval_minutes可用于在一个训练时段内每N分钟保存一次检查点。在某些情况下,一个epoch可能需要几个小时,定期保存检查点是一种良好且安全的做法。这个基于小数据集的简单教程实际上并不需要此功能。 现在,我们可以定义培训模型所需的最重要模块:

Added noise and reverb come from OpenRIR dataset, automatically downloaded and prepared with this Environmental Corruption class.

#&#x6DFB;&#x52A0;&#x7684;&#x566A;&#x58F0;&#x548C;&#x6DF7;&#x54CD;&#x6765;&#x81EA;OpenRIR&#x6570;&#x636E;&#x96C6;&#xFF0C;&#x81EA;&#x52A8;&#x4E0B;&#x8F7D;&#x5E76;&#x4F7F;&#x7528;&#x6B64;&#x73AF;&#x5883;&#x7834;&#x574F;&#x7C7B;&#x51C6;&#x5907;&#x3002;
env_corrupt: !new:speechbrain.lobes.augment.EnvCorrupt
    openrir_folder: !ref <data_folder>
    babble_prob: 0.0
    reverb_prob: 0.0
    noise_prob: 1.0
    noise_snr_low: 0
    noise_snr_high: 15

Adds speech change + time and frequency dropouts (time-domain implementation)
#&#x6DFB;&#x52A0;&#x8BED;&#x97F3;&#x66F4;&#x6539;+&#x65F6;&#x95F4;&#x548C;&#x9891;&#x7387;&#x4E22;&#x5931;&#xFF08;&#x65F6;&#x57DF;&#x5B9E;&#x73B0;&#xFF09;
# A small speed change help to improve the performance of speaker-id as well.

#&#x4E00;&#x4E2A;&#x5C0F;&#x7684;&#x901F;&#x5EA6;&#x53D8;&#x5316;&#x4E5F;&#x6709;&#x52A9;&#x4E8E;&#x63D0;&#x9AD8;&#x626C;&#x58F0;&#x5668;id&#x7684;&#x6027;&#x80FD;&#x3002;
augmentation: !new:speechbrain.lobes.augment.TimeDomainSpecAugment
    sample_rate: !ref <sample_rate>
    speeds: [95, 100, 105]

Feature extraction &#x7279;&#x5F81;&#x8BC6;&#x522B;
compute_features: !new:speechbrain.lobes.features.Fbank
    n_mels: !ref <n_mels>

Mean and std normalization of the input features &#x8F93;&#x5165;&#x7279;&#x5F81;&#x7684;&#x5747;&#x503C;&#x548C;&#x6807;&#x51C6;&#x5F52;&#x4E00;&#x5316;
mean_var_norm: !new:speechbrain.processing.features.InputNormalization
    norm_type: sentence
    std_norm: False

To design a custom model, either just edit the simple CustomModel class that's listed here, or replace this !new call with a line pointing to a different file you've defined.

#&#x8981;&#x8BBE;&#x8BA1;&#x81EA;&#x5B9A;&#x4E49;&#x6A21;&#x578B;&#xFF0C;&#x53EA;&#x9700;&#x7F16;&#x8F91;&#x6B64;&#x5904;&#x5217;&#x51FA;&#x7684;&#x7B80;&#x5355;CustomModel&#x7C7B;&#xFF0C;&#x6216;&#x8005;&#x66FF;&#x6362;&#x6B64;!new&#x8C03;&#x7528;&#xFF0C;
#&#x5176;&#x4E2D;&#x4E00;&#x884C;&#x6307;&#x5411;&#x60A8;&#x5B9A;&#x4E49;&#x7684;&#x5176;&#x4ED6;&#x6587;&#x4EF6;&#x3002;
embedding_model: !new:custom_model.Xvector
    in_channels: !ref <n_mels>
    activation: !name:torch.nn.LeakyReLU
    tdnn_blocks: 5
    tdnn_channels: [512, 512, 512, 512, 1500]
    tdnn_kernel_sizes: [5, 3, 3, 1, 1]
    tdnn_dilations: [1, 2, 3, 1, 1]
    lin_neurons: !ref <emb_dim>

classifier: !new:custom_model.Classifier
    input_shape: [null, null, !ref <emb_dim>]
    activation: !name:torch.nn.LeakyReLU
    lin_blocks: 1
    lin_neurons: !ref <emb_dim>
    out_neurons: !ref <n_classes>

The first object passed to the Brain class is this "Epoch Counter" which is saved by the Checkpointer so that training can be resumed if it gets interrupted at any point.

#&#x4F20;&#x9012;&#x7ED9;Brain&#x7C7B;&#x7684;&#x7B2C;&#x4E00;&#x4E2A;&#x5BF9;&#x8C61;&#x662F;&#x8FD9;&#x4E2A;&#x201C;epoch&#x8BA1;&#x6570;&#x5668;&#x201D;&#xFF0C;&#x5B83;&#x7531;&#x68C0;&#x67E5;&#x70B9;&#x4FDD;&#x5B58;&#xFF0C;&#x4EE5;&#x4FBF;&#x5728;&#x4EFB;&#x4F55;&#x65F6;&#x5019;&#x4E2D;&#x65AD;&#x8BAD;&#x7EC3;&#x65F6;&#x53EF;&#x4EE5;&#x6062;&#x590D;&#x8BAD;&#x7EC3;&#x3002;
epoch_counter: !new:speechbrain.utils.epoch_loop.EpochCounter
    limit: !ref <number_of_epochs>

Objects in "modules" dict will have their parameters moved to the correct &#x201C;modules&#x201D;dict device, as well as having train()/eval() called on them by the Brain class.

#&#x201C;modules&#x201D;dict&#x4E2D;&#x7684;&#x5BF9;&#x8C61;&#x5C06;&#x5176;&#x53C2;&#x6570;&#x79FB;&#x52A8;&#x5230;&#x6B63;&#x786E;&#x7684;&#x201C;modules&#x201D;dict&#x8BBE;&#x5907;&#x4E2D;&#xFF0C;&#x5E76;&#x7531;Brain&#x7C7B;&#x8C03;&#x7528;train&#xFF08;&#xFF09;/eval&#xFF08;&#xFF09;&#x3002;
modules:
    compute_features: !ref <compute_features>
    env_corrupt: !ref <env_corrupt>
    augmentation: !ref <augmentation>
    embedding_model: !ref <embedding_model>
    classifier: !ref <classifier>
    mean_var_norm: !ref <mean_var_norm></mean_var_norm></classifier></embedding_model></augmentation></env_corrupt></compute_features></number_of_epochs></n_classes></emb_dim></emb_dim></emb_dim></n_mels></n_mels></sample_rate></data_folder>

增强部分基于env_corrupt(增加噪声和混响)和增强(增加时间/频率衰减和速度变化)。有关这些模块的更多信息,请查看有关环境破坏和语音增强的教程。 我们通过优化器、学习速率调度器和检查点的声明来总结超参数规范:

This optimizer will be constructed by the Brain class after all parameters are moved to the correct device. Then it will be added to the checkpointer.

#&#x5C06;&#x6240;&#x6709;&#x53C2;&#x6570;&#x79FB;&#x52A8;&#x5230;&#x6B63;&#x786E;&#x7684;&#x8BBE;&#x5907;&#x540E;&#xFF0C;Brain&#x7C7B;&#x5C06;&#x6784;&#x9020;&#x6B64;&#x4F18;&#x5316;&#x5668;&#x3002;&#x7136;&#x540E;&#x5B83;&#x5C06;&#x88AB;&#x6DFB;&#x52A0;&#x5230;&#x68C0;&#x67E5;&#x70B9;&#x3002;
opt_class: !name:torch.optim.Adam
    lr: !ref <lr_start>

This function manages learning rate annealing over the epochs. &#x6B64;&#x51FD;&#x6570;&#x7BA1;&#x7406;&#x5404;&#x4E2A;epochs&#x7684;&#x5B66;&#x4E60;&#x901F;&#x7387;&#x3002;
We here use the simple lr annealing method that linearly decreases the lr from the initial value to the final one.

#&#x8FD9;&#x91CC;&#x6211;&#x4EEC;&#x4F7F;&#x7528;&#x7B80;&#x5355;&#x7684;lr&#x9000;&#x706B;&#x65B9;&#x6CD5;&#xFF0C;&#x5C06;lr&#x4ECE;&#x521D;&#x59CB;&#x503C;&#x7EBF;&#x6027;&#x964D;&#x4F4E;&#x5230;&#x6700;&#x7EC8;&#x503C;&#x3002;
lr_annealing: !new:speechbrain.nnet.schedulers.LinearScheduler
    initial_value: !ref <lr_start>
    final_value: !ref <lr_final>
    epoch_count: !ref <number_of_epochs>

This object is used for saving the state of training both so that it can be resumed if it gets interrupted, and also so that the best checkpoint can be later loaded for evaluation or inference.

#&#x6B64;&#x5BF9;&#x8C61;&#x7528;&#x4E8E;&#x4FDD;&#x5B58;&#x8BAD;&#x7EC3;&#x72B6;&#x6001;&#xFF0C;&#x4EE5;&#x4FBF;&#x5728;&#x8BAD;&#x7EC3;&#x4E2D;&#x65AD;&#x65F6;&#x53EF;&#x4EE5;&#x7EE7;&#x7EED;&#x8BAD;&#x7EC3;&#xFF0C;&#x4E5F;&#x53EF;&#x4EE5;&#x5728;&#x4EE5;&#x540E;&#x52A0;&#x8F7D;&#x6700;&#x4F73;&#x68C0;&#x67E5;&#x70B9;&#x8FDB;&#x884C;&#x8BC4;&#x4F30;&#x6216;&#x63A8;&#x65AD;&#x3002;
checkpointer: !new:speechbrain.utils.checkpoints.Checkpointer
    checkpoints_dir: !ref <save_folder>
    recoverables:
        embedding_model: !ref <embedding_model>
        classifier: !ref <classifier>
        normalizer: !ref <mean_var_norm>
        counter: !ref <epoch_counter></epoch_counter></mean_var_norm></classifier></embedding_model></save_folder></number_of_epochs></lr_final></lr_start></lr_start>

在这种情况下,我们使用Adam作为优化器,线性学习率在15个epochs内衰减。 现在,让我们将最佳模型保存到一个单独的文件夹中(对于后面解释的推理部分很有用):(建在speech_siqin/templates/speaker_id/best_model)

jupyter:

#create folder for best model

!mkdir best_model/

#copy label encoder

!cp results/speaker_id/1986/save/label_encoder.txt best_model/

#copy best model

!cp "ls -td results/speaker_id/1986/save/CKPT* | tail -l"/*best_model

&#x7EC8;&#x7AEF;&#xFF1A;

cd speech_siqin/templates/speaker_id

mkdir best_model

cp -r results/speaker_id/1986/save/label_encoder.txt best_model

cp "ls -td results/speaker_id/1986/save/CKPT* | tail -l"/* best_model

从零开始的语音分类

3.Experiment file 实验文件

让我们来看看yaml文件中声明的对象、函数和超参数是如何在”train.py”中用于实现分类器的。

train.py:

Recipe begins!

if __name__ == "__main__":

    # Reading command line arguments. &#x8BFB;&#x53D6;&#x547D;&#x4EE4;&#x884C;&#x53C2;&#x6570;&#x3002;
    hparams_file, run_opts, overrides = sb.parse_arguments(sys.argv[1:])

    # Initialize ddp (useful only for multi-GPU DDP training).

    #&#x521D;&#x59CB;&#x5316;ddp&#xFF08;&#x4EC5;&#x9002;&#x7528;&#x4E8E;&#x591A;GPU ddp&#x57F9;&#x8BAD;&#xFF09;
    sb.utils.distributed.ddp_init_group(run_opts)

    # Load hyperparameters file with command-line overrides. &#x52A0;&#x8F7D;&#x5E26;&#x6709;&#x547D;&#x4EE4;&#x884C;&#x66FF;&#x4EE3;&#x7684;&#x8D85;&#x53C2;&#x6570;&#x6587;&#x4EF6;&#x3002;
    with open(hparams_file) as fin:
        hparams = load_hyperpyyaml(fin, overrides)

    # Create experiment directory &#x521B;&#x5EFA;&#x5B9E;&#x9A8C;&#x76EE;&#x5F55;
    sb.create_experiment_directory(
        experiment_directory=hparams["output_folder"],
        hyperparams_to_save=hparams_file,
        overrides=overrides,
    )

    # Data preparation, to be run on only one process. &#x6570;&#x636E;&#x51C6;&#x5907;&#xFF0C;&#x4EC5;&#x5728;&#x4E00;&#x4E2A;&#x8FDB;&#x7A0B;&#x4E0A;&#x8FD0;&#x884C;
    sb.utils.distributed.run_on_main(
        prepare_mini_librispeech,
        kwargs={
            "data_folder": hparams["data_folder"],
            "save_json_train": hparams["train_annotation"],
            "save_json_valid": hparams["valid_annotation"],
            "save_json_test": hparams["test_annotation"],
            "split_ratio": [80, 10, 10],
        },
    )

我们在这里做一些初步的操作,比如解析命令行,初始化分布式数据并行(如果使用多个GPU需要),创建输出文件夹,读取yaml文件。

使用 读取 yaml 文件后加载hyperpyyaml,超参数文件中声明的所有对象都将被初始化并以字典形式提供(以及 yaml 文件中报告的其他函数和参数)。举例来说,我们将有hparams[’embedding_model’],hparams[‘classifier’],hparams[‘batch_size’],等。

我们还运行prepare_mini_librispeech创建数据清单文件的数据准备脚本。它被包裹是sb.utils.distributed.run_on_main因为此操作将清单文件写入磁盘,即使在多 GPU DDP 场景中,这也必须在单个进程上完成。

Data-IO Pipeline 数据IO管道(都在train.py中)

我们调用一个特殊函数来创建用于训练、验证和测试的数据集对象。

Create dataset objects "train", "valid", and "test".

    datasets = dataio_prep(hparams)

详细看看dataio_prep():

def dataio_prep(hparams):
    """This function prepares the datasets to be used in the brain class.

    It also defines the data processing pipeline through user-defined functions.

    We expect prepare_mini_librispeech to have been called before this,
    so that the train.json, valid.json,  and valid.json manifest files
    are available.

    &#x6B64;&#x51FD;&#x6570;&#x7528;&#x4E8E;&#x51C6;&#x5907;&#x5728;brain&#x7C7B;&#x4E2D;&#x4F7F;&#x7528;&#x7684;&#x6570;&#x636E;&#x96C6;&#x3002;&#x5B83;&#x8FD8;&#x901A;&#x8FC7;&#x7528;&#x6237;&#x5B9A;&#x4E49;&#x7684;&#x51FD;&#x6570;&#x5B9A;&#x4E49;&#x6570;&#x636E;&#x5904;&#x7406;&#x7BA1;&#x9053;&#x3002;
    &#x6211;&#x4EEC;&#x9884;&#x8BA1;&#x5728;&#x8FD9;&#x4E4B;&#x524D;&#x4F1A;&#x8C03;&#x7528;&#x201C;prepare_mini_librispeech&#x201D;&#xFF0C;train.json&#x3001;valid.json&#x548C;valid.json&#x6E05;&#x5355;&#x6587;&#x4EF6;&#x90FD;&#x6709;&#x3002;
    Arguments
    ---------
    hparams : dict
        This dictionary is loaded from the train.yaml file, and it includes
        all the hyperparameters needed for dataset construction and loading.

        &#x6B64;&#x8BCD;&#x5178;&#x662F;&#x4ECE;'train.yaml'&#x6587;&#x4EF6;&#x52A0;&#x8F7D;&#x7684;&#xFF0C;&#x5B83;&#x5305;&#x62EC;&#x6570;&#x636E;&#x96C6;&#x6784;&#x9020;&#x548C;&#x52A0;&#x8F7D;&#x6240;&#x9700;&#x7684;&#x6240;&#x6709;&#x8D85;&#x53C2;&#x6570;&#x3002;
    Returns
    -------
    datasets : dict
        Contains two keys, "train" and "valid" that correspond
        to the appropriate DynamicItemDataset object.

        &#x5305;&#x542B;&#x4E24;&#x4E2A;&#x5BF9;&#x5E94;&#x7684;&#x952E;&#x201C;train&#x201D;&#x548C;&#x201C;valid&#x201D;&#x5230;&#x76F8;&#x5E94;&#x7684;DynamicItemDataset&#x5BF9;&#x8C61;&#x3002;
"""

    # Initialization of the label encoder. &#x6807;&#x7B7E;&#x7F16;&#x7801;&#x5668;&#x7684;&#x521D;&#x59CB;&#x5316;&#x3002;
    #The label encoder assignes to each of the observed label a unique index (e.g, 'spk01': 0, 'spk02': 1, ..)
    #&#x6807;&#x7B7E;&#x7F16;&#x7801;&#x5668;&#x4E3A;&#x6BCF;&#x4E2A;&#x89C2;&#x5BDF;&#x5230;&#x7684;&#x6807;&#x7B7E;&#x5206;&#x914D;&#x4E00;&#x4E2A;&#x552F;&#x4E00;&#x7684;&#x7D22;&#x5F15;&#xFF08;&#x4F8B;&#x5982;&#xFF0C;&#x201C;spk01&#x201D;&#xFF1A;0&#xFF0C;&#x201C;spk02&#x201D;&#xFF1A;1&#xFF0C;&#x2026;&#xFF09;
    label_encoder = sb.dataio.encoder.CategoricalEncoder()
    #&#x201C;CategoricalEncoder&#x201D;&#x7684;&#x58F0;&#x660E;&#xFF0C;&#x5B83;&#x5C06;&#x7528;&#x4E8E;&#x5C06;&#x5206;&#x7C7B;&#x6807;&#x7B7E;&#x8F6C;&#x6362;&#x4E3A;&#x76F8;&#x5E94;&#x7684;&#x7D22;&#x5F15;&#x3002;

    # Define audio pipeline &#x5B9A;&#x4E49;&#x97F3;&#x9891;&#x7BA1;&#x9053;
    @sb.utils.data_pipeline.takes("wav")
    @sb.utils.data_pipeline.provides("sig")
    #'audio_pipeline'&#x83B7;&#x53D6;&#x97F3;&#x9891;&#x4FE1;&#x53F7;&#x7684;&#x8DEF;&#x5F84;&#xFF08;'wav'&#xFF09;&#x5E76;&#x8BFB;&#x53D6;&#x5B83;&#x3002;&#x5B83;&#x8FD4;&#x56DE;&#x4E00;&#x4E2A;&#x5305;&#x542B;&#x5DF2;&#x8BFB;&#x8BED;&#x97F3;&#x53E5;&#x5B50;&#x7684;&#x5F20;&#x91CF;&#x3002;
    #&#x6B64;&#x51FD;&#x6570;&#x8F93;&#x5165;&#x4E2D;&#x7684;&#x6761;&#x76EE;&#xFF08;&#x5373;&#xFF0C;wav&#xFF09;&#x5FC5;&#x987B;&#x4E0E;&#x6570;&#x636E;&#x6E05;&#x5355;&#x6587;&#x4EF6;&#x4E2D;&#x7684;&#x76F8;&#x5E94;&#x5BC6;&#x94A5;&#x540C;&#x540D;
    def audio_pipeline(wav):
        """Load the signal, and pass it and its length to the corruption class.

        This is done on the CPU in the collate_fn."""
        #&#x52A0;&#x8F7D;&#x4FE1;&#x53F7;&#xFF0C;&#x5E76;&#x5C06;&#x5176;&#x53CA;&#x5176;&#x957F;&#x5EA6;&#x4F20;&#x9012;&#x7ED9;&#x635F;&#x574F;&#x7C7B;&#x3002;&#x8FD9;&#x662F;&#x5728;&#x201C;collate_fn&#x201D;&#x4E2D;&#x7684;CPU&#x4E0A;&#x5B8C;&#x6210;&#x7684;
        sig = sb.dataio.dataio.read_audio(wav)
        return sig

    # Define label pipeline: &#x5B9A;&#x4E49;&#x6807;&#x7B7E;&#x7BA1;&#x9053;&#xFF1A;
    @sb.utils.data_pipeline.takes("spk_id")
    @sb.utils.data_pipeline.provides("spk_id", "spk_id_encoded")
    def label_pipeline(spk_id):
        #&#x540D;&#x4E3A;&#x201C;label_pipeline&#x201D;&#x7684;&#x51FD;&#x6570;&#x6765;&#x5904;&#x7406;&#x8BDD;&#x8BED;&#x7EA7;&#x522B;&#x7684;&#x6807;&#x7B7E;&#xFF0C;&#x5E76;&#x5C06;&#x5B83;&#x4EEC;&#x653E;&#x5728;&#x5B9A;&#x4E49;&#x7684;&#x6A21;&#x578B;&#x53EF;&#x7528;&#x7684;&#x683C;&#x5F0F;&#x4E2D;&#x3002;
        #&#x8BE5;&#x51FD;&#x6570;&#x8BFB;&#x53D6;JSON&#x6587;&#x4EF6;&#x4E2D;&#x5B9A;&#x4E49;&#x7684;&#x5B57;&#x7B26;&#x4E32;&#x201C;spk_id&#x201D;&#xFF0C;&#x5E76;&#x4F7F;&#x7528;&#x5206;&#x7C7B;&#x7F16;&#x7801;&#x5668;&#x5BF9;&#x5176;&#x8FDB;&#x884C;&#x7F16;&#x7801;&#x3002;
        yield spk_id
        spk_id_encoded = label_encoder.encode_label_torch(spk_id)
        yield spk_id_encoded

    # Define datasets. We also connect the dataset with the data processing functions defined above.

    #&#x5B9A;&#x4E49;&#x6570;&#x636E;&#x96C6;&#x3002;&#x521B;&#x5EFA;&#x201C;DynamicItemDataset&#x201D;,&#x5C06;&#x6570;&#x636E;&#x96C6;&#x4E0E;&#x4E0A;&#x9762;&#x5B9A;&#x4E49;&#x7684;&#x6570;&#x636E;&#x5904;&#x7406;&#x51FD;&#x6570;&#x8FDE;&#x63A5;&#x8D77;&#x6765;&#x3002;
    datasets = {}
    hparams["dataloader_options"]["shuffle"] = False
    for dataset in ["train", "valid", "test"]:
        datasets[dataset] = sb.dataio.dataset.DynamicItemDataset.from_json(
            json_path=hparams[f"{dataset}_annotation"],
            replacements={"data_root": hparams["data_folder"]},
            dynamic_items=[audio_pipeline, label_pipeline],
            output_keys=["id", "sig", "spk_id_encoded"],
        )

    #&#x51FD;&#x6570;&#x7684;&#x6700;&#x540E;&#x4E00;&#x90E8;&#x5206;&#x4E13;&#x7528;&#x4E8E;&#x6807;&#x7B7E;&#x7F16;&#x7801;&#x5668;&#x7684;&#x521D;&#x59CB;&#x5316;&#x3002;
    #&#x6807;&#x7B7E;&#x7F16;&#x7801;&#x5668;&#x63A5;&#x6536;&#x8BAD;&#x7EC3;&#x6570;&#x636E;&#x96C6;&#x7684;&#x8F93;&#x5165;,&#x5E76;&#x4E3A;&#x6240;&#x6709;&#x5EFA;&#x7ACB;&#x7684;&#x201C;spk_id&#x201D;&#x6761;&#x76EE;&#x5206;&#x914D;&#x4E0D;&#x540C;&#x7684;&#x7D22;&#x5F15;&#x3002;
    #&#x8FD9;&#x4E9B;&#x7D22;&#x5F15;&#x5C06;&#x4E0E;&#x5206;&#x7C7B;&#x5668;&#x7684;&#x8F93;&#x51FA;&#x7D22;&#x5F15;&#x76F8;&#x5BF9;&#x5E94;&#x3002;
    # Load or compute the label encoder (with multi-GPU DDP support) Please, take a look into the lab_enc_file to see the label to index mappinng.

    #&#x52A0;&#x8F7D;&#x6216;&#x8BA1;&#x7B97;&#x6807;&#x7B7E;&#x7F16;&#x7801;&#x5668;&#xFF08;&#x652F;&#x6301;&#x591A;GPU DDP&#xFF09;&#x8BF7;&#x67E5;&#x770B;lab_enc_&#x6587;&#x4EF6;&#x4EE5;&#x67E5;&#x770B;&#x6807;&#x7B7E;&#x7D22;&#x5F15;&#x6620;&#x5C04;&#x3002;
    lab_enc_file = os.path.join(hparams["save_folder"], "label_encoder.txt")
    label_encoder.load_or_create(
        path=lab_enc_file,
        from_didatasets=[datasets["train"]],
        output_key="spk_id",
    )

    return datasets

‘audio_pipeline’获取音频信号的路径(’wav’)并读取它。它返回一个包含已读语音句子的张量。此函数输入中的条目(即,wav)必须与数据清单文件中的相应密钥同名:

从零开始的语音分类

类似地,我们定义了另一个名为”label_pipeline”的函数来处理话语级别的标签,并将它们放在定义的模型可用的格式中。该函数读取JSON文件中定义的字符串”spk_id”,并使用分类编码器对其进行编码。

然后,我们创建”DynamicItemDataset”,并将其与上面定义的处理函数连接起来。我们定义要公开的所需输出键。这些键将在批处理变量中的brain类中可用,如下所示:

从零开始的语音分类

定义数据集后,main函数就可以继续初始化和使用brain类:(依旧在train.py主函数中)

Initialize the Brain object to prepare for mask training. &#x521D;&#x59CB;&#x5316;Brain&#x5BF9;&#x8C61;&#x4EE5;&#x51C6;&#x5907;&#x63A9;&#x85CF;&#x8BAD;&#x7EC3;&#x3002;
    spk_id_brain = SpkIdBrain(
        modules=hparams["modules"],
        opt_class=hparams["opt_class"],
        hparams=hparams,
        run_opts=run_opts,
        checkpointer=hparams["checkpointer"],
    )

    # The fit() method iterates the training loop, calling the methods 'fit&#xFF08;&#xFF09; necessary to update the parameters of the model.

    #'fit()&#x65B9;&#x6CD5;&#x8FED;&#x4EE3;&#x8BAD;&#x7EC3;&#x5FAA;&#x73AF;&#xFF0C;&#x8C03;&#x7528;&#x66F4;&#x65B0;&#x6A21;&#x578B;&#x53C2;&#x6570;&#x6240;&#x9700;&#x7684;'fit&#xFF08;&#xFF09;方法。
    #Since all objects with changing state are managed by the Checkpointer, training can be stopped at any point, and will be resumed on next call.

    #所有状态不断变化的对象都由检查点管理,因此可以在任何时候停止训练,并在下次调用时恢复训练。
    spk_id_brain.fit(
        epoch_counter=spk_id_brain.hparams.epoch_counter,
        train_set=datasets["train"],
        valid_set=datasets["valid"],
        train_loader_kwargs=hparams["dataloader_options"],
        valid_loader_kwargs=hparams["dataloader_options"],
    )

    # Load the best checkpoint for evaluation 加载用于评估的最佳检查点
    test_stats = spk_id_brain.evaluate(
        test_set=datasets["test"],
        min_key="error",
        test_loader_kwargs=hparams["dataloader_options"],
    )
</code></pre>
<p>"fit"方法执行训练,而测试使用"evaluate"方法执行。训练和验证数据加载器作为fit方法的输入,而测试数据集则输入评估方法。</p>
<p>现在让我们来看看brain类中定义的最重要的方法:</p>
<p><strong>Forward Computations</strong> 前向计算</p>
<p>让我们从"forward"函数开始,它定义了将输入音频转换为输出预测所需的所有计算。(train.py)</p>
<pre><code>def compute_forward(self, batch, stage):
        """Runs all the computation of that transforms the input into the output probabilities over the N classes.

        运行将输入转换为N个类上的输出概率的所有计算。
        Arguments 参数
        ---------
        batch : PaddedBatch 填充批次
            This batch object contains all the relevant tensors for computation.

            此批处理对象包含用于计算的所有相关张量。
        stage : sb.Stage
            One of sb.Stage.TRAIN, sb.Stage.VALID, or sb.Stage.TEST.

        Returns
        -------
        predictions : Tensor
            Tensor that contains the posterior probabilities over the N classes.

            包含N类上的后验概率的张量。
"""

        # We first move the batch to the appropriate device. 我们首先将批次移动到适当的设备。
        batch = batch.to(self.device)

        # Compute features, embeddings, and predictions 计算特征、嵌入和预测
        feats, lens = self.prepare_features(batch.sig, stage)
        embeddings = self.modules.embedding_model(feats, lens)
        predictions = self.modules.classifier(embeddings)

        return predictions
</code></pre>
<p>在这种情况下,计算链非常简单。我们只需将批次放在正确的设备上,并计算声学特征。然后,我们使用TDNN编码器处理特征,该编码器输出固定大小的张量。后者提供一个分类器,该分类器输出N个类(在本例中为28个说话人)的后验概率。</p>
<p>在prepare_features方法中添加了数据扩充:</p>
<pre><code>def prepare_features(self, wavs, stage):
        """Prepare the features for computation, including augmentation.

        为计算准备特征,包括增强。
        Arguments
        ---------
        wavs : tuple 元组,数组
            Input signals (tensor) and their relative lengths (tensor).

            输入信号(张量)及其相对长度(张量)。
        stage : sb.Stage
            The current stage of training. 目前的培训阶段。
"""
        wavs, lens = wavs

        # Add augmentation if specified. In this version of augmentation, we concatenate the original and the augment batches in a single bigger batch.

        #如果指定,则添加扩充。在这个版本的扩充中,我们将原始批和扩充批连接在一个更大的批中。
        #This is more memory-demanding, but helps to improve the performance. Change it if you run OOM.

        #这对内存要求更高,但有助于提高性能。如果运行OOM,请更改它。
        if stage == sb.Stage.TRAIN:
            if hasattr(self.modules, "env_corrupt"):
                wavs_noise = self.modules.env_corrupt(wavs, lens)
                wavs = torch.cat([wavs, wavs_noise], dim=0)
                lens = torch.cat([lens, lens])

            if hasattr(self.hparams, "augmentation"):
                wavs = self.hparams.augmentation(wavs, lens)

        # Feature extraction and normalization 特征提取与归一化
        feats = self.modules.compute_features(wavs)
        feats = self.modules.mean_var_norm(feats, lens)

        return feats, lens
</code></pre>
<p>当在yaml文件中声明环境损坏时,我们在同一批中连接信号的干净版本和增强版本。 这种方法使批量大小(以及所需的 GPU 内存)加倍,但它实现了一个非常 <strong>强大的正则化器</strong>。在同一批次中,信号的干净版本和噪声版本迫使梯度指向参数空间的方向,该方向 <strong>对信号失真</strong>具有鲁棒性。(鲁棒性:亦称健壮性,在异常和危险情况下系统生存的关键,指系统在一定(结构,大小)的参数摄动下,维持某些性能的特性。)</p>
<p><strong>Compute Objectives</strong> <em>计算目标</em></p>
<p>现在让我们来看看"compute objectives"方法,它输入目标、预测和估计损失函数:</p>
<pre><code>def compute_objectives(self, predictions, batch, stage):
        """Computes the loss given the predicted and targeted outputs.

        计算给定预测和目标输出的损失。
        Arguments
        ---------
        predictions : tensor
            The output tensor from compute_forward. “前向计算”的输出张量。
        batch : PaddedBatch
            This batch object contains all the relevant tensors for computation.

            此批处理对象包含用于计算的所有相关张量。
        stage : sb.Stage
            One of sb.Stage.TRAIN, sb.Stage.VALID, or sb.Stage.TEST.

        Returns
        -------
        loss : torch.Tensor
            A one-element tensor used for backpropagating the gradient. 用于反向传播梯度的单元素张量。
"""

        _, lens = batch.sig
        spkid, _ = batch.spk_id_encoded

        # Concatenate labels (due to data augmentation) 连接标签(由于数据增加)
        if stage == sb.Stage.TRAIN and hasattr(self.modules, "env_corrupt"):
            spkid = torch.cat([spkid, spkid], dim=0)
            lens = torch.cat([lens, lens])

        # Compute the cost function 计算成本函数
        loss = sb.nnet.losses.nll_loss(predictions, spkid, lens)

        # Append this batch of losses to the loss metric for easy 便于将这批损失附加到损失度量中
        self.loss_metric.append(
            batch.id, predictions, spkid, lens, reduction="batch"
        )

        # Compute classification error at test time 在测试时计算分类错误
        if stage != sb.Stage.TRAIN:
            self.error_metrics.append(batch.id, predictions, spkid, lens)

        return loss
</code></pre>
<p>输入中的预测是在前向方法中计算的。通过将这些预测与目标标签进行比较来评估成本函数。这是通过负对数似然 (NLL) 损失完成的。</p>
<p>似然性:用于根据一些观测结果,估计给定模型的参数可能值。</p>
<p>概率:用于在已知参数的情况下,预测接下来的观测结果。</p>
<p>在"模型已定,参数未知"的情况下,使用最大似然估计算法学习参数是比较普遍的。</p>
<p><strong>Other methods</strong></p>
<p>除了这两个重要的函数之外,我们还有一些brain类使用的其他方法。在on_state_starts被称为在每个epoch的开始,它是用来设置统计跟踪。在on_stage_end一个被称为在每个阶段结束时(例如,在每个训练epoch的结束)和主要以统计管理的护理,学习率退火,和检查点。</p>
<p>张量(tensor)=容器:将标量视为零阶张量,矢量视为一阶张量,矩阵二阶张量,3维张量=时间序列,4维=图像,5维=视频。</p>
<h2>三.使用分类器(推理)</h2>
<p>我们可以使用经过训练的分类器对新数据进行预测。Speechbrain提供了一些类,比如EncoderClassifier类,可以简化推理,该类还可用于提取编码器输出处的一些嵌入。SpeakerRecognition用于简化说话人验证任务的推理。</p>
<p>首先让我们看看如何使用它加载我们最好的xvector模型(在VoxEleb上训练并存储在HuggingFace上),以计算一些嵌入并执行说话人分类:</p>
<h2>pycharm训练模型(方便debug):</h2>
<p>准备:(以下5个文件)</p>
<p><img alt="从零开始的语音分类" src="https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230619/caf1d79913dbd76f25ca29aab68fa002.png" /></p>
<p><a href="https://zhuanlan.zhihu.com/p/94744929" title="用conda创建python虚拟环境">用conda创建python虚拟环境</a>(但我没有成功激活,最后用pycharm创建的)</p>
<p>pycharm终端命令运行:</p>
<p><code>python train.py train.yaml</code></p>
<p>(没有成功,又尝试)</p>
<p><code>python train.py train.yaml device=cpu</code></p>
<h3>bug1:后端没有可用的音频文件</h3>
<p>问题描述:</p>
<p><img alt="从零开始的语音分类" src="https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230619/b1bccb72ad137bc3cc63d3ed8380c9d8.png" /></p>
<p>解决:需要在torchaudio中安装soundfile,打开pycharm终端,在speaker_id虚拟环境下:</p>
<pre><code>pip install soundfile
</code></pre>
<h3>bug2:utf-8编码器无法解码</h3>
<p>问题描述:UnicodeDecodeError:'utf-8' code can not decode byte 0xb3 in position</p>
<p>解决:找到使用utf-8编码的位置,将其改成gbk,这类问题都是文件编码问题,至于文件到底是什么类型的编码,谁都不知道,只能用最常用的编码格式一个去试一下,若还不行可尝试gb18030</p>
<h3>bug3:torch._C没有属性—_cuda_setDevice</h3>
<p>问题描述:AttributeError:module 'torch._C' has no attribute '_cuda_setDevice'</p>
<p>解决:没有配置cuda,当然就没有此属性了</p>
<p>终端(torch和pytorch冲突,先卸载)</p>
<pre><code>conda uninstall pytorch

pip uninstall torch

pip uninstall torch(两次)

conda install pytorch torchvision torchaudio cudatoolkit=10.2 -c pytorch
</code></pre>
<h2>四.更改数据集</h2>
<h3>train.yaml</h3>
<p>更改数据来源,及输出结果文件</p>
<p><img alt="从零开始的语音分类" src="https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230619/d1cc5baa16c25984508140a1ae335672.png" /></p>
<h3>train.py</h3>
<p>将一些spkid改为langid,及为4种语言标签名分类</p>
<p><img alt="从零开始的语音分类" src="https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230619/359e6ae3242e528e4b374879f1bb5bd4.png" /></p>
<h3>mini_librispeech_prepare.py</h3>
<p>将训练集中的数据复制到自己的data文件中,每种语言的导入方式差不多(30行参数可能不一样,需要查看样本量多大)(我把数据集放到了01data中,后来又用终端放到data中)</p>
<p>中文数据集导入:</p>
<pre><code>import torch
from glob import glob
from tqdm import tqdm
from random import shuffle, sample, randint, choices
from speechbrain.dataio.dataio import read_audio, write_audio
from speechbrain.lobes.augment import TimeDomainSpecAugment
from speechbrain.lobes.augment import EnvCorrupt
from functools import reduce
import torchaudio
import numpy as np

MN_PATH = ['/data02/corpus/speech_corpus/mongolian_wav/*.wav',
           '/data02/corpus/speech_corpus/蒙古语电话录音/*/*/*.wav']
EN_PATH = ['/data02/corpus/speech_corpus/TIMIT/TIMIT-wav/*/*/*/*.WAV']
CN_PATH = ['/data02/corpus/speech_corpus/863/*/*/*/*.WAV']
NO_PATH = ['/home/ZhangHui/NewStudent/course-v3/zh-nbs/data/RIRS_NOISES/*/*.wav']
do_time_augment = TimeDomainSpecAugment(speeds=[80, 110, 120],
                                       perturb_prob=1.0,
                                       drop_freq_prob=1.0,
                                       drop_chunk_prob=1.0,
                                       drop_chunk_length_low=1000,
                                       drop_chunk_length_high=3000)

corrupter = EnvCorrupt(openrir_folder='/home/ZhangHui/NewStudent/course-v3/zh-nbs/data')

corrupt_wav = lambda x: do_time_augment(x, torch.ones(1))
corrupt_noise = lambda x: corrupter(x, torch.ones(1))

mn_data = reduce(lambda x,y: x+y, [glob(x) for x in MN_PATH])
en_data = reduce(lambda x,y: x+y, [glob(x) for x in EN_PATH])
cn_data = reduce(lambda x,y: x+y, [glob(x) for x in CN_PATH])
no_data = reduce(lambda x,y: x+y, [glob(x) for x in NO_PATH])
data = sample(cn_data, 6000)
label = 'C'*6000

pack = list(zip(data, label))
shuffle(pack)

lab = ''
for i, p in tqdm(enumerate(pack)):
    w, l = p
    lab += l
    clean = read_audio(w)
    clean = clean / clean.max()
    clean = clean.squeeze().unsqueeze(0)
    pipe = [corrupt_noise, corrupt_wav]
    pipe = choices(pipe, k=randint(0, 3))
    for f in pipe:
        clean = f(clean)

    clean = clean.squeeze()
    if len(clean)<32000: continue if len(clean)>80000:
        clean = clean[:80000]
    print(len(clean))
 #   clean = clean[randint(0, 3000):]
   clean = clean[:randint(32000, 60000)]
   print(len(clean))
    write_audio('/home/ZhangHui/NewStudent/course-v3/zh-nbs/speech_siqin/templates/speaker_id/01data/testlanguage/C/C%d.wav'%i, clean.squeeze(), 16000);</32000:>
</code></pre>
<p>未知数据集导入</p>
<pre><code>import torch
from glob import glob
from tqdm import tqdm
from random import shuffle, sample, randint, choices
from speechbrain.dataio.dataio import read_audio, write_audio
from speechbrain.lobes.augment import TimeDomainSpecAugment
from speechbrain.lobes.augment import EnvCorrupt
from functools import reduce
import torchaudio
import numpy as np
import random
import soundfile as sf

EPSILON = 1e-7
def mix2signal(sig1, sig2, snr):
    alpha = np.sqrt((np.sum(sig1 ** 2) / (np.sum(sig2 ** 2) + EPSILON)) / 10.0 ** (snr / 10.0))
    return alpha

MN_PATH = ['/data02/corpus/speech_corpus/mongolian_wav/*.wav']
EN_PATH = ['/data02/corpus/speech_corpus/TIMIT/TIMIT-wav/*/*/*/*.WAV']
CN_PATH = ['/data02/corpus/speech_corpus/863/*/*/*/*.WAV']
NO_PATH = ['/home/ZhangHui/NewStudent/course-v3/zh-nbs/data/RIRS_NOISES/*/*.wav']

mn_data = reduce(lambda x,y: x+y, [glob(x) for x in MN_PATH])
en_data = reduce(lambda x,y: x+y, [glob(x) for x in EN_PATH])
cn_data = reduce(lambda x,y: x+y, [glob(x) for x in CN_PATH])
no_data = reduce(lambda x,y: x+y, [glob(x) for x in NO_PATH])
noidata=sample(no_data,1200)
label = '0'*1200
print(len(no_data))
#具体数值是1200还是6000,需要看其样本大小
pack = list(zip(noidata, label))
for i,p in tqdm(enumerate(pack)):
    w, l = p
    clean, _ = sf.read(w)
    if(len(clean.shape)!=1):
            continue
    if len(clean)<32000: continue if len(clean)>64000:
        clean = clean[:64000]
    sf.write('/home/ZhangHui/NewStudent/course-v3/zh-nbs/speech_siqin/templates/speaker_id/01data/testlanguage/0/0%d.wav'%i,clean, 16000)</32000:>
</code></pre>
<p>将数据集改为压缩包的形式</p>
<p><img alt="从零开始的语音分类" src="https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230619/eeb69b9c65f6a7ba9353ec4ec90716e2.png" /></p>
<p>具体压缩方法,打开终端,切换至data目录下</p>
<pre><code>zip -r testlanguage.zip LibriSpeech/testlanguage
</code></pre>
<p><img alt="从零开始的语音分类" src="https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230619/37e42d37363ed52ed99cff2c1aa03f59.png" /></p>
<p><img alt="从零开始的语音分类" src="https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230619/680d15dfa1605a40e27ca1ccd3fe1d6a.png" /></p>
<p><img alt="从零开始的语音分类" src="https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230619/4de5652f9275b83d3b5ceeb18a053b31.png" /></p>
<p><img alt="从零开始的语音分类" src="https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230619/57040c8e5e948511e0a6bc24a185ad61.png" /></p>
<h3>跑模型</h3>
<pre><code>python train.py train.yaml
</code></pre>
<p><img alt="从零开始的语音分类" src="https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230619/d94a5cd055eb8db39663a1718450d7be.png" /></p>
<p><img alt="从零开始的语音分类" src="https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230619/91012a9e75b621ca3c6d7803c06e0e7e.png" /></p>
<pre><code>cp -r newresults/language_id/1986/save/label_encoder.txt newbest_model
</code></pre>
<p><img alt="从零开始的语音分类" src="https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230619/d30182fc331b3e8910c38819bfe4327a.png" /></p>
<p>这里的检查点CKPT用最旧的</p>
<p>!nvidia-smi查看gpu使用情况</p>
<p><img alt="从零开始的语音分类" src="https://johngo-pic.oss-cn-beijing.aliyuncs.com/articles/20230619/f431bc5b91c0a1fc9f11f9e20b05f519.png" /></p>
<h2>五.推断结果</h2>
<h3>hparams_inference.yaml</h3>
<p>此时,我们可以使用经过训练的分类器对新数据进行预测。Speechbrain提供了一些类,将这些类(接口)用于之前训练过的模型,我们必须创建一个<strong>推断yaml</strong>文件,该文件与用于训练的文件稍有不同。主要区别如下:</p>
<p>1.您可以仅删除培训所需的所有超参数和对象。您可以只保留与模型定义相关的零件。</p>
<p>2.您必须分配一个"分类编码器"对象,该对象允许您将索引转换为文本标签。</p>
<p>3.您必须使用pre trainer将您的模型与其相应的文件链接起来。</p>
<pre><code>%%writefile newbest_model/hparams_inference.yaml

#################################
Basic inference parameters for speaker-id. We have first a network that computes some embeddings. On the top of that, we employ a classifier.

#speaker-id的基本推理参数。我们首先有一个计算一些嵌入的网络。在此基础上,我们使用了一个分类器。

Author:
 * Mirco Ravanelli 2021
#################################

pretrain folders:
pretrained_path: newbest_model/

Model parameters
n_mels: 23
sample_rate: 16000
n_classes: 28 # In this case, we have 28 speakers
emb_dim: 512 # dimensionality of the embeddings 嵌入的维数

Feature extraction
compute_features: !new:speechbrain.lobes.features.Fbank
    n_mels: !ref <n_mels>

Mean and std normalization of the input features 输入特征的均值和标准归一化
mean_var_norm: !new:speechbrain.processing.features.InputNormalization
    norm_type: sentence
    std_norm: False

To design a custom model, either just edit the simple CustomModel
class that's listed here, or replace this !new` call with a line
pointing to a different file you've defined.

embedding_model: !new:custom_model.Xvector
    in_channels: !ref <n_mels>
    activation: !name:torch.nn.LeakyReLU
    tdnn_blocks: 5
    tdnn_channels: [512, 512, 512, 512, 1500]
    tdnn_kernel_sizes: [5, 3, 3, 1, 1]
    tdnn_dilations: [1, 2, 3, 1, 1]
    lin_neurons: !ref <emb_dim>

classifier: !new:custom_model.Classifier
    input_shape: [null, null, !ref <emb_dim>]
    activation: !name:torch.nn.LeakyReLU
    lin_blocks: 1
    lin_neurons: !ref <emb_dim>
    out_neurons: !ref <n_classes>

label_encoder: !new:speechbrain.dataio.encoder.CategoricalEncoder

Objects in "modules" dict will have their parameters moved to the correct
device, as well as having train()/eval() called on them by the Brain class.

modules:
    compute_features: !ref <compute_features>
    embedding_model: !ref <embedding_model>
    classifier: !ref <classifier>
    mean_var_norm: !ref <mean_var_norm>

pretrainer: !new:speechbrain.utils.parameter_transfer.Pretrainer
    loadables:
        embedding_model: !ref <embedding_model>
        classifier: !ref <classifier>
        label_encoder: !ref <label_encoder>
    paths:
        embedding_model: !ref <pretrained_path>/embedding_model.ckpt
        classifier: !ref <pretrained_path>/classifier.ckpt
        label_encoder: !ref <pretrained_path>/label_encoder.txt</pretrained_path></pretrained_path></pretrained_path></label_encoder></classifier></embedding_model></mean_var_norm></classifier></embedding_model></compute_features></n_classes></emb_dim></emb_dim></emb_dim></n_mels></n_mels>

我们这里只有模型定义(而不是优化器、检查点等)。yaml文件的最后一部分管理预训练,我们将模型对象与其在训练时创建的预训练文件绑定。

test.py

from speechbrain.pretrained import EncoderClassifier
import torchaudio
import os
from speechbrain.dataio.dataio import read_audio
from IPython.display import Audio
from speechbrain.pretrained import SpectralMaskEnhancement
classifier = EncoderClassifier.from_hparams(source="newbest_model/", hparams_file='hparams_inference.yaml', savedir="newbest_model/")

path='/home/ZhangHui/NewStudent/course-v3/zh-nbs/langid/dev'
files=os.listdir(path)
name=[0 for q in range(300)]
s=''
i=0

for file in files:
    if file=='.ipynb_checkpoints':
        continue
    x = file.split(".")
    name[i]=int(x[0])
    i=i+1
c=0
e=0
m=0
name.sort()
print(name)
i=0
for file in name:
    audio_file='/home/ZhangHui/NewStudent/course-v3/zh-nbs/langid/dev/'+str(file)+'.wav'
    signal, fs = torchaudio.load(audio_file)
    output_probs, score, index, text_lab = classifier.classify_batch(signal)
    s=s+text_lab[0]
    i=i+1
    if text_lab[0]=="C":
        c=c+1
    if text_lab[0]=="E":
        e=e+1
    if text_lab[0]=="M":
        m=m+1

print(s)
print(c,e,m)
And if you want to extract embeddings...

embeddings = classifier.encode_batch(signal)

从零开始的语音分类

提交结果

从零开始的语音分类

EncoderClassifier类

EncoderClassifier类,可以简化推理。该类还可用于提取编码器输出处的一些嵌入。 首先让我们看看如何使用它加载我们最好的xvector模型(在VoxEleb上训练并存储在HuggingFace上),以计算一些嵌入并执行说话人分类:

import torchaudio
from speechbrain.pretrained import EncoderClassifier

classifier = EncoderClassifier.from_hparams(source="newbest_model/", hparams_file='hparams_inference.yaml', savedir="newbest_model/")

Perform classification
audio_file = 'data/LibriSpeech/testlanguage/E/E0.wav'
signal, fs = torchaudio.load(audio_file)
output_probs, score, index, text_lab = classifier.classify_batch(signal)
print('Target: E, Predicted: ' + text_lab[0])
Posterior log probabilities
print(output_probs)

Score (i.e, max log posteriors)
print(score)

Index of the predicted speaker
print(index)

Text label of the predicted speaker &#x9884;&#x6D4B;&#x8BF4;&#x8BDD;&#x4EBA;&#x7684;&#x6587;&#x672C;&#x6807;&#x7B7E;
#print(text_lab)

And if you want to extract embeddings... &#x5982;&#x679C;&#x4F60;&#x60F3;&#x63D0;&#x53D6;&#x5D4C;&#x5165;&#x3002;&#x3002;&#x3002;
#embeddings = classifier.encode_batch(signal)

从零开始的语音分类

六.结果准确率

训练集:用来训练模型,模型相当于大脑,把训练集灌输进去先告诉大脑英语,汉语等这些是什么样的,提供先验知识。

开发集(验证集)(300条):测试模型的准确率

测试集(2423条):测试集的准确率比开发集的要低,毕竟数据量大了,当时准确率高的模型用在测试集上不一定高。

Original: https://blog.csdn.net/weixin_48985409/article/details/123499559
Author: 福尔摩斯.琴酒
Title: 从零开始的语音分类

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/662809/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球