DCN可形變卷積實現1:Python實現

DCN可形變卷積實現1:Python實現

我們會先用純 Python 實現一個 Pytorch 版本的 DCN ,然后實現其 C++/CUDA 版本。

本文主要關注 DCN 可形變卷積的代碼實現,不會過多的介紹其思想,如有興趣,請參考論文原文:

Deformable Convolutional Networks

Deformable ConvNets v2: More Deformable, Better Results

DCN簡介

考慮到傳統卷積必須是方方正正的 k×kk\times kk×k 的卷積核:
y(p0)=∑pn∈Rw(pn)?x(p0+pn)\mathbf{y}(\mathbf{p}_0)=\sum_{\mathbf{p}_n\in \mathcal{R}}\mathbf{w}(\mathbf{p}_n)\cdot \mathbf{x}(\mathbf{p}_0+\mathbf{p}_n) y(p0?)=pn?R?w(pn?)?x(p0?+pn?)
作者認為這個感受野太規則,無法很好地捕捉特殊形狀的特征,因此在其基礎上加了偏置:
y(p0)=∑pn∈Rw(pn)?x(p0+pn+Δpn)\mathbf{y}(\mathbf{p}_0)=\sum_{\mathbf{p}_n\in \mathcal{R}}\mathbf{w}(\mathbf{p}_n)\cdot \mathbf{x}(\mathbf{p}_0+\mathbf{p}_n+\Delta\mathbf{p}_n) y(p0?)=pn?R?w(pn?)?x(p0?+pn?+Δpn?)
使得模型能夠根據輸入計算偏移量,自己選擇對哪些位置進行卷積計算,而不用必須是正方形的樣子。

在這里插入圖片描述

如上圖所示,傳統的卷積輸入只能是圖 (a) 中的九個綠點,而在加上偏移量之后,皆可以四處飛,比如飛到圖 (bcd) 中藍點的位置。

而 DCNv2 則在此基礎上又為每個位置乘了一個可學習的權重:
y(p0)=∑pn∈Rw(pn)?x(p0+pn+Δpn)?Δmn\mathbf{y}(\mathbf{p}_0)=\sum_{\mathbf{p}_n\in \mathcal{R}}\mathbf{w}(\mathbf{p}_n)\cdot \mathbf{x}(\mathbf{p}_0+\mathbf{p}_n+\Delta\mathbf{p}_n)\cdot\Delta\mathbf{m}_n y(p0?)=pn?R?w(pn?)?x(p0?+pn?+Δpn?)?Δmn?

由于網絡學習出的偏移量通常是小數,因此下面會用到雙線性插值(下面會有圖示),這里先把原文中的公式給出來:
x(p)=∑qG(q,p)?x(q)\mathbf{x}(\mathbf{p})=\sum_\mathbf{q}G(\mathbf{q},\mathbf{p})\cdot\mathbf{x}(\mathbf{q}) x(p)=q?G(q,p)?x(q)

這里 p=(p0+pn+Δpn)\mathbf{p}=(\mathbf{p}_0+\mathbf{p}_n+\Delta\mathbf{p}_n)p=(p0?+pn?+Δpn?) 表示任意位置(可以是小數)坐標,而 q\mathbf{q}q 是枚舉特征圖 x\mathbf{x}x 中所有整數空間位置,G(?,?)G(\cdot,\cdot)G(?,?) 就是雙線性插值,注意這里的 GGG 是兩個維度(x,y)的,拆分為兩個單維度的話,就是:

G(q,p)=g(qx,px)?g(qy,py)G(\mathbf{q},\mathbf{p})=g(q_x,p_x)\cdot g(q_y,p_y) G(q,p)=g(qx?,px?)?g(qy?,py?)

其中 g(a,b)=max(0,1?∣a?b∣)g(a,b)=max(0,1-|a-b|)g(a,b)=max(0,1?a?b)

給出公式一方面是讓讀者了解具體算法,更重要的一點是我們參考的 DCN 的 Pytorch 實現代碼中變量的命名是與原文公式對應的,因此公式列在這里方便讀者下面看代碼的時候可以回頭看一下各個變量對應的是算法公式中的哪一項。

純Python實現

我們先來看一下Pytorch版本的實現,來更好地理解 DCN 可形變卷積的做法,然后用 C++/CUDA 實現高性能版本。本文參考的 Python 實現是:https://github.com/4uiiurz1/pytorch-deform-conv-v2/blob/master/deform_conv_v2.py 。

本小節參考博文:deformable convolution可變形卷積(4uiiurz1-pytorch版)源碼分析

_init_

def __init__(self, inc, outc, kernel_size=3, padding=1, stride=1, bias=None, modulation=False):"""Args:modulation (bool, optional): If True, Modulated Defomable Convolution (Deformable ConvNets v2)."""super(DeformConv2d, self).__init__()self.kernel_size = kernel_sizeself.padding = paddingself.stride = strideself.zero_padding = nn.ZeroPad2d(padding)self.conv = nn.Conv2d(inc, outc, kernel_size=kernel_size, stride=kernel_size, bias=bias)self.p_conv = nn.Conv2d(inc, 2*kernel_size*kernel_size, kernel_size=3, padding=1, stride=stride)nn.init.constant_(self.p_conv.weight, 0)self.p_conv.register_backward_hook(self._set_lr)self.modulation = modulationif modulation:self.m_conv = nn.Conv2d(inc, kernel_size*kernel_size, kernel_size=3, padding=1, stride=stride)nn.init.constant_(self.m_conv.weight, 0)self.m_conv.register_backward_hook(self._set_lr)

這里重點關注 self.p_convself.m_conv ,是這兩個卷積完成了對偏移量 offset 的學習,而 self.conv 是確在定偏移后的位置之后,最終進行計算的卷積。

(關于這里的 modulation 參數,如注釋所言,如果為 True ,就是一個模塊化的 DCN,即 DCNv2。)

具體來看這三個卷積及其參數:

  • self.conv這是負責進行最終計算的卷積。可形變卷積 DCN 雖然進行了形變,但是這是卷積輸入中空間像素的位置有了偏移,而輸入輸出的尺寸還是不變的,因此,輸入卷積的位置確定之后,最終負責完成卷積計算的 self.conv 的各個參數(輸入輸出通道數inc, outc、卷積核大小kernel_size、步長stride、填充padding等)就是我們整個 DCN 的對應參數參數。

  • self.p_conv該卷積操作負責計算偏移量。在卷積中,共有 kernel_size * kernel_size 個位置的像素需要參與計算,因此我們要計算出他們的偏移量,而每個位置都有寬、高兩個方向的偏移量,故該卷積輸出的通道數是 2 * kernel_size * kernel_size ,其他參數保持一致。

  • self.m_conv該卷積操作負責計算卷積核每個位置的權重。其輸出通道數為位置數,即 kernel_size * kernel_size ,其他參數保持一致,注意這個加權的想法是 DCNv2 中的。

在這里插入圖片描述

forward

看過 __init__ 函數之后,我們可以來看 forward 函數:

def forward(self, x):offset = self.p_conv(x)if self.modulation:m = torch.sigmoid(self.m_conv(x))dtype = offset.data.type()ks = self.kernel_sizeN = offset.size(1) // 2if self.padding:x = self.zero_padding(x)# (b, 2N, h, w)p = self._get_p(offset, dtype)# (b, h, w, 2N)p = p.contiguous().permute(0, 2, 3, 1)q_lt = p.detach().floor()q_rb = q_lt + 1q_lt = torch.cat([torch.clamp(q_lt[..., :N], 0, x.size(2)-1), torch.clamp(q_lt[..., N:], 0, x.size(3)-1)], dim=-1).long()q_rb = torch.cat([torch.clamp(q_rb[..., :N], 0, x.size(2)-1), torch.clamp(q_rb[..., N:], 0, x.size(3)-1)], dim=-1).long()q_lb = torch.cat([q_lt[..., :N], q_rb[..., N:]], dim=-1)q_rt = torch.cat([q_rb[..., :N], q_lt[..., N:]], dim=-1)# clip pp = torch.cat([torch.clamp(p[..., :N], 0, x.size(2)-1), torch.clamp(p[..., N:], 0, x.size(3)-1)], dim=-1)# bilinear kernel (b, h, w, N)g_lt = (1 + (q_lt[..., :N].type_as(p) - p[..., :N])) * (1 + (q_lt[..., N:].type_as(p) - p[..., N:]))g_rb = (1 - (q_rb[..., :N].type_as(p) - p[..., :N])) * (1 - (q_rb[..., N:].type_as(p) - p[..., N:]))g_lb = (1 + (q_lb[..., :N].type_as(p) - p[..., :N])) * (1 - (q_lb[..., N:].type_as(p) - p[..., N:]))g_rt = (1 - (q_rt[..., :N].type_as(p) - p[..., :N])) * (1 + (q_rt[..., N:].type_as(p) - p[..., N:]))# (b, c, h, w, N)x_q_lt = self._get_x_q(x, q_lt, N)x_q_rb = self._get_x_q(x, q_rb, N)x_q_lb = self._get_x_q(x, q_lb, N)x_q_rt = self._get_x_q(x, q_rt, N)# (b, c, h, w, N)x_offset = g_lt.unsqueeze(dim=1) * x_q_lt + \g_rb.unsqueeze(dim=1) * x_q_rb + \g_lb.unsqueeze(dim=1) * x_q_lb + \g_rt.unsqueeze(dim=1) * x_q_rt# modulationif self.modulation:m = m.contiguous().permute(0, 2, 3, 1)m = m.unsqueeze(dim=1)m = torch.cat([m for _ in range(x_offset.size(1))], dim=1)x_offset *= mx_offset = self._reshape_x_offset(x_offset, ks)out = self.conv(x_offset)return out

這里的 N 是 offset 的通道數除以2,就是卷積要處理的位置的個數(即 kernal_size * kernel_size)。

整個 forward 函數的流程:

  1. 首先通過上面介紹的 p_convv_conv 計算出偏移量 offset 和加權的權重m(如果有)。

  2. 比較關鍵的是這里的 self._get_p 函數,該函數通過上面計算出的 offset,去得到輸入到卷積的具體位置,即公式中的:
    p0+pn+Δpn\mathbf{p}_0+\mathbf{p}_n+\Delta\mathbf{p}_n p0?+pn?+Δpn?
    關于這個函數,我們會在下一小節詳細介紹。由于我們現在先過整個流程,只需要知道該函數通過 p_conv 卷積計算出的 offset,得到了要輸入最終卷積的位置 p。p 是一個形狀為 (bs,2?N,h,w)(bs,2*N,h,w)(bs,2?N,h,w) 的張量。

  3. 拿到 p 之后的問題是我們得到的肯定是一個浮點類型,即小數,但是像素的坐標肯定是整型,所以,這里我們需要做一個雙線性插值。雙線性插值的思想也很直接,就是將某個浮點坐標的左上、左下、右上、右下四個位置的像素值按照與該點的距離計算加權和,作為該點處的像素值。可參考下圖,也可參考博客圖像預處理之warpaffine與雙線性插值及其高性能實現,后半部分有對雙線性插值的講解與 Python 實現。

    這里的 lt, rb, lb, rt 分別代表左上,右下,左下,右上。

在這里插入圖片描述

  1. 現在我們通過雙線性插值拿到了每個位置的坐標,下一步就是根據坐標去取到對應位置的像素值,這在代碼中由 self._get_x_q 實現,會在下面的小節介紹。

  2. 這個時候如果有權重的話,要計算出 m,乘到 x_offset 上。

  3. 這時得到的 x_offset 的形狀是 b,c,h,w,Nb,c,h,w,Nb,c,h,w,N,而我們要的形狀肯定是 b,c,h,wb,c,h,wb,c,h,w,因此這里還有一個 reshape 的操作,由 self._reshape_x_offset 實現。

  4. 至此,我們終于得到了想要的 x_offset,接下來就將它送入 self.conv 進行卷積計算并返回結果即可。

_get_p、_get_p_0、_get_p_n

先貼一下代碼:

def _get_p(self, offset, dtype):N, h, w = offset.size(1)//2, offset.size(2), offset.size(3)# (1, 2N, 1, 1)p_n = self._get_p_n(N, dtype)# (1, 2N, h, w)p_0 = self._get_p_0(h, w, N, dtype)p = p_0 + p_n + offsetreturn pdef _get_p_n(self, N, dtype):p_n_x, p_n_y = torch.meshgrid(torch.arange(-(self.kernel_size-1)//2, (self.kernel_size-1)//2+1),torch.arange(-(self.kernel_size-1)//2, (self.kernel_size-1)//2+1))# (2N, 1)p_n = torch.cat([torch.flatten(p_n_x), torch.flatten(p_n_y)], 0)p_n = p_n.view(1, 2*N, 1, 1).type(dtype)return p_ndef _get_p_0(self, h, w, N, dtype):p_0_x, p_0_y = torch.meshgrid(torch.arange(1, h*self.stride+1, self.stride),torch.arange(1, w*self.stride+1, self.stride))p_0_x = torch.flatten(p_0_x).view(1, 1, h, w).repeat(1, N, 1, 1)p_0_y = torch.flatten(p_0_y).view(1, 1, h, w).repeat(1, N, 1, 1)p_0 = torch.cat([p_0_x, p_0_y], 1).type(dtype)return p_0

我們來看一下如何將 offset 傳入 self._get_p 獲得最終的 p,該函數會分別調用 self._get_p_0self._get_p_n 來分別獲得 p_0 和 p_n,分別是卷積核的中心坐標和相對坐標,對應到公式中的 p0,pn\mathbf{p}_0,\ \mathbf{p}_np0?,?pn?
y(p0)=∑pn∈Rw(pn)?x(p0+pn+Δpn)\mathbf{y}(\mathbf{p}_0)=\sum_{\mathbf{p}_n\in \mathcal{R}}\mathbf{w}(\mathbf{p}_n)\cdot \mathbf{x}(\mathbf{p}_0+\mathbf{p}_n+\Delta\mathbf{p}_n) y(p0?)=pn?R?w(pn?)?x(p0?+pn?+Δpn?)

在這里插入圖片描述

關于 p_0 和 p_n 具體是什么東西其實很好理解,畫個小圖就明白了,以 kernel_size = 3 的卷積為例,中心位置在全圖中的坐標就是 p_0,中心位置的相對坐標就是 p_n=(0,0),左上角的 p_n=(-1,-1),右下角的 p_n=(1,1) 其他位置以此類推。常規的卷積就只有 pn+p0\mathbf{p}_n+\mathbf{p}_0pn?+p0? ,輸入就是只能在上圖中的九個格子中,而 DCN 加入 Δpn\Delta\mathbf{p}_nΔpn? 之后,就可以四處飛啦。但是四處飛,也是要在 pn+p0\mathbf{p}_n+\mathbf{p}_0pn?+p0? 的基礎上再加上偏移量來計算具體的位置。所以我們先要獲得 p_0 和 p_n。

當然,p_0 和 p_n 都是固定的、不需要學習的、而且是很規則的,因此獲取他們只需要根據 kernel_size 和位置 h, w (僅 p_0 需要)來計算就好了。這里代碼實現中就是用 torch.arange 和 torch.meshgrid 將想要的 p_0 和 p_n,計算出來。

然后 p = p_0 + p_n + offset(對應公式),得到尺寸為 (bs,2?N,h,w)(bs, 2*N, h, w)(bs,2?N,h,w) 的 p。

_get_x_q

_get_x_q 函數是根據計算出的位置坐標,得到該位置的像素值。

再提醒一下,我們參考的 DCN 的 Pytorch 實現代碼中變量的命名是與原文公式對應的,如果有變量含義不明確的,可以回上面看看公式,對應代碼變量名理解。

def _get_x_q(self, x, q, N):b, h, w, _ = q.size()padded_w = x.size(3)c = x.size(1)# (b, c, h*w)x = x.contiguous().view(b, c, -1)# (b, h, w, N)index = q[..., :N]*padded_w + q[..., N:]  # offset_x*w + offset_y# (b, c, h*w*N)index = index.contiguous().unsqueeze(dim=1).expand(-1, c, -1, -1, -1).contiguous().view(b, c, -1)x_offset = x.gather(dim=-1, index=index).contiguous().view(b, c, h, w, N)return x_offset

_reshape_x_offset

我們在取完像素值之后得到的 x_offset 的形狀是 b,c,h,w,Nb,c,h,w,Nb,c,h,w,N,而我們要的形狀肯定是 b,c,h,wb,c,h,wb,c,h,w,因此這里還有一個 reshape 的操作,就是這里的 self._reshape_x_offset

@staticmethod
def _reshape_x_offset(x_offset, ks):b, c, h, w, N = x_offset.size()x_offset = torch.cat([x_offset[..., s:s+ks].contiguous().view(b, c, h, w*ks) for s in range(0, N, ks)], dim=-1)x_offset = x_offset.contiguous().view(b, c, h*ks, w*ks)return x_offset

小結

至此,我們已經使用 Pytorch 實現了純 Python 的 DCN 卷積結構,但是,如此實現由于不是原生的 C++/CUDA 算子,而且最后的 reshape 操作雖然比較巧妙,但其實空間冗余比較大,和原文作者的 cuda 版本內存占用量差了10幾倍。這個是因為在 im2col 上直接操作可以去掉很冗余。下面一篇我們會再介紹一個 C++/CUDA 實現的 DCN。

Ref

  • deformable convolution可變形卷積(4uiiurz1-pytorch版)源碼分析
  • 圖像預處理之warpaffine與雙線性插值及其高性能實現

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/532448.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/532448.shtml
英文地址,請注明出處:http://en.pswp.cn/news/532448.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

藍牙耳機聲音一頓一頓的_線控耳機黨陣地轉移成功,OPPO這款TWS耳機體驗滿分...

“你看到我手機里3.5mm的耳機孔了嗎”,這可能是許多線控耳機黨最想說的話了。確實,如今手機在做“減法”,而廠商們首先就拿3.5mm耳機孔“開刀”,我們也喪失了半夜邊充電邊戴耳機打游戲的樂趣。竟然如此,那如何在耳機、…

AI移動端優化之Im2Col+Pack+Sgemm

AI移動端優化之Im2ColPackSgemm 轉自:https://blog.csdn.net/just_sort/article/details/108412760 這篇文章是基于NCNN的Sgemm卷積為大家介紹Im2ColPackSgemm的原理以及算法實現,希望對算法優化感興趣或者做深度學習模型部署的讀者帶來幫助。 1. 前言 …

elementui的upload組件怎么獲取上傳的文本流、_抖音feed流直播間引流你還不會玩?實操講解...

本文由艾奇在線明星優化師寫作計劃出品在這個全民驚恐多災多難且帶有魔幻的2020,一場突如其來的疫情改變了人們很多消費習慣,同時加速了直播電商的發展,現在直播已經成為商家必爭的營銷之地,直播雖然很火,但如果沒有流…

FFmpeg 視頻處理入門教程

FFmpeg 視頻處理入門教程 轉自:https://www.ruanyifeng.com/blog/2020/01/ffmpeg.html 作者: 阮一峰 日期: 2020年1月14日 FFmpeg 是視頻處理最常用的開源軟件。 它功能強大,用途廣泛,大量用于視頻網站和商業軟件&…

checkbox wpf 改變框的大小_【論文閱讀】傾斜目標范圍框(標注)的終極方案

前言最常用的斜框標注方式是在正框的基礎上加一個旋轉角度θ,其代數表示為(x_c,y_c,w,h,θ),其中(x_c,y_c )表示范圍框中心點坐標,(w,h)表示范圍框的寬和高[1,2,7]。對于該標注方式,如果將w和h的值互換,再將θ加上或者…

徹底理解BP之手寫BP圖像分類你也行

徹底理解BP之手寫BP圖像分類你也行 轉自:https://zhuanlan.zhihu.com/p/397963213 第一節:用矩陣的視角,看懂BP的網絡圖 1.1、什么是BP反向傳播算法 BP(Back Propagation)誤差反向傳播算法,使用反向傳播算法的多層感知器又稱為B…

h5頁面禁止復制_H5移動端頁面禁止復制技巧

前言:業務需要,需要對整個頁面禁止彈出復制菜單。在禁止的頁面中加入以下css樣式定義* {-webkit-touch-callout:none;/*系統默認菜單被禁用*/-webkit-user-select:none;/*webkit瀏覽器*/-khtml-user-select:none;/*早起瀏覽器*/-moz-user-select:none;/*…

梯度下降法和牛頓法計算開根號

梯度下降法和牛頓法計算開根號 本文將介紹如何不調包,只能使用加減乘除法實現對根號x的求解。主要介紹梯度下降和牛頓法者兩種方法,并給出 C 實現。 梯度下降法 思路/步驟 轉化問題,將 x\sqrt{x}x? 的求解轉化為最小化目標函數&#xff…

匯博工業機器人碼垛機怎么寫_全自動碼垛機器人在企業生產中的地位越來越重要...

全自動碼垛機器人在企業生產中的地位越來越重要在智能化的各種全自動生產線中,全自動碼垛機器人成了全自動生產線的重要機械設備,在各種生產中發揮著不可忽視的作用。全自動碼垛機器人主要用于生產線上的包裝過程中,不僅能夠提高企業的生產率…

kmeans手寫實現與sklearn接口

kmeans手寫實現與sklearn接口 kmeans簡介 K 均值聚類是最基礎的一種聚類方法。它是一種迭代求解的聚類分析算法。 kmeans的迭代步驟 給各個簇中心 μ1,…,μc\mu_1,\dots,\mu_cμ1?,…,μc? 以適當的初值; 更新樣本 x1,…,xnx_1,\dots,x_nx1?,…,xn? 對應的…

小說中場景的功能_《流浪地球》:從小說到電影

2019年春節賀歲檔冒出一匹黑馬:國產科幻片《流浪地球》大年初一上映后口碑、票房雙豐收:截至9日下午,票房已破15億,并獲得9.2的高評分。著名導演詹姆斯卡梅隆通過社交媒體對我國春節期間上映的科幻影片《流浪地球》發出的祝愿&…

線性回歸與邏輯回歸及其實現

線性回歸與邏輯回歸及其實現 回歸與分類 預測值定性分析,即離散變量預測時,稱之為分類;預測值定量分析,即連續變量預測時,稱之為回歸。 如預測一張圖片是貓還是狗,是分類問題;預測明年的房價…

hbase 頁面訪問_HBase

HBase 特點 海量存儲 Hbase 適合存儲 PB 級別的海量數據,在 PB 級別的數據以及采用廉價 PC 存儲的情況下,能在幾十到百毫秒內返回數據。這與 Hbase 的極易擴展性息息相關。正式因為 Hbase 良好的擴展性,才為海量數據的存儲提供了便利。 2&…

深入理解L1、L2正則化

深入理解L1、L2正則化 轉自:【面試看這篇就夠了】L1、L2正則化理解 一、概述 正則化(Regularization)是機器學習中一種常用的技術,其主要目的是控制模型復雜度,減小過擬合。正則化技術已經成為模型訓練中的常用技術&a…

rk3128屏幕占空比參數設置_瑞芯微RK3128芯片怎么樣 性能全面解讀

最近,筆者聽說一款搭載瑞芯微RK3128芯片方案的盒子問市了,打聽了一下才知道還真有其事,這款上市的RK3128盒子叫做開博爾M1,報價229元,這個價位在如今的四核網絡機頂盒市場可謂是不多見,但是這款芯片的性能怎…

機器學習中的概率模型

機器學習中的概率模型 轉自:https://zhuanlan.zhihu.com/p/164551678 機器學習中的概率模型 概率論,包括它的延伸-信息論,以及隨機過程,在機器學習中有重要的作用。它們被廣泛用于建立預測函數,目標函數,以…

訪問云服務器儲存的mp4_訪問云服務器儲存的mp4

{"moduleinfo":{"card_count":[{"count_phone":1,"count":1}],"search_count":[{"count_phone":6,"count":6}]},"card":[{"des":"云服務器 ECS(Elastic Compute Service)是一…

先驗、后驗、似然

先驗、后驗、似然 先驗分布、后驗分布和似然函數 本節轉自:先驗分布、后驗分布、似然估計這幾個概念是什么意思,它們之間的關系是什么? 通俗解釋 先驗分布:根據一般的經驗認為隨機變量應該滿足的分布。先驗分布是你瞎猜參數服從啥…

max std value 宏_Rust Macro/宏 新手指南

Rust語言最強大的一個特點就是可以創建和利用宏/Macro。不過創建 Rust宏看起來挺復雜,常常令剛接觸Rust的開發者心生畏懼。這片文章 的目的就是幫助你理解Rust Macro的基本運作原理,學習如何創建自己的 Rust宏。相關鏈接:在線學編程 - 匯智網…

高斯分布及其極大似然估計

高斯分布及其極大似然估計 高斯分布 一維高斯分布 一維高斯分布的概率密度函數為: N(μ,σ2)12πσexp?(?(x?μ)22σ2)N(\mu,\sigma^2)\frac{1}{\sqrt{2\pi}\sigma}\exp(-\frac{(x-\mu)^2}{2\sigma^2}) N(μ,σ2)2π?σ1?exp(?2σ2(x?μ)2?) 多維高斯分布…