图像分类网络的参数剪枝与量化
这个例子展示了如何使用两个参数评分指标修剪训练过的神经网络的参数:Magnitude评分[1]和Synaptic Flow评分[2]。
在许多应用中,使用迁移学习重新训练图像分类网络以完成新任务,或者从头开始训练新网络,最优网络架构是未知的,并且网络可能被过度参数化。过度参数化的网络具有冗余连接。结构化剪枝,也称为稀疏化,是一种压缩技术,旨在识别冗余的、不必要的连接,可以在不影响网络精度的情况下删除。当您将修剪与网络量化结合使用时,您可以减少网络的推断时间和内存占用,从而使其更容易部署。
这个例子展示了如何:
在不需要训练数据的情况下执行训练后,迭代的非结构化修剪
评估两种不同剪枝算法的性能
研究修剪后引起的分层稀疏性
评估修剪对分类精度的影响
评估量化对修剪网络分类精度的影响
这个例子使用了一个简单的卷积神经网络将手写数字从0到9进行分类。有关设置用于训练和验证的数据的详细信息,请参见创建简单的深度学习网络分类.
加载预训练的网络和数据
加载训练和验证数据。训练卷积神经网络来完成分类任务。
[imdsTrain, imdsValidation] = loadDigitDataset;net = trainDigitDataNetwork(imdsTrain, imdsValidation);trueLabels = imdsValidation.Labels;classes = categories(trueLabels);
创建一个minibatchqueue
对象,其中包含验证数据。集executionEnvironment
来自动评估GPU上的网络,如果GPU可用的话。默认情况下,minibatchqueue
对象将每个输出转换为gpuArray
如果GPU可用。使用GPU需要并行计算工具箱™和受支持的GPU设备。金宝app有关受支持设备的信息,请参见金宝appGPU支金宝app持版本(并行计算工具箱).
executionEnvironment =“汽车”;miniBatchSize = 128;imdsValidation。ReadSize = miniBatchSize;mbqValidation = minibatchqueue(imdsValidation,1,...“MiniBatchSize”miniBatchSize,...“MiniBatchFormat”,“SSCB”,...“MiniBatchFcn”@preprocessMiniBatch,...“OutputEnvironment”, executionEnvironment);
神经网络剪枝
神经网络剪枝的目标是在不影响网络精度的情况下,识别和删除不重要的连接,以减小网络的大小。在下面的图中,在左边,网络具有将每个神经元映射到下一层神经元的连接。剪枝后的网络连接数比原来的网络少。
剪枝算法为网络中的每个参数分配一个分数。该分数对网络中每个连接的重要性进行排名。您可以使用以下两种修剪方法之一来实现目标稀疏性:
一次修剪-根据连接的分数在一步中删除指定百分比的连接。当您指定高稀疏值时,此方法容易导致层崩溃。
迭代修剪-在一系列迭代步骤中实现目标稀疏性。当评估的分数对网络结构敏感时,可以使用这种方法。分数在每次迭代中都会重新评估,因此使用一系列步骤可以让网络逐步走向稀疏性。
本例使用迭代修剪方法来实现目标稀疏性。
迭代修剪
转换为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)
accuracyOriginalNet = 0.9908
具有可学习参数的层是3个卷积层和一个全连接层。该网络最初由总共21578个可学习参数组成。
numTotalParams = sum(cellfun(@ nummel,dlnet.Learnables.Value))
numTotalParams = 21578
numNonZeroPerParam = cellfun(@(w)nnz(extractdata(w)),dlnet.Learnables.Value)
numNonZeroPerParam =8×172 8 1152 16 4608 32 15680 10
稀疏性定义为网络中参数值为零的百分比。检查网络的稀疏性。
初始值= 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]))“稀疏(%)”) ylabel (“精度(%)”)传说(“原来的准确性”,“位置”,“西南”)标题(“修剪准确性”网格)在
级修剪
幅度修剪[1]为每个参数分配一个等于其绝对值的分数。假设一个参数的绝对值对应于它对训练网络精度的相对重要性。
初始化掩码。对于第一次迭代,不修剪任何参数,稀疏度为0%。
pruningMaskMagnitude = cell(1,numIterations);pruningMaskMagnitude{1} = dlupdate(@(p)true(size(p)), dlnet.Learnables);
下面是幅度修剪的实现。该网络被修剪为循环中的各种目标稀疏度,以提供根据其精度选择修剪网络的灵活性。
lineAccuracyPruningMagnitude = animatedline(“颜色”,‘g’,“标记”,“o”,“线宽”, 1.5);传奇(“原来的准确性”,“星等剪枝精度”,“位置”,“西南”)计算震级分数scoresMagnitude = calculateMagnitudeScore(dlnet);为idx = 1: nummel (iterationScheme) prunedNetMagnitude = dlnet;更新修剪掩码pruningMaskMagnitude{idx} = calculateMask(scoresMagnitude,iterationScheme(idx));检查修剪掩码中零条目的数量numPrunedParams = sum(cellfun(@(m)nnz(~extractdata(m)),pruningMaskMagnitude{idx}.Value));sparsity = numPrunedParams/numTotalParams;对网络参数应用修剪掩码prunedNetMagnitude。可学习内容= 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。可学习内容= 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,pruningMaskMagnitude,pruningMethod);[sparsityPerLayer,prunedChannelsPerLayer,numOutChannelsPerLayer,layerNames] = pruningStatistics(prunedDLNet);
早期的卷积层通常被修剪得较少,因为它们包含更多关于图像的核心低级结构(例如边缘和角落)的相关信息,这些信息对于解释图像至关重要。
绘制所选修剪方法和迭代的每层稀疏度。
figure bar(sparsityPerLayer*100)“每层稀疏性”)包含(“层”) ylabel (“稀疏(%)”xticks(1:numel(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);accOriginal = mean(YPredOriginal == trueLabels)
accOriginal = 0.9908
YPredPruned = modelPredictions(prunedDLNet,mbqValidation,classes);accPruned = mean(YPredPruned == trueLabels)
accPruned = 0.9328
创建一个混淆矩阵图,以探索原始和修剪网络的真实类标签到预测类标签。
图confusionchart (trueLabels YPredOriginal);标题(“原始网络”)
数字数据的验证集包含每个类别的250张图像,因此如果一个网络完美地预测了每个图像的类别,那么对角线上的所有分数都等于250,并且对角线之外没有任何值。
confusionchart (trueLabels YPredPruned);标题(“删除网络”)
在修剪网络时,将原始网络和修剪后的网络的混淆图进行比较,以检查在所选稀疏度水平下,每个类标签的准确性是如何变化的。如果对角线上的所有数字都大致相等地减少,则不存在偏差。但是,如果减少的值不相等,您可能需要通过减少变量的值来从早期迭代中选择一个修剪过的网络selectedIteration
.
量化修剪网络
在MATLAB中训练的深度神经网络使用单精度浮点数据类型。即使是很小的网络也需要相当数量的内存和硬件来执行浮点算术操作。这些限制可以抑制具有低计算能力和较少内存资源的深度学习模型的部署。通过使用较低的精度来存储权重和激活,可以减少网络的内存需求。您可以将深度学习工具箱与深度学习模型量化库支持包结合使用,通过将卷积层的权重、偏差和激活量化为8位缩放整数数据类型来减少深度神经网络的内存占用。金宝app
修剪网络会影响每一层的参数和激活的范围统计,因此量化网络的准确性会发生变化。为了探究这种差异,对修剪后的网络进行量化,并使用量化后的网络进行推理。
将数据分成校准和验证数据集。
calibrationDataStore = splitEachLabel(imdsTrain,0.1,“随机”);validationDataStore = imdsValidation;
创建一个dlquantizer
对象,并指定修剪后的网络作为要量化的网络。
prunedNet =装配网络([prunedDLNet. net .]层;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);精度= mean(YPred == trueLabels);结束
SynFlow Score命令功能
的calculateSynFlowScore
函数计算突触流(SynFlow)分数。突触显著性[2]被描述为一类基于梯度的评分,由损失梯度乘以参数值的乘积定义:
*
SynFlow评分是一个突触显著性评分,它使用所有网络输出的总和作为损失函数:
函数是用神经网络表示的吗
是否为网络参数
是否是网络的输入数组
若要计算关于此损失函数的参数梯度,请使用dlfeval
并建立了梯度函数模型。
函数得分= calculateSynFlowScore(dlnet,dlX) dlnet。Learnables = dlupdate(@abs, dlnet.Learnables);gradients = dlfeval(@modelGradients,dlnet,dlX);得分= dlupdate(@(g,w)g。*w, gradients, dlnet.Learnables);结束
SynFlow评分的模型梯度
函数gradients = modelGradients(dlNet,inputArray)计算dlnetwork的给定输入的梯度dlYPred = predict(dlNet,inputArray);pseudo = sum(dlYPred,“所有”);gradients = dlgradient(pseudoloss,dlNet.Learnables);结束
幅度评分函数
的calculateMagnitudeScore
函数返回大小分数,定义为参数的元素绝对值。
函数score = calculateMagnitudeScore(dlnet) score = dlupdate(@abs, dlnet. learnables);结束
掩码生成命令功能
的calculateMask
函数根据给定的分数和目标稀疏度返回网络参数的二进制掩码。
函数mask = calculateMask(scoresMagnitude,sparsity)根据参数分数计算二进制掩码,这样掩码包含由稀疏性指定的零的百分比。将分数单元格数组平展为一个长分数向量flatedscores = cell2mat(cellfun(@(S)extractdata(gather(S(:)))),scoresMagnitude。值,“UniformOutput”、假));对分数进行排序并确定删除连接的阈值%给定稀疏度扁平化分数=排序(扁平化分数);k = round(稀疏度*数值(平坦分数));如果K ==0 thresh =0;其他的thresh =平坦分数(k);结束创建二进制掩码mask = dlupdate(@(S)S>thresh, scoresMagnitude);结束
模型预测函数
的modelPredictions
函数以a作为输入dlnetwor
K对象dlnet, aminibatchqueue
输入数据的兆贝可
,和网络类,并通过迭代minibatchqueue对象中的所有数据来计算模型预测。函数使用onehotdecode
函数来查找得分最高的预测班级。
函数forecasts = modelforecasts (dlnet,mbq,classes) forecasts = [];而hasdata(mbq) dlXTest = next(mbq);dlYPred = softmax(预测(dlnet,dlXTest));YPred = onehotdecode(dlYPred,classes,1)';预测=[预测;YPred];结束重置(兆贝可)结束
应用剪枝功能
的createPrunedNet
函数返回指定修剪算法和迭代的修剪后的dlnetwork。
函数prunedNet = createPrunedNet(dlnet,selectedIteration,pruningMaskSynFlow,pruningMaskMagnitude,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层= dlnet.Layers(layerIDs(idx));计算稀疏度paramIDs = strcmp(dlnet.Learnables.Layer,layerNames(idx));paramValue = dlnet.Learnables.Value(paramIDs);为p = 1: numParams(paramValue) numParams(idx) = numParams(idx) + nummel (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“填充”,“相同”maxPooling2dLayer(2,“步”2) convolution2dLayer(16日“填充”,“相同”maxPooling2dLayer(2,“步”32岁的,2)convolution2dLayer (3“填充”,“相同”reluLayer fullyConnectedLayer(10) softmaxLayer classificationLayer];指定培训选项选项= trainingOptions(“个”,...“InitialLearnRate”, 0.01,...“MaxEpochs”10...“洗牌”,“every-epoch”,...“ValidationData”imdsValidation,...“ValidationFrequency”30岁的...“详细”假的,...“阴谋”,“没有”,“ExecutionEnvironment”,“汽车”);%列车网络net = trainNetwork(imdsTrain,layers,options);结束
参考文献
[1]宋汉,杰夫·普尔,约翰·陈,威廉·j·戴利。2015。“学习有效神经网络的权重和连接”神经信息处理系统进展28 (NIPS 2015): 1135-1143。
[2]田中秀德,丹尼尔·库宁,丹尼尔·l·k·亚明斯和Surya Ganguli 2020。“通过迭代保存突触流来修剪没有任何数据的神经网络。”第34届神经信息处理系统会议(NeurlPS 2020)