目前人工智慧領域中,不斷學習的現況

人工智慧領域:不斷學習的現況' (Artificial Intelligence Field The Constant Learning Scenario)

ChatGPT为什么只训练到2021年?

由作者使用DALL-E 3生成的图像

知识先决条件:

几年前,我通过StatQuest视频、Lena Voita的NLP博客以及《Deep Learning for Coders》和《Talking Nets》等书籍学习了深度学习的基础知识。我现在想要了解深度学习中持续学习的当前状态。我发现没有太多简单概述这个主题的信息可供参考,而且需要筛选专家研究论文。因此,本文面向对该主题有基本了解但对研究论文难以理解且可能不是专家的读者。本文主要关注于聊天机器人,因此了解ChatGPT的训练阶段也会有所帮助。

介绍

ChatGPT告诉用户它只训练到2021年9月(作者的截图)

如果像ChatGPT这样的大型语言模型可以持续更新新数据,它们将加速各种任务,从软件开发到法律流程再到学习。这也会使得像本文这样的文章过时。

持续学习是暂停模型训练过程,保存模型当前状态,稍后再根据新数据继续训练的能力。模型应该能够在新数据上有良好的泛化能力,同时仍保持对旧数据的泛化能力。有关更正式的定义,请参考这篇论文

目前,行业中将聊天机器人与更多数据结合的趋势是使用向量存储库,通过将查询的向量与提示工程相结合来回答问题,而不是继续使用新数据训练语言模型。ChatGPT的零-shot学习能力使其能够回答关于新的、未知数据的问题,这种方法非常吸引人。例如,您可以教它一种新的编程语言,然后用几个提示问它关于该语言的问题,尽管性能会随着输入令牌数量的增加而稍微下降。将模型持续训练以回答基于这种新主题的问题需要大量的计算资源,更重要的是,需要大量关于相关主题的数据。此外,如果一个主题在训练集中的普遍性很低,它在这个主题上的泛化能力会很差。例如:拿一个不受欢迎的公共仓库,即使在训练过程中曾经看到过,它也对此一无所知。上下文窗口正在迅速变得越来越大,这使得向量存储库方法更具吸引力。但理想情况下,我们不是希望有一个全知全能的智能模型,而不需要任何外部数据库吗?

持续学习是通往通用人工智能(AGI)的重要步骤,一些人怀疑我们没有对深度学习网络架构进行重大改变的话,甚至可能无法实现它。Jeff Hawkins在他的书《千个大脑》中指出,他认为当前的人工神经网络无法有效地进行持续学习,并且相信未来的模型可能需要更类似于人脑,使用他关于新皮层皮质柱中参考框架的理论进行建模。

推广

我有一个开源项目提案,旨在帮助持续学习并增强聊天机器人的额外数据。我与来自VectorFlow的Dan Meier进行过讨论,尽管这个行业很大,但在这方面取得的进展很少。可以在这里找到幻灯片:)

语言模型的预训练阶段与微调阶段中的持续学习

今年早些时候,一篇名为“LIMA: Less Is More for Alignment”的研究论文被发表。它介绍了一种聊天机器人,该机器人并未通过人类反馈的增强学习(RLHF)进行训练,而是仅在1,000个精心注释的问答样本上进行了微调。令人惊讶的是,研究人员表示在43%的情况下,“聊天机器人的回答与GPT-4的回答相媲美”。我没有深入研究这些如何评估,但人们普遍认为模型的大部分知识和能力是在预训练阶段获得的,而这样的研究进一步证明了这一点。

像ChatGPT和Llama-chat这样的模型经过了大量的微调,以生成更加一致和有效的回答。OpenAI目前提供了一个API来进一步对模型进行微调,该API使用问答数据作为输入。然而,这不应该用来教导模型新数据,而是用来自定义语气和可操控性。试图通过微调模型来教导它新数据可能会导致灾难性遗忘,这是模型忘记已经学到的知识的问题。本文将介绍一些旨在缓解这个问题的技术。

这也引出了一些关于连续学习可行性和策略的关键问题:

  • 在开发的哪个阶段引入连续学习最有益且最容易?
  • 考虑到微调和RLHF都会改变整个模型的参数,是否可能回退到预训练阶段进行进一步修改?

注意:我为下面讨论的一些论文提供了一些类似PyTorch的伪代码。它没有经过测试,可能无法工作,仅用于逐步分解技术并翻译任何令人困惑的数学符号,以帮助读者理解。

连续学习技术的5个子类别

连续学习综述论文指出,连续学习的训练策略可以分为5个子类别:

  1. 基于正则化的方法:该方法在训练过程中添加约束或惩罚。
  2. 基于优化的方法:这种技术着重修改优化算法。
  3. 基于表示的方法:这旨在学习跨不同任务的共享特征表示,帮助模型更好地推广到新但相关的任务。
  4. 基于回放的方法:这涉及存储来自先前任务的一些数据或学习特征,并在训练新任务时重播它们,以保持对先前学习的任务的性能。换句话说,在训练新任务时混合旧数据集和新数据集。
  5. 基于体系结构的方法:在这种方法中,网络体系结构被动态调整,通常是通过增长或分割,将网络的不同部分委派给不同的任务。

1. 基于正则化的方法

参数的软遮罩

下面的软遮罩技术在训练过程中对每个参数的梯度进行遮罩和调整。接下来将介绍的基于优化的方法也使用这些梯度进行连续学习。请记住,梯度不仅仅是训练过程中出现和消失的临时数值;它们是引导权重演化的信号。

SPG

这篇论文提出了一种名为SPG(参数级梯度软遮罩)的技术,旨在实现以下目标:

  1. 在每个任务上训练模型直到收敛。
  2. 训练后,计算每个参数对任务的“重要性”。
  3. 基于累积重要性对参数进行软遮罩,使重要参数在学习新任务时不太可能改变。

让我们逐步分解这种方法:

1. 训练第一个任务

像往常一样,在第一个任务的数据集上训练模型。

2. 计算第一个任务的参数重要性

完成了第一个任务的训练后,我们计算每个模型参数的重要性。这里的直觉很简单,我们使用每个参数的梯度来计算其重要性。梯度越大意味着该参数的微小变化会导致损失的较大变化,这意味着模型的性能可能会有更大的变化,因此该参数很重要。

梯度也被标准化了,因为第一层的梯度可能较小,而最后一层的梯度可能较大。如果您基于这些原始梯度值计算重要性,由于梯度的尺度,最后一层的参数似乎更重要,并不一定是因为它们对任务确实更关键。

计算SPG模型参数重要性的方程(见论文第3.1节)

让我们将该计算转换为类似于PyTorch的伪代码:

import torchdef compute_final_importance(model, loss_function, data_loader):    # 从数据加载器中获取一个批次    inputs, labels = next(iter(data_loader))     # 正向传播和反向传播以计算所有参数的梯度    outputs = model(inputs)    loss = loss_function(outputs, labels)    loss.backward()        importances = []    # 根据梯度计算重要性    for param in model.parameters():        if param.grad is not None:  # 对于一些未使用的参数,梯度可能为None            normalized_grad = (param.grad - torch.mean(param.grad)) / torch.std(param.grad)            importance = torch.tanh(normalized_grad)            importances.append(importance)    return torch.stack(importances).mean(dim=0)

3. 跨任务累积重要性

每个参数在任务之间的累积重要性可以通过在任何阶段取最大值来计算。

4. 训练后续任务,组合损失和软遮罩机制:

当训练新任务时,研究人员使用一个由两部分组成的组合损失函数。其中之一是标准损失函数,像往常一样用于新任务和数据,而第二个部分是额外的损失函数,涉及将新数据通过旧模型(上一个任务后的收敛模型检查点)并累加产生的logits。在分类网络中,logits通常是模型在像softmax函数之前的最后一层生成的原始非标准化预测。这些logits的总和用作一种损失形式。其理论基础是,如果在模型参数改变时,累加的logits受到显著影响,那么这些参数对于先前学习的任务的性能至关重要。

从这个额外损失中生成的梯度在反向传播过程中充当指引,将共享参数推向一个不太可能对第一个任务性能造成损害的方向改变。因此,它作为一种惩罚项,强制要求对模型的任何更新不会导致与先前任务相关的信息的显著丧失。

在下一个任务上训练模型。使用标准的训练循环,但根据它们的累积重要性修改反向传播中的梯度。这就是软遮罩机制:

import torchaccumulated_importance = # 在每个任务结束时计算for epoch in range(num_epochs):  for x, y in train_loader:                # 正向传播:使用适当的损失函数计算当前任务的损失    logits = new_model(x)    loss_current_task = nn.CrossEntropyLoss()(logits, y)                # 正向传播:计算先前任务的附加损失(CHI机制)    loss_previous_tasks = 0    for prev_task_id in range(task_id):        logits_prev = old_model(x, prev_task_id)        loss_previous_tasks += logits_prev.sum()                # 组合损失    combined_loss = loss_current_task + loss_previous_tasks                # 反向传播    optimizer.zero_grad()    combined_loss.backward()                # 更新累积重要性    for param, acc_imp in zip(model.parameters(), accumulated_importance):        grad = param.grad        acc_imp = torch.max(acc_imp, torch.abs(grad))     # 在进行优化步骤之前对梯度进行软遮罩化    for param, imp in zip(model.parameters(), accumulated_importance):        param.grad *= (1 - importance)                optimizer.step()

5. 特殊情况下的软掩模

  • 特征提取器:基于特定的累积重要性,修改共享特征提取器中参数的梯度。
  • 分类头:对于分类头,基于特征提取器的平均重要性修改梯度。

应用于LLMs

请注意,本文并未在语言模型上实验这一技术,但我认为在语言模型中,可以将变压器层类比为“特征提取器”,将最终的分类层(预测序列中下一个单词或标记)视为“分类头”。

软掩模应用于语言模型的持续预训练

接下来,我们将介绍一篇应用类似软掩模技术的论文,用于语言建模的预训练阶段。

本文介绍了一种名为DAS(具有软掩模的LLMs的持续DA预训练)的技术,用于大型语言模型的持续学习预训练阶段。它应用了类似之前讨论的软掩模技术以及其他一些技术,以试图在不造成灾难性遗忘的情况下继续预训练LLM。

让我们逐步解析:

初始预训练阶段

像正常情况下那样预训练LLM。

在新领域进行进一步的预训练

准备新领域数据:

从不同领域准备一组新的数据集。

计算每个神经元的重要性

SPG使用梯度来确定每个参数的重要性,然后根据计算得到的重要性值,掩盖训练期间参数的梯度调整。本文尝试确定每个单元/神经元的重要性,然后通过在训练期间掩盖梯度来使用这种方法。

该论文使用了两种不同的方法来计算神经元的重要性,具体取决于任务。一种是基于梯度的重要性检测方法(最初在本论文中概述),另一种是自定义的“代理损失函数”。

首先介绍的方法在第一个新域的持续学习中根本没有使用。为什么?因为它需要来自训练数据集的数据才能工作,而作者们指出用户“无法访问大规模原始预训练数据集”,这是一个合理的假设。

他们提出了代理损失函数:

一开始我对这个术语感到困惑,但之所以称之为代理损失函数,是因为原始的基于梯度的重要性检测方法本身就被定义为一种损失函数,您可以通过该函数将网络的输出通过运行,以获得每个神经元的梯度,然后可以像SPG技术一样使用这些梯度来推导重要性。

根据论文,该重要性是针对网络中的每个“单元”计算的,其中单元可以是神经元或注意力头。

代理损失函数(“代理KL散度损失”):

  • 从我们想要进行训练的新领域中选择一部分数据子集,并将其通过模型传递两次以获得两种不同的表示。由于Transformer结构中存在的丢失屏蔽,这些表示将略有不同。
  • 计算这两种表示之间的KL散度。

使用代理和联合损失的修改后向传播流程

  1. 前向传递:数据通过神经网络进行前向传递。
  2. 反向传播:

应用代理损失进行梯度调整:代理损失函数的单元级重要性用于对原始梯度进行软掩模。具体表达为:

adjusted_grad *= (1 − unit_level_importance)

计算联合损失(MLM + 对比损失):使用MLM和对比损失计算联合损失。

在更多领域进行进一步的预训练

  1. 直接重要性计算:现在可以使用来自新领域的数据通过方程3中梯度方法直接计算每个单元的重要性,消除仅在初始预训练后使用的代理损失函数的需要。
  2. 神经元的重要性在学习每个新任务时进行增量更新。使用逐元素最大值进行此更新。 “逐元素最大值(EMax)运算”是指逐个元素地比较两个向量,并取每个相应元素的最大值来创建一个新向量。例如:如果你有两个长度相同的向量A和B,那么逐元素最大值将导致一个新向量C,其中每个元素C[i]是A[i]和B[i]之间的最大值。

2. 基于优化的方法

我们将在第3.1节中详细讨论综合调查论文中提出的两种技术。

梯度方向保持

该论文讨论了通过操纵基于梯度的优化过程,使新训练样本的梯度方向接近于旧训练样本的梯度方向的方法。公式如下:

⟨ ∇θ Lₖ(θ; Dₖ), ∇θ Lₖ(θ; Mₜ) ⟩ ≥ 0

这个公式强制学习新任务不能增加旧任务的损失。实质上,鼓励新任务和旧任务的梯度保持一致。

解读这个公式,我们计算新任务的损失梯度(∇θ Lₖ(θ; Dₖ))和旧任务的损失梯度(∇θ Lₖ(θ; Mₜ))的点积应该是非负的。在这种情况下,正的点积意味着旧任务和新任务的梯度通常指向相同的方向,并且两个向量之间的夹角小于或等于90度。

前/后向传递:

前向传递:

你会将新任务的输入数据Dₖ和旧任务的输入数据Mₜ通过相同的模型运行,以计算每个任务的损失。

后向传递:

  1. 计算旧任务和新任务对网络参数的损失梯度。
  2. 对齐检查:计算两个梯度的点积。然后使用这些信息修改新任务的梯度,以使点积为非负。
  3. 更新权重:使用这些“对齐”的梯度更新模型参数。
import torch# 新任务的前向传递output_k = model(D_k)loss_k = criterion(output_k, y_k)# 旧任务的前向传递output_t = model(M_t)loss_t = criterion(output_t, y_t)# 为两个任务计算梯度loss_k.backward(retain_graph=True)  # 计算新任务的梯度,保留计算图grad_k = torch.cat([p.grad.view(-1) for p in model.parameters()])  optimizer.zero_grad() loss_t.backward()  # 计算旧任务的梯度grad_t = torch.cat([p.grad.view(-1) for p in model.parameters()]) # 计算点积并修改梯度,如果它们不对齐dot_product = torch.dot(grad_k, grad_t)if dot_product < 0:    # 我不确定如何修改不对齐的梯度,我不确定论文是否指定了# 使用修改后的梯度更新模型参数index = 0for p in model.parameters():    num_params = p.numel()    # 使用修改后的梯度更新    p.grad = grad_k[index: index + num_params].view(p.shape)    index += num_paramsoptimizer.step()

无需存储旧训练样本的梯度方向保持

论文还指出,即使不存储旧样本,也可以执行梯度投影。在此总结的技术是NCL(自然连续学习,论文链接)。请注意,这既可以归类为正则化方法,也可以归类为基于优化的方法。

训练步骤:

前向传递:

您将通过网络运行您的新数据并像往常一样计算损失。

反向传递:

目标:目标是在遵守距离约束 d(θ,θ+δ)≤r 的情况下最小化任务特定的损失 ℓk(θ)。

算法步骤:

  1. 像正常一样,计算损失关于模型参数的梯度 ∇θ​ℓk​(θ)。
  2. 使用更新规则计算δ。这给出了基于新任务要求的模型参数θ的“建议”更改。
  3. 然后,将此δ插入距离约束公式中:d(θ,θ+δ)=根号(δ⊤Λ_k-1​δ)。该约束在当前参数θ周围形成一个边界,由距离度量d(θ,θ+δ)和半径r定义。我曾经疑惑为什么他们称之为“半径”,而不是“约束数”或其他类似的名称。我认为这是因为研究人员在高维空间中对梯度和训练过程进行可视化。当您根据距离度量应用约束时,实质上是在高维空间中为当前参数值定义一个“球体”。该球体的“半径”r在学习新任务时设置了参数可以移动的限制。
  4. 如果根据该距离度量,所提议的δ会使θ移动得太远,即超出该边界,则将其缩小,使其保持在由半径r定义的可允许区域内。

让我们更深入地了解每个部分:

更新规则:更新规则提供了θ应该移动的方向。

来自连续学习综述论文第3.1节的NCL更新规则

具体分解如下:

  • ∇θ ℓk(θ)表示由损失函数计算出的所有参数(θ)的梯度。
  • 参数重要性计算 (Λ^(k-1)_(-1)):该项表示精确矩阵,是计算网络参数重要性的另一种方式。更多细节请见下文。
  • 正则化项 (θ — μ_(k-1)):该项将更新的参数拉近到前一个任务的最优参数 μ_(k-1)​。与之前的技术类似,它作为正则化器避免与已学到的内容偏离。
  • 学习率 (λ)

距离约束:在应用此更新之前,通常会检查此更改δ是否违反了距离约束 d(θ,θ+δ)≤r。如果是,则通常会缩小δ以满足约束。

精确矩阵解释:之前在软掩蔽方法中我们看到通过神经元的输出或梯度计算参数重要性的计算。在这种方法中使用了精确矩阵。这有点复杂,所以我将尝试解释一下:

我们首先计算网络参数的协方差矩阵。在神经网络的上下文中,梯度矩阵 G 的列对应于模型的参数(权重和偏置)。G 中的每一行表示对单个训练示例相对于所有这些参数的梯度向量。

因此,如果您有一个具有 P 个参数的神经网络(其中包括所有层的所有权重和偏置),那么每个梯度向量将有 P 个元素,对应于每个参数一个。因此,G 将是形状为 N × P 的矩阵,其中 N 表示每个批次,因此每行表示给定批次中所有训练示例的平均梯度向量。

当您从 G 计算出协方差矩阵 Σ 时,得到的矩阵将具有维度 P × P。对角线条目 Σii​ 将指示与第 i 个参数相关的梯度的方差,而非对角线条目 Σij​ 将指示第 i 个和第 j 个参数的梯度之间的协方差。这让您了解这些参数在训练过程中如何交互或共变。该矩阵的逆矩阵是精确矩阵,用于确定重要性。

为什么要使用精确矩阵而不是协方差矩阵?协方差矩阵Σ确实捕捉了参数在训练过程中如何相互影响,但它并没有明确指示在考虑所有其他参数时每个参数对当前任务有多重要。相反,精确矩阵允许我们评估参数之间的条件独立性(这是概率论中的一个概念,请查阅相关资料)。精确矩阵中的较大值表明在考虑所有其他参数的情况下,了解一个参数对另一个参数的信息非常丰富。我不会介绍具体的工作原理示例,请让ChatGPT生成一些使用非常小的神经网络的例子,以了解如何解释这些值。

之前我们看到的计算重要性的先前方法集中在个别神经元或参数上,忽略了它们之间的关系。而精确矩阵则可以捕捉这些关系。与深度学习中的一切一样,这种计算网络重要性的更好方式是否适用,将是通过经验验证的,并且可能因任务和网络规模的不同而有所不同。

使用PyTorch的算法步骤:

import torch# 约束半径radius = 0.1for epoch in range(num_epochs):      for batch_idx, (data, target) in enumerate(data_loader):        optimizer.zero_grad()        # 前向传播        output = model(data)        loss = loss_function(output, target)        # 反向传播计算参数的梯度        loss.backward()        model_grad = torch.cat([p.grad.data.view(-1) for p in model.parameters()])        # 使用NCL方法计算 δ = Λ^(-1) * grad - (θ - µ)        delta = torch.matmul(torch.inverse(covarianceMatrix), model_grad) - (torch.cat([p.data.view(-1) for p in model.parameters()]) - parametersForPrevTask)        # 检查约束        if torch.norm(delta) > radius:            delta = radius * delta / torch.norm(delta)        # 使用 δ 更新模型参数 (θ)        idx = 0        for p in model.parameters():            length = p.data.numel()            p.data += delta[idx: idx + length].view(p.data.shape)            idx += length        # 更新Λ和µ以便下一个任务使用,可能需要根据具体任务进行调整和处理

3. 基于表示的方法

首先,需要注意的是,在该子类别中,对LLM进行预训练以在下游任务中进行进一步微调是一个连续学习的示例。我认为ChatGPT在推理从未见过的数据方面的能力也是这种方法的一个例子。尽管我们在技术上称之为零-shot学习,并且术语”连续学习”需要更新模型参数,但它超越了我们以前见过的任何内容。正如简介中讨论的那样,引导工程可能成为连续学习的未来,而不是不断更新参数。

接下来,我们将看一下使用知识蒸馏进行连续学习的方法。我不太确定这属于哪个子类别,但我猜它可能是表示、架构和重演方法的混合体。即使我们正在回顾的一些技术在大规模上可能看起来随机和没有证明,但在这个领域的突破经常是不可预测的。因此,保持广阔的视角是很重要的。

知识蒸馏用于连续学习

您可以将一个网络的知识“蒸馏”到另一个网络中,第二个网络可以合理地近似学习原始网络的功能。

蒸馏模型(学生)的训练目标是模仿较大网络(教师)的输出,而不是直接在原始数据上进行训练。例如,假设您想要训练一个较小的学生模型来模仿一个大型预训练语言模型(教师)。将原始的预训练数据集通过教师模型运行,以生成“软目标”。这些软目标是潜在输出的概率分布,例如下一个单词的预测。例如,在下一个单词预测任务中,教师可以提供“猫”概率90%、 “小猫”概率5%、 “猫科动物”概率3% 等。

这通常用于将知识转移给规模较小的模型,并且尽管模型较小,却能取得很好的结果。

让我们看看一些研究人员如何成功将其应用于命名实体识别(NER)模型。培训过程非常简单:

培训过程逐步介绍

该论文中提出了两种主要方法:AddNER和ExtendNER。

AddNER模型

值得注意的是,NER模型通过将一系列标记(通常是一个句子)作为输入,然后为每个标记输出一个概率分布(用于不同类型的实体)。NER模型通常使用IOB标记法,每个标记可以被标记为‘O’,或者作为类型为X的实体的开头(‘B-’)或内部(‘I-’)。‘O’表示‘外部’,它只意味着当前标记不属于任何实体。因此,对于n个实体类型,你将在分类层中有2n个输出神经元:n个用于‘B-’标记(每个实体类型一个)和n个用于‘I-’标记(同样,每个实体类型一个)。再加上表示一个标记不属于任何实体的‘O’标签,你会得到每个标记2n + 1种可能的标签。最终的维度可以表示为h × (2n + 1),其中h是隐藏层输出的大小。请记住,这仅适用于标记只能是一个实体的模型。例如:“Apple”可以被标记为“FOOD”和“COMPANY”。

架构和师生设置

在这种情况下,学生模型是教师模型的副本,它具有每个新实体类型的额外输出分类层,该模型应该通过学习来掌握。在训练过程中,新的输出层从新的注释数据中学习,而旧层则通过教师模型的输出进行指导,以最小化遗忘。

训练结束后,旧的输出层不会被丢弃。然后,它使用冲突解决器部分(第3.3节末尾)中描述的算法和启发式方法将这些输出组合成序列中每个标记的单个最终预测。

论文第3.2节中的AddNER模型图

前向传递

  1. 旧实体类型:输入句子经过教师模型,以获取旧实体类型的概率分布(在这个上下文中称为“软目标”)。
  2. 新实体类型:同样的句子也经过新的学生模型,其中包含针对新实体类型的附加输出层。

反向传递

综合损失函数:

  1. KD损失:通过比较新模型(学生)的旧实体类型的输出概率与旧模型(教师)的输出概率的相似程度来计算。它使用KL散度进行计算。它可能逐个标记计算,然后对句子或批处理中的所有标记求和或平均,但我认为论文没有详细介绍这一点。
  2. 交叉熵损失:这是一种常见的损失函数,用于比较模型对新实体类型的预测与新数据集中的实际标签。
  3. 组合两者:这两种损失通过对它们进行加权求和来组合为一个综合损失。将这两种损失组合在一起的权重由超参数alpha和beta设置,这些超参数像其他超参数一样根据实验进行调整以提高性能。
# 用于加权两个损失函数的超参数alpha和betaalpha = 0.5beta = 0.5for epoch in range(num_epochs):    for sentence, labels in D_new:        # 通过教师模型进行旧实体类型的前向传递        teacher_probs_Ei = teacher_model(sentence)                # 通过学生模型进行旧和新实体类型的前向传递        # 注意:新实体类型必须经过新的输出层(在此伪代码中未显示)        student_probs_Ei, student_probs_Enew = student_model(sentence)                # 计算KD损失        kd_loss = KL_divergence(teacher_probs_Ei, student_probs_Ei)                # 计算新实体类型的交叉熵损失        ce_loss = 交叉熵函数(labels, student_probs_Enew)                # 综合损失        total_loss = alpha * kd_loss + beta * ce_loss                # 反向传递        total_loss.backward()                # 更新学生模型参数        optimizer.step()

ExtendNER模型

架构和师生设置

ExtendNER模型通过扩展输出层的维度来适应新的实体类型,而不是添加新的输出层。该论文相当简单地解释了维度应如何设定:

“假设Mi能够识别n个实体类型,则其最终层可以被视为一个维度为h×(2n+1)的矩阵。Mi+1的输出层将扩展为一个维度为h×(2n + 2m + 1)的矩阵,以适应新的实体类型。”

ExtendNER模型的图示(来自论文第3.4节)

正向传播

与AddNER相同,但维度扩展。

反向传播

损失计算使用KL散度损失或交叉熵损失,具体取决于以下情况:

  • 当NER类别标签y为“O”(来自IOB标记方案)时,使用KL散度损失。
  • 当类别标签y不是“O”时,使用交叉熵损失。

最终预测

应用Viterbi算法解码最终的实体类型。

在持续学习方面,AddNER和ExtendNER模型表现良好,它们之间的结果没有太大的差异。

4. 基于回放的方法

“精调语言模型是持续学习者”

paper链接

该论文中的模型并不是像GPT这样专门用于对话回复的通用单任务模型。相反,它针对一系列专门的任务进行了精调,范围从文本简化到古诗生成。每个任务都具有独特的要求、评估指标和专门的训练数据集。

研究人员将旧数据集的部分内容与新数据集混合,通过在新任务上进行精调时仅混合上一个任务数据集的1%,取得了出色的结果。他们按顺序进行了许多任务(共8个)。该模型在零样本学习的情况下表现良好,意味着它能够很好地泛化到未经训练的任务上。例如,当给定一个未见过的主题时,它可以生成正确音节数的古诗,显示了它的泛化能力。研究人员还提到,他们的方法是任务顺序不变的,即学习任务的顺序不会影响模型的性能。实验证明,旧数据集与新数据集的混合程度对主要任务的性能影响不大。然而,它确实会影响零样本学习。在0%的重演情况下,模型倾向于忘记零样本任务,而在1%的重演情况下,模型在这些任务中的性能能够非常好地维持。

这一切似乎都很积极,我们只需添加1%的旧数据集,持续学习问题就得到解决,但当然,将其应用于chatGPT等聊天机器人上将是经验性的,并且可能完全不同。即使假设chatGPT可以像这样在精调和RLHF阶段进行持续训练,也需要大量标注对话数据。

5. 基于架构的方法

在这里,我不会详细介绍任何特定的论文或实现,但我将提供对这种方法的简要概述以及几种不同的技术。我建议阅读这篇综合调查论文的第4.5节。它比其他部分更易于阅读。

  1. 参数分配:这里,网络参数的一个子集被分配给每个任务。可以通过屏蔽无关的神经元或明确地识别当前任务的重要神经元来实现。
  2. 模块化网络:这涉及为每个任务使用单独的子网络或模块。

子网络可以以各种方式连接以形成一个整体或更复杂的架构。以下是连接子网络的几种常见方法:

输出的连接:

在这种方法中,多个子网络的输出被连接成一个单一的张量,然后可以通过其他层传递以产生最终输出。

投票机制:

在某些模型中,每个子网络对可能的结果进行“投票”,最终决策是通过多数投票或加权投票进行的。这在生物学上受到启发,因为它类似于新皮层中不同的皮层柱如何进行投票。

跳过连接:

某些架构允许子网络与模型的其他部分具有跳过连接,以实现模块之间的信息流动。

连续的:

在这种情况下,一个子网络的输出作为下一个子网络的输入。

回到聊天机器人的讨论,如果可能创建这样一个具有两个子网络的架构,我觉得特别有趣。第一个子网络是持有一般“知识”的预训练模型。第二个子网络持有将模型对齐所需的知识。一旦模型对齐,它就不再需要带有标记的对话数据。相反,可以通过以无监督的方式训练预训练子网络来不断更新它。

结论

总之,深度学习中的持续学习子领域具有挑战性,且大部分仍未知。这是因为我们并不完全理解LLM中的神经元如何工作,正如简介中概述的那样,当前的网络架构或深度学习可能不适用于此。

我注意到上个月ChatGPT(仅GPT-4)已经更新,现在它说“自2022年1月以来,我的训练已截止”,所以我想知道OpenAI的人们是如何做到这一点的。

ChatGPT(GPT-4 变种)告诉用户它训练至2022年1月(作者提供的截图)