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,也許還有其他方法(調節學習率、網路初始化等),大家自己研究吧。