В этом примере показано, как создать сеть для классификации видео путем объединения предварительно обученной модели классификации изображений и сети классификации последовательностей.
Вы можете выполнить классификацию видео, не используя пользовательский цикл обучения, используя trainNetwork
функция. Для получения примера смотрите Классификация видео Используя Глубокое Обучение. Однако, Если trainingOptions
не предоставляет необходимые опции (например, пользовательское расписание скорости обучения), тогда можно задать свой собственный пользовательский цикл обучения, как показано в этом примере.
Чтобы создать нейронную сеть для глубокого обучения для классификации видео:
Преобразуйте видео в последовательности векторов функций с помощью предварительно обученной сверточной нейронной сети, такой как GoogLeNet, чтобы извлечь функции из каждой системы координат.
Обучите сеть классификации последовательностей на последовательностях, чтобы предсказать метки видео.
Соберите сеть, которая классифицирует видео непосредственно путем объединения слоев из обеих сетей.
Следующая схема иллюстрирует сетевую архитектуру:
Чтобы ввести последовательности изображений в сеть, используйте входной слой последовательности.
Чтобы извлечь функции из последовательностей изображений, используйте сверточные слои из предварительно обученной сети GoogLeNet.
Чтобы классифицировать получившиеся векторные последовательности, включите слои классификации последовательностей.
При обучении этого типа сети с trainNetwork
функция (не выполняется в этом примере), вы должны использовать последовательность складывания и развертывания слоев, чтобы обрабатывать видеокадры независимо. Когда вы обучаете этот тип сети с dlnetwork
объект и пользовательский цикл обучения (как в этом примере), складывание и развертывание слоев последовательности не требуются, потому что сеть использует информацию о размерности, заданную dlarray
метки размерностей.
Чтобы преобразовать системы координат видео в векторы признаков, используйте активации предварительно обученной сети.
Загрузите предварительно обученную модель GoogLeNet с помощью googlenet
функция. Этой функции требуется Модель Deep Learning Toolbox™ для пакета поддержки GoogLeNet Network. Если этот пакет поддержки не установлен, то функция предоставляет ссылку на загрузку.
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
helper, заданная в конце этого примера, и просматривать размер видео. Видео является массивом H-на-W-на-C-на-T, где H, W, C и T являются высотой, шириной, количеством каналов и количеством систем координат видео, соответственно.
idx = 1; filename = files(idx); video = readVideo(filename); size(video)
ans = 1×4
240 352 3 115
Просмотрите соответствующую метку.
labels(idx)
ans = categorical
shoot_ball
Чтобы просмотреть видео, закольцовывайте отдельные системы координат и используйте image
функция. Также можно использовать implay
функция (требует Image Processing Toolbox).
numFrames = size(video,4); figure for i = 1:numFrames frame = video(:,:,:,i); image(frame); xticklabels([]); yticklabels([]); drawnow end
Используйте сверточную сеть как экстрактор функций: вводите видеокадры в сеть и извлекайте активации. Преобразуйте видео в последовательности векторов функций, где векторы функций являются выходными данными activations
функция на последнем слое объединения сети GoogLeNet ("pool5-7x7_s1"
).
Эта схема иллюстрирует поток данных через сеть.
Чтение видео, данных с помощью readVideo
функция, заданная в конце этого примера, и изменить ее размер, чтобы соответствовать размеру входа сети GoogLeNet. Обратите внимание, что выполнение этого шага может занять много времени. После преобразования видео в последовательности сохраните последовательности и соответствующие метки в файле MAT в tempdir
папка. Если файл MAT уже существует, загружайте последовательности и метки из файла MAT непосредственно. В случае, если файл MAT уже существует, но вы хотите перезаписать его, установите переменную overwriteSequences
на true
.
inputSize = netCNN.Layers(1).InputSize(1:2); layerName = "pool5-7x7_s1"; tempFile = fullfile(tempdir,"hmdb51_org.mat"); overwriteSequences = false; if exist(tempFile,'file') && ~overwriteSequences load(tempFile) else numFiles = numel(files); sequences = cell(numFiles,1); for i = 1:numFiles fprintf("Reading file %d of %d...\n", i, numFiles) video = readVideo(files(i)); video = imresize(video,inputSize); sequences{i,1} = activations(netCNN,video,layerName,'OutputAs','columns'); end % Save the sequences and the labels associated with them. save(tempFile,"sequences","labels","-v7.3"); end
Просмотрите размеры первых нескольких последовательностей. Каждая последовательность является массивом D-на-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
объект для последовательностей и меток, а затем объединяет их в один datastore.
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
. Не добавляйте формат к меткам классов.
Обучите на графическом процессоре, если он доступен. По умолчанию в minibatchqueue
объект преобразует каждый выход в gpuArray
объект, если доступен графический процессор. Для использования графический процессор требуется Parallel Computing Toolbox™ и поддерживаемый графический процессор. Для получения информации о поддерживаемых устройствах смотрите Поддержку GPU by Release (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 - число итерации, является начальной скоростью обучения, и k является распадом.
Обновляйте параметры сети с помощью adamupdate
функция.
Отображение процесса обучения.
Обратите внимание, что обучение может занять много времени.
iteration = 0; start = tic; % Loop over epochs. for epoch = 1:numEpochs % Shuffle data. shuffle(mbq); % Loop over mini-batches. while hasdata(mbq) iteration = iteration + 1; % Read mini-batch of data. [dlX, dlY] = next(mbq); % Evaluate the model gradients, state, and loss using dlfeval and the % modelGradients function. [gradients,state,loss] = dlfeval(@modelGradients,dlnet,dlX,dlY); % Determine learning rate for time-based decay learning rate schedule. learnRate = initialLearnRate/(1 + decay*iteration); % Update the network parameters using the Adam optimizer. [dlnet,averageGrad,averageSqGrad] = adamupdate(dlnet,gradients,averageGrad,averageSqGrad, ... iteration,learnRate,gradDecay,sqGradDecay); % Display the training progress. if plots == "training-progress" D = duration(0,0,toc(start),'Format','hh:mm:ss'); addpoints(lineLossTrain,iteration,double(gather(extractdata(loss)))) title("Epoch: " + epoch + " of " + numEpochs + ", Elapsed: " + string(D)) drawnow end end end
Протестируйте классификационную точность модели путем сравнения предсказаний на наборе валидации с истинными метками.
После завершения обучения для предсказаний новых данных не требуются метки.
Как создать minibatchqueue
объект для проверки:
Создайте массив datastore, содержащий только предикторы тестовых данных.
Укажите тот же размер мини-пакета, что и для обучения.
Предварительно обработайте предикторы, используя preprocessUnlabeledSequences
вспомогательная функция, перечисленная в конце примера.
Для одинарного выхода datastore задайте формат пакета 'CTB'
(канал, время, пакет).
dsXValidation = arrayDatastore(sequencesValidation,'OutputType','same'); mbqTest = minibatchqueue(dsXValidation, ... 'MiniBatchSize',miniBatchSize, ... 'MiniBatchFcn',@preprocessUnlabeledSequences, ... 'MiniBatchFormat','CTB');
Закольцовывайте мини-пакеты и классифицируйте изображения с помощью modelPredictions
вспомогательная функция, перечисленная в конце примера.
predictions = modelPredictions(dlnet,mbqTest,classes);
Оцените точность классификации путем сравнения предсказанных меток с истинными метками валидации.
accuracy = mean(predictions == labelsValidation)
accuracy = 0.6721
Чтобы создать сеть, которая классифицирует видео непосредственно, соберите сеть с помощью слоев из обеих созданных сетей. Используйте слои из сверточной сети, чтобы преобразовать видео в векторные последовательности, и слои из сети классификации последовательностей, чтобы классифицировать векторные последовательности.
Следующая схема иллюстрирует сетевую архитектуру:
Чтобы ввести последовательности изображений в сеть, используйте входной слой последовательности.
Чтобы использовать сверточные слои для извлечения функций, то есть для применения сверточных операций к каждой системе координат видео независимо, используйте сверточные слои GoogLeNet.
Чтобы классифицировать получившиеся векторные последовательности, включите слои классификации последовательностей.
При обучении этого типа сети с trainNetwork
функция (не сделана в этом примере), вы должны использовать последовательность складывания и развертывания слоев, чтобы обработать видеокадры независимо. При обучении этого типа сети с dlnetwork
объект и пользовательский цикл обучения (как в этом примере), складывание и развертывание слоев последовательности не требуются, потому что сеть использует информацию о размерности, заданную dlarray
метки размерностей.
Добавление сверточных слоев
Сначала создайте график слоев сети GoogLeNet.
cnnLayers = layerGraph(netCNN);
Удалите слой входа ("data"
) и слои после слоя объединения, используемые для активаций ("pool5-drop_7x7_s1"
, "loss3-classifier"
, "prob"
, и "output"
).
layerNames = ["data" "pool5-drop_7x7_s1" "loss3-classifier" "prob" "output"]; cnnLayers = removeLayers(cnnLayers,layerNames);
Добавление входного слоя последовательности
Создайте слой входа последовательности, который принимает последовательности изображений, содержащие изображения того же размера входа, что и сеть GoogLeNet. Чтобы нормализовать изображения, используя то же среднее изображение, что и сеть GoogLeNet, установите 'Normalization'
опция входного слоя последовательности для 'zerocenter'
и 'Mean'
опция для среднего изображения входного слоя GoogLeNet.
inputSize = netCNN.Layers(1).InputSize(1:2); averageImage = netCNN.Layers(1).Mean; inputLayer = sequenceInputLayer([inputSize 3], ... 'Normalization','zerocenter', ... 'Mean',averageImage, ... 'Name','input');
Добавьте входной слой последовательности к графику слоев. Соедините выход входного слоя с входом первого сверточного слоя ("conv1-7x7_s2"
).
lgraph = addLayers(cnnLayers,inputLayer); lgraph = connectLayers(lgraph,"input","conv1-7x7_s2");
Добавление слоев классификации последовательностей
Добавьте предварительно обученные слои сети классификации последовательностей к графику слоев и соедините их.
Возьмите слои из сети классификации последовательностей и удалите входной слой последовательности.
lstmLayers = dlnet.Layers; lstmLayers(1) = [];
Добавьте слои классификации последовательностей к графику слоев. Соедините последний сверточный слой pool5-7x7_s1
на bilstm
слой.
lgraph = addLayers(lgraph,lstmLayers); lgraph = connectLayers(lgraph,"pool5-7x7_s1","bilstm");
Преобразуйте в dlnetwork
Чтобы иметь возможность делать предсказания, преобразуйте график слоев в dlnetwork
объект.
dlnetAssembled = dlnetwork(lgraph)
dlnetAssembled = dlnetwork with properties: Layers: [144×1 nnet.cnn.layer.Layer] Connections: [170×2 table] Learnables: [119×3 table] State: [2×3 table] InputNames: {'input'} OutputNames: {'softmax'} Initialized: 1
Разархивируйте файл pushup_mathworker.zip.
unzip("pushup_mathworker.zip")
Извлеченная pushup_mathworker
папка содержит видео отжимания. Создайте файл datastore для этой папки. Используйте пользовательскую функцию чтения, чтобы считать видео.
ds = fileDatastore("pushup_mathworker", ... 'ReadFcn',@readVideo);
Прочтите первое видео из datastore. Чтобы снова прочитать видео, сбросьте datastore.
video = read(ds); reset(ds);
Чтобы просмотреть видео, закольцовывайте отдельные системы координат и используйте image
функция. Также можно использовать implay
функция (требует Image Processing Toolbox).
numFrames = size(video,4); figure for i = 1:numFrames frame = video(:,:,:,i); image(frame); xticklabels([]); yticklabels([]); drawnow end
Чтобы предварительно обработать видео, чтобы иметь размер входа, ожидаемый сетью, используйте transform
функцию и применить imresize
функция для каждого изображения в datastore.
dsXTest = transform(ds,@(x) imresize(x,inputSize));
Чтобы управлять и обрабатывать немаркированные видео, создайте minibatchqueue:
Задайте мини-пакет размером 1.
Предварительная обработка видео с помощью preprocessUnlabeledVideos
вспомогательная функция, перечисленная в конце примера.
Для одинарного выхода datastore задайте формат пакета 'SSCTB'
(пространственный, пространственный, канал, время, пакет).
mbqTest = minibatchqueue(dsXTest,... 'MiniBatchSize',1,... 'MiniBatchFcn', @preprocessUnlabeledVideos,... 'MiniBatchFormat',{'SSCTB'});
Классификация видео с помощью modelPredictions
вспомогательная функция, заданная в конце этого примера. Функция ожидает трёх входов: a dlnetwork
объект, a minibatchqueue
Объект и массив ячеек, содержащий сетевые классы.
[predictions] = modelPredictions(dlnetAssembled,mbqTest,classes)
predictions = categorical
pushup
The readVideo
функция считывает видео в filename
и возвращает H-на-W-на-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
The modelGradients
функция принимает как вход dlnetwork
dlnet объекта
и мини-пакет входных данных dlX
с соответствующими метками Y
, и возвращает градиенты потерь относительно настраиваемых параметров в dlnet
, состояние сети и потери. Чтобы вычислить градиенты автоматически, используйте dlgradient
функция.
function [gradients,state,loss] = modelGradients(dlnet,dlX,Y) [dlYPred,state] = forward(dlnet,dlX); loss = crossentropy(dlYPred,Y); gradients = dlgradient(loss,dlnet.Learnables); end
The modelPredictions
функция принимает как вход dlnetwork
dlnet объекта
, а minibatchqueue
объект входных данных mbq
, и сетевых классов, и вычисляет предсказания модели путем итерации по всем данным в очереди мини-пакетов. Функция использует onehotdecode
функция для поиска предсказанного класса с самым высоким счетом. Функция возвращает предсказанные метки.
function [predictions] = modelPredictions(dlnet,mbq,classes) predictions = []; while hasdata(mbq) % Extract a mini-batch from the minibatchqueue and pass it to the % network for predictions [dlXTest] = next(mbq); dlYPred = predict(dlnet,dlXTest); % To obtain categorical labels, one-hot decode the predictions YPred = onehotdecode(dlYPred,classes,1)'; predictions = [predictions; YPred]; end end
The 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
The 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
The 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