A few shot NER cook-off with concise-concepts!
ellaolsson

使用简洁概念的少样本 NER 竞赛!

2022年5月5日

David Berenstein (嘉宾)

Hola! 我是大卫,我喜欢烹饪 👨🏽‍🍳 和编程 👨🏽‍💻。

这篇文章将介绍 少样本学习命名实体识别 (NER),而无需依赖于通常仅适用于英语的复杂深度学习模型。我将向您展示如何在无需训练模型的情况下从食谱中识别成分。此外,我将使用 Rubrix 分析结果,并使用人机协作方法构建手动策划的训练集。这是一个视频,展示了我为这篇博文创建的 Rubrix 数据集

在我在 Pandora Intelligence 工作期间,我一直倡导创建酷炫的开源项目。因此最近,我编写了一个 spaCy 包,名为 concise-concepts,它可以用于少样本 NER。

您想及时了解我和 Pandora Intelligence 的最新动态吗?在 Github 上关注我们!

简洁概念

通常,预训练的 NER 模型经过训练以识别一组固定的 17 个通用实体标签,例如人物 (PER)、地点 (LOC) 和组织 (ORG)。对于我们的用例,我们希望识别成分,例如“水果”、“蔬菜”、“肉类”、“奶制品”、“香草”和“碳水化合物”。

理想情况下,我们希望训练一个自定义模型,这意味着我们需要足够高质量的带有新标签集的已标注训练数据。Rubrix 非常适合注释、改进和管理这些数据。但是,通常从头开始数据注释成本高昂或不可行,这就是为什么有一种方法可以“预注释”、评估免训练方法,甚至完全跳过数据注释过程可能非常有益。在 concise-concepts 中,少样本 NER 是通过依赖 word2vec 背后的原理来完成的。

像 word2vec 这样的模型是基于一个简单的想法设计的: 在相似上下文中使用的词语具有相似的含义。在训练期间,词语被映射到向量空间,其中相似的词语应该最终位于该向量空间内的相似区域。这使我们能够做两件事

  1. 检索 n 个最相似的词语。基于每个标签的几个示例,我们现在可以创建一个可能属于该标签的其他词语列表。使用此列表,我们可以通过查找完全匹配的词语来标记新数据。
  2. 比较词向量的相似性。在找到这些完全匹配的词语后,我们可以将它们的向量与整个组的向量进行比较。这种比较可以确定该词语对该组的代表性程度,这可以用于为识别的实体提供置信度评分。

想了解更多关于 concise-concepts 的信息吗?请随时观看此 YouTube 视频

概念验证(双关语意图)

在收集食谱数据并将数据实际上传到 Rubrix 之前。我将首先向您展示 concise-concepts 的功能。

import spacyfrom spacy import displacyimport concise_conceptsdata = {    "fruit": ["apple", "pear", "orange"],    "vegetable": ["broccoli", "spinach", "tomato", "garlic", "onion", "beans"],    "meat": ["beef", "pork", "fish", "lamb", "bacon", "ham", "meatball"],    "dairy": ["milk", "butter", "eggs", "cheese", "cheddar", "yoghurt", "egg"],    "herbs": ["rosemary", "salt", "sage", "basil", "cilantro"],    "carbs": ["bread", "rice", "toast", "tortilla", "noodles", "bagel", "croissant"],}text = """    Heat the oil in a large pan and add the Onion, celery and carrots.    Then, cook over a medium–low heat for 10 minutes, or until softened.    Add the courgette, garlic, red peppers and oregano and cook for 2–3 minutes.    Later, add some oranges and chickens. """nlp = spacy.load("en_core_web_lg", disable=["ner"])nlp.add_pipe("concise_concepts", config={"data": data, "ent_score": True})doc = nlp(text)options = {    "colors": {        "fruit": "darkorange",        "vegetable": "limegreen",        "meat": "salmon",        "dairy": "lightblue",        "herbs": "darkgreen",        "carbs": "lightbrown",    },    "ents": ["fruit", "vegetable", "meat", "dairy", "herbs", "carbs"],}ents = doc.entsfor ent in ents:    new_label = f"{ent.label_} ({float(ent._.ent_score):.0%})"    options["colors"][new_label] = options["colors"].get(ent.label_.lower(), None)    options["ents"].append(new_label)    ent.label_ = new_labeldoc.ents = entsdisplacy.render(doc, style="ent", options=options)

base demo displacy

收集数据

为了收集数据,我将使用 feedparser 库,它可以加载和解析任意 RSS 订阅源。我通过在 Google 上快速搜索“recipe data rss”找到了一些有趣的 RSS 订阅源。对于这篇文章,我选择了 FeedSpot 中的订阅源。请注意,我还使用 BeautifulSoup 清理了一些 HTML 相关的格式。

import feedparserfrom bs4 import BeautifulSoup as bsrss_feeds = [    "https://thestayathomechef.com/feed",    "https://101cookbooks.com/feed",    "https://spendwithpennies.com/feed",    "https://barefeetinthekitchen.com/feed",    "https://thesouthernladycooks.com/feed",    "https://ohsweetbasil.com/feed",    "https://panlasangpinoy.com/feed",    "https://damndelicious.net/feed",    "https://leitesculinaria.com/feed",    "https://inspiredtaste.com/feed",]summaries = []for source in rss_feeds:    result = feedparser.parse(source)    for entry in result.get("entries", []):        summaries.append(entry.get("summary"))summaries = [bs(text).get_text().replace(r"\w+", "") for text in summaries]summaries[7]

'烤羊腿用浓郁的柠檬汁、新鲜大蒜、迷迭香调味,并淋上用自身锅底汁和香草制成的酱汁。这是享用烤羊肉的最佳方式!\n博文《烤羊腿》最初发表在 thestayathomechef.com 上。'

doc = nlp(summaries[7])ents = doc.entsfor ent in ents:    new_label = f"{ent.label_} ({float(ent._.ent_score):.0%})"    options["colors"][new_label] = options["colors"].get(ent.label_.lower(), None)    options["ents"].append(new_label)    ent.label_ = new_labeldoc.ents = entsdisplacy.render(doc, style="ent", options=options)

rss demo displacy

调用 Rubrix

我们已经收集和清理了数据,并表明它也适用于新数据。现在让我们创建一个 Rubrix 数据集来探索、搜索,甚至注释/更正一些少样本预测。由于我在本地环境中运行,我决定使用 docker-compose 方法。简而言之,这会启动一个 ElasticSearch 实例来存储我们的数据,以及 Rubrix 服务器,它还带有一个漂亮的 Web 应用程序,网址为 http://localhost:6900/)。

对于我们的用例,我们正在进行实体分类,这属于 Token Classification 类别。太棒了!甚至还有一个专门针对 spaCy 的 教程

让我们使用我们的少样本 NER 管道来预测数据集并将预测记录在 Rubrix 中

import rubrix as rbimport spacy# Creating spaCy docdocs = nlp.pipe(summaries)records = []for doc in docs:    # Creating the prediction entity as a list of tuples (entity, start_char, end_char)    prediction = [(ent.label_, ent.start_char, ent.end_char, ent._.ent_score) for ent in doc.ents]    # Building TokenClassificationRecord    record = rb.TokenClassificationRecord(        text=doc.text,        tokens=[token.text for token in doc],        prediction=prediction,        prediction_agent="concise-concepts-cooking",    )    records.append(record)# Logging into Rubrixrb.log(records=records, name="concise-concepts")

我们现在可以访问 http://localhost:6900/,探索结果并开始为大多数标签进行基本注释。即使预测并不完美,但也肯定比从头开始要好。

rss demo displacy

现在让我们使用 Rubrix 的 Metrics 模块查看一些统计信息。这些指标可以帮助我们改进提供给 concise-concepts 的实体的定义和示例。例如,实体标签指标可以帮助我们概述 concise-concepts 中每个实体的出现次数。

from rubrix.metrics.token_classification import entity_labelsentity_labels("concise-concepts").visualize()

png

从上面我们可以看到,即使大多数膳食只包含一种肉类和多种蔬菜,MEAT 概念的识别频率也高于其他概念。这表明这个概念可能有点过于敏感。经过一些调查,结果表明 “烤”“酱汁” 是罪魁祸首,因为它们被识别为 MEAT

meat is trigger happy

在注释和验证了一些数据之后,我们能够根据一些评分指标评估我们的少样本模型的价值。我们可以使用它来评估对我们的输入数据进行潜在的微调,或在 concise-concepts 中扩展的词语数量。

from rubrix.metrics.token_classification import f1f1("concise-concepts").visualize()

png

查看这些结果,我们可以看到水果的预测非常好,仅使用了 3 个示例。其他标签的表现较差,这表明我们可能会从微调这些概念中获益。对于 MEAT,我们可能会将概念拆分为 MEATFISH。同样,表现不佳的 CARBS 可以拆分为 BREADGRAINS。而且,我们可能会将 HERBS 拆分为 HERBSCONDIMENTSSPICES

总结

我们还可以做很多事情,例如微调少样本训练数据、注释数据或训练模型。但是,我想我已经表达了我的观点,现在是时候进行简短的回顾了。

我们介绍了 少样本学习命名实体识别 (NER)。我们还展示了如何使用像 concise-concepts 这样的简单包来轻松地进行数据标记和实体评分,适用于任何语言,以及如何使用 Rubrix 存储和解释这些预测。最后,我已经展示了您如何将您的业余爱好完全过度设计成一个书呆子式的专业家庭烹饪!

别忘了,人生苦短,不要吃难吃的食物!