1. 项目概述:当古诗词遇上AI知识图谱
去年帮学弟调试这个毕设系统时,我们用了整整三周时间才让知识图谱的节点关系正常显示。这个基于Django框架和大语言模型的古诗词智能系统,本质上是在解决传统文化数字化过程中的三个核心问题:如何让机器理解古诗词的深层语义?如何建立诗词间的多维关联?如何让普通用户也能直观探索这些关联?
系统架构上分为四个关键层:最底层是使用Scrapy爬取的30万条结构化诗词数据(包含标题、作者、朝代、正文、注释等字段),中间层是融合了BERT和LSTM的混合模型处理语义分析与情感识别,上层用Neo4j构建包含"诗人-作品-意象-情感"四维关系的知识图谱,最外层则是用Django+Vue实现的可视化查询界面。这种设计使得系统既能处理"找出所有描写秋天且情绪悲伤的宋词"这类复杂查询,也能通过大模型生成符合格律的新诗词。
2. 核心技术实现解析
2.1 知识图谱构建流水线
数据采集阶段我们遇到的最大挑战是古籍文本的异体字处理。比如李白的《将进酒》在不同版本中"钟鼓馔玉"的"馔"字可能有五种写法。解决方案是结合OpenCC繁简转换和自定义规则字典,配合正则表达式清洗:
def text_cleaner(raw_text): # 处理异体字 variant_map = {'馔': ['籑', '饌', '䉵'], '樽': ['罇']} for std_char, variants in variant_map.items(): for var in variants: raw_text = raw_text.replace(var, std_char) # 移除现代标点保留古籍标点 modern_punct = ['"', "'", '“', '”', '‘', '’'] for punct in modern_punct: raw_text = raw_text.replace(punct, '') return raw_text知识抽取环节采用混合策略:使用Stanford CoreNLP进行基础实体识别,再通过预训练好的BERT-wwm模型抽取"春风又绿江南岸"中的"绿"字这种特殊用法。关系抽取时,我们定义了一套包含36种关系的本体库,比如"引用关系"(杜甫《春日忆李白》引用李白诗句)、"唱和关系"(苏轼与黄庭坚的往来诗作)等。
2.2 大模型与传统NLP的融合
在问答模块中,我们发现纯GPT-3生成的答案经常出现年代错乱(比如让辛弃疾评价毛泽东诗词)。最终方案是采用检索增强生成(RAG)架构:
- 用户问题先经过意图分类(使用FastText训练的七分类模型)
- 对于事实类问题(如"李白写过多少首七言律诗"),从Neo4j图谱直接查询
- 对于开放类问题(如"比较李白和杜甫的创作风格"),先用BM25检索相关段落,再将检索结果作为上下文输入ChatGLM-6B生成最终答案
情感分析模块则采用了双通道结构:传统机器学习部分使用基于SnowNLP改进的情感词典方法处理显性情感词(如"愁"、"喜"),深度学习部分用BiLSTM+Attention模型捕捉"却道天凉好个秋"这类隐晦表达。两者结果通过门控机制融合,在测试集上F1值达到0.87。
3. 可视化与交互设计
3.1 知识图谱可视化方案
放弃常见的D3.js方案而选用Cytoscape.js,主要考虑三点:一是支持Neo4j的Cypher查询结果直接渲染,二是能处理超过5000个节点的大规模图谱,三是内置力导向布局算法可清晰展示"诗人社交网络"。关键配置如下:
cy.style() .selector('node[type="poet"]') .css({ 'background-color': '#FF6B6B', 'shape': 'round-rectangle', 'border-width': 2, 'border-color': '#FFE66D' }) .selector('edge[relation="influence"]') .css({ 'line-color': '#4ECDC4', 'target-arrow-color': '#4ECDC4', 'curve-style': 'bezier' });实际运行中发现必须限制前端显示的关联度数,否则杜甫节点会与超过200个其他节点相连导致界面混乱。最终方案是默认只显示2度关系,用户可通过滑块动态调整。
3.2 智能问答接口优化
最初直接调用OpenAI API时遇到两个严重问题:一是响应速度慢(平均2.3秒),二是对"红酥手,黄縢酒"这类典故解释不准确。改进后的方案:
- 本地部署ChatGLM-6B量化模型,使用FastAPI封装成微服务
- 构建包含5万条诗词典故的本地向量数据库(FAISS索引)
- 查询时先做意图识别,若涉及典故则优先从本地库检索
def query_expansion(raw_query): # 查询扩展:将"李白描写月亮的诗"扩展为"李白 月亮 咏月 望月" synonyms = {'月亮': ['明月', '皓月', '玉轮']} expanded = [] for token in jieba.cut(raw_query): expanded.append(token) if token in synonyms: expanded.extend(synonyms[token]) return ' '.join(expanded)这种混合方案使平均响应时间降至680ms,且典故解释准确率提升42%。
4. 部署与性能调优
4.1 Django后端优化技巧
使用django-neomodel替代原生neo4j-driver后,查询性能提升显著但内存占用过高。通过以下措施将内存消耗从4.2GB降至1.8GB:
- 分页加载:修改NeoModel的batch_size参数为50
- 缓存策略:对高频查询如"唐诗三百首列表"设置Redis缓存
- 查询优化:将MATCH (a)-[*..5]->(b)这类宽泛查询改为指定关系类型
# 优化前后的查询对比 # 旧查询(耗时1200ms) results = Poem.nodes.filter(poet__name='杜甫').traverse('related_poems') # 新查询(耗时380ms) results = Poem.nodes.filter(poet__name='杜甫').relationships('CITES').filter(relation_type='allusion')4.2 大模型服务化实践
在阿里云ECS(g7ne.16xlarge)部署ChatGLM-6B时,发现直接加载FP16模型需要28GB显存。最终方案:
- 使用GPTQ量化到4bit(模型大小从12GB→3.8GB)
- 采用vLLM推理框架支持动态批处理
- 编写自定义的Django中间件处理并发请求
# 启动vLLM服务 python -m vllm.entrypoints.api_server \ --model chatglm-6b-4bit \ --tensor-parallel-size 2 \ --max-num-batched-tokens 4096这套配置使QPS从3提升到18,且99%的请求延迟控制在1.2秒内。
5. 典型问题排查实录
5.1 知识图谱关系缺失
现象:查询"苏轼与佛印的交往诗作"返回空结果 排查过程:
- 检查原始数据确认存在《赤壁赋》提及佛印
- 发现关系抽取规则未包含"僧侣称谓"模式
- 添加规则:
(人物实体) 号 (字号) → HAS_STYLE_NAME解决方案:重新运行抽取管道并更新Neo4j关系
5.2 情感分析偏差
现象:将"国破山河在"错误分类为积极情感 原因分析:
- 词典方法将"山河"识别为正向词
- LSTM模型未充分学习战争语境 改进措施:
- 添加特殊语境规则:当出现"破"、"亡"等字时触发二次校验
- 在训练数据中增强战争诗样本权重
5.3 大模型幻觉问题
现象:问"李清照晚年作品"时生成虚构的《暮春词》 应对方案:
- 在prompt中加入严格约束:"仅返回确凿存在的作品"
- 设置验证流程:先用BM25检索验证作品真实性
- 前端添加"可能存在误差"的免责提示
经过三个月迭代,系统现已稳定处理日均5000+次查询。最让我意外的是,用户最常查询的并非具体诗词内容,而是"哪些诗人曾在黄鹤楼题诗"这类时空轨迹分析。这提示我们下一步应该增强地理时空维度的可视化能力,或许可以结合Leaflet地图实现诗人行迹的可视化追踪。