聊聊池化層和步長為2的卷積層

華為雲開發者聯盟發表於2023-03-22
摘要:對於池化層和步長為2的卷積層來說,個人的理解是這樣的,池化層是一種先驗的下采樣方式,即人為的確定好下采樣的規則;而對於步長為2的卷積層來說,其引數是透過學習得到的,取樣的規則是不確定的。

本文分享自華為雲社群《對於池化層和步長為2的卷積層的一些思考》,作者: 李長安。

引言

對於池化層和步長為2的卷積層的思考源於前一段時間對於2.0文件API的評估。自從ResNet開始,大家逐漸使用步長為2的卷積層替代Size為2的池化層,二者都是對特徵圖進行下采樣的操作。池化層的主要意義(目前的主流看法,但是有相關論文反駁這個觀點)在於invariance(不變性),這個不變性包括平移不變性、尺度不變性、旋轉不變形。其過程如下圖所示。

聊聊池化層和步長為2的卷積層聊聊池化層和步長為2的卷積層聊聊池化層和步長為2的卷積層

對於池化層和步長為2的卷積層來說,個人的理解是這樣的,池化層是一種先驗的下采樣方式,即人為的確定好下采樣的規則;而對於步長為2的卷積層來說,其引數是透過學習得到的,取樣的規則是不確定的。下面對兩種下采樣方式進行一組對比實驗,實驗設計的可能不夠嚴謹,歡迎大家在評論區討論。

實驗設計

本次對比實驗採用LeNet進行對比,目的在於簡單的說明池化層與步長為2的卷積層之前的區別。採用MNIST資料集。

1、匯入paddle,使用2.0版本的paddle

import paddle
print(paddle.__version__)
2.0.0-rc0

2、匯入訓練資料和測試資料

train_dataset = paddle.vision.datasets.MNIST(mode='train')
test_dataset = paddle.vision.datasets.MNIST(mode='test')

3、檢視資料

%matplotlib notebook
import numpy as np
import matplotlib.pyplot as plt
train_data0, train_label_0 = train_dataset[0][0],train_dataset[0][1]
train_data0 = train_data0.reshape([28,28])
plt.figure(figsize=(2,2))
plt.imshow(train_data0, cmap=plt.cm.binary)
print('train_data0 label is: ' + str(train_label_0))

4、構建LeNet5網路

import paddle.nn.functional as F
class LeNet(paddle.nn.Layer):
 def __init__(self):
 super(LeNet, self).__init__()
 self.conv1 = paddle.nn.Conv2D(in_channels=1, out_channels=6, kernel_size=5, stride=1, padding=2)
        self.max_pool1 = paddle.nn.MaxPool2D(kernel_size=2,  stride=2)
 self.conv2 = paddle.nn.Conv2D(in_channels=6, out_channels=16, kernel_size=5, stride=1)
        self.max_pool2 = paddle.nn.MaxPool2D(kernel_size=2, stride=2)
 self.linear1 = paddle.nn.Linear(in_features=16*5*5, out_features=120)
 self.linear2 = paddle.nn.Linear(in_features=120, out_features=84)
 self.linear3 = paddle.nn.Linear(in_features=84, out_features=10)
 def forward(self, x):
        x = self.conv1(x)
        x = F.relu(x)
        x = self.max_pool1(x)
        x = F.relu(x)
        x = self.conv2(x)
        x = self.max_pool2(x)
 # print(x.shape)
        x = paddle.flatten(x, start_axis=1,stop_axis=-1)
        x = self.linear1(x)
        x = F.relu(x)
        x = self.linear2(x)
        x = F.relu(x)
        x = self.linear3(x)
 return x

5、模型封裝與配置

from paddle.metric import Accuracy
model2 = paddle.Model(LeNet()) # 用Model封裝模型
optim = paddle.optimizer.Adam(learning_rate=0.001, parameters=model2.parameters())
# 配置模型
model2.prepare(
 optim,
 paddle.nn.CrossEntropyLoss(),
 Accuracy(topk=(1, 2))
 )

6、模型訓練,這裡進行10次迭代。

# 訓練模型
model2.fit(train_dataset,
        epochs=10,
 batch_size=64,
        verbose=1
 )

7、驗證模型

model2.evaluate(test_dataset, batch_size=64, verbose=1)
Eval begin...
step 157/157 [==============================] - loss: 1.4789e-05 - acc_top1: 0.9810 - acc_top2: 0.9932 - 3ms/step       
Eval samples: 10000
{'loss': [1.4788801e-05], 'acc_top1': 0.981, 'acc_top2': 0.9932}

8、構建使用步長為2的卷積層替代池化層的LeNet5

import paddle.nn.functional as F
class LeNet_nopool(paddle.nn.Layer):
 def __init__(self):
 super(LeNet_nopool, self).__init__()
 self.conv1 = paddle.nn.Conv2D(in_channels=1, out_channels=6, kernel_size=5, stride=1, padding=2)
 # self.max_pool1 = paddle.nn.MaxPool2D(kernel_size=2,  stride=2)
 self.conv2 = paddle.nn.Conv2D(in_channels=6, out_channels=16, kernel_size=5, stride=2)
 self.conv3 = paddle.nn.Conv2D(in_channels=16, out_channels=16, kernel_size=3, stride=1, padding=1)
 self.conv4 = paddle.nn.Conv2D(in_channels=16, out_channels=16, kernel_size=3, stride=2)
 # self.max_pool2 = paddle.nn.MaxPool2D(kernel_size=2, stride=2)
 self.linear1 = paddle.nn.Linear(in_features=16*5*5, out_features=120)
 self.linear2 = paddle.nn.Linear(in_features=120, out_features=84)
 self.linear3 = paddle.nn.Linear(in_features=84, out_features=10)
 def forward(self, x):
        x = self.conv1(x)
 # print(x.shape)
        x = F.relu(x)
        x = self.conv2(x)
 # print(x.shape)
        x = F.relu(x)
        x = self.conv3(x)
 # print(x.shape)
        x = F.relu(x)
        x = self.conv4(x)
 # print(x.shape)
        x = paddle.flatten(x, start_axis=1,stop_axis=-1)
        x = self.linear1(x)
        x = F.relu(x)
        x = self.linear2(x)
        x = F.relu(x)
        x = self.linear3(x)
 return x

9、模型配置與訓練

from paddle.metric import Accuracy
model3 = paddle.Model(LeNet_nopool()) # 用Model封裝模型
optim = paddle.optimizer.Adam(learning_rate=0.001, parameters=model3.parameters())
# 配置模型
model3.prepare(
 optim,
 paddle.nn.CrossEntropyLoss(),
 Accuracy(topk=(1, 2))
 )
# 訓練模型
model3.fit(train_dataset,
        epochs=10,
 batch_size=64,
        verbose=1
 )

10、模型驗證

model3.evaluate(test_dataset, batch_size=64, verbose=1)
Eval begin...
step 157/157 [==============================] - loss: 1.7807e-06 - acc_top1: 0.9837 - acc_top2: 0.9964 - 3ms/step         
Eval samples: 10000
{'loss': [1.7806786e-06], 'acc_top1': 0.9837, 'acc_top2': 0.9964}

實驗結果分析

從兩者在MNIST測試集上的結果來看,使用步長為2的卷積層替代池化層,其模型的表現略高於原始的LeNet5。表明使用卷積層代替池化層是對模型表現有較好的提升。但是改進之後的LeNet5在引數量上是高於原始的LeNet5的,

11、引數量對比

#改進的LeNet5
print('# model3 parameters:', sum(param.numel() for param in model3.parameters()))
# model3 parameters: Tensor(shape=[1], dtype=int64, place=CUDAPlace(0), stop_gradient=True,
 [66346])
#原始的LeNet5
, dtype=int64, place=CUDAPlace(0), stop_gradient=True,
 [66346])
```python
#原始的LeNet5
print('# model2 parameters:', sum(param.numel() for param in model.parameters()))
# model2 parameters: Tensor(shape=[1], dtype=int64, place=CUDAPlace(0), stop_gradient=True,
 [61706])

總結

(1)從影像成像角度來看,影像在成像過程中接收模擬訊號變成電訊號再儲存的陣列都不是同時的。即圖片上每一點都是有時序的。結合影像的時域資訊進行多模態訓練可能會有突破。

(2)在影像中應用夏農定理,下采樣越多,資訊丟失越多,對於CNN中池化層的討論,大家可以參考:CNN真的需要下采樣(上取樣)嗎?

(3)對於池化層不一樣的看法,證偽:CNN中的圖片平移不變性

(4)實際上已經有眾多大佬對這個進行過論證,但是對於大家來說,自己動手永遠比聽別人講來得更好,希望能和大家一起成長。

 

點選關注,第一時間瞭解華為雲新鮮技術~

相關文章