Zhangzhe's Blog

The projection of my life.

0%

Paper Reading Note

URL

https://arxiv.org/pdf/1912.13200.pdf

TL;DR

  1. 使用-L1距离代替了Conv
  2. 因为-L1距离永远<=0,所以还是保留了bn,另外在梯度回传的时候加了HardTanh
  3. 实际训练很慢,收敛需要的epoch很多

Algorithm

模式匹配方式

  • 卷积本质上是一种模式匹配方式,但是模式匹配方式不是只有卷积,L1距离同样可以衡量模式的相似程度
  • L1距离只使用加法,理论速度比卷积快

卷积的数学表达(输入h*w*c)

  • Y(m,n,t)=i=0dj=0dk=0cinS(X(m+i,n+j,k),F(i,j,k,t))Y(m, n, t) = \sum_{i=0}^d\sum_{j=0}^d\sum_{k=0}^{c_{in}} S(X(m+i,n+j,k) , F(i,j,k,t))
    其中: S(x,y)=x×yS(x, y) = x \times y

-L1距离的数学表达

  • $Y(m, n, t) = -\sum_{i=0}d\sum_{j=0}d\sum_{k=0}^{c_{in}} |X(m+i,n+j,k) - F(i,j,k,t) | $

求导

  • 卷积是乘法和加法,乘法是交换门,也就是说输出对卷积核的梯度可以表示为:
    • Y(m,n,t)F(i,j,k,t)=X(m+i,n+j,k)\frac{\partial Y(m,n,t)}{\partial F(i,j,k,t)} = X(m+i,n+j,k)
    • Y(m,n,t)X(m+i,n+j,k)=F(i,j,k,t)\frac{\partial Y(m,n,t)}{\partial X(m+i,n+j,k)} = F(i,j,k,t)
  • -L1距离求导,由于绝对值函数梯度是分段函数,所以:
    • $ \frac{\partial Y(m,n,t)}{\partial F(i,j,k,t)} = sng(X(m+i,n+j,k) - F(i,j,k,t))$
    • $ \frac{\partial Y(m,n,t)}{\partial X(m+i,n+j,k)} = sng(F(i,j,k,t) - X(m+i,n+j,k))$
      其中: sng(x)={1,x<00,x=01,x>0sng(x) = \left\{\begin{matrix} -1, & x < 0\\ 0, & x = 0\\ 1, & x > 0 \end{matrix}\right.

优化

  • 分段函数求导麻烦,所以输出对卷积核的梯度,直接去掉符号函数,
    $ \frac{\partial Y(m,n,t)}{\partial F(i,j,k,t)} = X(m+i,n+j,k) - F(i,j,k,t)$ ,简单粗暴
  • 对于输出对feature的导数,论文中没有直接去掉符号函数,而是将符号函数换成了HardTanh函数,这个函数用在梯度上的时候还有一个大家熟悉的名字——梯度截断
    HardTanh(x)={1,x>1x,1<x<11,x<1HardTanh(x) = \left\{\begin{matrix} 1, & x > 1\\ x, & -1 < x < 1\\ -1, & x < -1 \end{matrix}\right.
    xx 表示梯度
  • 自此:反向传播时用到的梯度已经变形为:
    $ \frac{\partial Y(m,n,t)}{\partial F(i,j,k,t)} = X(m+i,n+j,k) - F(i,j,k,t)$
    $ \frac{\partial Y(m,n,t)}{\partial X(m+i,n+j,k)} = HardTanh(F(i,j,k,t) - X(m+i,n+j,k))$
  • 学习率:论文中详细的讲解了这个自适应学习率是如何推导出来的,如果想了解建议去看原文,这里直接说结论:
    αl=ηKΔ(Fl)2\alpha_l = \frac{\eta \sqrt K}{||\Delta(F_l)||_2}
    (论文第二版中这个 K\sqrt K 在分母位置,看了源码,还是以第三版为准)
    其中, KK 表示卷积核滤波器中元素的个数, ...2||...||_2 表示矩阵的F范数

优化部分一堆公式不够直观,直接上源码

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
class adder(Function):
@staticmethod
def forward(ctx, W_col, X_col):
ctx.save_for_backward(W_col, X_col)
# -L1距离,其中x_col是使用img2col处理过的
output = -(W_col.unsqueeze(2) - X_col.unsqueeze(0)).abs().sum(1)
return output
@staticmethod
def backward(ctx, grad_output):
W_col, X_col = ctx.saved_tensors
# 对卷积核的梯度,直接去掉了符号函数sgn
grad_W_col = (
(X_col.unsqueeze(0) - W_col.unsqueeze(2)) * grad_output.unsqueeze(1)
).sum(2)
# 这里是乘上推导出的自适应学习率,\eta=0.2
grad_W_col = (
grad_W_col
/ grad_W_col.norm(p=2).clamp(min=1e-12)
* math.sqrt(W_col.size(1) * W_col.size(0))
/ 5
)
# 梯度截断,也就是所谓的HardTanh
grad_X_col = (
-(X_col.unsqueeze(0) - W_col.unsqueeze(2)).clamp(-1, 1)
* grad_output.unsqueeze(1)
).sum(0)
return grad_W_col, grad_X_col

实际运行

  • 真的慢,可能是由于img2col还有求L1距离都是在python中做的,所以训练速度真的好慢啊!
  • 真的难收敛,官方源码中,使用resnet20(conv -> L1)训练CIFAR10,设置的默认epoch次数为400,训练CIFAR10都需要400 epoch
  • 真的不稳定,不像传统Conv网络随着训练次数,网络准确率逐步提高,或者在极小范围内抖动,AdderNet训练过程中,epoch 23结束后在val上准确率可能55%,第epoch 24结束后,val上的准确率直接到了28%,而且不是因为学习率太大导致的,抖动范围真的大…
  • 最终结果:AdderNet-ResNet20CIFAR-10epoch-400 最高正确率:91.67%

Thoughts

  • 虽然这个网络目前问题还很多,而且作者的理论很糙,有些地方简单粗暴,但是这也许是神经网络加速的一种思路

与CNN的对比

add1.png
add2.png
add3.png
add4.png

URL

https://arxiv.org/pdf/1611.10176

TL;DR

  • 提出一种基于DoReFa-Net的RNN量化方式

Algorithm

Dropout

  • 由于 RNN 本质是由多个 FC 组成,所以需要使用 Dropout 将一部分元素随机置0,但在量化神经网络中,0不在 Qk(X+0.5)0.5Q_k(X + 0.5)-0.5 ,所以需要将原始 DoReFa-Net 的量化范围: [0.5,0.5][0,1][-0.5, 0.5]\to [0, 1]

quantization of word embedding weights

  • 初始化
    WUniform(0,1)W\in Uniform(0, 1)
  • quantization
    W=Clip(W,0,1)W = Clip(W, 0, 1)

quantization of GRU

  • standard GRU
    zt=σ(Wz[ht1,xt])z_t = \sigma(W_z \cdot [h_{t-1},x_t])

    rt=σ(Wr[ht1,xt])r_t = \sigma(W_r \cdot [h_{t-1},x_t])

    h~t=tanh(W[rt×ht1,xt])\tilde h_{t}=tanh(W\cdot [r_t \times h_{t-1},x_t])

    $ h_t = (1-z_t)\times h_{t-1}+z_t\times \tilde{h_t}$
    其中:” \cdot “ 表示 matmul,” ×\times “ 表示 hadamard productσ\sigma 表示 sigmoid

  • quantization of GRU
    zt=σ(Wz[ht1,xt])z_t = \sigma(W_z \cdot [h_{t-1},x_t])

    rt=σ(Wr[ht1,xt])r_t = \sigma(W_r \cdot [h_{t-1},x_t])

    h~t=σ(W[Qk(rt×ht1),xt])\tilde h_{t}=\sigma(W\cdot [Q_k(r_t \times h_{t-1}), x_t])

    $ h_t = Q_k((1-z_t)\times h_{t-1}+z_t\times \tilde{h_t})$

    • 改进:
      • tanhsigmoid
      • W,Wz,WrW, W_z, W_r quantize 到 [-1, 1]
      • xtx_t quantize 到 [0, 1]
      • 量化函数 Qk()Q_k()

quantization of LSTM

  • standard LSTM
    ft=σ(Wf[ht1,xt]+bf)f_t = \sigma(W_f\cdot [h_{t-1},x_t]+b_f)

    it=σ(Wi[ht1,xt]+bi)i_t = \sigma(W_i\cdot [h_{t-1},x_t]+b_i)

    Ct~=tanh(WC[ht1,xt]+bi)\tilde{C_t} = tanh(W_C\cdot [h_{t-1},x_t]+b_i)

    Ct=ft×Ct1+it×Ct~C_t=f_t\times C_{t-1}+i_t\times \tilde{C_t}

    ot=σ(Wo[ht1,xt]+bo)o_t = \sigma(W_o\cdot [h_{t-1},x_t]+b_o)

    ht=ot×tanh(Ct)h_t=o_t \times tanh(C_t)

  • quantization of LSTM
    ft=σ(Wf[ht1,xt]+bf)f_t = \sigma(W_f\cdot [h_{t-1},x_t]+b_f)

    it=σ(Wi[ht1,xt]+bi)i_t = \sigma(W_i\cdot [h_{t-1},x_t]+b_i)

    Ct~=tanh(WC[ht1,xt]+bi)\tilde{C_t} = tanh(W_C\cdot [h_{t-1},x_t]+b_i)

    Ct=ft×Ct1+it×Ct~C_t=f_t\times C_{t-1}+i_t\times \tilde{C_t}

    ot=σ(Wo[ht1,xt]+bo)o_t = \sigma(W_o\cdot [h_{t-1},x_t]+b_o)

    ht=Qk(ot×σ(Ct))h_t=Q_k(o_t \times \sigma(C_t))

    • 改进:
      • tanhsigmoid
      • W,Wz,WrW, W_z, W_r quantize[-1, 1]
      • xtx_t quantize 到 [0, 1]
      • 量化函数 Qk()Q_k()

Thoughts

  • 目前芯片上都是将所有的线性操作吸到卷积的 affine_kaffine_b,所以需要解决 tanhsigmoid,一种最简单暴力的方式是分别 clip 到 [-1, 1][0, 1]

URL

https://openaccess.thecvf.com/content_cvpr_2017/papers/Cai_Deep_Learning_With_CVPR_2017_paper.pdf

TL;DR

  • 量化网络的反向传播通常使用STE,造成前后向结果与梯度不匹配,所以通常掉点严重
  • 前向过程中:HWGQ网络使用半波高斯分布量化函数Q代替sign函数
  • 反向过程中:HWGQ网络使用ReLU及其变体代替HardTanh来近似前向量化过程的梯度

Algorithm

  • weight binarization
    I×Wα(IB)I \times W \approx \alpha (I \oplus B)
    其中, BB 表示二值化权重, II 表示输入, αR+\alpha \in \mathbb{R^+} 表示缩放系数, \oplus 表示无系数卷积
  • 常用 activations 量化
    • 常用二值化特征量化函数:sign(x) = 1 if x >=0 else -1
    • 常用二值量化函数的梯度:hardTanh(x) = 1 if abs(x) <= 1 else 0
  • HWGQ activations 量化
    • 前向过程:
      Q(x)={qi,    if  x(ti,ti+1]0,     if  x0Q(x) = \begin{cases} q_i, \ \ \ \ if\ \ x\in (t_i, t_{i+1}] \\ 0, \ \ \ \ \ if \ \ x\le 0 \end{cases}
      qi+1qi=Δ,    iq_{i+1} - q_i = \Delta,\ \ \ \ \forall i
      Δ\Delta 为一个常数的时候, qiq_i 为均匀分布,但 tit_i 不是均匀分布
      其中超参数 Q(x)=argminQEx[(Q(x)x)2]=argminQp(x)(Q(x)x)2dxQ^{\star}(x) = arg\min_Q E_x[(Q(x) - x)^2] = arg\min_Q \int p(x) (Q(x) - x)^2 dx,其中 p(x)p(x) 表示半波高斯分布的概率密度
      • 看了源码发现:
        • 当 f_bits=2 时, q={0.538,1.076,1.614},   t={,0.807,1.345,+}q = \{0.538, 1.076, 1.614\},\ \ \ t=\{-\infty, 0.807, 1.345, +\infty\}
        • 当 f_bits=1 时, $q = {0.453, 1.51},\ \ \ t={-\infty, 0.97, +\infty} $
    • 反向过程:
      • 反向过程有三种方式,分别为:

        • ReLU
          Q~(x)={1,    if  x>00,    otherwise\tilde Q(x) = \begin{cases}1,\ \ \ \ if\ \ x > 0\\0,\ \ \ \ otherwise \end{cases}
        • Clipped ReLU
          Q~(x)={qm,    x>qm1,     if  x(0,qm]0,     otherwise\tilde Q(x) = \begin{cases}q_m,\ \ \ \ x>q_m\\1,\ \ \ \ \ if\ \ x \in (0, q_m]\\0,\ \ \ \ \ otherwise \end{cases}
          源码中,f_bits=2时, qm=1.614q_m = 1.614 ,f_bits=1时, qm=1.515q_m = 1.515
        • Log-tailed ReLU
          Q~(x)={1xτ,    x>qm1,     if  x(0,qm]0,     otherwise\tilde Q(x) = \begin{cases}\frac{1}{x-\tau},\ \ \ \ x>q_m\\1,\ \ \ \ \ if\ \ x \in (0, q_m]\\0,\ \ \ \ \ otherwise \end{cases}
      • 实际发现Clipped ReLU效果最好,复杂度低

Thoughts

  • 算法想法很本质,目的是减小STE带来的前向与反向的不一致
  • 没与DoReFa-Net的效果对比,当比特数较多的时候,round()与使用半波高斯近似那个更好?

图表

deep1.png
deep2.png
deep3.png

URL

http://openaccess.thecvf.com/content_cvpr_2018/papers/Wang_Two-Step_Quantization_for_CVPR_2018_paper.pdf

TL;DR

  • 对于 weight 的量化与对 activation 的量化如果同时学习,模型收敛比较困难,所以分成 code learningtransformation function learning 两个过程进行
  • code learning :先保持 weight 全精度,量化 activation
  • transformation function learning:量化 weight,学习 Al1AlA_{l-1} \to A_l 的映射
  • 最终结果:2-bits activations + 3 值 weights 的 TSQ 只比官方全精度模型准确率低0.5个百分点

Algorithm

传统量化网络

  • 优化过程
    minimize{Wl}   L(ZL,y)minimize_{\{W_l\}}\ \ \ \mathcal L(Z_L, y)

    subject to           W^l=QW(Wl)subject\ to \ \ \ \ \ \ \ \ \ \ \ \hat W_l = Q_W(W_l)

                                Zl^=W^lA^l1\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \hat {Z_l} = \hat W_l \hat A_{l-1}

                                Al=ψ(Zl)\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ A_l = \psi (Z_l)
                                A^l=Qn(A),   forl=1,2,...,L\ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \ \hat A_l = Q_n(A),\ \ \ for l=1,2,...,L

  • 难收敛的原因

    • 由于对 QW()Q_W()μLW\mu\frac{\partial L}{\partial W} 难以直接更新到 W^\hat W ,导致 WW 更新缓慢
    • QA()Q_A() 的 STE 会引起梯度高方差

Two-Step Quantization (TSQ)

  • step 1:code learning
    基于 HWGQ,不同点是:
    • weights 保持全精度
    • 引入超参数:稀疏阈值 ϵ0\epsilon \ge 0开源代码ϵ=0.32,δ=0.6487\epsilon = 0.32, \delta = 0.6487
      Qϵ(x)={qix(ti,ti+1]0xϵQ_{\epsilon}(x)=\left\{\begin{array}{ll}{q_{i}^{\prime}} & {x \in(t_{i}^{\prime}, t_{i+1}^{\prime}]} \\ {0} & {x \leq \epsilon}\end{array}\right.
    • 引入超参数的目的是使得网络更关注于 high activations
  • step 2:transformation function learning
    我对这个步骤的理解是:使用全精度weights网络蒸馏low-bits weights网络
    minimizeΛ,W^YQϵ(ΛW^X)F2=minimize{αi},{w^iT}iyiTQϵ(αiw^iTX)22\begin{aligned} \underset{\Lambda, \hat{W}}{\operatorname{minimize}} \left\|Y-Q_{\epsilon}(\Lambda \hat{W} X)\right\|_{F}^{2} = \operatorname{minimize}_{\left\{\alpha_{i}\right\},\left\{\hat{w}_{i}^{T}\right\}} \sum_{i}\left\|y_{i}^{T}-Q_{\epsilon}\left(\alpha_{i} \hat{w}_{i}^{T} X\right)\right\|_{2}^{2} \end{aligned}
    其中: αi\alpha_i 表示每个卷积核的缩放因子,XXYY 分别表示 A^l1\hat A_{l-1}A^l\hat A_l用全精度weights得到的量化activations网络蒸馏量化weights量化activations网络
    引入辅助变量 zz 对 transformation function learning 进行分解:
    minimizeα,w,zyQϵ(z)22+λzαXTw^22\underset{\alpha, w, z}{\operatorname{minimize}} \quad\left\|y-Q_{\epsilon}(z)\right\|_{2}^{2}+\lambda\left\|z-\alpha X^{T} \hat{w}\right\|_{2}^{2}
    • Solving α\alpha and w^\hat{w} with zz fixed:
      minimizeα,w^J(α,w^)=zαXTw^22\underset{\alpha, \hat{w}}{\operatorname{minimize}} \quad J(\alpha, \hat{w})=\left\|z-\alpha X^{T} \hat{w}\right\|_{2}^{2}
      J(α,w^)=zTz2αzTXTw^+α2w^TXXTw^J(\alpha, \hat{w})=z^{T} z-2 \alpha z^{T} X^{T} \hat{w}+\alpha^{2} \hat{w}^{T} X X^{T} \hat{w}
      α=zTXTw^w^TXXTw^\alpha^{*}=\frac{z^{T} X^{T} \hat{w}}{\hat{w}^{T} X X^{T} \hat{w}}
      w^=argmaxw^(zTXTw^)2w^TXXTw^\hat{w}^{*}=\underset{\hat{w}}{\operatorname{argmax}} \frac{\left(z^{T} X^{T} \hat{w}\right)^{2}}{\hat{w}^{T} X X^{T} \hat{w}}
    • Solving zz with α\alpha and w^\hat{w} fixed:
      minimizezi(yiQϵ(zi))2+λ(zivi)2\underset{z_{i}}{\operatorname{minimize}} \quad\left(y_{i}-Q_{\epsilon}\left(z_{i}\right)\right)^{2}+\lambda\left(z_{i}-v_{i}\right)^{2}
      czi(0)=min(0,vi){c}{z_{i}^{(0)}=\min \left(0, v_{i}\right)}
      zi(1)=min(M,max(0,λvi+yi1+λ){z_{i}^{(1)}=\min \left(M, \max \left(0, \frac{\lambda v_{i}+y_{i}}{1+\lambda}\right)\right.}
      zi(2)=max(M,vi){z_{i}^{(2)}=\max \left(M, v_{i}\right)}
      • 使用 Optimal TernaryWeights Approximation (OTWA) 初始化 α\alphaW^\hat W

        minα,w^ wαw^22      subject to    α>0,    w^{1,0,+1}m\min_{\alpha, \hat{w}} \ {\|w-\alpha \hat{w}\|_{2}^{2}}\ \ \ \ \ \ subject\ to\ \ \ \ \alpha>0, \ \ \ \ {\hat{w} \in\{-1,0,+1\}^{m}}
        α=wTw^w^Tw^\alpha^{*} =\frac{w^{T} \hat{w}}{\hat{w}^{T} \hat{w}}
        w^=argmaxw^(wTw^)2w^Tw^\hat{w}^{*} =\underset{\hat{w}}{\operatorname{argmax}} \frac{\left(w^{T} \hat{w}\right)^{2}}{\hat{w}^{T} \hat{w}}
        w^j={sign(wj)abs(wj) in top r of abs(w)0 others \hat{w}_{j}=\left\{\begin{array}{ll}{\operatorname{sign}\left(w_{j}\right)} & {\operatorname{abs}\left(w_{j}\right) \text { in top } r \text { of } a b s(w)} \\ {0} & {\text { others }}\end{array}\right.

      • α\alphaw^\hat{w} 初始值的计算过程 (OTWA)
        初始值的计算过程

Thoughts

  • 对 weights 的量化与 activations 的量化拆分是一个容易想到的简化量化问题的方法
  • 把对 weights 的量化转换成一种自蒸馏的方法,与 量化位宽 decay 有相似之处

URL

https://arxiv.org/pdf/2006.12030.pdf

TL;DR

  • 传统过参数化网络,在训练阶段能提高收敛速度并提高算法表现,但在推理阶段速度会变慢
  • 本文提出一种过参数化卷积 DO-Conv,在训练阶段有过参数化的优点——收敛速度快、算法表现好,在推理阶段将 DO-Conv 转化为标准卷积,不会带来任何额外耗时
  • DO-Conv 实际上是在训练阶段,将标准卷积拆分成标准卷积和Depthwise卷积的叠加;在推理阶段之前,将拆分后的标准卷积和Depthwise卷积再合并为一个标准卷积

Algorithm

何为过参数化?

  • 过参数化可以理解为:通过使用更多的参数,增大模型的假设空间,增加模型的表达能力(线性与非线性),从而加速训练甚至提高算法表现

标准卷积与Depthwise卷积的数学表示:

  • O=W×PO = W \times P
    • WRCout×(M×N)×CinW \in \mathbb{R}^{C_{out} \times (M \times N) \times C_{in}} ,表示 kernels,每一个 kernel.shape==(Cin,M,N)kernel.shape == (C_{in}, M, N) ,一共 CoutC_{out}kernel
    • PR(M×N)×CinP \in \mathbb{R}^{(M \times N) \times C_{in}} ,表示输入 feature mapkernel 覆盖的 patch,每一个 patch.shape==(Cin,M,N)patch.shape == (C_{in}, M, N)
    • ORCoutO \in \mathbb{R}^{C_{out}} ,表示卷积结果
      do1.png

Depthwise 卷积的数学表示:

  • O=WPO = W \circ P
    • WR(M×N)×Dmul×CinW \in \mathbb{R}^{(M \times N) \times D_{mul} \times C_{in}} ,表示 kernels,每一个 kernel.shape==(M,N)kernel.shape == ( M, N) ,每组(每个通道为一组) DmulD_{mul}kernel,一共 CinC_{in}
    • PR(M×N)×CinP \in \mathbb{R}^{(M \times N) \times C_{in}} ,表示输入 feature mapkernel 覆盖(所有通道)的 patch,每一个 patch.shape==(Cin,M,N)patch.shape == (C_{in}, M, N)
    • ORDmul×CinO \in \mathbb{R}^{D_{mul} \times C_{in}} ,表示卷积结果
      do2.png

DO-Conv 的数学表示:

  • O=(D,W)PO = (D, W) \star P
  • feature compositionO=W×(DP)O = W \times (D \circ P)
  • kernel compositionO=(DTW)×PO = (D^T \circ W ) \times P
    • WRCout×Dmul×CinW \in \mathbb{R}^{C_{out}\times D_{mul}\times C_{in}} ,表示 kernels_w,每一个 kernelw.shape== ( Cin,Dmul)kernel_w.shape ==\text{ ( }C_{in}, D_{mul}) ,一共CoutC_{out}kernel_w
    • DR(M×N)×Dmul×CinD \in \mathbb{R}^{(M\times N)\times D_{mul}\times C_{in}} ,表示 kernels_d,每一个 kerneld.shape==(M,N)kernel_d.shape == ( M, N) ,每组(每个通道为一组) DmulD_{mul}kernel_d,一共 CinC_{in}
    • PR(M×N)×CinP \in \mathbb{R}^{(M\times N)\times C_{in}} ,表示输入 feature mapkernel 覆盖(所有通道)的 patch,每一个 patch.shape==(Cin,M,N)patch.shape == (C_{in}, M, N)
    • ORCoutO \in \mathbb{R}^{C_{out}} ,表示卷积结果
      do3.png

这与Depthwise Separable 卷积有什么区别?

  • 相同点:
    • feature composition 可以看做是先对 feature mapDepthwise Conv ,再做标准卷积
  • 区别:
    • Depthwise Separable Conv 是`DO-Conv Dmul=1D_{mul} = 1 的特殊情况

DO-Conv 怎么能保证参数量比标准卷积多呢?

  • 标准卷积卷积核:
    • W1RCout×(M×N)×CinW_1 \in \mathbb{R}^{C_{out}\times (M\times N)\times C_{in}}
  • DO-Conv 卷积核:
    • W2RCout×Dmul×CinW_2 \in \mathbb{R}^{C_{out}\times D_{mul}\times C_{in}}
    • DR(M×N)×Dmul×CinD \in \mathbb{R}^{(M\times N)\times D_{mul}\times C_{in}}
  • Dmul=(M×N)D_{mul} = (M \times N) 时, W2=W1W2 = W1 ,此时 $ D + W_2 > W_1$ ,DO-Conv 拥有更多的参数量
  • 所以规定: Dmul(M×N)D_{mul} \ge (M \times N)

怎么能保证推理阶段不增加耗时呢?

  • 训练阶段 WD 都是可优化参数,所以模型会保存 WD
  • 推理阶段之前,使用 kernel compositionWD 处理, W=DTWW' = D^T \circ W ,然后就使用 WW' 去做标准卷积, W.shape==W1.shapeW'.shape == W_1.shape ,所以 inference 阶段不会增加任何耗时

DO-Conv 分组卷积 / Depthwise卷积

  • O=(D,W)PO = (D, W) \odot P
  • feature compositionO=W(DP)O = W \circ (D \circ P)
  • kernel compositionO=(DTWT)TPO = (D^T \circ W^T )^T \circ P

Thoughts

  • a = a1 * a2,能不能从数学角度证明学习 a1 * a2 比直接学习 a 更容易,效果更好?
  • 芯片上常常不支持 1 * 1 Conv,能否将输入的 (N2×Cin,H,W)(N^2 \times C_{in}, H, W) 使用 PixelShuffle 运算 reshape 成为 (Cin,N×H,N×W)(C_{in}, N \times H, N \times W) ,再使用 kernel.shape==(Cin,N,N),stride=Nkernel.shape == (C_{in},N, N), stride = N 的标准卷积去算?

DO-Conv 网络的实际表现

  • 在所有用到卷积的地方,无脑替换为 DO-Conv 基本都能涨点
    do4.png
    do5.png
    do6.png
    do7.png
    do8.png
    do9.png

URL

https://arxiv.org/pdf/2007.06191.pdf

TL;DR

  • 本文提出了一种新颖的卷积方式——PSConv,参数量与计算量都不变的情况下可以提高网络特征提取能力
  • PSConv的本质是一种dilated系数周期性变化的空洞卷积,周期性变化发生在 CinC_{in}CoutC_{out} 两个“正交”坐标系中

Algorithm

什么是PSConv

  • 标准卷积:
    Hc,x,y=k=1Cini=K12K12j=K12K12Gc,k,i,jFk,x+i,y+jH_{c,x,y} = \sum_{k=1}^{C_{in}}\sum_{i=-\frac{K-1}{2}}^{\frac{K-1}{2}}\sum_{j=-\frac{K-1}{2}}^{\frac{K-1}{2}} G_{c,k,i,j}F_{k,x+i,y+j}
    其中,F表示输入feature, FRCin×H×WF\in\mathbb{R}^{C_{in}\times H\times W} ,G表示kernel, GRCout×Cin×K×KG\in\mathbb{R}^{C_{out}\times C_{in}\times K\times K} ,H表示一个kernel卷积输出 HR1×H×WH\in\mathbb R^{1\times H\times W} ,i,j分表表示kernel中的点距离kernel中心在H于W方向上的偏移
  • 空洞卷积:
    Hc,x,y=k=1Cini=K12K12j=K12K12Gc,k,i,jFk,x+id,y+jdH_{c,x,y} = \sum_{k=1}^{C_{in}}\sum_{i=-\frac{K-1}{2}}^{\frac{K-1}{2}}\sum_{j=-\frac{K-1}{2}}^{\frac{K-1}{2}} G_{c,k,i,j}F_{k,x+id,y+jd}
    其中:id表示 偏移 i * 空洞系数 d
  • PSConv:
    Hc,x,y=k=1Cini=K12K12j=K12K12Gc,k,i,jFk,x+iD(c,k),y+jD(c,k)H_{c,x,y} = \sum_{k=1}^{C_{in}}\sum_{i=-\frac{K-1}{2}}^{\frac{K-1}{2}}\sum_{j=-\frac{K-1}{2}}^{\frac{K-1}{2}} G_{c,k,i,j}F_{k,x+iD(c,k),y+jD(c,k)}
    其中:i * D(c, k)表示 偏移 i * 空洞系数 D(c, k),D是空洞系数矩阵DRCout×CinD\in\mathbb R^{C_{out}\times C_{in}}D(i, j) 表示 第 i 个 kernel 第 j channel 的空洞系数 d

空洞系数矩阵D设计的基本法

  • D矩阵的一行表示一个kernel,kernel 的通道数为 CinC_{in}
  • PSConv 是将这 CinC_{in} 通道分为 P 个周期,每个周期的长度为 t=CinPt = \lceil \frac{C_{in}}{P}\rceil
  • 每个周期 t 个空洞系数 d={d1,d2,...,dt}d = \{d_1, d_2,...,d_t\} ,实验证明当 t=4,d={1,2,1,4}t=4,d=\{1,2,1,4\} 效果最好
  • 另外D矩阵的列上也要具有周期性,方法是让每一行相对与上一行偏移 1个相位(一个周期 t 个相位)

效果

  • 涨点,替换什么什么就涨点,原因是使用一种类似周期性金字塔kernel的方法,得到了非常有想象力的感受野
  • 虽然不增加参数量与计算量,但是train与inference都会变慢,所以作者附录中给出了一种优化实现方式,是将每个周期的相同相位合并到一起使用group Conv,然后再shuffle index,与ShuffleNet一个意思
  • 优化后确实会变快,可以将PS-ResNet-50/101在inference速度提升到与标准ResNet-50/101基本一致
  • 消融实验对比了只在 CinC_{in} 或者 只在CoutC_{out} 上周期性变化,发现哪个轴上周期变化都是必要的
  • 当 t = 1时,D中只会包含一种元素,此时PSconv退化为标准空洞卷积
  • PSConv 可以加入 group 参数,每个 group 内单独周期变化

Thoughts

  • 不增加计算量和参数量,且文章附录中提出经过优化实现可以将PS-ResNet-50/101在inference速度提升到与标准ResNet-50/101基本一致。
  • 如果在硬件设计中,预留了dilated Conv接口,并借鉴文中的优化实现方式,使用分组卷积+index shuffle减小filter通道间dilated不同的问题,这种白送的点也挺香的

图表

  • 标准卷积与PSConv (可以带group参数)
    ps1.png
  • PS group Conv
    ps2.png
  • 优化实现
    ps3.png
  • 搜索 t 与 d
    ps4.png
  • 横纵坐标周期消融实验
    ps5.png
  • 涨点涨点涨点…
    ps8.png
    ps9.png
    ps7.png
    ps6.png

URL

https://arxiv.org/pdf/2103.13425.pdf

TL;DR

  • 重参数化训练:模型的搜索空间更大,收敛更快,点更高,inference 前把多个算子拍成一个,inference 速度和未重参数化模型一样(白给的涨点)
  • 之前的重参数化工作大多都只有特定的一种或几种重参数模式,本文的重参数模式更丰富
  • repVGG 就是 DBB 的一种应用

Dataset/Algorithm/Model/Experiment Detail

dbb
以上六种训练时结构,在 inference 阶段都可拍成一个 Conv,且严格等价,具体的数学推导可以看原文

Thoughts

  • 这种工作我比较熟,毕竟和 neuwizard 也差不多嘛

URL

https://arxiv.org/pdf/2105.01601.pdf

TL;DR

  • 作者认为 CNN 与 Transformer 对视觉任务有利,但不是必要,MLP 也能在视觉任务中做的很好
  • 效果比 VIT 略差,但速度快

Algorithm

作者认为的 CV 的本质

  • 局部的特征提取 + 全局特征相关性的建模
  • CNN 天生对局部的特征提取很擅长,全局特征相关性的分析需要靠下采样 + 堆叠层来增大感受野完成
  • Transformer 与 CNN 相反,Transformation 很擅长全局特征相关性建模(关系矩阵),但局部特征提取比较弱,所以通常需要在超大数据集上预训练来获得局部先验
  • MLP 也是擅长全局特征相关性建模,对于不擅长的局部特征提取,使用 dim shuffle 交换 h*w 和 c,将全局特征提取变成针对 channel 的局部特征提取

网络结构

lm1

  • lecun 嘲讽
    lecun 嘲讽

Thoughts

  • 虽然 lecun 说的没错,但我想作者的意思是 MLP-mixer 架构不依赖于传统的 Conv 3*3 结构而是使用 matmul + dim shuffle 一样可以做到 局部特征提取 + 全局依赖
  • Lecun 的嘲讽翻译过来就是:“MLP 的本质是 Conv,Matmul 的本质是 Conv1d with 1*1 kernel”,这明显是抬杠…

实现代码(非伪代码)

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
from torch import nn
from functools import partial
from einops.layers.torch import Rearrange, Reduce
class PreNormResidual(nn.Module):
def __init__(self, dim, fn):
super().__init__()
self.fn = fn
self.norm = nn.LayerNorm(dim)
def forward(self, x):
return self.fn(self.norm(x)) + x
def FeedForward(dim, expansion_factor=4, dropout=0.0, dense=nn.Linear):
return nn.Sequential(
dense(dim, dim * expansion_factor),
nn.GELU(),
nn.Dropout(dropout),
dense(dim * expansion_factor, dim),
nn.Dropout(dropout),
)
def MLPMixer(
*,
image_size,
channels,
patch_size,
dim,
depth,
num_classes,
expansion_factor=4,
dropout=0.0
):
assert (image_size % patch_size) == 0, "image must be divisible by patch size"
num_patches = (image_size // patch_size) ** 2
chan_first, chan_last = partial(nn.Conv1d, kernel_size=1), nn.Linear
return nn.Sequential(
Rearrange(
"b c (h p1) (w p2) -> b (h w) (p1 p2 c)", p1=patch_size, p2=patch_size
),
nn.Linear((patch_size ** 2) * channels, dim),
*[
nn.Sequential(
PreNormResidual(
dim, FeedForward(num_patches, expansion_factor, dropout, chan_first)
),
PreNormResidual(
dim, FeedForward(dim, expansion_factor, dropout, chan_last)
),
)
for _ in range(depth)
],
nn.LayerNorm(dim),
Reduce("b n c -> b c", "mean"),
nn.Linear(dim, num_classes)
)

URL

https://arxiv.org/pdf/2105.01883.pdf

TL;DR

  • 本文提出一种 MLP 的重参数结构,训练过程中在 MLP 中构建多个 Conv branch,Inference 过程中,将多个 Conv branch 吸收到 MLP 中,不额外增加 MLP 的推理速度

Algorithm

总体结构

repmlp1

结构分解

  • Global Perceptron
    为了减小 MLP 恐怖的参数量,作者提出将 (N,C,H,W)(N,C,H,W) 的 feature map 通过:
    (N,C,H,W)(N,C,Hh,h,Ww,w)(N,Hh,Ww,C,h,w)(N×H×Wh×w,C,h,w)(N,C,H,W) \rightarrow (N, C, \frac{H}{h}, h,\frac{W}{w},w) \rightarrow (N, \frac{H}{h},\frac{W}{w},C,h,w) \rightarrow (\frac{N\times H\times W}{h\times w}, C, h,w)
    这种 Reshape -> Transpose -> Reshape 的方式缩小分辨率,这种方法的问题在于 patch 之间的关联关系被打破,所以需要一个作用于 Global 的 patch 权重,类似于 patch 版的 SENet,不同之处在于这里没有使用 SENetPer-channel Scale 而是只用了 Per-channel Bias
  • Partion Perceptron
    FC 本体,由于 MLP 的参数过于恐怖,所以这里的 FC 需要二次降参数,做法是 Group FC
  • Local Perceptron
    使用了多个 branch 的 Conv,提取 Global Perceptron 的输出 feature map,目的是局部特征提取,弥补 MLP 局部特征提取的短板,也就是题目的来源

Inference

  • Inference 时,重参数主要是两个方面:
    • FC 吸 BN
    • FC 吸 Conv

结果

  • 从消融实验来看,三种 Perceptron 中除了 Partion Perceptron 作为 FC 本体无法消融之外,重要程度:local Perceptron > Global Perceptron
  • 和 Wide ConvNet 对比 Flops 较小但精度较低,所以需要和 Conv 结合使用,结合方式是:
    repmlp2

Thoughts

  • 重参数过程中 Global Perceptron 只吸了一个 BN,感觉有点亏,而且 Global Perceptron 这个分支的存在让整个 RepMLP 变得没有像 RepVGG 一样优雅
  • 如果 Global Perceptron 的作用是建立 patch 之间的关联,可不可以把 (N,C,H,W)(N×H×Wh×w,C,h,w)(N,C,H,W) \rightarrow (\frac{N\times H\times W}{h\times w}, C, h,w) 这个过程变成类似 PixelShuffle upscale_factor < 1 的剪 patch 方法(可能效率会比文中方法低),每个 patch 信息分布相似,无需建立 patch 间的全局关系,是否可以省掉 Global Perceptron 分支,那样的话,Inference 过程就只有一个 FC 了

URL

https://arxiv.org/pdf/1904.04971.pdf

TL;DR

  • 一种动态卷积网络,每个 Conv 有多组 Weight 被称为多个 Expert,使用与 SENet 相似的方法产生每个 Expert 的权重向量,加权多个 Expert 产生最终的 Weight

Dataset/Algorithm/Model/Experiment Detail

结构

condconv

Thoughts

  • 因为 WeightNet 看了这篇,感觉和 SENet 差别太小了,SENet 是对输入 feature map 使用 GAP + FCs + Sigmoid 产生权重向量,权重向量作用于 Conv Weight 的 CinC_{in} 维度上;CondConv 使用同样的方法产生权重向量,权重向量作用于 Conv Weights 上。

pytorch 实现

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
import functools
import torch
from torch import nn
import torch.nn.functional as F
from torch.nn.modules.conv import _ConvNd
from torch.nn.modules.utils import _pair
from torch.nn.parameter import Parameter
class _routing(nn.Module):
def __init__(self, in_channels, num_experts, dropout_rate):
super(_routing, self).__init__()
self.dropout = nn.Dropout(dropout_rate)
self.fc = nn.Linear(in_channels, num_experts)
def forward(self, x):
x = torch.flatten(x)
x = self.dropout(x)
x = self.fc(x)
return F.sigmoid(x)
class CondConv2D(_ConvNd):
r"""Learn specialized convolutional kernels for each example.
As described in the paper
`CondConv: Conditionally Parameterized Convolutions for Efficient Inference`_ ,
conditionally parameterized convolutions (CondConv),
which challenge the paradigm of static convolutional kernels
by computing convolutional kernels as a function of the input.
Args:
in_channels (int): Number of channels in the input image
out_channels (int): Number of channels produced by the convolution
kernel_size (int or tuple): Size of the convolving kernel
stride (int or tuple, optional): Stride of the convolution. Default: 1
padding (int or tuple, optional): Zero-padding added to both sides of the input. Default: 0
padding_mode (string, optional): ``'zeros'``, ``'reflect'``, ``'replicate'`` or ``'circular'``. Default: ``'zeros'``
dilation (int or tuple, optional): Spacing between kernel elements. Default: 1
groups (int, optional): Number of blocked connections from input channels to output channels. Default: 1
bias (bool, optional): If ``True``, adds a learnable bias to the output. Default: ``True``
num_experts (int): Number of experts per layer
Shape:
- Input: :math:`(N, C_{in}, H_{in}, W_{in})`
- Output: :math:`(N, C_{out}, H_{out}, W_{out})` where
.. math::
H_{out} = \left\lfloor\frac{H_{in} + 2 \times \text{padding}[0] - \text{dilation}[0]
\times (\text{kernel\_size}[0] - 1) - 1}{\text{stride}[0]} + 1\right\rfloor
.. math::
W_{out} = \left\lfloor\frac{W_{in} + 2 \times \text{padding}[1] - \text{dilation}[1]
\times (\text{kernel\_size}[1] - 1) - 1}{\text{stride}[1]} + 1\right\rfloor
Attributes:
weight (Tensor): the learnable weights of the module of shape
:math:`(\text{out\_channels}, \frac{\text{in\_channels}}{\text{groups}},`
:math:`\text{kernel\_size[0]}, \text{kernel\_size[1]})`.
The values of these weights are sampled from
:math:`\mathcal{U}(-\sqrt{k}, \sqrt{k})` where
:math:`k = \frac{groups}{C_\text{in} * \prod_{i=0}^{1}\text{kernel\_size}[i]}`
bias (Tensor): the learnable bias of the module of shape (out_channels). If :attr:`bias` is ``True``,
then the values of these weights are
sampled from :math:`\mathcal{U}(-\sqrt{k}, \sqrt{k})` where
:math:`k = \frac{groups}{C_\text{in} * \prod_{i=0}^{1}\text{kernel\_size}[i]}`
.. _CondConv: Conditionally Parameterized Convolutions for Efficient Inference:
https://arxiv.org/abs/1904.04971
"""
def __init__(
self,
in_channels,
out_channels,
kernel_size,
stride=1,
padding=0,
dilation=1,
groups=1,
bias=True,
padding_mode="zeros",
num_experts=3,
dropout_rate=0.2,
):
kernel_size = _pair(kernel_size)
stride = _pair(stride)
padding = _pair(padding)
dilation = _pair(dilation)
super(CondConv2D, self).__init__(
in_channels,
out_channels,
kernel_size,
stride,
padding,
dilation,
False,
_pair(0),
groups,
bias,
padding_mode,
)
self._avg_pooling = functools.partial(F.adaptive_avg_pool2d, output_size=(1, 1))
self._routing_fn = _routing(in_channels, num_experts, dropout_rate)
self.weight = Parameter(
torch.Tensor(num_experts, out_channels, in_channels // groups, *kernel_size)
)
self.reset_parameters()
def _conv_forward(self, input, weight):
if self.padding_mode != "zeros":
return F.conv2d(
F.pad(input, self._padding_repeated_twice, mode=self.padding_mode),
weight,
self.bias,
self.stride,
_pair(0),
self.dilation,
self.groups,
)
return F.conv2d(
input,
weight,
self.bias,
self.stride,
self.padding,
self.dilation,
self.groups,
)
def forward(self, inputs):
b, _, _, _ = inputs.size()
res = []
for input in inputs:
input = input.unsqueeze(0)
pooled_inputs = self._avg_pooling(input)
routing_weights = self._routing_fn(pooled_inputs)
kernels = torch.sum(
routing_weights[:, None, None, None, None] * self.weight, 0
)
out = self._conv_forward(input, kernels)
res.append(out)
return torch.cat(res, dim=0)