专栏名称: 统计之都
专业、人本、正直的中国统计学门户网站
目录
相关文章推荐
田俊国讲坛  ·  “周鸿祎谈996加班要自愿”,引发网友群嘲“ ... ·  18 小时前  
田俊国讲坛  ·  【3月15日】田俊国老师线下公开课《教学引导 ... ·  18 小时前  
田俊国讲坛  ·  【3月20日】第7期“10天激活课堂训练营” ... ·  2 天前  
田俊国讲坛  ·  【2025重磅来袭】业务赋能导师特训营|直面 ... ·  2 天前  
田俊国讲坛  ·  【3月20日】第7期“10天激活课堂训练营” ... ·  3 天前  
51好读  ›  专栏  ›  统计之都

人工阅读 vs AI 阅读:以《苏东坡传》为例

统计之都  · 公众号  ·  · 2024-11-04 08:00

正文

去年五月,我在阅读林语堂的《苏东坡传》时,边读边整理了书中主要人物的关系。最后,借助图数据库 Neo4j (https://neo4j.com)将这些人物关系存储在数据库中,并以可视化的方式呈现。完成后,我还写了一篇读书博客(https://blog.hohoweiya.xyz/2023/05/31/su-shi/),其中展示了这个可视化的人物关系图。当时,怡轩读到这篇博客,邀请我投稿到统计之都。然而由于拖延症发作,一拖就是一年半。

前几天逛 GitHub 时,发现了一个名为LightRAG(https://github.com/HKUDS/LightRAG)的热门项目,其中的“RAG”指的是检索增强生成(Retrieval-Augmented Generation)。这是结合信息检索(retrieval)与生成模型(generation)的自然语言处理技术。RAG 的核心目标是通过引入外部知识库或文档中的相关信息(如一本书),提升生成模型的性能,尤其在需要特定领域知识或上下文信息的任务中有显著的表现。

一个很自然的应用场景就是让语言模型“读”一本书,然后构建基于该书的知识图谱。由于 RAG 的生成过程依赖于外部知识库(“读”书),它能够在生成过程中引用真实的外部信息,特别是在回答涉及特定知识的问题时,表现得更加可靠和准确。

于是便用 LightRAG 对《苏东坡传》进行“阅读”,构建了一个基于书本内容的知识图谱,这样就可以与去年手动整理的知识图谱做一个简单比较。

人工读书

首先,先介绍下去年我手动整理的知识图谱。当时,怡轩提醒我在投稿时可以补充一些数据整理的细节,但心想,哪有什么整理过程啊,所有数据都是一行一行手动输入的(哈哈哈)。

整理出的数据格式如下:

# 数据文件在此仓库中:https://github.com/szcf-weiya/Su-Shi/blob/master/Su-shi.csv
$ head Su-shi.csv 
chapter,chapter_name,location,name,name_zi,name_hao,name_other,career,relation,activities,comments,poem_to,poem_from
1,文忠公,,,,,,,,,,,大略如行云流水,初无定质,但常行于所当行,常止于不可不止。文理自然,姿态横生。
1,文忠公,黄州,李琪,,,,歌伎,,,,,东坡四年黄州住,何事无言及李琪。却似西川杜工部,海棠虽好不吟诗。
2,眉山,眉山,苏洵,明允,老泉,,,父亲,,,,
2,眉山,眉山,苏辙,子由,,颍滨遗老;苏栾城,,弟弟,,,,
2,眉山,眉山,苏轼,子瞻,东坡,,,本人,,,,
2,眉山,眉山,苏序,,,,,祖父,,,,
2,眉山,眉山,程某,,,,,外祖母,,,,
3,童年与青年,眉山,陈太初,,,,道士,同学,,,,
3,童年与青年,眉山,苏辙,,,,,弟弟,,,我初从公,赖以有知。抚我则兄,诲我则师。,我少知子由,天资和且清。岂独为吾弟,要是贤友生。

每一行记录了以下内容:

  • 人物(名字 name , 字 name_zi , 号 name_hao , 其他名字 name_other
  • 出现的章节 ( chapter chapter_name )
  • 地点 ( location )
  • 职业 ( career )
  • 与苏轼的关系 ( relation )
  • 写给苏轼的诗词 ( poem_to )
  • 苏轼写来的诗词 ( poem_from )

我的整理流程很简单:一边读书一边填写表格。随着《苏东坡传》读完,这个表格也逐渐填满。尽管部分数据存在缺失,但关键的人物关系已经基本完整,因此决定使用可视化工具来呈现这些关系。

选择 Neo4j 的原因是我之前接触过知识图谱项目,对这个工具比较熟悉。Neo4j 是一个专门用于存储和处理图数据的图数据库管理系统,它与传统的关系型数据库不同,采用图结构来表示数据:结点 (node) 表示实体,边 (edge) 表示实体之间的关系。这种结构特别适合处理复杂的关系数据,例如社交网络、推荐系统和知识图谱等。

Neo4j 使用 Cypher (https://neo4j.com/docs/cypher-manual/current/introduction/)作为查询语言,类似 SQL,但它专为图结构设计,能够更直观地查询和操作结点、边及其关系。

安装配置 Neo4j

对于 Neo4j 的入门及安装教程,可以参考这篇知乎文章(https://zhuanlan.zhihu.com/p/88745411),不做赘述。这里记录一下在 Ubuntu 系统下根据官方教程(https://neo4j.com/deployment-center/)的安装流程:

wget -O - https://debian.neo4j.com/neotechnology.gpg.key | sudo apt-key add -
echo 'deb https://debian.neo4j.com stable 4.4' | sudo tee /etc/apt/sources.list.d/neo4j.list
sudo apt-get update
sudo apt-get install openjdk-11-jdk
sudo apt-get install neo4j

然后安装 apoc (https://github.com/neo4j/apoc)插件:

  1. 从 GitHub releases 界面下载与 Neo4j 匹配版本的 Apoc .jar 文件
  2. .jar 文件复制到 $NEO4J_HOME/plugins 目录中

准备完成后,可以通过以下命令启动 Neo4j:

neo4j start

构建数据库

因为我关注的是人物关系,所以只采用原数据中的三列: name , relation , career 。以下分三步采用 Cypher 查询语言给每个人 ( name ) 创建一个结点(如有职业 career 信息,则标注),并为每个人与苏轼根据 relation 建立一条联系:

  1. 对于职业 ( career ) 不为空的人,创建结点时构造两个标签: People 和具体职业 career
LOAD CSV WITH HEADERS FROM 'https://github.com/user-attachments/files/17481791/sushi-relation2.csv' AS row
WITH row WHERE row.career <> ''
CALL apoc.create.node(["People", row.career], {name: row.name, relation: row.relation}) YIELD node
RETURN *
  1. 对于职业未知的人,创建结点时只赋予 People 的标签
LOAD CSV WITH HEADERS FROM 'https://github.com/user-attachments/files/17481791/sushi-relation2.csv' AS row
WITH row where row.career = ''
CALL apoc.create.node(["People"], {name: row.name, relation: row.relation}) YIELD node
RETURN *
  1. 然后对于每一个结点,都建立一个与苏轼的关系,其中具体关系类型便是数据中的 relation
// add relation
MATCH (a:People) where a.relation <> '' and a.name <> '苏轼'
MATCH (o:People) where o.name = '苏轼'
CALL apoc.create.relationship(a, a.relation, {}, o) YIELD rel
RETURN *

最后,执行以下查询展示整个关系图:

MATCH (n) RETURN n

AI“读”书

这一部分开始介绍使用大语言模型来“读”书并构建知识图谱。

安装运行 LightRAG

安装流程直接参考官方仓库(https://github.com/HKUDS/LightRAG):

conda create --name lightRAG python=3.10
conda activate lightRAG
git clone [email protected]:HKUDS/LightRAG.git
cd LightRAG
pip install -e .

如果你使用 OpenAI 的模型,需要申请一个 OPENAI_API_KEY ,并设置好环境变量(当然 LightRAG 也支持其他大语言模型):

export OPENAI_API_KEY="sk-..."

准备就绪后,便可以在 Python 运行以下脚本初始化 LightRAG:

from lightrag import LightRAG, QueryParam
from lightrag.llm import gpt_4o_mini_complete, gpt_4o_complete

WORKING_DIR = "./sushi"

rag = LightRAG(
    working_dir=WORKING_DIR,
    llm_model_func=gpt_4o_mini_complete 
)

接下来就是让语言模型“读”书了,我们需要一本文字版的《苏东坡传》,比如这个仓库(https://github.com/0voice/expert_readed_books)。下载到本地后,指定存储路径,便可以让 RAG 读书了:

with open("/home/weiya/GitHub/expert_readed_books/人物传/林语堂:苏东坡传.txt"as f:
    rag.insert(f.read())

读书过程中 ( f.read() ),可能会遇到编码问题,比如

UnicodeDecodeError: 'utf-8' codec can't decode byte 0xc4 in position 1: invalid continuation byte

这个错误通常是由于文件的编码格式不是 UTF-8 导致的,你可以使用 iconv 工具将文件转换为 UTF-8 编码格式:

iconv -f GB18030 林语堂:苏东坡传.txt -t UTF8 -o 林语堂:苏东坡传_UTF8.txt

如果书本文件过大,一次性读取整本书可能会超出 GPT 的速率限制,出现以下类似错误:

openai.RateLimitError: Error code: 429 - {'error': {'message': 'Rate limit reached for gpt-4o-mini in organization ****** on tokens per min (TPM): Limit 200000, Used 195869, Requested 4412. Please try again in 84ms. Visit https://platform.openai.com/account/rate-limits to learn more.', 'type': 'tokens', 'param': None, 'code': 'rate_limit_exceeded'}}

可以采用一种简单的解决方式:将书本拆分成多个小文件,然后逐一读取。例如,下面将书分成五部分,每部分大约 300 行,依次读取:

with open("/home/weiya/GitHub/expert_readed_books/人物传/sushi_utf8_part1.txt") as f:
    rag.insert(f.read())

with open("/home/weiya/GitHub/expert_readed_books/人物传/sushi_utf8_part2.txt") as f:
    rag.insert(f.read())

with open("/home/weiya/GitHub/expert_readed_books/人物传/sushi_utf8_part3.txt") as f:
    rag.insert(f.read())

with open("/home/weiya/GitHub/expert_readed_books/人物传/sushi_utf8_part4.txt") as f:
    rag.insert(f.read())

with open("/home/weiya/GitHub/expert_readed_books/人物传/sushi_utf8_part5.txt") as f:
    rag.insert(f.read())

构建数据库

在读书的过程中,LightRAG 已经得到了实体及其之间的关系,并存储在 graph_chunk_entity_relation.graphml 文件中。

为了可视化这些关系,使用 LightRAG 提供的示例程序(https://github.com/HKUDS/LightRAG/blob/main/examples/graph_visual_with_neo4j.py),将 graphml 文件转换为 JSON 格式后导入到 Neo4j 中。在导入之前,清空已有数据以免混淆:

MATCH (n) DETACH DELETE n

最终数据库中存储了 2004 个结点,1150 条关系。为了图形展示的清晰度,仅能展示 300 条关系( LIMIT 300 )。通过可视化图谱,可以直观地查看不同实体之间的关联。

知识图谱

以下基于已经构建好的图数据库,通过对结点的类型(比如,人 n:PERSON 或地点 n:POSITION )和关系的类型(比如,友情 r:friendship )做查询进行一些探索。

亲密关系

首先我们来看看苏东坡的家人:

MATCH (n:PERSON {id: '苏东坡'})-[r]-(m:PERSON)zzf
WHERE type(r) CONTAINS 'family'
OR type(r) CONTAINS 'love'
RETURN n, r, m

图中主要有

  • “我少知子由,天资和且清。岂独为吾弟,要是贤友生” 的弟弟苏辙(子由)
  • “十年生死两茫茫,不思量,自难忘。千里孤坟,无处话凄凉”的妻子王弗
  • 坊间传闻的初恋“堂妹”

这里我们也可以看出一个明显的问题——实体的不唯一,我们都知道“苏子由”,“兄弟子由”,以及“苏辙”都是同一个人,但上图却分成了三个实体。

同样,我们可以看看苏轼的朋友们:

MATCH (n:PERSON {id: '苏东坡'})-[r]-(m:PERSON)
WHERE type(r) CONTAINS 'friend'
RETURN n, r, m

其中有

  • “生不愿封万户侯,但愿一识苏徐州”的秦观
  • “画竹必先得成竹于胸中”的文与可
  • “师唱谁家曲,宗风嗣阿谁,借君拍板与门槌,我也逢场作戏莫相疑。溪女方偷眼,山僧莫皱眉,却愁弥勒下生迟,不见阿婆三五少年时”中“山僧”——大通禅师

人生足迹

接着我们可以看看苏轼曾造访过的地方,

MATCH (n)-[r]-(m)
WHERE any(label IN labels(n) WHERE label IN ['GEO', 'LOCATION'])
RETURN n, r, m
LIMIT 300

有两个很明显的地理簇:杭州和西湖。毕竟修浚西湖时建造了“西湖十景”之首的“苏堤春晓”;还有那脍炙人口的“水光潋滟晴方好,山色空蒙雨亦奇。欲把西湖比西子,淡妆浓抹总相宜。”

苏轼一生足迹遍布全中国,小至一叶扁舟,大至一座孤岛:

  • 江山小舟:“壬戌之秋,七月既望,苏子与客泛舟游于赤壁之下。...盖将自其变者而观之,则天地曾不能以一瞬;自其不变者而观之,则物与我皆无尽也,而又何羡乎!...”
  • 庐山:“横看成岭侧成峰,远近高低各不同。”
  • 承天寺:“念无与乐者,逐步至承天寺,寻张怀民。怀民亦未寝,相与步于中庭。庭下如积水空明,水中藻荇交横,盖竹柏影也。何夜无月,何处无竹柏,但少闲人如吾两人耳。”
  • 海南:“吾始至南海,环视天水无际,凄然伤之曰:‘何时得出此岛也?’已而思之:天地在积水中,九洲在大瀛海中,中国在少海中。有生孰不在岛者?譬如注水于地,小草浮其上,一蚁抱草叶求活。已而水干,遇他蚁而泣曰:‘不意尚能相见尔!’小蚁岂知瞬间竟得全哉?”

文学家

首先看直接与诗词 poet* 的关系:

MATCH p=()-[r]->() WHERE type(r) CONTAINS 'poet' RETURN p LIMIT 300

其中有

  • 门士:秦少游(秦观),黄庭坚
  • 至交诗僧参寥:“记取西湖西畔,正春山好处,空翠烟霏。算诗人相得,如我与君稀。约它年、东还海道,愿谢公雅志莫相违。西州路,不应回首,为我沾衣。”——《八声甘州·寄参寥子》
  • 名篇《赤壁怀古》:“大江东去,浪淘尽,千古风流人物。”

然而其中的李师师与苏轼的关系有点令人怀疑,在这本书中并没有提及二人之间的关系,唯一一次出现李师师的地方在于

598: 在苏东坡时代的生活里,酒筵公务之间与歌妓相往还,是官场生活的一部分。.....宋徽宗微服出宫,夜访名妓李师师家。....,可是当日杭州的诗人则为歌女公然写诗。即使是颇负众望的正人君子,为某名妓写诗相赠也是寻常事。在那个时代,不但韩琦、欧阳修曾留下有关妓女的诗,甚至端肃严谨的宰相如范仲淹、司马光诸先贤,也曾写有此类情诗。再甚至精忠爱国的民族英雄岳飞,也曾在一次宴席上写诗赠予歌妓。

但是 LightRAG 给出两人的关系为

苏东坡 interacted with 李师师, acknowledging the role of courtesans in society and expressing admiration for them through poetry.

虽然苏轼确实赠诗给过歌伎李琪——“东坡四年黄州住,何事无言及李琪。却似西川杜工部,海棠虽好不吟诗”,但却不是李师师,这有点张冠李戴之嫌。

接着,我们关注更宽泛的文学 literary* 联系:

MATCH p=()-[r]->() WHERE type(r) CONTAINS 'literary' RETURN p LIMIT 300

其中有

  • 文学名篇:“但愿人长久,千里共婵娟”——《水调歌头》;“江流有声,断岸千尺,山高月小,水落石出。”——《后赤壁赋》;《东坡志林》
  • 文学论辩:乌台诗案
  • 文学继承:李白
  • 文学源泉:西湖

需要指出的是《辩奸论》是苏洵所作,但它与苏轼的关系是 literary critique

"苏东坡 critiques the harshness of political discourse in his essays, showing the relationship of literary and political commentary."

这对应原文

269:  老苏写《辩奸论》时,苏东坡说他和弟弟子由都认为责骂得太重。

艺术家

现在我们来看艺术方面的关系:







请到「今天看啥」查看全文