1. 程式人生 > 其它 >LeNet 網路進行貓狗大戰

LeNet 網路進行貓狗大戰

最近給學生布置了貓狗大戰的作業,是我自己拍腦袋想的。我發現大多同學做的並太理想,主要原因是因為對pytorch不太熟悉。中秋假期我也做了這個作業,效果雖然並不算好,但可以做為一個範例提供給初學者學習。(其實我寫的網路和大家差不多,並不是 LeNet,是一個只有卷積、池化、全連線的簡單CNN)

大家普遍反映有兩個問題:1、網路不收斂;2、Colab上訓練時間太長。

問題1解決方法: 網路不收斂,可以使用更深的網路。網路變深時,sigmoid 啟用函式容易進入飽和區,就不收斂了,要把啟用函式替換為 ReLU 。另外,大家可以把優化器由 SGD 替換為 Adam ,一般情況下,我認為 Adam 效果會比 SGD 好一些。具體原因大家可以自己補課,這裡不多說。

問題2解決方法: 訓練時間長還是因為資料量大,這裡我採取策略是選擇較少的訓練樣本,貓狗各取了2000個,訓練時間會大大縮短。(總體思路還是先保證程式碼能夠跑起來,實際需要的話,再放到伺服器上跑)。

第1步:載入資料集,匯入基本庫

# 這個是訓練集,貓狗各取了2000個
! wget https://gaopursuit.oss-cn-beijing.aliyuncs.com/2021/files/train.zip
! unzip train.zip
# 這個是測試集
! wget https://gaopursuit.oss-cn-beijing.aliyuncs.com/202007/dogs_cats_test.zip
! unzip dogs_cats_test.zip
import numpy as np
import matplotlib.pyplot as plt
import torch
import torch.nn as nn
import torchvision
from torchvision import models,transforms,datasets
import torch.nn.functional as F
from PIL import Image
import torch.optim as optim
import json, random
import os

# 判斷是否存在GPU裝置
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
print('Using gpu: %s ' % torch.cuda.is_available())
# 訓練圖片和測試圖片的路徑
train_path = './train/'
test_path = './test/'

第2步:建立資料集,這裡採用了 齊昊 的程式碼。

def get_data(file_path):
    file_lst = os.listdir(file_path) #獲得所有檔名稱 xxxx.jpg
    data_lst = []
    for i in range(len(file_lst)):
        clas = file_lst[i][:3] #cat和dog在檔名的開頭
        img_path = os.path.join(file_path,file_lst[i])#將檔名與路徑合併得到完整路徑,以備讀取
        if clas == 'cat':
            data_lst.append((img_path, 0))
        else:
            data_lst.append((img_path, 1))
    return data_lst
class catdog_set(torch.utils.data.Dataset):
    def __init__(self, path, transform):
        super(catdog_set).__init__()
        self.data_lst = get_data(path)#呼叫剛才的函式獲得資料列表
        self.trans = torchvision.transforms.Compose(transform)
    def __len__(self):
        return len(self.data_lst)
    def __getitem__(self,index):
        (img,cls) = self.data_lst[index]
        image = self.trans(Image.open(img))
        label = torch.tensor(cls,dtype=torch.float32)
        return image,label
# 將輸入影象縮放為 128*128,每一個 batch 中影象數量為128
# 訓練時,每一個 epoch 隨機打亂影象的順序,以實現樣本多樣化
train_loader = torch.utils.data.DataLoader(
    catdog_set(train_path, [transforms.Resize((128,128)),transforms.ToTensor()]), 
    batch_size=128, shuffle=True)

第3步:定義網路

這裡是網路的定義,下面附有一段測試程式碼,因為輸入影象是 3x128x128,可以看到網路的處理為:

3x128x128 ==> conv1( 6x124x124 ==> 6x62x62 )

==> conv2( 16x58x58 ==> 16x29x29 )

==> conv3( 32x26x26 ==> 32x13x13 )

==> conv4( 32x10x10 ==> 32x5x5 )

==> conv5( 32x1x1 ==> 32)

==> 32 ==> 16 ==> 2

可以在 forward 函式中加一些 print 函式測試,觀察 feature map 形狀的變化

class Net(nn.Module):
    def __init__(self):
        super(Net, self).__init__()
        self.conv1 = nn.Conv2d(3, 6, 5)
        self.conv2 = nn.Conv2d(6, 16, 5)
        self.conv3 = nn.Conv2d(16, 32, 4)
        self.conv4 = nn.Conv2d(32, 32, 4)
        self.conv5 = nn.Conv2d(32, 32, 5)
        self.pool = nn.MaxPool2d(2, 2)
        self.fc1 = nn.Linear(32, 16)
        self.fc2 = nn.Linear(16, 2)

    def forward(self, x):
        x = self.pool(F.relu(self.conv1(x)))
        x = self.pool(F.relu(self.conv2(x)))
        x = self.pool(F.relu(self.conv3(x)))
        x = self.pool(F.relu(self.conv4(x)))
        x = F.relu(self.conv5(x))
        x = x.view(-1, 32)
        x = F.relu(self.fc1(x))
        x = F.softmax(self.fc2(x), dim=1)
        return x

# 隨機輸入,測試網路結構是否通
# x = torch.randn(1, 3, 128, 128)
# net = Net()
# y = net(x)
# print(y.shape)

第4步:網路訓練

網路訓練準備,三個要素:1、將網路放到 GPU 上;2、定義損失函式;3、定義優化器

# 網路放到GPU上
net = Net().to(device)
criterion = nn.CrossEntropyLoss()
optimizer = optim.Adam(net.parameters(), lr=0.001)

開始訓練,這裡是訓練30個 epoch。訓練過程中,首先梯度歸零,然後正向傳播 + 計算損失 + 反向傳播 + 優化。所有的網路訓練都是這個過程。

for epoch in range(30):  # 重複多輪訓練
    for i, (inputs, labels) in enumerate(train_loader):
        inputs = inputs.to(device)
        labels = labels.to(device)
        # 優化器梯度歸零
        optimizer.zero_grad()
        # 正向傳播 + 反向傳播 + 優化 
        outputs = net(inputs)
        loss = criterion(outputs, labels.long())
        loss.backward()
        optimizer.step() 
    print('Epoch: %d loss: %.6f' %(epoch + 1, loss.item()))
print('Finished Training')

Epoch: 1 loss: 0.693591

Epoch: 2 loss: 0.691867

Epoch: 3 loss: 0.682191

Epoch: 4 loss: 0.657503

Epoch: 5 loss: 0.705427

Epoch: 6 loss: 0.660462

Epoch: 7 loss: 0.634173

Epoch: 8 loss: 0.673097

Epoch: 9 loss: 0.593748

Epoch: 10 loss: 0.525426

Epoch: 11 loss: 0.550043

Epoch: 12 loss: 0.583569

Epoch: 13 loss: 0.669047

Epoch: 14 loss: 0.532821

Epoch: 15 loss: 0.591107

Epoch: 16 loss: 0.512886

Epoch: 17 loss: 0.551087

Epoch: 18 loss: 0.574038

Epoch: 19 loss: 0.604391

Epoch: 20 loss: 0.552344

Epoch: 21 loss: 0.493089

Epoch: 22 loss: 0.442594

Epoch: 23 loss: 0.584947

Epoch: 24 loss: 0.496618

Epoch: 25 loss: 0.462232

Epoch: 26 loss: 0.384848

Epoch: 27 loss: 0.506766

Epoch: 28 loss: 0.488330

Epoch: 29 loss: 0.462068

Epoch: 30 loss: 0.462078

Finished Training

可以看出,如果繼續訓練更多 epoch,或者使用更多訓練樣本,網路還可以優化。時間有限,不過多花時間了。

第5步:測試並輸出結果

測試集包括2000張圖片,一張張圖片讀取,輸入網路預測結果,最後將訓練結果寫入檔案。

resfile = open('res.csv', 'w')
for i in range(0,2000): 
    img_PIL = Image.open('./test/'+str(i)+'.jpg')
    img_tensor = transforms.Compose([transforms.Resize((128,128)),transforms.ToTensor()])(img_PIL)
    img_tensor = img_tensor.reshape(-1, img_tensor.shape[0], img_tensor.shape[1], img_tensor.shape[2])
    img_tensor = img_tensor.to(device)
    out = net(img_tensor).cpu().detach().numpy()
    if out[0, 0] < out[0, 1]:
        resfile.write(str(i)+','+str(1)+'\n')
    else:
        resfile.write(str(i)+','+str(0)+'\n')
resfile.close()

第一次測試,結果是 68.15 。第二次把 batch size 由 64 修改為了 128,準確率上升到了 74.6,也許還有其他方法(調節學習率、網路初始化等),大家自己研究吧。