迁移学习的概念
- 神经网络需要用数据来训练,它从数据中获得信息,进而把它们转换成相应的权重。
- 这些权重能够被提取出来,迁移到其他的神经网络中,
- 即“迁移”了这些学来的特征,就不需要从零开始训练一个神经网络了 。
迁移学习就像站在巨人的肩膀上,借助已有的高性能模型训练自己的算法,它可以为你节省大量功夫,使得自己模型性能快速飙升。
案例学习
1 加载ResNet18
pretrain_model = resnet18(pretrained=False) # 加载ResNet print(pretrain_model)
- 只需要网络结构,不需要用预训练模型的参数来初始化时 pretrained = False
2 修改模型结构
num_ftrs = pretrain_model.fc.in_features # 获取全连接层的输入 pretrain_model.fc = nn.Linear(num_ftrs, 5) # 全连接层改为不同的输出 print(pretrain_model)
# 加载预先训练好的模型 pretrained_dict = torch.load('./resnet18_pretrain.pth') print(pretrained_dict)
事实上,就是实现一个 y = kx + b 的操作,其中:
- x:图片对应输入
- y:整个模型输出
- k:权重
- b:偏置
# 修改resnet18的最后一层 # 弹出fc层参数,为后面修改fc层权重做准备 pretrained_dict.pop('fc.weight') pretrained_dict.pop('fc.bias') print(pretrained_dict)
model_dict = pretrain_model.state_dict() print(model_dict)
# 去除不需要的参数 {k: v for k, v in pretrained_dict.items() if k in model_dict}
#模型参数更新 model_dict.update(pretrained_dict) print(model_dict)
# 冻结部分层——将满足条件的参数的 requires_grad 属性设置为 False。 # requires_grad 为 true 进行更新,为 False 时权重和偏置不进行更新。 for name, value in pretrain_model.named_parameters(): if (name != 'fc.weight') and (name != 'fc.bias'): value.requires_grad = False # 将模型中属性 requires_grad = True 的参数选出来(要更新的参数在 parms_conv 当中) filter(lambda p: p.requires_grad, pretrain_model.parameters()) # 定义损失函数(分类常用交叉熵),计算相差多少 loss_fn = nn.CrossEntropyLoss() # 控制优化器只更新需要更新的层,这里使用随机梯度下降优化器 optimizer = torch.optim.SGD(params_conv, lr=1e-3)
3 整体概览
- Create Dateset
''' 生成训练集和测试集,保存在txt文件中 ''' # 相当于模型的输入。后面做数据加载器dataload的时候从里面读他的数据 import os import random#打乱数据用的 # 百分之60用来当训练集 train_ratio = 0.6 # 用来当测试集 test_ratio = 1-train_ratio rootdata = r"data"#数据的根目录 train_list, test_list = [],[]#读取里面每一类的类别 data_list = [] #生产train.txt和test.txt class_flag = -1 for a,b,c in os.walk(rootdata): print(a) for i in range(len(c)): data_list.append(os.path.join(a,c[i])) for i in range(0,int(len(c)*train_ratio)): train_data = os.path.join(a, c[i])+'\t'+str(class_flag)+'\n' train_list.append(train_data) for i in range(int(len(c) * train_ratio),len(c)): test_data = os.path.join(a, c[i]) + '\t' + str(class_flag)+'\n' test_list.append(test_data) class_flag += 1 print(train_list) random.shuffle(train_list)#打乱次序 random.shuffle(test_list) with open('train.txt','w',encoding='UTF-8') as f: for train_img in train_list: f.write(str(train_img)) with open('test.txt','w',encoding='UTF-8') as f: for test_img in test_list: f.write(test_img)
- 加载预训练模型
''' 加载预训练模型,冻结层 ''' import torch from torch import nn from torch.utils.data import DataLoader from utils import LoadData from torchvision.models import resnet18 # 定义训练函数,需要 def train(dataloader, model, loss_fn, optimizer): size = len(dataloader.dataset) # 从数据加载器中读取batch(一次读取多少张,即批次数),X(图片数据),y(图片真实标签)。 for batch, (X, y) in enumerate(dataloader): # 将数据存到显卡 X, y = X.cuda(), y.cuda() # 得到预测的结果pred pred = model(X) # 计算预测的误差 loss = loss_fn(pred, y) # 反向传播,更新模型参数 optimizer.zero_grad() loss.backward() optimizer.step() # 每训练100次,输出一次当前信息 if batch % 10 == 0: loss, current = loss.item(), batch * len(X) print(f"loss: {loss:>7f} [{current:>5d}/{size:>5d}]") def test(dataloader, model): size = len(dataloader.dataset) print("size = ", size) # 将模型转为验证模式 model.eval() # 初始化test_loss 和 correct, 用来统计每次的误差 test_loss, correct = 0, 0 # 测试时模型参数不用更新,所以no_gard() # 非训练, 推理期用到 with torch.no_grad(): # 加载数据加载器,得到里面的X(图片数据)和y(真实标签) for X, y in dataloader: # 将数据转到GPU X, y = X.cuda(), y.cuda() # 将图片传入到模型当中就,得到预测的值pred pred = model(X) # 计算预测值pred和真实值y的差距 test_loss += loss_fn(pred, y).item() # 统计预测正确的个数 correct += (pred.argmax(1) == y).type(torch.float).sum().item() test_loss /= size correct /= size print("correct = ", correct) print(f"Test Error: \n Accuracy: {(100 * correct):>0.1f}%, Avg loss: {test_loss:>8f} \n") if __name__ == '__main__': batch_size = 8 # 给训练集和测试集分别创建一个数据集加载器 train_data = LoadData("train.txt", True) valid_data = LoadData("test.txt", False) train_dataloader = DataLoader(dataset=train_data, num_workers=4, pin_memory=True, batch_size=batch_size, shuffle=True) test_dataloader = DataLoader(dataset=valid_data, num_workers=4, pin_memory=True, batch_size=batch_size) # 如果显卡可用,则用显卡进行训练 device = "cuda" if torch.cuda.is_available() else "cpu" print(f"Using {device} device") ''' 修改ResNet18模型的最后一层 ''' pretrain_model = resnet18(pretrained=False) # 加载ResNet num_ftrs = pretrain_model.fc.in_features # 获取全连接层的输入 pretrain_model.fc = nn.Linear(num_ftrs, 5) # 全连接层改为不同的输出 pretrained_dict = torch.load('./resnet18_pretrain.pth') # 弹出fc层的参数 pretrained_dict.pop('fc.weight') pretrained_dict.pop('fc.bias') # 自己的模型参数变量,在开始时里面参数处于初始状态,所以很多0和1 model_dict = pretrain_model.state_dict() # 去除一些不需要的参数 pretrained_dict = {k: v for k, v in pretrained_dict.items() if k in model_dict} # 模型参数列表进行参数更新,加载参数 model_dict.update(pretrained_dict) # 改进过的预训练模型结构,加载刚刚的模型参数列表 pretrain_model.load_state_dict(model_dict) ''' 冻结部分层 ''' # 将满足条件的参数的 requires_grad 属性设置为False for name, value in pretrain_model.named_parameters(): if (name != 'fc.weight') and (name != 'fc.bias'): value.requires_grad = False # filter 函数将模型中属性 requires_grad = True 的参数选出来 params_conv = filter(lambda p: p.requires_grad, pretrain_model.parameters()) # 要更新的参数在parms_conv当中 model = pretrain_model.to(device) # # 定义损失函数,计算相差多少,交叉熵, loss_fn = nn.CrossEntropyLoss() ''' 控制优化器只更新需要更新的层 ''' optimizer = torch.optim.SGD(params_conv, lr=1e-3) # 初始学习率 # 一共训练5次 epochs = 5 for t in range(epochs): print(f"Epoch {t + 1}\n-------------------------------") train(train_dataloader, model, loss_fn, optimizer) test(test_dataloader, model) print("Done!") # 保存训练好的模型 torch.save(model.state_dict(), "model_resnet18.pth") print("Saved PyTorch Model Success!")