主要内容

使用x向量识别说话人

说话人识别回答了“谁在说话?”说话人识别通常分为两个任务:演讲者识别而且演讲者验证.在说话人识别中,通过将说话人的语音与封闭的模板集进行比较来识别说话人。在说话人验证中,通过将讲话属于特定说话人的可能性与预先设定的阈值进行比较来识别说话人。在理想条件下,传统的机器学习方法在这些任务上表现良好。有关使用传统机器学习方法识别说话人的示例,请参见使用音调和MFCC识别说话人(音频工具箱)而且使用i-向量验证说话人(音频工具箱).音频工具箱™提供ivectorSystem(音频工具箱)它封装了训练i-向量系统的能力,登记扬声器或其他音频标签,评估系统的决策阈值,以及识别或验证扬声器或其他音频标签。

在不利条件下,x向量的深度学习方法已被证明在许多场景和应用中获得了最先进的结果[1].x向量系统是由最初为验证说话人而开发的i向量系统演变而来的。

在本例中,您将开发一个x向量系统。首先,你训练一个时滞神经网络(TDNN)来执行说话人识别。然后训练基于x矢量的扬声器验证系统的传统后端:LDA投影矩阵和PLDA模型。然后使用TDNN和后端降维和评分执行说话人验证。x向量系统后端或分类器与为i向量系统开发的相同。关于后台的详细信息,请参见使用i-向量验证说话人(音频工具箱)而且ivectorSystem(音频工具箱)

使用x向量(音频工具箱),则使用本例中训练过的x向量系统执行扬声器缩放。说话者diarization回答了这个问题:“谁在什么时候说话?”

在本例中,您将发现可调参数上的实时控件。更改控件不会重新运行示例。如果更改控件,则必须重新运行示例。

数据集管理

这个例子使用了LibriSpeech数据集的一个子集[2].LibriSpeech数据集是一个以16khz采样的大型英语语音阅读语料库。这些数据来源于LibriVox项目中阅读的有声读物。下载LibriSpeech训练数据的100小时子集、干净的开发集和干净的测试集。

dataFolder = tempdir;datasetTrain = fullfile(数据文件夹,“LibriSpeech”“培训-清洗- 100”);如果~datasetExists(datasetTrain) filename =“火车——清洁- 100. tar.gz”;url =“http://www.openSLR.org/resources/12/”+文件名;gunzip (url, dataFolder);unzippedFile = fullfile(数据文件夹,文件名);解压(unzippedFile {1} (1: end-3) dataFolder);结束datasetDev = fullfile(数据文件夹,“LibriSpeech”“dev-clean”);如果~datasetExists(datasetDev) filename =“dev-clean.tar.gz”;url =“http://www.openSLR.org/resources/12/”+文件名;gunzip (url, dataFolder);unzippedFile = fullfile(数据文件夹,文件名);解压(unzippedFile {1} (1: end-3) dataFolder);结束datasetttest = fullfile(数据文件夹,“LibriSpeech”“清洁测试”);如果~datasetExists(datasetttest) filename =“test-clean.tar.gz”;url =“http://www.openSLR.org/resources/12/”+文件名;gunzip (url, dataFolder);unzippedFile = fullfile(数据文件夹,文件名);解压(unzippedFile {1} (1: end-3) dataFolder);结束

创建audioDatastore(音频工具箱)指向数据的对象。扬声器标签编码在文件名中。将数据存储分为训练集、验证集和测试集。您将使用这些集来训练、验证和测试TDNN。

adsTrain = audioDatastore(datasetTrain, inclesubfolders =true);adsTrain。Labels = categorical(extractBetween(adsTrain.Files,fullfile(datasetTrain,filesep),filesep));adsDev = audioDatastore(datasetDev, inclesubfolders =true);adsDev。Labels = categorical(extractBetween(adsDev.Files,fullfile(datasetDev,filesep),filesep));adsEvaluate = audioDatastore(datasetTest, inclesubfolders =true);adsEvaluate。Labels = categorical(extractBetween(adsEvaluate.Files,fullfile(datasetTest,filesep),filesep));

分离audioDatastore对象分为五组:

  • adsTrain-包含TDNN和后端分类器的训练集。

  • adsValidation-包含验证集,用于评估TDNN训练进度。

  • adsTest-包含测试集,用于评估TDNN对说话人识别的性能。

  • adsEnroll-包含登记集,以评估x矢量系统的检测误差权衡,用于扬声器验证。

  • adsDET-包含评估集,用于确定用于扬声器验证的x矢量系统的检测误差权衡。

[adsTrain,adsValidation,adsTest] = splitEachLabel(adsTrain,0.8,0.1,0.1,“随机”);[adsEnroll,adsLeftover] = splitEachLabel(adsEvaluate,3,“随机”);adsDET = audioDatastore([adslefover . files;adsDev.Files]);adsDET。标签= [adslefover .Labels;adsDev.Labels];

您可以减少本例中使用的训练和检测误差权衡数据集,以性能为代价加快运行时。一般来说,减少数据集对于开发和调试是一种很好的实践。

speedupExample =如果adsTrain = splitEachLabel(adsTrain,5);adsValidation = splitEachLabel(adsValidation,2);adsDET = splitEachLabel(adsDET,5);结束

特征提取

创建一个audioFeatureExtractor(音频工具箱)对象从30毫秒的Hann窗口中提取30个mfc,跳数为10毫秒。数据集的采样率为16千赫。

Fs = 16e3;windowDuration =0.03;hopDuration =0.01;windowSamples = round(windowDuration*fs);hopSamples = round(hopDuration*fs);overlapSamples = windowSamples - hopSamples;numCoeffs =30.;afe = audioFeatureExtractor(...SampleRate = fs,...窗口=损害(windowSamples,“周期”),...OverlapLength = overlapSamples,...mfcc = true);setExtractorParameters (afe“mfcc”, NumCoeffs = NumCoeffs)

创建一个转换数据存储,将预处理应用于音频和输出特性。支撑函数金宝app,xVectorPreprocess,执行语音检测,从语音区域提取特征。当参数设置为时,语音检测区域被连接在一起。

adsTrain = transform(adsTrain,@(x)xVectorPreprocess(x,afe,Segment=false,MinimumDuration=0.5));features = preview(adsTrainTransform)
特点=1×1单元格数组{30×363单}

在循环中,从训练集中提取所有特征。如果您有并行计算工具箱™,那么计算将分布在多个工作线程上。

numPar = numpartitions(adsTrain);feature = cell(1,numPar);parforadsPart =分区(adsTrainTransform,numPar,ii);N = nummel (adspare . underlyingdatastores {1}.Files);f = cell(1,N);jj = 1:N f{jj} = read(adsPart);结束特征{ii} = cat(2,f{:});结束

连接特征,然后在结构中保存全局平均值和标准偏差。您将使用这些因素来规范化特征。

Features = cat(2, Features {:});Features = cat(2, Features {:});因素= struct(“的意思是”,意味着(功能,2),“性病”性病(特性、0 2));清晰的特性f

为训练集创建一个新的转换数据存储,这次指定归一化因子和作为真正的.现在,特征通过全局均值和标准偏差进行标准化,然后是文件级均值。检测到的单个语音区域没有连接。输出是一个表,第一个变量包含特征矩阵,第二个变量包含标签。

adstraaintransform = transform(adsTrain,@(x,myInfo)xVectorPreprocess(x,afe,myInfo,)...= = true,因素因素,MinimumDuration = 0.5),...IncludeInfo = true);featuresTable =预览(adsTrainTransform)
featuresTable =3×2表标签功能  _______________ ______ { 30×142单1034}{30×64单}1034{30×157单}1034

将相同的转换应用到验证、测试、登记和DET集。

adsValidationTransform = transform(adsValidation,@(x,myInfo)xVectorPreprocess(x,afe,myInfo,...= = true,因素因素,MinimumDuration = 0.5),...IncludeInfo = true);adsTestTransform = transform(adsTest,@(x,myInfo)xVectorPreprocess(x,afe,myInfo,)...= = true,因素因素,MinimumDuration = 0.5),...IncludeInfo = true);adsEnrollTransform = transform(adsEnroll,@(x,myInfo)xVectorPreprocess(x,afe,myInfo,...= = true,因素因素,MinimumDuration = 0.5),...IncludeInfo = true);adsDETTransform = transform(adsDET,@(x,myInfo)xVectorPreprocess(x,afe,myInfo,)...= = true,因素因素,MinimumDuration = 0.5),...IncludeInfo = true);

x向量特征提取模型

在本例中,定义x向量特征提取器模型[1]作为一个图层图,并使用自定义训练循环来训练它。此范例使您能够预处理小批并将序列修剪为一致的长度。

中描述的网络体系结构的总结[1]并在本例中实现。T是音频信号中帧的总数(随时间变化的特征向量)。N是训练集中的类(演讲者)的数量。

定义网络。可以通过增加或减少属性来更改模型大小numFilters参数。

numFilters =512;dropProb = 0.2;numClasses = numel(唯一的(adsTrain.Labels));layers = [sequenceInputLayer(afe. layer)]FeatureVectorLength最小长度= 15,Name =“输入”) convolution1dLayer (5 numFilters DilationFactor = 1,名字=“conv_1”) batchNormalizationLayer (Name =“BN_1”) dropoutLayer (dropProb Name =“drop_1”) reluLayer (Name =“act_1”) convolution1dLayer (3 numFilters DilationFactor = 2, Name =“conv_2”) batchNormalizationLayer (Name =“BN_2”) dropoutLayer (dropProb Name =“drop_2”) reluLayer (Name =“act_2”) convolution1dLayer (3 numFilters DilationFactor = 3 =“conv_3”) batchNormalizationLayer (Name =“BN_3”) dropoutLayer (dropProb Name =“drop_3”) reluLayer (Name =“act_3”) convolution1dLayer (1 numFilters DilationFactor = 1,名字=“conv_4”) batchNormalizationLayer (Name =“BN_4”) dropoutLayer (dropProb Name =“drop_4”) reluLayer (Name =“act_4”1500年)convolution1dLayer (1, DilationFactor = 1,名字=“conv_5”) batchNormalizationLayer (Name =“BN_5”) dropoutLayer (dropProb Name =“drop_5”) reluLayer (Name =“act_5”) statisticsPooling1dLayer (Name =“statistics_pooling”) fullyConnectedLayer (numFilters Name =“fc_1”) batchNormalizationLayer (Name =“BN_6”) dropoutLayer (dropProb Name =“drop_6”) reluLayer (Name =“act_6”) fullyConnectedLayer (numFilters Name =“fc_2”) batchNormalizationLayer (Name =“BN_7”) dropoutLayer (dropProb Name =“drop_7”) reluLayer (Name =“act_7”) fullyConnectedLayer (numClasses Name =“fc_3”) softmaxLayer (Name =“softmax”));dlnet = dlnetwork(layerGraph(layers));

该模型需要统计池,它被实现为一个自定义层,并在打开此示例时放置在当前文件夹中。显示自定义层的内容。

类型(“statisticsPooling1dLayer.m”
classdef statisticsPooling1dLayer < nnet.layer.Layer & nnet.layer.Formattable %该类仅用于本例。在将来的版本中,它可能会被更改或%删除。this = statisticsPooling1dLayer(options)参数选项。Name = " end this. "Name = options.Name;end function X = predict(~, X) X = dlarray(stripdims([mean(X,3);std(X,0,3)]),"CB");end function X = forward(~, X) X = X + 0.0001*rand(size(X),"single");X = dlarray (stripdims([意味着(X, 3); std (X 0 3)]),“CB”);结束结束

火车模型

使用minibatchqueue为训练数据创建一个迷你批处理队列。设置适合您的硬件的迷你批大小。

miniBatchSize =256;mbq = minibatchqueue(adsTrainTransform,...MiniBatchSize = MiniBatchSize,...MiniBatchFormat = [“施”""),...MiniBatchFcn = @preprocessMiniBatch,...OutputEnvironment =“汽车”);

设置训练周期的数量、初始学习率、学习率下降周期、学习率下降因子以及每个epoch的验证次数。

numEpochs =5;learnRate =0.001;gradDecay = 0.5;sqGradDecay = 0.999;trailingAvg = [];trailingAvgSq = [];LearnRateDropPeriod =2;LearnRateDropFactor =0.1

要显示训练进度,请初始化支持对象金宝appprogressPlotter.支撑对象金宝app,progressPlotter,在打开此示例时放置在当前文件夹中。

进行循环训练。

classes = unique(adsTrain.Labels);pp = progressPlotter(字符串(类));迭代= 0;epoch = 1:numEpochs打乱迷你批处理队列洗牌(兆贝可)hasdata(兆贝可)%更新迭代计数器迭代=迭代+ 1;从迷你批处理队列中获取迷你批处理[dlX,Y] = next(mbq);使用dlfeval和modelGradients函数评估模型的梯度、状态和损失(渐变,dlnet。状态,损失,预测]= dlfeval(@modelGradients,dlnet,dlX,Y);使用Adam优化器更新网络参数。[dlnet,trailingAvg,trailingAvgSq] = adamupdate(dlnet,gradients,...trailingAvg trailingAvgSq,迭代,learnRate、gradDecay sqGradDecay eps (“单身”));更新培训进度图updateTrainingProgress (pp、时代=时代,迭代=迭代,LearnRate = LearnRate =预测,预测目标= Y,损失=损失)结束%通过模型传递验证数据[predictionValidation,labelsValidation] = predictBatch(dlnet,adsValidationTransform);predictionValidation = onehotdecode(predictionValidation,string(classes),1);用验证结果更新训练进度图。updateValidation (pp、迭代=迭代预测= predictionValidation目标= labelsValidation)更新学习率如果rem(epoch, learnratdroppperiod)==0 learnRate = learnRate* learnratdroppfactor;结束结束

使用保留测试集评估TDNN说话人识别的准确性。支撑函数金宝app,predictBatch,如果您有并行计算工具箱™,则可以并行预测计算。解码预测,然后计算预测精度。

[predictionTest,targetTest] = predictBatch(dlnet,adsTestTransform);predictionTest = onehotdecode(predictionTest,string(classes),1);accuracy = mean(targetTest(:)==predictionTest(:)))
准确度= 0.9460

训练x矢量系统后端

在说话人验证的x向量系统中,您刚刚训练的TDNN用于输出嵌入层。嵌入层的输出("fc_1”在这个例子中)是x向量系统中的“x向量”。

x向量系统的后端(或分类器)与i向量系统的后端相同。算法的详细介绍请参见ivectorSystem(音频工具箱)而且使用i-向量验证说话人(音频工具箱)

从集合中提取x向量。

[xvecsTrain,labelsTrain] = predictBatch(dlnet, adstraaintransform,Outputs= .“fc_1”);

创建一个线性判别分析(LDA)投影矩阵来降低x向量的维数。LDA试图最小化类内方差,最大化说话人之间的方差。

numEigenvectors =150;projMat = helptrainprojectionmatrix (xvecsTrain,labelsTrain,numEigenvectors);

将LDA投影矩阵应用于x向量。

xvecsTrainP = projMat*xvecsTrain;

训练G-PLDA模型进行评分。

numIterations =3.;numDimensions =150;plda = helptrainplda (xvecsTrainP,labelsTrain,numIterations,numDimensions);

求x向量系统

说话人验证系统验证说话人是他们声称的那个人。在演讲者被验证之前,他们必须在系统中注册。在系统中的注册意味着系统具有演讲者的模板x向量表示。

登记人

从保留的数据集中提取x向量,adsEnroll

[xvecsEnroll,labelsEnroll] = predictBatch(dlnet,adsEnrollTransform,Outputs=“fc_1”);

将LDA投影矩阵应用于x向量。

xvecsEnrollP = projMat*xvecsEnroll;

通过在注册文件中平均每个演讲者的x向量,为每个演讲者创建模板x向量。

uniqueLabels = unique(labelsEnroll);registrenttable = cell2table(cell(0,2),VariableNames=[“xvector”“NumSamples”]);ii = 1:numel(uniqueLabels) idx = uniqueLabels(ii)==labelsEnroll;wLocalMean = mean(xvecsEnrollP(:,idx),2);localTable = table({wLocalMean},(sum(idx)),...VariableNames = [“xvector”“NumSamples”),...RowNames =字符串(uniqueLabels (ii)));registrmenttable = [registrmenttable;localTable];% #好< AGROW >结束

说话人验证系统要求您根据应用程序的要求设置一个阈值,以平衡错误接受(FA)的概率和错误拒绝(FR)的概率。要确定满足FA/FR要求的阈值,请评估系统的检测误差权衡。

[xvecsDET,labelsDET] = predictBatch(dlnet,adsDETTransform,Outputs=“fc_1”);xvecsDETP = projMat*xvecsDET;detTable = helperDetectionErrorTradeoff(xvecsDETP,labelsDET,enrollmentTable,plda);

绘制PLDA评分和余弦相似度评分(CSS)的检测误差权衡评估结果。

图绘制(detTable.PLDA.Threshold detTable.PLDA.FAR,...detable . plda . threshold, detable . plda . frr) eer = helperEqualErrorRate(detable . plda);标题([“议长验证”“检测误差权衡”“PLDA得分”“等错误率=”+曾经]);包含(“阈值”) ylabel (“出错率”)传说([“远”“FRR”])

图绘制(detTable.CSS.Threshold detTable.CSS.FAR,...detable . css . threshold, detable . css . frr) eer = helperEqualErrorRate(detable . css);标题([“议长验证”“检测误差权衡”“余弦相似度评分”“等错误率=”+曾经]);包含(“阈值”) ylabel (“出错率”)传说([“远”“FRR”])

参考文献

[1]斯奈德,大卫,等。x向量:用于说话人识别的鲁棒DNN嵌入。2018 IEEE声学、语音和信号处理国际会议(ICASSP), IEEE, 2018, pp. 5329-33。DOI.org (Crossref), doi: 10.1109 / ICASSP.2018.8461375。

[2] V. Panayotov, G. Chen, D. Povey和S. Khudanpur,“Librispeech:基于公共领域有声书的ASR语料库,”2015 IEEE声学、语音与信号处理国际会议(ICASSP)中国农业科学,2015,pp. 5206-5210, doi: 10.1109/ICASSP.2015.7178964

金宝app支持功能

特征提取与归一化

函数[output,myInfo] = xVectorPreprocess(audioData,afe,myInfo,nvargs)此函数仅在本例中使用。它可能被改变或%将在将来的版本中删除。参数audioData afe myInfo = [] nvargs。Factors = [] nvargs。Segment = true;nvargs。MinimumDuration = 1;nvargs。使用GPU = false;结束如果需要,放置在GPU上如果nvargs。使用GPU audioData = gpuArray(audioData);结束%的规模audioData = audioData/max(abs(audioData(:)));%保护免受nanaudioData(isnan(audioData)) = 0;确定语音区域mergeDur = 0.2;%秒idx = detectSpeech(audioData,afe.SampleRate,MergeDistance=afe.SampleRate*mergeDur);%如果一个区域小于MinimumDuration seconds,则删除该区域。如果nvargs。段idxToRemove = (idx(:,2)-idx(:,1))结束提取特征numSegments = size(idx,1);features = cell(numSegments,1);ii = 1:numSegments temp = (single(extract(afe,audioData(idx(ii,1):idx(ii,2)))))';如果Isempty (temp) temp = 0 (30,15,“单身”);结束特征{ii} = temp;结束%标准化功能如果~isempty(nvargs.Factors) features = cellfun(@(x)(x-nvargs.Factors. mean)./nvargs.Factors. std,features,UniformOutput=false);结束倒谱平均减法(用于信道噪声)如果~isempty(nvargs.Factors) fileMean = mean(cat(2,features{:}),“所有”);features = cellfun(@(x)x - fileMean,features,UniformOutput=false);结束如果~ nvargs。段特点={cat(2,features{:})};结束如果isempty(myInfo) output = features;其他的labels = repelem(myInfo.Label,numel(features),1);输出=表(特征,标签);结束结束

计算模型梯度和更新状态

函数[gradients,state,loss,YPred] = modelGradients(dlnet,X,Y)此函数仅在本例中使用。它可能被改变或%将在将来的版本中删除。[YPred,state] = forward(dlnet,X);loss = crossentropy(YPred,Y);gradients = dlgradient(loss,dlnet.Learnables);损失= double(gather(extractdata(Loss)));结束

预处理Mini-Batch

函数[sequences,labels] = preprocessMiniBatch(sequences,labels)此函数仅在本例中使用。它可能被改变或%将在将来的版本中删除。trimDimension = 2;长度= cellfun(@(x)size(x,trimDimension),序列);minLength = min(长度);sequences = cellfun(@(x)randomTruncate(x,trimDimension,minLength),sequences,UniformOutput=false);Sequences = cat(3, Sequences {:});标签= cat(2,标签{:});标签= onehotencode(标签,1);标签(isnan(标签))= 0;结束

随机截断音频信号到指定的长度

函数y = randomTruncate(x,dim,minLength)此函数仅在本例中使用。它可能被改变或%将在将来的版本中删除。N = size(x,dim);如果N > minLength start = randperm(N-minLength,1);如果y = x(start:start+minLength-1,:);elseif2 y = x(:,start:start+minLength-1);结束其他的Y = x;结束结束

预测批

函数[xvecs,labels] = predictBatch(dlnet,ds,nvargs)参数Dlnet ds nvargs。输出= [];结束如果~ isempty(版本(“平行”)) pool = gcp;numPartition = numpartitions(ds,pool);其他的numPartition = 1;结束Xvecs = [];标签= [];outputs = nvargs.Outputs;parfordsPart =分区(ds,numPartition,partitionIndex);partitionFeatures = [];partitionLabels = [];hasdata(dsPart) table = read(dsPart);F = table.features;L = table.labels;kk = 1:数字(L)如果isempty(outputs) f = gather(extractdata(predict(dlnet,(dlarray(f {kk},“施”)))));其他的f = gather(extractdata(predict(dlnet,(dlarray(f {kk},“施”)),输出=输出)));结束l = l (kk);partitionFeatures = [partitionFeatures,f];partitionLabels = [partitionLabels,l];结束结束xvecs = [xvecs,partitionFeatures];标签=[标签,partitionLabels];结束结束