使用具有自定义训练循环的深度学习对视频进行分类
这个例子展示了如何通过结合预先训练的图像分类模型和序列分类网络来创建视频分类网络。
方法可以在不使用自定义训练循环的情况下执行视频分类trainNetwork
函数。示例请参见使用深度学习对视频进行分类.然而,如果trainingOptions
不提供您需要的选项(例如,自定义学习速率计划),那么您可以定义您自己的自定义训练循环,如本例所示。
创建视频分类深度学习网络:
使用预先训练的卷积神经网络(如GoogLeNet)将视频转换为特征向量序列,从每一帧提取特征。
在这些序列上训练一个序列分类网络来预测视频标签。
通过组合来自两个网络的层来组装一个网络,直接对视频进行分类。
网络架构如下图所示:
要向网络输入图像序列,使用序列输入层。
为了从图像序列中提取特征,使用来自预训练的GoogLeNet网络的卷积层。
要对结果向量序列进行分类,请包括序列分类层。
当训练这种类型的网络与trainNetwork
函数(在本例中没有执行),您必须使用序列折叠和展开层来独立处理视频帧。当你用一个dlnetwork
对象和自定义训练循环(如本例所示),则不需要序列折叠和展开层,因为网络使用由dlarray
尺寸标签。
加载预训练卷积网络
为了将视频帧转换为特征向量,使用预先训练的网络的激活。
加载一个预先训练的GoogLeNet模型googlenet
函数。此功能需要深度学习工具箱™模型GoogLeNet网络支持包。如果没有安装此支持包,则该功能提供下载链接。
netCNN = googlenet;
加载数据
下载HMBD51数据集HMDB:一个大型的人体运动数据库并将RAR文件解压缩到一个名为“hmdb51_org”
.该数据集包含大约2 GB的视频数据,涵盖了51个类中的7000个剪辑,例如“喝”
,“运行”
,“shake_hands”
.
解压RAR文件后,确保文件夹hmdb51_org
包含以身体动作命名的子文件夹。如果它包含RAR文件,您还需要提取它们。使用配套功能hmdb51Files
获取视频的文件名和标签。为了以准确性为代价加速训练,请在范围[0 1]中指定一个分数,以便只从数据库中读取文件的随机子集。如果分数
输入参数未指定,函数hmdb51Files
在不改变文件顺序的情况下读取完整的数据集。
dataFolder =“hmdb51_org”;分数= 1;(文件、标签)= hmdb51Files (dataFolder,分数);
阅读第一个视频使用readVideo
Helper函数(在本例末尾定义),并查看视频的大小。这个视频是H——- - - - - -W——- - - - - -C——- - - - - -T数组,H,W,C,T分别是视频的高度、宽度、通道数和帧数。
idx = 1;文件名=文件(idx);视频= readVideo(文件名);大小(视频)
ans =1×4240 352 3 115
查看对应标签。
标签(idx)
ans =分类shoot_ball
若要查看视频,请遍历各个帧并使用图像
函数。或者,您也可以使用implay
函数(需要图像处理工具箱)。
numFrames =大小(视频、4);数字为i = 1:numFrames frame = video(:,:,:,i);图像(框架);xticklabels ([]);yticklabels ([]);drawnow结束
将帧转换为特征向量
使用卷积网络作为特征提取器:将视频帧输入网络并提取激活。将视频转换为特征向量序列,其中特征向量是激活
函数在GoogLeNet网络的最后一个池化层(“pool5-7x7_s1”
).
这张图说明了通过网络的数据流。
方法读取视频数据readVideo
函数,并调整它的大小以匹配GoogLeNet网络的输入大小。注意,这个步骤可能需要很长时间才能运行。将视频转换为序列后,将序列和相应的标签保存在tempdir
文件夹中。如果MAT文件已经存在,则直接从MAT文件加载序列和标签。如果MAT文件已经存在,但您想要覆盖它,请设置该变量overwriteSequences
来真正的
.
inputSize = netCNN.Layers (1) .InputSize (1:2);layerName =“pool5-7x7_s1”;tempFile = fullfile (tempdir,“hmdb51_org.mat”);overwriteSequences = false;如果存在(tempFile“文件”&& ~重写序列加载(tempFile)其他的numFiles =元素个数(文件);序列=细胞(numFiles, 1);为i = 1:numFiles fprintf“正在读取文件%d的%d…\n”, i, numFiles) video = readVideo(files(i));视频= imresize(视频、inputSize);序列{我1}=激活(layerName netCNN、视频,“OutputAs”,“列”);结束保存序列和与之关联的标签。保存(tempFile,“序列”,“标签”,“-v7.3”);结束
查看前几个序列的大小。每个序列都是D——- - - - - -T数组,D特征的数量(池化层的输出大小)和T是视频的帧数。
序列(1:10)
ans =10×1单元阵列{1024×115单}{1024×227单}{1024×180单}{1024×40个}{1024×60个}{1024×156单}{1024×83单}{1024×42个}{1024×82单}{1024×110单}
准备训练数据
通过将数据划分为训练和验证分区并删除任何长序列,为训练准备数据。
创建培训和验证分区
分区的数据。将90%的数据分配给训练分区,10%分配给验证分区。
numObservations =元素个数(序列);idx = randperm (numObservations);N = floor(0.9 * numObservations);idxTrain = idx (1: N);sequencesTrain =序列(idxTrain);labelsTrain =标签(idxTrain);idxValidation = idx (N + 1:结束);sequencesValidation =序列(idxValidation);labelsValidation =标签(idxValidation);
删除长序列
在网络中比典型序列长得多的序列会在训练过程中引入大量的填充。填充过多会对分类精度产生负面影响。
获取训练数据的序列长度,并在训练数据的直方图中可视化它们。
numObservationsTrain =元素个数(sequencesTrain);numObservationsTrain sequenceLengths = 0 (1);为i = 1:numObservationsTrain sequence = sequencesTrain{i};sequenceLengths (i) =(序列,2)大小;结束图直方图(sequenceLengths)标题(“序列长度”)包含(“序列长度”) ylabel (“频率”)
只有少数序列的时间步数超过400。为了提高分类精度,可以去掉时间步数超过400的训练序列及其对应的标签。
最大长度= 400;idx = sequenceLengths > maxLength;sequencesTrain (idx) = [];labelsTrain (idx) = [];
为数据创建数据存储
创建一个arrayDatastore
对象获取序列和标签,然后将它们组合到单个数据存储中。
dsXTrain = arrayDatastore (sequencesTrain,“OutputType”,“相同”);dsYTrain = arrayDatastore (labelsTrain,“OutputType”,“细胞”);dsTrain =结合(dsXTrain dsYTrain);
确定训练数据中的类。
类=类别(labelsTrain);
创建序列分类网络
接下来,创建一个序列分类网络,可以对代表视频的特征向量序列进行分类。
定义序列分类网络体系结构。指定以下网络层:
一种序列输入层,其输入大小与特征向量的特征维数相对应。
有2000个隐藏单元的BiLSTM层,之后有一个退出层。若要为每个序列只输出一个标签,请设置
“OutputMode”
选项的BiLSTM层“最后一个”。
一个概率为0.5的退出层。
一个完全连接的层,输出大小与类的数量相对应,还有一个softmax层。
numFeatures =大小(sequencesTrain {1}, 1);numClasses =元素个数(类别(labelsTrain));layers = [sequenceInputLayer(numFeatures,“名字”,“序列”) bilstmLayer (2000,“OutputMode”,“最后一次”,“名字”,“bilstm”) dropoutLayer (0.5,“名字”,“下降”) fullyConnectedLayer (numClasses“名字”,“俱乐部”) softmaxLayer (“名字”,“softmax”));
将图层转换为alayerGraph
对象。
lgraph = layerGraph(层);
创建一个dlnetwork
对象从图层图。
dlnet = dlnetwork (lgraph);
指定培训选项
训练15个周期,并指定最小批大小为16。
numEpochs = 15;miniBatchSize = 16;
指定Adam优化的选项。的初始学习率1的军医
衰减为0.001,梯度衰减为0.9,平方梯度衰减为0.999。
initialLearnRate = 1的军医;衰变= 0.001;gradDecay = 0.9;sqGradDecay = 0.999;
把训练的过程想象成一个图形。
情节=“训练进步”;
列车序列分类网络
创建一个minibatchqueue
对象,该对象在训练期间处理和管理小批量序列。为每个mini-batch:
使用自定义的小批量预处理功能
preprocessLabeledSequences
(在本例末尾定义)将标签转换为虚拟变量。用维度标签格式化矢量序列数据
“施”
(通道、时间、批次)。默认情况下,minibatchqueue
对象将数据转换为dlarray
具有基础类型的对象单
.不要向类标签添加格式。如果有GPU,请使用GPU进行训练。默认情况下,
minibatchqueue
对象将每个输出转换为gpuArray
对象,如果GPU可用。使用GPU需要并行计算工具箱™和支持的GPU设备。有关支持的设备的信息,请参见GPU计算的需求(并行计算工具箱).
兆贝可= minibatchqueue (dsTrain,...“MiniBatchSize”miniBatchSize,...“MiniBatchFcn”@preprocessLabeledSequences,...“MiniBatchFormat”,{“施”,”});
初始化训练进度图。
如果情节= =“训练进步”图lineLossTrain = animatedline(“颜色”[0.85 0.325 0.098]);ylim([0正])包含(“迭代”) ylabel (“损失”网格)在结束
初始化平均梯度和平均平方梯度参数的亚当求解。
averageGrad = [];averageSqGrad = [];
使用自定义训练循环训练模型。对于每个历元,洗牌数据并遍历小批数据。为每个mini-batch:
对模型的梯度、状态和损失进行评估
dlfeval
和modelGradients
功能和更新网络状态。为基于时间的衰减学习率时间表确定学习率:对于每一次迭代,求解器使用给定的学习率 ,在那里t为迭代次数, 是初始学习率,和k是衰减的。
更新网络参数
adamupdate
函数。显示培训进度。
注意,训练可能需要很长时间。
迭代= 0;开始=抽搐;循环遍历各个时代。为时代= 1:numEpochs%洗牌数据。洗牌(兆贝可);在小批量上循环。而Hasdata (mbq)迭代=迭代+ 1;读取小批数据。[dlX, dlY] = next(mbq);评估模型的梯度,状态和损失使用dlfeval和% modelGradients函数。(渐变、州损失)= dlfeval (@modelGradients, dlnet dlX,海底);为基于时间的衰减学习率时间表确定学习率。learnRate = initialLearnRate/(1 + decay*iteration);使用Adam优化器更新网络参数。[dlnet, averageGrad averageSqGrad] = adamupdate (dlnet、渐变averageGrad averageSqGrad,...迭代,learnRate、gradDecay sqGradDecay);显示培训进度。如果情节= =“训练进步”D =持续时间(0,0,toc(开始),“格式”,“hh: mm: ss”);addpoints (lineLossTrain、迭代、双(收集(extractdata(损失))))标题(”时代:“+时代+“的”+ numEpochs +”,过去:“+ drawnow字符串(D))结束结束结束
测试模型
通过将验证集上的预测结果与真实标签进行比较,检验模型的分类精度。
训练完成后,对新数据进行预测不需要标签。
创建一个minibatchqueue
测试对象:
创建一个数组数据存储,只包含测试数据的预测器。
指定与培训时相同的小批大小。
方法对预测器进行预处理
preprocessUnlabeledSequences
Helper函数,在示例末尾列出。对于数据存储的单个输出,指定小批处理格式
“施”
(通道、时间、批次)。
dsXValidation = arrayDatastore (sequencesValidation,“OutputType”,“相同”);mbqTest = minibatchqueue (dsXValidation,...“MiniBatchSize”miniBatchSize,...“MiniBatchFcn”@preprocessUnlabeledSequences,...“MiniBatchFormat”,“施”);
循环遍历小批并使用modelPredictions
Helper函数,在示例末尾列出。
预测= modelPredictions (dlnet、mbqTest、类);
通过比较预测的标签和真实的验证标签来评估分类的准确性。
准确性=平均值(预测==标签验证)
精度= 0.6721
组建视频分类网络
要创建直接对视频进行分类的网络,请使用来自所创建的两个网络的层组合一个网络。利用卷积网络中的层将视频转换为向量序列,利用序列分类网络中的层对向量序列进行分类。
网络架构如下图所示:
要向网络输入图像序列,使用序列输入层。
使用卷积层提取特征,即独立对视频的每一帧进行卷积运算,可以使用GoogLeNet卷积层。
要对结果向量序列进行分类,请包括序列分类层。
当训练这种类型的网络与trainNetwork
函数(在本例中没有执行),您必须使用序列折叠和展开层来独立处理视频帧。当训练这种类型的网络时dlnetwork
对象和自定义训练循环(如本例所示),则不需要序列折叠和展开层,因为网络使用由dlarray
尺寸标签。
添加回旋的层
首先,创建GoogLeNet网络的层图。
cnnLayers = layerGraph (netCNN);
删除输入层(“数据”
)和用于激活的池化层之后的层(“pool5-drop_7x7_s1”
,“loss3-classifier”
,“概率”
,“输出”
).
layerNames = [“数据”“pool5-drop_7x7_s1”“loss3-classifier”“概率”“输出”];cnnLayers = removeLayers (cnnLayers layerNames);
添加序列输入层
创建一个序列输入层,接受包含与GoogLeNet网络相同输入大小的图像序列。若要使用与GoogLeNet网络相同的平均图像来规范化图像,请设置“归一化”
序列输入层的选项“zerocenter”
和“的意思是”
选项为GoogLeNet输入层的平均图像。
inputSize = netCNN.Layers (1) .InputSize (1:2);averageImage = netCNN.Layers (1) .Mean;inputLayer = sequenceInputLayer([inputSize 3],...“归一化”,“zerocenter”,...“的意思是”averageImage,...“名字”,“输入”);
将序列输入图层添加到图层图中。将输入层的输出连接到第一个卷积层的输入(“conv1-7x7_s2”
).
lgraph = addLayers (cnnLayers inputLayer);lgraph = connectLayers (lgraph,“输入”,“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)
dlnetinstalled = 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。
解压缩(“pushup_mathworker.zip”)
提取的pushup_mathworker
文件夹里有一个俯卧撑的视频。为这个文件夹创建一个文件数据存储。使用自定义读取功能来读取视频。
ds = fileDatastore (“pushup_mathworker”,...“ReadFcn”, @readVideo);
从数据存储中读取第一个视频。要能够再次读取视频,请重置数据存储。
视频=阅读(ds);重置(ds);
若要查看视频,请遍历各个帧并使用图像
函数。或者,您也可以使用implay
函数(需要图像处理工具箱)。
numFrames =大小(视频、4);数字为i = 1:numFrames frame = video(:,:,:,i);图像(框架);xticklabels ([]);yticklabels ([]);drawnow结束
要对视频进行预处理,使其具有网络期望的输入大小,请使用变换
函数并应用imresize
函数对应到数据存储中的每个图像。
dsXTest = transform(ds,@(x) imresize(x,inputSize));
要管理和处理未标记的视频,请创建minibatchqueue:
指定小批处理大小为1。
方法对视频进行预处理
preprocessUnlabeledVideos
Helper函数,在示例末尾列出。对于数据存储的单个输出,指定小批处理格式
“SSCTB”
(空间,空间,渠道,时间,批次)。
mbqTest = minibatchqueue (dsXTest,...“MiniBatchSize”,1,...“MiniBatchFcn”@preprocessUnlabeledVideos,...“MiniBatchFormat”,{“SSCTB”});
对视频进行分类modelPredictions
Helper函数,在本例末尾定义。该函数期望三个输入:adlnetwork
对象,minibatchqueue
对象,以及包含网络类的单元格数组。
(预测)= modelPredictions (dlnetAssembled、mbqTest类)
预测=分类俯卧撑
辅助函数
电子阅读功能
的readVideo
函数将视频读入文件名
并返回一个H——- - - - - -W——- - - - - -C-
由- - - - - -T数组,H,W,C,T分别是视频的高度、宽度、通道数和帧数。
函数视频= readVideo(文件名)vr = VideoReader(文件名);H = vr.Height;W = vr.Width;C = 3;预分配视频数组numFrames =地板(虚拟现实。Duration * vr.FrameRate); video = zeros(H,W,C,numFrames,“uint8”);%阅读框架我= 0;而hasFrame(vr) i = i + 1;视频(::,:,我)= readFrame (vr);结束删除未分配的帧如果Size (video,4) > I video(:,:,:, I +1:end) = [];结束结束
模型梯度函数
的modelGradients
函数以a作为输入dlnetwork
对象dlnet
以及一小批输入数据dlX
与相应的标签Y
,并返回损失相对于可学习参数的梯度dlnet
,网络状态和损失。方法可自动计算梯度dlgradient
函数。
函数[gradient,state,loss] = modelGradients(dlnet,dlX,Y) [dlYPred,state] = forward(dlnet,dlX);损失= crossentropy (dlYPred Y);梯度= dlgradient(损失、dlnet.Learnables);结束
模型的预测函数
的modelPredictions
函数以a作为输入dlnetwork
对象dlnet
,一个minibatchqueue
输入数据的对象。兆贝可
,并通过迭代小批处理队列中的所有数据来计算模型预测。函数使用onehotdecode
函数求出预测分数最高的班级。函数返回预测的标签。
函数[prediction] = modelforecasts (dlnet,mbq,classes) forecasts = [];而hasdata(兆贝可)从小批队列中提取一个小批,并将其传递给%预测网络[dlXTest] =下一个(兆贝可);dlYPred =预测(dlnet dlXTest);为了获得分类标签,一次热解码预测。YPred = onehotdecode (dlYPred、类1)';预测=[预测;YPred];结束结束
标记序列数据预处理函数
的preprocessLabeledSequences
函数对序列数据进行预处理,步骤如下:
使用
padsequences
函数在时间维度中填充序列,并将它们连接到批处理维度中。从传入的单元格数组中提取标签数据并连接到分类数组中。
一热编码类别标签到数字数组。
将单热编码标签的数组转置,以匹配网络输出的形状。
函数[X, Y] = preprocessLabeledSequences(XCell,YCell)在第二次元(时间)中用零填充序列,并沿第三次元连接。%维度(批处理)X = padsequences(伊势亚2);从单元格中提取标签数据并连接Y =猫(1,YCell{1:结束});单热编码标签Y, Y = onehotencode (2);将编码的标签转置以匹配网络输出Y = Y ';结束
无标记序列数据预处理函数
的preprocessUnlabeledSequences
函数对序列数据进行预处理padsequences
函数。这个函数在时间维度上用零填充序列,并在批处理维度上连接结果。
函数[X] = preprocessUnlabeledSequences(伊势亚)在第二次元(时间)中用零填充序列,并沿第三次元连接。%维度(批处理)X = padsequences(伊势亚2);结束
无标签视频数据预处理功能
的preprocessUnlabeledVideos
函数预处理未标记的视频数据padsequences
函数。该函数将视频在时间维度中填充为零,并将结果连接到批处理维度中。
函数[X] = preprocessUnlabeledVideos(伊势亚)在四维(时间)和中填充零序列%沿第五维连接(批)X = padsequences(伊势亚,4);结束
另请参阅
lstmLayer
|sequenceInputLayer
|dlfeval
|dlgradient
|dlarray