卷積神經網絡概念及使用 PyTorch 簡單實現


卷積神經網絡

  卷積神經網絡(CNN)是深度學習的代表算法之一  。具有表征學習能力,能夠按其階層結構對輸入信息進行平移不變分類,因此也被稱為“平移不變人工神經網絡”隨着深度學習理論的提出和數值計算設備的改進,卷積神經網絡得到了快速發展,並被應用於 計算機視覺自然語言處理等領域

  卷積是通過兩個函數 f,g 生成第三個函數的一種數學算子,表征函數 f 與 g 經過翻轉和平移的重疊部分的面積。數學定義公式:

  事實上,在卷積網絡上使用的離散卷積,也就是不連續的,它是一種運算方式,也就是按照卷積核,將輸入對應位置的數據進行加權和運算,接下來結合卷積核的概念,就會很好理解了。

  卷積神經網絡利用卷積結構減少需要學習的參數量,從而提高反向傳播算法的訓練效率。在卷積神經網絡中,第一個卷積層會直接接受圖像像素級的輸入,每一個卷積操作只處理一小塊圖像,進行卷積操作后傳遞到后面的網絡,每一層卷積都會提取數據中最有效的特征。這種方法可以提取到圖像中最基礎的特征,比如不同方向的拐角或者邊,而后進行組合和抽象成更高階的特征,因此卷積神經網絡對圖像縮放、平移和旋轉具有不變性。

卷積神經網絡的要點就是卷積層中的局部連接、權值共享、和池化層中降采樣。局部連接、權值共享和降采樣降低了參數量,使得訓練復雜度大大降低,並減輕了過擬合的風險。同時還賦予了卷積神經網絡對平移、形變、尺度的某種程度的不變性,提高了模型的泛化能力

 

 卷積神經網絡最重要的兩個知識點就是卷積核卷積神經網絡的結構

  • 卷積核
    • 卷積核定義
    • 卷積操作
    • 深度
    • 步幅
    • 零填充
  • 卷積神經網絡的結構
    • 輸入層 INPUT
    • 卷積層 CONV
    • 激活函數層 RELU
    • 池化層 POOL
    • 全連接層 FC

 

卷積核

​  卷積核是整個網絡的核心,訓練 CNN 的過程就是不斷更新卷積核參數直到最優的過程。

  卷積核的定義:對於輸入圖像中的一部分區域,進行加權平均的處理,其中這個過程的權重,由一個函數定義,這個函數就是卷積核。

如下圖彩色圖像有RGB三個色值通道,分別表示紅、綠、藍,每個通道內的像素可以用一個像下圖右邊的二維數組表示,數值代表0-255之間的像素值。假設一張900*600的彩色的圖片,計算機里面可以用 (900*600*3)的數組表示。

卷積過程

  卷積過程是基於一個小矩陣,也就是卷積核,在上面所說的每層像素矩陣上不斷按步長掃過去的,掃到數與卷積核對應位置的數相乘,然后求總和,每掃一次,得到一個值,全部掃完則生成一個新的矩陣。如下圖

  卷積核如何設置可以參考卷積神經網絡的卷積核大小、個數。卷積層數一般取(3,3)的小矩陣,卷積核里面每個值就是我們需要尋找(訓練)的神經元參數(權重),開始會隨機有個初始值,當訓練網絡時,網絡會通過后向傳播不斷更新這些參數值,直到尋找到最佳的參數值。“最佳”、需要通過損失函數去評估。

  卷積操作相當於特征提取,卷積核相當於一個過濾器,提取我們需要的特征。

如下圖卷積操作,從左上角掃到右下角,最終得到右邊的特征圖譜。

 

  圖解:一個過濾器(紅色邊框)在輸入圖像上移動(卷積操作)以生成特征映射。在同一張圖像上,另一個過濾器(綠色邊框)的卷積生成了不同的特征圖。需要注意到,卷積操作捕獲原始圖像中的局部依賴關系很重要。還要注意這兩個不同的過濾器如何從同一張原始圖像得到不同的特征圖。請記住,以上圖像和兩個過濾器只是數值矩陣。
  實際上,卷積神經網絡在訓練過程中會自己學習這些過濾器的值(盡管在訓練過程之前我們仍需要指定諸如過濾器數目、大小,網絡框架等參數)。過濾器數目越多,提取的圖像特征就越多,識別新圖像時效果就會越好。
 
特征映射(卷積特征)的大小由執行卷積步驟之前需要決定的三個參數控制:
  • 深度:深度對應於我們用於卷積運算的過濾器數量。在圖7所示的網絡中,我們使用三個不同的過濾器對初始的船圖像進行卷積,從而生成三個不同的特征圖。可以將這三個特征地圖視為堆疊的二維矩陣,因此,特征映射的“深度”為3。
  • 步幅:步幅是在輸入矩陣上移動一次過濾器矩陣的像素數量。當步幅為1時,我們一次將過濾器移動1個像素。當步幅為2時,過濾器每次移動2個像素。步幅越大,生成的特征映射越小。有橫行和縱向兩個方向
  • 零填充:有時,將輸入矩陣邊界用零來填充會很方便,這樣我們可以將過濾器應用於輸入圖像矩陣的邊界元素。零填充一個很好的特性是它允許我們控制特征映射的大小。添加零填充也稱為寬卷積,而不使用零填充是為窄卷積。

零填充(padding)

  卷積操作之后維度會變少,得到的矩陣會比原來矩陣小,這樣不好計算,所以需要Padding,在每次卷積操作之前,在原矩陣外邊補包一層0,可以只在橫向補,或只在縱向補,或者四周都補0,從而使得卷積后輸出的圖像跟輸入圖像在尺寸上一致。

比如:需要做一個5*5的原始矩陣的卷積,用一個3*3卷積核來掃,掃出來結果的矩陣應該是:3*3的矩陣,變小了。

卷積前加 Padding 操作補一圈0,即300*300矩陣外面周圍加一圈“0”,這樣的300*300就變成了302*302的矩陣,再進行卷積出來就是300*300 ,尺寸和原圖一樣。

 

卷積神經網絡結構

  CNN 一般來說分為五個部分:輸入層卷積層激活函數層池化層全連接層

  不過注意需要注的點是,對於大部分卷積網絡,都會交替地用到中間地四層結構,也就是呈現出一種 卷積層——激活函數層——池化層——卷積層——激活函數層——池化層…地交替結構,當然,對於一些新出現地卷積網絡,連池化層都省去了,以上五層結構只是一般會出現的層次。

 

輸入層

  整個網絡的輸入,一般是一張圖象地像素矩陣,在上面的圖中,可以看到輸入是一個立體的結構,這是因為一般的圖像都會有一個深度的概念,就像RGB的彩色圖像,就是 a*b*c 的形式,其中前兩維指定的是圖像的長和寬,第三維則是深度,彩色RGB的深度是3,而黑白圖像的深度是 1

 

卷積層

  這一層的主要部分就是進行卷積操作,前面已經介紹了卷積核的概念,卷積層實際上就是實現了這個卷積核的計算過程,在這一層中,可能會見到以下的幾種關鍵詞:

  • 濾波器 Filter:實現前面定義的卷積核的神經元。
  • 步長 Stride
  • 填充 Padding
  • 深度 Depth:這里的深度不是指圖像,而是指某一層中神經元(濾波器)的個數,不同的 Filter 重點處理的特征是不同的我們想要得到不同的這些特征圖,所以設置了多個 Filter ,這樣每個 Filter 處理得到一張 Feature Map ,多個 Filter 就會得到多個Feature Map, 將這些Feature Map 疊在一起就是輸出的立體,可以看到,Filter 與 Feature Map的數量是一樣的,這個數量,就是 深度。

 在PyTorch中, 類nn.Conv2d()是卷積核模塊。卷積核及其調用例子如下:

# 調用形式
nn.Conv2d(in_channels, out_channels, kernel_size, stride=1, padding=0,dilation=1,groups=1, bias=True)
'''
  nn.Conv2d中參數含義:
  in_channels表示輸入數據體的深度;
  out_channels表示輸出數據體的深度;
  kernel_size 表示卷積核的大小;
  stride表示滑動的步長;
  padding表示邊界0填充的個數;
  dilation表示輸入數據體的空間間隔;
  groups 表示輸入數據體和輸出數據體在深度上的關聯;
  bias 表示偏置。
'''

# With square kernels and equal stride
m = nn.Conv2d(16, 33, 3, stride=2)
# non-square kernels and unequal stride and with padding
m = nn.Conv2d(16, 33, (3, 5), stride=(2, 1), padding=(4, 2))
# non-square kernels and unequal stride and with padding and dilation
m = nn.Conv2d(16, 33, (3, 5), stride=(2, 1), padding=(4, 2), dilation=(3, 1))
nput = autograd.Variable(torch.randn(20, 16, 50, 100))
output = m(input)

 

激勵函數層

  實際中,使用激活函數處理往往是與之前的卷積層綁定在一起,這個作用其實也就是是激活函數的作用,去線性化,在卷積網絡中,一般使用的激勵函數是 ReLu 函數,注意大多數情況下都不會使用 Sigmoid 函數處理。

 

池化層

  作用在 Feature Map 上,相當於對輸入矩陣的尺寸進一步濃縮,也就是進一步提取特征。卷積操作后我們提取了很多特征信息,相鄰區域有相似特征信息,可以相互替代的,如果全部保留這些特征信息就會有信息冗余,增加了計算難度,這時候池化就相當於降維操作。池化是在一個小矩陣區域內,取該區域的最大值或平均值來代替該區域,該小矩陣的大小可以在搭建網絡的時候自己設置。小矩陣也是從左上角掃到右下角。如下圖

 

池化層有以下幾個功能:

  1. 對 Feature Map 又進行一次特征提取,這也是減小數據量的操作
  2. 獲取更抽象的特征,防止過擬合,提高泛化性
  3. 經過這個處理,對輸入的微小變化有更大的容忍,也就是說如果數據有一些噪音,那么經過這個特征提取的過程,就一定程度上減小了噪音的影響。

最后一點要注意的是,池化層並非是卷積網絡所必需的。一些新的CNN網絡設計時候並沒有使用池化層。

在PyTorch中,池化層是包括在類nn.MaxPool2d和nn.AvgPoo2d。下面介紹一下nn.MaxPool2d及其調用例子。其調用如下

nn.MaxPool2d(kernel_size, stride=None, padding=0, dilation=1,return_indices=False,ceil_mode=False)
'''
    nn.MaxPool2d中各個參數的含義:
    kernel_size, stride,padding, dilation在nn.Conv2d中已經解釋過。
    return_indices表示是否返回最大值所處的下標;
    ceil_model表示使用方格代替層結構
'''

# pool of square window of size=3, stride=2
m = nn.MaxPool2d(3, stride=2)
# pool of non-square window
m = nn.MaxPool2d((3, 2), stride=(2, 1))
input = autograd.Variable(torch.randn(20, 16, 50, 32))
output = m(input)

 

全連接層

  在一開始的結構圖中可以看出, CNN網絡還有一個特點 : 可能有多輪 卷積層 和池化層的處理,這時候,模型已經將圖像輸入處理成了信息含量更高的特征,為了實現分類的任務(一般是分類,當然也會有其他的任務),需要使用全連接層來完成分類任務。

  對n-1層和n層而言,n-1層的任意一個節點,都和第n層所有節點有連接。即第n層的每個節點在進行計算的時候,激活函數的輸入是n-1層所有節點的加權。像下面的中間層就是全連接方式。

 

 

使用PyTorch實現卷積神經網絡

  使用PyTorch實現一個簡單的卷積神經網絡,使用的數據集是 MNIST,預期可以達到 97.05%左右的准確率。該神經網絡由2個卷積層和3個全連接層構建,通過這個例子可以掌握設計卷積神經網絡的特征以及參數的配置。

  PyTorch 已經實現了專門為神經網絡設計的模塊化接口 torch.nn,nn構建於 Autograd之上,可用來定義和運行神經網絡。

# coding=utf-8
# 配置庫
import torch
from torch import nn, optim
import torch.nn.functional as F
from torch.autograd import Variable
from torch.utils.data import DataLoader
from torchvision import transforms
from torchvision import datasets

# 配置參數
torch.manual_seed(1)  # 設置隨機數種子,確保結果可重復  
batch_size = 128  # 批處理大小
learning_rate = 1e-2  # 學習率
num_epoches = 10  # 訓練次數 

# ---------------------- 加載MINSIT數據 ----------------------
# 下載訓練集 MNIST 手寫數字訓練集
train_dataset = datasets.MNIST(
    root='./data',  # 數據保持的位置
    train=True,  # 訓練集 
    transform=transforms.ToTensor(),  # 一個取值范圍是[0,255]的PIL.Image
    # 轉化為取值范圍是[0,1.0]的torch.FloadTensor
    download=True)  # 下載數據

test_dataset = datasets.MNIST(
    root='./data',
    train=False,  # 測試集
    transform=transforms.ToTensor())


# ---------------------- 數據的批處理 ----------------------
# 數據的批處理,尺寸大小為batch_size, 
# 在訓練集中,shuffle 必須設置為True, 表示次序是隨機的
train_loader = DataLoader(train_dataset, batch_size=batch_size, shuffle=True)
test_loader = DataLoader(test_dataset, batch_size=batch_size, shuffle=False)


# ---------------------- 創建CNN模型 ----------------------
# 一個類來建立CNN模型.這個CNN整體流程是:
# 卷積(Conv2d) -> 激勵函數(ReLU) -> 池化, 向下采樣(MaxPooling) -> 再來一遍 -> 展開多維的卷積成的特征圖 -> 接入全連接層(Linear) -> 輸出。

# 定義卷積神經網絡模型
class Cnn(nn.Module):
    def __init__(self, in_dim, n_class):  # 1x28x28
        super(Cnn, self).__init__()
        self.conv = nn.Sequential(
            nn.Conv2d(in_dim, 6, 3, stride=1, padding=1),  # 28 x 28 
            nn.ReLU(True),
            nn.MaxPool2d(2, 2),  # 14 x 14
            nn.Conv2d(6, 16, 5, stride=1, padding=0),  # 16 x 10 x 10
            nn.ReLU(True), nn.MaxPool2d(2, 2))  # 16x5x5

        self.fc = nn.Sequential(
            nn.Linear(400, 120),  # 400 = 16 x 5 x 5 
            nn.Linear(120, 84),
            nn.Linear(84, n_class))

    def forward(self, x):
        out = self.conv(x)
        out = out.view(out.size(0), 400)  # 400 = 16 x 5 x 5  
        out = self.fc(out)
        return out


model = Cnn(1, 10)  # 圖片大小是28x28, 10是數據的種類
# 打印模型,呈現網絡結構
print(model)


# ---------------------- 訓練 ----------------------
# 將img, label都用Variable包起來, 然后放入model中計算out, 最后再計算less和正確率.

# 定義loss和optimizer
criterion = nn.CrossEntropyLoss()
optimizer = optim.SGD(model.parameters(), lr=learning_rate)

# 開始訓練
for epoch in range(num_epoches):
    running_loss = 0.0
    running_acc = 0.0
    for i, data in enumerate(train_loader, 1):  # 批處理
        img, label = data
        img = Variable(img)
        label = Variable(label)
        # 前向傳播 
        out = model(img)
        loss = criterion(out, label)  # loss 
        running_loss += loss.data[0] * label.size(0)  # total loss , 由於loss 是batch 取均值的,需要把batch size 乘回去
        _, pred = torch.max(out, 1)  # 預測結果
        num_correct = (pred == label).sum()  # 正確結果的num
        # accuracy = (pred == label).float().mean() #正確率
        running_acc += num_correct.data[0]  # 正確結果的總數
        # 后向傳播
        optimizer.zero_grad()  # 梯度清零,以免影響其他batch
        loss.backward()  # 后向傳播,計算梯度
        optimizer.step()  # 利用梯度更新 W ,b參數

    # 打印一個循環后,訓練集合上的loss 和正確率
    print('Train {} epoch, Loss: {:.6f}, Acc: {:.6f}'.format(
        epoch + 1, running_loss / (len(train_dataset)), running_acc / (len(
            train_dataset))))


# ---------------------- 在測試集測試識別率 ----------------------
# 模型測試, 
model.eval()  # 由於訓練和測試 BatchNorm, Dropout配置不同,需要說明是否模型測試
eval_loss = 0
eval_acc = 0
for data in test_loader:  # test set 批處理
    img, label = data

    img = Variable(img, volatile=True)  # volatile 確定你是否不調用.backward(), 測試中不需要
    label = Variable(label, volatile=True)
    out = model(img)  # 前向算法 
    loss = criterion(out, label)  # 計算 loss
    eval_loss += loss.data[0] * label.size(0)  # total loss
    _, pred = torch.max(out, 1)  # 預測結果
    num_correct = (pred == label).sum()  # 正確結果
    eval_acc += num_correct.data[0]  # 正確結果總數

print('Test Loss: {:.6f}, Acc: {:.6f}'.format(eval_loss / (len(
    test_dataset)), eval_acc * 1.0 / (len(test_dataset))))

'''
最后的訓練和測試上loss和識別率分別是:
Train 1 epoch, Loss: 2.226007, Acc: 0.320517
Train 2 epoch, Loss: 0.736581, Acc: 0.803717
Train 3 epoch, Loss: 0.329458, Acc: 0.901117
Train 4 epoch, Loss: 0.252310, Acc: 0.923550
Train 5 epoch, Loss: 0.201800, Acc: 0.939000
Train 6 epoch, Loss: 0.167249, Acc: 0.949550
Train 7 epoch, Loss: 0.145517, Acc: 0.955617
Train 8 epoch, Loss: 0.128391, Acc: 0.960817
Train 9 epoch, Loss: 0.117047, Acc: 0.964567
Train 10 epoch, Loss: 0.108246, Acc: 0.966550
Test
Loss: 0.094209, Acc: 0.970500
'''

 

 

 

 

參考:http://www.sohu.com/a/241338315_787107

參考:https://blog.csdn.net/sinat_34328764/article/details/84192303

參考:https://www.cnblogs.com/wmr95/articles/7814892.html

 

 

            

 

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM