主要内容

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

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

方法可以在不使用自定义训练循环的情况下执行视频分类trainNetwork函数。有关示例,请参见使用深度学习分类视频.然而,如果trainingOptions不提供您需要的选项(例如,自定义学习速率计划),那么您可以定义自己的自定义训练循环,如本例所示。

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

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

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

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

组网结构如下图所示:

  • 要将图像序列输入到网络,请使用序列输入层。

  • 为了从图像序列中提取特征,使用预训练的GoogLeNet网络中的卷积层。

  • 为了对得到的向量序列进行分类,需要包含序列分类层。

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

加载预训练卷积网络

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

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

网络cnn =谷歌网络;

加载数据

从。下载HMBD51数据集HMDB:大型人体运动数据库并将RAR文件解压缩到一个名为“hmdb51_org”.该数据集包含约2 GB的视频数据,涵盖51个类别的7000个剪辑,例如“喝”“运行”,“shake_hands”

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

dataFolder =“hmdb51_org”;分数= 1;[files,labels] = hmdb51Files(dataFolder,fraction);

阅读第一个视频使用readVideoHelper函数,在本例的末尾定义,并查看视频的大小。这个视频是H——- - - - - -W——- - - - - -C——- - - - - -T数组,HWC,T分别为视频的高度、宽度、通道数和帧数。

Idx = 1;Filename = files(idx);视频= readVideo(文件名);大小(视频)
ans =1×4240 352 3 115

查看对应标签。

标签(idx)
ans =分类shoot_ball

要查看视频,请遍历各个帧并使用图像函数。或者,您可以使用implay函数(需要图像处理工具箱)。

numFrames = size(视频,4);数字i = 1:numFrames frame = video(:,:,:,i);图像(框架);xticklabels ([]);yticklabels ([]);drawnow结束

将帧转换为特征向量

使用卷积网络作为特征提取器:将视频帧输入网络并提取激活。将视频转换为特征向量序列,其中特征向量是函数的输出激活函数在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”);overwriteSequences = false;如果存在(tempFile“文件”overwriteSequences load(tempFile)其他的numFiles = numel(文件);sequences = cell(numFiles,1);i = 1:numFiles fprintf"读取%d的文件%d…\n", i, numFiles)视频= readVideo(文件(i));视频= imresize(视频,inputSize);序列{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×60个}{1024×156单}{1024×83单}{1024×42个}{1024×82单}{1024×110单}

准备培训数据

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

创建训练和验证分区

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

numObservations = numel(序列);idx = randperm(numObservations);N =地板(0.9 * numObservations);idxTrain = idx(1:N);sequencesTrain = sequences(idxTrain);labelsTrain = labels(idxTrain);idxValidation = idx(N+1:结束);sequencesValidation = sequences(idxValidation);labelsValidation = labels(idxValidation);

删除长序列

比网络中的典型序列长得多的序列可以在训练过程中引入大量的填充。填充过多会对分类精度产生负面影响。

获得训练数据的序列长度,并将其可视化为训练数据的直方图。

numObservationsTrain = numel(sequencesTrain);sequenceLengths = 0 (1,numObservationsTrain);i = 1:numObservationsTrain sequence = sequencesTrain{i};sequenceLengths(i) = size(sequence,2);结束数字直方图(sequenceLengths)“序列长度”)包含(“序列长度”) ylabel (“频率”

只有少数序列有超过400个时间步长。为了提高分类精度,将超过400个时间步长的训练序列与其对应的标签一起去除。

maxLength = 400;idx = sequenceLengths > maxLength;sequencesTrain(idx) = [];labelsTrain(idx) = [];

为数据创建数据存储

创建一个arrayDatastore对象获取序列和标签,然后将它们组合到单个数据存储中。

dsXTrain = arrayDatastore(sequencesTrain,“OutputType”“相同”);dsYTrain = arrayDatastore(labelsTrain,“OutputType”“细胞”);dsTrain = combine(dsXTrain,dsYTrain);

确定训练数据中的类。

类=类别(labelsTrain);

创建序列分类网络

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

定义序列分类网络体系结构。指定以下网络层:

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

  • 一个带有2000个隐藏单元的BiLSTM层,后面还有一个退出层。若要为每个序列只输出一个标签,请设置“OutputMode”选项的BiLSTM层“最后一个”。

  • 一个概率为0.5的退出层。

  • 一个全连接层,其输出大小对应于类的数量和一个softmax层。

numFeatures = size(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个epoch,并指定一个小批大小为16。

numEpochs = 15;miniBatchSize = 16;

指定Adam优化的选项。指定的初始学习速率1的军医衰减为0.001,梯度衰减为0.9,平方梯度衰减为0.999。

initialLearnRate = 1e-4;衰减= 0.001;gradDecay = 0.9;sqGradDecay = 0.999;

将训练过程可视化。

情节=“训练进步”

列车序列分类网络

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

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

  • 用维度标签格式化矢量序列数据“施”(通道,时间,批次)。默认情况下,minibatchqueue对象将数据转换为dlarray具有基础类型的对象.不要向类标签添加格式。

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

mbq = minibatchqueue(dsTrain,...“MiniBatchSize”miniBatchSize,...“MiniBatchFcn”@preprocessLabeledSequences,...“MiniBatchFormat”, {“施”});

初始化培训进度图。

如果情节= =“训练进步”图lineLossTrain = animatedline(“颜色”,[0.85 0.325 0.098]);Ylim ([0 inf]) xlabel(“迭代”) ylabel (“损失”网格)结束

初始化平均梯度和平均平方梯度参数的亚当求解器。

averageGrad = [];averageSqGrad = [];

使用自定义训练循环训练模型。对于每个纪元,洗牌数据并在小批量数据上循环。对于每个小批量:

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

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

  • 方法更新网络参数adamupdate函数。

  • 显示培训进度。

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

迭代= 0;开始= tic;%遍历epoch。epoch = 1:numEpochs% Shuffle数据。洗牌(兆贝可);在小批上循环。Hasdata (mbq)迭代=迭代+ 1;读取小批数据。[dlX, ly] = next(mbq);使用dlfeval和% modelGradients函数。[gradients,state,loss] = dlfeval(@modelGradients,dlnet,dlX,dlY);确定基于时间的衰减学习率计划的学习率。learnRate = initialLearnRate/(1 +衰减*迭代);使用Adam优化器更新网络参数。。[dlnet,averageGrad,averageSqGrad] = adamupdate(dlnet,gradients,averageGrad,averageSqGrad,...迭代,learnRate、gradDecay sqGradDecay);%显示培训进度。如果情节= =“训练进步”D = duration(0,0,toc(start),“格式”“hh: mm: ss”);addpoints (lineLossTrain、迭代、双(收集(extractdata(损失))))标题(”时代:“+ epoch +“的”+ numEpochs +,消失:"+字符串(D))现在绘制结束结束结束

测试模型

通过将验证集上的预测结果与真实标签进行比较,检验模型的分类精度。

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

要创建minibatchqueue测试对象:

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

  • 指定用于训练的相同的小批大小。

  • 属性预处理预测器preprocessUnlabeledSequencesHelper函数,在示例末尾列出。

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

dsXValidation = arrayDatastore(sequencesValidation,“OutputType”“相同”);mbqTest = minibatchqueue(dsXValidation,...“MiniBatchSize”miniBatchSize,...“MiniBatchFcn”@preprocessUnlabeledSequences,...“MiniBatchFormat”“施”);

对小批进行循环,并使用modelPredictionsHelper函数,在示例末尾列出。

预测= modelforecasts (dlnet,mbqTest,classes);

通过比较预测标签和真实验证标签来评估分类精度。

accuracy = mean(预测== labelsValidation)
准确度= 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(“输入”“conv1-7x7_s2”);

添加序列分类层

将之前训练好的序列分类网络层添加到层图中并连接。

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

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

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

lgraph = addLayers(lgraph,lstmLayers);lgraph = connectLayers(“pool5-7x7_s1”“bilstm”);

转换为dlnetwork

为了能够进行预测,将层图转换为adlnetwork对象。

dlnetinstalled = 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);

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

Video = read(ds);重置(ds);

要查看视频,请遍历各个帧并使用图像函数。或者,您可以使用implay函数(需要图像处理工具箱)。

numFrames = size(视频,4);数字i = 1:numFrames frame = video(:,:,:,i);图像(框架);xticklabels ([]);yticklabels ([]);drawnow结束

要对视频进行预处理,使其具有网络所期望的输入大小,请使用变换函数和应用imresize函数赋给数据存储中的每个图像。

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

若要管理和处理未标记的视频,请创建minibatchqueue:

  • 指定一个小批大小为1。

  • 对视频进行预处理preprocessUnlabeledVideosHelper函数,在示例末尾列出。

  • 对于数据存储的单个输出,指定迷你批处理格式“SSCTB”(空间,空间,通道,时间,批次)。

mbqTest = minibatchqueue(dsXTest,...“MiniBatchSize”, 1...“MiniBatchFcn”@preprocessUnlabeledVideos,...“MiniBatchFormat”, {“SSCTB”});

类对视频进行分类modelPredictionsHelper函数,在本例的末尾定义。该函数需要三个输入:adlnetwork对象,minibatchqueue对象,以及包含网络类的单元格数组。

[forecasts] = modelforecasts (dlnetassemble,mbqTest,classes)
预测=分类俯卧撑

辅助函数

视频读取功能

readVideo函数将视频读入文件名并返回H——- - - - - -W——- - - - - -C-由- - - - - -T数组,HWC,T分别为视频的高度、宽度、通道数和帧数。

函数视频= readVideo(文件名)vr = VideoReader(文件名);H = vr.高度;W = vr.宽度;C = 3;预分配视频数组numFrames =地板(vr。Duration * vr.FrameRate); video = zeros(H,W,C,numFrames,“uint8”);读帧百分比I = 0;hasFrame(vr) i = i + 1;video(:,:,:,i) = readFrame(vr);结束移除未分配的帧如果Size (video,4) > I video(:,:,:, I +1:end) = [];结束结束

模型梯度函数

modelGradients函数以a作为输入dlnetwork对象dlnet和一小批输入数据dlX有相应的标签Y,并返回损失相对于中可学习参数的梯度dlnet、网络状态、丢失。要自动计算梯度,请使用dlgradient函数。

函数[gradients,state,loss] = modelGradients(dlnet,dlX,Y) [dlYPred,state] = forward(dlnet,dlX);loss = crossentropy(dlYPred,Y);gradients = dlgradient(loss,dlnet.Learnables);结束

模型预测函数

modelPredictions函数以a作为输入dlnetwork对象dlnet,一个minibatchqueue输入数据的对象兆贝可,和网络类,并通过迭代小批队列中的所有数据来计算模型预测。函数使用onehotdecode函数来查找得分最高的预测班级。该函数返回预测的标签。

函数[forecasts] = modelforecasts (dlnet,mbq,classes) forecasts = [];hasdata(兆贝可)从minibatchqueue中提取一个迷你批,并将其传递给%预测网络[dlXTest] = next(mbq);dlYPred = predict(dlnet,dlXTest);为了获得分类标签,一次性解码预测YPred = onehotdecode(dlYPred,classes,1)';预测=[预测;YPred];结束结束

标记序列数据预处理函数

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

  1. 使用padsequences函数在时间维度中填充序列,并在批处理维度中连接它们。

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

  3. One-hot将分类标签编码为数字数组。

  4. 调换单热编码标签数组以匹配网络输出的形状。

函数[X, Y] = preprocessLabeledSequences(XCell,YCell)在第二个维度(时间)用零填充序列,并沿着第三个维度连接%尺寸(批)X = padsequences(XCell,2);从单元格和级联中提取标签数据Y = cat(1,YCell{1:end});单热编码标签Y = onehotencode(Y,2);转置编码标签以匹配网络输出Y = Y';结束

无标记序列数据预处理函数

preprocessUnlabeledSequences函数对序列数据进行预处理padsequences函数。该函数在时间维度上填充带零的序列,并在批处理维度上连接结果。

函数[X] = preprocessUnlabeledSequences(XCell)在第二个维度(时间)用零填充序列,并沿着第三个维度连接%尺寸(批)X = padsequences(XCell,2);结束

无标签视频数据预处理函数

preprocessUnlabeledVideos函数对未标记的视频数据进行预处理padsequences函数。该函数在时间维度上垫零视频,并在批处理维度上连接结果。

函数[X] = preprocessUnlabeledVideos(XCell)在第四维(时间)中用零填充序列和%沿第五维连接(批)X = padsequences(XCell,4);结束

另请参阅

||||

相关的话题