知识图谱与 LLM 的实战应用——从非结构化数据中抽取领域特定知识

8 阅读35分钟

本章涵盖

  • 从非结构化数据构建知识图谱
  • 档案管理的复杂性:以洛克菲勒档案中心为例
  • 使用大型语言模型抽取实体与关系

到目前为止,我们讨论的知识图谱(KG)主要建立在结构化数据之上,例如表格、知识库等。那么,非结构化数据怎么办?想想电子邮件、聊天记录、法律文本、研究论文、新闻文章、社交媒体内容等等——这个世界充斥着以非结构化形式存在的信息与知识。利用这些数据源,能够为你的业务提供极具价值的信息。

将非结构化数据转化为知识,这项任务涉及数据摄取与处理、各种自然语言处理(NLP)技术、数据增强、机器学习(ML)处理,以及用于构建下游应用的数据建模。从概念上看,这一过程面临两个主要挑战:

知识表示(如第 2 章所述)指的是如何对信息进行建模,使计算机(以及人类)能够自主访问这些信息,并用它来完成任务。如果设计得当,它可以通过让概念具备可复用性与可扩展性来加快处理速度。在这种语境下,KG 代表的是一种有序、互联的信息形式,而这些信息原本是孤立的、分散的、无组织的。

知识学习 则结合了多种框架与技术,例如 NLP 和大型语言模型(LLM),从非结构化文档中挖掘洞见。

为了说明从文本数据构建 KG 所涉及的复杂性,本章将深入一个示例项目,并展示如何克服从非结构化信息中挖掘知识时遇到的一些挑战。

5.1 档案挑战

档案通常覆盖广泛的主题和概念,横跨当代与历史数据,并纳入复杂度和语言风格各不相同的数据源。这些数据通常都是非结构化的,包括书籍、报告、会议纪要、资助分配记录等来源。在本章和下一章中,我们将聚焦于洛克菲勒档案中心(Rockefeller Archive Center,RAC)保存的一部分历史数据,这些数据与现代科学学科的起源有关。

RAC 不仅仅是历史与当代文献的存储库,它也是一个研究中心,致力于研究慈善事业以及受美国基金会、个人捐赠者和公民社会组织影响的研究领域。它保存着 40 多家慈善基金会、研究机构和文化组织的档案记录,其中包括洛克菲勒基金会和福特基金会,并向全球研究人员开放访问。

洛克菲勒基金会

洛克菲勒基金会成立于 1913 年,由约翰·D·洛克菲勒、其子以及他们的商业顾问弗雷德里克·泰勒·盖茨共同创立,是美国最早的大型慈善机构之一。在他所处的时代,洛克菲勒是美国历史上最富有的人:在其鼎盛时期,他控制了全国 90% 的石油生产。他担心自己的继承人可能会“挥霍他们继承的财富,或者沉醉于权力之中”[1],因此盖茨鼓励他建立“为了全人类福祉而永久存在的公司化慈善机构”。为实现这一目标,洛克菲勒与其他工业巨头——包括钢铁大王安德鲁·卡内基——共同奠定了现代定向慈善的模式,并创建了以他们名字命名的基金会。

我们的目标是揭示资助授予流程在幕后是如何运作的。为此,我们将设计一个高质量、面向特定领域的知识抽取系统,并遵循图 5.1 中心理模型所概述的步骤。

image.png

图 5.1 从领域特定的非结构化文本数据到结构化知识的路径。
每一步都依赖最先进的机器学习模型,例如用于文档数字化的光学字符识别、命名实体识别,以及关系抽取系统。

洛克菲勒基金会在为项目选择资助对象时,依赖项目官员来识别值得资助的研究方向。这些官员通过构建研究人员网络,并从这些网络之外寻找推荐,逐渐形成对各自专业领域的深厚理解。他们会在日记中记录会议、电话和晚宴的笔记,通常是匆忙地、用打字机写下,常常带有速记、缩写和领域特定术语。这些日记既是细节丰富的知识宝库,也包含大量职业和个人观察。如果能够被恰当地挖掘并建模,我们就可以利用这些信息来回答如下问题:

  • 某个想法获得资助之前,通常会出现哪些模式?
  • 获批项目是否往往遵循有影响力科学家或以往受资助者的推荐?
  • 一项资助被授予之前,通常会经历多少次内部讨论?
  • 各个子学科的资助是否存在趋势?这些趋势是否会随时间变化?

本章将展示如何将最先进的 ML 技术调整到特定领域,并从非结构化数据中挖掘复杂知识。在下一章中,我们将利用这些抽取出的信息构建一个 KG,并回答那些此前被锁定在非结构化文档中的复杂关系型问题。

5.2 知识抽取的关键概念

将文本数据转化为结构化 KG,核心在于两个基础过程:识别文本中的实体,以及抽取连接这些实体的关系。这两个相互关联的组成部分构成了任何 KG 的结构骨架。本节重点讨论知识抽取的这些构件。

5.2.1 识别命名实体

从文本(或其他非结构化数据源)走向 KG 的第一步,是命名实体识别(named entity recognition,NER)。NER 是一种 ML 分类系统,经过训练后能够在原始文本中识别命名实体的提及,并将其归入预定义类别。识别命名实体具有许多业务应用场景:

  • 通过连接来自不同数据源的文档来发现洞见(例如,将财务文档中提到的人与商业登记信息关联起来)
  • 提升企业的信息管理和数据治理能力
  • 为数据合规系统打下基础
  • 改善搜索能力
  • 建立因果关联(例如,天气条件与航班延误之间的关系)

许多开源 NLP 库都开箱即用地提供带有通用实体类别(如 Person、Location、Organization 等)的模型。但这通常远远不够。例如,图 5.2 展示了洛克菲勒基金会项目官员 Warren Weaver 日记中的一段记录。在这个案例中,识别人名和组织名很重要,但同样重要的是识别谈话主题。此外,Topic 实体还可细分为三种子类型:研究学科、技术或疾病。

image.png

图 5.2 Warren Weaver 日记片段,其中高亮了相关命名实体

该日记示例中提到了四个 discipline 类型的话题:“aerological research(高空气象研究)”、“meteorological research(气象研究)”、“polar air masses(极地气团)”和“temperate air masses(温带气团)”。一个基于简单词典的 NER 系统无法识别它们。为了处理这项任务,我们需要一个定制的实体抽取系统。实现这一点,要么训练一个定制 NER 模型,要么像我们稍后将看到的那样,借助 LLM。

5.2.2 关系抽取

从文本走向 KG 的第二步是关系抽取(relation extraction,RE):即识别文本文档中实体对之间的语义关系。例如,考虑下面这句话:

“Jane Austen, Victorian era writer, is currently employed by Google.”

这段文本中提到了 PERSON 类别的命名实体“Jane Austen”和 ORGANIZATION 类别的命名实体“Google”,而且这两个实体之间关系密切:一方是另一方的雇员。能够捕捉这种关联性,才是真正构建 KG 的关键。

注意
这种关系可以有多种建模方式,例如:

  • Jane Austen – WORKS_FOR -> Google
  • EMPLOYS: Jane Austen <– EMPLOYS - Google

重要的是,在跨文档建模时要保持一致。

5.3 使用大型语言模型构建 KG

到目前为止,我们讨论的是“传统”NLP:构建特定任务的训练数据集、识别最佳模型架构、微调超参数等。而这一过程中的一个重要部分,是提高训练数据的质量。说得委婉一点,这项工作非常艰巨。

2022 年底,OpenAI 发布了 GPT-3.5 系列的首批模型,包括具备惊人能力的聊天机器人 ChatGPT:只要基于自然语言提出问题或描述任务,它就可以帮助你起草信件、总结文章、回答事实性问题、翻译食谱、生成指定图像、编写 Python 代码等等。这类生成式模型被称为大型语言模型,它构建在 Transformer 架构之上 [2]。其核心概念是迁移学习:把在通用任务(例如预测被随机遮蔽的 token)中学到的语言模式和知识,复用到另一项任务(例如 RE)中。这大幅降低了监督学习所需训练数据集的规模。因为我们不必从头训练模型,而是复用在海量数据集上学得的权重,所以用更少的人工标注数据,也能达到相同精度。

LLM 的卓越之处,在于模型复杂度(参数数量)以及训练语料的规模与质量。更大的神经网络模型(即拥有更多可训练参数)只需要更少的数据样本,就能在测试集损失上达到相同表现 [3]。这并不令人意外:模型可学习的数据越多,效果通常越好。但这不仅仅关乎数据量,也关乎数据质量。在传统 ML 中,人们往往主要关注寻找最佳模型架构和微调超参数。这种以模型为中心的方法,在极端情况下会忽视潜在的数据质量问题——而“垃圾进,垃圾出”并不应让人意外。随着时间推移,一种以数据为中心的范式 [3] 日益受到重视,它强调通过数据工程提升用于训练高复杂度 ML 模型的数据质量(以及数量)。因此,今天的 LLM 已经足够强大,我们只需用自然语言来表述任务(即 prompt engineering,提示工程),模型就能生成答案,而无需再进行模型工程设计。

尽管 LLM,特别是 OpenAI 的 GPT 系列模型,能力非常强大,但在设计依赖它们的系统时,我们必须牢记一些局限性。对我们这个示例来说,最重要的一点是幻觉:也就是模型在训练数据中没有依据时,会虚构“事实”并进行错误推理的倾向。例如,如果我们询问 ChatGPT NASA 的 SLS 火箭造价,但它的训练数据中并不包含这类信息,那么它可能会随便给出一个数字,并声称那就是成本。OpenAI 团队一直在努力减轻这种行为,但在本书写作时,这仍然是一个开放问题。

尽管这些模型成就显著,我们仍不愿称其为真正的“AI”;距离实现真正的人工智能,还有很长的路要走。相反,我们建议将 LLM 用于构建更加完整、更加干净的 KG,让它们帮助抽取高质量的命名实体、关系和属性;图 5.3 中的心理模型可以帮助你回忆这一点。

image.png

图 5.3 将文本数据转化为结构化知识的心理模型:使用 LLM 进行 NER 与 RE

5.3.1 使用 LLM

LLM 可以直接开箱即用地帮助构建 KG,通过基于提示的推理完成任务;也可以通过微调,使其在你的领域内输出更加准确、更加稳定。通常,如果我们仅仅用自然语言描述任务,并(可选地)给出一个示例,LLM 就能开箱即用地表现良好。对任务的这种表述叫作 prompt(提示) ,而为了给定任务不断迭代、寻找表现最优的 prompt 的过程,则称为 prompt engineering(提示工程)

在本章中,我们关注的是文本补全:我们给模型一个 prompt,它会根据 prompt 中的上下文和任务描述来生成文本。我们的目标是利用 GPT 的能力,从文本输入中识别实体及其关系。这个过程在图 5.4 中做了示意说明。

image.png

图 5.4 从文本数据生成 KG 时 LLM 的两种角色:基于 prompt 的方法(上分支)和微调方法(下分支)

和数据科学项目一贯的流程一样,我们从数据探索开始:我们需要理解自己所面对的领域、前方的挑战,以及对结果形成合理预期。例如,还记得本章前面提到的那段日记吗?受访者 C. G. Rossby 只在开头被完整写出全名,之后就被简称为“R.”。因此,我们需要以一种能够解析这种不常见指代方式的方式来设计知识抽取系统。

下一步是提示工程。通常,需要经过若干轮迭代,才能识别出最有价值的 prompt:有时仅仅改变几个词,就会对性能产生显著影响。我们需要尽可能精确地描述任务,以减少歧义,避免给模型留下过度发挥想象力的空间。

完成 prompt engineering 之后,我们要么会对回复质量满意,直接进入 KG 生成;要么会发现任务太复杂,无法单纯依赖基于 prompt 的方法。在后一种情况下,我们将准备一个小型训练数据集,其中包含 prompt(仅限纯输入文档,不再附带任务描述)以及预期输出。(我们的提示工程工作并不会浪费,因为可以用它先对数据集进行预标注,再由人工补全标注。)一旦数据集准备完成,我们就可以使用 OpenAI 的 API 对模型进行微调。如果第一轮之后就满意了,就可以用它生成 KG;如果还不够好,就重复这个过程:加入更多训练数据,再试一次。这个过程需要花费时间和金钱,但最终我们会获得一个专门适配本领域的模型,从而提供更加稳定、更加准确的输出。

5.3.2 提示工程示例

让我们更仔细地看看提示工程。下面这个 prompt 要求 GPT 识别所有实体及其关系。

代码清单 5.1 Prompt 版本 1:识别实体与关系

prompt_segments = dict()

prompt_segments['task'] =   #1
"""You are an expert on constructing Knowledge 
↪Graphs from texts using named entity recognition and relation extraction.
↪Given a prompt, identify as many entities and relations among them as 
↪possible and output a list of relations in the format [ENTITY 1, ENTITY 1 
↪TYPE, RELATION, ENTITY 2, ENTITY 2 TYPE]. 
↪The relations are directed, so the order matters."""

prompt_segments['example'] = "J.R.Smith (Prof. Phys.) is employed by MIT 
↪and mentioned another scientist, Mary Hodge, who works on cyclotron 
↪research."   #2

prompt_segments['example_output'] =   #3
"""["J. R. Smith", "person", "has 
↪title", "Professor of Physics", "title"]
["J. R. Smith", "person", "works for", "MIT", "organization"]
["J. R. Smith", "person", "talked about", "Mary", "person"] 
["Mary", "person", "works on", "cyclotron", "occupation"]"""
#1 General task formulation
#2 An example: especially useful for complex tasks such as RE
#3 States the expected output

#1 通用任务描述
#2 一个示例:对于 RE 这类复杂任务尤其有用
#3 说明预期输出格式

我们可以通过下面这个 OpenAI API 调用,把该 prompt 应用于一段日记片段。

代码清单 5.2 OpenAI API 调用

import os
import time
from dotenv import load_dotenv
from openai import OpenAI
from listing_2 import prompt_segments

_ = load_dotenv()   #1

OPENAI_MODEL = "gpt-4o-mini"

def openai_query(client, prompt_segments: dict,   #2
↪query: str): 
    messages = [
      {"role": "system", "content": prompt_segments['task']},
      {"role": "user", "content": prompt_segments['example']},
      {"role": "assistant", "content": prompt_segments['example_output']},
      {"role": "user", "content": query}
    ]

    t_start = time.time()
    response = client.chat.completions.create(model=OPENAI_MODEL, 
    ↪messages=messages, temperature=0., max_tokens=2000)   #3
    print(response.choices[0].message.content)
    print(f"\nTime: {round(time.time() - t_start, 1)} sec\n")

    return response.choices[0].message.content


if __name__ == "__main__":
  client = OpenAI(api_key=os.environ['OPENAI_API_KEY'])

  text = """  #4
  ↪JOHNS HOPKINS UNIVERSITY Chemistry Department:
  ↪Wednesday, November 9, 1932
  ↪WW visits the Dept. with Dr. Frazer (Baker Prof. Chem.). There are 239 
  ↪undergraduate and 116 graduate students of chemistry, the latter group in-
  ↪cluding holders of the special State fellowships in chemistry under the
  ↪New Plan. D.H. Andrews (Prof.Chem.) is a physical chemist specializing in
  ↪thermodynamics. He is not present at the time of WW's call, but one of his
  ↪assistants explains his work. He is measuring specific heats of organic
  ↪compounds by a straight calorimetric method. This work is in its early
  ↪stages. He is also interested in making mechanical models of various atoms
  ↪from which can be demonstrated the theory of the Raman spectra.
  ↪J.B.Mayer (Assoc. in Chem.) is a former student of G. N. Lewis and works
  ↪with Max Born at Gottingen summers. He specializes in the energetics of
  ↪crystal lattices. His wife, last summer, prepared the new edition of
  ↪Born's treatise on this subject. In Mayer's laboratory Mrs. Wintner, wife
  ↪of the mathematician, is working on an experimental problem. Andrews says 
  ↪that Mayer is young and impresses one as an enthusiastic and able man.
  ↪"""

openai_query(client, prompt_segments, text)

#1 从 .env 文件中加载 OpenAI API key
#2 运行无状态 ChatGPT API 查询的函数
#3 指定模型、temperature 和其他参数
#4 待处理文本

结果令人鼓舞。例如,抽取出来的、涉及 D. H. Andrews 和 J. B. Mayer 的关系如下所示。

代码清单 5.3 清单 5.2 中 prompt 的 GPT 输出

["D.H. Andrews", "person", "has title", "Prof. Chem.", "title"]
["D.H. Andrews", "person", "specializes in", "thermodynamics", "field"]
["D.H. Andrews", "person", "is measuring", "specific heats of organic 
↪compounds", "research"]
["D.H. Andrews", "person", "is interested in", "mechanical models of 
↪various atoms", "research"]
["D.H. Andrews", "person", "demonstrates", "theory of the Raman spectra", ↪"theory"]
["J.B. Mayer", "person", "has title", "Assoc. in Chem.", "title"]
["J.B. Mayer", "person", "is a former student of", "G. N. Lewis", "person"]
["J.B. Mayer", "person", "works with", "Max Born", "person"]
["J.B. Mayer", "person", "works at", "Gottingen", "location"]
["J.B. Mayer", "person", "specializes in", "energetics of crystal 
↪lattices", "field"]

非常惊艳!D. H. Andrews 的确是一位化学教授,而且他确实参与了所有被识别出来的研究主题。类似地,我们也得知了 J. B. Mayer 的头衔、专长,以及他曾师从何人。更重要的是,这些关系中出现的是完整姓名,尽管日记中其实只写了“Mayer”或“he”。在传统 RE 流程中,通常会先用 NER 模型识别实体提及(如“Mayer”),然后再借助共指消解模型把“Mayer”和“he”链接到完整姓名,最后再用 RE 获取关系及消解后的名称。而在这里,由于 LLM 的生成式特性,共指消解是隐式完成的:模型基于对整篇文档上下文的理解来生成输出。

最后,请注意,这些头衔也被从“Prof. Chem.”和“Assoc. in Chem.”解析成了“Professor of Chemistry”和“Associate in Chemistry”。模型从缩写形式中推断出了正确的完整头衔——这是传统 NER 模型根本做不到的。

现在让我们来看实体关系类型。请注意它们极端细粒度的特征:specializes in、is measuring、is interested in 和 demonstrates 都是准确的,但这种输出对于 KG 来说真的有用吗?它们本质上表达的是同一个概念。人类很可能会把它们统一归到 works on 这样的关系类型下,但 GPT 却给出了四种不同类型!而这还只是来自一个短短的段落。想象一下,如果我们处理成千上万页文档,然后又想回答诸如“谁在从事有机化合物研究?”这样的问题,KG 的 schema 会变成什么样。为了不漏掉那些本质上与 works on 同义或近义的关系类型,我们不得不在 Cypher 查询中列出所有它们。说得客气一点,这几乎不可操作。

节点标签也是同样的问题。本段中四个研究主题,被赋予了三种不同标签:field、research、theory。从本质上讲,这些都不算错。但它们究竟是什么?它们其实都是 Andrews 教授的研究主题,因此人类很可能会把它们都统一称为 topic 或 occupation,并在图中只使用一个相应的节点标签。

最后,你是否注意到输出中缺失了 Mayer 的性格特征?又或者模型忽略了“straight calorimetric method”这段提及,以及与“theory of Raman spectra”有关的关系?于是我们决定再试一次。我们在相同文本上,使用同样的 prompt 和同样的模型配置,再次生成实体和关系,以测试预测稳定性:也就是,同一个 LLM 是否会在第二次运行时给出相同输出。答案是否定的。比如,在第二轮输出中,第一轮正确识别出的 theory of Raman spectra 消失了,straight calorimetric method 也没了。模型承认 Andrews 谈到了 Mayer 的性格特征(我们得到了关系 ["D.H. Andrews", "person", "describes", "J.B. Mayer", "person"]),但并没有捕捉 Andrews 对 Mayer 究竟说了什么。这个结果表明:如果仅依赖“识别所有实体及其关系”这种通用风格的 prompt,在从文档中挖掘知识并生成用于下游分析与任务的 KG 时,会出现预测不稳定的问题。

那么,我们来测试一个新的 prompt 版本,如清单 5.4 所示。为了应对归一化问题,我们在 prompt 中加入了实体类别和关系类型的列表,并为其中一些表现不佳的关系类型附上说明。

提示
与其使用这些简单列表,我们本来也可以在这里直接给出完整、权威的 KG schema。但当前这种方式给了模型更大的空间,可以输出一些我们此前尚未想到的实体和关系。

我们还在任务描述中加入了两条说明,以帮助引导模型并提升预测稳定性:一条解释文档中常常用姓名首字母来指代人物的习惯,另一条则说明大学及院系名称常会出现别名或缩写。

代码清单 5.4 Prompt 版本 2:识别特定实体与关系

prompt_segments = dict()

prompt_segments['task'] = """
...   #1

Entities of interest: person, location, organization, date, occupation 
↪(a.k.a. person's work, specialization, research discipline, interests, 
↪occupation, technology).   #2

↪Top relations of interest: "works for", "works with",  #3
↪"student of" (link students with their teachers/advisors), "talked about"
↪(a person talking about another person), "talked with" (a person talking 
↪with another person), "works on" (assignment of persons to 
↪their occupation, work, specialization, research
↪discipline, interests etc.).

↪Note that persons are often first referenced by their full name, and 
↪then mentioned only by their surname or initials, for example: "A. N. 
↪Richards" becomes "Richards", "ANR", or just "R.".

↪Note that organizations (universities, their departments) are often 
↪shortened, for example: "University of California" is written as "U. of 
↪Cal." or just "U. Cal.", "Department of Physics" is written as "Dept. Phys."
↪etc."""

prompt_segments['example'] = "..."   #4
prompt_segments['example_output'] = """..."""
#1 The same task description as in listing 5.1.
#2 List of the most important entity classes
#3 List of the most important relation classes
#4 Same as in listing 5.1

#1 与清单 5.1 相同的任务描述
#2 最重要的实体类别列表
#3 最重要的关系类别列表
#4 与清单 5.1 相同

结果表明,在 prompt 中明确指出关注的实体与关系,可以引导 LLM 产生更加归一化的输出。下面是它对 D. H. Andrews 和 J. B. Mayer 所抽取出的关系。

代码清单 5.5 清单 5.4 中 prompt 的 GPT 输出

["D. H. Andrews", "person", "has title", "Professor of Chemistry", "title"]
["D. H. Andrews", "person", "works on", "thermodynamics", "occupation"]
["D. H. Andrews", "person", "measures", "specific heats of organic 
↪compounds", "occupation"]
["D. H. Andrews", "person", "is interested in", "mechanical models of 
↪various atoms", "occupation"]
["D. H. Andrews", "person", "talked about", "Raman spectra", "occupation"]
["J. B. Mayer", "person", "has title", "Associate in Chemistry", "title"]
["J. B. Mayer", "person", "is a former student of", "G. N. Lewis", ↪"person"]
["J. B. Mayer", "person", "works with", "Max Born", "person"]
["J. B. Mayer", "person", "specializes in", "energetics of crystal 
↪lattices", "occupation"]

所有与工作相关的话题,现在都被赋予了相同的实体标签:occupation。头衔也以完整形式给出:Professor of Chemistry 和 Associate in Chemistry。并且 works on 关系终于出现了一个统一实例,这确实是进步,尽管输出里依然同时存在 measures 和 specializes in。这一次我们得到的是 Raman spectra,而不是 theory of Raman spectra;两者都算正确,但至少,我们希望模型在不同运行和不同文档之间保持一致。可是,“straight calorimetric method”这条关系又去哪了?虽然只花几分钟调 prompt 就得到了这样的结果,已经相当不错,但我们仍然可以做得更好。

让我们再做一次迭代,这一次我们扩展示例,希望它能提供更清晰的指导并提高预测稳定性。works on 关系对我们很重要,因此我们在示例中又加入了一次 occupation 的提及。我们还给出了一个 student of 的例子,因为之前的 prompt 在这个关系上表现不佳。

代码清单 5.6 Prompt 版本 3:更复杂的示例

prompt_segments = dict()

prompt_segments['task'] = """..."""   #1

prompt_segments['example'] = "J.R.Smith, Prof. Phys. is employed by MIT andmentioned another colleague Mary Hodge, who studies along with hermaster's student John Smith radioisotopes produced by cyclotron."

prompt_segments['example_output'] = """["J. R. Smith", "person", "has 
↪title", "Professor of Physics", "title"]
["J. R. Smith", "person", "works for", "MIT", "organization"]
["J. R. Smith", "person", "talked about", "Mary Hodge", "person"] 
["Mary Hodge", "person", "works for", "MIT", "organization"]
["John Smith", "person", "student of", "Mary Hodge", "person"]
["Mary Hodge", "person", "works on", "radioisotopes", "occupation"]
["Mary Hodge", "person", "works on", "cyclotron", "occupation"]"""
#1 The same task formulation as in listing 5.1

#1 与清单 5.1 相同的任务描述

我们再来详细看看与 D. H. Andrews 和 J. B. Mayer 有关的输出。

代码清单 5.7 清单 5.6 中 prompt 的 GPT 输出

["D. H. Andrews", "person", "has title", "Professor of Chemistry", "title"]
["D. H. Andrews", "person", "works for", "Johns Hopkins University 
↪Chemistry Department", "organization"]
["D. H. Andrews", "person", "works on", "thermodynamics", "occupation"]
["D. H. Andrews", "person", "works on", "specific heats of organic 
↪compounds", "occupation"]
["D. H. Andrews", "person", "works on", "calorimetric method", ↪"occupation"]
["J. B. Mayer", "person", "has title", "Associate in Chemistry", "title"]
["J. B. Mayer", "person", "works for", "Johns Hopkins University Chemistry 
↪Department", "organization"]
["J. B. Mayer", "person", "student of", "G. N. Lewis", "person"]
["J. B. Mayer", "person", "works with", "Max Born", "person"]
["Max Born", "person", "works at", "Gottingen", "location"]
["J. B. Mayer", "person", "works on", "energetics of crystal lattices", ↪"occupation"]

成功了!我们达成了想要的一切:在实体和关系类别分配上获得了高度稳定性,并且正确识别出了我们关心的所有关系。

注意
这个例子展示了 prompt 演化的一个可能过程。在真实项目中,我们又多做了几轮迭代,并把输出格式改成了 JSON,这样每个实体和关系在需要时都可以带有属性。这使我们能够抽取更复杂的知识,比如每条 TALKED_ABOUT 关系的情感倾向(作为属性存储),以及业务头衔(作为 WORK_FOR 关系的属性)。若想查看本章展示的 KG 在实际生成时所使用的最终 prompt 版本,请参阅本书的代码仓库。

请注意,本章中的 prompt 是为 ChatGPT 模型设计的,并在 GPT-4o mini 上完成测试。当你读到本书时,更新的模型可能已经出现。这就是为什么我们更关注基础原则,而非特定模型本身——你应该能够把这些示例 prompt 适配到当前最先进的模型上。

5.3.3 提示工程指南

LLM 正在快速演进,因此没有必要给出那些很快就会过时的技术性细则。不过,一些一般性的经验教训是可以迁移到新模型上的。我们在 RAC 项目中总结出的关键原则,概括如下。

任务描述与领域特定指导

一个解释清晰的任务描述,对于输出质量与稳定性至关重要。值得尝试不同的表述方式,并加入当前数据集的特殊性。例如,在我们的案例中,文档里经常用缩写和首字母来指代人物,这一点就应明确告知模型。

实体类别与关系类别的命名

在 prompt 中,加入你最关注的实体类别和关系类别列表。这有助于澄清任务,并促使模型输出更归一化的结果。所选择的术语非常重要!如果你发现某个实体类或关系类表现不佳,试着给它改名。比如,最开始我们把日记中谈话的主题叫作 Topic,但后来我们意识到,这个词对于实体类别来说过于泛泛。当我们把它改名为 Occupation 后,内容完全相同的 prompt 却产生了更全面、更稳定的结果。

复杂且具有代表性的示例

对于实体 RE 这类复杂任务,在 prompt 中提供输入与预期输出示例是非常有用的。这个示例可以是你实际要处理文本的浓缩版本,其中包含关键实体和关系的实例。应尽量纳入数据集中真实出现的复杂语言表达,并特别关注模型难以处理的关系类型。同时,示例还应尽量具有代表性:确保示例中包含你所有关键的关系类型。

LLM 配置

尝试不同的 LLM,并选择最适合你的方案。同时也要测试它们的参数,尤其是 temperature。temperature 越高,模型输出就越多样、越有创造性(同时也越容易出现幻觉);temperature 越低,输出越确定。因此,像文本生成这类创造性任务更适合较高 temperature,而像代码生成这类任务则需要较低 temperature(大约 0.2)。对于从文档中抽取实体和关系这样的事实导向型任务,我们使用 temperature 0。

测试预测稳定性

记住,LLM 是生成式模型,所以从定义上讲,它们是会“有情绪”的:同一个 prompt 反复运行在同一段文本上,也可能产生不同结果。你可以通过精心设计 prompt(减少歧义的任务表述)以及将 temperature 设得很低,来限制这种行为。如果你选择非零 temperature,那么可以考虑通过在同一测试集上多次运行同一个 prompt,并评估结果重叠度,来测试预测稳定性。因为在挖掘事实性知识时,你最不希望看到的,就是预测行为不稳定,以及由此导致对最终 KG 缺乏信心。

对 prompt 做单元测试

提示工程本该是一项快速、直接的工作,但有时你也可能需要经历很多轮迭代才能得到理想结果。因此,就像代码开发一样,你也应考虑建立一个简单的单元测试系统:每次更新 prompt 时,把相应文本片段及其期望输出加入测试列表;每隔几轮迭代,批量运行这些测试,计算成功率,并输出失败项,以便逐个检查。这样一来,你就不会在后续迭代中丢掉先前取得的成果。

目测一个 mini-KG

每当你达到一个 prompt 里程碑时,我们建议你把它部署到一小部分文档样本(几十页)上,并生成一个 KG。为什么?因为人类是感官驱动的生物,有时候,看见和触摸,比单纯假设和想象更有用。我们发现,快速看一眼 mini-KG,往往能帮助我们发现改进空间。

定义
提示工程是一个迭代过程。当你累积了足够多的 prompt 改进,以至于感觉自己在解决原始任务方面取得了显著进展时,你就达到了一个 prompt milestone(提示里程碑)

例如,在浏览 RAC 图谱时,我们发现有许多组织名称是缩写形式,例如 U. of PA。GPT 由于在训练中获得了大量通用知识,如果得到恰当引导,理应能够补全并识别出这是 University of Pennsylvania。同样,我们也看到很多人的名字被缩写成 S.。还有一些人看起来似乎都在同一个组织工作,但文本里只写了 Department of Physics——那到底是哪个大学的物理系?于是,在下一轮迭代中,我们把这些失败案例纳入考量,并在 prompt 示例中加入更多复杂性,以引导 GPT 生成我们想要的输出。

评估

当你对 prompt 感到满意之后,就应进行定量评估。处理几十页文本,指定一个人担任质量保证经理,让其逐条检查预测结果,并标注为正确、错误,或缺失的实体与关系。然后计算实体和关系在各类别上的 precision、recall 和 F1 score。

QA 经理非常适合发现那些在只测试少量示例时无法暴露出来的系统性问题。另一种选择是使用 LLM as a judge:不让人来评估,而是把这个任务交给另一个更强大的 LLM。它当然也会犯错,但人类的表现也并非 100% 完美。无论你选择哪种方式,规范的评估都能让你在花费时间和成本去基于大规模数据集生成 KG 之前,对预测质量建立信心;同时,它还可以成为未来监控模型漂移的基线,并再给你一次优化 prompt 的机会。

初始探索型 KG

要做 prompt engineering,你至少需要对数据、其内容,以及你希望从中抽取的知识有一个最低限度的理解。但我们见过很多应用场景中,公司对数据内容只有模糊概念:他们知道里面肯定有一些有价值的东西,但并不清楚具体应该挖什么,也就是说,不知道 KG schema 应该是什么样。在这种情况下,你就必须深入数据,花时间做探索和理解。

这个潜在上漫长而枯燥的过程,其实可以被加速:你可以先设计一个快速的通用实体与 RE prompt(让 LLM 抽取所有实体和关系,并给它一个简短示例来说明输出格式),然后在一部分文档样本上生成一个 KG,并去探索它、浏览它、从中获得启发。是的,这个 KG 会高度缺乏归一化,但它能帮助你快速理解数据集的内容,识别可以挖掘的实体与关系类型,并启动后续的 prompt engineering。

要有雄心

把目标定高一点!不要先入为主地认为某件事对 LLM 来说太难——先试一试。记住,LLM 是在海量数据上训练出来的,具有深层语言理解能力和丰富的通用知识。它们可以处理名字中的拼写错误,可以从上下文中推断出“Prof. Chem.”其实指的是 Professor of Chemistry,也能判断文中出现的“Stanford”指的是 Stanford University,而不是地名或人名。因此,当你在 prompt 中提供示例时,要大胆一些。给出完整、干净的实体名称和关系,这样一开始就能得到更干净、更准确的 KG,从而减少后处理阶段做实体清洗和消歧的工作量。

不要想太多

提示工程本质上是一种简单技术:零样本学习(只有任务描述)或单样本学习(提供一个示例)。我们期望一个在海量数据上预训练好的模型,仅仅基于任务描述和一两个示例,就能够在特定领域和任务上表现良好。尽管 LLM 很强大,但在面对复杂推理任务时,这依然很有挑战性:因为单样本学习终究无法让模型应对完整数据集中所有复杂性和歧义。

所以,我们最后的建议是:不要把 prompt 开发过度工程化,也不要过度思考。做几轮快速迭代;每达到一个里程碑,就通过生成并目测一个 mini-KG 来验证,找出潜在的系统性问题和新的改进方向;然后继续推进。当你已经尽力了,但 prompt engineering 仍然无法产生令人满意的结果时,与其无休止地继续打磨 prompt,不如把项目剩余时间投入到 LLM 微调上。

5.3.4 构建 KG:传统 NLP 还是 LLM?

到了这里,你可能会问:该如何判断自己应该走传统 NLP 路线,还是 LLM 路线?在现代 AI 世界里,传统 NLP 还有存在价值吗?我们先来看两种方法各自的优缺点。

传统 NLP 的优势包括:

  • 预测速度——更小、更简单的模型,即便在 CPU 上也能更快地产生预测结果。
  • 基础设施简单且成本低——训练和部署模型只需要简单、低成本的基础设施,通常不需要 GPU。
  • 预测成本低——由于基础设施简单且廉价,预测成本非常低。
  • 安全性——更容易、更低成本地配置本地部署方案,以满足隔离式安全部署需求。

传统 NLP 的劣势包括:

  • 对内部专业能力要求高——设计、训练、部署和维护,都需要高度专业化的数据科学技能。
  • 复杂的 NLP 流水线配置——需要构建专门的、定制化的模型链路,例如 NER、RE、共指消解、实体解析和歧义消除。
  • 数据标注复杂且成本高——每个模型都必须用你所在领域的专有数据来训练,这意味着要构建多个高质量训练数据集。这个过程既耗时,又需要数据科学专业知识、基础设施和软件支持。前期投入极高,而且尤其对于 RE 这类复杂任务,结果也并无保障。

LLM 的优势包括:

  • 领域定制的初始成本更低——借助迁移学习和提示工程,走向你所在特定领域的生产落地之路要快得多。
  • 学习曲线更平缓——无需资深数据科学家,你也可以快速上手并积累经验。任何具备基本技术能力的人都能学会做提示工程和微调。
  • All-in-one NLP——不再需要精心编排一串彼此依赖的独立处理步骤;一次过数据,很多任务就都完成了。
  • 生成式特性——除了处理流程更简单外,LLM 还因为迁移学习和生成式特性而产生高质量结果。它们的上下文理解、语言理解和推理能力,使它们即便开箱即用,也能生成比传统 NLP 模型更干净、更准确的 KG。
  • 后处理更简单——LLM 的生成式特性和语言理解能力,意味着后处理工作更少(如清洗、归一化、实体解析)。

LLM 的劣势则包括:

  • 预测速度慢——大模型意味着更慢的预测速度,即便运行在强大的 GPU 上也是如此。
  • 基础设施更复杂——不仅训练,连预测阶段也离不开 GPU,因此基础设施复杂度和成本都更高。
  • 微调成本高——如果提示工程不够,就必须对模型进行微调。这意味着更高的训练数据准备成本、基础设施成本,以及预测成本。(OpenAI 对定制模型的预测收费是普通模型的 10 倍;即便是本地部署,也必须考虑模型版本维护和管理带来的成本。)
  • 在超大数据集上的预测成本更高——对于中小规模数据集,使用 LLM 很可能比从零打造一个传统定制 NLP 流水线更便宜。但如果你的数据集规模巨大,成本结构可能就完全不同了。
  • 高安全场景下本地部署成本高——如果你处于高安全领域,那祝你好运,尤其是在无法使用云服务商、只能自己搭建、部署和维护 GPU 集群时。

注意
如今,许多闭源和开源 LLM 都可以通过 Amazon Web Services、Azure、Google Cloud Platform 等超级云厂商获得,这在一定程度上缓解了我们刚才提到的一些痛点。

回到开头那个问题:“在现代 AI 世界里,传统 NLP 还有存在价值吗?”我们的答案是:“当然有!”尽管我们非常看好 LLM 与 KG 结合的未来,但我们依然认为其他技术(如传统 NLP)仍有相当大的发展空间,尤其是在考虑安全要求、成本(超大数据集)以及流式场景(预测速度)时。

如果由于某种原因,你无法在生产环境中使用 LLM,那么也可以考虑把它们用于诸如训练定制 NLP 模型这样的任务;例如,可以借助快速 prompt engineering 让 LLM 做预标注,从而提升数据标注流程的效率。

小结

  • 从非结构化文本数据中构建 KG,需要识别领域特定的自定义命名实体,以及这些实体之间的关系。
  • 在传统 NLP 路线下,通常需要训练并串联多个模型,例如命名实体识别、关系抽取和共指消解,形成一个复杂工作流。训练这些模型需要对高质量人工标注训练数据集进行高投入建设,也需要专业的数据科学技能来训练、评估、部署和监控模型。
  • 像 OpenAI GPT 这样的 LLM,可以通过迭代式提示工程(或微调)开箱即用地构建准确的、领域特定的 KG,而其初始成本相比传统 NLP 更低。
  • 传统 NLP 与 LLM 各有优缺点,具体取决于实际领域和业务约束。它们也可以并存,或者彼此协作,例如一种方法用于为另一种方法准备训练数据集。
  • LLM 的生成式特性意味着,我们能够得到更干净、更准确的 KG,同时几乎不需要额外做实体归一化和实体解析。