当前位置:   article > 正文

预训练—冻结权重

冻结权重

1、迁移学习finetune冻结部分权重

方式:将不更新的参数的requires_grad设置为Fasle,同时不将该参数传入optimizer.

(1)不更新的参数的requires_grad设置为Fasle

  1. # 冻结fc1层的参数
  2. for name, param in model.named_parameters():
  3.     if "fc1" in name:
  4.         param.requires_grad = False

(2)不将该参数传入optimizer

  1. # 定义一个fliter,只传入requires_grad=True的模型参数
  2. optimizer = optim.SGD(filter(lambda p : p.requires_grad, model.parameters()), lr=1e-2

总的代码:

  1. # 最优写法
  2. loss_fn = nn.CrossEntropyLoss()
  3. # # 训练前的模型参数
  4. print("model.fc1.weight", model.fc1.weight)
  5. print("model.fc2.weight", model.fc2.weight)
  6. print("model.fc1.weight.requires_grad:", model.fc1.weight.requires_grad)
  7. print("model.fc2.weight.requires_grad:", model.fc2.weight.requires_grad)
  8. # 冻结fc1层的参数
  9. for name, param in model.named_parameters():
  10.     if "fc1" in name:
  11.         param.requires_grad = False
  12. optimizer = optim.SGD(filter(lambda p : p.requires_grad, model.parameters()), lr=1e-2)  # 定义一个fliter,只传入requires_grad=True的模型参数
  13. for epoch in range(10):
  14.     x = torch.randn((3, 8))
  15.     label = torch.randint(0,3,[3]).long()
  16.     output = model(x)
  17.  
  18.     loss = loss_fn(output, label)
  19.     optimizer.zero_grad()
  20.     loss.backward()
  21.     optimizer.step()
  22. print("model.fc1.weight", model.fc1.weight)
  23. print("model.fc2.weight", model.fc2.weight)
  24. print("model.fc1.weight.requires_grad:", model.fc1.weight.requires_grad)
  25. print("model.fc2.weight.requires_grad:", model.fc2.weight.requires_grad)

最优写法能够节省显存和提升速度:

节省显存:不将不更新的参数传入optimizer

提升速度:将不更新的参数的requires_grad设置为False,节省了计算这部分参数梯度的时间

二、 迁移训练,卷积层设置较小的学习率,全连接层设置较大的学习率,需要用到优化器

  1. # ============================ step 4/5 优化器 ============================
  2. # --------------------法2 : conv卷积层较小学习率,全连接层较大学习率----------------
  3. # flag = 0
  4. flag = 1 # 真
  5. # Python程序语言指定任何非0和非空(null)值为true0 或者 nullfalse"判断条件"成立时/条件为“true"(非零),则执行后面的语句
  6. if flag:
  7. fc_params_id = list(map(id, resnet18_ft.fc.parameters())) # 返回的是fc层的parameters的内存地址
  8. base_params = filter(lambda p: id(p) not in fc_params_id, resnet18_ft.parameters()) # 将resnet18中的参数过滤掉fc层的参数后得到base_params==卷积层参数
  9. optimizer = optim.SGD([
  10. {'params': base_params, 'lr': LR*0.1}, # 第一个LR*0.1
  11. {'params': resnet18_ft.fc.parameters(), 'lr': LR}], momentum=0.9) # 10^-3,两个参数的momentum都是0.9
  12. else:
  13. optimizer = optim.SGD(resnet18_ft.parameters(), lr=LR, momentum=0.9) # 选择优化器
  14. scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=lr_deca y_step, gamma=0.1) # 设置学习率下降策略

另外一个小技巧就是在nn.Module里冻结参数,这样前面的参数就是False,而后面的不变。

  1. class Net(nn.Module):
  2. def __init__(self):
  3. super(Net, self).__init__()
  4. self.conv1 = nn.Conv2d(1, 6, 5)
  5. for p in self.parameters():
  6. p.requires_grad=False
  7. self.fc1 = nn.Linear(16 * 5 * 5, 120)

唯一需要注意的是,pytorch 固定部分参数训练时需要在优化器中施加过滤。

optimizer = optim.Adam(filter(lambda p: p.requires_grad, net.parameters()), lr=0.1)

所有代码如下:finetune_resnet18.py

  1. # -*- coding: utf-8 -*-
  2. """
  3. # @file name : finetune_resnet18.py
  4. # @brief : 模型finetune方法
  5. """
  6. import os
  7. import numpy as np
  8. import torch
  9. import torch.nn as nn
  10. from torch.utils.data import DataLoader
  11. import torchvision.transforms as transforms
  12. import torch.optim as optim
  13. from matplotlib import pyplot as plt
  14. from tools.my_dataset import AntsDataset
  15. from tools.common_tools import set_seed
  16. import torchvision.models as models
  17. import torchvision
  18. BASEDIR = os.path.dirname(os.path.abspath(__file__))
  19. device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
  20. print("use device :{}".format(device))
  21. set_seed(1) # 设置随机种子
  22. label_name = {"ants": 0, "bees": 1}
  23. # 参数设置
  24. MAX_EPOCH = 25
  25. BATCH_SIZE = 16
  26. LR = 0.001
  27. log_interval = 10
  28. val_interval = 1
  29. classes = 2
  30. start_epoch = -1
  31. lr_decay_step = 7
  32. # ============================ step 1/5 数据 ============================
  33. data_dir = os.path.join(BASEDIR, "..", "..", "data/hymenoptera_data")
  34. train_dir = os.path.join(data_dir, "train")
  35. valid_dir = os.path.join(data_dir, "val")
  36. norm_mean = [0.485, 0.456, 0.406]
  37. norm_std = [0.229, 0.224, 0.225]
  38. train_transform = transforms.Compose([
  39. transforms.RandomResizedCrop(224),
  40. transforms.RandomHorizontalFlip(),
  41. transforms.ToTensor(),
  42. transforms.Normalize(norm_mean, norm_std),
  43. ])
  44. valid_transform = transforms.Compose([
  45. transforms.Resize(256),
  46. transforms.CenterCrop(224),
  47. transforms.ToTensor(),
  48. transforms.Normalize(norm_mean, norm_std),
  49. ])
  50. # 构建MyDataset实例
  51. train_data = AntsDataset(data_dir=train_dir, transform=train_transform)
  52. valid_data = AntsDataset(data_dir=valid_dir, transform=valid_transform)
  53. # 构建DataLoder
  54. train_loader = DataLoader(dataset=train_data, batch_size=BATCH_SIZE, shuffle=True)
  55. valid_loader = DataLoader(dataset=valid_data, batch_size=BATCH_SIZE)
  56. # ============================ step 2/5 模型 ============================
  57. # 1/3 构建模型
  58. resnet18_ft = models.resnet18()
  59. # 2/3 加载预训练模型的参数
  60. # flag = 0
  61. flag = 1
  62. if flag:
  63. path_pretrained_model = os.path.join(BASEDIR, "..", "..", "data/resnet18-5c106cde.pth")
  64. state_dict_load = torch.load(path_pretrained_model)
  65. resnet18_ft.load_state_dict(state_dict_load)
  66. # ----------------------法1 : 冻结卷积层------------------------
  67. flag_m1 = 0
  68. # flag_m1 = 1
  69. if flag_m1:
  70. for param in resnet18_ft.parameters():
  71. param.requires_grad = False #参数不求取梯度,即参数不再更新
  72. print("conv1.weights[0, 0, ...]:\n {}".format(resnet18_ft.conv1.weight[0, 0, ...]))
  73. # 3/3 替换fc层,适应新任务:设置新的Linear层
  74. num_ftrs = resnet18_ft.fc.in_features #获取原始resnet18模型fc层features的个数
  75. # 构建Linear层的两个参数:输入神经元个数,输出神经元个数=
  76. resnet18_ft.fc = nn.Linear(num_ftrs, classes)
  77. resnet18_ft.to(device) #将模型放到cpu or gpu
  78. # ============================ step 3/5 损失函数 ============================
  79. criterion = nn.CrossEntropyLoss() # 选择损失函数
  80. # ============================ step 4/5 优化器 ============================
  81. # --------------------法2 : conv卷积层较小学习率,全连接层较大学习率----------------
  82. # flag = 0
  83. flag = 1 # 真
  84. # Python程序语言指定任何非0和非空(null)值为true0 或者 nullfalse"判断条件"成立时/条件为“true"(非零),则执行后面的语句
  85. if flag:
  86. fc_params_id = list(map(id, resnet18_ft.fc.parameters())) # 返回的是parameters的 内存地址
  87. base_params = filter(lambda p: id(p) not in fc_params_id, resnet18_ft.parameters()) # 将resnet18中的参数过滤掉fc层的参数后得到base_params
  88. optimizer = optim.SGD([
  89. {'params': base_params, 'lr': LR*0}, # 0
  90. {'params': resnet18_ft.fc.parameters(), 'lr': LR}], momentum=0.9)
  91. else:
  92. optimizer = optim.SGD(resnet18_ft.parameters(), lr=LR, momentum=0.9) # 选择优化器
  93. scheduler = torch.optim.lr_scheduler.StepLR(optimizer, step_size=lr_deca y_step, gamma=0.1) # 设置学习率下降策略
  94. # ============================ step 5/5 训练 ============================
  95. train_curve = list()
  96. valid_curve = list()
  97. for epoch in range(start_epoch + 1, MAX_EPOCH):
  98. loss_mean = 0.
  99. correct = 0.
  100. total = 0.
  101. resnet18_ft.train()
  102. for i, data in enumerate(train_loader):
  103. # forward
  104. inputs, labels = data
  105. inputs, labels = inputs.to(device), labels.to(device) #将数据、标签放到cpu or gpu
  106. outputs = resnet18_ft(inputs)
  107. # backward
  108. optimizer.zero_grad()
  109. loss = criterion(outputs, labels)
  110. loss.backward()
  111. # update weights
  112. optimizer.step()
  113. # 统计分类情况
  114. _, predicted = torch.max(outputs.data, 1)
  115. total += labels.size(0)
  116. correct += (predicted == labels).squeeze().cpu().sum().numpy()
  117. # 打印训练信息
  118. loss_mean += loss.item()
  119. train_curve.append(loss.item())
  120. if (i+1) % log_interval == 0:
  121. loss_mean = loss_mean / log_interval
  122. print("Training:Epoch[{:0>3}/{:0>3}] Iteration[{:0>3}/{:0>3}] Loss: {:.4f} Acc:{:.2%}".format(
  123. epoch, MAX_EPOCH, i+1, len(train_loader), loss_mean, correct / total))
  124. loss_mean = 0.
  125. # if flag_m1:
  126. print("epoch:{} conv1.weights[0, 0, ...] :\n {}".format(epoch, resnet18_ft.conv1.weight[0, 0, ...]))
  127. scheduler.step() # 更新学习率
  128. # validate the model
  129. if (epoch+1) % val_interval == 0:
  130. correct_val = 0.
  131. total_val = 0.
  132. loss_val = 0.
  133. resnet18_ft.eval()
  134. with torch.no_grad():
  135. for j, data in enumerate(valid_loader):
  136. inputs, labels = data
  137. inputs, labels = inputs.to(device), labels.to(device)
  138. outputs = resnet18_ft(inputs)
  139. loss = criterion(outputs, labels)
  140. _, predicted = torch.max(outputs.data, 1)
  141. total_val += labels.size(0)
  142. correct_val += (predicted == labels).squeeze().cpu().sum().numpy()
  143. loss_val += loss.item()
  144. loss_val_mean = loss_val/len(valid_loader)
  145. valid_curve.append(loss_val_mean)
  146. print("Valid:\t Epoch[{:0>3}/{:0>3}] Iteration[{:0>3}/{:0>3}] Loss: {:.4f} Acc:{:.2%}".format(
  147. epoch, MAX_EPOCH, j+1, len(valid_loader), loss_val_mean, correct_val / total_val))
  148. resnet18_ft.train()
  149. train_x = range(len(train_curve))
  150. train_y = train_curve
  151. train_iters = len(train_loader)
  152. valid_x = np.arange(1, len(valid_curve)+1) * train_iters*val_interval # 由于valid中记录的是epochloss,需要对记录点进行转换到iterations
  153. valid_y = valid_curve
  154. plt.plot(train_x, train_y, label='Train')
  155. plt.plot(valid_x, valid_y, label='Valid')
  156. plt.legend(loc='upper right')
  157. plt.ylabel('loss value')
  158. plt.xlabel('Iteration')
  159. plt.show()

感谢作者:【PyTorch框架】 迁移学习 & 模型微调_迁移学习模型微调_HUI 别摸鱼了的博客-CSDN博客

当有大部分层需要冻结,只有少部分层需要权重更新时,一层一层的写比较麻烦。那么就可以在网络定义时直接设置requires_grad=False,具体如下:

  1. class Net(nn.Module):
  2. def __init__(self):
  3. super(Net, self).__init__()
  4. self.conv1 = nn.Conv2d()
  5. self.conv2 = nn.Conv2d()
  6. self.fc1 = nn.Squential(
  7. nn.Linear(),
  8. nn.Linear(),
  9. ReLU(inplace=True),
  10. )
  11. for param in self.parameters():
  12. param.requires_grad = False
  13. #这样for循环之前的参数都被冻结,其后的正常更新。
  14. self.classifier = nn.Linear()

同样,在定义过滤器时要进行过滤,如下:

目的是,告诉优化器,哪些需要更新,那些不需要,这一步至关重要!

optimizer.SGD(filter(lambda p: p.requires_grad, model.parameters()), lr=1e-5)

3、另外一个作者的finetune实现

resnet18是 imagenet 数据集的预训练模型

  1. import os
  2. import torch
  3. import torchvision
  4. from torch import nn
  5. from d2l import torch as d2l
  6. # 解压下载的数据集,我们获得了两个文件夹hotdog/train和hotdog/test。 这两个文件夹都有hotdog(有热狗)和not-hotdog(无热狗)两个子文件夹, 子文件夹内都包含相应类的图像。
  7. # d2l.DATA_HUB['hotdog'] = (d2l.DATA_URL + 'hotdog.zip', 'fba480ffa8aa7e0febbb511d181409f899b9baa5')
  8. # data_dir = d2l.download_extract('hotdog')
  9. data_dir = './hotdog'
  10. # 我们创建两个实例来分别读取训练和测试数据集中的所有图像文件。
  11. train_imgs = torchvision.datasets.ImageFolder(os.path.join(data_dir, 'train'))
  12. test_imgs = torchvision.datasets.ImageFolder(os.path.join(data_dir, 'test'))
  13. # 下面显示了前8个正类样本图片和最后8张负类样本图片。 正如你所看到的,[图像的大小和纵横比各有不同]。
  14. hotdogs = [train_imgs[i][0] for i in range(8)]
  15. not_hotdogs = [train_imgs[-i - 1][0] for i in range(8)]
  16. d2l.show_images(hotdogs + not_hotdogs, 2, 8, scale=1.4)
  17. d2l.plt.show()
  18. # 在训练期间,我们首先从图像中裁切随机大小和随机长宽比的区域,然后将该区域缩放为 224×224 输入图像。
  19. # 在测试过程中,我们将图像的高度和宽度都缩放到256像素,然后裁剪中央 224×224 区域作为输入。 此外,对于RGB(红、绿和蓝)颜色通道,我们分别标准化每个通道。
  20. # 具体而言,该通道的每个值减去该通道的平均值,然后将结果除以该通道的标准差。
  21. # 使用RGB通道的均值和标准差,以标准化每个通道
  22. normalize = torchvision.transforms.Normalize(
  23. [0.485, 0.456, 0.406], [0.229, 0.224, 0.225])
  24. train_augs = torchvision.transforms.Compose([
  25. torchvision.transforms.RandomResizedCrop(224),
  26. torchvision.transforms.RandomHorizontalFlip(),
  27. torchvision.transforms.ToTensor(),
  28. normalize])
  29. test_augs = torchvision.transforms.Compose([
  30. torchvision.transforms.Resize(256),
  31. torchvision.transforms.CenterCrop(224),
  32. torchvision.transforms.ToTensor(),
  33. normalize])
  34. # [定义和初始化模型] 我们使用在ImageNet数据集上预训练的ResNet-18作为源模型。 在这里,我们指定pretrained=True以自动下载预训练的模型参数。
  35. pretrained_net = torchvision.models.resnet18(pretrained=True)
  36. # [定义需要finetune的模型]
  37. finetune_net = torchvision.models.resnet18(pretrained=True)
  38. # 因为我们要做的是一个二分类问题,所以只需要改变最后一层的输出分类为2,然后只初始化最后一层的weight
  39. finetune_net.fc = nn.Linear(finetune_net.fc.in_features, 2)
  40. nn.init.xavier_uniform_(finetune_net.fc.weight)
  41. # [微调模型]
  42. # 首先,我们定义了一个训练函数train_fine_tuning,该函数使用微调,因此可以多次调用
  43. # 如果param_group=True,输出层中的模型参数将使用十倍的学习率
  44. def train_fine_tuning(net, learning_rate, batch_size=128, num_epochs=5, param_group=True):
  45. train_iter = torch.utils.data.DataLoader(torchvision.datasets.ImageFolder(os.path.join(data_dir, 'train'), transform=train_augs), batch_size=batch_size, shuffle=True)
  46. test_iter = torch.utils.data.DataLoader(torchvision.datasets.ImageFolder(os.path.join(data_dir, 'test'), transform=test_augs), batch_size=batch_size)
  47. devices = d2l.try_all_gpus()
  48. loss = nn.CrossEntropyLoss(reduction="none")
  49. if param_group:
  50. # 取出除了最后一层(fc)之外的全部层的参数:params_1x
  51. params_1x = [param for name, param in net.named_parameters() if name not in ["fc.weight", "fc.bias"]]
  52. # params_1x使用正常的学习率,但是fc的学习率为正常学习率的十倍
  53. trainer = torch.optim.SGD([{'params': params_1x},
  54. {'params': net.fc.parameters(),
  55. 'lr': learning_rate * 10}],
  56. lr=learning_rate, weight_decay=0.001)
  57. else:
  58. trainer = torch.optim.SGD(net.parameters(), lr=learning_rate, weight_decay=0.001)
  59. # 画出
  60. d2l.train_ch13(net, train_iter, test_iter, loss, trainer, num_epochs, devices)
  61. d2l.plt.show()
  62. # 我们[使用较小的学习率],通过微调预训练获得的模型参数。
  63. train_fine_tuning(finetune_net, 5e-5)

有finetune的效果:

没有finetune的效果:

# [为了进行比较,]我们定义了一个相同的模型,但是将其(所有模型参数初始化为随机值)。 由于整个模型需要从头开始训练,因此我们需要使用更大的学习率。

 

 

声明:本文内容由网友自发贡献,转载请注明出处:【wpsshop】
推荐阅读
相关标签
  

闽ICP备14008679号