第四次作業:貓狗大戰挑戰賽


貓狗大戰挑戰賽

提交結果截圖

VGG模型代碼解讀

首先,我們對給的樣例代碼進行解讀。可以看到樣例代碼使用了VGG模型

數據集加載

normalize = transforms.Normalize(mean=[0.485, 0.456, 0.406], std=[0.229, 0.224, 0.225])
vgg_format = transforms.Compose([
                transforms.CenterCrop(224),
                transforms.ToTensor(),
                normalize,
            ])
data_dir = './dogscats'
dsets = {x: datasets.ImageFolder(os.path.join(data_dir, x), vgg_format)
         for x in ['train', 'valid']}
dset_sizes = {x: len(dsets[x]) for x in ['train', 'valid']}
dset_classes = dsets['train'].classes
oader_train = torch.utils.data.DataLoader(dsets['train'], batch_size=64, shuffle=True, num_workers=6)
loader_valid = torch.utils.data.DataLoader(dsets['valid'], batch_size=5, shuffle=False, num_workers=6)

這段代碼的作用為加載數據集,vgg_format為對圖片的處理,加載的數據從原圖中心截取224*224,並進行正則化和歸一化。代碼使用了torchvision的datasets.ImageFolder方法來加載數據。
在加載完成后,使用DataLoader將dataset轉換為可迭代的數據,catch_size規定了迭代時每次分組的大小。

構建模型

model_vgg = models.vgg16(pretrained=True)
model_vgg = model_vgg.to(device)

model_vgg_new = model_vgg
for param in model_vgg_new.parameters():
    param.requires_grad = False
model_vgg_new.classifier._modules['6'] = nn.Linear(4096, 2)
model_vgg_new.classifier._modules['7'] = torch.nn.LogSoftmax(dim = 1)
model_vgg_new = model_vgg_new.to(device)

樣例並沒有選擇重新定義一個模型,然后訓練。樣例使用了VGG16的預訓練模型,事實上,對於貓狗大戰這樣訓練數據集較大的深度學習工作來說,重新訓練網絡較為費時。而另一方面,雖然不同的任務所進行的細節不相同,但在選定模型后,其內部的參數許多與實際完成的任務內容無關,可以使用預訓練模型確定無關的參數而只訓練與具體任務有關的參數,這樣可以降低訓練的復雜度,同時也有較好的效果。雖然之前就知道有凍結參數的方法,但這是我第一次實際進行操作。設置param.requires_grad = False表示不再接受梯度改變參數,並將模型的最后兩層設為分類類別數為2和輸出分類結果。

訓練模型

criterion = nn.NLLLoss()
lr = 0.001
optimizer_vgg = torch.optim.SGD(model_vgg_new.classifier[6].parameters(),lr = lr)
def train_model(model,dataloader,size,epochs=1,optimizer=None):
    model.train()
    for epoch in range(epochs):
        running_loss = 0.0
        running_corrects = 0
        count = 0
        for inputs,classes in dataloader:
            inputs = inputs.to(device)
            classes = classes.to(device)
            outputs = model(inputs)
            loss = criterion(outputs,classes)           
            optimizer = optimizer
            optimizer.zero_grad()
            loss.backward()
            optimizer.step()
            _,preds = torch.max(outputs.data,1)
            # statistics
            running_loss += loss.data.item()
            running_corrects += torch.sum(preds == classes.data)
            count += len(inputs)
            print('Training: No. ', count, ' process ... total: ', size)
        epoch_loss = running_loss / size
        epoch_acc = running_corrects.data.item() / size
        print('Loss: {:.4f} Acc: {:.4f}'.format(
                     epoch_loss, epoch_acc))
train_model(model_vgg_new,loader_train,size=dset_sizes['train'], epochs=1, 
            optimizer=optimizer_vgg)

正常的訓練模型,區別在於使用訓練模型並凍結了參數,使得訓練過程中僅修改模型最后兩層的參數。

思考與優化

樣例代碼展示了如何使用預訓練模型來降低模型訓練復雜度,這樣訓練出來的模型有較好的效果且比重新訓練節省時間。但這樣的模型還有一些改進的地方,主要有以下幾點:1. 用於訓練的數據可以優化,包括訓練數據的使用量和數據的處理,雖然很難說怎樣的圖片處理方式更好,但僅使用數據集中少部分圖片做訓練效果應該不如使用大量的數據做訓練。 2. 模型的選用,VGG模型效果不錯,但我想試試別的模型。 3. 訓練的方式,凍結參數降低了訓練的復雜性,但同時也使得模型被凍結參數的部分與實際任務不太相符,與完全凍結相比,也許可以在訓練時前面的預訓練模型的參數做少量修改。

基於以上思考,我使用ResNet18作為模型,並修改了訓練方式,對預訓練模型的參數做較小的修改。同時,秉承着大就是美,多就是好的原則,在訓練中選擇將給定數據集20000張圖片喂給模型。
參考博客地址

以下為代碼

import torch
import torchvision
import torchvision.transforms as transforms
import pandas as pd
import numpy as np
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
import re

device = torch.device('cuda' if torch.cuda.is_available() else 'cpu')

transform = transforms.Compose(
    [transforms.Resize(256),
     transforms.CenterCrop(size=224),
     transforms.ToTensor(),
     transforms.Normalize((0.5, 0.5, 0.5), (0.5, 0.5, 0.5))])

# 使用ImageFolder時注意數據集的要求
train_data = torchvision.datasets.ImageFolder('cat_dog/train', transform=transform)
test_data = torchvision.datasets.ImageFolder('cat_dog/test', transform=transform)
train_loader = torch.utils.data.DataLoader(train_data, batch_size=32, shuffle=True)
test_loader = torch.utils.data.DataLoader(test_data, batch_size=32, shuffle=False)

# 訓練模型
def train(net, train_iter, test_iter, criterion, optimizer, num_epochs):
    net = net.to(device)
    print("training on", device)
    for epoch in range(num_epochs):
        net.train()  # 訓練模式
        train_loss_sum, train_acc_sum, n, batch_count = 0.0, 0.0, 0, 0
        for i, (inputs, labels) in enumerate(train_iter):
            print(i)
            inputs, labels = inputs.to(device), labels.to(device)
            optimizer.zero_grad()  # 梯度清零
            labels_hat = net(inputs)
            loss = criterion(labels_hat, labels)
            loss.backward()
            optimizer.step()

            train_loss_sum += loss.cpu().item()
            train_acc_sum += (labels_hat.argmax(dim=1) == labels).sum().cpu().item()
            n += labels.shape[0]
            batch_count += 1
            
        print('epoch %d, loss %.4f, train acc %.3f'
              % (epoch + 1, train_loss_sum / batch_count, train_acc_sum / n))

# 使用ResNet18預訓練模型
pretrained_net = torchvision.models.resnet18(pretrained=True)
num_ftrs = pretrained_net.fc.in_features
pretrained_net.fc = nn.Linear(num_ftrs, 2)

# 對預訓練模型進行調整
output_params = list(map(id, pretrained_net.fc.parameters()))
feature_params = filter(lambda p: id(p) not in output_params, pretrained_net.parameters())
lr = 0.01
# 在ResNet18預訓練模型上進行微調,對於預訓練好的參數,使用較小的學習率來微調,而對於全連接層中的隨機初始化參數,需要更大的學習率從頭訓練。
optimizer = optim.SGD([{'params': feature_params},
                       {'params': pretrained_net.fc.parameters(), 'lr': lr * 10}],
                      lr=lr, weight_decay=0.001)

loss = torch.nn.CrossEntropyLoss()
# 訓練模型
train(pretrained_net, train_loader, test_loader, loss, optimizer, num_epochs=5)

res = []
for i, (inputs, labels) in enumerate(test_loader):
        labels_hat = pretrained_net(inputs).argmax(dim=1)
        for label in labels_hat:
            res.append(label)

# 正則匹配數字,獲取圖片名稱
pattern = re.compile(r'[0-9]+')
def getImg(path):
    s = re.findall(pattern, path)[0]
    return int(s)

imgs = [getImg(x[0]) for x in test_data.imgs]
# 分類結果與圖片對應
d = {}
for i in range(len(imgs)):
    d[imgs[i]] = int(res[i])
ans = np.zeros((len(res))).astype(int)
for k, v in d.items():
    ans[k] = v
# 保存結果
ans = pd.DataFrame(ans)
ans.to_csv('ans.csv')

總結與感悟

訓練的時候雖然用的是預訓練模型,但實際訓練的時間還是比我想的長。之前的練習使用的數據量比較小,網絡結果也比較簡單,所以訓練得快,但在實際應用中大量數據和較深的網絡模型是常見的,所以如何加速模型訓練十分重要。在自己寫代碼的時候,主要碰到兩個問題:1. 對如何加載數據不熟悉,我現在才知道使用dataset加載數據,數據集中的數據與文件夾內文件的順序並不相同,而是以類似於將文件名視為字符串進行排序。當然了,這很合理。2. 不知道該如何調整預訓練模型的參數,我是看了別人寫的博客才知道該怎么調的。深度學習涉及到的理論非常多,想要把技術問題從頭到尾弄明白還是挺難的。有的時候會使用一個模型來解決問題,可對於模型的架構、數據的處理還是缺少理解。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM