最近在搞数据的治理工作,遇到一些单一的简短字符串,需要一些基本的过滤筛选。总结几个简单的文本字符相似度过滤方法,不涉及语义处理,只能处理一些 粗糙的过滤。
在NLP中,计算序列相似度可以使用多种方法,从简单的字符串匹配到复杂的语义分析,以下是一些常见的技术:
1… 编辑距离(Levenshtein距离)
: 这是一个衡量两个字符串相似度的经典方法,它通过计算将一个字符串转换成另一个字符串所需的最少单字符编辑操作次数(插入、删除或替换)来表示。
2… 余弦相似度
: 在这种方法中,文本首先被转换为向量(例如,使用词袋模型),然后计算这两个向量之间的余弦角度,以此来度量它们的相似性。
3… Jaccard相似度
: 这种方法计算两组之间的交集与并集的比例,通常用于衡量基于集合(如单词集合)的相似度。
4… n-gram重叠
: n-gram是一个序列中连续的n项,通常用来衡量两个文本序列的局部相似性。比较两个序列共有的n-gram数量可以提供它们的相似度。
5… 基于词嵌入的相似度
: 用预训练的词嵌入(如Word2Vec或GloVe)来表示文本,可以捕捉到词汇的语义信息,然后通过计算向量之间的距离(如余弦距离)来衡量相似性。
6… 序列对齐
: 比如Smith-Waterman算法和Needleman-Wunsch算法,这些主要用于生物信息学中,但在考虑到结构化文本数据时也可以借鉴。
7…变换器模型(如BERT, GPT-3)
: 这些先进的深度学习模型能够生成具有丰富语义层面相似度的文本表示,适合更复杂的相似性判断任务。
8… 语义文本相似度(Semantic Textual Similarity, STS)
: 该任务涉及计算两个文本片段的相似度得分,通常是在0到1或者0到5之间,代表从不相关到完全语义相同的程度。
选择哪种方法取决于特定应用场景和需求。在实际操作中,可能需要根据任务的特点和数据的性质进行调整和优化。
编辑距离是衡量两个序列差异的常用方法,它通过计算将一个序列转换成另一个所需的最小编辑操作次数来工作。
# 需要先安装 python-Levenshtein 包
# pip install python-Levenshtein
import Levenshtein
str1 = '我喜欢吃苹果'
str2 = '我喜欢吃香蕉'
# 计算编辑距离
edit_dist = Levenshtein.distance(str1, str2)
# 编辑距离越小,相似度越高,可以根据需要转换为相似度分数
max_len = max(len(str1), len(str2))
similarity = 1 - edit_dist / max_len
print(f"编辑距离: {edit_dist}")
print(f"相似度: {similarity}")
原理分析
:
示例一:使用了编辑距离(Levenshtein Distance)
来计算两个字符串的相似度。编辑距离是一种衡量两个序列差异
的指标,它通过计算从一个字符串转换到另一个字符串所需的最少单字符编辑操作的数量来定义。
单字符编辑操作包括以下几种:
插入
:在一个字符串中插入一个字符。
删除
:从一个字符串中删除一个字符。
替换
:将一个字符串中的一个字符替换为另一个字符。
编辑距离的原理可以用一个二维矩阵来进行动态规划求解
,其中矩阵的每个元素dp[i][j]
表示第一个字符串前i
个字符和第二个字符串前j
个字符之间的最小编辑距离。矩阵的填充遵循如下规则:
初始化第一行和第一列,代表与空字符串(长度为0)的编辑距离。
对于其他格子dp[i][j]
:
如果当前两个字符相等(无需编辑),那么dp[i][j] = dp[i-1][j-1]
。
如果不相等,需要考虑三种情况(插入、删除、替换)并选取最小编辑距离:
插入:dp[i][j-1] + 1
删除:dp[i-1][j] + 1
替换:dp[i-1][j-1] + 1
矩阵的最后一个元素dp[m][n]
就是两个字符串的最小编辑距离
,其中m
和n
分别是两个字符串的长度。
编辑距离越小,意味着两个字符串越相似。因此,可以通过计算两个字符串的最大长度max_len
,然后用1 - (edit_dist / max_len)
来计算一个归一化的相似度分数,这个分数越接近1
,表示两个字符串越相似。
Jaccard相似度衡量两个集合的交集与并集之间的比例。在文本分析中,我们通常把句子切分成词集合来计算Jaccard相似度
import jieba
def jaccard_similarity(str1, str2):
# 分词并转换为集合
set1 = set(jieba.cut(str1))
set2 = set(jieba.cut(str2))
# 计算交集和并集
intersection = set1.intersection(set2)
union = set1.union(set2)
# 计算Jaccard相似度
return len(intersection) / len(union)
str1 = '我喜欢吃苹果'
str2 = '我喜欢吃香蕉'
similarity = jaccard_similarity(str1, str2)
print(f"Jaccard 相似度: {similarity}")
原理分析
:
示例2: 使用了 Jaccard 相似度来计算两个字符串的相似程度。Jaccard 相似度
是一种用于衡量两个集合之间相似性的统计度量,它被定义为两个集合交集的大小与并集大小之比。
对于文本数据,Jaccard 相似度通常按照以下步骤计算:
分词
:首先将每个字符串(句子)分割成单词或者词片段的集合。在中文中,由于词语之间没有像英文那样的空格分隔,因此需要使用分词工具(如 jieba)来实现这一分割过程。
构建集合
:将分割后得到的词汇转换成集合形式,以便去除重复的元素,并方便执行集合操作。
计算交集
:找出两个集合中共同的元素所组成的新集合,即为交集。
计算并集
:将两个集合中所有的元素合并成一个新集合,即为并集。合并时会自动去除重复元素。
求比值:Jaccard 相似度就是交集元素数量与并集元素数量之比。数学上可以表示为:
[J(A, B) = \frac{|A \cap B|}{|A \cup B|}]
这里 ( A )
和 ( B )
分别是两个文本经过分词后形成的集合,( |A \cap B|
) 是两者交集
的大小,( |A \cup B|
) 是两者并集
的大小。
Jaccard 相似度的值范围在 0 到 1 之间
,其中 0 表示没有相似性
(即两个集合完全不重叠),而 1 表示完全相同
(即两个集合完全重叠)。通常情况下,Jaccard 相似度作为一个比较粗略
的相似度度量,适用于基于存在共同特征的简单相似度
评估任务。
import jieba
import numpy as np
from collections import Counter
# 定义函数计算余弦相似度
def cosine_similarity(vec1, vec2):
"""计算两个向量之间的余弦相似度"""
dot_product = np.dot(vec1, vec2)
norm_vec1 = np.linalg.norm(vec1)
norm_vec2 = np.linalg.norm(vec2)
return dot_product / (norm_vec1 * norm_vec2)
# 定义函数将句子转换为向量
def sentence_to_vector(sentence, word_set, word_dict):
"""根据给定的单词集合将句子转换为向量"""
word_vec = [0] * len(word_set)
for word in sentence:
if word in word_dict:
word_vec[word_dict[word]] += 1
return word_vec
# 定义函数计算两个句子的相似度
def calculate_similarity(sentence1, sentence2):
# 分词
words1 = list(jieba.cut(sentence1))
words2 = list(jieba.cut(sentence2))
# 构建单词集合
word_set = set(words1).union(set(words2))
# 构建单词到索引的映射字典
word_dict = {word: i for i, word in enumerate(word_set)}
# 将句子转换为向量
sentence_vector1 = sentence_to_vector(words1, word_set, word_dict)
sentence_vector2 = sentence_to_vector(words2, word_set, word_dict)
# 计算余弦相似度
similarity = cosine_similarity(sentence_vector1, sentence_vector2)
return similarity
# 测试两个句子的相似度
sentence_a = '我喜欢吃苹果'
sentence_b = '我不喜欢吃香蕉'
similarity_score = calculate_similarity(sentence_a, sentence_b)
print(f"两个句子的相似度为:{similarity_score}")