Pytorch:十、卷積神經(jīng)網(wǎng)絡(luò)(基礎(chǔ))


  • 全連接網(wǎng)絡(luò):網(wǎng)絡(luò)中所使用的全都是線性層,并且串行器來,就跟上一章的那個模型一樣的樣子:

線性層中輸入\出層的任意兩個節(jié)點之間都有權(quán)重,即所有輸入節(jié)點都要參與到下一層輸出的計算中去。我們就把這樣的線性層叫做全連接層full connected(FC);

但是做成全連接層也有一些不足:

以上圖為例,將一個數(shù)字轉(zhuǎn)換為1維的長串時,可能會丟失一些空間信息,即在長串中隔得很遠(yuǎn)的兩個點可能在原來的數(shù)字圖像中是相鄰的。這說明:做成全連接之后會喪失一些原有的空間信息;

相應(yīng)的,CNN則是把圖像按照原始空間結(jié)構(gòu)進(jìn)行保存,從而保留住了許多空間信息。

卷積核進(jìn)行的卷積計算

卷積計算:就是像上面這樣,通過卷積核來對原來的圖像進(jìn)行壓縮;

CNN流程圖

不難看出,在進(jìn)行下采樣時,通道數(shù)不變,但是寬度和高度則是按照采樣規(guī)格進(jìn)行縮減;
下采樣:為了減少數(shù)據(jù)量,從而降低運算需求;
最終將n1的輸出映射到10維的n2去,這樣就可以接上 交叉熵?fù)p失 從而計算其分布,這樣就可以解決分類問題了;

通過上面的圖,可以得出一套工作流程:
通過這些層,來進(jìn)行維度/尺寸上的大小變化,最終將它映射到想要的輸出空間中去;

特征提取器:也就是卷積,下采樣等,表示通過卷積運算來找到某種特征(此階段直接對圖像進(jìn)行卷積運算從而轉(zhuǎn)為向量);
分類器,就是把經(jīng)過特征提取后的向量放到全連接層中去做分類;

可以通過擴展來提升像素值:

將原來2* 2的圖像擴展為一個3* 3的,空出來的部分用平均值來進(jìn)行填充即可

注意,經(jīng)過卷積操作后的通道數(shù)量也是可能發(fā)生變化的,因為原來有三個通道,現(xiàn)在卷積核有5個通道,這五個通道把你原來三個通道都遍歷一遍(多少個卷積核就會得到不同的特征圖),卷積核個數(shù)是下一層通道數(shù)

卷積的圖示

將上面的過程整合一下就是:

不難看出,無論中間那個卷積核是多少層的,最后輸出的結(jié)果一定是一個單通道的結(jié)果,如果想要一個多通道的結(jié)果就需要很多個卷積層,如下圖所示;

這里的cat是指拼接的意思;

從這張圖我們不難發(fā)現(xiàn):

  1. 每個卷積核的通道數(shù)量要求和輸入通道一樣;
  2. 最終輸出的通道數(shù)就是卷積核的數(shù)量;

如此一來就可以推出以下結(jié)論:

看看代碼的計算過程:

import torch
#分別是n和m
in_channels, out_channels = 5,10
#這里的寬和高是圖像的大小
w,h = 100, 100
#kernel_size表示卷積核的數(shù)量,輸入一個3就是3*3,也可以直接給個數(shù)組
kernel_size = 3
#這里是指圖片的數(shù)量
batch_size = 1

#生成一個輸入圖像
input = torch.rand(batch_size
                 ,in_channels #這個最需要關(guān)注,一定要和卷積的in一樣
                 ,w
                 ,h
                 )

#Conv2d:對由多個輸入平面組成的輸入信號進(jìn)行二維卷積
conv_layer = torch.nn.Conv2d(in_channels
                            ,out_channels
                            ,kernel_size = kernel_size
                            )

#用上面的那個卷積層來對input進(jìn)行卷積并生成output
output = conv_layer(input)

print(input.shape)
#torch.Size([1, 5, 100, 100])
print(output.shape)
#torch.Size([1, 10, 98, 98])
print(conv_layer.weight.shape) #卷積層權(quán)重的形狀
#torch.Size([10, 5, 3, 3]) 

卷積層中其他常見的參數(shù):

  • padding:通過這個參數(shù)來改變輸出矩陣的規(guī)格;

在外圍加了一圈格子,從而使其在經(jīng)過卷積核后的結(jié)果為5* 5 (7-3+1=5)的,一般都是按實際需求選擇是否填充。填充的圈數(shù)就是卷積核的大小/2后取整的值;

最常見的padding就是填充0,如下圖所示:

而這一步也可以通過代碼實現(xiàn)

import torch

#原來的矩陣
input = [3,4,5,6,7
        ,2,4,6,8,2
        ,1,6,7,8,4
        ,9,7,6,4,2
        ,3,7,5,4,1]
#按要求進(jìn)行轉(zhuǎn)換,view的四個數(shù)字分別是:batch, channel, w, h
input = torch.Tensor(input).view(1, 1, 5, 5)

#bias是加偏執(zhí)量的參數(shù),這里不需要加
conv_layer = torch.nn.Conv2d(1, 1, kernel_size = 3, padding=1, bias=False)

#構(gòu)造卷積核
kernel = torch.Tensor([1,2,3,4,5,6,7,8,9]).view(1, 1, 3, 3)
#通過賦值來進(jìn)行卷積層權(quán)重的初始化
conv_layer.weight.data = kernel.data

output = conv_layer(input)
print(output)
  • stride:步長,表示每次卷積之后框移動的距離;
import torch

input = [3,4,5,6,7
        ,2,4,6,8,2
        ,1,6,7,8,4
        ,9,7,6,4,2
        ,3,7,5,4,1]
input = torch.Tensor(input).view(1, 1, 5, 5)

conv_layer = torch.nn.Conv2d(1, 1, kernel_size = 3, stride=2, bias=False)

kernel = torch.Tensor([1,2,3,4,5,6,7,8,9]).view(1, 1, 3, 3)
conv_layer.weight.data = kernel.data

output = conv_layer(input)
print(output)
#tensor([[[[208., 263.],
#          [263., 167.]]]], grad_fn=<ConvolutionBackward0>)

下采樣方法介紹:

  1. MaxPooling:按寬和高進(jìn)行分組,然后在每組內(nèi)找到最大值來作為那一組的代表;


默認(rèn)stride=2。這里只能在同一個通道內(nèi)進(jìn)行MaxPooling,不能在不同通道之間進(jìn)行;

import torch

input = [3,4,5,6
        ,2,4,6,8
        ,1,6,7,8
        ,9,7,6,4]
input = torch.Tensor(input).view(1,1,4,4)

#當(dāng)kernel_size設(shè)為2時,默認(rèn)步長也會變成2
maxpooling_layer = torch.nn.MaxPool2d(kernel_size=2)

output = maxpooling_layer(input)
print(output)

接下來做一個簡單的CNN:

第一個卷積層:5* 5的卷積核,輸入通道為1,輸出通道為5;
第一個池化層:batch和10都不變,寬和高的24都減小一半;
以此類推。。。
全連接層:把結(jié)果映射成10個輸出向量來作為預(yù)測值;

最后的那個分類器最在乎每個樣本的元素個數(shù),因此要看看前面的輸出有多少個。但是在實操時可以先不寫最后這個分類器--,定義到最后一個batch就停,然后看看它的輸出是咋樣的,然后根據(jù)輸出來編寫全連接層即可;

CNN的網(wǎng)絡(luò)架構(gòu)圖

在放進(jìn)FC層之前要進(jìn)行一次view,也就是把2044變成320再丟給FC;

import torch.nn.functional as F

class Net(torch.nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.conv1 = torch.nn.Conv2d(1, 10, kernel_size=5)
        self.conv2 = torch.nn.Conv2d(10,20,kernel_size=5)
        self.pooling = torch.nn.MaxPool2d(2)
        self.fc = torch.nn.Linear(320,10)
        
    def forward(self, x):
        batch_size = x.size(0)
        x = self.pooling(F.relu(self.conv1(x)))
        x = self.pooling(F.relu(self.conv2(x)))
        x = x.view(batch_size, -1)
        x = self.fc(x)
        return x
    
model = Net()
#用顯卡來算,就是把模型遷移到GPU上去
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
#通過這句話改跑模型的設(shè)備
model.to(device)

def train(epoch):
    running_loss = 0.0
    for batch_idx, data in enumerate(train_loader, 0):
        inputs, target = data
        #把他倆都送去device上,一定要放到同一塊顯卡上
        inputs, target = inputs.to(device), target.to(device)
        optimizer.zero_grad()
        # forward + backward + update
        outputs = model(inputs)
        loss = criterion(outputs, target)
        loss.backward()
        optimizer.step()

        running_loss += loss.item()
        if batch_idx % 300 == 299:
            print('[%d, %5d] loss: %.3f' % (epoch + 1, batch_idx + 1, running_loss / 2000))
            running_loss = 0.0
            
def test():
    correct = 0
    total = 0
    with torch.no_grad():
        for data in test_loader:
            inputs, target = data
            #測試這里也要放到顯卡上
            inputs, target = inputs.to(device), target.to(device)
            outputs = model(inputs)
            _, predicted = torch.max(outputs.data, dim=1)
            total += target.size(0)
            correct += (predicted == target).sum().item()
        print('Accuracy on test set: %d %% [%d/%d]' % (100 * correct / total, correct, total))
用了GPU之后的結(jié)果,沒給數(shù)據(jù)我們自己不好跑
  • 作業(yè):
要求在右邊,具體參數(shù)自己設(shè)置就行
class Net1(torch.nn.Module):
    def __init__(self):
        super(Net1, self).__init__()
        self.conv1 = torch.nn.Conv2d(1, 10, kernel_size=4)#10,13,13
        self.conv2 = torch.nn.Conv2d(10,20,kernel_size=4) #20,6,6
        self.conv3 = torch.nn.Conv2d(20,40,kernel_size=4) #40,2,2
        self.pooling = torch.nn.MaxPool2d(2)
        self.fc1 = torch.nn.Linear(160,80)
        self.fc2 = torch.nn.Linear(80,40)
        self.fc3 = torch.nn.Linear(40,10)
        
    def forward(self, x):
        batch_size = x.size(0)
        x = self.pooling(F.relu(self.conv1(x)))
        x = self.pooling(F.relu(self.conv2(x)))
        x = self.pooling(F.relu(self.conv3(x)))
        x = x.view(batch_size, -1)
        x = self.fc1(x)
        x = self.fc2(x)
        x = self.fc3(x)
        return x

不知道對不對,應(yīng)該是這樣?

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容