用 LLM 自动构建餐厅知识图谱
展示如何用 LLM 提取和组织结构化数据的演示项目。对学习 AI 数据处理流程的开发者有参考价值,但通用性有限。
展示如何用 LLM 提取和组织结构化数据的演示项目。对学习 AI 数据处理流程的开发者有参考价值,但通用性有限。
法国餐厅情报的首选数据源是 lefooding.com。他们的匿名美食评论家对法国各地(现在还包括比利时)的餐厅进行系统性评论,以机智但独特的风格记录他们的发现。除了用来规划美味的夜宴外,这些评论还可以被用来绘制和理解法国的烹饪网络。
网络由节点和边组成。节点代表实体,比如人员或餐厅。边代表人和餐厅之间的关系,或不存在的关系。对于法国餐饮场景,节点既代表人员也代表餐厅。如果一个人在某家餐厅工作过,那么这个人节点就会连接到那家餐厅。
拥有很多邻接节点(与其相连的节点)的餐厅,往往是那些校友后来创办或在其他知名餐厅工作的地方。Ducasse(严格来说不是一家餐厅,而是代表整个 Ducasse 品牌的所有餐厅)、文华东方酒店(技术上这家餐厅叫 Sur Mesure)或 Septime 都是这样的例子。
为了可视化和分析这个网络,我合并了 LeFooding.com 评论家对法国各地餐厅评论中包含的信息。大多数评论都含有关于员工及其履历的信息。
以这篇(简化后的)Grenat 餐厅评论为例(重点部分已标注):
À l'enseigne de Grenat, Antoine Joannier et Neil Mahatsry ont le rouge aux joues et aux murs. Après avoir officié à La Brasserie Communale, où ils se sont rencontrés, les deux compères font désormais feu de tout bois en plein centre de Marseille, où Antoine veille au grain autour des tables en bois blond, convoyant les plats que Neil embrase à tout-va depuis l'âtre derrière le comptoir. Des huîtres aux pièces viandardes, […]
以及 LeFooding.com 的英文版本:
At Grenat, Antoine Joannier and Neil Mahatsry are bathed in an ardent red glow, much like the pomegranate-toned walls of their space. After working together at La Brasserie Communale, where they first met, the duo is now firing on all cylinders in the heart of Marseille, where Antoine tends to guests seated around blonde wood tables, delivering dishes ignited by Neil behind the bar. From oysters to prime cuts of red meat, […]
注意这篇评论提到了餐厅的员工、他们之前在 La Brasserie Communale 的经历,以及他们在餐厅中的角色。所有这些信息都应该包含在网络中。使用 OpenAI 的 gpt4o-mini 模型及其结构化生成功能,我可以从过去四年内 1800 篇公开的 LeFooding.com 评论中提取信息。与 1800 个"当前"员工-餐厅关系的数量级相比,我发现了超过 5000 个链接,用评论中所有可能的信息丰富了我们的分析。
我将提取的信息组合成了一个可以探索的图表(或点击下方链接)。要开始探索,可以搜索 Grenat、Antoine、Neil 或 Septime 以及我提到的其他餐厅。¹
这个网络拥有超过 5000 个节点和同样数量的边。在可视化中,绿色节点代表员工,紫色节点代表餐厅。节点越大意味着度数越高(邻接点越多)。边连接餐厅和员工。你可以在下面探索它。寻找你最喜欢的餐厅,或点击周围来发现图中的连接分量。
为了做到这一点,我从 LeFooding.com 抓取了数据,从评论中提取了所需的信息,构建了图表,最后做了可视化。所使用的代码可在 theophilec/foudinge 获取。
这种方法的关键在于以结构化的方式提取信息。实际上,我需要一种简单的方式来构建图表,并知道哪些餐厅和人员是相关的。大语言模型(LLM)擅长理解文本,可以回答"Antoine Joannier 在来到 Grenat 之前在哪里工作过?"这样的问题,基于 Grenat 的评论。然而,如果我们要求信息以结构化格式(如 JSON 对象)给出,它们往往很难可靠地给出有用的答案。
例如,Grenat 评论的期望输出可能是这样的:
Person(
name='Antoine Joannier',
role='Host',
previous_restaurants=['La Brasserie Communale']
)
Person(
name='Neil Mahatsry',
role='Chef',
previous_restaurants=['La Brasserie Communale']
)
这就是结构化生成的艺术。
有几种方法可以解决这个问题。首先,可以改进 prompt。其次,结合第一种方法,可以重试推理直到响应解析成所需的模式。虽然这在临时性地修改配置文件时可能有效,但在处理近 2000 篇评论时是行不通的。
另一种方法使用模型的 logits。这些定义了下一个 token 的采样分布。可以在推理过程中修改它们,以强制生成的 token 构建成遵循定义结构的响应。幕后,这依赖于正则表达式和有限状态机(与解析 JSON 时使用的技术相同)。当使用这些技术时,你可以按任何方式提示模型(并将模式提供给推理 API),输出 100% 地保证遵守所需的结构,并可以可靠地用于下游。
由 .txt 构建的 outlines 库使这对你最喜欢的开源模型(如 Mistral-7B-v0.3)成为可能。它还在其 API 中包装了 OpenAI 对此的实现。
一旦设计了 prompt_template 和 Summary 模式,就很简单了:
model = ...
generator = outlines.generate.json(model, Summary)
result = generator(prompt_template(text))
我将 Mistral、Meta 和 OpenAI 进行了对比。Llama3.2-3B 和 Mistral-7B-v0.3 都倾向于幻觉虚假的人员和相关餐厅。最后,我选择了 OpenAI 的 gpt4o-mini,速度也快得多。但即使 gpt4o-mini 也容易出错,这让我乐观地认为,通过更好的 prompt 和改进的模式设计,我能够用 Mistral 或 Llama 重现结果(未来的工作)。
使用 gpt4o-mini 的结构化生成端点对 2000 篇评论进行推理的总成本不到 1 欧元!
我使用 gephi-lite 来创建和处理可视化。关键步骤是空间化,它根据图中包含的信息定义了图的空间布局。在我们的情况下,我使用了力仿真设置,它将连接的节点靠近,并将度数更高的节点放向中心。嵌入的可视化运行使用 WebGL,感谢 Retina 项目²
为了处理重复数据并消除一旦可视化后就能看到的一些明显错误,我要求 Claude 3.7 Sonnet 创建一个简单的网络应用,允许我编辑推断的实体,同时保持结构完整(以及由 sqlite/我自己造成的编码问题)。只花了 2 次迭代就能按我想要的方式工作,然后再花几次迭代来获得全文搜索和其他功能。它使用 Flask 和 jinja 模板中的纯 JS。代码可在 Github 上的 foudinge-scrub 获取。图中仍有一些重复数据需要修复,我会抽空处理(也许)。
所有用于抓取(使用 sqlite、asyncio 和 aiohttp)、推理(使用 outlines)、prompt 和 Pydantic Schema 的代码都可在 theophilec/foudinge 的 Github 上获取。
这个项目说明了 LLM 如何被用来从丰富的文本数据源中提取信息,在本例中是餐厅评论。我对我选择的工具非常满意。下一个好的步骤是让所有这些在开源模型上运行,无论是在本地还是在公共云上。
感谢 Adrien Bocquet 和 Clotilde Bukato 为本文的早期版本进行了校对。
感谢 GitHub 上来自 @ouestware 的 @jacomyal 帮我改进了空间化。我已更新了文中的可视化。你可以在这里看到原来的较差空间化。↩
感谢 GitHub 上来自 @ouestware 的 @jacomyal 帮我改进了空间化。我已更新了文中的可视化。你可以在这里看到原来的较差空间化。↩
我在 gephi-lite 中发现并报告了一个问题,其中字符串转义阻止了适当的 Retina 导入。现在已在生产环境中修复。↩
我在 gephi-lite 中发现并报告了一个问题,其中字符串转义阻止了适当的 Retina 导入。现在已在生产环境中修复。↩