В этом примере показано, как создать сеть для видео классификации путем объединения предварительно обученной модели классификации изображений и сети LSTM.
Создать нейронную сеть для глубокого обучения для видео классификации:
Преобразуйте видео в последовательности характеристических векторов с помощью предварительно обученной сверточной нейронной сети, такие как GoogLeNet, чтобы извлечь функции из каждой системы координат.
Обучите сеть LSTM на последовательностях, чтобы предсказать видео метки.
Соберите сеть, которая классифицирует видео непосредственно путем объединения слоев от обеих сетей.
Следующая схема иллюстрирует сетевую архитектуру.
Чтобы ввести последовательности изображений к сети, используйте входной слой последовательности.
Чтобы использовать сверточные слои, чтобы извлечь функции, то есть, применить сверточные операции к каждой системе координат видео независимо, используют слой сворачивания последовательности, сопровождаемый сверточными слоями.
Чтобы восстановить структуру последовательности и изменить выход к векторным последовательностям, используйте слой разворачивания последовательности и сглаживать слой.
Чтобы классифицировать последовательности итогового вектора, включайте слои LSTM, сопровождаемые выходными слоями.
Чтобы преобразовать системы координат видео к характеристическим векторам, используйте активации предварительно обученной сети.
Загрузите предварительно обученную модель GoogLeNet с помощью googlenet
функция. Эта функция требует Модели Deep Learning Toolbox™ для пакета Сетевой поддержки GoogLeNet. Если этот пакет поддержки не установлен, то функция обеспечивает ссылку на загрузку.
netCNN = googlenet;
Загрузите набор данных HMBD51 с HMDB: большая человеческая база данных движения и извлечение файл RAR в папку под названием "hmdb51_org"
. Набор данных содержит приблизительно 2 Гбайт видеоданных для 7 000 клипов более чем 51 класс, такие как "drink"
запущенный
, и "shake_hands"
.
После извлечения файлов RAR используйте функцию поддержки hmdb51Files
получить имена файлов и метки видео.
dataFolder = "hmdb51_org";
[files,labels] = hmdb51Files(dataFolder);
Считайте первое видео с помощью readVideo
функция помощника, заданная в конце этого примера и представления размер видео. Видео является H W C 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
функция (требует Image Processing 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 с 2 000 скрытых модулей со слоем уволенного впоследствии. Выводить только одну метку для каждой последовательности путем установки '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
функция (требует Image Processing Toolbox). Эта функция ожидает данные в области значений [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
trainNetwork
| trainingOptions
| lstmLayer
| sequenceInputLayer
| sequenceFoldingLayer
| sequenceUnfoldingLayer
| flattenLayer