利用遷移學習優化食物分類模型:基于ResNet18的實踐
在深度學習的眾多應用中,圖像分類一直是一個熱門且具有挑戰性的領域。隨著研究的深入,我們發現利用預訓練模型進行遷移學習是一種非常有效的策略,可以顯著提高模型的性能,尤其是在數據量有限的情況下。在這篇文章中,我們將探討如何將ResNet18模型遷移到食物分類項目中,并通過一系列技術優化模型性能。
一、遷移學習的背景
遷移學習是一種機器學習技術,它允許模型在一個任務上訓練獲得的知識應用到另一個相關任務上。在圖像分類領域,遷移學習尤其有效,因為不同類別的圖像往往共享一些通用的特征。
二、項目概述
本項目的目標是構建一個能夠準確分類食物圖像的模型。我們選擇了ResNet18作為基礎模型,因為它在多個圖像分類任務上都表現出色。通過遷移學習,我們可以利用ResNet18在ImageNet數據集上預訓練的權重,加速模型的收斂并提高分類準確率。
三、模型遷移
1. 加載預訓練模型
我們首先加載了ResNet18的預訓練模型,并將其所有參數設置為不需要梯度更新,這樣可以防止在訓練過程中改變這些預訓練的權重。
resnet_model = models.resnet18(weights=models.ResNet18_Weights.DEFAULT)
for param in resnet_model.parameters():param.requires_grad = False
2. 修改全連接層
由于我們的食物分類任務有20個類別,因此我們需要修改ResNet18的最后一個全連接層,以輸出20個類別的預測。
in_features = resnet_model.fc.in_features
resnet_model.fc = nn.Linear(in_features, 20)
3. 選擇性更新參數
在遷移學習中,我們通常只更新模型的最后幾層參數。在我們的案例中,我們只更新了全連接層的參數。
params_to_update= []
for param in resnet_model.parameters():if param.requires_grad == True:params_to_update.append(param)
四、數據準備與增強
為了提高模型的泛化能力,我們對訓練數據進行了一系列的增強操作,包括隨機旋轉、裁剪、翻轉和灰度化等。
data_transforms = {'train': transforms.Compose([transforms.Resize([300, 300]),transforms.RandomRotation(45),transforms.CenterCrop(244),transforms.RandomHorizontalFlip(p=0.5),transforms.RandomVerticalFlip(p=0.5),transforms.RandomGrayscale(p=0.1),transforms.ToTensor(),transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])]),'valid': transforms.Compose([transforms.Resize([244, 244]),transforms.ToTensor(),transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])])
}
五、模型訓練與評估
我們使用交叉熵損失函數和Adam優化器進行模型訓練,并采用學習率調度器來動態調整學習率。
optimizer = torch.optim.Adam(params_to_update, lr=0.001)
scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=5, gamma=0.5)
在每個訓練周期結束后,我們在測試集上評估模型的性能,并記錄最佳準確率。
for t in range(epochs):print(f"Epoch {t+1}\n")train(train_dataloader, model, loss_fn, optimizer)scheduler.step()test(test_dataloader, model, loss_fn)
print('最優訓練結果為:', best_acc)
完整代碼
import numpy as np
from PIL import Image
import torch
from torch.utils.data import DataLoader,Dataset
from torchvision import transforms
import torchvision.models as models
from torch import nn'''將resnet18模型遷移到食物分類項目中'''
resnet_model = models.resnet18(weights=models.ResNet18_Weights.DEFAULT)for param in resnet_model.parameters():print(param)param.requires_grad = Falsein_features = resnet_model.fc.in_features
resnet_model.fc = nn.Linear(in_features, 20)params_to_update= []
for param in resnet_model.parameters():if param.requires_grad == True:params_to_update.append(param)# 數據增強
data_transforms = {'train': transforms.Compose([transforms.Resize([300, 300]),transforms.RandomRotation(45), # 隨機旋轉,-45到45度transforms.CenterCrop(244),#從中心裁剪240*240transforms.RandomHorizontalFlip(p=0.5), # 隨機水平翻轉transforms.RandomVerticalFlip(p=0.5), # 隨機垂直翻轉# transforms.ColorJitter(brightness=0.2, contrast=0.1, saturation=0.1, hue=0.1),transforms.RandomGrayscale(p=0.1), # 轉換為灰度圖transforms.ToTensor(),transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])]),'valid': transforms.Compose([transforms.Resize([244, 244]),transforms.ToTensor(),transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225])])
}class food_dataset(Dataset): #food_dataset是自己創建的類名稱,可以改為你需要的名稱def __init__(self, file_path, transform=None): #類的初始化,解析數據文件txtself.file_path = file_pathself.imgs = []self.labels = []self.transform = transformwith open(self.file_path) as f: #是把train.txt文件中圖片的路徑保存在 self.imgs,train.txt文件中標簽保存在 sesamples = [x.strip().split(' ') for x in f.readlines()]for img_path, label in samples:self.imgs.append(img_path) #圖像的路徑self.labels.append(label) #標簽,還不是tensor# 初始化:把圖片目錄加載到selfdef __len__(self):return len(self.imgs)def __getitem__(self, idx):image=Image.open(self.imgs[idx])if self.transform:image=self.transform(image)label=self.labels[idx]label=torch.from_numpy(np.array(label,dtype=np.int64))return image,label#training_data包含了本次需要訓練的全部數據集
training_data = food_dataset(file_path=r'D:\Users\妄生\PycharmProjects\人工智能\深度學習\trainda.txt', transform=data_transforms['train'])
test_data = food_dataset(file_path=r'D:\Users\妄生\PycharmProjects\人工智能\深度學習\testda.txt', transform=data_transforms['valid'])#training_data需要具備索引的功能,還需要確保數據是tensor
train_dataloader=DataLoader(training_data,batch_size=64,shuffle=True)
test_dataloader=DataLoader(test_data,batch_size=64,shuffle=True)device = "cuda" if torch.cuda.is_available() else "mps" if torch.backends.mps.is_available() else "cpu"
print(f"Using {device} device")model = resnet_model.to(device) # 將剛剛定義的模型傳入到GPU中def train(dataloader, model, loss_fn, optimizer): # 傳入參數 打包的數據,卷積模型,損失函數,優化器model.train() # 表示模型開始訓練batch_size_num = 1for x, y in dataloader: # 遍歷打包的圖片及其對應的標簽,其中batch為每一個數據的編號x, y = x.to(device), y.to(device) # 把訓練數據集和標簽傳入cpu或GPUpred = model.forward(x) # 自動初始化 W權值loss = loss_fn(pred, y) # 傳入模型訓練結果的預測值和真實值,通過交叉熵損失函數計算損失值L0optimizer.zero_grad() # 梯度值清零loss.backward() # 反向傳播計算得到每個參數的梯度optimizer.step() # 根據梯度更新網絡參數loss = loss.item() # 獲取損失值if batch_size_num % 100 == 0:print(f"loss: {loss:>7f}[number:{batch_size_num}]") # 打印損失值,右對齊,長度為7batch_size_num += 1 # 右下方傳入的參數,表示訓練輪數best_acc =0
def test(dataloader, model, loss_fn): # 定義一個test函數,用于測試模型性能global best_acc # 定義一個全局變量size = len(dataloader.dataset) # 返回打包的圖片總數num_batches = len(dataloader) # 返回打包的包的個數model.eval() # 表示模型進入測試模式test_loss, correct = 0, 0 # 初始化兩個值,一個用來存放總體損失值,一個存放預測準確的個數with torch.no_grad(): # 一個上下文管理器,關閉梯度計算。當你確認不會調用Tensor.backward()時可以減少for x, y in dataloader: # 遍歷數據加載器中測試集圖片的圖片及其標簽x, y = x.to(device), y.to(device) # 傳入GPUpred = model.forward(x) # 前向傳播,返回預測結果test_loss += loss_fn(pred, y).item() # 計算所有的損失值的和,item表示將tensor類型值轉化為python標量correct += (pred.argmax(1) == y).type(torch.float).sum().item() # 判斷預測的值是等于真實值,返回布爾值,將其轉換為0和1,然后求和# a = (pred.argmax(1)== y) dim=1表示每一行中的最大值對應的索引號,dim=日表示每 b=(pred.argmax(1)==y).type(torch.float)test_loss /= num_batches # 總體損失值除以數據條數得到平均損失值correct /= size # 求準確率print(f"Test result:in Accuracy: {(100 * correct)}%, Avg loss: {test_loss}") # 表示準確率機器對應的損失值acc_s.append(correct)loss_s.append(test_loss)if correct > best_acc: # 如果新訓練得到的準確率大于前面已經求出來的準確率best_acc = correct # 將新的準確率傳入值best_accloss_fn = nn.CrossEntropyLoss() # 創建交叉熵損失雨數對象,因為食物的類別是20
optimizer = torch.optim.Adam(model.parameters(), lr=0.001) # 創建一個優化器,SGD為隨機梯度下降,Adam為一種自適應優化器
scheduler = torch.optim.lr_scheduler.StepLR(optimizer,step_size=5,gamma=0.5)#調整學習率函數"訓練模型"
epochs = 50acc_s =[]
loss_s=[]
for t in range(epochs):print(f"Epoch {t+1}\n")train(train_dataloader, model, loss_fn, optimizer)scheduler.step()test(test_dataloader, model, loss_fn)
#在每個epoch的訓練中,使用scheduler.step()語句進行學習率更新
print('最優訓練結果為:',best_acc)
運行結果
六、總結
通過本項目,我們成功地將ResNet18模型遷移到了食物分類任務中,并通過遷移學習顯著提高了模型的性能。這種方法不僅減少了訓練時間,還提高了模型的泛化能力。未來,我們可以嘗試更多的遷移學習策略,如使用不同的預訓練模型或調整遷移學習的比例,以進一步提升模型性能。