Распознавание активности Используя R (2+1) видео классификация D

Этот пример сначала показывает, как выполнить распознавание активности с помощью предварительно обученного R (2+1) D [1] основанный на сверточной нейронной сети видео классификатор и затем показывает, как использовать передачу обучения, чтобы обучить такой видео классификатор с помощью видеоданных.

Обзор

Основанное на видении распознавание активности включает предсказание действия объекта, такого как обход, плавание или нахождение, с помощью набора видеокадров. Распознавание активности от видео имеет много приложений, таких как человеко-машинное взаимодействие, изучение робота, обнаружение аномалии, наблюдение и обнаружение объектов. Например, онлайновое предсказание нескольких действий для входящих видео от нескольких камер может быть важно для изучения робота. Сравненный с классификацией изображений, распознавание действия с помощью видео сложно к модели из-за неточных достоверных данных для наборов видеоданных, множества жестов, которые агенты в видео могут выполнить, в большой степени класс неустойчивые наборы данных и большой объем данных, требуемый обучать устойчивый классификатор с нуля. Методы глубокого обучения, такие как R (2+1) D [1] и SlowFast [2] показали улучшенную производительность на меньших наборах данных с помощью передачи обучения с сетями, предварительно обученными на больших видео наборах данных распознавания активности, таких как кинетика 400 [4].

Примечание: Этот пример требует Модели Computer Vision Toolbox™ для R (2+1) Видео Классификация D. Можно установить Модель Computer Vision Toolbox для R (2+1) Видео Классификация D из Add-On Explorer. Для получения дополнительной информации об установке дополнений, смотрите, Получают и Управляют Дополнениями.

Выполните распознавание активности Используя предварительно обученный R (2+1) видео классификатор D

Загрузите предварительно обученный R (2+1) видео классификатор D наряду с видеофайлом, на котором можно выполнить распознавание активности. Размер загруженного zip-файла составляет приблизительно 112 Мбайт.

pretrainedFolder = fullfile(tempdir,"hmdb51","pretrained","r2plus1d");
if ~isfolder(pretrainedFolder)
    mkdir(pretrainedFolder);
end
zipFile = 'activityRecognition-R2Plus1D-HMDB51-21b.zip';

if ~isfile(fullfile(pretrainedFolder,zipFile))
    disp('Downloading the pretrained network...')
    downloadURL = ['https://ssd.mathworks.com/supportfiles/vision/data/' zipFile];
    zipFile = fullfile(pretrainedFolder,zipFile);
    websave(zipFile,downloadURL);
    unzip(zipFile,pretrainedFolder);
    disp('Downloaded.')
end

Загрузите предварительно обученный R (2+1) видео классификатор D.

pretrainedDataFile = fullfile(pretrainedFolder,'r2plus1d-FiveClasses-hmdb51.mat');
pretrained = load(pretrainedDataFile);
r2plus1dPretrained = pretrained.data.r2plus1d;

Отобразите имена метки класса предварительно обученного видео классификатора.

classes = r2plus1dPretrained.Classes
classes = 5×1 categorical
     kiss 
     laugh 
     pick 
     pour 
     pushup 

Считайте и отобразите видео pour.avi использование VideoReader и vision.VideoPlayer.

videoFilename = fullfile(pretrainedFolder, "pour.avi");

videoReader = VideoReader(videoFilename);
videoPlayer = vision.VideoPlayer;
videoPlayer.Name = "pour";

while hasFrame(videoReader)
   frame = readFrame(videoReader);
   % Resize the frame for display.
   frame = imresize(frame, 1.5);
   step(videoPlayer,frame);
end
release(videoPlayer);

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

numSequences = 10;

Классифицируйте видеофайл с помощью classifyVideoFile функция.

[actionLabel,score] = classifyVideoFile(r2plus1dPretrained, videoFilename, "NumSequences", numSequences)
actionLabel = categorical
     pour 

score = single
    0.9727

Обучите видео классификатор распознаванию жеста

Этот раздел примера показывает, как видео классификатор, показанный выше, обучен с помощью передачи обучения. Установите doTraining переменная к false использовать предварительно обученный видео классификатор, не имея необходимость ожидать обучения завершиться. В качестве альтернативы, если вы хотите обучить видео классификатор, установите doTraining переменная к true.

doTraining = false;

Загрузите данные об обучении и валидации

Этот пример обучает сеть I3D с помощью набора данных HMDB51. Используйте downloadHMDB51 поддерживание функции, перечисленной в конце этого примера, чтобы загрузить набор данных HMDB51 на папку под названием hmdb51.

downloadFolder = fullfile(tempdir,"hmdb51");
downloadHMDB51(downloadFolder);

После того, как загрузка завершена, извлеките файл RAR hmdb51_org.rar к hmdb51 папка. Затем используйте checkForHMDB51Folder поддерживание функции, перечисленной в конце этого примера, чтобы подтвердить, что загруженные и извлеченные файлы существуют.

allClasses = checkForHMDB51Folder(downloadFolder);

Набор данных содержит приблизительно 2 Гбайт видеоданных для 7 000 клипов более чем 51 класс, такие как напиток, запуск, и обменяться рукопожатием. Каждый видеокадр имеет высоту 240 пикселей и минимальную ширину 176 пикселей. Количество кадров лежит в диапазоне от 18 до приблизительно 1 000.

Чтобы уменьшать учебное время, этот пример обучает сеть распознавания активности, чтобы классифицировать 5 классов действия вместо всего 51 класса в наборе данных. Установите useAllData к true обучаться со всем 51 классом.

useAllData = false;

if useAllData
    classes = allClasses;
else
    classes = string(classes);
end
dataFolder = fullfile(downloadFolder, "hmdb51_org");

Разделите набор данных в набор обучающих данных для обучения классификатор и набор тестов для оценки классификатора. Используйте 80% данных для набора обучающих данных и остальных для набора тестов. Используйте folders2labels и splitlabels создать информацию о метке из папок и разделить данные на основе каждой метки в обучение и тестовые данные устанавливают путем случайного выбора пропорции файлов от каждой метки.

[labels,files] = folders2labels(fullfile(dataFolder,classes),...
    "IncludeSubfolders",true,...
    "FileExtensions",'.avi');

indices = splitlabels(labels,0.8,'randomized');

trainFilenames = files(indices{1});
testFilenames  = files(indices{2});

Загрузите набор данных

Этот пример использует datastore, чтобы считать последовательности видео и соответствующие метки из видеофайлов.

Задайте количество видеокадров, которые datastore должен быть сконфигурирован, чтобы вывести в течение каждого раза, когда данные считаны из datastore.

numFrames = 32;

Значение 32 используется здесь, чтобы сбалансировать время классификации и использование памяти. Общие значения, чтобы рассмотреть равняются 8, 16, 32, 64, или 128. Используя большее количество систем координат помогает получить дополнительную временную информацию, но требует большей памяти. Эмпирический анализ требуется, чтобы определять оптимальное количество кадров. Вы можете должны быть увеличить или уменьшить это значение в зависимости от ваших системных ресурсов.

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

frameSize = [112,112];

Значение [112 112] используется, чтобы получить дольше временные отношения в видеоизображении, какая справка классифицируют жесты с долговременной длительностью. Общие значения для размера [112 112], [224 224], или [256 256]. Меньшие размеры включают использование большего количества видеокадров за счет использования памяти, время вычислений и пространственное разрешение. Минимальная высота и ширина видеокадров в наборе данных HMDB51 240 и 176, соответственно. Задайте [112, 112], чтобы получить большее число систем координат за счет пространственной информации. Если вы хотите задать формат кадра для datastore, чтобы считать, что больше, чем минимальные значения, такой, когда [256, 256], сначала измените размер систем координат с помощью imresize. Как с количеством кадров, эмпирический анализ требуется, чтобы определять оптимальные значения.

Задайте количество каналов как 3 для входных данных RGB.

numChannels = 3;

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

isDataForTraining = true;
dsTrain = createFileDatastore(trainFilenames,numFrames,numChannels,classes,isDataForTraining);

isDataForTraining = false;
dsVal = createFileDatastore(testFilenames,numFrames,numChannels,classes,isDataForTraining);

Сконфигурируйте R (2+1) видео классификатор D для передачи обучения

В этом примере вы создаете R (2+1) видео классификатор D на основе ResNet-3D архитектуры с 18 Пространственно-временными остаточными слоями, 3D Классификатор Видео Нейронной сети Свертки, предварительно обученный на кинетике 400 наборов данных [4].

Задайте ResNet-3D с 18 слоями Spatio-Temporal как основная сетевая архитектура для R (2+1) классификатор D.

baseNetwork = "resnet-3d-18";

Задайте входной размер для R (2+1) Видео Классификатор D.

inputSize = [frameSize, numChannels, numFrames];

Создайте R (2+1) Видео Классификатор D путем определения классов для набора данных HMDB51 и сетевого входного размера.

r2plus1d = r2plus1dVideoClassifier(baseNetwork,string(classes),"InputSize",inputSize);

Задайте имя модели для видео классификатора.

r2plus1d.ModelName = "R(2+1)D Activity Recognizer";

Увеличьте и предварительно обработайте обучающие данные

Увеличение данных обеспечивает способ использовать наборы ограниченных данных для обучения. Увеличение на видеоданных должно быть тем же самым для набора систем координат на основе сетевого входного размера. Незначительные изменения, такие как перевод, обрезка, или преобразование изображения, обеспечивают, новые, отличные, и уникальные изображения, которые можно использовать, чтобы обучить устойчивый видео классификатор. Хранилища данных являются удобным способом считать и увеличить наборы данных. Увеличьте учебные видеоданные при помощи augmentVideo поддерживание функции, заданной в конце этого примера.

dsTrain = transform(dsTrain, @augmentVideo);

Предварительно обработайте учебные видеоданные, чтобы изменить размер к размеру входа R(2+1)D Video Classifier, при помощи preprocessVideoClips, заданный в конце этого примера. Задайте InputNormalizationStatistics свойство видео классификатора и входного размера к предварительной обработке функционирует как значения полей в struct, preprocessInfo. InputNormalizationStatistics свойство используется, чтобы перемасштабировать видеокадры между 0 и 1, и затем нормировать перемасштабированные данные с помощью среднего и стандартного отклонения. Входной размер используется, чтобы изменить размер видеокадров с помощью imresize на основе SizingOption значение в info struct (). В качестве альтернативы вы могли использовать "randomcrop" или "centercrop" к случайной обрезке или центру обрезают входные данные к входному размеру видео классификатора. Обратите внимание на то, что увеличение данных не применяется к данным о валидации и тесту. Идеально, тест и данные о валидации должны быть представительными для исходных данных и оставлены немодифицированными для несмещенной оценки.

preprocessInfo.Statistics = r2plus1d.InputNormalizationStatistics;
preprocessInfo.InputSize = inputSize;
preprocessInfo.SizingOption = "resize";
dsTrain = transform(dsTrain, @(data)preprocessVideoClips(data, preprocessInfo));
dsVal = transform(dsVal, @(data)preprocessVideoClips(data, preprocessInfo));

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

modelGradients функция, перечисленная в конце этого примера, берет в качестве входа R (2+1) Видео Классификатор D r2plus1d, мини-пакет входных данных dlRGB, и мини-пакет основной истины помечает данные dlY. Функция возвращает учебное значение потерь, градиенты потери относительно настраиваемых параметров классификатора и мини-пакетной точности классификатора.

Потеря вычисляется путем вычисления потери перекрестной энтропии предсказаний от видео классификатора. Выходные предсказания сети являются вероятностями между 0 и 1 для каждого из классов.

predictions=forward(r2plus1d,dlRGB);

loss=crossentropy(predictions)

Точность классификатора вычисляется путем сравнения классификатора predictions к метке основной истины входных параметров, dlY.

Задайте опции обучения

Обучайтесь с мини-пакетным размером 5 для 900 итераций. Задайте итерацию, после которой можно сохранить модель с лучшей точностью валидации при помощи SaveBestAfterIteration параметр.

Задайте отжигающие косинус параметры расписания [3] скорости обучения:

  • Минимальная скорость обучения 1e-4.

  • Максимальная скорость обучения 1e-3.

  • Количество косинуса итераций 200, 300, и 400, после которого цикл расписания скорости обучения перезапускает. Опция CosineNumIterations задает ширину каждого цикла косинуса.

Задайте параметры для оптимизации SGDM. Инициализируйте параметры оптимизации SGDM в начале обучения:

  • Импульс 0,9.

  • Начальный скоростной параметр, инициализированный как [].

  • Фактор регуляризации L2 0,0005.

Задайте, чтобы диспетчеризировать данные в фоновом режиме с помощью параллельного пула. Если DispatchInBackground установлен в истинный, откройте параллельный пул с конкретным количеством параллельных рабочих и создайте DispatchInBackgroundDatastore, если как часть этого примера, который диспетчеризирует данные в фоновом режиме, чтобы ускорить обучение с помощью асинхронной загрузки данных и предварительной обработки. По умолчанию этот пример использует графический процессор, если вы доступны. В противном случае это использует центральный процессор. Используя графический процессор требует Parallel Computing Toolbox™, и CUDA® включил NVIDIA® графический процессор. Для получения информации о поддерживаемом вычислите возможности, смотрите Поддержку графического процессора Релизом (Parallel Computing Toolbox).

params.Classes = classes;
params.MiniBatchSize = 5;
params.NumIterations = 900;
params.SaveBestAfterIteration = 600;
params.CosineNumIterations = [200, 300, 400];
params.MinLearningRate = 1e-4;
params.MaxLearningRate = 1e-3;
params.Momentum = 0.9;
params.Velocity = [];
params.L2Regularization = 0.0005;
params.ProgressPlot = false;
params.Verbose = true;
params.ValidationData = dsVal;
params.DispatchInBackground = false;
params.NumWorkers = 12;

Обучите R (2+1) видео классификатор D

Обучите R (2+1) видео классификатор D с помощью видеоданных.

В течение каждой эпохи:

  • Переставьте данные перед цикличным выполнением по мини-пакетам данных.

  • Используйте minibatchqueue циклично выполняться по мини-пакетам. Функция поддержки createMiniBatchQueue, перечисленный в конце этого примера, использует данный учебный datastore, чтобы создать minibatchqueue.

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

Для каждого мини-пакета:

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

  • Чтобы позволить обработать измерение времени видеоданные с помощью R (2+1) Видео Классификатор D задают временную размерность последовательности, "T". Укажите, что размерность маркирует "SSCTB" (пространственный, пространственный, канал, временный, пакет) для видеоданных и "CB" для данных о метке.

minibatchqueue возразите использует функцию поддержки batchVideo, перечисленный в конце этого примера, чтобы обработать видеоданные RGB в пакетном режиме.

params.ModelFilename = "r2plus1d-FiveClasses-hmdb51.mat";
if doTraining
    epoch = 1;
    bestLoss = realmax;
    accTrain = [];
    lossTrain = [];

    iteration = 1;
    start = tic;
    trainTime = start;
    shuffled = shuffleTrainDs(dsTrain);

    % Number of outputs is two: One for RGB frames, and one for ground truth labels.
    numOutputs = 2;
    mbq = createMiniBatchQueue(shuffled, numOutputs, params);
    
    % Use the initializeTrainingProgressPlot and initializeVerboseOutput
    % supporting functions, listed at the end of the example, to initialize
    % the training progress plot and verbose output to display the training
    % loss, training accuracy, and validation accuracy.
    plotters = initializeTrainingProgressPlot(params);
    initializeVerboseOutput(params);

    while iteration <= params.NumIterations

        % Iterate through the data set.
        [dlVideo,dlY] = next(mbq);

        % Evaluate the model gradients and loss using dlfeval.
        [gradients,loss,acc,state] = ...
            dlfeval(@modelGradients,r2plus1d,dlVideo,dlY);

        % Accumulate the loss and accuracies.
        lossTrain = [lossTrain, loss];
        accTrain = [accTrain, acc];

        % Update the network state.
        r2plus1d.State = state;

        % Update the gradients and parameters for the classifier 
        % using the SGDM optimizer.
        [r2plus1d,params.Velocity,learnRate] = ...
            updateLearnables(r2plus1d,gradients,params,params.Velocity,iteration);

        if ~hasdata(mbq) || iteration == params.NumIterations
            % Current epoch is complete. Do validation and update progress.
            trainTime = toc(trainTime);

            [validationTime,cmat,lossValidation,accValidation] = ...
                doValidation(params, r2plus1d);

            accTrain = mean(accTrain);
            lossTrain = mean(lossTrain);

            % Update the training progress.
            displayVerboseOutputEveryEpoch(params,start,learnRate,epoch,iteration,...
                accTrain,accValidation,lossTrain,lossValidation,trainTime,validationTime);
            updateProgressPlot(params,plotters,epoch,iteration,start,lossTrain,accTrain,accValidation);

            % Save the trained video classifier and the parameters, that gave 
            % the best validation loss so far. Use the saveData supporting function,
            % listed at the end of this example.
            bestLoss = saveData(r2plus1d,bestLoss,iteration,cmat,lossTrain,lossValidation,accTrain,accValidation,params);
        end

        if ~hasdata(mbq) && iteration < params.NumIterations
            % Current epoch is complete. Initialize the training loss, accuracy
            % values, and minibatchqueue for the next epoch.
            accTrain = [];
            lossTrain = [];
            
            epoch = epoch + 1;
            trainTime = tic;
            shuffled = shuffleTrainDs(dsTrain);
            mbq = createMiniBatchQueue(shuffled, numOutputs, params);            
        end

        iteration = iteration + 1;
    end

    % Display a message when training is complete.
    endVerboseOutput(params);

    disp("Model saved to: " + params.ModelFilename);
end

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

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

Загрузите лучшую модель, сохраненную во время обучения, или используйте предварительно обученную модель.

if doTraining
    transferLearned = load(params.ModelFilename);
    r2plus1dPretrained = transferLearned.data.r2plus1d;
end

Создайте minibatchqueue возразите, чтобы загрузить пакеты тестовых данных.

numOutputs = 2;
mbq = createMiniBatchQueue(dsVal, numOutputs, params);

Для каждого пакета данных об оценке сделайте предсказания с помощью R (2+1) Видео Классификатор D и вычислите точность предсказания с помощью матрицы беспорядка.

numClasses = numel(params.Classes);
cmat = sparse(numClasses,numClasses);

while hasdata(mbq)
    [dlVideo,dlY] = next(mbq);

    % Computer the predictions of the trained R(2+1)D
    % Video Classifier.
    dlYPred = predict(r2plus1dPretrained,dlVideo);
    dlYPred = squeezeIfNeeded(dlYPred, dlY);

    % Aggregate the confusion matrix by using the maximum
    % values of the prediction scores and the ground truth labels.
    [~,YTest] = max(dlY,[],1);
    [~,YPred] = max(dlYPred,[],1);
    cmat = aggregateConfusionMetric(cmat,YTest,YPred);
end

Вычислите среднюю точность классификации клипов для обученного R (2+1) Видео Классификатор D.

evalClipAccuracy = sum(diag(cmat))./sum(cmat,"all")
evalClipAccuracy = 0.9937

Отобразите матрицу неточностей.

figure
chart = confusionchart(cmat,classes);

R (2+1) видео классификатор D, который предварительно обучен на кинетике 400 наборов данных, обеспечивает высокие показатели для распознавания деятельности человека на передаче обучения. Вышеупомянутое обучение было запущено на Титане-X 24GB графический процессор в течение приблизительно 30 минут. Когда обучение с нуля на маленьком наборе данных видео распознавания активности, учебное время и сходимость берет намного дольше, чем предварительно обученный видео классификатор. Transer, изучающий использование кинетики 400, предварительно обучил R (2+1), видео классификатор D также старается не сверхсоответствовать классификатору, когда запустился для большего числа эпох. Чтобы узнать больше о видео распознавании с помощью глубокого обучения, смотрите Начало работы с Видео Классификацией Используя Глубокое обучение.

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

createFileDatastore

createFileDatastore функция создает FileDatastore объект с помощью данного имени папки. FileDatastore объект считывает данные в 'partialfile' режим, таким образом, каждое чтение может возвратить частично системы координат чтения в видео. Эта функция помогает с чтением больших видеофайлов, если все системы координат не умещаются в памяти.

function datastore = createFileDatastore(trainingFolder,numFrames,numChannels,classes,isDataForTraining)
    readFcn = @(f,u)readVideo(f,u,numFrames,numChannels,classes,isDataForTraining);
    datastore = fileDatastore(trainingFolder,...
        'IncludeSubfolders',true,...
        'FileExtensions','.avi',...
        'ReadFcn',readFcn,...
        'ReadMode','partialfile');
end

shuffleTrainDs

shuffleTrainDs функционируйте переставляет файлы, существующие в учебном datastore, dsTrain.

function shuffled = shuffleTrainDs(dsTrain)
shuffled = copy(dsTrain);
transformed = isa(shuffled, 'matlab.io.datastore.TransformedDatastore');
if transformed
    files = shuffled.UnderlyingDatastores{1}.Files;
else 
    files = shuffled.Files;
end
n = numel(files);
shuffledIndices = randperm(n);  
if transformed
    shuffled.UnderlyingDatastores{1}.Files = files(shuffledIndices);
else
    shuffled.Files = files(shuffledIndices);
end

reset(shuffled);

end

readVideo

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

function [data,userdata,done] = readVideo(filename,userdata,numFrames,numChannels,classes,isDataForTraining)
    if isempty(userdata)
        userdata.reader      = VideoReader(filename);
        userdata.batchesRead = 0;
        
        userdata.label = getLabel(filename,classes);

        totalFrames = floor(userdata.reader.Duration * userdata.reader.FrameRate);
        totalFrames = min(totalFrames, userdata.reader.NumFrames);
        userdata.totalFrames = totalFrames;
        userdata.datatype = class(read(userdata.reader,1));
    end
    reader      = userdata.reader;
    totalFrames = userdata.totalFrames;
    label       = userdata.label;
    batchesRead = userdata.batchesRead;

    if isDataForTraining
        video = readForTraining(reader, numFrames, totalFrames);
    else
        video = readForValidation(reader, userdata.datatype, numChannels, numFrames, totalFrames);
    end   

    data = {video, label};

    batchesRead = batchesRead + 1;

    userdata.batchesRead = batchesRead;

    if numFrames > totalFrames
        numBatches = 1;
    else
        numBatches = floor(totalFrames/numFrames);
    end
    % Set the done flag to true, if the reader has read all the frames or
    % if it is training.
    done = batchesRead == numBatches || isDataForTraining;
end

readForTraining

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

function video = readForTraining(reader, numFrames, totalFrames)
    if numFrames >= totalFrames
        startIdx = 1;
        endIdx = totalFrames;
    else
        startIdx = randperm(totalFrames - numFrames + 1);
        startIdx = startIdx(1);
        endIdx = startIdx + numFrames - 1;
    end
    video = read(reader,[startIdx,endIdx]);
    if numFrames > totalFrames
        % Add more frames to fill in the network input size.
        additional = ceil(numFrames/totalFrames);
        video = repmat(video,1,1,1,additional);
        video = video(:,:,:,1:numFrames);
    end
end

readForValidation

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

function video = readForValidation(reader, datatype, numChannels, numFrames, totalFrames)
    H = reader.Height;
    W = reader.Width;
    toRead = min([numFrames,totalFrames]);
    video = zeros([H,W,numChannels,toRead], datatype);
    frameIndex = 0;
    while hasFrame(reader) && frameIndex < numFrames
        frame = readFrame(reader);
        frameIndex = frameIndex + 1;
        video(:,:,:,frameIndex) = frame;
    end
    
    if frameIndex < numFrames
        video = video(:,:,:,1:frameIndex);    
        additional = ceil(numFrames/frameIndex);
        video = repmat(video,1,1,1,additional);
        video = video(:,:,:,1:numFrames);       
    end
end

getLabel

getLabel функция получает имя метки из полного пути имени файла. Метка для файла является папкой, в которой она существует. Например, для пути к файлу, такого как "/path/to/dataset/clapping/video_0001.avi", именем метки является "clapping".

function label = getLabel(filename,classes)
    folder = fileparts(string(filename));
    [~,label] = fileparts(folder);
    label = categorical(string(label), string(classes));
end

augmentVideo

augmentVideo функционируйте увеличивает видеокадры для обучения видео классификатор. Функция увеличивает видео последовательность с тем же методом увеличения, обеспеченным augmentTransform функция.

function data = augmentVideo(data)
    numClips = size(data,1);
    for ii = 1:numClips
        video = data{ii,1};
        % HxWxC
        sz = size(video,[1,2,3]);
        % One augment fcn per clip
        augmentFcn = augmentTransform(sz);
        data{ii,1} = augmentFcn(video);
    end
end

augmentTransform

augmentTransform функция создает метод увеличения со случайным зеркальным отражением лево-права и масштабными коэффициентами.

function augmentFcn = augmentTransform(sz)
% Randomly flip and scale the image.
tform = randomAffine2d('XReflection',true,'Scale',[1 1.1]);
rout = affineOutputView(sz,tform,'BoundsStyle','CenterOutput');

augmentFcn = @(data)augmentData(data,tform,rout);

    function data = augmentData(data,tform,rout)
        data = imwarp(data,tform,'OutputView',rout);
    end
end

preprocessVideoClips

preprocessVideoClips функция предварительно обрабатывает учебные видеоданные, чтобы изменить размер к размеру входа R(2+1)D Video Classifier. Это берет InputNormalizationStatistics и InputSize свойства видео классификатора в struct, info. InputNormalizationStatistics свойство используется, чтобы перемасштабировать видеокадры между 0 и 1, и затем нормировать перемасштабированные данные с помощью среднего и стандартного отклонения. Входной размер используется, чтобы изменить размер видеокадров с помощью imresize на основе SizingOption значение в info struct (). В качестве альтернативы вы могли использовать "randomcrop" или "centercrop" как значения для SizingOption к случайной обрезке или центру обрезают входные данные к входному размеру видео классификатора.

function data = preprocessVideoClips(data, info)
    inputSize = info.InputSize(1:2);
    sizingOption = info.SizingOption;
    switch sizingOption
        case "resize"
            sizingFcn = @(x)imresize(x,inputSize);
        case "randomcrop"
            sizingFcn = @(x)cropVideo(x,@randomCropWindow2d,inputSize);
        case "centercrop"
            sizingFcn = @(x)cropVideo(x,@centerCropWindow2d,inputSize);
    end
    numClips = size(data,1);

    minValue = info.Statistics.Min;
    maxValue = info.Statistics.Max;
    minValue = reshape(minValue, 1, 1, 3);
    maxValue = reshape(maxValue, 1, 1, 3);

    meanValue = info.Statistics.Mean;
    stdValue  = info.Statistics.StandardDeviation;
    meanValue = reshape(meanValue, 1, 1, 3);
    stdValue  = reshape(stdValue, 1, 1, 3);

    for ii = 1:numClips
        video = data{ii,1};
        resized = sizingFcn(video);

        % Cast the input to single.
        resized = single(resized);

        % Rescale the input between 0 and 1.
        resized = rescale(resized,0,1,"InputMin",minValue,"InputMax",maxValue);

        % Normalize using mean and standard deviation.
        resized = resized - meanValue;
        resized = resized ./ stdValue;
        data{ii,1} = resized;
    end

    function outData = cropVideo(data, cropFcn, inputSize)
        imsz = size(data,[1,2]);
        cropWindow = cropFcn(imsz, inputSize);
        numBatches = size(data,4);
        sz = [inputSize, size(data,3), numBatches];
        outData = zeros(sz, 'like', data);
        for b = 1:numBatches
            outData(:,:,:,b) = imcrop(data(:,:,:,b), cropWindow);
        end
    end
end

createMiniBatchQueue

createMiniBatchQueue функция создает minibatchqueue объект, который обеспечивает miniBatchSize объем данных от данного datastore. Это также создает DispatchInBackgroundDatastore если параллельный пул открыт.

function mbq = createMiniBatchQueue(datastore, numOutputs, params)
if params.DispatchInBackground && isempty(gcp('nocreate'))
    % Start a parallel pool, if DispatchInBackground is true, to dispatch
    % data in the background using the parallel pool.
    c = parcluster('local');
    c.NumWorkers = params.NumWorkers;
    parpool('local',params.NumWorkers);
end
p = gcp('nocreate');
if ~isempty(p)
    datastore = DispatchInBackgroundDatastore(datastore, p.NumWorkers);
end

inputFormat(1:numOutputs-1) = "SSCTB";
outputFormat = "CB";
mbq = minibatchqueue(datastore, numOutputs, ...
    "MiniBatchSize", params.MiniBatchSize, ...
    "MiniBatchFcn", @batchVideo, ...
    "MiniBatchFormat", [inputFormat,outputFormat]);
end

batchVideo

batchVideo функционируйте обрабатывает в пакетном режиме видео и данные о метке из массивов ячеек. Это использует onehotencode функция, чтобы закодировать основную истину категориальные метки в одногорячие массивы. Одногорячий закодированный массив содержит 1 в положении, соответствующем классу метки и 0 в любом положении.

function [video,labels] = batchVideo(video, labels)
% Batch dimension: 5
video = cat(5,video{:});

% Batch dimension: 2
labels = cat(2,labels{:});

% Feature dimension: 1
labels = onehotencode(labels,1);
end

modelGradients

modelGradients функционируйте берет в качестве входа мини-пакет данных о RGB dlRGB, и соответствующий целевой dlY, и возвращает соответствующую потерю, градиенты потери относительно настраиваемых параметров и учебную точность. Чтобы вычислить градиенты, оцените modelGradients функция с помощью dlfeval функция в учебном цикле.

function [gradientsRGB,loss,acc,stateRGB] = modelGradients(r2plus1d,dlRGB,dlY)
[dlYPredRGB,stateRGB] = forward(r2plus1d,dlRGB);
dlYPred = squeezeIfNeeded(dlYPredRGB, dlY);

loss = crossentropy(dlYPred,dlY);

gradientsRGB = dlgradient(loss,r2plus1d.Learnables);

% Calculate the accuracy of the predictions.
[~,YTest] = max(dlY,[],1);
[~,YPred] = max(dlYPred,[],1);

acc = gather(extractdata(sum(YTest == YPred)./numel(YTest)));    
end

squeezeIfNeeded

function dlYPred = squeezeIfNeeded(dlYPred, Y)
if ~isequal(size(Y), size(dlYPred))
    dlYPred = squeeze(dlYPred);
    dlYPred = dlarray(dlYPred,dims(Y));
end
end

updateLearnables

updateLearnables функционируйте обновляет обеспеченный dlnetwork объект с градиентами и другими параметрами с помощью оптимизационной функции SGDM sgdmupdate.

function [r2plus1d,velocity,learnRate] = updateLearnables(r2plus1d,gradients,params,velocity,iteration)
    % Determine the learning rate using the cosine-annealing learning rate schedule.
    learnRate = cosineAnnealingLearnRate(iteration, params);

    % Apply L2 regularization to the weights.
    learnables = r2plus1d.Learnables;
    idx = learnables.Parameter == "Weights";
    gradients(idx,:) = dlupdate(@(g,w) g + params.L2Regularization*w, gradients(idx,:), learnables(idx,:));

    % Update the network parameters using the SGDM optimizer.
    [r2plus1d, velocity] = sgdmupdate(r2plus1d, gradients, velocity, learnRate, params.Momentum);
end

cosineAnnealingLearnRate

cosineAnnealingLearnRate функция вычисляет скорость обучения на основе текущего номера итерации, минимальную скорость обучения, максимальную скорость обучения и количество итераций для отжига [3].

function lr = cosineAnnealingLearnRate(iteration, params)
    if iteration == params.NumIterations
        lr = params.MinLearningRate;
        return;
    end
    cosineNumIter = [0, params.CosineNumIterations];
    csum = cumsum(cosineNumIter);
    block = find(csum >= iteration, 1,'first');
    cosineIter = iteration - csum(block - 1);
    annealingIteration = mod(cosineIter, cosineNumIter(block));
    cosineIteration = cosineNumIter(block);
    minR = params.MinLearningRate;
    maxR = params.MaxLearningRate;
    cosMult = 1 + cos(pi * annealingIteration / cosineIteration);
    lr = minR + ((maxR - minR) *  cosMult / 2);
end

aggregateConfusionMetric

aggregateConfusionMetric функционируйте инкрементно заполняет матрицу беспорядка на основе предсказанных результатов YPred и ожидаемые результаты YTest.

function cmat = aggregateConfusionMetric(cmat,YTest,YPred)
YTest = gather(extractdata(YTest));
YPred = gather(extractdata(YPred));
[m,n] = size(cmat);
cmat = cmat + full(sparse(YTest,YPred,1,m,n));
end

doValidation

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

function [validationTime, cmat, lossValidation, accValidation] = doValidation(params, r2plus1d)

    validationTime = tic;

    numOutputs = 2;
    mbq = createMiniBatchQueue(params.ValidationData, numOutputs, params);

    lossValidation = [];
    numClasses = numel(params.Classes);
    cmat = sparse(numClasses,numClasses);
    while hasdata(mbq)

        [dlVideo,Y] = next(mbq);

        % Pass the video input through the R(2+1)D Video Classifier.
        dlY = predict(r2plus1d,dlVideo);
        dlY = squeezeIfNeeded(dlY, Y);

        % Calculate the cross-entropy loss.
        loss = crossentropy(dlY,Y);

        % Calculate the accuracy of the predictions.
        [~,YTest] = max(Y,[],1);
        [~,YPred] = max(dlY,[],1);

        lossValidation = [lossValidation,loss];
        cmat = aggregateConfusionMetric(cmat,YTest,YPred);
    end
    lossValidation = mean(lossValidation);
    accValidation = sum(diag(cmat))./sum(cmat,"all");

    validationTime = toc(validationTime);
end

saveData

saveData функция сохраняет данный R (2+1) Видео Классификатор D, точность, потеря и другие параметры обучения к MAT-файлу.

function bestLoss = saveData(r2plus1d,bestLoss,iteration,cmat,lossTrain,lossValidation,accTrain,accValidation,params)
if iteration >= params.SaveBestAfterIteration
    lossValidtion = extractdata(gather(lossValidation));
    if lossValidtion < bestLoss
        params = rmfield(params, 'Velocity');
        bestLoss = lossValidtion;
        r2plus1d = gatherFromGPUToSave(r2plus1d);
        data.BestLoss = bestLoss;
        data.TrainingLoss = extractdata(gather(lossTrain));
        data.TrainingAccuracy = accTrain;
        data.ValidationAccuracy = accValidation;
        data.ValidationConfmat= cmat;
        data.r2plus1d = r2plus1d;
        data.Params = params;
        save(params.ModelFilename, 'data');
    end
end
end

gatherFromGPUToSave

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

function r2plus1d = gatherFromGPUToSave(r2plus1d)
if ~canUseGPU
    return;
end
r2plus1d.Learnables = gatherValues(r2plus1d.Learnables);
r2plus1d.State = gatherValues(r2plus1d.State);
    function tbl = gatherValues(tbl)
        for ii = 1:height(tbl)
            tbl.Value{ii} = gather(tbl.Value{ii});
        end
    end
end

checkForHMDB51Folder

checkForHMDB51Folder функционируйте проверки на загруженные данные в папке загрузки.

function classes = checkForHMDB51Folder(dataLoc)
hmdbFolder = fullfile(dataLoc, "hmdb51_org");
if ~isfolder(hmdbFolder)
    error("Download 'hmdb51_org.rar' file using the supporting function 'downloadHMDB51' before running the example and extract the RAR file.");    
end

classes = ["brush_hair","cartwheel","catch","chew","clap","climb","climb_stairs",...
    "dive","draw_sword","dribble","drink","eat","fall_floor","fencing",...
    "flic_flac","golf","handstand","hit","hug","jump","kick","kick_ball",...
    "kiss","laugh","pick","pour","pullup","punch","push","pushup","ride_bike",...
    "ride_horse","run","shake_hands","shoot_ball","shoot_bow","shoot_gun",...
    "sit","situp","smile","smoke","somersault","stand","swing_baseball","sword",...
    "sword_exercise","talk","throw","turn","walk","wave"];
expectFolders = fullfile(hmdbFolder, classes);
if ~all(arrayfun(@(x)exist(x,'dir'),expectFolders))
    error("Download hmdb51_org.rar using the supporting function 'downloadHMDB51' before running the example and extract the RAR file.");
end
end

downloadHMDB51

downloadHMDB51 функционируйте загружает набор данных и сохраняет его в директорию.

function downloadHMDB51(dataLoc)

if nargin == 0
    dataLoc = pwd;
end
dataLoc = string(dataLoc);

if ~isfolder(dataLoc)
    mkdir(dataLoc);
end

dataUrl     = "http://serre-lab.clps.brown.edu/wp-content/uploads/2013/10/hmdb51_org.rar";
options     = weboptions('Timeout', Inf);
rarFileName = fullfile(dataLoc, 'hmdb51_org.rar');

% Download the RAR file and save it to the download folder.
if ~isfile(rarFileName)
    disp("Downloading hmdb51_org.rar (2 GB) to the folder:")
    disp(dataLoc)
    disp("This download can take a few minutes...") 
    websave(rarFileName, dataUrl, options); 
    disp("Download complete.")
    disp("Extract the hmdb51_org.rar file contents to the folder: ") 
    disp(dataLoc)
end
end

initializeTrainingProgressPlot

initializeTrainingProgressPlot функция конфигурирует два графика для отображения учебной потери, учебной точности и точности валидации.

function plotters = initializeTrainingProgressPlot(params)
if params.ProgressPlot
    % Plot the loss, training accuracy, and validation accuracy.
    figure
    
    % Loss plot
    subplot(2,1,1)
    plotters.LossPlotter = animatedline;
    xlabel("Iteration")
    ylabel("Loss")
    
    % Accuracy plot
    subplot(2,1,2)
    plotters.TrainAccPlotter = animatedline('Color','b');
    plotters.ValAccPlotter = animatedline('Color','g');
    legend('Training Accuracy','Validation Accuracy','Location','northwest');
    xlabel("Iteration")
    ylabel("Accuracy")
else
    plotters = [];
end
end

updateProgressPlot

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

function updateProgressPlot(params,plotters,epoch,iteration,start,lossTrain,accuracyTrain,accuracyValidation)
if params.ProgressPlot
    
    % Update the training progress.
    D = duration(0,0,toc(start),"Format","hh:mm:ss");
    title(plotters.LossPlotter.Parent,"Epoch: " + epoch + ", Elapsed: " + string(D));
    addpoints(plotters.LossPlotter,iteration,double(gather(extractdata(lossTrain))));
    addpoints(plotters.TrainAccPlotter,iteration,accuracyTrain);
    addpoints(plotters.ValAccPlotter,iteration,accuracyValidation);
    drawnow
end
end

initializeVerboseOutput

function initializeVerboseOutput(params)
if params.Verbose
    disp(" ")
    if canUseGPU
        disp("Training on GPU.")
    else
        disp("Training on CPU.")
    end
    p = gcp('nocreate');
    if ~isempty(p)
        disp("Training on parallel cluster '" + p.Cluster.Profile + "'. ")
    end
    disp("NumIterations:" + string(params.NumIterations));
    disp("MiniBatchSize:" + string(params.MiniBatchSize));
    disp("Classes:" + join(string(params.Classes), ","));
    disp("|=======================================================================================================================================|")
    disp("| Epoch | Iteration | Time Elapsed | Mini-Batch | Validation | Mini-Batch | Validation |  Base Learning  | Train Time | Validation Time |")
    disp("|       |           |  (hh:mm:ss)  |  Accuracy  |  Accuracy  |    Loss    |    Loss    |      Rate       | (hh:mm:ss) |   (hh:mm:ss)    |")
    disp("|=======================================================================================================================================|")
end
end

displayVerboseOutputEveryEpoch

function displayVerboseOutputEveryEpoch(params,start,learnRate,epoch,iteration,...
        accTrain,accValidation,lossTrain,lossValidation,trainTime,validationTime)
    if params.Verbose
        D = duration(0,0,toc(start),'Format','hh:mm:ss');
        trainTime = duration(0,0,trainTime,'Format','hh:mm:ss');
        validationTime = duration(0,0,validationTime,'Format','hh:mm:ss');

        lossValidation = gather(extractdata(lossValidation));
        lossValidation = compose('%.4f',lossValidation);

        accValidation = composePadAccuracy(accValidation);

        lossTrain = gather(extractdata(lossTrain));
        lossTrain = compose('%.4f',lossTrain);

        accTrain = composePadAccuracy(accTrain);
        learnRate = compose('%.13f',learnRate);

        disp("| " + ...
            pad(string(epoch),5,'both') + " | " + ...
            pad(string(iteration),9,'both') + " | " + ...
            pad(string(D),12,'both') + " | " + ...
            pad(string(accTrain),10,'both') + " | " + ...
            pad(string(accValidation),10,'both') + " | " + ...
            pad(string(lossTrain),10,'both') + " | " + ...
            pad(string(lossValidation),10,'both') + " | " + ...
            pad(string(learnRate),13,'both') + " | " + ...
            pad(string(trainTime),10,'both') + " | " + ...
            pad(string(validationTime),15,'both') + " |")
    end
    function acc = composePadAccuracy(acc)
        acc = compose('%.2f',acc*100) + "%";
        acc = pad(string(acc),6,'left');
    end
end

endVerboseOutput

endVerboseOutput функционируйте отображает конец многословного выхода во время обучения.

function endVerboseOutput(params)
if params.Verbose
    disp("|=======================================================================================================================================|")
end
end

Ссылки

[1] Дю Тран, Хэн Ван, Лоренсо Торресани, Джейми Рэй, Yann LeCun, Manohar Paluri. "Более внимательное рассмотрение в Пространственно-временных Свертках для Распознавания Действия". Продолжения Конференции по IEEE по Компьютерному зрению и Распознаванию образов (CVPR), 2018, стр 6450-6459.

[2] Кристоф Файхтенхофер, Хэоки Фэн, Джитендра Малик и Кэйминг он. "Сети SlowFast для видео распознавания". Продолжения конференции по IEEE по компьютерному зрению и распознаванию образов (CVPR), 2019.

[3] Лощилов, Илья и Франк Хуттер. "SGDR: стохастический градиентный спуск с горячими перезапусками". Международный Conferencee на изучении представлений 2017. Тулон, Франция: ICLR, 2017.

[4] Уилл Кей, Жоао Карреира, Карен Симонян, Брайн Чжан, Хлоя Хиллир, Sudheendra Vijayanarasimhan, Фабио Виола, Тим Грин, Тревор Бэк, Пол Нацев, Мустафа Сулейман, Эндрю Зиссермен. "Набор данных Видео Человеческой деятельности кинетики". arXiv предварительно распечатывают arXiv:1705.06950, 2017.