用pytorch實作經典的LeNet網絡。
網絡結構圖:
模型建立代碼
LeNet包含如下層:
- 二維卷積層 ( [ 32 , 32 ] → [ 6 , 28 , 28 ] ) ([32,32] \rightarrow [6,28,28]) ([32,32]→[6,28,28]),激活函數 r e l u relu relu
- 池化層 ( → [ 6 , 14 , 14 ] ) (\rightarrow [6,14,14]) (→[6,14,14])
- 二維卷積層 ( → [ 16 , 10 , 10 ] ) (\rightarrow [16,10,10]) (→[16,10,10]),激活函數 r e l u relu relu
- 池化層 ( → [ 16 , 5 , 5 ] ) (\rightarrow [16,5,5]) (→[16,5,5])
- 打平向量 ( → [ 16 ∗ 5 ∗ 5 ] ) (\rightarrow [16*5*5]) (→[16∗5∗5])
- 全連接配接層 ( → [ 120 ] ) (\rightarrow [120]) (→[120]),激活函數 r e l u relu relu
- 全連接配接層 ( → [ 84 ] ) (\rightarrow [84]) (→[84]),激活函數 r e l u relu relu
- 全連接配接層 ( → [ 10 ] ) (\rightarrow [10]) (→[10])
import torch
import torch.nn as nn
import torch.nn.functional as F
class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
# 1 維輸入圖像通道,6 維輸出通道
# 卷積核為 5x5 的卷積核
self.conv1 = nn.Conv2d(1, 6, 5)
# 6 維輸入圖像通道,16 維輸出通道
# 卷積核為 5x5 的卷積核
self.conv2 = nn.Conv2d(6, 16, 5)
# 最後的三層全連接配接
self.fc1 = nn.Linear(16 * 5 * 5, 120)
self.fc2 = nn.Linear(120, 84)
self.fc3 = nn.Linear(84, 10)
def forward(self, x):
# 用 2x2 的視窗對第一層卷積進行 Max pooling池化操作
x = F.max_pool2d(F.relu(self.conv1(x)), (2, 2))
# 對第二層卷積進行同樣操作
# 如果池化視窗形狀為正方形,可以隻輸入一個數
x = F.max_pool2d(F.relu(self.conv2(x)), 2)
# 将 x 打平成一維向量
x = x.view(-1, self.num_flot_features(x))
# 全連接配接層的前向傳播
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
x = self.fc3(x)
return x
def num_flot_features(self, x):
"""
計算除批次外的所有次元
用來确定打平後的次元
如 x.shape = [100, 28, 28]
傳回值為 28 * 28
:param x: 資料
:return: 除第一維外的所有次元之積
"""
size = x.size()[1:]
num_features = 1
for s in size:
num_features *= s
return num_features
輸出網絡結構
可以通過列印對象直接檢視構成網絡的各層屬性。
示例代碼:
# 建立LetNet對象
net = Net()
# 輸出網絡結構
print('網絡結構:\n', net)
輸出結果:
網絡結構:
Net(
(conv1): Conv2d(1, 6, kernel_size=(5, 5), stride=(1, 1))
(conv2): Conv2d(6, 16, kernel_size=(5, 5), stride=(1, 1))
(fc1): Linear(in_features=400, out_features=120, bias=True)
(fc2): Linear(in_features=120, out_features=84, bias=True)
(fc3): Linear(in_features=84, out_features=10, bias=True)
)
檢視可訓練參數
可以通過
.parameters()
獲得各層參數,可用來更新權重。
示例代碼:
# 建立LetNet對象
net = Net()
# 使用 net.parameters 可以獲得被學習參數的清單和值
params = list(net.parameters())
print('需訓練的參數矩陣個數:', len(params))
for p in params:
print(p.size())
輸出結果:
需訓練的參數矩陣個數: 10
torch.Size([6, 1, 5, 5]) # 第一層卷積核的參數
torch.Size([6]) # 第一層卷積核的偏置
torch.Size([16, 6, 5, 5]) # 第二層卷積核的參數
torch.Size([16]) # 第二層卷積核的偏置
torch.Size([120, 400]) # 第一次全連接配接的權重
torch.Size([120]) # 第一層全連接配接的偏置
torch.Size([84, 120]) # 第二層全連接配接的權重
torch.Size([84]) # 第二層全連接配接的偏置
torch.Size([10, 84]) # 第三層全連接配接的權重
torch.Size([10]) # 第三層全連接配接的偏置
前向傳播
使用
對象名(輸入資料)
來進行前向計算。
示例代碼:
# 建立LetNet對象
net = Net()
# 表示為:有1批數量為1的32*32大小的圖檔
input_ = torch.randn(1, 1, 32, 32)
out = net(input_)
print('網絡輸出:', out)
輸出結果:
網絡輸出: tensor([[-0.0573, -0.0214, -0.0619, 0.0998, 0.0441, -0.0282, 0.0089, 0.0991,
-0.0603, -0.0076]], grad_fn=<AddmmBackward>)
反向傳播和梯度下降
可以用過
loss.backward()
來更新梯度,然後用
net.parameters()
來擷取并更新參數。
示例代碼:
# 建立LetNet對象
net = Net()
# 建立 X
# 表示為:有1批數量為1的32*32大小的圖檔
x = torch.randn(1, 1, 32, 32)
# 建立 Y
y = torch.randn(1,10)
# 前向計算
y_pred = net(x)
# 設定損失函數為 MSE
loss_func = nn.MSELoss()
# 計算損失
loss = loss_func(y_pred, y)
print('梯度下降前loss:', loss)
# 輸出第一層卷積在反向傳播前的梯度
print('conv1.bias.gard 反向傳播前:', net.conv1.bias.grad)
# 進行反向傳播
loss.backward()
# 輸出反向傳播後的梯度
print('conv1.bias.gard 反向傳播後:', net.conv1.bias.grad)
# 更新權重
learning_rate = 1 # 設定學習率為 1,是為了加快權重變化
for p in net.parameters():
# 更新參數
# w = w - lr * w_grad
p.data.sub_(p.grad.data * learning_rate)
# 再次計算結果和損失
y_pred = net(x)
loss = loss_func(y_pred, y)
print('梯度下降後loss:', loss)
輸出結果:
梯度下降前loss: tensor(0.7424, grad_fn=<MseLossBackward>)
conv1.bias.gard 反向傳播前: None
conv1.bias.gard 反向傳播後: tensor([ 0.0236, -0.0085, 0.0035, -0.0035, -0.0115, -0.0085])
梯度下降後loss: tensor(0.0246, grad_fn=<MseLossBackward>)
優化器
若想使用一些經典的優化器來進行權重更新,可在
torch.optim
中快速調用。
通過
optimzer = optim.SGD(net.parameters(), lr=1)
的方式綁定參數。然後在反向傳播後通過
optimzer.step()
來更新梯度。
示例代碼:
import torch.optim as optim
net = Net()
# 設定優化器
optimzer = optim.SGD(net.parameters(), lr=1)
# 計算梯度
x = torch.randn(1, 1, 32, 32)
y = torch.randn(1,10)
y_pred = net(x)
loss_func = nn.MSELoss()
loss = loss_func(y_pred, y)
print('梯度下降前loss:', loss)
loss.backward()
# 更新梯度
optimzer.step()
# 再次預測
y_pred = net(x)
loss = loss_func(y_pred, y)
print('梯度下降後loss:', loss)
輸出結果:
梯度下降前loss: tensor(1.0159, grad_fn=<MseLossBackward>)
梯度下降後loss: tensor(0.0834, grad_fn=<MseLossBackward>)
關于 zero_grad
.zero_gard()
用來清理累計的梯度,可以使用形如
net.zero_gard()
或
optimzer.zero_gard()
的代碼來使用。但由于上面代碼都隻進行了一次梯度下降,且經過嘗試發現删除有關
.zero_gard()
的代碼并不會産生什麼影響。為了讓自己對 pytorch 的了解更透徹,删除了可能沒有對結果産生影響的代碼。當在後續學習中,出現此代碼必不可少的情況時,再對其功能進行學習研究。