Распознавание разговорных цифр с вейвлет и глубоким обучением

Этот пример показов, как классифицировать разговорные цифры с помощью как машинного, так и глубокого метода обучения. В примере вы выполняете классификацию, используя рассеяние вейвлета времени с помощью машины опорных векторов (SVM) и с помощью сети долгой краткосрочной памяти (LSTM). Вы также применяете байесовскую оптимизацию, чтобы определить подходящие гиперпараметры, чтобы улучшить точность сети LSTM. Кроме сложения, пример иллюстрирует подход, использующий глубокую сверточную нейронную сеть (CNN) и мел-частотные спектрограммы.

Данные

Клон или скачать бесплатный набор данных Spoken Digit (FSDD), доступный в https://github.com/Jakobovski/free-spoken-digit-dataset. FSDD является открытым набором данных, что означает, что он может расти с течением времени. Этот пример использует версию, совершенную 29 января 2019 года, которая состоит из 2000 записей на английском языке цифр от 0 до 9, полученных от четырёх дикторов. В этой версии два носителя являются носителями американского английского языка, один носитель является неонативным носителем английского языка с бельгийским французским акцентом, а один носитель является неонативным носителем английского с немецким акцентом. Данные отбираются с частотой дискретизации 8000 Гц.

Использование audioDatastore управлять доступом к данным и обеспечивать случайное деление записей на обучающие и тестовые наборы. Установите location свойство расположения папки записей FSDD на вашем компьютере, например:

pathToRecordingsFolder = fullfile(tempdir,'free-spoken-digit-dataset-master','recordings');
location = pathToRecordingsFolder;

Точечные audioDatastore в это место.

ads = audioDatastore(location);

Функция помощника helpergenLabels создает категориальный массив меток из файлов FSDD. Исходный код для helpergenLabels приведено в приложении. Перечислите классы и количество примеров в каждом классе.

ads.Labels = helpergenLabels(ads);
summary(ads.Labels)
     0      300 
     1      300 
     2      300 
     3      300 
     4      300 
     5      300 
     6      300 
     7      300 
     8      300 
     9      300 

Набор данных FSDD состоит из 10 сбалансированных классов с 200 записями каждый. Записи в FSDD не имеют равной длительности. FSDD не является запрещенно большим, поэтому считывайте файлы FSDD и создайте гистограмму длин сигнала.

LenSig = zeros(numel(ads.Files),1);
nr = 1;
while hasdata(ads)
    digit = read(ads);
    LenSig(nr) = numel(digit);
    nr = nr+1;
end
reset(ads)
histogram(LenSig)
grid on
xlabel('Signal Length (Samples)')
ylabel('Frequency')

Гистограмма показывает, что распределение длин записи положительно искажено. Для классификации в этом примере используется общая длина сигнала 8192 выборок, консервативное значение, которое гарантирует, что усечение более длинных записей не отсекает содержимое речи. Если длина сигнала превышает 8192 выборки (1,024 секунды), запись обрезается до 8192 выборки. Если длина сигнала меньше 8192 отсчетов, сигнал предварительно складывается и откладывается симметрично нулями до длины 8192 отсчетов.

Вейвлет вейвлет-времени

Использование waveletScattering (Wavelet Toolbox), чтобы создать среду вейвлет рассеяния с помощью инвариантной шкалы 0,22 секунды. В этом примере вы создаете векторы функций путем усреднения преобразования рассеяния во всех временных выборках. Чтобы иметь достаточное количество коэффициентов рассеяния в каждом временном окне для среднего значения, установите OversamplingFactor 2 получить четырехкратное увеличение числа коэффициентов рассеяния для каждого пути относительно критически пониженного значения.

sf = waveletScattering('SignalLength',8192,'InvarianceScale',0.22,...
    'SamplingFrequency',8000,'OversamplingFactor',2);

Разделите FSDD на обучающие и тестовые наборы. Выделите 80% данных наборов обучающих данных и сохраните 20% для тестового набора. Обучающие данные предназначены для настройки классификатора на основе преобразования рассеяния. Тестовые данные предназначены для валидации модели.

rng default;
ads = shuffle(ads);
[adsTrain,adsTest] = splitEachLabel(ads,0.8);
countEachLabel(adsTrain)
ans=10×2 table
    Label    Count
    _____    _____

      0       240 
      1       240 
      2       240 
      3       240 
      4       240 
      5       240 
      6       240 
      7       240 
      8       240 
      9       240 

countEachLabel(adsTest)
ans=10×2 table
    Label    Count
    _____    _____

      0       60  
      1       60  
      2       60  
      3       60  
      4       60  
      5       60  
      6       60  
      7       60  
      8       60  
      9       60  

Функция помощника helperReadSPData обрезает или заполняет данные до длины 8192 и нормализует каждую запись по ее максимальному значению. Исходный код для helperReadSPData приведено в приложении. Создайте матрицу 8192 на 1600, где каждый столбец является записью разговорных цифр.

Xtrain = [];
scatds_Train = transform(adsTrain,@(x)helperReadSPData(x));
while hasdata(scatds_Train)
    smat = read(scatds_Train);
    Xtrain = cat(2,Xtrain,smat);
    
end

Повторите процесс для тестового набора. Получившаяся матрица 8192 на 400.

Xtest = [];
scatds_Test = transform(adsTest,@(x)helperReadSPData(x));
while hasdata(scatds_Test)
    smat = read(scatds_Test);
    Xtest = cat(2,Xtest,smat);
    
end

Примените преобразование вейвлет к обучающим и тестовым наборам.

Strain = sf.featureMatrix(Xtrain);
Stest = sf.featureMatrix(Xtest);

Получите средние функции рассеяния для обучающих и тестовых наборов. Исключить коэффициенты рассеяния нулевого порядка.

TrainFeatures = Strain(2:end,:,:);
TrainFeatures = squeeze(mean(TrainFeatures,2))';
TestFeatures = Stest(2:end,:,:);
TestFeatures = squeeze(mean(TestFeatures,2))';

Классификатор SVM

Теперь, когда данные были сведены к вектору функций для каждой записи, следующим шагом является использование этих функций для классификации записей. Создайте шаблон SVM с квадратичным полиномиальным ядром. Подбор SVM к обучающим данным.

template = templateSVM(...
    'KernelFunction', 'polynomial', ...
    'PolynomialOrder', 2, ...
    'KernelScale', 'auto', ...
    'BoxConstraint', 1, ...
    'Standardize', true);
classificationSVM = fitcecoc(...
    TrainFeatures, ...
    adsTrain.Labels, ...
    'Learners', template, ...
    'Coding', 'onevsone', ...
    'ClassNames', categorical({'0'; '1'; '2'; '3'; '4'; '5'; '6'; '7'; '8'; '9'}));

Используйте перекрестную валидацию k-fold, чтобы предсказать точность обобщения модели на основе обучающих данных. Разделите набор обучающих данных на пять групп.

partitionedModel = crossval(classificationSVM, 'KFold', 5);
[validationPredictions, validationScores] = kfoldPredict(partitionedModel);
validationAccuracy = (1 - kfoldLoss(partitionedModel, 'LossFun', 'ClassifError'))*100
validationAccuracy = 97.4167

Предполагаемая точность обобщения составляет приблизительно 97%. Используйте обученный SVM, чтобы предсказать классы разговорных цифр в тестовом наборе.

predLabels = predict(classificationSVM,TestFeatures);
testAccuracy = sum(predLabels==adsTest.Labels)/numel(predLabels)*100
testAccuracy = 97.1667

Результирующая эффективность модели на тестовом наборе с графиком неточностей. Отображение точности и отзыва для каждого класса с помощью сводных данных по столбцам и строкам. Таблица в нижней части графика неточностей показывает значения точности для каждого класса. Таблица справа от графика неточностей показывает значения отзыва.

figure('Units','normalized','Position',[0.2 0.2 0.5 0.5]);
ccscat = confusionchart(adsTest.Labels,predLabels);
ccscat.Title = 'Wavelet Scattering Classification';
ccscat.ColumnSummary = 'column-normalized';
ccscat.RowSummary = 'row-normalized';

Преобразование рассеяния, связанное с классификатором SVM, классифицирует разговорные цифры в тестовом наборе с точностью 98% (или частотой ошибок 2%).

Сети долгой краткосрочной памяти (LSTM)

Сеть LSTM является типом рекуррентной нейронной сети (RNN). RNN являются нейронными сетями, которые специализируются на работе с последовательными или временными данными, такими как речевые данные. Поскольку вейвлеты коэффициенты рассеяния являются последовательностями, они могут использоваться как входы к LSTM. Используя функции рассеяния в отличие от необработанных данных, можно уменьшить изменчивость, которую должна изучать сеть.

Измените функции обучения и тестирования рассеяния, которые будут использоваться в сети LSTM. Исключить коэффициенты рассеяния нулевого порядка и преобразовать функции в массивы ячеек.

TrainFeatures = Strain(2:end,:,:);
TrainFeatures = squeeze(num2cell(TrainFeatures,[1 2]));
TestFeatures = Stest(2:end,:,:);
TestFeatures = squeeze(num2cell(TestFeatures, [1 2]));

Создайте простую сеть LSTM с 512 скрытыми слоями.

[inputSize, ~] = size(TrainFeatures{1});
YTrain = adsTrain.Labels;

numHiddenUnits = 512;
numClasses = numel(unique(YTrain));

layers = [ ...
    sequenceInputLayer(inputSize)
    lstmLayer(numHiddenUnits,'OutputMode','last')
    fullyConnectedLayer(numClasses)
    softmaxLayer
    classificationLayer];

Установите гиперпараметры. Используйте оптимизацию Adam и мини-пакет размером 50. Установите максимальное количество эпох равным 300. Используйте скорость обучения 1e-4. Можно выключить график процесса обучения, если вы не хотите отслеживать прогресс с помощью графиков. Обучение по умолчанию использует графический процессор, если он доступен. В противном случае используется центральный процессор. Для получения дополнительной информации смотрите trainingOptions (Deep Learning Toolbox).

maxEpochs = 300;
miniBatchSize = 50;

options = trainingOptions('adam', ...
    'InitialLearnRate',0.0001,...
    'MaxEpochs',maxEpochs, ...
    'MiniBatchSize',miniBatchSize, ...
    'SequenceLength','shortest', ...
    'Shuffle','every-epoch',...
    'Verbose', false, ...
    'Plots','training-progress');

Обучите сеть.

net = trainNetwork(TrainFeatures,YTrain,layers,options);
predLabels = classify(net,TestFeatures);
testAccuracy = sum(predLabels==adsTest.Labels)/numel(predLabels)*100
testAccuracy = 96.3333

Байесовская оптимизация

Определение подходящих установок гиперпараметров часто является одной из самых сложных частей обучения глубокой сети. Чтобы уменьшить это, можно использовать байесовскую оптимизацию. В этом примере вы оптимизируете количество скрытых слоев и начальную скорость обучения с помощью байесовских методов. Создайте новую директорию для хранения MAT-файлов, содержащих информацию о установках гиперпараметров и сети вместе с соответствующей частотой ошибок.

YTrain = adsTrain.Labels;
YTest = adsTest.Labels;

if ~exist("results/",'dir')
    mkdir results
end

Инициализируйте оптимизированные переменные и их области значений значений. Поскольку количество скрытых слоев должно быть целым числом, задайте 'type' на 'integer'.

optVars = [
    optimizableVariable('InitialLearnRate',[1e-5, 1e-1],'Transform','log')
    optimizableVariable('NumHiddenUnits',[10, 1000],'Type','integer')
    ];

Байесовская оптимизация является вычислительно интенсивной и может занять несколько часов, чтобы закончить. В целях этого примера задайте optimizeCondition на false загрузку и использование предопределенных оптимизированных установок гиперпараметров. Если вы задаете optimizeCondition на true, целевая функция helperBayesOptLSTM минимизируется с помощью байесовской оптимизации. Целевой функцией, перечисленной в приложении, является вероятность ошибок сети при заданных установках гиперпараметров. Загруженные настройки предназначены для целевой функции минимум 0,02 (2% вероятность ошибки).

ObjFcn = helperBayesOptLSTM(TrainFeatures,YTrain,TestFeatures,YTest);

optimizeCondition = false;
if optimizeCondition
    BayesObject = bayesopt(ObjFcn,optVars,...
            'MaxObjectiveEvaluations',15,...
            'IsObjectiveDeterministic',false,...
            'UseParallel',true);
else
    url = 'http://ssd.mathworks.com/supportfiles/audio/SpokenDigitRecognition.zip';
    downloadNetFolder = tempdir;
    netFolder = fullfile(downloadNetFolder,'SpokenDigitRecognition');
    if ~exist(netFolder,'dir')
        disp('Downloading pretrained network (1 file - 12 MB) ...')
        unzip(url,downloadNetFolder)
    end
    load(fullfile(netFolder,'0.02.mat'));
end
Downloading pretrained network (1 file - 12 MB) ...

Если вы выполняете байесовскую оптимизацию, генерируются цифры, подобные следующим, чтобы отследить значения целевой функции с соответствующими значениями гиперзначений параметров и количеством итераций. Можно увеличить количество итераций оптимизации Байеса, чтобы убедиться, что достигнут глобальный минимум целевой функции.

Используйте оптимизированные значения для количества скрытых модулей и начальной скорости обучения и переобучите сеть.

numHiddenUnits = 768;
numClasses = numel(unique(YTrain));

layers = [ ...
    sequenceInputLayer(inputSize)
    lstmLayer(numHiddenUnits,'OutputMode','last')
    fullyConnectedLayer(numClasses)
    softmaxLayer
    classificationLayer];

maxEpochs = 300;
miniBatchSize = 50;

options = trainingOptions('adam', ...
    'InitialLearnRate',2.198827960269379e-04,...
    'MaxEpochs',maxEpochs, ...
    'MiniBatchSize',miniBatchSize, ...
    'SequenceLength','shortest', ...
    'Shuffle','every-epoch',...
    'Verbose', false, ...
    'Plots','training-progress');

net = trainNetwork(TrainFeatures,YTrain,layers,options);
predLabels = classify(net,TestFeatures);
testAccuracy = sum(predLabels==adsTest.Labels)/numel(predLabels)*100
testAccuracy = 97.5000

Как показывает график, использование байесовской оптимизации приводит к LSTM с более высокой точностью.

Глубокая сверточная сеть с использованием Mel-частотных спектрограмм

В качестве другого подхода к задаче распознавания разговорных цифр используйте глубокую сверточную нейронную сеть (DCNN), основанную на спектрограммах мел-частоты, для классификации набора данных FSDD. Используйте ту же процедуру усечения/заполнения сигнала, что и в преобразовании рассеяния. Точно так же нормализуйте каждую запись путем деления каждой выборки сигнала на максимальное абсолютное значение. Для консистенции используйте те же обучающие и тестовые наборы, что и для преобразования рассеяния.

Установите параметры для спектрограмм мел-частоты. Используйте то же окно, или систему координат, длительность, что и в преобразовании рассеяния, 0,22 секунды. Установите скачок между окнами на 10 мс. Используйте 40 полосы частот.

segmentDuration = 8192*(1/8000);
frameDuration = 0.22;
hopDuration = 0.01;
numBands = 40;

Сбросьте обучающие и тестовые хранилища данных.

reset(adsTrain);
reset(adsTest);

Функция помощника helperspeechSpectrograms, заданный в конце этого примера, использует melSpectrogram получить спектрограмму мель-частоты после стандартизации длины записи и нормализации амплитуды. Используйте логарифм спектрограмм mel-частоты в качестве входов для DCNN. Чтобы избежать взятия логарифма нуля, добавьте небольшой эпсилон к каждому элементу.

epsil = 1e-6;
XTrain = helperspeechSpectrograms(adsTrain,segmentDuration,frameDuration,hopDuration,numBands);
Computing speech spectrograms...
Processed 500 files out of 2400
Processed 1000 files out of 2400
Processed 1500 files out of 2400
Processed 2000 files out of 2400
...done
XTrain = log10(XTrain + epsil);

XTest = helperspeechSpectrograms(adsTest,segmentDuration,frameDuration,hopDuration,numBands);
Computing speech spectrograms...
Processed 500 files out of 600
...done
XTest = log10(XTest + epsil);

YTrain = adsTrain.Labels;
YTest = adsTest.Labels;

Определение архитектуры DCNN

Создайте небольшой DCNN как массив слоев. Используйте сверточные и пакетные слои нормализации и понижающее отображение карт признаков с помощью максимальных слоев объединения. Чтобы уменьшить возможность запоминания сетью специфических функций обучающих данных, добавьте небольшое количество отсева на вход к последнему полностью подключенному слою.

sz = size(XTrain);
specSize = sz(1:2);
imageSize = [specSize 1];

numClasses = numel(categories(YTrain));

dropoutProb = 0.2;
numF = 12;
layers = [
    imageInputLayer(imageSize)

    convolution2dLayer(5,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(2)

    dropoutLayer(dropoutProb)
    fullyConnectedLayer(numClasses)
    softmaxLayer
    classificationLayer('Classes',categories(YTrain));
    ];

Установите гиперпараметры, которые будут использоваться при обучении сети. Используйте мини-пакет размером 50 и скоростью обучения 1e-4. Задайте оптимизацию Адама. Поскольку объем данных в этом примере относительно невелик, установите окружение выполнения на 'cpu' для воспроизводимости. Можно также обучить сеть на доступном графическом процессоре, установив окружение выполнения на 'gpu' или 'auto'. Для получения дополнительной информации смотрите trainingOptions (Deep Learning Toolbox).

miniBatchSize = 50;
options = trainingOptions('adam', ...
    'InitialLearnRate',1e-4, ...
    'MaxEpochs',30, ...
    'MiniBatchSize',miniBatchSize, ...
    'Shuffle','every-epoch', ...
    'Plots','training-progress', ...
    'Verbose',false, ...
    'ExecutionEnvironment','cpu');

Обучите сеть.

trainedNet = trainNetwork(XTrain,YTrain,layers,options);

Используйте обученную сеть, чтобы предсказать метки цифр для тестового набора.

[Ypredicted,probs] = classify(trainedNet,XTest,'ExecutionEnvironment','CPU');
cnnAccuracy = sum(Ypredicted==YTest)/numel(YTest)*100
cnnAccuracy = 98.1667

Суммируйте эффективность обученной сети на тестовом наборе с графиком неточностей. Отображение точности и отзыва для каждого класса с помощью сводных данных по столбцам и строкам. Таблица в нижней части графика неточностей показывает значения точности. Таблица справа от графика неточностей показывает значения отзыва.

figure('Units','normalized','Position',[0.2 0.2 0.5 0.5]);
ccDCNN = confusionchart(YTest,Ypredicted);
ccDCNN.Title = 'Confusion Chart for DCNN';
ccDCNN.ColumnSummary = 'column-normalized';
ccDCNN.RowSummary = 'row-normalized';

DCNN, использующий mel-частотные спектрограммы в качестве входов, классифицирует разговорные цифры в тестовом наборе с частотой точности также приблизительно 98%.

Сводные данные

В этом примере показано, как использовать различные методы машинного и глубокого обучения для классификации разговорных цифр в FSDD. Пример иллюстрировал вейвлет в паре как с SVM, так и с LSTM. Байесовские методы использовались для оптимизации гиперпараметров LSTM. Наконец, пример показывает, как использовать CNN с мел-частотными спектрограммами.

Цель примера состоит в том, чтобы продемонстрировать, как использовать инструменты MathWorks ® для решения задачи принципиально различными, но взаимодополняющими способами. Все рабочие процессы используют audioDatastore управлять потоком данных с диска и обеспечивать правильную рандомизацию.

Все подходы, используемые в этом примере, одинаково хорошо выполняются на тестовом наборе. Этот пример не предназначен как прямое сравнение между различными подходами. Например, можно также использовать байесовскую оптимизацию для выбора гиперпараметра в CNN. Дополнительная стратегия, которая полезна в глубоком обучении с небольшими наборами обучающих данных, такими как эта версия FSDD, заключается в использовании увеличения данных. Как манипуляции влияют на класс, не всегда известно, поэтому увеличение данных не всегда допустимо. Однако для речи установленные стратегии увеличения данных доступны через audioDataAugmenter.

В случае вейвлет времени существует также ряд модификаций, которые можно попробовать. Для примера можно изменить инвариантный масштаб преобразования, изменить количество вейвлета фильтров на банк фильтров и попробовать другие классификаторы.

Приложение: Функции помощника

function Labels = helpergenLabels(ads)
% This function is only for use in Wavelet Toolbox examples. It may be
% changed or removed in a future release.
tmp = cell(numel(ads.Files),1);
expression = "[0-9]+_";
for nf = 1:numel(ads.Files)
    idx = regexp(ads.Files{nf},expression);
    tmp{nf} = ads.Files{nf}(idx);
end
Labels = categorical(tmp);
end
function x = helperReadSPData(x)
% This function is only for use Wavelet Toolbox examples. It may change or
% be removed in a future release.

N = numel(x);
if N > 8192
    x = x(1:8192);
elseif N < 8192
    pad = 8192-N;
    prepad = floor(pad/2);
    postpad = ceil(pad/2);
    x = [zeros(prepad,1) ; x ; zeros(postpad,1)];
end
x = x./max(abs(x));

end
function x = helperBayesOptLSTM(X_train, Y_train, X_val, Y_val)
% This function is only for use in the
% "Spoken Digit Recognition with Wavelet Scattering and Deep Learning"
% example. It may change or be removed in a future release.
x = @valErrorFun;

    function [valError,cons, fileName] = valErrorFun(optVars)
        %% LSTM Architecture
        [inputSize,~] = size(X_train{1});
        numClasses = numel(unique(Y_train));

        layers = [ ...
            sequenceInputLayer(inputSize)
            bilstmLayer(optVars.NumHiddenUnits,'OutputMode','last') % Using number of hidden layers value from optimizing variable
            fullyConnectedLayer(numClasses)
            softmaxLayer
            classificationLayer];
        
        % Plots not displayed during training
        options = trainingOptions('adam', ...
            'InitialLearnRate',optVars.InitialLearnRate, ... % Using initial learning rate value from optimizing variable
            'MaxEpochs',300, ...
            'MiniBatchSize',30, ...
            'SequenceLength','shortest', ...
            'Shuffle','never', ...
            'Verbose', false);
        
        %% Train the network
        net = trainNetwork(X_train, Y_train, layers, options);
        %% Training accuracy
        X_val_P = net.classify(X_val);
        accuracy_training  = sum(X_val_P == Y_val)./numel(Y_val);
        valError = 1 - accuracy_training;
        %% save results of network and options in a MAT file in the results folder along with the error value
        fileName = fullfile('results', num2str(valError) + ".mat");
        save(fileName,'net','valError','options')     
        cons = [];
    end % end for inner function
end % end for outer function
function X = helperspeechSpectrograms(ads,segmentDuration,frameDuration,hopDuration,numBands)
% This function is only for use in the 
% "Spoken Digit Recognition with Wavelet Scattering and Deep Learning"
% example. It may change or be removed in a future release.
%
% helperspeechSpectrograms(ads,segmentDuration,frameDuration,hopDuration,numBands)
% computes speech spectrograms for the files in the datastore ads.
% segmentDuration is the total duration of the speech clips (in seconds),
% frameDuration the duration of each spectrogram frame, hopDuration the
% time shift between each spectrogram frame, and numBands the number of
% frequency bands.
disp("Computing speech spectrograms...");

numHops = ceil((segmentDuration - frameDuration)/hopDuration);
numFiles = length(ads.Files);
X = zeros([numBands,numHops,1,numFiles],'single');

for i = 1:numFiles
    
    [x,info] = read(ads);
    x = normalizeAndResize(x);
    fs = info.SampleRate;
    frameLength = round(frameDuration*fs);
    hopLength = round(hopDuration*fs);
    
    spec = melSpectrogram(x,fs, ...
        'Window',hamming(frameLength,'periodic'), ...
        'OverlapLength',frameLength - hopLength, ...
        'FFTLength',2048, ...
        'NumBands',numBands, ...
        'FrequencyRange',[50,4000]);
    
    % If the spectrogram is less wide than numHops, then put spectrogram in
    % the middle of X.
    w = size(spec,2);
    left = floor((numHops-w)/2)+1;
    ind = left:left+w-1;
    X(:,ind,1,i) = spec;
    
    if mod(i,500) == 0
        disp("Processed " + i + " files out of " + numFiles)
    end
    
end

disp("...done");

end

%--------------------------------------------------------------------------
function x = normalizeAndResize(x)
% This function is only for use in the 
% "Spoken Digit Recognition with Wavelet Scattering and Deep Learning"
% example. It may change or be removed in a future release.

N = numel(x);
if N > 8192
    x = x(1:8192);
elseif N < 8192
    pad = 8192-N;
    prepad = floor(pad/2);
    postpad = ceil(pad/2);
    x = [zeros(prepad,1) ; x ; zeros(postpad,1)];
end
x = x./max(abs(x));
end

Копирайт 2018, The MathWorks, Inc.