目录
源码(觉得有用请点star,这对我很重要~)
首先,我们需要说明的是maml不同于常见的训练方式。以猫狗分类和resnet作为例子,我们将猫狗分类定义为一个task,正常训练一个猫狗分类器,只需要输入猫和狗的图片去训练就好了。所以我们的一个batch中就会有多张猫或者狗的图片,这样训练出来的模型虽说可以预测这张图片是猫还是狗,但要想这个分类器有泛化性,就需要大量猫或狗的图像,而标注大量的数据是要成本的。
现在我们假设一个场景,我们没有这么多猫狗分类的数据,但我们有其他task的数据。我们需要用少量的图像来训练一个强泛化性的模型。maml的训练方式允许我们用大量别的task的数据来得到一个初始化权重,这个初始化权重具有非常好的鲁棒性,仅用少量数据训练加上或者maml训练的初始化权重就可以达到和正常训练方式的准确率。
为什么maml能做到这样的效果,请读者移步MAML原理讲解和代码实现。
maml以task为单位,多个task组成一个batch,为了和正常训练方式区别开来,我们就成为meta-batch。以omniglot为例,如下图所示:

每个task之间都互相独立,都是不同的分类任务。
这里为大家实现了个MAML数据读取的基类,用户只需要实现get_file_list和get_one_task_data两个函数即可。
class MAMLDataset(Dataset):
def __init__(self, data_path, batch_size, n_way=10, k_shot=2, q_query=1):
self.file_list = self.get_file_list(data_path)
self.batch_size = batch_size
self.n_way = n_way
self.k_shot = k_shot
self.q_query = q_query
def get_file_list(self, data_path):
raise NotImplementedError('get_file_list function not implemented!')
def get_one_task_data(self):
raise NotImplementedError('get_one_task_data function not implemented!')
def __len__(self):
return len(self.file_list) // self.batch_size
def __getitem__(self, index):
return self.get_one_task_data()
还是以omniglot为例,实现特殊数据集的子类数据读取的方法。
此函数要求得到一个所有task文件目录的list。比如一个总的文件夹中,下面有很多不同的task,这里因为omniglot数据命名比较统一,所以实现比较简单。
此函数要求返回一个task的数据,包括训练集和验证集,以下面代码为例,每次调用get_one_task_data时,返回一个n_way=5分类的task,其中训练集图像和标签的数量各为k_shot=1张,验证集图像和标签的数量各为q_query=1张。
class OmniglotDataset(MAMLDataset):
def get_file_list(self, data_path):
"""
Get all fonts list.
Args:
data_path: Omniglot Data path
Returns: fonts list
"""
return [f for f in glob.glob(data_path + "**/character*", recursive=True)]
def get_one_task_data(self):
"""
Get ones task maml data, include one batch support images and labels, one batch query images and labels.
Returns: support_data, query_data
"""
img_dirs = random.sample(self.file_list, self.n_way)
support_data = []
query_data = []
support_image = []
support_label = []
query_image = []
query_label = []
for label, img_dir in enumerate(img_dirs):
img_list = [f for f in glob.glob(img_dir + "**/*.png", recursive=True)]
images = random.sample(img_list, self.k_shot + self.q_query)
# Read support set
for img_path in images[:self.k_shot]:
image = Image.open(img_path)
image = np.array(image)
image = np.expand_dims(image / 255., axis=0)
support_data.append((image, label))
# Read query set
for img_path in images[self.k_shot:]:
image = Image.open(img_path)
image = np.array(image)
image = np.expand_dims(image / 255., axis=0)
query_data.append((image, label))
# shuffle support set
random.shuffle(support_data)
for data in support_data:
support_image.append(data[0])
support_label.append(data[1])
# shuffle query set
random.shuffle(query_data)
for data in query_data:
query_image.append(data[0])
query_label.append(data[1])
return np.array(support_image), np.array(support_label), np.array(query_image), np.array(query_label)
在调用Dataset的时候再使用torch的Dataloader包一下就好了,里面batch_size参数为任务的数量。相当于每训练1个step就要训练完这么多个task。
train_dataset = OmniglotDataset(args.train_data_dir, args.task_num,
n_way=args.n_way, k_shot=args.k_shot, q_query=args.q_query)
val_dataset = OmniglotDataset(args.val_data_dir, args.val_task_num,
n_way=args.n_way, k_shot=args.k_shot, q_query=args.q_query)
train_loader = DataLoader(train_dataset, batch_size=args.task_num, shuffle=True, num_workers=args.num_workers)
val_loader = DataLoader(val_dataset, batch_size=args.val_task_num, shuffle=False, num_workers=args.num_workers)
代码如下:
def maml_train(model, support_images, support_labels, query_images, query_labels, inner_step, args, optimizer, is_train=True):
"""
Train the model using MAML method.
Args:
model: Any model
support_images: several task support images
support_labels: several support labels
query_images: several query images
query_labels: several query labels
inner_step: support data training step
args: ArgumentParser
optimizer: optimizer
is_train: whether train
Returns: meta loss, meta accuracy
"""
meta_loss = []
meta_acc = []
for support_image, support_label, query_image, query_label in zip(support_images, support_labels, query_images, query_labels):
fast_weights = collections.OrderedDict(model.named_parameters())
for _ in range(inner_step):
# Update weight
support_logit = model.functional_forward(support_image, fast_weights)
support_loss = nn.CrossEntropyLoss().cuda()(support_logit, support_label)
grads = torch.autograd.grad(support_loss, fast_weights.values(), create_graph=True)
fast_weights = collections.OrderedDict((name, param - args.inner_lr * grads)
for ((name, param), grads) in zip(fast_weights.items(), grads))
# Use trained weight to get query loss
query_logit = model.functional_forward(query_image, fast_weights)
query_prediction = torch.max(query_logit, dim=1)[1]
query_loss = nn.CrossEntropyLoss().cuda()(query_logit, query_label)
query_acc = torch.eq(query_label, query_prediction).sum() / len(query_label)
meta_loss.append(query_loss)
meta_acc.append(query_acc.data.cpu().numpy())
# Zero the gradient
optimizer.zero_grad()
meta_loss = torch.stack(meta_loss).mean()
meta_acc = np.mean(meta_acc)
if is_train:
meta_loss.backward()
optimizer.step()
return meta_loss, meta_acc
support_images, support_labels, query_images, query_labels传入的都是以task为单位的,所以要用一个for循环来进行拆包,注意support_data和query_data数据集来源必须得一致,不能一个数据A task,另一个属于B task。
拆包完之后,首先进行训练集的训练,我们要注意,此时的训练是不能改动到模型权重,但我们又需要知道它的训练方向,所以我们需要copy出来一个权重,让它执行训练,用这个得到的权重对query_data执行前向传播,以此得到的loss再进行反向传播优化。这个过程很绕,建议多读几遍源码就懂了。
class Classifier(nn.Module):
def __init__(self, in_ch, n_way):
super(Classifier, self).__init__()
self.conv1 = ConvBlock(in_ch, 64)
self.conv2 = ConvBlock(64, 64)
self.conv3 = ConvBlock(64, 64)
self.conv4 = ConvBlock(64, 64)
self.logits = nn.Linear(64, n_way)
def forward(self, x):
x = self.conv1(x)
x = self.conv2(x)
x = self.conv3(x)
x = self.conv4(x)
x = x.view(x.shape[0], -1)
x = self.logits(x)
return x
def functional_forward(self, x, params):
x = ConvBlockFunction(x, params[f'conv1.conv2d.weight'], params[f'conv1.conv2d.bias'],
params.get(f'conv1.bn.weight'), params.get(f'conv1.bn.bias'))
x = ConvBlockFunction(x, params[f'conv2.conv2d.weight'], params[f'conv2.conv2d.bias'],
params.get(f'conv2.bn.weight'), params.get(f'conv2.bn.bias'))
x = ConvBlockFunction(x, params[f'conv3.conv2d.weight'], params[f'conv3.conv2d.bias'],
params.get(f'conv3.bn.weight'), params.get(f'conv3.bn.bias'))
x = ConvBlockFunction(x, params[f'conv4.conv2d.weight'], params[f'conv4.conv2d.bias'],
params.get(f'conv4.bn.weight'), params.get(f'conv4.bn.bias'))
x = x.view(x.shape[0], -1)
x = F.linear(x, params['logits.weight'], params['logits.bias'])
return x
模型定义比较简单,maml思想主要是个训练方式,和模型本身无关。但我们在刚刚模型训练的时候有一些特殊操作,所以要定义一个functional_forward,这个函数要求实现和模型一样结构的网络,同时参数输入为:1、图像 2、权重。这样就可以保证得到了loss,但模型权重没有被修改。
如何在buildr项目中使用Ruby?我在很多不同的项目中使用过Ruby、JRuby、Java和Clojure。我目前正在使用我的标准Ruby开发一个模拟应用程序,我想尝试使用Clojure后端(我确实喜欢功能代码)以及JRubygui和测试套件。我还可以看到在未来的不同项目中使用Scala作为后端。我想我要为我的项目尝试一下buildr(http://buildr.apache.org/),但我注意到buildr似乎没有设置为在项目中使用JRuby代码本身!这看起来有点傻,因为该工具旨在统一通用的JVM语言并且是在ruby中构建的。除了将输出的jar包含在一个独特的、仅限ruby
在rails源中:https://github.com/rails/rails/blob/master/activesupport/lib/active_support/lazy_load_hooks.rb可以看到以下内容@load_hooks=Hash.new{|h,k|h[k]=[]}在IRB中,它只是初始化一个空哈希。和做有什么区别@load_hooks=Hash.new 最佳答案 查看rubydocumentationforHashnew→new_hashclicktotogglesourcenew(obj)→new_has
我的主要目标是能够完全理解我正在使用的库/gem。我尝试在Github上从头到尾阅读源代码,但这真的很难。我认为更有趣、更温和的踏脚石就是在使用时阅读每个库/gem方法的源代码。例如,我想知道RubyonRails中的redirect_to方法是如何工作的:如何查找redirect_to方法的源代码?我知道在pry中我可以执行类似show-methodmethod的操作,但我如何才能对Rails框架中的方法执行此操作?您对我如何更好地理解Gem及其API有什么建议吗?仅仅阅读源代码似乎真的很难,尤其是对于框架。谢谢! 最佳答案 Ru
我的假设是moduleAmoduleBendend和moduleA::Bend是一样的。我能够从thisblog找到解决方案,thisSOthread和andthisSOthread.为什么以及什么时候应该更喜欢紧凑语法A::B而不是另一个,因为它显然有一个缺点?我有一种直觉,它可能与性能有关,因为在更多命名空间中查找常量需要更多计算。但是我无法通过对普通类进行基准测试来验证这一点。 最佳答案 这两种写作方法经常被混淆。首先要说的是,据我所知,没有可衡量的性能差异。(在下面的书面示例中不断查找)最明显的区别,可能也是最著名的,是你的
几个月前,我读了一篇关于rubygem的博客文章,它可以通过阅读代码本身来确定编程语言。对于我的生活,我不记得博客或gem的名称。谷歌搜索“ruby编程语言猜测”及其变体也无济于事。有人碰巧知道相关gem的名称吗? 最佳答案 是这个吗:http://github.com/chrislo/sourceclassifier/tree/master 关于ruby-寻找通过阅读代码确定编程语言的rubygem?,我们在StackOverflow上找到一个类似的问题:
我目前正在使用以下方法获取页面的源代码:Net::HTTP.get(URI.parse(page.url))我还想获取HTTP状态,而无需发出第二个请求。有没有办法用另一种方法做到这一点?我一直在查看文档,但似乎找不到我要找的东西。 最佳答案 在我看来,除非您需要一些真正的低级访问或控制,否则最好使用Ruby的内置Open::URI模块:require'open-uri'io=open('http://www.example.org/')#=>#body=io.read[0,50]#=>"["200","OK"]io.base_ur
前言作为一名程序员,自己的本质工作就是做程序开发,那么程序开发的时候最直接的体现就是代码,检验一个程序员技术水平的一个核心环节就是开发时候的代码能力。众所周知,程序开发的水平提升是一个循序渐进的过程,每一位程序员都是从“菜鸟”变成“大神”的,所以程序员在程序开发过程中的代码能力也是根据平时开发中的业务实践来积累和提升的。提高代码能力核心要素程序员要想提高自身代码能力,尤其是新晋程序员的代码能力有很大的提升空间的时候,需要针对性的去提高自己的代码能力。提高代码能力其实有几个比较关键的点,只要把握住这些方面,就能很好的、快速的提高自己的一部分代码能力。1、多去阅读开源项目,如有机会可以亲自参与开源
嗨~大家好,这里是可莉!今天给大家带来的是7个C语言的经典基础代码~那一起往下看下去把【程序一】打印100到200之间的素数#includeintmain(){ inti; for(i=100;i 【程序二】输出乘法口诀表#includeintmain(){inti;for(i=1;i 【程序三】判断1000年---2000年之间的闰年#includeintmain(){intyear;for(year=1000;year 【程序四】给定两个整形变量的值,将两个值的内容进行交换。这里提供两种方法来进行交换,第一种为创建临时变量来进行交换,第二种是不创建临时变量而直接进行交换。1.创建临时变量来
文章目录git常用命令(简介,详细参数往下看)Git提交代码步骤gitpullgitstatusgitaddgitcommitgitpushgit代码冲突合并问题方法一:放弃本地代码方法二:合并代码常用命令以及详细参数gitadd将文件添加到仓库:gitdiff比较文件异同gitlog查看历史记录gitreset代码回滚版本库相关操作远程仓库相关操作分支相关操作创建分支查看分支:gitbranch合并分支:gitmerge删除分支:gitbranch-ddev查看分支合并图:gitlog–graph–pretty=oneline–abbrev-commit撤消某次提交git用户名密码相关配置g
打印1:defsum(i)i=i+[2]end$x=[1]sum($x)print$x打印12:defsum(i)i.push(2)end$x=[1]sum($x)print$x后者是修改全局变量$x。为什么它在第二个例子中被修改而不是在第一个例子中?类Array的任何方法(不仅是push)都会发生这种情况吗? 最佳答案 变量范围在这里无关紧要。在第一段代码中,您仅使用赋值运算符=为变量i赋值,而在第二段代码中,您正在修改$x(也称为i)使用破坏性方法push。赋值从不修改任何对象。它只是提供一个名称来引用一个对象。方法要么是破坏性