自学内容网 自学内容网

【深度学习】(11)--迁移学习

迁移学习

迁移学习是指利用已经训练好的模型,在新的任务上进行微调。迁移学习可以加快模型训练速度,提高模型性能,并且在数据稀缺的情况下也能很好地工作。

一、迁移学习步骤

  1. 选择预训练的模型和适当的层:通常,我们会选择在大规模图像数据集(如ImageNet)上预训练的模型,如VGG、ResNet等。然后,根据新数据集的特点,选择需要微调的模型层。对于低级特征的任务(如边缘检测),最好使用浅层模型的层,而对于高级特征的任务(如分类),则应选择更深层次的模型。
  2. 冻结预训练模型的参数:保持预训练模型的权重不变,只训练新增加的层或者微调一些层,避免因为在数据集中过拟合导致预训练模型过度拟合。
  3. 在新数据集上训练新增加的层:在冻结预训练模型的参数情况下,训练新增加的层。这样,可以使新模型适应新的任务,从而获得更高的性能。
  4. 微调预训练模型的层:在新层上进行训练后,可以解冻一些已经训练过的层,并且将它们作为微调的目标。这样做可以提高模型在新数据集上的性能。
  5. 评估和测试:在训练完成之后,使用测试集对模型进行评估。如果模型的性能仍然不够好,可以尝试调整超参数或者更改微调层。

二、以残差网络为例

1. 导入模型

torchvision中导入模型,库中已经存放好了大量模型框架。

import torchvision.models as models
resnet_model = models.resnet18(weights = models.ResNet18_Weights.DEFAULT)
# weights = models.ResNet18_Weights.DEFAULT表示在使用ImageNet数据集上预先训练好的权重来初始化模型参数

2. 冻结参数

冻结参数,使得在反向传播过程中,不要在计算他们的梯度,减少计算量。

for param in resnet_model.parameters():
    print(param)
    # 模型所有的参数(权重和偏置项)的requires_grad属性设置为False,冻结所有模型参数
    # 使得在反向传播过程中,不要在计算他们的梯度,减少计算量
    param.requires_grad = False

3. 修改全连接层

因为原本模型中的输出有1000种特征,而我们现在训练的数据仅有20种特征,需要需改输出:

# 获取模型原输入的特征个数
in_features = resnet_model.fc.in_features
# 创建一个全连接层(将原全连接层覆盖),输入特征为in_features,输出为20
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)

4. 创建数据集的类

残差模型的传入数据大小为(224),所以要对数据进行裁剪
data_transforms = {
    'train':
        transforms.Compose([
            transforms.Resize([300,300]),
            transforms.RandomRotation(45), # 随机旋转,-45到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),# 参数1亮度,参数2对比度,参数3饱和度,参数4色相
            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([224,224]),
            transforms.ToTensor(),
            transforms.Normalize([0.485,0.456,0.406],[0.229,0.224,0.225])
        ]),
}

5. 处理数据

划分数据中的特征与标签:

"""-----处理数据-----"""
class food_dataset(Dataset):
    def __init__(self,file_path,transform = None):
        self.file_path = file_path
        self.imgs = []
        self.labels = []
        self.transform = transform
        with open(self.file_path) as f:
            samples = [x.strip().split(' ') for x in f.readlines()]
            for img_path,label in samples:
                self.imgs.append(img_path) # 特征
                self.labels.append(label)# 标签

    def __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 = food_dataset(file_path='trainda.txt',transform=data_transforms['train'])
test_data = food_dataset(file_path='testda.txt',transform=data_transforms['valid'])

train_dataloader = DataLoader(training_data,batch_size=64,shuffle=True)
test_dataloader = DataLoader(test_data,batch_size=64,shuffle=True)

6. 装配设备

"""---判断当前设备是否支持GPU,其中mps是苹果m系列芯片的GPU"""
device = "cuda" if torch.cuda.is_available() else "mps" if torch.backends.mps.is_available() else "cpu"

7. 建立模型

"""-----建立模型-----"""
model = resnet_model.to(device)

8. 训练模型

"""-----训练集-----"""
def train(dataloader,model,loss_fn,optimizer):
    model.train()

    batch_size_num =1
    for x,y in dataloader:
        x,y = x.to(device),y.to(device)
        pred = model.forward(x)
        loss = loss_fn(pred,y)

        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

        loss_value = loss.item()  # 获取损失值
        if batch_size_num %20 == 0:  # 每200次迭代打印一次损失
            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()
    test_loss,correct = 0,0
    with torch.no_grad():
        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()
    test_loss /= num_batches
    correct /= size
    correct = round(correct, 4)
    print(f"Test result: \n Accuracy:{(100*correct)}%,Avg loss:{test_loss}")
    acc_s.append(correct)
    loss_s.append(test_loss)

    if correct > best_acc:
        best_acc = correct
"""-----损失函数-----"""
loss_fn = nn.CrossEntropyLoss()

"""-----优化器-----"""
optimizer = torch.optim.Adam(params_to_update,lr=0.001)
scheduler = torch.optim.lr_scheduler.StepLR(optimizer,step_size=5,gamma=0.5)

epochs = 100
acc_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)
print('最优训练结果:',best_acc)

结果:

在这里插入图片描述

三、完整代码展示

import torch
import torchvision.models as models
from torch import nn
from torch.utils.data import Dataset,DataLoader
import numpy as np
from PIL import Image
from torchvision import transforms

"""将resnet18模型迁移到食物分类项目中"""#残差网络是固定的网络结构,不需要自己来类定义
resnet_model = models.resnet18(weights = models.ResNet18_Weights.DEFAULT)
# weights = models.ResNet18_Weights.DEFAULT表示在使用ImageNet数据集上预先训练好的权重来初始化模型参数
for param in resnet_model.parameters():
    print(param)
    # 模型所有的参数(权重和偏置项)的requires_grad属性设置为False,冻结所有模型参数
    # 使得在反向传播过程中,不要在计算他们的梯度,减少计算量
    param.requires_grad = False

"""-----修改残差模型中的全连接层-----"""# 因为原本模型中的输出有1000种特征,而我们现在训练的数据仅有20种特征,需要需改输出
# 获取模型原输入的特征个数
in_features = resnet_model.fc.in_features
# 创建一个全连接层(将原全连接层覆盖),输入特征为in_features,输出为20
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)

"""-----创建数据集的类-----"""# 残差模型的传入数据大小为(224),所以要对数据进行裁剪
data_transforms = {
    'train':
        transforms.Compose([
            transforms.Resize([300,300]),
            transforms.RandomRotation(45), # 随机旋转,-45到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),# 参数1亮度,参数2对比度,参数3饱和度,参数4色相
            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([224,224]),
            transforms.ToTensor(),
            transforms.Normalize([0.485,0.456,0.406],[0.229,0.224,0.225])
        ]),
}

"""-----处理数据-----"""
class food_dataset(Dataset):
    def __init__(self,file_path,transform = None):
        self.file_path = file_path
        self.imgs = []
        self.labels = []
        self.transform = transform
        with open(self.file_path) as f:
            samples = [x.strip().split(' ') for x in f.readlines()]
            for img_path,label in samples:
                self.imgs.append(img_path)
                self.labels.append(label)

    def __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

"""---判断当前设备是否支持GPU,其中mps是苹果m系列芯片的GPU"""
device = "cuda" if torch.cuda.is_available() else "mps" if torch.backends.mps.is_available() else "cpu"
print(f"Using {device} device")

"""-----数据处理-----"""
training_data = food_dataset(file_path='trainda.txt',transform=data_transforms['train'])
test_data = food_dataset(file_path='testda.txt',transform=data_transforms['valid'])

train_dataloader = DataLoader(training_data,batch_size=64,shuffle=True)
test_dataloader = DataLoader(test_data,batch_size=64,shuffle=True)

"""-----建立模型-----"""
model = resnet_model.to(device)

"""-----损失函数-----"""
loss_fn = nn.CrossEntropyLoss()

"""-----优化器-----"""
optimizer = torch.optim.Adam(params_to_update,lr=0.001)
scheduler = torch.optim.lr_scheduler.StepLR(optimizer,step_size=5,gamma=0.5)

"""-----训练集-----"""
def train(dataloader,model,loss_fn,optimizer):
    model.train()

    batch_size_num =1
    for x,y in dataloader:
        x,y = x.to(device),y.to(device)
        pred = model.forward(x)
        loss = loss_fn(pred,y)

        optimizer.zero_grad()
        loss.backward()
        optimizer.step()

        loss_value = loss.item()  # 获取损失值
        if batch_size_num %20 == 0:  # 每200次迭代打印一次损失
            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()
    test_loss,correct = 0,0
    with torch.no_grad():
        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()
    test_loss /= num_batches
    correct /= size
    correct = round(correct, 4)
    print(f"Test result: \n Accuracy:{(100*correct)}%,Avg loss:{test_loss}")
    acc_s.append(correct)
    loss_s.append(test_loss)

    if correct > best_acc:
        best_acc = correct

"""-----训练模型-----"""
epochs = 100
acc_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)
print('最优训练结果:',best_acc)

总结

本篇介绍了:

  1. 如何进行迁移学习
  2. 对迁移模型进行微调:
    1. 微调全连接层
    2. 微调卷积层(本篇未写),原理相同,可自行尝试
  3. 注意:原本的模型参数务必要冻结住,那是已经调好的,可以节省计算时间。仅需要调整修改部分的参数。

原文地址:https://blog.csdn.net/m0_74896766/article/details/142628872

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!