從零開始寫模型LeNet
技術標籤:深度學習
個人部落格同步更新,介面更美觀呀
Introduction
近來安裝了MindSpore框架,便想測試一下它和Pytorch等其他深度學習框架有什麼區別。首先便從模型的訓練耗時和測試耗時來測試,MindSpore官方提供了使用LeNet解決MNIST手寫數字識別的Demo,此時還需要一個用Pytorch編寫的作為對比,為了使引數、模型等各方面儘可能一致,便有了照貓畫虎自己編寫的需求。
Coding
為了能總覽模型編寫訓練流程,我決定從__main__函式入手,用到什麼寫什麼。
parser
首先要用的就是一個命令列程式碼的解析工具,使用方法概括如下:
# 導包
import argparse
# 定義一個解析器
parser = argparse.ArgumentParser(description="")
# 給解析器新增需要從命令列讀入的引數
parser.add_argument('--device_target', type=str, default="CPU", choices=['GPU', 'CPU'])
# 呼叫解析函式生成引數物件
args = parser.parse_args()
dataset
準備資料集,最好單獨封裝為一個處理函式,提供路徑載入和臨時下載兩種方式。
super parameter
超引數的設定,其實也可以放到第一步命令列引數解析裡面。
模型的定義
在__main__
函式中例項化一個模型:
model = LeNet()
在前面預先定義模型類:
class LeNet(nn.Module):
"""lenet network structure."""
def __init__(self, num_channel=1, num_class=10):
self.conv1 = nn.Conv2d(in_channels=num_channel, out_channels=6,
kernel_size= 5, stride=1, padding=2)
self.conv2 = nn.Conv2d(in_channels=6, out_channels=16, kernel_size=5)
self.fc1 = nn.Linear(16*5*5, 120)
self.fc2 = nn.Linear(120, 84)
self.fc3 = nn.Linear(84, num_class)
self.relu = nn.ReLU()
self.max_pool2d = nn.MaxPool2d(2, 2)
def forward(self, x):
x = self.max_pool2d(self.relu(self.conv1(x)))
x = self.max_pool2d(self.relu(self.conv2(x)))
# 展開為一維
x = x.view(x.size()[0], -1)
x = self.relu(self.fc1(x))
x = self.relu(self.fc2(x))
x = self.fc3(x)
return x
模型的訓練
訓練的過程就是送入資料執行計算過程,得到輸出結果,與標準值進行誤差計算,將誤差進行梯度反向傳播更新模型引數。然後重複此過程,訓練結束條件可以為設定的epoch次數,或者給定一個收斂條件(誤差的縮小程度已經不值得繼續訓練了)。
**損失函式:**此例採用交叉熵損失函式,多用於分類問題。
loss_function = nn.CrossEntropyLoss()
**優化器:**即計算得到的損失以怎樣的方式作用到模型引數上,此例採用隨機梯度下降(SGD)。
optimizer = optim.SGD(model.parameters(), lr=lr, momentum=momentum)
訓練迴圈:
第一層迴圈一般為epoch維度的迴圈,簡單說就是在這個資料集上訓練幾次,epoch=2即利用這個資料集進行兩次訓練過程。第二層迴圈就是在batch上的訓練了,對所有資料分批進行前向計算和反向傳播。
訓練體一般有如下流程:
- 對得到的資料x和標籤label進行device上的轉換,比如使用了GPU。
- 前向計算:pred=model(x)
- 計算損失(看看算的對不對):loss=loss_function(pred, label)
- 反向傳播(不對的話如何改正):先要對梯度進行一個清零操作optimizer.zero_grad(),然後進行反向傳播loss.backward(),執行一個優化步optimizer.step()。
至此一個訓練過程就結束了,有需要的話可以在不同邏輯層進行損失和準確率等引數的列印輸出,方便監測訓練狀況。
for epoch in range(1, epoch_size+1):
# data loop
for batch_idx, (img, label) in enumerate(train_loader):
img = img.to(device)
label = label.to(device)
img, label = Variable(img), Variable(label)
# 前向傳播
pred = model(img)
# 計算損失
loss = loss_function(pred, label)
# 梯度清零
optimizer.zero_grad()
# 誤差反向傳播
loss.backward()
# 優化步
optimizer.step()
# 為了和MindSpore版本保持一致 每次都列印一遍
print("epoch: {} step: {}, loss id {}".format(epoch, batch_idx, loss.item()))
模型的測試
測試過程即對一些樣本逐個進行預測並記錄是否正確,最終輸出準確率、召回率等引數指標。
eval_acc = 0
for img, label in test_loader:
img = img.to(device)
label = label.to(device)
img, label = Variable(img), Variable(label)
# 預測
output = model(img)
_, pred = torch.max(output, 1)
num_cortect = (pred == label).sum()
eval_acc += num_cortect.item()
acc = eval_acc / (len(test_loader.dataset))
Experiment
對於添加了argparse的訓練指令碼來說,執行命令一般為:
python lenet.py --device_target=GPU --other=otherValue
需要通過命令列傳入的引數以如上方式進行匹配賦值。
執行無誤的話就能得到預期的結果反饋,有錯誤就根據提示Debug啦。
執行命令一般為:
python lenet.py --device_target=GPU --other=otherValue
需要通過命令列傳入的引數以如上方式進行匹配賦值。
執行無誤的話就能得到預期的結果反饋,有錯誤就根據提示Debug啦。