本文通過記錄在pytorch中訓練CIFAR-10數據集的一些過程,實現一個基本的數據集的分類,並在此過程中加強對圖片、張量、CNN網絡的理解,並嘗試去總結一些訓練技巧,記錄一個新手對數據及網絡的理解。
CIFAR—10數據集
CIFAR-10數據集包含10個類別的60000個32x32彩色圖像,每個類別6000個圖像。 有50000張訓練圖像和10000張測試圖像。數據集地址如下:
示例如下:
接下來我們要做的事情便是利用各種CNN的網絡模型,在訓練集上訓練我們的模型,然後在測試集上測試模型的泛化能力,然後不斷調節各種超參數及網絡模型的細節,已到達在測試集上有更高的分類準確率的目的。
代碼實現
代碼實現如下:
import torch
import torch.nn as nn
import torch.optim as optim
import torchvision
import torchvision.datasets as datasets
import torchvision.transforms as transforms
import matplotlib.pyplot as plt
import io
import sys
#超參數定義
EPOCH = 100
BATCH_SIZE = 64
LR = 0.001
#數據集加載
#對訓練集及測試集數據的不同處理組合
transform_train = transforms.Compose([
transforms.RandomHorizontalFlip(),
transforms.RandomGrayscale(),
transforms.ToTensor(),
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])
transform_test = transforms.Compose([
transforms.ToTensor(),
transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])
#將數據加載進來,本地已經下載好, root=os.getcwd()爲自動獲取與源碼文件同級目錄下的數據集路徑
train_data = datasets.CIFAR10(root=os.getcwd(), train=True,transform=transform_train,download=False)
test_data =datasets.CIFAR10(root=os.getcwd(),train=False,transform=transform_test,download=False)
#數據分批
from torch.utils.data import DataLoader
#使用DataLoader進行數據分批,dataset代表傳入的數據集,batch_size表示每個batch有多少個樣本
#shuffle表示在每個epoch開始的時候,對數據進行重新排序
#數據分批之前:torch.Size([3, 32, 32]):Tensor[[32*32][32*32][32*32]],每一個元素都是歸一化之後的RGB的值;數據分批之後:torch.Size([64, 3, 32, 32])
#數據分批之前:train_data([50000[3*[32*32]]])
#數據分批之後:train_loader([50000/64*[64*[3*[32*32]]]])
train_loader = DataLoader(dataset=train_data,batch_size=BATCH_SIZE,shuffle=True,num_workers=2)
test_loader = DataLoader(dataset=test_data,batch_size=BATCH_SIZE,shuffle=True,num_workers=2)
#模型加載,有多種內置模型可供選擇
model = torchvision.models.densenet201(pretrained=False)
#定義損失函數,分類問題使用交叉信息熵,迴歸問題使用MSE
criterion = nn.CrossEntropyLoss()
#torch.optim來做算法優化,該函數甚至可以指定每一層的學習率,這裏選用Adam來做優化器,還可以選其他的優化器
optimizer = optim.Adam(model.parameters(),lr=LR)
#設置GPU
device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu')
#模型和輸入數據都需要to device
mode = model.to(device)
#模型訓練
from torch.utils.tensorboard import SummaryWriter
writer = SummaryWriter('cifar-10')
for epoch in range(EPOCH):
for i,data in enumerate(train_loader):
#取出數據及標籤
inputs,labels = data
#數據及標籤均送入GPU或CPU
inputs,labels = inputs.to(device),labels.to(device)
#前向傳播
outputs = model(inputs)
#計算損失函數
loss = criterion(outputs,labels)
#清空上一輪的梯度
optimizer.zero_grad()
#反向傳播
loss.backward()
#參數更新
optimizer.step()
#利用tensorboard,將訓練數據可視化
if i%50 == 0:
writer.add_scalar("Train/Loss", loss.item(), epoch*len(train_loader)+i)
#print('it’s training...{}'.format(i))
print('epoch{} loss:{:.4f}'.format(epoch+1,loss.item()))
#保存模型參數
torch.save(model,'cifar10_densenet161.pt')
print('cifar10_densenet161.pt saved')
#模型加載
model = torch.load('cifar10_densenet161.pt')
#測試
#model.eval()
model.train()
correct,total = 0,0
for j,data in enumerate(test_loader):
inputs,labels = data
inputs,labels = inputs.to(device),labels.to(device)
#前向傳播
outputs = model(inputs)
_, predicted = torch.max(outputs.data,1)
total =total+labels.size(0)
correct = correct +(predicted == labels).sum().item()
#準確率可視化
if j%20 == 0:
writer.add_scalar("Train/Accuracy", 100.0*correct/total, j)
print('準確率:{:.4f}%'.format(100.0*correct/total))
訓練過程
以下整理訓練過程的大致情況
trick:
A:model.eval()換成了model.train()
B:pretrained=True
C:優化器使用SGD
D:0-50,lr=0.01;50-150,lr=0.001,150-200,lr=0.0001
E:transform_train,transform_test
F:num_workers=2
先試了一下各個模型在同等條件下的表現,EPOCH選的比較小,是爲了更快的得出結果:
Model | EPOCH | BATCH_SIZE | LR | Tricks | Min_Loss | Accuracy |
---|---|---|---|---|---|---|
Resnet50 | 10 | 64 | 0.001 | - | 0.7573 | 68.0700% |
Resnet101 | 10 | 64 | 0.001 | - | 1.1134 | 52.9000% |
Resnet150 | 10 | 64 | 0.001 | - | 1.3076 | 51.0700% |
Resnet18 | 10 | 64 | 0.001 | - | 0.2364 | 67.7100% |
Resnet34 | 10 | 64 | 0.001 | - | 0.4274 | 71.9800% |
Densenet161 | 10 | 64 | 0.001 | - | 0.1355 | 73.8000% |
Densenet201 | 10 | 64 | 0.001 | - | 0.2540 | 77.7300% |
可以看出:同樣使用Resnet,並不是網絡層數越多網絡表現越好;同等條件下Densenet的表現更好,而且很明顯;同樣使用Densenet,網絡層數越深,效果越好,但實際運行時耗時較多。
選用Densenet161及Densenet201作爲基準模型,開始調整其他參數:
Model | EPOCH | BATCH_SIZE | LR | Tricks | Min_Loss | Accuracy |
---|---|---|---|---|---|---|
Densenet161 | 10 | 64 | 0.001 | - | 0.1355 | 73.8000% |
30 | 64 | 0.001 | - | 0.0039 | 79.4600% | |
200 | 64 | 0.001 | - | 0.0000(e73) 0.0002(e37) |
81.5100% | |
Densenet161 | 10 | 64 | 0.001 | A | 0.1401 | 78.0000% |
10 | 32 | 0.001 | A | 0.1470 | 78.8200% | |
10 | 128 | 0.001 | A | 0.2270 | 77.8300% | |
10 | 512 | 0.001 | A | 0.2391 | 74.5100% | |
10 | 64 | 0.0001 | A | 0.1392 | 64.0600% | |
37 | 32 | 0.001 | A | 0.0012 | 79.4100% | |
10 | 64 | 0.001 | A+B | 0.5316 | 76.6300% | |
Densenet201 | 10 | 64 | 0.001 | - | 0.2540 | 77.7300% |
10 | 64 | 0.001 | C | 1.0627 | 53.7300% | |
200 | 64 | 0.001 | D | 0.0000(e62) | 80.8600% | |
10 | 64 | 0.001 | E+F | 0.3051 | 78.8100% |
可以看出,在epoch=10、batch_size=64,lr=0.001的同等條件下,densenet201的效果要好於densenet161,而且batch_size=32的效果也要更好,但分批設置LR(D)的效果還沒看出來,從數據看幾個trick中,A、E、F對於提升準確率也是有幫助的,B貌似起到了負效果,C嚴重影響了模型的訓練。接下來設置一組參數:
Model | EPOCH | BATCH_SIZE | LR | Tricks | Times | Min_Loss | Accuracy |
---|---|---|---|---|---|---|---|
densenet201 | 100 | 32 | 0.001 | AEF | 8H | 0.0001(E87) | 83.2100% |
果然,效果達到了最佳,那還能不能繼續改進呢。比如加上D(分段設置lr),會不會更好一點呢:經過一晚上的訓練之後結果如下:
(D:e1-e24:0.01,e25-e79:0.001,e80-e100:0.0005)
Model | EPOCH | BATCH_SIZE | LR | Tricks | Times | Min_Loss | Accuracy |
---|---|---|---|---|---|---|---|
densenet201 | 100 | 32 | 0.001 | ADEF | 8H | 0.0002(E86) | 82.5300% |
效果上來看沒有之前好。那就這樣吧,在這個上面浪費了太多的時間,暫且這樣。
總結:網絡層數的加深一般能起到比較積極的作用;訓練的循環次數加深在一定範圍內對網絡模型訓練的正向作用較大;合適且較小的BATCH_SIZE能在數據量一定的情況下提升訓練效果;變化的學習率可能會起到比較好的作用,但是這個變化本身也是很難設定的;優化器的選取也十分重要;數據的預處理、模型的小細節也可以爲優化網絡作出一份貢獻。