Zhangzhe's Blog

The projection of my life.

0%

URL

TL;DR

  • 本文提出一种以自动驾驶规划为目的的神经网络架构,该架构对每个感知子任务显式监督,合理的将子任务连接起来,增加了子任务之间的协调性,并增加了模型的可解释性

WIP

URL

TL;DR

  • 使用预测下一个词(语言建模 language modeling)任务进行自监督预训练
  • 预训练的模型需要使用 reinforement learning with human feedback(RLHF) 进行对齐(align),这个过程不会在测试数据集上提高模型表现,但可以更好的对齐人类的意图和三观
  • 模型输入可以是图片和文本,输出为文本

Details

  • 使用了很强大的基建,可以做到准确预测模型训练的最终效果(scaling),可以以较小的代价和较快的时间找到最合适的模型架构和超参数设置
  • 为模型引入了 steerability(操纵性),可以在模型的 prompt 中加入一些 System message,让模型回复风格拥有某种特质(比如老师、政客等)
  • GPT-4 使用了很多机制提高了模型的安全性

Thought

  • 这篇技术报告更多是对模型效果的分析,基本没有模型细节的描述
  • 大模型逐渐变成大厂垄断,普通研究者能摸到的最后只剩下一个 API

URL

TL;DR

  • 作者团队训练了一个 96Transformer1750 亿参数的超大模型(GPT2 只有约 15 亿参数),在下游任务上无需 fine-tuning 即可得到很好的效果。
  • 本质是 GPT2 的放大版(参数量放大了一百多倍)

Algorithm

  • 在下游任务上,可以使用 Zero ShotOne ShotFew Shot 三种方式推理模型,下图以英语翻译法语的例子介绍三者的区别:
    GPT3.png
  • GPT3 系列模型详细设置:
    GP3_1.png
  • GPT3 自监督训练数据:
    GP3_2.png

使用了 common crawl 数据集,由于 common crawl 数据集很脏,所以训练是数据采样率并不高

  • 下图是在几个下游任务上和 SOTA 算法的比较:
    GPT3_3.png
    GPT3_5.png
    GPT3_4.png

从普遍表现看,GPT3 few shot 效果 > one shot > zero shot,不一定比 SOTA 点高(SOTA 普遍使用了 fine tuning,直接比较不公平)

Thought

  • 在某些任务上,GPT3 few shot 效果可媲美 fine tuning SOTA,可以说明 GPT3 还是非常强大的
  • 比上一代参数量提高一百多倍,开启了大模型时代…

URL

TL;DR

  • 本文提出一种类似 chatGPT 的交互式 Zero-shot 分割算法,用户给出一个 prompt(支持 point / box / mask / text),模型会根据 Prompt 语义完成分割,无需在特定分割任务数据上 fine-tuning(类似于 GPT2 和之后的系列模型)
  • 本文一个非常重要的理念是 Data Centric,即以数据为中心而不是以模型为中心,这一点和 GPT 系列也不谋而合
    • 传统视觉算法是在固定的数据集上修改模型结构实现模型效果的提升,实际是以模型为中心
    • 数据为中心的算法通常固定模型结构,通过例如 RLHF(reinforcement learning from human feedback) 的方法,使用模型辅助标注员高效标注大量数据(11 亿个 mask 区域),重复迭代提高效果
  • 模型本身由三部分组成:
    • image_encoder:提取图片特征,使用的是 ViT 模型,在交互过程中只需要推理一次
    • prompt_encoder: 提取 prompt 特征,将人的输入(例如点或框)编码到特征空间
    • mask_decoder: 输入为图片特征和 prompt 特征,融合后输出分割 mask

Algorithm

任务定义

sam3.png

理论上支持 point / box / mask / text,但 demo 和 code 都只包含了 point / box / mask

模型结构

sam4.png

image encoder 是 VIT
prompt encoder 对于 box / point prompt 只是简单的位置编码;对于 mask 是几层简单的卷积
lightweight mask decoder 是轻量级 transformer

Data centric

sam2.png

模型为中心和数据为中心的对比

使用效果

sam1.png

图中的框为用户输入的 prompt,模型会根据 prompt 输出分割结果

Thought

  • Data centric 感觉一定是未来,但形式一定不会以 RLHF 形式存在,而更多的以自监督形式存在
  • prompt 未来会取代 fine-tuning 这个词

URL

TL;DR

  • Bert 全方位打败 GPT 之后,OpenAI 推出了参数量更大的 GPT2
  • GPT2 与之前所有的 NLP 预训练模型使用的 自监督预训练 + 任务相关 fine-tuning 范式不同,GPT2 不再需要任何数据相关 fine-tuning,而是使用 prompt(提示词)
  • prompt(提示词) 是一段在模型推理阶段用于描述任务的文本,通常加在问题之前,起到提示模型的作用

Algorithm

GPT2 很大程度上只是 GPT 的放大版,所引入的创新并不多

  • 使用了 Byte Pair Encoding(BPE) 分词算法,本质是一种贪心算法
  • 由于自回归(auto-regression)模型推理容易出现死循环,所以本文提出一种 top-k 输出按 softmax 概率采样的 trick,增加模型的随机性

Thought

  • GPT2 最重要的作用是提出了使用 Prompt 替代 fine-tuning 的范式,为之后的 AIGC 大面积推广扫平了障碍

URL

TL;DR

  • 本文基于 FCOS 论文提出一种架构简单的 Anchor Free 的单目 3D 检测算法 FCOS3D,在 NeurIPS 2020nuScenes 3D 检测比赛纯视觉赛道上取得了第一名。

Algorithm

问题定义

  • 本文提出的 FCOS3D 要解决的核心问题是一个 图片到 7-DoF 属性(x, y, z, w, l, h, yaw+dir)的预测

    • DoF 是指 degree of freedom(自由度)。
    • (x, y, z, w, l, h, yaw+dir) 分别表示物体在相机坐标系下的 3 维坐标和长宽高(单位都是米),和偏航角(俯视图角度,单位是弧度)和方向 2 分类共同构成朝向。
  • 对于 nuScenes 3D 检测比赛,还需要解决的非核心问题包括:

    • 预测出的 3D 框物体的类别(10类物体)
    • 预测出的 3D 框物体的属性(9种属性)
    • 预测出的 3D 框物体的 x, y 轴速度(不是 “病态” 问题了,已经属于癌症问题了…)

网络结构

fcos3d.png

  • backboneFPN 比较常规

  • decode headFCOS 一样,使用了不同 level feature 的参数共享(反正是全卷积,不存在 shape 问题)

  • decode head 中包括:

    • 分类分支:

      • class: output_shape = (N, 10, H, W),使用 FocalLoss
      • attribute: output_shape = (N, 9, H, W),使用 CrossEntropyLoss
    • 回归分支:

      • box: output_shape = (N, 9, H, W)(dx, dy, log(z), log(w), log(l), log(h), yaw, vx, vy),使用 SmoothL1Loss
      • centerness: output_shape = (N, 1, H, W),使用 BCEWithLogitsLoss
      • direction: output_shape = (N, 2, H, W),使用 CrossEntropyLoss

target 设置

  • target 设置中使用了很多 2D 引导 3D 的思想。

(x, y, z) target 设置

  • 由于是 3D 检测,所以 GT 的 3D 框坐标(x, y, z, w, l, h)单位都是米,这对神经网络是不友好的(因为神经网络看到的是像素,预测以像素为单位更容易)。
  • 因此,本文实际是一个 2.5D 的预测(xy 2D, z 3D),实际预测的 x, y 是像素坐标系下相对于 feature map 每一个点的偏移量(由相机坐标系和相机内参可计算得到像素坐标系),z, w, l, h 的预测是相机坐标系下的米为单位的真值取 log

centerness target 设置

  • FCOS 不同,FCOS3D centerness: c=eα((Δx)2+(Δy)2)c = e^{-\alpha((\Delta x)^2+(\Delta y)^2)}α=2.5\alpha=2.5

yaw target 设置

  • 本文将 yaw (0 ~ 2π2\pi)编码成 yaw (0 ~ π\pi)和方向

正样本选择

  • FCOS 是将 feature map 上的每个位置到 GT 中心点的距离小于 1.5 * stride 的点作为正样本。
  • FCOS3D3D 检测,没办法直接使用 FCOS 提出的方法;解决方法和 x, y 坐标回归方法类似,如果 2.5D 坐标下的 x, yfeature map 位置距离小于 1.5 * stride,则算作正例。

GT 尺度分配

  • FCOS 思想一样

不同尺度 feature map 缩放

  • FCOS 思想一样,只是更丰富 scale.shape == (num_of_level, 3),分别表示 scale_offset(for xy) / scale_depth(for z) / scale_size(for wlh)

Thought

  • 本文极大程度的借鉴了 FCOS,相当于 FCOS2.5D
  • 加入了很多 trick: log(z), centerness target 定义,encode yaw 等,很 work

URL

TL;DR

  • Faster RCNN 系列、SSDYOLOv2~v5(注意 YOLOv1 不包括在内)都是基于 Anchor 进行预测的。
  • 本文提出一种 Anchor Free 的 one stage 目标检测方法,整个模型结构非常轻量,效果强大。
  • 由于没有了 anchor,所以 fcos 可方便拓展到其他任务。

Algorithm

网络结构

fcos.png

  • backbone + FPN 输出了 5 种尺度的 feature map 用于预测,由于是全卷积网络,所以 5 个输出头共享一份参数对于每个尺度的 feature map 上的每一个位置 预测包括类别(N,Cls,H,W)、框的位置(N,4,H,W)和一个中心置信度(N,1,H,W)。
  • 由于共享输出头,所以本文作者 为每个输出头增加了不共享的 scale 参数,scale.shape == (num_of_level, 1)
    fcos_2.png
  • 其中位置参数模型预测的是如上图所示的(l,t,b,r),即相对于 feature map 上的点到 GT 的上下左右偏移量。

centerness

  • centerness=min(l,r)max(l,r)×min(t,b)max(t,b)centerness=\sqrt{\frac{min(l^\star,r^\star)}{max(l^\star,r^\star)}\times \frac{min(t^\star,b^\star)}{max(t^\star,b^\star)}} ,即 GT bbox 内的点越靠近中心越大,越远离中心越小,取值范围 [0, 1],可视化 centerness 热力图如上图所示。
  • 最终预测时,score 阈值过滤的是 centerness * score

损失函数

L({px,y},{tx,y})=1Nposx,yLcls(px,y,cx,y)+λNposx,yIcx,y>0Lreg(tx,y,tx,y)+γNposx,yIcx,y>0Lctr(sx,y,sx,y)L(\{p_{x,y}\},\{t_{x,y}\})=\frac{1}{N_{pos}}\sum_{x,y}L_{cls}(p_{x,y},c^\star_{x,y})+\frac{\lambda}{N_{pos}}\sum_{x,y}\mathbb{I}_{c_{x,y}^\star>0}L_{reg}(t_{x,y},t^\star_{x,y})+\frac{\gamma}{N_{pos}}\sum_{x,y}\mathbb{I}_{c_{x,y}^\star>0}L_{ctr}(s_{x,y},s^\star_{x,y})

  • 其中 px,yp_{x,y} 表示在特征图点(x,y)处预测的每个类别的 score
  • cx,yc^\star_{x,y} 表示在特征图点(x,y)处的真实类别(负样本类别为 0
  • tx,yt_{x,y} 表示在特征图点(x,y)处预测的目标边界信息
  • sx,ys_{x,y} 表示在特征图点处预测的centerness
  • LclsL_{cls} 使用 focal loss 以平衡正负样本
  • LregL_{reg} 使用 GIOU loss且只对正样本计算
  • LctrL_{ctr} 使用 focal loss且只对正样本计算

正样本选择策略

  • anchor base 方法不同,fcos 对正样本的选择较为苛刻,仅当 feature map 上的某个点落入 gt bbox 中心区域(sub-box)时才被当做正样本
  • sub-box 的定义: (cxrs,cyrs,cx+rs,cy+rs)(c_x-rs,c_y-rs,c_x+rs,c_y+rs) ,其中 (cx,cy)(c_x,c_y) 表示 gt bbox 中心点在原始图上的坐标;s 表示 stride 即当前 feature map 相较于原图下采样倍数;r 表示 radius 半径超参数,在 coco 数据集上取 1.5
  • 除了正样本之外,其他样本的 cls 类别都被置为 0(background),负样本只计算 cls loss,不计算 reg losscenterness loss(也没法计算,有框才能计算)。

Ambiguous sample

  • anchor free 的检测方法绕不开一个天然的问题:如果一个 feature map 的特征点(x,y)同时是两个 GT bbox 的正例,应该如何预测,毕竟 fcos 每个特征点只预测一个框。
  • 本文缓解该问题的方法是:使用 FPN box 尺度分配 + center sampling
    • FPN bbox 尺度分配是一个常用的解决 Ambiguity 问题的方法,越大的 feature map 负责检测越小的框。(将 Ambiguity 出现的概率从 23.16% 降低到 7.24%
    • center sampling:即上面提到的 sub-box 采样方法,radius = 1.5。(将 Ambiguity 出现的概率从 7.24% 降低到 2.66%

Thought

  • FCOS 是一种很简单高效的 2D anchor free 物体检测算法,迁移性强,启发了后面的 FCOS3D 单目 3D 检测。

URL

TL;DR

  • 本文提出一种新的车道线检测范式,可以在极低的计算复杂度下精准预测车道线位置。
  • 与常见的使用语义分割算法实现车道线检测的范式不同,本文提出的车道线检测范式是将图片 ROI 区域分割成若干像素块,使用分类的方法判断像素块是否包含车道线。

Algorithm

算法思想

ufld.png

  • 将 ROI 区域(通常是一张图片的下半部分,上半部分是天空不包含车道线)分成若干 稀疏的行和稠密的列,论文给出的行数是 18 行 200 列
  • 模型预测每个小格子是否包含车道线,以及包含的车道线属于哪一个车道线实例(主流 benchmark 要求模型预测相邻的 4 条车道线:| |车| |)。
  • 对于 CULane 数据集,模型输出 shape == (N, 4, 18, 201),分别表示 18 行 200 列每个格子是否包含车道线(所以是 201 分类),以及包含的车道线的实例编号。
  • 加入了一个普通分割辅助任务头加速训练,推理时丢弃,不影响速度。
  • 另外除了分类交叉熵损失函数之外,本文加入了两个车道线相关的先验损失函数:
    • 基于车道线连续属性:每条车道线的第 i 行和第 i + 1 行应该具有相近的位置。

    • 基于车道线相对笔直属性:每条车道线点第 i 行和第 i + 1 行的连线应该和第 i + 1 行与第 i + 2 行的连线共线。

部署优化

  • 网络末尾使用的高维 FC 层对部署模型加速不利,使用 conv + pixelshuffle(depth to space) 可有效解决。

Thought

  • 辅助训练输出头是分割任务的标配
  • 结构先验损失函数貌似是个故事,作者开源代码中这两个 loss 的权重都是 0
  • 范式很好,可经过部署优化后上车

TL;DR

  • 《俄罗斯方块》这部电影里游戏作者用命令行玩俄罗斯方块原型机太酷了,所以决定自己实现一把
    tetris.png
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
import numpy as np
from func_timeout import FunctionTimedOut, func_set_timeout
from copy import deepcopy
import keyboard
from random import choice
def random_choice_block():
blocks = [
(
# . . . . . . [x][x] . . . . . .
# . . . . . . [x][x] . . . . . .
(
[0, space.shape[1] // 2 - 1],
[0, space.shape[1] // 2],
[1, space.shape[1] // 2 - 1],
[1, space.shape[1] // 2],
),
),
(
# . . . . . . [x][x] . . . . . .
# . . . . . . . [x][x] . . . . .
(
[0, space.shape[1] // 2 - 2],
[0, space.shape[1] // 2 - 1],
[1, space.shape[1] // 2 - 1],
[1, space.shape[1] // 2],
),
# . . . . . . [x] . . . . . .
# . . . . . [x][x] . . . . . .
# . . . . . [x] . . . . . . .
(
[0, space.shape[1] // 2],
[1, space.shape[1] // 2 - 1],
[1, space.shape[1] // 2],
[2, space.shape[1] // 2 - 1],
),
),
(
# . . . . . . [x][x] . . . . . .
# . . . . . [x][x] . . . . . . .
(
[0, space.shape[1] // 2],
[0, space.shape[1] // 2 + 1],
[1, space.shape[1] // 2 - 1],
[1, space.shape[1] // 2],
),
# . . . . . [x] . . . . . .
# . . . . . [x][x] . . . . . .
# . . . . . . [x] . . . . . . .
(
[0, space.shape[1] // 2 - 1],
[1, space.shape[1] // 2 - 1],
[1, space.shape[1] // 2],
[2, space.shape[1] // 2],
),
),
(
# . . . . . . [x][x][x][x] . . . . . .
(
[0, space.shape[1] // 2 - 2],
[0, space.shape[1] // 2 - 1],
[0, space.shape[1] // 2],
[0, space.shape[1] // 2 + 1],
),
# . . . . . . [x] . . . . . .
# . . . . . . [x] . . . . . .
# . . . . . . [x] . . . . . .
# . . . . . . [x] . . . . . .
(
[0, space.shape[1] // 2],
[1, space.shape[1] // 2],
[2, space.shape[1] // 2],
[3, space.shape[1] // 2],
),
),
(
# . . . . . . [x] . . . . . .
# . . . . . [x][x][x] . . . . .
(
[0, space.shape[1] // 2],
[1, space.shape[1] // 2 - 1],
[1, space.shape[1] // 2],
[1, space.shape[1] // 2 + 1],
),
# . . . . . . [x] . . . . . .
# . . . . . . [x][x] . . . . .
# . . . . . . [x] . . . . . .
(
[0, space.shape[1] // 2 - 1],
[1, space.shape[1] // 2 - 1],
[1, space.shape[1] // 2],
[2, space.shape[1] // 2 - 1],
),
# . . . . . [x][x][x] . . . . .
# . . . . . . [x] . . . . . .
(
[0, space.shape[1] // 2 - 1],
[0, space.shape[1] // 2],
[0, space.shape[1] // 2 + 1],
[1, space.shape[1] // 2],
),
# . . . . . . [x] . . . . . .
# . . . . . [x][x] . . . . .
# . . . . . . [x] . . . . . .
(
[0, space.shape[1] // 2],
[1, space.shape[1] // 2 - 1],
[1, space.shape[1] // 2],
[2, space.shape[1] // 2],
),
),
(
# . . . . . [x] . . . . . . .
# . . . . . [x][x][x] . . . . .
(
[0, space.shape[1] // 2 - 1],
[1, space.shape[1] // 2 - 1],
[1, space.shape[1] // 2],
[1, space.shape[1] // 2 + 1],
),
# . . . . . [x][x] . . . . . .
# . . . . . [x] . . . . . . .
# . . . . . [x] . . . . . . .
(
[0, space.shape[1] // 2 - 1],
[0, space.shape[1] // 2],
[1, space.shape[1] // 2 - 1],
[2, space.shape[1] // 2 - 1],
),
# . . . . . [x][x][x] . . . . .
# . . . . . . . [x] . . . . .
(
[0, space.shape[1] // 2 - 1],
[0, space.shape[1] // 2],
[0, space.shape[1] // 2 + 1],
[1, space.shape[1] // 2 + 1],
),
# . . . . . [x] . . . . . . .
# . . . . . [x] . . . . . . .
# . . . . [x][x] . . . . . . .
(
[0, space.shape[1] // 2],
[1, space.shape[1] // 2],
[2, space.shape[1] // 2 - 1],
[2, space.shape[1] // 2],
),
),
(
# . . . . . . . [x] . . . . .
# . . . . . [x][x][x] . . . . .
(
[0, space.shape[1] // 2 + 1],
[1, space.shape[1] // 2 - 1],
[1, space.shape[1] // 2],
[1, space.shape[1] // 2 + 1],
),
# . . . . . [x] . . . . . . .
# . . . . . [x] . . . . . . .
# . . . . . [x][x] . . . . . .
(
[0, space.shape[1] // 2 - 1],
[1, space.shape[1] // 2 - 1],
[2, space.shape[1] // 2 - 1],
[2, space.shape[1] // 2],
),
# . . . . . [x][x][x] . . . . .
# . . . . . [x] . . . . . . .
(
[0, space.shape[1] // 2 - 1],
[0, space.shape[1] // 2],
[0, space.shape[1] // 2 + 1],
[1, space.shape[1] // 2 - 1],
),
# . . . . [x][x] . . . . . . .
# . . . . . [x] . . . . . . .
# . . . . . [x] . . . . . . .
(
[0, space.shape[1] // 2 - 1],
[0, space.shape[1] // 2],
[1, space.shape[1] // 2],
[2, space.shape[1] // 2],
),
),
]
return choice(blocks)
def can_transform():
global block_idx, position
block = block_list[block_idx]
next_block = block_list[(block_idx + 1) % len(block_list)]
shift = (position[1][0] - block[1][0], position[1][1] - block[1][1])
next_position = np.zeros_like(position, dtype=np.int64)
for i, p in enumerate(next_block):
next_position[i][0] = p[0] + shift[0]
next_position[i][1] = p[1] + shift[1]
if next_position[:, 0].min() < 0:
next_position[:, 0] -= next_position[:, 0].min()
if next_position[:, 0].max() >= space.shape[0]:
next_position[:, 0] -= next_position[:, 0].max() - space.shape[0] + 1
if next_position[:, 1].min() < 0:
next_position[:, 1] -= next_position[:, 1].min()
if next_position[:, 1].max() >= space.shape[1]:
next_position[:, 1] -= next_position[:, 1].max() - space.shape[1] + 1
for p in next_position:
if space[p[0], p[1]] and list(p) not in position.tolist():
return False
for p in position:
space[p[0], p[1]] = False
position = next_position
block_idx = (block_idx + 1) % len(block_list)
return True
def transform():
if can_transform():
for p in position:
space[p[0], p[1]] = True
show_space()
def get_block():
global position
global block_list
global block_idx
global next_block_list
global next_block_idx
if position is not None:
block_list = next_block_list
block_idx = next_block_idx
else:
block_list = random_choice_block()
block_idx = choice([i for i in range(len(block_list))])
block = block_list[block_idx]
next_block_list = random_choice_block()
next_block_idx = choice([i for i in range(len(next_block_list))])
for p in block:
if space[p[0], p[1]]:
show_space()
return False
for p in block:
position = np.array(
block,
dtype=np.int64,
)
space[p[0], p[1]] = True
show_space()
return True
def can_down():
down_position = deepcopy(position)
down_position[:, 0] += 1
if down_position[:, 0].max() >= space.shape[0]:
return False
for dp in down_position:
if dp.tolist() not in position.tolist() and space[dp[0], dp[1]]:
return False
return True
def can_cancel_layer():
for i, line in enumerate(reversed(space)):
if line.sum() == len(line):
return len(space) - i - 1
return -1
def cancel_layer(layer_label):
global score
space[1 : layer_label + 1] = space[:layer_label]
space[0, :] = False
score += 100
def down():
if can_down():
for p in position:
space[p[0], p[1]] = False
position[:, 0] += 1
for p in position:
space[p[0], p[1]] = True
show_space()
def can_left():
left_position = deepcopy(position)
left_position[:, 1] -= 1
if left_position[:, 1].min() < 0:
return False
for lp in left_position:
if lp.tolist() not in position.tolist() and space[lp[0], lp[1]]:
return False
return True
def left():
if can_left():
for p in position:
space[p[0], p[1]] = False
position[:, 1] -= 1
for p in position:
space[p[0], p[1]] = True
show_space()
def can_right():
right_position = deepcopy(position)
right_position[:, 1] += 1
if right_position[:, 1].max() >= space.shape[1]:
return False
for rp in right_position:
if rp.tolist() not in position.tolist() and space[rp[0], rp[1]]:
return False
return True
def right():
if can_right():
for p in position:
space[p[0], p[1]] = False
position[:, 1] += 1
for p in position:
space[p[0], p[1]] = True
show_space()
def show_space():
print()
print("=" * 10 + "\tNEXT\t" + "=" * 10)
block = np.array(next_block_list[next_block_idx])
block[:, 1] -= block[:, 1].min()
next_block = np.zeros((4, 4), dtype=np.bool8)
for p in block:
next_block[p[0], p[1]] = True
for line in next_block:
s = ""
for item in line:
if item:
s += "[X]"
else:
s += " . "
print(s)
print()
print("=" * 10 + "\tGAME AREA\t" + "=" * 10)
for line in space:
s = ""
for item in line:
if item:
s += "[X]"
else:
s += " . "
print(s)
print("-" * 30 + f"\tSCORE: {score}\t" + "-" * 30)
def keyboard_callback(event: keyboard.KeyboardEvent):
if event.event_type == "down":
if event.name == "left":
left()
elif event.name == "right":
right()
elif event.name == "down":
down()
elif event.name == "up":
transform()
@func_set_timeout(1)
def listen_keyboard():
keyboard.hook(callback=keyboard_callback, suppress=True)
keyboard.wait()
def main():
while get_block():
while can_down():
try:
listen_keyboard()
except FunctionTimedOut:
down()
try:
listen_keyboard()
except FunctionTimedOut:
while can_cancel_layer() > -1:
cancel_layer(can_cancel_layer())
print(f"score: {score}\tgame over !!!")
if __name__ == "__main__":
space_shape = (20, 10)
score = 0
space = np.zeros(shape=space_shape, dtype=np.bool8)
position = None
main()

遇到的问题

  • python 监听字符读入(非 input,input 需要回车结束)好困难,所以该程序 必须用 root 用户下命令行运行…
  • python 的超时阻塞式监听更难,func_timeoutlinux 上运行疑似还有 bug:多线程打开文件但没有关闭,超出 OS limit,在玩十分钟可能才会出现…

Topic

  • 本文汇总多种 语义分割算法 decode head 结构和 部分分割专用 backbone,用于理解语义分割算法的演进过程
  • decode head 模型来源: mmsegmentaion decode head
  • 本文的语义分割 decode head 是指满足如下要求的网络结构:
    1. 输入为 backbone / neck 提取的 feature mapfeature map list
    2. 输出为 segmentation 结果

语义分割推理过程

1. 原始特征处理

  • 输入的原始特征包括两类:
    • backbone 输出的 feature map(例如 PSPNet 输出)
    • backbone 不同阶段 / neck (例如 FPN) 输出的不同尺度的 feature map list
  • 对于 feature map,可以 resize 到输出大小再送入 decode head,也可以直接送入 decode head,根据具体算法选择
  • 对于 feature map list,一般有两种做法,根据具体算法选择:
    1. resize concat: 将所有 feature map 全部 resize 到输出大小后再 concat(例如 FCN-8s
    2. multiple select: 根据 indexfeature map list 中索引并输出对应的 feature map sub list

2. 特征解码

  • 1 中输出的 feature map / feature map list 转化成与输出 宽高一致feature map,也是本文具体展开讲的内容

3. 特征映射到分割任务空间

  • 2 中输出的特征映射到分割空间,具体通道数与任务定义相关(例如:二分类的语义分割输出通道为 12N 分类的语义分割输出通道数为 N

演进过程

第一代:在 CNN 结构上创新

  • FCN: 2014年,出自 UC Berkeley,分割算法起点
  • PSP: 2016年,出自商汤,FCN + 多尺度
  • ASPP: 2017年,出自 GooglePSP 的优雅实现版(DeepLab V2DeepLab V3
  • FPN: 2018年,出自 FAIRUNet 多尺度的升级版
  • UperNet: 2018年,出自旷视,PSP + FPN 更暴力的多尺度
  • DepthwiseSeparableASPP: 2018年,出自 GoogleDeepLab V3 结构的小改动(DeepLab V3+
  • DepthwiseSeparableFCN: 2019年,出自东芝 + 剑桥,FCN 的轻量化改造(Fast-SCNN
  • PointRend: 2019年,出自 FAIR,在其他 decode head 基础上级联了一个 subnetwork 实现了图像分割边缘的细化

第二代:Self-Attention (Non-local / Channel Attention)

  • Non-Local: 2017年,出自 FAIRSelf Attention 经典
  • PSANet: 2018年,出自商汤,Non-local 的二维 狗尾续貂
  • CCNet: 2018年,出自地平线,Non-local 的低算力版,使用两个低算力的 Attention 替代 Non-local Attention
  • DANet: 2018年,出自京东,两路 Non-local,一路 attention to postion 一路 attention to channel
  • EncNet: 2018年,出自商汤 + Amazon,优化了 SENet 中的暴力编码方式,在分割任务中额外加入了分类辅助监督
  • EMANet: 2019年,出自北大,attention to channelattention to postion 可分离的 attention
  • ANN: 2019年,出自华中科技大学,简化 Non-local 同时引入 PPM,极大的降低了 matmulsoftmax 两类算子的耗时
  • GCNet: 2019年,出自 MSRA,简化版 Non-local + SENet 的缝合怪
  • OCRNet: 2019年,出自 MSRA,级联结构,在其他 decode head 的输出结果上做了 Self-Attention,并在论文中从 Transformer 角度解释了 Self-Attention(Transformer 开始觉醒)
  • APCNet: 2019年,出自商汤,复杂网络结构 + 简化矩阵乘实现的 Attention
  • DMNet: 2019年,出自商汤,根据输入特征的全局信息动态生成卷积核,本质也是 Attention
  • LRASPP: 2019年,出自 Google,全局 scale 实现的 AttentionMobileNet V3
  • ISANet: 2019年,出自 MSAR,使用 feature map shuffle 实现长范围和短范围的稀疏注意力机制
  • DNLNet: 2020年,出自 MSAR,改进 Non-local,加入了归一化和一元分支
  • BiSeNet: 2019年,出自旷视,在 backbone 之外加入了一个 context branch,将特征提取和 attention 解耦,降低了 attention 恐怖的计算量
  • BiSeNet V2: 2020年,出自腾讯,BiSeNet 的改进
  • SDTC: 2021年,出自美团,BiSeNet 系列的改进版,但由于融合了两路分支到一处,不再 Bilateral,所以用特征提取 SDTC block 命名…

第三代:Transformer

  • SETR: 2020年,出自腾讯,Vitbackbone + FCN / FPN decode head
  • DPT: 2021年,出自 IntelSETR 的升级版,backbone 不变,decode headFPN 了一些
  • Segmenter: 2021年,出自法国 INRIA 研究所,用了纯 Transformer 架构而不是像 SETR / DPT 一样用 Transformer Encoder + CNN Decoder 架构
  • SegFormer: 2021年,出自 NVIDIASETR 的高效版
  • KNet: 2021年,出自商汤,decode head 融合了 Channel Attention + Multi-head Attention + RNN,统一了语义分割、实例分割、全景分割框架

Algorithms

1. FCN

1.1 原始特征处理

  • 原始特征处理使用了 resize concat 方式,将多个不同尺度(backbone 不同阶段)的 feature map resize concat 到输出尺寸,如下图所示:
    FCN1.png
    FCN2.png

实验证明越多尺度融合分割效果越好

1.2 特征解码

  • 特征解码只使用了几层普通 Conv + 可选择的 concat inputshortcut)结构

2. PSP

2.1 原始特征处理

  • PSPNet 的原始特征是 backbone 最后一层的输出,所以无需原始特征处理

2.2 特征解码

  • PSPNet 将输入特征通过 Pyramid Pooling Module 结构做了 feature map 不同尺度 down sample + up sample,如下图所示:
    PSP.png

3. ASPP

3.1 原始特征处理

  • DeepLabV3 输入为单个单尺度 feature map,所以此步骤可省略

3.2 特征解码

ASPP.png

PSPNet 很像,PSPNet 是使用普通 Conv 去卷积多种尺度的 Pooled feature mapASPP 是不改变 feature map 而是使用 不同空洞系数的 Conv

4. FPN

5. UperNet

5.1 原始特征处理

  • 本算法在 decode head 中内嵌使用 FPN(而不是以网络 neck 方式使用),所以 feature map list 格式的原始特征无需处理,直接透传到特征解码部分

5.2 特征解码

UperNet.png

本文只讨论图中蓝色框部分
UperNet_2.png
只需要看蓝色框为输出的通路,算法:

  1. 在最小尺度 feature map 上使用 PPM(全称 Pyramid Pooling Module,来自于 PSPNet
  2. 使用 FPN 融合多尺度特征

6. DepthwiseSeparableASPP

相较于 DeepLab V3 在 8 倍下采样的 feature map 上使用 ASPP,DeepLab V3+ 在更小尺度(16 倍下采样) feature map 上使用 DepthwiseSeparable ASPP
同时为了解决小尺度预测的问题,加入了一个 vanilla FPN 做不同尺度特征融合

7. DepthwiseSeparableFCN

图中的 DWConv 是指 Depthwise Convic == oc == group
图中的 DSConv 是指 Depthwise Separable ConvDSConv 不是一个 Conv 而是 Depthwise ConvPointwise Convkernel_size == 1 and group == 1) 以及激活函数 / BN 一起组成的一个 block

8. PointRend

  • 渲染:渲染(render)是指在电脑中使用三维制作软件将制作的模型经过纹理、绑定、动画、灯光处理后得到模型和动画的图像。三维渲染是使用计算机从数字三维场景中生成二维影像的过程
  • 细分表面算法:细分表面算法(subdivision surface algorithm)在3D计算机图形中使用,通过递归完善基本级多边形网格来创建弯曲表面
    point2.png
  • 本文的核心思想:
    • 将计算机图形学中的 Subdivision render 思想用于分割,使用 coarse-to-fine 思想,逐级细分,提高分割效果
    • 使用非均匀采样方法,越高频的区域使用越多的采样点,提高边缘分割效果
      point3.png
      point4.png
  • Inference 过程(以 FCN 作为 prev_decode_head 为例):
    • 输入:
      • backbone 的输出 xshape = [batch, channels, height, width]
      • FCN 的输出 prev_outputshape = [batch, num_cls, height, width]
    • 输出:refine 后的输出,shape = [batch, num_cls, 2 * subdivision_steps * height, 2 * subdivision_steps * width]
    1. prev_output copy 一份作为 refined_seg_logits
    2. refined_seg_logits 插值放大两倍,shape = [batch, num_cls, 2 * height, 2 * width]
    3. refined_seg_logits 上挑选最 hardN 个点(hard 的定义是:如果一个像素的 top1_cls_logitistop2_cls_logits 越接近,则该点越 hard),输出相对坐标,shape = [batch, N, 2]
    4. 根据选出的 N 个点的坐标在 x 中找到对应的点(需要插值找出),作为 fine_grained_point_featsshape = [batch, channels, N]
    5. 根据选出的 N 个点的坐标在 prev_output 中找到对应的点(需要插值找出),作为 coarse_point_featsshape = [batch, num_cls, N]
    6. fine_grained_point_featscoarse_point_feats concat 后经过 Subnetwork(几层 MLP)映射到类别空间 point_logitsshape = [batch, num_cls, N]
    7. 根据 3 中的 point index,将 6 输出的 point_logits 替换到 1 中的 refined_seg_logits 对应位置
    8. 重复 2 ~ 7 subdivision_steps 次,输出最终的 refined_seg_logitsshape = [batch, num_cls, 2 * subdivision_steps * height, 2 * subdivision_steps * width]
  • Train 过程:
    • 输入:
      • backbone 的输出 xshape = [batch, channels, height, width]
      • FCN 的输出 prev_outputshape = [batch, num_cls, height, width]
      • gt_semantic_segshape = [batch, num_cls, height, width]
    • 输出:loss
    • Train 过程与 Inference 过程基本相同,区别在于:
      • 由于 topk 运算对梯度反向传播不友好,所以在 Train 的过程中使用随机采样点的策略,没有挖掘 hard case
      • Train 不会引入多尺度,只会在同一尺度学习 subnetworkpoint 的分类

9. Non-Local

用于 2 维图像,所以 T == 1,通过增加 (HW, HW) 的特征相关性矩阵给特征带来全局相关性(Attention

  • decode head 前后处理和 FCN 一致

10. PSANet

借鉴于 Non-local,强行给了比较牵强的数学解释,推理过程复杂到需要调用 CUDA 而不是使用 pure pytorch

11. CCNet

使用两个十字交叉注意力模块的串联替代 Non-local,降低算力
CC2.png
整体流程平平无奇

  • decode head 前后处理和 FCN 一致

12. DANet

13. EncNet

对于 SE-loss: 监督图中包含哪些类别的像素,使用交叉熵实现
对于 Encode:

  • 从本质上看:
    • 上图使用的 EncodeSENet (Squeeze and Excitation Network) 对 feature map per channel 编码没有区别
  • 从实现层面看:
    1. Encode 使用了更在数学上更好解释的编码方式(而不是 SENet 粗暴的 Global Average Pooling 编码方式)
    2. Encode 编码空间比 SENet 更大(SENet 每个通道使用 R\mathbb{R} 空间编码,Encode 每个通道使用 Rd\mathbb{R}^d 空间编码)

14. EMANet

15. ANN

key / value 上对特征进行了降维 N -> S,由下图可知,上图的 sample 方法具体是指 PPMPyramid Pooling Module
ANN2.png
AFNB 全称是 Asymmetric Fusion Non-local Block
APNB 全称是 Asymmetric Pyramid Non-local Block
二者对 Non-localSelf-Attention 进行简化,例如 share key value

16. GCNet

Non-local 结构的化简
GC.png
作者认为一个全局上下文建模结构如图 (a) 所示
图 (b) 为简化后的 Non-local 结构
图 © 是 SENet 结构
图 (d) 是本文提出的 GC 结构

  • decode head 前后处理和 FCN 一致

17. OCRNet

上图中粉红色的部分即为 OCRNet decode head
OCR2.png
论文中给出的算法架构图,给中间结果赋予了可解释的含义

18. APCNet

19. DMNet

之前的网络结构都是通过空洞卷积或大卷积核实现多尺度
DMNet 通过输入特征的 Adaptive Pooling 生成动态卷积核实现多尺度
DMNet.png

20. LRASPP

21. ISANet

  • 利用 feature map 重排实现长范围或短范围的稀疏注意力。

22. DNLNet

DNL 结构(图 d)在原始 Non-local 结构(图 a)上做了如下改动:

  1. 加入了一元 Non-local 分支 Unary Non-local
  2. 在二元分支矩阵乘之前加入了白化操作( H*W 维度减均值,相当于 instance norm
    DNL2.png
    由于减了均值,所以二元分支上 “+” 这一点在 Attention map RHW×HW\in \mathbb{R}^{HW\times HW} 上的索引 heat map RH×W\in \mathbb{R}^{H\times W} 变干净很多(相当只学习残差)
    这张图也从侧面反映了 Non-local 还是很强的,Attention 不是在讲故事

23. BiSeNet

  • backbone 主要分成两个分支 spatial pathcontext path,本质就是在基础 backbone 的基础上加入了一个计算量(通道数)非常小的 attention branch 增加上下文信息,最后融合两通道特征送入 decode head
  • decode head 就是基础的 FCN

24. BiSeNet V2

BiSeNet 主要改进有:

  • context branch 上增加了更多更复杂的模块,可更好收集上下文信息
  • context branch 上增加了更多监督,每个尺度上都有监督损失
  • 分支融合模块设计的更加复杂

25. SDTC

很新颖的 Loss 设计,效果和计算量都优于 BiSeNet 系列
SDTC2.png
这就是 SDTC 模块

26. SETR

  • 本质是 Vit(vision transformer)backboneFCN / 类似 FPNdecode head 的分割算法
  • 为了缩减计算量,Vit 会将原图剪成多个 patchworth 16x16 words...),每个 patch 单独输入到 24 层 Transformer Encoder 中,每个 patch 内部独立做全局 attention
  • patch 带来的问题是:与其他 CNN backbone + decode head 结构不同,Transformer backbone + decode head 结构中 decode head 需要顺序 inference 每个 patch feature(注意图a Decoder 输入为多个 patch feature),最后拼回到整张图大小
  • SETR_UPU decode head == sequence FCN
  • SETR_MLA decode head == sequence FPNAttention 不改变输入宽高,所以不存在严格意义上的 多尺度,只是不同网络深度的特征)

27. DPTNet

  • 本质和 SETR 一样都是使用 Vitbackbone
  • SETR 不同的地方在于:
    • 不同 backbone 深度特征融合方式更复杂,更接近 FPN
    • decode head 不再是输入 sequence patch feature,而是输入融合后的全图 feature

28. Segmenter

  • 用了纯 Transformer 架构(Transformer Encoder + Decoder),SETRDPT 都是 Transformer Encoder + CNN Decoder

29. SegFormer

  • backbone 不再是标准 Transformer Encoder(Vit),而是改成了更轻量化的 MixVisionTransformer(Mit)
    • Mit 使用了更大的 patchpatch 之间存在 overlap
    • Mit 使用了 coarse-to-fine 的特征表示,随着 Transformer Encoder 变深 feature map 宽高变小
    • Mit 使用了更简单的 Self-Attention 公式
    • Mit 去掉了 position embeding,使用了 Mix-FFN
  • decode head 使用了纯 MLP,且很自然的融合了多尺度(真.多尺度

30. KNet

从框架上统一了三种分割方式
KNet.png
红字标出的是每个张量的 shape
绿字标出的是每个计算过程实际是在做什么
上述过程会像 RNN 一样循环多次去更新 kernel,使得结果更好(重复使用 backbone 的输出)