- 全連接網(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)行下采樣時,通道數(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):
- 每個卷積核的通道數(shù)量要求和輸入通道一樣;
- 最終輸出的通道數(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 (
)的,一般都是按實際需求選擇是否填充。填充的圈數(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>)
下采樣方法介紹:
-
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ù)輸出來編寫全連接層即可;

在放進(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))

- 作業(yè):

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)該是這樣?
