主要内容

图像分类网络的参数修剪与量化

这个例子展示了如何使用两个参数评分指标修剪经过训练的神经网络的参数:量级评分[1]和Synaptic Flow评分[2]。

在许多应用中,转移学习被用来为新任务重新训练图像分类网络,或者从头开始训练新网络,最优的网络架构是未知的,网络可能被过度参数化。过度参数化的网络具有冗余连接。结构化修剪,也称为稀疏化,是一种压缩技术,旨在识别冗余的、不必要的连接,可以在不影响网络精度的情况下删除。当您将修剪与网络量化结合使用时,您可以减少网络的推断时间和内存占用,使其更容易部署。

这个例子展示了如何:

  • 执行训练后的、迭代的、非结构化修剪,而不需要训练数据

  • 评估两种不同修剪算法的性能

  • 研究修剪后引起的层间稀疏性

  • 评估修剪对分类精度的影响

  • 评估量化对裁剪网络分类精度的影响

这个例子使用一个简单的卷积神经网络将手写数字从0到9进行分类。有关设置用于培训和验证的数据的更多信息,请参见创建简单的深度学习网络分类

加载预先训练的网络和数据

加载培训和验证数据。训练卷积神经网络的分类任务。

[imdsTrain, imdsValidation] = loadDigitDataset;net = trainDigitDataNetwork(imdsTrain, imdsValidation);truelabel = imdsValidation.Labels;classes = categories(trueLabels);

创建一个minibatchqueue对象,该对象包含验证数据。集executionEnvironment自动评估一个GPU上的网络,如果一个可用的话。默认情况下,minibatchqueue对象将每个输出转换为gpuArray如果有可用的GPU。使用GPU需要并行计算工具箱™和支持的GPU设备。有关支持的设备的信息,请参见GPU计算要求(并行计算工具箱)

executionEnvironment =“汽车”;miniBatchSize = 128;imdsValidation。ReadSize = miniBatchSize;mbqValidation = minibatchqueue(imdsValidation,1,...“MiniBatchSize”miniBatchSize,...“MiniBatchFormat”“SSCB”...“MiniBatchFcn”@preprocessMiniBatch,...“OutputEnvironment”, executionEnvironment);

神经网络剪枝

神经网络修剪的目标是在不影响网络精度的前提下,识别并删除不重要的连接,以减少网络的规模。在下面的图中,在左边,网络有连接,将每个神经元映射到下一层的神经元。修剪后的网络连接数比原来的网络少。

pruneImage.bmp

修剪算法为网络中的每个参数分配一个分数。该分数对网络中每个连接的重要性进行排名。你可以使用以下两种修剪方法中的一种来实现目标稀疏性:

  • 一次性修剪——根据连接的评分一步删除指定百分比的连接。当您指定高稀疏值时,此方法容易导致层崩溃。

  • 迭代修剪——通过一系列迭代步骤实现目标稀疏性。当评估分数对网络结构很敏感时,可以使用此方法。分数在每次迭代时都要重新评估,因此使用一系列步骤可以使网络逐步向稀疏性移动。

这个例子使用迭代修剪方法来实现目标稀疏性。

迭代修剪

pruningWF.png

转换为dlnetwork对象

在本例中,您使用Synaptic Flow算法,它要求您创建一个自定义的成本函数,并计算相对于成本函数的梯度,以计算参数得分。要创建一个自定义的成本函数,首先要将预训练的网络转换为一个dlnetwork

将网络转换为层图,并删除用于分类使用的层removeLayers

lgraph = layerGraph(net.Layers);lgraph = removeLayers(lgraph,[“softmax”“classoutput”]);Dlnet = dlnetwork(lgraph);

使用analyzeNetwork分析网络结构和可学习参数。

analyzeNetwork (dlnet)

修剪前评估网络的准确性。

accuracyOriginalNet = evaluateAccuracy(dlnet,mbqValidation,classes,trueLabels)
准确性originalnet = 0.9908

具有可学习参数的层为3个卷积层和1个全连通层。该网络最初由总共21578个可学习参数组成。

numTotalParams = sum(cellfun(@numel,dlnet. learables . value))
numTotalParams = 21578
numNonZeroPerParam = cellfun(@(w)nnz(extractdata(w)),dlnet. learables . value)
numNonZeroPerParam =8×172 8 1152 16 4608 32 15680 10

稀疏性定义为网络中参数值为零的百分比。检查网络的稀疏性。

initialSparsity = 1-(sum(numNonZeroPerParam)/numTotalParams)
initialSparsity = 0

在修剪之前,网络的稀疏度为零。

创建迭代方案

要定义一个迭代修剪方案,指定目标稀疏度和迭代次数。对于本例,使用线性间隔迭代来实现目标稀疏性。

numIterations = 10;targetSparsity = 0.90;iterationScheme = linspace(0,targetSparsity,numIterations);

修剪循环

对于每个迭代,本例中的自定义修剪循环执行以下步骤:

  • 计算每个连接的得分。

  • 基于所选修剪算法对网络中所有连接的分数进行排序。

  • 确定删除分数最低的连接的阈值。

  • 使用阈值创建修剪掩码。

  • 对网络的可学习参数应用修剪掩码。

网络掩码

修剪算法不是将权重数组中的条目直接设置为零,而是为每个可学习参数创建一个二进制掩码,以指定连接是否被修剪。掩码允许您探索修剪网络的行为,并在不改变底层网络结构的情况下尝试不同的修剪方案。

例如,考虑以下权重。

testWeight = [10.4 5.6 0.8 9];

为testWeight中的每个参数创建一个二进制掩码。

testMask = [1 0 1 0];

应用蒙版到testWeight得到修剪后的权值。

testWeightsPruned = testWeight.*testMask
testWeightsPruned =1×410.4000 0 0.8000 0

在迭代修剪中,您为每个包含修剪信息的迭代创建一个二进制掩码。对权重数组应用掩码不会改变数组的大小或神经网络的结构。因此,在推断或压缩磁盘上的网络大小时,修剪步骤不会直接导致任何加速。

初始化一个图,比较修剪后的网络与原始网络的准确性。

图图(100*iterationScheme([1,end]),100*accuracyOriginalNet*[1 1],“* - b”“线宽”,2,“颜色”“b”) ylim([0 100]) xlim(100*iterationScheme([1,end])) xlabel([0 100])“稀疏(%)”) ylabel (“精度(%)”)传说(“原来的准确性”“位置”“西南”)标题(“修剪准确性”网格)

级修剪

幅度修剪[1]为每个参数分配一个等于其绝对值的分数。假设参数的绝对值对应于其对训练网络精度的相对重要性。

初始化掩码。对于第一次迭代,您不修剪任何参数,稀疏度为0%。

pruningMaskMagnitude = cell(1,numIterations);pruningmask量级{1}= dlupdate(@(p)true(size(p)), dlnet.Learnables);

下面是大小修剪的实现。该网络在循环中被修剪到不同的目标稀疏度,以提供根据其精度选择修剪网络的灵活性。

lineAccuracyPruningMagnitude = animatedline(“颜色”‘g’“标记”“o”“线宽”, 1.5);传奇(“原来的准确性”“幅度修剪精度”“位置”“西南”%计算数量级分数scoresMagnitude = calculateMagnitudeScore(dlnet);idx = 1: numl (iterationScheme) prunedNetMagnitude = dlnet;更新修剪掩码pruningmask量级{idx} = calculateMask(scores量级,iterationScheme(idx));检查修剪掩码中零条目的数量numPrunedParams = sum(cellfun(@(m)nnz(~extractdata(m)), pruningmask量级{idx}.Value));sparsity = numPrunedParams/numTotalParams;%对网络参数应用修剪掩码prunedNetMagnitude。Learnables = dlupdate(@(W,M)W。* M, prunedNetMagnitude。可学的,pruningMaskMagnitude {idx});计算修剪网络上的验证精度accuracyMagnitude = evaluateAccuracy(prunedNetMagnitude,mbqValidation,classes,trueLabels);显示修剪进度addpoints (lineAccuracyPruningMagnitude 100 *稀疏,100 * accuracyMagnitude) drawnow结束

SynFlow修剪

突触流量守恒(SynFlow)[2]评分用于修剪。您可以使用此方法修剪使用线性激活函数(如ReLU)的网络。

初始化掩码。对于第一次迭代,没有参数被修剪,稀疏度为0%。

pruningMaskSynFlow = cell(1,numIterations);pruningMaskSynFlow{1} = dlupdate(@(p)true(size(p)),dlnet.Learnables);

用于计算分数的输入数据是包含多个分数的单个图像。如果使用GPU,请将数据转换为agpuArray

dlX = dlarray(ones(net.Layers(1).InputSize),SSC的);如果(executionEnvironment = =“汽车”&& canUseGPU) || executionEnvironment ==“图形”dlX = gpuArray(dlX);结束

下面的循环为修剪[2]实现迭代突触流评分,其中自定义成本函数为用于网络修剪的每个参数评估SynFlow评分。

lineAccuracyPruningSynflow = animatedline(“颜色”“r”“标记”“o”“线宽”, 1.5);传奇(“原来的准确性”“幅度修剪精度”“突触流量精度”“位置”“西南”) prunedNetSynFlow = dlnet;迭代地增加稀疏性idx = 1:numel(iterationScheme)计算SynFlow评分scoresSynFlow = calculateSynFlowScore(prunedNetSynFlow,dlX);更新修剪掩码pruningMaskSynFlow{idx} = calculateMask(scoresSynFlow,iterationScheme(idx));检查修剪掩码中零条目的数量numPrunedParams = sum(cellfun(@(m)nnz(~extractdata(m)),pruningMaskSynFlow{idx}.Value));sparsity = numPrunedParams/numTotalParams;%对网络参数应用修剪掩码prunedNetSynFlow。Learnables = dlupdate(@(W,M)W。* M, prunedNetSynFlow。可学的,pruningMaskSynFlow {idx});计算修剪网络上的验证精度accuracySynFlow = evaluateAccuracy(prunedNetSynFlow,mbqValidation,classes,trueLabels);显示修剪进度addpoints (lineAccuracyPruningSynflow 100 *稀疏,100 * accuracySynFlow) drawnow结束

研究剪枝网络的结构

选择修剪网络的程度是准确性和稀疏性之间的权衡。使用稀疏度与精度图来选择具有所需稀疏度水平和可接受精度的迭代。

pruningMethod =“SynFlow”;selectedIteration =8;prunedDLNet = createPrunedNet(dlnet,selectedIteration,pruningMaskSynFlow, pruningmask量级,pruningMethod);[sparsityPerLayer,prunedChannelsPerLayer,numOutChannelsPerLayer,layerNames] = pruningStatistics(prunedDLNet);

早期的卷积层通常修剪较少,因为它们包含更多关于图像的核心底层结构(如边缘和角落)的相关信息,这对解释图像至关重要。

绘制所选修剪方法和迭代的每层稀疏度。

图栏(sparsityPerLayer*100) title(“每层稀疏度”)包含(“层”) ylabel (“稀疏(%)”) xticks(1: numl (sparsityPerLayer)) xticklabels(layerNames) xtickangle(45) set(gca,“TickLabelInterpreter”“没有”

当指定较低的目标稀疏性时,修剪算法会修剪单个连接。当你指定一个高的目标稀疏性,修剪算法可以修剪整个滤波器和神经元在卷积或全连接层。

图酒吧([prunedChannelsPerLayer, numOutChannelsPerLayer-prunedChannelsPerLayer],“堆叠”)包含(“层”) ylabel (“过滤器数量”)标题(“每层滤镜数量”) xticks(1:(numel(layerNames))) xticklabels(layerNames) xtickangle(45) legend(“修剪的通道/神经元数量”“原通道数/神经元”“位置”“southoutside”甘氨胆酸)组(,“TickLabelInterpreter”“没有”

评估网络准确性

比较修剪前后网络的精度。

YPredOriginal = modelforecasts (dlnet,mbqValidation,classes);accorigoriginal = mean(YPredOriginal == trueLabels)
accorigoriginal = 0.9908
YPredPruned = modelforecasts (prunedDLNet,mbqValidation,classes);accPruned = mean(YPredPruned == trueLabels)
accPruned = 0.9328

创建一个混淆矩阵图,对原始裁剪后的网络从真实的类标签到预测的类标签进行探索。

图confusionchart (trueLabels YPredOriginal);标题(“原始网络”

每个类别的数字数据验证集包含250张图像,因此,如果网络完美地预测了每个图像的类别,对角线上的所有分数都等于250,对角线以外没有值。

confusionchart (trueLabels YPredPruned);标题(“删除网络”

在对网络进行修剪时,将原始网络的混淆图与修剪后的网络进行比较,看看在选定的稀疏度水平下,每个类标签的准确率发生了怎样的变化。如果对角线上的所有数字都大致相等地减少,则不存在偏差。然而,如果减少不相等,您可能需要通过减少变量的值从早期迭代中选择一个修剪网络selectedIteration

量化修剪网络

用MATLAB训练的深度神经网络使用单精度浮点数据类型。即使是很小的网络也需要大量的内存和硬件来执行浮点算术操作。这些限制可能会抑制计算能力较低、内存资源较少的深度学习模型的部署。通过使用较低的精度来存储权重和激活,可以减少网络的内存需求。您可以将深度学习工具箱与深度学习模型量化库支持包结合使用,通过将卷积层的权重、偏差和激活量化为8位缩放整数数据类型,来减少深度神经网络的内存占用。

修剪网络会影响每一层的参数和激活的范围统计,因此量化网络的精度会发生变化。为了探究这种差异,可以对修剪后的网络进行量化,并使用量化后的网络进行推理。

将数据分成校准和验证数据集。

校正数据存储= splitEachLabel(imdsTrain,0.1,“随机”);validationDataStore = imdsValidation;

创建一个dlquantizer对象,并指定修剪后的网络作为要量化的网络。

prunedNet = assembly enetwork ([prunedDLNet.]层;net.Layers (end-1:结束)]);quantObjPrunedNetwork = dlquantizer(prunedNet,“ExecutionEnvironment”“图形”);

使用校准功能用于使用校准数据测试网络,并在每一层收集权重、偏差和激活的范围统计信息。

calResults = calibrate(quantObjPrunedNetwork, calibrationDataStore)

使用验证函数,使用验证数据集比较量化前后网络的结果。

valResults = validate(quantObjPrunedNetwork, validationDataStore);

检查MetricResults。结果字段的验证输出,看量化网络的准确性

valResults.MetricResults.Result valResults。统计数据

小批量预处理功能

preprocessMiniBatch函数通过从输入单元格数组中提取图像数据并连接到数值数组来预处理小批预测器。对于灰度输入,连接第四个维度上的数据将为每个图像添加第三个维度,用作单通道维度。

函数X = preprocessMiniBatch(XCell)从单元格中提取图像数据并连接。X = cat(4,XCell{:});结束

模型精度函数

评估分类的准确性dlnetwork.准确率是网络正确分类标签的百分比。

函数YPred = modelforecasts (dlnet,mbqValidation,classes,trueLabels);accuracy = mean(YPred == trueLabels);结束

SynFlow Score命令功能

calculateSynFlowScore函数计算突触流(SynFlow)分数。突触显著性[2]被描述为一类基于梯度的评分,定义为损失梯度乘以参数值的乘积:

synFlowScore d 损失 d θ θ

SynFlow评分是一个突触显著性评分,它使用所有网络输出的总和作为损失函数:

损失 f 腹肌 θ X

f 函数是用神经网络表示的吗

θ 网络的参数

X 输入数组是网络吗

若要计算与此损失函数相关的参数梯度,请使用dlfeval以及一个梯度函数模型。

函数= calculateSynFlowScore(dlnet,dlX) dlnet。Learnables = dlupdate(@abs, dlnet.Learnables);gradient = dlfeval(@modelGradients,dlnet,dlX);分数= dlupdate(@(g,w)g。*w, gradients, dlnet.Learnables);结束

SynFlow评分的模型梯度

函数gradient = modelGradients(dlNet,inputArray)计算dlnetwork的给定输入的梯度dlYPred = predict(dlNet,inputArray);pseudoloss = sum(dlYPred,“所有”);gradient = dlgradient(pseudoloss,dlNet.Learnables);结束

幅度评分函数

calculateMagnitudeScore函数返回大小评分,定义为参数的元素绝对值。

函数score = calculateMagnitudeScore(dlnet) score = dlupdate(@abs, dlnet. learnables);结束

掩码生成功能

calculateMask函数根据给定的分数和目标稀疏性返回网络参数的二进制掩码。

函数mask = calculateMask(scores量级,稀疏性)根据按参数计算的分数计算二进制掩码,使掩码包含由稀疏性指定的零百分比。将分数单元格数组压扁为一个长分数向量。flatedscores = cell2mat(cellfun(@(S)extractdata(gather(S(:))),scoresMagnitude。值,“UniformOutput”、假));对分数进行排序,并确定删除连接的阈值给定稀疏度%扁平化分数=排序(扁平化分数);k = round(sparsity*numel(flatedscores));如果K ==0阈值=0;其他的thresh = flatedscores (k);结束创建二进制掩码mask = dlupdate(@(S)S>thresh, scores量级);结束

模型预测函数

modelPredictions函数以a作为输入dlnetworK对象dlnet, aminibatchqueue输入数据的兆贝可,并通过迭代minibatchqueue对象中的所有数据来计算模型预测。函数使用onehotdecode函数求出预测分数最高的班级。

函数forecasts (dlnet,mbq,classes) forecasts = [];hasdata(mbq) dlXTest = next(mbq);dlYPred = softmax(predict(dlnet,dlXTest));YPred = onehotdecode(dlYPred,classes,1)';预言=[预言;YPred];结束重置(兆贝可)结束

应用剪枝函数

createPrunedNet函数返回指定修剪算法和迭代的修剪后的dlnetwork。

函数prunedNet = createPrunedNet(dlnet,selectedIteration,pruningMaskSynFlow, pruningmask量级,pruningMethod)开关pruningMethod情况下“级”prunedNet = dlupdate(@(W,M)W。*M, dlnet, pruningMaskMagnitude{selectedIteration});情况下“SynFlow”prunedNet = dlupdate(@(W,M)W。*M, dlnet, pruningMaskSynFlow{selectedIteration});结束结束

剪枝统计命令功能

pruningStatistics函数提取详细的层级修剪统计信息,如层级稀疏性和被修剪的过滤器或神经元的数量。

sparsityPerLayer -每一层修剪参数的百分比

prunedChannelsPerLayer -每个层中的通道/神经元的数量,可以作为修剪的结果被删除

numOutChannelsPerLayer -每层中的通道/神经元的数量

函数[sparsityPerLayer,prunedChannelsPerLayer,numOutChannelsPerLayer,layerNames] = pruningStatistics(dlnet) layerNames = unique(dlnet. learables . layer,“稳定”);numLayers = numel(layerNames);layerIDs = 0 (numLayers,1);idx = 1:numel(layerNames) layerIDs(idx) = find(layerNames(idx)=={dlnet.Layers.Name});结束sparsityPerLayer = 0 (numLayers,1);prunedChannelsPerLayer = 0 (numLayers,1);numOutChannelsPerLayer = 0 (numLayers,1);numParams = 0 (numLayers,1);numPrunedParams = 0 (numLayers,1);idx = 1:numLayers layer = dlnet.Layers(layerIDs(idx));计算稀疏性paramIDs = strcmp(dlnet. learables . layer,layerNames(idx));paramValue = dlnet. learables . value (paramIDs);p = 1:numel(paramValue) numParams(idx) = numParams(idx) + numel(paramValue{p});numPrunedParams(idx) = numPrunedParams(idx) + nnz(extractdata(paramValue{p})==0);结束%计算通道统计信息sparsityPerLayer(idx) = numPrunedParams(idx)/numParams(idx);开关类(层)情况下“nnet.cnn.layer.FullyConnectedLayer”numOutChannelsPerLayer(idx) = layer.OutputSize;prunedChannelsPerLayer(idx) = nnz(all(layer.Weights==0,2)&layer.Bias(:)==0);情况下“nnet.cnn.layer.Convolution2DLayer”numOutChannelsPerLayer(idx) = layer.NumFilters;prunedChannelsPerLayer (idx) = nnz(重塑(所有(layer.Weights = = 0, [1, 2, 3]), [], 1) &layer.Bias (:) = = 0);情况下“nnet.cnn.layer.GroupedConvolution2DLayer”numOutChannelsPerLayer(idx) = layer.NumGroups*layer.NumFiltersPerGroup;prunedChannelsPerLayer (idx) = nnz(重塑(所有(layer.Weights = = 0, [1, 2, 3]), [], 1) &layer.Bias (:) = = 0);否则错误(“未知层:”+类(层))结束结束结束

加载数字数据集功能

loadDigitDataset函数加载Digits数据集,并将数据分割为训练数据和验证数据。

函数[imdsTrain, imdsValidation] = loadDigitDataset() digitDatasetPath = fullfile(matlabroot,“工具箱”“nnet”“nndemos”...“nndatasets”“DigitDataset”);imds = imageDatastore(digitDatasetPath,...“IncludeSubfolders”,真的,“LabelSource”“foldernames”);[imdsTrain, imdsValidation] = splitEachLabel(imds,0.75,“随机”);结束

训练数字识别网络功能

trainDigitDataNetwork函数训练卷积神经网络来分类灰度图像中的数字。

函数net = trainDigitDataNetwork(imdsTrain,imdsValidation) layers = [imageInputLayer([28 28 1],“归一化”“rescale-zero-one”) convolution2dLayer (3 8“填充”“相同”reluLayer maxPooling2dLayer(2,“步”2) convolution2dLayer(16日“填充”“相同”reluLayer maxPooling2dLayer(2,“步”32岁的,2)convolution2dLayer (3“填充”“相同”) reluLayer fullyConnectedLayer(10) softmaxLayer classificationLayer];指定培训选项options = trainingOptions(“个”...“InitialLearnRate”, 0.01,...“MaxEpochs”10...“洗牌”“every-epoch”...“ValidationData”imdsValidation,...“ValidationFrequency”30岁的...“详细”假的,...“阴谋”“没有”“ExecutionEnvironment”“汽车”);列车网络net = trainNetwork(imdsTrain,layers,options);结束

参考文献

[1]宋汉,Jeff Pool, John Tran, William J. Dally, 2015。学习有效神经网络的权值和连接神经信息处理系统进展28 (NIPS 2015): 1135-1143。

[2]田中秀秀,丹尼尔·库宁,丹尼尔·l·k·亚明斯和Surya Ganguli 2020。“通过迭代保存突触流来修剪没有任何数据的神经网络。”第34届神经信息处理系统会议(NeurlPS 2020)

另请参阅

功能

相关的话题

Baidu
map