李宏毅机器学习课程——Lifelong learning学习笔记
概述
lifelong learning非常直观,意思是机器不能前边学后边忘。常见的方法是对前边的task中学习出来的参数加一个保护系数,在后面的任务中,训练参数时,对保护系数大的参数很难训练,而保护系数小的参数则容易一些。
下面的图非常直观,颜色的深浅代表loss的大小,颜色越深loss越小。在task1中\(\theta_2\)的变化对loss的变化非常敏感,而\(\theta_1\)则不敏感,所以在task2中尽量只通过改变\(\theta_1\)来减小loss,而不要改变\(\theta_2\)。
在lifelong learning中,loss的计算公式如下:
\(L'(\theta)=L(\theta)+\lambda\Sigma_{i}b_{i}(\theta_i-\theta_i^b)^2\)
其中\(b_i\)就是对\(\theta\)的保护系数,\(\theta_i\)表示本次task中需要学习的参数,\(\theta_i^b\)是从之前的task中学习到的参数。
不同的方法差异就在于\(b_i\)的计算。
这里将会结合Coding整理一下遇到的三个方法。
Coding
这部分针对\(HW14\),介绍了EWC,MAS,SCP三种方法,这里讲解一下具体的代码实现,并定性地分析一下这些方法是如何把哪些重要的参数保护起来。
- EWC
EWC中不同的保护系数\(f_i\)使用如下的方法计算得到:
$ F = [ \nabla \log (p(y_n | x_n, \theta_{A}^{*} )) \nabla \log (p(y_n | x_n, \theta_{A}^{*} ))^T ] $
\(F\)的对角线的各个数就是各个\(\theta\)的保护系数。
\(p(y_n | x_n, \theta_{A}^{*})\) 指的就是模型在给点之前 task 的 data \(x_n\) 以及给定训练完 task A (原来)存下来的模型参数 \(\theta_A^*\) 得到 \(y_n\)(\(x_n\) 对应的 label ) 的后验概率。
其实对参数\(\theta_i\),它的保护系数就是向量\(\log(p(y_n | x_n, \theta_{A}^{*}))\)对\(\theta_1\)的偏导数\(\frac{\partial \log(p(y_n|x_n,\theta_A^*))}{\partial \theta_1}\)与自身的内积。当对这个参数敏感时,这个偏导数会变大,当预测结果正确率高时,\(p(y_n|x_n)\)也会高,最终都会使的保护系数变大。某一个参数比较敏感,这个参数下正确率高时,这个参数就会被很好地保护起来。
for dataloader in self.dataloaders:
for data in dataloader:
self.model.zero_grad()
input = data[0].to(self.device)
output = self.model(input).view(1,-1)
label = output.max(1)[1].view(-1)
loss = F.nll_loss(F.log_softmax(output,dim),label)
loss.backward()
for n,p in self.model.named_parameters():
precision_matrices[n].data += p.grad.data ** 2 / number_data
precision_matrices = {n: p for n, p in precision_matrices.items()}
- MAS
MAS中保护系数的计算方法如下所示:
$\Omega_i = || \frac{\partial \ell_2^2(M(x_k; \theta))}{\partial \theta_i} || $
\(x_k\) 是来自于前面 task 的 sample data。 式子上的作法就是对最后模型的 output vector (最后一层)做2范数后取平方,再对各自的weight微分(取gradient) 并且取该 gradient 的绝对值。
for dataloader in self.dataloaders:
for data in dataloader:
self.model.zero_grad()
output = self.model(data[0].to(self.device))
output.pow_(2)
loss = torch.sum(output,dim=1) # 2范数的平方即元素的平方和
loss = loss.mean()
loss.backward()
for n, p in self.model.named_parameters():
precision_matrices[n].data += p.grad.abs() / num_data ## difference with EWC
precision_matrices = {n: p for n, p in precision_matrices.items()}
- SCP
SCP方法保护系数的计算方法(\(\Gamma\)矩阵)如下:
初始化矩阵为0矩阵。
模型的\(output\)对所有的task A的输入\(x_A\)取平均值:
\(\bar{\phi}_A^*=\frac{1}{N}\Sigma_{n=1}^N\phi(x_n^A;\theta_A^*)\)
从\(k\)维球面依次随机取L个单位向量,注意要与\(\bar\phi_A^*\)的维度要一致,每次取得的\(\xi_l\),依次执行如下操作:
- 计算内积\(\rho=\xi_l * \bar{\phi}_A^*\)
- 取梯度\(\nabla_\theta\rho\)
- \(\Gamma += \frac{1}{L}(\nabla_\theta\rho)(\nabla_\theta\rho)^T\)
def sample_spherical(npoints, ndim=3):
vec = np.random.randn(npoints, ndim)
vec = (vec.T / np.linalg.norm(vec, axis=1)).T
return vec
## main
for dataloader in self.dataloaders:
for data in dataloader:
self.model.zero_grad()
output = self.model(data[0].to(self.device))
vec_mean = output.mean(dim=0)
L_vecs = sample_spherical(self.L,vec_mean.size()[0])
for vec in L_vecs:
rou = torch.dot(torch.from_numpy(vec).to(self.device),vec_mean.double())
rou.backward(retain_graph=True)
for n, p in self.model.named_parameters():
precision_matrices[n].data += p.grad.data ** 2 / self.L
self.model.zero_grad()
precision_matrices = {n: p for n, p in precision_matrices.items()}