查看原文
其他

数据治理 | 从“今天中午吃什么”中学习Python文本相似度计算

分享小技巧的 数据Seminar 2023-10-20

目录

一、前言

二、用 Python 计算文本相似度

  1. 编辑距离

  2. 杰卡德距离

  3. 余弦相似度

  4. TF-IDF

  5. 基于近义词

三、总结

本文共4540个字,阅读大约需要15分钟,欢迎指正!

Part1前言

在现实生活中,文本信息无处不在。理解并学习文本数据的内在涵义一直是一个非常活跃的研究课题,文本相似度分析作为文本数据分析的一大基础领域,其重要性不言而喻。在社科研究和数据清洗中,文本相似度常用于模糊匹配,文本分类、聚类等应用场景。是文本分析的一大利器。
本文将会为大家介绍几种计算文本相似度的方法以及对应的 Python 实现代码,希望对大家的学习有所帮助。
本文中所有 Python 代码均在集成开发环境 Visual Studio Code (VScode) 中使用交互式开发环境 Jupyter Notebook 编写和运行

Part2用 Python 计算文本相似度

计算文本相似度的算法有很多种,对于相同的文本,不同的方法得到的相似度可能并不相同,根据需求选择合适的算法得到的才是优解。下面将会为大家介绍五种常用的相似度计算方法和对应的 Python 代码
本文中使用的模块较多,导入模块前请先安装。

1编辑距离

编辑距离(Edit Distance),又称 Levenshtein Distance,是指两个字串之间,由一个转换成另一个所需的最少编辑操作次数,如果它们的编辑距离越大,说明它们越是不同。许可的编辑操作包括将一个字符替换成另一个字符,插入一个字符,删除一个字符等。编辑距离的大小一定程度反映了两个字符串的相似程度。
举个例子:让我们来计一下下面两个字符串的编辑距离。
S1 = '今天中午吃什么' 
S2 = '今天中午什么都不想吃'
以 S2 转换为 S1 为例,我们只需要将 S2 中的“都不想吃”四个字符逐一删除,再在“中午”和“什么”之间加上“吃”字即可。一共删除四个字符,添加一个字符,所以字符串 S1 和 S2 的编辑距离就是 5。使用 Python 计算编辑距离的代码如下:
# 导入模块
from distance import levenshtein
# 计算编辑距离
levenshtein('今天中午吃什么','今天中午什么都不想吃')  
# 得到:5
以编辑距离算法为基础开发而来的文本相似度计算模块不多,fuzzywuzzy 是其中非常好用的一个,支持简单匹配、非完全匹配、忽略顺序匹配等多种计算文本相似度的方式。下面是简单使用 fuzzywuzzy 模块的代码:
# 导入 fuzzywuzzy 中计算文本相似度的方法
from fuzzywuzzy import fuzz

# 文本相似度分数从 0 到 100,数值越大,相似度越高
# 1.简单匹配:默认方式计算两个字符串的相似度。
fuzz.ratio('今天中午吃什么','今天中午什么都不想吃')
# 得到: 71

# 2.非完全匹配,当一个字符串和另一个字符串的某一部分
# 相似度较高时,可以得到较高的结果
fuzz.partial_ratio('今天中午吃什么''中午了,该去吃饭了。今天中午吃什么呢?反正我吃什么都行。')
# 得到:100

# 3.忽略顺序匹配:顾名思义,倒装句的死敌;但是
# 词语之间需要使用空格分隔,最好先分词再计算
fuzz.token_sort_ratio('今天中午 吃什么''吃什么 今天中午')
# 得到:100

# 4.去重子集匹配,使用前最好先分词,原因同上
fuzz.token_set_ratio('今天 中午 吃什么','今天 中午 中午 中午 吃什么')
# 得到:100

2杰卡德距离

杰卡德距离 (Jaccard Distance),也称杰卡德系数,是用来衡量两个集合差异性的一种指标,常用于文本查重。杰卡德距离的计算方式也很简单,按照以下方式计算即可:
  1. 去掉 junk 字符。什么是 junk 字符呢?就是我们不希望计入匹配的字词,可以自定义。
  2. 计算两字符串之间所有匹配片段的长度之和 M,则杰卡德距离的计算公式就是:
若两个字符串完全相同,则结果为 1,完全不相同结果为 0。其结果将会在区间 [0,1] 之间。
举个例子,计算以下两个字符串的杰卡德系数:
S1 = '今天中午吃什么' 
S2 = '今天中午什么都不想吃'
默认不添加 junk 字符,那么 S1 中的 7 个字符都能在 S2 中匹配到同样的字符,故所有匹配片段的长度就是 7,则杰卡德系数的计算公式是 7*2/(7+10), 计算结果为 0.8235294117647058。
在 Python 中使用标准库 difflib 即可计算杰卡德系数,也可以说是基于杰卡德系数计算文本相似度,代码如下:
# 导入模块
from difflib import SequenceMatcher
# 计算相似度
SequenceMatcher(None, '今天中午吃什么','今天中午什么都不想吃').quick_ratio()
# 得到:0.8235294117647058

3余弦相似度

余弦相似度就是通过一个向量空间中两个向量夹角的余弦值作为衡量两个个体之间差异的大小。把 1 设为相同,0 设为不同,那么相似度的值就是在 0~1 之间,所有的事物的相似度范围都应该是 0 ~ 1。余弦相似度的特点是余弦值接近于 1,夹角趋于 0,表明两个向量越相似。根据这种理论,我们需要做的就是将文本转化为向量,再计算其余弦相似度即可。
我们可以使用 Python 中的 simtext 模块轻松地计算文本之间的余弦相似度,还可以计算杰卡德距离和编辑距离,非常容易上手。操作代码如下:
# 导入计算文本相似度的类
from simtext import similarity
# 定义文本相似度对象
sim = similarity()
# 计算文本相似度
res = sim.compute('今天中午吃什么','今天中午什么都不想吃')
res
所得结果为字典,键为算法名称,值为对应的结果,具体的计算可以参考下图:

4TF-IDF

TF-IDF(Term Frequency-Inverse Document Frequency)是一种统计方法,广泛应用于文本分析多个领域,用以评估某一字词对于一个文件集或一个语料库中的其中一份文件的重要程度。字词的重要性随着它在文件中出现的次数成正比增加,但同时会随着它在语料库中出现的频率成反比下降。
在一份给定的文件中,词频 (term frequency, TF) 指的是某一个给定的词语在该文件中出现的次数。这个数字通常会被归一化,以防止它偏向长的文本。逆向文件频率 (inverse document frequency, IDF) 是一个词语普遍重要性的度量。某一特定词语的IDF,可以由总文件数目除以包含该词语之文件的数目,再将得到的商取对数得到。
某一特定文件内的高频率词语,以及该词语在整个文件集合中的低文件频率,可以产生出高权重的TF-IDF。因此,TF-IDF倾向于过滤掉常见的词语,保留重要的词语。TF-IDF的主要思想是:某个词或短语在一篇文章中出现的频率TF高,并且在其他文章中很少出现,则认为此词或者短语具有很好的类别区分能力,适合用来分类。
详细的计算规则可以参考这篇文章:TF-IDF详解http://www.ruanyifeng.com/blog/2013/03/tf-idf.html
基于 TF-IDF 的文本相似度 Python 代码如下:
# 导入 sklearn 中可以将文本进行 tf-idf 处理并转化为向量的方法
from sklearn.feature_extraction.text import TfidfVectorizer
# 导入数学计算方法
import numpy as np
from scipy.linalg import norm
# 导入分词工具
import jieba

# 定义计算相似度的方法
def tfidf_similarity(s1, s2):
    def add_space(s):
        # 分词,词语间使用空格分隔
        return ' '.join(jieba.lcut(s))
    
    # 将词语中间加入空格,方便转为矩阵
    s1, s2 = add_space(s1), add_space(s2)
    # 转化为矩阵
    cv = TfidfVectorizer(tokenizer=lambda s: s.split(' '))
    corpus = [s1, s2]
    vectors = cv.fit_transform(corpus).toarray()
    # 计算TF-IDF系数
    return np.dot(vectors[0], vectors[1]) / (norm(vectors[0]) * norm(vectors[1]))

s1 = '今天中午吃什么'
s2 = '今天中午什么都不想吃'
print(tfidf_similarity(s1, s2))
# 得到:0.7092972666062738,换算为百分制就是 71

5基于近义词

中华文化博大精深,有时两句话仅相差一字,含义差之千里;有时两句话一个字都不同,含义则十分接近。以上几种文本相似度算法几乎都只关心文本中的字词,当遇到表述不同的近义语句时,效果并不理想。于是就有人开发出利用中文近义词计算文本相似度的算法。
Python 中的 synonyms 模块是一个使用中文近义词进行自然语言处理的工具,可以用于自然语言理解的很多任务:文本对齐,推荐算法,相似度计算,语义偏移,关键字提取,概念提取,自动摘要,搜索引擎等。由于这个模块是依赖近义词语料库的,所以首次导入时会下载约 100+ MB 的词典包并加载它,所以会稍慢些。另外这个模块的安装容易出现问题,具体问题请到网上查询解决方案。
下面我们使用 synonyms 计算文本相似度,并对比这种方和其他算法的差异,代码如下:
S1 = '尽管步履维艰,我们依然不会放弃'
S2 = '就算困难重重,咱们仍旧不能退出'

# 使用 synonyms 计算文本相似度,基于近义词
import synonyms
print(synonyms.compare(S1, S2, seg=True))  # 得到:0.659

# 编辑距离法
print(fuzz.ratio(S1, S2))  # 得到:20

# 杰卡德系数法
print(SequenceMatcher(None, S1,S2).quick_ratio())  # 得到: 0.2
以上结果充分说明基于近义词的方法可以在一定程度上减少同义词对文本相似度的负面影响。除此之外,synonyms 还有以下有趣的用法:
# 列出一个词语的近义词和相似度,基于语料库
synonyms.display('转发')
# 得到:
'转发'近义词:
  1. 转发:1.0
  2. 留言:0.62906045
  3. 跟帖:0.5896571
  4. 回帖:0.58484876
  5. 转贴:0.58105844
  6. 删帖:0.57204753
  7. 刷屏:0.5678909
  8. 朋友圈:0.56369257
  9. 登出:0.5478899
  10. 截取:0.52591866
  
 # 分词并列出词语的词性
 synonyms.seg('尽管步履维艰,我们依然不会放弃')
 (['尽管''步履维艰'',''我们''依然''不会''放弃'],
 ['c''l''x''r''d''v''v'])
尽管 synonyms 的算法看起来非常科学,但它仍无法完美应对中文文本中出现的各种情况,而且语料库还不够完善,请谨慎在大规模文本分析中使用这种工具进行文本相似度分析。

Part3总结

中文文本分析在数据分析中是一个宏大的方向,但即使是在较为基础的文本相似度分析方面,迄今为止仍没有接近完美的计算方法。尽管如此,本文中介绍的几种也依然非常实用,关键在于如何根据需求选择合适的计算方法。
我们将在数据治理板块中推出一系列原创推文,帮助读者搭建一个完整的社科研究数据治理软硬件体系。该板块将涉及以下几个模块(点击标题即可跳转至相应合集):
  1. 计算机基础知识
  2. 编程基础
  3. 数据采集
  4. 数据存储
  5. 数据清洗
  6. 数据实验室搭建
  7. 数据治理特别篇



星标⭐我们不迷路!想要文章及时到,文末“在看”少不了!

点击搜索你感兴趣的内容吧

往期推荐


基本无害 | 第三章第一节(全)—— 回归的基本原理

数据治理 | 地址数据可视化—教你如何绘制地理散点图和热力图

基本无害 | 使回归有意义——基本原理(3)

数据治理 | 根据地址获取经纬度及行政区划——API的妙用

基本无害 | 使回归有意义——基本原理(2)

数据治理 | 工企地址清洗——Python的妙用





数据Seminar




这里是大数据、分析技术与学术研究的三叉路口


文 | 《社科领域大数据治理实务手册》


    欢迎扫描👇二维码添加关注    

点击下方“阅读全文”了解更多

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存