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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

netCNN = googlenet;

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

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

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

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

Считайте первое видео с помощью readVideo функция помощника, заданная в конце этого примера и представления размер видео. Видео является 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-by-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 с 2 000 скрытых модулей со слоем уволенного впоследствии. Чтобы вывести только одну метку для каждой последовательности, установите '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;

Задайте опции для оптимизации Адама. Задайте начальную скорость обучения 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™ и поддерживаемого устройства графического процессора. Для получения информации о поддерживаемых устройствах смотрите Поддержку графического процессора Релизом (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 функция помощника, заданная в конце этого примера. Функция ожидает три входных параметров: dlnetwork объект, minibatchqueue объект и массив ячеек, содержащий сетевые классы.

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

Функции помощника

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

readVideo функционируйте читает видео в filename и возвращает H W C-- 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

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

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

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

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

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

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

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

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

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

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

Смотрите также

| | | |

Похожие темы