[{"content":"对于经典的扩散模型DDPM，图像逆向生成推理过程中，我们需要一步步地将随机高斯噪声$\\mathbf{x}_T$还原成有意义的图像$\\mathbf{x}_0$，总共需要$T$次推理，生成速度较慢。为此，DDIM（Denoising Diffusion Implicit Models）采用更小的采样次数来加速推理过程，同时从随机噪音生成图像的过程并没有引入其它噪音，即逆向过程是一个确定的过程。DDIM与DDPM的优化目标是一致的，但并不要求前向过程是一个马尔可夫链。\n前提 DDPM的优化目标$L_t$以及采样过程$q(\\mathbf{x}_{t-1}\\vert\\mathbf{x}_t)$（由$p_\\theta(\\mathbf{x}_{t-1} \\vert \\mathbf{x}_t)$去近似）如下：\n$$ \\begin{aligned} L_t \u0026= \\mathbb{E}_{t \\sim [1, T], \\mathbf{x}_0, \\boldsymbol{\\epsilon}_t} \\Big[\\|\\boldsymbol{\\epsilon}_t - \\boldsymbol{\\epsilon}_\\theta(\\sqrt{\\bar{\\alpha}_t}\\mathbf{x}_0 + \\sqrt{1 - \\bar{\\alpha}_t}\\boldsymbol{\\epsilon}_t, t)\\|^2 \\Big] \\\\ p_\\theta(\\mathbf{x}_{t-1} \\vert \\mathbf{x}_t) \u0026= \\mathcal{N}(\\mathbf{x}_{t-1}; \\frac{1}{\\sqrt{\\alpha_t}} \\Big( \\mathbf{x}_t - \\frac{1 - \\alpha_t}{\\sqrt{1 - \\bar{\\alpha}_t}} \\boldsymbol{\\epsilon}_\\theta(\\mathbf{x}_t, t) \\Big), \\frac{1-\\bar\\alpha_{t-1}}{1-\\bar\\alpha_t}\\beta_t) \\\\ \\end{aligned} $$从上面两个式子可以得到DDPM的损失函数只依赖于$q(\\mathbf{x}_t\\vert\\mathbf{x}_0)$，而采样过程只依赖于$q(\\mathbf{x}_{t-1}\\vert\\mathbf{x}_t)$。DDIM中把DDPM公式推导用到的$q(\\mathbf{x}_{t}\\vert\\mathbf{x}_{t-1})$给去掉了，这意味着DDIM并没有去定义前向过程的具体操作。\n逆向过程 在DDPM中，我们借助贝叶斯定理将逆向过程转变为了前向过程：\n$$ q(\\mathbf{x}_{t-1}\\vert\\mathbf{x}_t,\\mathbf{x}_0) = \\frac{q(\\mathbf{x}_t\\vert\\mathbf{x}_{t-1},\\mathbf{x}_0)q(\\mathbf{x}_{t-1}\\vert\\mathbf{x}_0)}{q(\\mathbf{x}_t\\vert\\mathbf{x}_0)} $$整理后两边同时对$\\mathbf{x}_t$取积分可以把$q(\\mathbf{x}_t\\vert\\mathbf{x}_0)$给消除掉，最后得到下式：\n$$ \\begin{aligned} \\int q(\\mathbf{\\mathbf{x}_{t-1}\\vert\\mathbf{x}_t,\\mathbf{x}_0})q(\\mathbf{x}_t\\vert\\mathbf{x}_0)d\\mathbf{x}_t = q(\\mathbf{x}_{t-1}\\vert\\mathbf{x}_0) \\end{aligned} \\tag{1} $$上式即是扩散模型需要满足的边际分布条件。\n对于$q(\\mathbf{x}_{t-1}\\vert\\mathbf{x}_t,\\mathbf{x}_0)$，它是一个高斯分布，所以我们直接假设它为下面的形式：\n$$ \\begin{aligned} q(\\mathbf{x}_{t-1}\\vert\\mathbf{x}_t,\\mathbf{x}_0) = \\mathcal{N}(\\mathbf{x}_{t-1}; \\kappa_t\\mathbf{x}_t+\\lambda_t\\mathbf{x}_0,\\sigma^2_t\\mathbf{I}) \\end{aligned} \\tag{2} $$由于DDIM的优化目标与DDPM相同，所以我们可以复用DDPM的训练过程，即$q(\\mathbf{x}_{t-1}\\vert\\mathbf{x}_0)$与$q(\\mathbf{x}_t\\vert\\mathbf{x}_0)$，将这两个变量与(1)和(2)结合，可以得到下面式子：\n$$ \\begin{aligned} \\sqrt{\\overline\\alpha_{t-1}} \u0026= \\kappa_t\\sqrt{\\overline\\alpha_t} + \\lambda_t \\\\ 1-\\overline\\alpha_t \u0026= \\kappa_t^2(1-\\overline\\alpha_t)+\\sigma^2_t \\end{aligned} $$将$\\sigma_t$看作参数，则可以解出：\n$$ \\begin{aligned} \\kappa_t \u0026= \\frac{\\sqrt{1-\\overline\\alpha_t-\\sigma^2_t}}{1-\\overline\\alpha_t} \\\\ \\lambda_t \u0026= \\sqrt{\\overline\\alpha_{t-1}} - \\frac{\\sqrt{1-\\overline\\alpha_t-\\sigma^2_t}\\sqrt{\\overline\\alpha_t}}{\\sqrt{1-\\overline\\alpha_t}} \\end{aligned} $$代入(2)式可得：\n$$ \\begin{aligned} \u0026q(\\mathbf{x}_{t-1}\\vert\\mathbf{x}_t,\\mathbf{x}_0) \\\\ =\u0026 \\mathcal{N}(\\mathbf{x}_{t-1};\\sqrt{\\overline\\alpha_{t-1}}\\mathbf{x}_0+\\sqrt{1-\\overline\\alpha_t-\\sigma^2_t}\\frac{\\mathbf{x}_t-\\sqrt{\\overline\\alpha_t}\\mathbf{x}_0}{\\sqrt{1-\\overline\\alpha_t}}, \\sigma^2_t\\mathbf{I}) \\end{aligned} \\tag{3} $$关于(3)式更加详细的推导说明可以参考这篇博客，原论文中也有另外一种推导方法可以参考。\n由于我们已知$q(\\mathbf{x}_t\\vert\\mathbf{x}_0)$，即已知$\\mathbf{x}_t$和$\\mathbf{x}_0$的转换关系，由重参数法可以进一步地得到$\\mathbf{x}_{t}$到$\\mathbf{x}_{t-1}$的转换如下：\n$$ \\mathbf{x}_{t-1} = \\sqrt{\\overline\\alpha_{t-1}}\\frac{\\mathbf{x}_t-\\sqrt{1-\\overline\\alpha_t}\\epsilon_\\theta(\\mathbf{x}_t,t)}{\\sqrt{\\overline\\alpha_t}} + \\sqrt{1-\\overline\\alpha_{t-1}-\\sigma^2_t}\\cdot\\epsilon_\\theta(\\mathbf{x}_t,t) + \\sigma_t\\epsilon_t $$与DDPM一样，$\\epsilon_\\theta(\\mathbf{x}_t,t)$是由神经网络预测的噪音。\nDDIM作者将$\\sigma_t$定义为了下面形式：\n$$ \\begin{aligned} \\sigma_t \u0026= \\eta\\cdot\\sqrt{\\tilde{\\beta}_t} \\\\ \u0026= \\eta\\cdot\\sqrt{\\frac{1-\\overline\\alpha_{t-1}}{1-\\overline\\alpha_t}}\\sqrt{1-\\alpha_t} \\end{aligned} \\tag{4} $$当$\\eta$为1时，上式有$\\sigma^2_t = \\tilde\\beta_t$，即与DDPM的逆向过程一致。而当$\\eta=0$，此时的逆向过程没有随机噪音（$\\sigma_t\\epsilon_t$项为0），即最开始输入的随机噪音$\\mathbf{x}_T$确定时，逆向过程就是一个确定的过程，这种情况下的该模型就是DDIM。\n总体上看，DDIM与DDPM的训练过程是一样的，所以我们可以利用训练好的DDPM模型，接上DDIM的逆向过程来做推理。\n加速采样 DDIM依然是需要逐步地采样得到生成的图片，但它采样的序列$\\tau=[\\tau_1,\\tau_2,\\dots,\\tau_{len(\\tau)}]$是DDPM采样序列$[1,2,\\dots,T]$的任意子序列。假如我们用的是DDPM按$T$步采样训练好的模型，利用DDIM，我们可以在推理阶段将$T$步缩减为$len(\\tau)$步，达到加速推理的作用。\n参考 Denoising Diffusion Implicit Models\n扩散模型之DDIM\n生成扩散模型漫谈（四）：DDIM = 高观点DDPM\n","permalink":"https://simplestory.github.io/posts/20240817_d9d0d187039bec56d5fc1024c51a882c/","summary":"\u003cp\u003e对于经典的扩散模型DDPM，图像逆向生成推理过程中，我们需要一步步地将随机高斯噪声$\\mathbf{x}_T$还原成有意义的图像$\\mathbf{x}_0$，总共需要$T$次推理，生成速度较慢。为此，DDIM（Denoising Diffusion Implicit Models）采用更小的采样次数来加速推理过程，同时从随机噪音生成图像的过程并没有引入其它噪音，即逆向过程是一个确定的过程。DDIM与DDPM的优化目标是一致的，但并不要求前向过程是一个马尔可夫链。\u003c/p\u003e","title":"DDIM"},{"content":"这是一个diffusion model类型的图像生成模型，简单地概括就是通过在原图$\\mathbf{x}_0$上不断地叠加高斯噪声（一共$T$轮），最终可以将$\\mathbf{x}_0$变为纯高斯噪声$\\mathbf{x}_T$，而模型负责将$\\mathbf{x}_T$还原为$\\mathbf{x}_0$。图示如下：\n其中$\\mathbf{x}_0$到$\\mathbf{x}_T$的过程称为前向过程，而$\\mathbf{x}_T$到$\\mathbf{x}_0$的过程为逆向过程或推断过程。\n本文大部分内容参考自What are Diffusion Models。\n前向过程（forward） 输入图片$\\mathbf{x}_0 \\sim q(\\mathbf{x})$，进行$T$次高斯噪声的叠加得到$\\mathbf{x}_1$、$\\mathbf{x}_2$、$\\dots$、$\\mathbf{x}_T$。这个过程中$\\mathbf{x}_t$只与$\\mathbf{x}_{t-1}$有关，所以可以认为是一个马尔科夫过程。有下式：\n$$ \\begin{aligned} q(\\mathbf{x}_t \\vert \\mathbf{x}_{t-1}) \u0026= \\mathcal{N}(\\mathbf{x}_t; \\sqrt{1 - \\beta_t} \\mathbf{x}_{t-1}, \\beta_t\\mathbf{I}) \\\\ q(\\mathbf{x}_{1:T} \\vert \\mathbf{x}_0) \u0026= \\prod^T_{t=1} q(\\mathbf{x}_t \\vert \\mathbf{x}_{t-1}) \\end{aligned} $$其中$\\{\\beta_t\\in (0, 1)\\}^T_{t=1}$是给定的超参数，表示一系列高斯分布的方差。实际中$\\beta_t$会随着$t$的增大而增大。\n令$\\alpha_t = 1 - \\beta_t$，且$\\overline{\\alpha}_t = \\Pi_{i=1}^t\\alpha_i$。使用重参数化技巧有：\n$$ \\begin{aligned} \\mathbf{x}_t \u0026= \\sqrt{\\alpha_t}\\mathbf{x}_{t-1} + \\sqrt{1 - \\alpha_t}\\boldsymbol{\\epsilon}_{t-1} \\\\ \u0026= \\sqrt{\\alpha_t \\alpha_{t-1}} \\mathbf{x}_{t-2} + \\sqrt{1 - \\alpha_t \\alpha_{t-1}} \\bar{\\boldsymbol{\\epsilon}}_{t-2} \u0026 \\\\ \u0026= \\dots \\\\ \u0026= \\sqrt{\\bar{\\alpha}_t}\\mathbf{x}_0 + \\sqrt{1 - \\bar{\\alpha}_t}\\boldsymbol{\\epsilon} \\\\ q(\\mathbf{x}_t \\vert \\mathbf{x}_0) \u0026= \\mathcal{N}(\\mathbf{x}_t; \\sqrt{\\bar{\\alpha}_t} \\mathbf{x}_0, (1 - \\bar{\\alpha}_t)\\mathbf{I}) \\end{aligned} $$其中$\\boldsymbol{\\epsilon}_{t-1}, \\boldsymbol{\\epsilon}_{t-2}, \\dots \\sim \\mathcal{N}(\\mathbf{0}, \\mathbf{I})$，$\\bar\\epsilon_{t-2}$表示两个高斯分布的合并，即通过独立高斯分布的可加性来进行合并：\n$$ \\mathcal{N}(\\mathbf{0}, \\sigma_2^2\\mathbf{I}) + \\mathcal{N}(\\mathbf{0}, \\sigma_2^2\\mathbf{I}) \\sim \\mathcal{N}(\\mathbf{0}, (\\sigma^2_1+\\sigma^2_2)\\mathbf{I}) $$综上我们可以得到：\n$$ \\begin{aligned} \\mathbf{x}_t = \\sqrt{\\overline{\\alpha}_t}\\mathbf{x}_0 + \\sqrt{1-\\overline{\\alpha}_t}\\boldsymbol\\epsilon \\end{aligned} \\tag{1} $$即任意时刻的$\\mathbf{x}_t$可由$\\mathbf{x}_0$和$\\beta$表示。同时通过$(1)$式可以看到随着$t$增大，$\\beta_t$也会增大，$\\overline\\alpha_t$则越接近于0，所以$\\mathbf{x}_t$也越接近于标准的高斯分布$\\mathcal{N}(\\mathbf{0}, \\mathbf{I})$。\n逆向过程（reverse） 假如我们可以得到逆转后的分布$q(\\mathbf{x}_{t-1} \\vert \\mathbf{x}_t)$，就可以从完全的标准高斯分布$\\mathbf{x}_t\\sim\\mathcal{N}(\\mathbf{0}, \\mathbf{I})$还原出$\\mathbf{x}_0$。注意当$\\beta_t$足够小时，$q(\\mathbf{x}_{t-1}\\vert\\mathbf{x}_t)$仍然为高斯分布，但通常我们无法简单地推断出$q(\\mathbf{x}_{t-1}\\vert\\mathbf{x}_t)$，所以我们使用深度学习模型（以$\\theta$为参数，目前主流结构为Unet+Attention）去预测一个分布$p_\\theta$来近似分布$q(\\mathbf{x}_{t-1}\\vert\\mathbf{x}_t)$。\n$$ \\begin{aligned} p_\\theta(\\mathbf{x}_{0:T}) \u0026= p(\\mathbf{x}_T) \\prod^T_{t=1} p_\\theta(\\mathbf{x}_{t-1} \\vert \\mathbf{x}_t) \\\\ p_\\theta(\\mathbf{x}_{t-1} \\vert \\mathbf{x}_t) \u0026= \\mathcal{N}(\\mathbf{x}_{t-1}; \\boldsymbol{\\mu}_\\theta(\\mathbf{x}_t, t), \\boldsymbol{\\Sigma}_\\theta(\\mathbf{x}_t, t)) \\end{aligned} $$通过上式可以得知，为了得到分布$p_\\theta$，需要获得$\\boldsymbol{\\mu}_\\theta(\\mathbf{x}_t, t)$和$\\boldsymbol{\\Sigma}_\\theta(\\mathbf{x}_t, t)$。\n对于逆转后的分布$q(\\mathbf{x}_{t-1}\\vert\\mathbf{x}_t)$，将$\\mathbf{x}_0$作为已知条件加入，有：\n$$ q(\\mathbf{x}_{t-1} \\vert \\mathbf{x}_t, \\mathbf{x}_0) = \\mathcal{N}(\\mathbf{x}_{t-1}; {\\tilde{\\boldsymbol{\\mu}}}(\\mathbf{x}_t, \\mathbf{x}_0), {\\tilde{\\beta}_t} \\mathbf{I}) $$这里的方差用$\\tilde{\\beta_t}$是因为DDPM论文做了简化，将$\\boldsymbol{\\Sigma}_\\theta(\\mathbf{x}_t, t)$直接取值为$\\tilde{\\beta_t}$。\n由贝叶斯公式可得：\n$$ q(\\mathbf{x}_{t-1}\\vert\\mathbf{x}_t,\\mathbf{x}_0) = \\frac{q(\\mathbf{x}_t\\vert\\mathbf{x}_{t-1},\\mathbf{x}_0)q(\\mathbf{x}_{t-1}\\vert\\mathbf{x}_0)}{q(\\mathbf{x}_t\\vert\\mathbf{x}_0)} $$即将逆向过程全部转换为了前向过程。代入高斯分布的密度函数可得：\n$$ \\begin{aligned} \u0026 q(\\mathbf{x}_{t-1} \\vert \\mathbf{x}_t, \\mathbf{x}_0) \\\\ = \u0026 \\frac{q(\\mathbf{x}_t\\vert\\mathbf{x}_{t-1},\\mathbf{x}_0)q(\\mathbf{x}_{t-1}\\vert\\mathbf{x}_0)}{q(\\mathbf{x}_t\\vert\\mathbf{x}_0)} \\\\ \\propto \u0026 \\exp\\Big( -\\frac{1}{2} \\big( (\\frac{\\alpha_t}{\\beta_t} + \\frac{1}{1 - \\bar{\\alpha}_{t-1}}) \\mathbf{x}_{t-1}^2 - (\\frac{2\\sqrt{\\alpha_t}}{\\beta_t} \\mathbf{x}_t + \\frac{2\\sqrt{\\bar{\\alpha}_{t-1}}}{1 - \\bar{\\alpha}_{t-1}} \\mathbf{x}_0) \\mathbf{x}_{t-1} + C(\\mathbf{x}_t, \\mathbf{x}_0) \\big) \\Big) \\end{aligned} $$通常的高斯分布$\\mathcal{N}(\\mu, \\sigma^2)$密度函数如下：\n$$ \\begin{aligned} f(x) \u0026= \\frac{1}{\\sqrt{2\\pi}\\sigma}\\exp(-\\frac{(x-\\mu)^2}{2\\sigma^2}) \\\\ \u0026=\\frac{1}{\\sqrt{2\\pi}\\sigma}\\exp(-\\frac{1}{2}(\\frac{1}{\\sigma^2}x^2-\\frac{2\\mu}{\\sigma^2}x+\\frac{\\mu^2}{\\sigma^2})) \\end{aligned} $$对照上式，有：\n$$ \\begin{aligned} \\frac{1}{\\sigma^2} \u0026= \\frac{1}{\\tilde{\\beta}_t} = \\frac{\\alpha_t}{\\beta_t}+\\frac{1}{1-\\bar\\alpha_{t-1}} \\\\ \u0026\\Rightarrow \\tilde{\\beta}_t = \\frac{1-\\bar\\alpha_{t-1}}{1-\\bar\\alpha_t}\\cdot\\beta_t \\\\ \\frac{2\\mu}{\\sigma^2} \u0026= \\frac{2\\sqrt{\\alpha_t}}{\\beta_t} \\mathbf{x}_t + \\frac{2\\sqrt{\\bar{\\alpha}_{t-1}}}{1 - \\bar{\\alpha}_{t-1}} \\mathbf{x}_0 \\\\ \u0026\\Rightarrow \\tilde\\mu_t(\\mathbf{x}_t, \\mathbf{x}_0) = \\frac{\\sqrt{\\alpha_t}(1-\\bar\\alpha_{t-1})}{1-\\bar\\alpha_t}\\mathbf{x}_t+\\frac{\\sqrt{\\bar\\alpha_{t-1}}\\beta_t}{1-\\bar\\alpha_t}\\mathbf{x}_0 \\end{aligned} $$可知方差$\\tilde{\\beta}_t$是一个定量，而均值$\\tilde\\mu_t$与$\\mathbf{x}_t$和$\\mathbf{x}_0$有关。由$(1)$式可得$\\mathbf{x}_0 = \\frac{1}{\\sqrt{\\bar{\\alpha}_t}}(\\mathbf{x}_t - \\sqrt{1 - \\bar{\\alpha}_t}\\boldsymbol{\\epsilon}_t)$，代入上式有：\n$$ \\begin{aligned} \\tilde{\\boldsymbol{\\mu}}_t =\\frac{1}{\\sqrt{\\alpha_t}} \\Big( \\mathbf{x}_t - \\frac{1 - \\alpha_t}{\\sqrt{1 - \\bar{\\alpha}_t}} \\boldsymbol{\\epsilon}_t \\Big) \\end{aligned} \\tag{2} $$其中高斯分布$\\boldsymbol{\\epsilon}_t$在模型中则为预测的噪声分布，为$\\boldsymbol{\\epsilon}_\\theta(\\mathbf{x}_t, t)$，则有：\n$$ \\begin{aligned} \\boldsymbol{\\mu}_\\theta(\\mathbf{x}_t, t) =\\frac{1}{\\sqrt{\\alpha_t}} \\Big( \\mathbf{x}_t - \\frac{1 - \\alpha_t}{\\sqrt{1 - \\bar{\\alpha}_t}} \\boldsymbol{\\epsilon}_\\theta(\\mathbf{x}_t, t) \\Big) \\end{aligned} \\tag{3} $$综合上面的逆向过程，我们可以得到模型推断的步骤如下：\n通过$\\mathbf{x}_t$和$t$由模型预测出高斯噪声$\\boldsymbol{\\epsilon}_\\theta(\\mathbf{x}_t, t)$，之后由$(3)$式得到$\\boldsymbol{\\mu}_\\theta(\\mathbf{x}_t, t)$ 计算方差$\\Sigma_\\theta(\\mathbf{x}_t, t)$。DDPM用$\\tilde\\beta_t$来代替$\\Sigma_\\theta(\\mathbf{x}_t, t)$，且认为$\\beta_t$与$\\tilde\\beta_t$近似 由$p_\\theta(\\mathbf{x}_{t-1} \\vert \\mathbf{x}_t) = \\mathcal{N}(\\mathbf{x}_{t-1}; \\boldsymbol{\\mu}_\\theta(\\mathbf{x}_t, t), \\boldsymbol{\\Sigma}_\\theta(\\mathbf{x}_t, t))$以及重参数法可以得到$\\mathbf{x}_{t-1}$ DDPM的推断流程如下：\n训练过程（training） 上面提到我们使用模型去预测一个分布$p_\\theta(\\mathbf{x}_{t-1}\\vert\\mathbf{x}_t)$来近似表达逆转后的分布$q(\\mathbf{x}_{t-1}\\vert\\mathbf{x}_t)$。针对该问题一般有两种方法，一种是优化负对数的最大似然概率$-\\log p_\\theta(\\mathbf{x}_0)$；另一种是优化预测分布与真实分布的交叉熵$\\mathbb{E}_{q(\\mathbf{x}_0)}[-\\log p_\\theta(\\mathbf{x}_0)]$。两种方法直接进行优化都比较困难，所以考虑去优化它们的变分下限（VLB）。\n由于KL散度非负，故有：\n$$ -\\log p_\\theta(\\mathbf{x}_0) \\leq -\\log p_\\theta(\\mathbf{x}_0) + D_{KL}(q(\\mathbf{x}_{1:T}\\vert\\mathbf{x}_0)\\vert\\vert p_\\theta(\\mathbf{x}_{1:T}\\vert\\mathbf{x}_0)) $$将KL散度展开化简，并对式子两边同时取期望$\\mathbb{E}_{q(\\mathbf{x}_0)}$可得：\n$$ \\begin{aligned} \\mathcal{L}_{VLB} \u0026= \\mathbb{E}_{q(\\mathbf{x}_0)}\\Big( -\\log p_\\theta(\\mathbf{x}_0) + \\mathbb{E}_{q(\\mathbf{x}_{1:T}\\vert\\mathbf{x}_0)}\\Big[\\log\\frac{q(\\mathbf{x}_{1:T}\\vert\\mathbf{x}_0)}{p_\\theta(\\mathbf{x}_{0:T})} + \\log p_\\theta (\\mathbf{x}_0)\\Big]\\Big)\\\\ \u0026= \\mathbb{E}_{q(\\mathbf{x}_0)}\\Big( \\mathbb{E}_{q(\\mathbf{x}_{1:T}\\vert\\mathbf{x}_0)}\\Big[\\log\\frac{q(\\mathbf{x}_{1:T}\\vert\\mathbf{x}_0)}{p_\\theta(\\mathbf{x}_{0:T})}\\Big]\\Big) \\\\ \u0026= \\mathbb{E}_{q(\\mathbf{x}_{0:T})}\\Big[\\log\\frac{q(\\mathbf{x}_{1:T}\\vert\\mathbf{x}_0)}{p_\\theta(\\mathbf{x}_{0:T})}\\Big] \\\\ \u0026\\geq \\mathbb{E}_{q(\\mathbf{x}_0)}[-\\log p_\\theta(\\mathbf{x}_0)] \\end{aligned} $$上面式子的第二个等号用的是Fubini定理，也可由Jensen不等式得到一样的结果：\n$$ \\begin{aligned} \u0026 \\mathbb{E}_{q(\\mathbf{x}_0)}[-\\log p_\\theta(\\mathbf{x}_0)] \\\\ =\u0026 -\\mathbb{E}_{q(\\mathbf{x}_0)}\\Big[\\log\\Big(p_\\theta(\\mathbf{x}_0)\\cdot\\int p_\\theta(\\mathbf{x}_{1:T})d\\mathbf{x}_{1:T}\\Big)\\Big] \\\\ =\u0026 -\\mathbb{E}_{q(\\mathbf{x}_0)}\\Big[\\log\\Big(\\int p_\\theta(\\mathbf{x}_{0:T})d\\mathbf{x}_{1:T}\\Big)\\Big] \\\\ =\u0026 -\\mathbb{E}_{q(\\mathbf{x}_0)}\\Big[\\log\\Big(\\int q(\\mathbf{x}_{1:T}\\vert\\mathbf{x}_0)\\frac{p_\\theta(\\mathbf{x}_{0:T})}{q(\\mathbf{x}_{1:T}\\vert\\mathbf{x}_0)}d\\mathbf{x}_{1:T}\\Big)\\Big] \\\\ =\u0026 -\\mathbb{E}_{q(\\mathbf{x}_{0:T})}\\Big[\\log\\Big(\\mathbb{E}_{q(\\mathbf{x}_{1:T}\\vert\\mathbf{x}_0)}\\frac{p_\\theta(\\mathbf{x}_{0:T})}{q(\\mathbf{x}_{1:T}\\vert\\mathbf{x}_0)}\\Big)\\Big] \\\\ \\leq \u0026 -\\mathbb{E}_{q(\\mathbf{x}_{0:T})}\\Big[\\log\\frac{p_\\theta(\\mathbf{x}_{0:T})}{q(\\mathbf{x}_{1:T}\\vert\\mathbf{x}_0)}\\Big] \\\\ =\u0026 \\mathbb{E}_{q(\\mathbf{x}_{0:T})}\\Big[\\log\\frac{q(\\mathbf{x}_{1:T}\\vert\\mathbf{x}_0)}{p_\\theta(\\mathbf{x}_{0:T})}\\Big] = \\mathcal{L}_{VLB} \\end{aligned} $$注意$\\log$为凹函数，$-\\log$为凸函数。\n故最终得到下式：\n$$ \\mathcal{L}_{VLB} = \\mathbb{E}_{q(\\mathbf{x}_{0:T})}\\Big[\\log\\frac{q(\\mathbf{x}_{1:T}\\vert\\mathbf{x}_0)}{p_\\theta(\\mathbf{x}_{0:T})}\\Big] \\geq \\mathbb{E}_{q(\\mathbf{x}_0)}[-\\log p_\\theta(\\mathbf{x}_0)] $$所以我们最小化$\\mathbf{L}_{VLB}$即可最小化目标损失函数$\\mathbb{E}_{q(\\mathbf{x}_0)}[-\\log p_\\theta(\\mathbf{x}_0)]$。\n对$\\mathbf{L}_{VLB}$进行推导有：\n$$ \\begin{aligned} \\mathcal{L}_{VLB} \u0026= \\mathbb{E}_{q(\\mathbf{x}_{0:T})} \\Big[ \\log\\frac{q(\\mathbf{x}_{1:T}\\vert\\mathbf{x}_0)}{p_\\theta(\\mathbf{x}_{0:T})} \\Big] \\\\ \u0026= \\mathbb{E}_q \\Big[ \\log\\frac{\\prod_{t=1}^T q(\\mathbf{x}_t\\vert\\mathbf{x}_{t-1})}{ p_\\theta(\\mathbf{x}_T) \\prod_{t=1}^T p_\\theta(\\mathbf{x}_{t-1} \\vert\\mathbf{x}_t) } \\Big] \\\\ \u0026= \\mathbb{E}_q \\Big[ -\\log p_\\theta(\\mathbf{x}_T) + \\sum_{t=1}^T \\log \\frac{q(\\mathbf{x}_t\\vert\\mathbf{x}_{t-1})}{p_\\theta(\\mathbf{x}_{t-1} \\vert\\mathbf{x}_t)} \\Big] \\\\ \u0026= \\mathbb{E}_q \\Big[ -\\log p_\\theta(\\mathbf{x}_T) + \\sum_{t=2}^T \\log \\frac{q(\\mathbf{x}_t\\vert\\mathbf{x}_{t-1})}{p_\\theta(\\mathbf{x}_{t-1} \\vert\\mathbf{x}_t)} + \\log\\frac{q(\\mathbf{x}_1 \\vert \\mathbf{x}_0)}{p_\\theta(\\mathbf{x}_0 \\vert \\mathbf{x}_1)} \\Big] \\\\ \u0026= \\mathbb{E}_q \\Big[ -\\log p_\\theta(\\mathbf{x}_T) + \\sum_{t=2}^T \\log \\Big( \\frac{q(\\mathbf{x}_{t-1} \\vert \\mathbf{x}_t, \\mathbf{x}_0)}{p_\\theta(\\mathbf{x}_{t-1} \\vert\\mathbf{x}_t)}\\cdot \\frac{q(\\mathbf{x}_t \\vert \\mathbf{x}_0)}{q(\\mathbf{x}_{t-1}\\vert\\mathbf{x}_0)} \\Big) + \\log \\frac{q(\\mathbf{x}_1 \\vert \\mathbf{x}_0)}{p_\\theta(\\mathbf{x}_0 \\vert \\mathbf{x}_1)} \\Big] \\\\ \u0026= \\mathbb{E}_q \\Big[ -\\log p_\\theta(\\mathbf{x}_T) + \\sum_{t=2}^T \\log \\frac{q(\\mathbf{x}_{t-1} \\vert \\mathbf{x}_t, \\mathbf{x}_0)}{p_\\theta(\\mathbf{x}_{t-1} \\vert\\mathbf{x}_t)} + \\sum_{t=2}^T \\log \\frac{q(\\mathbf{x}_t \\vert \\mathbf{x}_0)}{q(\\mathbf{x}_{t-1} \\vert \\mathbf{x}_0)} + \\log\\frac{q(\\mathbf{x}_1 \\vert \\mathbf{x}_0)}{p_\\theta(\\mathbf{x}_0 \\vert \\mathbf{x}_1)} \\Big] \\\\ \u0026= \\mathbb{E}_q \\Big[ -\\log p_\\theta(\\mathbf{x}_T) + \\sum_{t=2}^T \\log \\frac{q(\\mathbf{x}_{t-1} \\vert \\mathbf{x}_t, \\mathbf{x}_0)}{p_\\theta(\\mathbf{x}_{t-1} \\vert\\mathbf{x}_t)} + \\log\\frac{q(\\mathbf{x}_T \\vert \\mathbf{x}_0)}{q(\\mathbf{x}_1 \\vert \\mathbf{x}_0)} + \\log \\frac{q(\\mathbf{x}_1 \\vert \\mathbf{x}_0)}{p_\\theta(\\mathbf{x}_0 \\vert \\mathbf{x}_1)} \\Big]\\\\ \u0026= \\mathbb{E}_q \\Big[ \\log\\frac{q(\\mathbf{x}_T \\vert \\mathbf{x}_0)}{p_\\theta(\\mathbf{x}_T)} + \\sum_{t=2}^T \\log \\frac{q(\\mathbf{x}_{t-1} \\vert \\mathbf{x}_t, \\mathbf{x}_0)}{p_\\theta(\\mathbf{x}_{t-1} \\vert\\mathbf{x}_t)} - \\log p_\\theta(\\mathbf{x}_0 \\vert \\mathbf{x}_1) \\Big] \\\\ \u0026= \\mathbb{E}_q [\\underbrace{D_\\text{KL}(q(\\mathbf{x}_T \\vert \\mathbf{x}_0) \\parallel p_\\theta(\\mathbf{x}_T))}_{L_T} + \\sum_{t=2}^T \\underbrace{D_\\text{KL}(q(\\mathbf{x}_{t-1} \\vert \\mathbf{x}_t, \\mathbf{x}_0) \\parallel p_\\theta(\\mathbf{x}_{t-1} \\vert\\mathbf{x}_t))}_{L_{t-1}} \\underbrace{- \\log p_\\theta(\\mathbf{x}_0 \\vert \\mathbf{x}_1)}_{L_0} ] \\end{aligned} $$即将$\\mathcal{L}_{VLB}$转为多个熵或KL散度的和的形式。最后由下面式子表示：\n$$ \\begin{aligned} \\mathcal{L}_{VLB} \u0026= L_T + L_{T-1} + \\dots + L_0 \\\\ \\text{where } L_T \u0026= D_\\text{KL}(q(\\mathbf{x}_T \\vert \\mathbf{x}_0) \\parallel p_\\theta(\\mathbf{x}_T)) \\\\ L_t \u0026= D_\\text{KL}(q(\\mathbf{x}_t \\vert \\mathbf{x}_{t+1}, \\mathbf{x}_0) \\parallel p_\\theta(\\mathbf{x}_t \\vert\\mathbf{x}_{t+1})) \\text{ for }1 \\leq t \\leq T-1 \\\\ L_0 \u0026= - \\log p_\\theta(\\mathbf{x}_0 \\vert \\mathbf{x}_1) \\end{aligned} $$前向过程$q$并没有可供学习的参数，且$\\mathbf{x}_T$为纯高斯噪声，所以$L_T$可以视为常量。$L_t$可以看作是拉近两个高斯分布的距离，由多元高斯分布的KL散度求解有：\n$$ \\mathcal{L}_{VLB} = \\mathbb{E}_q\\Big[\\frac{1}{2{\\vert\\vert\\Sigma_\\theta(\\mathbf{x}_t, t)\\vert\\vert}^2_2}{\\vert\\vert\\tilde\\mu_t(\\mathbf{x}_t, \\mathbf{x}_0)-\\mu_\\theta(\\mathbf{x}_t, t)\\vert\\vert}^2\\Big] + C $$其中$C$与模型参数无关，将$(1), (2), (3)$代入上式，可得：\n$$ \\begin{aligned} L_t \u0026= \\mathbb{E}_{\\mathbf{x}_0, \\boldsymbol{\\epsilon}} \\Big[\\frac{1}{2 \\| \\boldsymbol{\\Sigma}_\\theta(\\mathbf{x}_t, t) \\|^2_2} \\| \\tilde{\\boldsymbol{\\mu}}_t(\\mathbf{x}_t, \\mathbf{x}_0) - \\boldsymbol{\\mu}_\\theta(\\mathbf{x}_t, t) \\|^2 \\Big] \\\\ \u0026= \\mathbb{E}_{\\mathbf{x}_0, \\boldsymbol{\\epsilon}} \\Big[\\frac{1}{2 \\|\\boldsymbol{\\Sigma}_\\theta \\|^2_2} \\| \\frac{1}{\\sqrt{\\alpha_t} \\Big( \\mathbf{x}_t - \\frac{1 - \\alpha_t}{\\sqrt{1 - \\bar{\\alpha}_t}} \\boldsymbol{\\epsilon}_t \\Big)} - \\frac{1}{\\sqrt{\\alpha_t}} \\Big( \\mathbf{x}_t - \\frac{1 - \\alpha_t}{\\sqrt{1 - \\bar{\\alpha}_t} \\boldsymbol{\\boldsymbol{\\epsilon}}_\\theta(\\mathbf{x}_t, t) \\Big)} \\|^2 \\Big] \\\\ \u0026= \\mathbb{E}_{\\mathbf{x}_0, \\boldsymbol{\\epsilon}} \\Big[\\frac{ (1 - \\alpha_t)^2 }{2 \\alpha_t (1 - \\bar{\\alpha}_t) \\| \\boldsymbol{\\Sigma}_\\theta \\|^2_2} \\|\\boldsymbol{\\epsilon}_t - \\boldsymbol{\\epsilon}_\\theta(\\mathbf{x}_t, t)\\|^2 \\Big] \\\\ \u0026= \\mathbb{E}_{\\mathbf{x}_0, \\boldsymbol{\\epsilon}} \\Big[\\frac{ (1 - \\alpha_t)^2 }{2 \\alpha_t (1 - \\bar{\\alpha}_t) \\| \\boldsymbol{\\Sigma}_\\theta \\|^2_2} \\|\\boldsymbol{\\epsilon}_t - \\boldsymbol{\\epsilon}_\\theta(\\sqrt{\\bar{\\alpha}_t}\\mathbf{x}_0 + \\sqrt{1 - \\bar{\\alpha}_t}\\boldsymbol{\\epsilon}_t, t)\\|^2 \\Big] \\end{aligned} $$上式可以看出，模型训练的重点就是优化两个高斯噪声$\\boldsymbol\\epsilon$和$\\boldsymbol\\epsilon_\\theta$之间的MAE。\n对于$L_0$，DDPM针对$p_\\theta(\\mathbf{x}_0\\vert\\mathbf{x}_1)$设计了一个离散化的分段积分累计，同时将$\\mathcal{L}_{VLB}$进行化简（忽略了系数），有：\n$$ \\begin{aligned} L_t^\\text{simple} \u0026= \\mathbb{E}_{t \\sim [1, T], \\mathbf{x}_0, \\boldsymbol{\\epsilon}_t} \\Big[\\|\\boldsymbol{\\epsilon}_t - \\boldsymbol{\\epsilon}_\\theta(\\mathbf{x}_t, t)\\|^2 \\Big] \\\\ \u0026= \\mathbb{E}_{t \\sim [1, T], \\mathbf{x}_0, \\boldsymbol{\\epsilon}_t} \\Big[\\|\\boldsymbol{\\epsilon}_t - \\boldsymbol{\\epsilon}_\\theta(\\sqrt{\\bar{\\alpha}_t}\\mathbf{x}_0 + \\sqrt{1 - \\bar{\\alpha}_t}\\boldsymbol{\\epsilon}_t, t)\\|^2 \\Big] \\\\ \\Rightarrow \u0026 L_{simple} = L^{simple}_t + C \\end{aligned} $$如前面所提，DDPM并没有去计算$\\Sigma_\\theta(\\mathbf{x}_t, t)$，而是用一个不可训练的$\\beta_t$或$\\tilde\\beta_t$。所以DDPM的训练流程大致如下：\n获取$\\mathbf{x}_0$，从$1\\dots T$中随机采样一个$t$ 从标准高斯分布随机采样一个噪声$\\epsilon_t \\sim \\mathcal{N}(\\mathbf{0}, \\mathbf{I})$ 最小化$\\|\\boldsymbol{\\epsilon}_t - \\boldsymbol{\\epsilon}_\\theta(\\sqrt{\\bar{\\alpha}_t}\\mathbf{x}_0 + \\sqrt{1 - \\bar{\\alpha}_t}\\boldsymbol{\\epsilon}_t, t) \\|$ DDPM的训练流程图如下：\nAppendix A. 重参数（reparameterization） 通常从某一个分布中随机采样一个样本的这个过程并不能进行反向传播。重参数使用了一个独立的随机变量将随机性引导出去。\n假设$\\mathbf{z}$服从一个期望为$\\mu_\\theta$，方差为$\\sigma_\\theta^2$的高斯分布。重随机采样用重参数可写成如下形式：\n$$ \\mathbf{z} = \\mu_\\theta + \\sigma_\\theta \\odot \\epsilon, \\quad \\epsilon\\sim\\mathcal{N}(\\mathbf{0}, \\mathbf{I}) $$其中$\\odot$表示逐元素相乘。上式将$\\mathbf{z}$的随机性转移到了$\\epsilon$上，同时满足$\\mathbf{z}\\sim \\mathcal{N}(\\mathbf{z}; \\mu_\\theta, \\sigma^2_\\theta\\mathbf{I})$，并且这个过程可导。\nB. Jensen不等式 $x$为一随机变量，且$\\phi$为凸函数，则有下式成立：\n$$ \\phi(E(x)) \\leq E(\\phi(x)) $$若$x$为离散随机变量，则可写为：$\\phi(\\frac{1}{n}\\sum x)\\leq\\frac{1}{n}\\sum\\phi(x)$\nC. KL散度 分布$p(x)$和$q(x)$的KL散度记为$D_{KL}(p\\|q)$，有：\n$$ D_{KL}(p\\|q) = E_{p(x)}[\\log\\frac{p(x)}{q(x)}] $$由Jensen不等式可以推导出KL散度恒大于0：\n$$ \\begin{aligned} D_{KL}(p\\|q) \u0026= E_{p(x)}[\\log\\frac{p(x)}{q(x)}] \\\\ \u0026= E_{p(x)}[-\\log\\frac{q(x)}{p(x)}] \\\\ \u0026\\ge-\\log\\Big( E_{p(x)}[\\frac{q(x)}{p(x)}] \\Big) \\\\ \u0026=-\\log\\Big(\\int p(x)\\frac{q(x)}{p(x)} dx\\Big) \\\\ \u0026=-\\log\\Big(\\int q(x)dx\\Big) \\\\ \u0026=-\\log(1) = 0 \\end{aligned} $$参考 Denoising Diffusion Probabilistic Models\nWhat are Diffusion Models\n由浅入深了解Diffusion Model\n一文解释Diffusion Model DDPM\n扩散模型之DDPM\n","permalink":"https://simplestory.github.io/posts/20240817_60a1868ea6b6f7b05126264f9d212a9c/","summary":"\u003cp\u003e这是一个\u003cstrong\u003ediffusion model\u003c/strong\u003e类型的图像生成模型，简单地概括就是通过在原图$\\mathbf{x}_0$上不断地叠加高斯噪声（一共$T$轮），最终可以将$\\mathbf{x}_0$变为纯高斯噪声$\\mathbf{x}_T$，而模型负责将$\\mathbf{x}_T$还原为$\\mathbf{x}_0$。图示如下：\u003c/p\u003e","title":"DDPM"},{"content":"以往的diffusion model（DM）一般会在全图范围内进行操作，这样在运行DM模型所需的显存会变得非常大。而latent diffusion model（LDM）则是将输入通过预训练好的autoencoder encoder映射到潜在空间，在潜在空间上进行DM操作，最后得到的输出再通过autoencoder decoder部分映射回原图空间。作者还通过cross attention的方式将限制条件引入DM中，从而实现可控的图像生成。\nAutoencoder Autoencoder是一类神经网络，采用无监督的方式进行学习。训练方式是生成的图片与原图做比较，即关注的是模型重建的能力，AE模型也不具备生成能力。该网络包含两部分：\nEncoder：将原始high-dimension输入数据转换为latent low- dimension code。输入尺寸大于输出尺寸。 Decoder：将上面Encoder得到的latent low-dimension code恢复为原始的输入数据。 在LDM中，作者为了避免latent空间很可能出现的大方差使用了两种可选的正则化方式$\\text{KL-reg}$和$\\text{VQ-reg}$。损失函数使用的是感知损失函数（perceptual loss）以及包含对抗训练过程的Discriminator loss。\nKL-reg：该惩罚项下的自编码模型称为AutoencoderKL，可以说是标准的VAE（带KL损失）。该模型是通过对学习的隐变量添加一个较小的KL惩罚，以使隐变量接近标准正态分布。 VQ-reg：这个自编码模型可以视为VQ-GAN，但是向量量化层（vector quantized layer）被放进decoder中。 Loss function LDM中的感知损失是利用可学习的感知图像块相似度（LPIPS，Learned Perceptual Image Patch Similarity）来衡量两张图片之间的差别。而Discriminator loss则是借鉴了taming transformer的训练过程。\nLPIPS 在计算图像相似度中，传统方式如L2、SSIM和PSNR与人类认为的会有较大的出入，特别是当图片平滑时，传统方式大概率会失效。作者使用了深度学习网络特征来度量图像相似度，并通过大量的实验验证了该设计的有效性。这里作者引入了两个新的评价指标：\n2AFC（two alternative forced choice）：给实验员一张原始图像与两张经过变化的图像，然后判断哪一张更接近原始图像。这种评价指标有个问题就是实验员可能会有意识地选择自己关注哪些方面的相似度。 JND（just noticeable difference）：先后给实验员两张图片，每张展示1秒，中间有250毫秒的间隔，然后让实验员判断两张图片是否相同。只让实验员判断两张图是否相似一定程度上避免了两个图像都部分相似的时候实验员选择的主观性。 LPIPS的结构如下图所示：\n将两个patch $\\mathbf{x}$和$\\mathbf{x}_0$分别输入到特征提取模型$\\mathcal{F}$中，$\\mathcal{F}$可以是VGG、AlexNet等网络（在LDM中是VGG-16） 提取$\\mathcal{F}$中不同层（准确说应该是不同阶段）的输出并沿channel维度做归一化得到$\\hat{y}^l, \\hat{y}^l \\in \\mathbb{R}^{H_l\\times W_l\\times C_l}$，其中$l$表示提取的是第$l$层 使用$w^l\\in\\mathbb{R}^{C_l}$对$\\hat{y}^l$和$\\hat{y}^l$按channel进行缩放，之后计算它们的L2距离。最后取平均值即可得到结果 公示描述如下：\n$$ d(x,x_0)=\\sum_l\\frac{1}{H_lW_l}\\sum_{h,w}\\vert\\vert w_l\\odot(\\hat{y}^l_{hw} - \\hat{y}^l_{0hw})\\vert\\vert_2^2 $$当对$\\forall l$，有$w_l=1$，上式可以看为余弦距离。\n训练时如上图，主要是把两个距离作为小型网络$\\mathcal{G}$的输入计算得分$\\hat{h}\\in(0,1)$，然后用交叉熵损失进行训练，图中的$h$即是作者引入的新指标，这里用的是2AFC。\n在LPIPS论文中，作者给出了三种模型训练方式：\nlin: 固定$\\mathcal{F}$预训练权重，只训练$w$权重 tune: 加载$\\mathcal{F}$预训练权重，并和$w$一起进行训练 scratch: 使用随机高斯分布来初始化模型权重，重新开始训练 Discriminator loss 使用PatchGAN的判别器获取图片patch级别的真实性评价并利用对抗损失函数进行优化。参考VQ-GAN，其损失函数大致可以表示如下：\n$$ L_D = [\\log(D(x)) + \\log(1-D(\\hat{x}))] $$其中$D$是判别器，$x$是真实样本，$\\hat{x}$则为生成样本。这部分的详细内容可以参考VQ-GAN。\nConditoinal Denoiser DM部分与DDPM类似，也是由UNet来预测噪声。Latent Diffusion Model和Stable Diffusion都采用了带time-conditional的UNet结构来预测每一步的噪声，但官方文件并没有给出详细的UNet结构图。这里参考了另外一篇论文，获得time-conditional UNet的大致结构如下：\n注意：该结构是基于SD2.1版本的，且使用的条件约束是text condition。跟SD1.5相比，主要更改的点是将text encoder从CLIP ViT-L/14替换为OpenCLIP-ViT/H，其余基本一致。\n从上图可看出，与原始UNet相比，主要是将原本的卷积结构替换为了Cross Attention Down Block，Down Block，Cross Attention Mid Block，Up Block，Cross Attention Up Block模块以及添加了time embedding和condition embedding（在上图中为text embedding）作为额外输入。\n额外输入 首先是time embedding。依据diffusion model的前向过程，我们会有一个时间步（timestep），而UNet的目标是预测前向过程中每个timestep所添加的随机噪声，所以timestep对UNet来说是一个必要的信息。这里参考了transformer对timestep做了正弦位置编码（Sinusoidal Positional Embedding），之后经过两层线性层得到time embedding。\ncondition embedding则需要依据不同类型的条件约束使用不同的网络提取特征得到condition embedding。如图中是文本类型，对应使用的模型是OpenCLIP。\n额外模块 对于Cross Attention Down Block和Cross Attention Up Block模块。其中Cross Attention Down Block的结构如下：\nCross Attention Up Block的结构如下：\n上面两个模块中，time embedding都是只与ResNet Block做操作，而condition embedding则是通过交叉注意力参与到Transformer Model中。\nResNet Block详细结构如下。time embedding会通过线性层映射到合适的尺寸以便与卷积层的输出进行逐元素相加。最后的$1\\times 1$卷积是可选项，目的是用于调整输出特征图的通道数。\nTransformer Model详细结构如下。该结构可以分为三部分。第一部分将输入的特征图转为一维向量输出到transformer中。第二部分则是一个常规的transformer，其中第二个Multi-Head Attention中的K，V值由condition embedding产生。第三部分则是将transformer输出的一维向量转换为与输入相同尺寸的特征图。\n其余模块（Up Block、Down Block和Cross Attention Mid Block）的结构如下：\nAppendix Latent diffusion model与Stable diffusion model的区别（两个模型结构上基本是一样的）：\n相比Latent diffusion的训练数据（LAION-400M），stable diffusion使用了更大量的数据（LAION-2B-en）进行训练，同时数据也有经过初步筛选，质量会高一些 Latent diffusion model中使用的text encoder是随机初始化的transformer（即从零开始训练），而stable diffusion model则是通过预训练的CLIP模型 训练的尺寸也有所不同。例如Latent diffusion model只是在$256\\times 256$的分辨率上训练，而stable diffusion model会先在$256\\times 256$上预训练，之后在$512\\times 512$上微调 更详细的回答可以参考这里\nStable diffusion各版本的区别。自stable diffusion发布以来，官方已经推出了许多版本的SD模型，但这些模型原理结构上的改变其实并不大，更多的可能是生成风格上的差别。\nSD 1.x 版本的text encoder采用的是OpenAI的CLIP ViT-L/14模型，其模型参数量为123.65M；而SD 2.0采用了更大的text encoder：基于OpenCLIP在laion-2b数据集上训练的CLIP ViT-H/14模型，其参数量为354.03M 在text encoder中，SD 1.x 提取的是倒数第一层的特征。由于倒数第一层的特征之后就是CLIP的对比学习任务，所以倒数第一层的特征可能部分丢失细粒度语义信息。SD 2.0提取的是text encoder倒数第二层的特征 SD 2.0不同stage的attention模块是固定attention head dim为64，而SD 1.x 则是不同stage的attention模块采用固定attention head数量，明显SD 2.0的这种设定更常用，但是这个变动不会影响模型参数 训练数据上，SD 1.x 版本其实最后主要采用LAION-2B中美学评分为5以上的子集来训练，而SD 2.0版本采用评分在4.5以上的子集，相当于扩大了训练数据集 更详细内容可以参考这里\n参考 High-Resolution Image Synthesis with Latent Diffusion Models\nThe Unreasonable Effectiveness of Deep Features as a Perceptual Metric\nThe Big Data Myth: Using Diffusion Models for Dataset Generation to Train Deep Detection Models\nLatent Diffusion(Stable-Diffusion) Model (video)\nLatent Diffusion(Stable-Diffusion) Model (pdf)\nThe Illustrated Stable Diffusion\nFrom Autoencoder to Beta-VAE\nA Beginner\u0026rsquo;s Guide to Variational Methods: Mean-Field Approximation\n","permalink":"https://simplestory.github.io/posts/20240817_86b1d339bba4e0c154df43f21e99fe50/","summary":"\u003cp\u003e以往的diffusion model（DM）一般会在全图范围内进行操作，这样在运行DM模型所需的显存会变得非常大。而latent diffusion model（LDM）则是将输入通过预训练好的autoencoder encoder映射到潜在空间，在潜在空间上进行DM操作，最后得到的输出再通过autoencoder decoder部分映射回原图空间。作者还通过cross attention的方式将限制条件引入DM中，从而实现可控的图像生成。\u003c/p\u003e","title":"LDM"},{"content":"Ref: https://lilianweng.github.io/posts/2017-08-20-gan/\n一些背景知识\n对于两个分布$P_1$和$P_2$，其KL散度（Kullback-Leibler divergence）和JS散度（Jensen-Shannon divergence）可用于描述两个分布之间的差异，具体表示如下：\n$$ \\begin{aligned} D_{KL}(P_1 \\vert\\vert P_2) \u0026= \\mathbb{E}_{x\\sim P_1}\\left[\\log\\frac{P_1}{P_2}\\right] = \\int_x P_1\\log\\frac{P_1}{P_2}dx \\\\ D_{JS}(P_1 \\vert\\vert P_2) \u0026= \\frac{1}{2}D_{KL}\\left(P_1\\vert\\vert\\frac{P_1+P_2}{2}\\right) + \\frac{1}{2}D_{KL}\\left(P_2\\vert\\vert\\frac{P_1+P_2}{P_2}\\right) \\end{aligned} $$GAN通常包含有两个模型：判别器$D$（discriminator）和生成器$G$（generator）。判别器输出是样本来自于真实数据的概率，即目的是分辨出真实数据和生成数据，而生成器的目的是让生成的数据尽可能接近真实数据分布，即让判别器难以分辨。\n上面这一过程是通过交叉训练$D$和$G$实现的。实际中一般会先训练判别器，在训练生成器，反复训练直至两个模型达到平衡（纳什平衡）。\n进入正文前，先定义一些变量：\n$p_z$：噪声输入$z$的数据分布，一般为高斯分布 $p_g$：生成器生成数据分布 $p_r$：真实数据分布 Original GAN 在原始GAN中，对于$D$，我们希望对于真实数据，判别器的得分越高越好，即最大化$\\mathbb{E}_{x\\sim p_r(x)}[\\log D(x)]$，而对于虚假数据，判别器的得分越小越好（接近0），即最大化$\\mathbb{E}_{z\\sim p_z(z)}[\\log(1-D(G(z)))]$。对于$G$，我们希望生成的数据越接近真实数据越好（接近1），反映出来就是最小化$\\mathbb{E}_{z\\sim p_z(z)}[\\log(1-D(G(z)))]$。\n总上可以得到原始GAN的损失函数：\n$$ \\begin{aligned} \u0026\\min_G \\max_D L(D,G) \\\\ \u0026 \\begin{aligned} L(D,G) \u0026= \\mathbb{E}_{x\\sim p_r(x)}[\\log D(x)] + \\mathbb{E}_{z\\sim p_z(z)}[\\log(1-D(G(z)))] \\\\ \u0026= \\mathbb{E}_{x\\sim p_r(x)}[\\log D(x)] + \\mathbb{E}_{x\\sim p_g(x)}[\\log(1-D(x))] \\end{aligned} \\end{aligned} $$$L(D,G)$对$D$进行求导，可以得到最优判别器$D$为：\n$$ D^\\star(x) = \\frac{p_r(x)}{p_r(x)+p_g(x)} \\in [0, 1] $$生成器达到最优时，此时$p_r = p_g$，即判别器为$\\frac{1}{2}$。\n当判别器与生成器都达到最优时，即$D^\\star(x)=\\frac{1}{2}$，代入$L(G,D)$中，可得全局最优下损失函数的值为：\n$$ \\begin{aligned} L(G^\\star,D^\\star) \u0026= \\mathbb{E}_{x\\sim p_r(x)}[\\log D(x)] + \\mathbb{E}_{x\\sim p_g(x)}[\\log(1-D(x))] \\\\ \u0026= \\int_x \\bigg(p_r(x)\\log(D^\\star(x)) + p_g(x)\\log(1-D^\\star(x))\\bigg)dx \\\\ \u0026= \\log\\frac{1}{2}\\int_x p_r(x)dx + \\log\\frac{1}{2}\\int_x p_g(x)dx \\\\ \u0026= -2\\log2 \\end{aligned} $$假设判别器达到最优，则通过对JS散度做变换，可以得到：\n$$ \\begin{aligned} D_{JS}(p_{r} \\| p_g) =\u0026 \\frac{1}{2} D_{KL}(p_{r} || \\frac{p_{r} + p_g}{2}) + \\frac{1}{2} D_{KL}(p_{g} || \\frac{p_{r} + p_g}{2}) \\\\ =\u0026 \\frac{1}{2} \\bigg( \\log2 + \\int_x p_{r}(x) \\log \\frac{p_{r}(x)}{p_{r} + p_g(x)} dx \\bigg) + \\\\\u0026 \\frac{1}{2} \\bigg( \\log2 + \\int_x p_g(x) \\log \\frac{p_g(x)}{p_{r} + p_g(x)} dx \\bigg) \\\\ =\u0026 \\frac{1}{2} \\bigg( \\log4 + L(G, D^*) \\bigg) \\end{aligned} $$即$L(G,D^\\star) = 2D_{JS}(p_r\\vert\\vert p_g) - 2\\log2$。\n当判别器能够准确划分真实数据和虚假数据时，有$\\forall x \\in p_r, D(x) = 1$和$\\forall x \\in p_g, D(x) = 0$，代入$L(G,D)$中可得为0，即有梯度为0。\n这里我们可以得到原始GAN训练困难的主要原因：判别器收敛得很好的时候，生成器梯度消失，loss无法下降。判别器收敛不好时，生成器梯度没有指导作用。只有当判别器收敛到某一程度时，生成器梯度才是有效且具有指导作用的，而这个程度很难掌握。\n后续作者针对生成器提出了一个改进的损失函数：$\\mathbb{E}_{x\\sim p_g}[-\\log D(x)]$。\n对KL散度做变换有：\n$$ \\begin{aligned} D_{KL}(p_g\\vert\\vert p_r) \u0026= \\mathbb{E}_{x\\sim p_g}\\bigg[\\log\\frac{p_g}{p_r}\\bigg] \\\\ \u0026= \\mathbb{E}_{x\\sim p_g}\\bigg[\\log\\frac{p_g/(p_r+p_g)}{p_r/(p_r+p_g)}\\bigg] \\\\ \u0026= \\mathbb{E}_{x\\sim p_g}\\bigg[\\log\\frac{1-D^\\star(x)}{D^\\star(x)}\\bigg] \\\\ \u0026= \\mathbb{E}_{x\\sim p_g}\\log[1-D^\\star(x)] - \\mathbb{E}_{x\\sim p_g}[\\log D^\\star(x)] \\end{aligned} $$结合上面的$L(G, D^\\star)$有：\n$$ \\begin{aligned} \\mathbb{E}_{x\\sim p_g}[-\\log D(x)] \u0026= D_{KL}(p_g\\vert\\vert p_r) - \\mathbb{E}_{x\\sim p_g}[\\log(1-D^\\star(x))] \\\\ \u0026= D_{KL}(p_g\\vert\\vert p_r) - 2D_{JS}(p_r\\vert\\vert p_g) + 2\\log2 + \\mathbb{E}_{x\\sim p_r}[\\log D^\\star(x)] \\end{aligned} $$由于上式中后两项不依赖于生成器，所以最终最小化$\\mathbb{E}_{x\\sim p_g}[-\\log D(x)]$可以等价为最小化$D_{KL}(p_g\\vert\\vert p_r) - 2D_{JS}(p_r\\vert\\vert p_g)$。\n这个式子一边要最小化生成分布和真实分布之间的KL散度，同时又要最大化它们之间的JS散度，属于自相矛盾。而且JS散度那一项会让模型训练不稳定。\n对于KL散度项$D_{KL}(p_g\\vert\\vert p_r)$，有：\n当$p_g(x) \\rightarrow 0$而$p_r(x)\\rightarrow 1$时，$p_g(x)\\log\\frac{p_g(x)}{p_r(x)}\\rightarrow 0$，即对$D_{KL}(p_g\\vert\\vert p_r)$的贡献趋于零 当$p_g(x) \\rightarrow 1$而$p_r(x)\\rightarrow 0$时，$p_g(x)\\log\\frac{p_g(x)}{p_r(x)}\\rightarrow +\\infty$，即对$D_{KL}(p_g\\vert\\vert p_r)$的贡献趋于正无穷 所以模型对上面两种错误的惩罚是不一样的。第一种错误对应于生成器没有生成真实的样本，惩罚微小。第二种错误对应于生成器生成了不真实的样本，惩罚巨大。这样的操作下，模型会趋向于生成一些重复但接近真实分布的样本，导致模型生成的数据缺乏多样性，即模式坍塌（mode collapse）问题。\nWGAN Wasserstein GAN: https://zhuanlan.zhihu.com/p/25071913\n支撑集（support）：函数中的非零部分子集，一个概率分布的支撑集就是所有概率密度非零部分集合。\n流形（manifold）：是高维空间中曲线、曲面概念的拓广，我们可以在低维上直观理解这个概念，比如我们说三维空间中的一个曲面是一个二维流形，因为它的本质维度（intrinsic dimension）只有2，一个点在这个二维流形上移动只有两个方向的自由度。同理，三维空间或者二维空间中的一条曲线都是一个一维流形。\n测度（measure）：是高维空间中长度、面积、体积概念的拓广，可以理解为超体积。\nWGAN针对GAN难训练的问题提出了一些解决方案，其中最主要的就是使用Wasserstein距离替换KL散度和JS散度。\n跟原始GAN相比，做了如下更改：\n判别器最后一层去掉sigmoid 生成器和判别器的loss不取log 每次更新判别器的参数之后将它们的绝对值截断到不超过一个固定常数 不使用基于动量的优化算法（如momentum和Adam等），推荐使用SGD，RMSProp之类的 但实际上WGAN依然会有训练困难的问题，主要原因是强行通过weight clipping的方法来实现判别器的梯度符合Lipschitz限制。所以又提出了WGAN-GP。\n","permalink":"https://simplestory.github.io/posts/20240811_787700a547309b9912534d7c3540b79b/","summary":"\u003cp\u003eRef: \u003ca href=\"https://lilianweng.github.io/posts/2017-08-20-gan/\"\u003ehttps://lilianweng.github.io/posts/2017-08-20-gan/\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e\u003cstrong\u003e一些背景知识\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e对于两个分布$P_1$和$P_2$，其KL散度（Kullback-Leibler divergence）和JS散度（Jensen-Shannon divergence）可用于描述两个分布之间的差异，具体表示如下：\u003c/p\u003e","title":"GAN"},{"content":"Note: VAE并不是一个只关注重建的模型，它更重要的能力是图像生成，所以VAE并不是一个AE模型，它具备生成能力。\n与常见的Autoencoder不同，变分自编码器并不是将输入映射为一个固定的向量，而是映射为一个分布$p_\\theta$（参数为$\\theta$）。输入数据$\\mathbf{x}$与隐变量$\\mathbf{z}$的关系如下：\n$p_\\theta(\\mathbf{z})$：先验概率 $p_\\theta(\\mathbf{x}\\vert\\mathbf{z})$：似然概率 $p_\\theta(\\mathbf{z}\\vert\\mathbf{x})$：后验概率 假设在获得模型最佳参数$\\theta^{\\star}$后，我们可以通过以下步骤生成符合真实数据分布的样本：\n从先验概率$p_{\\theta^\\star}(\\mathbf{z})$中采样一个$\\mathbf{z}^{(i)}$ 依据条件概率$p_{\\theta^\\star}(\\mathbf{x}\\vert\\mathbf{z}=\\mathbf{z}^{(i)})$生成$\\mathbf{x}^{(i)}$ 最优参数$\\theta^\\star$可以通过最大化恢复真实数据分布的概率得到：\n$$ \\theta^\\star = argmax_\\theta\\prod_{i=1}^np_\\theta(\\mathbf{x}^{(i)}) $$转为对数空间可得：\n$$ \\theta^\\star = argmax_\\theta\\sum\\log p_\\theta(\\mathbf{x}^{(i)}) $$为了引入encoding vector有：\n$$ p_\\theta(\\mathbf{x}^{(i)}) = \\int p_\\theta(\\mathbf{x^{(i)}}\\vert\\mathbf{z})p_\\theta(\\mathbf{z})d\\mathbf{z} $$依据上式，我们需要遍历所有$\\mathbf{z}$来计算，所以实际上$p_\\theta(\\mathbf{x}^{(i)})$很难得到。作者采用了近似分布的方式来解决这个问题，即对于每个输入样本$\\mathbf{x}$，引入了一个由参数$\\phi$表达的分布$q_\\phi(\\mathbf{z}\\vert\\mathbf{x})$来直接去近似后验概率分布$p_\\theta(\\mathbf{z}\\vert\\mathbf{x})$。这样可以保证输入样本、隐变量和重构样本是一一对应的。所以最终VAE的结构与Autoencoder就比较类似了：\n由近似后验概率$q_\\phi(\\mathbf{z}\\vert\\mathbf{x})$来得到隐变量，即encoder部分。$q_\\phi(\\mathbf{z}\\vert\\mathbf{x})$也称为probabilistic encoder。 由似然概率$p_\\theta(\\mathbf{x}\\vert\\mathbf{z})$来生成结果，即相当于decoder部分。$p_\\theta(\\mathbf{x}\\vert\\mathbf{z})$也称为probabilistic decoder。 我们期望近似分布$q_\\phi(\\mathbf{z}\\vert\\mathbf{x})$与$p_\\theta(\\mathbf{z}\\vert\\mathbf{x})$能够尽可能相似。一个自然的想法是最小化它们之间的KL散度$D_{KL}(q_\\phi(\\mathbf{z}\\vert\\mathbf{x})\\vert\\vert p_\\theta(\\mathbf{z}\\vert\\mathbf{x}))$。将$D_{KL}$展开化简可得：\n$$ \\begin{aligned} \u0026 D_\\text{KL}( q_\\phi(\\mathbf{z}\\vert\\mathbf{x}) \\vert\\vert p_\\theta(\\mathbf{z}\\vert\\mathbf{x}) ) \\\\ =\u0026 \\log p_\\theta(\\mathbf{x}) + D_\\text{KL}(q_\\phi(\\mathbf{z}\\vert\\mathbf{x}) \\vert\\vert p_\\theta(\\mathbf{z})) - \\mathbb{E}_{\\mathbf{z}\\sim q_\\phi(\\mathbf{z}\\vert\\mathbf{x})}\\log p_\\theta(\\mathbf{x}\\vert\\mathbf{z}) \\end{aligned} $$即：\n$$ \\begin{aligned} \u0026 \\log p_\\theta(\\mathbf{x}) - D_\\text{KL}( q_\\phi(\\mathbf{z}\\vert\\mathbf{x}) \\vert\\vert p_\\theta(\\mathbf{z}\\vert\\mathbf{x}) ) \\\\ =\u0026 \\mathbb{E}_{\\mathbf{z}\\sim q_\\phi(\\mathbf{z}\\vert\\mathbf{x})}\\log p_\\theta(\\mathbf{x}\\vert\\mathbf{z}) - D_\\text{KL}(q_\\phi(\\mathbf{z}\\vert\\mathbf{x}) \\vert\\vert p_\\theta(\\mathbf{z})) \\end{aligned} $$最大化上式就是我们的优化目标，即最大化生成真实数据分布的似然概率，同时最小化近似后验分布与真实后验分布的KL散度。取负号可得损失函数：\n$$ \\begin{aligned} L_\\text{VAE}(\\theta, \\phi) \u0026= -\\log p_\\theta(\\mathbf{x}) + D_\\text{KL}( q_\\phi(\\mathbf{z}\\vert\\mathbf{x}) \\vert\\vert p_\\theta(\\mathbf{z}\\vert\\mathbf{x}) )\\\\ \u0026= - \\mathbb{E}_{\\mathbf{z} \\sim q_\\phi(\\mathbf{z}\\vert\\mathbf{x})} \\log p_\\theta(\\mathbf{x}\\vert\\mathbf{z}) + D_\\text{KL}( q_\\phi(\\mathbf{z}\\vert\\mathbf{x}) \\vert\\vert p_\\theta(\\mathbf{z}) ) \\\\ \\theta^{*}, \\phi^{*} \u0026= \\arg\\min_{\\theta, \\phi} L_\\text{VAE} \\end{aligned} \\tag{1} $$由于KL散度是非负的，即有：\n$$ -L_\\text{VAE} = \\log p_\\theta(\\mathbf{x}) - D_\\text{KL}( q_\\phi(\\mathbf{z}\\vert\\mathbf{x}) \\| p_\\theta(\\mathbf{z}\\vert\\mathbf{x}) ) \\leq \\log p_\\theta(\\mathbf{x}) $$故通过最小化$L_\\text{VAE}$，我们可以最大化生成真实数据样本概率的下界。$L_\\text{VAE}$也被称为变分下限（VLB，variational lower bound）或证据下限（ELBO，evidence lower bound）。\n从上面(1)式中可以看到计算损失函数时需要对分布$q_\\phi(\\mathbf{z}\\vert\\mathbf{x})$进行采样，而随机采样过程并不能去计算梯度。VAE论文中采用重参数化技巧（reparameterization trick）将采样过程的随机性转移到其它变量上：\n$$ \\begin{aligned} \\mathbf{z} \u0026\\sim q_\\phi(\\mathbf{z}\\vert\\mathbf{x}^{(i)}) = \\mathcal{N}(\\mathbf{z}; \\boldsymbol{\\mu}^{(i)}, \\boldsymbol{\\sigma}^{2(i)}\\boldsymbol{I}) \u0026 \\\\ \\mathbf{z} \u0026= \\boldsymbol{\\mu} + \\boldsymbol{\\sigma} \\odot \\boldsymbol{\\epsilon} \\text{, where } \\boldsymbol{\\epsilon} \\sim \\mathcal{N}(0, \\boldsymbol{I}) \u0026 \\scriptstyle{\\text{; Reparameterization trick.}} \\end{aligned} $$即先从标准正态分布随机采样一个样本$\\mathbf{\\epsilon}$，然后乘以VAE encoder部分预测的标准差，再加上encoder预测的均值，这样就能计算该损失对VAE encoder网络参数的梯度了。\n$L_\\text{VAE}$也可以看成是重建损失和正则化两项的和，其中第一项$\\mathbb{E}_{\\mathbf{z}\\sim q_\\phi(\\mathbf{z}\\vert\\mathbf{x})}$对应的是重建损失，而$D_{KL}$则相当于是一个KL正则项。依据数据类型的不同，损失中第一项包含的$p_\\theta(\\mathbf{x}\\vert\\mathbf{z})$分布也是不同的，最终得到的重建损失也是不同的。例如当$p_\\theta(\\mathbf{x}\\vert\\mathbf{z})$是一个各分量独立的多元高斯分布时，代入相关的表达式得到重建损失就是一个L2损失，而当该分布是一个伯努利分布时，得到的重建损失为交叉熵。\n这里有个小注意点。由于KL散度是不对称的，所以为何我们是去最小化$D_{KL}(q_\\phi\\vert\\vert p_\\theta)$而不是$D_{KL}(p_\\theta\\vert\\vert q_\\phi)$呢？（具体可以参考这篇文章）\n$D_{KL}(p_\\theta\\vert\\vert q_\\phi)$又被称为Forward KL，而$D_{KL}(q_\\phi\\vert\\vert p_\\theta)$称为Reversed KL。对Forward KL展开有：\n$$ D_{KL}(p_\\theta\\vert\\vert q_\\phi) = \\sum_z p_\\theta(z)\\log\\frac{p_\\theta(z)}{q_\\phi(z)} $$对任意$p_\\theta(z) \\gt 0$，有$\\lim_{q_\\phi(z) \\to 0} \\log \\frac{p_\\theta(z)}{q_\\phi(z)} \\to \\infty$，这意味着当$q_\\phi$不能覆盖$p_\\theta$时，Forward KL的值会变得非常大。所以最后得到的$q_\\phi$会覆盖整个$q_\\theta$，如下图：\n对Reversed KL展开有：\n$$ D_{KL}(q_\\phi\\vert\\vert q_\\theta) = \\sum_z q_\\phi(z)\\log \\frac{q_\\phi(z)}{p_\\theta(z)} $$上式当$p_\\theta(z)$为0时，$q_\\phi(z)$也必须为0，不然KL散度的值会出现异常。所以最后得到的$q_\\phi$会被压缩在$p_\\theta$范围内，如下图：\n参考 Auto-Encoding Variational Bayes\n生成模型之VAE\n","permalink":"https://simplestory.github.io/posts/20240811_a9423d60164f001a6715546c56c77154/","summary":"\u003cp\u003e\u003cstrong\u003eNote: VAE并不是一个只关注重建的模型，它更重要的能力是图像生成，所以VAE并不是一个AE模型，它具备生成能力。\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e与常见的Autoencoder不同，变分自编码器并不是将输入映射为一个固定的向量，而是映射为一个分布$p_\\theta$（参数为$\\theta$）。输入数据$\\mathbf{x}$与隐变量$\\mathbf{z}$的关系如下：\u003c/p\u003e","title":"VAE"},{"content":"VQGAN是一个可以在多种图像生成任务上（如无条件图像生成、图像补全、条件引导图像生成等）均有着良好性能的生成模型，其中最大的亮点在于超像素级别的图像生成（百万级像素）。\n模型整体流程图如下：\n整体上来说与VQVAE相近，但是将VQVAE中的PixelCNN替换为Transformer，同时在训练过程中加入PatchGAN的判别器做对抗损失。\n损失函数 在VQVAE中，其损失函数可以表示如下：\n$$ \\begin{aligned} L_{VQ}(E,G,Z) \u0026= L_{rec} + \\vert\\vert\\text{sg}[E(x)] - z_q\\vert\\vert_2^2 + \\vert\\vert\\text{sg}[z_q] - E(x)\\vert\\vert_2^2 \\\\ \u0026= \\vert\\vert x - \\hat{x}\\vert\\vert_2^2 + \\vert\\vert\\text{sg}[E(x)] - z_q\\vert\\vert_2^2 + \\vert\\vert\\text{sg}[z_q] - E(x)\\vert\\vert_2^2 \\end{aligned} $$其中$E,G,Z$分别代表encoder、decoder和codebook。$\\text{sg}$是stop gradient操作。$L_{rec}$是重建损失。\n在VQGAN中，作者将$L_{rec}$替换为感知损失（perceptual loss），同时还加入了GAN中的对抗loss。在实际应用中，为了在某一任务上实现更好的效果，可能会出现L2损失与感知损失一同使用的情况，甚至是将L2损失替换为L1损失，需要按照具体任务通过实验确定。\nPerceptual loss 在一些图像风格迁移、超分辨率等的任务中，传统的损失函数pixel-loss（如L1、L2或cross entropy等）都是追求模型输出与目标在像素级上相等，但像素值上一个很小的偏差，在实际效果上基本没有区别，而pixel-loss的值却会很大。感知损失主要关注图像的风格、颜色以及纹理之类的特征，可以更好地反映模型优化目标。\n该损失函数的计算需要借助额外的网络来提取特征，具体计算流程大致如下：\n上图中的$f_W$对应的是我们需要训练的模型。VGG-16是在Imagenet上预训练好的模型，主要用来提取特征，不参与权重更新。感知损失分为两部分：content loss和style loss。\ncontent loss就是在feature层上的L2损失。选择在feature层上而不是原图上去做是为了避免output与target过分相似，同时还保留有指导output中content更新的能力。数学表达如下：\n$$ L^{\\phi, j}_{feat}(\\hat{y}, y) = \\frac{1}{C_jH_jW_j}\\vert\\vert \\phi_j(\\hat{y}) - \\phi_j(y)\\vert\\vert_2^2 $$其中$\\phi_j(\\hat{y})$和$\\phi_j(y)$分别代表模型输出$\\hat{y}$和目标$y$在VGG-16网络中第$j$层的输出特征，且有$\\phi_j(\\hat{y}), \\phi_j(y)\\in\\mathbb{R}^{H_j\\times W_j\\times C_j}$。由上图可以看出，论文中提取的特征层为relu3_3的输出。\nstyle loss用来计算图像在风格、颜色、纹理等方面的不同。假设$\\phi_j(x)\\in\\mathbb{R}^{H_j\\times W_j\\times C_j}$表示输入$x$在模型$\\phi$中第$j$层网络的输出特征。这里先定义一个格拉姆矩阵（Gram matrix）$G_j^\\phi(x)$，大小为$C_j\\times C_j$：\n$$ G_j^\\phi(x)_{c,c^\\prime} = \\frac{1}{C_jH_jW_j}\\sum^{H_j}_{h=1}\\sum^{W_j}_{w=1}\\phi_j(x)_{h,w,c}\\phi_j(x)_{h,w,c^\\prime} $$实际计算中我们可以将$\\phi_j(x)$reshape为$\\psi\\in\\mathbb{R}^{C_j\\times H_jW_j}$，则格拉姆矩阵的计算可以简化如下：\n$$ G^\\phi_j(x) = \\frac{\\psi\\psi^T}{C_jH_jW_j} $$style loss则是定义在格拉姆矩阵上的Frobenius范数距离：\n$$ L_{style}^{\\phi,j}(\\hat{y},y) = \\vert\\vert G_j^\\phi(\\hat{y}) - G_j^\\phi(y)\\vert\\vert_F^2 $$格拉姆矩阵计算输出的大小只与输入的通道数有关（输出大小为$C_j\\times C_j$），所以只要保证通道数相同，即使模型输出与目标的尺寸不一致也是可以计算损失的。\n在特征提取网络中，不同深度的卷积层提取的特征是不一样的（低层网络会提取一些点线特征，高层网络会提取一些抽象特征），所以为了能综合评估模型的style能力，我们需要提取各个不同的网络层输出来计算style损失。依据上图。作者分别提取了relu1_2、relu2_2、relu3_3和relu4_3这四层的输出特征去计算style损失，最终求和作为style loss的值。\n关于Frobenius范数，其计算公式如下（$A\\in\\mathbb{R}^{m\\times n}$）：\n$$ \\vert\\vert A\\vert\\vert_F = \\sqrt{\\sum^m_{i=1}\\sum^n_{j=1}\\vert a_{ij}\\vert^2} = \\sqrt{\\text{trace}(A^\\star A)} = \\sqrt{\\sum_{i=1}^{\\min(m,n)}\\sigma_i^2} $$上面给出了Frobenius范数的三种计算方式。其中$A^\\star$表示$A$的共轭转置矩阵（共轭转置矩阵在将行与列对换后还要将每个元素共轭一下，实数矩阵的共轭转置矩阵就是转置矩阵），$\\sigma_i$则是$A$的奇异值，且使用了迹函数。\nAdversarial loss 关于对抗损失部分，作者选用的是适用于GAN的Hinge loss。一般的Hinge loss通常用于最大边际分类器（maximal margin classifer）如支持向量机中，通过尽可能地拉大模型正负样本之间的得分差距来提高模型性能。数学表示如下：\n$$ L = \\max(0, 1-\\hat{y}y) $$其中$\\hat{y}$表示模型输出的样本得分，$y$表示样本真实标签（1或-1）。\n当$\\hat{y}$与$y$同号，即模型分类类别与真实类别相同。此时若$\\vert\\hat{y}\\vert\\le 1$，则表示此时的间距还不够大，损失函数不为0，还会继续更新，直到$\\vert\\hat{y}\\vert\\gt 1$损失函数为0 当$\\hat{y}$与$y$异号，即模型分类类别与真实类别不同，损失函数不为0 在Geometric GAN中，引入了Hinge loss且变换为以下形式：\n$$ \\begin{aligned} L(G,D) \u0026= L(G) + L(D) \\\\ \u0026= -E(D(G(z))) + E(\\max(0, 1-D(x))) + E(\\max(0, 1+D(G(z)))) \\end{aligned} $$Hinge loss主要应用在了判别器$D$上。只有当$D(x)\\lt 1$的真实样本和$D(G(z))\\gt -1$的生成样本会产生损失，即只有一些被不合理划分的样本会有贡献，这样GAN模型的训练会更加稳定。\n在VQGAN中，对抗损失笼统地表示如下：\n$$ L_{GAN}(\\{E,G,Z\\}, D) = [\\log(D(x)) + \\log(1-D(\\hat{x}))] $$我们的目标是求得最优的$E, G, Z$，最后总结的优化目标如下：\n$$ Q^\\star = argmin_{E,G,Z}\\max_D\\mathbb{E}_{x\\sim p(x)}[L_{VQ}(E,G,Z) + \\lambda L_{GAN}(\\{E,G,Z\\}, D)] $$其中$\\lambda$是自适应权重，表达式如下：\n$$ \\lambda = \\frac{\\nabla_{G_L}[L_{rec}]}{\\nabla_{G_L}[L_{GAN}]+\\delta} $$$L_{rec}$就是感知损失，$\\nabla_{G_L}[\\cdot]$是一个函数，表示该函数的输入相对于模型decoder最后一层$L$的梯度，$\\delta=10^{-6}$是为了防止除0错误。\nVQGAN中新加的判别器$D$结构是PatchGAN的判别器，它是一个全卷积的模型结构。通常的GAN判别器输出的只是一个值，而PatchGAN判别器输出的是一个$n\\times n$的矩阵，矩阵中的每个值都代表输入模型在图像中对应的每个小区域（patch）的得分。这种设计可以让模型更关注图像的细节，也不再是用单单一个值来评价图像的真实性。\n随机图像生成 上面的优化目标其实已经可以完成模型各个模块的训练了，但在随机图像生成任务中，我们是需要在没有encoder的情况下，采样出$z_q$给到decoder的。VQVAE的方法通过PixelCNN来预测$z_q$。考虑到Transformer具有长距离注意力，并且这个采样过程也是可以类比文本序列预测问题，所以作者采用了Transformer结构的模型来预测$z_q$（具体结构是GPT-2）。\n将采样过程视为自回归预测，即让模型去预测分布$p(s)=\\prod_i p(s_i\\vert s_{\\lt i})$，相应的损失函数是最大化对数似然函数：\n$$ L_{Transformer} = \\mathbb{E}_{x\\sim p(x)}[-\\log p(s)] $$Transformer模块的训练是需要在其它模块训练好后进行的，具体的训练过程如下：\n输入经过encoder和codebook后得到$z_q\\in \\mathbb{R}^{h\\times w\\times n}$并展开为二维形式$z_q^\\prime\\in \\mathbb{R}^{hw\\times n}$，即得到$hw$个维度为$n$的编码。定义此时$z_q^\\prime$中的编码组合对应到codebook中的索引为unmodified_indices 将$z_q^\\prime$中的部分编码替换为随机生成的编码（维度依然为$n$），定义修改后的编码组合对应的索引为modified_indices。替换为随机编码这一步相当于是在特征中添加了噪声，以此来提升Transformer的泛化性 将modified_indices输入到Transformer模型中，通过交叉熵损失训练模型，使其重构出unmodified_indices 借助于Transformer，VQGAN实现了超像素级的图像生成任务。\n参考 Taming Transformers for High-Resolution Image Synthesis\nPerceptual Losses for Real-Time Style Transfer and Super-Resolution\nImage-to-Image Translation with Conditional Adversarial Networks\n结合离散化编码与Transformer的百万像素图像生成\n","permalink":"https://simplestory.github.io/posts/20240811_f09978b03d9ad93516b6003551892ddc/","summary":"\u003cp\u003eVQGAN是一个可以在多种图像生成任务上（如无条件图像生成、图像补全、条件引导图像生成等）均有着良好性能的生成模型，其中最大的亮点在于超像素级别的图像生成（百万级像素）。\u003c/p\u003e","title":"VQ-GAN"},{"content":"正常来说图像的像素值是离散的（在0到255之间），所以我们其实可以把图像看成是一个长为$H\\times W\\times C$的序列，在预测当前像素值时，把之前的像素值作为已知信息参与到预测中，这样递归地逐像素预测下去，即自回归模型（autoregressive model）：\n$$ p(x) = p(x_1)p(x_2\\vert x_1)\\dots p(x_n\\vert x_1,x_2,\\dots,x_{n-1}) $$但上面这一过程是逐像素生成的，所以运行速度慢。VQ-VAE借鉴了自回归模型的思想，一个最明显的特征是通过encoder得到的隐变量是一个离散变量，将图像大小压缩到了一个小的特征空间。整体的流程大致如下图所示：\n输入大小为$H\\times W\\times 3$的数据$\\mathbf{x}$，通过encoder得到初步的中间变量$\\mathbf{z}=\\text{encoder}(\\mathbf{x})$，大小缩小为$h\\times w\\times d$ 模型会维护一个embedding层$\\mathbf{E}=[\\mathbf{e}_1, \\mathbf{e}_2, \\dots, \\mathbf{e}_K]$，即codebook，其中每个$\\mathbf{e}_i$的长度都是$d$。对于$\\mathbf{z}$中的每个$d$，通过最近邻搜索得到相应的$\\mathbf{e}_i$值，即有映射关系$\\mathbf{z}_q(\\mathbf{x}) =\\mathbf{e}_{argmin_j\\vert\\vert \\mathbf{z}(\\mathbf{x})-\\mathbf{e}_j\\vert\\vert_2}$。得到的编码向量用$\\mathbf{z}_q$来表示，这样的编码向量其实就是一个离散向量 将$\\mathbf{z}_q$输入到decoder中得到结果$\\hat{\\mathbf{x}} = \\text{decoder}(\\mathbf{z}_q)$ 论文中给出了VQ-VAE的损失函数如下：\n$$ L = -\\log p(\\mathbf{x}\\vert\\mathbf{z}_q(\\mathbf{x}))+\\vert\\vert\\text{sg}[\\mathbf{z}(\\mathbf{x})]-\\mathbf{z}_q(\\mathbf{x})\\vert\\vert_2^2+\\beta\\vert\\vert\\mathbf{z}(\\mathbf{x})-\\text{sg}[\\mathbf{z}_q(\\mathbf{x})]\\vert\\vert_2^2 $$其中第一项是用来优化encoder和decoder的，即重建损失，从图中也可以看出，损失函数对$\\mathbf{z}$的梯度是会推给encoder的。这一项通常是重建图像与输入图像的MSE损失$\\vert\\vert \\mathbf{x}-\\text{decoder}(\\mathbf{z}_q)\\vert\\vert_2^2$。\n由于我们在前向计算中有用到$\\argmin$这个操作，这个是没有梯度的。对此，作者采用了Straight-Through Estimator方法人为地设计一个梯度，即$\\vert\\vert \\mathbf{x}-\\text{decoder}(\\mathbf{z} + \\text{sg}[\\mathbf{z}_q-\\mathbf{z}])\\vert\\vert_2^2$。式子中的$\\text{sg}$代表的是stop gradient，即在反向传播时为0，前向计算为原值，数学描述如下：\n$$ \\text{sg}(x) = \\begin{cases} x \\quad \\text{in forward process} \\\\ 0 \\quad \\text{backward process} \\end{cases} $$损失函数的后面两项与codebook有关。通常我们会希望$\\mathbf{z}_q$与$\\mathbf{z}$尽可能地靠近，所以优化目标是$\\vert\\vert\\mathbf{z}_q - \\mathbf{z}\\vert\\vert_2^2$。由于这个目标的梯度等于对$\\mathbf{z}$的梯度加上对$\\mathbf{z}_q$的梯度，所以我们可以将它等价为如下形式：\n$$ \\vert\\vert\\text{sg}[\\mathbf{z}]-\\mathbf{z}_q\\vert\\vert_2^2 + \\vert\\vert\\mathbf{z}-\\text{sg}[\\mathbf{z}_q]\\vert\\vert_2^2 $$等价在这里是指梯度等价，实际前向计算出来的loss值会是原来的两倍。上面式子中，第一项是$\\mathbf{z}_q$的梯度，相当于固定$\\mathbf{z}$，让$\\mathbf{z}_q$靠近$\\mathbf{z}$；而第二项是$\\mathbf{z}$的梯度，相当于固定$\\mathbf{z}_q$，让$\\mathbf{z}$靠近$\\mathbf{z}_q$。因为$\\mathbf{z}_q$相对于$\\mathbf{z}$来说更自由一些，$\\mathbf{z}$是encoder的输出，为了尽力保证重建后的效果，应该尽量让$\\mathbf{z}_q$去靠近$\\mathbf{z}$。这一步可以用权重去调整损失各项的重要性。\n综上，VQ-VAE最终损失函数如下：\n$$ L = \\vert\\vert \\mathbf{x}-\\text{decoder}(\\mathbf{z} + \\text{sg}[\\mathbf{z}_q-\\mathbf{z}])\\vert\\vert_2^2 + \\gamma\\vert\\vert\\text{sg}[\\mathbf{z}]-\\mathbf{z}_q\\vert\\vert_2^2 +\\beta \\vert\\vert\\mathbf{z}-\\text{sg}[\\mathbf{z}_q]\\vert\\vert_2^2 $$其中$\\beta \\lt \\gamma$，论文中选取$\\gamma = 1$，$\\beta = 0.25$。\n从上面的描述中，我们可以看出VQ-VAE其实跟VAE并没有太大关系，它只是一个AE模型，目标在于图像重建。\n在VQ-VAE论文中，作者利用PixelCNN去预测$\\mathbf{z}_q$，实现了随机图像的生成。训练时模型是共同训练VQ-VAE和PixelCNN的，输入数据通过encoder得到$\\mathbf{z}$，查询codebook得到$\\mathbf{z}_q$，再经过PixelCNN得到与$\\mathbf{z}_q$同大小的变量$\\mathbf{z}_p$作为decoder的输入。而在推理阶段则是直接将空图像输入到PixelCNN中得到$\\mathbf{z}_p^\\prime$，之后直接输入到decoder中生成图像。\n关于PixelCNN模型，可以参考这篇文章。\n参考 Neural Discrete Representation Learning\nVQ-VAE的简明介绍\n","permalink":"https://simplestory.github.io/posts/20240811_520db3fb8aba368d31c1f3e5e9fbd8ba/","summary":"\u003cp\u003e正常来说图像的像素值是离散的（在0到255之间），所以我们其实可以把图像看成是一个长为$H\\times W\\times C$的序列，在预测当前像素值时，把之前的像素值作为已知信息参与到预测中，这样递归地逐像素预测下去，即自回归模型（autoregressive model）：\u003c/p\u003e","title":"VQ-VAE"},{"content":"LSS是早期一篇使用BEV来做自动驾驶感知的开山之作，后面的很多bev相关的算法都是基于LSS这一套框架做的改变。\nBEV感知 BEV（Bird‘s’ Eye View）感知，顾名思义是用一种俯瞰的视角来解决自动驾驶感知的问题。这个视角下的目标检测、分割、跟踪等问题都会很直接，而且针对于自动驾驶场景，目标的高度信息其实并没有那么重要，下游的决策规划之类的任务大多数也是用的这种自上而下的视图来做的，多个相机信息的融合会变得方便很多，所以bev下的自动驾驶方案会更加的简单直接。\n对于纯视觉的自动驾驶来说，BEV方案的关键在于如何将相机前向视角的数据给转换或者编码到俯瞰视角下。目前来说主要有四种方法：\nIPM（Inverse Perspective Mapping）逆透视变换，即将相机透视空间反映射到bev空间。IPM会假设地面是完美的平面，没有任何的路面高度变化。这一假设与实际应用有很大的出入，所以IPM一般应用在车道线检测，这个场景下该假设一般是合理的。 Lift-Splat，大致意思是使用Lift操作将2D图像特征提升为3D的视锥体，之后用Splat在bev方向上拍扁。这个框架是由LSS提出的，对后续算法有很大的影响。 MLP（Multi-Layer Perceptron）多层感知机，即用mlp来对转换关系进行学习。可参考VPN了解更多。这一类方法对部署十分友善，但需要依靠大量的数据来学习这种映射关系。 Transformer，利用transformer来进行视图转换，它不会像CNN那样被感受野限制住视野。特斯拉之前分享的自动驾驶方案里用的便是transformer。这类方法训练和部署都有一些难度，但效果上可能会更优。 Lift-Splat-Shoot LSS是一个端到端的框架，从一个或多个相机图像数据上提取特征并分别提升（lift）为一个对应相机的视锥体（frustum），之后将这些视锥体给拍扁（splat）到一个bev网格中。以上这套操作就是lift-splat的大致流程，后续的shoot可以理解为是下游任务，例如LSS中就是对路径的预测。\nLift 对于纯视觉来说，图片中每个像素对应的深度并不是确定的，所以LSS在每个像素的所有可能深度上都生成了表达。具体来讲，Lift操作就是对特征图上每个点都生成一个离散化的深度分布$\\alpha$（一共有$D$个值），然后和对应点的特征$\\mathbf{c}$（长度为$C$）做外积并乘上相应的$\\alpha$值。这样就可以得到相机的视锥体。过程图大致如下：\nSplat 这一步的操作是将前面生成的视锥体投影到给定大小的BEV网格中。在LSS原文中，$x$和$y$轴方向上的感知范围均为$-50m\\sim 50m$，$z$轴方向上则是$-10m\\sim 10m$，BEV的单元格大小为$x$和$y$轴为$0.5m$，$z$轴为$20m$，所以最终生成的BEV网格大小为$200\\times 200\\times 1$。\n在投影过程中，会出现一个BEV单元格内有多个点，LSS采取的方法是用sum pooling来融合这些点。由于生成的视锥体中点数较多，模型效率并不高，所以LSS针对sum pooling进行了优化，利用cumsum trick来提升效率。\n关于LSS细节上的处理建议阅读一下nvidia官方开源的代码，并不复杂，只有5，6个python文件，我简单整理了一下算法流程，大致如下：\n网上也有一些优质的代码解析：\nLSS (Lift, Splat, Shoot) 论文+源码万字长文解析 参考 Lift, Splat, Shoot: Encoding Images from Arbitrary Camera Rigs by Implicitly Unprojecting to 3D\n自动驾驶感知新范式——BEV感知经典论文总结和对比\n","permalink":"https://simplestory.github.io/posts/20230624_593a3023b826e242be5651751b69d09e/","summary":"\u003cp\u003eLSS是早期一篇使用BEV来做自动驾驶感知的开山之作，后面的很多bev相关的算法都是基于LSS这一套框架做的改变。\u003c/p\u003e\n\u003ch2 id=\"bev感知\"\u003eBEV感知\u003c/h2\u003e\n\u003cp\u003eBEV（Bird‘s’ Eye View）感知，顾名思义是用一种俯瞰的视角来解决自动驾驶感知的问题。这个视角下的目标检测、分割、跟踪等问题都会很直接，而且针对于自动驾驶场景，目标的高度信息其实并没有那么重要，下游的决策规划之类的任务大多数也是用的这种自上而下的视图来做的，多个相机信息的融合会变得方便很多，所以bev下的自动驾驶方案会更加的简单直接。\u003c/p\u003e","title":"LSS"},{"content":"单目3D检测在自动驾驶领域是一个重要且充满挑战的任务，目前的难点是如何准确对目标深度进行预测。MonoRCNN利用几何关系将深度估计问题解耦为目标实际高度和在图像上的投影高度问题。\n作者给这个模型的使用限定了两个条件：\n模型只预测目标的航向角，即yaw角 在训练和推理阶段，相机内参已知 Architecture 模型类似于Faster R-CNN，backbone采用ResNet-50和FPN结合的形式，之后使用RoIAlign来截取目标特征，然后就接了三个分支：2D head、3D attribute head、3D distance head。最终预测得到以下参数：\nclass label $cls$和confidence $score$ 2D bounding box $\\mathbf{b} = (x_1, y_1, x_2, y_2)$，使用左上角和右下角坐标表示 3D bounding box的中心点在图像上的投影坐标 $\\mathbf{p} = (p_1, p_2)$ 3D bounding box的物理尺寸 $\\mathbf{m} = (W, H, L)$，分别表示目标真实的宽、高和长 3D bounding box的yaw角 $\\mathbf{a} = (sin\\theta, cos\\theta)$ 3D bounding box中心点的深度$Z$ MonoRCNN预测出在图像坐标系下的3D bounding box中心坐标$(p_1, p_2, Z)$，之后借助相机内参矩阵$\\mathbf{P}$转为相机坐标下的点：\n$$ \\begin{bmatrix} p_1 \\cdot Z \\\\ p_2 \\cdot Z \\\\ Z \\end{bmatrix}_P = \\mathbf{P} \\cdot \\begin{bmatrix} x \\\\ y \\\\ z \\\\ 1 \\end{bmatrix}_C $$3D Distance Head 如上图，由简单的几何关系可以得到：\n$$ Z = \\frac{fH}{h} = fHh_{rec} $$其中$h_{rec} = \\frac{1}{h}$。作者通过回归预测$H$和$h_{rec}$来间接得到$Z$。由于只需要获取图像上的目标高度，所以该方法可以有效地应对遮挡、反光、目标被截断等复杂场景。同时，这种解耦方式还保持了自洽性（self-consistency），从而在两个预测高度都不准确的情况下仍能准确地预测出深度，例如预测出的$H$较大时，模型会自适应地减小$h_{rec}$。这部分具体的内容建议参考原论文。\n这个分支的损失函数采用的是uncertainty-aware regression loss：\n$$ \\begin{aligned} L_H \u0026= \\frac{L_1(\\hat{H}, H)}{\\sigma_H} + \\lambda_Hlog(\\sigma_H) \\\\ L_{h_{rec}} \u0026= \\frac{L_1(\\hat{h}_{rec}, h_{rec})}{\\sigma_{h_{rec}}} + \\lambda_{h_{rec}}log(\\sigma_{h_{rec}}) \\end{aligned} $$其中$\\hat{H}$和$\\hat{h}_{rec}$是ground truths，$H$和$h_{rec}$是预测值，$\\lambda_H$和$\\lambda_{h_{rec}}$为正的系数用于平衡不确定项，$\\sigma_H$和$\\sigma_{h_{rec}}$是不确定性的可学习变量。\n作者实验证明$\\sigma_{h_{rec}}$相比$\\sigma_H$对不同距离的目标更具有判别性，且$fH\\sigma_{h_{rec}}$可以表示距离的不确定性。所以作者使用$\\frac{score}{fH\\sigma_{h_{rec}}}$来代替$score$对预测结果进行排序以提升模型准确率。\n3D Attribute Head 这个分支主要预测目标的物理尺寸，yaw角以及2D关键点。对于物理尺寸和yaw角，直接使用L1损失进行回归：\n$$ \\begin{aligned} L_{size} \u0026= L_1(\\hat{\\mathbf{m}}, \\mathbf{m}) \\\\ L_{yaw} \u0026= L_1(\\hat{\\mathbf{a}}, \\mathbf{a}) \\end{aligned} $$对于2D关键点，定义：$\\hat{\\mathbf{t}} = \\left ( \\frac{\\hat{p}_1 - x_1}{x_2 - x_1}, \\frac{\\hat{p}_2 - y_1}{y_2 - y_1} \\right )$，有：\n$$ L_{kpt} = L_1(\\hat{\\mathbf{t}}, \\mathbf{t}) $$最后，模型的整体损失如下：\n$$ \\begin{aligned} L =\u0026 \\lambda_{cls}L_{cls} + \\lambda_{bbox}L_{bbox} + \\lambda_{size}L_{size} \\\\ \u0026+ \\lambda_{yaw}L_{yaw} + \\lambda_{kpt}L_{kpt} + \\lambda_HL_H + \\lambda_{h_{rec}}L_{h_{rec}} \\end{aligned} $$其中$\\lambda_{cls}=1$、$\\lambda_{bbox}=1$、$\\lambda_{size}=3$、$\\lambda_{yaw}=5$、$\\lambda_{kpt}=5$、$\\lambda_{H}=0.25$、$\\lambda_{h_{rec}}=1$\n参考 Geometry-based Distance Decomposition for Monocular 3D Object Detection\n","permalink":"https://simplestory.github.io/posts/20230623_f25271ca7acc8b883a6ebeba1a0b263d/","summary":"\u003cp\u003e单目3D检测在自动驾驶领域是一个重要且充满挑战的任务，目前的难点是如何准确对目标深度进行预测。MonoRCNN利用几何关系将深度估计问题解耦为目标实际高度和在图像上的投影高度问题。\u003c/p\u003e","title":"MonoRCNN"},{"content":"在2D实时目标检测框架yolo上引入一个轻量级立体匹配模块来实现高效的3D物体检测。大致结构如下图：\nAnchors 对于3D目标检测，原本yolo针对2D物体的anchors需要重新设计，作者采用了12个参数来表示一个anchor：\n$[x_{2d}, y_{2d}, w_{2d}, h_{2d}]$，表示2D目标框 $[c_x, c_y, z]$表示3D目标中心在左图像中的位置去 $[w_{3d}, h_{3d}, l_{3d}]$表示3D目标框的宽、高、长 $[sin(2\\alpha), cos(2\\alpha)]$来评估目标的角度 这里作者发现$[sin\\alpha, cos\\alpha]$和$[sin(\\alpha+\\pi), cos(\\alpha+\\pi)]$可能会导致生成相同的矩形框，所以作者采用$[sin(2\\alpha), cos(2\\alpha)]$来表示目标的角度，同时添加了一个分类分支来预测是否$\\vert\\alpha\\vert \\gt \\frac{\\pi}{2}$，这样也可以表示目标是否面向相机。\n将3D统计先验应用到anchor中以改善回归结果。遍历训练集，对于每个不同尺寸的anchor，基于IOU收集那些分配给该anchor的目标框，然后计算$z$、$sin(2\\alpha)$、$cos(2\\alpha)$的平均值。由于不同类的3D框差异较大，所以作者是分类别的统计3D先验信息。\n考虑到自动驾驶的应用场景，作者将每个anchor按其平均$z$值投影回3D空间中，然后过滤掉那些远离地面的anchor。这部分也是会按类别进行过滤。\nMulti-Scale Stereo Feature Extraction 在stereo matching上，主要是构建cost volume。目前最优的方法是基于concatenation的做法：将右侧特征图在左侧特征图上沿水平方向进行平移来计算cost volume。为了缩减计算时间，作者采用了基于correlation的方法来构建一个轻量的cost volume：用归一化内积层（normalized dot-product）来对左右特征图进行计算（可以参考论文引用的两篇论文中提到的inner-product layer和correlation layer）。这种方法输出的维度是$[B, max\\_disp, H, W]$，输出的通道较少，会导致模型在fusion阶段数值偏向单目特征，同时后续对结果进行下采样时会进一步损失信息。\n作者采用了ghost module来扩展通道数。ghost module使用了depthwise convolution来产生额外的特征。作者更进一步地将ghost module的输出与输入拼接在一起，从而使通道数增加三倍。上面结构图的b部分中，淡紫色部分即为ghost module的输出。\n为了最大限度地减少stereo matching阶段的信息丢失，同时保持计算时间的可控性，作者构建了一个分层多尺度融合机制：\n在下采样倍率为4倍和8倍时，作者分别构建了最大视差为96和192的cost volume，之后输入到ghost module、下采样并与后续更小尺度的特征图拼接。对于下采样倍率为16时，首先用$1\\times 1$卷积对通道数进行下采样，然后作者用基于concatenation的方法创建一个小的cost volume以保留更多的语义信息。具体如上面结构图所示。\nTraining Scheme and Loss Function 首先在数据增强方面，作者对双目图像同时进行光度失真，在训练期间进行随机翻转。这里的随机翻转包括同时翻转RGB图像；翻转目标的位置和方向，然后交换左右图像。\n从结构图中可以看出，模型在训练期间多出了一个辅助分支。视差监督（disparity supervision）对提升模型检测效果有一定的重要性。作者将最后输出的stereo feature提升到$[W/4, H/4]$，并且通过OpenCV传统的块匹配算法得到粗糙的视差ground truth来对预测的视差进行监督。在推理阶段，该分支不参与计算。\n损失函数方面，对于分类采用focal loss，对于目标框采用smoothed-L1 loss，而对于辅助的视差估计，选用stereo focal loss（具体可参考AcfNet）。\n首先计算出预期的视差分布（直接设置方差为$\\sigma = 0.5$）：\n$$ P(d) = softmax(-\\frac{\\vert d-d^{gt}\\vert}{\\sigma}) = \\frac{exp(-c_d^{gt})}{\\sum_{d'=0}^{D-1}exp(-c_{d'}^{gt})} $$其中$d$表示视差，$c_d$表示视差预测为$d$时的置信度。则stereo focal loss可以定义为：\n$$ \\mathcal{L}_{SF} = \\frac{1}{\\vert \\mathcal{P}\\vert}\\sum_{p\\in\\mathcal{P}}\\left(\\sum_{d=0}^{D-1}(1-P_p(d))^{-\\alpha}\\cdot\\big(-P_p(d)\\cdot log\\hat{P}_p(d)\\big)\\right) $$其中$D$为定义的最大视差（max-disparity），$\\alpha$为focal loss的权重，当$\\alpha$越大，对应的正确视差值的权重越高，$\\mathcal{P}$为参与计算的像素集合，$P_p(d)$和$\\hat{P}_p(d)$分别表示预期的和实际预测的视差分布函数。\n最终的损失函数定义为以上三个损失函数的和。\n参考 YOLOStereo3D: A Step Back to 2D for Efficient Stereo 3D Detection\n","permalink":"https://simplestory.github.io/posts/20230623_e5106aece831780fe8decad391998e6a/","summary":"\u003cp\u003e在2D实时目标检测框架yolo上引入一个轻量级立体匹配模块来实现高效的3D物体检测。大致结构如下图：\u003c/p\u003e\n\u003cp\u003e\u003cimg loading=\"lazy\" src=\"https://simplestory-blog-img.oss-cn-guangzhou.aliyuncs.com/in_posts/20230623/yolostereo3d_architecture.png\" title=\"yolostereo3d_architecture\"\u003e\u003c/p\u003e\n\u003ch2 id=\"anchors\"\u003eAnchors\u003c/h2\u003e\n\u003cp\u003e对于3D目标检测，原本yolo针对2D物体的anchors需要重新设计，作者采用了12个参数来表示一个anchor：\u003c/p\u003e","title":"YOLOStereo3D"},{"content":"点云有着分布上稀疏、结构上不规则、几何上无序的特性，所以直接在点云上使用卷积操作可能会导致生成的特征极度地偏离它原本的表达。\nVoxelNet 该模型是对点云进行体素化，然后利用3D卷积来获取特征。具体是：\n将点云空间均分为大小为$v_D$、$v_H$、$v_W$的体素网格，并且同一网格内随机保留最多$T$个点（多的剔除，少的不管）。 对体素单元使用VFE层（voxel feature encoding layer）来提取体素特征并合并为一个4D的张量。 在上面得到的张量上使用3D卷积进一步提取特征，之后用RPN结构来获得结果 如下图：\nVFE VFE是VoxelNet里的最重要的一个结构。针对单一层的VFE，主要有下面几步操作：\n计算体素内所有点的坐标均值并计算它们到均值的偏差即可得到点的输入特征，合并在一起作为VFE的输入特征$\\mathbf{V}_{in} = \\{\\hat{\\mathbf{p}}_i = [x_i, y_i, z_i, r_i, x_i-v_x, y_i-v_y, z_i-v_z]^T \\in \\mathbb{R}^7\\}_{i=1\\dots t}$ 将每个$\\hat{\\mathbf{p}}_i$经过一个由全连接层、BN层和ReLU层组成的FCN结构获得点的特征$f_i \\in \\mathbb{R}^m$ 对$V_{in}$中的所有点的特征使用逐元素MaxPooling获得一个聚合特征$f \\in \\mathbb{R}^m$。这里的element-wise maxpooling可以理解为np.max(f, axis=0)，这样一个输入大小为(n_point_in_voxel, m)的特征，经过element-wise maxpooling后得到的特征大小为(1, m) 将$f$和$f_i$进行拼接即可得到每个点的最后特征$f^{out}_i = [f^T_i, f^T]^T \\in \\mathbb{R}^{2m}$。将体素内的每个点的$f^{out}_i$合并在一起就是VFE的最终输出特征$V_{out}=\\{f^{out}_i\\}_{i=1\\dots t}$ 具体的结构图如下：\n这种逐点的操作可能没能发挥出计算设备的全部性能，所以论文后面也提及到了有关VFE的高效实现方式，具体可以参考原论文。\nRPN 经过VFE得到的特征后会使用多个由3D卷积、BN层、ReLU层组成的结构进一步提取特征。最后通过如下的RPN结构来获得最终输出：\nSECOND VoxelNet中使用了3D卷积层，这个卷积层计算量大导致模型整体的推理速度较慢。该论文提出了一个稀疏3D卷积操作方法，同时针对VoxelNet对于目标的方向不敏感问题提出了angle loss regression。\n关于稀疏3D卷积方法或者SECOND，网上的文章有了很详细的说明，重点在于建立起输入输出的元素索引表。这里我推荐两篇：\nSECOND 算法解析\n通俗易懂的解释Sparse Convolution过程\nData Augmentation SECOND中特意花了大概半页的篇幅来说明了他们使用的一些数据增强方法。\nSample Ground Truths from the Database 由于点云ground truth的数据比较稀缺，所以模型训练的收敛速度以及最终性能都收到了很大的限制。作者通过了类似小目标粘贴的数据增强方法来增强点云数据。\n利用现有数据生成一个带有点云和相应ground truth的database 通过拼接将database中的目标引入到当前点云数据中 在移除和拼接目标时使用碰撞检测来校验当前操作是否符合物理规则 这种方法可以有效地增加点云中ground truths的目标数量，同时也可以提升数据的多样性。\nObject Noise 与VoxelNet中的类似，即独立、随机地去给点云中的目标添加噪声。但VoxelNet是对所有点都用相同的参数，而作者会在$\\Delta\\theta \\in [-\\pi/2, \\pi/2]$中随机选取一个值用于随机旋转操作，在均值为0，标准差为1的高斯分布上随机选取一个值用于随机线性变换。\nGlobal Rotation and Scaling 对整个点云数据和相应的标注框进行缩放和旋转，缩放的尺度范围为$[0.95, 1.05]$，旋转的范围为$[-\\pi/4, \\pi/4]$。\n参考 VoxelNet: End-to-End Learning for Point Cloud Based 3D Object Detection\nSECOND: Sparsely Embedded Convolutional Detection\n","permalink":"https://simplestory.github.io/posts/20230305_f73b955eec390396d75e03ababcc4909/","summary":"\u003cp\u003e点云有着分布上稀疏、结构上不规则、几何上无序的特性，所以直接在点云上使用卷积操作可能会导致生成的特征极度地偏离它原本的表达。\u003c/p\u003e\n\u003ch1 id=\"voxelnet\"\u003eVoxelNet\u003c/h1\u003e\n\u003cp\u003e该模型是对点云进行体素化，然后利用3D卷积来获取特征。具体是：\u003c/p\u003e","title":"VoxelNet和SECOND"},{"content":" 该网络通过双目视觉来实现3D目标检测，属于3D detection中image based的result-lifting方法，这类方法就是将3D检测分解为2D object detection和depth estimation这两部分。\nStereo RCNN是在Faster RCNN的基础上进行扩展的，具体如下图：\nBackbone 作者选用的是ResNet-101结合FPN的形式，并且共享权重。通过该backbone同时对左右图像进行处理，之后将结果合并作为Stereo RPN的输入。\nStereo RPN 为了能让左右框生成的anchor对应起来，作者对objectness classification和box regression使用了不同GT框，如下：\n在objectness classification阶段，将左右各自对应的GT框取外接矩形作为union GT。当anchor与union GT的IOU高于0.7时则作为正样本，小于0.3时作为负样本。由于该IOU的计算只涉及到anchor的尺寸，所以对于左右图像来说都是一样的。 在box regression阶段，使用各自的GT框来进行anchor的坐标回归。待回归的参数为$[u, w, u', w', v, h]$，其中$u$和$v$表示2D框中心的水平和垂直坐标，$h$和$w$表示框的长宽，由于输入的左右图像是经过矫正的，所以在垂直方向上的坐标左右图像基本是一致的，即$v$和$h$基本相同。 最后用NMS取前2000个样本用于训练，测试时取300个。\nStereo RCNN 从上面的网络结构图可以看出，在Stereo RPN以及常规的ROI Align之后有两个分支，分别为Stereo Regression和Keypoint Prediction。\nStereo Regression 在分别对左右图像做ROI Align操作后，将结果合并输入到全连接层。这里会对ROIs进行筛选：当左右图的ROI与GT的IOU均大于0.5时为正样本，小于0.5且大于等于0.1时为负样本。回归参数：\nobject class bounding box：与stereo rpn一样 object dimension：先统计平均尺寸，然后预测相对量 viewpoint angle：$[sin\\alpha, cos\\alpha]$ 关于viewpoint angle，如下图：\n$\\theta$为相机坐标系下的目标朝向角，$\\beta$为相机中心点下的目标方位角。这三个目标投影到相机上基本是一样的，为了更好地区分，作者选择去回归视野角，即$\\alpha = \\theta + \\beta$，其中$\\beta = arctan(-\\frac{x}{z})$，同时为了保持连续性，最终回归参数为$[sin\\alpha, cos\\alpha]$。\nKeypoint Prediction 将3D目标框底部的四个点投影到图像平面上可以发现，最多只有一个3D关键点可以明显地投影到框中间（非左右边缘），作者将这个3D关键点定义为透视关键点并且只在左图上进行预测。利用这个关键点可以有效地约束3D目标框。这里具体的实现细节建议参考原论文。\n3D Box Estimation 现在我们已经得到了2D box、尺寸、视野角、透视关键点，通过这些我们可以初步得到一个3D框：$(x, y, z, \\theta)$，即3D框中心点和朝向角。使用的方法的最小化3D框投影到2D框和关键点的误差。\n输入为$\\mathbf{z} =\\{u_l, v_t, u_r, v_b, u'_l, u'_r, u_p\\}$，表示左图像2D框的左上角和右下角的坐标，右图像2D框的左右$u$方向上坐标，透视关键点的坐标。通过将世界坐标系下的目标点投影到图像坐标下的目标点来建立方程，如左上角投影：\n$$ \\begin{bmatrix} u_l \\\\ v_t \\\\ 1 \\end{bmatrix} = \\begin{bmatrix} x \\\\ y \\\\ z \\end{bmatrix} + \\begin{bmatrix} cos\\theta \u0026 0 \u0026 sin\\theta \\\\ 0 \u0026 1 \u0026 0 \\\\ -sin\\theta \u0026 0 \u0026 cos\\theta \\end{bmatrix} \\cdot \\begin{bmatrix} -\\frac{w}{2} \\\\ -\\frac{h}{2} \\\\ -\\frac{l}{2} \\end{bmatrix} $$可以得到$u_l$和$v_t$的方程。同理可以推出$\\mathbf{z}$中各个参数的方程（注意符号的变化），之后由Gauss-Newton方法求解。\n论文特别注明了为了方便表示，$\\mathbf{z}$中的参数已经经过相机内参normalize了\nDense 3D Box Alignment 这部分是对上面得到的3D框结果进行精度上的优化。作者将目标范围内其高度一半以下且在目标左右边缘之间的区域规定为有效区域，并在这个ROI上进行优化。定义误差函数如下：\n$$ \\begin{aligned} E \u0026 = \\sum_{i = 0}^Ne_i \\\\ \u0026 = \\sum_{i = 0}^N\\left \\| I_l(u_i, v_i) - I_r(u_i - \\frac{b}{z + \\Delta z_i}, v_i) \\right \\| \\end{aligned} $$上式结合三角测距可以理解到作者将目标深度上的变化转化为图像像素坐标的变化，即视差，上式其实可以视为对cost volume的一种计算方法。这样通过改变像素坐标值即可改变深度值，当左右图像像素点总体像素差值最小时，对应的即为最优的$z$值。具体可以用coarse-to-fine 的策略，例如先用0.5m的精度找50步，再以0.05m的精度找20次。\n这个方法可以应用到其它stereo 3D检测的后处理中。因为目标ROI是物理约束，而该方法避免了深度估计中不连续、病态的问题，且对光照是鲁棒的，因为每个像素都会对估计起作用。\n参考 Stereo R-CNN based 3D Object Detection for Autonomous Driving\npaper_reading-Stereo R-CNN based 3D Object Detection for Autonomous Driving\nStereo RCNN official code\n","permalink":"https://simplestory.github.io/posts/20230127_2a9bce23520b48c68e00a87aa00aa611/","summary":"\u003cblockquote\u003e\n\u003cp\u003e该网络通过双目视觉来实现3D目标检测，属于3D detection中image based的result-lifting方法，这类方法就是将3D检测分解为2D object detection和depth estimation这两部分。\u003c/p\u003e\n\u003c/blockquote\u003e","title":"Stereo RCNN"},{"content":" 决定将哪个gt分配给哪个anchor的问题是目标检测中的一个重要问题，即标签分配问题。OTA提供了一个新的思考该问题的角度：把标签分配问题视为最优传输问题。\nLabel Assignment 以往的标签分配策略主要分为：fixed和dynamic两种。\n对于fixed label assignment，通常Anchor-based的检测模型采用IoU阈值作为分配依据（如Fast-RCNN、Faster-RCNN、YoLo3、RetinaNet等），而Anchor-free检测模型有一部分将目标中心位置一定范围内的锚点指定为正样本（如FCOS、Foveabox等）。固定的分配方式通常都会采用一个固定的分配策略（例如固定的IoU阈值或固定的范围半径），对于不同尺寸、形状等的目标都是一样的，这样模型可能会得到一个次优的分配结果。\n对于Dynamic label assignment，一般每个锚点的预测置信度是一个动态分配的指标，高置信度的锚点会被分配给相关的目标对象，但是动态分配的方法依然会有因为不能利用全局信息而导致锚点分配模糊的问题。\nOTA利用了全局信息，将标签分配问题看为最优传输问题，主要目的是找到图像中目标全局最优的匹配。\nOptimal Transport 标准的最优传输问题如下：\n有$m$个供应者和$n$个需求者，第$i$个供应者能提供$s_i$件货物，而第$j$个需求者需要$d_j$件货物。每件货物从供应者$i$运输到需求者$j$所需的付出定义为$c_{ij}$。OT问题的目标是找到一个运输策略$\\pi^\\ast=\\{\\pi_{i,j}\\vert i=1,2,\\dots,m, j=1,2,\\dots,n\\}$，使得运输全部货物所需的付出最小，表示如下：\n$$ \\begin{aligned} \\min_{\\pi}\u0026 \\sum_{i=1}^m\\sum_{j=1}^n c_{ij}\\pi_{ij} \\\\ s.t. \u0026 \\sum_{i=1}^m\\pi_{ij} = d_j,\\quad \\sum_{j=1}^n\\pi_{ij} = s_i \\\\ \u0026 \\sum_{i=1}^ms_i = \\sum_{j=1}^nd_j \\\\ \u0026 \\pi_{ij} \\ge 0,\\quad i=1,2,\\dots,m,\\quad j=1,2,\\dots,n \\end{aligned} $$该问题可以在线性时间内解决，但考虑到目标检测上涉及到特征图维度、anchor的尺度，作者使用了Sinkhorn Iteration来解决计算时间过长的问题。\nSinkhorn Iteration将上面的线性规划问题转换为如下的增加熵的正则化项的凸非线性形式：\n$$ \\min_\\pi \\sum_{i=1}^m\\sum_{j=1}^n c_{ij}\\pi_{ij}+\\gamma E(\\pi_{ij}) $$其中$E(\\pi_{ij}) = \\pi_{ij}(\\log\\pi_{ij}+1)$，$\\gamma$是控制正则化强度的超参数。由拉格朗日乘数法（Lagrange Multiplier Method）有：\n$$ \\min_\\pi \\sum_{i=1}^m\\sum_{j=1}^n c_{ij}\\pi_{ij}+\\gamma E(\\pi_{ij})+\\alpha_j(\\sum_{i=1}^m\\pi_{ij}-d_j)+\\beta_i(\\sum_{j=1}^n\\pi_{ij}-s_i) $$其中$\\alpha_j(j=1,2,\\dots,n)$和$\\beta_i(i=1,2,\\dots,m)$是拉格朗日乘子。对上式求导取0可得：\n$$ \\pi_{ij}^\\ast = \\exp(-\\frac{\\alpha_j}{\\gamma})\\exp(-\\frac{c_{ij}}{\\gamma})\\exp(-\\frac{\\beta_i}{\\gamma}) $$令$u_j = \\exp(-\\frac{\\alpha_j}{\\gamma})$，$v_i = \\exp(-\\frac{\\beta_i}{\\gamma})$，$M_{ij} = \\exp(-\\frac{c_{ij}}{\\gamma})$。则可将约束条件改写为：\n$$ \\begin{aligned} \\sum_i\\pi_{ij} \u0026= u_j(\\sum_iM_{ij}v_i) = d_j \\\\ \\sum_j\\pi_{ij} \u0026= (\\sum_ju_jM_{ij})v_i = s_i \\end{aligned} $$以上两个式子必须同时满足，可得以下的迭代更新式：\n$$ \\begin{aligned} u^{t+1}_j \u0026= \\frac{d_j}{\\sum_iM_{ij}v^t_i} \\\\ v^{t+1}_i \u0026= \\frac{s_i}{\\sum_jM_{ij}u^{t+1}_j} \\end{aligned} $$迭代完成$T$轮后，由下式来计算得到最佳策略$\\pi^\\ast$：\n$$ \\pi^\\ast = diag(v)Mdiag(u) $$作者在论文中设置$\\gamma=0.1$，$T=50$。\nOptimal Transport for Label Assignment 标签分配问题与OT问题的结合。作者将gt视为供应者，每个gt供应$k$个label（$s_i=k,i=1,2,\\dots,m$），将anchor视为需求者，每个anchor需要1个label（即$d_j=1,j=1,2,\\dots,n$）。1个正标签从gt到anchor的传输代价定义为$c^{fg}$，采用$cls$和$reg$损失的和：\n$$ c^{fg}_{ij} = L_{cls}(P^{cls}_{ij}(\\theta),G^{cls}_i)+\\alpha L_{reg}(P^{box}_j(\\theta),G^{box}_i) $$其中$\\theta$表示模型的参数，$P^{cls}_j$和$P^{box}_j$分别表示锚框$a_j$产生的预测$cls$分数和框的坐标，$G^{cls}_i$和$G^{box}_i$分别表示真实标注$gt_i$的类别和框的坐标。$L_{cls}$和$L_{reg}$为分类损失（可以采用交叉熵损失或Focal loss）和框回归损失（可以采用IoU loss、GIoU loss或SmoothL1 loss）。$\\alpha$是平衡因子。\n除了正样本的匹配，还有相当一部分anchor在训练中会被指定为负样本，所以作者还增加了负样本的分配，引入了背景供应者（只提供负标签）。在标准OT问题中，供应者的总供应量应该与需求者的总需求量相同，所以背景供应量计算如下：\n$$ c^{bg}_j =L_{cls}(P^{cls}_j(\\theta), \\varnothing) $$其中$\\varnothing$表示背景类。将$c^{bg}\\in R^{1\\times n}$添加到$c^{fg}\\in R^{m\\times n}$末尾获得完整的代价矩阵$c\\in R^{(m+1)\\times n}$，供应向量$s$相应地更新为下式：\n$$ s_i = \\begin{cases} k, \u0026 if\\quad i\\le m \\\\ n-m\\times k, \u0026 if\\quad i=m+1 \\end{cases} $$已知代价矩阵$c$、供应向量$s\\in R^{m+1}$和需求向量$d\\in R^n$，通过求解该OT问题即可得到最优的分配策略$\\pi^\\ast\\in R^{(m+1)\\times n}$。\n一些优化设计 Center prior。中心先验，即在目标中心指定区域内选取anchor进行分配。理论上OTA可以将全局内的任意anchor指定为正样本，而通过center prior可以让OTA更关注潜在的正样本区域，可以稳定模型前期的训练。作者采用的具体操作是对于每个gt，依据anchor与gt的中心距离在每一层FPN中选取前$r^2$个anchor作为正样本候选，对于其余没有入选的anchor，会在代价矩阵相应位置加上一个常数来降低anchor被选为正样本的概率。 动态的$k$估计。一般对于每个gt应该具有不同的合适的正锚框/点个数，因为每个gt的尺寸、尺度以及遮挡程度都不同。作者依据预测框与gt的IoU数值对每个gt选取前q个IoU数值，并将其求和得到的值作为该gt合适的正锚框/点个数（因为某个gt合适的正锚框/点数量应该与能够很好回归该gt的锚框/点数量正相关）。 综合以上内容，可以得到OTA的伪代码如下：\n参考 OTA: Optimal Transport Assignment for Object Detection\nOTA code\n","permalink":"https://simplestory.github.io/posts/20221006_1087bec7ef12911010976d203f36fd7f/","summary":"\u003cblockquote\u003e\n\u003cp\u003e决定将哪个gt分配给哪个anchor的问题是目标检测中的一个重要问题，即标签分配问题。OTA提供了一个新的思考该问题的角度：把标签分配问题视为最优传输问题。\u003c/p\u003e\n\u003c/blockquote\u003e","title":"OTA"},{"content":" PointNet、PointNet++和Frustum PointNet。常用的3D点云目标检测。\nPointNet PointNet将深度学习直接应用于点云数据。点云数据是欧式空间下的点的一个子集，有无序性、点与点的空间关系以及不变性这三个性质。\n无序性。点云数据是一个集合，对数据顺序并不敏感，这要求模型对数据的不同排列保持不变性。有几种思路。首先是将点云按某种规则进行排序，但对于高维空间，很难找到一个稳定的排序规则。另外一种将其作为一种序列使用RNN进行训练，这种方式很难处理好长度极大（成千上万）的输入元素（如点云）。最后作者利用对称函数去聚集每个点的信息：$f(\\lbrace x_1,x_2,\\cdots,x_n\\rbrace)\\approx g(h(x_1),h(h(x_2),\\cdots,h(x_n))$，其中等式左侧为目标，右侧为设计的对称函数。由上式可看出对于点云中的各个点，使用$h$分别处理，再进入对称函数$g$函数处理。论文中作者选用的是$h$为MLP，$g$就是max pooling。 点与点之间的空间关系。一个物体通常由特定空间的点云组成，点与点之间是存在空间关系的，PointNet采用将全局特征与局部特征串联来利用空间关系。 不变性。点云数据所代表的目标对一些空间变换（刚性变换）应该具有不变性。PointNet在点云提取特征之前，对点云数据进行对齐来保证不变性。对齐操作是通过训练一个小型网络（T-Net）来得到转换矩阵，并和输入的点云数据相乘来实现的。这部分的操作可以看为是对空间点进行调整，直观上理解是旋转出一个有利于分类或分割的角度（刚性变换），在具体实现时还加入相关损失函数来使转换矩阵接近正交化，以减少点云信息损失。（正则化：$L_{reg}=\\lVert I-AA^T\\rVert_F^2$，即L2损失） PointNet的大致结构如下：\n图中包含了分类和分割两部分，其中分割结合了全局池化特征和第二次对齐后的点云特征。源码中分类部分的代码使用了T-Net，分割部分可以不使用，对结果没有太大提升，主要是因为PointNet结构本身不能学习到点与点之间的局部关系，即使添加了T-Net也一样。\nPointNet++ PointNet++针对PointNet的一些问题进行了改进。\n首先介绍一下基础网络层\n采样层。通常原始的点云数据数量巨大，对每个点提取局部特征会导致巨大的计算量，为此PoinNet++先对点云进行了采样。使用的采样方法为最远点采样（FPS，Farthest Point Sampling），相对于随机采样，这种采样方式能够更好地覆盖整个采样空间。 组合层。为了获取点的局部特征，我们需要获得点的局部范围。在图片中，像素点的局部定义为在其周围一定曼哈顿距离下的像素点。对于点云数据，点的局部定义为在其周围给定半径划出的球形空间中的点。组合层即是找出采样后点云中每个点构成的局部所包含的点，以便进行后续的特征提取。 特征提取层。这里借用了PointNet来对各个局部点云提取特征。 以上各层即可构成PointNet++的基础模块。这些基础模块类似于CNN，将多个这样的模块级联起来即可得到PointNet++，可以提取出点云数据的浅层特征到深层特征。针对PointNet的改进点如下：\nPointNet仅仅是对每个点进行特征表达，对局部结构信息的提取能力较差。所以PointNet++使用了sampling层和grouping层来整合局部领域。 PointNet的global feature直接由max pooling获得，这对于分类和分割任务都会造成较大的信息损失。为此PointNet++通过多个set abstraction（如下图）逐级降采样，获得不同规模不同层次的local-global feature。 对于分割任务，PointNet直接将global feature和local feature进行拼接，这样生成的辨别特征较弱，而PointNet++设计了一个encoder-decoder结构来进行分类和分割任务，对于分割部分，先降采样再上采样，并使用skip connection将对应层的local-global feature拼接。 最后模型大致结构如下：\n由于点云在空间中的分布是不规则且不均匀的，PointNet++虽然可以学习点云的局部特征，但点云的在各个局部的分布不均匀可能会导致模型不能很好地提取特征。为此，作者采取了两种方法：\n多尺度组合（Multi-Scale Grouping，MSG）。即提取不同尺度的局部特征并将它们串联拼接在一起。这种操作方式会增加许多计算量。如下图a所示 多分辨率组合（Multi-Resolution Grouping，MRG）。该方法对每个局部提取到的特征都由两个向量串联表示，如下图b所示。第一部分由其前一层提取到的特征再次通过特征提取层得到，第二部分则通过直接对这个局部对应的原始点云数据中的所有点进行特征提取得到。 FPS算法原理：\n输入点云有$N$个点，随机选择一个点$P_0$作为起始点，得到采样集$S=\\lbrace P_0\\rbrace$； 计算所有点到$P_0$的距离，构成$N$维数组$L$，从中选择距离最大的点作为采样点$P_1$，并添加至采样集$S=\\lbrace P_0,P_1\\rbrace$； 计算所有点到$P_1$的距离，对于每个点$N_i$，若其距离$P_1$的距离小于$L[i]$，则更新距离数组$L[i]=d(N_i,P_1)$，即数组$L$存放的是每个点到采样集$S$的最近距离； 从$L$中选取最大值对应的点加入采样集中$S=\\lbrace P_0,P_1,P_2\\rbrace$； 重复2-4步，直至采样集长度达到$n$，即采样了$n$个目标点 这里涉及到初始点的选取和距离度量方式的问题，一般如下：\n初始点选择： 随机选择一点，这样每次结果都不同 选择距离点云重心距离最远的点，这样每次结果都相同且初始点一般位于局部极值点，有一定的刻画能力 距离度量方式： 欧式距离，主要针对点云，在3D立体空间均匀采样 测地距离，即两顶点沿网格表面最短路径的距离，主要针对三角网格，在三角网格面上进行均匀采样 最后说一下分割任务的decoder。经过前面encoder的处理，我们得到的是少量点的特征表达，为了对点云进行分割，PointNet++使用了一种反向插值来上采样。具体如下：\n假设interpolate之前的点云为$P_1$，维度为$N_1\\times C$；之后的点云为$P_2$，维度为$N_2\\times C_2$，其中$N_2\\gt N_1$（因为该操作为上采样）。依据下式进行上采样计算：\n$$ \\begin{aligned} \u0026 f^{(j)}(x)=\\frac{\\sum_{i=1}^kw_i(x)f_i^{(j)}}{\\sum_{i=1}^kw_i(x)} \\\\ where\\qquad \u0026w_i(x)=\\frac{1}{d(x,x_i)^p}, \\ j=1,2,\\cdots,C \\end{aligned} $$即对于$P_2$中的每个点$x$，找到在采样前点云空间$P_1$中与其距离最短的$k$个点$x_1,x_2,\\cdots,x_k$，对这$k$个点的特征进行加权求和即可得$x$的特征，其中这个权重是与距离成反相关的，即越远离相应的贡献度越低。这里的距离度量函数为$d$。论文中选取的参数为$p=2$，$k=3$。\n而以上的操作得到的只是global级别的特征，为了得到准确的分割结构，我们还需要local级别的特征，所以PointNet++还加入了skip connection。操作较简单，即将encoder对应层的点云特征拿过来与当前层进行拼接，之后再经过unit pointnet（一系列mlp）进行整合。具体可参考上面PointNet++网络结构图。\nFrustum PointNet 将RGB图像信息与点云信息结合进行检测。主要流程是先通过目标检测模型得到2D图像上的目标框，将目标框映射到3D点云中，得到frustnum proposal，再使用基于点云的模型对frustum proposal区域进行实例分割和3D边界框回归。大致结构如下：\n图中第一部分就是2D目标检测网络，得到目标框及其类别，然后将目标框映射为3D的frustum proposal。第二部分是实例分割部分，将上一部分得到的frustum proposal中的点采样到$N$个，对应维度为$N\\times C$，与之前得到的类别one-hot向量一起作为实例分割网络的输入，输出一个掩码。最后一部分，将被上一步掩码过滤过的$M$个点作为输入，先由T-Net进行校正对齐，然后通过另一个网络回归预测出3D边界框。\n在将2D检测框映射到3D点云生成frustum proposal时，由于每个frustum proposal的朝向都不一样，因此论文里将其旋转到与相机正交的方向，增加了后续算法的旋转不变性。如下a到b所示。\n仅使用frustum proposal时，得到的点云包含有大量的背景点。所以第二部分的实例分割将前景从背景中分离出来，对于分割出来的前景点还要将其中心化来引入平移不变性，如上图c所示。\n实例分割部分虽然得到了目标的中心点，但由于其前景点云可能没能全部包含目标，所以还需要对中心点进行修正。最后一部分使用了T-Net来学习目标中心点的残差。最后Box Estimation Net输出最终3D边界框的各种参数，输出的维度为$3+4\\times NS+2\\times NH$，其中3代表对于中心点的残差回归（XYZ)，是接在T-Net回归之后的，$NS$代表不同size的anchor个数，每个anchor有四个维度，分别是该anchor的置信度以及长宽高的残差回归，$NH$代表不同朝向的anchor，每个anchor有两个参数，分别是置信度和朝向角$\\theta$。\n2D检测网络之后模型各部分的详细结构大致如下：（包含有两个版本，分别对应于使用PointNet和PointNet++来提取点云特征）\n实际中3D边界框的尺寸和角度共同决定了边界框的信息，所以论文还提出了边界框的corner loss：\n$$ L_{corner} = \\sum_{i=1}^{NS}\\sum_{j=1}^{NH}\\delta_{ij}min\\{\\sum_{k=1}^8\\vert\\vert P_k^{ij}-P_k^\\ast\\vert\\vert, \\sum_{i=1}^8\\vert\\vert P_k^{ij}-P_k^{\\ast\\ast}\\vert\\vert\\} $$在$NS\\times NH$个anchor中，只有正确的size和head朝向的那个边界框才会计算损失（其余情况下$\\delta=0$）。对于第$i$个size的第$j$个朝向的box的第$k$个角$P_k^{ij}$，会移动到ground truth box的中心，与每个对应的角算L1 Loss。考虑到翻转180度的问题，这里会算与180度旋转之后的角$P^{\\ast\\ast}$的损失，并取最小值。总体损失如下：\n$$ L_{multi−task} =L_{seg} + \\lambda(L_{c1−reg} + L_{c2−reg} + L_{h−cls} + L_{h−reg} + L_{s−cls} + L_{s−reg} + \\gamma L_{corner}) $$参考 PointNet: Deep Learning on Point Sets for 3D Classification and Segmentation\npointnet code\nPointNet++: Deep Hierarchical Feature Learning on Point Sets in a Metric Space\npointnet++ code\nFrustum PointNets for 3D Object Detection from RGB-D Data\nfrustum-pointnet code\nPointNet系列论文解读\n（3D目标检测）Frustum PointNets\n","permalink":"https://simplestory.github.io/posts/20210807_1c4ff5cd7d69cb58295eab75872ba2fd/","summary":"\u003cblockquote\u003e\n\u003cp\u003ePointNet、PointNet++和Frustum PointNet。常用的3D点云目标检测。\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch2 id=\"pointnet\"\u003ePointNet\u003c/h2\u003e\n\u003cp\u003ePointNet将深度学习直接应用于点云数据。点云数据是欧式空间下的点的一个子集，有无序性、点与点的空间关系以及不变性这三个性质。\u003c/p\u003e","title":"PointNet系列"},{"content":" 利用三种视图进行3D点云检测（鸟瞰图、前视图、原RGB图）。\nBird\u0026rsquo;s view 将点云数据等分为$M$份，在每一份上以$0.1m$的粒度划分网格，并提取每一格内的最高点云高度，作为鸟瞰图的高度图。强度图则采用网格内高度最高的点对应的反射率值，密度图则统计每个网格内的点云数$N$，对特征进行归一化，最后结果为$\\min (1,\\frac{\\log (N+1)}{\\log 64})$，其中64应该为激光线程数。最后得到通道数为$M+2$的特征。\nFront\u0026rsquo;s view 考虑到激光点云十分稀疏，将点云投影到圆柱面上形成前视图。给定一个3D点$p=(x,y,z)$，对应的前视图坐标为$p_{fv}=(r,c)$，有：\n$$ \\begin{aligned} r \u0026= \\lfloor\\arctan \\frac{z}{\\sqrt{x^2+y^2}}/\\Delta\\phi\\rfloor \\\\ c \u0026= \\lfloor\\arctan \\frac{y}{x}/\\Delta\\theta\\rfloor \\end{aligned} $$其中$\\Delta\\phi$、$\\Delta\\theta$分别表示垂直、水平方向上的分辨率。\n3D prior box 受到RPN的启发，作者也设计了一个网络结构来生成3D prior box。该结构只在鸟瞰图中使用，prior box以参数$(x_{bv},y_{bv},l_{bv},w_{bv})$表示。其中$l_{bv}$和$w_{bv}$可由训练集聚类得到，然后由于在鸟瞰图中，目标框还具有旋转角，所以prior box也需要有旋转角，作者设定为$\\lbrace 0^\\circ,90^\\circ\\rbrace$。\nprior box的正负样本分配主要参考prior box与真实框的IOU大小，当IOU大于0.7时则认定为正样本，低于0.5时则认定为负样本，介于两者之间为忽略样本，为了减少计算量，作者忽略样本全部删除。在剩余的有效prior box中，还会使用IOU阈值为0.7的NMS进一步减少框的个数，在训练阶段，仅保留NMS中的Top 2000个框，而测试时保留Top 300个。\n该结构对类别使用交叉熵损失，对位置大小使用$smooth\\ l1$损失。其中位置尺寸的回归变量为$t=(\\Delta x,\\Delta y,\\Delta z,\\Delta l,\\Delta w,\\Delta h)$，$(\\Delta x,\\Delta y,\\Delta z)$为中心偏移量，并使用相应的prior box尺寸进行归一化，$(\\Delta l,\\Delta w,\\Delta h)$的计算为：$\\Delta S = \\log \\frac{S_{GT}}{S_{anchor}}$，$S\\in\\lbrace l,w,h\\rbrace$。\nNetwork 模型最终训练时通过回归3D box的8个角的角偏移量$(\\Delta x_0,\\Delta x_1,\\dots,\\Delta y_0,\\Delta y_1,\\dots,\\Delta z_0,\\Delta z_1,\\dots)$来修正框的位置和大小。该偏移量采用对应prior box的对角线长度进行归一化。\n正负预测结果由prior box与预测框的IOU决定，大于0.5则认定预测为正，反之为负。损失函数方面参照上面RPN结构，类别损失用交叉熵损失，回归损失用$smooth\\ l1$损失。推断时，在最后使用IOU阈值为0.05的NMS移除预测多余框。\n模型结构如下：\n图中各个视图通过ROI pooling转换为相同大小的特征向量再拼接在一起，最后通过Deep fusion的形式更深一步地融合特征。\n结构正则化 网络结构还使用了drop-path和auxiliary losses两种方法。\n每次迭代中，随机选择global drop path和local drop path。如果为global drop path，则随机从三个视图中选择一个视图，只保留该视图上的路径，其余视图路径抑制，如果为local drop path，则输入到每个连接节点的路径以 50% 的概率随机丢弃。\nauxilary losses结构如下：\n该结构与对应的网络层共享权重，并参与训练损失回传，在推断时去掉该结构。\n参考 Multi-View 3D Object Detection Network for Autonomous Driving\n","permalink":"https://simplestory.github.io/posts/20210806_8e5f2ccbcaba22801139e42b929ed820/","summary":"\u003cblockquote\u003e\n\u003cp\u003e利用三种视图进行3D点云检测（鸟瞰图、前视图、原RGB图）。\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch3 id=\"birds-view\"\u003eBird\u0026rsquo;s view\u003c/h3\u003e\n\u003cp\u003e将点云数据等分为$M$份，在每一份上以$0.1m$的粒度划分网格，并提取每一格内的最高点云高度，作为鸟瞰图的高度图。强度图则采用网格内高度最高的点对应的反射率值，密度图则统计每个网格内的点云数$N$，对特征进行归一化，最后结果为$\\min (1,\\frac{\\log (N+1)}{\\log 64})$，其中64应该为激光线程数。最后得到通道数为$M+2$的特征。\u003c/p\u003e","title":"Multi-View 3D Object Detection Network"},{"content":" 当前工业界常用的多目标跟踪框架。\nSORT 该算法基于目标检测网络（Faster Rcnn），并利用卡尔曼滤波和匈牙利算法（或KM算法），极大地提高了多目标跟踪的速度。大致流程如下：\n目标检测算法获得当前帧的目标框Detections，卡尔曼滤波获得当前帧的轨迹Tracks，对Detections和Tracks进行IOU匹配，最终结果分为三类：\nUnmatched Tracks：即Tracks部分失配，如果失配持续了$T_{lost}$次，则将该目标删除。 Unmatched Detections：即Detections部分失配，需要为该Detection分配一个新的Track。 matched Tracks：这部分说明已经匹配上了，利用这部分对卡尔曼滤波进行更新。 卡尔曼滤波可以根据Tracks的状态来预测下一帧的目标框状态，更新是根据观测值（匹配上的Track）和估计值更新所有Tracks的状态。\nDeepSORT 相比SORT，DeepSORT通过集成表观信息并使用级联匹配来提升SORT的表现。这样模型能够更好地处理目标被长时间遮挡的情况，很大程度地降低了ID switch指标测试值。表观信息也就是目标对应的特征，论文中通过在大型行人重识别数据集上训练得到的深度关联度量来提取表观特征(借用了ReID领域的模型)。整体流程大致如下：\n每个轨迹都会维护一个变量$h$用于记录当前帧与上一次匹配成功帧的差值，在轨迹与detections匹配时$h$置为0。当$h$大于事先设定的阈值$A_{max}$时，则认为该轨迹终止（即长时间匹配不上的轨迹认为已经结束了）。对于没有匹配成功的detections，有可能是新轨迹，也有可能是误检测。论文里设置了一个Unconfirmed状态来辨别，当连续$n\\_init$帧（论文设置为3）该轨迹都能成功匹配则将该轨迹状态更改为Confirmed，否则删除该轨迹。各状态的转移如下：\n匹配上，算法采用了表观模型和运动模型来计算相似度得到代价矩阵，同时使用门控矩阵来限制代价矩阵中过大的值。\n运动模型使用detection和track的马氏距离的平方来刻画运动匹配程度：\n$$ \\begin{aligned} d^{(1)}(i,j) \u0026= (d_j-y_i)^TS_i^{-1}(d_j-y_i) \\\\ b^{(1)}_{i,j} \u0026= I[d^{(1)}(i,j) \\le t^{(1)}] \\end{aligned} $$其中$d_j$表示第$j$个detection，$y_i$表示第$i$个track，$S_i^{-1}$代表$d$和$y$的协方差。$b_{i,j}^{(1)}$为指示器，使用卡方分布的0.95分位点（$t^{(1)}=9.4877$）作为阈值，小于该阈值则匹配成功。\n马氏距离（Mahalanobis Distance）：\n马氏距离表示数据的协方差距离，是一种有效的计算两个未知样本集的相似度的方法。与欧氏距离不同的是它考虑到数据各种特性之间的联系。\n对一个均值为$\\mu=(\\mu_1,\\mu_2,\\dots,\\mu_p)^T$，协方差矩阵为$\\Sigma$的多变量向量$x=(x_1,x_2,\\dots,x_p)^T$，其马氏距离为：\n$$ D_M(x) = \\sqrt{(x-\\mu)^T\\Sigma^{-1}(x-\\mu)} $$协方差矩阵为方阵，维度与样本维度一致。\n表观模型采用余弦距离来度量表观特征之间的距离（由模型提取出一个特征向量，使用余弦距离来进行比对）：\n$$ \\begin{aligned} d^{(2)}(i,j) \u0026= min\\{1-r_j^Tr_k^{(i)}\\vert r_k^{(i)}\\in R_i\\} \\\\ b^{(2)}_{i,j} \u0026= I[d^{(2)}(i,j) \\le t^{(2)}] \\end{aligned} $$其中$r_j^Tr_k^{(i)}$计算的是余弦相似度（余弦距离=1-余弦相似度）。SORT中仅仅用运动信息进行匹配会导致ID Switch比较严重，引入表观模型以及级联匹配可以缓解这个问题。$b_{i,j}^{(2)}$为指示器，余弦距离小于阈值则认为匹配，该阈值属于超参数（论文中设为0.2）。由于轨迹太长会导致表观产生变化，继续使用这种最小余弦距离作为度量就有风险，所以论文中只对轨迹的最新的$L_k=100$之内detections进行计算最小余弦距离。\n综合匹配度是由运动模型和表观模型加权得到的：\n$$ c_{i,j} = \\lambda d^{(1)}(i,j)+(1-\\lambda)d^{(2)}(i,j) \\tag{5} $$$\\lambda$是一个超参数，作者考虑到摄像头会有移动所以将其设置为0。\n门控矩阵计算如下：\n$$ b_{i,j} = \\prod_{m=1}^2b^{(m)}_{i,j} \\tag{6} $$$b_{i,j}$也为指示器，当$b_{i,j}=1$的时候才会被初步匹配。\n为了解决目标被长时间遮挡，DeepSORT提出的另一个方法是级联匹配。当一条轨迹被遮挡了较长时间，卡尔曼滤波器连续预测无法得到更新。这种情况会导致在两个轨迹竞争同一个detection时，遮挡时间较长的轨迹往往匹配得到的马氏距离更小，使得detection更可能和遮挡时间较长的轨迹匹配，干扰了轨迹的持续性。所以使用级联匹配来让每次匹配时考虑的都是遮挡时间相同的轨迹，同时遮挡时间较少的轨迹优先考虑。算法大致如下：\n在匹配的最后阶段还对Unconfirmed和age=1的未匹配轨迹进行基于IOU的匹配。这可以缓解因为表观突变或者部分遮挡导致的较大变化。\n表观特征这部分借用了行人重识别领域的网络模型，这部分的网络是需要提前离线学习好，独立于目标检测和跟踪器模块，功能是提取对应bounding box中的feature，得到一个固定维度的embedding作为该bbox的代表，供计算相似度时使用。论文中用的是wide residual network, 具体结构如下：\n参考 SIMPLE ONLINE AND REALTIME TRACKING\nSIMPLE ONLINE AND REALTIME TRACKING WITH A DEEP ASSOCIATION METRIC\nDeep SORT多目标跟踪算法代码解析\n","permalink":"https://simplestory.github.io/posts/20210805_59094eb979620e0e11f92ffaa71cf228/","summary":"\u003cblockquote\u003e\n\u003cp\u003e当前工业界常用的多目标跟踪框架。\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch2 id=\"sort\"\u003eSORT\u003c/h2\u003e\n\u003cp\u003e该算法基于目标检测网络（Faster Rcnn），并利用卡尔曼滤波和匈牙利算法（或KM算法），极大地提高了多目标跟踪的速度。大致流程如下：\u003c/p\u003e","title":"SORT和DeepSORT"},{"content":" 目标检测中因为定位和分类分支合并的复杂性而产生了许多算法，这些方法从不同角度出发提升了算法性能，但难以进行统一度量。论文采用多个注意力机制将不同的目标检测头进行统一，主要有特征层次的注意力、空间位置的注意力、通道间的注意力，分别对应于尺度感知、空间感知和任务感知。该方法可以在不增加计算量的情况下明显地提升模型性能。\n给定一个从特征金字塔（即backbone）中$L$个不同层抽取的特征图组成的集合$F_{in} = \\lbrace F_i\\rbrace^L_{i=1}$，将集合中的特征图通过上采样或下采样到中间大小，得到一个特征张量$F\\in R^{L\\times H\\times W\\times C}$，其中$L$表示抽取的层数，$H$、$W$、$C$表示高、宽、和通道数。令$S=H\\times W$，将特征转为$F\\in R^{L\\times S\\times C}$。这三个维度分别对应了三种作用：\n目标尺度的差异对应了不同尺度的特征； 不同目标形状的不同集合变换对应了特征的不同空间位置； 目标表达与任务的多样性对应了不同的通道特征 直接在所有维度上学习注意力函数会产生大量的计算，所以论文将注意力函数转换为对应3个维度的序列注意力，每个注意力只关注它当前的维度：\n$$ W(F) = \\pi_C(\\pi_S(\\pi_L(F)\\cdot F)\\cdot F)\\cdot F $$尺度感知注意力（Scale-aware Attention） 首先引入尺度上的注意力基于语义重要性来动态融合来自不同尺度的特征：\n$$ \\pi_L(F)\\cdot F = \\sigma\\left(f\\left(\\frac{1}{SC}\\sum_{S,C}F\\right)\\right)\\cdot F $$$f(\\cdot)$是线性函数，这里用$1\\times 1$卷积层来近似。$\\sigma(\\cdot)$是hard-sigmoid函数，如下：\n$$ \\sigma(x) = \\max (0,\\min (1,\\frac{x+1}{2})) $$空间感知注意力（Spatial-aware Attention） 接下来使用空间感知注意力来提升模型不同空间位置的判别能力。由于$S$维度较大，论文对其进行解藕：首先采用形变卷积对注意力学习稀疏化，然后进行特征跨尺度集成：\n$$ \\pi_S(F)\\cdot F = \\frac{1}{L}\\sum^L_{l=1}\\sum^K_{k=1}w_{l,k}\\cdot F(l;p_k+\\Delta p_k;c)\\cdot \\Delta m_k $$其中$K$为稀疏采样位置数，其余参数与形变卷积中的参数类似。\n任务感知注意力（Task-aware Attention） 为促进联合学习与目标表达能力的泛化性，论文还设计了一种任务感知注意力。它可以动态开关特征通道以辅助不同任务：\n$$ \\pi_C(F)\\cdot F = \\max(\\alpha^1(F)\\cdot F_c+\\beta^1(F),\\alpha^2(F)\\cdot F_c+\\beta^2(F)) $$其中$[\\alpha^1,\\alpha^2,\\beta^1,\\beta^2]^T = \\theta(\\cdot)$是超参数，用于控制激活阈值，而$\\theta(\\cdot)$的实现与动态ReLU类似。\n结构 将上述的注意力机制按序列使用，同时还可以进行多次堆叠，DynamicHead结构图大致如下：\n任意类型的backbone均可用于提取特征金字塔并进一步缩放到统一尺度构建特征张量，然后将其作为DynamicHead的输入，再经过多个包含尺度感知、空间感知以及任务感知的DyHead模块串行堆叠运算。DyHead的输出可以用于不同的任务，比如分类、中心/方框回归等等。\none stage：单阶段检测器通过在特征图上密集采样来获取目标位置，简化了目标检测器的设计。DynamicHead与单阶段检测器的集成如下所示：\ntwo stage：两阶段检测器通过RPN层和ROI池化层从特征金字塔中提取中间表达。为了耦合这种特性，论文在ROI池化前使用尺度感知和空间感知，用任务感知替换原始的全连接层，具体如下：\n参考 Dynamic Head: Unifying Object Detection Heads with Attentions\nmicrosoft/DynamicHead\n","permalink":"https://simplestory.github.io/posts/20210804_697dd2d087a31ba4aab4ff575bde80be/","summary":"\u003cblockquote\u003e\n\u003cp\u003e目标检测中因为定位和分类分支合并的复杂性而产生了许多算法，这些方法从不同角度出发提升了算法性能，但难以进行统一度量。论文采用多个注意力机制将不同的目标检测头进行统一，主要有特征层次的注意力、空间位置的注意力、通道间的注意力，分别对应于尺度感知、空间感知和任务感知。该方法可以在不增加计算量的情况下明显地提升模型性能。\u003c/p\u003e\n\u003c/blockquote\u003e","title":"Dynamic Head"},{"content":" 让它动起来。\nDynamic Convolution 一般的静态卷积用同一个卷积核对所有输入图像做相同的操作，而动态卷积会对不同的图像做调整，用更适合的卷积参数进行处理，这样可以在不增加网络深度或宽度的情况下增加模型的表达能力。\n动态卷积根据注意力动态地聚合多个并行卷积核。注意力会根据输入动态地调整每个卷积核的权重，从而生成自适应的动态卷积。由于注意力是输入的函数，动态卷积不再是一个线性函数。通过注意力以非线性的方式叠加卷积核具有更强的表示能力。大致结构如下：\n如上所示，动态卷积相比普通静态卷积引入了两部分额外的计算：注意力模块和卷积核的叠加。但这两部分的计算量很少，并且叠加多个卷积核在计算上也非常高效。少量的额外计算与显著的表达能力提升使得动态卷积非常适合轻量级的神经网络。\n对注意力取值进行限制会简化注意力模型的学习，同时也会缩小多个卷积的叠加核的取值空间。论文将注意力取值限制在0到1之间，同时所有注意力的和为1。如下图所示，假如使用3个卷积核，注意力在0到1之间把叠加核限制在两个三棱锥中，注意力和为1把叠加核进一步限制在以这三个卷积核为顶点的三角形中。对于这两个限制，论文选用了softmax来实现。\n另外，限制注意力取值接近均匀分布有利于多个卷积核在训练初期同时学习。因为softmax会输出更稀疏的注意力，所以论文采用了温度退火（temperature annealing）来提升模型的准确率（接近均匀分布的注意力可以通过使用较大的temperature来实现）。\nDynamic ReLU 通常ReLU和它的一些改进版（Leaky ReLU、PReLU）激活函数的参数都是固定的，所以论文提出了动态ReLU。它可以根据输入特征来调整ReLU的参数。\n原版的ReLU为$y = \\max \\lbrace x,0\\rbrace$，对于输入向量$x$的$c$维特征，激活值计算为$y_c = \\max \\lbrace x_c, 0\\rbrace$。ReLU可统一表示为分段线性函数$y_c = \\max\\limits_k \\lbrace a^k_cx_c+b^k_c\\rbrace$。基于前面的式子，可以将DY-ReLU定义为多个（$K$个）线性函数的最大值。即对所有的输入$x=\\lbrace x_c\\rbrace$自适应$a^k_c$、$b^k_c$有：\n$$ \\begin{aligned} y_c \u0026= f_{\\theta(x)}(x_c) \\\\ \u0026= \\max\\limits_{1\\le k\\le K}\\{a^k_c(x)x_c+b^k_c(x)\\} \\end{aligned} $$参数$(a^k_c,b^k_c)$为超函数$\\theta(x)$的输出：\n$$ \\theta(x) = [a^1_1,\\dots,a^1_C,\\dots,a^K_1,\\dots,a^K_C,b^1_1,\\dots,b^1_C,\\dots,b^K_1\\dots,b^K_C]^T $$其中$C$为通道数，激活参数$(a^k_c, b^k_c)$不仅与$x_c$相关，也与$x_{j\\ne c}$相关，即还与其它通道的输入有关。\n论文采用类似于SE模块的轻量级网络进行超函数的实现。对于大小为$C\\times H\\times W$的输入$x$，首先用全局平均池化来压缩空间信息，之后通过两个全连接层（中间包含ReLU），最后接一个归一化层，归一化层使用$2\\sigma(x)-1$将结果约束在-1到1之间（$\\sigma$为Sigmoid函数）。最后一共输出$2KC$个元素，分别对应于$a_{1:C}^{1:K}$和$b_{1:C}^{1:K}$的残差，即$\\Delta a_{1:C}^{1:K}$和$\\Delta b_{1:C}^{1:K}$。最终输出为初始值和残差的和：\n$$ \\begin{aligned} a^k_c(x) \u0026= \\alpha^k+\\lambda_a\\Delta a^k_c(x) \\\\ b^k_c(x) \u0026= \\beta^k+\\lambda_b\\Delta b^k_c(x) \\end{aligned} $$其中$\\alpha^k$、$\\beta^k$为$a^k_c$、$b^k_c$的初始值。$\\lambda_a$、$\\lambda_b$是用于控制残差大小的标量。$\\alpha^k$、$\\beta^k$、$\\lambda_a$、$\\lambda_b$为超参数且与通道无关，默认情况下，$\\lambda_a=1.0$、$\\lambda_b=0.5$，故默认情况下的超参数为$\\alpha^{1:K}$、$\\beta^{1:K}$。\n论文还给出了DY-ReLU的三种实现，大致如下：\nDY-ReLU-A 空间位置和通道均共享（spatial and channel-shared）。最终输出大小为$2K$，计算较简单，表达能力也较弱。\nDY-ReLU-B 空间位置共享而通道不共享（spatial-shared and channel-wise）。最终输出大小为$2KC$，即每个通道$2K$个。\nDY-ReLU-C 空间位置和通道均不共享（spatial and channel-wise）。每个通道的每个元素都有一个独立的激活函数，最终输出的参数为$2KHWC$，直接使用全连接层会带来大量的额外计算。论文对此进行了改进，如上图所示，将空间位置解藕到另一个注意力分支，最后将通道参数$a_{1:C}^{1:K}$、$b_{1:C}^{1:K}$乘以空间位置注意力$\\pi_{1:HW}$。空间位置注意力的计算简单地使用$1\\times 1$卷积和归一化方法，归一化使用了带约束的softmax函数：\n$$ \\pi_{h,w} = \\min \\{\\frac{\\gamma\\exp (z_{h,w}/\\tau)}{\\sum_{h,w}\\exp (z_{h,w}/\\tau)}, 1\\} $$其中$\\gamma$是一个标量，用于扩大softmax的值，以防止梯度消失，论文设置为$\\frac{HW}{3}$。$z_{h,w}$为$1\\times 1$卷积的输出。$\\tau$是temperature，一个大的temperature（如$\\tau$ = 10)可用于防止训练早期空间位置注意力过于稀疏。\n参考 Dynamic Convolution: Attention over Convolution Kernels\nDynamic ReLU\n","permalink":"https://simplestory.github.io/posts/20210803_2d8c6e5a2f995c3808422afc8967c85e/","summary":"\u003cblockquote\u003e\n\u003cp\u003e让它动起来。\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch2 id=\"dynamic-convolution\"\u003eDynamic Convolution\u003c/h2\u003e\n\u003cp\u003e一般的静态卷积用同一个卷积核对所有输入图像做相同的操作，而动态卷积会对不同的图像做调整，用更适合的卷积参数进行处理，这样可以在不增加网络深度或宽度的情况下增加模型的表达能力。\u003c/p\u003e","title":"动态卷积与动态ReLU"},{"content":" 数学是科学之王。 ——高斯\n概率无向图模型 在了解概率无向图模型之前，先看看概率图模型（PGM，probabilistic graphical model）的定义：它是由图表示的概率分布。设有联合概率分布$P(Y)$，$Y\\in \\mathcal{Y}$是一组随机变量，由无向图$G=(V, E)$表示概率分布$P(Y)$，即在图$G$中，结点$v\\in V$表示一个随机变量$Y_v$，$Y_v=(Y_v)_{v\\in V}$；边$e\\in E$表示随机变量之间的概率依赖关系。\n给定一个概率联合分布$P(Y)$和表示它的无向图$G$。以下说明三个性质。\n成对马尔可夫性（pairwise Markov property）\n$$ P(Y_u,Y_v\\mid Y_O) = P(Y_u\\mid Y_O)P(Y_v\\mid Y_O) $$其中$u$，$v$为无向图中任意两个没有边连接的结点，对应的随机变量分别为$Y_u$和$Y_v$，$O$为无向图中除了$u$、$v$外的其他所有结点，对应的随机变量组为$Y_O$。即给定随机变量组为$Y_O$的条件下，随机变量$Y_u$和$Y_v$是条件独立的。\n局部马尔可夫性（local Markov property）\n$$ P(Y_u,Y_O\\mid Y_W) = P(Y_v\\mid Y_W)P(Y_O\\mid Y_W) $$其中$v\\in V$为无向图$G$中任意一个结点，对应的随机变量为$Y_v$，$W$是与$v$有边连接的所有结点，对应随机变量组为$Y_W$，$O$是$v$，$W$之外的其他所有结点，对应随机变量组为$Y_O$。即给定随机变量组$Y_W$的条件下随机变量$Y_v$与随机变量组$Y_O$是独立的。\n全局马尔可夫性（global Markov property）\n$$ P(Y_A,Y_B\\mid Y_C) = P(Y_A\\mid Y_C)P(Y_B\\mid Y_C) $$其中结点集合$A$、$B$是在无向图$G$中被结点集合$C$分开的任意结点集合，对应的随机变量组分别为$Y_A$，$Y_B$，$Y_C$。即给定随机变量组$Y_C$条件下随机变量组$Y_A$和$Y_B$是条件独立的。\n上述三种定义是等价的。\n综合以上给出概率无向图模型的定义。设有联合概率分布$P(Y)$，由无向图$G=(V,E)$表示，在图$G$中，结点表示随机变量，边表示随机变量之间的依赖关系，如果联合概率分布$P(Y)$满足成对、局部或全局马尔可夫性，就称此联合概率分布为概率无向图模型（probability undirected graphical model），或马尔可夫随机场（MRF，Markov random field）。\n因子分解 概率图模型的核心是计算联合概率分布\n无向图$G$中任何两个结点均有边连接的结点子集称为团（clique）。若$C$是无向图$G$的一个团，并且不能再加进任何一个$G$的结点使其成为一个更大的团，则称此$C$为最大团（maximal clique）。\n将概率无向图模型的联合概率分布表示为其最大团上的随机变量的函数的乘积形式的操作为概率无向图模型的因子分解（factorization）。\n给定概率无向图模型，设其无向图为$G$，$C$为$G$上的最大团，$Y_c$表示$C$对应的随机变量。由Hammersley-Cliffoed定理有**当且仅当随机变量的联合概率密度严格为正时，概率无向图模型等价于吉布斯随机场（GRF，Gibbs Random Field）。**而GRF的联合概率分布$P(Y)$可写成图中所有最大团$C$上的函数$\\Psi_C(Y_C)$的乘积形式，即：\n$$ \\begin{aligned} P(Y) \u0026 = \\frac{1}{Z}\\prod_C\\Psi_C(Y_C) \\\\ Z \u0026 = \\sum_Y\\prod_C\\Psi_C(Y_C) \\end{aligned} $$其中$Z$是规范化因子（normalization factor），用于保证$P(Y)$构成一个概率分布，即和为1。函数$\\Psi_C(Y_C)$称为势函数（potential function）。这里要求势函数是严格为正的，为了方便，通常定义为指数函数：\n$$ \\Psi_C(Y_C) = exp\\{-E(Y_C)\\} $$条件随机场 定义：设$X$与$Y$是随机变量，$P(X\\mid Y)$是在给定$X$的条件下$Y$的条件概率分布，若随机变量$Y$构成一个由无向图$G=(V,E)$表示的马尔可夫随机场，即\n$$ P(Y_v\\mid X,Y_w,w\\ne v) = P(Y_v\\mid X,Y_w,w\\sim v) $$对任意结点$v$成立，则称$P(Y\\mid X)$为条件随机场。其中$w\\sim v$表示在图$G=(V,E)$中与结点$v$有边连接的所有结点$w$，$w\\ne v$表示结点$v$以外的所有结点，$Y_v$、$Y_u$与$Y_w$为结点$v$，$u$与$w$对应的随机变量。\n$X$、$Y$可以有不同的图结构，但一般情况下假设他们有相同的图结构，这就有线性条件随机场（linear chain conditional random field)。设$X=(X_1,X_2,\\cdots,X_n)$，$Y=(Y_1,Y_2,\\cdots,Y_n)$均为线性链表示的随机变量序列，若在给定随机变量序列$X$的条件下，随机变量序列$Y$的条件概率分布$P(Y\\mid X)$构成条件随机场，即满足马尔可夫性\n$$ P(Y_i\\mid X,Y_1,\\cdots,Y_{i-1},Y_{i+1},\\cdots,Y_n) = P(Y_i\\mid X,Y_{i-1},Y_{i+1}) \\\\ i=1,2,\\cdots,n \\ (在i=1和n时只考虑单边) $$则称$P(Y\\mid X)$为线性链条件随机场，也是对数线性模型（log linear model)。\n线性链条件随机场参数化形式 对于线性链条件随机场$P(Y\\mid X)$，在随机变量$X$取值为$x$的条件下，随机变量$Y$取值为$y$的条件概率有参数化形式如下：\n$$ P(y\\mid x) = \\frac{1}{Z(x)}exp\\left(\\sum_{i,k}\\lambda_kt_k(y_{i-1},y_i,x,i)+\\sum_{i,l}\\mu_ls_l(y_i,x,i)\\right) $$其中\n$$ Z(x) = \\sum_yexp\\left(\\sum_{i,k}\\lambda_kt_k(y_{i-1},y_i,x,i)+\\sum_{i,l}\\mu_ls_l(y_i,x,i)\\right) $$式中，$t_k$和$s_l$是特征函数，$\\lambda_k$和$\\mu_l$是对应的权重，$Z(x)$是规范化因子，求和是在所有可能的输出序列上进行的。$t_k$是定义在边上的特征函数，为转移特征，依赖于当前和前一个位置，$s_l$是定义在结点$l$上的特征函数，为状态特征，依赖于当前位置，这两个特征函数都依赖于位置，是局部特征函数，而且一般取值为1或0，满足条件时为1否则为0。\n线性链条件随机场简化形式 设转移特征有$K_1$个，状态特征有$K_2$个，且$K=K_1+K_2$，有\n$$ \\begin{aligned} \u0026 f_k(y_{i-1},y_i,x,i) = \\begin{cases} t_k(y_{i-1},y_i,x,i), \u0026\\ k=1,2,\\cdots, K_1 \\\\ s_l(y_i,x,i), \u0026\\ k=K_1+l;\\ l=1,2,\\cdots, K_2 \\end{cases} \\\\ \u0026 \\Rightarrow f_k(y,x) = \\sum_{i=1}^nf_k(y_{i-1},y_i,x,i), \\qquad k=1,2,\\cdots,K \\end{aligned} $$同样，用$w_k$来统一表达权值：\n$$ w_k = \\begin{cases} \\lambda_k, \u0026\\ k=1,2,\\cdots,K_1 \\\\ \\mu_l, \u0026\\ k=K_1+l;\\ l=1,2,\\cdots,K \\end{cases} $$综上可得：\n$$ P(y\\mid x) = \\frac{1}{Z(x)}exp\\sum_{k=1}^{K}w_kf_k(y,x) \\\\ Z(x) = \\sum_yexp\\sum_{k=1}^Kw_kf_k(y,x) $$若用$w=(w_1,w_2,\\cdots,w_K)^T$表示权值向量，$F(y,x)=(f_1(y,x),f_2(y,x),\\cdots,f_K(y,x))^T$表示全局特征向量，则上式可进一步表达如下：\n$$ P_w(y\\mid x) = \\frac{exp(w\\cdot F(y,x))}{Z_w(x)} \\\\ Z_w(x) = \\sum_yexp(w\\cdot F(y,x)) $$线性链条件随机场矩阵形式 对于输入序列$x$的每个位置$i=1,2,\\cdots,n+1$，定义一个$m$阶矩阵（$m$是标记$y_i$取值的个数）：\n$$ M_i(x) = [M_i(y_{i-1},y_i\\mid x)] \\\\ M_i(y_{i-1},y_i\\mid x) = exp(W_i(y_{i-1},y_i\\mid x)) \\\\ W_i(y_{i-1},y_i\\mid x) = \\sum_{k=1}^Kw_kf_k(y_{i-1},y_i,x,i) $$由上面的简化形式结合这里的$m$阶矩阵有\n$$ \\begin{aligned} \u0026 exp\\sum_{k=1}^Kw_k\\sum_{i=1}^nf_k(y_{i-1},y_i,x,i) \\\\ = \u0026 exp\\sum_{k=1}^K\\sum_{i=1}^nw_kf_k(y_{i-1},y_i,x,i) \\\\ = \u0026 exp\\sum_{i=1}^n\\sum_{k=1}^Kw_kf_k(y_{i-1},y_i,x,i) \\\\ = \u0026 exp\\sum_{i=1}^nW_i(y_{i-1},y_i\\mid x) \\\\ = \u0026 \\prod_{i=1}^nexp(W_i(y_{i-1},y_i\\mid x)) \\\\ = \u0026 \\prod_{i=1}^nM_i(y_{i-1},y_i\\mid x) \\\\ = \u0026 \\prod_{i=1}^{n+1}M_i(y_{i-1},y_i\\mid x) \\qquad (添加起点状态和终止状态) \\end{aligned} $$上面推导的最后一步中$y_0=start$和$y_{n+1}=stop$分别表示考试状态和终止状态。可得：\n$$ P_w(y\\mid x) = \\frac{1}{Z(x)}\\prod_{i=1}^{n+1}M_i(y_{i-1},y_i\\mid x) \\\\ Z_w(x) = (M_1(x)M_2(x)\\cdots M_{n+1}(x))_{start,stop} $$规范化因子$Z_w(x)$是以start为起点stop为终点通过状态的所有路径$y_1y_2\\cdots y_n$的非规范化概率$\\prod_{i=1}^{n+1}M_i(y_{i-1},y_i\\mid x)$之和。\n一些应用 CRF适用于对上下文相关的样本间存在依赖关系的情况。在NLP领域有着大量的应用，对于CV领域，受限于本人的见识，目前见到的应用都是在图片分割领域。分割是像素级的，所以图像中的每一个像素都是一个结点$x$，对应的标签即为$y$，像素与像素之间的关系可以表示为边，由于周围像素对标签的影响程度要远大于其它像素，所以我们可以通过周围像素的标签来推断出目标像素的标签，这一套下来就形成了条件随机场。\nDenseCRF DenseCRF为全连接条件随机场，与普通条件随机场的不同就在于，二元势函数描述的是每一个像素与其他所有像素的关系。\n由之前的定义可知，CRF的势函数为$\\Phi_c(X_c)=e^{-E(X_c)}$（可选用其它函数，但需保证$\\Phi_c(X_c) \u003e 0$恒成立），其中$E(X_c)$为能量函数，对于分割领域，能量函数表达式一般如下：\n$$ E(X) = \\sum_i\\phi_u(x_i)+\\sum_{i","permalink":"https://simplestory.github.io/posts/20210516_5df2f4dc23f490c5d68321fa51cabc59/","summary":"\u003cblockquote\u003e\n\u003cp\u003e数学是科学之王。 ——高斯\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch2 id=\"概率无向图模型\"\u003e概率无向图模型\u003c/h2\u003e\n\u003cp\u003e在了解概率无向图模型之前，先看看概率图模型（PGM，probabilistic graphical model）的定义：它是由图表示的概率分布。设有联合概率分布$P(Y)$，$Y\\in \\mathcal{Y}$是一组随机变量，由无向图$G=(V, E)$表示概率分布$P(Y)$，即在图$G$中，结点$v\\in V$表示一个随机变量$Y_v$，$Y_v=(Y_v)_{v\\in V}$；边$e\\in E$表示随机变量之间的概率依赖关系。\u003c/p\u003e","title":"CRF及其在分割领域上的应用"},{"content":" 图像分割是对图像进行像素级的分类，主要有语义分割(Semantic Segmentation)和实例分割(Instance Segmentation)。语义分割只要求做到像素层面的分割，而实例分割要求在语义分割的基础上实现对不同个体的分割。这篇文章主要简单介绍几种基础的语义分割网络。\nFCN FCN(Full Convolution Network)全卷积网络，是一个经典的图像分割网络，主要组成结构为Encoder-Decoder，其中Encoder负责提取图像特征信息，Decoder负责解析获取的特征信息。FCN将网络中的全连接层更换为卷积层，这样可以得到2D的特征图，在通过转置卷积(Deconvolution)进行上采样，最后通过softmax层得到各类别mask，同时也能适应不同尺寸的输入图片。大致结构如下图所示。\n上面结构图包含有3个FCN模型，分别是FCN-8s、FCN-16s和FCN-32s。\nFCN-32s是输入图片经过32倍下采样后直接32倍上采样进行分割。FCN-16s则是进行2倍上采样后与上一层16倍的下采样特征图逐元素相加(Eltwise)，再进行16倍上采样进行分割。FCN-8s是之前Eltwise操作后再进行2倍上采样，与8倍下采样特征图进行Eltwise操作，再进行8倍上采样进行分割。\n论文经过对比，FCN-8s的效果要优于其它两个模型，即多层特征图融合有利于提高分割准确性。\nSegNet 由上面的FCN结构可以看出在Decoder阶段模型只用了一层转置卷积进行上采样，操作十分粗糙。SegNet的结构也是Encoder-Decoder，但两个结构为对称的，其中Encoder沿用了Vgg-16网络模型进行特征提取，而Decoder采用Uppooling对特征图进行上采样，然后对上采样图像进行卷积操作，目的是完善物体的几何形状，弥补Encoder中池化层将物体缩小造成的细节损失。在上采样过程中还用到了pooling indices，即Encoder池化操作中保存的权值索引，这样在上采样时直接将数据放在索引位置，再接卷积层进行训练学习。结构如下。\nDeconvNet 这个网络与SegNet相似，也是采用的Encoder-Decoder对称结构，在上采样时也使用了pooling indices，但与SegNet不同的是模型在Encoder与Decoder之间采用了全连接层。为了得到实例级分割结果，可以将训练好的模型，应用到图片的proposals中，最终将这些结合组成最终的分割结果，这样同时也解决了原始FCN网络中的尺寸问题，物体的细节信息也更详尽。模型大致结构如下。\nUNet UNet的结构也是一个对称的Encoder-Decoder。先上结构图：\n从结构图中可以看出每次进行上采样（该模型采用反卷积进行上采样）时都会和Encoder对应部分进行通道拼接，拼接前还需要对Encoder对应部分的特征图进行裁剪，这里的裁剪主要是保留图像中间部分，裁去边缘部分使得图像大小与Decoder对应部分一致。最后得到一个通道数为2的特征图，为了进一步得到概率图，还需要对特征图进行softmax。\n总结 对于深度学习的语义分割，一个通用的框架大致如下：\n前端：使用全卷积网络进行特征提取 下采样+上采样：Convlution + Deconvlution/Resize 多尺度特征融合：特征逐点相加、特征拼接 获得像素级的segement map：对每个像素点进行类别判定 后端：使用CRF（条件随机场）/MRF（马尔可夫随机场）优化前端的输出得到优化后的分割图 参考 Fully Convolutional Networks for Semantic Segmentation\nSegNet: A Deep Convolutional Encoder-Decoder Architecture for Image Segmentation\nLearning Deconvolution Network for Semantic Segmentation\nU-Net: Convolutional Networks for Biomedical Image Segmentation\n","permalink":"https://simplestory.github.io/posts/20210430_b30ab79aff271e301dde9ca18c2e8085/","summary":"\u003cblockquote\u003e\n\u003cp\u003e图像分割是对图像进行像素级的分类，主要有语义分割(Semantic Segmentation)和实例分割(Instance Segmentation)。语义分割只要求做到像素层面的分割，而实例分割要求在语义分割的基础上实现对不同个体的分割。这篇文章主要简单介绍几种基础的语义分割网络。\u003c/p\u003e\n\u003c/blockquote\u003e","title":"图像分割基础网络"},{"content":" 目标检测是计算机视觉领域里的一个重要分支，其本质内容是：What objects are where？ 它同时作为一个基础任务为领域内一些更复杂的问题（如实例分割、图片文本识别、目标跟踪等）提供支持。从应用的角度来看，目标检测可以划分为两大类：通用目标检测和检测应用。通用目标检测主要是探索在统一的算法框架下检测不同类型目标的方法，类似于人类的视觉感知；检测应用则侧重于实际场景，例如行人检测、人脸检测和文本检测等。\nMap 目标检测领域从1999年发展至今已过了大约20个年头。这期间相关的算法模型发展路线如下图所示（摘自原论文）：\n本文以此为依据按照发展历程记录各个算法的关键贡献。\nTraditional Detection Methods 在发展前期，该领域内的算法主要以传统算法为准，即需要自己去设计相关特征供机器学习。\nViola-Jones Detectors 2001年，P.Viola和M.Jones发表了经典的目标检测算法（Viola-Jones Detector）。该算法是在Adaboost的基础上使用类Haar特征作为输入进行检测，同时使用滑窗的方式来判断某一区域是否有目标。滑窗的引入增加了大量的计算负担，为此作者采用了几种方法对算法进行了加速：\nIntegral image：Haar特征考虑的是某一特定位置相邻的矩形区域，把每个矩形区域的像素相加然后再相减（example）。为了计算Haar-like特征，需要对矩形区域的所有像素求和，一个图像所能形成的矩形区域有大有小，如果每个矩形区域都用遍历所有像素再求和的运算方法。积分图就是对于图像中的任何一点，该点的积分图像值等于位于该点左上角的所有像素之和，公式如下： $$ I(x,y) = \\sum_{x'\u003c x}\\sum_{y' \u003c y}f(x',y') $$积分图中存在如下关系：\n$$ I(x,y) = f(x,y)+I(x,y-1)+I(x-1,y)-I(x-1,y-1) $$以上$I$表示积分图，$f$表示原图像。通过积分图，可以计算图像上任意矩形区域的像素和：\n$$ S_{abcd} = I(d)-I(b)-I(c)+I(a) $$ Feature selection：图像提取出来的Haar特征是比较多的，作者假设这些特征中只有一小部分是有用的，并使用了Adaboost进行了特征筛选，通过减少特征数目来减轻运算负担。 Detection cascades：为了进一步降低计算量，作者没有使用传统的Adaboost分类器，而是使用多个小型Adaboost级联，同时一旦在某一级分类器中检测出不是目标，则立即停止该区域的检测。 HOG Detectors 2005年，N.Dalal和B.Triggs提出了HOG（Histogram of Oriented Gradients）特征提取算法，它通过计算和统计图像局部区域的梯度方向直方图来构成特征。这么做是因为图像中局部目标的表象和形状能够被梯度或边缘的方向密度分布很好地描述。具体是对灰度图像进行分块，在计算各块中各像素点的梯度或边缘方向直方图，最后组合这些直方图构成特征。\n上图展示了一个典型的HOG检测器执行的具体步骤。这里使用Gamma矫正目的是调节图像的对比度，降低图像局部的阴影和光照变化所造成的影响，同时可以抑制噪音的干扰。由于局部光照的变化以及前景背景对比度的变化，使得梯度强度的变化范围非常大。这就需要对梯度强度做归一化，归一化能够进一步地对光照、阴影和边缘进行压缩。作者通过联合几个cell的特征来进行归一化操作。\nDeformable Part-based Model (DPM) 2008年，P.Felzenszwalb提出了可变组件模型（Deformable Part Model），$v_i$即一种基于组件的检测算法，是HOG检测器的一种扩展。一个典型的DPM模型包括一个根滤波器和一些组件滤波器，其中根滤波器覆盖了整个目标，而组件滤波器是覆盖目标某一部分的高分辨率模板。对于一个目标，该模型在形式上将其定义为一个元组：$(F_0,P_0,P_1,...,P_n,b)$，$F_0$为根滤波器，$P_i$为第$i$个部件的模型， 是表示偏差值。每一个部件模型用一个三元组表示$(F_I, v_i, d_i)$，$F_i$是第$i$个部件的模型，是一个二维向量，指定第$i$个滤波器的锚点位置（改位置为未发生形变的标准位置）相对于根的坐标，$d_i$是一个四维向量，指定了一个二次函数的参数，此二次函数表示部件的每一个可能位置相对于锚点位置的偏移代价。\n上图是根模型、组件模型和组件偏移代价的可视化图。这里以行人为例，其中左边的图片是根模型，它比较粗略地表示了一个直立行人；中间图片是组件模型，各组件模型为矩形框内的部分，分辨率为根模型的两倍，这样可以获得更好的效果；右边图片则是组件模型的偏离损失，越亮的区域表示偏离损失代价越大，组件模型的标准位置的偏离损失为0。为了降低模型的复杂度，根模型和组件模型都是轴对称的。\nDPM采用了传统的滑动窗口检测方式，通过构建尺度金字塔在各个尺度搜索。下图为某一尺度下的行人检测流程，即行人模型的匹配过程。 某一位置与根模型或组件模型的响应得分，为该模型与以该位置为锚点（即左上角坐标）的子窗口区域内的特征的内积。也可以将模型看作一个滤波算子，响应得分为特征与待匹配模型的相似程度，越相似则得分越高。左侧为根模型的检测流程，滤波后的图中，越亮的区域代表响应得分越高。右侧为各组件模型的检测过程。首先，将特征图像与模型进行匹配得到滤波后的图像。然后，进行响应变换。响应变换是以锚点为参考位置，综合组件模型与特征的匹配程度和组件模型相对标准位置的偏离损失，得到的最优的组件模型位置和响应得分。\nDeep Larning Detection Methods 自从2012年Alex神经网络在Imagenet上获得SOTA的成绩，神经网络开始被人重视，这里介绍几种关键算法。\nRCNN 2014年，R.Girshick等人发表了RCNN，首次将神经网络模型引入目标检测领域。RCNN通过selective search从原图中获得大量的候选框，对于每个候选框使用卷积神经网络获取图像特征，之后对该图像特征调用线性SVM分类器进行分类，最后即可得到图像中目标的类别和位置大小。\n整体上模型可分为三部分：\n生成与类别无关的候选区域 用来从每一个候选区域中提取固定长度特征向量的卷积网络 一组线性SVM分类器 SPPNet 对于RCNN，它需要将每个候选区域缩放到固定尺寸，然后对每个区域提取卷积特征。这两步操作带来了一些性能瓶颈。为了解决这些瓶颈问题，2014年，K.He等人提出了SPPNet模型。作者在特征提取网络最后的全连接层前添加了一个池化层来对任意输入产生固定的输出，以此来解决RCNN中需要固定尺寸的候选区域输入问题。模型还借助了空间金字塔的思路，即将一副图像划分为不同尺度的块，对每一块提取特征最后拼接在一起。\n下图展示了SPPNet和RCNN在处理不同尺寸的输入图像的差别。RCNN会通过裁剪或缩放来获取固定大小的图片，再传入卷积神经网络进行计算。SPPNet则通过先经过卷积网络提取特征，再由池化层获得固定大小的特征向量作为全连接层的输入。最重要的一点是每幅图片只需要提取一次特征。\n空间金字塔池化层，其主要目的是对于任意尺寸的输入产生固定大小的输出。思路是对于任意大小的feature map首先分成16、4、1个块，然后在每个块上做最大池化操作，池化后的特征拼接得到一个固定维度的输出。以满足全连接层的需要。\nFast RCNN 和RCNN一样，SPPNet也需要训练CNN提取特征，然后训练SVM分类这些特征。需要巨大的存储空间，并且分开训练也很复杂。2015年，R.Girshick推出了Fast RCNN模型，他将检测器和框回归器统一在一个框架内，方便进行统一训练。它将整个图像和一组候选框都作为输入，模型首先用一组卷积层和最大池化层对整张图像进行特征提取，再在提取出的特征图上，针对每一个候选框用RoI pooling提取出一个固定长度的特征向量，之后送入全连接层，最终分成两个分支分别进行目标识别和边界框回归。虽然和RCNN和SPPNet相比，Fast RCNN有着很大的提升，但是该模型还是使用selective search生成候选框，这是该模型的主要速度瓶颈。\nFaster RCNN 对于RCNN、SPPNet以及Fast RCNN采用的都是selective search来获得目标候选框，这部分的操作拖慢了整个模型的运行速度。2015年，S.Ren等人提出了Faster RCNN，这是首个端到端的、运行速度接近实时的深度学习目标检测模型。主要贡献是提出了RPN层。通过RPN层可以快速的生成候选区域，同时也引入了锚框来框选候选区域。大致结构如下，其中红框表示RPN层（图片摘自网络）\nFaster RCNN抛弃了传统的通过滑动窗口和selective search来生成候选框的做法，直接使用RPN生成检测框，这也是Faster RCNN的巨大优势，能极大提升检测框的生成速度，也是从这里，目标检测引入了Anchor来作为目标候选框。\nAnchor的生成主要由长宽比和缩放尺度这两个参数决定的。例如右图是Faster RCNN在三个长宽比（ratios=[0.5, 1.0, 2.0]）和三个缩放尺度（scales=[8, 16, 32]）下生成的九个锚框。\n锚框是分布在特征图的每一个像素点上的，即锚框的中心位置为特征图像素点的位置。最后生成的锚框会布满整个特征图，并且同一像素点下的各个锚框尺度并不一样，所以实际上通过锚框就引入了检测中常用到的多尺度方法。\n大致计算方式如下：\n$$ \\begin{cases} w\\times h = s \\\\ \\frac{w}{h} = ratio \\end{cases} \\Rightarrow \\begin{cases} h = \\sqrt{\\frac{s}{ratio}} \\\\ w = ratio\\cdot h = \\sqrt{s\\cdot ratio} \\end{cases} $$进一步可以得到\n$$ \\begin{cases} h = \\sqrt{\\frac{s}{ratio}\\cdot scale} \\\\ w = ratio\\cdot h = \\sqrt{s\\cdot ratio \\cdot scale} \\end{cases} $$一张特征图生成锚框大致如下图（摘自网络）：\n对于RPN层，下图是RPN网络的具体结构。可以看到RPN网络实际分为2条线，上面一条通过softmax分类anchors获得positive和negative分类，下面一条用于计算对于anchors的bounding box 回归偏移量，以获得精确的proposal。而最后的Proposal层则负责综合positive anchors和对应bounding box regression偏移量获取proposals，同时剔除太小和超出边界的proposals。其实整个网络到了Proposal Layer这里，就完成了相当于目标定位的功能。\nRPN大致流程：\n生成anchors -\u0026gt; softmax分类器提取positvie anchors -\u0026gt; bbox reg回归positive anchors -\u0026gt; Proposal Layer生成proposals\nFeature Pyramid Networks (FPN) 在2017年，T.-Y.Lin等人在Faster RCNN的基础上提出了FPN。之前的神经网络目标检测模型都是只提取了最顶层输出的特征进行检测，而卷积网络较深层的功能有利于类别识别，但它不利于目标的定位。FPN包含了一种具有横向连接的自顶向下的网络结构，可用于构建各种规模的高级语义。后续提出的各种网络中，基本上都包含了这种结构。\n在FPN自顶向下的结构中，网络层之间的详细操作如下：\nYou Only Look Once (YOLO) 2015年，R.Joseph提出了首个深度学习目标检测领域的单阶段模型YOLO。该模型将图片划分为$S\\times S$个网格，如果有目标对象的中心落在某个网格内，则该网格负责预测该对象。每个网格都会预测$B$个边界框以及这些框的置信度，这里置信度是指该框里是否有对象和做出这个判断的把握：$Pr(Object)*IOU^{truth}_{pred}$。如果没有对象在框内，则$Pr(Object)$为0,否则使用预测框和真实框之间的$IOU$作为其置信度数值。\n作者将目标检测的流程统一为单个神经网络。该神经网络采用整个图像信息在预测目标的bounding boxes的同时识别目标的类别，实现端到端实时目标检测任务。每个bounding boxes预测五个变量：x、y、w、h、conf，其中前四个是坐标变量，其中x、y为框的中心，w、h则表示宽和长。这里注意中心坐标是相对网格大小的，而长宽是相对整张图像的，conf表示预测框与正类框的IOU。\n后面作者也吸取了其它网络模型的特点，对YOLO进行了改进推出了YOLO2和YOLO3。\nSingle Shot MultiBox Detector (SSD) 与YOLO一样，SSD是一个单阶段检测模型，在2015年由W.Liu等人提出。SSD主要特点是使用了多尺度检测结构。与FPN类似，SSD从不同的卷积层抽取特征图进行检测。由于不同层的感受野大小不一样，所以采取这种方式可以顾及到不同尺寸的目标。SSD还使用了卷积层替换全连接层进行最后的分类回归操作。\nRetinaNet 虽然单阶段模型的运行速度要快于二阶段模型，但精度普遍偏低。2017年，T.-Y.Lin等人提出了RetinaNet。作者认为造成单阶段模型精度低的原因是正负样本的极度不均衡，并为此设计出了focal loss损失函数来重新平衡正负样本，这也是RetinaNet的主要贡献点。\n最后推出的损失函数为：\n$$ loss = -\\alpha(1-p_t)^\\gamma * log(p_t) $$其中$\\alpha$和$\\gamma$都为超参数。作者在自己的模型上实验显示$\\alpha=0.25$，$\\gamma=2$时效果最好。该函数仅在计算分类损失时用到。\nCornerNet 最后这个方法虽然不在作者的路线图里，但我个人觉得无锚框这个思想还是挺有意思的，就加了进来。在单阶段的目标检测网络中，基本上都使用了Anchor-box的方法，但这种方法需要大量的anchor，这导致了正负样本不平衡，降低训练效率，同时anchor的设置引入了许多超参数（如：数量、大小、长宽比），增加了网络设计的难度。2018年，Hei Law发表了CornerNet，通过检测目标框的角点来获得目标的位置，这样就无需设置anchor这一步骤。大致结构如下：\n其中的特征提取网络主要为Hourglass。Hourglass结构是Newell等人在2016年提出的，主要通过重复自底向上和自顶向下同时联合中间结果，在人体姿态估计中可以很好地利用身体不同部位的空间关系。大致如下图：\ncorner pooling用于定位目标的顶点。为了更好地适应corner的检测，在目标检测的任务中，目标的角点往往在目标之外，所以角点的检测不能根据局部的特征，而是应该对该点所在行的所有特征与列的所有特征进行扫描。下图以左上角为例。\n这里注意对各行各列求池化是有方向的，如下图以黑色箭头的方向进行行或列的最大池化：\nDatasets Pascal VOC 在早期的计算机视觉（2005年到2012年）中，VOC是一个非常重要的开源数据集，其中用于目标检测任务最多的是VOC07和VOC12这两部分。VOC07大致有5k训练图片，包含有12k+标注目标；VOC12有11k的训练图片，包含27k+的标注目标，两者都是对日常生活中常见的20类物品进行标注。\nILSVRC 该数据集（2010年到2017年）包含有200个类别，总的图片数量要比VOC丰富得多，例如ILSVRC-14包含有517k图片数量、534k标注目标。\nMS-COCO 2015年起发展到现在的COCO数据集是当前目标检测领域最具挑战性的数据集，它的类别数没有ILSVRC的多，但标注目标远多于ILSVRC。在MS-COCO-17数据集上一共有164k图片，包含80个类别的897k标注目标。\nOpen Images 2018年开始的Open Images检测挑战赛公布的数据集比起COCO数据集的规模更大。对于目标检测任务，Open Images包含有1910k图片，600个种类的15440k标注目标。\nMetrics 准确率与召回率的计算：\n实际为正类 实际为负类 预测为正类 TP FP 预测为负类 FN TN 准确率（Precision）：预测为正类的样本中，真正类所占的比例。\n$$ P = \\frac{TP}{FP+TP} $$召回率（Recall）：在真正类样本中，被预测为正类所占的比例。\n$$ R = \\frac{TP}{FN+TP} $$在早期，通用的目标检测评估指标是单图虚检（FPPI)，即平均每张图片中能正确检索到的目标数目：\n$$ FPPI = \\frac{FP}{N} $$VOC2007引入了AP值。AP值是在不同召回率下准确率的平均值。这定义是针对于单类的，对于多类，通过mAP（各类AP的平均）来评估。\n在目标检测中，判断某个框是否正确可以通过计算该框与真实框的重合度（IOU），与设置的重合度阈值比较来得出。\n$$ IOU(A,B) = \\frac{A\\cap B}{A\\cup B} $$这样通过设置不同的IOU阈值可以改变评估标准的严厉程度。2014年后，MS-COCO数据集的评估标准将AP值定义为IOU阈值在0.5到0.95下的AP平均值。\n下图列举了各类检测算法的精度：\nChallenge Lightweight object detection: 目标检测算法的总体性能和人眼还是有一定的差距的，在速度上的差距更加明显。所以轻量化的模型设计是一个重要的研究方向；\nDetection meets AutoML: 现今的检测模型越来越复杂，同时也依赖于人们的经验。网络结构搜索可以很大程度上减少人们参与模型结构设计的程度，这方面目前已经有模型实现了，例如Efficientdet，在未来也会是一个发展方向；\nWeakly supervised detection: 从数据集的发展，我们可以看出检测模型对于数据量的要求是十分巨大的，而数据集的标注十分消耗人力和时间，而且不同人的标注会有一些偏差。所以怎样实现弱监督目标检测是一个重要问题；\nDetection in videos: 如何在视频中进行目标检测也是检测领域一个重要的问题。通常的办法是对视频的每一帧都进行检测，但这样做忽略了视频帧与帧之间的关联信息。挖掘视频中空间与时间上的关联对于视频上的目标检测是非常重要的\n参考 本文主要参考了以下文章，更详细的内容建议看原论文：Object Detection in 20 Years: A Survey\nRapid object detection using a boosted cascade of simple features\nHistograms of oriented gradients for human detection\nA discriminatively trained, multiscale, deformable part model\nRich feature hierarchies for accurate object detection and semantic segmentation\nSpatial Pyramid Pooling in Deep Convolutional Networks for Visual Recognition\nFast R-CNN\nFaster R-CNN: Towards Real-Time Object Detection with Region Proposal Networks\nFeature Pyramid Networks for Object Detection\nYou Only Look Once: Unified, Real-Time Object Detection\nSSD: Single Shot MultiBox Detector\nFocal Loss for Dense Object Detection\nCornerNet: Detecting Objects as Paired Keypoints\nCornerNet：目标检测算法新思路\nThe PASCAL Visual Object Classes (VOC) Challenge\nImageNet Large Scale Visual Recognition Challenge\nMicrosoft coco: Common objects in context\nThe Open Images Dataset V4: Unified image classification, object detection, and visual relationship detection at scale\n何恺明大神的「Focal Loss」，如何更好地理解？\nSSD目标检测\nYOLO v1深入理解\n一文读懂Faster RCNN\nSPP-Net\nDPM(Deformable Parts Model)\nHOG特征算法\nViola Jones Face Detector\n","permalink":"https://simplestory.github.io/posts/20201120_0595a0de31914bca4c526fb50d30b2dd/","summary":"\u003cblockquote\u003e\n\u003cp\u003e目标检测是计算机视觉领域里的一个重要分支，其本质内容是：What objects are where？\n它同时作为一个基础任务为领域内一些更复杂的问题（如实例分割、图片文本识别、目标跟踪等）提供支持。从应用的角度来看，目标检测可以划分为两大类：通用目标检测和检测应用。通用目标检测主要是探索在统一的算法框架下检测不同类型目标的方法，类似于人类的视觉感知；检测应用则侧重于实际场景，例如行人检测、人脸检测和文本检测等。\u003c/p\u003e\n\u003c/blockquote\u003e","title":"Object Detection"},{"content":" 通常的目标检测算法都是基于Anchor来做的，这样虽然会生成许多无用的锚框，同时带来耗时的NMS后处理操作，但算法的准确率较高，因为锚框基本上覆盖来所有目标区域。在检测领域还有一种做法的基于Anchor free的，即无锚框，我了解的范围内主要有Cornernet、Centernet(Keypoint Triplets for Object Detection)和Centernet(Objects as Points)。这种类型的算法无需生成锚框，也就不需要耗时的NMS后处理。这里主要记录一下Centernet(Objects as Points)的处理方法。\n模型结构 Centernet主要是借用关键点检测的操作来检测目标的中心位置，再扩展到目标检测领域上，即通过回归得到框的大小。该算法可以方便地扩展到其它领域，例如论文里提到的目标检测、3D框预测、姿态等。在backbone方面作者主要采用了hourglass、resnet和DLA模型，对于目标检测，则在backbone后面加上三个分支，主要是：Heat maps分支用于获取目标中心点、Center offset用于矫正目标中心点、Box size用于回归目标框大小。大致结构如下（其中的cls表示检测的类别数）：\n数据流 说完模型结构，接下来是对数据的处理。作者是通过热图来标注目标中心点的，而热图是由高斯核得到的。\n首先对图片$H\\times W\\times 3$经过下采样处理（下采样倍数$R$这里设置为$4$）得到$H/R\\times W/R\\times cls$的图像，其中$cls$为类别数。之后计算真实框在缩放后图像上的中心点$\\hat{p}(x,y)=\\lfloor\\frac{p}{R}\\rfloor$，通过高斯核$exp(-\\frac{(x-\\hat{p}_x)^2+(y-\\hat{p}_y)^2}{2\\sigma_p^2})$对中心点进行映射，最后得到热图$\\hat{Y}\\in [0,1]^{\\frac{W}{R}\\times\\frac{H}{R}\\times 3}$。\n生成的热图并非是中心点位置为1，其它位置为0。它是目标点为1，向周围扩散逐渐变小。高斯核映射中以$\\hat{p}$为中心画出对应的高斯函数，并截取以$\\hat{p}$为圆心，$radius$为半径的区域，同时高斯核中的$\\sigma_p$为目标尺寸自适应标准差，作者实现中将其赋值为$\\frac{2\\times radius}{6}$。这里截取区域半径$radius$以及高斯核参数$\\sigma_p$的设计均参考了另一篇论文Cornernet。在Cornernet中，高斯核的标准差差设计为偏差允许区域半径（即$radius$）的$\\frac{1}{3}$。考虑到在目标点（Cornernet的目标点为目标框的角点）附近的点生成的标记框与目标框会有较大的IoU，直接将该点赋值为0并不合适，所以通过设置IoU阈值（Centernet中设置为0.7）形成偏差允许区域，在该区域内按照高斯函数来逐渐递减目标点周围点的数值。由这些点生成的标记框与目标框有三种相交极限情况，分别计算这三种情况下的区域半径取值，最后取三种情况下的最小区域半径即为$radius$的值。\nradius的计算 标记框全外切目标框\n由上图可计算出IoU：\n$$ IoU = \\frac{w\\cdot h}{(w+2r)\\cdot(h+2r)} \\ge iou\\_overlap $$则该情况下的最大半径满足下式：\n$$ \\begin{aligned} \u0026 \\frac{w\\cdot h}{(w+2r)\\cdot(h+2r)} = k = iou\\_overlap \\\\ \\Rightarrow \u0026 \\ 4k\\cdot r^2 + 2k(w+h)\\cdot r + (k-1)wh = 0 \\end{aligned} $$这是一个一元二次方程，在保证有解的情况下，可由公式计算出$r$的正解。（$y = \\frac{-b\\pm\\sqrt{b^2-4ac}}{2a}$，其中$b^2-4ac\\ge 0$）。\n标记框全内切目标框\n由上图可计算出IoU：\n$$ IoU = \\frac{(w-2r)\\cdot(h-2r)}{w\\cdot h} \\ge iou\\_overlap $$则该情况下的最大半径满足下式：\n$$ \\begin{aligned} \u0026 \\frac{(w-2r)\\cdot(h-2r)}{w\\cdot h} = k =iou\\_overlap \\\\ \\Rightarrow \u0026 \\ 4\\cdot r^2 - 2(w+h)\\cdot h + (1-k)wh = 0 \\end{aligned} $$同样可以求取$r$的正解\n标记框半外切半内切目标款\n可计算IoU如下：\n$$ IoU = \\frac{(w-r)\\cdot(h-r)}{2\\cdot h\\cdot w - (w-r)\\cdot(h-r)} \\ge iou\\_overlap $$则该情况下的最大半径满足下式：\n$$ \\begin{aligned} \u0026 \\frac{(w-r)\\cdot(h-r)}{2\\cdot h\\cdot w - (w-r)\\cdot(h-r)} = k = iou\\_overlap \\\\ \\Rightarrow \u0026 \\ (1+k)\\cdot r^2 - (w+h)(1+k)\\cdot r + (1-k)wh = 0 \\\\ \\Rightarrow \u0026 \\ r^2 -(w+h)\\cdot r + \\frac{1-k}{1+k}wh = 0 \\end{aligned} $$同理可以求出$r$的正解\n综合以上三种情况，取三者中的最小值作为$radius$的取值。\n损失函数 该模型具有良好的可扩展性，通过添加不同的分支可以应用于不同的领域，其中有两个分支是固定的（Heat maps和Center offset），对应的损失函数分别为中心点损失和中心点偏差损失。\n中心点损失 作者这里也是借鉴了Cornernet的损失函数来设计的，形式如下：\n$$ L_k = -\\frac{1}{N} \\begin{cases} (1-\\hat{Y}_{xyc})^\\alpha log(\\hat{Y}_{xyc}) \\ \u0026 \\text{if } Y_{xyc}=1 \\\\ (1-Y_{xyc})^\\beta(\\hat{Y}_{xyc})^\\alpha log(1-\\hat{Y}_{xyc}) \\ \u0026 \\text{otherwise} \\end{cases} $$其中$N$表示图片中目标中心点的个数，$\\alpha$和$\\beta$为超参数，作者选用了$\\alpha=2$和$\\beta=4$。$Y_{xyc}$表示真实标注热图上对应位置的取值，而$\\hat{Y}_{xyc}$表示模型预测的热图上对应位置的取值。\n从整体上看，这是一个改进后的$focal \\ loss$函数。当$Y_{xyc}=1$时，可以通过$(1-\\hat{Y}_{xyc})^\\alpha$来改变对应的权重，若模型预测值已经接近$1$，表明该样本对于模型来说属于容易样本，则对应的损失权重会降低，反之属于困难样本，权重上升。\n当$Y_{xyc}\\neq 1$时，这里可以分为两种情况，一种是预测点离目标点较近即$1-Y_{xyc}$较小，此时模型预测值应该为0，若模型预测值接近1，对应样本损失的权重上升，但该点离目标点较近，模型在该点取得的预测值较大是可以接受的，所以用$(1-Y_{xyc})^\\beta$来下降权重进行平衡；另一种是预测点离目标点较远，即$1-Y_{xyc}$较大，在$(1-Y_{xyc})^\\beta$的作用下，对应样本损失的权重会增大，但该点离目标点较远，模型在该点取得的预测值较小是可以接受的，所以用$\\hat{Y}_{xyc}^\\alpha$来降低权重进行平衡。\n中心点偏差损失 由于在对真实标注进行下采样时对中心点坐标做了取舍，引入了偏差，所以使用L1损失来引导模型弥补这段差值：\n$$ \\begin{aligned} L_{off} \u0026= \\frac{1}{N}\\sum_p\\lvert \\hat{O}_{\\hat{p}} - (\\frac{p}{R}-\\hat{p})\\rvert \\\\ \u0026= \\frac{1}{N}\\sum_p\\lvert \\hat{O}_{\\hat{p}} - (\\frac{p}{R}-\\lfloor \\frac{p}{R}\\rfloor)\\rvert) \\end{aligned} $$其中$N$表示图片中目标中心点的个数，$\\hat{O}\\in \\mathcal{R}^{\\frac{W}{R}\\times\\frac{H}{R}\\times 2}$表示模型预测的中心点偏差。注意该损失函数仅在对应的目标点上起作用，其余位置均忽略。\n其它损失 模型可以进行扩展，对应的损失函数也不太一样，这里只说明一下目标检测下的损失函数。\n令$(x_1^{(k)}, y_1^{(k)}, x_2^{(k)}, y_2^{(k)})$表示类别$c_k$中目标$k$的目标框坐标，其中心点坐标为$p_k=(\\frac{x_1^{(k)}+x_2^{(k)}}{2}, \\frac{y_1^{(k)}+y_2^{(k)}}{2})$，对于目标检测，模型主要回归每个目标框的尺寸$s_k=(x_2^{(k)}-x_1^{(k)}, y_2^{(k)}-y_1^{(k)})$。为了减轻计算负担，作者对每个类别对象使用了统一的目标框尺寸，即每个类别下的所有目标只有一个尺寸大小。使用L1损失如下：\n$$ L_{size} = \\frac{1}{N}\\sum_{k=1}^N\\lvert \\hat{S}_{p_k} - s_k\\rvert $$其中$N$表示图片中目标中心点的个数，$\\hat{S}\\in \\mathcal{R}^{\\frac{W}{R}\\times \\frac{H}{R}\\times 2}$表示模型预测的目标框尺寸。\n作者并没有对尺寸进行归一化，而是通过添加系数来缩放损失大小：\n$$ L_{det} = L_k + \\lambda_{size}L_{size} + \\lambda_{off}L_{off} $$上式是针对于目标检测的，作者选取的两个超参数值为$\\lambda_{size}=0.1$和$\\lambda_{off}=1$。\n推断阶段 在模型的热图输出上，对每个类别都提取出$topK$个高峰。提取的点是在该点在周围8个点内为最大，这步操作可通过一个$3\\times 3$的最大池化来完成。这里其实相当于进行NMS。之后通过获取的峰值点、中心点偏差预测值以及预测尺寸即可得到相应的预测框：\n$$ \\begin{aligned} \u0026(\\hat{x}_i+\\delta\\hat{x}_i-\\hat{w}_i/2,\\ \\hat{y}_i+\\delta\\hat{y}_i-\\hat{h}_i/2\\\\ \u0026\\hat{x}_i+\\delta\\hat{x}_i+\\hat{w}_i/2,\\ \\hat{y}_i+\\delta\\hat{y}_i+\\hat{h}_i/2) \\end{aligned} $$参考 Objects as Points\nCornerNet: Detecting Objects as Paired Keypoints\n扔掉anchor！真正的CenterNet——Objects as Points论文解读\n","permalink":"https://simplestory.github.io/posts/20201025_93f382c06f3476ad1f59351e0d99f842/","summary":"\u003cblockquote\u003e\n\u003cp\u003e通常的目标检测算法都是基于Anchor来做的，这样虽然会生成许多无用的锚框，同时带来耗时的NMS后处理操作，但算法的准确率较高，因为锚框基本上覆盖来所有目标区域。在检测领域还有一种做法的基于Anchor free的，即无锚框，我了解的范围内主要有Cornernet、Centernet(Keypoint Triplets for Object Detection)和Centernet(Objects as Points)。这种类型的算法无需生成锚框，也就不需要耗时的NMS后处理。这里主要记录一下Centernet(Objects as Points)的处理方法。\u003c/p\u003e\n\u003c/blockquote\u003e","title":"Anchor free--Centernet"},{"content":" 学习了那么多的深度学习算法，损失函数总是绕不过去的点，但好在一般情况下使用的损失函数都比较固定，偶尔会有一些随缘性的改变。这里主要列举了一些常用的损失函数。\nL1-loss L1损失函数，又称平均绝对误差(Mean Absolute Error, MAE)。数学表达式如下：\n$$ MAE = \\frac{1}{n}\\sum^n_{i=1}\\vert f(x_i)-y_i\\vert $$其中$n$表示训练批次样本数量，$f(x_i)$表示模型在样本$x_i$处的预测值，$y_i$为样本$x_i$对应的真实值。\nL2-loss L2损失函数，又称均方误差(Mean Square Error, MSE)。数学表达式如下：\n$$ MSE = \\frac{1}{n}\\sum^n_{i=1}(f(x_i)-y_i)^2 $$其中$n$表示训练批次样本数量，$f(x_i)$表示模型在样本$x_i$处的预测值，$y_i$为样本$x_i$对应的真实值。\nSmooth L1 loss L1损失函数对数据离群点并不敏感，而L2损失则过于敏感。为了让训练变得平稳，才提出了该损失函数。具体如下：\n$$ loss(x_i) = \\begin{cases} 0.5 * (f(x_i)-y_i)^2, \\ \u0026\\vert f(x_i)-y_i\\vert \u003c1 \\\\ \\vert f(x_i)-y_i\\vert - 0.5, \\ \u0026\\text{otherwise} \\end{cases} $$这里只展示了对于样本$x_i$的损失计算，$f(x_i)$表示模型在样本$x_i$处的预测值，$y_i$为样本$x_i$对应的真实值。\nCross Entropy loss 交叉熵损失函数(CE)主要用于分类算法中。\n对于0-1分类，有\n$$ loss(x_i) = -y_i * log(p_i)-(1-y_i) * log(1-p_i) $$其中$p_i$表示模型将样本$x_i$预测为正类的概率，$y_i$为样本$x_i$对应的真实类别，正样本为1负样本为0。\n对于多分类，有\n$$ loss(x) = -\\sum^c_{j=1}y_i * log(p_i) \\\\ y_i = \\begin{cases} 1, \\ \u0026\\text{the label of x is i} \\\\ 0, \\ \u0026\\text{otherwise} \\end{cases} $$其中$p_i$为模型将样本预测为类别$i$的概率，一般模型会通过softmax后在计算交叉熵损失。\nFocal loss 这个损失函数是为了解决正负样本不均衡的情况，主要是针对单阶目标检测算法中锚框的正负样本比例失调的情况。\n令\n$$ p_t = \\begin{cases} p, \\ \u0026 y=1 \\\\ 1-p, \\ \u0026 y=0 \\end{cases} $$则可将交叉熵损失函数统一为：\n$$ loss = -log(p_t) $$为了调节正负样本的比例，一个自然的想法就是用一个系数进行加权，如下：\n$$ loss = -\\alpha_t * log(p_t) \\\\ \\alpha_t = \\begin{cases} \\alpha, \\ \u0026 y=1 \\\\ 1-\\alpha, \\ \u0026 y=0 \\end{cases} $$但这样处理并不能区分出简单样本和困难样本，正常情况下，简单样本的数量要比困难样本数量多得多。最后换用自适应系数后如下：\n$$ loss = -(1-p_t)^\\gamma * log(p_t) $$作者在实验中发现对自适应系数作调整，模型效果会有轻微提升，所以最后损失函数如下：\n$$ loss = -\\alpha(1-p_t)^\\gamma * log(p_t) $$其中$\\alpha$和$\\gamma$都为超参数。作者在自己的模型熵实验显示$\\alpha=0.25$，$\\gamma=2$时效果最好。该函数仅在计算分类损失时用到，具体可参考Retina模型的损失函数。\n参考 Focal Loss for Dense Object Detection\n何恺明大神的「Focal Loss」，如何更好地理解？\n【目标检测】RetinaNet: Focal Loss for Object Detection\n","permalink":"https://simplestory.github.io/posts/20201018_25c3043509dadf35f8ecc40adf5a369c/","summary":"\u003cblockquote\u003e\n\u003cp\u003e学习了那么多的深度学习算法，损失函数总是绕不过去的点，但好在一般情况下使用的损失函数都比较固定，偶尔会有一些随缘性的改变。这里主要列举了一些常用的损失函数。\u003c/p\u003e\n\u003c/blockquote\u003e","title":"一些常见的损失函数"},{"content":" 在2019年年末和2020年年初，谷歌大脑发表了两篇论文，分别针对图片分类和目标检测领域提出了一个快速准确的模型，并命名为EfficientNet和EfficientDet。作者还通过对模型参数进行调整得到了一系列高效的分类和检测模型。\nEfficientNet 作者通过前人的工作以及自己的实验总结出了两个观点：\n缩放模型的任一维度（深度、宽度、分辨率）都会提升模型的精度，但对于过大的模型缩放尺度，其精度收益会减少 在卷积网络的缩放中，平衡地缩放模型各个维度可以获得更好的精度和速度 compound scaling 通过上述的两个观点，作者不再使用传统的单一维度的模型缩放，而是对模型的各个维度（深度、宽度和分辨率）进行缩放。\n首先对于卷积模型，我们可以表示为：\n$$ \\mathcal{N} = \\underset{i=1...s}{\\bigodot}\\mathcal{F}_i^{L_i}(X_{\\langle H_i, W_i, C_i\\rangle}) $$其中，$\\mathcal{F}_i^{L_i}$表示在网络块$i$中，网络层$F_i$重复$L_i$次，而$\\langle H_i,W_i,C_i\\rangle$表示第$i$层的输入尺寸，$\\mathcal{N}$则为整个卷积网络模型。\n由上式可以得到抽象后的模型缩放优化问题：\n$$ \\begin{aligned} \\underset{d,w,r}{max} \u0026\\ Accuracy(\\mathcal{N}(d,w,r)) \\\\ s.t. \u0026\\ \\mathcal{N} = \\underset{i=1...s}{\\bigodot}\\hat{\\mathcal{F}}_i^{d\\cdot \\hat{L}_i}(X_{\\langle r\\cdot \\hat{H}_i, r\\cdot \\hat{W}_i, w\\cdot \\hat{C}_i\\rangle}) \\\\ \u0026\\ \\text{Memory}(\\mathcal{N}) \\le target\\_memory \\\\ \u0026\\ \\text{FLOPS}(\\mathcal{N}) \\le target\\_flops \\end{aligned} \\tag{1} $$其中，$L_i,H_i,W_i,C_i$分别表示模型缩放后的模型深度、输入长度、输入宽度和模型宽度。$w,d,r$则为对应于模型宽度、深度和分辨率的缩放系数。\n作者采用了缩放比率系数$\\phi$来统一模型的各个维度的缩放：\n$$ \\begin{aligned} \\text{depth:} \u0026 \\ d = \\alpha^\\phi \\\\ \\text{width:} \u0026 \\ w = \\beta^\\phi \\\\ \\text{resolution:} \u0026 \\ r = \\gamma^\\phi \\\\ s.t. \u0026 \\ \\alpha \\cdot \\beta^2 \\cdot \\gamma^2 \\approx 2 \\\\ \u0026 \\ \\alpha \\ge 1,\\beta \\ge 1,\\gamma \\ge 1 \\\\ \\end{aligned} \\tag{2} $$其中，$\\alpha,\\beta,\\gamma$为一个常数，可通过小型网格搜索确定。对于作者给出的约束条件$\\alpha\\cdot\\beta^2\\cdot\\gamma^2\\approx 2$，这里表示缩放后的模型大致会是原模型的浮点计算量的$2^\\phi$左右，因为模型深度的两倍将扩大模型计算量的两倍，而模型宽度或分辨率的两倍将扩大计算量的四倍，即有模型计算量的扩大基数为$\\alpha\\cdot\\beta^2\\cdot\\gamma^2$。\nArchitecture 为了获得更好的效果，模型baseline也是十分重要的组成部分，作者采用了网络搜索的形式在限定的搜索空间中搜索到了EfficientNet-B0网络。搜索以$ACC(m)\\times[FLOPS(m)/T]^w$为目标，其中$ACC(m)$表示模型的精度，$FLOPS(m)$表示模型的计算量，$T$为模型计算量的目标且$w=-0.07$为超参数来控制精度和计算量的平衡，这里采用计算量而不是模型的计算延迟作为评价目标是因为作者为了将模型与设备解藕，不同的设备进行运算其模型计算延迟会是不同的，而模型的计算量确实相同的。最后模型baseline如下：\n由图中可以看出其主要组成部分为MBConv和SE block。MBConv即为Mobilenetv2里面使用的inverted bottleneck，而SE block类似于注意力机制，一个轻便的模块，是对模型的一种优化。\n从得到的baseline开始进行下面两步的缩放来得到最后的一系列分类模型：\n首先固定$\\phi=1$，假设有更多的可用资源，基于公式 1 和 2 对$\\alpha,\\beta,\\gamma$进行小型的网格搜索。最后得到在约束条件$\\alpha\\cdot\\beta^2\\cdot\\gamma^2\\approx 2$下的最佳值$\\alpha=1.2,\\beta=1.1,\\gamma=1.15$作为EfficientNet-B0模型的参数。 固定$\\alpha,\\beta,\\gamma$，利用公式 3 使用不同的$\\phi$进行缩放得到B1到B7模型。 至此，EfficientNet-B0到B7共八个模型已经建模完成。\nEfficientDet 为了获得精确快速的目标检测模型，作者基于单阶段检测算法提出了EfficientDet模型，主要解决了以下两个问题：\n如何进行高效的多尺度特征融合，作者给出的答案是BiFPN 如何有效的进行模型缩放，作者这里应用了和EfficientNet一样的组合缩放方法 BiFPN 多尺度特征融合的目的是汇总来自不同分辨率的特征是模型的泛化性能更好。FPN是一种有效的多尺度特征处理手段，它通过添加一条自顶向下的路线来获取其它分支的信息。但由于只有一条同路，其表达能力收到了限制，所以后面有提出了PANet，它在FPN的基础上添加了一条自低向上，再往后，研究人员通过网络搜索搜索出了NAS-FPN，它的表达能力要高于PANet，但其计算消耗巨大，并不适合于实际场景。最后，作者提出了BiFPN，具体结构如下：\nBiFPN是在PANet的基础上改进的。首先，作者移除了PANet中只有单一输入的节点，这是为了简化模型，因为作者只有单一输入的节点并不需要进行特征融合，这在一个目的在于进行特征融合的结构上的贡献会比较小，将它去掉不会有太大的影响。其次，在结构的输入和输出节点之间额外添加了一条连接，这是为了更多的融合特征而无需添加太多的运算量。最后，与PANet只有一个自顶向下和自底向上的路径不同，作者重复使用了BiFPN，以允许模型提取出的高级特征进行融合。\n在进行特征融合时，一般情况下是统一各个特征图的大小然后再进行相加，这意味这不同分辨率的特征图在融合时有着一样的权重，没有差别。然而作者观察到不同的分辨率对模型最后的输出贡献是不一样的，所以以同等重要性来对待不同分辨率的特征输入是不合适的。基于这种想法，作者考虑了三种加权的方法：\nUnbounded fusion: $O = \\sum_i w_i\\cdot I_i$，其中$w_i$是一个网络可学习权重，它可以是一个标量（针对每个特征进行加权）、一个向量（针对每个通道进行加权）或是一个张量（针对每个像素进行加权）。使用标量进行加权可以通过最小的计算量获得可观的精度提升，但标量权重可能会很大，也可能会很小，这会导致模型训练的不稳定。为此作者采取了权重归一化来界定各个权重的范围。 Softmax-based fusion: $O = \\sum_i\\frac{e^{w_i}}{\\sum_je^{w_j}}\\cdot I_i$，这是采用Softmax方法来进行加权，这样权重的大小范围就固定在0到1之间，也能通过数值代表各自的重要性。但额外的Softmax计算耗时较长，为了减少耗时，作者提出了下面的加权方法。 Fast normalized fusion: $O = \\sum_i\\frac{w_i}{\\epsilon+\\sum_jw_j}\\cdot I_i$，其中$w_i \\ge 0$在前面经过激活函数$Relu$之后可以得到保证。通过这种方法可以避免进行耗时的Softmax操作。 最后，BiFPN的组成如上图所示，由双向的跨尺度连接和加权特征融合组成。作者为了加快速度，还将特征融合部分的卷积更换为可分离卷积，同时在BiFPN添加了批量归一化并在每一个卷积计算后都进行激活函数计算。\nNote： 这里要注意，BiFPN输入到输出的连接和输入到中间节点的连接所使用的输入节点是不一样的，只是值相同。具体可参考这篇文章。\n最后EfficientDet的大致结构如下：\ncompound scaling 组合缩放采用了与EfficientNet类似的方法，只是对于目标检测模型，缩放的维度会比较多，这里作者使用了基于启发式的缩放方法，与之前一样采用$\\phi$作为缩放尺度系数。\n对于Backbone，作者使用了EfficientNet-B0到B6模型作为backbone。\nBiFPN缩放。线性增加BiFPN的深度$D_{bifpn}$，而BiFPN的宽度$W_{bifpn}$则使用指数型的增加。之后作者通过在${1.2,1.25,1.3,1.35,1.4,1.45}$中进行网格搜索得到最佳的尺度缩放基数$1.35$，最后有下式进行缩放：\n$$ \\begin{aligned} W_{bifpn} \u0026 = 64 \\cdot 1.35^\\phi \\\\ D_{bifpn} \u0026 = 3 + \\phi \\end{aligned} $$对于预测头部部分，将其宽度固定与BiFPN的宽度一致，即$W_{pred}=W_{bifpn}$，但线性增加它的深度：\n$$ D_{box} = D_{class} = 3+\\lfloor \\phi/3\\rfloor $$输入图像分辨率，因为模型特征第3到7层被用于BiFPN，所以输入的维度必须为$2^7=128$的倍数。使用线性增加如下：\n$$ R_{input} = 512+\\phi\\cdot 128 $$通过以上一系列的操作，我们可以得到EfficientDet-D0到D7共八个目标检测模型，对于EfficientDet-D6和D7，它们有相同的参数，除了分辨率参数不同。具体如下：\n参考 本文主要参考了以下文章：\nEfficientNet: Rethinking Model Scaling for Convolutional Neural Networks\nEfficientDet: Scalable and Efficient Object Detection\n全网第一SoTA成绩却朴实无华的pytorch版efficientdet\n","permalink":"https://simplestory.github.io/posts/20201003_fc3691951ecd38cc130a442bcc0566ed/","summary":"\u003cblockquote\u003e\n\u003cp\u003e在2019年年末和2020年年初，谷歌大脑发表了两篇论文，分别针对图片分类和目标检测领域提出了一个快速准确的模型，并命名为EfficientNet和EfficientDet。作者还通过对模型参数进行调整得到了一系列高效的分类和检测模型。\u003c/p\u003e\n\u003c/blockquote\u003e","title":"Efficient模型家族"},{"content":" IoU是目标检测里的一个重要指标，它是通过计算预测框与真实框的交集和并集的比值来衡量预测框的优劣。但通常的预测框调整函数一般采用的是L2范数，即以MSE的方式来计算损失，而目前来看，一些研究表明这并非最优的优化函数，所以就有了IOU损失函数。\nIoU的计算：\n$$ IoU = \\frac{\\vert B \\cap B^{gt}\\vert}{\\vert B \\cup B^{gt}\\vert} $$其中$B$表示的是预测框，$B^{gt}$表示的是真实框，整体上看$IoU$就是预测框与真实框的交集与预测框与真实框的并集的比值。它可以有效的反映出预测框的检测效果，同时它还具有尺度不变性，即对尺度不敏感。\nIoU Loss 从$IoU$的计算公式可以简单粗暴的得到一个损失函数：\n$$ \\mathcal{L}_{IoU} = 1-IoU = 1-\\frac{\\vert B \\cap B^{gt}\\vert}{\\vert B \\cup B^{gt}\\vert} $$这个损失函数有一个不可避免的缺陷：当两个框无交集时，损失恒为1，这样距离小的无交集和距离远的无交集的输出是一样的，失去了梯度的方向，无法优化。\nNote: $Iou \\ Loss$的实现形式不止上面提到的那种，例如还有$UnitBox$的交叉熵形式和$IoUNet$的$Smooth-L1$的形式，但都有上述问题。\nGIoU Loss(generalized IoU loss) 为了解决上述问题，$GIoU$出现了。它在$IoU \\ loss$的基础上添加了一个惩罚项。当预测框与真实框的距离越大时，惩罚项的值越大。具体公式如下：\n$$ \\mathcal{L}_{GIoU} = 1-IoU+\\frac{\\vert C-B\\cup B^{gt}\\vert}{\\vert C\\vert} $$其中$C$是包含预测框和真实框的最小包围框。在训练过程中，$GIoU \\ loss$倾向于先增大预测框使其与真实框有重合，然后在公式中的$IoU$项的引导下来最大化重叠区域。但当真实框完全包含预测框时，上述公式中的惩罚项则为0，即$GIoU loss$降级为$IoU \\ loss$，同时由于该损失过分依赖于$IoU$项，需要跟多的迭代次数来收敛，特别时水平和垂直方向。\nDIoU Loss(Distance-IoU loss) 与$GIoU$相同的是，$DIoU$也是在$IoU \\ loss$的基础上添加一个惩罚项，但$DIou$考虑了预测框与真实框的距离、重叠率以及尺度，具体如下：\n$$ \\begin{aligned} \\mathcal{L}_{DIoU} \u0026 = 1-IoU+\\mathcal{R}(\\mathcal{B}, \\mathcal{B}^{gt}) \\\\ \\mathcal{R}_{DIoU} \u0026 = \\frac{\\rho^2(b, b^{gt})}{c^2} \\end{aligned} $$其中，$b$和$b^{gt}$分别表示预测框和真实框的中心点，$\\rho$代表的是计算两点的距离公式，在这里为两个中心点的欧式距离，$c$代表的是包含预测款和真实框的最小框的对角线距离，详细如下图所示。\n该损失函数直接最小化两个框的距离，所以收敛速度要快于$GIoU$。$DIoU$可以应用于$NMS$，使得到的框更加合理。\nCIoU(Complete IoU Loss) 在$DIoU$的基础上考虑框的长宽比的话即可得到$CIoU$，具体如下：\n$$ \\begin{aligned} \\mathcal{L}_{CIoU} \u0026 = 1-IoU+\\mathcal{R}(\\mathcal{B}, \\mathcal{B}^{gt}) \\\\ \\mathcal{R}_{CIoU} \u0026 = \\frac{\\rho^2(b, b^{gt})}{c^2}+\\alpha \\mathcal{v} \\\\ \\mathcal{v} \u0026 = \\frac{4}{\\pi^2}\\left(arctan\\frac{\\mathcal{w}^{gt}}{\\mathcal{h}^{gt}}-arctan\\frac{\\mathcal{w}}{\\mathcal{h}}\\right)^2 \\\\ \\alpha \u0026 = \\frac{\\mathcal{v}}{(1-IoU)+\\mathcal{v}} \\end{aligned} $$Note:\n$CIoU$的梯度中包含有$\\mathcal{v}$的梯度，其梯度大致如下：\n$$ \\begin{aligned} \\frac{\\partial\\mathcal{v}}{\\partial\\mathcal{w}} \u0026 = \\frac{8}{\\pi^2}\\left(arctan\\frac{\\mathcal{w}^{gt}}{\\mathcal{h}^{gt}}-arctan\\frac{\\mathcal{w}}{\\mathcal{h}}\\right)\\times\\frac{\\mathcal{h}}{\\mathcal{w}^2+\\mathcal{h}^2} \\\\ \\frac{\\partial\\mathcal{v}}{\\partial\\mathcal{h}} \u0026 = -\\frac{8}{\\pi^2}\\left(arctan\\frac{\\mathcal{w}^{gt}}{\\mathcal{h}^{gt}}-arctan\\frac{\\mathcal{w}}{\\mathcal{h}}\\right)\\times\\frac{w}{\\mathcal{w}^2+\\mathcal{h}^2} \\end{aligned} $$在长宽比在$[0,1]$的情况下，$\\mathcal{w}^2+\\mathcal{h}^2$的值通常会很小，$\\frac{1}{\\mathcal{w}^2+\\mathcal{h}^2}$的值会比较大可能会导致梯度爆炸，所以实现中一般将其替换为常数1。\n参考 本文参考以下内容：\nDistance-IoU Loss: Faster and Better Learning for Bounding Box Regression\nIoU在目标检测中的正确打开方式\nIoU损失函数那些事\n","permalink":"https://simplestory.github.io/posts/20200926_ffb198e96e96a613e471e2f2eb1ae86c/","summary":"\u003cblockquote\u003e\n\u003cp\u003eIoU是目标检测里的一个重要指标，它是通过计算预测框与真实框的交集和并集的比值来衡量预测框的优劣。但通常的预测框调整函数一般采用的是L2范数，即以MSE的方式来计算损失，而目前来看，一些研究表明这并非最优的优化函数，所以就有了IOU损失函数。\u003c/p\u003e\n\u003c/blockquote\u003e","title":"几种IOU损失"},{"content":" 不知不觉又积累了好多论文，真是让人憔悴呢。回归正题，神经网络在很多领域已经有了应用，在图像识别领域甚至已经超越人类的能力，而模型准确率的提高带来的是更高的算力要求。在实际工程应用中往往提供不了很高的计算能力，有些甚至在嵌入式平台上运行算法，这意味着那些高大上的算法根本是不可能部署上去的。谷歌继mobilenetv1后提出了其改进版本mobilenetv2，该模型更适合于轻量级部署，在保持运行速度的同时也能拥有能看的准确率。\n深度可分离卷积 首先先来看一下已经mobilenetv1中使用了的深度可分离卷积。大致逻辑就是将一个普通的卷积计算分为两步来完成：depthwise卷积和pointwise卷积，在达到相同效果的同时降低了计算量。\n假设标准卷积的输入是$h_i\\times w_i\\times d_i$维度的$L_i$，核参数为$K\\in R^{k\\times k\\times d_i\\times d_j}$，最后输出为$h_i\\times w_i\\times d_j$维度的$L_j$。所以标准卷积的计算量为$h_i\\times w_i\\times k\\times k\\times d_i\\times d_j$。\n采用深度可分离卷积后，输入为$h_i\\times w_i\\times d_i$维度的$L_i$，depthwise卷积核参数为$K_1\\in R^{k\\times k\\times d_i\\times d_i}$，pointwise卷积核参数为$K_2\\in R^{1\\times 1\\times d_i\\times d_j}$，最后输出也为$h_i\\times w_i\\times d_j$维度的$L_j$。所以计算量为$k\\times k\\times h_i\\times w_i\\times d_i + 1\\times 1\\times h_i\\times w_i\\times d_i\\times d_j$，即$h_i\\times w_i\\times d_i\\times (k^2 + d_j)$。可以看出与标准卷积的计算量相差$\\frac{k^2d_j}{k^2+d_j}$的倍数，所以深度可分离卷积真的是一种高效的卷积设计。\n在论文中，mobilenetv2对可分离卷积全程使用$3\\times 3$核参数。\nLinear Bottlenecks 姑且叫它线性瓶颈层吧（不知道怎么翻译好。。。）这部分主要是说模型的特征保留能力。一直以来，神经网络中的“mainfold of interest”（应该是有效特征之类的）被认为可以嵌入到低维子空间中，所以就有了通过降低卷积层的维度来降低子空间的维度从而获得更多的有效特征。但是传统的神经网络在实际上在每个坐标转换上都具有非线性（However, this intuition breaks down when we recall that deep convolutional neural networks actually have non-linear per coordinate transformations）。\n以ReLU激活函数为例，将其应用于1维空间中的一条直线，在n维空间中会产生射线，实际上一般会产生具有n个接头的分段线性曲线。由ReLU激活函数的曲线也可以看出，深度网络仅在输出域的非零部分具有线性分类器功能。作者在最后给出了模型主要特征位于高维空间的低维子空间中的两点要求：\n对于ReLU输出结果为非0的情况，ReLU则只是一个简单的线性变换； ReLU在input mainfold处于输入空间的低维子空间的前提下，可以保留input mainfold的信息 在假设满足第二点的情况下，我们可以通过在卷积块里插入线性瓶颈层来捕捉有效特征。作者也通过实验表明了加入线性瓶颈层能有效阻止非线性变换破坏过多的特征信息。\n线性瓶颈层里有一个重要参数扩展比率$expansion \\ ratio$，即输入bottlenck的大小与内部大小之间的比率。\n这部分我看得有一些迷茫，还是得多看几遍。\nInverted residuals 受到bottleneck的启发，bottleneck实际上包含了所有必要的信息，而扩展层仅充当张量的非线性转换的实现细节，所以作者直接在瓶颈之间使用快捷连接的方式。具体如下图所示。\nbottleneck大致的结构如下图所示：\n模型结构 如图\n首先是一个包含有32个卷积核的标准卷积层，之后是19个残差bottleneck模块（但论文表格上却只有17个，官方复现中也是按表格来的）。激活函数使用的是ReLU6，卷积核参数为$3\\times 3$。除了第一个bottleneck外，作者在整个网络中都使用了恒定的扩展速率（实验得出5到10之间模型鲁棒性会比较好）\n模型实现中还有一些提升内存效率的操作，具体可以看论文原文。\n最后，针对目标检测领域，作者也修改了SSD网络，提出了SSDLite的轻量级实现。在mobilenetv2上，SSDLite的第一层连接到了mobilenetv2的第15层的扩展（输出跨度维16），其余的SSDLite层连接在最后一层的顶部（输出跨度维32）。\n参考 本文主要参考自以下论文\nMobileNetV2: Inverted Residuals and Linear Bottlenecks\n","permalink":"https://simplestory.github.io/posts/20200426_02a6a4457d33491491495f7fe2b383d1/","summary":"\u003cblockquote\u003e\n\u003cp\u003e不知不觉又积累了好多论文，真是让人憔悴呢。回归正题，神经网络在很多领域已经有了应用，在图像识别领域甚至已经超越人类的能力，而模型准确率的提高带来的是更高的算力要求。在实际工程应用中往往提供不了很高的计算能力，有些甚至在嵌入式平台上运行算法，这意味着那些高大上的算法根本是不可能部署上去的。谷歌继mobilenetv1后提出了其改进版本mobilenetv2，该模型更适合于轻量级部署，在保持运行速度的同时也能拥有能看的准确率。\u003c/p\u003e\n\u003c/blockquote\u003e","title":"MobileNetV2"},{"content":" 当前大部分的目标检测/分割模型是基于Fast/Faster RCNN和FPN，作者主要提供了一个可比的框架用于实例分割，即Mask R-CNN。\nIntroduction mask rcnn是通过在faster rcnn基础上，加一个分支用于并行地预测分割模板。该分支其实是一个小型的FCN网络，作用于每个ROI区域。\n这里小提一下fast/faster rcnn。fast rcnn在rcnn基础上引入了ROIPool；faster rcnn在fast rcnn的基础上引入了RPN。 实例分割是在像素级别上实现划分对象轮廓。常用的策略有两种：\n分割候选，即分割先于识别，先划分出候选的分割区域，再对区域进行类别识别从而达到实例分割的目的。这种方法速度慢准确率低。 语义分割。这个与实例分割不太一样，语义分割只划分同一类别的对象，而实例分割对同一类别的对象也要划分。所以这种方法会从按像素分类的结果开始，尝试将相同类像素分割为不同实例，即在语义分割的基础上实现实例分割。 mask rcnn是基于实例优先的策略的，并且与分类和框回归同时进行。\n模型关键 mask rcnn采用与faster rcnn相同的二阶段结构。第一阶段与faster rcnn一致，为RPN结构；第二阶段为并行的三个分支：分类，框回归，分割模板。\n在训练中，模型对每个roi采用多任务损失函数：\n$$ L = L_{cls}+L_{box}+L_{mask} $$其中$L_{cls}$为类别损失，$L_{box}$为目标框回归损失，$L_{mask}$为模板损失。mask分支针对每个RoI区域都会得到一个$Km^2$维度的结果，$K$为类别数，$m$则是RoI的分辨率。作者在每个像素上都应用了sigmoid，并将mask损失函数定义为平均二分类交叉熵损失函数。$L_{mask}$只计算与当前RoI关联的真实例损失。\n与其它分支不同，mask分支保留了输入图像的空间信息，实现了像素级的对应，这要求roi特征能很好地对齐并保留先前的像素对应空间位置。此前faster rcnn有一步roipool的操作对roi特征图进行了量化。这些量化会引入roi区域与提取特征之间的不对齐性，这对分类结果可能没有影响，但对像素级的分割任务有着很大的负面影响。所以作者去除了roipool里的量化操作，提出了ROIAlign层。该网络层可以对齐输入图像与提取的特征。它避免了roipool中所有的量化操作，取而代之的是双线性差值，利用双线性差值来计算对应点的像素值，如下图所示：\nroialign的提出可以说是这篇论文的一大贡献，前期的roipool在实例上执行的核心操作是粗略的空间量化以进行特征提取，而roialign则忠实地保留了确切的空间位置，同时roialign对最大/平均池化不敏感。它极大程度了解决了使用大步长特征的检测和分割长期面临的问题\n主要结构 为了证明该模型的通用性，作者实例化了多个不同结构的mask rcnn。主要区别在于模型的backbone和检测头网络。\n**对于backbone。**作者主要评估了深度为50层和101层的resnet以及resnext网络。论文里作者采用了$network-depth-features$命名法来命名网络。\n这里举例说明一下$network-depth-features$命名法。在原先faster rcnn的实现中，其特征提取部分是在backbone的最后第四段进行的，称为C4，而这个backbone采用的是resnet-50，所以整个backbone表示为$ResNet-50-C4$ 作者还尝试了FPN结构来提取ROI特征。\n**对于模型检测头网络。**作者选用faster rcnn with resnet和faster rcnn with fpn的检测头，再加上一个只包含卷积层的mask分支。细节附上论文图例：\n实现细节 模型训练阶段。训练采用的超参数与fast/faster rcnn一致。模板损失$L_{mask}$只定义在正例的roi上，(正反roi比例为1：3)。rpn的anchors采用5个尺度和3个长宽比。为了后续实验的方便，作者对rpn进行了单独训练。\n模型推断阶段。经过nms后，模型的mask分支只应用于得分最高的100个检测框。mxm的浮点型模板输出要重建为roi尺寸大小并用0.5的阈值来进行二值化。由于mask分支并非应用于全部的检测框，所以mask rcnn在faster rcnn的基础上只增加了少量的开销。\n更多具体的细节还是要看回原论文以及相关源代码\n参考 本文主要参考自以下内容\nMask R-CNN\n","permalink":"https://simplestory.github.io/posts/20200210_ba14ebd4efe67efd08cdc6410e758633/","summary":"\u003cblockquote\u003e\n\u003cp\u003e当前大部分的目标检测/分割模型是基于Fast/Faster RCNN和FPN，作者主要提供了一个可比的框架用于实例分割，即Mask R-CNN。\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch2 id=\"introduction\"\u003eIntroduction\u003c/h2\u003e\n\u003cp\u003emask rcnn是通过在faster rcnn基础上，加一个分支用于并行地预测分割模板。该分支其实是一个小型的FCN网络，作用于每个ROI区域。\u003c/p\u003e","title":"Mask RCNN"},{"content":" 是的，它来了。\n简介 yolo3在$320\\times 320$的分辨率输入下，准确率维持在$28.2mAP$，运行速度大致为$22ms$。\n这个模型跟yolo2相比，并没有什么重大的改变，作者通过尝试许多技巧来提升模型性能，最后筛选出当前有用的一些技巧组合成yolo3。\n边界框与类别预测 在yolo2中，边界框是通过预测框相对于图片左上角的中心坐标和先验框的长宽来得到框的位置大小：\n$$ \\begin{aligned} b_x \u0026 = \\sigma(t_x)+c_x \\\\ b_y \u0026 = \\sigma(t_y)+c_y \\\\ b_w \u0026 = p_we^{t_w} \\\\ b_h \u0026 = p_he^{t_h} \\\\ \\end{aligned} $$这里也沿用了yolo2的边界框预测方法。yolo3采用逻辑斯蒂回归对每个先验框进行物体对象存在性的评分。当某个先验框覆盖一个真实标记对象的面积相比于其它框都要大的时候，则该框的分数置1；当某个框不是最佳边界框，但确实与某个真实标记对象的重叠面积大于阈值（作者论文里选的阈值为0.5），作者仍将其忽略。所以作者只为每一个真实标记对象指定一个先验边界框，对于那些没有被指定的先验框，不会造成坐标和分类损失，而只会有目标对象存在性损失。通过这样的方式可以去除不必要的框，减少计算量。\n关于类别预测，yolo3针对每个框都采用多标签分类来预测边界框可能包含的类。作者这里并没有采用softmax是因为作者发现这对模型的高性能并没有什么影响，而且独立的逻辑斯蒂分类加上二分类交叉熵损失函数这个模型可以方便地迁移到其它数据集上。\n模型改进 在模型训练上，作者采用了跨尺度组合特征来进行预测。yolo3使用3个不同的长宽比来预测框，具体操作类似于$FPN$特征金字塔。作者在模型的特征提取器后面添加了几个卷积层，用来达到一个3维张量（包含了边界框、目标存在概率、各类别概率）。然后提取两层前的特征图并上采样与最后一层的输出进行合并，这样可以获得前面层的有意义的语义信息和细粒度。以此类推再添加一个合并层，所以最终模型使用了3个不同尺度进行预测。在先验框的选择上，作者延续了yolo2的操作，使用$k-means$聚类算法来得到候选框的尺寸。这里与yolo2不同的是作者选择了9个尺寸，然后将这些尺寸在3个不同的长宽比之间平均分配。附上一张结构图：\n特征提取器 作者在yolo3里使用了一个全新的特征提取器。新的卷积堆中使用了连续的$3\\times 3$和$1\\times 1$卷积核，同时添加上了残差模块。整个特征提取器共有53层，所以它被称为$Darknet-53$。结构如下图：\n模型训练与不足 yolo3的训练仍采用没有经过难分样本挖掘的数据集进行训练，同时使用多尺度训练、大量的数据增强方法、批归一化等。模型的mAP值在COCO数据集上的新评估方法上与SSD模型旗鼓相当，但SSD的运行速度大约是yolo3的3倍。而换到旧的评估方法，模型的mAP值与RetinaNet相当并甩了SSD几条街。\nyolo3还是有一些不足的。在前面版本中，yolo在小目标检测方面一直表现不优，经过多尺度训练后，模型对小目标检测有了很大的提升，但在中、大目标识别上的表现却不如从前。\n参考 本文主要参考自以下内容：\nYOLOv3: An Incremental Improvement\nYOLOv3 深入理解\nyolo系列之yolo v3【深度解析】\n","permalink":"https://simplestory.github.io/posts/20200115_1789a504337212c61f0b46c458695b7c/","summary":"\u003cblockquote\u003e\n\u003cp\u003e是的，它来了。\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch2 id=\"简介\"\u003e简介\u003c/h2\u003e\n\u003cp\u003eyolo3在$320\\times 320$的分辨率输入下，准确率维持在$28.2mAP$，运行速度大致为$22ms$。\u003c/p\u003e\n\u003cp\u003e这个模型跟yolo2相比，并没有什么重大的改变，作者通过尝试许多技巧来提升模型性能，最后筛选出当前有用的一些技巧组合成yolo3。\u003c/p\u003e","title":"YOLO_v3"},{"content":" yolov1算法发布后，引起了大众的强烈反应，有许多研究员将该算法与Fast Rcnn做了比较，yolov1在目标定位上出现了较多的偏差，同时有着较低的召回率。于是作者在几乎同年的时间段里就推出了yolov2以及用于超多类别检测的yolo9000，这算法的迭代速度令人一脸茫然（是真滴NB）。\n作者主要围绕着在保持模型精度的前提下提高模型的召回率和降低目标定位偏差这几点来提升算法模型，同时提出了共同训练的方法来实现超多类别检测算法yolo9000。\nBetter 作者采用了许多技巧来简化模型的同时提升准确率。 首先是批归一化（Batch Normalization），它可以显著提高模型的收敛性，同时无需其它形式的正则化，像yolov1里面的Dropout层就可以去掉了。作者在所有卷积层后面都添加了BN层，模型$mAP$值提升了至少$2\\%$。 yolov1在训练分类网络是使用的输入图像分辨率是$224\\times 224$，在实际检测中用到的图像分辨率为$448\\times 448$，而yolov2在微调训练分类网络是直接使用了$448\\times 448$的大小。这波操作给了模型适应分辨率变化的时间。提高分辨率后，模型$mAP$值提升了大约$4\\%$。\n众所周知，yolov1并没有使用anchor boxes那一套，而是通过全连接层直接预测出了边界框，但作者发现预测框的偏置量比预测框的坐标要来的简单，并且可以简化网络。所以在yolov2，移除了全连接层并使用anchor boxes来预测边界框。其次，消除了一个池化层，让网络的卷积层输出具有更高的分辨率，同时使用$416\\times 416$的图像输入而非$448\\times 448$。这里采用$416\\times 416$是因为作者想让最后的特征图是一个奇数（下采样32倍，最后输出的特征图大小为$13\\times 13$），这样在特征图上就有一个中心点。对于一个物体，特别是大物体，一般都会出现在图像的中心，所以在那里有一个中心点来预测边界框的效果会比周围四个点来预测好一点。当采用anchor boxes机制后，模型的$mAP$值并没有明显的提升，甚至有些许下降，但召回率却有了一定量的提升，总之还是有提升空间的。\n在引入anchor boxes机制后，作者面临了两个问题：\nboxes的尺寸。以往的boxes尺寸是手动选择的，之后网络再进行优化。选择一个好的尺寸对模型检测效果的提升是十分巨大的。 模型的不稳定性。大部分的不稳定性来自于预测边界框的坐标$(x,y)$。 对于boxes尺寸的选择问题，作者使用了k-means聚类算法来选取先验框的尺寸（讲道理，这波操作骚到不行）。引入anchor boxes的目标是让检测框获得更高的IOU值，而通常的K均值聚类算法使用的距离公式为欧拉公式，这并不符合要求，所以作者更改了距离公式，使用IOU来衡量距离：\n$$ d(box, centroid) = 1-IOU(box, centroid) $$由于卷积神经网络具有平移不变性，且anchor boxes的位置被每个栅格固定，因此只需通过k-means计算出先验框的长宽即可。通过实验，作者最后选择的k值为5。\n针对于模型的稳定性问题，作者沿用yolov1的做法，即预测边界框中心点相对于对应栅格左上角位置的偏移量，同时使用sigmoid函数将边界框中心约束在当前栅格中：\n$$ \\begin{aligned} b_x \u0026 = \\sigma(t_x)+c_x \\\\ b_y \u0026 = \\sigma(t_y)+c_y \\\\ b_w \u0026 = p_we^{t_w} \\\\ b_h \u0026 = p_he^{t_h} \\\\ Pr(object) * IOU(b, object) \u0026 = \\sigma(t_o) \\end{aligned} $$附上论文解说图：\n其中，$p_h$和$p_w$表示经由k均值聚类产生的边界框的尺寸\n所以，作者使用尺寸聚类以及直接预测边界框中心位置来生成边界框。\n为了让模型能够较好地处理大小目标的检测问题，作者引入了细粒度特征（Fine-Grained Features）。模型不再像yolov1那样使用仅最后一特征层$13\\times 13$来检测目标，而是加入了前面的特征层（$26\\times 26$）的信息，由此引出了passthrough层。作者在后期代码中借鉴了ResNet，不直接对高分辨率的图像处理，而是先经过$1\\times 1\\times 64$卷积处理。实现passthrough层的逻辑大致是选取底层的特征图$26\\times 26\\times 512$，利用$1\\times 1\\times 64$进行降维为$26\\times 26\\times 64$，再拆分为4份堆叠通道数：$13\\times 13\\times 256$，与最后一层$13\\times 13\\times 1024$结合有：$13\\times 13\\times (1024+256) = 13\\times 13\\times 1280$。这就是passthrough的大致处理过程。特征信息经过passthrough层后，再经$3\\times 3$和$1\\times 1$处理，输出为$13\\times 13\\times 125$。\n为了让模型更具鲁棒性，作者还采用了多尺度训练。每训练一定量的批次，就在尺寸范围了随机选取一个尺寸作为训练图像的输入尺寸（论文中选取10批次做一次变换）。因为模型为32倍的下采样，所以输入的图像尺寸应该为32的倍数，例如$\\{320, 352, ..., 608\\}$。\nFaster 通常的目标检测模型使用的特征提取网络是基于VGG-16的，这是一个十分强大但又有点庞大的网络。在先前的yolov1中，作者使用的特征提取网络是基于Googlenet架构的，运行速度要块于VGG-16，但它的精度是要低于VGG-16的。为了尽可能地同步提高模型运行效率和性能，作者重新设计了yolov2。\n于是乎Darknet-19诞生了。该模型与VGG-16相似，主要使用了$3\\times 3$卷积核并在池化操作后将通道数加倍。根据NIN（Network in Network）的结论，作者使用了全局平均池化（global average pooling），同时穿插使用$1\\times 1$卷积核来压缩特征通道数。BN层自然是必不可少的，BN层不仅能加速模型收敛，还能正则化模型。最终，Darknet-19有19个卷积层、5个最大池化层，具体结构见下图：\n特征提取网络搭建好后就是对它进行训练了，作者通过初始学习率设定为$0.1$的SGD梯度下降法在标准的ImageNet 1000类分类数据集进行了$160$次的迭代训练。对于学习率的更新问题，作者采用的是多项式衰减策略来更新学习率：\n$$ lr_{cur} = (lr_{begin}-lr_{end})\\cdot (1-\\frac{gstep}{dstep})^{power} + lr_{end} $$其中，$lr_{begin}$为起始学习率，$lr_{end}$为最终学习率，$dstep$为衰减步数（decay steps），$gstep$为全局步数（global steps），并且有下式成立：\n$$ gstep = \\min(gstep, dstep) $$具体的参数设计请看作者原文。\n在经过分辨率为$224\\times 224$的初始训练后，再将输入图像分辨率提高到$448\\times 448$对模型进行微调以适应大分辨率的图像。\nDarknet-15训练结束后，作者将其最后一层卷积层去除并添加了3层带$3\\times 3\\times 1024$的卷积层，并在每一层后面加一个$1\\times 1$的卷积层，这一层的通道数取决于检测的类别数。对于前面提到的passthrough层，作者将它用在了最后的$3\\times 3\\times 512$层和倒数第二个卷积层中。\n到这里YoLov2的部分就结束了，后面是YoLo9000的主场。\nStronger 为了能检测尽可能多的目标，作者提出了分类数据集与检测数据集联合训练的方法。在模型训练阶段，作者混合了来自两个数据集的图片，当遇到用于分类的图片时则只用网络结构中的分类部分来后向传播损失，当遇到用于检测的图像则基于整个损失函数进行后向传播。对于数据集的混合，面临的一个问题就是如何统一标签，而且大多数分类算法使用的损失函数为softmax函数，该函数假设了类之间为相互独立的，这并不符合这里的逻辑。\n针对上述的问题，作者引入了等级分类，类似于生物学里的物种树。这里利用了Word Net（Imagenet数据集的标签就是取自于它的），它是基于认知的语言数据库，为一个有向图。作者并没有使用全部的图结构（毕竟这图十分庞大），而是仅用Imagenet中的概念来构建图结构。为了构建图，对比了Imagenet数据集的各个标签在WordNet中到根节点的路径，将具有唯一路径的词添加到了图中，对于有多个路径到达根节点的，则选择最短路径添加的图中，最终构建了一个$WordTree$。\n在给定同义词集的情况下，针对该同义集的每个下位词的概率，预测每个节点的条件概率。例如在terrier节点上预测：\n$$ Pr(Norfolk\\ terrier\\vert terrier) \\\\ Pr(Yorkshire\\ terrier\\vert terrier) \\\\ Pr(Bedlington\\ terrier\\vert terrier) \\\\ ... $$若要计算特定节点的绝对概率，只需沿着特定节点到树的根节点的路径，乘以路径上节点的条件概率即可。例如Norfolk terrier节点：\n$$ Pr(Norfolk\\ terrier) = Pr(Norfolk\\ terrier\\vert terrier) \\\nPr(terrier\\vert hunting\\ dog) \\ \u0026hellip; \\ * \\ Pr(mammal\\vert animal) \\ Pr(animal\\vert physical\\ object) $$ 这里为了达到分类的目的令$Pr(physical\\ object) = 1$，即图片必包含有目标。\n在Imagenet 1000类的基础上构建WordTree来训练Darknet-19，添加所有的中间节点后，标签数量从1000扩展到1369。在训练中，将真实标签向上传递，例如一个标签为“Norfolk terrir”，它也应该被标为“dog”和“mammal”。为了计算条件概率，模型预测了一个1369长度的向量并计算所有相同概念下同义词集的softmax，如下图：\n在分类时，作者假设了每幅图都包含一个对象，而对于检测任务，$Pr(physical\\ object)$的值不再指定为1，而是由Yolov2的对象检测器给出该值。模型检测部分给出物体的一个边界框及其概率数，然后向下遍历WordTree，找到每个softmax分支的最高置信度路径直到到达某个阈值，此时路径上离根路径最远的节点即为预测标签。\n在对Imagenet数据集和COCO数据集进行合并时，由于两个数据集的数量差异较大，所以作者对COCO数据集进行重采样，最后两个数据集的比例大致为$4:1$。模型沿用了YoLov2结构，但改为了使用3个先验框（原先5个先验框）来限制输出大小。分类时，模型仅后向传播分类损失部分，同时预测框于真实标签框的IOU不小于0.3时后向传播目标对象损失（objectness loss）；检测时，模型正常进行后向传播。\n具体的细节可参考原论文\n参考 本文参考自以下：\nYOLO9000: Better, Faster, Stronger\n","permalink":"https://simplestory.github.io/posts/20200110_aa4020065e766e48007d2b3ae229757d/","summary":"\u003cblockquote\u003e\n\u003cp\u003eyolov1算法发布后，引起了大众的强烈反应，有许多研究员将该算法与Fast Rcnn做了比较，yolov1在目标定位上出现了较多的偏差，同时有着较低的召回率。于是作者在几乎同年的时间段里就推出了yolov2以及用于超多类别检测的yolo9000，这算法的迭代速度令人一脸茫然（是真滴NB）。\u003c/p\u003e\n\u003c/blockquote\u003e","title":"YOLO_v2 and YOLO9000"},{"content":" 目标检测模型有单阶和二阶的区分，二阶中以Faster Rcnn为代表，对于单阶模型，除了SSD之外，我们还有YoLo这一个主流模型。\n简介 Yolo将目标检测问题转为边界框和类概率的回归问题，它通过一个卷积网络来同时处理多个边界框的预测以及这些框的类概率，所以这个模型的运行速度十分的快，但在精度方面的话会有点损失，毕竟没了像Faster RCnn那样的候选框生成过程。其次，Yolo模型有着更低的背景误识率和很好的泛化特性，作者甚至在抽象画作里进行了目标检测，效果海星:)\n设计 首先模型先将输入图片划分为$S \\times S$个网格，如果有目标对象的中心落在某个网格内，则该网格负责预测该对象（具体则是再训练阶段选取与真实框有最大IOU的边界框来负责对应真实例的检测。这样每个检测器都会各司其职，只检测一类对象）。每一个网格都会预测$B$个边界框以及这些框的置信度。这里置信度是指该框里是否有对象和做出这个判断的把握：$Pr(Object)*IOU^{truth}_{pred}$。如果没有对象在框内则$Pr(Object)$为0，否则使用预测框和真实框之间的$IOU$作为其置信度数值。\n每个边界框都包含有5个变量：$x,y,w,h,conf$。前四个是坐标变量，其中$x,y$为框的中心，$w,h$则表示宽和长。这里要注意中心坐标是相对网格大小的，而长宽是相对整张图像的。\n每个网格会预测$C$个类条件概率（$C$为待预测类别数）。这些概率是在有对象的条件下计算的：$Pr(Class_i\\vert Object)$。在训练阶段，将该概率跟框的置信度相乘从而得到每个框对于每一类的预测概率：\n$$ Pr(Class_i\\vert Object) * Pr(Object) * IOU^{truth}_{pred} = Pr(Class_i) * IOU^{truth}_{pred} $$综上可以得到模型最终输出的张量大小为$S\\times S\\times (B*5+C)$。作者选择的参数为$S=7$，$B=2$，训练所用的数据集为$PASCAL \\ VOC$，所以标签数为$C=20$。这里附上论文里的一张经典图：\n结构 整体的模型结构是卷积网络来提取特征和全连接网络层来输出预测概率以及坐标值。对于特征提取部分，作者构建的网络一共有24层卷积层（对于yolo tiny，使用9层卷积同时减少卷积核数），同时使用了$1\\times 1$降采样层加$3\\times 3$的卷积层来替代GoogLeNet的inception部分。同样附上论文结构图：\n训练 作者使用Darknet框架来完成模型的训练和推断。预训练阶段，作者选用了模型的前20层卷积网络，后接一个全局平均池化层和全连接层组成一个分类网络在$ImageNet$数据集上进行训练。由于将卷积层和连接层都添加到预训练模型中能提高性能，所以作者添加了随机进行初始化的四个卷积层（所以最后模型为24层卷积网络层）和两个全连接层。因为目标检测要求有更大的分辨率，所以将输入图片由预训练的$224\\times 224$提高到$448\\times 448$。最后一层同时预测出类概率和框坐标，这里将框坐标进行归一化，具体操作上面已经有提及（中心坐标是相对网格大小的，而长宽是相对整张图像的）。网络层使用激活函数为$Leak\\ ReLU$：\n$$ \\phi(x) = \\begin{cases} x, \\ \u0026\\text{if x \u003e 0} \\\\ 0.1x, \\ \u0026\\text{otherwise} \\end{cases} $$模型的损失函数选用的是平方和误差损失函数，因为优化简单（嗯，有时就是你想多了），当然作者对该函数进行了一些调整以适应本模型。这里列举了几点不足之处：\n一些并不够理想的框的定位权重跟预测得较好的框的定位权重是一样的； 在划分出来的网格中，有时候有许多网格内并没有目标对象存在，而这些网格的置信度为0，这样它的梯度通常会超过包含目标对象的那些框的梯度，从而造成模型的不稳定。 为了弥补这些问题，作者通过参数$\\lambda_{coord}$增加了边界框的坐标损失同时通过$\\lambda_{noobj}$减少了不包含对象的框的置信度损失，论文里设置了$\\lambda_{coord}=5$和$\\lambda_{noobj}=0.5$。\n同时，平方和误差对大框和小框的误差是一样的，这并不符合我们大框的小偏差要比小框中的小偏差小的要求。作者采取了一个取巧的做法：对框的宽高求平方根再作平方误差。\n最终的损失函数如下：\n$$ \\begin{aligned} Loss =\\ \u0026 \\lambda_{coord}\\sum_{i=0}^{S^2}\\sum_{j=0}^B\\mathbb{I}_{ij}^{obj}[(x_i-\\hat{x}_i)^2+(y_i-\\hat{y}_i)^2] \\\\ \u0026+\\lambda_{coord}\\sum_{i=0}^{S^2}\\sum_{j=0}^B\\mathbb{I}_{ij}^{obj}\\left[\\left(\\sqrt{w_i}-\\sqrt{\\hat{w}_i}\\right)^2-\\left(\\sqrt{h_i}-\\sqrt{\\hat{h}_i}\\right)^2\\right] \\\\ \u0026+\\sum_{i=0}^{S^2}\\sum_{j=0}^B\\mathbb{I}_{ij}^{obj}(C_i-\\hat{C}_i)^2 \\\\ \u0026+\\lambda_{noobj}\\sum_{i=0}^{S^2}\\sum_{j=0}^B\\mathbb{I}_{ij}^{obj}(C_i-\\hat{C}_i)^2 \\\\ \u0026+\\sum_{i=0}^{S^2}\\mathbb{I}_i^{obj}\\sum_{c \\in classes}(p_i(c)-\\hat{p}_i(c))^2 \\end{aligned} $$上式注意：\n$\\mathbb{I}_i^{obj}$为指示函数，用来指示目标对象是否在相应的单元格$i$内；\n$\\mathbb{I}_{ij}^{obj}$也为指示函数，用来指示第$i$单元格的第$j$个边界框是否负责预测某一对象。\n该函数的前两项为模型的坐标预测损失，第三项为含有目标对象的边界框的置信度预测损失，第四项为不含目标对象的边界框的置信度预测损失。最后一项为模型的类别损失（这里注意该函数只惩罚了那些网格内有目标对象的分类损失）。\n关于训练的具体参数设置可以参考原论文。作者为了避免模型过拟合，还使用了Dropout层和常见的数据扩充技术。\n局限 每个单元格只预测2个边界框，然后每个单元格最后只取与真实标记框的IOU值最高的那个作为最后的检测框，即每个单元格最多只预测一个目标，对小目标的检测并不好； 虽然损失函数中采用平方根来缓解相同位置误差对大、小框产生的影响相同的情况，但其作用较小，没有从根本上解决问题； 模型学会了根据数据预测边界框，因此很难推广到具有新的或不同的长宽比配置的对象中，而且模型还是使用比较粗略的特征来生成边界框。 参考 本文主要参考自一下论文：\nYou Only Look Once: Unified, Real-Time Object Detection\n","permalink":"https://simplestory.github.io/posts/20200101_83ba412c3f5a7a54968bf73cc917cd13/","summary":"\u003cblockquote\u003e\n\u003cp\u003e目标检测模型有单阶和二阶的区分，二阶中以Faster Rcnn为代表，对于单阶模型，除了SSD之外，我们还有YoLo这一个主流模型。\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch2 id=\"简介\"\u003e简介\u003c/h2\u003e\n\u003cp\u003eYolo将目标检测问题转为边界框和类概率的回归问题，它通过一个卷积网络来同时处理多个边界框的预测以及这些框的类概率，所以这个模型的运行速度十分的快，但在精度方面的话会有点损失，毕竟没了像Faster RCnn那样的候选框生成过程。其次，Yolo模型有着更低的背景误识率和很好的泛化特性，作者甚至在抽象画作里进行了目标检测，效果海星:)\u003c/p\u003e","title":"YOLO_v1"},{"content":" 首先必须祝贺中华人民共和国成立70周年，为了庆祝国庆假期的到来，我抽空过了一遍SSD目标检测模型，然后趁热打铁更了博客。。。SSD算法跟以往的RCNN系列算法最大的区别就是它是单级式的，而RCNN系列是多级式的。具体的细节见下文。\n基本框架 先前的RCNN系列算法基本上都遵循生成候选框、提取特征、分类和回归这一模式，而SSD以及类似算法采用的是在特征图上进行密集采样得到先验框再进行分类和回归。简单来讲，多级式和单极式的区分就是看有没有生成候选框这一过程。多级式含有这一过程，所以相关的算法检测速度较缓慢，但精度高；单级式算法没有这一过程，相关算法检测速度快但精度低。\n$SSD$算法是基于前向传播的卷积算法生成固定尺寸的边界框以及框内存在的目标的得分再接一个非极大值抑制来生成最终的检测结果的。这里前半部分采用的卷积网络是用于图像分类的标准结构网络（再分类层之前进行截断），作者称之为$base \\ network$。在$base \\ network$后面接一些辅助结构便形成了$SSD$目标检测网络。这个模型有一些关键特征，列举如下。\nMulti-scale feature maps for detection 作者在截断的基础网络后面接了一些卷积特征层，这些特征层的尺度逐渐递减。$SSD$模型会提取多个不同尺寸的卷积层的特征来进行目标检测，这就形成了多尺度特征图检测，大尺度特征可以用来检测小物体，小尺度特征用来检测大物体。这里作者还跟$YOLO$模型进行了比较，$YOLO$是在单一尺度的特征图上进行操作的，很明显这样获取的信息量要少于$SSD$的多尺度范围的检测，所以在精度方面$SSD$要优于$YOLO$。\nConvolutional predictors for detection 在上面提到的添加卷积特征层中，每一层都可以使用一系列卷积来生成一系列固定大小的检测预测结果（这些预测要么是目标类别的得分，要么是边界框的偏置）。这一点跟$YOLO$算法不同，$YOLO$经过卷积层后还要经过全连接层再进行检测，而$SSD$直接采用卷积对不同的特征图进行提取检测结果。对于$m \\times n$大小，通道数为$p$的卷积特征层，后接的检测卷积层基本的尺寸为$3 \\times 3 \\times p$，即用小型核来进一步提取特征。输出的边界框偏置量是相对于先验框测量的，而先验框又是相对于每一个特征图的位置的。\nDefault boxes and aspect ratios 作者在每一张特征图上关联了一系列不同尺寸和长宽比的先验框，这类似于$Faster \\ RCNN$的$Anchor \\ box$。每一个先验框都会得到类别分数及其对应的边界框参数。对于特征图上的每个点都有$k$个先验框的情况，模型会计算对应于$c$个类别的置信度（这里注意$SSD$将背景也当为一个类而算进类别个数里面），$4$个偏置量。\n注意：预测过程中，置信度最高的那个类别就是边界框所属的类别，而当第一个置信度最高时，则表示边界框中不包含目标。\n这意味着会有$(c+4)k$个卷积核应用于特征图上的各个位置，而对于$m \\times n$大小的特征图，一共生成$(c+4)kmn$个输出。这种方式跟$Faster \\ RCNN$的$anchor \\ boxes$很类似，但不同的是$SSD$将先验框应用于几个不同分辨率的特征图上，这种处理方式允许我们高效地离散化可能的输出框大小空间。\n总体结构图如下：\n先验框的细节如下图：\n模型训练 $SSD$模型的训练与其它带有区域候选框的典型检测器不同，主要是$SSD$需要将真实实例信息分配给检测器输出的固定集合中的特定输出（这里附上作者原话：The key difference between training SSD and training a typical detector that uses region proposals, is that ground truth information needs to be assigned to specific outputs in the fixed set of detector outputs）。\n在训练过程中，$SSD$需要确定哪个先验框对应哪个真实实例，并依据这些映射进行模型的训练。根据论文，先验框与$ground \\ truth$的匹配原则如下：\n对于图片中每个$ground \\ truth$，找到与其$IoU$最大的先验框，该框与其匹配，这样可以保证每个$ground \\ truth$一定与某个先验框匹配； 剩余的未匹配先验框，若某个$ground \\ truth$的$IoU$大于事先定下的阈值，则该先验框也与这个$ground \\ truth$匹配。 上面的匹配原则会造成一个$ground \\ truth$与多个先验框匹配，但不允许有一个先验框对应多个$ground \\ truth$。\n$SSD$训练的目标函数是来自于$MultiBox$的，但作者进行了扩展以处理多目标分类问题。定义$x^P_{ij} \\in \\\\{1,0\\\\}$为指示函数，当$x^P_{ij}=1$时表示第$i$个先验框与第$j$个$ground \\ truth$匹配并且$ground \\ truth$的类别为$P$。在上面的匹配策略中，我们可以有$\\sum_ix^P_{ij} \\ge 1$。总体的损失函数是位置损失和置信度损失的加权和，如下：\n$$ L(x,c,l,g) = \\frac{1}{N}(L_{conf}(x,c)+\\alpha L_{loc}(x,l,g)) $$其中$N$表示先验框的正样本数，$c$为类别置信度预测值，$l$为先验框所对应边界框的位置预测值，$g$是$ground \\ truth$的位置参数。位置损失采用的是$Smooth \\ L1 \\ loss$，与$Faster \\ RCNN$类似，作者对先验框中心$d(cx,cy)$及其宽度$w$和高度$h$的偏移量进行回归。这里注意要先对$ground \\ truth$的$g$进行编码，因为预测值$l$为编码值。\n$$ \\begin{aligned} L_{loc}(x,l,g) \u0026 = \\sum_{i\\in Pos}^N \\sum_{m\\in \\{cx,cy,w,h\\}}x_{ij}^ksmooth_{L1}(l_i^m-\\hat{g}_j^m) \\\\ \\hat{g}_j^{cx} \u0026 = (g_j^{cx}-d_i^{cx})/d_i^w \\\\ \\hat{g}_j^{cy} \u0026 = (g_j^{cy}-d_i^{cy})/d_i^h \\\\ \\hat{g}_j^{w} \u0026 = log(\\frac{g_j^w}{d_i^w}) \\\\ \\hat{g}_j^{h} \u0026 = log(\\frac{g_j^h}{d_i^h}) \\\\ \\end{aligned} $$置信度损失采用的是$softmax \\ loss$。\n$$ L_{conf}(x,c) = - \\sum_{i \\in Pos}^Nx_{ij}^Plog(\\hat{c}_i^P)-\\sum_{i \\in Neg}log(\\hat{c}_i^0) $$其中\n$$ \\hat{c}_i^P = \\frac{exp(c_i^P)}{\\sum_Pexp(c_i^P)} $$为了处理不同对象的尺寸不同问题，有些方法建议将图像处理成不同的尺寸然后再合并结果，这可以通过利用单个网络中几个不同层的特征图进行预测来得到相似的效果，而且还可以在所有对象比例尺上共享参数。在此前已经有实验验证了低层卷积层产生的低级特征图能有效地提升语义分割的效果，因为低级网络层捕捉到了更多的输入对象的细节。作者在$SSD$中同时使用了低层和高层的网络层进行目标检测。\n从不同网络层获取的特征图会有不同的尺寸，而在$SSD$网络中，先验框不必需要去对应每一个网络层的实际感受野，作者设计了先验框的拼贴以便特定的特征图能对特定尺寸的对象做出响应。假设使用$m$个特征图来预测，每一个特征图上的先验框大小用下式计算：\n$$ s_k = s_{min}+\\frac{s_{max}-s_{min}}{m-1}(k-1), \\ k \\in [1,m] $$这里设置$s_{min}=0.2$以及$s_{max}=0.9$，表示最小尺寸为0.2，最大尺寸为0.9。先验框的尺度遵守一个线性递增规则：随着特征图大小的降低，先验框尺度线性增加。上式的$s_k$表示先验框相对于图片的比例。作者在先验框上强加了不同的长宽比，表示为$a_r \\in \\\\{1,2,3,\\frac{1}{2},\\frac{1}{3}\\\\}$，并利用下式计算先验框的长宽大小。\n$$ \\begin{aligned} w_k^a \u0026= s_k\\sqrt{a_r} \\\\ h_k^a \u0026= s_k/\\sqrt{a_r} \\end{aligned} $$对于长宽比为1的情况，作者添加了一个先验框（尺度为$s_k^*=\\sqrt{s_k s_{k+1}}$），所以最终特征图每个位置会产生6个先验框。作者将先验框的中心坐标设置为$(\\frac{i+0.5}{\\vert f_k \\vert},\\frac{j+0.5}{\\vert f_k \\vert})$，这里$\\vert f_k \\vert$表示第k个特征图的大小并且$i,j \\in [0,\\vert f_k \\vert]$。实际上，我们可以通过设计先验框的分布以适应特定的数据集。通过结合从多个特征图不同尺寸和长宽比生成的先验框完全可以布满整个图像，所以$SSD$可以算是密集采样。生成的先验框中有许多是负样本，这说明进行正负样本平衡是有必要的。作者采用了难分样本挖掘进行平衡，根据损失置信度对先验框进行排序，选择靠前的先验框并保持正负样本比例为$1:3$。\n在数据增强方面，作者对训练中每一张图像从下面随机选择一项进行增强：\n使用原始的全部图像； 采样块域以使与目标对象的最小$jaccard \\ overlap$为0.1，0.3，0.5，0.7或0.9； 随机采样块域 其中每一个块域的大小为$[0.1,1]$倍于原始图像大小，同时长宽比介于0.5和2之间。$jaccard \\ overlap$（交并比，即为IOU）的计算如下：\n$$ \\begin{aligned} J(A,B) \u0026 = \\frac{\\vert A\\cap B\\vert}{\\vert A\\cup B\\vert} \\\\ \u0026 = \\frac{\\vert A\\cap B\\vert}{\\vert A \\vert +\\vert B\\vert - \\vert A\\cap B\\vert} \\end{aligned} $$这里有个注意点。在$base \\ network$中，作者采用的是在$ILSVRC \\ CLS-LOC$上进行过预训练的$VGG16$模型，将$fc6$和$fc7$转为卷积层，将池化层$pool5$由原来的$stride=2$的$2\\times 2$转为$stride=1$的$3\\times 3$。这里作者使用了$Atrous$算法来让模型适应这种变化（实际上是$conv6$层采用扩展卷积或带孔卷积在不增加模型参数和复杂度的情况下扩大感受野）。\n其它模型上的细节可以参考原论文。\n附言 在实现这个模型的过程中，minsize和maxsize这两个参数让我比较困扰（这两个参数用于生成每个特征层地先验框尺寸）。对比了其他一些源码后，大概的计算过程如下：\n理论上，应该按原论文里给的公式进行计算：\n$$ s_k = s_{min}+\\frac{s_{max}-s_{min}}{m-1}(k-1), \\ k \\in [1.m] $$其中$s_k$为每个特征层的先验框大小与原图大小之比，$S_{min}$和$S_{max}$分别表示最小、最大比例，这里依据论文设定$S_{min} = 0.2$，$S_{max} = 0.9$，$m$表示特征层个数（这里经过多份代码的对比，选用的m不是6而是5），$k$为第几个特征层。\n而在实际使用中，我们需要考虑到取整的问题，所以使用的公式如下（这里选用向上取整，也可采用其它取整方式）：\n$$ s_k = \\lceil{s_{min} \\times 100}\\rceil+\\left\\lceil{\\frac{\\lceil{s_{max}\\times 100}\\rceil-\\lceil{s_{min}\\times 100}\\rceil}{m-1}}\\right\\rceil(k-1), \\ k \\in [1,m] $$这样可以得到一系列数字：\n$$ s1 \\sim s6: 20, 38, 56, 74, 92, 110 $$将其除以100转为真实比例：\n$$ s1 \\sim s6: 0.20, 0.38, 0.56, 0.74, 0.92, 1.10 $$最后乘上原始图片的尺寸（这里选用的是300）：\n$$ 60, 114, 168, 222, 276, 330 $$针对$s1$，还有一个$s1^{\\prime}= 0.5\\times s1$，最后可得:\n$$ 30, 60, 114, 168, 222, 276, 330 $$这样可以得到7个数值，对应6组$minsize$和$maxsize$，由此可以计算出每一个特征层的先验框尺寸。\n参考 本文参考自：\nSSD:Single Shot MultiBox Detector\n目标检测SSD原理与实现（作者：小小将）\n源码参考：kuhung/SSD_keras\n","permalink":"https://simplestory.github.io/posts/20191003_34df20bab5e85dc75bfc94ef569cced9/","summary":"\u003cblockquote\u003e\n\u003cp\u003e首先必须祝贺中华人民共和国成立70周年，为了庆祝国庆假期的到来，我抽空过了一遍SSD目标检测模型，然后趁热打铁更了博客。。。SSD算法跟以往的RCNN系列算法最大的区别就是它是单级式的，而RCNN系列是多级式的。具体的细节见下文。\u003c/p\u003e\n\u003c/blockquote\u003e","title":"SSD"},{"content":" 这种连更的感觉很是刺激，很是酸爽。不多说废话，如标题所述这是一个更快更强的目标检测算法，同时也出自Ross Girshick之手。。。讲道理，这位大佬连着几篇论文都在喷他自己之前写的模型，有点东西啊。Faster Rcnn采用了一种高效的算法来生成候选框，也引入了Anchor这个概念。\n基本结构 在$Fast \\ RCNN$中，并没有对候选框的提出方法进行过任何讨论，只是提及到了使用$selective \\ search$进行候选框的选择，后期这也成为了拖慢模型运行速度的主要原因。作者在$Faster \\ RCNN$这个模型中进行了改进，提出了一种新的候选框提取方法，即$RPN$。结合了这种方法后模型的运行速度得到了大幅提升，同时准确率也有了提高。$Faster \\ RCNN$的大致结构如下：\n它的结构主要包含两部分，首先是一个深度全卷积网络用于生成候选区域，之后是$Fast \\ RCNN$模型用于分类和边界框回归。$RPN$使用在经过卷积后的特征图上。\nRegion Proposal Networks $RPN$网络以任意尺寸的图像作为输入计算输出一组对象候选框以及分类分数。为了生成候选区域，$RPN$在卷积特阵图上滑动一个小网络，这个小网络将特征图上的$n \\times n$窗口作为输入。每一个滑窗生成一个低维特征，这个特征输入到两个分支中，一个是边界框回归层（$reg$），一个是框分类层（$cls$）。结构大致如下：\n这里有一个重点，那就是$Anchor$。在每个滑窗的位置，我们需要同步地预测多个候选框，定义每个位置候选框的最大数量为$k$，所以$reg$层的输入为$4k$（编码了候选框的坐标），$cls$层的输入为$2k$（即候选框是否为目标）。而$Anchor$实际上是滑动窗口的中心，并和尺度和长宽比有关（个人理解是当前滑窗中心在原像素空间上的映射点即为$Anchor$）。论文实验中采用了3组尺度和3组长宽比，即每个滑窗位置生成9个$anchor$，对于尺寸为$W \\times H$的卷积特征图，则一共生成$WHK$个$anchors$。跟以往的图像金字塔和特征金字塔相比，基于$Anchors$我们设计的多尺度检测只依赖于单一尺度的图像和特征映射，并使用单一尺寸的特征图上的滑动窗口。\n$Anchor$还有一个重要性质，那就是平移不变性。若一张图片上的目标发生了平移，候选区域应该平移，这时相同的函数在任意位置都可以预测到候选框。通过这一性质也能降低模型的参数量。\n为了对$RPN$进行训练，作者对每个$Anchor$都指定了一个二分类标签。对于以下两种情况的$anchor$指定为正类：\n和真实例有着最高$IoU$的候选框； 和任一真实例有着超过0.7的$IoU$的候选框 当候选框的$IoU$低于0.3则标记为反类，$IoU$在0.3到0.7之间的$anchors$不参与$RPN$训练。训练的损失函数为：\n$$ L(\\{p_i\\},\\{t_i\\}) = \\frac{1}{N_{cls}}\\sum_iL_{cls}(p_i,p_i^*)+\\lambda\\frac{1}{N_{reg}}\\sum_ip_i^*L_{reg}(t_i,t_i^*) $$其中$i$是$anchor$在一个$minibatch$中的序号，$p_i$表示第$i$个$anchor$为对象的概率。$p_i^{\\*}$为真实例标签表示，当$anchor$为正例时为1，为反例时为0。$t_i$是预测框的坐标向量，$t_i^{\\*}$则是真实例的坐标向量。$L_{cls}$是两个类上的对数损失，对于框回归的损失，采用的是:\n$$ L_{reg}(t_i,t_i^*)=R(t_i-t_i^*) $$其中$R$是$smooth \\ L_1$函数。上式中的$p_i^*L_{reg}$项意味着回归项损失只在$anchor$为正类时计算。$cls$层和$reg$层的输出分别由$\\{p_i\\}$和$\\{t_i\\}$组成。\n两项损失分别由$N_{cls}$和$N_{reg}$进行标准化并由$\\lambda$控制平衡。但作者通过实验表明模型对大范围内的$\\lambda$并不敏感，同时上面提及的标准化并不是必需的，可以进行简化。\n边界框回归采取的方式跟$RCNN$一致，这里还对真实例的边界框进行了参数化处理$(t_x^*, t_y^*, t_w^*, t_h^*)$：\n$$ \\begin{aligned} t_x \u0026 = (x-x_a)/w_a \\\\ t_y \u0026 = (y-y_a)/h_a \\\\ t_w \u0026 = log(w/w_a) \\\\ t_h \u0026 = log(h/h_a) \\\\ t_x^* \u0026 = (x^*-x_a)/w_a \\\\ t_y^* \u0026 = (y^*-y_a)/h_a \\\\ t_w^* \u0026 = log(w^*/w_a) \\\\ t_h^* \u0026 = log(h^*/h_a) \\end{aligned} $$用对数来表示长宽的差别还有一个作用是为了在差别大时能快速收敛，差别小时能较慢收敛来保证精度。\n模型训练 对于检测网络，作者采用的是$Fast \\ RCNN$。在训练时会分别对$RPN$和$Fast \\ RCNN$进行训练。有三种用于训练这种包含特征共享该网络的方法：\n$Alternating \\ training$。首先我们会先训练$RPN$，然后使用候选框去训练$Fast \\ RCNN$，网络经过$Fast \\ RCNN$微调后再返过来初始化$RPN$，这个过程可以迭代进行。 $Approximate \\ joint \\ training$。这种方法将$RPN$和$Fast \\ RCNN$融合为一个网络进行训练。在每一轮$SGD$中，前向传播经过$RPN$时并不改变其参数，使用预先计算的候选框对$Fast \\ RCNN$进行训练，后向传播则正常进行。对于共享卷积层的后向传播，传播信息来自$RPN$的损失和$Fast \\ RCNN$的损失。这种方法忽略了候选框坐标的导数信息。 $Non-approximate \\ joint \\ training$。这种方法修正了上一种方法的缺陷，包含了候选框坐标的信息。这种方法我们需要一个可区分框坐标不同的$RoI \\ pooling$池化层，这一点可以通过$RoI \\ warping$层来解决。 作者采用的是$4-step \\ Alternating \\ Training$，四步具体如下：\n训练$RPN$，网络用$ImageNet$预训练模型进行初始化并用区域候选框损失进行端到端的微调； 使用第一步生成的候选框对$Fast \\ RCNN$进行训练，这个网络也用$ImageNet$预训练模型进行初始化。到这一步，两个网络并没有共享卷积层的计算； 使用检测网络对$RPN$进行训练，同时冻结卷积层，只调整属于$RPN$模型的层； 保持卷积层冻结，微调属于$Fast \\ RCNN$模型的层。这样两个网络都共享了卷积层的计算。 $Faster \\ RCNN$和$Fast \\ RCNN$都是属于$two \\ stage$算法，即二阶段算法。这类算法的特征是先由算法生成一系列作为样本地候选框，然后再对这些框进行分类和回归操作。这样计算精度高但速度慢。而单阶段算法（$one \\ stage$）则是将目标定位问题转为回归问题，速度上有了较大地提升但精度却下降了。\n参考 关于$Faster \\ RCNN$模型还有许多细节这里没有提及，具体可以参照论文原文以及作者开源的相关代码。\n本文主要参考自以下文章：\nFaster R-CNN: Towards Real-Time Object Detection with Region Proposal Networks\n一文读懂Faster RCNN\n","permalink":"https://simplestory.github.io/posts/20190922_5e8040095d43e97ccedd09771f9eab38/","summary":"\u003cblockquote\u003e\n\u003cp\u003e这种连更的感觉很是刺激，很是酸爽。不多说废话，如标题所述这是一个更快更强的目标检测算法，同时也出自Ross Girshick之手。。。讲道理，这位大佬连着几篇论文都在喷他自己之前写的模型，有点东西啊。Faster Rcnn采用了一种高效的算法来生成候选框，也引入了Anchor这个概念。\u003c/p\u003e\n\u003c/blockquote\u003e","title":"Faster RCNN"},{"content":" 哈，难得这几天比较空闲，就抓紧总结一下之前看过的RCNN系列的论文，这篇是这一系列的第二篇。作者Ross Girshick看不惯自己曾经提出的模型（RCNN）运行起来如此之慢，所以提出了一个改进模型（Fast RCNN），接下来对该模型进行一个简单的分析。\nRCNN的缓慢 与图像识别任务相比，目标检测由于需要多一个定位对象位置这一步所以难度提升了不少，其中主要解决的问题有两个，一个是模型需要大量的对象候选框；另外一个是这些候选框只是粗略地提供了对象的大概位置，需要模型去做一个回归的操作。$RCNN$是一个多阶段训练模型。$RCNN$模型首先在对象候选框上使用对数损失来微调卷积神经网络，然后得到的特征图传给$SVM$进行目标分类，这些$SVM$代替了需要微调的$softmax$层，在第三训练阶段，模型学习边界框回归。对于$SVM$和边界框回归的训练，$RCNN$将每个目标候选框的特征都写入了磁盘中，这无疑耗费了十分巨大的时间和空间，其次，目标识别的速度过慢，在GPU上识别图片大概需要47秒每张图片。$RCNN$缓慢还有一个重要原因，即模型没有共享卷积层的计算。\n基本结构 $Fast \\ RCNN$是一个单阶段训练的模型。它将整个图像和一组候选框都作为输入，模型首先用一组卷积层和最大池化层对整张图像进行特征提取，再在提取出的特征图上，针对每一个候选框用$RoI \\ pooling$池化层提取出一个固定长度的特征向量，之后送入全连接层最终分成两个分支分别进行目标识别和边界框回归。具体框架如下：\n$RoI \\ pooling$将$h \\times w$的$RoI$区域划分为$h/H \\times w/W$规格的子窗口，再在各个子窗口上应用$max \\ pooling$从而输出一个固定长度的特征向量。\n预训练和微调网络 对于网络的初始化，作者使用了三个预训练的$ImageNet$的模型，并进行了如下更改：\n原网络最后一个$max \\ pooling$层用$RoI \\ pooling$层替代并指定参数$H \\times W$以适应网络的第一个全连接层的尺寸； 原网络最后一个全连接网络和$softmax$层替代为两个分支网络； 原网络修正为两个输入 作者提出了一个包含了特征共享优势的高效模型训练方法。在$Fast \\ Rcnn$训练中，对随机梯度下降的$minibatch$进行分层采样，首先采样$N$个样本然后从每一个样本中采样$R/N$个$RoI$。来自同一个图片的$RoI$共享前向以及后向传播中的计算和内存。\n模型采用的损失函数为多任务损失函数。$Fast \\ RCNN$有两个输出层，一个是输出每一个$RoI$的关于$K+1$个类的概率$p=(p_0,\\dots,p_K)$，另一个是输出边界框关于$K$个类的回归偏移量$t^k=(t_x^k,t_y^k,t_w^k,t_h^k)$。每个$RoI$被真实类别$u$和真实类别的目标框$v$标记。作者对每一个被标记的$RoI$使用多项任务损失函数$L$来共同训练分类和边界框回归：\n$$ L(p,u,t^u,v) = L_{cls}(p,u)+\\lambda[u \\ge 1]L_{loc}(t^u,v) $$其中$L_{cls}(p,u)=-logp_u$是对数损失，第二项损失由真实类别的回归框$v=(v_x,v_y,v_w,v_h)$和预测的回归框$t^u=(t_x^u,t_y^u,t_w^u,t_h^u)$组成的，$L_{loc}$忽略背景$RoI$类，所以对于边界框回归有：\n$$ L_{loc}(t^u,v) = \\sum_{t \\in \\{x,y,w,h\\}} smooth_{L_1}(t_i^u-v_i) $$对于$smooth$函数有：\n$$ smooth_{L_1}(x) = \\begin{cases} 0.5x^2 \u0026\\text{if $\\vert x \\vert\\lt 1$} \\\\ \\vert x \\vert-0.5 \\ \u0026\\text{otherwise} \\end{cases} $$这里采用$L_1$损失与$RCNN$采用的$L_2$相比少了些敏感性。多项任务损失函数中的超参数$\\lambda$控制着两个单一损失函数的平衡。作者在论文的实验中对真实实例的边界框进行了标准化（使其均值为0，方差为1），损失函数中的超参数选为1。\n关于$minibatch$的采样这里就不再细讲，具体可看看作者的论文。接下来说一下$RoI \\ pooling$池化层的后向传播。令$x_i \\in R$为$RoI \\ pooling$的第i个输入，$y_{rj}$为第r个$RoI$的第j个输出，则$RoI \\ pooling$的计算公式可以表示为\n$$ y_{rj}=x_{i^*(i,j)} $$其中$i^*(r,j)=argmax_{i^\\prime\\in R(r,j)}x_i^\\prime$，$R(r,j)$是输入子窗口的索引集。一个$x_i$可能对应着多个不同的输出$y_{rj}$。其后向传播形式为：\n$$ \\frac{\\partial{L}}{\\partial{x_i}} = \\sum_r\\sum_j[i=i^*(r,j)]\\frac{\\partial{L}}{\\partial{y_{rj}}} $$$RoI$池化层反向传播中损失函数$L$对输入节点$x_i$的梯度为$L$对各个有可能的候选区域$r$（$x_i$被第$r$个候选区域的第$j$个输出选为最大值）输出$y_{rj}$梯度的累加，上式中的判决函数具体如下：\n$$ [i=i^*(r,j)] = \\begin{cases} 1, \u0026 i=i^*(r,j) \\ge 1 \\\\ 0, \u0026 \\text{otherwise} \\end{cases} $$对于整个图像的分类，全连接层计算所消耗的时间是要小于卷积层的，但在这个模型中，有大量的$RoI$用于目标检测，所以全连接层的计算时间大概占用了整体模型的$50\\%$。所以作者将大型的全连接层进行了$SVD$截断操作。一个全连接层可以参数化为$u \\times v$大小的权重矩阵$W$，对该矩阵用$SVD$转换可得：\n$$ W \\approx U\\Sigma_tV^T $$其中$U$为$W$的大小为$u \\times t$的左奇异矩阵，$\\Sigma_t$为$W$的大小$t \\times t$的奇异值对角阵，$V$为$W$的大小$v \\times t$的右奇异矩阵。经过这一个简单的压缩操作能得到一个很好的速度提升。\n说说模型 作者在论文后面还以提问的形式对模型进行了一些讨论。首先是多项任务损失，它避免了去设计一个管道式的序列训练模型，实验结果也显示这种设计效果优于多阶段的训练。$Fast \\ RCNN$模型还将$RCNN$模型的$SVM$换成了$softmax$层，$softmax$层在为$RoI$预测分数时引入了类之间的竞争，所以最终效果要优于$SVM$。最后有一个注意点，并不是候选框越多，模型的性能越好，作者实验发现稀疏的对象候选框能够提升模型的性能。\n参考 本文参考以下论文：\nFast R-CNN\n","permalink":"https://simplestory.github.io/posts/20190921_817fe11366b1149fdc44a5a4c92685c4/","summary":"\u003cblockquote\u003e\n\u003cp\u003e哈，难得这几天比较空闲，就抓紧总结一下之前看过的RCNN系列的论文，这篇是这一系列的第二篇。作者Ross Girshick看不惯自己曾经提出的模型（RCNN）运行起来如此之慢，所以提出了一个改进模型（Fast RCNN），接下来对该模型进行一个简单的分析。\u003c/p\u003e\n\u003c/blockquote\u003e","title":"Fast RCNN"},{"content":" 目标识别是比单一的图像识别要复杂得多，算法模型要识别出图片哪里有哪些目标并将其框出来。Ross Girshick大神提出了一种利用卷积神经网络加上支持向量机的算法模型用于目标检测。下面是对这个经典模型进行的简单解析。\n基础结构 首先是目标定位问题，作者通过区域识别的方法来解决，这里采用的是$selective \\ search$。即从图片中框选出候选区域参加后续模型的训练。其次是训练数据稀疏的问题，论文提出了在一个大的辅助数据集中有监督的预训练，之后在一个小数据集中进行特定部分的微调。实验结果也证实了这种方法是有效的。大致结构如下：\n主要有三个组成部分：\n生成与类别无关的候选区域； 用来从每一个区域中提取固定长度的特征向量的卷积网络； 一组特定于类的线性SVM 模块设计 在选择候选区域方面，有许多种实现方法，例如$objectness$，$selective \\ search$，$category-independent \\ object \\ proposal$等。作者选择了$selective \\ search$，这里不得不说$R-CNN$对于候选框的提出方法是不太合理的，因为$selective \\ search$方法实在太慢了，严重拖慢了模型的速度。特征提取方面，作者使用$CNN$从每个候选框区域中提取出固定长度的特征。首先将候选区域周围紧密边界框的所有像素扭曲到到适合$CNN$网络的输入尺寸，在此前，作者扩展了紧密边界框以便在扭曲的尺寸上，原始周围的扭曲图像上下文恰有足够的像素框。在识别阶段，经过$SVM$得到各个候选框分数后再用贪婪的非极大值抑制（$NMS$）对结果进行整合。考虑到训练数据过大，作者还采用了标准的难分样本挖掘来对模型性能进行提升。\n模型的训练过程也分为三部分：\nCNN微调 SVM训练 边界框回归训练 由于前面两个的训练基本上都跟以往一样，就不细说了，这里主要提一下边界框回归训练。\n这一部分的输入是$N$个训练数据对$\\{(P^i, G^i)\\}_{i=1,\\dots,N}$，其中$P^i=(P^i_x,P^i_y,P^i_w,P^i_h)$表示候选框中心的像素坐标以及候选框的长宽信息，同理$G=(G_x,G_y,G_w,G_h)$对应真实对象边界框信息。我们的目标是学习一个候选框$P$到真实边界框$G$的映射。定义四个变量$d_x(P)$，$d_y(P)$，$d_w(P)$，$d_h(P)$，前两个指定$P$的边界框中心的尺度不变的平移，后两个指定$P$的边界框的宽度和高度的对数空间上的平移。则候选框$P$与预测的目标框$\\hat{G}$的映射可以表示为：\n$$ \\begin{aligned} \\hat{G}_x \u0026 = P_wd_x(P)+P_x \\\\ \\hat{G}_y \u0026 = P_hd_y(P)+P_y \\\\ \\hat{G}_w \u0026 = P_wexp(d_w(P)) \\\\ \\hat{G}_h \u0026 = P_hexp(d_h(P)) \\end{aligned} $$可以把它看成线性回归问题，则每个$d_x(P)$，$d_y(P)$，$d_w(P)$，$d_h(P)$都是由来自卷积网络候选框池化层的特征向量$\\phi(P)$建模成的，即有:\n$$ d_*(P)=w^T_*\\phi(P) $$其中$w_*$是模型的参数，可以通过正则化最小二乘目标（岭回归）来学习：\n$$ w_*=argmax_{\\hat{w}_*}\\sum_i^N(t_*^i-\\hat{w}_*^T\\phi(P^i))^2+\\lambda\\vert\\vert\\hat{w}_*\\vert\\vert^2 $$对于本文，回归目标$t_*$定义为：\n$$ \\begin{aligned} t_x \u0026 = (G_x-P_x)/P_w \\\\ t_y \u0026 = (G_y-P_y)/P_h \\\\ t_w \u0026 = log(G_w/P_w) \\\\ t_h \u0026 = log(G_h/P_h) \\end{aligned} $$即输入$\\phi$后，利用$(t_x,t_y,t_w,t_h)$来修正候选框位置。\n参考 本文参考自以下论文：\nRegion-based Convolutional Networks for Accurate Object Detection and Segmentation\n","permalink":"https://simplestory.github.io/posts/20190919_13243638926ee79bc7810c80a460408c/","summary":"\u003cblockquote\u003e\n\u003cp\u003e目标识别是比单一的图像识别要复杂得多，算法模型要识别出图片哪里有哪些目标并将其框出来。Ross Girshick大神提出了一种利用卷积神经网络加上支持向量机的算法模型用于目标检测。下面是对这个经典模型进行的简单解析。\u003c/p\u003e\n\u003c/blockquote\u003e","title":"RCNN"},{"content":" 首先祝所有教师节日快乐，感谢你们赠人玫瑰手留余香的奉献精神。然后时隔将近一个月，我又滚回来更新博客了，之前一直在忙着入职这件事，希望能尽快融入公司开发业务中而”忙碌“了一个月。接下来继续经典图像识别网络模型的介绍。上文我们已经介绍了AlexNet和VGG网络，这两个网络都将当时的神经网络的深度发挥到了极致，后续的神经网络模型性能基本上好不了多少。这是由于随着网络的加深，在误差后向传播时，靠前的网络层权重基本得不到更新，一直维持在某一个值附近。在2015年，何凯明大神提出了一种快捷连接方式将误差传播到了前面的网络层，很大程度上提高了网络的性能。下面简单解析一下ResNet网络。\n快捷连接 神经网络层数的增多理论上应该会得到性能更好的模型，但由于梯度消失的存在，限制了网络层数的发展。为此，何凯明大佬提出了一种网络结构单元解决了梯度无法传递到网络底层的问题，那就是快捷连接。基本结构如下图所示：\n这种结构其实是一种残差网络。其中$x$表示这一结构单元的输入，$\\mathcal{H}(x)$表示结构单元的映射函数。假设多层非线性网络结构可以拟合任何复杂函数，也即是说这种结构可以拟合残差函数$\\mathcal{H}(x)-x$，所以与其去拟合原映射函数$\\mathcal{H}(x)$不如拟合残差函数$\\mathcal{F}(x) := \\mathcal{H}(x)-x$，因为拟合原函数还是避免不了梯度消失这一现象，同时从图中可以看出实现残差函数的方式十分简单。\n在快捷连接中有一个十分重要的组成部分，就是$Identity \\ Mapping$，上图的连接单元可以表示为：$y = \\mathcal{F}(x,\\{\\mathcal{W}_i\\})+x$，其中$x$和$y$分别表示单元的输入和输出（在激活函数之前），$\\mathcal{F}(x,\\{\\mathcal{W}_i\\})$表示残差单元学习的函数映射。对于上图展开表示即是：$\\mathcal{F} = W_2\\sigma(W_1x)$，$\\sigma$表示$ReLU$激活函数，这里忽略偏差值。这种连接并没有引入额外的参数，没有增加模型的计算量。这里有个注意点，$x$和$\\mathcal{F}$的输出必须具有相同的维度，若不相同，可以通过线性修正$W_s$来同步维度：$y = \\mathcal{F}(x,\\{\\mathcal{W}_i\\})+W_sx$。\n基本结构 作者针对$ImageNet$的数据集设置了三组网络模型，以此为例来解说$ResNet$的结构\n普通的卷积神经网络 基本网络的设计大都来源于$VGG$网络，卷积层的卷积核大小基本为$3 \\times 3$并且遵循以下两个简单的设计原则：\n对于具有同样输出的网络层，都设置相同数量的卷积核； 若特征图的大小减半，设置加倍的卷积核数量以减少每一层的计算复杂度 对于具有2步幅的卷积层，作者直接对该层进行下采样操作。整体网络以全局平均池化层和1000路带$softmax$的全连接层。网络模型的权重层数为$34$。\n带残差单元的卷积神经网络 基于之前设计普通神经网络添加快捷连接来进行更改。快捷连接可以直接使用在连接前后的维度相同的情况下，而对于维度不同的情况，有以下两种处理方法：\n用零填充来扩展维度进行匹配，该方法不引入额外参数； 使用$1 \\times 1$的卷积核来扩展维度 具体结构图如下：\n其中左边为$VGG$网络（用来作参考），中间即为普通的卷积神经网络，右边为带残差单元的卷积神经网络，虚线的快捷连接表示连接过程中增加维度。\n后续的实验中作者也采用了许多$ResNet$网络的变种，具体结构如下：\nIdentity Mapping 作者在推出$ResNet$这篇论文不久后，就发了一篇关于$ResNet$网络快捷连接中$Identity \\ Mapping$数学原理的论文来进一步证实该模型的可靠性。这里进行一个大致解析。\n残差单元可以表示为如下形式：\n$$ \\begin{aligned} y_l \u0026 = h(x)+\\mathcal{F}(x_l,\\mathcal{W}_l) \\\\ x_{l+1} \u0026 = f(y_l) \\end{aligned} $$其中$x_l$，$x_{l+1}$为第$l$个残差单元的输入和输出，$\\mathcal{F}$为残差函数，$f$为$ReLU$激活函数，其中$\\mathcal{W}_l$表示如下：\n$$ \\mathcal{W}_l=\\{W_{l,k}\\vert 1\\le k\\le K\\} $$$l$是第$l$个单元的权重（包含偏差值），$K$是残差单元中的网络层数。\n为了证明$Identity \\ Mapping$选择的正确性，这里作者做了分类假设，且为了简化分析还将$f$函数进行了统一（均为$Identity \\ Mapping$），即有$x_{l+1}=y_l$。\n设$h(x_l)=x_l$，即$Identity \\ Mapping$ 由假设可以得到：\n$$ x_{l+1} = x_l+\\mathcal{F}(x_l, \\mathcal{W}_l) $$递归有：\n$$ \\begin{aligned} x_{l+2} \u0026 = x_{l+1}+\\mathcal{F}(x_{l+1},\\mathcal{W}_{l+1}) \\\\ \u0026 = x_l+\\mathcal{F}(x_l,\\mathcal{W}_l)+\\mathcal{F}(x_{l+1},\\mathcal{W}_{l+1}) \\\\ \u0026 = \\dots \\end{aligned} $$化简可得：\n$$ x_L = x_l+\\sum_{i=1}^{L-1}\\mathcal{F}(x_i,\\mathcal{W}_i) $$这形式有两个好处：\n任意深度的残差单元的特征$x_L$可由任意浅层的残差单元$l$加上残差项$\\sum_{i=1}^{L-1}\\mathcal{F}$表示，即模型在任何单元$L$和$l$之间处于残余方式； 对任意深度的残差单元$L$，式子$x_L = x_0+\\sum_{i=0}^{L-1}\\mathcal{F}(x_i,\\mathcal{W}_i)$都是对前面所有残差函数的输出进行求和，与普通网络模型的矩阵向量求积不同 （补充：普通网络模型的矩阵向量求积式为：$x_L=\\prod_{i=0}^{L-1}\\mathcal{W}_ix_0$）\n该形式对后向传播也有十分良好的性能，假设$\\epsilon$为损失函数，则有：\n$$ \\begin{aligned} \\frac{\\partial{\\epsilon}}{\\partial{x_l}} \u0026 = \\frac{\\partial{\\epsilon}}{\\partial{x_L}}\\cdot\\frac{\\partial{x_L}}{\\partial{x_l}} \\\\ \u0026 = \\frac{\\partial{\\epsilon}}{\\partial{x_L}}\\left(1+\\frac{\\partial{}}{\\partial{x_l}}\\sum_{i=1}^{L-1}\\mathcal{F}(x_i,\\mathcal{W}_i)\\right) \\end{aligned} $$其中$\\frac{\\partial{\\epsilon}}{\\partial{x_L}}$的传播不通过权重层而直接进行传播，另外一项的传播则通过权重层。对于$\\frac{\\partial{}}{\\partial{x_l}}\\sum_{i=1}^{L-1}\\mathcal{F}(x_i,\\mathcal{W}_i)$对一个$minibatch$中的样本并不会总是为-1，即不会出现梯度消失现象（因$\\frac{\\partial{\\epsilon}}{\\partial{x_l}}$不会为零）。\n设$h(x_l)=\\lambda_lx_l$，$\\lambda_l$为调制参数 同样由假设可以得到$x_{l+1}=\\lambda_lx_l+\\mathcal{F}(x_{l},\\mathcal{W}_{l})$，递归可得\n$$ x_L=(\\prod_{i=1}^{L-1}\\lambda_i)x_l+\\sum_{i=1}^{L-1}(\\prod_{j=i+1}^{L-1}\\lambda_j)\\mathcal{F}(x_i,\\{\\mathcal{W}_i\\}) $$进行化简可以得到下式：\n$$ x_L=(\\prod_{i=l}^{L-1}\\lambda_i)x_l+\\sum_{i=1}^{L-1}\\hat{\\mathcal{F}}(x_i,\\mathcal{W}_i) $$其中$\\hat{\\mathcal{F}}(x_i,\\mathcal{W}_i)$将标量包含在里面。对上式进行后向传播时有：\n$$ \\frac{\\partial{\\epsilon}}{\\partial{x_l}} = \\frac{\\partial{\\epsilon}}{\\partial{x_L}}\\left((\\prod_{i=l}^{L-1}\\lambda_i)+\\frac{\\partial{}}{\\partial{x_l}}\\sum_{i=l}^{L-1}\\hat{\\mathcal{F}}(x_i,\\mathcal{W}_i)\\right) $$当网络很深时（$L$很大），若对于所有的$i$都有$\\lambda_i \\lt 1$，则因子$\\prod_{i=l}^{L-1}\\lambda_i$会呈现出爆炸式增长；若对于所有的$i$都有$\\lambda_i \\gt 1$，则因子$\\prod_{i=l}^{L-1}\\lambda_i$会变得非常小甚至为零，这些都会阻碍网络的学习。\n激活函数的选取 上面论证了$Identity \\ Mapping$的重要性，而这是基于一个重要的假设，即激活函数$f$为$Identity \\ Mapping$，所以接下来作者对激活函数使用其它形式的情况进行了讨论。具体细节图如下：\n图中$a$是参考单元，保留了原来的结构，激活函数只有$ReLU$；$b$将$BN$层放在了相加操作之后，即激活函数包括了$BN$层和$ReLU$，经过作者实验，这种结构的表示能力不及原先结构的强，该结构中$BN$层会影响信息的传输；$c$则将$ReLU$放在了相加操作之前，激活函数就变成了$Identity \\ Mapping$，但这导致函数$\\mathcal{F}$的输出为非负的，而直观上函数$\\mathcal{F}$应该在$(+\\infty, -\\infty)$上取值，所以结果是残差单元前向传播信号会有单调递增，影响模型表达能力。\n残差单元的改进 对于原先的结构，有：\n$$ \\begin{aligned} y_l \u0026 = h(x_l)+\\mathcal{F}(x_l,\\mathcal{W}_l) \\\\ x_{l+1} \u0026 = f(y_l) \\end{aligned} $$对于下一个单元，有：\n$$ \\begin{aligned} y_{l+1} \u0026 = h(x_{l+1})+\\mathcal{F}(x_{l+1},\\mathcal{W}_{l+1}) \\\\ \u0026 = x_{l+1}+\\mathcal{F}(x_{l+1},\\mathcal{W}_{l+1}) \\\\ \u0026 = f(y_l)+\\mathcal{F}(f(y_l),\\mathcal{W}_{l+1}) \\end{aligned} $$从式中可以看出函数$x_{l+1}=f(y_l)$影响了本单元以及下一单元。\n作者提出了一种非对称结构解决了这个问题，具体结构如下：\n表示如下：\n$$ x_{l+1} = x_l+\\mathcal{F}(\\hat{f}(x_l), \\mathcal{W}_l) $$从图中也可以看出$post-activation$和$pre-activation$是等价的，数学表达式与$x_L = x_l+\\sum_{i=1}^{L-1}\\mathcal{F}(x_i,\\mathcal{W}_i)$相似，故反向传播形式也类似，即没有梯度消失或爆炸现象，同时上面结构中激活函数为$Identity \\ Mapping$形式。\n改进后激活函数的位置选取 对于改进后的残差单元激活函数位置的选取，作者考虑了两种结构，如下：\n实验表明，$e$图所示结构取得了较其它优秀的性能，主要有两方面的原因：\n激活函数为$Identity \\ Mapping$。作者在实验中发现快捷连接单元中的$h(x_l)$与激活函数$f$越接近$Identity \\ Mapping$时，模型表示能力越好； 在pre-activation中使用$BN$层提升了模型正则化能力，原先结构虽然也有$BN$层进行正则化，但之后又加上了快捷连接传递过来的值，所以整个单元的输出没有经过正则化，而pre-activation对单元的输入与输出都进行了正则化。 同时该结构易于优化，原先结构单元的激活函数$f=ReLU$在负数情况下会影响信号传播，而且这种影响会随这残差单元数量的增长而加强。改进的结构单元的激活函数采用的是$Identity \\ Mapping$，可以直接将误差传播到下一层。\n参考 本文参考自何凯明大佬在2015和2016年发布的两篇论文：\nDeep Residual Learning for Image Recognition\nIdentity Mappings in Deep Residual Networks\n","permalink":"https://simplestory.github.io/posts/20190910_cefb8aa8290a0b4cfddf3f88bf5a2a3c/","summary":"\u003cblockquote\u003e\n\u003cp\u003e首先祝所有教师节日快乐，感谢你们赠人玫瑰手留余香的奉献精神。然后时隔将近一个月，我又滚回来更新博客了，之前一直在忙着入职这件事，希望能尽快融入公司开发业务中而”忙碌“了一个月。接下来继续经典图像识别网络模型的介绍。上文我们已经介绍了AlexNet和VGG网络，这两个网络都将当时的神经网络的深度发挥到了极致，后续的神经网络模型性能基本上好不了多少。这是由于随着网络的加深，在误差后向传播时，靠前的网络层权重基本得不到更新，一直维持在某一个值附近。在2015年，何凯明大神提出了一种快捷连接方式将误差传播到了前面的网络层，很大程度上提高了网络的性能。下面简单解析一下ResNet网络。\u003c/p\u003e\n\u003c/blockquote\u003e","title":"ResNet"},{"content":" 神经网络已经有几个归一化算法了，它们长得都很相似，基本上都有统一的形式。本文参考了知乎专栏SIGAI关于归一化算法的文章，具体链接在文章底部。\n基本问题 在使用神经网络对数据集进行学习时，由于数据集的来源可能有着较大的差异，数据集不同属性的取值有时也会有很大的范围，这些都会引起数据的取舍误差，导致神经网络的学习效果不佳。为了解决这个问题，我们对数据进行归一化处理，即将有着角的取值方位的数据规范到同一尺度下，这样处理有利于网络的学习。发展到现在，归一化有了许多变种算法，常见的有：Batch Normalization(BN)、Layer Normalization(LN)、Instance Normalization(IN)以及Group Normalization(GN)，其实它们的处理方式都是类似的（减去均值，除以标准差，再进行线性映射），区别就在于计算时的纬度不一样，具体公式如下：\n$$ y = \\gamma \\left(\\frac{x-\\mu(x)}{\\sigma(x)}\\right)+\\beta $$注意这里还有一点不同，对于$BN$、$IN$和$GN$，参数$\\gamma$、$\\beta$的纬度都等于通道数$C$的向量，对于$LN$，这两个参数都是纬度等于$normalized\\ shape$的矩阵。\n为了后面说明方便，这里预先定义数据集纬度为：$x \\in R^{N \\times C \\times H \\times W}$，具体含义是数据集包含$N$个样本，每个样本通道数为$C$，高为$H$，宽为$W$。\nBatch Normalization 该方法是最早的归一化算法，效果也是不错的，可以说是最好的。计算纬度是$N \\times H \\times W$，保留通道数。具体公式如下：\n$$ \\begin{aligned} \\mu_c(x) \u0026 = \\frac{1}{NHW}\\sum_{n=1}^N\\sum_{h=1}^H\\sum_{w=1}^W x_{nchw} \\\\ \\sigma_c(x) \u0026 = \\sqrt{\\frac{1}{NHW}\\sum_{n=1}^N\\sum_{h=1}^H\\sum_{w=1}^W (x_{nchw}-\\mu_c(x))^2+\\epsilon} \\end{aligned} $$即保留通道数，将第一个样本的第一个通道加上第二个样本的第一个通道这样接下去知道第$N$个样本的第一个通道，在除以$N\\times H \\times W$得到一个数就是第一通道的平均数（而不是一个$H\\times W$的矩阵），求标准差时也是一样的。\n$BN$有一个缺点，就是需要大量的数据才能合理地估计出训练数据的均值和方差，这很有可能会导致内存不够，而且对于$RNN$模型长短不一的训练数据长度，$BN$也很难应用。\n这里注意一点，当在网络中采用了BN层时，就不必要再使用Dropout层了\nLayer Normalization $LN$有一个很大的优势，就是它不需要批训练，针对单个样本数据就能进行归一化。计算纬度是$C \\times H \\times W$，保留样本数。具体公式如下：\n$$ \\begin{aligned} \\mu_n(x) \u0026 = \\frac{1}{CHW}\\sum_{c=1}^C\\sum_{h=1}^H\\sum_{w=1}^W x_{nchw} \\\\ \\sigma_n(x) \u0026 = \\sqrt{\\frac{1}{CHW}\\sum_{c=1}^C\\sum_{h=1}^H\\sum_{w=1}^W (x_{nchw}-\\mu_n(x))^2+\\epsilon} \\end{aligned} $$即保留样本数，将样本的通道数、高和宽对应的数据相加再除以$C \\times H \\times W$得到的即为该样本的平均值，标准差同理。\nInstance Normalization 在生成模型中，数据集中各个通道数的均值和标准差会影响到最终生成图片的风格，所以$IN$的作者认为可以先把图像在通道层面上进行归一化，然后再用目标风格图片的均值和标准差去归一化，这样来获得目标图片的风格。$IN$也不需要批训练，针对单个样本就能归一化。计算纬度是$H \\times W$，保留样本数和通道数。具体公式如下：\n$$ \\begin{aligned} \\mu_{nc}(x) \u0026 = \\frac{1}{HW}\\sum_{h=1}^H\\sum_{w=1}^W x_{nchw} \\\\ \\sigma_{nc}(x) \u0026 = \\sqrt{\\frac{1}{HW}\\sum_{h}^H\\sum_{w=1}^W (x_{nchw}-\\mu_{nc}(x))^2+\\epsilon} \\end{aligned} $$即保留样本数和通道数，对单一样本的单个通道求高和宽对应数据的和再除以$H \\times W$即为所求均值。\nGroup Normalization $GN$主要应用于像图像分割这类占用显存较大的学习任务。这类任务由于受到显存大小的限制，所以设置的$batchsize$通常为个位数，$BN$根本没能发挥效果。$GN$是$LN$和$IN$的折中，也是对单一样本可以进行归一化。它将通道分为$G$组，每组有$C/G$个通道，然后再对各组中的元素求均值和标准差，并独立地用所求得的参数对对应组的通道进行归一化。具体计算公式如下：\n$$ \\begin{aligned} \\mu_{ng}(x) \u0026 = \\frac{1}{(C/G)HW}\\sum_{c=gC/G}^{(g+1)C/G}\\sum_{h=1}^H\\sum_{w=1}^W x_{nchw} \\\\ \\sigma_{ng}(x) \u0026 = \\sqrt{\\frac{1}{(C/G)HW}\\sum_{c=gC/G}^{(g+1)C/G}\\sum_{h=1}^H\\sum_{w=1}^W (x_{nchw}-\\mu_{ng}(x))^2+\\epsilon} \\end{aligned} $$参考 本文大部分参考自下面这篇文章：\n知乎专栏SIGAI（作者：伊相楠）\n","permalink":"https://simplestory.github.io/posts/20190811_58bd041ca5bf7b009cdc8d07392dc061/","summary":"\u003cblockquote\u003e\n\u003cp\u003e神经网络已经有几个归一化算法了，它们长得都很相似，基本上都有统一的形式。本文参考了知乎专栏SIGAI关于归一化算法的文章，具体链接在文章底部。\u003c/p\u003e\n\u003c/blockquote\u003e","title":"区分几种归一化算法"},{"content":" VGG(Visual geometry group)是继AlexNet后的又一个经典卷积神经网络模型。它在2014年的ILSVRC比赛中获得了第二名（第一名是GoogleLeNet模型），在工作和个人使用中应用较多的是VGG16和VGG19，这里16和19表示的是神经网络的层数。这里接上文提到的AlexNet网络模型对VGG模型进行解析。\n基础架构 模型训练输入图像为$224 \\times 224$的RGB图像，唯一的预处理为对训练集的每个像素减去训练集上的平均RGB值。卷积层选用的核大小是$3 \\times 3$，步长为1，部分卷积核采用$1 \\times 1$尺寸；最大池化层的核大小为$2 \\times 2$，步长为2。最后连接的3个全连接层中前两个有4096个通道，再接一个$softmax$层；隐藏层激活函数采用$ReLU$，不采用AlexNet中使用的LRN局部响应规范化。具体结构如下表：\n上面的网络中都遵循前文提到的基础架构，唯一不同的是网络的深度（在表中都有体现）。网络层的宽度的话，从第一层开始是$64 \\times 64$，之后在每一个最大池化层之后的卷积层都会加倍直至达到$512 \\times 512$。下面一张表展示的是每个网络模型的参数数量。\n一些技巧 在构建网络模型中，作者全部采用了小型的$3 \\times 3$卷积核，步长为1，两个$3 \\times 3$的卷积层堆叠（中间没有池化操作）有着等同于$5 \\times 5$卷积层的感受能力，同理，三个$3 \\times 3$ 的卷积层堆叠等同于一个$7 \\times 7$的卷积层。\n使用小卷积核的卷积层堆叠代替大卷积核的卷积层的优势主要有以下几个：\n使用多个非线性整流层而不是一个，这增强了模型的表达能力； 降低了模型的参数规模 这种操作也可以视为是对原卷积层的正规化，迫使它们通过小型卷积核进行分解，同时引入非线性因素，整流函数也引入了额外的非线性。\n训练过程中采用的是具有小批量梯度下降优化多项逻辑回归目标的方法来进行优化（动量参数为0.9，batch size为256）；使用权重衰减的正则化操作，其中$L_2$正则化系数为$5 \\times 10^{-4}$；在前两个全连接层中使用Dropout正则化（Dropout层的参数设置为0.5）；学习率初始化为$10^{-2}$，并同AlexNet模型类似：当验证集的得分不再提升时，学习率会发生一次下调，下调三次或者模型训练迭代次数达到370K（即满74轮）时则停止训练。\n作者在经过尝试训练后，采用均值为0，方差为0.01的正态分布对权重进行初始化，偏差初始化为0。为了适应$224 \\times 224$的图像输入，作者随机地从每一轮每张已重新调整过的图像中进行裁剪，同时对图片进行随机水平翻转和随机RGB色移。\n训练图像尺寸处理 假设$S$为各向同性重新缩放的训练图像的最小边（这个参数也指定为训练图像的尺度）。原则上$S$须取得不小于224的值（因图像输入尺寸为$224 \\times 224$），当$S = 224$，此时裁剪块可以取遍所有像素值；当$S \u003e\u003e 224$时，裁剪块对应于原图像的一小块区域，可能包括小目标或目标的一部分。作者提出了两种方法来设置训练尺度$S$。首先是固定尺度训练，对应于单一规模的训练；其次是多尺度训练，通过从特定范围$[S_{min}, S_{max}]$随机采样$S$来单独地重新调整每个训练图像（作者使用的范围是$[256, 512]$)。由于不同的目标有着不同的尺寸，所以这种处理方式能更好地捕捉到目标，同时这种方法也可视为是通过尺度抖动对训练集进行增强。\n测试集处理 假设测试集尺度为$Q$（作者实验发想测试集尺度不必与训练集尺度相同）。将模型的完全连接层转换为卷积层（第一个FC层转为$7 \\times 7$的卷积层，后面两个FC层转为$1 \\times 1$卷积层），更改后的模型直接应用于整个为裁剪图像中（包括对图像进行的增强处理），得到的结果是类别得分图（其中通道数与类别数相同），然后对得到的类别得分图进行空间平均（求和）以得到固定大小的图像的类别得分向量，最后将原始图像和翻转图像的$softmax$类后验平均化以获得图像的最终分数。\n由于全卷积网络是应用于整个图像的，所以在测试阶段并不需要进行多尺度裁剪。但多尺度裁剪评估可以是对测试集密集评估的补充，因为当卷积网络应用于裁剪时，卷积特征图会使用0填充，而对测试集的密集评估应用裁剪时会使用邻近的像素值来填充特征图（这是由卷积核池化操作造成的），这会获得更多的上下文信息。\n具体的模型和实验结果可以查看原论文\n参考 本文参考自Karen Simonyan等人于2015年发表的论文：\nVERY DEEP CONVOLUTIONAL NETWORKS FOR LARGE-SCALE IMAGE RECOGNITION\n","permalink":"https://simplestory.github.io/posts/20190730_1036fd1af9bc59d1a811b4e2ab23d60c/","summary":"\u003cblockquote\u003e\n\u003cp\u003eVGG(Visual geometry group)是继AlexNet后的又一个经典卷积神经网络模型。它在2014年的ILSVRC比赛中获得了第二名（第一名是GoogleLeNet模型），在工作和个人使用中应用较多的是VGG16和VGG19，这里16和19表示的是神经网络的层数。这里接上文提到的AlexNet网络模型对VGG模型进行解析。\u003c/p\u003e\n\u003c/blockquote\u003e","title":"VGG"},{"content":" 如今的深度神经网络已经具有了完备的能力，特别是在视觉领域已经接近甚至超过了人类的视觉能力，而在这些深度学习网络的背后都含有着一个基本框架，即卷积神经网络（CNN）。CNN的主要应用范围是在计算机视觉领域，这里我简单的介绍一下CNN中的经典模型AlexNet。AlexNet是由Alex Krizhevsky等人在2012年提出的，虽然现在这个模型已经比较少见了（大多数都使用resnet什么的），这个在当时的目标检测领域有着很好的表现，也为后续的其它CNN框架奠定了基础。\n介绍 为了从几百万张图片中学会识别几千个目标对象，Krizhevsky等人构建了一个具有强学习能力的模型，同时考虑到在某些目标识别任务上巨大的复杂性并不能通过增大数据集来解决，而是需要一定量的先验知识来补充模型。卷积神经网络模型的能力可以通过深度和宽度来控制，同时它也能准确捕捉到统计的平稳性和像素区域的局部性这两个自然图像规律，所以用CNN进行图像识别是个不错的选择。\n基本框架 不多bb，直接上截图：\n这个框架包含了8个学习层（5个卷积层和3个全连接层），由于受到GPU显存的限制，作者在两个GPU上运行，最后再将运行数据合并再一起，这实际上就是分组卷积的操作。模型中第二、四、五卷积层中的核只跟在同一侧GPU上的上一卷积层有联系，第三层卷积层连接了第二层卷积层的所有核。响应标准化层在第一以及第二卷积层之后。最大池化层位于响应标准层和第五层之后，ReLU激活函数应用于每一个卷积层和全连接层中。模型的目标为最大化多项逻辑回归目标函数。接下来是对模型使用的一些特殊技巧进行说明。\n激活函数 在2012年，神经网络的激活函数使用最多的是$f(x) = tanh(x)$或者$f(x) = (1+e^{-x})^{-1}$，而作者采用的是现在使用十分广泛的ReLU激活函数（$f(x) = max(0,x)$)，这个是不饱和非线性函数，它的下降速度快于之前两个饱和非线性函数，这让深度卷积神经网络在ReLU激活函数下的工作效率有了几倍的提升。\n不饱和非线性函数：非线性即是平常所说的非线性，而不饱和是指当变量x趋于无穷是，函数的取值也趋于无穷。\n局部响应标准化 ReLUs有一个非常好的属性，即它不需要输入规范化以防止它们饱和。但作者仍然使用了如下的归一化：\n$$ b^i_{x,y} = \\frac{a^i_{x,y}}{\\left(k+\\alpha\\sum^{min(N-1,i+n/2)}_{j=max(0,i-n/2)}{(\\alpha^j_{x,y})^2}\\right)^{\\beta}} $$其中$b^i_{x,y}$表示响应的标准化神经元激活程度，$\\alpha^i_{x,y}$为在位置$(x,y)$处应用核$i$和ReLU非线性激活函数的神经元的激活程度，$N$是层中的总核数，求和操作遍历了同一空间位置上的核映射。核映射的顺序是随意的，在训练开始时才决定，这种方式实现了一种横向抑制，即在使用不同内核计算的神经元输出之间创建大活动的竞争。\n关于超参数的选用，作者选择的超参数为：$k=2$，$n=5$，$\\alpha=10^{-4}$以及$\\beta=0.75$。\n重叠池化 CNN中的池化层是对同一内核映射相邻组神经元的输出进行统计总结的操作。通常的池化操作通过相邻的池化单元来完成操作具体，来说池化层可以看作是由间隔S个像素的合并单元格组成，合并单元格统计了以该单元格为中心的$Z \\times Z$邻域。如果令$S = Z$，我们就得到了传统的卷积神经网络池化操作；对于$S \u003c Z$，我们可以得到重叠池化操作。作者选用的参数为$S = 2$，$Z = 3$。具有重叠池化的模型在训练期间过度拟合会稍微困难一些。\n降低过拟合 数据增强 降低过拟合的简便的常用操作就是进行数据增强以增加数据集。在论文里，作者采用的增强手法主要是图像平移水平反射和改变训练图像中RGB通道的强度。\n首先是图像平移水平反射，作者随机地从$256 \\times 256$的图像中提取出$224 \\times 224$的块以及对应的水平反射块。然后再用这些数据对模型进行训练。在预测阶段，模型使用五个$224 \\times 224$的数据块（四个角落和一个中心）以及它们的水平反射块，即一共10个数据块进行预测，最后将预测结果通过$softmax$层进行统计得到最终预测结果。\n对于改变训练图像中的RGB通道的强度这一方法，作者将$PCA$算法应用于图像训练集的RGB通道像素值集上。对于每个训练图像，我们添加多个找到的主成分，其幅值与相应的特征值成正比乘以从均值为0，标准差为0.1的高斯图形中取得的随机变量值。所以每个RGB像素值$I_{xy} = [I^R_{xy},I^G_{xy},I^B_{xy}]$，添加了以下的权值：\n$$ [\\mathbf{p}_1,\\mathbf{p}_2,\\mathbf{p}_3][\\alpha_1\\lambda_1,\\alpha_2\\lambda_2,\\alpha_3\\lambda_3]^T $$其中$\\mathbf{p}_i$和$\\lambda_i$是$3 \\times 3$的RGB像素值协方差矩阵第$i$个特征向量和特征值，$\\alpha_i$是上面提到的随机变量，对于特定训练图像的所有像素，每个$\\alpha_i$的值仅取一次，直到该图像被重新用于训练，此时$\\alpha_i$将被会再去一次值。这种方法大致的捕捉到了自然图像的属性，即对象身份对于照明的强度和颜色的变化是不变的。\nDropout 以一定的概率（作者选用0.5）将某个隐藏层神经元的输出设置为0，此时被设置为0的神经元对前向传播没有贡献同时也不会受到误差反向传播的影响，即神经结构发生了变化。这项技术减少了神经元复杂的共同适应，因为神经元不能依赖于特定的其他神经元。作者选用的Dropout概率0.5是由指数多个dropout网络产生的预测分布的几何平均值。dropout操作大概使得模型收敛所需的迭代次数加倍。\n一些训练细节 优化方法为SGD，其中batch size为128个，动量参数设置为0.9并且权值衰减参数设置为0.0005。作者发现少量的权值衰减对模型的学习很重要。对于权值的更新如下：\n$$ \\begin{aligned} \\mathbb{v}_{i+1} \u0026 := 0.9\\cdot\\mathbb{v}_i-0.0005\\cdot\\epsilon\\cdot\\mathbb{\\omega}_i-\\epsilon\\cdot\\left\\langle\\frac{\\partial{L}}{\\partial{\\mathbb{\\omega}}}\\vert_{\\mathbb{\\omega}_i}\\right\\rangle_{D_i} \\\\ \\mathbb{\\omega}_{i+1} \u0026 := \\mathbb{\\omega}_i+\\mathbb{v}_{i+1} \\end{aligned} $$其中第一个等式的最后一项偏导数取值为模型在第$i$个批次中$D_i$的损失对$w$的偏导数在$w_i$处的值的平均值，$i$是迭代次数索引，$v$是动量变量，$\\epsilon$是学习率。\n使用均值为0，标准差为0.01的高斯分布对模型权值进行初始化，初始化模型第二、四、五卷积层以及隐藏层的全连接层的偏差值为常数1，这会通过向ReLU提供正输入来加速学习的早期阶段，剩余的神经元偏差值设置为0。对于所有层，作者使用了相同的学习率(0.01)并在训练中进行调整，当在当前的学习率下验证集的错误率不再下降时，将学习率除以10，学习率下调三次后则停止训练。\n总之，AlexNet这个网络模型在2012年的ImageNet竞赛上取得了相当不错的成绩，其中Top-5错误率降低到了$15.3\\%$。这个模型可以说是入门深度学习视觉领域的一个非常经典的模型，里面用到的许多技巧在后续的模型发展中也有很好的应用。\n参考 本文参考自Alex Krizhevsky等人于2012年发表的论文：\nImageNet Classification with Deep Convolutional Neural Networks\nAlexNet Stanford Vision Lab\n","permalink":"https://simplestory.github.io/posts/20190722_cdebdc3cc4ecd47a04cff48d03a688d3/","summary":"\u003cblockquote\u003e\n\u003cp\u003e如今的深度神经网络已经具有了完备的能力，特别是在视觉领域已经接近甚至超过了人类的视觉能力，而在这些深度学习网络的背后都含有着一个基本框架，即卷积神经网络（CNN）。CNN的主要应用范围是在计算机视觉领域，这里我简单的介绍一下CNN中的经典模型AlexNet。AlexNet是由Alex Krizhevsky等人在2012年提出的，虽然现在这个模型已经比较少见了（大多数都使用resnet什么的），这个在当时的目标检测领域有着很好的表现，也为后续的其它CNN框架奠定了基础。\u003c/p\u003e\n\u003c/blockquote\u003e","title":"AlexNet"},{"content":" 上文提到了一个高效的树集成算法Xgboost模型，但是在处理特征维度和数据量较大的数据时，效率仍然是低迷的。相比于GBDT和Xgboost等模型，微软开源的Lightgbm模型采用了各种手段来降低数据量和数据特征维度，从而大大提高了模型的训练速度也确保了准确率。\nHow to do 为了降低因大量的实例数和较高的特征维度对模型训练的影响，一个自然的想法就是减少数据实例数和特征维度。而Lightgbm模型采取的方案分别为：Gradient-based One-Side Sampling(GOSS)和Exclusive Feature Bundling(EFB)。\nGOSS 这其实是一种采样方法。微软在实验中发现如果一个实例有着较小的梯度，那么它对模型训练的贡献也校少，因为小梯度意味着这个样本已经训练完全。GOSS通过保存大梯度样本，随机选取小梯度样本，并为其弥补上一个常数权重。这样，GOSS更关注训练不足的样本，同时也不会过多地改变原始分布。相关算法如下：\n具体操作是GOSS首先根据样本梯度的绝对值来排序（从大到小），并选取前面的$a \\times 100\\%$个样本。然后随机从剩下的样本中选取$b \\times 100\\%$个样本。再之后，GOSS对每个小梯度样本通过一个常数权重（通常为$\\frac{1-a}{b}$）来放大。\nEFB 接下来就是降低特征维度的问题了。其实我们可以发现具有高维度特征的数据通常都是离散的，而且在离散的特征空间中，许多特征都是相互独立的，所以我们可以将这些独立的特征捆绑在一起作为一个单独的特征。具体算法如下：\n首先我们建立一张带权图，其中的边的权值表示连接的两个特征之间的冲突量（即不相互独立的程度）。然后按照度对特征进行降序排序。最后遍历排序后的特征序列，检查特征是否能加入已有的捆绑中（由冲突量确定），否则新建一个捆绑。\n上面这种做法的确有效，但在大特征量的情况下依然不尽人意。我们可以根据特征的非零值对特征进行排序从而得到特征序列，这样就不用去创建特征图了。\n以上已将特征放在捆绑中，接下来就是合并同一捆绑中的特征。这一步的关键在于原始特征值可以从捆绑中区分出来。因为基于直方图的算法存储离散的条形而不是连续的特征值。这一点可以通过偏移量来实现。具体如下：\n详细的数学推导见下方论文\n参考 LightGBM: A Highly Efficient Gradient Boosting Decision Tree\n","permalink":"https://simplestory.github.io/posts/20190324_161af8745042398050ad585874c3eec3/","summary":"\u003cblockquote\u003e\n\u003cp\u003e上文提到了一个高效的树集成算法\u003ca href=\"https://simplestory.github.io/2019/03/17/Xgboost/\"\u003eXgboost模型\u003c/a\u003e，但是在处理特征维度和数据量较大的数据时，效率仍然是低迷的。相比于GBDT和Xgboost等模型，微软开源的Lightgbm模型采用了各种手段来降低数据量和数据特征维度，从而大大提高了模型的训练速度也确保了准确率。\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch2 id=\"how-to-do\"\u003eHow to do\u003c/h2\u003e\n\u003cp\u003e为了降低因大量的实例数和较高的特征维度对模型训练的影响，一个自然的想法就是减少数据实例数和特征维度。而Lightgbm模型采取的方案分别为：Gradient-based One-Side Sampling(GOSS)和Exclusive Feature Bundling(EFB)。\u003c/p\u003e","title":"LightGBM模型"},{"content":" 今天想起好久都没更新过我的博客了，最近都在忙着笔试面试之类的，还要毕业设计。刚好今天告一段落了，索性整理一下前几天复习的Xgboost模型，发一篇博文。\n树的集成 集成算法在机器学习中是一个很广泛，很重要的算法，一般分为Bagging和Boosting两种。决策树的集成自然是一个惯用手段。对于树的集成，Bagging方法的代表是随机森林(Random Forest)，它将属性和数据随机分组进行决策树学习，最后再统计结果；Boosting方法的代表是AdaBoost、GBDT(梯度提升决策树)。这几个算法都挺重要的，不是一两句话能概括得了的，所以就不展开讲了。而在树的Boosting中，陈天奇大佬提出了一个新模型Xgboost。这个模型利用了目标函数的二阶导，同时加入了正则化项预防模型过拟合。以下就是关于Xgboost模型的解读。\n目标函数 集成树模型的结果是由许多单体树模型的结果来生成的。Xgboost选择的单体树模型就是CART。CART模型与其它决策树树模型有点不同，它叶子的值是一个确定的分数而非实际的类别，这有利于实现高效率的优化算法。利用一堆CART树来作预测，我们可以简单地将各个CART模型的结果相加。于是可得到如下函数：\n$$ \\hat{y}_i = \\Phi(\\mathbf{x}_i) = \\sum_{k=1}^K f_k(\\mathbf{x}_i), \\ f_k \\in F \\tag{1} $$其中给定的数据集有n条数据，m个特征。 $D=\\{(\\mathbf{x}_i,y_i)\\}(\\vert{D}\\vert=n,\\mathbf{x}_i\\in R^m,y_i\\in R)$ 并且使用加性算法。\n$F=\\{f(\\mathbf{x})=w_{q(\\mathbf{x})}\\}(q:R^m\\rightarrow T.\\ w\\in R^T)$ 是整个可能的CART树空间，$f(x)$为单个CART树模型。$T$为树的叶子节点数，$q(x)$将样本映射到1到T的某个值，即分到某个叶子节点，其实就是代表了棵树的结构， $w_{q(x)}$就是这棵树对样本的预测值了。\n之后优化的目标函数可以定义为：\n$$ L(\\Phi) = \\sum_i l(\\hat{y}_i, y_i)+\\sum_k \\Omega(f_k) \\tag{2} $$其中$L(\\hat{y}_i,y_i)$为损失函数，后一项为正则化项，表示了树的复杂度：\n$$ \\Omega(f) = \\gamma T+\\frac{1}{2}\\lambda{\\Vert{w}\\Vert}^2 \\tag{3} $$通常损失函数选用均方误差(MSE)，这会得到一个非常漂亮的形式：\n$$ L^{(t)} = \\sum_{i=1}^n [2(\\hat{y}^{(t-1)}_i-y_i)f_t(x_i)+{f_t(x_i)}^2]+\\Omega(f_t)+const $$对于其它损失函数（只要保证它二阶导存在），使用泰勒公式将$L^{(t)}(y_i,\\hat{y}^{(t-1)}_i+f_t(x_i))$展开至二阶，其中令$x=\\hat{y}_i^{(t-1)}+f_t(x_i), \\ x_0=\\hat{y}_i^{(t-1)}$，有：\n$$ \\begin{aligned} L^{(t)}(y_i,x) \u0026 \\simeq l(y_i,x_0)+l^\\prime(y_i,x_0)\\cdot(x-x_0)+\\frac{1}{2!}l^{\\prime\\prime}(y_i,x_0)\\cdot{(x-x_0)}^2+\\Omega(f)+const \\\\ \u0026 = l(y_i,x_0)+\\frac{\\partial l(y_i,x_0)}{\\partial x_0}f_t(x_i)+\\frac{1}{2}\\frac{\\partial^2 l(y_i,x_0)}{\\partial^2 x_0}f_t^2(x_i)+\\Omega(f)+const \\end{aligned} \\tag{4} $$可令$g_i = \\frac{\\partial l(y_i,x_0)}{\\partial x_0}$,$h_i = \\frac{\\partial^2 l(h_i, x_0)}{\\partial^2 x_0}$，同时忽略常数项，得下式：\n$$ \\tilde{L}^{(t)} = \\sum_{i=1}^n[g_if_t(x_i)+\\frac{1}{2}h_if_i^2(x_i)]+\\Omega(f_t) \\tag{5} $$定义 $I_j=\\\\{i\\vert q(\\mathbf{x}_i)=j\\\\}$ 为归类到叶子j上的训练样本序号的集合。结合式子(3)、(5)可得：\n$$ \\begin{aligned} \\tilde{L}^{(t)} \u0026 = \\sum_{i=1}^n[g_if_t(x_i)+\\frac{1}{2}h_if_i^2(x_i)]+\\gamma T+\\frac{1}{2}\\lambda \\sum_{j=1}^T{w_j}^2 \\\\ \u0026 = \\sum_{j=1}^T[(\\sum_{i \\in I_j}g_i)w_i+\\frac{1}{2}(\\sum_{i \\in I_j}h_i+\\lambda){w_j}^2]+\\gamma T \\end{aligned} \\tag{6} $$对于已确定的树结构$q(x)$，可以通过下式计算叶子节点j的最佳权重$w$：\n$$ w_j^* = -\\frac{\\sum_{i \\in I_j}g_i}{\\sum_{i \\in I_j}h_i+\\lambda} \\tag{7} $$对应的损失为：\n$$ \\tilde{L}^{(t)}(q) = -\\frac{1}{2}\\sum_{j=1}^{T}\\frac{(\\sum_{i \\in I_j}g_i)^2}{\\sum_{i \\in I_j}h_i+\\lambda}+\\gamma T \\tag{8} $$式(8)可用于评估树模型的好坏，其值越小越好。 该值仅用来衡量树结构的好坏，与叶子节点的值无关。 这从推导过程可以看出，$\\tilde{L}^{(t)}(q)$只与$g_i$和$h_i$有关，而这两个值只和树的结构有关，与叶子节点的值毫无关系。\n树的生成 显然，CART树有无数种结构，生成树后再评估结构好坏并不现实。这里有一种贪婪算法采用按层生成树。即从根节点开始，逐层分枝。假设$I_L$和$I_R$分别为分枝后左右节点包含的实例，且$I = I_L \\bigcup I_R$，则是否分枝的衡量标准为：\n$$ L_{split} = \\frac{1}{2}\\left[\\frac{(\\sum_{i \\in I_L}g_i)^2}{\\sum_{i \\in I_L}h_i+\\lambda}+\\frac{(\\sum_{i \\in I_R}g_i)^2}{\\sum_{i \\in I_R}h_i+\\lambda}-\\frac{(\\sum_{i \\in I}g_i)^2}{\\sum_{i \\in I}h_i+\\lambda}\\right]-\\gamma \\tag{9} $$$L_{split}$的值为正且越大，则越有分枝的价值，因为分枝后的$\\tilde{L}^{(t)}(q)$会变得越小。其实，$\\gamma$为阈值，该值越大，则分枝的标准越严格。\n注意： Xgboost不需要进行单独的剪枝操作，因为在生成树的时候，树模型的复杂度已经纳入考虑，对应的即是参数$\\gamma$。\n基于贪婪算法寻找最佳分点的算法模型如下：\n贪婪算法寻找分点是一种十分有效的算法，但是对于无法一次性装入内存的大型数据集来说，这种方法便失效了。这时候就需要使用近似方法寻找分点。\n近似方法实际上就是根据特征分布的百分位数，提出候选划分点。之后将连续型特征映射到候选点划分的分桶中，具体如下：\n其中，Global为学习每棵树前给出候选分点；Local为每次分裂前给出候选分点\n加权分量草图 在上述的近似算法分枝中，重要的一步就是候选点的选取。假设集合$D_k=\\\\{(x_{1k},h_1),(x_{2k},h_2)\\cdots(x_{nk},h_n)\\\\}$表示第k个特征的值及其二阶导的统计。定义一个分值函数$r_k: \\ R \\rightarrow [0,+\\infty)$:\n$$ r_k(z) = \\frac{1}{\\sum_{(x,h) \\in D_k}h}\\sum_{(x,h) \\in D_k, x \\lt z}h \\tag{10} $$这代表了样本中对应特征值小于z的实例所占比例。我们的目标是找到最佳分点$\\\\{s_{k1},s_{k2},\\cdots,s_{kl}\\\\}$，可采用下式：\n$$ \\vert r_k(s_{k,j})-r_k(s_{k,j+1})\\vert \\lt \\epsilon, \\ s_{k1}=min_i x_{ik}, s_{kl}=max_i x_{ik} \\tag{11} $$这里$\\epsilon$是一个逼近因素，可以认为有大约$\\frac{1}{\\epsilon}$个候选分点。这里每个数据都有权重$h_i$。\n稀疏数据分点搜寻 这一部分主要是处理样本缺失值问题的。方法就是分别考虑将缺失值划分到左右分枝，选取增益最大的一侧，具体如下：\n缓存访问模式 为了加快模型运算，可以采用缓存访问模式，具体如下：\n针对贪婪搜索分点算法：采用缓存感知预取算法减缓内存访问问题。即为每一线程申请一个内部缓存，里面存放梯度统计信息，并以小批量的方式执行累积。 针对近似方法搜寻分点算法：采用合适的块大小来减缓内存访问问题。块过小会导致每个线程工作量小，效率低；块过大会导致缓存未命中，通常设置为$2^{16} \\ examples \\ per \\ block$。 Note\nXgboost防止过拟合的手段：\n添加正则化项，即目标函数中的$\\Omega(f)$ Shinkage(缩减)，在完成一次迭代后，会将将叶子节点上的权重乘上该系数，主要是削减每棵树的影响，让后面有更大的学习空间 Column subsampling(列抽样)，类似于随机森林，支持特征抽样，防止过拟合的同时降低运算量 参考 Introduction to Boosted Trees\nXGBoost: A Scalable Tree Boosting System\n","permalink":"https://simplestory.github.io/posts/20190317_8fc39e31f30337e40756914df1f8b607/","summary":"\u003cblockquote\u003e\n\u003cp\u003e今天想起好久都没更新过我的博客了，最近都在忙着笔试面试之类的，还要毕业设计。刚好今天告一段落了，索性整理一下前几天复习的Xgboost模型，发一篇博文。\u003c/p\u003e\n\u003c/blockquote\u003e","title":"Xgboost模型"},{"content":" 最优化是一个十分困难的问题，通常要精心设计一个目标函数及相关约束，同时还要确保所要优化的问题为凸函数，但现实中的问题大多为非凸函数，针对非凸优化问题，这里有一些基本的优化方法：SGD、Momentum、AdaGrad、RMSProp及Adam等\n随机梯度下降(Stochastic Gradient Descent, SGD) // 随机梯度下降\n给定数据集$X=\\\\{x^{(1)},x^{(2)},...,x^{(n)}\\\\}$, 数据集标记$Y=\\\\{y^{(1)}, y^{(2)},...,y^{(n)}\\\\}$\n学习器$f(x;w)$, 学习率（步长）$\\alpha$\nFor 迭代足够多次\n{\n随机选择数据： $\\\\{x^{(j)}, y^{(j)}\\\\}$\n计算损失梯度： $\\nabla\\mathbf{w}=\\frac{\\partial{L(y^{(j)},f(x^{(j)};w))}}{\\partial{w}}$\n修改权重： $\\mathbf{w_i}=\\mathbf{w_i}-\\alpha\\nabla\\mathbf{w}$\n}\n以上是从数据集中随机选择一条数据进行梯度下降，故在训练过程中会出现很强的随机现象，为了避免这种随机性，可以选择多条数据进行梯度计算，之后再取平均值。\n对于学习率，在上面的算法中，学习率一直保持不变，这在训练后期会造成算法在最优解附近振荡的现象，为了消除或缓解这种情况，我们可以在靠近最优解周围时尽可能地减小学习率\n综合以上可得以下梯度下降算法\n// 学习率衰减最小批量梯度下降\n给定数据集$X=\\\\{x^{(1)},x^{(2)},...,x^{(n)}\\\\}$, 数据集标记$Y=\\\\{y^{(1)}, y^{(2)},...,y^{(n)}\\\\}$\n随机采样$m$条数据,训练周期$k$，学习率衰减最低值$b$,学习器$f(x;w)$\n初始学习率（步长）$\\alpha_0$\nFor $i \u003c= k$\n{\n随机采样$m$条数据： $\\\\{(x^{(i)},y^{(i)}),...,(x^{(m)},y^{(m)})\\\\}$\n计算采样数据平均损失值梯度： $\\nabla \\mathbf{w} = \\frac{1}{m}\\sum^m_{j=1}\\frac{\\partial{L(y^{(j)},f(x^{(j)};w)})}{\\partial{\\mathbf{w}}}$\n计算衰减学习率： $a_i=(i-\\frac{i}{k})a_0+\\frac{i}{k}b$\n修改网络权重： $\\mathbf{w}_i=\\mathbf{w}_i-\\alpha\\nabla\\mathbf{w}$\n}\n动量学习法(Momentum) 上面的算法虽然解决了在最优解附近振荡以及随机性的问题，但却不能保证算法找到的解为全局最优解，算法随后有可能找到的是局部最优解，即为鞍点。\n为了跳出鞍点，可以借助物理上的概念——动量，冲出鞍点，故有以下动量随机梯度下降法\n// 动量学习法\n给定数据集$X=\\\\{x^{(1)},x^{(2)},...,x^{(n)}\\\\}$, 数据集标记$Y=\\\\{y^{(1)}, y^{(2)},...,y^{(n)}\\\\}$\n初始速度$v$,随机采样数据大小$m$，训练周期$k$\n学习器$f(x;w)$,初始学习率$\\alpha$,初始动量参数$\\beta$\nFor $i \u003c= k$\n{\n随机采样$m$条数据： $\\\\{(x^{(i)},y^{(i)}),...,(x^{(m)},y^{(m)})\\\\}$\n计算采样数据平均损失值梯度： $\\nabla \\mathbf{w} = \\frac{1}{m}\\sum^m_{j=1}\\frac{\\partial{L(y^{(j)},f(x^{(j)};w)})}{\\partial{\\mathbf{w}}}$\n更新速度： $v=\\beta v-\\alpha\\nabla\\mathbf{w}$\n更新参数： $\\mathbf{w}=\\mathbf{w}+v$\n}\n在实践中，常用的$\\beta$取值可为0.5，0.9或0.99，根据具体问题进行调试，但对于$\\beta$的调整，并没有$\\alpha$的调整重要，因此不太需要作为超参数进行选择，一般取值适当即可。动量学习法在训练后期会出现轻微振荡。\nNAG(Nesterov accelerated gradient) 为了解决动量学习法会出现轻微振荡的问题，NAG用$\\mathbf{w}-\\beta v_{t-1}$近似作为参数下一步会变成的值，故计算梯度时是在未来的位置上，在稀疏数据上表现很好。更新速度表示如下：\n$$ v_t = \\beta v_{t-1}+\\alpha\\nabla_\\mathbf{w} J(\\mathbf{w}-\\beta v_{t-1}) $$其中$J(\\cdot)$为损失函数。NAG在梯度方向将要改变时，可提前预知，从而减弱这个过程，减少无用迭代。\nAdaGrad、Adelta和RMSProp 以上的算法都使用一个全局学习率，所有的参数都是统一步伐整齐向前，但网络中的参数可能不会同时有一致的下降梯度和方向，以下算法针对每个参数单独配置学习率\nAdaGrad 该算法其实就是将每一维各自的历史梯度的平方叠加起来，然后在更新的时候除以该历史梯度即可（使用平方的原因是去除梯度的符号，只对梯度的量进行累加）\n$$cache_i = cache_i+(\\nabla\\mathbf{w}_i)^2$$更新参数：\n$$\\mathbf{w}_i=\\mathbf{w}_i-\\frac{\\alpha}{\\sqrt{cache_i}+\\delta}\\nabla\\mathbf{w}_i$$其中$\\delta=10^{-7}$, 防止除零导致数值溢出\nAdaGrad使得参数在累积的梯度量较小时(\u0026lt;1)，放大学习率，使得网络的训练更加快速，在梯度的累积量较大时(\u0026gt;1)，缩小学习率，延缓网络训练\n但AdaGrad随着epoch的增长，学习率降低得很快，即该算法很容易过分降低学习率\nAdelta 该方法对Adgard方法中随迭代次数的增多，学习率会变得非常小的情况进行了改进，主要是将$cache$更换为过去梯度平方的衰减平均值。参数更新公式如下：\n$$ \\begin{aligned} \\mathbf{w}_t \u0026= \\mathbf{w}_{t-1} - \\frac{\\alpha}{\\sqrt{E[g^2]_t}+\\delta}g_t \\\\ E[g^2]_t \u0026= \\frac{1}{t}\\sum_{i=1}^t g_i^2 \\\\ g_t \u0026= \\nabla\\mathbf{w}_t \\end{aligned} $$这个分母相当于梯度的均方根（数理统计中，均方根为数据平方的平均值再开方）。故可简写为下式：\n$$ \\mathbf{w}_t = \\mathbf{w}_{t-1}-\\frac{\\alpha}{RMS[g]_t}g_t $$求平均值部分可以更换为增量式，并引入超参数$\\gamma$（一般设定为0.9）来衡量过去与现在梯度的重要性：\n$$ E[g^2]_t = \\gamma E[g^2]_{t-1}+(1-\\gamma)g_t^2 $$还可将学习率更换为$RMS[\\Delta\\mathbf{w}]$，这样就不需要预设学习率。\n$$ \\begin{aligned} \\Delta\\mathbf{w}_t \u0026= -\\frac{RMS[\\Delta\\mathbf{w}]_{t-1}}{RMS[g]_t}g_t \\\\ \\mathbf{w}_t \u0026= \\mathbf{w}_{t-1}+\\Delta\\mathbf{w}_t \\\\ \\end{aligned} $$RMSProp 为了解决AdaGrad学习率衰减过快的问题，RMSProp算法引入了衰减因子，在进行梯度累积时会对“过去”和“现在”做一个权衡，通过$\\beta$来调节衰减量，常用的取值有0.9或0.5\n$$cache_i=\\beta cache_i+(1-\\beta)(\\nabla \\mathbf{w}_i)^2$$在参数更新阶段，和AdaGrad相同\n$$\\mathbf{w}_i=\\mathbf{w}_i-\\frac{\\alpha}{\\sqrt{cache_i}+\\delta}\\nabla\\mathbf{w}_i$$Adam 该算法是Momentum+RMSProp的微调版本，默认情况下，推荐使用这种优化方法\n在开始时梯度会非常小，$r$和$v$经常会接近于0，因此我们还需要对$r$和$v$进行调整\n$$ \\begin{aligned} vb=\\frac{v}{1-\\beta^t_1} \\\\ rb=\\frac{r}{1-\\beta^t_2} \\end{aligned} $$其中$t$表示训练次数，因此该算法仅在训练的前几轮中根据衰减因子来放大各自值，很快$vb$,$rb$会衰减为$v$，$r$\n// Adam\n给定数据集$X=\\\\{x^{(1)},x^{(2)},...,x^{(n)}\\\\}$, 数据集标记$Y=\\\\{y^{(1)}, y^{(2)},...,y^{(n)}\\\\}$\n初始速度$v$,随机采样数据大小$m$，训练周期$k$\n学习器$f(x;w)$,初始学习率$\\alpha$,初始动量参数$\\beta_1$\n学习率衰减参数$\\beta_2$, $\\delta=10^{-7}$\nFor $i \u003c= k$\n随机采样$m$条数据： $\\\\{(x^{(i)},y^{(i)}),...,(x^{(m)},y^{(m)})\\\\}$\n计算当前采样数据梯度： $g = \\frac{1}{m}\\sum^m_{j=1}\\frac{\\partial{L(y^{(j)},f(x^{(j)};w)})}{\\partial{\\mathbf{w}}}$\n更新当前速度： $v=\\beta_1 v+(1-\\beta_1)g$\n更新当前学习率： $r=\\beta_2 r+(1-\\beta_2)g^2$\n更新训练次数： $t=t+1$\n$$ \\begin{aligned} vb=\\frac{v}{1-\\beta^t_1} \\\\ rb=\\frac{r}{1-\\beta^t_2} \\end{aligned} $$更新新参数：\n$$w=w-\\frac{\\alpha}{\\sqrt{rb}+\\delta}vb$$","permalink":"https://simplestory.github.io/posts/20181103_7b202c9a380844d1b8ae2c4f5ee3461f/","summary":"\u003cblockquote\u003e\n\u003cp\u003e最优化是一个十分困难的问题，通常要精心设计一个目标函数及相关约束，同时还要确保所要优化的问题为凸函数，但现实中的问题大多为非凸函数，针对非凸优化问题，这里有一些基本的优化方法：SGD、Momentum、AdaGrad、RMSProp及Adam等\u003c/p\u003e\n\u003c/blockquote\u003e","title":"深度学习优化"},{"content":" CTR(点击率)预测在内容推荐投放中有着重要地位，而FFM模型擅长于处理这方面的数据，FFM模型是FM模型的变种，或者说FM模型是FFM模型的特例\n大致流程 转换数据集格式（采用LIBSVM或FFMs式） 计算损失函数（采用指数损失函数） 优化参数（采用AdaGrad+SG策略） 转换数据格式 LIBSVM格式（不储存0值特征）：\n$$ label \\quad feat1:val1 \\quad feat2:val2 \\quad ... \\quad $$FFMs格式：\n$$label \\quad field1:feat1:val1 \\quad field2:feat2:val2 \\quad ... \\quad$$针对不同的数据类型，数据格式的转换会有所不同\n针对类别特征(Categorical Features)\n例如有如下样本：\n$$Yes \\quad P:ESPN \\quad A:Nike \\quad G:Male$$转为LIBSVM格式：\n$$Yes \\quad P-ESPN:1 \\quad A-Nike:1 \\quad G-Male:1$$转为FFMs格式：\n$$Yes \\quad P:P-ESPN:1 \\quad A:A-Nike:1 \\quad G:G-Male:1$$数字型特征(Numerical Features)\n数字型特征有两种转换格式：第一种是将每一个特征视为一个域（这种方法只是在重复数据，没有增加信息）；第二种是离散化数据，并作为分类特征进行格式转换\n例如：\nAccepted AR Hidx Cite Yes 45.73 2 3 No 1.04 100 50,000 第一格式：\n$$Yes \\quad AR:AR:45.73 \\quad Hidx:Hidx:2 \\quad Cite:Cite:3$$第二格式：\n$$Yes \\quad AR:45:1 \\quad Hidx:2:1 \\quad Cite:3:1$$对于第二格式，使用离散化后可能会丢失一些信息\n单域特征(Single-field Features)\n这种特征会让FFM模型降为FM模型\n计算损失函数 在CTR预测中，常用的最优化为：\n$$ min_{\\mathbf{w}} \\quad \\frac{\\lambda}{2}||\\mathbf{w}||^2_2+\\sum_{i=1}^m log(1+exp(-y_i\\phi(\\mathbf{w},\\mathbf{x}_i))) $$其中函数$\\phi(\\mathbf{w},\\mathbf{x}_i)$依据不同的模型会更换为不同的函数，针对FFM模型有：\n$$ \\phi_{FFM}(\\mathbf{w},\\mathbf{x})=\\sum_{j_1=1}^n\\sum_{j_2=j_1+1}^n (\\mathbf{w}_{j1,f2}\\cdot\\mathbf{w}_{j2,f1})x_{j1}x_{j2} $$其中$f_1$, $f_2$分别代表$j_1$, $j_2$的域\n优化参数 参数的优化采用SG+AdaGrad的方法\n首先计算梯度：\n$$ \\begin{aligned} \\mathbf{g}_{j1,f2} \\equiv \\nabla_{\\mathbf{w}_{j1,f2}}f(\\mathbf{w}) = \\lambda \\cdot \\mathbf{w}_{j1,f2}+\\kappa\\cdot\\mathbf{w}_{j2,f1}x_{j1}x_{j2} \\\\ \\mathbf{g}_{j2,f1} \\equiv \\nabla_{\\mathbf{w}_{j2,f1}}f(\\mathbf{w}) = \\lambda \\cdot \\mathbf{w}_{j2,f1}+\\kappa\\cdot\\mathbf{w}_{j1,f2}x_{j1}x_{j2} \\end{aligned} \\tag{1} $$其中：\n$$ \\kappa = \\frac{\\partial{log(1+exp(-y\\phi_{FFM}(\\mathbf{w},\\mathbf{x})))}}{\\partial{\\phi_{FFM}({\\mathbf{w},\\mathbf{x}}})} =\\frac{-y}{1+exp(y\\phi_{FFM}({\\mathbf{w},\\mathbf{x}}))} $$然后，借助AdaGrad算法累积梯度的平方有：\n$$ \\begin{aligned} (G_{j1,j2})_d \\leftarrow (G_{j1,f2})_d+(g_{j1,f2})_d^2 \\\\ (G_{j2,f1})_d \\leftarrow (G_{j2,f1})_d+(g_{j2,f1})_d^2 \\end{aligned} \\tag{2} $$其中d要遍历1到k（k为超参数，代表潜在向量的维度）\n最后由下式更新相关参数：\n$$ \\begin{aligned} (w_{j1,f2})_d \\leftarrow (w_{j1,f2})_d-\\frac{\\eta}{\\sqrt{(G_{j1,f2})_d}}(g_{j1,f2})_d \\\\ (w_{j2,f1})_d \\leftarrow (w_{j2,f1})_d-\\frac{\\eta}{\\sqrt{(G_{j2,f1})_d}}(g_{j2,f1})_d \\end{aligned} \\tag{3} $$其中$\\eta$为超参数——学习率，$\\mathbf{w}$的初始值从服从在$[0,1/\\sqrt{k}]$区间内均匀分布的分布中随即抽取，G的初值设为1，为防止$(G_{j1,f2})_d^{-\\frac{1}{2}}$的值过大\n实验显示，将每个样本统一到一致的长度会提高模型的训练正确率，同时，该模型对参数较为敏感\n过拟合策略 FFM模型采用早停来防止过拟合，策略如下：\n将数据集划分为训练集和验证集 在每轮训练之后，使用验证集计算模型的损失 如果损失增大，记录训练轮数停止训练并转到步骤4 如果需要，使用全部数据集重新训练模型，训练轮数为步骤3中所得的轮数 一些总结 与FM模型相比，FFM模型引入了域(field)的概念，增加了潜在向量的个数，如果有n个features属于f个field，则FFM模型的二次项有nf个隐向量，而FM可视为FFM的特例，是将所有特征都归属于一个field时的FFM模型\nFFM模型对于包含有类别特征并转为二分类的数据有较好的表现 若转换后的数据集不够稀疏，FFM模型可能不会有太好的表现 FFM模型难以处理数字型特征 该模型有三个超参数：\nk 潜在向量维度，实验表明其大小对模型并无太大影响 $\\lambda$ 正则化系数，其值过大会出现欠拟合，而过小易过拟合 $\\eta$ 学习率，过大易过拟合，过小则模型训练是误差下降缓慢 相关的库或框架：\nlibffm\nxlearn\n参考论文\nField-aware Factorization Machines for CTR Prediction\n","permalink":"https://simplestory.github.io/posts/20181028_fa81994273e28bed9a6b443a7af7f69e/","summary":"\u003cblockquote\u003e\n\u003cp\u003eCTR(点击率)预测在内容推荐投放中有着重要地位，而FFM模型擅长于处理这方面的数据，FFM模型是FM模型的变种，或者说FM模型是FFM模型的特例\u003c/p\u003e\n\u003c/blockquote\u003e","title":"FFM模型原理"},{"content":" 决策树，一种常见的机器学习方法，有种类似于流程图那样的。就是对一个样本按其某个特征的取值将其划分到一个集合中，并递归下去\n基本算法 Input: 训练集$D=\\\\{(\\mathbf{x_1},y_1),(\\mathbf{x_2},y_2),...,(\\mathbf{x_m},y_m)\\\\}$; 属性集$A=\\\\{a_1,a_2,...,a_d\\\\}$\nOutput: 以node为根结点的一颗决策树\n简单过程：\n以某一划分标准，选择最优的特性来划分样本，这样递归下去，最终得到决策树的分支结点\n重要的是找到一当前的最优划分属性，使决策树的分支结点所包含的样本尽可能属于同一类别，即结点的纯度越高\n基本套路 信息熵与信息增益 假定当前样本集合D中第k类样本所占的比例为$p_k \\ (k=1,2,3,...,\\vert y\\vert)$，则D的信息熵定义为：\n$$Ent(D) = -\\sum_{k=1}^{\\vert y\\vert} p_k\\log_{2}p_k$$其中约定，若$p=0$,则$p\\log_{2}p = 0$，有$0 \\le Ent(D) \\le \\log_{2}\\vert y\\vert$，$Ent(D)$的值越小，则D的纯度越小\n若属性a有V个可能的取值$\\\\{a^1,a^2,...,a^V\\\\}$，用a对样本集D进行划分会产生V个分支结点，其中第v个分支结点包含了D中所有在属性a上取值为$a^v$的样本，即为$D^v$，则可得用属性a对样本集进行划分所获得的信息增益为：\n$$Gain(D,a) = Ent(D)-\\sum_{v=1}^V \\frac{\\vert D^v\\vert}{\\vert D\\vert} Ent(D^v)$$一般来说，信息增益越大，则意味着使用属性a进行划分所得到的纯度提升越大，所以我们可以采用信息增益来作为划分准则，这就是ID3决策树学习算法\n增益率 在上面中，我们采用了信息增益来作为我们的划分准则，但信息增益偏好于选择类别较多的属性来进行划分，假若我们有一个特征列为id，则信息增益会优先选择id这一特征进行划分，但这并非我们的目的。为此，我们考虑在信息增益的基础上加上一个与类别个数成正比的正则项，这就得到了增益率（又称信息增益比）：\n$$ \\begin{aligned} Gain\\_raito(D,a) = \\frac{Gain(D,a)}{IV(a)} \\\\ IV(a) = -\\sum_{v=1}^V \\frac{\\vert D^v\\vert}{\\vert D\\vert} \\log_{2}\\frac{\\vert D^v\\vert}{\\vert D\\vert} \\end{aligned} $$增益率准则偏好于选择取值较少的属性，而C4.5决策树算法则以增益率为基础加入了一个启发式设计，即先从候选划分属性中找出信息增益高于平均水平的属性，再从中选择增益率最高的\n基尼系数 信息熵是衡量信息不确定性的指标，实际上也是衡量信息纯度的指标，而基尼系数也是衡量这一方面的指标，计算如下：\n$$ \\begin{aligned} Gini(D) \u0026 = \\sum_{k=1}^{\\vert y\\vert} \\sum_{k‘ \\neq k} p_kp_{k’} \\\\ \u0026 = \\sum_{k=1}^{\\vert y\\vert} p_k(1-p_k) \\\\ \u0026 = 1-\\sum_{k=1}^{\\vert y\\vert} {p_k}^2 \\end{aligned} $$其中$p_k$表示样本属于第k类的概率，Gini(D)反映了从数据集D中随机抽取两个样本，其类别标记不一致的概率，因此Gini(D)越小，则数据集D的纯度越高\n对于属性a的基尼系数，为属性a取值的基尼系数加权和，即：\n$$Gini\\_index(D,a) = \\sum_{v=1}^V \\frac{\\vert D^v\\vert}{\\vert D\\vert} Gini(D^v)$$故在候选属性集A中，我们应该选择那个使得划分后基尼系数最小的属性作为最优划分属性：\n$$a_* = argmin_{a \\in A} Gini\\_index(D,a)$$基于基尼系数来进行划分的即为CART决策树算法\n优化处理 对于决策树的过拟合处理，主要有前剪枝和后剪枝两种方法\n前剪枝\n前剪枝是指在决策树构造过程中，对每个结点在划分前后进行估计，若当前结点的划分不能带来决策树泛化性能的提升，则停止划分并将当前结点作为叶结点\n后剪枝\n后剪枝则是在决策树构造完成之后，自底向上地对非叶子结点进行检查，若将该结点对应的子树替换为叶子结点能带来泛化性能的提升，则将该子树替换为叶子结点\n前后剪枝的比较\n项目 前剪枝 后剪枝 训练时间开销 小 大 测试时间开销 小 小 过拟合风险 小 小 欠拟合风险 大 小 泛化性能方面：后剪枝优于前剪枝\nScikit-learn应用 具体API参数查看官网：\nsklearn.tree.DecisionTreeClassifier\n参考 周志华的西瓜书，李航的统计学习方法\n","permalink":"https://simplestory.github.io/posts/20180829_3cb42cd7fd688b586f483bd682fc8209/","summary":"\u003cblockquote\u003e\n\u003cp\u003e决策树，一种常见的机器学习方法，有种类似于流程图那样的。就是对一个样本按其某个特征的取值将其划分到一个集合中，并递归下去\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch1 id=\"基本算法\"\u003e基本算法\u003c/h1\u003e\n\u003cp\u003eInput: 训练集$D=\\\\{(\\mathbf{x_1},y_1),(\\mathbf{x_2},y_2),...,(\\mathbf{x_m},y_m)\\\\}$;\n属性集$A=\\\\{a_1,a_2,...,a_d\\\\}$\u003c/p\u003e","title":"决策树"},{"content":" 这次整理一下常用的scikit-learn包，作为备忘\nK近邻算法：\nfrom sklearn.neighbors import KNeighborsClassifier\n线性回归：\nfrom sklearn.linear_model import LinearRegression\n逻辑回归：\nfrom sklearn.linear_model import LogisticRgression\n决策树：\nfrom sklearn.tree import DecisionTreeClassifier\n朴素贝叶斯：\n高斯分布：from sklearn.neive_bayes import GaussianNB 多项式分布：from sklearn.neive_baybes import MultinomnalNB 伯努利分布：from sklearn.neive_baybes import BernoulliNB 支持向量机：\n分类：from sklearn.svm import SVC 回归：from sklearn.svm import SVR 集合算法(sklearn.ensemble):\nbagging: 分类：from sklearn.ensemble import BaggingClassifier\n回归：from sklearn.ensemble import BaggingRegression\nAdaBoost: 分类：from sklearn.ensemble import AdaBoostClassifier\n回归：from sklearn.ensemble import AdaBoostRegression\n随机森林： 分类：from sklearn.ensemble import RandomForestClassifier\n回归：from sklearn.ensemble import RandomForestRegression\nK均值：\nfrom sklearn.cluster import KMeans\nPCA降维：\nfrom sklearn.decomposition import PCA\n最后附上一张scikit寻宝图：\nscikit-learn cheat sheet\n","permalink":"https://simplestory.github.io/posts/20180826_8cb44c1627565d8ede47d43e49e9c8ae/","summary":"\u003cblockquote\u003e\n\u003cp\u003e这次整理一下常用的scikit-learn包，作为备忘\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003eK近邻算法：\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"http://scikit-learn.org/stable/modules/generated/sklearn.neighbors.KNeighborsClassifier.html\"\u003efrom sklearn.neighbors import KNeighborsClassifier\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e线性回归：\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"http://scikit-learn.org/stable/modules/generated/sklearn.linear_model.LinearRegression.html\"\u003efrom sklearn.linear_model import LinearRegression\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e逻辑回归：\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"http://scikit-learn.org/stable/modules/generated/sklearn.linear_model.LogisticRegression.html\"\u003efrom sklearn.linear_model import LogisticRgression\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e决策树：\u003c/p\u003e\n\u003cp\u003e\u003ca href=\"http://scikit-learn.org/stable/modules/generated/sklearn.tree.DecisionTreeClassifier.html\"\u003efrom sklearn.tree import DecisionTreeClassifier\u003c/a\u003e\u003c/p\u003e\n\u003cp\u003e朴素贝叶斯：\u003c/p\u003e\n\u003col\u003e\n\u003cli\u003e高斯分布：\u003ca href=\"http://scikit-learn.org/stable/modules/generated/sklearn.naive_bayes.GaussianNB.html\"\u003efrom sklearn.neive_bayes import GaussianNB\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e多项式分布：\u003ca href=\"http://scikit-learn.org/stable/modules/generated/sklearn.naive_bayes.MultinomialNB.html#sklearn.naive_bayes.MultinomialNB\"\u003efrom sklearn.neive_baybes import MultinomnalNB\u003c/a\u003e\u003c/li\u003e\n\u003cli\u003e伯努利分布：\u003ca href=\"http://scikit-learn.org/stable/modules/generated/sklearn.naive_bayes.BernoulliNB.html#sklearn.naive_bayes.BernoulliNB\"\u003efrom sklearn.neive_baybes import BernoulliNB\u003c/a\u003e\u003c/li\u003e\n\u003c/ol\u003e\n\u003cp\u003e支持向量机：\u003c/p\u003e","title":"Scikit-learn调包合集"},{"content":" 贝叶斯决策论考虑的是如何基于相关的概率和误判损失来选择最优的类别标记\n假设有N种可能的类别标记，即$\\mathbf{y}=\\\\{c_1,c_2,...,c_N\\\\}$，$\\lambda_{ij}$是将一个真实标记为$c_j$的样本误分类为$c_i$所产生的损失。基于后验概率$P(c_i\\vert\\mathbf{x})$可获得将样本$\\mathbf{x}$分类为$c_i$所产生的期望损失，即在样本$\\mathbf{x}$上的“条件风险”：\n$$R(c_i\\vert\\mathbf{x})= \\ \\sum_{j=1}^N \\lambda_{ij}P(c_j\\vert\\mathbf{x})$$我们目的是寻找一个判定准则$h: \\ \\mathbf{x} \\mapsto \\mathbf{y}$以最小化总风险：\n$$R(h)=E_{\\mathbf{x}}[R(h(\\mathbf{x})\\vert\\mathbf{x})]$$由于对于每个样本$\\mathbf{x}$，若h能最小化条件风险$R(h(\\mathbf{x})\\vert\\mathbf{x})$，则总体风险$R(h)$也将被最小化，故有：\n贝叶斯判定准则：\n为最小化总体风险，只需在每个样本上选择那个能使条件风险$R(c\\vert\\mathbf{x})$最小的类别标记，即：\n$$h^*(\\mathbf{x})=argmin_{c\\in\\mathbf{y}} \\ R(c\\vert\\mathbf{x})$$此时$h^*$被称为贝叶斯最优分类器，与之对应的总体风险$R(h^*)$称为贝叶斯风险，$1-R(h^*)$反映了分类器所能达到的最好性能，即通过机器学习所能产生的模型精度的理论上限\n朴素贝叶斯 朴素贝叶斯通过训练数据集学习联合概率分布$P(X,Y)$，具体就是学习以下先验概率分布以及条件概率分布：\n先验概率分布：$\\ P(Y=c_k), \\ k=1,2,...,N$\n条件概率分布：$\\ P(X=x\\vert Y=c_k)=P(X^{(1)}=x^{(1)},...,X^{(n)}=x^{(n)}\\vert Y=c_k), \\ k=1,2,...N$\n故有联合概率分布：$P(X,Y)=P(Y=c_k)P(X=x\\vert Y=c_k)$\n推导过程 朴素贝叶斯有一个重要假设，即条件独立性假设，即：\n$$ \\begin{aligned} P(X=x\\vert Y=c_k) \u0026 =P(X^{(1)}=x^{(1)},...,X^{(n)}=x^{(n)}\\vert Y=c_k) \\\\ \u0026 =\\prod_{j=1}^n P(X^{(j)}=x^{(j)}\\vert Y=c_k) \\end{aligned} \\tag{1} $$由贝叶斯定理有后验概率：\n$$P(Y=c_k\\vert X=x)=\\frac{P(X=x\\vert Y=c_k)P(Y=c_k)}{\\sum_k P(X=x\\vert Y=c_k)P(Y=c_k)}$$将(1)式代入上式有：\n$$ \\begin{aligned} \u0026 P(Y=c_k\\vert X=x)=\\frac{P(Y=c_k)\\prod_j P(X^{(j)}=x^{(j)}\\vert Y=c_k)}{\\sum_k P(Y=c_k)\\prod_j P(X^{(j)}=x^{(j)}\\vert Y=c_k)} \\\\ \u0026 \\text{其中} \\ k=1,2,...,k \\end{aligned} \\tag{2} $$则朴素贝叶斯分类器可表示为：\n$$ y=f(x)=argmax_{c_k} \\ \\frac{P(Y=c_k)\\prod_j P(X^{(j)}=x^{(j)}\\vert Y=c_k)}{\\sum_k P(Y=c_k)\\prod_j P(X^{(j)}=x^{(j)}\\vert Y=c_k)} \\tag{3} $$注意到在(3)式中，分母对所有$c_k$都是相同的，则有：\n$$ y=argmax_{c_k} \\ p(Y=c_k)\\prod_j P(X^{(j)}=x^{(j)}\\vert Y=c_k) $$Scikit-learn应用 具体API参数查看官网：\nsklearn.naive_bayes\n参考 周志华的西瓜书，李航的统计学习方法\n","permalink":"https://simplestory.github.io/posts/20180819_72c962ee57eaf5ebe2b4645dd6827ce3/","summary":"\u003cblockquote\u003e\n\u003cp\u003e贝叶斯决策论考虑的是如何基于相关的概率和误判损失来选择最优的类别标记\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003e假设有N种可能的类别标记，即$\\mathbf{y}=\\\\{c_1,c_2,...,c_N\\\\}$，$\\lambda_{ij}$是将一个真实标记为$c_j$的样本误分类为$c_i$所产生的损失。基于后验概率$P(c_i\\vert\\mathbf{x})$可获得将样本$\\mathbf{x}$分类为$c_i$所产生的期望损失，即在样本$\\mathbf{x}$上的“条件风险”：\u003c/p\u003e","title":"贝叶斯分类"},{"content":" SVM是机器学习里面一个非常重要的模型，它是由统计学习理论之父弗拉基米尔·瓦普尼克提出的\n本文以SVM二分类为例\n推导过程 给定训练数据集$D=\\\\{(\\mathbf{x_1},y_1),(\\mathbf{x_2},y_2),...,(\\mathbf{x_m},y_m)\\\\}, y_i \\in \\\\{-1, +1\\\\}$，分类器最基本的想法就是基于训练集D在样本空间中找到一个划分空间，将不同类别的样本分开。划分超平面可用如下的线性方程描述：\n$$ {\\mathbf{w}}^T\\mathbf{x}+b=0$$其中$\\mathbf{w}=(w_1;w_2;...;w_d)$为法向量，决定了超平面的方向，b为偏移项，决定了超平面与原点之间的距离，可知超平面有w和d决定。则有空间中任意点到超平面(w,b)的距离为：\n$$\\gamma = \\frac{\\vert{\\mathbf{w}}^T\\mathbf{x}+b\\vert}{\\Vert\\mathbf{w}\\Vert}$$假设超平面(w,b)将样本完全分类正确，即对于$(\\mathbf{x_i}, y_i) \\in D$，若$y_i=+1$则有${\\mathbf{w}}^T\\mathbf{x_i}+b \u003e 0$; 若$y_i=-1$，则有${\\mathbf{w}}^T\\mathbf{x_i}+b \u003c 0$，故有：\n$$ \\begin{cases} {\\mathbf{w}}^T\\mathbf{x_i}+b \\ge +1, \\ y_i = +1 \\\\ {\\mathbf{w}}^T\\mathbf{x_i}+b \\le -1, \\ y_i = -1 \\end{cases} \\tag{1} $$上式可以合并为：$y_i({\\mathbf{w}}^T\\mathbf{x_i}+b) \\ge 1, i=1,2,...,m$\n在距离超平面最近的几个训练样本点使(1)式等号成立，这些样本点被称为“支持向量”，两个异类支持向量到超平面的距离之和为：\n$$\\gamma = \\frac{2}{\\Vert\\mathbf{w}\\Vert}$$它被称为“间隔”\nSVM则是找到具有最大间隔的划分超平面，即满足下式：\n$$ \\begin{aligned} \\min_{\\mathbf{w},b} \u0026 \\frac{1}{2}||\\mathbf{w}||^2 \\\\ s.t. \u0026 \\ y_i({\\mathbf{w}}^T\\mathbf{x_i}+b) \\ge 1, i=1,2,...,m \\end{aligned} \\tag{2} $$上式即为SVM基本型\n对(2)使用拉格朗日乘数法可得下式：\n$$ L(\\mathbf{w},b,\\mathbf{\\alpha}) = \\frac{1}{2}{\\Vert\\mathbf{w}\\Vert}^2 + \\sum_{i=1}^m \\alpha_i(1-y_i({\\mathbf{w}}^T\\mathbf{x_i}+b)) \\tag{3} $$其中$\\alpha = (\\alpha_1;\\alpha_2;...;\\alpha_m)$，令$L(w,b,\\alpha)$对w，b的偏导数为0：\n$$ \\begin{aligned} \\frac{\\partial L(\\mathbf{w},b,\\mathbf{\\alpha})}{\\partial \\mathbf{w}} \\ = \\mathbf{w} - \\sum_{i=1}^m \\alpha_iy_i\\mathbf{x_i} = 0 \\\\ \\frac{\\partial L(\\mathbf{w},b,\\mathbf{\\alpha})}{\\partial b} \\ = 0+\\sum_{i=1}^m \\alpha_iy_i = 0 \\end{aligned} $$则可得下式：\n$$ \\begin{aligned} \\mathbf{w} = \\sum_{i=1}^m \\alpha_iy_i\\mathbf{x_i} \\\\ 0 = \\sum_{i=1}^m \\alpha_iy_i \\end{aligned} \\tag{4} $$将(4)式代入(3)有：\n$$ \\begin{aligned} L(\\mathbf{w},b,\\mathbf{\\alpha}) \u0026 = \\frac{1}{2}{\\mathbf{w}}^T\\mathbf{w}+\\sum_{i=1}^m \\alpha_{1} -\\sum_{i=1}^m \\alpha_iy_i{\\mathbf{w}}^T\\mathbf{x_i}-\\sum_{t=1}^m \\alpha_iy_ib \\\\ \u0026 =\\frac{1}{2}\\sum_{i=1}^m \\alpha_iy_i{\\mathbf{x_i}}^T\\cdot\\sum_{j=1}^m \\alpha_jy_j\\mathbf{x_j}+\\sum_{i=1}^m \\alpha_i-\\sum_{i=1}^m \\sum_{j=1}^m \\alpha_iy_i\\alpha_jy_j{\\mathbf{x_j}}^T\\mathbf{x_i} \\\\ \u0026 =\\sum_{i=1}^m \\alpha_i + \\frac{1}{2}\\sum_{i=1}^m \\sum_{j=1}^m \\alpha_iy_i\\alpha_jy_j{\\mathbf{x_i}}^T\\mathbf{x_j}-\\sum_{i=1}^m \\sum_{j=1}^m \\alpha_iy_i\\alpha_jy_j{\\mathbf{x_j}}^T\\mathbf{x_i} \\\\ \u0026 =\\sum_{i=1}^m \\alpha_i - \\frac{1}{2}\\sum_{i=1}^m \\sum_{j=1}^m \\alpha_iy_i\\alpha_jy_j{\\mathbf{x_i}}^T\\mathbf{x_j} \\end{aligned} $$则有下式：\n$$ \\begin{aligned} \\max_\\mathbf{\\alpha} \u0026 \\sum_{i=1}^m \\alpha_i - \\frac{1}{2}\\sum_{i=1}^m \\sum_{j=1}^m \\alpha_i\\alpha_jy_iy_j\\mathbf{x_i}^T\\mathbf{x_j} \\\\ s.t. \u0026 \\sum_{i=1}^m \\alpha_iy_i=0, \\\\ \u0026 \\alpha_i \\ge 0, i=1,2,...,m \\end{aligned} \\tag{5} $$解出$\\mathbf{\\alpha}$后，求出$\\mathbf{w}$, $\\mathbf{b}$即可得到模型如下：\n$$ f(\\mathbf{x})={\\mathbf{w}}^T\\mathbf{x}+b=\\sum_{i=1}^m \\alpha_iy_i{\\mathbf{x_i}}^T\\mathbf{x}+b $$求解(5)式通常采用的是SMO算法得出$\\mathbf{\\alpha}$的值，再由(6)式计算b的值：\n$$ b = \\frac{1}{\\vert{S}\\vert}\\sum_{s \\in S}(\\frac{1}{y_c}-\\sum_{i \\in s}\\alpha_iy_i{\\mathbf{x_i}}^T\\mathbf{x_s}) \\tag{6} $$核技巧 在前面的讨论中，我们假设了数据是线性可分的，但现实数据大多数情况下并非线性可分。为此，我们可以考虑对原始数据做一个映射，使其线性可分，之后再交由SVM模型训练。具体如下：\n假设映射函数为$\\phi(\\mathbf{x})$，则有：$f(\\mathbf{x})={\\mathbf{w}}^T\\phi(\\mathbf{x})+b$\n$$ \\begin{aligned} \\min_{\\mathbf{w},b} \u0026 \\frac{1}{2}||\\mathbf{w}||^2 \\\\ s.t. \u0026 \\ y_i({\\mathbf{w}}^T\\phi(\\mathbf{x_i})+b) \\ge 1, i=1,2,...,m \\end{aligned} $$其对偶问题为：\n$$ \\begin{aligned} \\max_\\mathbf{\\alpha} \u0026 \\sum_{i=1}^m \\alpha_i-\\frac{1}{2}\\sum_{i=1}^m \\sum_{j=1}^m \\alpha_i\\alpha_jy_iy_j\\phi(\\mathbf{x_i})^T\\phi(\\mathbf{x_j}) \\\\ s.t. \u0026 \\ \\sum_{i=1}^m \\alpha_iy_i=0 \\\\ \u0026 \\alpha_i \\ge 0, i=1,2,...,m \\end{aligned} $$对于以上式子出现的$\\phi(\\mathbf{x_i})^T\\phi(\\mathbf{x_j})$可用一个函数来替代$k(\\mathbf{x_i},\\mathbf{x_j})$，即为核函数\n$$k(\\mathbf{x_i},\\mathbf{x_j})=\\langle \\phi(\\mathbf{x_i}),\\phi(\\mathbf{x_j}) \\rangle=\\phi(\\mathbf{x_i})^T\\phi(\\mathbf{x_j})$$故有：\n$$ \\begin{aligned} \\max_\\mathbf{\\alpha} \u0026 \\sum_{i=1}^m \\alpha_i - \\frac{1}{2}\\sum_{i-1}^m \\sum_{j=1}^m \\alpha_i\\alpha_jy_iy_jk(\\mathbf{x_i},\\mathbf{x_j}) \\\\ s.t. \u0026 \\ \\sum_{i=1}^m \\alpha_iy_i = 0, \\\\ \u0026 \\alpha_i \\ge 0, i=1,2,..,m \\end{aligned} $$求解后有：\n$$ \\begin{aligned} f(x) \u0026 ={\\mathbf{w}}^T\\phi(\\mathbf{x})+b \\\\ \u0026 =\\sum_{i=1}^m \\alpha_iy_i\\phi(\\mathbf{x_i})^T\\phi(\\mathbf{x_j})+b \\\\ \u0026 =\\sum_{i=1}^m \\alpha_iy_ik(\\mathbf{x},\\mathbf{x_i})+b \\end{aligned} $$常用的核函数：\n名称 表达式 参数 线性核 $k(\\mathbf{x_i},\\mathbf{x_j})=\\mathbf{x_i}^T\\mathbf{x_j}$ 多项式核 $k(\\mathbf{x_i},\\mathbf{x_j})=(\\mathbf{x_i}^T\\mathbf{x_j})^d$ $d \\ge 1$，为多项式次数 高斯核 $k(\\mathbf{x_i},\\mathbf{x_j})=exp(-\\frac{\\Vert\\mathbf{x_i}-\\mathbf{x_j}\\Vert^2}{2\\sigma^2})$ $\\sigma \\gt 0$，为高斯核的带宽 拉普拉斯核 $k(\\mathbf{x_i},\\mathbf{x_j})=exp(-\\frac{\\Vert\\mathbf{x_i}-\\mathbf{x_j}\\Vert}{\\sigma})$ $\\sigma \\gt 0$ Sigmoid核 $k(\\mathbf{x_i},\\mathbf{x_j})=tanh(\\beta \\mathbf{x_i}^T\\mathbf{x_j}+\\theta)$ tanh为双曲正切函数，$\\beta \\gt 0$，$\\theta \\gt 0$ 通常的选择：\n线性核一般用于线性可分的数据或文本数据\n高斯核一般用于线性不可分或数据情况不明的时候\nSVM的正则化 为了防止过拟合，我们通常采用软间隔支持向量机，即允许有某些样本在间隔内，则模型方程为：\n$$ \\min_{\\mathbf{w},b} \\frac{1}{2}{\\Vert\\mathbf{w}\\Vert}^2+C\\sum_{i=1}^m l_{0/1}(y_i({\\mathbf{w}}^T\\mathbf{x_i}+b)-1) \\tag{7} $$其中C\u0026gt;0是一个常数，$l_{0/1}$为0/1损失函数：\n$$ l_{0/1}(z)=\\begin{cases} 1, \\ \\text{if } \\ z \\le 0; \\\\ 0, \\ \\text{otherwise} \\end{cases} $$通常用其它函数替换$l_{0/1}$，如下：\n$$ \\begin{cases} \\text{hinge损失: } \\ l_{hinge}(z)=\\max(0,1-z); \\\\ \\text{指数损失: } \\ l_{exp}(z)=exp(-z); \\\\ \\text{对率损失: } \\ l_{log}(z)=log(1+exp(-z)) \\end{cases} $$参考(7)式，更一般的形式为：\n$$\\min_f \\Omega(f)+C\\sum_{i=1}^m l(f(\\mathbf{x_i}),y_i)$$其中$\\Omega(f)$为结构风险，用于描述模型f的某些性质（$\\Omega(f)$又可称为正则化项，常用的有$L_0,L_1,L_2$正则化），第二项$\\sum_{i=1}^m l(f(\\mathbf{x_i}),y_i)$为经验风险，由于描述模型与训练数据的契合程度，C由于两者进行折中。\nScikit-learn应用 具体API参考Skicit-learn官网：\nsklearn.svm.LinearSVC\nsklearn.svm.SVC\n参考 周志华的西瓜书，李航的统计学习方法\n","permalink":"https://simplestory.github.io/posts/20180816_7ed73172fd758adf99c76ebc108ea063/","summary":"\u003cblockquote\u003e\n\u003cp\u003eSVM是机器学习里面一个非常重要的模型，它是由统计学习理论之父弗拉基米尔·瓦普尼克提出的\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003e\u003cstrong\u003e本文以SVM二分类为例\u003c/strong\u003e\u003c/p\u003e\n\u003ch2 id=\"推导过程\"\u003e推导过程\u003c/h2\u003e\n\u003cp\u003e给定训练数据集$D=\\\\{(\\mathbf{x_1},y_1),(\\mathbf{x_2},y_2),...,(\\mathbf{x_m},y_m)\\\\}, y_i \\in \\\\{-1, +1\\\\}$，分类器最基本的想法就是基于训练集D在样本空间中找到一个划分空间，将不同类别的样本分开。划分超平面可用如下的线性方程描述：\u003c/p\u003e","title":"SVM支持向量机"},{"content":" 好久没更博客了，停了大半年，也看了大半年的机器学习和深度学习方面的书籍，现在是时候输出一下了\n线性回归 给定数据集$D=\\\\{(\\mathbf{x_1},y_1),(\\mathbf{x_2},y_2),...,(\\mathbf{x_m},y_m)\\\\}$，其中$\\mathbf{x_i}=(x_{i1};x_{i2};...;x_{id}), \\ y_{i} \\in R$. 线性回归试图学得一个线性模型\n$$f(x_i)=wx_i+b$$使得$f(x_i)\\simeq y_i$.\n推导过程 考虑使用MSE（均方差误差）来衡量模型好坏，则有损失函数$L(w,b)$:\n$$ \\begin{aligned} L(w,b) \u0026 =\\sum_{i=1}^m (f(x_i)-y_i)^2 \\\\ \u0026 =\\sum_{i-1}^m (y_i-wx_i-b)^2 \\end{aligned} $$目标函数为：\n$$ \\begin{aligned} (w^*,b^*) \u0026 =argmin_{(w,b)} L(w,b) \\\\ \u0026 =argmin_{(w,b)} \\sum_{i=1}^m (y_i-w_i-b)^2 \\end{aligned} $$将$L(w,b)$分别对$w,b$求偏导，并令导数为零联立可求解\n$$ \\begin{cases} \\nabla_w L(w,b) = 2(w\\sum_{i=1}^m x_i^2 -\\sum_{i=1}^m (y_i-b)x_i )=0 \\\\ \\nabla_n L(w,b) = a(mb-\\sum_{i=1}^m (y_i-wx_i))=0 \\end{cases} $$求解可得$w,b$的最优闭式解：\n$$w=\\frac{\\sum_{i=1}^m y_i(x_i-\\overline{x})}{\\sum_{i=1}^m x_i^2 - \\frac{1}{m}(\\sum_{i=1}^m x_i)^2}$$$$b=\\frac{1}{m}\\sum_{i=1}^m (y_i-wx_i)$$其中$\\overline{x}=\\frac{1}{m} \\sum_{i=1}^m x_i$为$x$的均值\nScikit-learn应用 具体API参考Scikit-learn官网： sklearn.linear_model.LinearRegression\nLogistic回归 这是一种分类方法\nLogistic分布：\n设X是连续随机变量，$\\mathbf{X}$服从逻辑斯蒂分布是指$\\mathbf{X}$具有下列分布函数和密度函数：\n$$ \\begin{cases} F(x)=P(X \\leq x)=\\frac{1}{1+e^{-(x-\\mu)/\\gamma}} \\\\ f(x)=F\\prime(x)=\\frac{e^{-(x-\\mu)/\\gamma}}{\\gamma(1+e^(x-\\mu)/\\gamma)^2} \\end{cases} $$式中$\\mu$为位置参数，$\\gamma \u003e 0$为形状参数\n二项逻辑斯蒂回归模型是如下的条件概率：\n$$P(Y=1|x)=\\frac{exp(w\\cdot x+b)}{1+exp(w\\cdot x+b)}$$$$P(Y=0|x)=\\frac{1}{1+exp(w\\cdot x+b)}$$这里$x\\in R^n$是输入，$Y\\in \\\\{0,1\\\\}$是输出，$w\\in R^n$为权值向量，$b\\in R$为偏置，$w\\cdot x$为內积.\n为了表达方便，将w,b进行合并可得：\n$$P(Y=1|x)=\\frac{exp(w\\cdot x)}{1+exp(w\\cdot x)}$$$$P(Y=0|x)=\\frac{1}{1+exp(w\\cdot x)}$$由前面这些假设和推论可得，逻辑斯蒂回归模型返回的是样本为正例的概率, 即$P(Y=1\\vert x)$\n推导过程 由极大似然函数有损失函数$L(w)$:\n$$L(w)=\\prod_{i=1}^N P(Y=1|x_i)^{y_i}[1-P(Y=0|x_i)]^{1-y_i}$$为防止因连乘而导致溢出，取对数可得：\n$$L(w)=\\sum_{i=1}^N [y_i\\log{P(Y=1|x_i)}+(1-y_i)\\log{1-P(Y=0|x_i)}]$$化简可得：\n$$L(w)=\\sum_{i=1}^N [y_i(w\\cdot x_i)-\\log{(1+exp(w\\cdot x_i))}]$$对$L(w)$求极大值，得到w的估计值。通常采用梯度下降法和拟牛顿法求解\nScikit-learn应用 sklearn.linear_model.LogistRegression\n参考 周志华的西瓜书，李航的统计学习方法\n","permalink":"https://simplestory.github.io/posts/20180813_135c495c1e86bceb067f1bc3135c278b/","summary":"\u003cblockquote\u003e\n\u003cp\u003e好久没更博客了，停了大半年，也看了大半年的机器学习和深度学习方面的书籍，现在是时候输出一下了\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch1 id=\"线性回归\"\u003e线性回归\u003c/h1\u003e\n\u003cp\u003e给定数据集$D=\\\\{(\\mathbf{x_1},y_1),(\\mathbf{x_2},y_2),...,(\\mathbf{x_m},y_m)\\\\}$，其中$\\mathbf{x_i}=(x_{i1};x_{i2};...;x_{id}), \\ y_{i} \\in R$. 线性回归试图学得一个线性模型\u003c/p\u003e","title":"线性回归和Logistic回归"},{"content":" G++是Unix/Linux下C++的编译器，gdb则是Unix/Linux下有力的调试工具。大多数时候，我们编写的C/C++代码并不是运行在Windows上，而是在Linux系统上跑。这里我记录了一些有关于g++和gdb的一些使用方法以备不时之需。\ng++的使用 g++在执行编译工作的时候，总共需要4步：\n预处理，生成.i的文件，主要是头文件和宏定义的展开：g++ -E test.cpp -o test.i 编译，将预处理后的文件转换成汇编语言，生成文件.s: g++ -S test.i -o test.s 汇编，有汇编变为目标代码(机器代码)生成.o的文件，主要是将.s文件转为二进制文件: g++ -c test.s -o test.o 链接，连接目标代码,生成可执行程序: g++ test.o -o test 以上步骤也可以用一条命令解决：\ng++ test.cpp -o test\n当涉及多个文件的编译时，有以下两种做法：\n$ g++ a.cpp b.cpp c.cpp -o test #一次性编译所有文件 $ g++ a.cpp -o a.o #分开编译 $ g++ b.cpp -o b.o $ g++ c.cpp -o c.o $ g++ a.o b.o c.o -o test 这里建议采用分开编译的方法，这样当你对其中一个或多个文件进行修改时，只需要对应的编译那一个或多个文件，而不需要从头开始编译。\n常用的g++选项：\n命令 描述 -Wall 编译时输出一些警告和错误信息 -c 只编译不连接 -E 对源文件进行预处理 -S 将预处理后的文件转为汇编文件 -o 指定输出文件名 -I 指定一个包含头文件的路径 -l 指定一个附加库 -L 指定一个附加的库路径 -shared 生成动态库文件 Linux下文件的类型是不依赖于其后缀名的，但一般来讲： .o是目标文件，相当于windows中的.obj文件 .so为共享库,是shared object,用于动态连接的，和.dll差不多 .a为静态库,是好多个.o合在一起，用于静态连接 .la为libtool自动生成的一些共享库，主要记录了一些配置信息。\ngdb的使用 .cpp在编译时要加上-g选项，生成的可执行文件才能用gdb进行源码级调试\ng++ -g test.cpp -o test\n值得注意的是，-g分4个等级：\ng0等于不加-g。即不包含任何信息 g1只包含最小信息，一般来说只有你不需要debug，只需要backtrace信息，并且真的很在意程序大小，或者有其他保密/特殊需求时才会使用-g1。 g2为gdb默认等级，包含绝大多数你需要的信息。 g3包含一些额外信息，例如包含宏定义信息。当你需要调试宏定义时，请使用-g3 一般来说，gdb主要帮助你完成下面四个方面的功能：\n启动程序，可以按照自定义的要求运行程序。\n可以让调试程序在指定的位置的断点处停止。\n当程序停止时，可以检查此时程序中所发生的事情。\n动态的改变程序的执行环境。\n几种方法在gdb下运行程序：\ngdb ${程序} 进入gdb后，输入run(简写r) ${arg1} ${arg2} … ${argN} gdb --args ${程序} ${arg1} ${arg2} … ${argN}进入gdb后，运行run 进入gdb后，输入file ${程序}。然后使用set args ${arg1} ${arg2} … ${argN}设定好程序参数，再运行run 常用gdb调试命令：\n命令 描述 start 开始执行程序，停在主函数第一行语句前面 backtrace/bt 查看各级函数调用，显示栈信息 step/s 单步调试，步入当前函数 next/n 单步调试，步过当前函数 until/u 执行到当前循环完成 continue/c 继续运行程序 finish 执行到当前函数返回 list/l 列出源代码，一次10行 list \u0026lt;行号\u0026gt; 列出从第几行开始的源代码 list \u0026lt;函数名\u0026gt; 列出函数源代码 print/p \u0026lt;表达式\u0026gt; 打印表达式的值 frame/f x 切换到第x帧,其中x会在bt命令中显示，从0开始，0表示栈顶 up/down x 往栈顶/栈底移动x帧，当不输入x时，默认为1 info/i locals 打印当前栈帧的本地变量 set var x 修改当前x变量的值 break \u0026lt;行号\u0026gt; 源文件指定行号设置断点 break \u0026lt;函数名\u0026gt; 源文件指定函数设置断点 break \u0026lt;行号/函数名\u0026gt; if \u0026lt;条件\u0026gt; 设置条件断点，满足条件时则在设置处断开 watch \u0026lt;变量\u0026gt; 写监视指定变量，当变量发生变化时停止程序运行 rwatch \u0026lt;变量\u0026gt; 读监视指定变量，当变量被读取时停止程序运行 awatch \u0026lt;变量\u0026gt; 读写监视指定变量，当变量发生变化或被读取时程序停止运行 delete \u0026lt;行号/函数名\u0026gt; 删除断点 info/i b 查看断点信息 save breakpoint \u0026lt;文件名\u0026gt;.dp 断点保存在指定文件中 -x \u0026lt;文件名\u0026gt;.dp 该选项读取指定文件，恢复文件中的断点设置 quit 退出gdb调试 参考 macky0668——linux下c++的编译器g++的基本使用\n爱折腾的西山居士——GDB常用命令系列\n改变自己chenyu——linux之gdb基本调试命令和使用总结\ngdb 调试入门，大牛写的高质量指南\n","permalink":"https://simplestory.github.io/posts/20180302_5ed219da904f7b6718ebebb4c8b47d3c/","summary":"\u003cblockquote\u003e\n\u003cp\u003eG++是Unix/Linux下C++的编译器，gdb则是Unix/Linux下有力的调试工具。大多数时候，我们编写的C/C++代码并不是运行在Windows上，而是在Linux系统上跑。这里我记录了一些有关于g++和gdb的一些使用方法以备不时之需。\u003c/p\u003e\n\u003c/blockquote\u003e","title":"g++与gdb使用"},{"content":" 2018新春之际，鬼知道我经历了些什么，在大年初四推博文。Django其实是在新年前配置好的，当时刚好学到了python做web开发方面的知识就顺手配置了。\n配置使用环境\n系统：Ubuntu16.04(LTS) python版本：python3.6（使用Anaconda建立虚拟环境） 准备工作 卸载旧版本Django 如果你从一个旧版本的Django进行升级，那你需要稍微处理一下旧版本的Django。\n之前版本的Django是通过pip或easy_install安装的，再一次执行pip或easy_install安装时会自动处理旧版的Django，所以你不用进行处理可以直接进行安装。 之前是通过python setup.py install安装的，卸载时就是从python包中删除Django库，Django库的路径可以使用以下命令查找： $ python -c \u0026#34;import django; print(django.__path__)\u0026#34; 安装Apache（可选） 如果是将Django用于实际环境而非测试开发环境，则需要安装Apache和mod_wsgi。安装Apache之前需要安装APR、APR-Util和Pcre。\n安装APR： 下载APR安装包,解压后在解压文件夹（文件夹的名字不要包含版本号）里执行以下命令安装：\n$ ./configure --prefix=/usr/local/apr $ make $ make install 安装APR-Util： 同样下载APR-Util安装包，解压后在解压文件夹（文件夹的名字不要包含版本号）里执行以下命令安装：\n$ ./configure --prefix=/usr/local/apr-util --with-apr=/usr/local/apr $ make $ make install 安装Pcre： 下载Pcre安装包，注意是pcre而非pcre2。之后解压并在解压文件夹中执行：\n$ ./configure --prefix=/usr/local/pcre $ make $ make install 安装Apache2： 下载Apache2安装包，解压后进入文件中执行以下命令：\n$ ./configure --prefix=/usr/local/apache2 --with-apr=/usr/local/apr --with-apr-util=/usr/local/apr-util/ --with-pcre=/usr/local/pcre $ make $ make install 安装mod_wsgi 使用以下命令安装：\n$ sudo apt-get install apache2-dev $ sudo pip install mod_wsgi 安装Django 首先创建虚拟环境：\n$ conda create -n django_py36 python=3.6 在虚拟环境中安装Django：\n(django_py36)$ sudo pip install Django 至此，Django安装结束。\n参考 Django Documentation\nApache文档\nfresheer的博客——ubuntu下安装 Apache and mod_wsgi\n","permalink":"https://simplestory.github.io/posts/20180219_c851e812699d1207837d404c0af593ce/","summary":"\u003cblockquote\u003e\n\u003cp\u003e2018新春之际，鬼知道我经历了些什么，在大年初四推博文。Django其实是在新年前配置好的，当时刚好学到了python做web开发方面的知识就顺手配置了。\u003c/p\u003e\n\u003c/blockquote\u003e","title":"Ubuntu配置Django"},{"content":" 套用TensorFlow中文社区的一句话：TensorFlow，一个用于人工智能的开源神器。\n配置使用环境\n系统：Ubuntu16.04(LTS) python版本：python3.6（使用Anaconda建立虚拟环境） Nvidia卡：Geforce 920M（如果没有Nvidia卡，则只能安装CPU版本） Tensorflow安装 tensorflow分为两个CPU/GPU两个版本，我建立了两个python虚拟环境，分别安装了这两个版本。在此记录一下安装的痛苦过程\ntensorflow CPU 建立虚拟环境 conda create -n tensorflow python=3.6 source activate tensorflow #进入虚拟环境 安装tensorflow CPU版本的安装还是比较简单的，直接使用pip安装即可 pip install tensorflow 若安装报错，可选择下载安装： 到pypi选择相应版本下载,在文件放置路径下执行以下命令:\n#这里文件名字以自己的为准 pip install tensorflow-1.6.0rc0-cp36-cp36m-manylinux1_x86_64.whl 验证安装 进入pytohn环境中,进行测试:\n$ python \u0026gt;\u0026gt;\u0026gt; import tensorflow as tf \u0026gt;\u0026gt;\u0026gt; hello = tf.constant(\u0026#39;Hello TensorFlow!\u0026#39;) \u0026gt;\u0026gt;\u0026gt; sess = tf.Session() \u0026gt;\u0026gt;\u0026gt; print(sess.run(hello)) Hello Tensorflow! \u0026gt;\u0026gt;\u0026gt; a = tf.constant(12) \u0026gt;\u0026gt;\u0026gt; b = tf.constant(23) \u0026gt;\u0026gt;\u0026gt; print(sess.run(a+b)) 35 \u0026gt;\u0026gt;\u0026gt; 或导入tensorflow模块并运行sess = tf.Session()，输出大致如下：\n至此，tensorflow CPU版本安装完成\ntensorflow GPU 这将会是一个苦恼的过程，出错率很高，我折腾了两天才把这个GPU版本装上去了。\n建立虚拟环境 conda create -n tensorflow_gpu python=3.6 安装tensorflow GPU版本之前要向安装GUDA和cuDNN。CUDA是一种由NVIDIA推出的通用并行计算架构，该架构使GPU能够解决复杂的计算问题。 它包含了CUDA指令集架构（ISA）以及GPU内部的并行计算引擎。cuDNN则是用于深度学习神经网络的GPU加速库，它强调性能、易用性和低内存开销。NVIDIA cuDNN可以集成到更高级别的机器学习框架中。\n这里有个建议：\n大家可以先将CUDA文档下载下来，但是不要急于安装，先将NVIDIA给出的官方指导手册仔细看一下，然后再找几篇好的博客看一下，大致了解一下CUDA的安装过程，对安装过程中可能出现的问题要大致有一个了解。\n安装条件 验证电脑是否有支持CUDA的GPU: 在终端下输入命令lspci | grep -i nvidia，查看输出。如果没有输出，可以尝试update-pciids（该命令一般在/sbin目录下）更新一下电脑的PCI硬件数据库，之后再次输入之前的查询命令。最后输出类似下图，如果你的GPU是来自Nvidia制造商且在CUDA列表中，则表示你拥有一块支持CUDA的GPU。\n验证Linux版本是否支持CUDA: 用命令uname -m \u0026amp;\u0026amp; cat /etc/*release可进行查看(ubuntu16.04受支持)\n验证系统是否安装gcc: 输入命令gcc --version可查看gcc版本，若无安装，按终端提示进行安装即可。\n验证系统是否安装了匹配的kernel header和package development： uname -r命令可查看内核版本，之后可运行命令sudo apt-get install linux-headers-$(uname -r)进行安装，假如你已经安装了，终端会提示并终止安装。\n以上各项均符合时，即可开始安装CUDA。\n安装CUDA CUDA提供两种安装方式：package manager安装和runfile安装。我在其他博客上看到大多数都选择使用runfile这种安装方式，我也尝试过几次，当最终都失败了，两天的安装时间，大部分都耗在安装CUDA上了。最后我选择package manager安装方式。\n下载CUDA包： 首先在CUDA下载页面上选择CUDA版本,我选择的是CUDA 9.0版本\n然后按照页面指示的安装步骤进行安装\n页面中还有一个path1包是CUDA9.0的补丁,安装好CUDA9.0后可以使用dpkg -i命令进行安装\n配置CUDA: 以上步骤结束后，还需要配置系统环境变量，用超级权限编辑家目录下的.bashrc文件，将以下内容添加至文件中，保存退出。\nexport PATH=\u0026#34;/usr/local/cuda-9.0/bin${PATH:+:${PATH}}\u0026#34; export LD_LIBRARY_PATH=\u0026#34;/usr/local/cuda-9.0/lib64${LD_LIBRARY_PATH:+:${LD_LIBRARY_PATH}}\u0026#34; 之后用命令source ~/.bashrc重新载入该文件。\n验证CUDA的安装 查看CUDA版本号： 在终端中输入nvcc --version查看输出，若输出为相应的版本号，可进行下一步骤，否则应该卸载并重新安装\n卸载命令：\nsudo /usr/local/cuda-9.0/bin/uninstall_cuda_9.0.pl（默认情况下，CUDA卸载脚本位于/usr/local/cuda-9.0/bin目录下）；\nsudo /usr/bin/nvidia-uninstall 卸载nvidia驱动及其配置。\n编译CUDA提供的例子： 一般情况下，CUDA提供的例子放在/usr/local/cuda-9.0/samples目录下，可以在该目录下进行编译，也可以将整个目录拷贝到其他地方进行编译。\n在samples/下执行命令make进行编译，编译成功会显示Finished building CUDA samples\n编译好后，进入目录/samples/bin/x86_64/linux/release，执行./deviceQuery，结果大致如下：\n然后检查一下系统和CUDA-Capable device的连接情况，执行./bandwidthTest命令，输出结果大概如下：\n若以上各项均通过，恭喜你，成功安装了CUDA！接下来是安装cuDNN\n安装cuDNN 首先需要注册一个Nvidia官网帐号用于下载cuDNN包，之后选择相应的安装包下载（注意要适配所安装的CUDA版本）\n下载后用tar命令解压，解压后把相应的文件拷贝到对应的CUDA目录下即可\nsudo cp cuda/include/cudnn.h /usr/local/cuda-9.0/include/ sudo cp cuda/lib64/libcudnn* /usr/local/cuda-9.0/lib64/ sudo chmod a+r /usr/local/cuda-9.0/include/cudnn.h sudo chmod a+r /usr/local/cuda-9.0/lib64/libcudnn* 安装tensorflow GPU 进入虚拟环境：source activate tensorflow_gpu\n可以到pypi选择相应版本下载，之后用pip install安装。\n也可以直接用相应的安装包连接进行安装\n$ pip install https://pypi.python.org/pypi/tensorflow-gpu/tensorflow_gpu-1.6.0rc0-cp36-cp36m-manylinux1_x86_64.whl 验证安装 在虚拟环境下的python中导入tensorflow模块并运行sess = tf.Session()，输出大致如下：\nOK了，漫长的tensorflow GPU版本安装就结束了，接下来可以实践那些神奇的人工智能算法啦！\n参考 QLULIBIN——Ubuntu 16.04 上安装 CUDA 9.0 详细教程\nmultiangle——ubuntu16.04下安装CUDA，cuDNN及tensorflow-gpu版本过程\n安装过程可能会遇到的一些坑以及解决方法：\n会思考的鱼——Ubuntu16.04安装NVIDIA显卡驱动和CUDA时的一些坑与解决方案\nycszen——Ubuntu安装NVIDIA驱动(咨询NVIDIA工程师的解决方案)\n一些扩展内容：\nma_fighting——Ubuntu下如何查看CPU信息, 包括位数和多核信息\nZeroZone零域——NVIDIA GPU的计算能力 Compute Capability 一览\nsuperPershing——Ubuntu系列切换Intel和NVIDIA显卡\n","permalink":"https://simplestory.github.io/posts/20180204_68b9e972b5a534bc31d12d9be53b57ec/","summary":"\u003cblockquote\u003e\n\u003cp\u003e套用\u003ca href=\"http://www.tensorfly.cn/\"\u003eTensorFlow中文社区\u003c/a\u003e的一句话：TensorFlow，一个用于人工智能的开源神器。\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003cp\u003e\u003cstrong\u003e配置使用环境\u003c/strong\u003e\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e系统：Ubuntu16.04(LTS)\u003c/li\u003e\n\u003cli\u003epython版本：python3.6（使用Anaconda建立虚拟环境）\u003c/li\u003e\n\u003cli\u003eNvidia卡：Geforce 920M（如果没有Nvidia卡，则只能安装CPU版本）\u003c/li\u003e\n\u003c/ul\u003e\n\u003ch1 id=\"tensorflow安装\"\u003eTensorflow安装\u003c/h1\u003e\n\u003cp\u003etensorflow分为两个CPU/GPU两个版本，我建立了两个python虚拟环境，分别安装了这两个版本。在此记录一下安装的痛苦过程\u003c/p\u003e","title":"Linux配置Tensorflow"},{"content":" 以下是Linux机器的渗透测试备忘录，我无意间在网上看到的，便搬运到这边。\nLinux机器渗透测试 命令 描述 netstat -tulpn 在Linux中显示对应于ID（PID）的网络端口 watch ss -stplu 通过套接字实时观察TCP，UDP端口 lsof -i 显示确认了的连接 macchanger -m MACADDR INTR 在KALI Linux上修改MAC地址 ifconfig eth0 192.168.2.1/24 在Linux中设置ID地址 ifconfig eth0:192.168.2.3/24 在Linux中向现有的网络接口添加IP地址 ifconfig eth0 hw ether MAC ADDR 使用ifconfig修改Linux中的地址 ifconfig eth0 mtu 1500 使用ifconfig修改MTU的大小，将1500改为想要的MTU dig -x 192.168.1.1 对IP地址进行反向查找 host 192.168.1.1 在一个IP地址上进行反向查找，适用于没有安装dig的情况 dig @192.168.2.2 domain.com -t AXFR 使用dig执行一次DNS区域传输 host -l domain.com nameserver 使用host执行一次DNS区域传输 nbtstat -A x.x.x.x 获取IP地址对应的域名 ip addr add 192.168.2.22/24 dev eth0 添加一个隐藏的IP地址，执行ifconfig时不会显示这个IP地址 tcpkill -9 host goole.com 阻止从主机访问goole.com echo \u0026quot;1\u0026quot;\u0026gt;/proc/sys/net/ipv4/ip_forward 启用IP转发，将Linux盒子变成一个路由器——这样就方便通过这个盒子进行路由流量的控制 echo \u0026quot;8.8.8.8\u0026quot;\u0026gt;/etc/resolv.conf 使用Goole的DNS 系统信息命令 对于本地的枚举检查有很大的帮助\n命令 描述 whoami 显示Linux上当前已登录用户 id 向用户显示当前已登录的用户和组 last 显示最后一次登录的用户 mount 显示已挂载的驱动 df -h 用人类可读的输出显示磁盘使用情况 echo \u0026quot;user:passwd\u0026quot; | chpasswd 用一行命令重置密码 getent passwd 列出Linux上的用户 strings /usr/local/bin/blah 显示非文本文件的内容，例如：一个二进制文件里面有什么 uname -ar 显示运行中的内核版本 PATH=$PATH:/my/new-path 添加一个新的路径，方便进行蹦迪文件系统（FS)操作 history 显示用户在之前执行的bash脚本历史记录，还有敲入的命令 基于Redhat/CentOS/RPM的发行版 命令 描述 cat /etc/redhat-release 显示Redhat/CentOS版本号 rpm -qa 在基于RPM的Linux上列出所有已经安装上的RPM包 rpm -q --changelog openvpn 检查已安装的RPM是否针对CVE打了补丁，可以用grep命令过滤出跟CVE有关的输出 YUM命令 命令 描述 yum update 使用YUM更新所有的RPM包，也会显示出哪些已经过时了 yum update httpd 更新单独的包，在此例中是HTTPD（Apache） yum install package 使用YUM安装一个包 yum --exclude=package kernel* update 在使用YUM时将一个包排除在外不更新 yum remove package 使用YUM删除包 yum erase package 使用YUM删除包 yum list package 列出有关YUM包的信息 yum provides httpd 显示一个包的用途 yum info httpd 显示包信息，架构，版本等信息 yum local install blah.rpm 使用YUM来安装本地RPM，从资源库进行安装 yum deplist package 显示包的提供方信息 yum list installed | more 列出所有已安装的包 yum grouplist | more 显示所有的YUM分组 yum groupinstall \u0026#39;Development Tools\u0026#39; 安装YUM分组 基于Debian/Ubuntu/.deb的发行版 命令 描述 cat /etc/debian_version 显示Debian版本号 cat /etc/*-release 小时Ubuntu版本号 dpkg -l 在基于Debian/.deb的Linux发行版列出所有已安装的包 Linux用户管理 命令 描述 useradd new-user 创建一个新的Linux用户 passwd username 重置Linux用户密码，如果你是root用户，只要输入密码即可 userdel username 删除一个Linux用户 用户创建命令 adduser：会自动为创建的用户指定主目录、系统shell版本，会在创建时输入用户密码 useradd：需要使用参数选项指定上述基本设置，如果不使用任何参数，则创建的用户无密码、无主目录、没有指定shell版本\n用户删除命令 userdel：删除用户，添加选项-r可以删除与用户相关的所有文件，-f可强制删除用户，即使用户处于登录状态 deluser：将一个用户从一个组中删除（即退出用户组）deluser username usergroup\nLinux解压缩命令 命令 描述 unzip archive.zip 在Linux上提取zip包中的文件 zipgrep *.txt archive.zip 在一个zip压缩包中搜索 tar xf archive.tar 在Linux上提取tar包中的文件 tar xvzf archive.tar.gz 在Linux上提取tar.gz包中的文件 tar xjf archive.tar.bz2 在Linux上提取tar.bz2包中的文件 tar ztvf file.tar.gz | grep blah 在一个tar.gz文件中搜索 gzip -d archive.gz 在Linux上提取gzip中的文件 zcat archive.gz 在Linux上以不解压缩的方式读取一个.gz文件 zless archive.gz 用较少的命令实现对.gz压缩包相同的功能 zgrep \u0026#39;blah\u0026#39; /var/log/maillog*.gz 在Linux上对.gz压缩包里面的内容执行搜索，比如搜索被压缩过的日志文件 vim file.txt.gz 使用vim读取.txt.gz文件 Linux压缩命令 命令 描述 zip -r file.zip /dir/* 在Linux上创建一个.zip文件 tar cf archive.tar files 在Linux上创建一个.tar文件 tar czf archive.tar.bz2 files 在Linux上创建一个tar.gz文件 tar cjf archive.tar.bz2 files 在Linux上创建一个tar.bz2文件 gzip file 在Linux上创建一个.gz文件 upx -9 -o output.exe input.exe 在Linux上使用UPX压缩.exe文件 Linux文件命令 命令 描述 df -h blah 在Linux上显示文件/目录的大小 diff file1 file2 在Linux上对比/显示两个文件之间的差别 md5sum file 在Linux上生成MD5摘要 md5sum -c blah.iso.md5 在Linux上检查文件的MD5摘要,这里假设文件和.md5处在相同的路径下 file blah 在Linux上查找出文件的类型,也会将文件是32还是64位显示出来 dos2unix 将Windows的行结束符转成Unix/Linux的 base64 input-file output-file 对输入文件进行Base64编码,然后输出一个叫做output-file的Base64编码文件 base64 -d input-file output-file 对输入文件进行Base64解码,然后输出一个叫做output-file的Base64解码文件 touch -r ref-file new-file 使用来自应用文件的时间戳数据创建一个新文件,放上-r以简单地创建一个文件 rm -rf 不显示确认提示就删除文件和目录 Misc命令 命令 描述 init 6 从命令行重启Linux gcc -o output.c input.c 编译C代码 gcc -m32 -o output.c input.c 交叉编译C代码,在64为Linux上将编译出32位的进制文件 unset HISTORYFILE 关闭bash历史日志记录功能 rdesktop x.x.x.x 从Linux连接到RDP服务器 kill -9 $$ 关掉当前的会话 chown user:group blah 修改文件或者目录的所有者 chown -R user:group blah 修改文件或者目录,以及目录下面文件/目录的拥有者——递归执行chown chmod 600 file 修改文件/目录的权限设定，详情见Linux文件系统权限 清除bash历史 $ ssh user@x.x.x.x | cat /dev/null \u0026gt; ~/.bash_history .bash_history文件可保存多条历史记录,可通过设置HISTFILESIZE和HISTSIZE参数来减少保存的命令数。\n两个参数的意义：\nHISTSIZE: 定义了history命令输出的命令数 HISTFILESIZE: 定义了.bash_history文件保存的命令数 $ HISTFILESIZE=\u0026lt;num\u0026gt; $ HISTSIZE=\u0026lt;num\u0026gt; 命令history -c可以清空本次输出的命令，但不清空.bash_history文件，所以下次登陆后，旧命令还将出现。\nLinux文件权限 取值 意义 777 rwxrwxrwx 没有限制，完全可读可写可执行（RWX），用户可以做任何事情 755 rwxr-xr-x 拥有这可完全访问，其他人只能读取和执行 700 rwx------ 拥有者可完全访问，其他人都不能访问 666 rw-rw-rw- 所有人可以读取和写入，但不可执行 644 rw-r--r-- 拥有者可以读取和写入，其他人只可以读取 600 rw------- 拥有者可以读取和写入，其他人都不能访问 Linux文件系统的渗透测试备忘录 路径 描述 / /也被称为“斜杠”或者根 /bin 由系统、系统管理员以及用户共享的通用程序 /boot Boot文件，启动加载器（grub），内核，vmlinuz /dev 包含了对系统设备、带有特殊属性的文件的引用 /etc 重要的系统配置文件 /home 系统用户的主目录 /lib 库文件，包括系统和用户都需要的所有类型的程序的文件 /lost+found 文件操作失败会被保存在这里 /mnt 外部文件系统的标准挂载点 /media 外部文件系统（或者某些发行版）的挂载点 /net 整个远程文件系统的标准挂载点——nfs /opt 一般都是包含一些附加的或者第三方软件 /proc 一个包含了系统资源相关信息的虚拟文件系统 /root root用户的主目录 /sbin 由系统和系统管理员来使用的程序 /tmp 供系统使用的临时空间，重启时会被清空 /usr 供所有用户相关程序使用的程序、库、文档等等 /var 存储所有由用户创建的可变文件和零食文件，比如日志文件、邮件队列、后台打印程序、Web服务器、数据库等等 Linux中一些有趣的文件/目录 对特权升级/执行后期开发有较大帮助\n路径 描述 /etc/passwd 包含了本地Linux的用户 /etc/shadow 包含了哈希过的本地账户密码 /etc/group 包含了本地账户分组 /etc/init.d/ 包含了服务网初始化脚本——具体都安装了写什么值得一看 /etc/hostname 系统的hostname /etc/network/interfaces 网络接口 /etc/resolv.conf 系统的DNS服务 /etc/profile 系统的环境变量 ~/.ssh/ SSH密钥 ~/.bash_history 用户的bash历史日志 /var/log/ Linux系统的日志文件一般被存放在这里 /var/adm/ UNIX系统的日志文件一般就被存放在这里 /var/log/apache2/access.log /var/log/httpd/access.log Apache访问日志文件通常的存放路径 /etc/fstab 挂载的文件系统 突破shell限制 Python小技巧:\npython -c \u0026#39;import pty;pty.spawn(\u0026#34;/bin/bash\u0026#34;)\u0026#39; echo os.system(\u0026#34;/bin/bash\u0026#34;) /bin/sh -i Samba命令 从Linux连接到Samba共享:\n$ smbmount //server/share /mnt/win -o user=username,password=password1 $ smbclient -U user \\\\\\\\server\\\\share $ mount -t cifs -o username=user,password=password //x.x.x.x/share /mnt/share 参考 Linux爱好者\n","permalink":"https://simplestory.github.io/posts/20180120_3e2c28e497c306d34f88081093e32c89/","summary":"\u003cblockquote\u003e\n\u003cp\u003e以下是Linux机器的渗透测试备忘录，我无意间在网上看到的，便搬运到这边。\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch2 id=\"linux机器渗透测试\"\u003eLinux机器渗透测试\u003c/h2\u003e\n\u003ctable\u003e\n\u003cthead\u003e\n\u003ctr\u003e\n\u003cth style=\"text-align:left\"\u003e命令\u003c/th\u003e\n\u003cth style=\"text-align:left\"\u003e描述\u003c/th\u003e\n\u003c/tr\u003e\n\u003c/thead\u003e\n\u003ctbody\u003e\n\u003ctr\u003e\n\u003ctd style=\"text-align:left\"\u003enetstat -tulpn\u003c/td\u003e\n\u003ctd style=\"text-align:left\"\u003e在Linux中显示对应于ID（PID）的网络端口\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd style=\"text-align:left\"\u003ewatch ss -stplu\u003c/td\u003e\n\u003ctd style=\"text-align:left\"\u003e通过套接字实时观察TCP，UDP端口\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd style=\"text-align:left\"\u003elsof -i\u003c/td\u003e\n\u003ctd style=\"text-align:left\"\u003e显示确认了的连接\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd style=\"text-align:left\"\u003emacchanger -m MACADDR INTR\u003c/td\u003e\n\u003ctd style=\"text-align:left\"\u003e在KALI Linux上修改MAC地址\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd style=\"text-align:left\"\u003eifconfig eth0 192.168.2.1/24\u003c/td\u003e\n\u003ctd style=\"text-align:left\"\u003e在Linux中设置ID地址\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd style=\"text-align:left\"\u003eifconfig eth0:192.168.2.3/24\u003c/td\u003e\n\u003ctd style=\"text-align:left\"\u003e在Linux中向现有的网络接口添加IP地址\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd style=\"text-align:left\"\u003eifconfig eth0 hw ether MAC ADDR\u003c/td\u003e\n\u003ctd style=\"text-align:left\"\u003e使用ifconfig修改Linux中的地址\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd style=\"text-align:left\"\u003eifconfig eth0 mtu 1500\u003c/td\u003e\n\u003ctd style=\"text-align:left\"\u003e使用ifconfig修改MTU的大小，将1500改为想要的MTU\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd style=\"text-align:left\"\u003edig -x 192.168.1.1\u003c/td\u003e\n\u003ctd style=\"text-align:left\"\u003e对IP地址进行反向查找\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd style=\"text-align:left\"\u003ehost 192.168.1.1\u003c/td\u003e\n\u003ctd style=\"text-align:left\"\u003e在一个IP地址上进行反向查找，适用于没有安装dig的情况\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd style=\"text-align:left\"\u003edig @192.168.2.2 domain.com -t AXFR\u003c/td\u003e\n\u003ctd style=\"text-align:left\"\u003e使用dig执行一次DNS区域传输\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd style=\"text-align:left\"\u003ehost -l domain.com nameserver\u003c/td\u003e\n\u003ctd style=\"text-align:left\"\u003e使用host执行一次DNS区域传输\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd style=\"text-align:left\"\u003enbtstat -A x.x.x.x\u003c/td\u003e\n\u003ctd style=\"text-align:left\"\u003e获取IP地址对应的域名\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd style=\"text-align:left\"\u003eip addr add 192.168.2.22/24 dev eth0\u003c/td\u003e\n\u003ctd style=\"text-align:left\"\u003e添加一个隐藏的IP地址，执行ifconfig时不会显示这个IP地址\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd style=\"text-align:left\"\u003etcpkill -9 host goole.com\u003c/td\u003e\n\u003ctd style=\"text-align:left\"\u003e阻止从主机访问goole.com\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd style=\"text-align:left\"\u003eecho \u0026quot;1\u0026quot;\u0026gt;/proc/sys/net/ipv4/ip_forward\u003c/td\u003e\n\u003ctd style=\"text-align:left\"\u003e启用IP转发，将Linux盒子变成一个路由器——这样就方便通过这个盒子进行路由流量的控制\u003c/td\u003e\n\u003c/tr\u003e\n\u003ctr\u003e\n\u003ctd style=\"text-align:left\"\u003eecho \u0026quot;8.8.8.8\u0026quot;\u0026gt;/etc/resolv.conf\u003c/td\u003e\n\u003ctd style=\"text-align:left\"\u003e使用Goole的DNS\u003c/td\u003e\n\u003c/tr\u003e\n\u003c/tbody\u003e\n\u003c/table\u003e\n\u003ch2 id=\"系统信息命令\"\u003e系统信息命令\u003c/h2\u003e\n\u003cp\u003e对于本地的枚举检查有很大的帮助\u003c/p\u003e","title":"Linux渗透测试命令"},{"content":"Server version: 5.7.20-0ubuntu0.16.04.1 (Ubuntu)\n进入Mysql后,可用如下命令查询相关文件的储存路径:\nshow variables like \u0026#39;%dir%\u0026#39;; 输出如下图:\n通常有: 数据库文件默认位置为: /usr/share/mysql/ 配置文件: /etc/mysql/my.cnf\n数据库目录: /var/lib/mysql 配置文件: /etc/share/mysql(mysql.server命令及配置文件) mysql相关命令: /usr/bin 启动脚本: /etc/init.d/\nmysql的一种安全启动方式: /usr/bin/mysqld_safe-user=root\u0026amp;\n","permalink":"https://simplestory.github.io/posts/20171201_7eec743fae4833726b0d923cf6db1da6/","summary":"\u003cp\u003e\u003cstrong\u003eServer version: 5.7.20-0ubuntu0.16.04.1 (Ubuntu)\u003c/strong\u003e\u003c/p\u003e\n\u003cp\u003e进入Mysql后,可用如下命令查询相关文件的储存路径:\u003c/p\u003e\n\u003cdiv class=\"highlight\"\u003e\u003cpre tabindex=\"0\" class=\"chroma\"\u003e\u003ccode class=\"language-sql\" data-lang=\"sql\"\u003e\u003cspan class=\"line\"\u003e\u003cspan class=\"cl\"\u003e\u003cspan class=\"k\"\u003eshow\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"n\"\u003evariables\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"k\"\u003elike\u003c/span\u003e\u003cspan class=\"w\"\u003e \u003c/span\u003e\u003cspan class=\"s1\"\u003e\u0026#39;%dir%\u0026#39;\u003c/span\u003e\u003cspan class=\"p\"\u003e;\u003c/span\u003e\u003cspan class=\"w\"\u003e\n\u003c/span\u003e\u003c/span\u003e\u003c/span\u003e\u003c/code\u003e\u003c/pre\u003e\u003c/div\u003e\u003cp\u003e输出如下图:\u003c/p\u003e\n\u003cp\u003e\u003cimg loading=\"lazy\" src=\"https://simplestory-blog-img.oss-cn-guangzhou.aliyuncs.com/in_posts/20171201/mysql.png\" title=\"mysql\"\u003e\u003c/p\u003e\n\u003cp\u003e通常有:\n数据库文件默认位置为: \u003ccode\u003e/usr/share/mysql/\u003c/code\u003e\n配置文件: \u003ccode\u003e/etc/mysql/my.cnf\u003c/code\u003e\u003c/p\u003e\n\u003cp\u003e数据库目录: \u003ccode\u003e/var/lib/mysql\u003c/code\u003e\n配置文件: \u003ccode\u003e/etc/share/mysql\u003c/code\u003e(mysql.server命令及配置文件)\nmysql相关命令: \u003ccode\u003e/usr/bin\u003c/code\u003e\n启动脚本: \u003ccode\u003e/etc/init.d/\u003c/code\u003e\u003c/p\u003e","title":"Linux下的MySQL数据文件路径"},{"content":" Linux下使用命令行是家常便饭，通常情况下，我们都会熟记四五十个命令，但有一些少用却有用的命令被大部分人遗漏了。这篇博文收集了其中的一小部分。\nsudo !! 有些命令在没有超级用户权限下运行是会报错的，之后可以用sudo !!来调用之前的命令。!!可以抓取最后的命令。\npython命令 我们可以通过简单的python命令生产一个通过HTTP显示文件夹结构树的简单网页，可以通过浏览器在端口8000访问，直到发出中断信号。\n$ python -m SimpleHTTPServer mtr mtr命令将ping和traceroute命令的功能结合在一起，mtr命令的使用前可能需要用apt/yum进行安装\n$ sudo apt install mtr #基于Debian系统 $ yum install mtr #基于Red Hat系统 Ctrl+x+e 为了使每天的任务自动化，管理员需要通过输入vi、vim、nano等打开编辑器。仅仅从命令行快速的敲击Ctrl-x-e，就可以在编辑器中开始工作了。\nnl nl命令添加文件的行数，即给指定文件添加行号。\nshuf 该命令随机从一个文件或文件夹中选择行/文件/文件夹。可以接在ls命令后面来随机抓取。\n$ ls|shuf -n1 #随机选择一个 $ ls|shuf -n2 #随机选择两个。以此类推 ss ss表示socket统计。这个命令调查socket，显示类似netstat命令的信息。它可以比其他工具显示更多的TCP和状态信息。\nlast 该命令显示的是上次登录用户的历史信息（通过搜索文件“/var/log/wtmp”，显示logged-in和logged-out及其tty‘s的用户列表。\ncurl ifconfig.me 该命令用于得到你的外部IP地址，使用curl可能需要安装相关包。\ntree 以树形结构显示当前文件夹。\npstree 显示当前运行命令的所有进程及相关子进程，输出显示为树形结构。\n\u0026lt;空格\u0026gt; 在终端键入的每个命令都会记录到history，可用history命令进行查看。在终端输入命令时可以在命令前加一个空格，这样你的命令就不会被记录到history。\nstat Linux中stat命令用来显示文件或文件系统的状态信息。使用文件名作为参数时，该命令返回文件的全部信息，包括文件大小、块、权限、访问时间、修改时间、状态改变时间等。\npv 该命令仿照任何模型风的文本输出，包括流水线输出。使用前可能需要先行安装pv包。\nmount | column -t 用一个规范的模式显示所有挂载文件系统。\nCtrl + l 这更像是快捷键，作用等同于clear。\nat 该命令与cron命令相似，也可以用于安排一个任务或者在某个特定时刻执行命令。\n$ echo \u0026#34;ls -l \u0026gt; /dev/pts/0\u0026#34; | at 14:012 $ echo \u0026#34;ls -l \u0026gt; /dev/pts/0\u0026#34; | at 2:12 PM du -h -max-depth=1 该命令以人类可读的形式输出当前目录的子目录的大小。这在检查系统使用率是非常有用的。\nexpr expr在终端中计算简单的算数。\nlook 在终端上从英文字典上查单词以防混淆。\n$ look car #显示字典上所有以’car‘开头`的`单词 yes 该命令会持续地输出给定的字符串，直至收到中断命令。这在脚本语言和系统管理时有很大用处。\nfactor 该命令会输出所有给定数字的因数。\nping -i n -a IP_address 在等待或者持续盯着你的终端等待命令的回应或者等待服务器的连接时。当服务器连通时会发出声音（确保你的系统不是静音的）。参数n是命令等待时间。\n#该命令等60秒PING一次 $ ping -i 60 -a www.google.com tac 该命令会以倒序输出文本文件内容，也就是从最后一行到第一行。\nstrace strace是一个调试工具并被主要用于Linux的故障排除。它可能在你的系统内没有默认安装，你可能需要apt 或者 yum 安装所需要的包。\n$ strace pwd #使用strace命令追踪一个命令的执行 具体有关strace的命令参数参考man页。\ndisown -a \u0026amp;\u0026amp; exit disown可以在后台持续运行任务，即使关闭了终端会话。\n$ \u0026lt;command\u0026gt; disown -a \u0026amp;\u0026amp; exit 为了在终端中再次脱离一个已经长期运行的任务（译注：可能已经后台运行了，但是并没有脱离终端），使用jobs命令来找出任务号，接着使用disown %n，这里的%n是作业号。为了验证作业确实在运行，使用ps 或者top命令。nohup命令也是一个disown命令的替代品。\ngetconf LONG_BIT 显示你的机器架构是32bit还是64bit。\n显示日期 这应该算是一个脚本，在终端提示符后输入如下的命令你就会在终端的右上角看到日期和时间。\n$ while sleep 1;do tput sc;tput cup 0 $(($(tput cols)-29));date;tput rc;done \u0026amp; watch -t -n1 \u0026ldquo;date + %T | figlet\u0026rdquo; 在使用之前，确保系统上已安装了figlet包。 该命令通过管道输出到figlet而在终端上显示一个动画电子钟。\nhost与dig host是一个DNS查询工具，dig命令用来解析域名，与nslookup相比可以获得更多的域名信息。\ndstat dstat是一个多用途的工具，它会依据系统资源生成统计。默认情况下你的系统可能没有安装dstat。在使用这个彩色的描述系统信息的生成器前使用apt 或者 yum来安装。\nbind -p 该命令会显示所有的BASH shell可用的快捷方式。\nlsb_release lsb_release命令会打印特殊发行版的信息。如果 lsb_release 还没安装，你可以在基于 Debain 的发行版中用命令 apt 安装，在基于 Red Hat 系统下用 yum 来安装包。\n注意：选项‘-a’，会显示有关版本、ID、详情、发行号和研发代号的全部可用信息。\nnc -zv localhost 参数num为任何端口号，该命令用来检查端口是否被打开或关闭。\ncurl ipinfo.io 该命令会输出并提供IP地址的地理位置。\nfind . -user root 该命令会输出当前目录下( root )用户所拥有的文件（译注：即owner为root）。下面是在当前目录下列出的所有 ‘root’用户拥有的文件。\nsudo apt-get build-dep ffmpeg 该命令会在相应的包安装时自动构建依赖关系。因此包安装的过程将非常流畅，也是非常容易的。\nlsof -iTCP: -sTCP:LISTEN 该命令会输出所用正在使用端口的进程/服务的名称。\npdftk pdftk用来合并几个pdf文件，使用前需确保系统已安装pdftk包。\nps -LF -u user_name 该命令会输出一个用户的进程和线程。选项“L”（列出线程），选项“-F”（完整格式化）\nstartx - :1 分享 X 会话。如果你需要频繁的（以不同用户身份）登入或登出桌面时，那就需要 startx 来救场。这个命令建立了一个新的会话从而避免了在一个会话中反复的登入和登出。为了在X会话间进行交换，我们可以通过ctrl+Alt+F7和ctrl+Alt+F8的组合键来完成。\n注意：快捷键ctrl+Alt+F1～F6是为了控制台会话准备的，而ctrl+Alt+F7～F12则为X会话服务。因此我们有6个控制台会话和6个X会话，不需要频繁的登入登出。上面的顺序适用于大多数的发行版，然而不同发行版可能会有不同的实现。\n参考 Linux.中国-开源社区\n","permalink":"https://simplestory.github.io/posts/20171107_619e2d5ba49593e40e16667bafdcf2c1/","summary":"\u003cblockquote\u003e\n\u003cp\u003eLinux下使用命令行是家常便饭，通常情况下，我们都会熟记四五十个命令，但有一些少用却有用的命令被大部分人遗漏了。这篇博文收集了其中的一小部分。\u003c/p\u003e\n\u003c/blockquote\u003e","title":"Linux一些命令"},{"content":" Opencv包含了超过2500个函数，几乎任何一个能想到的成熟算法都可以通过调用Opencv的函数来实现，超级方便。这篇博文结合了Python与Opencv来实现在监控视频中识别汽车并检测其是否违规（压双黄线）。\n系统环境：Ubuntu16.04LTS+OpenCV3.3.0+Python2.7.12\n背景提取 对于监控来说，其位置相对固定，所以背景在长时间内不会随时间的改变而产生相当大的改变。所以可以考虑使用背景差分法来粗略地提取出背景。\n在这里我使用的是Opencv中自带的KNN算法，该算法利用监控视频中前面一小部分作为训练帧来训练模型得到大概的背景图像。\n当然OpenCV已经实现了几种非常容易使用的算法。具体差异可以参考：背景减除\n# coding = utf-8 import numpy as np import cv2 cap = cv2.VideoCapture(\u0026#39;filename\u0026#39;) #指定训练帧数为30 trainframe = 30 #KNN算法背景差分 bgsubstractor = cv2.creatBackgroundSubtractorKNN(detectShadow = True) 预处理 对视频帧依据视频分辨率进行中值滤波处理。对不同的视频分辨率，使用不同的核参数。例如：视频分辨率为240X320，选用的核参数为5X5。\n中值滤波后将图像转为灰度图像进行高斯模糊，通过试验可以得到以适合的核参数。 之后是边缘检测，也是通过多次试验得到最低和最高阈值。 最后是进行形态学处理，腐蚀、膨胀等操作，在这里使用默认核，迭代次数为2。\n#对原始帧进行中值滤波处理 gray = fg_mask.copy() gray = cv2.medianBlur(gray, 11, 0) #图像预处理 thresh = cv2.threshold(gray, 244, 255, cv2.THRESH_BINARY)[1] thresh = cv2.erode(thresh, None, iterations = 2) thresh = cv2.dilate(thresh, None, iterations = 2) 运动目标检测 使用Opencv中的findContours()函数获取视频对应帧中的检测框。\n注意：应该使用视频帧的副本\n选用的轮廓检索方式为cv2.RETR_EXTERNAL,只返回最外的轮廓。轮廓近似方法为 cv2.CHAIN_APPROX_SIMPLE,返回检索框最少的关键点。\n#获取检测框 _, conts, _ = cv2.findContours(thresh.copy(), cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE) 运动目标筛选 这部分可以使用Opencv中的级联分类器来识别，但迫于没有足够的样本集和时间，在这里选用基于轮廓面积大小进行目标筛选的方法来区别目标与非目标。\n具体可参考：Opencv级联分类器目标检测\n#目标提取 #得到大概车辆的列表集合carlist carslist = [] cars = [] cars = sorted(conts, contoursSort) for car in cars: area = cv2.contourArea(car) if area \u0026gt; cv2.contourArea(cars[0])/2 and area \u0026gt; 400: carslist.append(car) 上面代码段中有一函数contoursSort()，这个使用来排序的。\n#轮廓面积排序规则函数 def contoursSort(c1, c2): return cv2.contourArea(c1) \u0026gt; cv2.contourArea(c2) 双黄线标记 可以使用霍夫变换得到较为接近的双黄线的位置。\n#霍夫变换 rho = 2 theta = np.pi/360 threshold = 15 min_line_length = 40 max_line_gap = 20 line_frame = np.copy(frame) * 0 lines = cv2.HoughLinesP(masked_edges, rho, theta, threshold, np.array([]), min_line_length, max_line_gap) #绘制图线 for x1,y1,x2,y2 in lines[0]: cv2.line(frame, (x1,y1), (x2,y2), (0,255,0), 2) 碰撞处理 先获得当前图框的坐标点,使用boundingRect函数得到图框左上角坐标值$(x,y)$及图框宽、高$(w,h)$。\n双黄线位置记为:左边黄线$x$坐标$x_b$,右边黄线$x$坐标$x_c$\n碰撞情况: 前进压双黄线: $x_b \u003c x \u003c x_c$ 或 $x_b \u003c x+w \u003c x_c$;\n静止或跨越压双黄线: $x \u003c x_b$ 并且 $x+w \u003e x_c$\n检测到碰撞情况后绘制违规车辆图框。分别计算轮廓的$x$方向上和$y$方向上的质心坐标,以此为基础选择适当距离画出一矩形框。\n#标记违规车辆（红色方框） #双黄线位置 x_b = 155 x_c = 160 for car in carslist: x, y, w, h = cv2.boundingRect(car) #前进压双黄线检测值 k1 = (x_b \u0026lt; x \u0026lt; x_c) or (x_b \u0026lt; (x + x+w)/2 \u0026lt; x_c) #静止或跨越压双黄线检测值 k2 = x \u0026lt; x_b and (x + x+w)/2 \u0026gt; x_c if k1 or k2: M = cv2.moments(car) #计算轮廓各属性 cx = int(M[\u0026#39;m10\u0026#39;]/M[\u0026#39;m00\u0026#39;]) #x方向上的质心 cy = int(M[\u0026#39;m01\u0026#39;]/M[\u0026#39;m00\u0026#39;]) #y方向上的质心 cv2.rectangle(frame, (cx-10, cy-10), (cx+20, cy+20), (0, 0, 255), 2) 保存视频 通常我们都会保存处理后的视频，可以通过预先构建VideoWriter类来实现。\nprename = raw_input(\u0026#39;文件名字(.avi): \u0026#39;) name = prename + \u0026#39;.avi\u0026#39; #获取原视频的帧率 fps = int(cap.get(cv2.CAP_PROP_FPS)) #获取原视频的尺寸 size = (int(cap.get(cv2.CAP_PROP_FRAME_WIDTH)), int(cap.get(cv2.CAP_PROP_FRAME_HEIGHT))) #设置视频格式 fourcc = cv2.VideoWriter_fourcc(*\u0026#39;MJPG\u0026#39;) out = cv2.VideoWriter(name, fourcc, fps, size) 总结 以上就是我对监控视频识别汽车违规的实现过程，这个过程十分难熬，但因为我的坚持，整个思路渐渐变得清晰，内心的烦躁也平息了不少。从开始对Python-Opencv的使用一脸懵逼到现在写了这篇入门级别的博文，我学到了不少，先不说那一堆什么形态学转换、边缘检测等图像和视频处理算法，百度/谷歌的技能也是突飞猛进的。从检测结果来看，这个程序并不是那么令人满意，还有很大的改进空间，例如增加阴影消除算法。\n参考 感谢万能的百度/Google。\n之后是一些参考文献：\nOpenCV官方教程（中文版）\n秦秀丽.基于 YUV 颜色空间和图论切割的阴影去除算法[D].武汉理工大学.2010\n王志勇.基于视频序列的交通违章监测系统设计[D].郑州大学.2015\n骆玉荣.安全带识别系统的研究与实现[D].北京工业大学.2008\n吴培敏.基于视频的人脸检测与识别研究[D].南昌大学.2011\n最后是几个博客/网站：\n谷歌首席程序员\n伯乐在线\nthefutureisour\nOpenCV Tutorials\n","permalink":"https://simplestory.github.io/posts/20171104_bb6e9cb8623b3bf044c62bbbca472d53/","summary":"\u003cblockquote\u003e\n\u003cp\u003eOpencv包含了超过2500个函数，几乎任何一个能想到的成熟算法都可以通过调用Opencv的函数来实现，超级方便。这篇博文结合了Python与Opencv来实现在监控视频中识别汽车并检测其是否违规（压双黄线）。\u003c/p\u003e\n\u003c/blockquote\u003e","title":"Opencv实现汽车违规检测识别"},{"content":" 近期由于项目需要，要用到计算机视觉识别，所以折腾起了OpenCV，虽然安装过程曲折漫长，但是配合Python使用起来体验还是相当不错的。在此记录OpenCV安装编译过程。\n系统环境：Ubuntu16.04\n安装软件：OpenCV3.3.0+Python2.7.12\n更新系统安装包： $ sudo apt update $ sudo apt upgrade 安装一些开发者工具： $ sudo apt-get install build-essential cmake pkg-config 其中pkg-config包很有可能已经默认安装了，以防万一，还是将它添加进去。cmake程序是用来之后编译OpenCV的。\n$ sudo apt-get install libjpeg8-dev libtiff5-dev $ sudo apt-get install libjasper-dev libpng12-dev 以上安装包用于OpenCV处理JPEG，PNG，TIFF等图片。对于视频，可以用以下安装的包进行逐帧处理。\n$ sudo apt-get install libavcodec-dev libavformat-dev libswscale-dev $ sudo apt-get install libv4l-dev libxvidcore-dev libx264-dev 处理OpenCV GUI操作的模块highgui依赖于GTK库，所以还要安装GTK库\n$ sudo apt-get install libgtk-3-dev 安装优化模块\n$ sudo apt-get install libatlas-base-dev gfortran 安装Python开发相关文件和库\n$ sudo apt-get install python2.7-dev python3.5-dev 下载OpenCV源代码： $ cd ~ $ wget -O opencv.zip https://github.com/Itseez/openCV/archive/3.3.0.zip $ unzip opencv.zip 可以检查OpenCV官方在Github上推出的新版本来代替wget后面.zip前的版本号\n传送门：OpenCV Github\n还需要下载opencv_contrib库\n$ wget -O opencv_contrib.zip https://github.com/Itseez/opencv_contrib/archive/3.3.0.zip $ unzip opencv_contrib.zip 同样可以将版本号替换为新版本\n传送门：opencv_contrib\n注意替换新版本时要保持你的opencv和opencv_contrib的版本号一致\n设置Python环境： 首先确保你已经安装了pip安装工具\n为了保持系统环境的干净，我决定安装virtualenv和virtuanenvwrapper。这些工具允许你在一个独立于系统的虚拟环境下工作。\n安装相关工具：\n$ sudo pip install virtualenv virtualenvwrappper $ sudo rm -rf ~/.cache/pip 之后我们需要更新一下用户主目录下的.bashrc,打开该文件，在文件末尾添加如下内容：\n# virtualenv and virtualenvwrapper export $WORKON_HOME=$HOME/.virtualenvs source /usr/local/bin/virtualenvwrapper.sh 最后重新执行.bashrc时，可能会报错：\n/usr/bin/python: No module named virtualenvwrapper virtualenvwrapper.sh: There was a problem running the initialization hooks. If Python could not import the module virtualenvwrapper.hook_loader, check that virtualenvwrapper has been installed for VIRTUALENVWRAPPER_PYTHON=/usr/bin/python and that PATH is set properly. 由于ubuntu安装有两个版本的Python，而安装virtualenvwrapper是用的是pip3，但系统默认运行的是Python2,Python2中缺少一些模块。virtualenvwrapper.sh文件部分代码如下：\n# Locate the global Python where virtualenvwrapper is installed. if [ \u0026#34;$VIRTUALENVWRAPPER_PYTHON\u0026#34; = \u0026#34;\u0026#34; ] then VIRTUALENVWRAPPER_PYTHON=\u0026#34;$(command \\which python)\u0026#34; fi 当不存在VIRTUALENVWRAPPER_PYTHON环境时，会默认选择使用which python 所以还需要添加环境变量：\nVIRTUALENVWRAPPER_PYTHON=/usr/bin/python3 选择依赖的Python版本（Python2或Python3）创建虚拟环境\n$ mkvirtualenv opencvwork -p python2 这里我选择Python2,创建名为opencvwork的虚拟环境,相关目录在~/.virtualenv下\n然后进入虚拟环境\n$ workon opencvwork 之后终端提示符开头会有相关标识\n安装Numpy（在虚拟环境下）\n$ pip install numpy 配置编译OpenCV： 在虚拟环境下\n$ cd ~/opencv-3.3.0/ $ mkdir build $ cd build $ cmake -D CMAKE_BUILD_TYPE=RELEASE \\ -D CMAKE_INSTALL_PREFIX=/usr/local \\ -D INSTALL_PYTHON_EXAMPLES=ON \\ -D INSTALL_C_EXAMPLES=OFF \\ -D OPENCV_EXTRA_MODULES_PATH=~/opencv_contrib-3.3.0/modules \\ -D PYTHON_EXECUTABLE=~/.virtualenvs/cv/bin/python \\ -D BUILD_EXAMPLES=ON .. 如果编译出现错误：stdlib.h: No such file or directory\n那需要在Cmake选项中添加-D ENABLE_PRECOMILED_HEADERS=OFF\n安装后返回的界面中Python项应该有Interpreter，Libraries，numpy以及packages path项目列出（对应于你所选的Python版本）\n确认CMake执行无误后，就可以编译OpenCV了：\n$ make -j4 其中数字4可以换为你电脑上的cpu核心数\n$ make clean $ make 执行成功后就可以将OpenCV安装到Ubuntu上：\n$ sudo make install $ sudo ldconfig 结束OpenCV安装： 以上命令均成功执行后，Python+OpenCV相关文件会保存在/usr/local/lib/python-2.7/site-packages/ (对应你之前选择的Python版本)\n之后创建一符号链接：\n$ cd ~/.virtualenv/opencvwork/lib/python2.7/site-packages/ $ ln -s /usr/local/lib/python2.7/site-packages/cv2.so cv2.so 有时候cv2.so可能会在/usr/local/lib/python2.7/dist-packages下\n简单的检测OpenCV安装状况： $ cd ~ $ workon opencvwork $ python Python 2.7.12 (default, Nov 19 2016, 06:48:10) [GCC 5.4.0 20160609] on linux2 Type \u0026#34;help\u0026#34;, \u0026#34;copyright\u0026#34;, \u0026#34;credits\u0026#34; or \u0026#34;license\u0026#34; for more information. \u0026gt;\u0026gt;\u0026gt; import cv2 \u0026gt;\u0026gt;\u0026gt; cv2.__version__ \u0026#39;3.3.0\u0026#39; \u0026gt;\u0026gt;\u0026gt; 安装成功！\n文末附上一OpenCV3.3.0官方教程： OpenCV3.3.0\n参考： Adrian Rosebrock\n小小攻城狮\nHello World\n","permalink":"https://simplestory.github.io/posts/20171020_df3860e05b3ca04bdc2f774c5c1914fd/","summary":"\u003cblockquote\u003e\n\u003cp\u003e近期由于项目需要，要用到计算机视觉识别，所以折腾起了OpenCV，虽然安装过程曲折漫长，但是配合Python使用起来体验还是相当不错的。在此记录OpenCV安装编译过程。\u003c/p\u003e\n\u003c/blockquote\u003e","title":"Linux下安装OpenCV"},{"content":" Visual Studio Code是微软推出的一款轻量型编辑器,通过安装各种插件可以转变成一款强大的多语言,跨平台的编译器.近日,由于实践需要,需要在调试一些C/C++代码,于是折腾起Vscode,以下是记录折腾过程中困扰着我的问题.\n本文所用系统为ubuntu16.04LTS\n关于Vscode在linux系统下的安装过程我就不详细说明了,度娘有大把资源.\n安装C/C++插件 用ctrl+shift+P调出命令行,搜索C++,找到微软官方发布的那个插件,如图:\n编辑C/C++源文件 用Vscode打开一个目录,因为编译C/C++时会产生一些配置文件,针对于目录下的文件.\n现在写一个简单的cpp文件实验一下:\n// hello.cpp # include\u0026lt;iostream\u0026gt; using namespace std; int main() { cout \u0026lt;\u0026lt; \u0026#34;Hello World!\u0026#34; \u0026lt;\u0026lt; endl; return 0; } 编译C/C++源文件 保存编写好cpp源文件,若直接进行编译运行,Vscode会报错,原因是还没配置好相关文件.\n配置launch.json文件 根据报错信息,选择C/C++(GDB)打开相关launch.json文件,更改\u0026quot;program\u0026quot;对应段的代码为: \u0026quot;${workspaceRoot}/${fileBaseNoExtension}.out\u0026quot; 有些可能还需要修改\u0026quot;cwd\u0026quot;项,改为:\u0026quot;${workspaceRoot}\u0026quot; 修改后如下图:\n配置tasks.json文件 配置好launch.json文件后依然不能顺利执行编译,还需要配置tasks.json文件 用ctrl+shift+P打开命令行,输入tasks runner,选择configure tasks项回车打开 修改\u0026quot;task\u0026quot;项下的\u0026quot;taskName\u0026quot;,\u0026quot;command\u0026quot;并添加\u0026quot;args\u0026quot;项,结果如图:\n两个文件均配置并保存好后即可编译运行,点击调试,可以看见终端一闪而过,在windows下我们为了看清终端显示的结果,会在cpp文件中加一语句:system(pasue),但在linux系统中pasue不再是一条命令,故并不生效.可以改用getchar()实现相同功能.\n# include\u0026lt;iostream\u0026gt; # include\u0026lt;stdio.h\u0026gt; using namespace std; int main() { cout \u0026lt;\u0026lt; \u0026#34;Hello World!\u0026#34; \u0026lt;\u0026lt; ehdl; getchar(); return 0; } 附上一张结果图:\n","permalink":"https://simplestory.github.io/posts/20171001_f9fd02f8e181115a55cec2936857675c/","summary":"\u003cblockquote\u003e\n\u003cp\u003eVisual Studio Code是微软推出的一款轻量型编辑器,通过安装各种插件可以转变成一款强大的多语言,跨平台的编译器.近日,由于实践需要,需要在调试一些C/C++代码,于是折腾起Vscode,以下是记录折腾过程中困扰着我的问题.\u003c/p\u003e\n\u003c/blockquote\u003e","title":"Vscode下编译C/C++"},{"content":" “类”是一个模板，模板中包装了多个“函数”供使用，这篇博文将介绍Python类的成员、成员修饰符、类的特殊成员。\n类的成员 类的成员大概可分为三大类：字段、方法和属性\n注：所有成员中，只有普通字段的内容保存在对象中，即：根据此类创建了多少对象，在内存中就有多少个普通字段。而其他的成员，则都是保存在类中， 即：无论对象的多少，在内存中只创建一份。\n字段 字段包括普通字段和静态字段，普通字段属于对象，静态字段属于类。\nclass World: string=\u0026#39;earth\u0026#39; #静态字段 def _init_(self,name): self.name=\u0026#39;name\u0026#39; #普通字段 obj=World(\u0026#39;China\u0026#39;) #直接访问普通字段 print(obj.name) #直接访问静态字段 world.string 由上述代码可以看出，普通字段通过对象访问，静态字段通过类访问。即静态字段归属于类，而普通字段归属于对象，类中并没有保存对象中的普通字段。\n通过类创建对象时，如果每个对象都有相同的字段，那么就使用静态字段\n方法 方法包括：普通方法、静态方法和类方法，三种方法在内存中都归属于类，区别在于调用方式不同。\n普通方法：由对象调用；至少一个self参数；执行普通方法时，自动将调用该方法的对象赋值给self；\n类方法：由类调用；至少一个cls参数；执行类方法时，自动将调用该方法的类复制给cls；\n静态方法：由类调用；无默认参数；\nclass Foo: def _init_(self,name): self.name-name #定义普通方法，至少有一个self参数 def ord_func(self): print(\u0026#39;普通方法\u0026#39;) #定义类方法，至少有一个cls参数 @classmethod def class_func(cls): print(\u0026#39;类方法\u0026#39;) #定义静态方法，无默认参数 @staticmethod def static_func(): print(\u0026#39;静态方法\u0026#39;) #调用普通方法 f=Foo() f.ord_func() #调用类方法 Foo.class_func() #调用静态方法 Foo.static_func() 这些方法的相同点是均属于类（非对象）中，所以，在内存中只保存一份。不同点是方法的调用不同、调用方法时传入的参数也不同。\n属性 属性的基本使用 class Foo: def func(self): pass @property #定义属性 def prop(self): pass foo_obj=Foo() foo_obj.func() foo_obj.prop #调用属性 由属性的定义和调用要注意几点：\n定义时，在普通方法的基础上添加@property装饰器; 定义时，属性仅有一个self参数; 调用时，无需括号; 方法：foo_obj.func(); 属性：foo_obj.prop 属性由方法变种而来，如果Python中没有属性，方法完全可以代替它。\n属性的存在可以制造出和访问字段完全相同的假象。\nPython属性的功能是：属性内部进行一系列的逻辑计算，最终将计算结果返回。\n属性的两种定义方式： 装饰器，即：在方法上应用装饰器 静态字段，即：在类中定义值为property对象的静态字段 装饰器方式：在类的普通方法上应用@property装饰器\n对于经典类，具有一种@property装饰器\nclass Goods: @property def price(self): return \u0026#34;four yuan\u0026#34; obj=Goods() result=obj.price #自动执行@property修饰的price方法，并获取方法的返回值 对于新式类，具有三种@property装饰器\nclass Goods(object): @property def price(self): print(\u0026#39;@property\u0026#39;) @price.setter def price(self,value): print(\u0026#39;@price.setter\u0026#39;) @price.deleter def price(self): print(\u0026#39;@price.deleter\u0026#39;) obj=Goods() obj.price #自动执行@property修饰的price方法，并获取方法的返回值 obj.price=890 #自动执行@property修饰的price方法，并将890赋值给方法的参数 del obj.price #自动执行@property修饰的price方法 由上述可知：经典类中的属性只有一种访问方式，其对应被@property修饰的方法;新式类中的属性有三种访问方式，并分别对应了三个被@property、@方法名.setter、@方法名.deleter修饰的方法\n由于新式类具有三种访问方法，根据这几个属性的访问特点，分别将三个方法定义为对同一个属性：获取、修改、删除。\n静态字段方式，创建值为property对象的静态字段\n当使用静态字段的方式创建属性时，经典类和新式类无区别\nclass Foo: def get_bar(self): return \u0026#39;five yuan\u0026#39; BAR=property(get_bar) obj=Foo() result=obj.BAR #自动调用get_bar并获取方法的返回值 print(result) property的构造方法有四个参数\n第一个参数是方法名，调用对象.属性时自动触发执行方法 第二个参数是方法名，调用对象.属性=XXX时自动触发执行方法 第三个参数是方法名，调用del 对象.属性时自动触发执行方法 第四个参数是字符串，调用对象.属性.__doc__，此参数是该属性的描述信息 class Foo： def get_bar(self): return \u0026#39;five yuan\u0026#39; # 必须两个参数 def set_bar(self, value): return \u0026#39;set value\u0026#39; + value def del_bar(self): return \u0026#39;five yuan\u0026#39; BAR ＝ property(get_bar, set_bar, del_bar, \u0026#39;description...\u0026#39;) obj = Foo() obj.BAR #自动调用第一个参数中定义的方法：get_bar #自动调用第二个参数中定义的方法：set_bar方法，并将“alex”当作参数传入 obj.BAR = \u0026#34;alex\u0026#34; del Foo.BAR #自动调用第三个参数中定义的方法：del_bar方法 obj.BAR.__doc__ #自动获取第四个参数中设置的值：description... 由于静态字段方式创建属性具有三种访问方式，同样道理，根据几个属性的访问特点，分别将三个方法定义为同一个属性：获取、修改、删除\n类成员的修饰符 类的所有成员在前面中已经做了详细的介绍，对于每个类的成员而言都有两种形式：\n公有成员，在任何地方都能访问 私有成员，只有在类的内部才能访问 私有成员和公有成员的定义不同：私有成员命名时，前两个字符是下划线。（特殊成员除外，例如：__init__、__call__、__dict__等）\nclass C: def __init__(self): self.first=\u0026#39;公有字段\u0026#39; self._second=\u0026#39;私有字段\u0026#39; 私有成员与公有成员的访问限制不同：\n静态字段\n公有静态字段：类可以访问;类内部可以访问;派生类中可以访问 私有静态字段：仅类内部可以访问 class C: name=\u0026#39;公有静态字段\u0026#39; def func(self): print(C.name) class B(C): def show(self): print(C.name) C.name #类访问 obj=C() obj.func() #类内部访问 obj_son=D() obj_son.show() #派生类访问 class C: __name=\u0026#39;私有静态字段\u0026#39; def func(self): print(C.__name) class B(C): def show(self): print(C.__name) C.__name #类访问，结果为错误 obj=C() obj.func() #类内部访问 obj_son=D() obj_son.show() #派生类中访问，结果为错误 普通字段\n公有普通字段：对象可以访问;类内部可以访问;派生类中可以访问 私有普通字段：仅类内部可以访问 （可以通过对象.__类名__私有字段名访问，不建议强制访问私有成员）\n关于方法、属性的访问与上述方式相似，即：私有成员只能在类内部访问\n类的特殊成员 Python下存在着一些具有特殊含义的成员，如下：\n__doc__:表示类的描述信息 class Foo: \u0026#34;\u0026#34;\u0026#34;descrptioin....\u0026#34;\u0026#34;\u0026#34; def func(self): pass print(Foo.__doc__) #输出\u0026#34;\u0026#34;\u0026#34;descrption...\u0026#34;\u0026#34;\u0026#34; __module__和__class__\n__module__ 表示当前操作的对象在那个模块;\n__class__ 表示当前操作的对象的类是什么\n__init__:构造方法，在对象创建的同时即自动触发执行\n__del__:析构方法，当对象在内存中被释放时，自动触发执行\n此方法一般无需定义，通常情况下析构函数是有解释器在进行垃圾回收是自动触发执行的。\n__call__:对象后面加括号，触发执行\n构造方法的执行是由创建的对象触发的，即：对象=类名();而对于__call__的执行是由对象家括号触发的，即：对象()或者类()()\nclass Foo: def __init__(self): pass def __call__(self,*args,**kwargs): print(\u0026#39;__call__\u0026#39;) obj=Foo() #执行__init__ obj() #执行__call__ __dict__:类或对象中的成员\n类的普通字段属于对象;类中的静态字段和方法等属于类\n__str__:如果一个类中定义了__str__方法，在打印对象时，默认输出该方法的返回值\nclass A: def __str__(self): return \u0026#39;simplestory\u0026#39; obj=A() print(obj) #输出:simplestory __getitem__、__setitem__、__delitem__\n用于索引操作，分别表示获取、设置、删除数据\nclass A(object): def __getitem__(self,key): print(\u0026#39;__getitem__\u0026#39;,key) def __setitem__(self,key,value): print(\u0026#39;__setitem__\u0026#39;,key,value) def __delitem__(self,key): print(\u0026#39;__delitem__\u0026#39;,key) obj=A() result=obj[\u0026#39;k1\u0026#39;] #自动执行__getitem__ obj[\u0026#39;k2\u0026#39;]=\u0026#39;simplestory\u0026#39; #自动执行__setitem__ del obj[\u0026#39;k1\u0026#39;] #自动执行__delitem__ __getslice__、__setslice__、__delslice__\n这三种方法用于分片操作，如：列表\n__iter__\n之所以列表、字典、元组可以进行for循环，是因为类型内部定义了__iter__ for循环迭代的其实是iter\nobj=iter([1,2,3,4,5,6,7]) for i in obj: print(i) __new__和__metcalss__ class Foo(object): def __init__(self): pass obj=Foo() #obj是通过Foo类实例化的对象 Python中一切事物都是对象 按照这一理论可以得知：obj对象是通过执行Foo类的构造方法创建，那么Foo类对象应该也是通过执行某个类的构造方法创建。（可以由type()函数查看类的父类）\n#继上述代码片段 print(type(obj)) #输出：\u0026lt;class \u0026#39;__main__.Foo\u0026#39;\u0026gt;，表示obj对象由Foo类创建 print(type(Foo)) #输出：\u0026lt;type \u0026#39;type\u0026#39;\u0026gt;，表示Foo类对象由type类创建 类的创建其实可以有两种方式：\n普通方式 class Foo(object): def func(self): print(\u0026#39;Hello World!\u0026#39;) 特殊方式（type类的构造函数） def func(self): print(\u0026#39;Hello World\u0026#39;) Foo=type(\u0026#39;Foo\u0026#39;,(object,),{\u0026#39;func\u0026#39;:func}) #type第一个参数：类名 #type第二个参数：当前类的基类 #type第三个参数：类的成员 类是由type类实例化产生\n那对于type类，有一个__metaclass__，其用来表示该类由谁来实例化创建，所以，我们可以为__metaclass__设置一个type类的派生类,然后在__metaclass__的作用域内所创建的类都会基于__metaclass__所指定的类或函数作为type类的派生类。\n","permalink":"https://simplestory.github.io/posts/20170820_7cb802827b60ef8c770d824ec28a65a0/","summary":"\u003cblockquote\u003e\n\u003cp\u003e“类”是一个模板，模板中包装了多个“函数”供使用，这篇博文将介绍Python类的成员、成员修饰符、类的特殊成员。\u003c/p\u003e\n\u003c/blockquote\u003e\n\u003ch2 id=\"类的成员\"\u003e类的成员\u003c/h2\u003e\n\u003cp\u003e类的成员大概可分为三大类：字段、方法和属性\u003c/p\u003e","title":"Python的类"},{"content":" Python是一门解释型的、面向对象的、带有动态语义的高级程序语言。面向对象程序设计（Object Oriented Programming，OOP），这种编程方式需要使用“类”和“对象”来实现，所以面向对象编程就是对“类”和“对象”的使用。\n简述 面向过程： 根据程序逻辑自顶而下的写代码。\n函数式： 将某功能代码封装到函数中，日后便无需重复书写，调用函数即可，一般适用于各个函数之间是独立无共享数据的场景。\n面向对象： 对函数进行分类和封装。\n关于类与对象 类就是一个模板，在模板里包含有许多函数，每个函数实现一些功能 对象是根据模板创建的实例，通过对象可以执行模板中的函数 #创建类 class A: def Func(self): print(\u0026#34;Afun\u0026#34;) def Hello(self,name): print(\u0026#34;Hello,%s\u0026#34;%name) #根据类A创建对象obj obj=A() obj.Func #调用Func方法 obj.Hello(\u0026#39;Alex\u0026#39;) #调用Hello方法 类中的函数第一个参数必须是self（它是对于对象自身的引用）；\n类中定义的函数称为“方法”。\n面向对象三大特性 面向对象三大特性是指：封装、继承和多态。\n封装（Encapsulation） 封装即对外部世界隐藏对象的工作细节。在使用面向对象的封装特性时，需要：\n将内容封装到某处 从某处调用被封装的内容 class Foo: #__init__称为构造方法，创建类的实例时会自动执行 def __init__(self,name,age): self.name=name self.age=age obj=Foo(\u0026#39;Alex\u0026#39;,20) #将Alex和20封装在obj/self的name和age属性中 调用被封装的内容时有两种方法：\n通过对象直接调用 通过self间接调用 class Foo: def __init__(self,name,age): self.name=name self.age=age def Detail(self): print(self.name) print(self.age) obj=Foo(\u0026#39;Alex\u0026#39;,20) print(obj.name) #直接调用obj对象的name属性 obj.Detail() #间接调用obj对象属性 综上所述，对于面向对象的封装来说，其实就是使用构造方法将内容封装到对象中，然后通过对象直接或者self间接获取被封装的内容。\n继承（Inheritance） 以通用的类为基础建立专门的类对象\nclass Animal(): def eat(self): pass def drink(self): pass def shit(self): pass def pee(self): pass class Cat(Animal): def sound(self): print(\u0026#34;喵\u0026#34;) class Dog(Animal): def sound(self): print(\u0026#34;汪\u0026#34;) 从上面的代码上可以看出，Cat与Dog类都以Animal为基类，所以Cat和Dog都有Animal类的方法，这就是继承。\n多继承\nPython的类可以继承多个类 Python的类如果继承了多个类，有两种寻找方法：深度优先，广度优先 当类是经典类时，多继承情况下，会按照深度优先方式查找 当类是新式类时，多继承情况下，会按照广度优先方式查找 （当类或者父类继承object类，那么该类便是新式类，否则便是经典类） class A(): def foo(self): print(\u0026#39;A.foo\u0026#39;) class B(A): def foo(self): print(\u0026#39;B.foo\u0026#39;) class C(A): def foo(self): print(\u0026#39;C.foo\u0026#39;) class D(B,C): def foo(self): print(\u0026#39;D.foo\u0026#39;) k=D() 经典类：首先去D类中查找，如果D类中没有，则继续去B类中找，如果B类中没有，则继续去A类中查找，如果A类中没有，则继续去C类中找，如果还是找不到，则报错。\n新式类：首先去D类中查找，如果D类中没有，则继续去B类中找，如果B类中没有，则继续去C类中查找，如果C类中没有，则继续去A类中找，如果还是找不到，则报错。\n上述过程中，一旦找到，则寻找过程立即中断，不会在继续查找了。\n多态（Polymorphism) 多态意味着可以对不同类的对象使用同样的操作。\nclass Foo: pass class S1(Foo): def show(self): print(\u0026#39;S1.show\u0026#39;) class S2(Foo): def show(self): print(\u0026#39;S2.show\u0026#39;) def Func(obj): print(obj.show) s1_obj=S1() Func(s1_obj) #在Func()中传入S1类的对象，执行S1类的show方法 s2_obj=S2() Func(s2_obj) #在Func()中传入S2类的对象，执行S2类的show方法 总结 以上即为面向对象的编程方式介绍，这种方式的实现是基于对类和对象的使用 类是一个模板，模板中包装了多个函数供使用 对象，根据模板创建的实例，实例用于调用被包装在类中的函数 面向对象有三各个特性：封装、继承、多态 外话 类和对象在内存中的保存 类以及类中的方法在内存中只有一份，而有类创建出的对象都在内存中需要存一份，以为对象与对象、对象与基类之间的方法或变量的值可能是不一样的。根据类创建对象时，对象中处理封装各自变量的值之外，还会保存一个类对象指针，该指针指向当前对象的类。\n","permalink":"https://simplestory.github.io/posts/20170810_3193456a2ce85ef1e01f5a219b37cebd/","summary":"\u003cblockquote\u003e\n\u003cp\u003ePython是一门解释型的、面向对象的、带有动态语义的高级程序语言。面向对象程序设计（Object Oriented Programming，OOP），这种编程方式需要使用“类”和“对象”来实现，所以面向对象编程就是对“类”和“对象”的使用。\u003c/p\u003e\n\u003c/blockquote\u003e","title":"Python，面向对象"},{"content":" Git是一个极其优秀的分布式版本控制系统。该系统可以记录用户文件的每一次变动，还具有强大的团队协同工作能力，每个人都可以提交对文本的改动，而不必担心会覆盖他人的操作。Github更是一个通过Git进行版本控制的软件源代码托管服务。截至到2015年，Github已经有超过九百万注册用户和2110万代码库。Github已经成为了世界上最大的代码存放网站和开源社区。\nGIT使用教程 创建版本库 选择一个合适的地方，创建一个空目录\nmkdir \u0026lt;dir-name\u0026gt; cd \u0026lt;dir-name\u0026gt; #在仓库中进行操作 pwd #可查看当前目录 git init #将目录变为Git可以管理的仓库，即初始化仓库 之后会在当前目录下产生一个.git的目录，该目录是Git用来跟踪管理版本库，也可以选用一个非空目录创建Git仓库\n编写保存文件 编写文件并存放至\u0026lt;dir-name\u0026gt;目录下（或子目录）\n用纯文本方式编辑并保存文件，文件编码格式保存为UTF-8编码格式\ngit add \u0026lt;filename1\u0026gt; \u0026lt;filename2\u0026gt; ... #告知Git将文件添加至仓库 git commit -m \u0026#34;...\u0026#34; #将文件提交到仓库 -m后面输入本次提交的说明，可以一次提交多个文件 git status #可查看仓库当前的状态，告知用户是否有文件被修改 git diff #查看文件具体修改了什么，或对比文件 git log #显示从最近到最远的提交日志（可加上参数--pretty=oneline改变显示视图） git reset --hard HEAD^ #文件回退至上一版本 Git中，用HEAD表示当前版本，上一版本就是HEAD^，上上版本为HEAD^^,往上N个版本可写为HEAD~N\ngit reset --hard \u0026lt;fileID\u0026gt; #文件版本跳至版本ID为\u0026lt;fileID\u0026gt;的文件 git reflog #记录用户的每一次操作，可借由此查看文件回退前的版本号 工作区与暂存区 工作区 在计算机里可以看到的目录就是工作区，用于存放文件\n版本库与暂存区 工作区下有一个隐藏目录.git,这个是git的版本库，版本库里存有暂存区（stage/index）\nGit大致工作原理 文件添加至Git版本库分为两步：\ngit add将文件添加进暂存区，即把对文件的修改或相关操作添加到暂存区 git commit提交更改，即把暂存区的全部内容提交到当前分支 文件撤销操作 git checkout -- \u0026lt;filename\u0026gt;\t将在工作区的修改全部撤销，命令--很重要，没有--就变成了切换到另一个分支的命令\n文件撤销情况 关于文件需要撤销操作的，大致有两种情况：\n\u0026lt;filename\u0026gt;自修改后还没放到暂存区，则命令执行后文件撤销修改就回到和版本库一模一样的状态 \u0026lt;filename\u0026gt;已经添加到暂存区后，又作了修改，则命令执行后文件撤销修改就回到添加到暂存区后的状态 总之，就是让文件回到最近一次git commit或git add时的状态 撤销与回退小结 当你改乱了工作区某个文件的内容，想直接丢弃工作区的修改时，用命令 git checkout -- \u0026lt;filename\u0026gt; 当你不但改乱了工作区某个文件的内容，还添加到了暂存区时，想丢弃修改，分两步：用命令 git reset HEAD \u0026lt;filename\u0026gt;，就回退到1中，然后继续1的操作 当你已经提交了不合适的修改到版本库时，想要撤销本次提交，可参考版本回退，不过前提时没有推送到远程库 版本回退：git reset --hard \u0026lt;ID\u0026gt;，将文件跳至版本号ID的文件，HEAD表示最新的文件版本\nGit撤销删除 Git中删除也是一种修改操作\n一般情况下，在提交至版本库后，我们直接在文件管理器中删除文件（或通过rm命令），git status会告知那些文件被删除，这时有两种选择：\n确实要从版本库中删除，可执行git rm命令删除，并git commit 手误删除文件，这时可执行git checkout -- \u0026lt;filename\u0026gt;命令把误删的文件恢复到最新版本 git checkout其实是用版本库里的版本替换工作区的版本，无论工作区是修改还是删除，都可以\u0026quot;一键还原\u0026quot;, 命令git rm用于删除一个文件，如果一个文件已经被提交到版本库，那永远不用担心误删，但你只能恢复文件到最新版本，你会丢失最近一次修改提交后的内容 创建SSH Key ssh-keygen -t rsa -C \u0026#34;youremail@example.com\u0026#34; 创建后，可在用户主目录里找到.ssh目录，里面有id_rsa和id_rsa.pub两个文件，这两个文件就是SSH Key的秘钥对，id——rsa是私钥，不能泄露，id_rsa.pub是 公钥，可以公布。之后登陆Github进行设置，进入\u0026quot;SSH Key\u0026quot;页面，点击\u0026quot;Add SSH Key\u0026quot;，填上任意标题，在Key文本框里粘贴id_rsa.pub文件里的内容\nGithub允许添加多个Key\n关联远程库 首先在Github上新建一个仓库（或者使用已有的仓库），在本地的一个仓库中运行以下命令：\ngit remote add origin git@github.com:\u0026lt;accountname\u0026gt;/\u0026lt;localreponame\u0026gt;.git 添加后，远程库名字Git默认为origin，可以更改:\ngit push -u origin master git push命令，实际是把当前分支master推送到远程, 如果这是你第一次推送master分支，你需要加上-u参数，Git不但会把本地的master分支内容推送到远程的新的master分支，还会把本地的master分支和远程的master分支关联起来， 在以后的推送或者拉取时可简化命令，之后只要本地作了提交，就可以通过命令：git push origin master完成提交 在第一次使用Git的clone或push命令时会得到一个SSH警告，输入yes即可\n#关联一个远程库 git remote add origin git@server-name:path/repo-anme.git 克隆远程库到本地 在Github中新建库后，可以使用git clone克隆一个本地库 git clone git@github.com:\u0026lt;git库地址\u0026gt;\nGit支持多种协议，包括https\nwindows系统：git clone \u0026lt;Git远程库的url\u0026gt;\nGit分支 git branch #查看分支 git branch \u0026lt;name\u0026gt; #创建分支 git checkout \u0026lt;name\u0026gt; #切换分支 git checkout -b \u0026lt;name\u0026gt; #创建+切换分支 git merge \u0026lt;name\u0026gt; #合并某分支到当前分支 git branch -d \u0026lt;name\u0026gt; #删除分支 多个分支是共用暂存区，即如果在分支1上仅add而不commit，实际暂存区中已经记录该次修改，哪怕之后切换到分支2上再进行commit也是有效操作 当Git无法自动合并分支时，需要首先解决冲突。解决冲突后再提交, 用git log --graph命令可以看到分支合并图，git status也可以查看冲突的文件\ne.g:git log --graph --pretty=oneline --abbrev-commit\nGit分支管理 合并分支时，加上--no-ff参数就可以用普通模式合并，合并后的历史有分支，能看出曾经做过合并，而fast-forward合并就看不出曾经做过合并\ngit merge --no-ff -m\u0026#34;...\u0026#34; \u0026lt;name\u0026gt; 以普通方式合并分支（即禁用fast forward）因为本次合并要创建一个新的commit，所以加上-m参数，把commit描述写进去`\n分支策略： 在实际开发中，我们应该按照几个基本原则进行分制管理： master分支应该是非常稳定的，也就是仅用来发布新版本，平时不在上面干活； 干活都在dev分支，即devd分支是不稳定的，到某个时候，再把dev分支合并到master分支上； 每个人都在dev分支上工作，每个人都有自己的分支，要保存时往dev分支上合并就可以了 Bug分支 git stash命令相当于对当前工作环境做一个快照，等以后恢复环境后继续工作\n在哪个分支上修复bug，就在从哪个分区创建临时分支\n修复完后再提交并删除分支\ngit stash list #查看是否存在stash git stash apply #恢复stash内容，该命令执行后并不删除stash内容 git stash drop #删除stash内容 git stash pop #恢复stash内容，在恢复的同时也删除内容 修复Bug时，当手头工作没有完成时，先把工作现场git stash一下，然后再去修复Bug，修复后，再git stash pop回到工作现场\nFeature分支（添加新功能） 在开发新功能时最好新建一个分支， 若要丢弃一个没有被合并过的分支，可以通过git branch -D \u0026lt;name\u0026gt;强行删除\n多人协作 git remote查看远程库的信息\ngit remote -v查看远程库详细的信息（上面显示了抓取和推送的\u0026lt;远程库名字\u0026gt;的地址，如果没有推送权限，就看不到push的地址）\n推送分支： 推送分支就是把该分支上的所有本地提交推送远程库。推送时，要指定本地分支\ngit push \u0026lt;远程库名字\u0026gt; \u0026lt;本地库名字\u0026gt; git pull #把最新的提交从远程库中抓取下来 抓取后在本地合并并解决冲突后再推送\n工作模式： 首先，可以试图用git push origin branch-name推送自己的修改 如果推送失败，则因为远程分支比你的本地版本新，需要先用git push试图合并 如果合并有冲突，则解决冲突，并在本地提交 没有冲突或者解决掉冲突后，再用git push origin branch-name推送就能成功 如果git pull提示\u0026quot;no tracking information\u0026quot;，则说明本地分支和远程分支的链接关系没有创建，用命令git branch --set-upstream branch-name origin/branch-name（该命令用于建立本地分支和远程分支的关联）\n标签管理 发布一个版本时，我们通常先在版本库中打一个标签tag，这样，就唯一确定了打标签时的版本。在将来的某个时候，取某一个标签的某一版本，就是把那个打标签的时刻对应的版本取出。标签也是版本库的一个快照\nGit标签虽然是版本库的快照，但其实它是指向某个commit的指针（分支可以移动，标签不能移动）\nTag就是一个让人容易记住的名字，它与某个commit绑定在一起\ngit tag \u0026lt;name\u0026gt; #在git中打标签应先切换至需要打标签的分支 git tag #查看所有标签默认标签是打在最新提交的commit上的 git tag \u0026lt;name\u0026gt; \u0026lt;commit ID\u0026gt; #给对应ID的commit打上标签 标签不是按时间顺序列出，而是按字母排序的，可以用git show \u0026lt;tagname\u0026gt;查看标签信息\ngit tag -a \u0026lt;tagname\u0026gt; -m \u0026#34;...\u0026#34; #创建带说明的标签，-a指定标签名，-m指定说明文字 git tag -d \u0026lt;tagname\u0026gt; #删除本地标签，因为创建的标签都只储存在本地，不会自动推送至远程 git push \u0026lt;远程库名字\u0026gt; \u0026lt;tagname\u0026gt; #推送某个标签到远程 git push \u0026lt;远程库名字\u0026gt; --tags #一次性推送全部尚未推送到远程的本地标签 其中在上面第一行代码中若用参数-s替换-a则可以用PGP签名标签\n已推送到远程的标签的删除：\ngit push \u0026lt;远程库名字\u0026gt; :refs/tags/\u0026lt;tagname\u0026gt; 在Github上，可以任意Fork开源仓库；\n自己拥有Fork后的仓库的读写权限；\n可以推送Pull request给他人仓库来贡献代码；\nFork他人仓库之后再在自己的账号下clone仓库，这样才能推送修改\n自定义Git git config --global color.ui true #git会适当显示不同的颜色 忽略文件 在Git工作区的根目录下有一个.gitignore文件（没有可创建），然后把要忽略的文件名填进去，Git就会自动忽略这些文件\n忽略文件的原则：\n忽略操作系统自动生成的文件，比如缩略图等； 忽略编译生成的中间文件、可执行文件等，也就是一个文件通过另一个文件自动生成的，那自动生成的文件就没必要放进版本库； 忽略你自己的带有敏感信息的配置文件，比如存放口令的配置文件 之后将.gitignore也提交到Git，就完成了忽略文件设置\ngit check-ignore #检查.gitignore文件 git add -f \u0026lt;filename\u0026gt; #强制添加文件 我们可以对.gitignore进行版本管理\n设置别名 git config --global alias \u0026lt;别名\u0026gt; \u0026lt;命令原名\u0026gt; \u0026ndash;global参数是全局参数，也就是这些命令在电脑的所有Git仓库下都有效\n配置Git时，加上--global是针对当前用户启作用，如果不加，则只针对当前仓库起作用。 每个仓库的Git配置文件都放在.git/config文件中， 别名在[alias]后面，删除别名则直接删除对应行即可。 当前用户的Git配置文件在用户主目录的一个隐藏文件.gitconfig中\n搭建Git服务器（linux ubuntu or debian） 安装git:sudo apt-get install git; 创建一个git用户，用来运行git服务：sudo adduser git; 创建证书登录：收集所有需要登录的用户的公钥，即他们的id_rs.pub文件，将所有公钥导入到/home/git/.shh/authorized_keys文件中（可改名）; 初始化Git仓库：sudo git init --bare \u0026lt;reponame\u0026gt;;参数\u0026ndash;bare为创建一个裸仓库，即没有工作区（服务器上的Git仓库通常都以.git结尾，创建后把owner改为git sudo chown -R git:git \u0026lt;reponame\u0026gt;； 禁用shell登录：出于安全考录，2中创建的git用户不允许登录shell，可通过编辑/etc/passwd文件实现 找到类似一行：git:x:1001:1001:,,,:/home/git:/bin/bash 改为：git:x:1001:1001:,,,:/home/git:/usr/bin/got-shell 这样当用户用shell登录时会自动退出； 克隆远程仓库：git clone git@server:/srv/\u0026lt;reponame\u0026gt;;在各自的电脑中运行 参考： 廖雪峰的Git教程\n","permalink":"https://simplestory.github.io/posts/20170704_497c40137cedefb55b1afe3bd1e6b13f/","summary":"\u003cblockquote\u003e\n\u003cp\u003eGit是一个极其优秀的分布式版本控制系统。该系统可以记录用户文件的每一次变动，还具有强大的团队协同工作能力，每个人都可以提交对文本的改动，而不必担心会覆盖他人的操作。Github更是一个通过Git进行版本控制的软件源代码托管服务。截至到2015年，Github已经有超过九百万注册用户和2110万代码库。Github已经成为了世界上最大的代码存放网站和开源社区。\u003c/p\u003e\n\u003c/blockquote\u003e","title":"GIT使用基础教程"}]