迁移学习resnet18模型及调用模型预测
目录
- 迁移学习resnet18模型及调用模型预测
- 1 迁移学习
- 1.1 概念
- 1.2 主要思想
- 1.3 优点
- 1.4 迁移学习的步骤
- 2 模型迁移和调整
- 2.1 ResNet18模型
- 2.2 新数据
- 2.3 冻结参数
- 2.4 微调层
- 2.5 新增层
- 2.6 数据预处理
- 3 代码测试
- 3.1 微调模型代码测试及保存模型
- 3.2 新增层模型训练与测试
- 3.3 调用模型预测新数据
1 迁移学习
1.1 概念
迁移学习是指利用已经训练好的模型,在新的任务上进行微调。迁移学习可以加快模型训练速度,提高模型性能,并且在数据稀缺的情况下也能很好地工作。
1.2 主要思想
利用已有的知识来帮助解决新的问题。在深度学习中,这通常意味着使用在大型数据集上预训练的神经网络作为起点,然后针对特定任务进行微调(fine-tuning)。
1.3 优点
- 减少数据需求:对于新任务,不需要大量标注数据来从头开始训练模型。
- 提高性能:预训练模型已经学习了通用的特征表示,这有助于新任务上的性能提升。
- 节省计算资源:迁移学习可以减少训练时间和计算资源的需求。
1.4 迁移学习的步骤
- 1.选择预训练的模型和适当的层:通常,我们会选择在大规模图像数据集(如lmageNet)上预训练的模型,如VGG、ResNet等。然后,根据新数据集的特点,选择需要微调的模型层。对于低级特征的任务(如边缘检测),最好使用浅层模型的层,而对于高级特征的任务(如分类),则应选择更深层次的模型。
- 2.冻结预训练模型的参数:保持预训练模型的权重不变,只训练新增加的层或者微调一些层,避免因为在数据集中过拟合导致预训练模型过度拟合。
- 3.在新数据集上训练新增加的层:在冻结预训练模型的参数情况下,训练新增加的层。这样,可以使新模型适应新的任务,从而获得更高的性能。
- 4.微调预训练模型的层:在新层上进行训练后,可以解冻一些已经训练过的层,并且将它们作为微调的目标。这样做可以提高模型在新数据集上的性能。
- 5.评估和测试:在训练完成之后,使用测试集对模型进行评估。如果模型的性能仍然不够好,可以尝试调整超参数或者更改微调层。
2 模型迁移和调整
2.1 ResNet18模型
2.2 新数据
新数据保存在train.txt和test.txt里,内容如下,是食物图片地址和食物图片的类别,一共有20种类别。
2.3 冻结参数
保持预训练模型的权重不变,模型的参数中有requires_grad,为True时权重参数会变化,为False时不会,所以对预训练模型所有参数冻结可指定:
model.parameters().requires_grad=False
for param in resnet_model.parameters():print(param)param.requires_grad = False
2.4 微调层
由于模型的输出为1000,而我们新数据的结果类别为20个,所以需要更改微调输出层。
更改输出层的输出,输入不变,需要先获取输入再重新定义输出层,如下,
## 获取输入
in_features = resnet_model.fc.in_features
# 重构输出
resnet_model.fc = nn.Linear(in_features,20)
2.5 新增层
需要保存原模型的权重参数不变,再新增一层输入输出分别为原模型的输出、新数据特征类别数。需要重新定义模型,可以先调用resnet18模型,再将其输出的1000,及新数据的20作为输入输出重新定义
class Resnet_add(nn.Module):def __init__(self):super().__init__()self.fc_add = torch.nn.Linear(1000, 20)def forward(self, x):x = resnet_model(x)out = self.fc_add(x)return out
2.6 数据预处理
由于模型的输入图片是224*224,所以需要对我们的新数据进行处理,同时也进行了数据增强
data_transforms = { #字典'train':transforms.Compose([ # 对图片做预处理的。组合,transforms.Resize([300,300]), #数据进行改变大小[256,256]transforms.RandomRotation(45),transforms.CenterCrop(224),transforms.RandomHorizontalFlip(p=0.5),transforms.RandomVerticalFlip(0.5),transforms.RandomGrayscale(0.1),transforms.ToTensor(), #数据转换为tensor,默认把通道维度放在前面transforms.Normalize([0.485,0.456,0.406],[0.229,0.224,0.225]),]),'valid':transforms.Compose([transforms.Resize([224,224]),transforms.ToTensor(),transforms.Normalize([0.485,0.456,0.406],[0.229,0.224,0.225]),]),
}
3 代码测试
3.1 微调模型代码测试及保存模型
模型保存:torch.save(model.state_dict(),‘best_rnt18.pth’)
代码展示:
import torch
from torch.utils.data import Dataset,DataLoader #用于处理数据集的
import numpy as np
from PIL import Image #
from torchvision import transforms #对数据进行处理工具 转换
import torchvision.models as models
from torch import nn
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]), #数据进行改变大小[256,256]transforms.RandomRotation(45),transforms.CenterCrop(224),transforms.RandomHorizontalFlip(p=0.5),transforms.RandomVerticalFlip(0.5),transforms.RandomGrayscale(0.1),transforms.ToTensor(), #数据转换为tensor,默认把通道维度放在前面transforms.Normalize([0.485,0.456,0.406],[0.229,0.224,0.225]),]),'valid':transforms.Compose([transforms.Resize([224,224]),transforms.ToTensor(),transforms.Normalize([0.485,0.456,0.406],[0.229,0.224,0.225]),]),
}#数组增强,
#Dataset是用来处理数据的。
device = "cuda" if torch.cuda.is_available() else "mps" if torch.backends.mps.is_available() else "cpu"
model = resnet_model.to(device)
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文件中标签保存在 self.labelssamples = [x.strip().split(' ') for x in f.readlines()]for img_path, label in samples:self.imgs.append(img_path) #图像的路径self.labels.append(label) #标签,还不是tensor
#初始化:把图片目录加载到self,def __len__(self): #类实例化对象后,可以使用len函数测量对象的个数return len(self.imgs)#training_data[1]def __getitem__(self, idx): #关键,可通过索引的形式获取每一个图片数据及标签image = Image.open(self.imgs[idx]) #读取到图片数据,还不是tensor,BGRif self.transform: #将pil图像数据转换为tensorimage = self.transform(image) #图像处理为256*256,转换为tenorlabel = self.labels[idx] #label还不是tensorlabel = torch.from_numpy(np.array(label,dtype = np.int64)) #label也转换为tensor,return image, labeltraining_data = food_dataset(file_path = './train_test/train.txt',transform = data_transforms['train']) #
test_data = food_dataset(file_path = './train_test/test.txt',transform = data_transforms['valid'])
# test_data = food_dataset(file_path = './test_true.txt',transform = data_transforms['valid'])#training_data需要具备索引的功能,还要确保数据是tensor
train_dataloader = DataLoader(training_data, batch_size=64,shuffle=True)#64张图片为一个包,
test_dataloader = DataLoader(test_data, batch_size=64,shuffle=True)loss_fn = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(params_to_update, lr=0.001)def train(dataloader, model, loss_fn, optimizer):model.train()
#pytorch提供2种方式来切换训练和测试的模式,分别是:model.train() 和 model.eval()。
# 一般用法是:在训练开始之前写上model.trian(),在测试时写上 model.eval() 。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) #通过交叉熵损失函数计算损失值loss# Backpropagation 进来一个batch的数据,计算一次梯度,更新一次网络optimizer.zero_grad() #梯度值清零loss.backward() #反向传播计算得到每个参数的梯度值optimizer.step() #根据梯度更新网络参数loss = loss.item() #获取损失值if batch_size_num %1 == 0:print(f"loss: {loss:>7f} [number:{batch_size_num}]")batch_size_num += 1best_acc = 0
def test(dataloader, model, loss_fn):size = len(dataloader.dataset)num_batches = len(dataloader)model.eval() #测试模式test_loss, correct = 0, 0global best_accwith torch.no_grad(): #一个上下文管理器,关闭梯度计算。当你确认不会调用Tensor.backward()的时候。这可以减少计算所用内存消耗。for X, y in dataloader:X, y = X.to(device), y.to(device)pred = model.forward(X)test_loss += loss_fn(pred, y).item() #correct += (pred.argmax(1) == y).type(torch.float).sum().item()a = (pred.argmax(1) == y) #dim=1表示每一行中的最大值对应的索引号,dim=0表示每一列中的最大值对应的索引号b = (pred.argmax(1) == y).type(torch.float)test_loss /= num_batchescorrect /= sizeif correct > best_acc:best_acc = correctprint(model.state_dict().keys())torch.save(model.state_dict(),'best_rnt18.pth')print(f"Test result: \n Accuracy: {(100*correct)}%, Avg loss: {test_loss}")scheduler = torch.optim.lr_scheduler.StepLR(optimizer,step_size=5,gamma=0.5)epochs = 20
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(f"Done!,best: {best_acc}")
model.load_state_dict(torch.load('best_rnt18.pth'))
运行结果:
3.2 新增层模型训练与测试
代码展示:
import torch
from torch.utils.data import Dataset,DataLoader #用于处理数据集的
import numpy as np
from PIL import Image #
from torchvision import transforms #对数据进行处理工具 转换
import torchvision.models as models
from torch import nnresnet_model = models.resnet18(weights = models.ResNet18_Weights.DEFAULT)
for param in resnet_model.parameters():print(param)param.requires_grad = Falsedata_transforms = { #字典'train':transforms.Compose([ # 对图片做预处理的。组合,transforms.Resize([300,300]), #数据进行改变大小[256,256]transforms.RandomRotation(45),transforms.CenterCrop(224),transforms.RandomHorizontalFlip(p=0.5),transforms.RandomVerticalFlip(0.5),transforms.RandomGrayscale(0.1),transforms.ToTensor(), #数据转换为tensor,默认把通道维度放在前面transforms.Normalize([0.485,0.456,0.406],[0.229,0.224,0.225]),]),'valid':transforms.Compose([transforms.Resize([224,224]),transforms.ToTensor(),transforms.Normalize([0.485,0.456,0.406],[0.229,0.224,0.225]),]),
}#数组增强,
#Dataset是用来处理数据的。
device = "cuda" if torch.cuda.is_available() else "mps" if torch.backends.mps.is_available() else "cpu"class Resnet_add(nn.Module):def __init__(self):super().__init__()self.fc_add = torch.nn.Linear(1000, 20)def forward(self, x):x = resnet_model(x)out = self.fc_add(x)return outmodel = Resnet_add().to(device)
params_to_update = []
for param in model.parameters():if param.requires_grad == True:params_to_update.append(param)
# model = models.resnet18(weights = models.ResNet18_Weights.DEFAULT)
# model = resnet_model.to(device)
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文件中标签保存在 self.labelssamples = [x.strip().split(' ') for x in f.readlines()]for img_path, label in samples:self.imgs.append(img_path) #图像的路径self.labels.append(label) #标签,还不是tensor
#初始化:把图片目录加载到self,def __len__(self): #类实例化对象后,可以使用len函数测量对象的个数return len(self.imgs)#training_data[1]def __getitem__(self, idx): #关键,可通过索引的形式获取每一个图片数据及标签image = Image.open(self.imgs[idx]) #读取到图片数据,还不是tensor,BGRif self.transform: #将pil图像数据转换为tensorimage = self.transform(image) #图像处理为256*256,转换为tenorlabel = self.labels[idx] #label还不是tensorlabel = torch.from_numpy(np.array(label,dtype = np.int64)) #label也转换为tensor,return image, labeltraining_data = food_dataset(file_path = './train_test/train.txt',transform = data_transforms['train']) #
test_data = food_dataset(file_path = './train_test/test.txt',transform = data_transforms['valid'])
# test_data = food_dataset(file_path = './test_true.txt',transform = data_transforms['valid'])#training_data需要具备索引的功能,还要确保数据是tensor
train_dataloader = DataLoader(training_data, batch_size=64,shuffle=True)#64张图片为一个包,
test_dataloader = DataLoader(test_data, batch_size=64,shuffle=True)loss_fn = nn.CrossEntropyLoss()
optimizer = torch.optim.Adam(params_to_update, lr=0.001)def train(dataloader, model, loss_fn, optimizer):model.train()
#pytorch提供2种方式来切换训练和测试的模式,分别是:model.train() 和 model.eval()。
# 一般用法是:在训练开始之前写上model.trian(),在测试时写上 model.eval() 。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) #通过交叉熵损失函数计算损失值loss# Backpropagation 进来一个batch的数据,计算一次梯度,更新一次网络optimizer.zero_grad() #梯度值清零loss.backward() #反向传播计算得到每个参数的梯度值optimizer.step() #根据梯度更新网络参数loss = loss.item() #获取损失值if batch_size_num %1 == 0:print(f"loss: {loss:>7f} [number:{batch_size_num}]")batch_size_num += 1best_acc = 0
def test(dataloader, model, loss_fn):size = len(dataloader.dataset)num_batches = len(dataloader)model.eval() #测试模式test_loss, correct = 0, 0global best_accwith torch.no_grad(): #一个上下文管理器,关闭梯度计算。当你确认不会调用Tensor.backward()的时候。这可以减少计算所用内存消耗。for X, y in dataloader:X, y = X.to(device), y.to(device)pred = model.forward(X)test_loss += loss_fn(pred, y).item() #correct += (pred.argmax(1) == y).type(torch.float).sum().item()a = (pred.argmax(1) == y) #dim=1表示每一行中的最大值对应的索引号,dim=0表示每一列中的最大值对应的索引号b = (pred.argmax(1) == y).type(torch.float)test_loss /= num_batchescorrect /= sizeif correct > best_acc:best_acc = correctprint(model.state_dict().keys())torch.save(model.state_dict(),'best_rnt18_add.pth')print(f"Test result: \n Accuracy: {(100*correct)}%, Avg loss: {test_loss}")scheduler = torch.optim.lr_scheduler.StepLR(optimizer,step_size=5,gamma=0.5)epochs = 20
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(f"Done!,best: {best_acc}")
运行结果:
3.3 调用模型预测新数据
需要注意,调用保存的模型时,该模型的框架也需要先搭建好,才能加载训练好的模型参数,再预测
代码展示:
import torch
from torch.utils.data import Dataset,DataLoader #用于处理数据集的
import numpy as np
from PIL import Image #
from torchvision import transforms #对数据进行处理工具 转换
import torchvision.models as models
from torch import nndata_transforms = { #字典'train':transforms.Compose([ # 对图片做预处理的。组合,transforms.Resize([300,300]), #数据进行改变大小[256,256]transforms.RandomRotation(45),transforms.CenterCrop(224),transforms.RandomHorizontalFlip(p=0.5),transforms.RandomVerticalFlip(0.5),transforms.RandomGrayscale(0.1),transforms.ToTensor(), #数据转换为tensor,默认把通道维度放在前面transforms.Normalize([0.485,0.456,0.406],[0.229,0.224,0.225]),]),'valid':transforms.Compose([transforms.Resize([224,224]),transforms.ToTensor(),transforms.Normalize([0.485,0.456,0.406],[0.229,0.224,0.225]),]),
}#数组增强,
#Dataset是用来处理数据的。
device = "cuda" if torch.cuda.is_available() else "mps" if torch.backends.mps.is_available() else "cpu"
# model = resnet_model.to(device)
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文件中标签保存在 self.labelssamples = [x.strip().split(' ') for x in f.readlines()]for img_path, label in samples:self.imgs.append(img_path) #图像的路径self.labels.append(label) #标签,还不是tensor
#初始化:把图片目录加载到self,def __len__(self): #类实例化对象后,可以使用len函数测量对象的个数return len(self.imgs)#training_data[1]def __getitem__(self, idx): #关键,可通过索引的形式获取每一个图片数据及标签image = Image.open(self.imgs[idx]) #读取到图片数据,还不是tensor,BGRif self.transform: #将pil图像数据转换为tensorimage = self.transform(image) #图像处理为256*256,转换为tenorlabel = self.labels[idx] #label还不是tensorlabel = torch.from_numpy(np.array(label,dtype = np.int64)) #label也转换为tensor,return image, label
test_data = food_dataset(file_path = './test_true.txt',transform = data_transforms['valid'])
test_dataloader = DataLoader(test_data, batch_size=64,shuffle=True)
resnet_model = models.resnet18()
in_features = resnet_model.fc.in_features
resnet_model.fc = nn.Linear(in_features,20)
model = resnet_model
model.load_state_dict(torch.load('best_rnt18.pth'))
loss_fn = nn.CrossEntropyLoss()
def test(dataloader, model, loss_fn):size = len(dataloader.dataset)num_batches = len(dataloader)model.eval() #测试模式test_loss, correct = 0, 0global best_accwith torch.no_grad(): #一个上下文管理器,关闭梯度计算。当你确认不会调用Tensor.backward()的时候。这可以减少计算所用内存消耗。for X, y in dataloader:X, y = X.to(device), y.to(device)pred = model.forward(X)test_loss += loss_fn(pred, y).item() #correct += (pred.argmax(1) == y).type(torch.float).sum().item()a = (pred.argmax(1) == y) #dim=1表示每一行中的最大值对应的索引号,dim=0表示每一列中的最大值对应的索引号b = (pred.argmax(1) == y).type(torch.float)test_loss /= num_batchescorrect /= sizeprint(f'true:{y}')print(f'pre:{pred.argmax(1)}')print(f"Test result: \n Accuracy: {(100*correct)}%, Avg loss: {test_loss}")test(test_dataloader, model, loss_fn)
运行结果: