仅用于站内搜索,没有排版格式,具体信息请跳转上方微信公众号内链接
以下文章来源于微信公众号:oldpan博客
作者:Oldpan
链接:https ://mp. weixin.qq. com/s/BhsZ2eFPNk9D80xvb4ReaQ
本文仅用于学术分享,如有侵权,请联系后台作删文处理
导读
网络训练时,通过大量数据可以训练出一个网络权重模型,但是模型权重为什么有用?内部的原理是什么?本文从模型部署的角度进行了分析,希望对大家有帮助!
简单聊聊模型权重,也就是我们俗称的weight。
深度学习中,我们一直在训练模型,通过反向传播求导更新模型的权重,最终得到一个泛化能力比较强的模型。同样,如果我们不训练,仅仅随机初始化权重,同样能够得到一个同样大小的模型。虽然两者大小一样,不过两者其中的权重信息分布相差会很大,一个脑子装满了知识、一个脑子都是水,差不多就这个意思。
所谓的AI模型部署阶段,说白了就是将训练好的权重挪到另一个地方去跑。一般来说,权重信息以及权重分布基本不会变(可能会改变精度、也可能会合并一些权重)。
不过执行模型操作(卷积、全连接、反卷积)的算子会变化,可能从Pytorch->TensorRT或者TensorFlow->TFLITE,也就是实现算子的方式变了,同一个卷积操作,在Pytorch框架中是一种实现,在TensorRT又是另一种时间,两者的基本原理是一样的,但是精度和速度不一样,TensorRT可以借助Pytorch训练好的卷积的权重,实现与Pytorch中一样的操作,不过可能更快些。
那么权重都有哪些呢?他们长什么样?
这还真不好描述…其实就是一堆数据。对的,我们千辛万苦不断调优训练出来的权重,就是一堆数据而已。也就是这个神奇的数据,搭配各种神经网络的算子,就可以实现各种检测、分类、识别的任务。
Conv模型权重
例如上图,我们用Netron这个工具去查看某个ONNX模型的第一个卷积权重。很显然这个卷积只有一个W权重,没有偏置b。而这个卷积的权重值的维度是[64,3,7,7],也就是输入通道3、输出通道64、卷积核大小7x7。
再仔细看,其实这个权重的数值范围相差还是很大,最大的也就0. 1的级别。但是最小的呢,肉眼看了下(其实应该统计一波),最小的竟然有1e-10级别。
部分模型权重值极小
一般我们训练的时候,输入权重都是0-1,当然也有0-255的情况,但不论是0-1还是0-255,只要不溢出精度上限和下限,就没啥问题。对于FP32来说,1e-10是小case,但是对于FP16来说就不一定了。
我们知道FP16的普遍精度是~5. 96e−8(6. 10e−5)…65504,具体的精度细节先不说,但是可以很明显的看到,上述的1e-10的精度,已经溢出了FP16的精度下限。如果一个模型中的权重分布大部分都处在溢出边缘的话,那么模型转换完FP16精度的模型指标可能会大大下降。
除了FP16,当然还有很多其他精度(TF32、BF16、IN8),这里暂且不谈,不过有篇讨论各种精度的文章可以先了解下。
话说回来,我们该如何统计该层的权重信息呢?利用Pytorch中原生的代码就可以实现:
#假设v是某一层conv的权重,我们可以简单通过以下命令查看到该权重的分布v. max()tensor(0. 8559)v. min()tensor(-0. 9568)v. abs()tensor([[0. 0314,0. 0045,0. 0182,…,0. 0309,0. 0204,0. 0345],[0. 0295,0. 0486,0. 0746,…,0. 0363,0. 0262,0. 0108],[0. 0328,0. 0582,0. 0149,…,0. 0932,0. 0444,0. 0221],…,[0. 0337,0. 0518,0. 0280,…,0. 0174,0. 0078,0. 0010],[0. 0022,0. 0297,0. 0167,…,0. 0472,0. 0006,0. 0128],[0. 0631,0. 0144,0. 0232,…,0. 0072,0. 0704,0. 0479]])v. abs().min()#可以看到权重绝对值的最小值是1e-10级别tensor(2. 0123e-10)v. abs().max()tensor(0. 9568)torch. histc(v. abs())#这里统计权重的分布,分为100份,最小最大分别是[-0. 9558,0. 8559]tensor([3. 3473e+06,3. 2437e+06,3. 0395e+06,2. 7606e+06,2. 4251e+06,2. 0610e+06,1. 6921e+06,1. 3480e+06,1. 0352e+06,7. 7072e+05,5. 5376e+05,3. 8780e+05,2. 6351e+05,1. 7617e+05,1. 1414e+05,7. 3327e+04,4. 7053e+04,3. 0016e+04,1. 9576e+04,1. 3106e+04,9. 1220e+03,6. 4780e+03,4. 6940e+03,3. 5140e+03,2. 8330e+03,2. 2040e+03,1. 7220e+03,1. 4020e+03,1. 1130e+03,1. 0200e+03,8. 2400e+02,7. 0600e+02,5. 7900e+02,4. 6400e+02,4. 1600e+02,3. 3400e+02,3. 0700e+02,2. 4100e+02,2. 3200e+02,1. 9000e+02,1. 5600e+02,1. 1900e+02,1. 0800e+02,9. 9000e+01,6. 9000e+01,5. 2000e+01,4. 9000e+01,2. 2000e+01,1. 8000e+01,2. 8000e+01,1. 2000e+01,1. 3000e+01,8. 0000e+00,3. 0000e+00,4. 0000e+00,3. 0000e+00,1. 0000e+00,1. 0000e+00,0. 0000e+00,1. 0000e+00,1. 0000e+00,0. 0000e+00,0. 0000e+00,0. 0000e+00,0. 0000e+00,0. 0000e+00,1. 0000e+00,0. 0000e+00,0. 0000e+00,0. 0000e+00,0. 0000e+00,2. 0000e+00,0. 0000e+00,2. 0000e+00,1. 0000e+00,0. 0000e+00,1. 0000e+00,0. 0000e+00,2. 0000e+00,0. 0000e+00,0. 0000e+00,0. 0000e+00,0. 0000e+00,0. 0000e+00,0. 0000e+00,0. 0000e+00,0. 0000e+00,0. 0000e+00,0. 0000e+00,1. 0000e+00,0. 0000e+00,0. 0000e+00,0. 0000e+00,0. 0000e+00,0. 0000e+00,0. 0000e+00,0. 0000e+00,0. 0000e+00,0. 0000e+00,1. 0000e+00])
这样看如果觉着不是很直观,那么也可以自己画图或者通过Tensorboard来时候看。
Tensorboard查看权重分布
那么看权重分布有什么用呢?
肯定是有用处的,训练和部署的时候权重分布可以作为模型是否正常,精度是否保持的一个重要信息。不过这里先不展开说了。
在模型训练过程中,有很多需要通过反向传播更新的权重,常见的有:
卷积层
全连接层
批处理化层(BN层、或者各种其他LN、IN、GN)
transformer-encoder层
DCN层
这些层一般都是神经网络的核心部分,当然都是有参数的,一定会参与模型的反向传播更新,是我们在训练模型时候需要注意的重要参数。
也有不参与反向传播,但也会随着训练一起更新的参数。比较常见的就是BN层中的running_mean和running_std:
可以看到上述代码的注册区别,对于BN层中的权重和偏置使用的是register_parameter,而对于running_mean和running_var则使用register_buffer,那么这两者有什么区别呢,那就是注册为buffer的参数往往不会参与反向传播的计算,但仍然会在模型训练的时候更新,所以也需要认真对待。
关于BN层,转换模型和训练模型的时候会有暗坑,需要注意一下。
刚才描述的这些层都是有参数的,那么还有一些没有参数的层有哪些呢?当然有,我们的网络中其实有很多op,仅仅是做一些维度变换、索引取值或者上/下采样的操作,例如:
Reshape
Squeeze
Unsqueeze
Split
Transpose
Gather
等等等等,这些操作没有参数仅仅是对上一层传递过来的张量进行维度变换,用于实现一些”炫技“的操作。至于这些炫技吗,有些很有用有些就有些无聊了。
比较繁琐变换维度的op
上图这一堆乱七八槽的op,如果单独拆出来都认识,但是如果都连起来(像上图这样),估计连它爸都不认识了。
开个玩笑,其实有时候在通过Pytorch转换为ONNX的时候,偶尔会发生一些转换诡异的情况。比如一个简单的reshape会四分五裂为gather+slip+concat,这种操作相当于复杂化了,不过一般来说这种情况可以使用ONNX-SIMPLIFY去优化掉,当然遇到较为复杂的就需要自行优化了。
哦对了,对于这些变形类的操作算子,其实有些是有参数的,例如下图的reshap:
reshape也可能有参数
不过这些都是小问题,大部分情况我们可以通过改模型或者换结构解决,而且成本也不高。但是还会有一些其他复杂的问题,可能就需要我们重点研究下了。
想要将训练好的模型从这个平台部署至另一个平台,那么首要的就是转移权重。不过实际中大部分的转换器都帮我们做好了(比如onnx-TensorRT),不用我们自己操心!
不过如果想要对模型权重的有个整体认知的话,还是建议自己亲手试一试。
先简单说下Caffe和Pytorch之间的权重转换。这里推荐一个开源仓库Caffe-python,已经帮我们写好了提取Caffemodel权重和根据prototxt构建对应Pytorch模型结构的过程,不需要我们重复造轮子。
caffe结构与权重
caffe_pb2就是caffemodel格式的protobuf结构,具体的可以看上方老潘提供的库,总之就是定义了一些Caffe模型的结构。
很简单吧。
先举个简单的例子,一般我们使用Pytorch模型进行训练。训练得到的权重,我们一般都会使用torch. save()保存为. pth的格式。
PTH是Pytorch使用python中内置模块pickle来保存和读取,我们使用netron看一下pth长什么样。
看看PTH模型的BN层
可以看到只有模型中有参数权重的表示,并不包含模型结构。不过我们可以通过. py的模型结构一一加载.pth的权重到我们模型中即可。
torch. load细节
看一下我们读取. pth后,state_dict的key。这些key也就对应着我们在构建模型时候注册每一层的权重名称和权重信息(也包括维度和类型等)。
keys
对于pth,我们可以通过以下代码将其提取出来,存放为TensorRT的权重格式。
需要注意,这里的TensorRT权重格式指的是在build之前的权重,TensorRT仅仅是拿来去构建整个网络,将每个解析到的层的权重传递进去,然后通过TensorRT的network去build好engine。
那么被TensorRT优化后?模型又长什么样子呢?我们的权重放哪儿了呢?
肯定在build好后的engine里头,不过这些权重因为TensorRT的优化,可能已经被合并/移除/merge了。
模型参数的学问还是很多,近期也有很多相关的研究,比如参数重参化,是相当solid的工作,在很多训练和部署场景中经常会用到。
先说这些吧,比较基础,也偏向于底层些。神经网络虽然一直被认为是黑盒,那是因为没有确定的理论证明。但是训练好的模型权重我们是可以看到的,模型的基本结构我们也是可以知道的,虽然无法证明模型为什么起作用?为什么work?但通过结构和权重分布这些先验知识,我们也可以大概地对模型进行了解,也更好地进行部署。
欢迎加入《AI未来星球》,一起成长
扫描下方二维码即可加入~
真诚分享AI落地过程(AI商机->项目签约->算法开发->产品开发->实施运维)中的各方面经验和踩过的坑。
你可以获得什么?
1、大白之前花费10W+购买,AI行业各场景私有数据集下载,星球内倾情分享;2、AI行业研发、产品、商业落地问题咨询(目前AI公司创业中),都可获高质量解答,有效期一年,无限次提问,有问必答。3、定期邀请AI行业各类嘉宾分享,创业/商业等方面的经验!
帮助你解决遇到的实际问题,升职加薪!
大家一起加油!