【深度学习】(11)--迁移学习
文章目录
- 迁移学习
- 一、迁移学习步骤
- 二、以残差网络为例
- 1. 导入模型
- 2. 冻结参数
- 3. 修改全连接层
- 4. 创建数据集的类
- 5. 处理数据
- 6. 装配设备
- 7. 建立模型
- 8. 训练模型
- 三、完整代码展示
- 总结
迁移学习
迁移学习是指利用已经训练好的模型,在新的任务上进行微调。迁移学习可以加快模型训练速度,提高模型性能,并且在数据稀缺的情况下也能很好地工作。
一、迁移学习步骤
- 选择预训练的模型和适当的层:通常,我们会选择在大规模图像数据集(如ImageNet)上预训练的模型,如VGG、ResNet等。然后,根据新数据集的特点,选择需要微调的模型层。对于低级特征的任务(如边缘检测),最好使用浅层模型的层,而对于高级特征的任务(如分类),则应选择更深层次的模型。
- 冻结预训练模型的参数:保持预训练模型的权重不变,只训练新增加的层或者微调一些层,避免因为在数据集中过拟合导致预训练模型过度拟合。
- 在新数据集上训练新增加的层:在冻结预训练模型的参数情况下,训练新增加的层。这样,可以使新模型适应新的任务,从而获得更高的性能。
- 微调预训练模型的层:在新层上进行训练后,可以解冻一些已经训练过的层,并且将它们作为微调的目标。这样做可以提高模型在新数据集上的性能。
- 评估和测试:在训练完成之后,使用测试集对模型进行评估。如果模型的性能仍然不够好,可以尝试调整超参数或者更改微调层。
二、以残差网络为例
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)
总结
本篇介绍了:
- 如何进行迁移学习
- 对迁移模型进行微调:
- 微调全连接层
- 微调卷积层(本篇未写),原理相同,可自行尝试
- 注意:原本的模型参数务必要冻结住,那是已经调好的,可以节省计算时间。仅需要调整修改部分的参数。