持续学习动态架构算法LwF(Learning without Forgetting )解读总结与代码注释

本文主要是介绍持续学习动态架构算法LwF(Learning without Forgetting )解读总结与代码注释,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

0.持续学习

  • 持续学习相关文章汇总,包含论文地址、代码地址、具体分析解读地址

1.LwF算法相关链接

  • 论文地址
  • 代码地址

2.基本想法

  • 针对问题:在无法获得原始任务训练数据的情况下,适合使视觉系统适应新任务,并且保证其在旧任务上的性能
  • 问题建模:学习对新任务具有判别能力的参数,同时保留训练数据上原始任务的输出
  • 将网络分为所有任务共享部分和特定任务独享部分,网络架构如下:
    图片

3.损失函数

  • 待学习参数有三种:共享部分参数、旧任务们的独享参数、新任务独享参数
  • 由三部分组成:旧任务损失、新任务损失、正则化项
  • 旧任务损失:增长后的网络的输出与增长前的输出尽可能相同,采用知识蒸馏损失,类似交叉熵损失,只不过加大了较小概率的惩罚权重(其中关键参数T,要大于1来加大小概率的权重,文中通过网格搜索将其定位2)
  • 新任务损失:对于新任务的预测与真实值尽可能相同,使用交叉熵损失或者NLL损失
  • 正则化项:限制网络中所有参数,权重0.0005
  • 新旧任务权衡:在新任务损失前面有一个系数来表示对新旧任务性能的权衡,文中取1,参数越大,在新任务上的性能越好,在旧任务上的性能越差。通过改变该参数可以获得新旧任务性能曲线。

4.训练流程

  • 热身阶段(warm-up step):冻结共享部分参数、旧任务们的独享参数,单独训练新任务独享参数
  • 联合优化阶段(joint-optimize step):优化所有参数

5.特点

  • 与传统联合调优方法相比:无需存储旧任务的数据,新任务只需要通过一次共享层便可以用来进行旧任务和新任务的更新,却具有了联合调优的优点。但因为不同任务的分布会不相同,所以文中的方法效果会不如传统联合调优,传统联合调优的效果可以视为本文方法的上限。
  • 效率分析
    • 最慢:共享参数的正反向传播
    • 最快:特征提取层,因为只需要训练新任务的参数
    • 与传统微调相比:多了一步旧任务的独享参数更新,效率稍微低一点
    • 与传统联合调优相比:新旧任务共享的参数只需要进行一次前后向传播,效率更高

6.具体细节

  • 使用动量0.9的随机梯度下降
  • 在全连接层使用了dropout
  • 用旧任务的信息对新任务进行归一化
  • 数据增强:
    • 5X5的网格上对调整过大小的图像进行随机的固定尺寸裁剪
    • 随机镜像裁剪
    • RGB值上添加方差
  • 使用Xavier初始化新任务独享参数
  • 学习率是原网络学习率的0.1-0.02倍
  • 由于任务独享的特征提取部分参数量少,所以使用5倍学习率
  • 对于学习速度相似的方法,使用相同的训练epoch来进行公平比较
  • 有时为了防止过拟合、提升学习速度,会接近平稳在的时候将学习率变为0.1倍
  • 为了公平比较,将热身阶段后的共享网络作为联合训练和微调训练的起始点

7.实验

  • 添加单个新任务
  • 添加多个新任务
  • 数据集大小的影响
  • 网络设计的影响
  • 不同损失
  • 扩展网络结构的效用
  • 小学习率微调来保证旧任务的影响
  • 改变任务专属部分的网络层数

8.结论

  • 对于增长节点式的任务专属网络,其性能与原本的LwF性能相近,但是计算开销却大很多
  • 仅仅降低共享网络的学习率对保留旧任务性能的帮助并不大,但却会很大程度影响新任务
  • 用网络输出的变化来现在旧任务的变化要优于用网络参数的变化来衡量,因为网络参数一点小小的改变就可能引起输出巨大的改变
  • 知识蒸馏损失略优于L1、L2、交叉熵损失,但优势很小
  • 训练速度优于联合优化,对新任务的性能优于微调
  • 本文针对旧任务的损失对旧任务性能上的表现更可解释

9.未来工作

  • 应用到图像分类、跟踪等更多领域:分割、检测、视觉外的任务
  • 探索根据任务分布针对性地保留一些过去的任务数据和输出(由于是面对重尾分布)

10.代码解读

  • 参考文章
  • 含有备注的model.py
import torch
torch.backends.cudnn.benchmark=True
import torch.nn as nn
import torch.nn.functional as F
import torch.optim as optim
from torch.autograd import Variable
import numpy as np
from PIL import Image
from tqdm import tqdm
import time
import copyimport torchvision.models as models
import torchvision.transforms as transformsdef MultiClassCrossEntropy(logits, labels, T):# Ld = -1/N * sum(N) sum(C) softmax(label) * log(softmax(logit))labels = Variable(labels.data, requires_grad=False).cuda()outputs = torch.log_softmax(logits/T, dim=1)   # compute the log of softmax valueslabels = torch.softmax(labels/T, dim=1)# print('outputs: ', outputs)# print('labels: ', labels.shape)outputs = torch.sum(outputs * labels, dim=1, keepdim=False)outputs = -torch.mean(outputs, dim=0, keepdim=False)# print('OUT: ', outputs)return Variable(outputs.data, requires_grad=True).cuda()def kaiming_normal_init(m):if isinstance(m, nn.Conv2d):#判断m是不是nn.Conv2d的类型或子类nn.init.kaiming_normal_(m.weight, nonlinearity='relu')#一种初始化方法,要指明激活函数,保证输出有一定方差https://zhuanlan.zhihu.com/p/536483424elif isinstance(m, nn.Linear):nn.init.kaiming_normal_(m.weight, nonlinearity='sigmoid')class Model(nn.Module):'''分为超参数、网络架构、类增加三个部分前向传播里没有softmax'''def __init__(self, classes, classes_map, args):# Hyper Parametersself.init_lr = args.init_lrself.num_epochs = args.num_epochsself.batch_size = args.batch_sizeself.lower_rate_epoch = [int(0.7 * self.num_epochs), int(0.9 * self.num_epochs)] #hardcoded decay scheduleself.lr_dec_factor = 10self.pretrained = Falseself.momentum = 0.9self.weight_decay = 0.0001# Constant to provide numerical stability while normalizingself.epsilon = 1e-16# Network architecturesuper(Model, self).__init__()self.model = models.resnet34(pretrained=self.pretrained)self.model.apply(kaiming_normal_init)"""独享层:一层全连接层,与classes数量有关,且没有偏置"""num_features = self.model.fc.in_featuresself.model.fc = nn.Linear(num_features, classes, bias=False)self.fc = self.model.fc'''共享层:resnet34除去最后一层'''#nn.Sequential按序列构建模型https://blog.csdn.net/hxxjxw/article/details/106231242#.children()返回模型的最外层,与.model()的区别类似于attend和extendself.feature_extractor = nn.Sequential(*list(self.model.children())[:-1])#*用于迭代地取出list中的内容#用nn.DataParallel包装模型,可以在多GPU上运行https://zhuanlan.zhihu.com/p/647169457self.feature_extractor = nn.DataParallel(self.feature_extractor) # n_classes is incremented(递增) before processing new data in an iteration# n_known is set to n_classes after all data for an iteration has been processed数据处理完后n_known设为n_classesself.n_classes = 0self.n_known = 0self.classes_map = classes_mapdef forward(self, x):x = self.feature_extractor(x)x = x.view(x.size(0), -1)x = self.fc(x)return xdef increment_classes(self, new_classes):"""Add n classes in the final fc layer"""n = len(new_classes)print('new classes: ', n)in_features = self.fc.in_featuresout_features = self.fc.out_featuresweight = self.fc.weight.data#保存旧任务的网络权重if self.n_known == 0:new_out_features = nelse:new_out_features = out_features + nprint('new out features: ', new_out_features)self.model.fc = nn.Linear(in_features, new_out_features, bias=False)self.fc = self.model.fckaiming_normal_init(self.fc.weight)#所有任务网络统一初始化self.fc.weight.data[:out_features] = weight#还原旧任务网络权重self.n_classes += ndef classify(self, images):"""Classify images by softmaxArgs:x: input image batchReturns:preds: Tensor of size (batch_size,)"""_, preds = torch.max(torch.softmax(self.forward(images), dim=1), dim=1, keepdim=False)return predsdef update(self, dataset, class_map, args):self.compute_means = True# Save a copy to compute distillation outputs保存旧网络来计算旧任务原始输出prev_model = copy.deepcopy(self)prev_model.cuda()classes = list(set(dataset.train_labels))#print("Classes: ", classes)print('Known: ', self.n_known)if self.n_classes == 1 and self.n_known == 0:#self.n_classes初始值是1不是0吗?!new_classes = [classes[i] for i in range(1,len(classes))]else:new_classes = [cl for cl in classes if class_map[cl] >= self.n_known]#有新任务就动态调整网络if len(new_classes) > 0:self.increment_classes(new_classes)self.cuda()loader = torch.utils.data.DataLoader(dataset, batch_size=self.batch_size,shuffle=True, num_workers=12)print("Batch Size (for n_classes classes) : ", len(dataset))optimizer = optim.SGD(self.parameters(), lr=self.init_lr, momentum = self.momentum, weight_decay=self.weight_decay)with tqdm(total=self.num_epochs) as pbar:for epoch in range(self.num_epochs):# Modify learning rate# if (epoch+1) in lower_rate_epoch:# 	self.lr = self.lr * 1.0/lr_dec_factor# 	for param_group in optimizer.param_groups:# 		param_group['lr'] = self.lrfor i, (indices, images, labels) in enumerate(loader):seen_labels = []images = Variable(torch.FloatTensor(images)).cuda()seen_labels = torch.LongTensor([class_map[label] for label in labels.numpy()])labels = Variable(seen_labels).cuda()# indices = indices.cuda()optimizer.zero_grad()logits = self.forward(images)cls_loss = nn.CrossEntropyLoss()(logits, labels)if self.n_classes//len(new_classes) > 1:dist_target = prev_model.forward(images)logits_dist = logits[:,:-(self.n_classes-self.n_known)]dist_loss = MultiClassCrossEntropy(logits_dist, dist_target, 2)loss = dist_loss+cls_losselse:loss = cls_lossloss.backward()optimizer.step()if (i+1) % 1 == 0:tqdm.write('Epoch [%d/%d], Iter [%d/%d] Loss: %.4f' %(epoch+1, self.num_epochs, i+1, np.ceil(len(dataset)/self.batch_size), loss.data))pbar.update(1)

这篇关于持续学习动态架构算法LwF(Learning without Forgetting )解读总结与代码注释的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/481975

相关文章

在Java中实现线程之间的数据共享的几种方式总结

《在Java中实现线程之间的数据共享的几种方式总结》在Java中实现线程间数据共享是并发编程的核心需求,但需要谨慎处理同步问题以避免竞态条件,本文通过代码示例给大家介绍了几种主要实现方式及其最佳实践,... 目录1. 共享变量与同步机制2. 轻量级通信机制3. 线程安全容器4. 线程局部变量(ThreadL

Python标准库datetime模块日期和时间数据类型解读

《Python标准库datetime模块日期和时间数据类型解读》文章介绍Python中datetime模块的date、time、datetime类,用于处理日期、时间及日期时间结合体,通过属性获取时间... 目录Datetime常用类日期date类型使用时间 time 类型使用日期和时间的结合体–日期时间(

python使用Akshare与Streamlit实现股票估值分析教程(图文代码)

《python使用Akshare与Streamlit实现股票估值分析教程(图文代码)》入职测试中的一道题,要求:从Akshare下载某一个股票近十年的财务报表包括,资产负债表,利润表,现金流量表,保存... 目录一、前言二、核心知识点梳理1、Akshare数据获取2、Pandas数据处理3、Matplotl

Django开发时如何避免频繁发送短信验证码(python图文代码)

《Django开发时如何避免频繁发送短信验证码(python图文代码)》Django开发时,为防止频繁发送验证码,后端需用Redis限制请求频率,结合管道技术提升效率,通过生产者消费者模式解耦业务逻辑... 目录避免频繁发送 验证码1. www.chinasem.cn避免频繁发送 验证码逻辑分析2. 避免频繁

精选20个好玩又实用的的Python实战项目(有图文代码)

《精选20个好玩又实用的的Python实战项目(有图文代码)》文章介绍了20个实用Python项目,涵盖游戏开发、工具应用、图像处理、机器学习等,使用Tkinter、PIL、OpenCV、Kivy等库... 目录① 猜字游戏② 闹钟③ 骰子模拟器④ 二维码⑤ 语言检测⑥ 加密和解密⑦ URL缩短⑧ 音乐播放

Python使用Tenacity一行代码实现自动重试详解

《Python使用Tenacity一行代码实现自动重试详解》tenacity是一个专为Python设计的通用重试库,它的核心理念就是用简单、清晰的方式,为任何可能失败的操作添加重试能力,下面我们就来看... 目录一切始于一个简单的 API 调用Tenacity 入门:一行代码实现优雅重试精细控制:让重试按我

C语言中%zu的用法解读

《C语言中%zu的用法解读》size_t是无符号整数类型,用于表示对象大小或内存操作结果,%zu是C99标准中专为size_t设计的printf占位符,避免因类型不匹配导致错误,使用%u或%d可能引发... 目录size_t 类型与 %zu 占位符%zu 的用途替代占位符的风险兼容性说明其他相关占位符验证示

Linux系统之lvcreate命令使用解读

《Linux系统之lvcreate命令使用解读》lvcreate是LVM中创建逻辑卷的核心命令,支持线性、条带化、RAID、镜像、快照、瘦池和缓存池等多种类型,实现灵活存储资源管理,需注意空间分配、R... 目录lvcreate命令详解一、命令概述二、语法格式三、核心功能四、选项详解五、使用示例1. 创建逻

go动态限制并发数量的实现示例

《go动态限制并发数量的实现示例》本文主要介绍了Go并发控制方法,通过带缓冲通道和第三方库实现并发数量限制,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面... 目录带有缓冲大小的通道使用第三方库其他控制并发的方法因为go从语言层面支持并发,所以面试百分百会问到

Spring Boot 与微服务入门实战详细总结

《SpringBoot与微服务入门实战详细总结》本文讲解SpringBoot框架的核心特性如快速构建、自动配置、零XML与微服务架构的定义、演进及优缺点,涵盖开发环境准备和HelloWorld实战... 目录一、Spring Boot 核心概述二、微服务架构详解1. 微服务的定义与演进2. 微服务的优缺点三