首页
学习
活动
专区
圈层
工具
发布
社区首页 >专栏 >伪排练解决NLP灾难性遗忘

伪排练解决NLP灾难性遗忘

原创
作者头像
用户11764306
发布2026-06-06 18:08:59
发布2026-06-06 18:08:59
140
举报

伪排练:针对NLP灾难性遗忘的一个简单解决方案

2017年8月23日 · 8分钟阅读

有时你需要微调一个预训练模型,以添加新的标签或修正某些特定错误。这可能会引入“灾难性遗忘”问题。伪排练是一个很好的解决方案:使用原始模型对示例进行标注,并将它们混入你的微调更新过程中。

当你先后优化两个学习问题,并且第一个问题的权重被用作第二个问题权重的初始化的一部分时,就会发生灾难性遗忘问题。大量工作致力于设计对初始化不那么敏感的优化算法。理想情况下,我们的优化器应该足够优秀,无论权重如何初始化,它们总能找到给定问题的最优解。事实并非如此,但这是我们的目标。这意味着,如果你先后优化两个问题,灾难性遗忘就是应该发生的事情。

某机构spaCy v2.0.0a10中的多任务学习

为了帮助你避免灾难性遗忘问题,最新的spaCy v2.0 alpha模型将多任务CNN与每个任务特定的局部CNN混合在一起。这样你就可以独立更新某个任务,而不会写入共享组件。

灾难性遗忘问题最近对spaCy用户变得更为相关,因为spaCy v2的词性、命名实体、句法依存和句子分割模型都共享同一个由卷积神经网络生成的输入表示。这使得不同模型可以共享大部分权重,从而使整个模型非常小——最新版本只有18MB,而之前的线性模型接近1GB。通过doc.tensor属性,多任务输入表示也可用于其他任务,如文本分类和语义相似度。

然而,在所有模型之间共享权重设置了一个微妙的陷阱。假设你正在解析短命令,因此你有大量示例表明第一个单词是祈使动词。默认的spaCy模型在此类输入上表现不佳,所以我们希望在我们将要处理的用户命令文本的某些示例上更新模型。

代码语言:python
复制
import spacy
nlp = spacy.load("en_core_web_sm")
doc = nlp(u"search for pictures of playful rodents")
spacy.displacy.serve(doc)

这个解析是错误的——它将“search”分析为名词,而它应该是动词。如果你只知道句子的第一个单词应该是动词,你仍然可以用它来更新spaCy的模型。要更新模型,我们将一个Doc实例和一个GoldParse实例传递给nlp.update()方法:

代码语言:python
复制
from spacy.gold import GoldParse
new_tags = [None] * len(doc)
new_tags[0] = "VBP"
gold = GoldParse(doc, tags=new_tags)
nlp.update(doc, gold, update_shared=True)

None值表示这些标签上没有监督信号,因此这些预测的梯度将为0。同时也没有依存解析或实体识别器的标签,所以这些模型的权重不会被更新。然而,所有模型共享相同的输入表示,因此如果该表示被更新,所有模型都可能受到影响。为了解决这个问题,spaCy v2.0.0a10引入了一个新标志update_shared。该标志默认设置为False

如果我们在这一单个例子上进行几次更新,我们将得到一个能正确标注它的模型。然而,仅从一个例子,模型无法猜测它应该学习什么程度的泛化性。是所有单词现在都被标记为VBP?还是所有句子的第一个单词?还是所有“search”的出现?我们需要给模型更多关于我们寻求的解决方案的信息,否则学习问题将过于不受约束,我们不太可能得到我们想要的解决方案。

超越隐喻

为了明确这里的“遗忘”隐喻,我们可以说整体的多任务模型一开始“知道”如何为多种英文书面体裁标注实体并生成依存解析。然后我们专注于一些更具体的修正,但这导致模型失去了更通用的能力。这个隐喻让问题看起来很惊人:为什么我们的AI会如此愚蠢和脆弱?这正是在这一点上隐喻已经失去了它的用处,我们需要更精确地思考正在发生的事情。

当我们调用nlp.update()时,我们要求模型根据其当前权重产生分析。然后为每个子任务计算误差梯度,并通过反向传播更新权重。本质上,我们调整权重,直到得到一组权重,使得分析结果中的误差梯度接近零。任何产生零损失的权重集都是稳定的。

正则化对嵌入仍然有益

嵌入表定义了一个向量空间,因此参数值的变化与解的变化之间存在线性关系。在这种情况下,惩罚与初始值偏差的L2范数是合理的。

保留先前行为的一种方法是编码一个反对过多改变参数的偏置。然而,这种正则化惩罚并不总是我们想要的良好近似。在深度神经网络中,模型权重与其预测行为之间的关系是非线性的。非常深的网络可能完全是混沌的。我们真正关心的是输出,而不是参数值——所以这应该成为我们构建目标的方式。随着模型变得越复杂、越非线性,最好避免试图猜测参数应该是什么样子。

伪排练

所有这些都指向一个非常简单的建议来解决“灾难性遗忘”问题。当我们开始微调模型时,我们希望得到一个在新训练示例上正确,同时产生的输出与原始模型相似的解。这很简单:我们可以根据需要生成任意多的原始输出。我们只需要创建原始输出和新示例的某种混合。毫不奇怪,这并不是一个新的建议。

伪排练

代码语言:python
复制
revision_data = []
# 将初始模型应用于原始示例。你需要试验找到合适的修订文本数量。
# 过滤掉一些数据也可能有帮助。
for doc in nlp.pipe(revision_texts):
    tags = [w.tag_ for w in doc]
    heads = [w.head.i for w in doc]
    deps = [w.dep_ for w in doc]
    entities = [(e.start_char, e.end_char, e.label_) for e in doc.ents]
    revision_data.append((doc, GoldParse(doc, tags=tags, heads=heads,
                                         deps=deps, entities=entities)))

# 现在将先前的行为打乱混入新的微调数据中,并一起更新。
# 你可能希望对微调示例进行上采样(例如包含5个副本)。
# 这样可以在不改变修订数据与微调数据比例的情况下,使用更多样化的修订数据。
n_epoch = 5
batch_size = 32
for i in range(n_epoch):
    examples = revision_data + fine_tune_data
    losses = {}
    random.shuffle(examples)
    for batch in partition_all(batch_size, examples):
        docs, golds = zip(*batch)
        nlp.update(docs, golds, losses=losses)

这个过程中的一个关键细节是,你混入新素材中的“修订练习”不能由当前正在优化的权重生成。你应该保持生成修订材料的模型静态不变。否则,模型可能会稳定在平凡解上。如果你在流式处理示例,你需要在内存中保存两个模型副本。或者,你可以预先解析一批文本,然后使用这些标注来稳定你的微调。

这个方法还有一个待改进之处。目前spaCy将教师模型提供的分析与任何其他类型的金标准数据同等对待。这似乎不理想,因为模型使用对数损失。对于词性标注器,这意味着原始预测“80%置信度标签是‘NN’”被转换为“100%置信度标签是‘NN’”。更好的做法是使用教师模型返回的分布进行监督,或者使用对数损失。

总结

在计算机视觉和自然语言处理中使用预训练模型很常见。图像、视频、文本和音频输入具有丰富的内部结构,可以从大量训练样本中学习并跨任务泛化。这些预训练模型如果能够针对特定问题进行“微调”,则尤其有用。然而,微调过程可能会引入“灾难性遗忘”问题:找到了一个优化特定微调数据的解,但损失了泛化能力。

一些人建议使用正则化惩罚来解决这个问题。然而,这编码了一种偏好,即偏好参数空间中接近先前模型的解,而真正需要的是输出空间中接近先前模型的解。伪排练是实现这一目标的好方法:用初始模型预测一批示例,并将它们混入微调数据中。这代表了一个模型的目标:除了微调数据之外,其行为与预训练模型相似。FINISHED

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 某机构spaCy v2.0.0a10中的多任务学习
  • 超越隐喻
  • 正则化对嵌入仍然有益
  • 伪排练
  • 总结
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档