简介一套基于Python与BERT模型的深度学习文本相似度检测系统毕业设计源码面向计算机相关专业毕业生和NLP入门学习者可用于课程设计、论文实现或实战演练。系统采用Python 3.6.8与MySQL 5.7搭建利用BERT双向Transformer提取深层次语义特征完成文本对的相似度匹配与结果可视化。压缩包共389个文件约52.27MB包含72个Python源码、数据库SQL脚本、前端页面与交互资源CSS/JS/HTML、部署说明文档和设计文档docx/pdf另有pyc编译文件及图片、字体等静态资源目录划分明确便于按模块阅读和二次开发。资源同时涉及PyCharm开发环境配置与Navicat数据库工具的使用说明可帮助快速理解项目结构并复现运行。目前已有69人学习对准备毕业设计或希望深入掌握BERT文本匹配应用的同学而言这是一份可直接运行、附带完整文档的高质量参考项目。1. 基于 Python 的 BERT 文本相似度检测源码包里不止是一个模型文本相似度检测是毕业设计里的高频题目但很多同学卡在同一个位置TF-IDF 和 Word2Vec 的效果太弱换到基于 Python 的 BERT 深度学习方案后又不知道代码怎么组织、模型参数怎么调、数据库怎么接。这套基于 Python 的 BERT 文本相似度检测系统源码包正好把从模型加载到 MySQL 存储的完整链路补齐了——它不是一个孤立的模型脚本而是一个能跑通“输入文本对、输出相似度分数、结果落库”的完整系统。项目里能看到 Python 3.6.8、MySQL 5.7、Navicat 11、PyCharm 这一整套开发环境组合文件结构里还有部署说明文档、数据库脚本、论文说明文档LW 目录和主工程目录project。也就是说这套东西既适合做毕设框架参考也适合课程设计直接复现。下面我从环境搭建开始把每个环节怎么落地、参数怎么设、坑在哪里按实际动手的顺序拆给你看。2. 环境搭建与数据库初始化Python 3.6.8 MySQL 5.7 的部署拆解2.1 为什么是 Python 3.6.8 而不是最新版本很多初学者会顺手装最新的 Python然后发现 BERT 相关依赖怎么装都报错。原因在于旧版 transformers、tensorflow/pytorch 的某些轮子只针对特定 Python 版本编译Python 3.9 以上直接无法安装或运行时崩溃。这个项目指定 Python 3.6.8 是合理的它是 3.6 系列的最终维护版本稳定性好兼容当时主流的深度学习库版本。创建虚拟环境是第一步避免污染系统 Python。# Windows 下用 py -3.6.8 -m venv venvLinux/Mac 用 python3.6 -m venv venv python -m venv venv # 激活虚拟环境 # Windows: venv\Scripts\activate # Linux/Mac: source venv/bin/activate # 确认版本 python --version pip --version虚拟环境的核心作用是隔离依赖。你本机如果还装了别的 Python 项目依赖版本互不干扰。第一次跑深度学习项目的人最容易犯的错误是全局安装一堆包最后版本冲突到无法收拾。venv 就是后悔药环境坏了直接删掉重建。requirements.txt 如果没有随包提供就按部署说明文档里列的包逐个安装。核心依赖一般是 torch 或 tensorflow、transformers、pandas、numpy、flask如果带 Web 界面。安装时优先用清华或阿里镜像加速不要直接怼默认源。2.2 MySQL 5.7 建库导表Navicat 11 的批量执行方式MySQL 5.7 是经典稳定版本支持 utf8mb4 编码对中文文本存储没有压力。项目里数据库目录下会提供建库建表 SQL 文件用 Navicat 11 导入最方便。打开 Navicat新建连接填主机 localhost、端口 3306、用户名 root、密码。连接成功后新建数据库注意字符集选择 utf8mb4排序规则选 utf8mb4_general_ci 或 utf8mb4_unicode_ci前者性能略好后者排序更准确中文场景下差别不大。-- 建库也可以在 Navicat 图形界面操作 CREATE DATABASE IF NOT EXISTS text_similarity DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci; USE text_similarity; -- 典型表结构保存每一次相似度检测的记录 CREATE TABLE similarity_record ( id INT AUTO_INCREMENT PRIMARY KEY, text_a TEXT NOT NULL, text_b TEXT NOT NULL, similarity_score FLOAT NOT NULL, create_time DATETIME DEFAULT CURRENT_TIMESTAMP ) ENGINEInnoDB DEFAULT CHARSETutf8mb4;导入数据库文件时在 Navicat 里右键目标数据库选“运行 SQL 文件”选中 database 目录下的 .sql 文件执行。执行完成后查看表列表确认表结构是否存在。如果 sql 文件里带了 CREATE DATABASE 语句执行时要注意当前连接指向避免建到错误的实例上。这里有个细节MySQL 5.7 默认的 sql_mode 会影响数据写入如果导入时报“Incorrect string value”错误大概率是表或库的字符集不对改成 utf8mb4 后重新导入即可。2.3 PyCharm 打开工程与运行入口用 PyCharm 打开 project 目录后需要把刚才创建的虚拟环境配置为解释器。File - Settings - Project - Python Interpreter选 Existing environment定位到 venv 目录下的 python.exe 或 bin/python。# 确认核心依赖已安装 pip list | findstr -i torch transformers flask运行主入口之前先找到项目里负责读取配置的文件通常是 config.py 或 settings.py。数据库连接信息host、port、user、password、database都在里面改成你自己环境的值。# config.py 典型内容 DB_HOST localhost DB_PORT 3306 DB_USER root DB_PASSWORD your_password DB_NAME text_similarity # 模型路径本地权重目录避免每次启动都联网下载 MODEL_PATH ./model/bert-base-chinese如果你拿到的是 PyTorch 版启动入口一般是 app.py 或 main.py运行后控制台会输出类似 Running on http://127.0.0.1:5000 的地址浏览器打开即可输入文本测试。3. BERT 文本相似度的核心逻辑从双向 Transformer 到向量余弦3.1 为什么 BERT 比 TF-IDF 和 Word2Vec 更适合相似度检测TF-IDF 基于词频统计只认识字面重合两个句子用词不同但语义相同“明天开会吗”和“明天有会么”相似度会很低。Word2Vec 虽然能表达词义但句子向量的传统做法是把词向量平均或加权求和词序信息几乎丢失且无法解决一词多义。BERT 的解决思路完全不同双向 Transformer 编码器在预训练阶段通过掩码语言模型Masked Language Model, MLM和下一句预测Next Sentence Prediction, NSP学习词语在上下文中的动态表示。同一个“苹果”在“苹果手机”和“苹果很好吃”里得到不同的向量这是静态词向量做不到的。文本相似度检测里常用的做法是把两个句子拼成一条输入序列中间用 [SEP] 分隔。BERT 的输入格式是[CLS] 句子A [SEP] 句子B [SEP][CLS] 位置的输出向量被设计为聚合整个序列语义的表示把它拿出来做余弦相似度或丢进分类层就是最常见的 Sentence Pair 方案。这套源码里采用的正是这个思路。3.2 模型加载与编码tokenizer 和 padding 的参数细节使用 BERT 最核心的操作是把原始文本转成 token id、attention mask 和 token type ids。transformers 库把这一整套封装好了但如果参数设置不对你会发现相似度计算结果非常离谱——原因通常是句子被截断得太短或者 padding 策略导致无效 token 参与了注意力计算。from transformers import BertTokenizer, BertModel import torch # 加载本地权重避免在线下载 tokenizer BertTokenizer.from_pretrained(./model/bert-base-chinese) model BertModel.from_pretrained(./model/bert-base-chinese) model.eval() def encode_pair(sentence_a, sentence_b, max_length128): 将句子对编码为 BERT 输入格式 参数说明 - max_length: 单句最大长度。中文场景下 128 足够覆盖绝大多数短文本 超过部分会被截断如果内容关键信息在后半句结果会受影响。 - padding: 填充到统一长度批处理时必须设置 - truncation: 超长截断True 表示直接截到 max_length inputs tokenizer( sentence_a, sentence_b, paddingmax_length, truncationTrue, max_lengthmax_length, return_tensorspt ) return inputspadding 选择 “max_length” 会让所有输入变成相同长度显存浪费一点但写法简单“longest” 则按批次内最长句子填充更节省空间。return_tensors“pt” 表示返回 PyTorch 张量如果你用的是 TensorFlow 后端这里要改成 “tf”。3.3 从 hidden states 到相似度分数三种取向量的方式对比拿到模型输出后怎么把每个 token 的向量汇集成句子向量是关键一步。常见方式有三种def get_sentence_vector(outputs, inputs, pool_strategycls): 从 BERT 输出中提取句子向量 outputs.last_hidden_state: [batch_size, seq_len, hidden_size] hidden_size 768base 模型 if pool_strategy cls: # 方式一取 [CLS] 位置向量维度 [batch_size, 768] return outputs.last_hidden_state[:, 0, :] elif pool_strategy mean: # 方式二对所有 token 向量做均值池化 # 注意要乘以 attention_mask把 padding 部分的向量排除 mask inputs[attention_mask].unsqueeze(-1).float() summed (outputs.last_hidden_state * mask).sum(dim1) counts mask.sum(dim1) return summed / counts elif pool_strategy max: # 方式三最大池化取每个维度上的最大值 mask_bool inputs[attention_mask].bool().unsqueeze(-1) masked_output outputs.last_hidden_state.masked_fill(~mask_bool, -1e9) return masked_output.max(dim1).values[CLS] 向量适合直接输入分类头的场景均值池化在语义相似度任务上往往比 [CLS] 更稳因为[CLS] 在预训练时的任务目标更偏向分类而不是相似度最大池化保留显著特征但对噪声敏感。建议跑几个例子对比一下差异通常在 0.02~0.08 之间。得到两个句子的向量之后用余弦相似度计算import numpy as np def cosine_similarity(vec_a, vec_b): 计算两个 768 维向量的余弦相似度范围 [-1, 1] dot: 向量点积 norm: 向量 L2 范数即各维度平方和的平方根 除数为 0 表示零向量返回 0语义完全不相关 dot np.dot(vec_a, vec_b) norm_a np.linalg.norm(vec_a) norm_b np.linalg.norm(vec_b) if norm_a 0 or norm_b 0: return 0.0 return float(dot / (norm_a * norm_b))余弦相似度只关注方向、不关注长度。均值池化后的向量长度会因为句子长短而波动所以余弦相似度比欧氏距离更适合这个场景。4. 源码走读与参数调整从代码结构到相似度落库4.1 project 目录的典型结构入口、服务层、模型层分离拿到源码包后不要急着双击运行先把目录结构读一遍。这套毕设源码的惯例是入口、业务逻辑、模型推理、数据访问分离即使你拿到的工程没有那么严格也会至少拆成几个模块。一个典型的 project 目录长这样目录/文件作用app.py 或 main.pyWeb 服务入口Flask/Django 路由model_service.py加载 BERT 模型、提供编码和相似度计算接口db_utils.py 或 database.pyMySQL 连接、增删改查封装templates/ 和 static/前端页面和静态资源bootstrap、layui 等config.py数据库连接、模型路径、超参数配置requirements.txt依赖包清单先打开 app.py 看路由再追 model_service 的调用链最后看 db_utils。这种读法比从头到尾乱翻高效得多——你能在半小时内定位到“文本进来之后谁在算相似度结果存在哪”。4.2 相似度计算的完整调用链从 HTTP 请求到 MySQL 落库源码里通常用 Flask 或 Django 提供接口。前端输入两个文本框点击提交后后端做相似度计算再把结果存进数据库。伪代码级别的调用链如下# app.py 中的核心路由Flask 风格 from flask import Flask, request, jsonify from model_service import similarity_service from db_utils import save_record app Flask(__name__) app.route(/compare, methods[POST]) def compare(): # 1. 接收前端提交的两个文本 data request.get_json() text_a data.get(text_a, ).strip() text_b data.get(text_b, ).strip() if not text_a or not text_b: return jsonify({code: 400, msg: 两个文本都不能为空}) # 2. 调用模型服务计算相似度内部完成向量化余弦计算 score similarity_service.compute_similarity(text_a, text_b) # 3. 结果写入 MySQL save_record(text_a, text_b, score) # 4. 返回给前端展示 return jsonify({code: 200, similarity: score})这个流程里最值得关注的是 similarity_service.compute_similarity。它内部会做 tokenize、模型推理、池化、余弦计算四件事。如果你要对模型做修改所有逻辑都集中在这里不需要碰前端和数据库代码。一个优化点是多次请求时重复加载模型的问题。BERT-base 模型参数量约 1.1 亿加载一次就需要数秒到十几秒如果每次请求都从磁盘重新加载系统根本无法用。源码里的正确做法是模块级单例# model_service.py _model None _tokenizer None def _get_model(): 懒加载单例模式第一次调用时加载之后复用 注意这个模块级缓存是必须的 如果不做缓存每次请求都重新读权重文件 在 CPU 机器上单次加载可能耗时 10 秒以上 global _model, _tokenizer if _model is None: _tokenizer BertTokenizer.from_pretrained(./model/bert-base-chinese) _model BertModel.from_pretrained(./model/bert-base-chinese) _model.eval() return _tokenizer, _model4.3 阈值设置与业务语义多少分算“相似”余弦相似度输出一个 [-1, 1] 之间的浮点数但业务上我们通常需要它变成一个可解释的结论——是“相似”还是“不相似”。这就是阈值的艺术也是你答辩时一定能讲出内容的点。源码里一般会在配置文件或前端判断逻辑处设置阈值# config.py 中的阈值配置 SIMILARITY_THRESHOLD 0.82 # 判断逻辑可能在前端 JS也可能在后端返回 def judge_similarity(score, thresholdSIMILARITY_THRESHOLD): 将分数映射为业务结论 阈值设定说明 - 0.8 以上文本对语义高度相似常见于同一事件的不同表述 - 0.6~0.8部分相关可能有共享主题但细节不同 - 0.6 以下基本不相关 具体阈值需要根据你的语料分布标定不要盲抄默认值 if score threshold: return 相似 return 不相似阈值不是玄学但确实需要实测标定。你可以先跑 100 对人工标注过的文本对记录分数分布再用分类效果最好的切分点作为阈值。如果业务场景是查重系统阈值应该设高一点0.85宁可漏报不可误报如果是问答匹配0.75 可能就已经够用。数据库落库这一步存储的不仅是分数建议把阈值和文本也一起存方便后续回测。表结构可以加一个 result_label 字段把 judge_similarity 的结果直接存进去——这样查历史记录时不需要重新计算。5. 避坑与排查环境、权重与中文编码的 5 个真实问题5.1 BERT 权重下载失败模型路径和网络问题的区别现象运行代码报 OSError: Cant load model或者下载进度条卡住不动。原因两种情况。第一种是代码里写的是 from_pretrained(bert-base-chinese)transformers 会尝试在线下载但网络不稳定导致失败第二种是你改了路径但路径下没有模型文件。解决先把权重文件下载到本地。常见做法是用镜像站下载设置环境变量 HF_ENDPOINT 指向镜像地址再执行下载脚本。下载完成后把 bert-base-chinese 目录放到工程下的 model 目录代码里统一写 from_pretrained(./model/bert-base-chinese)。如果是从别的机器拷贝权重注意目录里必须包含 config.json、pytorch_model.bin或 tf_model.h5、vocab.txt 三个核心文件。# Linux/Mac 下临时设置镜像下载权重 export HF_ENDPOINThttps://hf-mirror.com python -c from transformers import BertTokenizer, BertModel; BertTokenizer.from_pretrained(bert-base-chinese); BertModel.from_pretrained(bert-base-chinese)5.2 transformers 版本过高导致接口不兼容现象代码逻辑明明没改升级 transformers 库后 from_pretrained 报参数错误或者 tokenizer 的返回值结构不对。原因transformers 迭代极快旧版本代码里的一些写法在新版本中被移除或改名。比如某些版本的模型输出类型从 tuple 变为 ModelOutput索引方式就要改。解决以部署说明文档里锁定的版本为准不要手贱升级。装完依赖后执行下面命令确认版本并记到项目 README 里pip freeze | findstr -i transformers torch如果已经升级坏了不要尝试逐个降级直接删掉虚拟环境重建按 requirements.txt 重新安装这是最省时间的方案。5.3 中文乱码MySQL 5.7 字符集的经典翻车现象数据库表里存的中文变成问号或者前端传过来的是中文落库后变成乱码。原因三层配置只要有一层不对就会乱。数据库实例的 character_set_server 不是 utf8mb4表 DEFAULT CHARSET 不是 utf8mb4连接串或驱动没指定 charset。解决把三层全部统一。建库时强制指定Navicat 连接的高级属性里也设一遍如果代码里用 pymysql连接参数里加 charset“utf8mb4”。# db_utils.py 中的连接示例 import pymysql connection pymysql.connect( hostlocalhost, userroot, passwordyour_password, databasetext_similarity, charsetutf8mb4, # 这个参数经常被漏掉 cursorclasspymysql.cursors.DictCursor )注意 Python 3.6 里 pymysql 要用较新版本如果安装的是老版本可能不支持 utf8mb4。这个问题排查起来很费时间建议最开始建库时就把这一条做对。5.4 CPU 环境下推理速度极慢不是死机是模型在算现象点击提交后页面转圈十几秒甚至几十秒才返回结果以为程序卡死。原因BERT-base 模型参数量约 1.1 亿在纯 CPU 环境下做一次前向传播需要几百毫秒到数秒如果句子长度没限制速度会更慢。如果还叠加了每次请求重新加载模型的问题那就不是“慢”而是“不可用”了。解决先确认模型是否走了单例加载。确认后限制 max_length 到 128这个长度对短文本语义足够更彻底的方案是升级到 GPU 环境或者在深度学习框架中启用半精度 float16显存占用减半、速度提升明显。毕设答辩时用 CPU 演示是常见操作提前把按钮 disable、加载状态提示做好观感会好很多。5.5 相似度计算结果全是 0.99 或全是 0.5池化方式与模型未微调现象随便输入两句话相似度都显示 0.95 以上或者无论怎么换输入分数都稳定在一个固定值附近。原因全 0.99 说明向量被 padding 主导了——attention_mask 没有正确应用模型把大量 [PAD] 位置的向量也池化进了句子向量全 0.5 则有可能是向量没取对位置取了 [SEP] 或随机位置的输出。解决回到 3.3 节的代码检查 mask 是否乘上。调试时可以打印向量前 20 个数值如果大部分都是 0基本可以确定 mask 没生效。另一个原因是模型没有微调通用预训练模型直接做相似度输出会偏中性这在毕设里可以接受但你要能在答辩时说出这个局限和改进方向。6. 进阶用法用批量回测验证相似度阈值替换候选集做召回拿到这套系统以后我建议你做的第一件事不是改界面而是先跑一遍批量回测把阈值定在合理区间。项目自带 Web 界面只适合演示真正评估模型能力要靠离线测试集。# batch_test.py 批量回测脚本 import random import numpy as np from model_service import get_sentence_vector, similarity_service random.seed(42) def build_test_pairs(): 构造测试集50 对语义相同/不同文本 真实项目中这里应该替换为你自己的标注数据 格式: [(text_a, text_b, label)] label: 1 表示相似, 0 表示不相似 pairs [] # 示例数据实际从文件或数据库读取 pairs.append((今天天气怎么样, 今天天气如何, 1)) pairs.append((今天天气怎么样, 明天会下雨吗, 0)) # ... 省略其余 98 对 return pairs def find_best_threshold(test_pairs, thresholdsnp.arange(0.5, 0.95, 0.01)): 在测试集上搜索最优阈值 逻辑对每个候选阈值计算准确率并记录 准确率最高的阈值即为当前数据集上的最优切分点 best_acc 0.0 best_threshold 0.8 for thr in thresholds: correct 0 for text_a, text_b, label in test_pairs: score similarity_service.compute_similarity(text_a, text_b) pred 1 if score thr else 0 if pred label: correct 1 acc correct / len(test_pairs) if acc best_acc: best_acc acc best_threshold thr return best_threshold, best_acc best_t, best_acc find_best_threshold(build_test_pairs()) print(f最优阈值: {best_t:.2f}, 最高准确率: {best_acc:.2%})参数说明thresholds 从 0.5 到 0.95 每 0.01 步进搜索覆盖大多数相似度场景的敏感区间。如果你的语料偏严格比如代码查重搜索范围可以改成 0.7 到 0.99如果语料偏宽松比如新闻分类从 0.3 开始搜。准确率最优和 F1 最优的阈值可能不一样分类不平衡时建议换成 F1。第二个进阶方向是替换候选集做召回。当前系统是两两比较文本对复杂度是 O(n²)。如果应用中你有 10 万条文本需要比对两两计算要 50 亿次完全不现实。常见做法是把所有句子向量提前算好存进数据库或内存用近似最近邻检索比如 faiss先召回 Top-K 候选再做精确 BERT 相似度排序。这样把 O(n²) 降成 O(n log n) 量级。# 伪代码向量化入库 近似检索 def build_vector_index(all_texts, conn): 把全部文本编码成向量存入数据库 for text in all_texts: vec similarity_service.encode_text(text) # (768,) ndarray # 存入向量表字段: text, vector BLOB, create_time insert_vector_record(conn, text, vec.tobytes()) def search_similar(query_text, conn, top_k10): 检索相似文本先用 SQL 粗筛再精排 query_vec similarity_service.encode_text(query_text) # 实际项目中这里用 faiss 或 MySQL 的向量插件 candidates ann_search(query_vec, top_k) return sorted(candidates, keylambda x: x[score], reverseTrue)我没有在这套源码里看到完整的 faiss 集成代码但这不影响你把它作为毕设的进阶亮点——答辩时你能说清“如果数据量扩大我会怎么改进”这个点比单纯做完一个 demo 会高一个档次。最后一次跑完整流程时我自己的习惯是先跑一遍 batch_test把阈值打印出来贴到论文的实验分析里再对那 50 个测试样本逐个手工核对分数确认没有明显反例然后才会截 Web 界面的图放进论文。从那以后我每次拿到这类 NLP 毕设源码都会强制走一遍这个回测流程——它能用十分钟的时间帮你提前发现模型加载、池化逻辑、数据库写入三层的问题比上线后再排查省太多时间。希望帮到你。本文还有配套的精品资源点击获取
