使用 EntityRuler 创建训练集

训练集简介

在这个笔记本中,我们将更深入地探讨训练集,了解它们是什么,为什么它们很重要,以及如何使用 spaCy 的 EntityRuler 自动化创建一个良好(而非优秀!)的训练数据集,该数据集将需要手动检查。在下一个视频中,我将向您展示如何使用这个训练集在 spaCy 中训练一个自定义的 NER 模型。

除了 spaCy 能够很好地扩展(意味着它可以在小数据和大数据上表现良好)之外,它还很容易定制,并且可以执行高级机器学习方法,而无需具备机器学习的知识。然而,了解机器学习的基本知识(如本系列笔记本 03 中讨论的那样)是有帮助的,因为它将使您能够了解如何培养良好的训练集,以及为什么某些方法可能会失败或遇到困难。实际上,您将通过实践来培养对机器学习 NER 中什么有效、什么无效的感觉。

在本系列的第三本笔记本中,我提到训练机器学习模型的数据存在三种形式:训练数据、验证数据和测试数据。所有这些数据都将具有相同的形式。它将是一个列表数据结构,其中每个索引将包含一个文本(一个句子、段落或整个文本)。文本的长度将取决于您希望通过 ML NER 实现的目标。文本的大小将影响训练过程。然而,现在让我们忽略这一点。训练数据还需要另一个组件,即文本中实体的列表,包括其实体开始位置、结束位置和标签。在训练过程中,这些注释将允许卷积神经网络(spaCy 机器学习训练过程背后的架构)从数据中学习,并能够正确识别您正在训练的实体。

spaCy 训练集是什么样的?

SpaCy 要求您的训练数据具有非常特定的格式:

TRAIN_DATA = [ (TEXT AS A STRING, {“entities”: [(START, END, LABEL)]}) ]

请注意,TRAIN_DATA 是大写的。在 Python 中,通常不将对象大写,但有少数例外。TRAIN_DATA 就是其中之一。我不知道这个约定的历史,但在每一本书/教程中,你都会看到 TRAIN_DATA 都是这样写的。当然,这不是必需的,但始终在代码中尽可能遵循 Python 风格是一种好习惯,这样别人就能更容易地阅读你的代码。任何机器学习从业者都会期望看到 TRAIN_DATA 是这样写的。

获取训练数据到这种格式非常困难,手动操作。研究人员必须计算字符来分配实体的起始和结束位置。即使你考虑使用 Python 内置的字符串函数来获取起始和结束字符,你也会遇到另一个问题。spaCy 的训练过程读取起始和结束字符的方式与你可能使用字符串函数计数的方式不同。这意味着在训练过程中,spaCy 将丢弃与标记起始和结束位置不匹配的注释。这是因为与你的字符串函数对文本进行分词的方式相比,spaCy 的分词方式不同。幸运的是,spaCy 通过 EntityRuler 内置了解决方案,以帮助你在这一过程中。

如果你对手动标注感兴趣,我强烈建议你探索来自 Explosion AI 的付费软件 Prodigy(https://prodi.gy/)。我并不是为了推广该产品而获得报酬。它很贵,但如果你需要做大量的标注(针对图像、文本、视频甚至音频),那么 Prodigy 就是你的工具。它有一个很好的用户界面,并且因为它是由为我们提供 spaCy 的同一团队开发的,所以它可以无缝地融入 spaCy 的工作流程。你可以在这里探索 Prodigy 的演示:https://prodi.gy/demo。

创建训练集

在下面的代码中,我们将通过 EntityRuler 创建 spaCy 机器学习训练集。换句话说,我们将使用基于规则的方法自动生成一个基本训练集。这个训练集会有错误吗?可能会。这就是为什么查看训练集并手动验证是个好主意。然而,以这种方式做,你可以大大增加原型设计,以查看你想要训练的定制实体是否有潜在可行性。在机器学习中,很少有针对特定领域的具体解决方案。如果有,人们就不需要专家了。实验通常是机器学习的游戏名称,NER 机器学习也不例外。

我们将创建一个空白英语模型,因为我们只会临时使用这个模型。我们不需要其他组件。这个模型将只包含 EntityRuler,我们将临时使用它来生成训练集。回想一下我们上一个笔记本,spaCy 的小型模型无法正确识别 Treblinka 作为地点吗?在下面的代码中,我们将从这三句话中创建一个基本的训练集,这将使我们能够生成一个非常小的训练集。我想明确一点。这些训练数据远远不足以训练一个模型。然而,这个过程扩展得非常好。

这里是之前看到的相同代码,但文本略有不同。注意输出结果。它已经正确地将 Treblinka 识别为 GPE。

#Import the requisite library
import spacy

#Build upon the spaCy Small Model
nlp = spacy.blank("en")


#Sample text
text = "Treblinka is a small village in Poland. Wikipedia notes that Treblinka is not large."

#Create the EntityRuler
ruler = nlp.add_pipe("entity_ruler")

#List of Entities and Patterns
patterns = [
                {"label": "GPE", "pattern": "Treblinka"}
            ]

ruler.add_patterns(patterns)

doc = nlp(text)

#extract entities
for ent in doc.ents:
    print (ent.text, ent.label_)

现在,我们将稍微修改一下这段代码,以便我们可以生成一个略有不同的输出,一个包含文本起始和结束位置的输出。

#Import the requisite library
import spacy

#Build upon the spaCy Small Model
nlp = spacy.blank("en")

#Sample text
text = "Treblinka is a small village in Poland. Wikipedia notes that Treblinka is not large."

#Create the EntityRuler
ruler = nlp.add_pipe("entity_ruler")

#List of Entities and Patterns
patterns = [
                {"label": "GPE", "pattern": "Treblinka"}
            ]

ruler.add_patterns(patterns)

doc = nlp(text)

#extract entities
for ent in doc.ents:
    print (ent.text, ent.start_char, ent.end_char, ent.label_)

注意现在,我们的输出为每个实体的起始和结束位置分别是 0,9 和 61,71。有了这些数据,我们现在可以开始生成我们想要的输出。然而,让我们先尝试将输入文本分解成句子,然后拥有两组不同的训练数据。

#Import the requisite library
import spacy

#Build upon the spaCy Small Model
nlp = spacy.load("en_core_web_sm")

#Sample text
text = "Treblinka is a small village in Poland. Wikipedia notes that Treblinka is not large."

#Create a blank list for appending later.
corpus = []

doc = nlp(text)

#use the spacy tokenizer to get the sentences.
for sent in doc.sents:
    corpus.append(sent.text)

print (corpus)


#Build upon the spaCy Small Model
nlp = spacy.blank("en")

#Create the EntityRuler
ruler = nlp.add_pipe("entity_ruler")

#List of Entities and Patterns
patterns = [
                {"label": "GPE", "pattern": "Treblinka"}
            ]

ruler.add_patterns(patterns)



#iterate over the sentences
for sentence in corpus:
    doc = nlp(sentence)

    #extract entities
    for ent in doc.ents:
        print (ent.text, ent.start_char, ent.end_char, ent.label_)

注意现在我们得到了一个不同起始和结束的输出。现在,我们可以再次修改我们的代码,使其符合我们想要的格式:

#Import the requisite library
import spacy

#Build upon the spaCy Small Model
nlp = spacy.load("en_core_web_sm")

#Sample text
text = "Treblinka is a small village in Poland. Wikipedia notes that Treblinka is not large."

corpus = []

doc = nlp(text)
for sent in doc.sents:
    corpus.append(sent.text)

#Build upon the spaCy Small Model
nlp = spacy.blank("en")

#Create the EntityRuler
ruler = nlp.add_pipe("entity_ruler")

#List of Entities and Patterns
patterns = [
                {"label": "GPE", "pattern": "Treblinka"}
            ]

ruler.add_patterns(patterns)


TRAIN_DATA = []

#iterate over the corpus again
for sentence in corpus:
    doc = nlp(sentence)
    
    #remember, entities needs to be a dictionary in index 1 of the list, so it needs to be an empty list
    entities = []
    
    #extract entities
    for ent in doc.ents:

        #appending to entities in the correct format
        entities.append([ent.start_char, ent.end_char, ent.label_])
        
    TRAIN_DATA.append([sentence, {"entities": entities}])

print (TRAIN_DATA)

练习

为了本视频中的练习,我想让你尝试用你熟悉的语料库来复制这个过程。制定一系列规则来识别你想要识别的几个或许多实体。不用担心找到所有实体。这样做的目的是生成一个足够好的训练集,具有足够的多样性,以便在下一笔记本中教授机器学习模型。在下面的视频中,我会向你展示如何使用《哈利·波特》第一本书中的角色在大规模上完成这个操作。