StructBERT文本相似度模型Python入门实战:快速构建文本查重系统

你是不是遇到过这样的情况?写论文时担心引用的部分和别人的太像,做内容审核时需要快速找出重复的帖子,或者只是想看看两篇文章到底有多相似。手动去对比?那太费时费力了。

今天,我就带你用Python和StructBERT模型,从零开始搭建一个属于自己的文本查重小工具。整个过程就像搭积木一样简单,不需要你有多深的AI背景,只要会一点Python基础,跟着步骤走,一个小时左右就能看到成果。我们会从安装环境开始,一步步走到最后用图形展示查重结果,全程代码清晰,解释直白。

准备好了吗?我们开始吧。

1. 环境准备:搭建你的Python工作台

工欲善其事,必先利其器。第一步,我们需要把“厨房”收拾好,把需要的“食材”和“厨具”都准备好。别担心,大部分工作就是复制粘贴几条命令。

1.1 安装Python和包管理工具

首先,确保你的电脑上安装了Python。推荐使用Python 3.8或以上的版本,兼容性更好。打开你的命令行终端(Windows上是CMD或PowerShell,Mac/Linux上是Terminal),输入下面的命令检查一下:

python --version
# 或者
python3 --version

如果显示了类似 Python 3.8.10 的信息,那就没问题。如果没有,你需要去Python官网下载并安装。

接下来,我们需要 pip,这是Python的包安装管理器,通常会和Python一起安装。同样,在终端里输入 pip --version 确认一下。

1.2 安装必需的Python库

我们的查重系统主要依赖几个核心的Python库。我们一次性把它们都安装好。在终端里逐条执行下面的命令:

# 安装PyTorch,这是我们的深度学习框架。根据你的电脑是否有NVIDIA显卡,命令稍有不同。
# 如果你有显卡并且想用GPU加速(速度会快很多),用这条命令:
pip install torch torchvision torchaudio --index-url https://download.pytorch.org/whl/cu118

# 如果你没有独立显卡,或者不确定,就用这条CPU版本的命令(更通用):
pip install torch torchvision torchaudio

# 安装Transformers库,这是Hugging Face提供的,里面包含了我们需要的StructBERT模型
pip install transformers

# 安装其他辅助工具库
pip install numpy   # 用于数值计算
pip install scikit-learn # 用于计算余弦相似度
pip install matplotlib # 用于画图可视化结果
pip install pandas  # 用于方便地处理数据(可选,但推荐)

安装过程可能需要几分钟,取决于你的网速。如果遇到某个包安装特别慢,可以考虑临时使用国内的镜像源,比如在命令后面加上 -i https://pypi.tuna.tsinghua.edu.cn/simple

全部安装完成后,我们的基础环境就准备好了。

2. 核心概念:三分钟理解文本相似度

在动手写代码之前,我们花两三分钟搞清楚我们要做的事情的核心逻辑。这样写代码时你会更清楚每一步在干什么。

想象一下,你怎么判断两段文字像不像?你可能会看它们用的词是不是差不多,句子结构是不是类似,表达的意思是不是一致。

计算机没法直接“理解”文字,所以我们需要把文字转换成它能理解的格式——数字向量,也叫嵌入。这个过程就像给每段文字拍一张“特征身份证”。

  1. 文本转向量:StructBERT模型就像一个非常厉害的“翻译官”,它能把一句话(比如“今天天气真好”)转换成一长串有意义的数字(比如 [0.1, -0.5, 0.8, ...])。语义相近的句子,转换出来的数字串也会在数学空间里比较接近。
  2. 计算相似度:得到了两个数字向量(代表两段文本)后,我们怎么衡量它们的“接近程度”呢?最常用的方法就是余弦相似度。你可以把它想象成比较两个箭头的方向:方向越一致,夹角越小,余弦值就越接近1,表示越相似;方向完全相反,余弦值接近-1,表示越不相关;方向垂直,余弦值接近0。

我们的查重流程,其实就是把上面两步自动化: 输入文本A -> StructBERT模型 -> 向量A 输入文本B -> StructBERT模型 -> 向量B 计算向量A和向量B的余弦相似度 -> 得到一个0到1之间的分数 -> 分数越高,文本越相似

是不是很简单?接下来我们就用代码把这个流程实现出来。

3. 分步实战:构建你的查重系统

现在进入最核心的实操环节。我会把代码分成几个小块,每块都有详细说明。你可以新建一个Python文件(比如叫 text_similarity.py),把代码一块块复制进去。

3.1 第一步:导入工具包

就像做菜前要把菜刀、案板摆好一样,我们先导入所有需要的Python库。

# 导入所需的库
import torch
from transformers import AutoTokenizer, AutoModel
from sklearn.metrics.pairwise import cosine_similarity
import numpy as np
import matplotlib.pyplot as plt
import warnings
warnings.filterwarnings('ignore') # 忽略一些不影响运行的警告信息

print("所有库导入成功!")

运行一下,如果没有报错,说明环境安装没问题。

3.2 第二步:加载StructBERT模型和分词器

我们需要从网上下载预训练好的StructBERT模型。Hugging Face的模型库就像一个“模型超市”,我们直接指定名字就能获取。这里我们使用 structbert-base-uncased 这个版本,它对英文处理效果很好。

# 指定模型名称
model_name = "structbert-base-uncased"

# 加载分词器。分词器负责把句子拆分成模型能认识的单词或子词单元。
print(f"正在加载分词器: {model_name}...")
tokenizer = AutoTokenizer.from_pretrained(model_name)

# 加载模型。这个模型就是我们的核心“翻译官”。
print(f"正在加载模型: {model_name}...")
model = AutoModel.from_pretrained(model_name)

# 将模型设置为评估模式(非训练模式),这样计算更快更稳定。
model.eval()
print("模型和分词器加载完毕!")

第一次运行这段代码时,它会从网上下载模型文件(大约几百MB),需要一点时间。下载完成后,以后运行就快了。

3.3 第三步:将单句转换为向量

我们来写一个函数,它的任务就是完成“文本->向量”的转换。

def get_sentence_embedding(sentence, tokenizer, model):
    """
    将单个句子转换为向量表示。

    参数:
        sentence (str): 输入的文本句子。
        tokenizer: 加载好的分词器。
        model: 加载好的StructBERT模型。

    返回:
        numpy.ndarray: 句子的向量表示(768维)。
    """
    # 1. 分词:将句子转换为模型需要的输入格式(token IDs, attention mask等)
    inputs = tokenizer(sentence, return_tensors="pt", padding=True, truncation=True, max_length=512)

    # 2. 模型推理:不计算梯度,以提升速度
    with torch.no_grad():
        outputs = model(**inputs)

    # 3. 提取句向量:通常取最后一层隐藏状态的第0个token([CLS])的向量作为整个句子的表示
    # `last_hidden_state` 的形状是 (batch_size, sequence_length, hidden_size)
    # 我们取第一个样本(batch 0)的第一个token([CLS], index 0)的所有隐藏单元
    sentence_embedding = outputs.last_hidden_state[:, 0, :].squeeze()

    # 4. 将PyTorch Tensor转换为NumPy数组,方便后续计算
    return sentence_embedding.numpy()

# 我们来测试一下这个函数
test_sentence = "The quick brown fox jumps over the lazy dog."
embedding = get_sentence_embedding(test_sentence, tokenizer, model)
print(f"句子 '{test_sentence}'")
print(f"转换后的向量维度: {embedding.shape}") # 应该输出 (768,)
print(f"向量前10个值: {embedding[:10]}") # 看一眼向量的样子

你会看到输出一个768维的向量,这就是我们句子的“数字身份证”。

3.4 第四步:计算两个句子的相似度

有了两个句子的向量,计算它们的余弦相似度就一行代码的事。

def calculate_similarity(sentence1, sentence2, tokenizer, model):
    """
    计算两个句子之间的余弦相似度。

    参数:
        sentence1 (str): 第一个句子。
        sentence2 (str): 第二个句子。
        tokenizer: 分词器。
        model: 模型。

    返回:
        float: 余弦相似度得分,范围在[-1, 1]之间,通常文本相似度在[0,1]。
    """
    # 获取两个句子的向量
    emb1 = get_sentence_embedding(sentence1, tokenizer, model).reshape(1, -1) # 重塑为(1, 768)
    emb2 = get_sentence_embedding(sentence2, tokenizer, model).reshape(1, -1) # 重塑为(1, 768)

    # 计算余弦相似度
    similarity_score = cosine_similarity(emb1, emb2)[0][0]
    return similarity_score

# 测试几组句子
pairs = [
    ("I love programming.", "Coding is my passion."), # 语义相似
    ("The cat sits on the mat.", "The dog plays in the park."), # 语义不同
    ("The weather is nice today.", "The weather is nice today."), # 完全相同
]

for sent1, sent2 in pairs:
    score = calculate_similarity(sent1, sent2, tokenizer, model)
    print(f"句子1: '{sent1}'")
    print(f"句子2: '{sent2}'")
    print(f"相似度得分: {score:.4f}")
    print("-" * 40)

运行后,你会看到第一对句子得分较高(可能0.7-0.9),第二对得分较低(可能0.1-0.3),第三对完全相同,得分应该非常接近1。看,你的查重系统已经能工作了!

4. 快速上手:构建一个完整的查重示例

让我们把上面的功能整合一下,模拟一个更真实的场景:检查一段新文本与一组现有文本的相似度。

def check_duplicate(new_text, existing_texts, tokenizer, model, threshold=0.85):
    """
    检查新文本与一组现有文本的相似度,找出可能重复的文本。

    参数:
        new_text (str): 待检查的新文本。
        existing_texts (list of str): 已有的文本库。
        tokenizer: 分词器。
        model: 模型。
        threshold (float): 相似度阈值,高于此值则认为可能重复。

    返回:
        list of tuples: 每个元组包含(已有文本索引, 相似度得分)。
    """
    print(f"检查新文本: '{new_text[:50]}...'") # 只打印前50字符
    new_embedding = get_sentence_embedding(new_text, tokenizer, model).reshape(1, -1)

    results = []
    for idx, existing_text in enumerate(existing_texts):
        existing_embedding = get_sentence_embedding(existing_text, tokenizer, model).reshape(1, -1)
        score = cosine_similarity(new_embedding, existing_embedding)[0][0]
        results.append((idx, score))

    # 按相似度从高到低排序
    results.sort(key=lambda x: x[1], reverse=True)

    print("\n相似度排名:")
    for idx, score in results:
        status = "【可能重复】" if score >= threshold else ""
        print(f"  与文本{idx}的相似度: {score:.4f} {status}")

    # 返回所有超过阈值的匹配
    duplicates = [(idx, score) for idx, score in results if score >= threshold]
    return duplicates

# 模拟一个“文本库”
corpus = [
    "Machine learning is a subset of artificial intelligence.",
    "Python is a popular programming language for data science.",
    "Deep learning models require large amounts of data for training.",
    "The sky is blue on a clear sunny day."
]

# 新来的文本
new_document = "Artificial intelligence encompasses machine learning as one of its branches."

# 进行查重
possible_duplicates = check_duplicate(new_document, corpus, tokenizer, model)

if possible_duplicates:
    print(f"\n发现 {len(possible_duplicates)} 处可能重复。")
    for idx, score in possible_duplicates:
        print(f"  文本{idx}: {corpus[idx]}")
else:
    print("\n未发现高度相似的文本。")

这个例子展示了如何将查重功能用于一个简单的文档库。你可以调整 threshold 阈值来控制查重的严格程度。

5. 结果可视化:让相似度一目了然

数字虽然精确,但不够直观。我们画个图来看看。

def visualize_similarity(new_text, existing_texts, tokenizer, model):
    """
    可视化新文本与所有现有文本的相似度。

    参数:
        new_text (str): 新文本。
        existing_texts (list of str): 现有文本列表。
        tokenizer: 分词器。
        model: 模型。
    """
    scores = []
    new_emb = get_sentence_embedding(new_text, tokenizer, model)

    for text in existing_texts:
        existing_emb = get_sentence_embedding(text, tokenizer, model)
        score = cosine_similarity(new_emb.reshape(1, -1), existing_emb.reshape(1, -1))[0][0]
        scores.append(score)

    # 创建图表
    fig, ax = plt.subplots(figsize=(10, 6))
    bars = ax.bar(range(len(existing_texts)), scores, color='skyblue')
    ax.axhline(y=0.85, color='r', linestyle='--', label='阈值 (0.85)') # 添加阈值线

    # 为每个柱子添加数值标签
    for bar, score in zip(bars, scores):
        height = bar.get_height()
        ax.text(bar.get_x() + bar.get_width()/2., height + 0.01,
                f'{score:.3f}', ha='center', va='bottom', fontsize=9)

    ax.set_xlabel('现有文本索引')
    ax.set_ylabel('余弦相似度')
    ax.set_title('新文本与现有文本库的相似度对比')
    ax.set_xticks(range(len(existing_texts)))
    ax.set_xticklabels([f'文本{i}' for i in range(len(existing_texts))])
    ax.legend()
    ax.set_ylim([0, 1.1]) # 相似度范围通常在0-1之间
    plt.tight_layout()
    plt.show()

# 使用之前的文本库和新文档进行可视化
visualize_similarity(new_document, corpus, tokenizer, model)

运行这段代码,会弹出一个柱状图。红色的虚线是我们设定的阈值(0.85),柱子高度代表相似度得分。一眼就能看出新文本和哪个旧文本最像。

6. 实用技巧与常见问题

到这里,核心功能已经完成了。但在实际使用中,你可能会遇到一些小问题,这里分享几个技巧。

技巧1:处理长文本 StructBERT模型对输入长度有限制(通常是512个token)。如果你的文本很长,有两种简单方法:

  • 截断:只取前512个token。tokenizertruncation=True 参数会自动做这件事。
  • 分段-平均:将长文本分成多个符合长度的小段,每段分别获取向量,然后对所有段的向量求平均,作为整个长文本的向量。这种方法更精细一些。

技巧2:提升计算速度 如果你要对比海量文本(比如上万篇),每次都实时计算向量会很慢。标准的做法是:

  1. 预先计算并存储向量:将你的文本库(corpus)中所有文本的向量提前算好,存到文件(如.npy)或向量数据库(如FAISS、Milvus)里。
  2. 查询时快速检索:当新文本到来时,只计算它的向量,然后去向量数据库里做快速的相似度搜索。这能极大提升效率。

常见问题排查:

  • 报错 CUDA out of memory:这是显卡内存不够了。如果你在用GPU,可以尝试:1) 减小 max_length 参数;2) 使用 model.to('cpu') 将模型转到CPU上运行(会慢一些)。
  • 相似度得分全是0.99或1:检查一下是不是输入了完全相同的句子,或者句子太短太简单(如单个单词)。模型对完全相同或极其简单的句子会给出高分。
  • 下载模型太慢或失败:可以尝试设置环境变量 HF_ENDPOINT=https://hf-mirror.com 使用国内镜像,或者在代码中指定本地已下载的模型路径。

7. 总结

跟着走完这一趟,你应该已经成功搭建起了一个基础的文本查重系统。我们从安装Python环境开始,到加载强大的StructBERT模型,再到写出将文字转换成向量的函数,最后计算出相似度并用图表展示出来。整个过程没有涉及特别复杂的理论,重点在于一步步动手实现。

用下来的感觉是,StructBERT模型的效果对于日常的语义相似度判断已经相当够用了,而且借助Hugging Face的Transformers库,调用起来非常方便。对于Python新手来说,这是一个很好的AI入门项目,你不仅学会了调用一个现成的深度学习模型,更重要的是理解了“文本->向量->相似度”这个在AI领域非常核心的处理流程。

你可以基于这个基础版本做很多有趣的扩展,比如做一个图形界面,连接一个真正的数据库来存储文本和向量,或者尝试不同的句子向量提取方法(比如用所有token向量的平均)。希望这个小小的项目能成为你探索自然语言处理世界的一块敲门砖。


获取更多AI镜像

想探索更多AI镜像和应用场景?访问 CSDN星图镜像广场,提供丰富的预置镜像,覆盖大模型推理、图像生成、视频生成、模型微调等多个领域,支持一键部署。

Logo

这里是“一人公司”的成长家园。我们提供从产品曝光、技术变现到法律财税的全栈内容,并连接云服务、办公空间等稀缺资源,助你专注创造,无忧运营。

更多推荐