В этом примере показано, как создать сеть для классификации видео путем объединения предварительно подготовленной модели классификации изображений и сети LSTM.
Чтобы создать сеть глубокого обучения для классификации видео:
Преобразуйте видео в последовательности векторов функций, используя предварительно обученную сверточную нейронную сеть, такую как GoogLeNet, для извлечения элементов из каждого кадра.
Обучение сети LSTM последовательностям для прогнозирования меток видео.
Соберите сеть, которая классифицирует видео непосредственно, комбинируя слои из обеих сетей.
На следующей схеме показана сетевая архитектура.
Для ввода последовательностей изображений в сеть используется уровень ввода последовательностей.
Для использования сверточных слоев для извлечения элементов, то есть для применения сверточных операций к каждому кадру видео независимо, используйте слой складывания последовательности, за которым следуют сверточные слои.
Для восстановления структуры последовательности и изменения формы выходных данных векторных последовательностей используйте развернутый слой последовательности и плоский слой.
Чтобы классифицировать результирующие векторные последовательности, включите уровни LSTM, за которыми следуют выходные слои.

Чтобы преобразовать кадры видео в векторы функций, используйте активации предварительно обученной сети.
Загрузка предварительно подготовленной модели GoogLeNet с помощью googlenet функция. Для этой функции требуется модель Deep Learning Toolbox™ для пакета поддержки сети GoogLeNet. Если этот пакет поддержки не установлен, функция предоставляет ссылку для загрузки.
netCNN = googlenet;
Загрузите HMBD51 набор данных из HMDB: большая база данных движения человека и извлеките файл RAR в папку с именем "hmdb51_org". Набор данных содержит около 2 ГБ видеоданных для 7000 клипов более 51 класса, таких как "drink", "run", и "shake_hands".
После извлечения файлов RAR используйте функцию поддержки hmdb51Files чтобы получить имена файлов и метки видео.
dataFolder = "hmdb51_org";
[files,labels] = hmdb51Files(dataFolder);Прочитайте первое видео с помощью readVideo вспомогательная функция, определенная в конце этого примера, и просмотр размера видео. Видео является массивом H-by-W-by-C-by-S, где H, W, C и S - высота, ширина, количество каналов и количество кадров видео, соответственно.
idx = 1; filename = files(idx); video = readVideo(filename); size(video)
ans = 1×4
240 320 3 409
Просмотрите соответствующую метку.
labels(idx)
ans = categorical
brush_hair
Для просмотра видео используйте implay (требуется Toolbox™ обработки изображений). Эта функция ожидает данные в диапазоне [0,1], поэтому сначала необходимо разделить данные на 255. Кроме того, можно закольцовывать отдельные кадры и использовать imshow функция.
numFrames = size(video,4); figure for i = 1:numFrames frame = video(:,:,:,i); imshow(frame/255); drawnow end
Используйте сверточную сеть в качестве экстрактора функций путем активации при вводе видеокадров в сеть. Преобразуйте видео в последовательности векторов функций, где векторы функций являются выходными данными activations функция на последнем уровне объединения сети GoogLeNet ("pool5-7x7_s1").
Эта диаграмма иллюстрирует поток данных через сеть.

Чтобы прочитать видеоданные и изменить их размер в соответствии с входным размером сети GoogLeNet, используйте readVideo и centerCrop вспомогательные функции, определенные в конце этого примера. Выполнение этого шага может занять много времени. После преобразования видео в последовательности сохраните последовательности в MAT-файле в tempdir папка. Если файл MAT уже существует, загрузите последовательности из MAT-файла без их повторного преобразования.
inputSize = netCNN.Layers(1).InputSize(1:2); layerName = "pool5-7x7_s1"; tempFile = fullfile(tempdir,"hmdb51_org.mat"); if exist(tempFile,'file') load(tempFile,"sequences") 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 = centerCrop(video,inputSize); sequences{i,1} = activations(netCNN,video,layerName,'OutputAs','columns'); end save(tempFile,"sequences","-v7.3"); end
Просмотрите размеры первых нескольких последовательностей. Каждая последовательность является массивом D-by-S, где D - количество признаков (выходной размер уровня объединения), а S - количество кадров видео.
sequences(1:10)
ans = 10×1 cell array
{1024×409 single}
{1024×395 single}
{1024×323 single}
{1024×246 single}
{1024×159 single}
{1024×137 single}
{1024×359 single}
{1024×191 single}
{1024×439 single}
{1024×528 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) = [];
Затем создайте сеть LSTM, которая может классифицировать последовательности векторов функций, представляющих видео.
Определите архитектуру сети LSTM. Укажите следующие сетевые уровни.
Слой ввода последовательности с размером ввода, соответствующим размеру элемента векторов элемента
Уровень BiLSTM с 2000 скрытыми единицами измерения и уровнем отсева после этого. Вывод только одной метки для каждой последовательности путем установки 'OutputMode' параметр уровня BiLSTM для 'last'
Полностью подключенный уровень с размером выхода, соответствующим количеству классов, уровню 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')
classificationLayer('Name','classification')];Укажите параметры обучения с помощью trainingOptions функция.
Установите размер мини-партии 16, начальную скорость обучения 0,0001 и порог градиента 2 (для предотвращения взрыва градиентов).
Тасуйте данные каждую эпоху.
Проверяйте сеть один раз в эпоху.
Отображение хода обучения на графике и подавление подробных выходных данных.
miniBatchSize = 16; numObservations = numel(sequencesTrain); numIterationsPerEpoch = floor(numObservations / miniBatchSize); options = trainingOptions('adam', ... 'MiniBatchSize',miniBatchSize, ... 'InitialLearnRate',1e-4, ... 'GradientThreshold',2, ... 'Shuffle','every-epoch', ... 'ValidationData',{sequencesValidation,labelsValidation}, ... 'ValidationFrequency',numIterationsPerEpoch, ... 'Plots','training-progress', ... 'Verbose',false);
Обучение сети с помощью trainNetwork функция. Это может занять много времени.
[netLSTM,info] = trainNetwork(sequencesTrain,labelsTrain,layers,options);

Вычислите точность классификации сети в наборе проверки. Используйте тот же размер мини-партии, что и для параметров обучения.
YPred = classify(netLSTM,sequencesValidation,'MiniBatchSize',miniBatchSize);
YValidation = labelsValidation;
accuracy = mean(YPred == YValidation)accuracy = 0.6647
Чтобы создать сеть, которая классифицирует видео непосредственно, соберите сеть с использованием слоев из обеих созданных сетей. Используйте слои сверточной сети для преобразования видео в векторные последовательности, а слои из сети LSTM - для классификации векторных последовательностей.
На следующей схеме показана сетевая архитектура.
Для ввода последовательностей изображений в сеть используется уровень ввода последовательностей.
Для использования сверточных слоев для извлечения элементов, то есть для применения сверточных операций к каждому кадру видео независимо, используйте слой складывания последовательности, за которым следуют сверточные слои.
Для восстановления структуры последовательности и изменения формы выходных данных векторных последовательностей используйте развернутый слой последовательности и плоский слой.
Чтобы классифицировать результирующие векторные последовательности, включите уровни LSTM, за которыми следуют выходные слои.

Добавление сверточных слоев
Сначала создайте график слоев сети 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").
layers = [
inputLayer
sequenceFoldingLayer('Name','fold')];
lgraph = addLayers(cnnLayers,layers);
lgraph = connectLayers(lgraph,"fold/out","conv1-7x7_s2");Добавление слоев LSTM
Добавьте уровни LSTM к графу уровней, удалив входной уровень последовательности сети LSTM. Чтобы восстановить структуру последовательности, удаленную слоем свертки последовательности, включите слой развертки последовательности после слоев свертки. Уровни LSTM ожидают последовательности векторов. Чтобы изменить форму выходного сигнала слоя развертки последовательности на векторные последовательности, включите плоский слой после слоя развертки последовательности.
Извлеките уровни из сети LSTM и удалите входной уровень последовательности.
lstmLayers = netLSTM.Layers; lstmLayers(1) = [];
Добавьте слой складывания последовательности, плоский слой и слои LSTM к графу слоев. Подсоедините последний сверточный слой ("pool5-7x7_s1") на вход слоя развертки последовательности ("unfold/in").
layers = [
sequenceUnfoldingLayer('Name','unfold')
flattenLayer('Name','flatten')
lstmLayers];
lgraph = addLayers(lgraph,layers);
lgraph = connectLayers(lgraph,"pool5-7x7_s1","unfold/in");Чтобы включить развернутый слой для восстановления структуры последовательности, подключите "miniBatchSize" вывод слоя складывания последовательности на соответствующий вход слоя развертывания последовательности.
lgraph = connectLayers(lgraph,"fold/miniBatchSize","unfold/miniBatchSize");
Собрать сеть
Убедитесь, что сеть действительна с помощью analyzeNetwork функция.
analyzeNetwork(lgraph)
Соберите сеть таким образом, чтобы она была готова к прогнозированию с помощью assembleNetwork функция.
net = assembleNetwork(lgraph)
net =
DAGNetwork with properties:
Layers: [148×1 nnet.cnn.layer.Layer]
Connections: [175×2 table]
Чтение и центровка видео "pushup.mp4" используя те же шаги, что и ранее.
filename = "pushup.mp4";
video = readVideo(filename);Для просмотра видео используйте implay (требуется панель инструментов обработки изображений). Эта функция ожидает данные в диапазоне [0,1], поэтому сначала необходимо разделить данные на 255. Кроме того, можно закольцовывать отдельные кадры и использовать imshow функция.
numFrames = size(video,4); figure for i = 1:numFrames frame = video(:,:,:,i); imshow(frame/255); drawnow end

Классифицируйте видео по собранной сети. classify функция ожидает массив ячеек, содержащий входные видео, поэтому необходимо ввести массив ячеек 1 к 1, содержащий видео.
video = centerCrop(video,inputSize);
YPred = classify(net,{video})YPred = categorical
pushup
readVideo функция считывает видео в filename и возвращает Hоколо-Wоколо-C-около-S массив, где H, W, C, и S - высота, ширина, количество каналов и количество кадров видео соответственно.
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); % 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
centerCrop функция выполняет кадрирование самых длинных краев видео и изменяет его размер inputSize.
function videoResized = centerCrop(video,inputSize) sz = size(video); if sz(1) < sz(2) % Video is landscape idx = floor((sz(2) - sz(1))/2); video(:,1:(idx-1),:,:) = []; video(:,(sz(1)+1):end,:,:) = []; elseif sz(2) < sz(1) % Video is portrait idx = floor((sz(1) - sz(2))/2); video(1:(idx-1),:,:,:) = []; video((sz(2)+1):end,:,:,:) = []; end videoResized = imresize(video,inputSize(1:2)); end
flattenLayer | lstmLayer | sequenceFoldingLayer | sequenceInputLayer | sequenceUnfoldingLayer | trainingOptions | trainNetwork