目录
一、迁移学习
1.什么是迁移学习
迁移学习是指利用已经训练好的模型,在新的任务上进行微调。迁移学习可以加快模型训练速度,提高模型性能,并且在数据稀缺的情况下也能很好地工作。
2.迁移学习的步骤
1、选择预训练的模型和适当的层
通常,我们会选择在大规模图像数据集 (如ImageNet)上预训练的模型,如VGG、ResNet 等。然后,根据新数据集的特点,选择需要微调的模型层。对于低级特征 的任务(如边缘检测),最好使用浅层模型 的层,而对于高级特征 的任务(如分类),则应选择更深层次的模型。
2、冻结预训练模型的参数
保持预训练模型的权重不变 ,只训练新增加 的层或者微调一些层,避免因为在数据集中过拟合导致预训练模型过度拟合。
3、在新数据集上训练新增加的层
在冻结预训练模型的参数情况下,训练新增加的层。这样,可以使新模型适应新的任务,从而获得更高的性能。
4、微调预训练模型的层
在新层上进行训练后,可以解冻 一些已经训练过 的层,并且将它们作为微调的目标。这样做可以提高模型在新数据集上的性能。
5、评估和测试
在训练完成之后,使用测试集对模型进行评估。如果模型的性能仍然不够好,可以尝试调整超参数或者更改微调层。
二、迁移学习实例
- 该实例使用的模型是ResNet-18残差神经网络模型
1.导入模型
- 导入所要用的库,加载ResNet18模型
python
import torch
import torchvision.models as models
from torch import nn
from torch.utils.data import Dataset, DataLoader
from torchvision import transforms
from PIL import Image
import numpy as np
"""将resnet18模型迁移到食物分类项目中"""
resent_model = models.resnet18(weights=models.ResNet18_Weights.DEFAULT) # 既调用了resnet18网络,又使用了训练好的模型 在这里下载了模型
2.冻结模型参数
- 将导入的模型参数冻结
python
for param in resent_model.parameters():
param.requires_grad = False # 设置每个参数的requires_grad属性为False,表示在训练过程中这些参数不需要计算梯度,也就是说它们不会在反向传播中更新。
# print(param)
# 模型所有参数(即权重和偏差)的requires_grad属性设置为False,从而冻结所有模型参数
# 使得在反向传播过程中不会计算它们的梯度,以此减少模型的计算量,提高理速度。
3.修改参数
- 因为我们所用的数据分类是20个,原模型分类是1000个,所以需要修改全连接层
- 获取原模型输入层的特征个数
- 将原模型的全连接层替换成原输入,输出为20的全连接层
- 保存需要训练的参数,后面优化器进行优化时就可以只训练该层参数
python
in_features = resent_model.fc.in_features # 获取模型原输入的特征个数
resent_model.fc = nn.Linear(in_features, 20) # 创建一个全连接层,输入特征为in_features,输出为20
param_to_update = [] # 保存需要训练的参数,仅仅包含全连接层的参数
for param in resent_model.parameters():
if param.requires_grad == True:
param_to_update.append(param)
4.创建类,数据增强,导入数据
- 将图片从本地导入,并进行数据增强,最后进行打包
python
class food_dataset(Dataset):
def __init__(self, file_path, transform=None): # 类的初始化,解析数据文件txt
self.file_path = file_path
self.imgs = []
self.labels = []
self.transform = transform
with open(self.file_path) as f: # 是把train.txt文件中图片的路径保存在 self.imgs,train.txt文件中标签保存在self.label里
samples = [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)
def __getitem__(self, idx): # 关键,可通过索引的形式获取每一个图片数据及标签
image = Image.open(self.imgs[idx]) # 读取到图片数据,还不是tensor
if self.transform:
# 将pil图像数据转换为tensor
image = self.transform(image) # 图像处理为256x256,转换为tenor
label = self.labels[idx] # label还不是tensor
label = torch.from_numpy(np.array(label, dtype=np.int64)) # label也转换为tensor
return image, label
data_transforms = {
'train':
transforms.Compose([
transforms.Resize([300, 300]),
transforms.RandomRotation(45),
transforms.CenterCrop(224),
transforms.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]) # 为 ImageNet 数据集计算的标准化参数
]),
'test':
transforms.Compose([
transforms.Resize([224, 224]),
transforms.ToTensor(),
transforms.Normalize([0.485, 0.456, 0.406], [0.229, 0.224, 0.225]) # 为 ImageNet 数据集计算的标准化参数
])
}
train_data = food_dataset(file_path=r'trainda.txt',transform=data_transforms['train']) # 64张图片为一个包 训练集60000张图片 打包成了938个包
test_data = food_dataset(file_path=r'testda.txt', transform=data_transforms['test'])
train_dataloader = DataLoader(train_data, batch_size=64, shuffle=True)
test_dataloader = DataLoader(test_data, batch_size=64, shuffle=True)
5.定义训练集和测试集函数
python
def train(dataloader, model, loss_fn, optimizer):
model.train() # 告诉模型,我要开始训练,模型中w进行随机化操作,已经更新w.在训练过程中,w会被修改的
batch_size_num = 1
for x, y in dataloader:
x, y = x.to(device), y.to(device) # 把训练数据集和标签传入CPU或GPU
pred = model.forward(x) # 向前传播
loss = loss_fn(pred, y) # 通过交叉熵损失函数计算损失值loss
optimizer.zero_grad() # 梯度值清零
loss.backward() # 反向传播计算得到每个参数的梯度值w
optimizer.step() # 根据梯度更新网络w参数
loss_value = loss.item() # 从tensor数据中提取数据出来,tensor获取损失值
if batch_size_num % 40 == 0:
print(f"loss:{loss_value:>7f} [number:{batch_size_num}]")
batch_size_num += 1
best_acc = 0
def test(dataloader, model, loss_fn):
global best_acc
size = len(dataloader.dataset)
num_batches = len(dataloader)
model.eval() # 测试,w就不能再更新。
test_loss, correct = 0, 0
with 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() # test loss是会自动累加每一个批次的损失值
correct += (pred.argmax(1) == y).type(torch.float).sum().item()
test_loss /= num_batches # 能来衡量模型测试的好坏。
correct /= size # 平均的正确率
print(f"Test result: \n Accuracy: {(100 * correct)}%, Avg loss: {test_loss}\n")
acc_s.append(correct)
loss_s.append(test_loss)
if correct > best_acc: # 保存正确率最大的那一次的模型
best_acc = correct
6.将模型传入GPU,并有序调整学习率
python
from torch import nn
device = 'cuda' if torch.cuda.is_available() else 'mps' if torch.backends.mps.is_avaibale() else 'cpu'
model = resent_model.to(device) # 为什么不需要加括号,之前是model = CNN().to(device) 因为 resnet_model 是对象不是类
"""有序调整学习率"""
loss_fn = nn.CrossEntropyLoss() # 处理多分类
optimizer = torch.optim.Adam(param_to_update, lr=0.001) # 仅训练最后一层的参数
scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=5, gamma=0.5) # 调整学习率
7.进行训练和测试
- 选择训练100轮,每训练一轮,输出测试结果
python
epchos = 100
acc_s = []
loss_s = []
for t in range(epchos):
print(f"Epoch {t + 1}\n--------------------------")
train(train_dataloader, model, loss_fn, optimizer)
scheduler.step()
test(test_dataloader, model, loss_fn)
print('最优测试结果为:', best_acc)
输出: