Классификация видео с использованием глубокого обучения с помощью пользовательского цикла обучения

В этом примере показано, как создать сеть для классификации видео путем объединения предварительно обученной модели классификации изображений и сети классификации последовательностей.

Вы можете выполнить классификацию видео, не используя пользовательский цикл обучения, используя trainNetwork функция. Для получения примера смотрите Классификация видео Используя Глубокое Обучение. Однако, Если trainingOptions не предоставляет необходимые опции (например, пользовательское расписание скорости обучения), тогда можно задать свой собственный пользовательский цикл обучения, как показано в этом примере.

Чтобы создать нейронную сеть для глубокого обучения для классификации видео:

  1. Преобразуйте видео в последовательности векторов функций с помощью предварительно обученной сверточной нейронной сети, такой как GoogLeNet, чтобы извлечь функции из каждой системы координат.

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

  3. Соберите сеть, которая классифицирует видео непосредственно путем объединения слоев из обеих сетей.

Следующая схема иллюстрирует сетевую архитектуру:

  • Чтобы ввести последовательности изображений в сеть, используйте входной слой последовательности.

  • Чтобы извлечь функции из последовательностей изображений, используйте сверточные слои из предварительно обученной сети GoogLeNet.

  • Чтобы классифицировать получившиеся векторные последовательности, включите слои классификации последовательностей.

При обучении этого типа сети с trainNetwork функция (не выполняется в этом примере), вы должны использовать последовательность складывания и развертывания слоев, чтобы обрабатывать видеокадры независимо. Когда вы обучаете этот тип сети с dlnetwork объект и пользовательский цикл обучения (как в этом примере), складывание и развертывание слоев последовательности не требуются, потому что сеть использует информацию о размерности, заданную dlarray метки размерностей.

Загрузка предварительно обученной сверточной сети

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

Загрузите предварительно обученную модель GoogLeNet с помощью googlenet функция. Этой функции требуется Модель Deep Learning Toolbox™ для пакета поддержки GoogLeNet Network. Если этот пакет поддержки не установлен, то функция предоставляет ссылку на загрузку.

netCNN = googlenet;

Загрузка данных

Загрузите набор HMBD51 данных из HMDB: большой базы данных движения человека и извлечите файл RAR в папку с именем "hmdb51_org". Набор данных содержит около 2 ГБ видеоданных для 7000 клипов в 51 классе, таких как "drink", "run", и "shake_hands".

После извлечения файла RAR убедитесь, что папка hmdb51_org содержит подпапки, названные в честь движений тела. Если он содержит файлы RAR, необходимо также извлечь их. Используйте вспомогательную функцию hmdb51Files для получения имен файлов и меток видео. Чтобы ускорить обучение за счет точности, укажите долю в области значений [0 1], чтобы считать только случайный подмножество файлов из базы данных. Если на fraction входной параметр не задан, функция hmdb51Files считывает полный набор данных, не меняя порядок файлов.

dataFolder = "hmdb51_org";
fraction = 1;
[files,labels] = hmdb51Files(dataFolder,fraction);

Прочтите первое видео с помощью readVideo helper, заданная в конце этого примера, и просматривать размер видео. Видео является массивом H-на-W-на-C-на-T, где H, W, C и T являются высотой, шириной, количеством каналов и количеством систем координат видео, соответственно.

idx = 1;
filename = files(idx);
video = readVideo(filename);
size(video)
ans = 1×4

   240   352     3   115

Просмотрите соответствующую метку.

labels(idx)
ans = categorical
     shoot_ball 

Чтобы просмотреть видео, закольцовывайте отдельные системы координат и используйте image функция. Также можно использовать implay функция (требует Image Processing Toolbox).

numFrames = size(video,4);
figure
for i = 1:numFrames
    frame = video(:,:,:,i);
    image(frame);
    xticklabels([]);
    yticklabels([]);
    drawnow
end

Преобразуйте системы координат в векторы объектов

Используйте сверточную сеть как экстрактор функций: вводите видеокадры в сеть и извлекайте активации. Преобразуйте видео в последовательности векторов функций, где векторы функций являются выходными данными activations функция на последнем слое объединения сети GoogLeNet ("pool5-7x7_s1").

Эта схема иллюстрирует поток данных через сеть.

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

inputSize = netCNN.Layers(1).InputSize(1:2);
layerName = "pool5-7x7_s1";

tempFile = fullfile(tempdir,"hmdb51_org.mat");

overwriteSequences = false;

if exist(tempFile,'file') && ~overwriteSequences
    load(tempFile)
else
    numFiles = numel(files);
    sequences = cell(numFiles,1);
    
    for i = 1:numFiles
        fprintf("Reading file %d of %d...\n", i, numFiles)
        
        video = readVideo(files(i));
        video = imresize(video,inputSize);
        sequences{i,1} = activations(netCNN,video,layerName,'OutputAs','columns');
        
    end
    % Save the sequences and the labels associated with them.
    save(tempFile,"sequences","labels","-v7.3");
end

Просмотрите размеры первых нескольких последовательностей. Каждая последовательность является массивом D-на-T, где D - количество функций (выход размер слоя объединения), а T - количество систем координат видео.

sequences(1:10)
ans=10×1 cell array
    {1024×115 single}
    {1024×227 single}
    {1024×180 single}
    {1024×40  single}
    {1024×60  single}
    {1024×156 single}
    {1024×83  single}
    {1024×42  single}
    {1024×82  single}
    {1024×110 single}

Подготовка обучающих данных

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

Создайте разделы для обучения и валидации

Разделите данные на разделы. Присвойте 90% данных обучающему разделу и 10% - разделу валидации.

numObservations = numel(sequences);
idx = randperm(numObservations);
N = floor(0.9 * numObservations);

idxTrain = idx(1:N);
sequencesTrain = sequences(idxTrain);
labelsTrain = labels(idxTrain);

idxValidation = idx(N+1:end);
sequencesValidation = sequences(idxValidation);
labelsValidation = labels(idxValidation);

Удаление длинных последовательностей

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

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

numObservationsTrain = numel(sequencesTrain);
sequenceLengths = zeros(1,numObservationsTrain);

for i = 1:numObservationsTrain
    sequence = sequencesTrain{i};
    sequenceLengths(i) = size(sequence,2);
end

figure
histogram(sequenceLengths)
title("Sequence Lengths")
xlabel("Sequence Length")
ylabel("Frequency")

Только несколько последовательностей имеют более 400 временных шагов. Чтобы улучшить точность классификации, удалите обучающие последовательности, которые имеют более 400 временных шагов вместе с соответствующими метками.

maxLength = 400;
idx = sequenceLengths > maxLength;
sequencesTrain(idx) = [];
labelsTrain(idx) = [];

Создайте Datastore для данных

Создайте arrayDatastore объект для последовательностей и меток, а затем объединяет их в один datastore.

dsXTrain = arrayDatastore(sequencesTrain,'OutputType','same');
dsYTrain = arrayDatastore(labelsTrain,'OutputType','cell');

dsTrain = combine(dsXTrain,dsYTrain);

Определите классы в обучающих данных.

classes = categories(labelsTrain);

Создайте сеть классификации последовательностей

Затем создайте сеть классификации последовательностей, которая может классифицировать последовательности векторов функций, представляющих видео.

Определите архитектуру сети классификации последовательностей. Задайте следующие слои сети:

  • Входной слой последовательности с размером входа, соответствующей размерностью признаков векторов функции.

  • Слой BiLSTM с 2000 скрытыми модулями с выпадающим слоем после. Чтобы вывести только одну метку для каждой последовательности, установите 'OutputMode' опция слоя BiLSTM для 'last'.

  • Выпадающий слой с вероятностью 0,5.

  • Полносвязный слой с размером выхода, соответствующим количеству классов, и слоем softmax.

numFeatures = size(sequencesTrain{1},1);
numClasses = numel(categories(labelsTrain));

layers = [
    sequenceInputLayer(numFeatures,'Name','sequence')
    bilstmLayer(2000,'OutputMode','last','Name','bilstm')
    dropoutLayer(0.5,'Name','drop')
    fullyConnectedLayer(numClasses,'Name','fc')
    softmaxLayer('Name','softmax')
    ];

Преобразуйте слои в layerGraph объект.

lgraph = layerGraph(layers);

Создайте dlnetwork объект из графика слоев.

dlnet = dlnetwork(lgraph);

Настройка опций обучения

Обучите на 15 эпох и укажите мини-партию размером 16.

numEpochs = 15;
miniBatchSize = 16;

Задайте опции для оптимизации Adam. Задайте начальную скорость обучения 1e-4 с распадом 0,001, градиентным распадом 0,9 и квадратным градиентным распадом 0,999.

initialLearnRate = 1e-4;
decay = 0.001;
gradDecay = 0.9;
sqGradDecay = 0.999;

Визуализируйте процесс обучения на графике.

plots = "training-progress";

Обучите сеть классификации последовательностей

Создайте minibatchqueue объект, который обрабатывает и управляет мини-пакетами последовательностей во время обучения. Для каждого мини-пакета:

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

  • Форматируйте данные векторной последовательности с помощью меток размерностей 'CTB' (канал, время, пакет). По умолчанию в minibatchqueue объект преобразует данные в dlarray объекты с базовым типом single. Не добавляйте формат к меткам классов.

  • Обучите на графическом процессоре, если он доступен. По умолчанию в minibatchqueue объект преобразует каждый выход в gpuArray объект, если доступен графический процессор. Для использования графический процессор требуется Parallel Computing Toolbox™ и поддерживаемый графический процессор. Для получения информации о поддерживаемых устройствах смотрите Поддержку GPU by Release (Parallel Computing Toolbox).

mbq = minibatchqueue(dsTrain,...
    'MiniBatchSize',miniBatchSize,...
    'MiniBatchFcn', @preprocessLabeledSequences,...
    'MiniBatchFormat',{'CTB',''});

Инициализируйте график процесса обучения.

if plots == "training-progress"
    figure
    lineLossTrain = animatedline('Color',[0.85 0.325 0.098]);
    ylim([0 inf])
    xlabel("Iteration")
    ylabel("Loss")
    grid on
end

Инициализируйте средние градиентные и средние квадратные параметры градиента для решателя Адама.

averageGrad = [];
averageSqGrad = [];

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

  • Оцените градиенты модели, состояние и потери с помощью dlfeval и modelGradients функционировать и обновлять состояние сети.

  • Определите скорость обучения для основанного на времени расписания скорости обучения с распадом: для каждой итерации решатель использует скорость обучения, заданную как ρt=ρ01+kt, где t - число итерации, ρ0 является начальной скоростью обучения, и k является распадом.

  • Обновляйте параметры сети с помощью adamupdate функция.

  • Отображение процесса обучения.

Обратите внимание, что обучение может занять много времени.

iteration = 0;
start = tic;

% Loop over epochs.
for epoch = 1:numEpochs
    % Shuffle data.
    shuffle(mbq);
    
    % Loop over mini-batches.
    while hasdata(mbq)
        iteration = iteration + 1;
        
        % Read mini-batch of data.
        [dlX, dlY] = next(mbq);
        
        % Evaluate the model gradients, state, and loss using dlfeval and the
        % modelGradients function.
        [gradients,state,loss] = dlfeval(@modelGradients,dlnet,dlX,dlY);
        
        % Determine learning rate for time-based decay learning rate schedule.
        learnRate = initialLearnRate/(1 + decay*iteration);
        
        % Update the network parameters using the Adam optimizer.
        [dlnet,averageGrad,averageSqGrad] = adamupdate(dlnet,gradients,averageGrad,averageSqGrad, ...
            iteration,learnRate,gradDecay,sqGradDecay);
        
        % Display the training progress.
        if plots == "training-progress"
            D = duration(0,0,toc(start),'Format','hh:mm:ss');
            addpoints(lineLossTrain,iteration,double(gather(extractdata(loss))))
            title("Epoch: " + epoch + " of " + numEpochs + ", Elapsed: " + string(D))
            drawnow
        end
    end
end

Экспериментальная модель

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

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

Как создать minibatchqueue объект для проверки:

  • Создайте массив datastore, содержащий только предикторы тестовых данных.

  • Укажите тот же размер мини-пакета, что и для обучения.

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

  • Для одинарного выхода datastore задайте формат пакета 'CTB' (канал, время, пакет).

dsXValidation = arrayDatastore(sequencesValidation,'OutputType','same');
mbqTest = minibatchqueue(dsXValidation, ...
    'MiniBatchSize',miniBatchSize, ...
    'MiniBatchFcn',@preprocessUnlabeledSequences, ...
    'MiniBatchFormat','CTB');

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

predictions = modelPredictions(dlnet,mbqTest,classes);

Оцените точность классификации путем сравнения предсказанных меток с истинными метками валидации.

accuracy = mean(predictions == labelsValidation)
accuracy = 0.6721

Сборка сети классификации видео

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

Следующая схема иллюстрирует сетевую архитектуру:

  • Чтобы ввести последовательности изображений в сеть, используйте входной слой последовательности.

  • Чтобы использовать сверточные слои для извлечения функций, то есть для применения сверточных операций к каждой системе координат видео независимо, используйте сверточные слои GoogLeNet.

  • Чтобы классифицировать получившиеся векторные последовательности, включите слои классификации последовательностей.

При обучении этого типа сети с trainNetwork функция (не сделана в этом примере), вы должны использовать последовательность складывания и развертывания слоев, чтобы обработать видеокадры независимо. При обучении этого типа сети с dlnetwork объект и пользовательский цикл обучения (как в этом примере), складывание и развертывание слоев последовательности не требуются, потому что сеть использует информацию о размерности, заданную dlarray метки размерностей.

Добавление сверточных слоев

Сначала создайте график слоев сети GoogLeNet.

cnnLayers = layerGraph(netCNN);

Удалите слой входа ("data") и слои после слоя объединения, используемые для активаций ("pool5-drop_7x7_s1", "loss3-classifier", "prob", и "output").

layerNames = ["data" "pool5-drop_7x7_s1" "loss3-classifier" "prob" "output"];
cnnLayers = removeLayers(cnnLayers,layerNames);

Добавление входного слоя последовательности

Создайте слой входа последовательности, который принимает последовательности изображений, содержащие изображения того же размера входа, что и сеть GoogLeNet. Чтобы нормализовать изображения, используя то же среднее изображение, что и сеть GoogLeNet, установите 'Normalization' опция входного слоя последовательности для 'zerocenter' и 'Mean' опция для среднего изображения входного слоя GoogLeNet.

inputSize = netCNN.Layers(1).InputSize(1:2);
averageImage = netCNN.Layers(1).Mean;

inputLayer = sequenceInputLayer([inputSize 3], ...
    'Normalization','zerocenter', ...
    'Mean',averageImage, ...
    'Name','input');

Добавьте входной слой последовательности к графику слоев. Соедините выход входного слоя с входом первого сверточного слоя ("conv1-7x7_s2").

lgraph = addLayers(cnnLayers,inputLayer);
lgraph = connectLayers(lgraph,"input","conv1-7x7_s2");

Добавление слоев классификации последовательностей

Добавьте предварительно обученные слои сети классификации последовательностей к графику слоев и соедините их.

Возьмите слои из сети классификации последовательностей и удалите входной слой последовательности.

lstmLayers = dlnet.Layers;
lstmLayers(1) = [];

Добавьте слои классификации последовательностей к графику слоев. Соедините последний сверточный слой pool5-7x7_s1 на bilstm слой.

lgraph = addLayers(lgraph,lstmLayers);
lgraph = connectLayers(lgraph,"pool5-7x7_s1","bilstm");

Преобразуйте в dlnetwork

Чтобы иметь возможность делать предсказания, преобразуйте график слоев в dlnetwork объект.

dlnetAssembled = dlnetwork(lgraph)
dlnetAssembled = 
  dlnetwork with properties:

         Layers: [144×1 nnet.cnn.layer.Layer]
    Connections: [170×2 table]
     Learnables: [119×3 table]
          State: [2×3 table]
     InputNames: {'input'}
    OutputNames: {'softmax'}
    Initialized: 1

Классификация с использованием новых данных

Разархивируйте файл pushup_mathworker.zip.

unzip("pushup_mathworker.zip")

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

ds = fileDatastore("pushup_mathworker", ...
    'ReadFcn',@readVideo);

Прочтите первое видео из datastore. Чтобы снова прочитать видео, сбросьте datastore.

video = read(ds);
reset(ds);

Чтобы просмотреть видео, закольцовывайте отдельные системы координат и используйте image функция. Также можно использовать implay функция (требует Image Processing Toolbox).

numFrames = size(video,4);
figure
for i = 1:numFrames
    frame = video(:,:,:,i);
    image(frame);
    xticklabels([]);
    yticklabels([]);
    drawnow
end

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

dsXTest = transform(ds,@(x) imresize(x,inputSize));

Чтобы управлять и обрабатывать немаркированные видео, создайте minibatchqueue:

  • Задайте мини-пакет размером 1.

  • Предварительная обработка видео с помощью preprocessUnlabeledVideos вспомогательная функция, перечисленная в конце примера.

  • Для одинарного выхода datastore задайте формат пакета 'SSCTB' (пространственный, пространственный, канал, время, пакет).

mbqTest = minibatchqueue(dsXTest,...
    'MiniBatchSize',1,...
    'MiniBatchFcn', @preprocessUnlabeledVideos,...
    'MiniBatchFormat',{'SSCTB'});

Классификация видео с помощью modelPredictions вспомогательная функция, заданная в конце этого примера. Функция ожидает трёх входов: a dlnetwork объект, a minibatchqueue Объект и массив ячеек, содержащий сетевые классы.

[predictions] = modelPredictions(dlnetAssembled,mbqTest,classes)
predictions = categorical
     pushup 

Вспомогательные функции

Функция чтения видео

The readVideo функция считывает видео в filename и возвращает H-на-W-на-C -массив by-T, где H, W, C и T являются высотой, шириной, количеством каналов и количеством систем координат видео, соответственно.

function video = readVideo(filename)

vr = VideoReader(filename);
H = vr.Height;
W = vr.Width;
C = 3;

% Preallocate video array
numFrames = floor(vr.Duration * vr.FrameRate);
video = zeros(H,W,C,numFrames,'uint8');

% Read frames
i = 0;
while hasFrame(vr)
    i = i + 1;
    video(:,:,:,i) = readFrame(vr);
end

% Remove unallocated frames
if size(video,4) > i
    video(:,:,:,i+1:end) = [];
end

end

Функция градиентов модели

The modelGradients функция принимает как вход dlnetwork dlnet объекта и мини-пакет входных данных dlX с соответствующими метками Y, и возвращает градиенты потерь относительно настраиваемых параметров в dlnet, состояние сети и потери. Чтобы вычислить градиенты автоматически, используйте dlgradient функция.

function [gradients,state,loss] = modelGradients(dlnet,dlX,Y)

    [dlYPred,state] = forward(dlnet,dlX);
    
    loss = crossentropy(dlYPred,Y);
    gradients = dlgradient(loss,dlnet.Learnables);

end

Функция предсказаний модели

The modelPredictions функция принимает как вход dlnetwork dlnet объекта, а minibatchqueue объект входных данных mbq, и сетевых классов, и вычисляет предсказания модели путем итерации по всем данным в очереди мини-пакетов. Функция использует onehotdecode функция для поиска предсказанного класса с самым высоким счетом. Функция возвращает предсказанные метки.

function [predictions] = modelPredictions(dlnet,mbq,classes)
    predictions = [];
    
    while hasdata(mbq)
        
        % Extract a mini-batch from the minibatchqueue and pass it to the
        % network for predictions
        [dlXTest] = next(mbq);
        dlYPred = predict(dlnet,dlXTest);
        
        % To obtain categorical labels, one-hot decode the predictions 
        YPred = onehotdecode(dlYPred,classes,1)';
        predictions = [predictions; YPred];
    end
end

Маркированная функция предварительной обработки данных последовательности

The preprocessLabeledSequences функция предварительно обрабатывает данные последовательности с помощью следующих шагов:

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

  2. Извлеките данные метки из входящего массива ячеек и соедините в категориальный массив.

  3. Однократное кодирование категориальных меток в числовые массивы.

  4. Транспонируйте массив закодированных меток с одним «горячим» кодом, чтобы соответствовать форме выхода сети.

function [X, Y] = preprocessLabeledSequences(XCell,YCell)
    % Pad the sequences with zeros in the second dimension (time) and concatenate along the third
    % dimension (batch)
    X = padsequences(XCell,2);
    
    % Extract label data from cell and concatenate
    Y = cat(1,YCell{1:end});
    
    % One-hot encode labels
    Y = onehotencode(Y,2);
    
    % Transpose the encoded labels to match the network output
    Y = Y';
end

Функция предварительной обработки данных немаркированной последовательности

The preprocessUnlabeledSequences функция предварительно обрабатывает данные последовательности с помощью padsequences функция. Эта функция заполняет последовательности нулями во временной размерности и объединяет результат в пакетную размерность.

function [X] = preprocessUnlabeledSequences(XCell)
    % Pad the sequences with zeros in the second dimension (time) and concatenate along the third
    % dimension (batch)
    X = padsequences(XCell,2);
end

Немаркированное видео Данных функция предварительной обработки

The preprocessUnlabeledVideos функция предварительно обрабатывает немаркированное видео данных используя padsequences функция. Эта функция заполняет видео нулем во временной размерности и объединяет результат в пакетном измерении.

function [X] = preprocessUnlabeledVideos(XCell)
    % Pad the sequences with zeros in the fourth dimension (time) and
    % concatenate along the fifth dimension (batch)
    X = padsequences(XCell,4);
end

См. также

| | | |

Похожие темы