2021年小结
好久没有写随笔了,这不,2022年开始了,这一次早点开始《2021年小结》,回顾一下这一年在工作、学习和生活上的发展以及一些自己对于未来的思考。
工作
在万维艾斯的工作也不知不觉的过了一年了。.在这段时间里,非常开心能够参与到公司的日常项目开发,能够认识到一群充满活力的童鞋。在工作过程中,参与了多个项目,横跨后端、目标检测、目标分类、视频理解、视频编解码等领域,收获成长了很多,也逐渐明确了自己下一步的发展方向,希望在模型部署领域能够有更多的发挥。
到5月份的时候换了一份新工作,来到了目前就职的浙江由由科技有限公司,这是一家从事生鲜AI平台开发的软件公司,我的工作内容就是进行生鲜识别算法的开发和优化,以及嵌入式开发板的模型部署。在这里做的内容非常明确,就是针对具体的硬件环境优化识别模型的速度和精度。
在具体平台上的开发会遇到很多的问题,包括但不限于数据集的采集、清洗和训练,硬件环境的适配以及算法速度和精度的提高。如何清洗几千万数据?如何在有限内存条件下训练几个T数据?对于极度不平衡数据如何处理?如何定义类别?如何定义算法以及如何优化算法性能?这一个个问题都是真实环境下遇到的极限场景。
在这一年的工作过程中也体会了推动工作进度的困难,我总以为每次都能够很好的完成所有的工作任务,在任务推进过程中往往选择不主动向领导汇报,但是很多时候事情就是搞砸了,没有完成不说,最后还会拖延项目时间规划。这种情况下自己在项目推进的过程中承受了巨大的心理压力,而且到了最后还要受到领导和同事的质疑。思考了很多,上面的这种工作方式确实不可行,应该在项目推进过程中主动向相关参与人员汇报当前的进度以及预期的规划,在整个过程中给予大家足够的信息,也能够有时间去更好的调节项目安排。工作就是工作,尽自己力量做好它就行了。
学习
大体上分为以下几部分内容展开:
- 模型训练
- 网络剪枝
- 知识迁移
- 目标分类
- 细粒度识别
- 人脸识别
- 模型部署
- 工具开发
模型训练
大批量数据训练
当前开发的生鲜识别算法拥有几百种类别以及几千万的训练数据,不断增加的训练数据逐渐逼近了内存极限,同时减慢了训练速度。主要在三方面进行改进:
- 替换预处理库,提高图像预处理性能;
- 重写数据类,提高内存加载数据性能;
- 支持多GPU训练以及混合精度训练。
之前使用的是Pytorch官方提供的torchvision库,从实现角度来看,torchvision整体架构清晰,调用很方便,许多论文也是基于该预处理库进行的训练,但是其他图像预处理库能够提供更加丰富的操作以及更快速的处理速度,比如albumentations。通过替换预处理库,能够提高至少30%的预处理速度。
Pytorch提供的数据类会把整个数据集都加载进内存,虽然可以通过预加载图像路径的方式节省一部分内存空间,但是相对于几个T的训练数据而言,其图像路径集合也占据了非常大的内存空间。为了进一步提高加载速度,往往会选择多进程方式进行加载,这样会在每个进程内部保存完整的数据集合副本,进程数越多,相应的需要更大的内存空间。为了满足不断增大的内存需求,重新编写了数据类 - MPDataset。其基本思路是每个进程仅保留训练所需的数据集合。通过这种方式,能够直接降低约4倍内存空间,大大缓解了不断扩充的训练数据对于内存的需求。
多GPU训练DDP通过分布式训练方式能够有效的扩展训练数据,从而提高训练速度。而混合精度训练AMP能够满足半精度推理的需求,同时满足更大批次的训练量,完成训练后的模型在半精度模式下运行,不仅能够保持识别精度,还可以提高推理速度。
其余优化
在这一年的开发过程中,逐步接触到更多的训练技巧,包括图像预处理模块以及训练模块
- 自动数据增强(Augment)
- 数据预先提取(Prefetcher)
- LMDB
- cutmix/cutup
- 不对BN层进行权重衰减
相关阅读
网络剪枝
针对具体硬件平台上的模型进行优化,常用的方式包括了剪枝、蒸馏和量化。就剪枝而言,其原理是通过消除网络中不重要的权重/神经元/通道/层,在保留精度的同时减少模型大小、运行时内存和计算量。剪枝算法大体可分为非结构化剪枝和结构化剪枝,前者针对单个权重进行操作,能够最大程度的减少模型大小和推理内存,但是需要专门的稀疏矩阵运算库以及硬件支持;后者针对神经元/通道或者更高维度进行剪枝操作,相当于模型结构搜索,完成后不需要特定底层库支持即可运行。
一个典型的网络剪枝算法可分为3个步骤进行:
- 训练一个大的、过度参数化的模型;
- 通过一个评判标准剪枝训练模型;
- 微调剪枝模型以获得最好的性能。
Note:第一步的训练方式取决于第二步的剪枝评判标准,当前实现了NetworkSlimming(针对BN层缩放因子)和SSL(针对滤波器、通道、滤波器形状和层深度)。
在剪枝算法调研过程中,可以发现相关论文更偏向于工程性质,即直接针对模型结构进行优化,而不是先从理论入手,再推理到具体实现。看论文的过程相对来说更加简单直观,不过实现过程也会更加有难度挑战。
剪枝类似于简易版本的自动神经架构搜索,从最开始的模型中搜索得到一个不同参数的新模型,能够适用于更严苛的部署环境。不过目前深度学习发展十分迅速,不断的出现满足不同硬件环境的模型架构,所以在选用剪枝算法的时候也应该广泛的参考其他模型实现。
- Learning Efficient Convolutional Networks through Network Slimming
- Rethinking the Value of Network Pruning
- Deep Compression: Compressing Deep Neural Networks with Pruning, Trained Quantization and Huffman Coding
- Learning Structured Sparsity in Deep Neural Networks
- Learning both Weights and Connections for Efficient Neural Networks
- Lossless CNN Channel Pruning via Decoupling Remembering and Forgetting
知识迁移
知识迁移是一个很有创造力的实现,它在正常数据训练的基础上加上了教师网络训练,试图将大模型的性能迁移到小模型上,从而实现模型压缩的要求。通常知识训练是一个二阶段的训练:
- 单独训练教师模型;
- 结合教师模型对学生模型进行知识迁移训练。
基于教师模型参与学生模型的训练方式的不同,大体可分为以下几种迁移方式:
- 基于输出向量(logits)的知识迁移: 《知识蒸馏KD》将大模型的输出概率作为软目标(soft targets),结合正常标签(硬目标,hard targets)参与到小模型的训练中;
- 基于中间层特征(hints)的知识迁移:《FitNets》设计了一个二阶段训练,首先利用教师网络的中间层特征对学生网络前半部分权重进行蒸馏训练;其次利用第一阶段训练得到的权重作为学生网络初始化特征,再结合教师网络进行知识蒸馏训练。
- 基于注意力(attention)的知识迁移:《AT》提出了对教师网络的空间注意力图进行知识迁移。成对匹配教师网络和学生网络的中间层,通过激活函数将特征张量转换成二维空间注意力图,再结合知识蒸馏算法或者Hints算法进行知识蒸馏训练。
- 基于特征关系(Gramian矩阵)的知识迁移:《FSP》是一个二阶段训练,首先提取教师网络不同层特征的Gramian矩阵,作为输出特征对学生网络进行迁移训练,获取学生网络的初始化权重;完成后再对学生网络进行知识蒸馏训练。
- 基于多层特征(overhaul)的知识迁移:《OFD》模块化了知识迁移训练,将特征蒸馏损失的计算划分为教师特征转换、学生特征转换、特征蒸馏位置以及特征蒸馏函数。提取激活函数(ReLU)之前的特征向量以获取更多的暗信息,设计partial L2距离函数以过滤负值特征,共采集了多层中间层特征进行知识迁移训练;
- 基于跨层特征(review)的知识迁移:不同于以往的同级特征之间的知识迁移,《RFD》将教师网络底层特征同时作用于学生网络高层特征训练,通过跨级连接的方式进行知识迁移。
当前重点关注了OFD和RFD算法,分别实验证明了其可行性:overhaul和KnowledgeReview。剪枝+蒸馏是一个非常好的组合,能够提速的同时保持更高的精度。
- Distilling the Knowledge in a Neural Network
- FitNets: Hints for Thin Deep Nets
- Paying More Attention to Attention
- A Gift from Knowledge Distillation
- A Comprehensive Overhaul of Feature Distillation
- Distilling Knowledge via Knowledge Review
目标分类
在工作中遇到最多的场景就是目标分类,在2021年里也陆陆续续的接触了更多的识别模型:
- SKNet:论文设计了一个SKUnit模块,内部集成了多分支计算,不同分支产生不同大小的神经元感受野,最后通过注意力融合方式进行特征计算。虽然标准卷积的计算方式同样拥有随刺激分配权重的能力,不过相对而言其操作比较粗粒度,而SKUnit通过多分支卷积+注意力机制的实现方式表现的的更加细粒度,从实现结果来看其提取特征的能力也比较好。SKNet的整体实现相当于多分支连接+残差连接+注意力机制;
- ResNeSt:ResNeSt首先参数化了SKUnit的实现,同时进一步发展了特征提取的细粒度计算,并不只是简单的扩充分支数,而是将输入的特征图进行分组,针对每组特征图执行多分支特征提取以及soft注意力机制进行特征重分配,最后将每组特征图串联成输出数据。简单的说,SKNet针对上一层的特征数据进行了多尺度特征提取+注意力机制,而ResNeSt先对上一层的特征数据进行分组,对每组数据单独执行多分支特征提取+注意力机制,最后通过串联的方式输出特征数据;
- ACNet:核心在于提出了一个构建块 - 非对称卷积块(Asymmtric Convolution Block, ACB)。在训练阶段,将ACB替代标准卷积进行训练;在测试阶段,将ACB还原回标准卷积。通过分离训练阶段和测试阶段的模型,在训练时提高了算法泛化能力,在测试时保留了原始模型的执行速度;
- RepVGG:RepVGG参考了VGG网络,并通过结构重参数化技术来解耦训练时多分支和推理时简单架构的实现。在训练阶段插入构建块RepVGGBlock,用于增强训练阶段的模型泛化能力;在推理阶段通过多分支融合,仅包含Conv3x3和ReLU,得到一个快速推理模型;
- GhostNet:论文在实验中发现了中间特征图之间存在冗余性和相关性,以此设计了一个新的网络模块GhostModule,运用简单线性操作对一部分卷积生成的特征图进行扩增,通过生成更多冗余特征图的实现方式在更少参数和计算量的情况下实现更好的性能。
在实际操作中,还接触了TinyNet、Efficientnet/Efficientnet_lite、DBB等优秀的网络结构。对于目前学术界火热的Transformer架构,各种SOTA的模型层出不穷,不过我还没有深入接触,让子弹再飞一会儿。
- Selective Kernel Networks
- ResNeSt: Split-Attention Networks
- ACNet
- RepVGG
- GhostNet: More Features from Cheap Operations
细粒度识别
细粒度识别是2021年新接触的识别领域。不同于传统的目标识别,细粒度识别更针对的是相似类别之间的分类,比如绵羊和山羊的识别,所以细粒度识别需要模型采集更精细的识别特征。常规的端到端训练的识别模型往往获取的特征比较粗糙,并不能很好的完成细粒度识别的任务,通常需要在模型中额外加入注意力或者检测模块进行辅助识别。除了扩增识别模型以外,还有在训练阶段进行优化的方式,论文《DCL》就是一个训练优化的算法:在训练过程中,除了正常的分类网络推理之外,还添加了一个解构模块和一个构造模块;在推理过程中,不需要额外的计算模块,这样就不会产生额外的计算开销。
对于解构模块,论文提出了区域融合机制(Region Confusion Mechanism, RCM),通过分块打乱输入图像(分块:保持局部细节;打乱:舍弃全局结构),强迫分类网络学习有判别力区域的信息;同时为了消除RCM带来的噪声影响,添加了一个对抗损失来最小化原始图像和打乱图像之间的噪声干扰。
对于构造模块,论文提出一个区域对齐网络( region alignment network),通过建模区域之间的语义相关性来恢复原始区域布局,这种方式可以促使分类网络进一步理解每个区域的语义。
通过实验,可以发现DCL训练确实能够提高细粒度识别准确率。相关实现查看DCL。实际使用过程中,发现DCL算法不一定适用于专门场景的数据集,还是需要实际训练测试。
人脸识别
人脸识别可以说是最最细粒度的识别领域了,模型需要区分每一张人脸。重温了经典的人脸识别论文《FaceNet》,将深度网络直接训练输出128维特征向量作为人脸嵌入表示,计算两个特征向量之间的L2距离作为人脸相似度;在训练过程中设计了三元组损失函数,通过锚点图像比对正负样本对,能够保证相同类别的人脸向量之间的距离足够小,不同类别的人脸向量之间的距离足够大。完成FaceNet的训练后,可以直接将模型运用于不同领域:
- 人脸验证(是否属于同一个人):计算两个特征向量的相似度,然后通过阈值进行判断;
- 人脸识别(属于其中哪个人):通过KNN分类器计算特征向量所属类别;
- 人脸聚类(发现具有相同特性的一群人):使用k-means等常用的聚类算法即可实现。
在torchvision实现的基础上增加了多GPU训练方式:facenet
模型部署
目前通常在Python环境下进行模型训练和调试,然后在C++环境下进行模型部署和推理。基本的模型部署流程如下:
- 先转换模型为onnx格式;
- 再转换成指定推理引擎格式;
- 最后转换成.h文件方便调用。
在2021年,也尝试了多个模型推理框架,包括onnxruntime、mnn、openvino和rknn.
总的来说,不同推理引擎的推理速度差异性不大,不过在不同硬件环境下还是会有所区分。小小记录了一部分模型部署的实现:zjykzj/onnx
工具开发
在2021上半年重构了当前使用的博客网站,适配了最新版本的Hexo+NexT,同时将网站托管到了香港阿里,在腾讯云服务器部署Jenkins实现自动编译和部署功能。具体的实现架构可查看:部署
生活
不知不觉已经熟悉了在南京的生活,虽然工作地点是在相对偏僻的郊区,但是工作期间也会去南京的其他产业园区交流,平时生活中也会时不时的爬爬紫金山,逛逛玄武湖,其中也游历了不少的城市景区。南京是一座结合了南方婉约气质和北方豪放气质的城市,身边的童鞋也拥有这它独特的气质,不慌不乱,确实是一个非常适合生活的地方。
年中的时候回到了杭州,已经在杭州待了好多年,但是这近一年在外的生活,让我对杭州有一种即熟悉又陌生的感觉,也可能是之前没有在城西生活过的原因吧。回到杭州后就一头扎入了繁忙的工作中,突然发现杭州的节奏好快啊,大家都催促着向前跑,有空闲时间的时候也没有了精力停下来。已经好久都没有和之前的朋友联系,感觉和大家陌生了。
如果2021年需要一个关键词,我给出的回答是孤独。之前在读书的时候都是集体生活,周围都是同学老师,不需要主动的去追求交流。经过了一年多的工作,现在你下班后面对的是空荡荡的宿舍,你需要主动的去寻找朋友交流。很可惜的是我发现自己并没有主动交流的能力,渴望交流但是不知道如何开始,开始了又不知道如何维持。我很孤独,我需要小伙伴。
突然发现人与人之间的相处也是一门需要不断打磨的技术,只不过在读书的时候往往不会意识到这一点。既然现在意识到了这一点,就主动一些,试着去摸索,期待有好的结果。
未来
在2022年,希望能够继续扎根于模型部署领域,深入模型优化内容,更好的处理和解析工作中面对的业务难题。从工作中得到了一个小小的感悟,Enjoy the process, not expecting every step to be perfect。生活也应该一样,既然想要摆脱孤独,那就需要自己主动出发,不需要预设过多的想法,不必要在意最后是否一定和朋友很好的交流,被忽视很正常,每个人都有自己的生活,享受这个过程。
坚持写文章是一件非常耗时耗力的事情。面对现在爆炸式发展的计算机视觉,总会有来不及赶上的念头。自己也试着转变心态,虽然不一定一天就能把一篇文章看完,但是边看边写,多看几次总能够完成的。对于文章不期望写的太多,一周能写一篇,一年就有48篇了;一周能够集成一个功能,一年也就有48个功能的更新了。
转眼2021年就过去了,回顾这一年的发展,一步一步向前走,也确实有了很多的收获,所以面对纷繁的世界坚持下去,勇敢的向前迈一步。
2021年过去了,2022年开始了,这是新的一年,也是新的开始,期待2022年的改变!!!