Mask Scoring RCNN

论文: https://arxiv.org/abs/1903.00241 CVPR2019

code: https://github.com/zjhuang22/maskscoring_rcnn

这篇论文从实例分割中mask 的分割质量角度出发,提出过去的经典分割框架存在的一个缺陷:用Bbox bounding box的classification confidence作为mask score,导致mask score和mask quality不配准。因此文章基于Mask R-CNN提出一个新的框架Mask Scoring R-CNN,能自动学习出mask quality,试图解决不配准的问题。

摘要

让一个深度网络意识到自己预测的质量是一个有趣但重要的问题。在实例分割任务中,大多数实例分割框架使用实例分类的置信度作为mask质量分数。然而,将mask质量量化为实例mask与其ground truth之间的IoU,通常与分类分数的相关性并不好。

在本文中,我们提出Mask Scoring R-CNN来学习预测实例mask的质量。提出的网络块将实例特征和相应的预测mask结合起来回归mask IoU。Mask评分策略校准mask质量和mask评分之间的偏差,并通过在COCO AP评估期间优先处理更准确的mask预测来改进实例分割性能。

通过对COCO数据集的广泛评估,Mask Scoring R-CNN与不同的模型带来一致和显着的增益,并优于Mask RCNN。

总而言之,这项工作的主要贡献突出如下:

1. 提出Mask Scoring R-CNN,这是第一个解决实例分割假设得分问题的框架。它探索了改善实例分割模型性能的新方向。通过考虑实例mask的完整性,如果实例mask的得分较高而mask不够好,则可以对实例mask的分数进行惩罚。

2. MaskIoU head非常简单有效。能够在各个backbone上涨点。

正文:

在实例分割(instance segmentation)中,比如Mask R-CNN,mask 分支的分割质量(quality)来源于检测分支的classification confidence。Mask R-CNN其实Faster R-CNN系列的延伸,其在Faster R-CNN的基础上添加一个新的分支用来预测object mask,该分支以检测分支的输出作为输入,mask的质量一定程度上依赖于检测分支。这种简单粗暴的做法取得了SOTA的性能,近年来COCO比赛的冠军或者前几名基本是Mask R-CNN及其变体,但依然有上升的空间。

更仔细的来讲,Mask R-CNN存在的问题是:bounding box的classification confidence不能代表mask的分割质量。classification confidence高可以表示检测框的置信度高(严格来讲不能表示框的定位精准),但也会存在mask分割的质量差的情况。高的分类置信度也应该同时有好的mask 结果。

回到原始的初衷,文章希望得到精准的mask质量,那么如何评价输出的mask质量呢?

是AP,或者说是instance-level的IoU。这个IoU和检测用到的IoU是一个东西,前者是predict mask和gt mask的pixel-level的Intersection-over-Union,而后者则是predict box和gt box的box-level的Intersection-over-Union。所以一个直观的方法就是用IoU来表示分割的质量,那么让网络自己学习输出分割的质量也是简单直观的做法。学习出mask的IoU,那么最后的mask score就等于maskIoU乘以classification score,mask score就同时表示分类置信度和分割的质量。

作者在Mask R-CNN的基础上添加了一个MaskIoU分支用于预测当前输出的mask和gt mask的IoU。MaskIoU的输入由两部分组成,一是ROIAlign得到的RoI feature map,二是mask分支输出的mask。两者concat之后经过3层卷积和2层全连接输出MaskIoU。

training过程:

box分支和mask保持不变,输出的mask先经过阈值为0.5的binarize,再计算binary mask和gt的IoU作为target,采用L2 loss作为损失函数,loss weight设为1,3个分支同时end-to-end训练。

inference过程:

检测分支输出score最高的100个框,再送入mask分支,得到mask结果,RoI feature map再和mask送入MaskIoU分支得到mask iou,与box的classification score相乘就得到最后的mask score。

实验结果,在COCO 2017 test集上,相对于Mask R-CNN,mask AP有1个点多的提升。

同时作者还做了对比实验,验证不同的MaskIoU输入对性能的影响。文章列举了4种输入方式:

  1. target mask和ROI feature concat
  2. target mask和ROI feature 相乘
  3. 所有mask和ROI feature concat
  4. target mask和高分辨率的ROI feature concat

其网络结构示意图如下:

验证不同training target对性能的影响:

  1. 只学习target类别的MaskIoU,忽略其他类别
  2. 学习所有类别的MaskIoU,相应的其他类别的MaskIoU的学习目标就是0
  3. 学习出现在ROI区域的类别的MaskIoU。

可以看出,setting#1的效果最好,setting#2的效果最差。

同时作者还做了实验探索Mask Scoring R-CNN的性能上界。

对每个输出的MaskIoU,用输出的mask 和匹配的gt mask iou来代替,MaskIoU分支就输出了mask分支的真实quality,这时就得到了Mask Scoring R-CNN的性能上界。实验结果表明Mask Scoring R-CNN依然比Mask R-CNN更好,说明MaskIoU起到了alignment的效果,但很显然会比用gt mask iou 代替的效果差,说明一方面box的准确性和mask分支本身也会影响mask任务的性能,另一方面MaskIoU 分支的学习能力可以进一步提升,Mask Scoring R-CNN依然有提升的空间。

速度方面,作者在Titan V GPU上测试一张图片,对于ResNet18-FPN用时0.132s,Resnet101-DCN-FPN用时0.202s,Mask Scoring R-CNN和Mask R-CNN速度一样。

总结:

作者motivation就是想让mask的分数更合理,从而基于mask rcnn添加一个新的分支预测来得到更准确的分数,做法简单粗暴,从结果来看也有涨点。其实mask的分割质量也跟box输出结果有很大关系,这种detection-based分割方法不可避免,除非把detection结果做的非常高,不然mask也要受制于box的结果。这种做法与IoU-Net类似,都是希望直接学习最本质的metric方式来提升性能。

为了同时提升detection和mask的效果,最近的Cascade方法很受欢迎,从人脸检测领域的Cascade CNN, 到Cascade R-CNN: Delving into High Quality Object Detection,再到友商的HTC不仅在COCO中拿了冠军,同时也被CVPR2019接收,Cascade方式展现了强大实力,相信在未来会出现越来越多的Cascade,如Cascade RetinaNet,Cascade TridentNet。。。

NeRF:类神经网路在View Synthesis的热门新方向

项目主页:https://www.matthewtancik.com/nerf

论文地址: https://arxiv.org/abs/2003.08934  
code:https://github.com/bmild/nerf https://github.com/yenchenlin/nerf-pytorch
ECCV 2020 (Oral Presentation, Best Paper Honorable Mention)

NeRF:用深度学习完成3D渲染任务的蹿红

文章难度:★★★★☆
阅读建议: 这篇文章尽量深入浅出地介绍 NeRF,这个近期 deep learning的热门新宠儿。 NeRF因为牵扯到许多 rendering与机率的设计,所以直接啃起来是挺硬的。这篇文章一开始会先简单地介绍 NeRF以及 Volume Rendering的概念,而后才会一步一步地解释 NeRF的运作细节与训练方法。最后也会简单带过几个接续 NeRF研究的优秀方法。
推荐背景知识: deep learning, multilayer perceptron (MLP), volume rendering, ray tracing, light field, view synthesis, neural rendering, quadrature, stratified sampling.

NeRF 是 2020 年 ECCV 上获得最佳论文荣誉提名的工作,其影响力是十分巨大的。NeRF 将隐式表达推上了一个新的高度,仅用 2D 的 posed images 作为监督,即可表示复杂的三维场景,在新视角合成这一任务上的表现是非常 impressive 的。

问题1:3D渲染是要干什么?

用照相机拍照是一个现实世界的物理过程,主要是光学过程,拍照对象是现实世界中真实的万事万物,形成照片的机制主要就是:光经过镜头,到达传感器,被记录下来。

渲染就是用计算机模拟这一过程,模拟“拍照”的对象是已存在的某种三维场景表示(3D representation of the scene),模拟生成照片的机制是图形学研究人员精心设计的算法。

关键前提:渲染的前提是某种三维场景表示已经存在。渲染一词本身不包办生成三维场景表示。不过,渲染的确与三维场景表示的形式息息相关;因此研究渲染的工作通常包含对三维场景表示的探讨。

  • 问题2:3D渲染是图形学问题,那么原先大家是用什么传统图形学方法实现3D渲染的呢?

主要有两种算法:光栅化(rasterization),光线追踪(ray tracing);都是对照相机拍照的光学过程进行数学物理建模来实现的

Quick Overview: Neural Radiance Fields (NeRF)

NeRF的核心精神是将物体与场景的信息,编码 (encode) 进 MLP (multi-layer perceptron)中。然后使用 computer graphics(计算机图形学)中的 volume rendering(立体渲染)将 MLP中的信息投影出来。

在训练的部分, NeRF 所需要的是某个物体与场景的 multi-view多视角影像,利用这些影像来训练NeRF,或者更直接地说,将信息 encode进 NeRF。之后就可以对这个物体与场景 render出连续、不存在于原始信息的视角

NeRF整体的结果非常令人惊艳,特别是在金属或有光泽物体的结果,视觉的真实性真的非常高。可以参考以下的图片,或者直接到官方的页面看影片会更有感觉。

不过如果对于没有 rendering相关知识的人,要直接理解NeRF可能会有点障碍。因此,接下来会先简单带一些基础的 volume rendering与 ray tracing观念,再回头去看 NeRF到底是怎么做的。

Volume Rendering

所谓的 volume rendering (立体渲染) 指的是将 discretely sampled 3D data投影到 2D的技术。像是以下这张图片,我们已知一个物体的离散3D信息,对于目前的视角,就可以动态地渲染出这个视角看到的画面。

使用shear warp algorithm做volume rendering的例子。(资料来源)

Ray Tracing

而在 rendering上其中一个实作方法是 ray tracing。 这个方法可以用眼睛看物体来解释。我们眼睛之所以看的到物体,是因为有光源打到了物体,然后反射进我们的眼睛。而Ray tracing的想法其实就是反推这个射线 (ray),从相机中心发出射线,与物体相交就根据规则反射、折射或吸收,直到遇到光源或者走太远停住。

Ray tracing的方法示意图 (资料来源)

总之, ray tracing就是借由这个概念算出在 image平面上的 2D投影该长什么样子。实际上这边的名词蛮复杂的 (至少对非图学出身的笔者来说),像是 ray tracing、 ray casting、 ray marching什么的。为了避免混淆视听,这边就先不解释过多了。

接下来,就开始正式介绍 NeRF的细节。

Neural Radiance Fields (NeRF)-神经辐射场

NeRF网路的输入是一组 5D的参数,包含一组 3D的 location X = (x, y, z)跟一组 2D的 view direction (θ, φ),实事上这个 view direction表示为 3D Cartesian unit vector(笛卡尔单位矢量),称为 d。而NeRF网路的输出则是一组 emitted color (如RPG的c=(r, g, b)) 与 volume density σ

以下图来解释会更加清楚。这个 3D location指的是在 3D空间中的任何一个点,比如说怪手的尖端,而 view direction则是说我要从哪个视角看。NeRF网路的输出则是这个 3D点实际 (或者说推估) 的颜色,以及推估的 volume density(体积密度σ:可以简单理解为不透明度)。

有了RGB和体密度3D物体表示,就可以使用 Ray Tracing 等3D渲染方法来渲染出任意视角的2D投影。(Volume rendering)

NeRF的运作概念。(资料来源)

Radiance Fields,或者说映射 gθ ,能对三维场景进行隐式表示(implicit scene representation)。在上一节,我们说过某种三维场景表示正是渲染的前提。实现渲染也是 作者提出Radiance Fields这一新型三维场景表示方法 的目的所在。

NEeRF输入数据准备(将一系列的2d图片转成网络所需的输入):

多视角2D图像准备

想要利用NeRF拟合自己拍摄的3D场景,首先需要准备多张从不同角度拍摄的同场景静态2D图像,以沙发为例。

沙发多视图

相机内外参估计

不过仅仅有2D多视图还不够,在重建阶段,我们还需要各个点的位置 x={x,y,z} 和方位 d={θ,ϕ} 作为输入进行重建。为了获得这些数据,NeRF中采用了传统方法COLMAP进行参数估计。还是以沙发为例,通过COLMAP可以得到场景的稀疏重建结果,其输出文件包括相机内参,相机外参和3D点的信息,然后进一步利用LLFF开源代码中的imgs2poses文件将内外参整合到一个文件poses_boudns.npy中,该文件记录了相机的内参,包括图片分辨率(图片高与宽度)、焦距,共3个维度、外参(包括相机坐标到世界坐标转换的平移矩阵 t 与旋转矩阵 R,其中旋转矩阵为 R∈R3×3 的矩阵,共9个维度,平移矩阵为 t∈R3×1 的矩阵,3个维度,因此该文件中的数据维度为 N×17 (另有两个维度为光线的始发深度与终止深度,通过COLMAP输出的3D点位置计算得到),其中N为图片样本数。

ps: 这里建议将nerf-pytorch中加载poses_boudns.npy数据中的代码与imgs2poses中保存poses_boudns.npy的代码对着看一遍,就可以理解加载时为什么要搞一些奇怪的维度变换等操作了。

DCOLMAP重建结果

Raw数据到MPL的输入

输入生成与坐标系变换

在理解NeRF的过程中涉及到一个很重要的问题,即原文中提到的MLP的输入 x={x,y,z} 和 d={θ,ϕ} 是什么,其与相机内外参有什么关系?要搞懂这个问题,需要先理解坐标系变换

总的来说,坐标变换的目的在于将不同视角下视角特定的坐标系投影到一个统一的世界坐标中进行三维重建,见get_rays_np函数。首先,我们在像素坐标下进行网格点采样,得到特定分辨率图像的各个像素点坐标

i, j=np.meshgrid(np.arange(W, dtype=np.float32), np.arange(H, dtype=np.float32), indexing='xy')

然后,我们进行像素坐标到相机坐标的变换,要理解这个变换需要清楚两个点。

dirs=np.stack([(i-K[0][2])/K[0][0], -(j-K[1][2])/K[1][1], -np.ones_like(i)], -1)

首先像素坐标到相机坐标的变换属于投影变换的逆变换,即2D点到3D的变化,即我们需要根据像素点的坐标 (u,v) 计算该点在相机坐标系下的三维的坐标 (X,Y,Z)。

像素坐标到相机坐标

这时帅气的小伙伴就发现了,为什么相机坐标的 Y 和 Z 前面有负号?那是因为COLMAP采用的是opencv定义的相机坐标系统,其中x轴向右,y轴向下,z轴向内;而Nerf pytorch采用的是OpenGL定义的相机坐标系统,其中x轴向右,y轴向上,z轴向外。因此需要在y与z轴进行相反数转换。

最后,我们进行相机坐标到世界坐标的转换得到光线始发点与方向向量,这里需要了解世界坐标到相机坐标的变换过程,本质上为矩阵相乘,要进行反向变化只需要对变化矩阵求逆即可(COLMAP原输出的是世界坐标到相机坐标的旋转矩阵,在LLFF的imgs2poses.py中已经进行了求逆操作)。

rays_d=np.sum(dirs[..., np.newaxis, :] *c2w[:3,:3], -1)
rays_o=np.broadcast_to(c2w[:3,-1], np.shape(rays_d))

首先因为相机坐标与世界坐标的坐标原点不一致,要解决这个问题只需要将相机坐标进行平移变化即可进行对齐,实现平移的方式在是世界坐标系下减去一个平移矩阵,即相机外参中的平移矩阵 t ,对应得到代码中的rays_o。

平移以对齐坐标系的原点

此外,由于相机坐标与世界坐标的各个轴的朝向也是不同的,因此需要进一步通过旋转来对齐坐标轴的朝向,实现旋转的方式是在平移对齐坐标原点之后的基础上进行旋转,即与相机外参中的旋转矩阵 R 做矩阵乘法,对应得到代码中的rays_d。

旋转以对齐坐标轴的朝向

另附:NeRF的代码中其实还存在另外一个坐标系统:Normalized device coordinates(ndc)坐标系,一般对于一些forward facing的场景(景深很大)会选择进一步把世界坐标转换为ndc坐标,因为ndc坐标系中会将光线的边界限制在0-1之间以减少在景深比较大的背景下产生的负面影响。但是需要注意ndc坐标系不能和spherify pose(球状多视图采样时)同时使用,这点nerf-pytorch中并没有设置两者的互斥关系,而nerf-pl则明确设置了。

输入的进一步预处理

介绍完了将像素坐标转换到世界坐标形成光线始发点与方向向量之后,接下来便是文章中使用的一些trick,关于如何进一步处理光线起点与光线方向形成真正的MLP的输入值。

首先由于体素渲染需要沿着光线进行积分,而积分在计算机中是以离散的乘积和进行计算的,那么这里就涉及到在光线上进行点的采样。NeRF在光线的点采样过程中的进行了一些设计。首先为了避免大量的点采样导致的计算量的激增,NeRF设计了coarse to fine的采样策略。在coarse采样阶段,采用了带有扰动的均匀采样方法。第一步在光线的边界之间进行深度空间的均匀采样:

z_steps = torch.linspace(0, 1, N_samples, device=rays.device)
z_vals = near * (1-z_steps) + far * z_steps

然后在规定了下界与上界的范围内将采样点进行扰动:

z_vals_mid = 0.5 * (z_vals[: ,:-1] + z_vals[: ,1:])
upper = torch.cat([z_vals_mid, z_vals[: ,-1:]], -1)
lower = torch.cat([z_vals[: ,:1], z_vals_mid], -1)
perturb_rand = perturb * torch.rand(z_vals.shape, device=rays.device)
z_vals = lower + (upper - lower) * perturb_rand

最终coarse采样阶段在每条光线上采样了64个样本点。然后基于coarse采样得到的结果进一步指导fine的点采样,即在对最终颜色贡献更大(权重更大)的点附近进行更加密集的点采样。

然后是类似Transformer中的位置编码,将低维的坐标点与方向映射到高维空间以提升网络捕捉高频信息的能力,以nerf-pl中的代码nerf.py为例,embedding函数将3维的位置编码映射为63维,将3维的方向编码映射为27维。

处理好的样本点通过embedding函数进行低维到高维的映射之后得到的结果才会最终作为MLP的输入,MLP最终预测每个样本点的颜色值与不透明度。具体的网络结构图如下,NeRF为了建模view-specific的像素值,即不同的视角下看同一个点的颜色是有区别的,在网络的倒数第二层进一步把表示光线方向的高维编码输入以预测最终的像素值,而为了增强空间位置的condition,在网络的第四层又将表示位置的高维编码再一次输入到了网络中。

Nerf中MLP结构图

NeRF的训练

训练NeRF的输入数据是:从不同位置拍摄同一场景的图片,拍摄这些图片的相机位姿、相机内参,以及场景的范围。若图像数据集缺少相机参数真值,作者便使用经典SfM重建解决方案COLMAP估计了需要的参数,当作真值使用。

在训练使用NeRF渲染新图片的过程中,

  • 先将这些位置输入MLP以产生volume density和RGB颜色值;
  • 取不同的位置,使用体积渲染技术将这些值合成为一张完整的图像;
  • 因为体积渲染函数是可微的,所以可以通过最小化上一步渲染合成的、真实图像之间的差来训练优化NeRF场景表示。

这样的一个NeRF训练完成后,就得到一个以多层感知机的权重表示的模型。一个模型只含有该场景的信息,不具有生成别的场景的图片的能力。

除此之外,NeRF还有两个优化的trick:

  • 位置编码(positional encoding),类似于傅里叶变换,将低维输入映射到高维空间,提升网络捕捉高频信息的能力;
  • 体积渲染的分层采样(hierarchical volume sampling),通过更高效的采样策略减小估算积分式的计算开销,加快训练速度。

从MLP的输出到2D图像—体素渲染

在得到MLP对于某条光线中采样点的颜色与不透明度的预测值之后,便可以通过体素渲染进行该光线对应的像素颜色值的计算。这里的代码可以参考nerf-pl中的inference函数,每一步代码都有比较详细的说明。在训练阶段就将沿着特定光线预测的像素值与对应像素点的像素真值做L2损失函数以更新网络;在测试阶段就计算相机视锥中所有光线得到所有像素的像素值以组成完整的2D图像。

最后附上一些讲解关于相机内外参解释以及坐标系变换的资料:www.cs.cmu.edu/~16385/s17/Slides/11.1_Camera_matrix.pdf

ndc坐标系说明:github.com/bmild/nerf/files/4451808/ndc_derivation.pdf

Nerf-pytorch:https://github.com/yenchenlin/nerf-pytorch​

Nerf-pl:https://github.com/kwea123/nerf_pl​

Emitted Color & Volume Density

事实上 volume rendering的方法百百种,论文中使用的也只是其中一种依靠 emitted color与 volume density的方法。在论文中所谓的 volume density(密度)可以解释为这条射线会停在这个 3D位置的机率,而 emitted color指的是由某个视角看出去这个 3D位置的颜色

依照这样的设定, volume density在设计上应该只与 3D location有关,而 emitted color则是与 location与 view direction有关。因此 NeRF网路 (MLP) 会先吃进 3D location,通过 8层 FC layer,输出 volume density与 volume density以及 256维的 latent feature。而后与 view direction concatenate,在通过 FC layer得到 emitted color。

Vanilla NeRF

NeRF 所要做的 task 是 Novel View Synthesis,一般翻译为新视角合成任务,定义是:在已知视角下对场景进行一系列的捕获 (包括拍摄到的图像,以及每张图像对应的内外参),合成新视角下的图像。传统方法使用 IBR (Image Based Rendering) 方法的较多,也有一些使用深度学习和 IBR 结合的方法,这些我们不过多介绍。

NeRF 想做这样一件事,不需要中间三维重建的过程,仅根据位姿内参和图像,直接合成新视角下的图像。为此 NeRF 引入了辐射场的概念,这在图形学中是非常重要的概念,在此我们给出渲染方程的定义:

那么辐射和颜色是什么关系呢?我们不希望把这个文章讲复杂,简单讲就是,光就是电磁辐射,或者说是振荡的电磁场,光又有波长和频率,二者乘积为光速,光的颜色是由频率决定的,大多数光是不可见的,人眼可见的光谱称为可见光谱,对应的频率就是我们认为的颜色:

Positional Encoding

其实在 2018 年 Bengio 等人就发现 deep networks 更倾向于学习低频的函数,而可以想象的是,实际场景的神经辐射场基本上都是高频的,为此作者提出了 Positional Encoding(注意这里的 Positional Encoding 和 Transformer 中的 Positional Encoding 很像,但是解决问题是不一样的):

这里 p,L 都是标量, L 是超参数,对原始的坐标以及视角方向的每一维度都做相同的操作,然后再输入到网络中,事实上在官方的代码实现中,输入的是 (γ(p),p) ,也就是说原始信息也保留了下来。下图是一个关于 Positional Encoding 以及视角方向相关的 ablation study:

关于 Positional Encoding 以及 SIREN 等工作我们会在之后专门选一个章节进行讲解。读者暂时仅需保留一个印象即可。

Hierarchical volume sampling

使用体渲染积分就会遇到以下几个问题,虽然可以离散的近似计算积分,但是面临的问题就是如何采样。采样点过多开销过大,采样点过少近似误差有太大。直观的一个想法是,最好尽可能的避免在空缺部分以及被遮挡了的部分进行过多的采样,因为这些部分对最好的颜色贡献是很少的,基于这一想法 NeRF 提出分层采样训练的方式,如下图所示:

使用两个网络同时进行训练 (后称 coarse 和 fine 网络), coarse 网络输入的点是通过对光线均匀采样得到的,根据 coarse 网络预测的体密度值,对光线的分布进行估计,然后根据估计出的分布进行第二次重要性采样,然后再把所有的采样点 (Nc+Nf) 一起输入到 fine 网络进行预测。关于 loss 函数我们同样作为一个开放性的内容,不在此多做讲解了。

后NeRF时代

GIRAFFE:composition方向的代表作

2021CVPR的最佳论文奖得主GIRAFFE是NeRF、GRAF工作的延申。

在NeRF之后,有人提出了GRAF(Generative Radiance Fields),关键点在于引入了GAN来实现Neural Radiance Fields(神经辐射场);并使用conditional GAN实现对渲染内容的可控性。

在GRAF之后,GIRAFFE实现了composition。在NeRF、GRAF中,一个Neural Radiance Fields表示一个场景,one model per scene。而在GIRAFFE中,一个Neural Radiance Fields只表示一个物体,one object per scene(背景也算一个物体)。这样做的妙处在于可以随意组合不同场景的物体,可以改变同一场景中不同物体间的相对位置,渲染生成更多训练数据中没有的全新图像。

GIRAFFE实现composition

如图所示,GIRAFFE可以平移、旋转场景中的物体,还可以在场景中增添原本没有的新物体。

另外,GIRAFFE还可以改变物体的形状和外观,因为网络中加入了形状编码、外观编码变量(shape codes zsi , appearance codes zai )。

其他最新相关工作

2021年CVPR还有许多相关的精彩工作发表。例如,提升网络的泛化性:

  • pixelNeRF:将每个像素的特征向量而非像素本身作为输入,允许网络在不同场景的多视图图像上进行训练,学习场景先验,然后测试时直接接收一个或几个视图为输入合成新视图。
  • IBRNet:学习一个适用于多种场景的通用视图插值函数,从而不用为每个新的场景都新学习一个模型才能渲染;且网络结构上用了另一个时髦的东西 Transformer。
  • MVSNeRF:训练一个具有泛化性能的先验网络,在推理的时候只用3张输入图片就重建一个新的场景。

针对动态场景的NeRF:

  • Nerfies:多使用了一个多层感知机来拟合形变的SE(3) field,从而建模帧间场景形变。
  • D-NeRF:多使用了一个多层感知机来拟合场景形变的displacement。
  • Neural Scene Flow Fields:多提出了一个scene flow fields来描述时序的场景形变。

其他创新点:

  • PhySG:用球状高斯函数模拟BRDF(高级着色的上古神器)和环境光照,针对更复杂的光照环境,能处理非朗伯表面的反射。
  • NeX:用MPI(Multi-Plane Image )代替NeRF的RGBσ作为网络的输出。

5 不止是NeRF:Neural Rendering

Neural Radiance Fields的外面是Neural Rendering;换句话说,NeRF(Neural Radiance Fields)是Neural Rendering方向的子集。

在针对这个更宽泛的概念的综述State of the Art on Neural Rendering中,Neural Rendering的主要研究方向被分为5类,NeRF在其中应属于第2类“Novel View Synthesis”(不过这篇综述早于NeRF发表,表中没有NeRF条目)。

Neural Rendering的5类主要研究方向

表中彩色字母缩写的含义:

在这篇综述中,Neural Rendering被定义为:

Deep image or video generation approaches that enable explicit or implicit control of scene properties such as illumination, camera parameters, pose, geometry, appearance, and semantic structure.

Neural Rendering包含所有使用神经网络生成可控(且photo-realistic)的新图片的方法。“可控”指人可以显式或隐式地控制生成新图片的属性,常见的属性包括:光照,相机内参,相机位姿(外参),几何关系,外观,语义分割结构。在这个大框架下,NeRF是一种比较受欢迎的可控相机位姿的Neural Rendering算法。但Neural Rendering这个方向不止于此。

在目前的Neural Rendering方向,最火的子方向就是“Novel View Synthesis”,这与NeRF的强势蹿红密不可分;第二火的子方向是“Semantic Photo Synthesis”,这主要归功于语义分割以及相关的GAN领域的成熟度。“Semantic Photo Synthesis”方向也是成果颇丰,例如2019年CVPR的Semantic Image Synthesis with Spatially-Adaptive Normalization,其效果图如下。

Semantic Image Synthesis

最后我们给出一些 NeRF 的 paper list 以及代码整理:

综述论文

可以说是官方综述,作者列表是目前在Neural Rendering领域最活跃的一群人。两篇分别是2021、2020年的SIGGRAPH、CVPR讲座用到的综述,很全面很有条理,值得每位从业者一读!

SIGGRAPH 2021 Course: Advances in Neural Rendering

CVPR 2020 Tutorial: State of the Art on Neural Rendering

范围限定为可微渲染方法的综述:

Differentiable Rendering: A Survey

linux — bug记录(1)

最近实验室的服务器总会出现各种各样的问题。其中有一个服务器执行apt-get install 或者 update、autoremove等命令报错:

E: Write error – write (28: No space left on device)

插入u盘、移动硬盘时报错:

Error creating mount point No space left on device

甚至执行 rm 命令会显示无法在/tmp 写入日志文件。

解决:

输入df -h:

可以看到, / 目录100%使用率,好家伙,/ 根目录 ——— 所有目录挂在其下面

应该 就是根目录满了,导致包、日志无法写入

然后其实apt -install的软件放在了/var/cache/apt/archives里面

接下来看看那些占的空间多?

1. 在 / 目录下用du --max-depth=1 -h命令查看最大占用的一级目录:

sudo du --max-depth=1 -h

发现占用磁盘最大的目录为/var,然后 tmp也占了很大一部分

然后,Linux有两个公知的临时目录:/tmp与/var/tmp,这两个目录被用户用于存储临时性的文件,亦经常被程读写用户存储临时性数据。

于是我把/tmp里面东西删掉 (注意:必须在root用户下删除,好像普通用户使用sudo命令无法生成log日志)

最终:

但实际上无法 根治。。。

根治:ubuntu根目录内存爆满?教你快速扩容!

首先,之前我的工作环境是windows,由于工作需要,将开发环境迁移至了ubuntu,所以装了双系统,由于分出来给ubuntu的磁盘过小,加上工作文件多了起来,所以就有了这次的扩容的想法。

查询磁盘的空间:

sudo df -h

在网上查阅了很多资料,扩容大概有两种解决方式:

第一种是挂载,这种方式治标不治本,但是设置比较简单,这里不做赘述,感兴趣的自己查;

第二种也是我比较推荐的方式,是根目录扩容,从根本上解决磁盘空间不足的问题。比如我之前的磁盘是分配了95G给根目录,现在想要扩容到256G,那么要怎么做呢?

ubuntu有个图形化分区工具非常好用,我们先安装一下:

在安装之前,记得更新一下apt源,如果已经更新过的请忽略:

sudo apt update

sudo apt install gparted

安装完成后,点击gparted图标或者输入命令启动:

sudo gparted

这时候就可以对ubuntu系统进行分区操作了,但是无法扩展(向左扩展)根目录磁盘的空间,你会看到根目录磁盘被上了锁。

我这里的图片是另外一台电脑,像这种情况是可以直接对根目录进行扩容,因为未分配空间就在根目录的右侧,如果你的未分配空间在ubuntu系统根目录的左侧的话,那么这种情况下,只可以压缩磁盘,不可扩容。

简而言之,就是gparted不允许ubuntu系统磁盘向左扩容。

扩容以后,会发现根目录磁盘由刚才的88G变成了120G,扩容成功。

不放心的可以输入命令查看磁盘空间:

sudo df -h

Prompt Learning(模板学习)

论文:https://arxiv.org/pdf/2107.13586.pdf

“Prompt:NLP 新范式”

“Pre-train, Prompt, and Predict” —- Prompt可以认为就是下游任务来适应预训练模型而做的微调 (所需数据量少、训练快、效果好),原始的微调是让预训练模型来适应下游任务。

文章摘自:未闻 Prompt 名

个人觉得 2021 年 NLP 最火的两个 idea,一个是对比学习(Contrastive Learning),另一个就是 Prompt

浅谈我对 Prompt 的理解

Prompt 说简单也简单,看了几篇论文以及博客后发现其实就是构建一个语言模版。但是细想起来又觉得复杂,因为总感觉里面还有很多细节,因此本文就来从头梳理一下 Prompt(Prompt 很多地方会翻译成「范式」,但是「范式」这个词本身也不好理解,因此读者把他看作是「模板」即可)

今天我还与室友讨论预训练模型(例如 BERT)到底做了什么,我给出的回答是

预训练模型提供了一个非常好的初始化参数,这组参数在预训练任务上的表现非常好(预训练损失非常低),但是由于下游任务千奇百怪,我们需要在这组参数的基础上进行 Fine-tune 以适应我们的下游任务(使得下游任务的损失值非常低)

上面这段话其实隐含了目前做 NLP 任务的大致流程,即 “Pre-train, Fine-tune”,而对我们来说实际上大部分时候都是直接拿别人预训练好的模型做 Fine-tune,并没有 Pre-train 这一步

融入了 Prompt 的模式大致可以归纳成 “Pre-train, Prompt, and Predict”,在该模式中,下游任务被重新调整成类似预训练任务的形式。例如,通常的预训练任务有 MLM(Masked Language Model),在文本情感分类任务中,对于 “I love this movie” 这句输入,可以在后面加上 Prompt:”the movie is ___”,组成如下这样一句话:

I love this movie, the movie is ___

然后让预训练模型用表示情感的答案(例如 “great”、”terrible” 等)做完形填空,最后再将该答案转换为情感分类的标签。这样一来,我们就可以通过构造合适的「模板」,通过小样本数据集训练一个模型来解决各种各样的下游任务

注意,Prompt 设计的这种完形填空和 MLM(Masked Language Modeling) 任务是有区别的,二者虽然都是都是词分类,但是候选集不同,MLM 的候选词是整个词库,不过如果是生成任务,那么 Prompt 和 MLM 的候选集就是一样的,都是整个词库

如何构建 Prompt

对于输入文本 x,存在一个函数 fPrompt(x),将 x 转化成 x′ 的形式,即

该函数通常会进行两步操作:

  1. 使用一个模板,模板通常为一段自然语言句子,并且该句子包含两个空位置:用于填输入 x 的位置 [X]、用于生成答案文本 z 的位置 [Z]
  2. 把输入 x 填到 [X] 的位置

以前文提到的例子为例,在文本情感分类任务中,假设输入是

x = "I love this movie"

使用的模板是

[X]. Overall, it was a [Z] movie

那么得到的 x′ 就应该是

I love this movie. Overall, it was a [Z] movie

在实际情况中,Prompt 来填充答案的位置一般在句中或句末。如果在句中,一般称这种 Prompt 为 Cloze Prompt;如果在句末,一般称这种 Prompt 为 Prefix Prompt。[X] 和 [Z] 的位置、数量以及使用模板句的不同,都有可能对结果造成影响,因此需要灵活调整

上面讲的都是简单的情感分类任务的 Prompt 设计,读者看到这里自然而然的会想到,其他 NLP 任务的 Prompt 如何设计呢?实际上刘鹏飞大神在他的论文中给我们提供了一些参考

Text Generation 中摘要任务里有一个关键字 TL;DR,这其实是 Too Long; Don't Read 的缩写

Prompt 的选择非常重要且困难

有上述 Prompt 的基础后,我们可以得知 Prompt 的设计主要包含两部分:

  1. 模板 T:例如 [X]. Overall, It was [Z]
  2. 标签词映射:即 [Z] 位置预测输出的词汇集合与真实标签 y 构成的映射关系。例如,标签 positive 对应单词 great,标签 negative 对应单词 terrible

基于 Prompt 的微调方法中,不同的模板和标签词对最终结果影响很大,下图是陈丹琦团队论文中的实验结果

从上图我们可以看出两点:

  1. 使用相同的「模板」,不同的「标签词」会产生不一样的效果。例如 great/terribel 和 cat/dog 这两组标签词的效果不一样,而且即便是相同标签词,互换顺序也会导致最终效果有所变化,例如 cat/dog 和 dot/cat
  2. 使用相同「标签词」,对「模板」进行小改动(例如增删标点)也会呈现不同的结果

Prompt 的设计

Prompt 大概可以从下面三个角度进行设计:

  • Prompt 的形状
  • 人工设计模板
  • 自动学习模板

Prompt 的形状

Prompt 的形状主要指的是 [X] 和 [Z] 的位置和数量。上文提到的 Cloze Prompt 与 Maksed Language Model 的训练方式非常类似,因此对于 MLM 任务来说,Cloze Prompt 更合适;对于生成任务或者使用自回归 LM 解决的任务,Prefix Prompt 更合适。

人工设计模板

Prompt 的模板最开始是人工设计的,人工设计一般基于人类的自然语言知识,力求得到语义流畅且高效的「模板」。例如,Petroni 等人在著名的 LAMA 数据集中为知识探针任务人工设计了 Cloze Templates;Brown 等人为问答、翻译和探针等任务设计了 Prefix Templates。人工设计模板的优点是直观,但缺点是需要很多实验、经验以及语言专业知识。下图是 GPT Understands, Too 论文中的一个实验结果

可以看到不同的 Prompt 只有细微的区别,有的甚至只是增加减少一个词,但是最后的结果会差几十个点

自动学习模板

为了解决人工设计模板的缺点,许多研究员开始探究如何自动学习到合适的模板。自动学习的模板又可以分为离散(Discrete Prompts)和连续(Continuous Prompts)两大类。离散方法主要包括:Prompt Mining,Prompt Paraphrasing,Gradient-based SearchPrompt Generation 和 Prompt Scoring;连续的则主要包括 Prefix TuningTuning Initialized with Discrete promptsHard-Soft Prompt Hybrid TuningP-Tuning v2

离散 Prompts

简单说一下上述几种方法,首先是离散的 Prompt Mining,这篇文章发表在 TACL 2020,讲的是如何拿预训练语言模型当作「知识库」使用,并且引入了依存树和 Paraphrase(转述)等方法来挖掘更好的「模板」,下图是实验结果

可以看到,被挖掘出来的若干「连接谓词」相比于人工设计的「模板」结果提升还是很明显的

有很多种方法可以实现 Prompt Paraphrsing,例如「回译」,我们通过 DeepL 翻译看个例子:

这样我们就得到了 x shares a border with y 的一个 Prompt Paraphrasing:x and y share a boundary

论文 BARTScore 干脆给我们提供了一张表,里面有各种词组的同义替换,这个我再熟悉不过了,因为以前英语考试我也背过类似的东西

Gradient-based Search(基于梯度的搜索)是由论文 AUTOPROMPT 提出的,这篇文章发表在 EMNLP 2020,它的主要思想用下面这张图就可以表示

上图中,a real joy 是原始的输入句子 xinp,红色的 Trigger tokens 是由 xinp「激发」的相关词汇集合 xtrig,根据 Template λ 的配置,将 xtrig 和 xinp 组合起来构造最终的输入 xprompt,送入 Masked LM 预测情感标签。下面的表格增加了很多 NLP 其他任务的例子

关于如何生成 xtrig 集合,实际上主要使用的是 HotFlip 和对抗训练的思想,感兴趣的同学可以看原论文以及 HotFlip: White-box adversarial examples for text classificationUniversal Adversarial Triggers for Attacking and Analyzing NLP 这两篇论文

Prompt Generation 是陈丹琦团队的一项工作,主要是把 Seq2Seq 预训练模型 T5 应用到模板搜索的过程。T5 基于多种无监督目标进行预训练,其中最有效的一个无监督目标就是:利用 <X> 或 < Y > 替换一个或多个连续 span,然后生成对应输出。例如:

Thank you <X> me to your party <Y> week

T5 会在 <X> 生成 for inviting,在 <Y> 生成 last。很显然,T5 这种方式很适合生成模板,而且不需要指定模板的 token 数。具体来说,有三种可能的生成方式⟨S1⟩→⟨X⟩ M(y) ⟨Y⟩ ⟨S1⟩⟨S1⟩→⟨S1⟩ ⟨X⟩ M(y) ⟨Y⟩⟨S1⟩,⟨S2⟩→⟨S1⟩ ⟨X⟩ M(y) ⟨Y⟩ ⟨S2⟩

具体的模板生成过程如下图所示:

首先在标签词前后添加填充位 <X> 和 < Y>(上面提到的三种生成方式),然后将其送入 T5 模型中,T5 会自动在填充位生成序列,最后将标签词(great 或 terribel)转换为 [MASK] 标签,形成多个模板。具体过程中采用 Beam Search 的方法生成多个候选模板,然后对每一个候选模板利用 dev 集进行微调,选择其中一个最佳模板

我还想说一下这篇论文中另外一个有意思的点,最后送入模型进行预测的句子还拼接上了每种类别的「示例」(Demonstration),如下图所示

这种 Prompt 的设计有点像是在做语义相似度任务,X 为原始 Input 句子,已知 Y 为正例,Z 为负例,构造了如下形式的输入:

X是[MASK]例?Y为正例;Z为负例

这有点像是编程语言中的三目运算符,或者说相当于让模型比较 X 与 Y、Z 的语义相似度。这里我们自然而然会想问:Y、Z 是如何挑选出来的?实际上是依据下面两条规则:

  1. 对于每个原始输入句子,从每个类别中随机采样一个样本「示例」拼接到 Prompt 中
  2. 对于每个原始输入句子,在每个类别中,通过与 Sentence-BERT 进行相似度计算,从相似度最高的前 50% 样本中随机选择一个样本「示例」

连续 Prompts

构造 Prompt 的初衷是能够找到一个合适的方法,让 Pre-trained Language Model(PLM)更好地输出我们想要的结果,但其实并不一定要将 Prompt 的形式设计成人类可以理解的自然语言,只要机器理解就行了。因此,还有一些方法探索连续型 Prompts—— 直接作用到模型的 Embedding 空间。连续型 Prompts 去掉了两个约束条件:

  1. 模版中词语的 Embedding 可以是整个自然语言的 Embedding,不再只是有限的一些 Embedding
  2. 模版的参数不再直接取 PLM 的参数,而是有自己独立的参数,可以通过下游任务的训练数据进行调整

Prefix Tuning 最开始由 Li 等人提出,这是一种在输入句子前添加一组连续型向量的方法,该方法保持 PLM 的参数不动,仅训练前缀(Prefix)向量。Prefix Tuning 的提出主要是为了做生成任务,因此它根据不同的模型结构定义了不同的 Prompt 拼接方式,在 GPT 类的 Auto-Regressive(自回归)模型上采用的是 [Prefix;x;y] 的方式,在 T5 类的 Encoder-Decoder 模型上采用的是 [Prefix;x;Prefix′;y] 的方式

输入部分 Prefix, \(x, y\) 的 Position id 分别记作

\(\mathrm{P}{\mathrm{idx}}\) , \(\mathrm{X}{\mathrm{idx}}\) , \(\mathrm{Y}{\mathrm{idx}}\)。Prefix Tuning 初始化一 个可训练的矩阵,记作 \(P\theta \in \mathbb{R}^{\left|P_{\mathrm{idx}}\right| \times \operatorname{dim}\left(h_i\right)}\) ,其中
\(h_i= \begin{cases}P_\theta[i,:], & \text { if } i \in \mathrm{P}{\mathrm{idx}} \ \mathbf{L M}\phi\left(z_i, h_{<i}\right), & \text { otherwise }\end{cases}\)
上述公式的含义是,索引 $i$ 如果属于前缀的部分,则从 \(P_\theta\) 中抽取向量; \(i\) 如果不是前缀部 分,则由参数固定的预训练模型生成对应的向量。训练目标为:
\(\max \phi \log p\phi(y \mid x)=\sum_{i \in \mathrm{Y}{\mathrm{idx}}} \log p\phi\left(z_i \mid h_{<i}\right)\)

 \(P_\theta\) 本质上是一个矩阵,而生成一个矩阵的方法又很多,可以用 nn.Embedding(),或者 nn.Linear()

同样是在连续空间上搜索 Prompt,OptiPrompt 构建的「模板」并不局限于前缀,也可以在句子的中间

Hard-Soft Prompt Hybrid Tuning 方法可以说是人工设计和自动学习的结合,它通常不单纯使用可学习的 Prompt 模板,而是在人工设计的模板中插入一些可学习的 Embedding。实际上有了上面的基础我们都知道,连续的 Prompt 要比离散的 Prompt 好一点,但是在此基础上还有什么改进的余地吗?Liu 等人提出的 P-Tuning 解决了 Prompt token 之间的关联性问题

之前连续的 Prompt 生成方式无非都是训练一个矩阵,然后通过索引出矩阵的某几行向量拼起来。坦白地说,我们希望这些 prompt token Embedding 之间有一个比较好的关联性,而不是独立地学习,为了解决这个问题,P-Tuning 引入了一个 Prompt Encoder(如下图 b 所示)

上图 a 是传统的离散型 Prompt,我们把生成离散 Prompt token 的东西叫做 Prompt Generator;上图 b 首先传入一些 Virtual(Pseudo)token,例如 BERT 词表中的 [unused1],[unused2],… 当然,这里的 token 数目是一个超参数,插入的位置也可以调整。将这些 Pseudo token 通过一个 Prompt Encoder 得到连续的向量 h0,…,hm,其中

大家可能想问,如何优化 P-tuning?实际上根据标注数据量的多少,分两种情况讨论

  1. 标注数据比较少。这种情况,我们固定 PLM 的参数,只优化 [P0]∼[Pm] 这几个 token 的 Embedding。换句话说,我们只是要更新 Prompt Encoder 的参数
  2. 标注数据很充足。这种情况直接放开所有参数微调

就在 P-Tuning 方法提出不久后,Liu 等人又提出了 P-Tuning v2,主要解决 P-Tuning 的两个问题:

  1. 当预训练模型的参数量低于 100 亿(10B)时,Prompt tuning 会比传统的 Fine-tuning 差
  2. 诸如序列标注这样对推理和理解要求高的任务,prompt tuning 效果会变差

Liu 等人认为先前的 P-Tuning 只用了一层 BiLSTM 来编码 Pseudo token,这是其推理能力不足的原因之一,因此 v2 版本提出 Deep Prompt Tuning,用 Prefix Tuning 中的深层模型替换 BiLSTM,如下图所示

P-Tuning v2 相比于 P-Tuning,区别在于:

  • 取消 Reparameterization:以前的方法利用重参数化功能来提高训练速度和鲁棒性(例如,用于 Prefix-Tuning 的 MLP 和用于 P-Tuning 的 LSTM)。在 P-Tuning v2 中,作者发现重参数化的改进很小,尤其是对于较小的模型,同时还会影响模型的表现
  • Multi-task Learning:Deep Prompt Tuning 的优化难题可以通过增加额外的任务数据或者无标注数据来缓解,同时可微调的 Prefix Continuous Prompt 也可以用来做跨任务的知识共享。例如在 NER 中,可以同时训练多个数据集,不同数据集使用不同的顶层 Classifier,但是 Prefix Continuous Prompt 是共享的
  • 取消 verbalizer:v2 取消了标签映射,完全变为生成模型,可以在 [CLS] 部分输出句子级别的标签(Sentence-level label),也可以在每个 token 位置输出 token 级别的标签(Token-level label),直接输出真实标签

关于 P-Tuning 还有一些碎碎念,主要是从各个博客上看到的,汇总在这里。首先是 v1 版本的 LSTM,实际上引入 LSTM 目的是为了帮助「模板」生成的 token(某种程度上)更贴近自然语言,或者说 token 之间的语义更流畅,但更自然的方法应该是在训练下游任务的时候,不仅预测下游任务的目标 token(例如 “great”、”terrible”),还应该同时做其他 token 的预测

比如,如果是 MLM 模型,那么也随机 MASK 掉其它的一些 token 来预测,如果是 LM 模型,则预测完整的序列,而不单单是目标词。这样做的理由是:因为我们的 MLM/LM 都是经过自然语言预训练的,所以我们认为它能够很好的完成序列的重构,即便一开始不能,随着迭代轮数的增加,模型也能很好完成这项任务。所以这本质上是让模型进行「负重训练」

* 为什么要引入 Prompt?

在标准的 Fine-tune 过程中(如上图 b 所示),新引入的参数量可能会很大(独立于原始预训练模型外的参数),例如基于 RoBERTa-large 的二分类任务会新引入 2048 个参数(nn.Linear(1024, 2)),如果你仅有例如 64 个标注数据这样的小样本数据集,微调会非常困难

为解决这一问题,Prompt 应运而生(如上图 a 所示),直接将下游任务转换为输出空间有限的 MLM 任务。值得注意的是:上述方法在预训练参数的基础上进行微调,并且没有引入任何新参数,同时还减少了微调和预训练任务之间的差距。总的来说,这可以更有效地用于小样本场景

Prompt 的挑战与展望

尽管 Prompt 研究搞得如火如荼,但目前仍存在许多问题值得研究者们去探究

  1. Prompt 的设计问题。目前使用 Prompt 的工作大多集中于分类任务和生成任务,其它任务则较少。另外,「模板」和「答案」的联系也亟待解决。模型的表现同时依赖于使用的「模板」和「答案」的映射,如何同时搜索或者学习出两者联合的最好效果仍然很具挑战性
  2. Prompt 的理论分析和可解释性。尽管 Prompt 方法在很多情况下都取得了成功,但是目前 Prompt-based Learning 理论分析还很少,人们很难了解 Prompt 为什么能达到好的效果,又为什么在自然语言中意义相近的 Prompt 有时效果却相差很大
  3. Prompt 在 PLM debias 方面的应用。由于 PLM 在预训练过程中见过了大量的人类世界的自然语言,所以很自然地会受到一些影响。举一个简单的例子,比如说训练语料中有非常多 “The capital of China is Beijing”,导致模型每次看到 “capital” 的时候都会预测出 “Beijing”,而不是去分析到底是哪个国家的首都。在应用的过程中,Prompt 还暴露了 PLM 学习到的很多其它 bias,比如种族歧视、性别对立等。这也许会是一个值得研究的方向

One More Thing

最后我还想提一个实际 Code 过程中存在的问题。我们知道 MLM 任务会输出句子中 [MASK] 位置最有可能的词,而 Prompt 也类似的,例如下面的例子

这是一条__新闻。中国足球出线的可能性只有0.001%,留给中国队的时间不多了

这是一个新闻分类问题,真实标签有 “体育”、”财经”、”娱乐” 等,上面的样本很明显是一条体育新闻,因此我们希望模型对 [MASK] 部分输出 “体育”,但事实真的如此吗?实际情况模型的输出可能是 “足球”,但你认为模型预测的 “足球” 有问题吗?好像也没啥毛病,因此这就引申出了 Prompt 的一个问题,是否应该限制模型的输出空间?

还是上面新闻分类的例子,我们是否应该限制模型输出的空间,让他固定只能预测 “体育”、”财经”、”娱乐” 这几个标签?或者我们干脆把这几个标签换成索引,那就是让模型从 0,1,2 这三个数字选一个。Wait Wait Wait,如果这么做的话,和 Fine-Tune 有什么区别,Fine-Tune 也是把标签转换成索引,让模型看了句子之后,从这几个索引中选一个作为预测值

这么说的话,那我们就不应该限制模型的输出空间,可是这样的话 [MASK] 位置的输出就限制的太死了,必须一定是 “good”、”财经” 才算对,如果输出 “nice”、”财政” 就算错。实际上输出近义词或者相似词,在零样本的情况下会经常出现,但是如果你用一些有标签的样本去训练,模型自己就会慢慢固定输出空间。例如 “财经”,它不会预测成 “财政”,只会预测成其它类型的新闻,例如 “体育”

References

Point Transformer –ICCV2021

论文:Point Transformer
作者单位:牛津大学, 港中文(贾佳亚等), Intel Labs

transformer应用到了点云任务处理中。为点云设计了自注意力层,并使用它们来构造诸如语义场景分割,object part分割和对象分类等任务的自注意力网络。

attention层设计:

这里的y是输出的feature,ϕ、ψ、α都是逐点特征变换的一种方式(比如mlp),δ是一个位置编码函数,ρ是正则化函数,简单来说,xi是点i的feature向量,先通过特征变换将点i和点j(Xj是Xi的邻域上的点,而非全局的,目的是减少计算量)的特征得到,这里的β是关系函数,通过这个函数得到两个点特征之间的关系,也就是建立每个点特征之间的关系,然后加上位置编码函数δ,γ是映射函数,也就是映射到某一维度而用。在这基础上就可以设计这里的重点,Point transformer层了

输入是(x,p)也就是每个点的位置信息,首先通过两个线性函数编码不同主次点的特征向量(也就是得到前面的key向量),再用一个MLP得到位置函数,也就是前面的查询向量),两者结合得到relation关系,然后再用一个线性函数得到它的值向量,将relation和值向量结合,也就是前面说的对于每个点既关注它的和其他点之间的语义关系,也关注它和其他点之间的位置关系,最后输出y作为点云处理结果。

位置函数也就是计算查询向量的那个函数:

在这里插入图片描述

p就是各自点的三维坐标值,θ是一个MLP层,而前面的线性函数也就是ax+b的形式(就是linear层)

定义完了transformer层,就可以定义一个block来作为基本的block(下图a):

输入是点集合x(拥有各自的三维点坐标等点特征),输出就是将每个点x的更新后的特征输出:

down的功能是根据需要减少点集的基数,简单来说就是减少点,而up就是根据两个不同数量的点来得到结合后的结果,常常使用在U型网络设计中(也就是当前层结果是结合了当前层的输入和之前某一层不同维度的输出而得到)

transition down:

step1:farthest point sample,把p1个点采样到 p个点,通过MLP改变特征向量(y,p),通过KNN算法,把p个点分成p2类,每个类内部做最大池化得到最终输出(y,p2)。

up模块:input1 如何才能扩充点数:通过线性插值算法

网络结构:

实验结果:

用于大规模语义场景分割的具有挑战性的S3DIS数据集上,Point Transformer在Area 5上的mIoU达到70.4%,比最强的现有模型高3.3个绝对百分点,并首次超过70%mIoU阈值 。

在ModelNet40和ShapeNetPart数据集上的性能表现:

目前paper with code 网站的排名:

3D Point Cloud Classification on ModelNet40
3D Point Cloud Classification on ModelNet40
3D Part Segmentation on ShapeNet-Part
3D Semantic Segmentation on SemanticKITTI

Induction Networks for Few-Shot TextClassification

论文:https://arxiv.org/abs/1902.10482?context=cs.CL

                            IJCNLP 2019 paper

代码: https://github.com/wuzhiye7/Induction-Network-on-FewRel

在深度学习领域,监督式深度学习对大型标记数据集的贪婪需求是出了名的,然而又由于标注数据集的昂贵成本,这就限制了深度模型对新类的可泛化性。本文提出了一个用于在文本分类领域的小样本学习训练工作。

什么是小样本学习(以图片为例)

few-shot learing 的训练目标与传统的监督学习目标不同,传统的分类是学会识别训练集合里面的图片,并且泛化到测试集合,神经网络识别出该图片属于哪个类。而few shot learing是让机器自己学会学习,学习的目的不是让机器学会那个是大象那个是老虎,而是让模型学会学习不同类别的不同之处,给定两张图片,模型知道两个图片是否是同一类别。哪怕模型训练集中没有出现过该类别。

当前的小样本学习技术经常会将输入的query和support的样本集合进行sample-wise级别的对比。但是,如果跟同一个类别下的不同表达的样本去对比的时候产生的效果就不太好,除此之外,目前的技术会使用简单地求和或平均表示来计算类别,这会丢失一些信息。因此本文利用胶囊网络,通过学习sample所属于的类别的表示得到class-wise的向量,然后跟输入的query进行对比。

模型如下:

模型分为三个模块:Encoder Module, Induction Module and Relation Module.

Encoder Module

编码器使用双向LSTM,然后对每个隐藏层进行self-attention。

其中H维度为[C*K, T, 2u] ,经过矩阵变化,a的维度变为[C*K, T] ,最后e的维度为[C*K, 2u]

Induction Module

本模块的主要目的是设计一个从样本向量到类向量的非线性映射。

这是使用动态路由算法,输出的capsule数为1.

首先将样本表征进行一次变换,这里为了能够支持不同大小的C,对原Capsule Network中不同类别使用不同的W做了修改,也就是使用一个所有类别共享的W。

Relation Module

在得到类表示后,就可以计算ci与query set的相关性了。

Objective Function

使用均方误差来计算损失,匹配对的相似度为1,不匹配的相似度为0。

Docker

资源: Docker 从入门到实践

Docker: https://www.docker.com

1、什么是Docker?

容器技术的起源

假设你们公司正在秘密研发下一个“今日头条”APP,我们姑且称为明日头条,程序员自己从头到尾搭建了一套环境开始写代码,写完代码后程序员要把代码交给测试同学测试,这时测试同学开始从头到尾搭建这套环境,测试过程中出现问题程序员也不用担心,大可以一脸无辜的撒娇,“明明在人家的环境上可以运行的”。

测试同学测完后终于可以上线了,这时运维同学又要重新从头到尾搭建这套环境,费了九牛二虎之力搭建好环境开始上线,糟糕,上线系统就崩溃了,这时心理素质好的程序员又可以施展演技了,“明明在人家的环境上可以运行的”。

从整个过程可以看到,不但我们重复搭建了三套环境还要迫使程序员转行演员浪费表演才华,典型的浪费时间和效率,聪明的程序员是永远不会满足现状的,因此又到了程序员改变世界的时候了,容器技术应运而生。

有的同学可能会说:“等等,先别改变世界,我们有虚拟机啊,VMware好用的飞起,先搭好一套虚拟机环境然后给测试和运维clone出来不就可以了吗?”

在没有容器技术之前,这确实是一个好办法,只不过这个办法还没有那么好。

先科普一下,现在云计算其底层的基石就是虚拟机技术,云计算厂商买回来一堆硬件搭建好数据中心后使用虚拟机技术就可以将硬件资源进行切分了,比如可以切分出100台虚拟机,这样就可以卖给很多用户了。

你可能会想这个办法为什么不好呢?

容器技术 vs 虚拟机

我们知道和一个单纯的应用程序相比,操作系统是一个很重而且很笨的程序,简称笨重,有多笨重呢?

我们知道操作系统运行起来是需要占用很多资源的,大家对此肯定深有体会,刚装好的系统还什么都没有部署,单纯的操作系统其磁盘占用至少几十G起步,内存要几个G起步。

假设我有一台机器,16G内存,需要部署三个应用,那么使用虚拟机技术可以这样划分:

在这台机器上开启三个虚拟机,每个虚拟机上部署一个应用,其中VM1占用2G内存,VM2占用1G内存,VM3占用了4G内存。

我们可以看到虚拟本身就占据了总共7G内存,因此我们没有办法划分出更过虚拟机从而部署更多的应用程序,可是我们部署的是应用程序,要用的也是应用程序而不是操作系统

如果有一种技术可以让我们避免把内存浪费在“无用”的操作系统上岂不是太香?这是问题一,主要原因在于操作系统太重了。

还有另一个问题,那就是启动时间问题,我们知道操作系统重启是非常慢的,因为操作系统要从头到尾把该检测的都检测了该加载的都加载上,这个过程非常缓慢,动辄数分钟,因此操作系统还是太笨了。

那么有没有一种技术可以让我们获得虚拟机的好处又能克服这些缺点从而一举实现鱼和熊掌的兼得呢?

答案是肯定的,这就是容器技术。

什么是容器

容器一词的英文是container,其实container还有集装箱的意思,集装箱绝对是商业史上了不起的一项发明,大大降低了海洋贸易运输成本。让我们来看看集装箱的好处:

  • 集装箱之间相互隔离
  • 长期反复使用
  • 快速装载和卸载
  • 规格标准,在港口和船上都可以摆放

回到软件中的容器,其实容器和集装箱在概念上是很相似的。

现代软件开发的一大目的就是隔离,应用程序在运行时相互独立互不干扰,这种隔离实现起来是很不容易的,其中一种解决方案就是上面提到的虚拟机技术,通过将应用程序部署在不同的虚拟机中从而实现隔离。

但是虚拟机技术有上述提到的各种缺点,那么容器技术又怎么样呢?

与虚拟机通过操作系统实现隔离不同,容器技术只隔离应用程序的运行时环境但容器之间可以共享同一个操作系统,这里的运行时环境指的是程序运行依赖的各种库以及配置。

从图中我们可以看到容器更加的轻量级且占用的资源更少,与操作系统动辄几G的内存占用相比,容器技术只需数M空间,因此我们可以在同样规格的硬件上大量部署容器,这是虚拟机所不能比拟的,而且不同于操作系统数分钟的启动时间容器几乎瞬时启动,容器技术为打包服务栈提供了一种更加高效的方式,So cool。

那么我们该怎么使用容器呢?这就要讲到docker了。

注意,容器是一种通用技术,docker只是其中的一种实现。

什么是docker

docker是一个用Go语言实现的开源项目,可以让我们方便的创建和使用容器,docker将程序以及程序所有的依赖都打包到docker container,这样你的程序可以在任何环境都会有一致的表现,这里程序运行的依赖也就是容器就好比集装箱,容器所处的操作系统环境就好比货船或港口,程序的表现只和集装箱有关系(容器),和集装箱放在哪个货船或者哪个港口(操作系统)没有关系

Docker 的主要用途,目前有三大类。

(1)提供一次性的环境。比如,本地测试他人的软件、持续集成的时候提供单元测试和构建的环境。

(2)提供弹性的云服务。因为 Docker 容器可以随开随关,很适合动态扩容和缩容。

(3)组建微服务架构。通过多个容器,一台机器可以跑多个服务,因此在本机就可以模拟出微服务架构

因此我们可以看到docker可以屏蔽环境差异,也就是说,只要你的程序打包到了docker中,那么无论运行在什么环境下程序的行为都是一致的,程序员再也无法施展表演才华了,不会再有“在我的环境上可以运行”,真正实现“build once, run everywhere”。

此外docker的另一个好处就是快速部署,这是当前互联网公司最常见的一个应用场景,一个原因在于容器启动速度非常快,另一个原因在于只要确保一个容器中的程序正确运行,那么你就能确信无论在生产环境部署多少都能正确运行。

如何使用docker

docker中有这样几个概念:

  • dockerfile
  • image
  • container

实际上你可以简单的把image理解为可执行程序,container就是运行起来的进程。

那么写程序需要源代码,那么“写”image就需要dockerfile,dockerfile就是image的源代码,docker就是”编译器”。

因此我们只需要在dockerfile中指定需要哪些程序、依赖什么样的配置,之后把dockerfile交给“编译器”docker进行“编译”,也就是docker build命令,生成的可执行程序就是image,之后就可以运行这个image了,这就是docker run命令,image运行起来后就是docker container。

接下来我们用几个命令来讲解一下docker的工作流程:

1,docker build

当我们写完dockerfile交给docker“编译”时使用这个命令,那么client在接收到请求后转发给docker daemon,接着docker daemon根据dockerfile创建出“可执行程序”image。

2,docker run

有了“可执行程序”image后就可以运行程序了,接下来使用命令docker run,docker daemon接收到该命令后找到具体的image,然后加载到内存开始执行,image执行起来就是所谓的container。

3,docker pull

其实docker build和docker run是两个最核心的命令,会用这两个命令基本上docker就可以用起来了,剩下的就是一些补充。

那么docker pull是什么意思呢?

我们之前说过,docker中image的概念就类似于“可执行程序”,我们可以从哪里下载到别人写好的应用程序呢?很简单,那就是APP Store,即应用商店。与之类似,既然image也是一种“可执行程序”,那么有没有”Docker Image Store”呢?答案是肯定的,这就是Docker Hub,docker官方的“应用商店”,你可以在这里下载到别人编写好的image,这样你就不用自己编写dockerfile了。

docker registry 可以用来存放各种image,公共的可以供任何人下载image的仓库就是docker Hub。那么该怎么从Docker Hub中下载image呢,就是这里的docker pull命令了。

因此,这个命令的实现也很简单,那就是用户通过docker client发送命令,docker daemon接收到命令后向docker registry发送image下载请求,下载后存放在本地,这样我们就可以使用image了。

最后,让我们来看一下docker的底层实现。

docker的底层实现

docker基于Linux内核提供这样几项功能实现的:

  • NameSpace
    我们知道Linux中的PID、IPC、网络等资源是全局的,而NameSpace机制是一种资源隔离方案,在该机制下这些资源就不再是全局的了,而是属于某个特定的NameSpace,各个NameSpace下的资源互不干扰,这就使得每个NameSpace看上去就像一个独立的操作系统一样,但是只有NameSpace是不够。
  • Control groups
    虽然有了NameSpace技术可以实现资源隔离,但进程还是可以不受控的访问系统资源,比如CPU、内存、磁盘、网络等,为了控制容器中进程对资源的访问,Docker采用control groups技术(也就是cgroup),有了cgroup就可以控制容器中进程对系统资源的消耗了,比如你可以限制某个容器使用内存的上限、可以在哪些CPU上运行等等。

有了这两项技术,容器看起来就真的像是独立的操作系统了。

2、Docker使用教程

2.1 Docker 的安装

Docker 是一个开源的商业产品,有两个版本:社区版(Community Edition,缩写为 CE)和企业版(Enterprise Edition,缩写为 EE)。企业版包含了一些收费服务,个人开发者一般用不到。下面的介绍都针对社区版。

Docker CE 的安装请参考官方文档。

安装完成后,运行下面的命令,验证是否安装成功。


$ docker version
# 或者
$ docker info

Docker 需要用户具有 sudo 权限,为了避免每次命令都输入sudo,可以把用户加入 Docker 用户组(官方文档)。


$ sudo usermod -aG docker $USER

Docker 是服务器—-客户端架构。命令行运行docker命令的时候,需要本机有 Docker 服务。如果这项服务没有启动,可以用下面的命令启动(官方文档)。


# service 命令的用法
$ sudo service docker start

# systemctl 命令的用法
$ sudo systemctl start docker

2.2 image 文件

Docker 把应用程序及其依赖,打包在 image 文件里面。只有通过这个文件,才能生成 Docker 容器。image 文件可以看作是容器的模板。Docker 根据 image 文件生成容器的实例。同一个 image 文件,可以生成多个同时运行的容器实例。

image 是二进制文件。实际开发中,一个 image 文件往往通过继承另一个 image 文件,加上一些个性化设置而生成。举例来说,你可以在 Ubuntu 的 image 基础上,往里面加入 Apache 服务器,形成你的 image。


# 列出本机的所有 image 文件。
$ docker image ls

# 删除 image 文件
$ docker image rm [imageName]

image 文件是通用的,一台机器的 image 文件拷贝到另一台机器,照样可以使用。一般来说,为了节省时间,我们应该尽量使用别人制作好的 image 文件,而不是自己制作。即使要定制,也应该基于别人的 image 文件进行加工,而不是从零开始制作。

为了方便共享,image 文件制作完成后,可以上传到网上的仓库。Docker 的官方仓库 Docker Hub 是最重要、最常用的 image 仓库。此外,出售自己制作的 image 文件也是可以的。

2.3 实例:hello world

下面,我们通过最简单的 image 文件”hello world”,感受一下 Docker。

需要说明的是,国内连接 Docker 的官方仓库很慢,还会断线,需要将默认仓库改成国内的镜像网站。

首先,运行下面的命令,将 image 文件从仓库抓取到本地。


$ docker image pull library/hello-world

上面代码中,docker image pull是抓取 image 文件的命令。library/hello-world是 image 文件在仓库里面的位置,其中library是 image 文件所在的组,hello-world是 image 文件的名字。

由于 Docker 官方提供的 image 文件,都放在library组里面,所以它的是默认组,可以省略。因此,上面的命令可以写成下面这样。


$ docker image pull hello-world

抓取成功以后,就可以在本机看到这个 image 文件了。


$ docker image ls

现在,运行这个 image 文件。


$ docker container run hello-world

docker container run命令会从 image 文件,生成一个正在运行的容器实例。

注意,docker container run命令具有自动抓取 image 文件的功能。如果发现本地没有指定的 image 文件,就会从仓库自动抓取。因此,前面的docker image pull命令并不是必需的步骤。

如果运行成功,你会在屏幕上读到下面的输出。


$ docker container run hello-world

Hello from Docker!
This message shows that your installation appears to be working correctly.

... ...

输出这段提示以后,hello world就会停止运行,容器自动终止。

有些容器不会自动终止,因为提供的是服务。比如,安装运行 Ubuntu 的 image,就可以在命令行体验 Ubuntu 系统。


$ docker container run -it ubuntu bash

对于那些不会自动终止的容器,必须使用docker container kill 命令手动终止。


$ docker container kill [containID]

2.4 Dockerfile 文件

学会使用 image 文件以后,接下来的问题就是,如何可以生成 image 文件?如果你要推广自己的软件,势必要自己制作 image 文件。

这就需要用到 Dockerfile 文件。它是一个文本文件,用来配置 image。Docker 根据 该文件生成二进制的 image 文件。

下面通过一个实例,演示如何编写 Dockerfile 文件。

2.5 制作自己的 Docker 容器

下面我以 koa-demos 项目为例,介绍怎么写 Dockerfile 文件,实现让用户在 Docker 容器里面运行 Koa 框架。

作为准备工作,请先下载源码


$ git clone https://github.com/ruanyf/koa-demos.git
$ cd koa-demos

1 编写 Dockerfile 文件

首先,在项目的根目录下,新建一个文本文件.dockerignore,写入下面的内容


.git
node_modules
npm-debug.log

上面代码表示,这三个路径要排除,不要打包进入 image 文件。如果你没有路径要排除,这个文件可以不新建。

然后,在项目的根目录下,新建一个文本文件 Dockerfile,写入下面的内容


FROM node:8.4
COPY . /app
WORKDIR /app
RUN npm install --registry=https://registry.npm.taobao.org
EXPOSE 3000

上面代码一共五行,含义如下。

  • FROM node:8.4:该 image 文件继承官方的 node image,冒号表示标签,这里标签是8.4,即8.4版本的 node。
  • COPY . /app:将当前目录下的所有文件(除了.dockerignore排除的路径),都拷贝进入 image 文件的/app目录。
  • WORKDIR /app:指定接下来的工作路径为/app
  • RUN npm install:在/app目录下,运行npm install命令安装依赖。注意,安装后所有的依赖,都将打包进入 image 文件。
  • EXPOSE 3000:将容器 3000 端口暴露出来, 允许外部连接这个端口。

2 创建 image 文件

有了 Dockerfile 文件以后,就可以使用docker image build命令创建 image 文件了。


$ docker image build -t koa-demo .
# 或者
$ docker image build -t koa-demo:0.0.1 .

上面代码中,-t参数用来指定 image 文件的名字,后面还可以用冒号指定标签。如果不指定,默认的标签就是latest。最后的那个点表示 Dockerfile 文件所在的路径,上例是当前路径,所以是一个点。

如果运行成功,就可以看到新生成的 image 文件koa-demo了。


$ docker image ls

3 生成容器

docker container run命令会从 image 文件生成容器。


$ docker container run -p 8000:3000 -it koa-demo /bin/bash
# 或者
$ docker container run -p 8000:3000 -it koa-demo:0.0.1 /bin/bash

上面命令的各个参数含义如下:

  • -p参数:容器的 3000 端口映射到本机的 8000 端口。
  • -it参数:容器的 Shell 映射到当前的 Shell,然后你在本机窗口输入的命令,就会传入容器。
  • koa-demo:0.0.1:image 文件的名字(如果有标签,还需要提供标签,默认是 latest 标签)。
  • /bin/bash:容器启动以后,内部第一个执行的命令。这里是启动 Bash,保证用户可以使用 Shell。

如果一切正常,运行上面的命令以后,就会返回一个命令行提示符。


root@66d80f4aaf1e:/app#

这表示你已经在容器里面了,返回的提示符就是容器内部的 Shell 提示符。执行下面的命令。


root@66d80f4aaf1e:/app# node demos/01.js

这时,Koa 框架已经运行起来了。打开本机的浏览器,访问 http://127.0.0.1:8000,网页显示”Not Found”,这是因为这个 demo 没有写路由。

这个例子中,Node 进程运行在 Docker 容器的虚拟环境里面,进程接触到的文件系统和网络接口都是虚拟的,与本机的文件系统和网络接口是隔离的,因此需要定义容器与物理机的端口映射(map)。

现在,在容器的命令行,按下 Ctrl + c 停止 Node 进程,然后按下 Ctrl + d (或者输入 exit)退出容器。此外,也可以用docker container kill终止容器运行。


# 在本机的另一个终端窗口,查出容器的 ID
$ docker container ls

# 停止指定的容器运行
$ docker container kill [containerID]

容器停止运行之后,并不会消失,用下面的命令删除容器文件。


# 查出容器的 ID
$ docker container ls --all

# 删除指定的容器文件
$ docker container rm [containerID]

也可以使用docker container run命令的--rm参数,在容器终止运行后自动删除容器文件。


$ docker container run --rm -p 8000:3000 -it koa-demo /bin/bash

4 CMD 命令

上一节的例子里面,容器启动以后,需要手动输入命令node demos/01.js。我们可以把这个命令写在 Dockerfile 里面,这样容器启动以后,这个命令就已经执行了,不用再手动输入了。


FROM node:8.4
COPY . /app
WORKDIR /app
RUN npm install --registry=https://registry.npm.taobao.org
EXPOSE 3000
CMD node demos/01.js

上面的 Dockerfile 里面,多了最后一行CMD node demos/01.js,它表示容器启动后自动执行node demos/01.js

你可能会问,RUN命令与CMD命令的区别在哪里?简单说,RUN命令在 image 文件的构建阶段执行,执行结果都会打包进入 image 文件;CMD命令则是在容器启动后执行。另外,一个 Dockerfile 可以包含多个RUN命令,但是只能有一个CMD命令。

注意,指定了CMD命令以后,docker container run命令就不能附加命令了(比如前面的/bin/bash),否则它会覆盖CMD命令。现在,启动容器可以使用下面的命令。


$ docker container run --rm -p 8000:3000 -it koa-demo:0.0.1

5 发布 image 文件

容器运行成功后,就确认了 image 文件的有效性。这时,我们就可以考虑把 image 文件分享到网上,让其他人使用。

首先,去 hub.docker.com 或 cloud.docker.com 注册一个账户。然后,用下面的命令登录。


$ docker login

接着,为本地的 image 标注用户名和版本。


$ docker image tag [imageName] [username]/[repository]:[tag]
# 实例
$ docker image tag koa-demos:0.0.1 ruanyf/koa-demos:0.0.1

也可以不标注用户名,重新构建一下 image 文件。


$ docker image build -t [username]/[repository]:[tag] .

最后,发布 image 文件。


$ docker image push [username]/[repository]:[tag]

发布成功以后,登录 hub.docker.com,就可以看到已经发布的 image 文件。

2.6 基于已有的image发布新的image

  • 将容器里面运行的程序及运行环境打包生成新的镜像
docker commit [选项] 容器ID/名称 仓库名称:[标签]
-m:说明信息
-a:作者信息
-p:生成过程中停止容器的运行

其他有用的命令

docker 的主要用法就是上面这些,此外还有几个命令,也非常有用。

(1)docker container start

前面的docker container run命令是新建容器,每运行一次,就会新建一个容器。同样的命令运行两次,就会生成两个一模一样的容器文件。如果希望重复使用容器,就要使用docker container start命令,它用来启动已经生成、已经停止运行的容器文件。


$ docker container start [containerID]

(2)docker container stop

前面的docker container kill命令终止容器运行,相当于向容器里面的主进程发出 SIGKILL 信号。而docker container stop命令也是用来终止容器运行,相当于向容器里面的主进程发出 SIGTERM 信号,然后过一段时间再发出 SIGKILL 信号。


$ docker container stop [containerID]

这两个信号的差别是,应用程序收到 SIGTERM 信号以后,可以自行进行收尾清理工作,但也可以不理会这个信号。如果收到 SIGKILL 信号,就会强行立即终止,那些正在进行中的操作会全部丢失。

(3)docker container logs

docker container logs命令用来查看 docker 容器的输出,即容器里面 Shell 的标准输出。如果docker run命令运行容器的时候,没有使用-it参数,就要用这个命令查看输出。


$ docker container logs [containerID]

(4)docker container exec

docker container exec命令用于进入一个正在运行的 docker 容器。如果docker run命令运行容器的时候,没有使用-it参数,就要用这个命令进入容器。一旦进入了容器,就可以在容器的 Shell 执行命令了。


$ docker container exec -it [containerID] /bin/bash

(5)docker container cp

docker container cp命令用于从正在运行的 Docker 容器里面,将文件拷贝到本机。下面是拷贝到当前目录的写法。


$ docker container cp [containID]:[/path/to/file] .

使用Dorker来运行深度学习

假设已经配置好了Dorker容器,里面已经存在深度学习所需的环境。

Ubuntu系统,创建一个docker,然后搭建conda深度学习环境,这样可以用conda或pip安装相关的依赖库了。

一、创建一个docker

为了方便开发,在Docker Hub官方中选择一个合适的conda docker镜像,然后下载到本地。

我选择了“docker-anaconda”,地址是:Docker Hub

下载命令如下:

docker pull continuumio/anaconda3

二、进入docker

通常使用 docker run 命令进入docker镜像,例如:

docker run -i -t continuumio/anaconda3 /bin/bash

其中 -i: 以交互模式运行容器,通常与 -t 同时使用;

2.1 映射目录

平常进入了docker环境,然后创建或产生的文件,在退出docker环境后会“自动销毁”;或者想运行本地主机的某个程序,发现在docker环境中找不到。

我们可以通过映射目录的方式,把本地主机的某个目录,映射到docker环境中,这样产生的文件会保留在本地主机中。

比如:

docker run -i -t continuumio/anaconda3 -v /home/xxx/xxx/:/home/xxxx:rw /bin/bash

通过-v 把本地主机目录 /home/xxx/xxx/ 映射到docker环境中的/home/xxxx 目录;其权限是rw,即能读能写。

2.2 支持GPU

默认是不把GPU加入到docker环境中的,但可以通过参数设置:

--gpus all

但我发现,这样有时不能在docker里正常使用GPU;可以使用如下参数,在Pytorch中亲测有效。 

--gpus all  -e NVIDIA_DRIVER_CAPABILITIES=compute,utility -e NVIDIA_VISIBLE_DEVICES=all

举个例子:

docker run -i -t continuumio/anaconda3 --gpus all  -e NVIDIA_DRIVER_CAPABILITIES=compute,utility -e NVIDIA_VISIBLE_DEVICES=all /bin/bash

2.3 设置内存

 默认分配很小的内参,在训练模型时不够用,可以通过参数设置:

--shm-size xxG

比如,我电脑有32G内参,想放16G到docker中使用,设置为 –shm-size 16G,即:

docker run -i -t continuumio/anaconda3  --shm-size 16G /bin/bash

2.4 综合版本

结合映射目录、支持GPU、设置内存,打开docker的命令如下:

docker run -i -t  -v /home/disk1/guopu/:/home/guopu:rw --gpus all --shm-size 16G -e NVIDIA_DRIVER_CAPABILITIES=compute,utility -e NVIDIA_VISIBLE_DEVICES=all continuumio/anaconda3  /bin/bash

详细的参数解析如下

  • -a stdin: 指定标准输入输出内容类型,可选 STDIN/STDOUT/STDERR 三项;
  • -d: 后台运行容器,并返回容器ID;
  • -i: 以交互模式运行容器,通常与 -t 同时使用;
  • -P: 随机端口映射,容器内部端口随机映射到主机的端口
  • -p: 指定端口映射,格式为:主机(宿主)端口:容器端口
  • -t: 为容器重新分配一个伪输入终端,通常与 -i 同时使用;
  • –name=”nginx-lb”: 为容器指定一个名称;
  • –dns 8.8.8.8: 指定容器使用的DNS服务器,默认和宿主一致;
  • –dns-search example.com: 指定容器DNS搜索域名,默认和宿主一致;
  • -h “mars”: 指定容器的hostname;
  • -e username=”ritchie”: 设置环境变量;
  • –env-file=[]: 从指定文件读入环境变量;
  • –cpuset=”0-2″ or –cpuset=”0,1,2″: 绑定容器到指定CPU运行;
  • -m :设置容器使用内存最大值;
  • –net=”bridge”: 指定容器的网络连接类型,支持 bridge/host/none/container: 四种类型;
  • –link=[]: 添加链接到另一个容器;
  • –expose=[]: 开放一个端口或一组端口;
  • –volume , -v: 绑定一个卷

三、检验docker

进入docker中,首先查看一下GPU,用nvidia-smi命令。正常显示CUDA版本,正常加载了显卡(这里是两张1080ti)。

使用两张显卡训练YOLOv5时,显示正常;

打开dorker:

sudo docker start 容器名

docker run 只在第一次运行时使用,将镜像放到容器中,以后再次启动这个容器时,只需要使用命令docker start
即可。docker run相当于执行了两步操作:将镜像放入容器中(docker
create),然后将容器启动,使之变成运行时容器(docker start)。而docker
start的作用是,重新启动已存在的镜像。也就是说,如果使用这个命令,我们必须事先知道这个容器的ID,或者这个容器的名字,我们可以使用docker
ps找到这个容器的信息。

四、进入已打开的docker

思路:首先使用docker ps 查询正在运行docker的ID,然后使用docker exec 命令进入。

命令如下:

$ sudo docker ps  
$ sudo docker exec -it docker_ID /bin/bash

其中docker_ID,是使用docker ps查询正在运行docker的ID,比如是fe8984f24b79。

点云基础知识+ 三维计算机视觉研究内容

1)点云概念

点云是在同一空间参考系下表达目标空间分布和目标表面特性的海量点集合,在获取物体表面每个采样点的空间坐标后,得到的是点的集合,称之为“点云”(Point Cloud)。

2)点云图像是最基础也是最常见的三维图像。

那什么是三维图像呢?三维图像是一种特殊的图像信息表达形式。相比较于常见的二维图像,其最大的特征是表达了空间中三个维度(长度宽度和深度)的数据。

3)三维图像的表现形式

深度图(以灰度表达物体与相机的距离),几何模型(由CAD软件建立),点云模型(所有逆向工程设备都将物体采样成点云)。

4)点云根据测量原理主要分为两种

根据激光测量原理得到的点云,包括三维坐标(XYZ)和激光反射强度(Intensity)。强度信息与目标的表面材质、粗糙度、入射角方向,以及仪器的发射能量,激光波长有关。

根据摄影测量原理得到的点云,包括三维坐标(XYZ)和颜色信息(RGB)。

当然也有把激光和摄影相结合在一起的(多传感器融合技术),这种结合激光测量和摄影测量原理得到点云,包括三维坐标(XYZ)、激光反射强度(Intensity)和颜色信息(RGB)。

本次的文章主要讲的是基于摄像技术的点云配准。

5)点云的获取设备

RGBD设备(深度摄像机)是可以获取点云的设备。比如PrimeSense公司的PrimeSensor、微软的Kinect、华硕的XTionPRO。

6)点云的属性

空间分辨率、点位精度、表面法向量等。

7)点云存储格式

.pts; .asc ; *.dat; .stl ; [1] .imw;.xyz;.las。

8)点云的数据类型

(1)pcl::PointCloudpcl::PointXYZ

PointXYZ 成员:float x,y,z;表示了xyz3D信息,可以通过points[i].data[0]或points[i].x访问点X的坐标值

(2)pcl::PointCloudpcl::PointXYZI

PointXYZI成员:float x, y, z, intensity; 表示XYZ信息加上强度信息的类型。

(3)pcl::PointCloudpcl::PointXYZRGB

PointXYZRGB 成员:float x,y,z,rgb; 表示XYZ信息加上RGB信息,RGB存储为一个float。

(4)pcl::PointCloudpcl::PointXYZRGBA

PointXYZRGBA 成员:float x , y, z; uint32_t rgba; 表示XYZ信息加上RGBA信息,RGBA用32bit的int型存储的。

(5) PointXY 成员:float x,y;简单的二维x-y点结构

(6)Normal结构体:

表示给定点所在样本曲面上的法线方向,以及对应曲率的测量值,用第四个元素来占位,兼容SSE和高效计算

9)点云处理的三个层次

一般将图像处理分为三个层次,低层次包括图像强化,滤波,关键点/边缘检测等基本操作。中层次包括连通域标记(label),图像分割等操作。高层次包括物体识别,场景分析等操作。工程中的任务往往需要用到多个层次的图像处理手段。

低层次处理方法

①滤波方法:双边滤波、高斯滤波、条件滤波、直通滤波、随机采样一致性滤波。②关键点:ISS3D、Harris3D、NARF,SIFT3D

中层次处理方法

①特征描述:法线和曲率的计算、特征值分析、SHOT、PFH、FPFH、3D Shape Context、Spin Image

②分割与分类:

分割:区域生长、Ransac线面提取、全局优化平面提取

K-Means、Normalize Cut(Context based)

3D Hough Transform(线、面提取)、连通分析

分类:基于点的分类,基于分割的分类,基于深度学习的分类(PointNet,OctNet)

高层次处理方法

①配准

点云配准分为粗配准(Coarse Registration)和精配准(Fine Registration)两个阶段。

精配准的目的是在粗配准的基础上让点云之间的空间位置差别最小化。应用最为广泛的精配准算法应该是ICP以及ICP的各种变种(稳健ICP、point to plane ICP、Point to line ICP、MBICP、GICP、NICP)。

粗配准是指在点云相对位姿完全未知的情况下对点云进行配准,可以为精配准提供良好的初始值。当前较为普遍的点云自动粗配准算法包括基于穷举搜索的配准算法和基于特征匹配的配准算法。

基于穷举搜索的配准算法:

遍历整个变换空间以选取使误差函数最小化的变换关系或者列举出使最多点对满足的变换关系。如RANSAC配准算法、四点一致集配准算法(4-Point Congruent Set, 4PCS)、Super4PCS算法等……

基于特征匹配的配准算法:

通过被测物体本身所具备的形态特性构建点云间的匹配对应,然后采用相关算法对变换关系进行估计。如基于点FPFH特征的SAC-IA、FGR等算法、基于点SHOT特征的AO算法以及基于线特征的ICL等…

②SLAM图优化

Ceres(Google的最小二乘优化库,很强大), g2o、LUM、ELCH、Toro、SPA

SLAM方法:ICP、MBICP、IDC、likehood Field、NDT

③三维重建

泊松重建、 Delaunay triangulations、表面重建,人体重建,建筑物重建,树木重建。结构化重建:不是简单的构建一个Mesh网格,而是为场景进行分割,为场景结构赋予语义信息。场景结构有层次之分,在几何层次就是点线面。实时重建:重建植被或者农作物的4D(3D+时间)生长态势;人体姿势识别;表情识别;

④点云数据管理

点云压缩,点云索引(KD、Octree),点云LOD(金字塔),海量点云的渲染。

三维计算视觉研究内容

 1)三维匹配:两帧或者多帧点云数据之间的匹配,因为激光扫描光束受物体遮挡的原因,不可能通过一次扫描完成对整个物体的三维点云的获取。因此需要从不同的位置和角度对物体进行扫描。三维匹配的目的就是把相邻扫描的点云数据拼接在一起。三维匹配重点关注匹配算法,常用的算法有最近点迭代算法 ICP和各种全局匹配算法。

 2)多视图三维重建:计算机视觉中多视图一般利用图像信息,考虑多视几何的一些约束,射影几何和多视图几何是视觉方法的基础,在摄影测量中类似的存在共线方程。光束平差法是该类研究的核心技术。这里也将点云的多视匹配放在这里,比如人体的三维重建,点云的多视重建不再是简单的逐帧的匹配,还需要考虑不同角度观测产生误差累积,因此存在一个针对三维模型进行优化或者平差的过程在里面。多视图三维重建这里指的只是静态建模,输入是一系列的图像或者点云集合。可以只使用图像,或者只使用点云,也可以两者结合(深度图像)实现,重建的结果通常是Mesh网格。

  • SFM(运动恢复结构) vs Visual SLAM  [摘抄] SFM 和 Visual SLAM
  • Multi-View Stereo (MVS)多视图立体视觉,研究图像一致性,实现稠密重建。

  3)3D SLAM

  按照传感器类型分类:可以分为基于激光的SLAM和基于视觉的SLAM。

  基于激光的SLAM可以通过点云匹配(最近点迭代算法 ICP、正态分布变换方法 NDT)+位姿图优化(g2o、LUM、ELCH、Toro、SPA)来实现;实时激光3D SLAM算法 (LOAM,Blam,CartoGrapher等);Kalman滤波方法。通常激光3D SLAM侧重于定位,在高精度定位的基础上可以产生3D点云,或者Octree Map。

  基于视觉(单目、双目、鱼眼相机、深度相机)的SLAM,根据侧重点的不同,有的侧重于定位,有的侧重于表面三维重建。不过都强调系统的实时性

  (1)侧重于定位的VSLAM系统比如orbSLAM,lsdSLAM;VINS是IMU与视觉融合的不错的开源项目。

  

  (2)侧重于表面三维重建SLAM强调构建的表面最优,或者说表面模型最优,通常包含Fusion融合过程在里面。通常SLAM是通过观测形成闭环进行整体平差实现,优先保证位姿的精确;而VSLAM通过Fusion过程同时实现了对构建的表面模型的整体优化,保证表面模型最优。最典型的例子是KinectFusion,Kinfu,BundleFusion,RatMap等等。

  (4)目标检测与识别:无人驾驶汽车中基于激光数据检测场景中的行人、汽车、自行车、道路(车道线,道路标线,路边线)以及道路设施(路灯)和道路附属设施(行道树等)。这部分工作也是高精度电子地图的主要内容。当然高精度电子地图需要考虑的内容更多。同时室内场景的目标识别的研究内容也很丰富,比如管线设施,消防设施等。

  (5)形状检测与分类:点云技术在逆向工程中有很普遍的应用。构建大量的几何模型之后,如何有效的管理,检索是一个很困难的问题。需要对点云(Mesh)模型进行特征描述,分类。根据模型的特征信息进行模型的检索。同时包括如何从场景中检索某类特定的物体,这类方法关注的重点是模型。

  (6)语义分类:获取场景点云之后,如何有效的利用点云信息,如何理解点云场景的内容,进行点云的分类很有必要,需要为每个点云进行Labeling。可以分为基于点的分类方法和基于分割的分类方法。从方法上可以分为基于监督分类的技术或者非监督分类技术,深度学习也是一个很有希望应用的技术。最近深度学习进行点云场景理解的工作多起来了,比如PointNet,各种八叉树的Net。

(7)双目立体视觉与立体匹配 ZNCC:立体视觉(也称双目视觉)主要研究的两个相机的成像几何问题,研究内容主要包括:立体标定(Stereo Calibration)、立体校正(Stereo Rectification)和立体匹配(Stereo Matching)。目前,立体标定主要研究的已经比较完善,而立体匹配是立体视觉最核心的研究问题。按照匹配点数目分类,立体匹配可分为稀疏立体匹配(sparse stereo matching)和密集立体匹配(dense stereo matching)。稀疏立体匹配由于匹配点数量稀少,一般很难达到高精度移动测量和环境感知的要求。因此,密集立体匹配是学术界和工业界的主要研究和应用方向。

参考:https://mp.weixin.qq.com/s/cOHAQX12k19eogxfpk95tA

(8)自动造型(构型),快速造型(构型)技术。对模型进行凸分割,模型剖分,以实现模型进一步的编辑修改,派生出其他的模型。

(9)摄像测量技术,视频测量


上网限制和翻墙的基本原理

相关文章:翻墙与科学上网全面解析  翻墙与科学上网

本文转载自:http://blog.021xt.cc/archives/85

目前在国内基本访问不了google站点和android的站点,下载个gradle都要等很久。所以如果不翻墙很多工作都没办法正常做。所以在学习翻墙的同时也顺便了解了下目前限制网络访问的一些基本知识。

网络限制和监控应该说大家都有体会。比如很多公司都会限制一些网站的访问,比如网盘、视屏网站。有时也会对你访问的内容进行监控。还有一些公共WIFI,可能限制你只能访问80端口。在比如在国内无法访问google,facebook,android等网站。要想绕过这些限制,必须先知道他们是如何限制的。

本文主要是从技术角度来了解的网络限制方式和应对方式。并不做任何翻墙方式的推荐和指导。对于网络的知识,还是停留在上过网络课 的水平,文章内容也都是自己了解后总结的。可能会有错误和遗漏。会定期更新。

DNS污染和劫持

以下解释来之百度百科:
某些网络运营商为了某些目的,对DNS进行了某些操作,导致使用ISP的正常上网设置无法通过域名取得正确的IP地址。
某些国家或地区出于某些目的为了防止某网站被访问,而且其又掌握部分国际DNS根目录服务器或镜像,也会利用此方法进行屏蔽。

目前我们访问网站主要都是通过域名进行访问,而真正访问这个网站前需要通过DNS服务器把域名解析为IP地址。而普通的DNS服务使用UDP协议,没有任何的认证机制。DNS劫持是指返回给你一个伪造页面的IP地址,DNS污染是返回给你一个不存在的页面的IP地址。

比如你使用电信、联通、移动的宽带,默认你是不需要设置任何DNS服务器的。这些DNS服务器由他们提供。一旦检测到你访问的网页是不允许的访问的,就会返回一个不存在的网页。而很多运营商也会使用DNS劫持来投放一些广告。

解决办法:

  1. 使用OpenDNS(208.67.222.222)或GoogleDNS(8.8.8.8)(现在不太好用,被封锁,速度慢)
  2. 使用一些第三方的DNS服务器
  3. 自己用VPS搭建DNS服务器
  4. 修改机器host文件,直接IP访问

封锁IP

通过上面一些方式,可以绕过DNS污染,通过IP地址访问无法访问的网页。但是目前针对IP进行大范围的封锁。虽然google这种大公司有很多镜像IP地址,但是目前基本全部被封锁掉,有漏网的可能也坚持不了多久。而且很多小公司的服务是部署在一些第三方的主机上,所以封锁IP有时会误伤,封锁一个IP导致主机上本来可以使用的页面也无法访问了。

不过目前不可能把所有国外的IP全部封锁掉,所以我们采用机会从国内连接到国外的VPS,进行翻墙。

解决办法:

  1. 使用VPS搭建代理
  2. 使用IPV6 (IPV6地址巨大,采用封地址不现实,但是目前国内只有部分高校部署了IPV6)

封锁HTTP代理

对于没有办法搭建VPS的人来说,最好的办法就是使用HTTP代理。客户端不在直接请求目标服务器,而是请求代理服务器,代理服务器在去请求目标服务器。然后返回结果。关于HTTP代理可以参考

1《HTTP权威指南》第六章:代理
代理示意图

对于HTTP代理来说,封锁起来非常简单。因为HTTP协议是明文,Request Message中就带有要请求的URL或IP地址,这样很容易就被检测到。对于HTTPS来说,虽然通信是进行加密了,但是在建连之前会给代理服务器发送CONNECT方法,这里也会带上要访问的远端服务器地址。如果代理服务器在国外,在出去前就会被检测到。 如果代理服务器在国内,呵呵,你也出不去啊。

对于HTTP代理,因为是明文,所以很容易被服务器了解你的一些数据。所以不要随便使用第三方的HTTP代理访问HTTP网站,而HTTPS虽然不知道你的数据,但是可以知道你去了那里。

解决办法:

  1. 使用VPS搭建VPN
  2. 使用第三方VPN

封锁VPN

虚拟专用网(英语:Virtual Private Network,简称VPN),是一种常用于连接中、大型企业或团体与团体间的私人网络的通讯方法。虚拟私人网络的讯息透过公用的网络架构(例如:互联网)来传送内联网的网络讯息。它利用已加密的通道协议(Tunneling Protocol)来达到保密、发送端认证、消息准确性等私人消息安全效果。

VPN示意图

正常网络通信时,所有网络请求都是通过我们的物理网卡直接发送出去。而VPN是客户端使用相应的VPN协议先与VPN服务器进行通信,成功连接后就在操作系统内建立一个虚拟网卡,一般来说默认PC上所有网络通信都从这虚拟网卡上进出,经过VPN服务器中转之后再到达目的地。

VPN发送流程示意图

通常VPN协议都会对数据流进行强加密处理,从而使得第三方无法知道数据内容,这样就实现了翻墙。翻墙时VPN服务器知道你干的所有事情(HTTP,对于HTTPS,它知道你去了哪)。

VPN有多种协议:OPENVPN、PPTP、L2TP/IPSec、SSLVPN、IKEv2 VPN,Cisco VPN等。其中的PPTP和L2TP是明文传输协议。只负责传输,不负责加密。分别利用了MPPE和IPSec进行加密。

背景PPTP 是一个基于 PPP 的很基本的协议。PPTP 是微软 Windows 平台第一个支持的 VPN 协议。PPTP 标准并没有实际描述加密和授权特性,并且依赖于 PPP 协议的隧道来实现安全功能。L2TP 是一个在 IETF RFC 3193 中被正式标准化的高级协议。推荐在需要安全加密的地方用来替代 PPTP。OpenVPN 是一个高级的开源 VPN 解决方案,由 “OpenVPN technologies” 支持,并且已经成为开源网络领域里的事实标准。OpenVPN 使用成熟的 SSL/TLS 加密协议。
数据加密PPP 负载是使用微软点对点协议(Microsoft’s Point-to-Point Encryption protocol,MPPE)加密。MPPE 实现了 RSA RC4 加密算法,并使用最长 128 位密钥。L2TP 负载使用标准的 IPSec 协议加密。在 RFC 4835 中指定了使用 3DES 或 AES 加密算法作为保密方式。OpenVPN 使用 OpenSSL 库来提供加密。OpenSSL 支持好几种不同的加密算法,如:3DES,AES,RC5 等。
安装/配置Windows 所有版本和大多数其他操作系统包括移动平台都内建了对 PPTP 的支持。PPTP 只需要一个用户名和密码,以及一个服务器地址,所以安装和配置相当简单。从 2000/XP 起的所有 Windows 平台和 Mac OS X 10.3+ 都内建了 L2TP/IPSec 的支持。大多数现代的移动平台比如 iPhone 和 Android 也有内建的客户端。OpenVPN 不包含在任何操作系统中,需要安装客户端软件,但安装也是相当简单,基本上 5 分钟可以完成。
速度由于使用 128 位密钥,加密开销相比 OpenVPN 使用 256位密钥要小,所以速度感觉稍快一点,但这个差异微不足道。L2TP/IPSec 将数据封装两次,所以相比其他竞争者效率稍低,速度也慢一些。当使用默认的 UDP 模式,OpenVPN 的表现是最佳的。
端口PPTP 使用 TCP 1723 端口和 GRE(协议 47)。通过限制 GRE 协议,PPTP 可以轻易地被封锁。L2TP/IPSec 使用 UDP 500 端口用来初始化密钥交换,使用协议 50 用来传输 IPSec 加密的数据( ESP ),使用 UDP 1701 端口用来初始化 L2TP 的配置,还使用 UDP 4500 端口来穿过 NAT。L2TP/IPSec 相比 OpenVPN 容易封锁,因为它依赖于固定的协议和端口。OpenVPN 可以很容易的配置为使用任何端口运行,也可以使用 UDP 或 TCP 协议。为了顺利穿越限制性的防火墙,可以将 OpenVPN 配置成使用 TCP 443 端口,因为这样就无法和标准的 HTTPS 无法区分,从而极难被封锁。
稳定性/兼容性PPTP 不如 OpenVPN 可靠,也不能像 OpenVPN 那样在不稳定网络中快速恢复。另外还有部分同 GRE 协议和一些路由器的兼容性问题。L2TP/IPSec 比 OpenVPN 更复杂,为了使在 NAT 路由器下的设备可靠地使用,配置可以会更加困难。但是,只要服务器和客户端都支持 NAT 穿越,那么就没什么问题了。无论是无线网络、蜂窝网络,还是丢包和拥塞经常发生的不可靠网络,OpenVPN 都非常稳定、快速。对于那些相当不可以的连接,OpenVPN 有一个 TCP 模式可以使用,但是要牺牲一点速度,因为将 TCP 封装在 TCP 时效率不高。
安全弱点微软实现的 PPTP 有一个严重的安全问题(serious security vulnerabilities)。对于词典攻击来说 MSCHAP-v2 是很脆弱的,并且 RC4 算法也会遭到“位翻转攻击( bit-flipping attack )”。如果保密是重要的,微软也强烈建议升级到 IPSec。IPSec 没有明显的漏洞,当和安全加密算法如 AES 一起使用时,被认为是很安全的。OpenVPN 也没有明显漏洞,当和安全加密算法如 AES 一起使用时,也被认为是相当安全的。
客户端的兼容性Windows、Mac OS X、Linux、Apple iOS、Android、DD-WRTWindows、Mac OS X、Linux、Apple iOS、AndroidWindows、Mac OS X、Linux
结论由于主要的安全漏洞,除了兼容性以外没有好的理由选择使用 PPTP。如果你的设备既不支持 L2TP/IPSec 又不支持 OpenVPN,那么 PPTP 是一个合理的选择。如果关心快速安装和简易配置,那么 L2TP/IPSec 值得考虑。L2TP/IPSec 是优秀的,但相比 OpenVPN 的高效和杰出的稳定性要落后一点。如果你使用运行 iOS 或 Android 的移动设备,那么这就是最佳的选择,因为 OpenVPN 目前还不支持这些平台。另外,如果需要快速安装,L2TP/IPSec 也是一个较佳的选择。对于所有的 Windows, Mac OS X 以及 Linux 桌面用户来说,OpenVPN 是最好的选择。OpenVPN 速度快,并且安全可信。但劣势是缺乏对移动设备的支持,另外还需要安装第三方客户端。
等级

对于VPN和其他一些加密的传输的协议来说,没有办法直接获取明文的请求信息,所以没有办法直接封锁,而是使用了监控的方式:

暴力破解

对于一些使用弱加密方式的协议来说,直接使用暴力破解检查传输内容。比如PPTP使用MPPE加密,但是MPPE是基于RC4,对于强大的防火墙背后的超级计算机集群,破解就是几秒钟的事情。

破解后明文中一旦包含了违禁内容,请求就会被封。而对应的IP可能会进入重点关怀列表。

特征检测

要想成功翻墙都必须与对应的远程服务器建立连接,然后再用对应的协议进行数据处理并传输。
而问题就出在这里:翻墙工具和远程服务器建立连接时,如果表现的很独特,在一大堆流量里很显眼,就会轻易被GFW识别出从而直接阻断连接,而VPN(尤其是OPENVPN)和SSH这方面的问题尤其严重。

流量监控

当一个VPN地址被大量人请求,并保持长时间连接时,就很容易引起关注。SSH接口有大量数据请求。一般会结合其他特征。

深度包检测

深度数据包检测(英语:Deep packet inspection,缩写为 DPI),又称完全数据包探测(complete packet inspection)或信息萃取(Information eXtraction,IX),是一种电脑网络数据包过滤技术,用来检查通过检测点之数据包的数据部分(亦可能包含其标头),以搜索不匹配规范之协议、病毒、垃圾邮件、入侵,或以预定之准则来决定数据包是否可通过或需被路由至其他不同目的地,亦或是为了收集统计数据之目的。

比如我们用HTTPS来访问一个网站,TLS/SSL协议在建连过程如下:

https连接示意图

很明显的会发送“client hello”和“server hello” 这种特诊很明显的信息。(当然不会根据这个就封掉,否则https没法用了)。而后续会有服务端证书发送,验证,客户端密钥协商等过程。有明显的协议特征。

VPN原理示意图

下面是网上找的两张图:提醒大家最好不要随便用不安全的VPN来访问不合适的网页,开开android没啥问题。

不安全VPN示意图

Socks代理/SSH Socks

SOCKS是一种网络传输协议,主要用于客户端与外网服务器之间通讯的中间传递。SOCKS是”SOCKetS”的缩写[1]。
当防火墙后的客户端要访问外部的服务器时,就跟SOCKS代理服务器连接。这个代理服务器控制客户端访问外网的资格,允许的话,就将客户端的请求发往外部的服务器。
这个协议最初由David Koblas开发,而后由NEC的Ying-Da Lee将其扩展到版本4。最新协议是版本5,与前一版本相比,增加支持UDP、验证,以及IPv6。
根据OSI模型,SOCKS是会话层的协议,位于表示层与传输层之间

与HTTP代理的对比

SOCKS工作在比HTTP代理更低的层次:SOCKS使用握手协议来通知代理软件其客户端试图进行的连接SOCKS,然后尽可能透明地进行操作,而常规代理可能会解释和重写报头(例如,使用另一种底层协议,例如FTP;然而,HTTP代理只是将HTTP请求转发到所需的HTTP服务器)。虽然HTTP代理有不同的使用模式,CONNECT方法允许转发TCP连接;然而,SOCKS代理还可以转发UDP流量和反向代理,而HTTP代理不能。HTTP代理通常更了解HTTP协议,执行更高层次的过滤(虽然通常只用于GET和POST方法,而不用于CONNECT方法)

Socks代理本身协议是明文传输,虽然相对HTTP有一些优势,但是明文也导致Socks代理很容易被封。所以可以考虑对Socks进行加密。所以出现了SSH Socks,对于MAC和Linux来说,不需要Client就可以进行访问。详细可以看:SSH隧道技术简介:端口转发&SOCKS代理

但是网上看有些地区好像会对一些VPS的SSH进行端口干扰。我在武汉好像SSH到我的VPS一会就会断。在上海一直没这问题。而且SSH一般是小流量数据,如果数据量特别大,也会被认为是翻墙,进入特别关怀列表。

Shadowsocks

认准官网:https://shadowsocks.org/en/index.html (.com那个是卖账号的)

1
2
A secure socks5 proxy,
designed to protect your Internet traffic.

Shadowsocks 目前不容易被封杀主要是因为:

  1. 建立在socks5协议之上,socks5是运用很广泛的协议,所以没办法直接封杀socks5协议
  2. 使用socks5协议建立连接,而没有使用VPN中的服务端身份验证和密钥协商过程。而是在服务端和客户端直接写死密钥和加密算法。所以防火墙很难找到明显的特征,因为这就是个普通的socks5协议。
  3. Shadowsock搭建也比较简单,所以很多人自己架设VPS搭建,个人使用流量也很小,没法通过流量监控方式封杀。
  4. 自定义加密方式和密钥。因为加密主要主要是防止被检测,所以要选择安全系数高的加密方式。之前RC4会很容易被破解,而导致被封杀。所以现在推荐使用AES加密。而在客户端和服务端自定义密钥,泄露的风险相对较小。

所以如果是自己搭建的Shadosocks被封的概率很小,但是如果是第三方的Shadeowsocks,密码是server定的,你的数据很可能遭受到中间人攻击。

顺便说一下,Shadowssocks是天朝的clowwindy大神写的。不过Shadowsocks项目源码已经从github上删除了并停止维护了,但是release中还有源码可以下载。https://github.com/shadowsocks/shadowsocks

Shadowsocks-rss

前面认为Shadowssocks特征并不是很明细,但是了解协议工作原理后会发现,SS协议本身还有有漏洞,可以被利用来检测特征,具体讨论看:ShadowSocks协议的弱点分析和改进。 里面中间那些撕逼就不用看了,我总结了下大致意思是:
协议过于简单,并且格式固定,很容易被发起中间人攻击。先看看协议结构

1
2
3
4
5
+————–+———————+——————+———-+
| Address Type | Destination Address | Destination Port | Data |
+————–+———————+——————+———-+
| 1 | Variable | 2 | Variable |
+————–+———————+——————+———-+

Possible values of address type are 1 (IPv4), 4 (IPv6), 3 (hostname). For IPv4 address, it’s packed as a 32-bit (4-byte) big-endian integer. For IPv6 address, a compact representation (16-byte array) is used. For hostname, the first byte of destination address indicates the length, which limits the length of hostname to 255. The destination port is also a big-endian integer.

The request is encrypted using the specified cipher with a random IV and the pre-shared key, it then becomes so-called payload.

结构很简单,上面解释也很清楚。Client每一个请求都是这种格式,然后进行加密。Server端解密然后解析。看起来没什么问题,没有密钥你无法模拟中间人攻击,也没什么明显特征。但是看看Server处理逻辑会发现存在一些问题:

Client数据在加密目前用的最多的是AES系列,加密后在协议数据前会有16位的IV。而Server段解析后,首先判断请求是否有效,而这个判断很简单:

1判断的依据就是Address Type的那个字节,看它是不是在那三个可能取值,如果不是,立即断开连接,如果是,就尝试解析后面的地址和端口进行连接。

如果能发起中间人攻击,模拟Client请求,这个就是一个很明显的特征,如果把Address Type穷举各种情况,其中只有3种情况会连接成功。那么很可能就是一个Shadowsocks 服务器。

所以只需要先劫持一条socks5的请求,因为AES加密后Address Type位置是固定的(第17位),篡改这一位,穷举256种情况(AES-256),然后发送给服务器。如果服务器在3种情况没有关闭连接,就说明这个很可能是Shadowsock服务。你这个IP很快就进入关怀列表了。

这里的关键就是AES加密明文和密文对应关系。密码学不是太懂,贴帖子里面一个回复:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
举个例子,现在有一个协议包,共7个字节

0x01, 0x08, 0x08, 0x08, 0x08, 0x00, 0x50
对照socks5协议,很明显这是一个IPv4包(第一个字节是0x01),目的地是8.8.8.8的80端口

被shadowsocks加密了以后(密码abc,加密方式aes-256-cfb),数据包就变成了这样

0xbb, 0x59, 0x1c, 0x4a, 0xb9, 0x0a, 0x91, 0xdc, 0x07, 0xef, 0x72, 0x05, 0x90, 0x42, 0xca, 0x0d, 0x4c, 0x3b, 0x87, 0x8e, 0xca, 0xab, 0x32
前16个字节,从0xbb到0x0d,都是iv,根据issue中提到的弱点和之前的总结,只需要修改0x4c,即真正密文中的第一个字节,就可要起到修改明文中的第一个字节的效果。

那就把0x4c修改成0x4d吧,解密以后的结果是

0x00, 0x08, 0x08, 0x08, 0x08, 0x00, 0x50
的确只有第一个字节被改掉了,根据breakwa11的理论,不难推出其他情况,其中合法的是

0x4e => 0x03 (Domain Name)
0x49 => 0x04 (IPv6)

所以目前Shadowsocks应该是比较容易被检测出来。但是为什么没有被封掉,呵呵,就不知道了。所以这个项目目的就是在SS基础上进行一些混淆。因为原有实现确实有漏洞。 不过目前这个项目好像也停止更新了。并且木有开源。

当然如果是自己用完全可以自己修改一个私有协议,这样就没法被检测到了。但是需要同时修改Server段,MAC Client,Windows Client, Android Client。 – -!

GoAgent和GoProxy

Google App Engine是一个开发、托管网络应用程序的平台,使用Google管理的数据中心

GAE示意图

GoAgent的运行原理与其他代理工具基本相同,使用特定的中转服务器完成数据传输。它使用Google App Engine的服务器作为中传,将数据包后发送至Google服务器,再由Google服务器转发至目的服务器,接收数据时方法也类似。由于服务器端软件基本相同,该中转服务器既可以是用户自行架设的服务器,也可以是由其他人架设的开放服务器。

GoAgent其实也是利用GAE作为代理,但是因为他是连接到google的服务器,因为在国内现在google大量被封,所以GoAgent也基本很难使用。目前github上源码也已经删除。

但是GoAgent本身不依赖于GAE,而且使用Python编写,完全可以部署到VPS上进行代理。GoProxy是GoAgent的后续项目https://github.com/phuslu/goproxy
还有一个XX-NET:https://github.com/XX-net/XX-Net 有兴趣都可以去了解下。

Tor

Tor(The Onion Router,洋葱路由器)是实现匿名通信的自由软件。Tor是第二代洋葱路由的一种实现,用户通过Tor可以在因特网上进行匿名交流。

Tor: Overview

1The Tor network is a group of volunteer-operated servers that allows people to improve their privacy and security on the Internet. Tor’s users employ this network by connecting through a series of virtual tunnels rather than making a direct connection, thus allowing both organizations and individuals to share information over public networks without compromising their privacy. Along the same line, Tor is an effective censorship circumvention tool, allowing its users to reach otherwise blocked destinations or content. Tor can also be used as a building block for software developers to create new communication tools with built-in privacy features.

下面的图来自官网的介绍。具体内容大家可以自己看,简单说和其他翻墙方式不同,简单可以理解为Tor有一群代理服务器,然后要访问远端站点,是通过随机的代理路径来完成的,数据经历了多个代理服务器的传递。它主要作用是隐藏访问者信息,而翻墙只是顺带的功能。

要访问远程站点,Client需要知道Tor nodes,这些nodes就是普通加入的用户,就好像P2P下载一样。获取nodes信息后,会随机选择一条路径访问。 因为这个原因,Tor的速度可能不会很好。

Tor示意图1
Tor示意图2
Tor示意图3

而关于Tor的漏洞和检测看这里:Tor真的十分安全么 其原理以及漏洞详解!

目前有结合Tor+Shadowsocks前置代理使用的。

Reference

VPN翻墙,不安全的加密,不要相信墙内公司
GFW的工作原理(1) ————GFW是如何识别并封锁翻墙工具的
关于翻墙和匿名与网络安全类科普文大集合
为什么不应该用 SSL 翻墙
科学上网的一些原理