简介:这份资源是面向Python初学者、高校学生及毕设开发者的一套深度学习文本相似度检测系统完整项目包,基于BERT模型与Django框架构建,适合作为课程设计、毕业设计或Web开发练手参考。压缩包为zip格式,整体约75.86MB,内含项目源码、数据库脚本、说明文档、LW论文及PPT等文件,覆盖后端逻辑、模型调用与数据库集成等环节,便于按模块对照学习。目前已有164人学习下载,说明其在同类毕设项目中具备一定参考价值。通过研读源码与文档,读者可掌握BERT文本相似度计算、Django接口设计及数据库交互等关键技能,并借助论文与PPT快速梳理系统架构与实现思路,为后续开发或答辩积累可复用的实践经验。
1. 文本相似度检测系统:从 BERT 到 Django 的完整落地路径
做毕设或企业原型时,最常被问到的一个需求就是「判断两段文本是不是一个意思」。传统做法靠编辑距离、Jaccard 系数,遇到同义替换、语序调整就翻车。这个项目用 Python + BERT + Django 搭了一套完整的文本相似度检测系统,把深度学习模型封装成 Web 服务,前端提交两段文本,后端返回相似度分数和判定结果。它适合三类人:正在找毕设题目的学生、想学 BERT 工程化落地的开发者、需要快速搭一个语义匹配原型的团队。资源包里包含项目源码、数据库脚本、设计文档、论文和答辩 PPT,拿到手就能跑通全流程。下面按「模型怎么用 → 系统怎么搭 → 坑在哪」的顺序拆开讲。
2. BERT 文本相似度检测:模型选型与推理链路拆解
2.1 为什么选 BERT 而不是 TF-IDF 或 Word2Vec
文本相似度检测的核心是判断两段文本在语义层面是否接近。TF-IDF 只看词频,两句话用词不同但意思一样时,相似度直接归零。Word2Vec 能捕捉部分语义,但词向量是静态的,同一个词在不同语境下向量相同,「苹果手机」和「苹果水果」分不开。BERT 通过 Transformer 的注意力机制,让每个词的表示都跟上下文相关,能真正理解句子的语义。
这个项目用的是 BERT-base 的中文预训练模型,输出 768 维的句向量。具体做法是把两段文本拼接后送入模型,取[CLS]位置的输出接一个全连接层做二分类,判断「相似」或「不相似」。也有另一种做法是分别编码两段文本,用余弦相似度计算分数。项目源码里两种方式都有实现,可以根据场景切换。
选型上还有一个现实考量:BERT-base 中文模型大约 400MB,推理时单条文本在 CPU 上约 50-100ms,GPU 上可以压到 10ms 以内。如果对延迟不敏感、追求精度,这个方案是合理的。如果要做高并发在线服务,可以考虑蒸馏后的 TinyBERT 或 ALBERT,体积和延迟都能降一个量级。
2.2 数据预处理与模型加载的关键步骤
拿到原始文本后不能直接喂给模型,需要经过分词、截断、padding 三步。中文 BERT 用的是字级别分词,不需要额外做分词工具。下面是项目里数据预处理的核心代码:
from transformers import BertTokenizer, BertForSequenceClassification import torch # 加载预训练分词器和模型 # num_labels=2 表示二分类:相似/不相似 tokenizer = BertTokenizer.from_pretrained('bert-base-chinese') model = BertForSequenceClassification.from_pretrained( 'bert-base-chinese', num_labels=2 ) def preprocess(text_a, text_b, max_len=128): """ 将两段文本编码为 BERT 输入格式 text_a: 第一段文本 text_b: 第二段文本 max_len: 最大序列长度,超出截断 """ encoding = tokenizer( text_a, text_b, max_length=max_len, padding='max_length', truncation=True, return_tensors='pt' ) return encoding # 推理示例 def predict_similarity(text_a, text_b): model.eval() encoding = preprocess(text_a, text_b) with torch.no_grad(): outputs = model(**encoding) logits = outputs.logits probs = torch.softmax(logits, dim=1) # probs[0][1] 是「相似」的概率 similarity_score = probs[0][1].item() return similarity_score这段代码的逻辑是:tokenizer把两段文本拼成一个序列,中间用[SEP]分隔,开头加[CLS]。padding='max_length'保证所有输入长度一致,truncation=True防止超长文本报错。推理时用torch.no_grad()关闭梯度计算,减少显存占用。softmax把 logits 转成概率值,取「相似」类别的概率作为最终分数。
参数方面,max_len设 128 是常见做法,覆盖大多数短文本场景。如果文本较长,可以调到 256 或 512,但显存占用和推理时间会线性增长。num_labels=2是二分类,如果要做多级相似度(如「高度相似」「部分相似」「不相似」),改成 3 即可。
2.3 训练数据的构造与微调策略
预训练模型直接拿来用效果有限,需要在领域数据上微调。项目里用的是 LCQMC 和 STS-B 的中文部分作为训练数据,格式是「句子A \t 句子B \t 标签」。标签 1 表示相似,0 表示不相似。数据量建议至少 5000 对以上,否则容易过拟合。
微调时的关键参数:学习率设 2e-5 到 5e-5,batch size 设 16 或 32,训练 3-5 个 epoch。学习率太大会破坏预训练权重,太小则收敛慢。项目源码里用的是 AdamW 优化器,带权重衰减 0.01,这是 BERT 微调的标准配置。
from transformers import AdamW, get_linear_schedule_with_warmup # 优化器和学习率调度器 optimizer = AdamW(model.parameters(), lr=2e-5, weight_decay=0.01) total_steps = len(train_dataloader) * 3 # 3 个 epoch scheduler = get_linear_schedule_with_warmup( optimizer, num_warmup_steps=int(total_steps * 0.1), num_training_steps=total_steps )warmup设总步数的 10%,让学习率从 0 线性上升到设定值,避免训练初期梯度震荡。这是 BERT 微调的常规操作,不加 warmup 有时也能跑,但收敛稳定性差一些。
3. Django 后端集成:把 BERT 模型封装成 Web 接口
3.1 项目目录结构与数据库设计
这个项目用的是 Django 作为 Web 框架,整体目录结构如下:
text_similarity/ ├── manage.py ├── requirements.txt ├── similarity/ # 核心应用 │ ├── models.py # 数据库模型 │ ├── views.py # 视图函数 │ ├── urls.py # 路由配置 │ ├── bert_utils.py # BERT 推理封装 │ └── templates/ # 前端页面 ├── static/ # 静态资源 └── media/ # 上传文件存储数据库用的是 MySQL,核心表有两张:text_record存储每次检测的文本和结果,user表存用户信息。建表 SQL 在资源包的database.sql里,导入后直接可用。
CREATE TABLE text_record ( id INT AUTO_INCREMENT PRIMARY KEY, text_a TEXT NOT NULL, text_b TEXT NOT NULL, similarity_score FLOAT, result_label VARCHAR(20), created_at DATETIME DEFAULT CURRENT_TIMESTAMP, user_id INT, FOREIGN KEY (user_id) REFERENCES auth_user(id) );similarity_score存浮点分数,result_label存「相似」或「不相似」的判定结果。加索引的话,user_id和created_at是常用查询字段,建议加上。
3.2 模型加载与视图函数实现
Django 启动时如果每次都重新加载 BERT 模型,响应会非常慢。正确做法是在应用初始化时加载一次,全局复用。项目里用的是 Django 的AppConfig.ready()方法:
# similarity/apps.py from django.apps import AppConfig import torch from transformers import BertTokenizer, BertForSequenceClassification class SimilarityConfig(AppConfig): default_auto_field = 'django.db.models.BigAutoField' name = 'similarity' def ready(self): # 全局加载模型,只执行一次 self.tokenizer = BertTokenizer.from_pretrained('bert-base-chinese') self.model = BertForSequenceClassification.from_pretrained( 'bert-base-chinese', num_labels=2 ) self.model.eval() # 有 GPU 就用 GPU self.device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') self.model.to(self.device)视图函数接收前端 POST 过来的两段文本,调用模型推理,把结果写入数据库并返回 JSON:
# similarity/views.py from django.http import JsonResponse from django.views.decorators.csrf import csrf_exempt from django.apps import apps import torch import json @csrf_exempt def detect_similarity(request): if request.method != 'POST': return JsonResponse({'error': '仅支持 POST 请求'}, status=405) data = json.loads(request.body) text_a = data.get('text_a', '').strip() text_b = data.get('text_b', '').strip() if not text_a or not text_b: return JsonResponse({'error': '文本不能为空'}, status=400) config = apps.get_app_config('similarity') encoding = config.tokenizer( text_a, text_b, max_length=128, padding='max_length', truncation=True, return_tensors='pt' ) encoding = {k: v.to(config.device) for k, v in encoding.items()} with torch.no_grad(): outputs = config.model(**encoding) probs = torch.softmax(outputs.logits, dim=1) score = probs[0][1].item() label = '相似' if score > 0.5 else '不相似' return JsonResponse({ 'similarity_score': round(score, 4), 'result_label': label })这里有几个细节值得注意:csrf_exempt装饰器是为了方便接口测试,生产环境应该用 token 认证替代。encoding里的张量要手动搬到 GPU 上,否则模型在 GPU 而输入在 CPU 会报错。阈值 0.5 是默认值,实际使用时可以根据业务需求调整,比如要求高精度时调到 0.7。
3.3 前端交互与结果展示
前端页面用 Django 模板渲染,一个简单的表单提交两段文本,AJAX 请求后端接口,把返回的分数和判定结果展示出来。核心 JS 逻辑:
async function checkSimilarity() { const textA = document.getElementById('textA').value; const textB = document.getElementById('textB').value; if (!textA || !textB) { alert('请填写两段文本'); return; } const response = await fetch('/api/similarity/', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({text_a: textA, text_b: textB}) }); const data = await response.json(); document.getElementById('score').textContent = data.similarity_score; document.getElementById('label').textContent = data.result_label; }页面展示时,相似度分数用进度条可视化会更直观。项目模板里用的是 Bootstrap 的 progress bar,根据分数动态设置宽度和颜色。分数高于 0.8 显示绿色,0.5 到 0.8 显示黄色,低于 0.5 显示红色。
4. 避坑与排查:BERT + Django 集成中最容易翻车的五个点
4.1 模型加载超时导致 Django 启动失败
现象:运行python manage.py runserver时卡住不动,或者报TimeoutError。
原因:AppConfig.ready()里加载 BERT 模型时,如果本地没有缓存,会从网络下载预训练权重。国内网络环境下下载 400MB 文件很容易超时。
解决:提前手动下载模型文件放到本地目录,然后用绝对路径加载。常见做法是在 HuggingFace 镜像站下载pytorch_model.bin、config.json、vocab.txt三个文件,放到pretrained/bert-base-chinese/目录下,加载时把模型名改成路径:
model = BertForSequenceClassification.from_pretrained( './pretrained/bert-base-chinese', num_labels=2 )4.2 GPU 显存不足导致推理中断
现象:推理时报CUDA out of memory,或者服务跑一段时间后崩溃。
原因:BERT-base 模型本身占约 1.5GB 显存,加上输入数据和中间激活值,如果 batch size 设得大,或者同时有多个请求并发,显存很快耗尽。
解决:把max_len从 512 降到 128,batch size 设为 1(Web 服务通常单条推理),并在推理后手动释放缓存:
torch.cuda.empty_cache()如果显存实在紧张,直接用 CPU 推理,虽然慢但稳定。
4.3 中文编码问题导致文本乱码
现象:前端提交的中文文本到后端变成乱码,或者数据库里存的是问号。
原因:Django 默认编码是 UTF-8,但 MySQL 数据库如果建库时没指定字符集,默认可能是 latin1。
解决:建库时指定CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci。Django 的settings.py里确认DATABASES配置中有'OPTIONS': {'charset': 'utf8mb4'}。前端页面<meta charset="UTF-8">也要加上。
4.4 模型推理结果每次不一样
现象:同样的两段文本,多次请求返回的相似度分数有细微差异。
原因:模型没有切换到评估模式,Dropout 层还在随机丢弃神经元。
解决:加载模型后立即调用model.eval(),推理时用torch.no_grad()包裹。这两个操作缺一不可,只加no_grad不加eval仍然会有随机性。
4.5 数据库连接数耗尽
现象:服务运行一段时间后报Too many connections。
原因:Django 默认每个请求新建一个数据库连接,请求量大时连接数暴涨。
解决:在settings.py里配置CONN_MAX_AGE,让连接复用:
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'text_similarity', 'CONN_MAX_AGE': 600, # 连接保持 600 秒 # ... 其他配置 } }5. 进阶技巧:用 ONNX 加速推理与阈值调优
模型跑通之后,下一步就是优化推理速度和判定准确率。这里分享两个我实际用过的技巧。
ONNX 导出与推理加速。PyTorch 模型直接推理有框架开销,导出成 ONNX 格式后用onnxruntime推理,CPU 上通常能快 2-3 倍。导出脚本:
import torch from transformers import BertTokenizer, BertForSequenceClassification model = BertForSequenceClassification.from_pretrained( './pretrained/bert-base-chinese', num_labels=2 ) model.eval() dummy_input = { 'input_ids': torch.randint(0, 1000, (1, 128)), 'attention_mask': torch.ones(1, 128, dtype=torch.long), 'token_type_ids': torch.zeros(1, 128, dtype=torch.long) } torch.onnx.export( model, (dummy_input,), 'bert_similarity.onnx', input_names=['input_ids', 'attention_mask', 'token_type_ids'], output_names=['logits'], dynamic_axes={ 'input_ids': {0: 'batch', 1: 'seq'}, 'attention_mask': {0: 'batch', 1: 'seq'}, 'token_type_ids': {0: 'batch', 1: 'seq'} }, opset_version=14 )导出后加载 ONNX 模型推理:
import onnxruntime as ort import numpy as np session = ort.InferenceSession('bert_similarity.onnx') inputs = { 'input_ids': encoding['input_ids'].numpy(), 'attention_mask': encoding['attention_mask'].numpy(), 'token_type_ids': encoding['token_type_ids'].numpy() } logits = session.run(['logits'], inputs)[0] score = np.exp(logits[0][1]) / np.sum(np.exp(logits[0]))dynamic_axes让模型支持变长输入,opset_version=14是兼容性较好的版本。ONNX 推理不依赖 PyTorch,部署时环境更轻量。
阈值调优。默认 0.5 的阈值在多数场景下够用,但如果你做的是「查重」类应用,宁可漏判不可误判,阈值应该调高到 0.7 甚至 0.8。反过来,如果是「推荐相似内容」,阈值可以降到 0.3 让更多结果通过。调优方法是在验证集上画 P-R 曲线,找到 F1 分数最高的点。项目文档里有一节专门讲这个,用的是 sklearn 的precision_recall_curve。
批量推理。如果一次要检测多对文本,不要循环单条推理,把 batch size 设大一次性送入模型。比如一次检测 32 对,速度比循环快 5-8 倍。注意max_len要统一,padding 到同一长度。
def batch_predict(pairs, batch_size=32): results = [] for i in range(0, len(pairs), batch_size): batch = pairs[i:i+batch_size] texts_a = [p[0] for p in batch] texts_b = [p[1] for p in batch] encoding = tokenizer( texts_a, texts_b, max_length=128, padding='max_length', truncation=True, return_tensors='pt' ) with torch.no_grad(): outputs = model(**encoding) probs = torch.softmax(outputs.logits, dim=1) scores = probs[:, 1].tolist() results.extend(scores) return results从那以后我每次部署 BERT 模型前,都强制走一遍 ONNX 导出和阈值验证,确认推理速度和判定边界都符合预期再上线。这套流程帮我省了不少返工时间,希望帮到你。
本文还有配套的精品资源,点击获取