天天看點

pytorch學習13:實作LetNet和學習nn.Module相關基本操作模型建立代碼輸出網絡結構檢視可訓練參數前向傳播反向傳播和梯度下降優化器關于 zero_grad

用pytorch實作經典的LeNet網絡。

網絡結構圖:

pytorch學習13:實作LetNet和學習nn.Module相關基本操作模型建立代碼輸出網絡結構檢視可訓練參數前向傳播反向傳播和梯度下降優化器關于 zero_grad

模型建立代碼

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 的了解更透徹,删除了可能沒有對結果産生影響的代碼。當在後續學習中,出現此代碼必不可少的情況時,再對其功能進行學習研究。

繼續閱讀