主要内容

分类视频使用深度学习与自定义训练循环

这个例子展示了如何结合预先训练的图像分类模型和序列分类网络来创建一个视频分类网络。

您可以执行视频分类而不使用自定义训练循环使用Trainnetwork.函数。例如,请参见使用深度学习对视频进行分类.但是,如果培训选项不提供所需的选项(例如,自定义学习率计划),那么您可以定义自己的自定义训练循环,如此示例中所示。

创建视频分类深度学习网络:

  1. 使用预先训练的卷积神经网络(如GoogLeNet)将视频转换为特征向量序列,从每一帧中提取特征。

  2. 在序列上培训序列分类网络以预测视频标签。

  3. 组装一个网络,通过合并来自两个网络的层直接对视频进行分类。

网络架构如下图所示:

  • 要向网络输入图像序列,使用序列输入层。

  • 要从图像序列中提取特征,请使用覆盖陀螺网络网络的卷积层。

  • 为了对所得到的矢量序列进行分类,包括序列分类层。

当训练这种类型的网络Trainnetwork.函数(在本例中未完成),必须使用序列折叠和展开层来独立处理视频帧。当你用dlnetwork.对象和自定义训练循环(如在本示例中),不需要序列折叠和展开层,因为网络使用由所提供的维度信息dlarray.尺寸标签。

载荷净化卷积网络

将视频帧转换为特征向量,使用预先训练的网络的激活。

加载一个预先训练的GoogLeNet模型使用googlenet.函数。该功能需要深度学习工具箱™模型GoogLeNet网络金宝app支持包。如果没有安装此支金宝app持包,则该函数将提供下载链接。

netcnn = googlenet;

加载数据

下载HMBD51数据集从HMDB:大型人类运动数据库并将RAR文件提取到名为的文件夹中“hmdb51_org”.该数据集包含约2gb的视频数据,超过51类7000个片段,例如“喝”“运行”, 和“shake_hands”

提取RAR文件后,确保文件夹hmdb51_org包含以身体动作命名的子文件夹。如果它包含RAR文件,您也需要提取它们。使用支持函数金宝apphmdb51Files获取视频的文件名和标签。为了以准确性为代价提高训练速度,请指定范围[0 1]中的一个分数,以便仅从数据库中读取文件的随机子集。如果分数输入参数未指定,该函数hmdb51Files读取完整的数据集,而不改变文件的顺序。

dataFolder =“hmdb51_org”;分数= 1;(文件、标签)= hmdb51Files (dataFolder,分数);

使用读取第一个视频readvideo.Helper函数,在本例的最后定义,并查看视频的大小。这个视频是H——- - - - - -W——- - - - - -C——- - - - - -T数组,HWC, 和T分别为视频的高度、宽度、频道数和帧数。

Idx = 1;filename =文件(IDX);Video = ReadVideo(文件名);大小(视频)
ans =.1×4240 352 3 115

查看对应的标签。

标签(idx)
ans =.分类shoot_ball

要查看视频,请循环各个帧并使用图像函数。或者,您可以使用im功能(需要图像处理工具箱)。

numframes = size(视频,4);数字i = 1:numframes frame =视频(:,::,i);图像(帧);XTicklabels([]);yticklabels([]);drawn结尾

将帧转换为特征向量

使用卷积网络作为特征提取器:将视频帧输入到网络并提取激活。将视频转换为特征向量的序列,其中特征向量是输出的激活函数的最后一个池层的GoogLeNet网络(“pool5-7x7_s1”)。

这个图表说明了通过网络的数据流。

使用读取视频数据readvideo.函数,并调整其大小以匹配GoogLeNet网络的输入大小。注意,这个步骤可能需要很长时间才能运行。将视频转换为序列后,将序列和对应的标签保存在MAT文件中Tempdir.文件夹中。如果MAT文件已经存在,则直接从MAT文件加载序列和标签。如果MAT文件已经存在,但您想要覆盖它,请设置变量overwriteSequences真的

inputSize = netCNN.Layers (1) .InputSize (1:2);layername =“pool5-7x7_s1”;tempfile = fullfile(tempdir,“hmdb51_org.mat”);覆盖物= false;如果存在(tempfile,“文件”) && ~覆盖序列加载(tempFile)别的numFiles =元素个数(文件);序列=细胞(numFiles, 1);i = 1:numFiles fprintf("读取文件%d…\n",i,numfiles)视频= readvideo(文件(i));Video = Imresize(视频,输入);序列{i,1} =激活(netcnn,视频,layername,'outputas'“列”);结尾%保存序列及其关联的标签。保存(TempFile,“序列”“标签”“-v7.3”);结尾

查看前几个序列的大小。每个序列都是一个D——- - - - - -T数组,D是功能数量(池池层的输出大小)和T为视频的帧数。

序列(1:10)
ans =.10×1单元阵列{1024×115单} {1024×227单} {1024×180单} {1024×40单} {1024×156单} {1024×83单} {1024×42单} {1024×42单} {1024×82单} {1024×110单}

准备培训数据

通过将数据划分为培训和验证分区并删除任何长序列来准备培训的数据。

创建培训和验证分区

分区的数据。将90%的数据分配给训练分区,10%分配给验证分区。

numObservations =元素个数(序列);idx = randperm (numObservations);N = floor(0.9 * numobations);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步的训练序列及其相应的标签。

maxlength = 400;idx = sequencelength> maxlength;Sequencestain(IDX)= [];Labelstrain(IDX)= [];

为数据创建数据存储

创建一个ArrayDataStore.对象的序列和标签,然后将它们合并到一个数据存储中。

dsXTrain = arrayDatastore (sequencesTrain,“OutputType”'相同的');dsYTrain = arrayDatastore (labelsTrain,“OutputType”'细胞');dsTrain =结合(dsXTrain dsYTrain);

确定培训数据中的类。

Classes =类别(Labelstrain);

创建序列分类网络

接下来,创建一个序列分类网络,可以对代表视频的特征向量序列进行分类。

定义序列分类网络架构。指定以下网络图层:

  • 序列输入层,其输入大小与特征向量的特征维数相对应。

  • 一个包含2000个隐藏单元的BiLSTM层,然后是一个dropout层。要为每个序列只输出一个标签,请设置'OutputMode'选项BiLSTM层“最后一个”。

  • 退出层的概率为0.5。

  • 一个完全连接的层,输出大小对应于类别的类别和软MAX层。

numfeatures = size(Sequencestain {1},1);numclasses = numel(类别(labelstrain));图层= [sequentInputlayer(numfeatures,“名字”'顺序') bilstmLayer (2000,'OutputMode'“最后一次”“名字”'bilstm') dropoutLayer (0.5,“名字”'降低')全连接列(numcrasses,“名字”'fc')softmaxlayer(“名字”'softmax')];

将层转换为分层图对象。

lgraph = layerGraph(层);

创建一个dlnetwork.来自图层图的对象。

dlnet = dlnetwork (lgraph);

指定培训选项

训练15个纪元,指定16个小批量。

numEpochs = 15;miniBatchSize = 16;

指定Adam优化的选项。指定初始学习率为1E-4衰减量为0.001,梯度衰减量为0.9,平方梯度衰减量为0.999。

initialLearnRate = 1的军医;衰变= 0.001;gradDecay = 0.9;sqGradDecay = 0.999;

在一个情节中想象训练的进展。

情节=“训练进步”

列车序列分类网络

创建一个minibatchqueue对象,在训练期间处理和管理小批量序列。为每个mini-batch:

  • 使用自定义小批量预处理功能preprocessLabeledSequences(在本例的最后定义)将标签转换为虚拟变量。

  • 使用维标签格式化向量序列数据'CTB'(通道、时间、批次)。默认情况下,minibatchqueue对象将数据转换为dlarray.底层类型的对象.不要向类标签添加格式。

  • 在可用的GPU上进行训练。默认情况下,minibatchqueue对象将每个输出转换为GPUArray.如果可用GPU,则对象。使用GPU需要并行计算工具箱™和支持的GPU设备。金宝app有关支持设备的信息,请参阅金宝appGPU通金宝app过发布支持(并行计算工具箱)

MBQ = Minibatchqueue(Dstrain,...'minibatchsize',小匹马,...“MiniBatchFcn”@preprocessLabeledSequences,...“MiniBatchFormat”,{'CTB'''});

初始化培训进度图。

如果plots ==“训练进步”figure lineosstrain = animatedline(“颜色”,[0.85 0.325 0.098]);ylim([0 inf])xlabel(“迭代”) ylabel (“损失”) 网格结尾

初始化ADAM求解器的平均梯度和平均平均平均梯度参数。

averageGrad = [];averageSqGrad = [];

使用自定义训练循环训练模型。对于每个epoch,洗牌数据并在小批数据上循环。为每个mini-batch:

  • 评估模型梯度、状态和损失使用dlfevalmodelGradients函数并更新网络状态。

  • 确定基于时间的衰减学习率计划的学习率:对于每个迭代,求解器使用给定的学习率 ρ t ρ 0 1 + k t , 在哪里t为迭代数, ρ 0 是初始学习率,和k是衰减的。

  • 使用使用的网络参数更新网络参数adamupdate函数。

  • 显示培训进度。

注意,跑步训练可能需要很长时间。

迭代= 0;start = tic;%循环epochs。时代= 1:numEpochs%Shuffle数据。洗牌(MBQ);%循环在迷你批次。尽管Hasdata(MBQ)迭代=迭代+ 1;%读取迷你批次数据。[dlX, dlY] = next(mbq);使用dlfeval和% modelGradients函数。[梯度,状态,损失] = DLFeval(@ Maposgradients,Dlnet,DLX,DLY);%确定基于时间的衰减学习率计划的学习率。learnRate = initialLearnRate/(1 +衰减*迭代);%使用Adam优化器更新网络参数。[dlnet,sharmergrad,averagesqgrad] = adamupdate(dlnet,渐变,平均普利德,averagesqgrad,...迭代,学习,graddecay,sqgraddecay);%显示训练进度。如果plots ==“训练进步”d =持续时间(0,0,toc(start),'格式''hh:mm:ss');Addpoints(LineLoStrain,迭代,Double(收集(提取数据(丢失))))标题(”时代:“+时代+“ 的 ”+ numepochs +”,过去:“+字符串(d))绘制结尾结尾结尾

测试模型

通过比较真实标签的验证集的预测来测试模型的分类准确性。

培训完成后,对新数据进行预测不需要标签。

创建一个minibatchqueue测试对象:

  • 仅创建一个仅包含测试数据的预测器的数组数据存储区。

  • 指定用于培训的相同百分比大小。

  • 使用该预处理预测器preprocessUnlabeledSequences辅助功能,在示例的末尾列出。

  • 对于数据存储的单个输出,请指定迷你批处理格式'CTB'(通道、时间、批次)。

dsXValidation = arrayDatastore (sequencesValidation,“OutputType”'相同的');mbqtest = minibatchqueue(dsxvalidation,...'minibatchsize',小匹马,...“MiniBatchFcn”,@ preprocessunlabeledsequences,...“MiniBatchFormat”'CTB');

循环使用小批量和分类的图像模特预分规辅助功能,在示例的末尾列出。

预测= modelPredictions (dlnet、mbqTest、类);

通过将预测标签与真实验证标签进行比较,评价分类的准确性。

精度=平均值(预测== labelsValidation)
精度= 0.6721

组装视频分类网络

要创建直接对视频进行分类的网络,请使用来自所有创建的网络的图层组装网络。使用来自卷积网络的图层将视频从序列分类网络转换为向量序列和层,以对矢量序列进行分类。

网络架构如下图所示:

  • 要向网络输入图像序列,使用序列输入层。

  • 使用卷积层提取特征,即对视频的每一帧单独进行卷积操作,使用GoogLeNet卷积层。

  • 为了对所得到的矢量序列进行分类,包括序列分类层。

当训练这种类型的网络Trainnetwork.功能(未在此示例中完成),您必须使用序列折叠和展开层独立处理视频帧。培训这种类型的网络时dlnetwork.对象和自定义训练循环(如在本示例中),不需要序列折叠和展开层,因为网络使用由所提供的维度信息dlarray.尺寸标签。

添加回旋的层

首先,创建Googlenet网络的图层图。

cnnLayers = layerGraph (netCNN);

删除输入层(“数据”)和用于激活的池化层之后的层(“pool5-drop_7x7_s1”“损失3分类器”“概率”, 和“输出”)。

LayerNames = [“数据”“pool5-drop_7x7_s1”“损失3分类器”“概率”“输出”];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”...“的意思是”,平均,...“名字”“输入”);

将序列输入层添加到层图中。将输入层的输出连接到第一卷积层的输入(“conv1-7x7_s2”)。

Lgraph = Addlayers(CNNLayers,InputLayer);Lgraph = ConnectLayers(LAPHAGE,“输入”“conv1-7x7_s2”);

添加序列分类层

将之前训练的序列分类网络层添加到层图中,并将它们连接起来。

从序列分类网络中取出各层,去掉序列输入层。

lstmLayers = dlnet.Layers;lstmLayers (1) = [];

将序列分类图层添加到图层图。连接最后一个卷积层pool5-7x7_s1到了bilstm层。

lgraph = addLayers (lgraph lstmLayers);Lgraph = ConnectLayers(LAPHAGE,“pool5-7x7_s1”“bilstm”);

转换为dlnetwork.

为了能够做到预测,将图层图转换为adlnetwork.对象。

dlnetassembled = dlnetwork(lgraph)
dlnetassemble = 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_mathwanker.zip。

解压缩(“pushup_mathworker.zip”

提取的pushup_mathworker文件夹包含一个俯卧撑的视频。为此文件夹创建文件数据存储。使用自定义读取功能来读取视频。

ds = filedatastore(“pushup_mathworker”...“ReadFcn”, @readVideo);

从数据存储中读取第一个视频。为了能够再次读取视频,重置数据存储。

视频=阅读(ds);重置(ds);

要查看视频,请循环各个帧并使用图像函数。或者,您可以使用im功能(需要图像处理工具箱)。

numframes = size(视频,4);数字i = 1:numframes frame =视频(:,::,i);图像(帧);XTicklabels([]);yticklabels([]);drawn结尾

为了对视频进行预处理,使其具有网络所期望的输入大小,使用变换功能并应用imresize.函数对数据存储中的每个图像执行。

dsXTest = transform(ds,@(x) imresize(x,inputSize));

为了管理和处理未标记的视频,创建一个minibatchqueue:

  • 指定迷你批处理大小为1。

  • 预处理视频使用preprocessUnlabeledVideos辅助功能,在示例的末尾列出。

  • 对于数据存储的单个输出,请指定迷你批处理格式'ssctb'(空间、空间、通道、时间、批处理)。

mbqTest = minibatchqueue (dsXTest,...'minibatchsize',1,...“MiniBatchFcn”@preprocessUnlabeledVideos,...“MiniBatchFormat”,{'ssctb'});

分类视频使用模特预分规辅助函数,在本例的最后定义。该函数需要三个输入:adlnetwork.对象,Aminibatchqueue对象和包含网络类的单元格数组。

(预测)= modelPredictions (dlnetAssembled、mbqTest类)
预测=分类俯卧撑

辅助功能

电子阅读功能

readvideo.函数读取视频文件名并返回A.H——- - - - - -W——- - - - - -C-经过-T数组,HWC, 和T分别为视频的高度、宽度、频道数和帧数。

功能视频= readVideo(filename) vr = VideoReader(filename);H = vr.Height;W = vr.Width;C = 3;%预分配视频阵列numframes =楼层(vr.duration * vr.framerate);Video = Zeros(H,W,C,NumFrames,“uint8”);%阅读框架我= 0;尽管hasfame(vr)i = i + 1;视频(:,:,:,i)= ReadFrame(VR);结尾%删除未分配的框架如果尺寸(视频,4)> i视频(:,:,:,i + 1:结束)= [];结尾结尾

模型梯度函数

modelGradients函数接受输入adlnetwork.对象DLNET.和迷你批次输入数据dlX与相应的标签Y,并返回损失相对于中可学习参数的梯度DLNET.,网络状态和丢失。要自动计算渐变,请使用Dlgradient.函数。

功能[gradient,state,loss] = modelGradients(dlnet,dlX,Y) [dlpred,state] = forward(dlnet,dlX);损失= crossentropy (dlYPred Y);梯度= dlgradient(损失、dlnet.Learnables);结尾

模型的预测函数

模特预分规函数接受输入adlnetwork.对象DLNET., 一种minibatchqueue输入数据的对象MBQ.,并通过迭代迷你批处理队列中的所有数据来计算模型预测。函数使用onehotdecode函数找到具有最高分的预测类。该函数返回预测的标签。

功能[预测] = ModelPrictictions(DLNET,MBQ,类)预测= [];尽管hasdata(兆贝可)%从小闩扣上提取迷你批次并将其传递给%网络用于预测[dlXTest] =下一个(兆贝可);dlYPred =预测(dlnet dlXTest);%要获得分类标签,一次热解码预测YPred = onehotdecode (dlYPred、类1)';预测=[预测;YPred];结尾结尾

标记序列数据预处理功能

preprocessLabeledSequences函数使用以下步骤对序列数据进行预处理:

  1. 使用拼图序列在时间尺寸下填充序列并在批处理维度中连接它们。

  2. 从传入单元格数组中提取标签数据并连接到分类数组中。

  3. 单热编码分类标签到数字阵列中。

  4. 转置一热编码标签的数组以匹配网络输出的形状。

功能[X, Y] = preprocessLabeledSequences(XCell,YCell)%在第二个维度(时间)中用零填充序列,并沿第三个维度连接%维度(批量)X = padsequences(伊势亚2);%提取来自细胞和连接的标记数据Y =猫(1,YCell{1:结束});%一次性编码标签y = onehotencode(y,2);%转换编码标签以匹配网络输出Y = Y ';结尾

未标记的序列数据预处理功能

preprocessUnlabeledSequences功能预处理序列数据使用拼图序列函数。这个函数在时间维度中用0填充序列,并在批处理维度中连接结果。

功能[X] = preprocessUnlabeledSequences(伊势亚)%在第二个维度(时间)中用零填充序列,并沿第三个维度连接%维度(批量)X = padsequences(伊势亚2);结尾

未标记的视频数据预处理功能

preprocessUnlabeledVideos功能预处理未标记的视频数据使用拼图序列函数。此功能在时间范围内填充零的视频,并将结果连接在批处理维度中。

功能[X] = preprocessUnlabeledVideos(伊势亚)%在第四维度(时间)和零中的序列%沿第五维度连接(批量)X = padsequences(伊势亚,4);结尾

另请参阅

||||

相关的话题