如何运行 c++ 代码/编译过程

运行cpp文件:

如何运行cpp文件:
方法1、vscode runcode
方法2、使用g++命令简单编译,在终端输入 g++ -o test test.cpp # -L. -l动态库名 (如果需要导入动态库)
方法3、对于复杂的程序,需要编写makefile or 使用cmake ,然后执行 make命令

CMake说明: 一般把CMakeLists.txt文件放在工程目录下,使用时,先创建一个叫build的文件夹(这个并非必须,因为cmake命令指向CMakeLists.txt所在的目录,例如cmake .. 表示CMakeLists.txt在当前目录的上一级目录。cmake后会生成很多编译的中间文件以及makefile文件,所以一般建议新建一个新的目录,专门用来编译),然后执行下列操作:

cd build
cmake ..
make
其中cmake .. 在build里生成Makefile,make根据生成makefile文件,编译程序,make应当在有Makefile的目录下,根据Makefile生成可执行文件。

C++编译过程主要分为,预处理、编译、汇编、链接四个过程。如下图所示:

在这里插入图片描述

第一步:预处理 将源代码的.c 、.cpp 、.h 等文件包含到一个文件中。在这个过程中会使用一些预处理指令要求编译器使用什么样的方式包含这些文件。预处理结束之后对于c语言编译器会生成一个.i 文件。C++会生成.ii文件。

预编译过程主要处理那些源代码中以#开始的预编译指令,主要处理规则如下:

·将所有的#define删除,并且展开所有的宏定义;

·处理所有条件编译指令,如#if,#ifdef等;

·处理#include预编译指令,将被包含的文件插入到该预编译指令的位置。该过程递归进行,及被包含的文件可能还包含其他文件。

·删除所有的注释//和 /**/;

·添加行号和文件标识,如#2 “hello.c” 2,以便于编译时编译器产生调试用的行号信息及用于编译时产生编译错误或警告时能够显示行号信息;

·保留所有的#pragma编译器指令,因为编译器须要使用它们;

第二步:编译 编译过程就是把预处理完的文件进行一系列词法分析,语法分析,语义分析及优化后生成相应的汇编代码文件.

第三步:汇编 汇编器是将汇编代码转变成机器可以执行的命令,每一个汇编语句几乎都对应一条机器指令。汇编相对于编译过程比较简单,根据汇编指令和机器指令的对照表一一翻译即可。

第四步:链接 链接器ld将各个目标文件组装在一起,解决符号依赖,库依赖关系,并生成可执行文件。

动态链接和静态链接

方法库大体上可以分为两类:静态库和动态库(共享库)。

1. windows中静态库是以 .lib 为后缀的文件,动态库是以 .dll 为后缀的文件。

2. linux中静态库是以 .a 为后缀的文件,动态库是以 .so为后缀的文件。

静态链接:

  • 静态库 在链接阶段,会将汇编生成的目标文件.o与引用到的库一起链接打包到可执行文件中。因此对应的链接方式称为静态链接。
  • 静态库可以简单看成是一组目标文件(.o .obj文件)的集合, 将若干个.o文件转换为静态库的过程,称之为打包. Linux下是使用ar工具, Windows下是使用lib.exe。
  • Linux下静态链接库的后缀是.libWindows下静态链接库的后缀是.a

动态链接:

  • 动态库在程序编译时并不会被连接到目标代码中,而是在程序运行时才被载入。不同的应用程序如果调用相同的库,那么在内存里只需要有一份该共享库的实例,规避了空间浪费问题。动态库在程序运行是才被载入,也解决了静态库对程序的更新、部署和发布页会带来麻烦。用户只需要更新动态库即可,增量更新。
  • 在Windows系统下的执行文件格式是PE(Portable Executable)格式,动态库需要一个DllMain函数做出初始化的入口,通常在导出函数的声明时需要有_declspec(dllexport)关键字。 跟exe有个main或者WinMain入口函数一样,DLL也有一个入口函数,就是DllMain。根据编写规范,Windows必须查找并执行DLL里的DllMain函数作为加载DLL的依据,它使得DLL得以保留在内存里。这个函数并不属于导出函数,而是DLL的内部函数。这意味着不能直接在应用工程中引用DllMain函数,DllMain是自动被调用的。
    对于动态链接库,DllMain是一个可选的入口函数。一个动态链接库不一定要有DllMain函数,比如仅仅包含资源信息的DLL是没有DllMain函数的。
  • Linux下gcc编译的执行文件默认是ELF格式,不需要初始化入口,亦不需要函数做特别的声明,编写比较方便。
    无需打包工具,直接使用编译器即可创建动态库。
  • Linux下动态链接库的后缀是.so;Windows下动态链接库的后缀是.dll

区别:

 当程序与静态库链接时,静态库中所包含的所有函数方法都会被copy到最终的可执行文件中去。这就会导致最终生成的可执行代码量相对变多,相当于编译器将代码补充完整了。这种方式会让程序运行起来相对快一些,不过也会有个缺点: 占用磁盘和内存空间,导致可执行exe程序过大。另外,静态库会被添加到和它链接的每个程序中去, 而且这些程序运行时, 都会被加载到内存中,无形中又多消耗了更多的内存空间。

与动态库链接的可执行文件只包含它需要的函数方法的引用表,而不是所有的函数代码,只有在程序执行时, 那些需要的函数代码才会被拷贝到内存中。这样就使可执行文件比较小, 节省磁盘空间,更进一步,操作系统使用虚拟内存,使得一份动态库驻留在内存中被多个程序使用,也同时节约了内存。不过由于运行时要去链接库会花费一定的时间,执行速度相对会慢一些。

       总的来说,静态库是牺牲了空间效率,换取了时间效率,动态库是牺牲了时间效率换取了空间效率,没有好与坏的区别,只看具体需要了。
       另外,一个程序编好后,有时需要做一些修改和优化,如果我们要修改的刚好是库函数的话,在接口不变的前提下,使用动态库的程序只需要将动态库重新编译就可以了,而使用静态库的程序则需要将静态库重新编译好后,将程序再重新编译一遍。

推荐系统–特征交叉

特征交叉用于召回和排序阶段。

特征交叉01:Factorized Machine (FM) 因式分解机

线性模型对输入的特征取加权和,作为对目标的预估。如果先做特征交叉,再用线性模型,通常可以取得更好的效果。如果做二阶特征交叉,那么参数量为O(特征数量平方),计算量大,而且容易造成过拟合因式分解机(Factorized Machine, FM)用低秩矩阵分解的方式降低参数量,加速计算。任何可以用线性模型(比如线性回归、逻辑回归)解决的问题,都可以用 FM 解决。

线性模型的特征xi之间没有交叉,只是简单的加法,没有乘法。

二阶交叉特征:如果d很大,复杂度就太高了,计算代价太大,所以需要一种低复杂度的特征交叉方法,关键在于优化uij这个矩阵。

目前FM已经过时,不再使用该方法

特征交叉02:DCN 深度交叉网络

Deep & Cross Networks (DCN) 译作“深度交叉网络”,用于代替传统的全连接网络。可以用于召回双塔模型、粗排三塔模型、精排模型。DCN 由一个深度网络和一个交叉网络组成,交叉网络的基本组成单元是交叉层 (Cross Layer)。这节课最重点的部分就是交叉层。

交叉层 (Cross Layer)

深度交叉网络

特征交叉03:LHUC (PPNet)

LHUC 这种神经网络结构,只用于精排。LHUC 的起源是语音识别,后来被应用到推荐系统,快手将其称为 PPNet,现在已经在业界广泛落地。 遗漏一个细节:将LHUC用于推荐系统,门控神经网络(2 x sigmoid)的梯度不要传递到用户ID embedding特征,需要对其做 stop gradient。 参考文献: 1. Pawel Swietojanski, Jinyu Li, & Steve Renals. Learning hidden unit contributions for unsupervised acoustic model adaptation. IEEE/ACM Transactions on Audio, Speech, and Language Processing, 2016. 2. 快手落地万亿参数推荐精排模型,2021。

特征交叉04:SENet 和 Bilinear 交叉

1. SENet 是计算机视觉中的一种技术,可以用在推荐系统中对特征做动态加权。

2. 双线性(bilinear)特征交叉可以提升排序模型的表现。有很多种 bilinear 交叉的方法。 3. FiBiNet 是将 SENet 与 Bilinear 模型结合。

Filed间特征交叉

双线性(bilinear)特征交叉:

推荐系统–排序

课件地址:https://github.com/wangshusen/RecommenderSystem

结合小红书的业务场景和内部实践,讲解主流的工业界推荐系统技术。

主要内容:

排序01:多目标模型

第一部分是模型结构。模型把用户特征、物品特征、统计特征、场景特征作为输入,输出对多个指标的预估。 第二部分内容是降采样和校准。在实际的推荐系统中,正负样本的比例严重不平衡,负样本数量远多于正样本,因此需要对负样本做降采样。以点击率为例,对负样本做降采样会导致模型高估点击率,因此需要用公式做校准。

粗排和精排用到的方法都差不多,但精排的模型比较大,因此需要先用粗排模型从几千篇笔记中选择几百篇用于精排。

用户点击以后才会出现点赞、收藏和转发行为:

排序的依据是笔记的点击率、点赞率、收藏率、转发率等多种分数。

多目标模型:

模型输出某个用户对于某篇笔记的预估点赞率、点击率、收藏率以及转发率

训练:类别不平衡。

预估值校准

排序02:多目标排序模型的改进MMoE

Multi-gate Mixture-of-Experts (MMoE) 是一种多目标排序模型。MMoE 用多个独立的塔提取特征,并对多塔提取的特征向量做加权平均,然后送入多头。MMoE 的训练容易出现极化现象(polarize),可以用 dropout 解决。

这里假设神经网络的目标只有点击率和点赞率这两个目标,因此只需要两组权重:p和q

极化现象:softmax函数导致

极化解决方法:

排序03:预估分数的融合

前面通过MMoE获得了点赞、点击、转发率等指标的预估值,得到这些指标之后,需要将它们融合成一个分数。这节课介绍工业界常用的几种融分公式。

用预估的视频时长做排序,用排名来计算得分:

排序04:视频播放建模

视频播放的建模主要有两个目标:视频播放时长、视频完播率。这节课讨论如何在多目标排序模型添加与播放时长、完播率相关的预估任务。

视频越长,完播率越低:

排序05:排序模型的特征

推荐系统中的排序模型需要用户画像、物品画像、统计特征、场景特征。

排序06:粗排的三塔模型

粗排三塔模型,它介于前期融合的双塔模型和后期融合的精排模型之间。用户塔可以很大,因为它只需要做1次推理。物品塔可以比较大,因为物品塔的输出可以缓存在PS。交叉塔必须很小,如果有n个物品,交叉塔需要做n次推理。三塔模型的计算量主要是上层的全连接层。

推荐系统–召回

召回01:基于物品的协同过滤(ItemCF)

Item Based Collaborative Filtering,缩写 ItemCF

ItemCF 的原理:如果用户喜欢物品1,而且物品1与物品2相似,那么用户很可能喜欢物品2。

1. 如何计算两个物品之间的相似度。

2. 如何预估用户对候选物品的兴趣。

3. 如何利用索引在线上快速做召回。

ItemCF的实现

两个物体的受众重合度越高,表示两个物体越相似。

余弦相似度

ItemCF完整流程:

step1:事先做离线计算

step2 线上做召回:

为什么要用索引:

召回02:Swing召回通道

ItemCF :(缺点)

Swing模型:(为用户设置权重)

召回03:基于用户的协同过滤(UserCF)

UserCF 的原理:如果用户1跟用户2相似,而且用户2喜欢某物品,那么用户1很可能喜欢该物品。

关键:1. 如何计算两个用户之间的相似度。 2. 如何预估用户对候选物品的兴趣。 3. 如何利用索引在线上快速做召回。

用户相似度:

如果是热门物体,那么大概率两个用户都会喜欢,因此需要降低热门物体的权重:

UserCF召回的完整流程

step1:离线计算

step2:线上做召回

召回04: 向量召回 ,离散特征处理

one-hot encoding (独热编码) 和 embedding (嵌入)

召回05: 向量召回 ,矩阵补充、最近邻查找

矩阵补充(matrix completion),它是一种向量召回通道。矩阵补充的本质是对用户 ID 和物品 ID 做 embedding,并用两个 embedding 向量的内积预估用户对物品的兴趣。值得注意的是,矩阵补充存在诸多缺点,在实践中效果远不及双塔模型。 做向量召回需要做最近邻查找(nearest neighbor search)。

用绿色的信息做训练,来预测灰色的值,进而为用户做召回

近似最近邻查找:

step1:划分区域

step2:用一个向量表示但各区域,给定一个物体,则返回所在的区域的物体

step3:只需计算该区域所在的相似度。

召回06:双塔模型:矩阵补充的升级版

双塔模型(two-tower)也叫 DSSM,是推荐系统中最重要的召回通道,没有之一。双塔模型有两个塔:用户塔、物品塔。两个塔各输出一个向量,作为用户、物品的表征。两个向量的内积或余弦相似度作为对兴趣的预估。有三种训练双塔模型的方式:pointwise、pairwise、listwise。

双塔模型训练:

pointwise:

pariwise:

listwise:

召回07:双塔模型–正负样本选择

双塔模型(two-tower,也叫 DSSM)正负样本的选取。正样本是有点击的物品。负样本是被召回、排序淘汰的物品,分为简单负样本和困难负样本。

负样本:

简单负样本:Batch内负样本

热门样本成为负样本的概率过大,解决办法:

困难负样本:

召回08:双塔模型的线上服务和模型更新

在开始线上服务之前,需要把物品向量存储到Milvus、Faiss、HnswLib这类向量数据库,供最近邻查找(KNN 或 ANN)。当用户发起推荐请求时,用户塔用用户ID和用户画像现算一个用户向量,作为query,去向量数据库中做最近邻查找。

模型需要定期做更新,分为全量更新(天级别)和增量更新(实时)。全量更新会训练整个模型,包括embedding和全连接层。而增量更新只需要训练embedding层。

模型更新:

全量更新:跟新用户和物体向量

增量更新:每隔几小时实现用户模型更新

为什么需要这个:用户的兴趣可能会随时改变,因此需要随时做用户的更新,且只更新用户embedding参数,其他参数不需要更新。

注意:每天的全量更新是基于昨天的全量更新后的模型进行训练的。

问题:能否只做增量更新,不做全量更新

召回09:双塔模型的改进–自监督学习

改进双塔模型的方法,叫做自监督学习(self-supervised learning),用在双塔模型上可以提升业务指标。这种方法由谷歌在2021年提出,工业界(包括小红书)普遍验证有效。

长尾效应:

参考文献: Tiansheng Yao et al. Self-supervised Learning for Large-scale Item Recommendations. In CIKM, 2021.

自监督学习:

对物品进行不同的特征变换得到的特征向量同类之间尽可能的相同,不同物体之间尽可能不同。

特征变换方法:

1、Random Mask

2、Dropout

3、互补特征

最好的办法 :Random mask(将一组相关联的特征全部mask)

召回10:其他召回通道

地理位置召回包括GeoHash召回和同城召回作者召回包括关注作者、有交互作者、相似作者。缓存召回是储存精排打分高、而且未曝光的笔记。

6个其他召回通道:

【深度估计 Transformer】Vision Transformers for Dense Prediction

作者单位:Intel Labs

https://arxiv.org/abs/2103.13413

https://github.com/isl-org/DPT

Hugging Face

密集预测视觉Transformer(DPT),其在单目深度估计、语义分割等任务上表现SOTA!

我们介绍了密集视觉Transformer,该架构利用视觉Transformer代替卷积网络作为密集预测任务的骨干。我们将视觉Transformer各个阶段的token组装成各种分辨率的图像表示形式,并使用卷积解码器将它们逐步组合为全分辨率预测。

Transformer主干以恒定且相对较高的分辨率处理表示,并且在每个阶段都具有全局感受野。与全卷积网络相比,这些特性使密集视觉Transformer可以提供更细粒度和更全局一致的预测。

实验表明,这种架构密集的预测任务上产生了实质性的改进,特别是当有大量的训练数据可用时。对于单眼深度估计,与最先进的完全卷积网络(FCN,fully-convolutional networks.)相比,其相对性能提高了高达28%。应用于语义分割时,Dense vision transformer在ADE20K上是49.02%的mIoU。同时进一步表明,当处于新技术设置,该架构可以在较小的数据集上进行微调,如NYUv2、KITTIPascal Context

Introduction

密集预测架构主要基于卷积网络,其设计通常遵循一种从逻辑上将网络分为编码器解码器的模式编码器通常基于一个图像分类网络(主干),在像ImageNet这样的大型语料库上进行预训练。解码器聚合来自编码器的特征,并将其转换为最终的密集预测。密集预测的体系结构研究往往集中在解码器及其聚合策略上。主干架构对整个模型有很大的影响,因为编码器中丢失的任何信息都不可能在解码器中恢复。

卷积主干逐步对输入图像进行向下采样以提取多个尺度上的特征。下采样使接受域的逐渐增加,低级特征分组为抽象的高级特征,同时确保网络的内存和计算需求保持易于处理。然而,下采样有明显的缺点,这在密集的预测任务中特别突出:特征分辨率和粒度在模型的更深层次的阶段丢失,因此很难在解码器中恢复。虽然特征分辨率和粒度可能对某些任务并不重要,如图像分类,但它们对于密集预测至关重要,在理想情况下,体系结构应该能够在或接近输入图像的分辨率时分辨特征。

现提出的减轻特征粒度损失的各种技术。这些包括训练在更高的输入分辨率(如果计算预算允许),扩大卷积,迅速增加接受域没有下采样,适当放置从编码器的多个阶段跳过连接到解码器,或者最近的通过在整个网络并行连接多分辨率表示。虽然这些技术可以显着提高预测质量,但网络仍然受到其基本构建块—卷积的瓶颈。卷积和非线性一起构成了图像分析网络的基本计算单元。根据定义,卷积是具有有限接受域的线性算子。有限的接受域和单个卷积的有限表达性需要顺序堆叠成非常深的结构,以获得非常广泛的背景和足够高的表征能力。 然而,这需要产生许多需要大量的间接表示。降采样中间表示是必要的,以保持内存消耗在现有计算机架构中可行的水平。

在本工作中,介绍了密集的预测变压器(DPT)。DPT是一种密集的预测体系结构,它基于编码器-解码器的设计,利用transformer作为编码器的基本计算构建块。具体来说,使用最近提出的Vision Transformer(ViT)作为主干架构ViT提供的bag-of-words表示重新组装为不同分辨率的类图像特征表示,使用卷积解码器逐步将特征表示组合到最终的密集预测中。与完全卷积网络不同,ViT主干在计算初始图像嵌入后放弃显式下采样操作,并在所有处理阶段保持恒定维度的表示。此外,在每个阶段都有一个全局性的接受域,实验表明,这些特性对于密集的预测任务特别有利,因为它们会导致细粒度和全局相一致的预测。

单眼深度估计和语义分割的实验。对于通用单眼深度估计的任务,其中有大规模的训练数据可用,与该任务中性能最好的全卷积网络相比,DPT提供的性能提高了超过28%。该架构还可以微调到小的单眼深度预测数据集,如NYUv2和KITTI,当它也设置了新的技术状态。实验利用语义分割实验进一步提供了DPT的强性能的证据。对于这个任务,DPT在具有挑战性的ADE20K和Pascal Context数据集上设置了一个新的最新技术状态。定性结果表明,与卷积网络相比,这些改进可以归因于更细粒度和更全局一致的预测。

Transformer encoder 

在一个高水平上,Vision Transformer在图像的bag-of-words表示上进行操作。单独嵌入到特征空间中的图像补丁,或者从图像中提取的深度特征,扮演“words”的角色。在本工作的其余部分中,将嵌入的“word”称为tokens 。Transformer使用multi-headed self-attention(MHSA)的顺序块对tokens进行转换,该块将tokens相互联系起来来转换表示。

image–extracted–image patch–word–token(变化):特征空间中的图像patch

image patch:图像补丁是较大形式的像素容器例如,假设有一张 100 像素 x 100 像素的图像。如果您将这些图像划分为 10×10 块,那么将拥有一个包含 100 个块的图像(即每个块为 100 像素)。如果开发了一种算法,可以在 10px x 10px 上运行,那么 10px x 10px 就是补丁大小。例如,CNN 的池化层采用更大的块并将它们变成一个像素,可以将其视为信号处理中的窗口。在图像处理补丁窗口大多数情况下是可以互换的,但是当您的算法主要关注一堆像素共享相似属性的事实时,通常会在上下文中使用补丁。例如,补丁用于稀疏表示或图像压缩的上下文,而窗口用于边缘检测或图像增强。

Multi-headed self-attention(MHSA) Net:一种新的人物再识别模型,称为多头自我注意网络(MHSA-Net),用于从人物图像中剔除不重要的信息捕捉关键的局部信息。MHSA-Net包含两个主要的新颖组件:多头自我注意分支(MHSAB)和注意力竞争机制(ACM)。MHSAM自适应地捕获关键的局域人信息,然后产生有效的图像多样性嵌入,用于人的匹配。ACM进一步帮助过滤剔除注意力、噪音和非关键信息。通过广泛的消融研究,验证了结构化的自我注意分支和注意竞争机制都有助于MHSA-Net的性能提高。MHSA-Net在有遮挡的图像上作用深远。

对于应用程序来说,重要的是,transformer在所有计算过程中保持token数量。由于token与image patches有一对一的对应关系,这意味着ViT编码器在所有transformer阶段保持初始嵌入的空间分辨率。此外,MHSA是一个全局操作,因为每个token都可以关注并影响其他token。因此,transformer在初始嵌入后的每个阶段都有一个全局的接受域。这与卷积网络形成了鲜明的对比,卷积网络随着特征通过连续的卷积和下采样层时,逐渐增加了它们的接受域。 

具体而言,ViT通过处理所有不重叠的正方形斑块从图像中提取嵌入的补丁从图像中获得p^{2}像素的大小。这些补丁被扁平(flattened into)到向量中,并使用线性投影单独嵌入。另一种,更具样本效率的ViT变体通过对图像应用ResNet50来提取嵌入,并使用生成的特征映射的像素特征作为标记。由于transformer是set-to-set functions,因此它们本质上并不保留单个token的空间位置信息。因此,图像嵌入与可学习的位置嵌入连接,以将这些信息添加到表示中。在NLP中的工作之后,ViT还添加了一个与输入图像不相关的special token(readout token),并作为最终用于分类的全局图像表示。在此将这个特殊的标记称为读出标记。

Convolutional decoder

解码器将tokens组合成不同分辨率的类图像特征表示。特征表示逐渐融合到最终的密集预测中。文章提出了一个简单的三阶段重组操作,以从transformer encoder任意层输出的token中恢复类图像的表示形式

不管具体的transformer主干如何,都在四个不同的阶段和四个不同的分辨率上重新组装特征。以更低分辨率组装transformer深层的特征,而早期层的特征以更高分辨率组装。当使用ViT-Large时,从 l ={5,12,18,24}层重新组装tokens,而使用ViT-Base,使用 l ={3,6,9,12}层。当使用ViT-Hybrid时,使用了来自嵌入网络的第一和第二个ResNet块和阶段 l ={9,12}的特性。默认体系结构使用投影作为读出操作,并使用\hat{D}=256维度生成特性映射,将这些架构分别称为DPT-Base、DPT-Large和DPTHybrid。

最后,使用基于RefineNet的特征融合块结合从连续阶段提取的特征图,并在每个融合阶段对表示向上采样两次。最终的表示大小的分辨率是输入图像的一半,在此,附加一个特定于任务的输出头来产生最终的预测。完整架构的示意图概述如上图所示。

鲁棒性的单目深度估计: Mixing Datasets for Zero-shot Cross-dataset Transfer

Towards Robust Monocular Depth Estimation: Mixing Datasets for Zero-shot Cross-dataset Transfer, TPAMI 2022

MiDaS v3.1https://github.com/isl-org/MiDaS(最近也在更新)

Hugging Face

这篇文章提出了一种监督的深度估计方法,其中使用一些很有价值的策略使得最后深度估计的结果具有较大提升。具体来讲文章的策略可以归纳为:
1)数据集 : 现有的深度数据集的场景不够丰富, 不能训练出一个在任意场景下都健壮的模型. 因此作者选择结合这些数据集。使用多个深度数据集(各自拥有不同的scale和shift属性)加入进行训练,增大数据量与实现场景的互补

但是组合这些数据集有三个挑战:

  • 深度表达不同, 有的是0表示最近, 有的是0表示最远
  • 部分数据集没有提供缩放信息
  • 部分数据集提供了单张图像的相对深度(disparity), 但是跨数据集的相对深度无法直接转换


2)提出了一种scale-shift invariable的loss(具有深度和偏移不变性的损失函数)用于去监督深度的回归过程,从而使得可以更加有效使用现有数据;
3)采用从3D电影中进行采样的方式扩充数据集,从而进一步增加数据量;
4)使用带有原则属性的多目标训练方法,从而得到一种更加行之有效的优化方法;
结合上述的优化策略与方法,文章的最后得到的模型具有较强的泛化能力,从而摆脱了之前一些公开数据集场景依赖严重的问题。

优势:

  1. 问题的转化
    为了解决数据本身存在深度不一致的问题, 转化成设计一个对深度不敏感的loss
  2. 预训练模型
    可以直接得到任意单张图像的深度信息, 用于下游任务的训练, 

效果:

Single-View View Synthesis :图片-网络-深度图-网络-新视点

Single-View View Synthesis in the Wild with Learned Adaptive
Multiplane Images SIGGRAPH 2022.

现有方法已经显示出利用单眼深度估计和具有分层深度表示的颜色修复的有希望的结果。 然而,这些方法处理复杂3D 几何场景的能力仍然有限。 我们提出了一种基于多平面的新方法图像 (MPI) 表示。适应野外场景布局多样化,解决高维MPI内容生成难的问题,我们设计了一个网络结构,由两部分组成,一个用于平面深度调整,另一个用于深度感知颜色预测。前者使用 RGBD 上下文特征和注意机制调整初始平面位置。 给定调整后的深度值,后者通过特征屏蔽策略实现适当的平面间交互,分别预测每个平面的颜色和密度。

为了训练我们的方法,我们通过简单而有效的 warp-back 策略仅使用不受约束的单视图图像集合来构建大规模立体训练数据。 在合成数据集和真实数据集上进行的实验表明,我们训练的模型效果非常好,并取得了最先进的结果

Code:https://github.com/yxuhan/AdaMPI

网络结构:

训练过程:

结果:

TorchScript—模型部署

摘自:https://zhuanlan.zhihu.com/p/486914187

官网:https://pytorch.org/docs/stable/jit.html

PyTorch 无疑是现在最成功的深度学习训练框架之一,是各种顶会顶刊论文实验的大热门。比起其他的框架,PyTorch 最大的卖点是它对动态网络的支持,比其他需要构建静态网络的框架拥有更低的学习成本。PyTorch 源码 Readme 中还专门为此做了一张动态图:


对研究员而言, PyTorch 能极大地提高想 idea、做实验、发论文的效率,是训练框架中的豪杰,但是它不适合部署。动态建图带来的优势对于性能要求更高的应用场景而言更像是缺点,非固定的网络结构给网络结构分析并进行优化带来了困难,多数参数都能以 Tensor 形式传输也让资源分配变成一件闹心的事。另外由于图是由 python 代码构建的,一方面部署要依赖 python 环境,另一方面模型也毫无保密性可言。

而 TorchScript 就是为了解决这个问题而诞生的工具。包括代码的追踪及解析、中间表示的生成、模型优化、序列化等各种功能,可以说是覆盖了模型部署的方方面面。

TorchScript

动态图模型通过牺牲一些高级特性来换取易用性,那到底 JIT 有哪些特性,在什么情况下不得不用到 JIT 呢?下面主要通过介绍 TorchScript(PyTorch 的 JIT 实现)来分析 JIT 到底带来了哪些好处。

  1. 模型部署

PyTorch 的 1.0 版本发布的最核心的两个新特性就是 JIT 和 C++ API,这两个特性一起发布不是没有道理的,JIT 是 Python 和 C++ 的桥梁,我们可以使用 Python 训练模型,然后通过 JIT 将模型转为语言无关的模块,从而让 C++ 可以非常方便得调用,从此「使用 Python 训练模型,使用 C++ 将模型部署到生产环境」对 PyTorch 来说成为了一件很容易的事。而因为使用了 C++,我们现在几乎可以把 PyTorch 模型部署到任意平台和设备上:树莓派、iOS、Android 等等…

2. 性能提升

既然是为部署生产所提供的特性,那免不了在性能上面做了极大的优化,如果推断的场景对性能要求高,则可以考虑将模型(torch.nn.Module)转换为 TorchScript Module,再进行推断。

3. 模型可视化

TensorFlow 或 Keras 对模型可视化工具(TensorBoard等)非常友好,因为本身就是静态图的编程模型,在模型定义好后整个模型的结构和正向逻辑就已经清楚了;但 PyTorch 本身是不支持的,所以 PyTorch 模型在可视化上一直表现得不好,但 JIT 改善了这一情况。现在可以使用 JIT 的 trace 功能来得到 PyTorch 模型针对某一输入的正向逻辑,通过正向逻辑可以得到模型大致的结构,但如果在 `forward` 方法中有很多条件控制语句,这依然不是一个好的方法,所以 PyTorch JIT 还提供了 Scripting 的方式。

TorchScript Module 的两种生成方式

1. 编码(Scripting)

可以直接使用 TorchScript Language 来定义一个 PyTorch JIT Module,然后用 torch.jit.script 来将他转换成 TorchScript Module 并保存成文件。而 TorchScript Language 本身也是 Python 代码,所以可以直接写在 Python 文件中。

使用 TorchScript Language 就如同使用 TensorFlow 一样,需要前定义好完整的图。对于 TensorFlow 我们知道不能直接使用 Python 中的 if 等语句来做条件控制,而是需要用 tf.cond,但对于 TorchScript 我们依然能够直接使用 if 和 for 等条件控制语句,所以即使是在静态图上,PyTorch 依然秉承了「易用」的特性。TorchScript Language 是静态类型的 Python 子集,静态类型也是用了 Python 3 的 typing 模块来实现,所以写 TorchScript Language 的体验也跟 Python 一模一样,只是某些 Python 特性无法使用(因为是子集),可以通过 TorchScript Language Reference 来查看和原生 Python 的异同。

理论上,使用 Scripting 的方式定义的 TorchScript Module 对模型可视化工具非常友好,因为已经提前定义了整个图结构。

2. 追踪(Tracing)

使用 TorchScript Module 的更简单的办法是使用 Tracing,Tracing 可以直接将 PyTorch 模型(torch.nn.Module)转换成 TorchScript Module。「追踪」顾名思义,就是需要提供一个「输入」来让模型 forward 一遍,以通过该输入的流转路径,获得图的结构。这种方式对于 forward 逻辑简单的模型来说非常实用,但如果 forward 里面本身夹杂了很多流程控制语句,则可能会有问题,因为同一个输入不可能遍历到所有的逻辑分枝。

此外,还可以混合使用上面两种方式。

模型转换

作为模型部署的一个范式,通常我们都需要生成一个模型的中间表示(IR),这个 IR 拥有相对固定的图结构,所以更容易优化,让我们看一个例子:

import torch 
from torchvision.models import resnet18 
 
# 使用PyTorch model zoo中的resnet18作为例子 
model = resnet18() 
model.eval() 
 
# 通过trace的方法生成IR需要一个输入样例 
dummy_input = torch.rand(1, 3, 224, 224) 
 
# IR生成 
with torch.no_grad(): 
    jit_model = torch.jit.trace(model, dummy_input) 

JIT 是一种概念,全称是 Just In Time Compilation,中文译为「即时编译」,是一种程序优化的方法

到这里就将 PyTorch 的模型转换成了 TorchScript 的 IR。这里我们使用了 trace 模式来生成 IR,所谓 trace 指的是进行一次模型推理,在推理的过程中记录所有经过的计算,将这些记录整合成计算图。

那么这个 IR 中到底都有些什么呢?我们可以可视化一下其中的 layer1 看看:

jit_layer1 = jit_model.layer1 
print(jit_layer1.graph) 
 
# graph(%self.6 : __torch__.torch.nn.modules.container.Sequential, 
#       %4 : Float(1, 64, 56, 56, strides=[200704, 3136, 56, 1], requires_grad=0, device=cpu)): 
#   %1 : __torch__.torchvision.models.resnet.___torch_mangle_10.BasicBlock = prim::GetAttr[name="1"](%self.6) 
#   %2 : __torch__.torchvision.models.resnet.BasicBlock = prim::GetAttr[name="0"](%self.6) 
#   %6 : Tensor = prim::CallMethod[name="forward"](%2, %4) 
#   %7 : Tensor = prim::CallMethod[name="forward"](%1, %6) 
#   return (%7) 

是不是有点摸不着头脑?TorchScript 有它自己对于 Graph 以及其中元素的定义,对于第一次接触的人来说可能比较陌生,但是没关系,我们还有另一种可视化方式:

print(jit_layer1.code) 
 
# def forward(self, 
#     argument_1: Tensor) -> Tensor: 
#   _0 = getattr(self, "1") 
#   _1 = (getattr(self, "0")).forward(argument_1, ) 
#   return (_0).forward(_1, ) 

没错,就是代码!TorchScript 的 IR 是可以还原成 python 代码的,如果你生成了一个 TorchScript 模型并且想知道它的内容对不对,那么可以通过这样的方式来做一些简单的检查。

刚才的例子中我们使用 trace 的方法生成IR。除了 trace 之外,PyTorch 还提供了另一种生成 TorchScript 模型的方法:script。这种方式会直接解析网络定义的 python 代码,生成抽象语法树 AST,因此这种方法可以解决一些 trace 无法解决的问题,比如对 branch/loop 等数据流控制语句的建图。script方式的建图有很多有趣的特性,会在未来的分享中做专题分析,敬请期待。

模型优化

聪明的同学可能发现了,上面的可视化中只有resnet18forward的部分,其中的子模块信息是不是丢失了呢?如果没有丢失,那么怎么样才能确定子模块的内容是否正确呢?别担心,还记得我们说过 TorchScript 支持对网络的优化吗,这里我们就可以用一个pass解决这个问题:

# 调用inline pass,对graph做变换 
torch._C._jit_pass_inline(jit_layer1.graph) 
print(jit_layer1.code) 
 
# def forward(self, 
#     argument_1: Tensor) -> Tensor: 
#   _0 = getattr(self, "1") 
#   _1 = getattr(self, "0") 
#   _2 = _1.bn2 
#   _3 = _1.conv2 
#   _4 = _1.bn1 
#   input = torch._convolution(argument_1, _1.conv1.weight, None, [1, 1], [1, 1], [1, 1], False, [0, 0], 1, False, False, True, True) 
#   _5 = _4.running_var 
#   _6 = _4.running_mean 
#   _7 = _4.bias 
#   input0 = torch.batch_norm(input, _4.weight, _7, _6, _5, False, 0.10000000000000001, 1.0000000000000001e-05, True) 
#   input1 = torch.relu_(input0) 
#   input2 = torch._convolution(input1, _3.weight, None, [1, 1], [1, 1], [1, 1], False, [0, 0], 1, False, False, True, True) 
#   _8 = _2.running_var 
#   _9 = _2.running_mean 
#   _10 = _2.bias 
#   out = torch.batch_norm(input2, _2.weight, _10, _9, _8, False, 0.10000000000000001, 1.0000000000000001e-05, True) 
#   input3 = torch.add_(out, argument_1, alpha=1) 
#   input4 = torch.relu_(input3) 
#   _11 = _0.bn2 
#   _12 = _0.conv2 
#   _13 = _0.bn1 
#   input5 = torch._convolution(input4, _0.conv1.weight, None, [1, 1], [1, 1], [1, 1], False, [0, 0], 1, False, False, True, True) 
#   _14 = _13.running_var 
#   _15 = _13.running_mean 
#   _16 = _13.bias 
#   input6 = torch.batch_norm(input5, _13.weight, _16, _15, _14, False, 0.10000000000000001, 1.0000000000000001e-05, True) 
#   input7 = torch.relu_(input6) 
#   input8 = torch._convolution(input7, _12.weight, None, [1, 1], [1, 1], [1, 1], False, [0, 0], 1, False, False, True, True) 
#   _17 = _11.running_var 
#   _18 = _11.running_mean 
#   _19 = _11.bias 
#   out0 = torch.batch_norm(input8, _11.weight, _19, _18, _17, False, 0.10000000000000001, 1.0000000000000001e-05, True) 
#   input9 = torch.add_(out0, input4, alpha=1) 
#   return torch.relu_(input9) 

这里我们就能看到卷积、batch_norm、relu等熟悉的算子了。

上面代码中我们使用了一个名为inlinepass,将所有子模块进行内联,这样我们就能看见更完整的推理代码。pass是一个来源于编译原理的概念,一个 TorchScript 的 pass 会接收一个图,遍历图中所有元素进行某种变换,生成一个新的图。我们这里用到的inline起到的作用就是将模块调用展开,尽管这样做并不能直接影响执行效率,但是它其实是很多其他pass的基础。PyTorch 中定义了非常多的 pass 来解决各种优化任务,未来我们会做一些更详细的介绍。

序列化

不管是哪种方法创建的 TorchScript 都可以进行序列化,比如:

# 将模型序列化 
jit_model.save('jit_model.pth') 
# 加载序列化后的模型 
jit_model = torch.jit.load('jit_model.pth') 

序列化后的模型不再与 python 相关,可以被部署到各种平台上。

PyTorch 提供了可以用于 TorchScript 模型推理的 c++ API,序列化后的模型终于可以不依赖 python 进行推理了:

// 加载生成的torchscript模型 
auto module = torch::jit::load('jit_model.pth'); 
// 根据任务需求读取数据 
std::vector<torch::jit::IValue> inputs = ...; 
// 计算推理结果 
auto output = module.forward(inputs).toTensor(); 

与 torch.onnx 的关系:ONNX 是业界广泛使用的一种神经网络中间表示,PyTorch 自然也对 ONNX 提供了支持。torch.onnx.export函数可以帮助我们把 PyTorch 模型转换成 ONNX 模型,这个函数会使用 trace 的方式记录 PyTorch 的推理过程。聪明的同学可能已经想到了,没错,ONNX 的导出,使用的正是 TorchScript 的 trace 工具。具体步骤如下:

  1. 使用 trace 的方式先生成一个 TorchScipt 模型,如果你转换的本身就是 TorchScript 模型,则可以跳过这一步。
  2. 使用许多 pass 对 1 中生成的模型进行变换,其中对 ONNX 导出最重要的一个 pass 就是ToONNX,这个 pass 会进行一个映射,将 TorchScript 中primaten空间下的算子映射到onnx空间下的算子。
  3. 使用 ONNX 的 proto 格式对模型进行序列化,完成 ONNX 的导出。

D-NeRF—针对动态场景的NeRF

主页:https://www.albertpumarola.com/research/D-NeRF/index.html

https://github.com/albertpumarola/D-NeRF

D-NeRF: Neural Radiance Fields for Dynamic Scenes https://arxiv.org/abs/2011.13961

Abstract


NeRF只能重建静态场景,本文提出的方法可以把神经辐射场扩展到动态领域,可以在单相机围绕场景旋转一周的情况下重建物体的刚性和非刚性运动。由此把时间作为附加维度加到输入中,同时把学习过程分为两个阶段:第一个把场景编码到基准空间,另一个在特定时间把这种基准表达形式map到变形场景中。两个map都用fcn学习,训练完后可以通过控制相机视角和时间变量达到物体运动的效果。

Introduction

和已有的4d方式不同点有:

  • 只需单相机
  • 无需预先算好一个三维重建
  • 可以端到端

想做的就是在原始nerf五维输入上加个时间t,完成

到density和radiance的输出。不过如果直接加,时间冗余没有很好的利用,效果并不好。所以分两个模块:

而且可以产生完整三维mesh,捕捉时变几何。

Pipeline

Formulation

Method


Canonical network(规范网络)

希望找到一种场景的表示能把所有图的相关点的信息都汇聚起来。网络 Ψx 用来编码基准空间中的密度和颜色,给一个坐标x,通过fcn输出256维向量再和d concatenate起来输出density和color


Deformation network(变形网络)

Ψt 训练用来估计某一具体时刻的场景和基准空间中场景的变形场(deformation field),给一个坐标x,输出是这个点和基准空间中的这个点的位移。并且把基准空间场景设为t=0

也用了位置编码,x十维,d,t都是四维

Volume rendering

带时间的渲染公式为:

p(ℎ,t) 表示canonical space中由x(h)变换来的点所以其实渲染的时候还是在canonical space里边进行渲染的离散化形式。

Experiment


可以出mesh,depth:

还可以合成阴影:

相比其他方法:

MPViT : Multi-Path Vision Transformer for Dense Prediction

https://arxiv.org/abs/2112.11010(CVPR2022)

https://github.com/youngwanLEE/MPViT

Introduction

  • 在这项工作中,作者以不同于现有Transformer的视角,探索多尺度path embedding与multi-path结构,提出了Multi-path Vision Transformer(MPViT)
  • 通过使用 overlapping convolutional patch embedding,MPViT同时嵌入相同大小的patch特征。然后,将不同尺度的Token通过多条路径独立地输入Transformer encoders,并对生成的特征进行聚合,从而在同一特征级别上实现精细和粗糙的特征表示。
  • 在特征聚合步骤中,引入了一个global-to-local feature interaction(GLI)过程,该过程将卷积局部特征与Transformer的全局特征连接起来,同时利用了卷积的局部连通性和Transformer的全局上下文。

Vision Transformers for dense predictions.

  • 密集的计算机视觉任务,如目标检测和分割,需要有效的多尺度特征表示,以检测或分类不同大小的物体或区域。Vision Transformer(ViT)构建了一个简单的多阶段结构(即精细到粗糙),用于使用单尺度patch的多尺度表示。然而ViT的变体专注于降低自注意的二次复杂度,较少关注构建有效的多尺度表示。
  • CoaT通过使用一种co-scale机制,同时表示精细和粗糙的特征,允许并行地跨层注意,从而提高了检测性能。然而,co-scale机制需要大量的计算和内存开销,因为它为基础模型增加了额外的跨层关注(例如,CoaT-Lite)。因此,对于ViT体系结构的多尺度特征表示仍有改进的空间。

Comparison to Concurrent work.

  • CrossViT利用了不同的patch大小和单级结构中的双路径,如ViT和XCiT。然而,CrossViT的分支之间的相互作用只通过[CLS]token发生,而MPViT允许所有不同规模的patch相互作用。此外,与CrossViT(仅限分类)不同的是,MPViT更普遍地探索更大的路径维度(例如,超过两个维度),并采用多阶段结构进行密集预测。

Method

Conv-stem

输入图像大小为:H×W×3,两层卷积:采用两个3×3的卷积,通道分别为C2/2,C2,stride为2,生成特征的大小为H/4×W/4×C2,其中C2为stage 2的通道大小。

  • 说明:每个卷积之后都是Batch Normalization 和一个Hardswish激活函数。
  • In LeViT , a convolutional stem block shows better low-level representation (i.e., without losing salient information) than non-overlapping patch embedding.

从stage 2到stage 5,作者在每个阶段对所提出的Multi-scale Patch Embedding(MS-PatchEmbed)和Multi-path Transformer(MP-Transformer)块进行堆叠

Multi-Scale Patch Embedding

通过改变stride和padding来调整token的序列长度。也就是说,可以输出具有不同patch大小的相同大小(即分辨率)的特征。因此,作者并行地形成了几个具有不同卷积核大小的卷积patch embedding层。例如,如图1所示,可以生成相同序列长度的不同大小的vision token,patch大小分别为3×3,5×5,7×7。

由于具有相同通道和滤波器大小的连续卷积操作扩大了接受域,并且需要更少的参数,在实践中选择了连续的3×3卷积层。为了减少参数量,在实践中选择了两个连续的3×3卷积层代替5×5卷积。对于triple-path结构,使用三个连续的3×3卷积,通道大小为C’,padding为1,步幅为s,其中s在降低空间分辨率时为2,否则为1。

  • 说明:为了减少模型参数和计算开销,采用3×3深度可分离卷积,包括3×3深度卷积和1×1点卷积。
  • 每个卷积之后都是Batch Normalization 和一个Hardswish激活函数。

接着,不同大小的token embedding features 分别输入到transformer encoder中。

Multi-path Transformer

原因:Transformer中的self-attention可以捕获长期依赖关系(即全局上下文),但它很可能会忽略每个patch中的结构性信息和局部关系。相反,cnn可以利用平移不变性中的局部连通性,使得CNN在对视觉对象进行分类时,对纹理有更强的依赖性,而不是形状。

因此,MPViT以一种互补的方式将CNN与Transformer结合起来。

  • 为了表示局部特征 
  • 采用了一个 depthwise residual bottleneck block,包括1×1卷积、3×3深度卷积和1×1卷积和残差连接。
  • 为了减轻多路径结构的计算负担,使用了CoaT中提出的有效的因素分解自注意:

Global-to-Local Feature Interaction

将局部特征和全局特征聚合起来:

为了保持可比性的参数和FLOPs,增加路径的数量需要减少通道C或层数L(即,transformer encoder的数量)。作者通过减少C而不是L,从单路径(即CoaT-Lite baseline)扩展到triple-path。在消融研究中,验证了减少C比减少L获得更好的性能(见表5)。由于stage2的特征分辨率较高,导致计算成本较高,作者在stage2中将triple-path模型的路径数设置为2。从stage3开始,三路径模型有3条路径。

作者还发现,虽然 triple-path和双路径在ImageNet分类中产生相似的精度,但 triple-path模型在密集预测任务中表现出更好的性能。因此,建立了基于 triple-path结构的MPViT模型。MPViT的详细情况见表1。

Experiments

Ablation study

对MPViT-XS的每个组件进行消融研究,以研究提出的多路径结构对图像分类和使用Mask R-CNN检测的有效性。

Exploring path dimension.