ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

基于Django与DeepSeek大模型的古诗词情感分析及推荐系统

基于Django与DeepSeek大模型的古诗词情感分析及推荐系统

还在纠结毕设题目?看到标题里"计算机毕业设计Django+DeepSeek大模型知识图谱古诗词情感分析 古诗词推荐系统 古诗词可视化 大数据毕业设计"这一长串关键词,我大概能猜到你需要的是一个既能在答辩时讲出技术深度、又能现场展示出视觉效果、还能让评委一眼看懂"这不是拼凑项目"的选题。古诗词平台这类题目本身不算冷门,但一旦把大模型情感分析、知识图谱和推荐系统这三个关键能力糅在一起,整个项目的含金量就上来了。这篇博文我围绕自己实际摸过的同款架构展开,把从选题拆解、数据库设计、大模型接口调通、推荐算法落地到可视化看板打包的全过程讲清楚,参考价值和回忆录都会有一点。

先说定位:这套系统定位是"文化内容个性化服务"方向,不是单纯的增删改查管理系统。它要解决的痛点很明确——传统古诗词检索网站只能按标题、作者、朝代查,用户面对上万首诗不知道从哪读起,也感受不到诗词之间、诗人和作品之间的深层关联。因此系统核心能力锁定在三件事:读懂一诗的情感倾向、讲清诗人与作品的关联脉络、按用户兴趣把合适的诗推到眼前。正是这三个能力,构成了毕设答辩时最容易被追问、也最能体现思考深度的技术点。

1. 项目整体设计与思路拆解

1.1 选型落点:为什么是Django + DeepSeek + 知识图谱

技术选型不是拍脑袋,每一步都是冲着"可解释、可维护、有亮点"这三个要求去的。

后端我没犹豫就选了Django。原因很实际:毕设周期短,Django自带ORM、Admin后台、模板引擎和一套完整的请求/响应体系,能把用户模块、数据管理模块和审核逻辑快速串起来。加上Python生态对大模型调用、数据处理天然友好,不用像Java那边还要额外包装一层HTTP客户端。数据模型建好后,Django的迁移机制能随时改表结构,对开发期频繁调整字段的场景非常省事。你可以把这个选型类比成装修时选了一套模块化家具,单个零件拆装方便,整体风格统一,出了问题也好定位。

AI能力这块采用的是DeepSeek大模型的对话式推理接口。传统的情感分析大多依赖词典和机器学习分类器,遇到古汉语里特有的借景抒情、用典、反衬,效果崩塌得厉害。比如"感时花溅泪,恨别鸟惊心",字面是一片春色,实际是沉痛到了极点,词典法基本抓瞎。大模型能理解上下文语义和意象背后的文化含义,情感标签的质量直接高出一个档次。更关键的是,DeepSeek是国产模型,接入方式和OpenAI兼容,文档规范、离线可用知识储备里有大量古诗词语料,对中文古文的把握明显优于通用翻译模型。

知识图谱要解决的是"关联"问题。诗词不是一座座孤岛,诗人与诗人之间存在师承、唱和,作品与作品之间存在用典、意向,朝代影响创作风格,风格反过来又决定情感底色。把这些关系建模成"实体—关系—实体"的三元组,比如"李白——创作——《静夜思》","《静夜思》——包含意象——月",就能支撑起两类很有价值的产出:一是知识图谱可视化,让评委直观看到整个诗词世界的结构;二是图谱路径推荐,从用户当前读的诗出发,顺着诗人的风格分支找到下一首,推荐结果可解释性极强。

1.2 功能模块拆解与业务流程

整个系统按用户视角可以切成四条线:

  • 数据展示线:诗词列表、诗人主页、朝代聚合页、搜索与筛选。这是系统的底子,所有其他功能都挂在上面。
  • 情感分析线:对每首诗词调用大模型接口,返回情感类别、情感强度、判断理由和典型意象标签。
  • 个性化推荐线:用户浏览、收藏、搜索行为被记录后,系统从内容相似、图谱路径、行为协同三个维度产出推荐结果。
  • 可视化与图谱线:后台统计各朝代数量、情感分布、高频意象,前端用图表呈现;知识图谱页面展示诗人—诗作—意象的关联网络。

业务闭环是这样的:用户进入系统,浏览某首诗词,前端把行为事件异步写入后端行为日志;后端根据行为日志和当前页面上的诗词,实时拉取推荐接口,返回一个长度为10的诗词列表;用户点击推荐中的某一首,下一次推荐计算会把这个点击作为新的权重因子。整体上推荐效果会随着使用行为增多而慢慢"养"起来,这本身就是数据量的积累过程,也是论文里可以写数据实验的部分。

1.3 技术栈与数据流转总览

系统技术栈大概是这个结构:

  • 后端核心:Django + Django REST Framework,用于搭建API接口、用户鉴权和数据持久化。
  • AI层:DeepSeek对话接口(OpenAI兼容格式),负责情感分析;文本向量化使用中文句子向量模型,负责内容相似度计算。
  • 存储层:MySQL放业务数据,Redis做推荐结果缓存和热点诗词排行。
  • 可视化层:ECharts绘制朝代统计、情感分布、词云、力导向图,知识图谱展示页面用纯前端渲染。
  • 部署与辅助:Nginx + Gunicorn部署演示环境,整个系统可运行在单台服务器上。

数据流转上是典型的"读多写少"场景:数据初始化时跑一遍全量情感分析,结果落库,后续用户访问时直接查表,不回源AI接口。推荐计算优先查Redis缓存,缓存未命中才做实时计算。这种方式既控制了成本,又能保住演示时接口不超时。

2. 核心细节解析与实操要点

2.1 古诗词情感分析的两种路径

情感分析是整个项目的创新牌,但做的时候千万别只押一条路。我实际采用的是"双路径并联+规则兜底"方案。

路径一是大模型推理路径。系统把诗词全文、作者、朝代、背景信息塞进提示词模板,要求模型输出严格JSON格式。实际操作中提示词写得越结构化,返回结果就越稳定。我的提示词大致是这样的:明确要求模型扮演古诗词研究助理,指定情感类别候选集合(喜、怒、哀、乐、思、愁、闲适、豪放、婉约),要求输出强度值0到1之间,需要罗列三个以内的核心意象标签,最后用一句话描述判断依据。这样返回的数据可以直接解析进数据库,不需要再做一堆字符串清洗。

路径二是传统情感词典路径。我用公开的汉语情感词库为基础,手工补充一批古诗词高频情感词,比如"断肠""浮萍""轩冕""谪仙"这类词,再结合否定词、程度副词做加权计算。这个路径准确率只有五成到六成左右,但它的价值在于:大模型接口抖动或者网络超时时,系统可以自动降级到词典规则,保证演示不会当场黑屏。这在答辩现场是救命的。

双路径之间还做一个简单的一致性校验:两条路径结果一致时写入高置信度标记;不一致时以大模型结果为准,但保留词典结果字段,论文里可以专门写一段分析方法对比。

2.2 知识图谱数据建模

建模这块需要先想清楚两个问题:实体有哪些,关系有哪些。我最终定了五类实体和四类关系。

实体类型包括诗人、诗作、朝代、意象、风格流派。诗人属性有姓名、字号、生卒年、朝代、籍贯、作品数量、风格标签;诗作属性有标题、体裁、正文、译文、赏析、创作背景;朝代属性有起止时间、主要诗人和代表作品;意象属性有名称、出现频次、典型诗句示例;风格流派有流派名、代表人物、主张和代表作品。

关系类型设计为:诗人—创作—诗作,诗人—属于—流派,诗人—生活于—朝代,诗作—包含意象,诗作—体现情感。其中"体现情感"这个关系实际上是由后面要做的情感分析结果反哺生成的,也就是说,大模型输出的情感类别(如"愁")会作为图里诗作节点的属性,同时在图上形成一个按情感分类的聚合视图。

存储方式我选了关系型数据库表加前端图谱渲染的双层结构,没有上Neo4j。原因有两层:一是毕设答辩现场如果评委想看架构图,你可以讲清楚图谱逻辑模型;二是Neo4j部署和运维成本对一个毕设系统来说偏重,而将三元组数据直接存在MySQL的三张表里,"实体表、关系表、属性表",在数据量几千条级别下查询性能完全够用。图谱页面需要的数据只是把关系表一次性导出成ECharts的nodes和links结构,前端渲染根本不吃力。

2.3 推荐系统算法取舍

推荐不能只做一个"猜你喜欢"的浅层功能,评委追问两句就会露馅。我是按"字典式三层推荐"设计的,每层解决一个特定问题。

第一层是内容相似推荐。先把所有诗词的标题、正文、译文、标签拼成一个长字符串,用中文句子向量模型转成向量,再计算当前诗词与全部诗词的余弦相似度,取Top10。这一层的优点是无需用户历史,新用户进来也能推;缺点是只看文本表面相似,风格差异大的诗可能在向量空间里距离很远。

第二层是知识图谱路径推荐。核心逻辑是利用刚才建好的图谱关系做多跳查询。比如当前用户在看苏轼的词,图谱路径可以这样走:苏轼属于豪放派,豪放派关联了辛弃疾,辛弃疾的代表作有《破阵子》,于是把《破阵子》推荐给用户。实现上用BFS宽度优先算法跑两跳路径,附带路径详情返回给前端,这样推荐列表旁边可以直接显示"因为你也喜欢豪放派作品,所以推荐了这首",可解释性直接拉满。

第三层是协同过滤推荐(ItemCF)。逻辑是统计"喜欢诗A的人还喜欢哪首诗"。考虑到毕设数据量不大,我在内存里构建用户—诗作评分矩阵,按是否点击过、是否收藏过、浏览量高低给加权分数,然后用余弦相似度计算诗作之间的相似度,取TopN。这一层适合在用户产生一定行为数据之后工作,冷启动时作用有限。

三层推荐的聚合策略很简单但有效:内容相似结果占权重40%,图谱路径结果占35%,协同过滤占25%,最终去重后按综合分数排序。如果你的数据量实在太少,协同过滤层甚至可以降到10%,不会影响整体效果。

2.4 可视化看板设计思路

可视化是本项目的门面工程,一口气拉低了答辩时评委的理解成本。

第一个看板是朝代产量图,横轴朝代,纵轴作品数量,一眼看出唐诗宋词的量级差异。第二块是情感分布雷达图,标注各朝代情感类别占比,能看出宋代词的"愁"情绪比例远高于唐诗,这类对比在论文里可以写成"情感演变的时代因素"。第三个是高频意象词云,把"月、酒、花、风、雨、舟、柳、春、秋、山"这些高频词用词云组件渲染,视觉冲击力很强。第四个是力导向知识图谱,诗人、诗作、意象作为图节点,节点大小映射作品数量,节点颜色映射流派,关系线用不同颜色区分类型。

可视化这块最有心得的一点是:别让后端一次返回所有数据再让前端慢慢渲染。几十个节点没问题,但几千个节点浏览器会卡死。我采用分层渲染策略,第一层只渲染诗人节点,点击某个诗人后第二层渲染该诗人的诗作节点,再点击诗作节点展开意象节点。这样一次最多渲染几十个节点,动态交互体验非常好。

3. 实操过程与核心环节实现

3.1 数据准备与清洗

数据来源这块,我建议优先从开放的古诗文数据库中获取,尽量选择带作者、朝代、正文、注释的结构化数据集。我最终整理出约2000首唐诗宋词、200多位诗人的数据量作为项目运行的基石。这个体量对情感分析和推荐实验已经足够出效果,对演示环境的内存压力也小。

清洗是最费时间但又必须做的活。实际会遇到的问题包括:繁体字与简体字混排、标点符号不统一、诗作正文里混入注释文字、同一作者在不同数据源里的名称不一致(比如"李白"和"李太白")。我统一用字符映射表把繁体转简体,用正则把正文里的括号注释和序号剔除,同时建了一个作者别名映射字典,所有入库数据归一化处理。对这个环节我的经验是别指望一次清洗到位,建议清洗完成后随机抽三十首人工核对一遍,出错率高于5%就再跑一轮。

3.2 数据表设计与核心字段

数据库由六张核心表和两张辅助表组成。直接上关键字段:

诗人表(poet):主键poet_id,姓名name,朝代dynasty,字号courtesy_name,籍贯hometown,风格标签style_tags,作品数poem_count,简介description。

诗作表(poem):主键poem_id,标题title,作者poet_id外键,朝代dynasty,体裁genre,正文content,译文translation,赏析appreciation,标签tags,浏览数view_count。

情感分析表(emotion):主键emotion_id,诗作poem_id外键,情感类别emotion_type,强度intensity,判断理由reason,意象标签imagery_tags,分析方式method(LM或DICT),置信度confidence。

图关系表(kg_relation):主键relation_id,头实体head,尾实体tail,关系类型relation_type,权重weight,附加属性extra_info。

用户表(user):用户ID、用户名、密码哈希、创建时间、偏好风格。

行为日志表(behavior):主键behavior_id,用户user_id,诗作poem_id,行为类型behavior_type(VIEW、LIKE、COLLECT、SEARCH),行为时间。

有了这套表结构,前后端联调就不用反复改字段了。我在设计阶段就把情感分析结果合并进诗作展示详情页,避免前端需要同时请求两个接口才能拼出数据的情况。

3.3 大模型情感分析接口的实现

这一部分是代码实现的重点。整体设计是批量任务与单条实时分析两种模式并存。批量任务在数据初始化时跑,用异步循环逐首调用;实时分析在管理员后台对单首诗词触发,也供前端展示"AI情感解构"详情。

下面是核心实现代码,直接可复用:

import requests import json import time DEEPSEEK_API_URL = "https://api.deepseek.com/chat/completions" API_KEY = "你的API_KEY" # 建议放到环境变量或Django settings中 def analyze_poem_emotion(poem_text: str, author: str = "", dynasty: str = "") -> dict: prompt = f"""你是资深古诗词研究者,精通古典文学情感解析。 请分析下面这首诗词的情感倾向,并严格按照JSON格式返回结果。 诗词内容: {poem_text} 作者:{author} 朝代:{dynasty} 要求返回: {{"emotion": "喜/怒/哀/乐/思/愁/闲/豪/婉", "intensity": 0-1的小数, "tags": ["核心意象1", "核心意象2"], "reason": "一句话判断依据"}} """ headers = { "Authorization": f"Bearer {API_KEY}", "Content-Type": "application/json" } payload = { "model": "deepseek-chat", "messages": [{"role": "user", "content": prompt}], "temperature": 0.3 } for attempt in range(3): try: response = requests.post(DEEPSEEK_API_URL, headers=headers, json=payload, timeout=20) response.raise_for_status() data = response.json() content = data["choices"][0]["message"]["content"] content = content.replace("```json", "").replace("```", "").strip() parsed = json.loads(content) return { "emotion": parsed.get("emotion", "未知"), "intensity": float(parsed.get("intensity", 0.5)), "tags": parsed.get("tags", []), "reason": parsed.get("reason", ""), "method": "LM" } except Exception as e: print(f"第{attempt + 1}次调用失败: {e}") time.sleep(2 ** attempt) return fallback_dict_analysis(poem_text) # 降级到词典规则

这个接口有几个细节值得注意。一是temperature调低到0.3,让模型尽量稳定输出,减少随机性影响。二是做了三次指数退避重试,每次失败等待时间翻倍,避免接口临时限流导致整体任务失败。三是返回结果强制JSON解析,如果模型偶尔输出多余文字,把```json标记和反引号去掉再解析。四是超时异常全部捕获后降级到词典分析,这是演示保底的最后一道防线。

批量跑2000首诗词时,实际用时会比较长,建议把结果直接写入情感分析表,然后在前端直接读取。我当时一台普通笔记本跑全量大约花了四十多分钟,完全可接受。

3.4 知识图谱查询与推荐接口实现

图谱查询部分,BFS两跳路径可以这样实现:

def graph_recommend(poem_id: str, top_n: int = 5, max_depth: int = 2): poem = Poem.objects.get(poem_id=poem_id) poet = poem.poet # 第一跳:当前诗人的流派和朝代 first_jump = KG_Relation.objects.filter( head=poet.name, relation_type__in=["属于流派", "生活于朝代"] ) reco_scores = {} for rel in first_jump: tail = rel.tail # 第二跳:同流派或同朝代的诗人 second_jump = KG_Relation.objects.filter( head=tail, relation_type="代表作家" ) for sj in second_jump: same_poet = sj.tail if same_poet == poet.name: continue # 第三跳:该诗人的重要作品 poems = Poem.objects.filter(poet__name=same_poet) for p in poems[:3]: score = 0.7 if rel.relation_type == "属于流派" else 0.3 reco_scores[p.poem_id] = reco_scores.get(p.poem_id, 0) + score top_reco = sorted(reco_scores.items(), key=lambda x: x[1], reverse=True)[:top_n] return [Poem.objects.get(poem_id=pid) for pid, score in top_reco]

内容相似推荐和协同过滤我放在同一个推荐聚合服务里:

def aggregate_recommend(user, current_poem, top_n=10): results = {} # 1. 内容相似推荐 content_scores = content_similar_recommend(current_poem, top_n=5) for pid, score in content_scores.items(): results[pid] = results.get(pid, 0) + 0.4 * score # 2. 图谱路径推荐 graph_results = graph_recommend(current_poem.poem_id, top_n=5) for pid, score in graph_results: results[pid] = results.get(pid, 0) + 0.35 * score # 3. 协同过滤推荐(用户有行为时才生效) if user and user.behavior_exists(): cf_scores = itemcf_recommend(user, current_poem, top_n=5) for pid, score in cf_scores.items(): results[pid] = results.get(pid, 0) + 0.25 * score sorted_res = sorted(results.items(), key=lambda x: x[1], reverse=True) return [poem_id for poem_id, _ in sorted_res[:top_n]]

这个聚合接口就是前端推荐栏的单一数据源,返回结果是排序好的诗作ID列表。每次请求还支持携带用户行为日志ID,后端用于更新用户画像。

3.5 前端可视化与API联调

前端我采用的是Vue单页应用加ECharts的方案,Django后端只提供JSON接口。从实际联调效率看,前后端分离比Django模板直出省太多事,尤其图谱数据这种高频变化的结构,后端模板做起来极其痛苦。

关键页面的API约定是这样的:

  • GET /api/poems/,参数支持分页、朝代筛选、风格筛选、关键词搜索
  • GET /api/poems/{id}/,返回详情,附带情感分析结果
  • POST /api/behavior/,接收用户行为,行为类型和诗作ID
  • GET /api/recommend/?poem_id=xxx,返回推荐列表
  • GET /api/stats/overview/,返回看板统计
  • GET /api/knowledge-graph/?center_poet=xxx,返回分层图谱数据

可视化接收到数据的结构约定为:朝代统计返回名称数组和数值数组,情感分布返回情感类别和各类别数量,高频意象词云返回词与权重列表,知识图谱返回nodes和links两个数组,每个节点带category用于着色。

调试中最容易出问题的点在于跨域配置。Django默认不允许跨域请求,需要在INSTALLED_APPS里加django-cors-headers并在settings里配置允许的域名。这个坑几乎每个前后端分离项目都会踩一次,提前配好可以省下很多排查时间。

4. 常见问题与排查技巧实录

4.1 大模型接口返回不稳定

这是使用外部大模型接口几乎必踩的坑。症状包括:偶尔返回超时、偶尔返回内容不全是JSON、偶尔返回结果情感类别跑到候选项之外。

我的应对方案第一是重试,上一节代码里已经实现了指数退避。第二是"严格校验+自动降级",解析结果前先检查emotion字段是否在预定义类别集合里,不在就判定本次解析失败,直接降级到词典结果。第三是异步批量跑任务时对单首诗词失败次数做统计,连续失败三次就记录待办状态,全部跑完后自动重试一次,尽量避免人工干预。

还有一点很实用:把每次大模型请求的输入输出都记录在日志表里。答辩时你会有真实的数据支撑来展示"AI分析过程中遇到哪些典型诗词",评委很吃这一套。

4.2 情感分析结果不准

古诗词的情感确实容易判偏。我遇到过一个典型例子是诗句"醉卧沙场君莫笑,古来征战几人回",表面是豪迈洒脱,大模型把它标成"豪",但仔细品其实是悲凉到了极点。这类由豪转悲的复杂性暂时没有万全之策,但我摸索出一个补救办法:从模型返回的reason字段里提取判断逻辑,在前端展示情感分析详情时把"判断理由"高亮显示,这样即便标签有争议,用户也能看到系统思考的依据。论文里则如实写"大模型情感标签与传统词典方法存在不一致,以深度语义为准",把严谨性自然表达出来。

另一个优化技巧是给模型喂创作背景信息。同一首《静夜思》,脱离背景和加入"写于羁旅思乡之时"的分析结果会有细微差别,加入背景后的判断往往更贴近公认解读。数据准备阶段有创作背景字段的诗尽量保留,没有的可以用译文和赏析代替。

4.3 数据量不足导致推荐不理想

有段时间我把数据压缩到只有200首,内容相似推荐出来的东西跟自己太像,协同过滤完全没有效果。后来尝试了一个补救方案:把搜索行为也算进协同过滤矩阵,用户搜过"月"这个关键词,那么在诗作相似度上给所有包含"月"意象的诗统一加权重。搜索结果对推荐的影响立竿见影。

还有个更简单的冷启动兜底:当推荐结果列表不足10首时,用情感分布作为填充依据,即优先推荐与当前诗词情感类别相同但朝代不同的诗。这样用户浏览一首悲伤的宋词,推荐列表里可能带上一首同样悲伤的唐诗,逻辑上说得通,演示效果也不错。

4.4 答辩演示与源码包整理

快到答辩阶段有几个实用经验值得提前说。环境依赖务必用requirements.txt锁版本,别让评委演示时现场装依赖装出兼容性问题。演示时优先展示知识图谱力导向图和情感分析详情页,这两个页面视觉冲击力最强且能自然引出"大模型+知识图谱+推荐"的组合讲法。

代码仓库里建议区分"业务代码"和"模型标注数据"两类资源。所有涉及API密钥的文件一律用环境变量注入,千万别硬编码到源码里然后发给评委。答辩前在全新环境里跑一遍从安装到启动的完整流程,这一步能提前暴露一堆你开发机上根本不会遇到的依赖坑。

论文部分,正文的算法设计章节建议重点写情感分析双路径对比实验,用准确率、召回率和F1值来量化大模型与传统词典的差距,推荐系统则用推荐命中率和覆盖度作为评价指标。项目文档(LW)跟源码交付物保持一致的数据结构和接口说明,PPT就围绕"为什么要做大模型分析、图谱怎么建、推荐怎么算、可视化怎么呈现"四个问题展开,现场讲解的节奏十到十五分钟最合适。

5. 一些个人体会

这套系统从立项到完整跑通,我个人最大的感受是:一个看起来花哨的毕设题目,能不能做出真实深度,关键不在技术本身,而在你愿不愿意把其中一条链路彻底钻透。大模型接口调用并不复杂,知识图谱建模也不烧脑,但把两者的结果真正串起来、让它影响推荐排序、再呈现在可视化页面上,这个"闭环"才是评委最看重的工程思维。如果你正在做类似方向,建议不要贪多,先把"情感分析结果写入图谱节点"这一个打通,剩下的功能会自然而然跟着长出来。就算现场出了Bug,你只要能讲清楚每一条数据的来路和算法的设计逻辑,评委一般都不会为难,反而会觉得你是真的做进去了。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进