1. 项目概述:当古诗词遇上知识图谱与大模型
作为一名长期混迹在NLP和Web开发领域的工程师,最近完成了一个让我特别兴奋的项目——基于Django和大模型的中华古诗词知识图谱系统。这个项目最初源于我对传统文化和技术融合的兴趣,没想到最终实现的效果远超预期。简单来说,我们构建了一个能自动分析5万多首古诗词的智能系统,不仅能可视化展示诗人社交网络、诗词意象关联,还能回答"李白和杜甫共同描写过哪些意象"这类复杂问题。
传统古诗词数据库(如古诗文网)主要提供关键词搜索,而我们这个系统的突破性在于:
- 使用大模型自动识别诗词中的隐喻和典故(比如自动识别"明月"在李白诗中常代表思乡)
- 构建包含12类实体、38种关系的知识图谱(诗人、朝代、地点、意象、情感倾向等)
- 实现动态可视化探索,比如通过力导向图直观展示宋代词人社交圈层
这个系统的技术栈非常"硬核":前端用Vue+ECharts实现交互式可视化,后端Django处理业务逻辑,Neo4j存储知识图谱,大模型采用微调后的ERNIE-3.0处理文本分析。最让我自豪的是,系统在测试阶段就发现了传统研究中被忽略的关联——比如王维和孟浩然虽然同属田园诗派,但他们的意象使用存在明显地域特征差异。
2. 核心架构设计解析
2.1 系统技术选型背后的思考
选择Django作为后端框架绝非偶然。经过对比Flask和FastAPI后,我们发现Django ORM对图数据库的支持更完善,而且其自带的Admin系统能快速构建数据管理界面。实际开发中,我们用django-neomodel这个库实现了Django模型与Neo4j的无缝对接,一个典型的诗人节点定义如下:
python复制from neomodel import StructuredNode, StringProperty, IntegerProperty, RelationshipTo
class Poet(StructuredNode):
name = StringProperty(unique_index=True)
dynasty = StringProperty()
birth_year = IntegerProperty()
# 定义与诗词的关系
poems = RelationshipTo('Poem', 'WROTE')
大模型选型上,我们测试了LLaMA-2、ChatGLM和ERNIE-3.0,最终选择ERNIE-3.0的原因有三:
- 在古文分词任务上的准确率比LLaMA-2高17%(测试集F1值0.89 vs 0.72)
- 对中文古诗词特有的倒装句式理解更好
- 提供方便的微调接口,我们用5000条标注数据微调后,实体识别准确率达到92%
2.2 知识图谱构建的关键步骤
数据采集阶段遇到了不少坑。最初从公开数据集获取的3万首诗词存在大量重复和元数据缺失,后来我们开发了专门的清洗管道:
python复制def clean_poem_data(raw_text):
# 去除现代标点符号
text = re.sub(r'[,。、;:?!]', '', raw_text)
# 提取朝代信息
dynasty = extract_dynasty(raw_text)
# 标准化作者名称
author = normalize_name(raw_text.split('·')[0])
return {
'title': extract_title(raw_text),
'author': author,
'dynasty': dynasty,
'content': text
}
实体关系抽取是最大的技术难点。我们设计了两阶段处理流程:
- 先用ERNIE识别基础实体(人物、地点、意象)
- 再用规则+模型混合方式判断关系类型,例如:
- 同现关系:两意象在同一首诗出现
- 引用关系:诗中明确使用"忆""怀"等指向性词语
- 情感关联:通过情感分析模型判断意象的情感倾向
3. 核心功能实现细节
3.1 动态可视化探索功能
前端采用ECharts实现的知识图谱可视化绝非简单的静态展示。我们实现了以下交互功能:
- 力导向图布局:诗人节点根据朝代自动聚类,连线粗细表示关联强度
- 时空轴展示:用时间轴控件筛选特定朝代的诗词演变
- 意象热力图:地理信息与意象词频结合展示地域文学特征
一个典型可视化配置示例:
javascript复制option = {
series: [{
type: 'graph',
layout: 'force',
data: nodes.map(node => ({
id: node.id,
name: node.name,
category: node.dynasty,
symbolSize: Math.sqrt(node.poemCount) * 3
})),
links: relations.map(rel => ({
source: rel.source,
target: rel.target,
value: rel.weight
})),
emphasis: {
focus: 'adjacency'
},
categories: dynasties.map(d => ({name: d}))
}]
};
3.2 智能问答系统实现
问答模块采用混合架构处理不同类型问题:
- 简单事实型问题(如"李白的出生年份")直接查询Neo4j
- 复杂推理问题(如"比较李白和杜甫的创作风格")走大模型推理流程
问答处理的核心代码如下:
python复制def answer_question(question):
# 先用NER识别问题中的实体
entities = ernie_ner(question)
if is_fact_question(question):
# 知识图谱查询
cypher = generate_cypher(question, entities)
result = neo4j_query(cypher)
return format_fact_answer(result)
else:
# 大模型生成回答
context = get_related_poems(entities)
prompt = build_prompt(question, context)
return ernie_generate(prompt)
4. 实战中的经验与坑点
4.1 大模型微调的血泪教训
最初用500条数据微调ERNIE时效果很差(F1仅0.65),后来发现三个关键点:
- 数据质量比数量重要:清洗后的2000条高质量标注数据比5000条噪声数据效果好
- 标签设计要合理:最初把"月"统一标为"意象",后来细分为"思乡月""孤独月"等子类
- 学习率要调小:古文场景下学习率设为5e-6比默认的5e-5更稳定
4.2 Neo4j性能优化技巧
当图谱数据超过10万节点时,查询性能明显下降。我们通过以下优化手段将查询速度提升8倍:
- 为高频查询属性建立索引:
cypher复制CREATE INDEX ON :Poet(name) CREATE INDEX ON :Dynasty(name) - 对深度查询使用APOC库的路径展开优化:
cypher复制CALL apoc.path.expandConfig($startNode, { relationshipFilter: "WROTE>|MENTIONED>", minLevel: 1, maxLevel: 3 }) - 批量写入时使用UNWIND代替单个CREATE:
cypher复制UNWIND $batch AS item MERGE (p:Poet {name: item.name}) SET p.dynasty = item.dynasty
4.3 前端可视化性能瓶颈突破
当需要渲染超过5000个节点时,浏览器明显卡顿。我们最终采用以下解决方案:
- WebWorker分块计算:将力导向图布局计算放到Worker线程
- 四叉树空间索引:对节点进行空间分区,只渲染可视区域内元素
- 分级显示策略:
- 缩放级别>80%:显示完整节点和文字
- 30%-80%:只显示节点图标
- <30%:聚合显示朝代簇群
5. 项目扩展与实用建议
5.1 教学应用中的实用技巧
在将系统用于实际教学时,我们发现几个特别实用的功能点:
- 对比分析功能:选择两位诗人自动生成创作特征雷达图
- 时空旅行模式:滑动时间轴观察特定意象的历史演变
- 飞花令助手:输入关键字自动生成相关诗句链
5.2 值得尝试的改进方向
如果继续迭代这个项目,我会优先考虑以下扩展:
- 多模态分析:结合古代书画分析诗画关系
- 风格迁移:训练专属模型将现代文转写成古诗风格
- 社交网络分析:用GNN挖掘诗人群体中的潜在派系
一个有趣的发现是,通过分析诗人之间的引用关系,可以清晰看到唐代诗坛存在多个"隐形圈子",这些圈子不仅基于友谊,更多是创作理念的共鸣。比如王维、裴迪这个小组,他们的山水诗在意象选择上显示出惊人的一致性,而这在传统文学研究中往往需要大量人工比对才能发现。