李巨集毅機器學習課程——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}{*})
\(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}\)
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()}