模型压缩技术

目前在人工智能领域,算法的商业应用方式一般有云端和嵌入式两种。其中嵌入式设备对内存和算法响应时长有一定的限制,而绝大部分深度学习模型为了获取较好的性能设计的层数较高(原因涉及到模型复杂性、模型性能、模型过拟合三者之间的关系),因此训练得到的参数及模型文件较大,在工业上的应用价值不高,由此出现了模型压缩的需求,目标是保证模型预测效果的同时尽可能减小模型大小。可以从两个方面入手:

  • 从模型设计出发:更小、更高效、更精细的网络模块设计(八度卷积、深度可分离卷积、分组卷积、1×1卷积、全卷积、非对称卷积核、全局平均池化等)
  • 获得收敛的模型后:模型剪枝、权重分解、削减精度、共享权重等

其实以上两种思路的原则是一样的,都是通过将模型中不太重要、重复的权重进行合并或者剔除,从而达到精简模型的目的,区别在于一个在模型设计之初精简模型结构,一个在模型收敛后进行精简。

1、精细的模型设计

1.1 八度卷积(Octave Convolution

原文链接:https://export.arxiv.org/pdf/1904.05049

经典模型:ResNet-152

论文作者认为不仅自然世界中的图像存在高低频,卷积层的输出特征图和输入通道也都存在高低频,octave将特征映射张量分解为低频组、高频组,低频分量支撑整体结构(比如企鹅白色大肚皮),其信息存在冗余,因此可通过对低频特征图进行压缩从而使得特征图大小减半,达到模型压缩目的。其操作特点如下:

(1)由四条计算路径组成,两条绿色路径对应于高频和低频特征图的信息更新;两条红色路径对应高低频之间的信息交换,其中高频特征图到低频特征图利用pooling,低频特征图到高频特征图利用上采样保持shape一致(下图a)

(2)控制低频图和高频特征图的通道比例就是alpha

(3)低频分量信息冗余,因此将低频分量的通道长/和宽设置为高频分量通道长和宽的1/2

1.2 深度可分离卷积(depthwise separable convolution,DSC)

原文链接:https://arxiv.org/pdf/1610.02357.pdf

经典模型:Xception、MobileNet

深度可分离卷积不同于正常卷积,正常卷积是对N个通道同时做卷积,并输出一个数(图a)。

深度可分离卷积由两步构成:第一步用N个卷积对N个通道分别做卷积,并输出N个数;第二步再通过一个1x1xN的卷积核(pointwise核),得到一个数。

深度卷积参数量为 ,逐点卷积参数量为 ,所以深度可分离卷积参数量是标准卷积的

1.3 分组卷积(Grouped convolution)

原文链接:https://arxiv.org/pdf/1605.06489.pdf

经典网络:AlexNet

传统的卷积操作中输出和输入的channel 一一对应,Group Convolution将输入和输出的 channels分为 g个 组(groups),每个组内输入和输出的 channels 进行稠密连接,组与组之间无连接,然后对每组分别进行卷积,因此其参数量只有传统卷积的1/g。

1.4 1×1卷积

原文链接:https://arxiv.org/pdf/1409.4842.pdf

经典网络:Inception

1*1卷积核作用主要有两个:

(1)升维降维,减少参数量。左图参数量(1x1x192x64)+(3x3x192x128)+(5x5x192x32) =387072;右图参数量(1x1x192x64)+(1x1x192x96)+(1x1x192x16)+(3x3x96x128)+(5x5x16x32)+(1x1x192x32)=163328;

(2)增加模型的非线性拟合能力,多层小卷积核和一个大卷积核相比,参数量减少,但激活函数可使用多个,能把直线掰的更弯。

1.5 全卷积

原文链接:Fully Convolutional Networks for Semantic Segmentation

经典网络:SSD、FasterRCNN(RPN)、MTCNN(PNet)、FCN

全连接成可以用(**分辨率和输入特征图相同的**)卷积代替。其优点有:

(1)解除模型对输入shape的限制。由于全连接层的输入神经元个数固定,其输入网络的图片shape也是固定的;若将所有的全连接层都用卷积层替换,则网络中卷积层输出可与输入图片的shape无关,输入图片大小将不受限制。

(2)提高计算效率。在需要保留位置信息时候(语义分割等),由于卷积层输出包含原始图像的空间信息,全卷积层一个输出神经元相当于全连接层3个输出神经元(分类结果、坐标x、坐标y)。

1.6 非对称卷积核

原文链接:https://arxiv.org/pdf/1512.00567.pdf

经典网络:Inception

非对称卷积通过对 n×n 卷积拆解为n×1 卷积与1×n 卷积串联,从而达到与直接进行卷积的结果等价。其特点如下:

(1)感受野与传统卷积等价

(2)降低运算量,n*n的运算量降为n*2

(3)非对称卷积不适用在接近输入图片层,会影响精度,并且其输入张量大小介于12×12到20×20大小之间时效果较好

1.7 全局平均池化(Golbal Average Pooling,GAP)

原文链接:https://arxiv.org/pdf/1312.4400.pdf

经典网络:NIN

全局平均池化就是在进行k分类任务时,把k个特征图全局平均处理分别输出一个值,也就是把W*H*k的张量变成1*1*k的张量,之后将1×1*k的张量输入到softmax激活函数之后,得到K个类别的概率(或置信度 confidence),从而取代全连接层。其特点如下:

(1)全局平均池化层减少了参数,减轻了在该层产生过拟合,减少了计算量

(2)全局平均池化对空间信息进行求和,具有全局的感受野,使得网络低层也能利用全局信息

(3)全局平均池化层代替全连接层不利于迁移学习。其参数固化在卷积网络中,迁移学习增加新的分类时,要对较多的卷积层进行调整,但在全连接层模型只需调整最后一层神经元个数

2、模型收敛后处理

2.1 剪枝(Pruning)

原文链接:https://machinethink.net/blog/compressing-deep-neural-nets/

在神经网络中有很多权重较小(几乎为0),剪枝试图删除对输出预测几乎不起作用的神经元,保留在推理过程中较重要性的权重(权重值较大),之后重新设计剪枝后的网络结构进行重训练,最终可以得到更紧凑、精度几乎无损的优化模型。

迭代式剪枝:修剪-训练-重复(Prune / Train / Repeat),其操作流程如下(对多少层进行简直操作即对多少层依次执行以下操作):

(1)模型训练收敛,保存权重文件

(2)对该层各权重根据std、sum(abs)、mean等标准判断其对模型贡献(可排序后绘出权重贡献走势图,有助于判断该删除的权重数目)

(3)删除贡献较小的权重,此时输出层的channel数也需要进行相应处理,删除输出层和上层权重所对应的channel即可。例如原网络输入28*28*3,第一层卷积核3*3(*3)*8,第一层输出28*28(*1)*8,第二层卷积核3*3(*8)*16,第二层输出28*28(*1)*16;当对第一层卷积核进行剪枝删除3个卷积核后,第一层输出变为28*28(*1)*5,那么原来第二层的卷积核3*3(*8)*16对应的channel应从8改为5,将与上层删除的卷积核对应的3层删除即可得到3*3(*5)*16的剪枝后第二层卷积核,而剪枝后的第二层输出与剪枝前相同28*28(*1)*16,并不影响后续网络

(4)构建剪枝后的网络结构,加载剪枝后的权重,与原模型进行准确度比对

(5)使用较小的学习率,重训练得到剪枝后模型

(6)重复以上操作,依次对其余层进行剪枝

2.2 权重分解(Weight Factorization)

经典模型:ALBERT的embedding层

权重矩阵可以进行低秩矩阵分解,即low-rank matrix factorization,通过将参数矩阵分解成多个较小矩阵的乘积来逼近原始参数矩阵,从而使得一些参数为0;权重因子分解既可以应用于输入嵌入层(压缩),也可以应用于前馈/自注意力层的参数(加速)。类似前文提到的非对称卷积,n*n的权重矩阵分解成两个权重矩阵1*n和n*1。

常见的低秩分解有:

  • SVD(二维矩阵运算)

  • CP分解

  • Tucker分解

  • Tensor Train分解

  • Block Term分解

2.3 削减精度(Quantization)

消减精度在模型压缩中也被称为网络量化,其原理在于神经网络模型在参数计算时默认采用32bit长度的浮点型数,然而实际上并不需要使用如此高的精度,通过量化牺牲精度消减权值所占空间,而超过消减后的类型范围的值将被压缩到能够表达范围内的最大或最小值。

GPU上低浮点的计算速度会远高于高浮点的计算速度,因此目前主流的方式是从float32压缩到int8,内存节省了约3/4,极大的提升了计算效率。

根据量化方法不同,大致可以分为:

  • 二值量化

  • 三值量化

  • 多值量化

2.4 共享权重(Weight Sharing)

原文链接:https://arxiv.org/pdf/1510.00149.pdf

很多layer的参数可以共享,通过聚类的方式挖掘出这些可以共享的权重系数,并且以类别的方式让它们共享一些权重,就可以实现模型的压缩。例如CNN的局部感受野和权值共享;NLP中ALBert中12层共用同一套参数。此种方法可以对模型进行明显压缩,但由于没有减少计算量,因此对于推理加速并没有效果。

其方案为:

(1)使用聚类方法对权值根据相近程度聚类为不同类别

(2)每个类别保存一个聚类中心权值和索引(数据量减少,压缩)

(3)反向传播进行权重更新操作,将每项权值梯度按照之前聚类的类别进行梯度累加后结合学习率将中心权值更新

3、模型转换和压缩平台工具

    (1)常见的模型转换工具

  • MMdnn:支持caffe、keras、mxnet、tensorflow、cntk、pytorch onnx、coreml之间模型转换

  • ONNX:支持ONNX (.onnx, .pb, .pbtxt), Keras (.h5, .keras), TensorFlow Lite (.tflite), Caffe (.caffemodel, .prototxt), Darknet (.cfg), Core ML (.mlmodel), MNN (.mnn), MXNet (.model, -symbol.json), ncnn (.param), PaddlePaddle (.zip, __model__), Caffe2 (predict_net.pb), Barracuda (.nn), Tengine (.tmfile), TNN (.tnnproto), RKNN (.rknn), MindSpore Lite (.ms), UFF (.uff);部分支持TensorFlow (.pb, .meta, .pbtxt, .ckpt, .index), PyTorch (.pt, .pth), TorchScript (.pt, .pth), OpenVINO (.xml), Torch (.t7), Arm NN (.armnn), BigDL (.bigdl, .model), Chainer (.npz, .h5), CNTK (.model, .cntk), Deeplearning4j (.zip), MediaPipe (.pbtxt), ML.NET (.zip), scikit-learn (.pkl), TensorFlow.js (model.json, .pb).

  • X2Paddle:只支持将其余深度学习框架得到的网络模型转换为PaddlePaddle模型

(2)常见的模型压缩工具

  • tflite:模型量化
# -*- coding: utf-8 -*-

import tensorflow as tf
import os


def convert_classifier():
    in_path = "../frozen_graph/mbnv2_1.0_resized_standard.pb"
    out_path = "../tflite_models/mbnv2_1.0_resized_standard.tflite"
    # out_path = "./model/quantize_frozen_graph.tflite"

    # 模型输入节点
    input_tensor_name = ["input"]
    input_tensor_shape = {"input": [1, 224, 224, 3]}
    # 模型输出节点
    # classes_tensor_name = ["MobilenetV1/Logits/SpatialSqueeze"]
    classes_tensor_name = ["MobilenetV2/Logits/output"]

    # 类不实例化,直接调用类方法
    """
    converter = tf.lite.TFLiteConverter.from_frozen_graph(in_path,
                                                          input_tensor_name, classes_tensor_name,
                                                          input_shapes=input_tensor_shape)
    """
    saved_model_dir = '../saved_model'
    converter = tf.lite.TFLiteConverter.from_saved_model(saved_model_dir,
                                                         input_arrays=input_tensor_name,
                                                         input_shapes=classes_tensor_name,
                                                         output_arrays=classes_tensor_name)
    """
    Converter will do its best to improve size and latency based on the information provided.
    Enhanced optimizations can be gained by providing a representative_dataset.
    This is recommended, and is currently equivalent to the modes below.
    Currently, weights will be quantized and if representative_dataset is 
    provided, activations for quantizable operations will also be quantized.
    """
    converter.optimizations = [tf.lite.Optimize.DEFAULT]
    # converter.optimizations = [tf.lite.Optimize.OPTIMIZE_FOR_SIZE]
    # converter.optimizations = [tf.lite.Optimize.OPTIMIZE_FOR_LATENCY]
    # converter.post_training_quantize = True

    tflite_model = converter.convert()
    with open(out_path, "wb") as f:
        f.write(tflite_model)

def convert_detector():
    in_path = "../frozen_graph/ssdlite_mv2_standard_post.pb"
    # out_path = "../tflite_model/ssdlite_mobilenet_v2.tflite"
    out_path = "../tflite_model/ssdlite_mv2_quant_uint8_test.tflite"
    # out_path = "./model/quantize_frozen_graph.tflite"

    # ssdlite not add_postprocessing_op
    postprocessing = True

    if postprocessing == False:
        input_tensor_name = ['normalized_input_image_tensor']
        input_tensor_shape = {"normalized_input_image_tensor": [1, 300, 300, 3]}
        classes_tensor_name = ['raw_outputs/box_encodings', 'raw_outputs/class_predictions']
    else:
        # 模型输入节点,需要把名称中:0去掉
        input_tensor_name = ["normalized_input_image_tensor"]
        input_tensor_shape = {"normalized_input_image_tensor": [1, 300, 300, 3]}
        # 模型输出节点
        classes_tensor_name = ['TFLite_Detection_PostProcess',
                               'TFLite_Detection_PostProcess:1',
                               'TFLite_Detection_PostProcess:2',
                               'TFLite_Detection_PostProcess:3']

    converter = tf.lite.TFLiteConverter.from_frozen_graph(in_path,
                                                          input_tensor_name, classes_tensor_name,
                                                          input_shapes=input_tensor_shape)
    converter.allow_custom_ops = True

    # 尝试训练后量化 (https://tensorflow.google.cn/versions/r1.15/api_docs/python/tf/lite/TFLiteConverter)
    """
    Optimizations that reduce the size of the model.
    Currently, weights will be quantized and if representative_dataset is
    provided, activations for quantizable operations will also be quantized.
    """
    # converter.optimizations = [tf.lite.Optimize.OPTIMIZE_FOR_SIZE]
    """
    Target data type of real-number arrays in the output file. Must be {tf.float32, tf.uint8}. 
    If optimzations are provided, this parameter is ignored. (default tf.float32)
    """
    # converter.inference_type = tf.lite.constants.QUANTIZED_UINT8
    """
    Deprecated. Please specify [Optimize.DEFAULT] for optimizations instead. 
    Boolean indicating whether to quantize the weights of the converted float model. 
    Model size will be reduced and there will be latency improvements (at the cost of accuracy). (default False)
    optimizations == new attribute == post_training_quantize
    """
    # converter.post_training_quantize = True

    tflite_model = converter.convert()
    with open(out_path, "wb") as f:
        f.write(tflite_model)


if __name__ == '__main__':
    os.environ['CUDA_VISIBLE_DEVICES'] = '1'
    convert_classifier()
    # convert_detector()


'''
# -*- coding:utf-8 -*-

import cv2
import numpy as np
import time
import tensorflow as tf
import os
os.environ["CUDA_VISIBLE_DEVICES"] = "-1"

test_image_dir = 'slim_finetune/left'

# 遍历文件
file_paths = [os.path.join(test_image_dir, file_path) for file_path in os.listdir(test_image_dir)][0:2]
file_num = len(file_paths)
print('number of test files: {}' .format(file_num))

"""TFlite性能测试"""
print('=========================')
tflite_model_path = "../tflite_model/ssdlite_mv2.tflite"
# tflite_model_path = "./model/quantize_frozen_graph.tflite"

# Load TFLite model and allocate tensors.
interpreter = tf.lite.Interpreter(model_path=tflite_model_path)
interpreter.allocate_tensors()

# Get input and output tensors.
input_details = interpreter.get_input_details()
print(str(input_details))
output_details = interpreter.get_output_details()
print(str(output_details))

start_time = time.time()
for file_path in file_paths:
    # 数据预处理:缩放、扩增维度
    image_data = cv2.imread(file_path)
    image_data = cv2.resize(image_data, (300, 300)).astype(np.float32)
    image_data = image_data / 128.0 - 1
    image_data = np.expand_dims(image_data, 0)

    # 填装数据
    interpreter.set_tensor(input_details[0]['index'], image_data)

    # 调用模型
    interpreter.invoke()
    output_data_bbx = interpreter.get_tensor(output_details[0]['index'])
    output_data_class = interpreter.get_tensor(output_details[1]['index'])
    output_data_scores = interpreter.get_tensor(output_details[2]['index'])
    output_data_numbbx = interpreter.get_tensor(output_details[3]['index'])

    print(np.squeeze(output_data_bbx))
    print(np.squeeze(output_data_class))
    print(np.squeeze(output_data_scores))
    print(np.squeeze(output_data_numbbx))

    # 数据后处理
    #result_class = np.squeeze(output_data_class)
    #print('raw class result with logistic format:{}'.format(result_class))

    # 输出结果是长度为标签类别总数的一维数据,最大值的下标就是预测的数字
    #print('result:{}'.format((np.where(result_class == np.max(result_class)))[0][0]))

inference_time = (time.time() - start_time) / file_num
print('tflite_inference_time: {}'.format(inference_time))
'''

  • TNN:模型压缩、代码裁剪、低精度优化、计算优化
https://gitee.com/huiwei13/TNN#/huiwei13/TNN/blob/master/doc/cn/user/quantization.md
  • PaddleSlim:网络剪枝、量化、蒸馏(只支持图像领域模型)
https://github.com/PaddlePaddle/models/tree/v1.4/PaddleSlim
  • PocketFlow:裁剪、权重稀疏和量化
  • distiller:稀疏算法(剪枝+正则化)+低精度算法(量化)
  • TensorRT:精度矫正、动态张量内存、层/张量融合、内核自动微调、多级流执行
  • TVM:计算图优化、张量运算符优化
  • rknnrknpu:非对称量化、动态定点量化

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值