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

При обучении этому типу сети с помощью trainNetwork (в этом примере не выполняется), необходимо использовать слои последовательного сворачивания и развертки для независимой обработки видеокадров. При обучении этого типа сети с помощью dlnetwork объект и пользовательский обучающий цикл (как в этом примере), складывание последовательности и развертывание слоев не требуются, поскольку сеть использует размерную информацию, заданную dlarray метки размеров.
Чтобы преобразовать кадры видео в векторы функций, используйте активации предварительно обученной сети.
Загрузка предварительно подготовленной модели GoogLeNet с помощью googlenet функция. Для этой функции требуется модель Deep Learning Toolbox™ для пакета поддержки сети GoogLeNet. Если этот пакет поддержки не установлен, функция предоставляет ссылку для загрузки.
netCNN = googlenet;
Загрузите HMBD51 набор данных из HMDB: большая база данных движения человека и извлеките файл RAR в папку с именем "hmdb51_org". Набор данных содержит около 2 ГБ видеоданных для 7000 клипов более 51 класса, таких как "drink", "run", и "shake_hands".
После извлечения файла RAR убедитесь, что папка hmdb51_org содержит вложенные папки с именами движений тела. Если он содержит файлы RAR, их также необходимо извлечь. Использовать вспомогательную функцию hmdb51Files чтобы получить имена файлов и метки видео. Чтобы ускорить обучение за счет точности, укажите долю в диапазоне [0 1] для чтения только случайного подмножества файлов из базы данных. Если fraction входной аргумент не указан, функция hmdb51Files считывает полный набор данных без изменения порядка файлов.
dataFolder = "hmdb51_org";
fraction = 1;
[files,labels] = hmdb51Files(dataFolder,fraction);Прочитайте первое видео с помощью readVideo вспомогательная функция, определенная в конце этого примера, и просмотр размера видео. Видео представляет собой массив H-by-W-by-C-by-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 (требуется панель инструментов обработки изображений).
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) = [];
Создание arrayDatastore для последовательностей и меток, а затем объедините их в единое хранилище данных.
dsXTrain = arrayDatastore(sequencesTrain,'OutputType','same'); dsYTrain = arrayDatastore(labelsTrain,'OutputType','cell'); dsTrain = combine(dsXTrain,dsYTrain);
Определите классы в данных обучения.
classes = categories(labelsTrain);
Затем создайте сеть классификации последовательностей, которая может классифицировать последовательности векторов элементов, представляющих видео.
Определите сетевую архитектуру классификации последовательностей. Укажите следующие сетевые уровни:
Слой ввода последовательности с размером ввода, соответствующим размеру элемента векторов элемента.
Уровень BiLSTM с 2000 скрытыми единицами измерения и уровнем отсева после этого. Чтобы вывести только одну метку для каждой последовательности, установите 'OutputMode' параметр уровня BiLSTM для 'last'.
Уровень отсева с вероятностью 0,5.
Полностью подключенный уровень с размером выхода, соответствующим количеству классов, и уровень softmax.
numFeatures = size(sequencesTrain{1},1);
numClasses = numel(categories(labelsTrain));
layers = [
sequenceInputLayer(numFeatures,'Name','sequence')
bilstmLayer(2000,'OutputMode','last','Name','bilstm')
dropoutLayer(0.5,'Name','drop')
fullyConnectedLayer(numClasses,'Name','fc')
softmaxLayer('Name','softmax')
];Преобразование слоев в layerGraph объект.
lgraph = layerGraph(layers);
Создать dlnetwork объект из графа слоев.
dlnet = dlnetwork(lgraph);
Потренироваться на 15 эпох и указать размер мини-партии 16.
numEpochs = 15; miniBatchSize = 16;
Укажите параметры оптимизации Adam. Укажите начальную скорость обучения 1e-4 при распаде 0,001, градиентном распаде 0,9 и квадратичном градиентном распаде 0,999.
initialLearnRate = 1e-4; decay = 0.001; gradDecay = 0.9; sqGradDecay = 0.999;
Визуализация хода обучения на графике.
plots = "training-progress";Создать minibatchqueue объект, который обрабатывает и управляет мини-партиями последовательностей во время обучения. Для каждой мини-партии:
Использование пользовательской функции предварительной обработки мини-партии preprocessLabeledSequences (определяется в конце этого примера) для преобразования меток в фиктивные переменные.
Форматирование данных векторной последовательности с метками размеров 'CTB' (канал, время, партия). По умолчанию minibatchqueue объект преобразует данные в dlarray объекты с базовым типом single. Не добавляйте формат к меткам класса.
Обучение на GPU, если он доступен. По умолчанию minibatchqueue объект преобразует каждый вывод в gpuArray объект, если графический процессор доступен. Для использования графического процессора требуется 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 - итераций, α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 объект для тестирования:
Создайте хранилище данных массива, содержащее только предикторы тестовых данных.
Укажите размер мини-партии, используемый для обучения.
Предварительная обработка предикторов с помощью preprocessUnlabeledSequences вспомогательная функция, перечисленная в конце примера.
Для одиночного вывода хранилища данных укажите формат мини-пакета. '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 содержит видео отжимания. Создайте хранилище данных для этой папки. Используйте пользовательскую функцию чтения для чтения видео.
ds = fileDatastore("pushup_mathworker", ... 'ReadFcn',@readVideo);
Прочитайте первое видео из хранилища данных. Чтобы снова прочитать видео, сбросьте хранилище данных.
video = read(ds); reset(ds);
Чтобы просмотреть видео, закольцовывайте отдельные кадры и используйте image функция. Кроме того, можно использовать implay (требуется панель инструментов обработки изображений).
numFrames = size(video,4); figure for i = 1:numFrames frame = video(:,:,:,i); image(frame); xticklabels([]); yticklabels([]); drawnow end

Для предварительной обработки видео с размером входных данных, ожидаемым сетью, используйте transform и применить imresize для каждого образа в хранилище данных.
dsXTest = transform(ds,@(x) imresize(x,inputSize));
Для управления и обработки немаркированных видео создайте minibatchqueue:
Укажите размер мини-пакета 1.
Предварительная обработка видео с помощью preprocessUnlabeledVideos вспомогательная функция, перечисленная в конце примера.
Для одиночного вывода хранилища данных укажите формат мини-пакета. 'SSCTB' (пространственный, пространственный, канальный, временной, пакетный).
mbqTest = minibatchqueue(dsXTest,... 'MiniBatchSize',1,... 'MiniBatchFcn', @preprocessUnlabeledVideos,... 'MiniBatchFormat',{'SSCTB'});
Классифицировать видео с помощью modelPredictions вспомогательная функция, определенная в конце этого примера. Функция ожидает три входа: dlnetwork объект, a minibatchqueue и массив ячеек, содержащий сетевые классы.
[predictions] = modelPredictions(dlnetAssembled,mbqTest,classes)
predictions = categorical
pushup
readVideo функция считывает видео в filename и возвращает H-by-W-by-C-Массив by-T, где H, W, C и T - высота, ширина, количество каналов и количество кадров видео, соответственно.
function video = readVideo(filename) vr = VideoReader(filename); H = vr.Height; W = vr.Width; C = 3; % Preallocate video array numFrames = floor(vr.Duration * vr.FrameRate); video = zeros(H,W,C,numFrames,'uint8'); % Read frames i = 0; while hasFrame(vr) i = i + 1; video(:,:,:,i) = readFrame(vr); end % Remove unallocated frames if size(video,4) > i video(:,:,:,i+1:end) = []; end end
modelGradients функция принимает в качестве входного значения a 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 функция принимает в качестве входного значения a dlnetwork объект dlnet, a 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 функция предварительно обрабатывает данные последовательности, используя следующие шаги:
Используйте padsequences используется для заполнения последовательностей во временном измерении и их объединения в пакетном измерении.
Извлеките данные метки из входящего массива ячеек и объедините их в категориальный массив.
Одноконтурное кодирование категориальных меток в числовые массивы.
Транспонируйте массив одноступенчатых кодированных меток в соответствии с формой сетевого выхода.
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
dlarray | dlfeval | dlgradient | lstmLayer | sequenceInputLayer