【Pytorch神经网络实战案例】24 基于迁移学习识别多种鸟类(CUB-200数据集)

1 迁移学习

在实际开发中,常会使用迁移学习将预训练模型中的特征提取能力转移到自己的模型中。

1.1 迁移学习定义

迁移学习指将在一个任务上训练完成的模型进行简单的修改,再用另一个任务的数据继续训练,使之能够完成新的任务。

1.1.1 迁移学习举例

在ImageNet数据集上训练过的ResNet模型,其任务是进行图片分类,可以对其进行修改使用在目标定位任务上。

1.2 迁移学习的分类

迁移学习是机器学习的分支,按照学习方式可以分为基于样本的迁移、基于特征的迁移、基于模型的迁移,以及基于关系的迁移。

1.2 迁移学习的目的

节省人工标注样本的时间,让模型可以通过一个已有的标记数据领域向未标记数据领域进行迁移,从而训练出适用于该领域的模型。

简而言之,即运用已有的相关知识来辅助尽快地学习新知识。

1.3 迁移学习的优势

  1. 对于数据集本身很小(几千张图片)的情况,从头开始训练具有几千万个参数的大型神经网络是不现实的,因为越大的模型对数据量的要求越大,过拟合无法避免。如果还想用大型神经网络的超强特征提取能力,只能靠微调已经训练好的模型。
  2. 可以降低训练成本。如果使用导出特征向量的方法进行迁移学习,后期的训练成本非常低。
  3. 前人花很大精力训练出来的模型在大概率上会比你自己从零开始训练的模型要强大,没有必要重复造轮子。
  4. 将已训练好的模型用在其他图片分类任务中的迁移学习,也常叫作对已有模型的微调。

1.4 迁移学习与微调的关系

微调更常用于形容迁移学习的后期微调,微调应该是迁移学习中的组成部分,即微调是一个技巧。

2 CUB-200数据集简介

2.1 细粒度

简单理解细粒度分类:识别出一张图片是狗的情况下还需要得知狗的品种;这里识别出是狗就用到粗粒度分类,识别出狗的种类就用到细 粒度分类

细粒度分类的关键:提取有辨识力的特征(类似人眼对狗的品种的分类,提取出狗的最有特点的区域进行品种分类,类比到卷积神经网络,卷积网络提取出自己感兴趣的区域)是提升效果 的关键

2.2 CUB-200数据集组成

CUB-200数据集由加州理工学院在2010年提出的细粒度数据集,也是目前细粒度分类识别研究的基准图像数据集,共有11788张鸟类图像,包含200类鸟类子类,其中训练数据集有5994张图像,测试集有5794张图像,每张图像均提供图像类标记信息。

3 本节案例

3.1 案例简介

使用迁移学习对预训练模型进行微调,让其学习鸟类数据集,实现对多种鸟类进行识别。

3.2 代码实现:load_data函数加载图片名称与标签的加载----Transfer_bird.py(第1部分)

import glob
import numpy as np
from PIL import Image
import matplotlib.pyplot as plt #plt 用于显示图片
import torch
import torch.nn as nn
import torch.optim as optim
from torch.optim import lr_scheduler
from torch.utils.data import Dataset,DataLoader
import torchvision
import torchvision.models as model
from torchvision.transforms import ToPILImage
import torchvision.transforms as transforms
import os
os.environ['KMP_DUPLICATE_LIB_OK'] = 'True'# 1.1 实现load_data函数加载图片名称与标签的加载,并使用torch.utils.data接口将其封装成程序可用的数据集类OwnDataset。
def load_dir(directory,labstart=0): # 获取所有directory中的所有图与标签# 返回path指定的文件夹所包含的文件或文件名的名称列表strlabels = os.listdir(directory)# 对标签进行排序,以便训练和验证按照相同的顺序进行:在不同的操作系统中,加载文件夹的顺序可能不同。目录不同的情况会导致在不同的操作系统中,模型的标签出现串位的现象。所以需要对文件夹进行排序,保证其顺序的一致性。strlabels.sort()# 创建文件标签列表file_labels = []for i,label in enumerate(strlabels):print(label)jpg_names = glob.glob(os.path.join(directory,label,"*.jpg"))print(jpg_names)# 加入列表file_labels.extend(zip(jpg_names, [i + labstart] * len(jpg_names)))return file_labels,strlabelsdef load_data(dataset_path): # 定义函数load_data函数完成对数据集中图片文件名称和标签的加载。# 该函数可以实现两层文件夹的嵌套结构。其中,外层结构使用load_data函数进行遍历,内层结构使用load_dir函进行遍历。sub_dir = sorted(os.listdir(dataset_path)) # 跳过子文件夹:在不同的操作系统中,加载文件夹的顺序可能不同。目录不同的情况会导致在不同的操作系统中,模型的标签出现串位的现象。所以需要对文件夹进行排序,保证其顺序的一致性。start = 1 # 第0类是nonetfile_lables,tstrlabels = [],['none'] # 在制作标签时,人为地在前面添加了一个序号为0的none类。这是一个训练图文类模型的技巧,为了区分模型输出值是0和预测值是0这两种情况。for i in sub_dir:directory = os.path.join(dataset_path,i)if os.path.isdir(directory) == False: # 只处理文件夹中的数据print(directory)continuefile_labels,strlables = load_dir(directory,labstart=start)tfile_lables.extend(file_labels)tstrlabels.extend(strlables)start = len(strlables)# 将数据路径与标签解压缩,把数据路径和标签解压缩出来filenames,labels = zip(*tfile_lables)return filenames, labels, tstrlabels

3.3 代码实现:自定义数据集类OwnDataset----Transfer_bird.py(第2部分)

# 1.2 实现自定义数据集OwnDataset
def default_loader(path) : # 定义函数加载图片return Image.open(path).convert('RGB')class OwnDataset(Dataset): # 复用性较强,可根据自己的数据集略加修改使用# 在PyTorch中,提供了一个torch.utis.data接口,可以用来对数据集进行封装。在实现时,只需要继承torch.utis.data.Dataset类,并重载其__gettem__方法。# 在使用时,框架会向__gettem__方法传入索引index,在__gettem__方法内部根据指定index加载数据,并返回。def __init__(self,img_dir,labels,indexlist=None,transform=transforms.ToTensor(),loader=default_loader,cache=True): # 初始化self.labels = labels # 存放标签self.img_dir = img_dir # 样本图片文件名self.transform = transform # 预处理方法self.loader = loader # 加载方法self.cache = cache # 缓存标志if indexlist is None: # 要加载的数据序列self.indexlist = list(range(len(self.img_dir)))else:self.indexlist = indexlistself.data = [None] * len(self.indexlist) # 存放样本图片def __getitem__(self, idx): # 加载指定索引数据if self.data[idx] is None: # 第一次加载data = self.loader(self.img_dir[self.indexlist[idx]])if self.transform:data = self.transform(data)else:data = self.data[idx]if self.cache: # 保存到缓存里self.data[idx] = datareturn data,self.labels[self.indexlist[idx]]def __len__(self): # 计算数据集长度return len(self.indexlist)

3.4 代码实战:测试数据集----Transfer_bird.py(第3部分)

# 1.3 测试数据集:在完成数据集的制作之后,编写代码对其进行测试。
data_transform = { #定义数据的预处理方法'train':transforms.Compose([transforms.RandomResizedCrop(224),transforms.RandomHorizontalFlip(),transforms.ToTensor(),transforms.Normalize([0.485,0.456,0.406],[0.229,0.224,0.225])]),'val':transforms.Compose([transforms.Resize(256),transforms.CenterCrop(224),transforms.ToTensor(),transforms.Normalize([0.485,0.456,0.406],[0.229,0.224,0.225])]),
}
def Reduction_img(tensor,mean,std): #还原图片,实现了图片归一化的逆操作,显示数据集中的原始图片。dtype = tensor.dtypemean = torch.as_tensor(mean,dtype=dtype,device=tensor.device)std = torch.as_tensor(std,dtype=dtype,device=tensor.device)tensor.mul_(std[:,None,None]).add_(mean[:,None,None]) # 还原操作dataset_path = r'./data/cub200/' # 加载数据集路径
filenames,labels,classes = load_data(dataset_path) # 调用load_data函数对数据集中图片文件名称和标签进行加载,其返回对象classes中包含全部的类名。
# 打乱数据顺序
# 110-115行对数据文件列表的序号进行乱序划分,分为测试数据集和训练数集两个索引列表。该索引列表会传入OwnDataset类做成指定的数据集。
np.random.seed(0)
label_shuffle_index = np.random.permutation(len(labels))
label_train_num = (len(labels)//10) * 8 # 划分训练数据集和测试数据集
train_list = label_shuffle_index[0:label_train_num]
test_list = label_shuffle_index[label_train_num:] # 没带:train_dataset = OwnDataset(filenames,labels,train_list,data_transform['train'])# 实例化训练数据集
val_dataset = OwnDataset(filenames,labels,test_list,data_transform['val']) # 实例化测试数据集
# 实例化批次数据集:OwnDataset类所定义的数据集,其使用方法与PyTorch中的内置数据集的使用方法完全一致,配合DataLoader接口即可生成可以进行训练或测试的批次数据。具体代码如下。
train_loader = DataLoader(dataset=train_dataset,batch_size=32,shuffle=True)
val_loader = DataLoader(dataset=val_dataset,batch_size=32,shuffle=True)sample = iter(train_loader) # 获取一批次数据,进行测试
images,labels = sample.next()
print("样本形状",np.shape(images))
print("标签个数",len(classes))
mulimgs = torchvision.utils.make_grid(images[:10],nrow=10) # 拼接多张图片
Reduction_img(mulimgs,[0.485,0.456,0.406],[0.229,0.224,0.225])
_img = ToPILImage()(mulimgs) # 将张量转化为图片
plt.axis('off')
plt.imshow(_img) # 显示
plt.show()
print(','.join('%5s' % classes[labels[j]] for j in range(len(images[:10]))))

输出:

样本形状 torch.Size([32, 3, 224, 224])
标签个数 6

输出数据集中的10个图片

3.5 代码实战:获取并改造ResNet模型----Transfer_bird.py(第4部分)

# 1.4 获取并改造ResNet模型:获取ResNet模型,并加载预训练模型的权重。将其最后一层(输出层)去掉,换成一个全新的全连接层,该全连接层的输出节点数与本例分类数相同。
# 指定设备
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
print(device)
# get_ResNet函数,获取预训练模型,可指定pretrained=True来实现自动下载预训练模型,也可指定loadfile来从本地路径加载预训练模型。
def get_ResNet(classes,pretrained=True,loadfile=None):ResNet = model.resnet101(pretrained) # 自动下载官方的预训练模型if loadfile != None:ResNet.load_state_dict(torch.load(loadfile)) # 加载本地模型# 将所有的参数层进行冻结:设置模型仅最后一层可以进行训练,使模型只针对最后一层进行微调。for param in ResNet.parameters():param.requires_grad = False# 输出全连接层的信息print(ResNet.fc)x = ResNet.fc.in_features # 获取全连接层的输入ResNet.fc = nn.Linear(x,len(classes)) # 定义一个新的全连接层print(ResNet.fc) # 最后输出新的模型return ResNet
ResNet = get_ResNet(classes) # 实例化模型
ResNet.to(device=device)

3.6 代码实战:定义损失函数、训练函数及测试函数,对模型的最后一层进行微调----Transfer_bird.py(第5部分)

# 1.5 定义损失函数、训练函数及测试函数,对模型的最后一层进行微调。
criterion = nn.CrossEntropyLoss()
# 指定新加的全连接层的学习率
optimizer = torch.optim.Adam([{'params':ResNet.fc.parameters()}],lr=0.01)
def train(model,device,train_loader,epoch,optimizer): # 定义训练函数model.train()allloss = []for batch_idx,data in enumerate(train_loader):x,y = datax = x.to(device)y = y.to(device)optimizer.zero_grad()y_hat = model(x)loss = criterion(y_hat,y)loss.backward()allloss.append(loss.item())optimizer.step()print('Train Epoch:{}\t Loss:{:.6f}'.format(epoch,np.mean(allloss))) # 输出训练结果def test(model,device,val_loader): # 定义测试函数model.eval()test_loss = []correct = []with torch.no_grad(): # 使模型在运行时不进行梯度跟踪,可以减少模型运行时对内存的占用。for i,data in enumerate(val_loader):x, y = datax = x.to(device)y = y.to(device)y_hat = model(x)test_loss.append(criterion(y_hat,y).item()) # 收集损失函数pred = y_hat.max(1,keepdim=True)[1] # 获取预测结果correct.append(pred.eq(y.view_as(pred)).sum().item()/pred.shape[0]) # 收集精确度print('\nTest:Average loss:{:,.4f},Accuracy:({:,.0f}%)\n'.format(np.mean(test_loss),np.mean(correct)*100)) # 输出测试结果# 迁移学习的两个步骤如下
if __name__ == '__main__':
# 迁移学习步骤①:固定预训练模型的特征提取部分,只对最后一层进行训练,使其快速收敛。firstmodepth = './data/cub200/firstmodepth_1.pth' # 定义模型文件的地址if os.path.exists(firstmodepth) == False:print("—————————固定预训练模型的特征提取部分,只对最后一层进行训练,使其快速收敛—————————")for epoch in range(1,2): # 迭代两次train(ResNet,device,train_loader,epoch,optimizer)test(ResNet,device,val_loader)# 保存模型torch.save(ResNet.state_dict(),firstmodepth)

3.7 代码实战:使用退化学习率对模型进行全局微调----Transfer_bird.py(第6部分)

# 1.6 使用退化学习率对模型进行全局微调
#迁移学习步骤②:使用较小的学习率,对全部模型进行训练,并对每层的权重进行细微的调节,即将模型的每层权重都设为可训练,并定义带有退化学习率的优化器。(1.6部分)secondmodepth = './data/cub200/firstmodepth_2.pth'optimizer2 = optim.SGD(ResNet.parameters(),lr=0.001,momentum=0.9) # 第198行代码定义带有退化学习率的SGD优化器。该优化器常用来对模型进行手动微调。有实验表明,使用经过手动调节的SGD优化器,在训练模型的后期效果优于Adam优化器。exp_lr_scheduler = lr_scheduler.StepLR(optimizer2,step_size=2,gamma=0.9) # 由于退化学习率会在训练过程中不断地变小,为了防止学习率过小,最终无法进行权重需要对其设置最小值。当学习率低于该值时,停止对退化学习率的操作。for param in ResNet.parameters(): # 所有参数设计为可训练param.requires_grad = Trueif os.path.exists(secondmodepth):ResNet.load_state_dict(torch.load(secondmodepth)) # 加载本地模型else:ResNet.load_state_dict(torch.load(firstmodepth)) # 加载本地模型print("____使用较小的学习率,对全部模型进行训练,定义带有退化学习率的优化器______")for epoch in range(1,100):train(ResNet,device,train_loader,epoch,optimizer2)if optimizer2.state_dict()['param_groups'][0]['lr'] > 0.00001:exp_lr_scheduler.step()print("___lr:",optimizer2.state_dict()['param_groups'][0]['lr'])test(ResNet,device,val_loader)# 保存模型torch.save(ResNet.state_dict(),secondmodepth)

4 代码总览Transfer_bird.py

import glob
import numpy as np
from PIL import Image
import matplotlib.pyplot as plt #plt 用于显示图片
import torch
import torch.nn as nn
import torch.optim as optim
from torch.optim import lr_scheduler
from torch.utils.data import Dataset,DataLoader
import torchvision
import torchvision.models as model
from torchvision.transforms import ToPILImage
import torchvision.transforms as transforms
import os
os.environ['KMP_DUPLICATE_LIB_OK'] = 'True'# 1.1 实现load_data函数加载图片名称与标签的加载,并使用torch.utils.data接口将其封装成程序可用的数据集类OwnDataset。
def load_dir(directory,labstart=0): # 获取所有directory中的所有图与标签# 返回path指定的文件夹所包含的文件或文件名的名称列表strlabels = os.listdir(directory)# 对标签进行排序,以便训练和验证按照相同的顺序进行:在不同的操作系统中,加载文件夹的顺序可能不同。目录不同的情况会导致在不同的操作系统中,模型的标签出现串位的现象。所以需要对文件夹进行排序,保证其顺序的一致性。strlabels.sort()# 创建文件标签列表file_labels = []for i,label in enumerate(strlabels):print(label)jpg_names = glob.glob(os.path.join(directory,label,"*.jpg"))print(jpg_names)# 加入列表file_labels.extend(zip(jpg_names, [i + labstart] * len(jpg_names)))return file_labels,strlabelsdef load_data(dataset_path): # 定义函数load_data函数完成对数据集中图片文件名称和标签的加载。# 该函数可以实现两层文件夹的嵌套结构。其中,外层结构使用load_data函数进行遍历,内层结构使用load_dir函进行遍历。sub_dir = sorted(os.listdir(dataset_path)) # 跳过子文件夹:在不同的操作系统中,加载文件夹的顺序可能不同。目录不同的情况会导致在不同的操作系统中,模型的标签出现串位的现象。所以需要对文件夹进行排序,保证其顺序的一致性。start = 1 # 第0类是nonetfile_lables,tstrlabels = [],['none'] # 在制作标签时,人为地在前面添加了一个序号为0的none类。这是一个训练图文类模型的技巧,为了区分模型输出值是0和预测值是0这两种情况。for i in sub_dir:directory = os.path.join(dataset_path,i)if os.path.isdir(directory) == False: # 只处理文件夹中的数据print(directory)continuefile_labels,strlables = load_dir(directory,labstart=start)tfile_lables.extend(file_labels)tstrlabels.extend(strlables)start = len(strlables)# 将数据路径与标签解压缩,把数据路径和标签解压缩出来filenames,labels = zip(*tfile_lables)return filenames, labels, tstrlabels# 1.2 实现自定义数据集OwnDataset
def default_loader(path) : # 定义函数加载图片return Image.open(path).convert('RGB')class OwnDataset(Dataset): # 复用性较强,可根据自己的数据集略加修改使用# 在PyTorch中,提供了一个torch.utis.data接口,可以用来对数据集进行封装。在实现时,只需要继承torch.utis.data.Dataset类,并重载其__gettem__方法。# 在使用时,框架会向__gettem__方法传入索引index,在__gettem__方法内部根据指定index加载数据,并返回。def __init__(self,img_dir,labels,indexlist=None,transform=transforms.ToTensor(),loader=default_loader,cache=True): # 初始化self.labels = labels # 存放标签self.img_dir = img_dir # 样本图片文件名self.transform = transform # 预处理方法self.loader = loader # 加载方法self.cache = cache # 缓存标志if indexlist is None: # 要加载的数据序列self.indexlist = list(range(len(self.img_dir)))else:self.indexlist = indexlistself.data = [None] * len(self.indexlist) # 存放样本图片def __getitem__(self, idx): # 加载指定索引数据if self.data[idx] is None: # 第一次加载data = self.loader(self.img_dir[self.indexlist[idx]])if self.transform:data = self.transform(data)else:data = self.data[idx]if self.cache: # 保存到缓存里self.data[idx] = datareturn data,self.labels[self.indexlist[idx]]def __len__(self): # 计算数据集长度return len(self.indexlist)# 1.3 测试数据集:在完成数据集的制作之后,编写代码对其进行测试。
data_transform = { #定义数据的预处理方法'train':transforms.Compose([transforms.RandomResizedCrop(224),transforms.RandomHorizontalFlip(),transforms.ToTensor(),transforms.Normalize([0.485,0.456,0.406],[0.229,0.224,0.225])]),'val':transforms.Compose([transforms.Resize(256),transforms.CenterCrop(224),transforms.ToTensor(),transforms.Normalize([0.485,0.456,0.406],[0.229,0.224,0.225])]),
}
def Reduction_img(tensor,mean,std): #还原图片,实现了图片归一化的逆操作,显示数据集中的原始图片。dtype = tensor.dtypemean = torch.as_tensor(mean,dtype=dtype,device=tensor.device)std = torch.as_tensor(std,dtype=dtype,device=tensor.device)tensor.mul_(std[:,None,None]).add_(mean[:,None,None]) # 还原操作dataset_path = r'./data/cub200/' # 加载数据集路径
filenames,labels,classes = load_data(dataset_path) # 调用load_data函数对数据集中图片文件名称和标签进行加载,其返回对象classes中包含全部的类名。
# 打乱数据顺序
# 110-115行对数据文件列表的序号进行乱序划分,分为测试数据集和训练数集两个索引列表。该索引列表会传入OwnDataset类做成指定的数据集。
np.random.seed(0)
label_shuffle_index = np.random.permutation(len(labels))
label_train_num = (len(labels)//10) * 8 # 划分训练数据集和测试数据集
train_list = label_shuffle_index[0:label_train_num]
test_list = label_shuffle_index[label_train_num:] # 没带:train_dataset = OwnDataset(filenames,labels,train_list,data_transform['train'])# 实例化训练数据集
val_dataset = OwnDataset(filenames,labels,test_list,data_transform['val']) # 实例化测试数据集
# 实例化批次数据集:OwnDataset类所定义的数据集,其使用方法与PyTorch中的内置数据集的使用方法完全一致,配合DataLoader接口即可生成可以进行训练或测试的批次数据。具体代码如下。
train_loader = DataLoader(dataset=train_dataset,batch_size=32,shuffle=True)
val_loader = DataLoader(dataset=val_dataset,batch_size=32,shuffle=True)sample = iter(train_loader) # 获取一批次数据,进行测试
images,labels = sample.next()
print("样本形状",np.shape(images))
print("标签个数",len(classes))
mulimgs = torchvision.utils.make_grid(images[:10],nrow=10) # 拼接多张图片
Reduction_img(mulimgs,[0.485,0.456,0.406],[0.229,0.224,0.225])
_img = ToPILImage()(mulimgs) # 将张量转化为图片
plt.axis('off')
plt.imshow(_img) # 显示
plt.show()
print(','.join('%5s' % classes[labels[j]] for j in range(len(images[:10]))))# 1.4 获取并改造ResNet模型:获取ResNet模型,并加载预训练模型的权重。将其最后一层(输出层)去掉,换成一个全新的全连接层,该全连接层的输出节点数与本例分类数相同。
# 指定设备
device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
print(device)
# get_ResNet函数,获取预训练模型,可指定pretrained=True来实现自动下载预训练模型,也可指定loadfile来从本地路径加载预训练模型。
def get_ResNet(classes,pretrained=True,loadfile=None):ResNet = model.resnet101(pretrained) # 自动下载官方的预训练模型if loadfile != None:ResNet.load_state_dict(torch.load(loadfile)) # 加载本地模型# 将所有的参数层进行冻结:设置模型仅最后一层可以进行训练,使模型只针对最后一层进行微调。for param in ResNet.parameters():param.requires_grad = False# 输出全连接层的信息print(ResNet.fc)x = ResNet.fc.in_features # 获取全连接层的输入ResNet.fc = nn.Linear(x,len(classes)) # 定义一个新的全连接层print(ResNet.fc) # 最后输出新的模型return ResNet
ResNet = get_ResNet(classes) # 实例化模型
ResNet.to(device=device)# 1.5 定义损失函数、训练函数及测试函数,对模型的最后一层进行微调。
criterion = nn.CrossEntropyLoss()
# 指定新加的全连接层的学习率
optimizer = torch.optim.Adam([{'params':ResNet.fc.parameters()}],lr=0.01)
def train(model,device,train_loader,epoch,optimizer): # 定义训练函数model.train()allloss = []for batch_idx,data in enumerate(train_loader):x,y = datax = x.to(device)y = y.to(device)optimizer.zero_grad()y_hat = model(x)loss = criterion(y_hat,y)loss.backward()allloss.append(loss.item())optimizer.step()print('Train Epoch:{}\t Loss:{:.6f}'.format(epoch,np.mean(allloss))) # 输出训练结果def test(model,device,val_loader): # 定义测试函数model.eval()test_loss = []correct = []with torch.no_grad(): # 使模型在运行时不进行梯度跟踪,可以减少模型运行时对内存的占用。for i,data in enumerate(val_loader):x, y = datax = x.to(device)y = y.to(device)y_hat = model(x)test_loss.append(criterion(y_hat,y).item()) # 收集损失函数pred = y_hat.max(1,keepdim=True)[1] # 获取预测结果correct.append(pred.eq(y.view_as(pred)).sum().item()/pred.shape[0]) # 收集精确度print('\nTest:Average loss:{:,.4f},Accuracy:({:,.0f}%)\n'.format(np.mean(test_loss),np.mean(correct)*100)) # 输出测试结果# 迁移学习的两个步骤如下
if __name__ == '__main__':
# 迁移学习步骤①:固定预训练模型的特征提取部分,只对最后一层进行训练,使其快速收敛。firstmodepth = './data/cub200/firstmodepth_1.pth' # 定义模型文件的地址if os.path.exists(firstmodepth) == False:print("—————————固定预训练模型的特征提取部分,只对最后一层进行训练,使其快速收敛—————————")for epoch in range(1,2): # 迭代两次train(ResNet,device,train_loader,epoch,optimizer)test(ResNet,device,val_loader)# 保存模型torch.save(ResNet.state_dict(),firstmodepth)
# 1.6 使用退化学习率对模型进行全局微调
#迁移学习步骤②:使用较小的学习率,对全部模型进行训练,并对每层的权重进行细微的调节,即将模型的每层权重都设为可训练,并定义带有退化学习率的优化器。(1.6部分)secondmodepth = './data/cub200/firstmodepth_2.pth'optimizer2 = optim.SGD(ResNet.parameters(),lr=0.001,momentum=0.9) # 第198行代码定义带有退化学习率的SGD优化器。该优化器常用来对模型进行手动微调。有实验表明,使用经过手动调节的SGD优化器,在训练模型的后期效果优于Adam优化器。exp_lr_scheduler = lr_scheduler.StepLR(optimizer2,step_size=2,gamma=0.9) # 由于退化学习率会在训练过程中不断地变小,为了防止学习率过小,最终无法进行权重需要对其设置最小值。当学习率低于该值时,停止对退化学习率的操作。for param in ResNet.parameters(): # 所有参数设计为可训练param.requires_grad = Trueif os.path.exists(secondmodepth):ResNet.load_state_dict(torch.load(secondmodepth)) # 加载本地模型else:ResNet.load_state_dict(torch.load(firstmodepth)) # 加载本地模型print("____使用较小的学习率,对全部模型进行训练,定义带有退化学习率的优化器______")for epoch in range(1,100):train(ResNet,device,train_loader,epoch,optimizer2)if optimizer2.state_dict()['param_groups'][0]['lr'] > 0.00001:exp_lr_scheduler.step()print("___lr:",optimizer2.state_dict()['param_groups'][0]['lr'])test(ResNet,device,val_loader)# 保存模型torch.save(ResNet.state_dict(),secondmodepth)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/469266.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

linux工作队列

这里对Linux的工作队列(work_queue)不做深层次的挖掘,只对如何使用它以及一些简单的结构做简单地介绍。 Linux源代码(3.0.8)中和工作队列(work_queue)相关的结构主要在 include/linux/workqueue.h这个头文件中,这里就不摘抄了。这里就直接给出例子代码…

【Pytorch神经网络理论篇】 33 基于图片内容处理的机器视觉:目标检测+图片分割+非极大值抑制+Mask R-CNN模型

同学你好!本文章于2021年末编写,获得广泛的好评! 故在2022年末对本系列进行填充与更新,欢迎大家订阅最新的专栏,获取基于Pytorch1.10版本的理论代码(2023版)实现, Pytorch深度学习理论篇(2023版)目录地址…

linux inputuevent使用

input 输入子系统 在应用层使用的时候,容易出现找不到UEventObserver.java 这时候就要导入jar包 导入classes.jar这个jar包 weiqifaweiqifa-Inspiron-3847:~/weiqifa/tm100$ ls out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/ classes classes…

linq中给字段添加别名

linq 是我们在查询中经常回用到的一种形式,比如我们创建一个类,然后List<添加> 并绑定到表格中public class Modeltest{string id;public string Id{get { return id; }set { id value; }}string pwd;public string Pwd{get { return pwd; }set { pwd value; }}string…

报错 ValueError: too many values to unpack (expected 2)

enc_output,enc_slf_attn self.slf_attn(user_embedding,item_input,item_input,mask slf_attn_mask) 实际上只有一个返回值&#xff0c;但是却写了两个返回值&#xff0c;所以报错。 改正为 enc_output self.slf_attn(user_embedding,item_input,item_input,mask sl…

Python带*参数和带**参数

一、带*形参 1、格式&#xff1a;*形参名&#xff0c;如*args 2、数据类型&#xff1a;元组 3、传参方式&#xff1a;接收任意个位置参数&#xff08;可以不传参&#xff09;。 4、位置&#xff1a;在一个函数里只能有一个&#xff0c;且放在末尾&#xff08;没有带**形参的…

IE浏览器解决无法识别js中getElementsByClassName问题

关于ie浏览器无法识别js中getElementsByClassName问题&#xff0c;现通过以下方法&#xff0c;引用如下js /***打印js对象详细信息*/ function alertObj(obj) {var description "";for ( var i in obj){var property obj[i];description i " " prope…

arduino i2c 如何写16位寄存器_树莓派3B开发Go语言(二)寄存器版本GPIO

作者&#xff1a;爪爪熊链接&#xff1a;https://www.jianshu.com/p/0495c0554a63來源&#xff1a;简书之前将go语言的运行环境给搭建起来了&#xff0c;但是没有开始真正的试试Go 语言操作树莓派硬件的效果。一、树莓派3B硬件介绍树莓派3B采用了博通的BCM2837方案&#xff0c;…

Android aidl在Framework的使用

为何要做这个 我要在framework的PhoneWindowManager.java里面调用LightService.java里面的函数&#xff0c;用来做灯光的提示之类的&#xff0c;为何我要在PhoneWindowManager.java里面加这个呢&#xff0c;这里就不做讨论了&#xff0c;但是直接调用哪些接口是不行的&#xf…

没有理智的欲望会走向毁灭,没有欲望的理智会永守清贫

欲望是人类的本性,哥伦布因为它片帆浮海、横渡大洋发现了美州。理性是人类的禀赋,哥白尼因为它仰望星空、冥想终生提出了日心说。在金融市场上没有理智的欲望会走向毁灭,没有欲望的理智会永守清贫。转载于:https://www.cnblogs.com/timlong/p/6509870.html

【Pytorch神经网络实战案例】25 (带数据增强)基于迁移学习识别多种鸟类(CUB-200数据集)

1 数据增强 在目前分类效果最好的EficientNet系列模型中&#xff0c;EfficientNet-B7版本的模型就是使用随机数据增强方法训练而成的。 RandAugment方法也是目前主流的数据增强方法&#xff0c;用RandAugment方法进行训练&#xff0c;会使模型的精度得到提升。 2 RandAugment…

Capture images using V4L2 on Linux

这文章相当好&#xff0c;没有理由不转载 I have always been using OpenCV’s VideoCapture API to capture images from webcam or USB cameras. OpenCV supportsV4L2 and I wanted to use something other than OpenCV’s VideoCapture API so I started digging up about …

diskgenius 数据迁移_U盘格式化后数据恢复免费方法教程

U盘里的数据一般都很重要&#xff0c;比如论文或者办公文件&#xff0c;而有时候我们会被病毒或者误操作把U盘给格式化了&#xff0c;这时候要怎么恢复U盘里的数据呢&#xff0c;只有一个办法&#xff0c;就是用U盘数据恢复软件&#xff0c;但网上此类软件虽然很多&#xff0c;…

结对编程1

Deadline&#xff1a; 2017-3-15 12:00AM&#xff0c;以博客发表日期为准。 评分基准: 按时交 - 有分&#xff0c;检查的项目包括后文的三个方面 题目要求代码提交博文规范晚交 - 0分迟交两周以上 - 倒扣本次作业分数抄袭 - 倒扣本次作业分数题目描述&#xff1a; 不知道大家是…

【Pytorch神经网络理论篇】 34 样本均衡+分类模型常见损失函数

同学你好&#xff01;本文章于2021年末编写&#xff0c;获得广泛的好评&#xff01; 故在2022年末对本系列进行填充与更新&#xff0c;欢迎大家订阅最新的专栏&#xff0c;获取基于Pytorch1.10版本的理论代码(2023版)实现&#xff0c; Pytorch深度学习理论篇(2023版)目录地址…

我的2015年

2015年的收获 1、结婚了&#xff0c;这是很开心的一件事情&#xff0c;从刚开始的吵吵闹闹&#xff0c;到现在的结婚成家&#xff0c;自己的责任也增加了许多。 2、老婆怀了宝宝&#xff0c;这件事跟结婚的喜悦是一样的&#xff0c;从开始到现在&#xff0c;很多人都在问我&a…

安卓 camera 调用流程_安卓如何做出微信那样的界面仿微信“我”的界面1/5

本系列目标通过安卓编程仿写微信“我”的界面,让大家也能做出类似微信界面.效果图如下:本文目标做出页面顶部的相机部分(其他部分在后续文章中逐步分享).效果图如下:实现方案通过截图工具或者下载一张照相机照片,放到工程的src/main/res/drawable目录下,命名为camera.png添加一…

【Pytorch神经网络实战案例】26 MaskR-CNN内置模型实现目标检测

1 Pytorch中的目标检测内置模型 在torchvision库下的modelsldetecton目录中&#xff0c;找到__int__.py文件。该文件中存放着可以导出的PyTorch内置的目标检测模型。 2 MaskR-CNN内置模型实现目标检测 2.1 代码逻辑简述 将COCO2017数据集上的预训练模型maskrcnm_resnet50_fp…

MTK平台Android4.4 拍照默认图片格式修改

因为摄像头效果要调试&#xff0c;需要把摄像头拍照的照片格式修改了 晚上看了一下资料&#xff0c;这个链接&#xff0c;这个链接比较有用 http://www.cnblogs.com/peterzd/archive/2012/10/11/2695640.html 里面有一段话这样写&#xff1a; ** Environment.getExternalS…

法与时应,度与情合

不能制定脱离实际的法度&#xff0c;否则就是恶法&#xff0c;不可操作&#xff0c;事与愿违。转载于:https://www.cnblogs.com/jcode/p/6514698.html