Этот пример показывает, как обучить модель глубокого обучения, которая обнаруживает наличие речевых команд в аудио. Пример использует набор данных речевых команд [1], чтобы обучить сверточную нейронную сеть распознавать данный набор команд.
Чтобы обучить сеть с нуля, необходимо сначала загрузить набор данных. Если вы не хотите загружать набор данных или обучать сеть, то можно загрузить предварительно обученную сеть, предоставленную этим примером, и выполнить следующие два раздела примера: Распознавать команды с предварительно обученной сетью и Обнаруживать команды с использованием передачи потокового аудио из микрофона.
Прежде чем подробно войти в процесс обучения, вы будете использовать предварительно обученную сеть распознавания речи для идентификации речевых команд.
Загрузите предварительно обученную сеть.
load('commandNet.mat')
Сеть обучена распознавать следующие речевые команды:
«да»
«нет»
вверх
«вниз»
«слева»
правильно
«on»
«off»
Стоп
«перейти»
Загрузите короткий речевой сигнал, где человек говорит «стоп».
[x,fs] = audioread('stop_command.flac');
Послушайте команду.
sound(x,fs)
Предварительно обученная сеть принимает спектрограммы на основе слуха в качестве входов. Сначала вы преобразуете форму речевого сигнала в спектрограмму на основе слуха.
Используйте функцию extractAuditoryFeature
для вычисления слуховой спектрограммы. Подробные сведения о редукции данных будут рассмотрены позже в примере.
auditorySpect = helperExtractAuditoryFeatures(x,fs);
Классифицируйте команду на основе ее слуховой спектрограммы.
command = classify(trainedNet,auditorySpect)
command = categorical stop
Сеть обучена классифицировать слова, не принадлежащие этому набору, как «неизвестные».
Теперь вы классифицируете слово («play»), которое не было включено в список команд для идентификации.
Загрузите речевой сигнал и прослушайте его.
x = audioread('play_command.flac');
sound(x,fs)
Вычислите слуховую спектрограмму.
auditorySpect = helperExtractAuditoryFeatures(x,fs);
Классифицируйте сигнал.
command = classify(trainedNet,auditorySpect)
command = categorical unknown
Сеть обучена классифицировать фоновый шум как «фон».
Создайте сигнал на одну секунду, состоящий из случайного шума.
x = pinknoise(16e3);
Вычислите слуховую спектрограмму.
auditorySpect = helperExtractAuditoryFeatures(x,fs);
Классифицируйте фоновый шум.
command = classify(trainedNet,auditorySpect)
command = categorical background
Протестируйте предварительно обученную сеть обнаружения команд на передаче потокового аудио с микрофона. Попробуйте сказать одну из команд, например, да, нет, или остановить. Затем попробуйте сказать одно из неизвестных слов, таких как Марвин, Шейла, кровать, дом, кошка, птица или любое число от нуля до девяти.
Укажите частоту классификации в Гц и создайте аудио устройство считыватель, который может считать аудио с вашего микрофона.
classificationRate = 20; adr = audioDeviceReader('SampleRate',fs,'SamplesPerFrame',floor(fs/classificationRate));
Инициализируйте буфер для аудио. Извлеките классификационные метки сети. Инициализируйте буферы в полсекунды для меток и классификационных вероятностей передачи потокового аудио. Используйте эти буферы, чтобы сравнить результаты классификации за более длительный период времени и по этому сборке 'agreement' over когда команда обнаружена. Задайте пороги для логики принятия решений.
audioBuffer = dsp.AsyncBuffer(fs);
labels = trainedNet.Layers(end).Classes;
YBuffer(1:classificationRate/2) = categorical("background");
probBuffer = zeros([numel(labels),classificationRate/2]);
countThreshold = ceil(classificationRate*0.2);
probThreshold = 0.7;
Создайте рисунок и обнаружите команды, пока существует созданный рисунок. Чтобы запустить цикл бесконечно, установите timeLimit
на Inf
. Чтобы остановить живое обнаружение, просто закройте рисунок.
h = figure('Units','normalized','Position',[0.2 0.1 0.6 0.8]); timeLimit = 20; tic while ishandle(h) && toc < timeLimit % Extract audio samples from the audio device and add the samples to % the buffer. x = adr(); write(audioBuffer,x); y = read(audioBuffer,fs,fs-adr.SamplesPerFrame); spec = helperExtractAuditoryFeatures(y,fs); % Classify the current spectrogram, save the label to the label buffer, % and save the predicted probabilities to the probability buffer. [YPredicted,probs] = classify(trainedNet,spec,'ExecutionEnvironment','cpu'); YBuffer = [YBuffer(2:end),YPredicted]; probBuffer = [probBuffer(:,2:end),probs(:)]; % Plot the current waveform and spectrogram. subplot(2,1,1) plot(y) axis tight ylim([-1,1]) subplot(2,1,2) pcolor(spec') caxis([-4 2.6445]) shading flat % Now do the actual command detection by performing a very simple % thresholding operation. Declare a detection and display it in the % figure title if all of the following hold: 1) The most common label % is not background. 2) At least countThreshold of the latest frame % labels agree. 3) The maximum probability of the predicted label is at % least probThreshold. Otherwise, do not declare a detection. [YMode,count] = mode(YBuffer); maxProb = max(probBuffer(labels == YMode,:)); subplot(2,1,1) if YMode == "background" || count < countThreshold || maxProb < probThreshold title(" ") else title(string(YMode),'FontSize',20) end drawnow end
Этот пример использует набор данных Google Speech Commands Dataset [1]. Загрузите набор данных и распакуйте загруженный файл. Установите PathToDatabase в местоположение данных.
url = 'https://ssd.mathworks.com/supportfiles/audio/google_speech.zip'; downloadFolder = tempdir; dataFolder = fullfile(downloadFolder,'google_speech'); if ~exist(dataFolder,'dir') disp('Downloading data set (1.4 GB) ...') unzip(url,downloadFolder) end
Создайте audioDatastore
это указывает на обучающие данные набор.
ads = audioDatastore(fullfile(dataFolder, 'train'), ... 'IncludeSubfolders',true, ... 'FileExtensions','.wav', ... 'LabelSource','foldernames')
ads = audioDatastore with properties: Files: { ' ...\AppData\Local\Temp\google_speech\train\bed\00176480_nohash_0.wav'; ' ...\AppData\Local\Temp\google_speech\train\bed\004ae714_nohash_0.wav'; ' ...\AppData\Local\Temp\google_speech\train\bed\004ae714_nohash_1.wav' ... and 51085 more } Folders: { 'C:\Users\jibrahim\AppData\Local\Temp\google_speech\train' } Labels: [bed; bed; bed ... and 51085 more categorical] AlternateFileSystemRoots: {} OutputDataType: 'double' SupportedOutputFormats: ["wav" "flac" "ogg" "mp4" "m4a"] DefaultOutputFormat: "wav"
Задайте слова, которые вы хотите, чтобы ваша модель распознавала как команды. Пометьте все слова, которые не являются командами, как unknown
. Маркировка слов, которые не являются командами, как unknown
создает группу слов, которая аппроксимирует распределение всех слов, кроме команд. Сеть использует эту группу, чтобы узнать различие между командами и всеми другими словами.
Чтобы уменьшить классовый дисбаланс между известными и неизвестными словами и ускорить обработку, включите только часть неизвестных слов в набор обучающих данных.
Использование subset
чтобы создать datastore, который содержит только команды и подмножество неизвестных слов. Подсчитайте количество примеров, принадлежащих каждой категории.
commands = categorical(["yes","no","up","down","left","right","on","off","stop","go"]); isCommand = ismember(ads.Labels,commands); isUnknown = ~isCommand; includeFraction = 0.2; mask = rand(numel(ads.Labels),1) < includeFraction; isUnknown = isUnknown & mask; ads.Labels(isUnknown) = categorical("unknown"); adsTrain = subset(ads,isCommand|isUnknown); countEachLabel(adsTrain)
ans = 11×2 table Label Count _______ _____ down 1842 go 1861 left 1839 no 1853 off 1839 on 1864 right 1852 stop 1885 unknown 6483 up 1843 yes 1860
Создайте audioDatastore
это указывает на набор данных валидации. Выполните те же шаги, которые используются для создания обучающего datastore.
ads = audioDatastore(fullfile(dataFolder, 'validation'), ... 'IncludeSubfolders',true, ... 'FileExtensions','.wav', ... 'LabelSource','foldernames') isCommand = ismember(ads.Labels,commands); isUnknown = ~isCommand; includeFraction = 0.2; mask = rand(numel(ads.Labels),1) < includeFraction; isUnknown = isUnknown & mask; ads.Labels(isUnknown) = categorical("unknown"); adsValidation = subset(ads,isCommand|isUnknown); countEachLabel(adsValidation)
ads = audioDatastore with properties: Files: { ' ...\AppData\Local\Temp\google_speech\validation\bed\026290a7_nohash_0.wav'; ' ...\AppData\Local\Temp\google_speech\validation\bed\060cd039_nohash_0.wav'; ' ...\AppData\Local\Temp\google_speech\validation\bed\060cd039_nohash_1.wav' ... and 6795 more } Folders: { 'C:\Users\jibrahim\AppData\Local\Temp\google_speech\validation' } Labels: [bed; bed; bed ... and 6795 more categorical] AlternateFileSystemRoots: {} OutputDataType: 'double' SupportedOutputFormats: ["wav" "flac" "ogg" "mp4" "m4a"] DefaultOutputFormat: "wav" ans = 11×2 table Label Count _______ _____ down 264 go 260 left 247 no 270 off 256 on 257 right 256 stop 246 unknown 850 up 260 yes 261
Чтобы обучить сеть со набором данных в целом и достичь максимально возможной точности, установите reduceDataset
на false
. Чтобы запустить этот пример быстро, установите reduceDataset
на true
.
reduceDataset = false; if reduceDataset numUniqueLabels = numel(unique(adsTrain.Labels)); % Reduce the dataset by a factor of 20 adsTrain = splitEachLabel(adsTrain,round(numel(adsTrain.Files) / numUniqueLabels / 20)); adsValidation = splitEachLabel(adsValidation,round(numel(adsValidation.Files) / numUniqueLabels / 20)); end
Чтобы подготовить данные для эффективного обучения сверточной нейронной сети, преобразуйте формы речи в спектрограммы на основе слуха.
Задайте параметры редукции данных. segmentDuration
- длительность каждого речевого клипа (в секундах). frameDuration
- длительность каждой системы координат для вычисления спектра. hopDuration
- временной шаг между каждым спектром. numBands
количество фильтров в слуховой спектрограмме.
Создайте audioFeatureExtractor
объект для выполнения редукции данных.
fs = 16e3; % Known sample rate of the data set. segmentDuration = 1; frameDuration = 0.025; hopDuration = 0.010; segmentSamples = round(segmentDuration*fs); frameSamples = round(frameDuration*fs); hopSamples = round(hopDuration*fs); overlapSamples = frameSamples - hopSamples; FFTLength = 512; numBands = 50; afe = audioFeatureExtractor( ... 'SampleRate',fs, ... 'FFTLength',FFTLength, ... 'Window',hann(frameSamples,'periodic'), ... 'OverlapLength',overlapSamples, ... 'barkSpectrum',true); setExtractorParams(afe,'barkSpectrum','NumBands',numBands,'WindowNormalization',false);
Считайте файл из набора данных. Настройка сверточной нейронной сети требует, чтобы вход был допустимым размером. Некоторые файлы в наборе данных имеют длину менее 1 секунды. Примените заполнение нуля к передней и задней частотам аудиосигнала так, чтобы он имел длину segmentSamples
.
x = read(adsTrain); numSamples = size(x,1); numToPadFront = floor( (segmentSamples - numSamples)/2 ); numToPadBack = ceil( (segmentSamples - numSamples)/2 ); xPadded = [zeros(numToPadFront,1,'like',x);x;zeros(numToPadBack,1,'like',x)];
Чтобы извлечь аудио функции, вызовите extract
. Выходные выходы - спектр Корка со временем между строками.
features = extract(afe,xPadded); [numHops,numFeatures] = size(features)
numHops = 98 numFeatures = 50
В этом примере вы постпроцессируете слуховую спектрограмму путем применения логарифма. Взятие журнала малых чисел может привести к ошибке округления.
Чтобы ускорить обработку, можно распределить редукцию данных между несколькими работниками с помощью parfor
.
Во-первых, определите количество разделов для набора данных. Если у вас нет Parallel Computing Toolbox™, используйте один раздел.
if ~isempty(ver('parallel')) && ~reduceDataset pool = gcp; numPar = numpartitions(adsTrain,pool); else numPar = 1; end
Для каждого раздела считайте из datastore, обнулите сигнал и затем извлеките функции.
parfor ii = 1:numPar subds = partition(adsTrain,numPar,ii); XTrain = zeros(numHops,numBands,1,numel(subds.Files)); for idx = 1:numel(subds.Files) x = read(subds); xPadded = [zeros(floor((segmentSamples-size(x,1))/2),1);x;zeros(ceil((segmentSamples-size(x,1))/2),1)]; XTrain(:,:,:,idx) = extract(afe,xPadded); end XTrainC{ii} = XTrain; end
Преобразуйте выход в 4-мерный массив со слуховыми спектрограммами по четвертой размерности.
XTrain = cat(4,XTrainC{:}); [numHops,numBands,numChannels,numSpec] = size(XTrain)
numHops = 98 numBands = 50 numChannels = 1 numSpec = 25021
Масштабируйте функции по степени окна, а затем берите журнал. Чтобы получить данные с более плавным распределением, примите логарифм спектрограмм с помощью небольшого смещения.
epsil = 1e-6; XTrain = log10(XTrain + epsil);
Выполните шаги редукции данных, описанные выше, к набору валидации.
if ~isempty(ver('parallel')) pool = gcp; numPar = numpartitions(adsValidation,pool); else numPar = 1; end parfor ii = 1:numPar subds = partition(adsValidation,numPar,ii); XValidation = zeros(numHops,numBands,1,numel(subds.Files)); for idx = 1:numel(subds.Files) x = read(subds); xPadded = [zeros(floor((segmentSamples-size(x,1))/2),1);x;zeros(ceil((segmentSamples-size(x,1))/2),1)]; XValidation(:,:,:,idx) = extract(afe,xPadded); end XValidationC{ii} = XValidation; end XValidation = cat(4,XValidationC{:}); XValidation = log10(XValidation + epsil);
Изолируйте метки train и валидации. Удалите пустые категории.
YTrain = removecats(adsTrain.Labels); YValidation = removecats(adsValidation.Labels);
Постройте графики форм волны и слуховых спектрограмм нескольких обучающих выборок. Воспроизведение соответствующих аудиоклипов.
specMin = min(XTrain,[],'all'); specMax = max(XTrain,[],'all'); idx = randperm(numel(adsTrain.Files),3); figure('Units','normalized','Position',[0.2 0.2 0.6 0.6]); for i = 1:3 [x,fs] = audioread(adsTrain.Files{idx(i)}); subplot(2,3,i) plot(x) axis tight title(string(adsTrain.Labels(idx(i)))) subplot(2,3,i+3) spect = (XTrain(:,:,1,idx(i))'); pcolor(spect) caxis([specMin specMax]) shading flat sound(x,fs) pause(2) end
Сеть должна быть способна не только распознавать различные разговорные слова, но и обнаруживать, содержит ли вход тишину или фоновый шум.
Используйте аудио файлов в _background
_ папку для создания выборок односекундных клипов фонового шума. Создайте равное количество фоновых клипов из каждого файла фонового шума. Можно также создать собственные записи фонового шума и добавить их в _background
_ папку. Перед вычислением спектрограмм функция пересматривает каждый аудиоклип с фактором, выбранным из логарифмического распределения в области значений, заданной volumeRange
.
adsBkg = audioDatastore(fullfile(dataFolder, 'background')) numBkgClips = 4000; if reduceDataset numBkgClips = numBkgClips/20; end volumeRange = log10([1e-4,1]); numBkgFiles = numel(adsBkg.Files); numClipsPerFile = histcounts(1:numBkgClips,linspace(1,numBkgClips,numBkgFiles+1)); Xbkg = zeros(size(XTrain,1),size(XTrain,2),1,numBkgClips,'single'); bkgAll = readall(adsBkg); ind = 1; for count = 1:numBkgFiles bkg = bkgAll{count}; idxStart = randi(numel(bkg)-fs,numClipsPerFile(count),1); idxEnd = idxStart+fs-1; gain = 10.^((volumeRange(2)-volumeRange(1))*rand(numClipsPerFile(count),1) + volumeRange(1)); for j = 1:numClipsPerFile(count) x = bkg(idxStart(j):idxEnd(j))*gain(j); x = max(min(x,1),-1); Xbkg(:,:,:,ind) = extract(afe,x); if mod(ind,1000)==0 disp("Processed " + string(ind) + " background clips out of " + string(numBkgClips)) end ind = ind + 1; end end Xbkg = log10(Xbkg + epsil);
adsBkg = audioDatastore with properties: Files: { ' ...\AppData\Local\Temp\google_speech\background\doing_the_dishes.wav'; ' ...\AppData\Local\Temp\google_speech\background\dude_miaowing.wav'; ' ...\AppData\Local\Temp\google_speech\background\exercise_bike.wav' ... and 3 more } Folders: { 'C:\Users\jibrahim\AppData\Local\Temp\google_speech\background' } AlternateFileSystemRoots: {} OutputDataType: 'double' Labels: {} SupportedOutputFormats: ["wav" "flac" "ogg" "mp4" "m4a"] DefaultOutputFormat: "wav" Processed 1000 background clips out of 4000 Processed 2000 background clips out of 4000 Processed 3000 background clips out of 4000 Processed 4000 background clips out of 4000
Разделите спектрограммы фонового шума между наборами для обучения, валидации и тестирования. Потому что _background_noise
_ папка содержит только около пяти с половиной минут фонового шума, фоновые выборки в различных наборах данных сильно коррелируют. Чтобы увеличить изменение фонового шума, можно создать собственные файлы фона и добавить их в папку. Чтобы повысить робастность сети до шума, можно также попробовать смешать фоновый шум в речевые файлы.
numTrainBkg = floor(0.85*numBkgClips); numValidationBkg = floor(0.15*numBkgClips); XTrain(:,:,:,end+1:end+numTrainBkg) = Xbkg(:,:,:,1:numTrainBkg); YTrain(end+1:end+numTrainBkg) = "background"; XValidation(:,:,:,end+1:end+numValidationBkg) = Xbkg(:,:,:,numTrainBkg+1:end); YValidation(end+1:end+numValidationBkg) = "background";
Постройте график распределения различных меток классов в наборах обучения и валидации.
figure('Units','normalized','Position',[0.2 0.2 0.5 0.5]) subplot(2,1,1) histogram(YTrain) title("Training Label Distribution") subplot(2,1,2) histogram(YValidation) title("Validation Label Distribution")
Создайте простую сетевую архитектуру как массив слоев. Используйте сверточные и пакетные слои нормализации и понижающее отображение карты признаков «пространственно» (то есть по времени и частоте) с помощью максимальных слоев объединения. Добавьте конечный слой максимального объединения, который объединяет входную карту функций глобально с течением времени. Это обеспечивает (приблизительную) инвариацию преобразования времени в вход спектрограммах, позволяя сети выполнять ту же классификацию независимо от точного положения речи во времени. Глобальное объединение также значительно уменьшает количество параметров в конечном полносвязном слое. Чтобы уменьшить возможность запоминания сетью специфических функций обучающих данных, добавьте небольшое количество отсева на вход к последнему полностью подключенному слою.
Сеть небольшая, так как она имеет всего пять сверточных слоев с небольшим количеством фильтров. numF
управляет количеством фильтров в сверточных слоях. Чтобы увеличить точность сети, попробуйте увеличить глубину сети, добавив одинаковые блоки сверточных, нормализации партии . и слоев ReLU. Можно также попробовать увеличить количество сверточных фильтров путем увеличения numF
.
Используйте взвешенные потери классификации перекрестной энтропии. weightedClassificationLayer(classWeights)
создает пользовательский слой классификации, который вычисляет потери перекрестной энтропии с наблюдениями, взвешенными по classWeights
. Задайте веса классов в том же порядке, в котором появляются классы categories(YTrain)
. Чтобы задать каждому классу равный общий вес при потере, используйте веса классов, которые обратно пропорциональны количеству примеров обучения в каждом классе. При использовании оптимизатора Адама для обучения сети алгоритм настройки не зависит от общей нормализации весов классов.
classWeights = 1./countcats(YTrain); classWeights = classWeights'/mean(classWeights); numClasses = numel(categories(YTrain)); timePoolSize = ceil(numHops/8); dropoutProb = 0.2; numF = 12; layers = [ imageInputLayer([numHops numBands]) convolution2dLayer(3,numF,'Padding','same') batchNormalizationLayer reluLayer maxPooling2dLayer(3,'Stride',2,'Padding','same') convolution2dLayer(3,2*numF,'Padding','same') batchNormalizationLayer reluLayer maxPooling2dLayer(3,'Stride',2,'Padding','same') convolution2dLayer(3,4*numF,'Padding','same') batchNormalizationLayer reluLayer maxPooling2dLayer(3,'Stride',2,'Padding','same') convolution2dLayer(3,4*numF,'Padding','same') batchNormalizationLayer reluLayer convolution2dLayer(3,4*numF,'Padding','same') batchNormalizationLayer reluLayer maxPooling2dLayer([timePoolSize,1]) dropoutLayer(dropoutProb) fullyConnectedLayer(numClasses) softmaxLayer weightedClassificationLayer(classWeights)];
Задайте опции обучения. Используйте оптимизатор Adam с мини-пакетом размером 128. Обучите 25 эпох и уменьшите скорость обучения в 10 раз после 20 эпох.
miniBatchSize = 128; validationFrequency = floor(numel(YTrain)/miniBatchSize); options = trainingOptions('adam', ... 'InitialLearnRate',3e-4, ... 'MaxEpochs',25, ... 'MiniBatchSize',miniBatchSize, ... 'Shuffle','every-epoch', ... 'Plots','training-progress', ... 'Verbose',false, ... 'ValidationData',{XValidation,YValidation}, ... 'ValidationFrequency',validationFrequency, ... 'LearnRateSchedule','piecewise', ... 'LearnRateDropFactor',0.1, ... 'LearnRateDropPeriod',20);
Обучите сеть. Если у вас нет графический процессор, то обучение сети может занять время.
trainedNet = trainNetwork(XTrain,YTrain,layers,options);
Вычислите окончательную точность сети на набор обучающих данных (без увеличения данных) и наборе валидации. Сеть очень точна на этом наборе данных. Однако обучение, валидация и тестовые данные все имеют аналогичные распределения, которые не обязательно отражают реальные окружения. Это ограничение особенно относится к unknown
категория, которая содержит высказывания лишь небольшого числа слов.
if reduceDataset load('commandNet.mat','trainedNet'); end YValPred = classify(trainedNet,XValidation); validationError = mean(YValPred ~= YValidation); YTrainPred = classify(trainedNet,XTrain); trainError = mean(YTrainPred ~= YTrain); disp("Training error: " + trainError*100 + "%") disp("Validation error: " + validationError*100 + "%")
Training error: 1.907% Validation error: 5.5376%
Постройте график матрицы неточностей. Отображение точности и отзыва для каждого класса с помощью сводных данных по столбцам и строкам. Отсортируйте классы матрицы неточностей. Самая большая путаница между неизвестными словами и командами, вверх и назад, вниз и нет, и идти и нет.
figure('Units','normalized','Position',[0.2 0.2 0.5 0.5]); cm = confusionchart(YValidation,YValPred); cm.Title = 'Confusion Matrix for Validation Data'; cm.ColumnSummary = 'column-normalized'; cm.RowSummary = 'row-normalized'; sortClasses(cm, [commands,"unknown","background"])
При работе с приложениями с ограниченными аппаратными ресурсами, такими как мобильные приложения, учитывайте ограничения на доступную память и вычислительные ресурсы. Вычислите общий размер сети в килобайтах и протестируйте ее скорость предсказания при использовании центральный процессор. Время предсказания является временем для классификации одного входного изображения. Если вы вводите несколько изображений в сеть, они могут быть классифицированы одновременно, что приводит к более короткому времени предсказания на изображение. Однако при классификации передачи потокового аудио наиболее актуальным является время предсказания с одним изображением.
info = whos('trainedNet'); disp("Network size: " + info.bytes/1024 + " kB") for i = 1:100 x = randn([numHops,numBands]); tic [YPredicted,probs] = classify(trainedNet,x,"ExecutionEnvironment",'cpu'); time(i) = toc; end disp("Single-image prediction time on CPU: " + mean(time(11:end))*1000 + " ms")
Network size: 286.7402 kB Single-image prediction time on CPU: 2.5119 ms
[1] Warden P. «Speech Commands: A public dataset for single-word speech recognition», 2017. Доступно из https://storage.googleapis.com/download.tensorflow.org/data/speech_commands_v0.01.tar.gz. Копирайт Google 2017. Набор данных Speech Commands лицензирован по лицензии Creative Commons Attribution 4.0, доступной здесь: https://creativecommons.org/licenses/by/4.0/legalcode.