Introduction
Machine Learning 让机器能够学习
Meta Learning 让机器学习应该如何学习
以上图为例,假设现在有一百个识别任务,例如是语音识别,图像识别等等,在这些任务学习完成之后,我们需要给机器一个新的任务,这个新的任务跟之前的一百个任务没有任何的关联,例如是一个文本分类任务。Meta-Learning要做的就是希望能够从前一百个识别任务中让机器在文本分类任务上表现得更加优秀。也就是说,机器在前面的学习中不仅仅学到了如何解决特定任务,并且学习到了学习本身这件事情,从而提高了在新任务上的学习能力。(个人理解是从不同的任务中学到了一些先验知识,然后把它用到新任务里。)
区别
- Life-long Learning:one model for all the tasks(一个模型—多个任务)
- Meta-Learning:How to learn a new model(如何学一个新的模型)
刚刚讲的Meta-Learning概念描述可能还是有点抽象,那么我们用一个具体的模型结构来解释一下Meta-Learning实际在做的事情。
简单地说,机器学习过程是这样的:人为设计一个Learning Algorithm,然后输入一堆训练数据,训练之后得到一个function,然后这个function用来辨认新的数据,如果这个function辨认新数据的结果还不错,我们就认为机器学到了对于该特定任务的实现函数。
So what about Meta Learning?
在Meta Learning中,也把Learning algorithm当成一个函数 ,即输入一堆训练数据,然后根据函数得到,最后根据来测试新的数据。
跟机器学习相比,元学习其实是想让机器从数据中自己学习到Learning Algorithm,而不是人为设计。
实际上,我们知道Meta Learning的训练资料是一堆训练集和的组合,训练集的准备是比较容易的,但是的准备就比较复杂了,因为它本身是一个抽象概念。我们需要通过一个实例去介绍:
上图是gradient descent的算法,它的流程可以简述为:设计一个网络架构—>给参数做初始化—>读入训练数据批次—>计算梯度—>基于梯度更参数—>进入下一轮训练……灰色的部分其实就构成了一个function ,而训练的过程中很多参数是自己设计(红色方块)的,例如网络结构,参数,以及怎么基于梯度去更新参数(lr的设置等等),每当我们手动去更新这些模块的时候,其实都在定义一个新的,所以对于gradient descent方法来说,元学习的最终学习成果是在给定训练资料的条件下,机器能够找到。
接下来,我们需要做的就是设计评价函数 好坏的指标,具体来说,可以选择各种不同的训练流程,如何评价然后找到,以及如何找到都是元学习中比较重要的部分。
然后说明一下元学习中函数的损失函数的定义
如图所示,在Task1中,函数的训练算法是,而Task1中的测试集在上的测试结果被记作在Task1上的损失值(这里的测试结果不仅仅是分类任务中的分类损失,也可以是损失下降的速率等,这取决于我们想函数学习到什么样的算法效果);同理在Task2也是如此。
综上所述,函数的损失函数就定义在所有Task上的损失总和:
得到损失函数之后我们应该怎样去降低的损失?由于元学习的求解是非常复杂的过程,接下来以MAML(Model-Agnostic Meta-Learning for Fast Adaptation of Deep Networks)算法为例介绍一个Meta Learning的简单情况求解。
MAML算法介绍及其实现
MAML算法介绍
MAML算法要解决的问题是,对于函数在每个任务中学习到的,规定只负责决定参数的赋值方式,而不设计模型结构,也不改变参数更新的方式。也就是说MAML中的的网络结构和更新方式都是固定的,要解决的其实就是如何针对不同任务为网络赋不同的初值。
如上图,只考虑参数的初始化方式。假设当前函数的初始化为,将应用于所有的训练任务中,并将所有任务最终训练结束后的参数作为(n表示第n个任务),然后该参数下的测试损失记为。那么当前的初始化参数的损失函数就表示为:
接下来我们需要求解使得:
怎么求得上式?通过梯度下降方法求解:
MAML为了更快地计算出上面的结果,做了两处计算上的调整:
如上图所示,由于在每一个训练任务上更新多次参数会让训练周期变得很长,因此MAML选择只更新一次的参数结果作为该任务的最终参数,也就是只走一次梯度下降:
能这样做的原因是,如果模型只训练一次就能达到好的效果,那么这样的训练初始参数基本上能复合好的参数,不过在测试资料上依然需要训练多次才能检验该初始参数的真正效果。
MAML的第二处调整是,对
数学推导比较简单,略。具体见纸质笔记(有空可以黑板推导)。
最终得到
简化后MAML的计算就变得比较简单了,理论分析到此为止。
接下来我们通过一个实际例子来理解上面的计算是怎么执行的。
首先,最开始有一个初始化的参数,然后,在Task m上训练一次得到最终参数,接着计算的梯度(即上图中第二根绿色箭头),将这一梯度乘以学习率赋给,得到的第一次更新结果。
接下来,同样地,在Task n上训练一次得到最终参数,接着计算的梯度(即上图中第二根黄色箭头),将这一梯度乘以学习率赋给,得到第二次训练的更新结果。这样不断循环往复,直至在所有的训练Task上完成训练,就找到了最终的初始化参数。
算法实现
import numpy as np
import matplotlib.pyplot as plt
pi = np.pi
def samplepoints(k):#k为对函数asin(x+b)在0到2π的采样点数
a,b = np.random.uniform(0,2,2)
x = np.arange(0,2pi,2pi/k)
y = anp.sin(x+b)
return x,y,a,b
def draw_sin(a,b):
x = np.arange(0,2pi,0.1)
y = anp.sin(x+b)
plt.plot(x,y)
定义一些基本参数
task_num = 10 #每个batch中含有的task,即一次梯度下降所含的task数目
points_num =10
alpha = 0.01 #子模型的学习率
beta = 0.01 #元学习初始化参数更新的学习率
a_init = np.random.normal()
b_init = np.random.normal() #要更新的初始化参数的最初数值
epoch = 10000 #元学习模型更新次数
然后开始更新,寻找最优的初始化参数a_init和b_init,采用MSELoss,复合求导略。
for epoch in range(epoch):
xtrain = []
y_train = []
a_train = []
b_train = []
# 生成每个task的数据
for i in range(task_num):
x,y,a,b = sample_points(points_num)
x_train.append(x)
y_train.append(y)
a_gradient = 0
b_gradient = 0
# 对每个task进行一次梯度下降,更新a,b,更新之后再计算一次梯度,并把这第二次的梯度累加,用来更新a_init和b_init
loss = 0
for i in range(task_num):
a_0 = a_init
b_0 = b_init #梯度下降的初始值为元学习模型要学习的初始化的值
x = x_train[i]
y = y_train[i] #第i个task的x和y
y = a0np.sin(x+b_0) #通过参数a,b对y的预测值
a_0 = a_0 - sum(2alpha*(y-y)np.sin(x+b_0))/points_num #MSE损失,复合函数求导
b_0 = b_0 - sum(2alphaa_0(y-y)*np.cos(x+b_0))/points_num
#更新完后,再计算一次梯度,累加
y = a0*np.sin(x+b_0)
loss += sum(np.square(y-y))/pointsnum
a_gradient += sum(2alpha(y-y)np.sin(x+b_0))/points_num
b_gradient += sum(2alphaa_0(y-y)np.cos(x+b_0))/points_num
a_init -= betaa_gradient
b_init -= beta*b_gradient
if epoch%1000==0:
print(“epoch:%d,loss:%f”%(epoch_,loss))
可能是学习率设置的太大了,收敛到一定程度开始震荡,不过loss至少是下降的
对MAML学习到的ainit和b_init画出来,进行0次gradient descent,直接画出来和随机生成的a,b做比较。
x,y,a,b = sample_points(points_num)
draw_sin(a,b_)
draw_sin(a_init,b_init)
不进行MAML,随机生成a,b,进行0次gradient descent,直接画出。
a = np.random.normal()
b = np.random.normal()
drawsin(a,b_)
draw_sin(a,b)
左边是使用MAML算法的,右边是不使用MAML算法的
100次迭代的时候,MAML学习到的初始化参数以及能够比较贴近函数了,但是随机出来的ab则差得比较多。