天天看點

0802_轉載-nn子產品中的網絡層介紹

這篇文章的内容到這裡就差不多了,這次以基礎的内容為主,簡單的梳理一下,首先我們上次知道了建構神經網絡的兩個步驟:搭建子子產品和拼接子子產品。而這次就是學習各個子子產品的使用。從比較重要的卷積層開始,學習了1d 2d 3d 卷積到底在幹什麼事情,采用了動圖的方式進行示範,卷積運算其實就是通過不同的卷積核去提取不同的特征。然後學習了 Pytorch 的二維卷積運算及轉置卷積運算,并進行了對比和分析了代碼上如何實作卷積操作。

第二塊是池化運算和池化層的學習,關于池化,一般和卷積一塊使用,目的是收集和合并卷積提取的特征,去除一些備援,分為最大池化和平均池化。然後學習了全連接配接層,這個比較簡單,不用多說,最後是非線性激活函數,比較常用的 sigmoid,tanh, relu等。

今天的内容就到這裡,模型子產品基本上到這裡也差不多了,根據我們的那個步驟:資料子產品 -> 模型子產品 -> 損失函數 -> 優化器 -> 疊代訓練。是以下一次開始學習損失函數子產品,但是在學習損失函數之前,還得先看一下常用的權重初始化方法,這個對于模型來說也是非常重要的。是以下一次整理權值初始化和損失函數。

0802_轉載-nn 子產品中的網絡層介紹

目錄

  • 一、寫在前面
  • 二、卷積運算與卷積層
    • 2.1 1d 2d 3d 卷積示意
    • 2.2 nn.Conv2d
    • 2.3 轉置卷積
  • 三、池化層
  • 四、線性層
  • 五、激活函數層
  • 六、總結

pytorch完整教程目錄:https://www.cnblogs.com/nickchen121/p/14662511.html

一、寫在前面

疫情在家的這段時間,想系統的學習一遍 Pytorch 基礎知識,因為我發現雖然直接 Pytorch 實戰上手比較快,但是關于一些内部的原理知識其實并不是太懂,這樣學習起來感覺很不踏實,對 Pytorch 的使用依然是模模糊糊, 跟着人家的代碼用 Pytorch 玩神經網絡還行,也能讀懂,但自己親手做的時候,直接無從下手,啥也想不起來, 我覺得我這種情況就不是對于某個程式練得不熟了,而是對 Pytorch 本身在自己的腦海根本沒有形成一個概念架構,不知道它内部運作原理和邏輯,是以自己寫的時候沒法形成一個代碼邏輯,就無從下手。這種情況即使背過人家這個程式,那也隻是某個程式而已,不能說會 Pytorch, 并且這種背程式的思想本身就很可怕, 是以我還是習慣學習知識先有架構(至少先知道有啥東西)然後再通過實戰(各個東西具體咋用)來填充這個架構。而「這個系列的目的就是在腦海中先建一個 Pytorch 的基本架構出來, 學習知識,知其然,知其是以然才更有意思 」。

今天是該系列的第五篇文章, 通過上一篇内容我們已經知道了如何搭模組化型并且也學習了關于搭模組化型非常重要的一個類 nn.Module 和模型容器 Containers。搭模組化型我們提到兩個步驟,建立子子產品和拼接子子產品。而這次我們再細一點,具體學習幾個重要的子子產品,比如卷積層,池化層,激活函數,全連接配接層等。首先我們從卷積層開始, 學習一下 1/2/3 次元卷積,然後學習一下

nn.Conv2d

這個方法,最後介紹一下什麼是轉置卷積。然後我們進行池化層的學習,包括平均池化和最大池化,然後介紹一下全連接配接層,這個一般稱為線性層, 最後我們介紹一些常用的非線性激活函數,為什麼會用到非線性激活函數?一句話,沒有非線性激活函數,再深的神經網絡也沒有“深度”的意義。這一篇文章中會見識到很多的動圖,通過這些動圖,可以更好的了解卷積,池化到底在做什麼。

「大綱如下」:

  • 卷積運算與卷積層(1/2/3d卷積,

    nn.Conv2d, nn.ConvTranspose

    )
  • 池化運算與池化層(最大池化和平均池化)
  • 全連接配接層
  • 非線性激活函數層
  • 總結梳理

下面是一張導圖把知識拎起來:

0802_轉載-nn子產品中的網絡層介紹

二、卷積運算與卷積層

說卷積層,我們得先從卷積運算開始,卷積運算就是卷積核在輸入信号(圖像)上滑動, 相應位置上進行「乘加」。卷積核又稱為濾過器,過濾器,可認為是某種模式,某種特征。

卷積過程類似于用一個模闆去圖像上尋找與它相似的區域, 與卷積核模式越相似, 激活值越高, 進而實作特征提取。好吧,估計依然懵逼,下面我們就看看 1d、2d、3d的卷積示意圖,通過動圖的方式看看卷積操作到底在幹啥?

2.1 1d 2d 3d 卷積示意

一般情況下,卷積核在幾個次元上滑動,就是幾維卷積。下面再看幾張動圖感受一下不同次元的卷積操作,注意下面都是一個卷積核:

一維卷積示意:

0802_轉載-nn子產品中的網絡層介紹

二維卷積示意:

0802_轉載-nn子產品中的網絡層介紹

三維卷積示意:

0802_轉載-nn子產品中的網絡層介紹

2.2 nn.Conv2d

nn.Conv2d

: 對多個二維信号進行二維卷積:

0802_轉載-nn子產品中的網絡層介紹

主要參數:

  • in_channels: 輸入通道數
  • out_channels: 輸出通道數, 等價于卷積核個數
  • kernel_size: 卷積核尺寸, 這個代表着卷積核的大小
  • stride: 步長, 這個指的卷積核滑動的時候,每一次滑動幾個像素。下面看個動圖來了解步長的概念:左邊那個的步長是 1, 每一次滑動 1 個像素,而右邊的步長是 2,會發現每一次滑動 2個像素。
    0802_轉載-nn子產品中的網絡層介紹
  • padding: 填充個數,通常用來保持輸入和輸出圖像的一個尺寸的比對,依然是一個動圖展示,看左邊那個圖,這個是沒有 padding 的卷積,輸入圖像是 4 * 4,經過卷積之後,輸出圖像就變成了 2 * 2 的了,這樣分辨率會遍變低,并且我們會發現這種情況卷積的時候邊緣部分的像素參與計算的機會比較少。是以加入考慮 padding 的填充方式,這個也比較簡單,就是在原輸入周圍加入像素,這樣就可以保證輸出的圖像尺寸分辨率和輸入的一樣,并且邊緣部分的像素也受到同等的關注了。
    0802_轉載-nn子產品中的網絡層介紹
  • dilation: 孔洞卷積大小,下面依然是一個動圖:
    0802_轉載-nn子產品中的網絡層介紹
    • 孔洞卷積就可以了解成一個帶孔的卷積核,常用于圖像分割任務,主要功能就是提高感受野。也就是輸出圖像的一個參數,能看到前面圖像更大的一個區域。
  • groups: 分組卷積設定,分組卷積常用于模型的輕量化。我們之前的 AlexNet 其實就可以看到分組的身影, 兩組卷積分别進行提取,最後合并。
    0802_轉載-nn子產品中的網絡層介紹
  • bias: 偏置

下面是尺寸計算的方式:

  1. 沒有 padding:\(out_{size}=\frac{In_{size}-kernel_{size}}{stride}+1\)
  2. 如果有 padding 的話:\(out_{size}=\frac{In_{size}+2*padding_{size}-kernel_{size}}{stride}+1\)
  3. 如果再加上孔洞卷積的話:\(out_{size}=\frac{In_{size}+2*padding_{size}+dilation_{size}*(kernel_{size}-1)-1}{stride}+1\)

下面我們用代碼看看卷積核是怎麼提取特征的,畢竟有圖才有真相:

0802_轉載-nn子產品中的網絡層介紹

接下來,我們改變seed, 也就是相當于換一組卷積核, 看看提取到什麼樣的特征:

0802_轉載-nn子產品中的網絡層介紹

再換一個随機種子:

0802_轉載-nn子產品中的網絡層介紹

通過上面,我們會發現不同權重的卷積核代表不同的模式,會關注不同的特征,這樣我們隻要設定多個卷積核同時對圖檔的特征進行提取,就可以提取不同的特征。

下面我們看一下圖像尺寸的變化:

卷積前尺寸:torch.Size([1, 3, 512, 512])
卷積後尺寸:torch.Size([1, 1, 510, 510])
           

卷積前,圖像尺寸是 \(512*512\),卷積後,圖像尺寸是 \(510*510\) 。我們這裡的卷積核設定,輸入通道 3,卷積核個數 1,卷積核大小 3,無 padding,步長是 1,那麼我們根據上面的公式,輸出尺寸:\((512-3)/1+1=510\)

下面再來看一下卷積層有哪些參數:我們知道卷積層也是繼承于

nn.Module

的,是以肯定又是那 8 個字典屬性, 我們主要看看它的

_modules

參數和

_parameters

參數字典。

0802_轉載-nn子產品中的網絡層介紹

我們可以看到 Conv2d 下面的

_parameters

存放着權重參數,這裡的 weight 的形狀是 [1, 3, 3, 3], 這個應該怎麼了解呢?首先 1 代表着卷積核的個數,第 1 個 3 表示的輸入通道數,後面兩個 3 是二維卷積核的尺寸。那麼這裡有人可能會有疑問,我們這裡是3維的卷積核啊,怎麼實作的二維卷積呢?下面再通過一個示意圖看看:

0802_轉載-nn子產品中的網絡層介紹

我們的圖像是 RGB 3 個通道的圖像,我們建立 3 個二維的卷積核,這 3 個二維的卷積核分别對應一個通道進行卷積,比如紅色通道上,隻有一個卷積核在上面滑動,每一次滑動,對應元素相乘然後相加得到一個數, 這三個卷積核滑動一次就會得到三個數,這三個數之和加上偏置才是我們的一個輸出結果。這裡我們看到了,一個卷積核隻在 2 個次元上滑動,是以最後得到的就是 2 維卷積。這也能了解開始的卷積次元的概念了(一般情況下,卷積核在幾個次元上滑動,就是幾維卷積),為什麼最後會得到的 3 維的張量呢?這是因為我們不止這一個卷積核啊,我們有多個卷積核的時候,每個卷積核都産生一個二維的結果,那麼最後的輸出不就成 3 維的了,第三個次元就是卷積核的個數了。下面用一個網站上的神圖來看一下多個卷積核的提取特征,下面每一塊掃描都是對應元素相乘再相加得到最後的的結果:

0802_轉載-nn子產品中的網絡層介紹

上面這一個是一個三維的卷積示意,并且使用了 2 個卷積核。最後會得到 2 個二維的張量。

二維卷積差不多說到這裡吧,不明白我也沒招了,我這已經渾身解數了,看這些動圖也能看到吧,哈哈。畢竟這裡主要講 Pytorch,關于這些深度學習的基礎這裡不做過多的描述。下面再介紹一個轉置卷積,看看這又是個啥?

2.3 轉置卷積

轉置卷積又稱為反卷積和部分跨越卷積(當然轉置卷積這個名字比逆卷積要好,原因在下面),用于對圖像進行上采樣。在圖像分割任務中經常被使用。首先為什麼它叫轉置卷積呢?

在解釋這個之前,我們得先來看看正常的卷積在代碼實作過程中的一個具體操作:對于正常的卷積,我們需要實作大量的相乘相加操作,而這種乘加的方式恰好是矩陣乘法所擅長的。是以在代碼實作的時候,通常會借助矩陣乘法快速的實作卷積操作, 那麼這是怎麼做的呢?

我們假設圖像尺寸為 \(4*4\) ,卷積核為 \(3*3\) ,

padding=0, stride=1

,也就是下面這個圖:

0802_轉載-nn子產品中的網絡層介紹

首先将圖像尺寸的 \(4*4\) 拉長成 \(16*1\) ,16 代表所有的像素,1 代表隻有 1 張圖檔。然後 \(3*3\) 的卷積核會變成一個 \(4*16\) 的一個矩陣,一臉懵逼了,這是怎麼變的,首先這個 16,是先把 9 個權值拉成一列,然後下面補 7 個 0 變成16, 這個 4 是根據我們輸出的尺寸計算的,根據輸入尺寸,卷積核大小,padding, stride 資訊可以得到輸出尺寸是 \((4-3)/1+1=2\), 是以輸出是 \(2*2\),那麼拉成一列就是 4。這樣我們的輸出:\(O_{4*4}=K_{4*16}*I_{16*1}\)

這樣就得到了最後一列輸出 4 個元素,然後 reshape 就得到了 \(2*2\) 的一個輸出特征圖了。這就是用矩陣乘法輸出一個二維卷積的這樣一個示例。這就是那個過程:

0802_轉載-nn子產品中的網絡層介紹

下面我們看看轉置卷積是怎麼樣的:

轉置卷積是一個上采樣,輸入的圖像尺寸是比較小的,經過轉置卷積之後,會輸出一個更大的圖像,看下面示意圖:

0802_轉載-nn子產品中的網絡層介紹

我們這裡的輸入圖像尺寸是 \(2*2\), 卷積核為 \(3*3\),

padding=0, stride=1

,我們的輸入圖像尺寸是 \(4*4\),我們看看這個在代碼中是怎麼通過矩陣乘法進行實作的。首先,依然是把輸入的尺寸進行拉長,成一個 \(4*1\) 的一個向量,然後我們的卷積核會變成 \(16*4\) 的,注意這裡的卷積核尺寸,這個 4 依然是根據卷積核得來的,記得上面的那個 16 嗎?我們是把卷積核拉長然後補 0, 在這裡我們不是補 0 了,而是采用剔除的方法,因為我們根據上面的圖像可以發現,雖然這裡的卷積核有 9 個權值,可是能與圖像相乘的最多隻有四個(也就是卷積核在中間的時候),是以這裡采用剔除的方法,從 9 個權值裡面剔除 5 個,得到 4 個權重, 而這個 16,依然是根據輸出圖像的尺寸計算得來的。因為我們這裡的輸出是 \(4*4\), 這個可以用上面尺寸運算的逆公式。是以這裡的輸出:\(O_{16*1}=K_{16*4}*I_{4*1}\)

這次注意這個卷積核的尺寸是 \(16*4\),而我們正常卷積運算的卷積核尺寸 \(4*16\),是以在形狀上這兩個卷積操作卷積核恰恰是轉置的關系,這也就是轉置卷積的由來了。這是因為卷積核在轉變成矩陣的時候,與正常卷積的卷積核形狀上是互為轉置,注意是形狀,具體數值肯定是不一樣的。是以正常卷積核轉置卷積的關系并不是可逆的,故逆卷積這個名字不好。下面就具體學習 Pytorch 提供的轉置卷積的方法:

nn.ConvTranspose2d

: 轉置卷積實作上采樣

0802_轉載-nn子產品中的網絡層介紹

這個參數和卷積運算的參數差不多,就不再解釋一遍了。

下面看看轉置卷積的尺寸計算(卷積運算的尺寸逆):

  1. 無 padding:\(out_{size} = (in_{size}-1)*Stride+kerne_{size}\)
  2. 有 padding:\(out_{size} = (in_{size}-1)*Stride+kerne_{size}-2*padding_{size}\)
  3. 有 padding 和孔洞卷積:\(out_{size} = (in_{size}-1)*Stride+kerne_{size}-2*padding_{size}+(dilation_{size}-1)+1\)

下面從代碼中看看轉置卷積怎麼用:

0802_轉載-nn子產品中的網絡層介紹

轉置卷積有個通病叫做“棋盤效應”,看上面圖,這是由于不均勻重疊導緻的。至于如何解決,這裡就不多說了。

關于尺寸變化:

卷積前尺寸:torch.Size([1, 3, 512, 512])
卷積後尺寸:torch.Size([1, 1, 1025, 1025])
           

我們發現,輸入圖像是 512 的,卷積核大小是 3,stride=2, 是以輸出尺寸:\((512-1)*2+3=1025\)

簡單梳理,卷積部分主要是卷積運算,卷積尺寸的計算,然後又學習了轉置卷積。下面我們看看 nn 中其他常用的層。

三、池化層

池化運算:對信号進行“「收集」”并“「總結」”, 類似水池收集水資源, 因而美其名曰池化層。

  • 收集:多變少,圖像的尺寸由大變小
  • 總結:最大值/平均值

下面是一個最大池化的動态圖看一下(平均池化就是這些元素去平均值作為最終值):

0802_轉載-nn子產品中的網絡層介紹

最大池化就是這些元素裡面去最大的值作為最終的結果。

下面看看 Pytorch 提供的最大池化和平均池化的函數——

nn.MaxPool2d

: 對二維信号(圖像)進行最大值池化。

0802_轉載-nn子產品中的網絡層介紹
  • kernel_size: 池化核尺寸
  • stride: 步長
  • padding: 填充個數
  • dilation: 池化核間隔大小
  • ceil_mode: 尺寸向上取整
  • return_indices: 記錄池化像素索引

前四個參數和卷積的其實類似,最後一個參數常在最大值反池化的時候使用,那什麼叫最大值反池化呢?看下圖:

0802_轉載-nn子產品中的網絡層介紹

反池化就是将尺寸較小的圖檔通過上采樣得到尺寸較大的圖檔,看右邊那個,那是這些元素放到什麼位置呢? 這時候就需要當時最大值池化記錄的索引了。用來記錄最大值池化時候元素的位置,然後在最大值反池化的時候把元素放回去。

下面看一下最大池化的效果:

0802_轉載-nn子產品中的網絡層介紹

可以發現,圖像基本上看不出什麼差别,但是圖像的尺寸減少了一半, 是以池化層是可以幫助我們剔除一些備援像素的。

除了最大池化,還有一個叫做平均池化——

nn.AvgPool2d

: 對二維信号(圖像)進行平均值池化

0802_轉載-nn子產品中的網絡層介紹
  • count_include_pad: 填充值用于計算
  • divisor_override: 除法因子, 這個是求平均的時候那個分母,預設是有幾個數相加就除以幾,當然也可以自己通過這個參數設定

下面也是通過代碼看一下結果:

0802_轉載-nn子產品中的網絡層介紹

這個平均池化和最大池化在這上面好像看不出差別來,其實最大池化的亮度會稍微亮一些,畢竟它都是取的最大值,而平均池化是取平均值。

好了,這就是池化操作了,下面再整理一個反池化操作,就是上面提到的

nn.MaxUnpool2d

: 這個的功能是對二維信号(圖像)進行最大池化上采樣

0802_轉載-nn子產品中的網絡層介紹

這裡的參數與池化層是類似的。唯一的不同就是前向傳播的時候我們需要傳進一個 indices, 我們的索引值,要不然不知道把輸入的元素放在輸出的哪個位置上呀,就像上面的那張圖檔:

0802_轉載-nn子產品中的網絡層介紹

下面通過代碼來看一下反池化操作:

0802_轉載-nn子產品中的網絡層介紹

四、線性層

線性層又稱為全連接配接層,其每個神經元與上一層所有神經元相連實作對前一層的「線性組合,線性變換」

線性層的具體計算過程在這裡不再贅述,直接學習 Pytorch 的線性子產品。

nn.Linear(in_features, out_features, bias=True)

: 對一維信号(向量)進行線性組合

  • in_features: 輸入節點數
  • out_features: 輸出節點數
  • bias: 是否需要偏置

計算公式:\(y=xW^T+bias\)

0802_轉載-nn子產品中的網絡層介紹

下面可以看代碼實作:

inputs = torch.tensor([[1., 2, 3]])
linear_layer = nn.Linear(3, 4)
linear_layer.weight.data = torch.tensor([[1., 1., 1.], [2., 2., 2.],
                                         [3., 3., 3.], [4., 4., 4.]])

linear_layer.bias.data.fill_(0.5)
output = linear_layer(inputs)
print(inputs, inputs.shape)
print(linear_layer.weight.data, linear_layer.weight.data.shape)
print(output, output.shape)
           

這個就比較簡單了,不多說。

五、激活函數層

激活函數 Udine 特征進行非線性變換, 賦予多層神經網絡具有「深度」的意義。

如果沒有激活函數,我們可以看一下下面的計算:

0802_轉載-nn子產品中的網絡層介紹

我們如果沒有激活函數, 那麼:

\[H_1 = X*W_1 \\

H_2 = H_1*W_2 \\

\begin{align}

\quad\quad\quad\quad\quad\quad\quad{Output} &= H_2*W_3 \\

&=H_1*W_2*W_3 \\

&=X*(W_1*W_2*W_3)\\

&= X*W\\

\end{align}

\]

這裡就可以看到,一個三層的全連接配接層,其實和一個線性層一樣。這是因為我們線性運算的矩陣乘法的結合性,無論多少個線性層的疊加,其實就是矩陣的一個連乘,最後還是一個矩陣。是以如果沒有激活函數,再深的網絡也沒有啥意義。

下面介紹幾個常用的非線性激活函數:

  1. sigmoid 函數:
    1. 0802_轉載-nn子產品中的網絡層介紹
  2. nn.tanh:
    1. 0802_轉載-nn子產品中的網絡層介紹
  3. nn.ReLU:
    1. 0802_轉載-nn子產品中的網絡層介紹

ReLU 相對于前面的兩個,效果要好一些, 因為不容易造成梯度消失,但是依然存在問題,是以下面就是對ReLU 進行的改進。

0802_轉載-nn子產品中的網絡層介紹

六、總結

這篇文章的内容到這裡就差不多了,這次以基礎的内容為主,簡單的梳理一下,首先我們上次知道了建構神經網絡的兩個步驟:搭建子子產品和拼接子子產品。而這次就是學習各個子子產品的使用。從比較重要的卷積層開始,學習了1d 2d 3d 卷積到底在幹什麼事情,采用了動圖的方式進行示範,卷積運算其實就是通過不同的卷積核去提取不同的特征。然後學習了 Pytorch 的二維卷積運算及轉置卷積運算,并進行了對比和分析了代碼上如何實作卷積操作。

第二塊是池化運算和池化層的學習,關于池化,一般和卷積一塊使用,目的是收集和合并卷積提取的特征,去除一些備援,分為最大池化和平均池化。然後學習了全連接配接層,這個比較簡單,不用多說,最後是非線性激活函數,比較常用的 sigmoid,tanh, relu等。

今天的内容就到這裡,模型子產品基本上到這裡也差不多了,根據我們的那個步驟:資料子產品 -> 模型子產品 -> 損失函數 -> 優化器 -> 疊代訓練。是以下一次開始學習損失函數子產品,但是在學習損失函數之前,還得先看一下常用的權重初始化方法,這個對于模型來說也是非常重要的。是以下一次整理權值初始化和損失函數。

繼續Rush