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

本文涉及的产品
函数计算FC,每月15万CU 3个月
简介: 在目前分类效果最好的EficientNet系列模型中,EfficientNet-B7版本的模型就是使用随机数据增强方法训练而成的。

103b746101d146cd93357daedb91d512.png


1 数据增强


在目前分类效果最好的EficientNet系列模型中,EfficientNet-B7版本的模型就是使用随机数据增强方法训练而成的。


RandAugment方法也是目前主流的数据增强方法,用RandAugment方法进行训练,会使模型的精度得到提升。


2 RandAugment


2.1 RandAugment方法简介


RandAugment方法是一种新的数据增强方法,它比自动数据增强(AutOAugment)方法更简单、更好用。它可以在原有的训练框架中,直接对AutoAugment方法进行替换。


2.1.1 Tip


AuoAugment方法包含30多个参数,可以对图片数据进行各种变换(参见arXiv网站上编号为1805.09501的论文)。


2.2 RandAugment方法的构成


RandAugment方法是在AutoAugment方法的基础之上,将30多个参数进行策略级的优化管理,使这30多个参数被简化成两个参数:图片的N次变换和每次变换的强度M。其中每次变换的强度M,取值为0~10(只取整数),表示使原有图片增强失真的大小。


RandAugment方法以结果为导向,使数据增强过程更加面向用。在减少AutoAugment的运算消耗的同时,又使增强的效果变得可控。详细内容可以参考相关论文(参见arXⅳ网站上编号为1909.13719的论文)。


2.2 代码获取


https://github.com/heartInsert/randaugment
# 只有一个代码文件Rand_Augment,py,将其下载后,直接引入代码即可使用。


3 本节案例(带有数据增强的识别)


3.1 案例简介


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


3.2 代码实现:load_data函数加载图片名称与标签的加载----Transfer_bird2_Augmentation.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,strlabels
def load_data(dataset_path): # 定义函数load_data函数完成对数据集中图片文件名称和标签的加载。
    # 该函数可以实现两层文件夹的嵌套结构。其中,外层结构使用load_data函数进行遍历,内层结构使用load_dir函进行遍历。
    sub_dir = sorted(os.listdir(dataset_path)) # 跳过子文件夹:在不同的操作系统中,加载文件夹的顺序可能不同。目录不同的情况会导致在不同的操作系统中,模型的标签出现串位的现象。所以需要对文件夹进行排序,保证其顺序的一致性。
    start = 1 # 第0类是none
    tfile_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)
            continue
        file_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_bird2_Augmentation.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 = indexlist
        self.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] = data
        return data,self.labels[self.indexlist[idx]]
    def __len__(self): # 计算数据集长度
        return len(self.indexlist)


3.4 代码实战:测试数据集----Transfer_bird2_Augmentation.py(第3部分)【数据增强模块】


# 1.3 测试数据集:在完成数据集的制作之后,编写代码对其进行测试。
# 数据增强模块
from Rand_Augment import  Rand_Augment
data_transform = { #定义数据的预处理方法
    'train':transforms.Compose([
        Rand_Augment(), # 数据增强的方法带入 仅此一处修改
        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.dtype
    mean = 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个图片


16be4204dcf248db87af3d7c4755ab54.png


801f8945ed9946f8bafc55ecbb3e9e9d.png


3.5 代码实战:获取并改造ResNet模型----Transfer_bird2_Augmentation.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_bird2_Augmentation.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 = data
        x = 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 = data
            x = 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_bird2_Augmentation.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 = True
    if 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_bird2_Augmentation.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,strlabels
def load_data(dataset_path): # 定义函数load_data函数完成对数据集中图片文件名称和标签的加载。
    # 该函数可以实现两层文件夹的嵌套结构。其中,外层结构使用load_data函数进行遍历,内层结构使用load_dir函进行遍历。
    sub_dir = sorted(os.listdir(dataset_path)) # 跳过子文件夹:在不同的操作系统中,加载文件夹的顺序可能不同。目录不同的情况会导致在不同的操作系统中,模型的标签出现串位的现象。所以需要对文件夹进行排序,保证其顺序的一致性。
    start = 1 # 第0类是none
    tfile_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)
            continue
        file_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 = indexlist
        self.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] = data
        return data,self.labels[self.indexlist[idx]]
    def __len__(self): # 计算数据集长度
        return len(self.indexlist)
# 1.3 测试数据集:在完成数据集的制作之后,编写代码对其进行测试。
# 数据增强模块
from Rand_Augment import  Rand_Augment
data_transform = { #定义数据的预处理方法
    'train':transforms.Compose([
        Rand_Augment(), # 数据增强的方法带入 仅此一处修改
        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.dtype
    mean = 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 = data
        x = 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 = data
            x = 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 = True
    if 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)
相关实践学习
【文生图】一键部署Stable Diffusion基于函数计算
本实验教你如何在函数计算FC上从零开始部署Stable Diffusion来进行AI绘画创作,开启AIGC盲盒。函数计算提供一定的免费额度供用户使用。本实验答疑钉钉群:29290019867
建立 Serverless 思维
本课程包括: Serverless 应用引擎的概念, 为开发者带来的实际价值, 以及让您了解常见的 Serverless 架构模式
目录
相关文章
|
1月前
|
机器学习/深度学习 数据可视化 测试技术
YOLO11实战:新颖的多尺度卷积注意力(MSCA)加在网络不同位置的涨点情况 | 创新点如何在自己数据集上高效涨点,解决不涨点掉点等问题
本文探讨了创新点在自定义数据集上表现不稳定的问题,分析了不同数据集和网络位置对创新效果的影响。通过在YOLO11的不同位置引入MSCAAttention模块,展示了三种不同的改进方案及其效果。实验结果显示,改进方案在mAP50指标上分别提升了至0.788、0.792和0.775。建议多尝试不同配置,找到最适合特定数据集的解决方案。
348 0
|
3月前
|
机器学习/深度学习 PyTorch 算法框架/工具
PyTorch 中的动态计算图:实现灵活的神经网络架构
【8月更文第27天】PyTorch 是一款流行的深度学习框架,它以其灵活性和易用性而闻名。与 TensorFlow 等其他框架相比,PyTorch 最大的特点之一是支持动态计算图。这意味着开发者可以在运行时定义网络结构,这为构建复杂的模型提供了极大的便利。本文将深入探讨 PyTorch 中动态计算图的工作原理,并通过一些示例代码展示如何利用这一特性来构建灵活的神经网络架构。
283 1
|
3月前
|
机器学习/深度学习 人工智能 PyTorch
【深度学习】使用PyTorch构建神经网络:深度学习实战指南
PyTorch是一个开源的Python机器学习库,特别专注于深度学习领域。它由Facebook的AI研究团队开发并维护,因其灵活的架构、动态计算图以及在科研和工业界的广泛支持而受到青睐。PyTorch提供了强大的GPU加速能力,使得在处理大规模数据集和复杂模型时效率极高。
193 59
|
2月前
|
机器学习/深度学习
小土堆-pytorch-神经网络-损失函数与反向传播_笔记
在使用损失函数时,关键在于匹配输入和输出形状。例如,在L1Loss中,输入形状中的N代表批量大小。以下是具体示例:对于相同形状的输入和目标张量,L1Loss默认计算差值并求平均;此外,均方误差(MSE)也是常用损失函数。实战中,损失函数用于计算模型输出与真实标签间的差距,并通过反向传播更新模型参数。
|
2月前
|
机器学习/深度学习 数据采集 数据可视化
深度学习实践:构建并训练卷积神经网络(CNN)对CIFAR-10数据集进行分类
本文详细介绍如何使用PyTorch构建并训练卷积神经网络(CNN)对CIFAR-10数据集进行图像分类。从数据预处理、模型定义到训练过程及结果可视化,文章全面展示了深度学习项目的全流程。通过实际操作,读者可以深入了解CNN在图像分类任务中的应用,并掌握PyTorch的基本使用方法。希望本文为您的深度学习项目提供有价值的参考与启示。
|
3月前
|
机器学习/深度学习 安全 网络协议
网络安全公开数据集Maple-IDS,恶意流量检测数据集开放使用!
【8月更文挑战第29天】Maple-IDS 是东北林业大学网络安全实验室发布的网络入侵检测评估数据集,旨在提升异常基础入侵检测和预防系统的性能与可靠性。该数据集包含多种最新攻击类型,如 DDoS 和 N-day 漏洞,覆盖多种服务和网络行为,兼容 CIC-IDS 格式,便于直接使用或生成 csv 文件,适用于多种现代协议。
151 0
|
3月前
|
机器学习/深度学习 PyTorch 测试技术
深度学习入门:使用 PyTorch 构建和训练你的第一个神经网络
【8月更文第29天】深度学习是机器学习的一个分支,它利用多层非线性处理单元(即神经网络)来解决复杂的模式识别问题。PyTorch 是一个强大的深度学习框架,它提供了灵活的 API 和动态计算图,非常适合初学者和研究者使用。
54 0
|
2天前
|
安全 网络安全 数据安全/隐私保护
网络安全与信息安全:关于网络安全漏洞、加密技术、安全意识等方面的知识分享
在数字化时代,网络安全和信息安全已成为我们生活中不可或缺的一部分。本文将介绍网络安全漏洞、加密技术和安全意识等方面的知识,并提供一些实用的技巧和建议,帮助读者更好地保护自己的网络安全和信息安全。
|
2天前
|
安全 算法 网络安全
网络安全与信息安全:关于网络安全漏洞、加密技术、安全意识等方面的知识分享
在当今数字化时代,网络安全和信息安全已经成为了全球关注的焦点。随着技术的发展,网络攻击手段日益狡猾,而防范措施也必须不断更新以应对新的挑战。本文将深入探讨网络安全的常见漏洞,介绍加密技术的基本概念和应用,并强调培养良好安全意识的重要性。通过这些知识的分享,旨在提升公众对网络安全的认识,共同构建更加安全的网络环境。
|
4天前
|
安全 算法 网络协议
网络安全与信息安全:关于网络安全漏洞、加密技术、安全意识等方面的知识分享
在数字时代,网络安全和信息安全已经成为了我们生活中不可或缺的一部分。本文将介绍网络安全漏洞、加密技术和安全意识等方面的内容,帮助读者更好地了解网络安全的重要性和应对措施。通过阅读本文,您将了解到网络安全的基本概念、常见的网络安全漏洞、加密技术的原理和应用以及如何提高个人和组织的网络安全意识。
下一篇
无影云桌面