文本相似度BM25算法python实现(可执行)以及所有函数的详细解释

本文介绍了一种基于BM25算法的文本相似度计算方法,包括文档预处理、词语统计及权重计算等步骤,适用于信息检索及文本处理场景。
各个部分代码详解
inition(docs)
def inition(docs):
    # D为所有文档的总共数量
    D = len(docs)
    # 通过lamba计算所有的文档的长度的总和,并求取平均值,计算总平均长度
    avgdl = sum([len(doc) + 0.0 for doc in docs]) / D
    # 遍历所有的文档
    for doc in docs:
        tmp = {}
        # 遍历某一个文档中的所有的词语,统计其出现的次数
        for word in doc:
            # 以字典的形式对结果进行存储,如果不存在对应word,就将之默认设为0
            tmp[word] = tmp.get(word, 0) + 1
        # 将每一个文档的单词统计进行保存
        f.append(tmp)
        # 遍历每一文档的key值,遍历当前文档中的所有的值
        for k in tmp.keys():
            # 统计每一个词语在所有的文档中出现的次数。第一次出现就默认置为0
            tf[k] = tf.get(k, 0) + 1
    # 遍历字典中每一个条目的key值和value值,计算每一个单词出现的权重
    for k, v in tf.items():
        idf[k] = math.log(D - v + 0.5) - math.log(v + 0.5)
    # 返回总长度和平均长度
    return D, avgdl
  • 基本的功能:遍历文章库中的所有的文章,以字典的形式统计文章中所有词语的出现次数
  • dict.get(k,0):在字典dict中,查找key为k的对应的value,如果没有将之置为0。
get_sentences()
# 操作文本,将对应的文本进行分词和去停词操作,获取一个文章或者文字中的所有的断句
def get_sentences(doc):
    # 匹配所有换行符,回车符
    line_break = re.compile('[\r\n]')
    # 匹配所有的标点符号
    delimiter = re.compile('[,。?!;]')
    sentences = []
    # 将文章分成一个一个地句子,并遍历每一个句子
    for line in line_break.split(doc):
        # 去除一个句子中的所有的空格
        line = line.strip()
        # 如果当前的行只有空格,为空,直接跳过
        if not line:
            continue
        # 对每一个句子按照标点符号进行分割,保留每一个断句
        for sent in delimiter.split(line):
            # 去除每一个断句的空格
            sent = sent.strip()
            # 如果断句为空,直接跳过当前的循环
            if not sent:
                continue
            # 将每个断句保存到sentences的表格中
            sentences.append(sent)
    return sentences
  • 基本功能描述:获取一片文章中所有的句子
  • line_break = re.compile(’[\r\n]’),生成识别句子的正则识别对象
  • delimiter = re.compile(’[,。?!;]’):生成将完整的句子按照标点符号进行分割的正则识别器
    • 先将完整的文章,分割成一行一行的句子,然后再将一个一行句子按照标点分成一个一个的小分句。
filter_stop()
stop = set()
fr = codecs.open('stopwords.txt', 'r', 'utf-8')
# 将完整的停止词字典加载入集合当中,并保存
for word in fr:
    stop.add(word.strip())
fr.close()
# re_zh专门用来匹配汉子
re_zh = re.compile('([\u4E00-\u9FA5]+)')

def filter_stop(words):
    # 将语句中的所有的停止词过滤了
    return list(filter(lambda x: x not in stop, words))
  • filter(函数,容器):遍历容器中的每一个元素,将符合函数条件的生成一个新的列表
  • stop:是停止词构成的集合
  • words:是输入的词构成的列表
  • 整体函数功能:过滤掉words单词列表中的所有停止词
  • strip():去除所有为空的字符,比如果’\r\n’等等
    • 字符串.strip(‘字符’):从头到尾遍历所有的字符串,删除所有与字符串相同字符,并将字符串返回
    • 默认为空,表示去除其中的所有的空字符
  • re.compile(’([\u4E00-\u9FA5]+)’):创造能匹配所有中文字符的正则匹配对象
可执行的完全的代码
import math
import jieba #结巴分词
import os
import re
import codecs

# 测试文本
text = '''
自然语言处理是计算机科学领域与人工智能领域中的一个重要方向。
它研究能实现人与计算机之间用自然语言进行有效通信的各种理论和方法。
自然语言处理是一门融语言学、计算机科学、数学于一体的科学。
因此,这一领域的研究将涉及自然语言,即人们日常使用的语言,
所以它与语言学的研究有着密切的联系,但又有重要的区别。
自然语言处理并不是一般地研究自然语言,
而在于研制能有效地实现自然语言通信的计算机系统,
特别是其中的软件系统。因而它是计算机科学的一部分。
'''
# 列表的每一个元素是一个dict,dict存储着一个文档中每个词的出现次数
f = []
# 储存每个词以及该词出现的文本数量
tf = {}
# 储存每个词的idf值,每一个词语和文章的相关度,计算某个词语在总共的n个文件中出现的次数
idf = {}
# 调节因子,调节K
k1 = 1.5
# 调节因子
b = 0.75
# 定义所有文档的平均长度
avgdl = 0
# 定义包含了所有的文档长度的数组序列
document = list()
# 定义总容量,来表示所有文章的数量
D = 0

def inition(docs):
    # D为所有文档的总共数量
    D = len(docs)
    # 通过lamba计算所有的文档的长度的总和,并求取平均值,计算总平均长度
    avgdl = sum([len(doc) + 0.0 for doc in docs]) / D
    # 遍历所有的文档
    for doc in docs:
        tmp = {}
        # 遍历某一个文档中的所有的词语,统计其出现的次数
        for word in doc:
            # 以字典的形式对结果进行存储,如果不存在对应word,就将之默认设为0
            tmp[word] = tmp.get(word, 0) + 1
        # 将每一个文档的单词统计进行保存
        f.append(tmp)
        # 遍历每一文档的key值,遍历当前文档中的所有的值
        for k in tmp.keys():
            # 统计每一个词语在所有的文档中出现的次数。第一次出现就默认置为0
            tf[k] = tf.get(k, 0) + 1
    # 遍历字典中每一个条目的key值和value值,计算每一个单词出现的权重
    for k, v in tf.items():
        idf[k] = math.log(D - v + 0.5) - math.log(v + 0.5)
    # 返回总长度和平均长度
    return D, avgdl

# 单独计算文本相似度得分,各个语素相关性得分的加权平均和
def sim(doc, index):
    score = 0.0
    # 遍历doc中的每一个单词,各个单词的加权的加权和
    for word in doc:
        # 如果这个单词没有在文档库中出现过,就直接跳过不计算
        if word not in f[index]:
            continue
        # 在文档库中,就要进行计算
        # 此处的d是文档的实际长度,document对应的是保存了所有文档的实际长度的序列
        d = len(document[index])
        # 计算doc短语在某一个篇文章中的权重
        score += (idf[word] * f[index][word] * (k1 + 1) / (f[index][word] + k1 * (1 - b + b * d / avgdl)))
    return score

# 批量计算文本相似度的得分
def simall(doc):
    scores = []
    # 遍历所有的文章,并计算传入的字符串在所有文章中的权重构成
    for index in range(D):
            # 传入对应的文章库,和特定的索引
            score = sim(doc, index)
            # 记录所有的文章相关性的列表
            scores.append(score)
    # 将生成的所有的列表权重返回
    return scores



# 过滤掉特定列表中的所有的停止词
def filter_stop(words):
    stop = set()
    fr = codecs.open('stopwords.txt', 'r', 'utf-8')
    # 将完整的停止词字典加载入集合当中,并保存
    for word in fr:
        stop.add(word.strip())
    fr.close()
    # re_zh专门用来匹配汉子
    re_zh = re.compile('([\u4E00-\u9FA5]+)')
    # 将语句中的所有的停止词过滤了
    return list(filter(lambda x: x not in stop, words))

# 操作文本,将对应的文本进行分词和去停词操作,获取一个文章或者文字中的所有的断句
def get_sentences(doc):
    # 匹配所有换行符,回车符
    line_break = re.compile('[\r\n]')
    # 匹配所有的标点符号
    delimiter = re.compile('[,。?!;]')
    sentences = []
    # 将文章分成一个一个地句子,并遍历每一个句子
    for line in line_break.split(doc):
        # 去除一个句子中的所有的空格
        line = line.strip()
        # 如果当前的行只有空格,为空,直接跳过
        if not line:
            continue
        # 对每一个句子按照标点符号进行分割,保留每一个断句
        for sent in delimiter.split(line):
            # 去除每一个断句的空格
            sent = sent.strip()
            # 如果断句为空,直接跳过当前的循环
            if not sent:
                continue
            # 将每个断句保存到sentences的表格中
            sentences.append(sent)
    return sentences

# 获取目标文章中的句子
sents = get_sentences(text)
doc = []
for sent in sents:
    words = list(jieba.cut(sent))
    words = filter_stop(words)
    doc.append(words)
print(doc)
document = doc

D, avgdl = inition(doc)
# 这里是将每一个句话当做是一个文本,然后进行相关的变化
print(tf)
# 输出最终的每一个词语出现的次数
print(f)
# 计算每一个词语的idf
print(idf)
# 输入一个测试文本和来计算和最终的query的相关度
print(simall(['自然语言', '计算机科学', '领域', '人工智能', '领域']))
分析与总结
  • 这个方法是有一点问题的,已经人工地将句子处理为一行一个,在实际中并不是这样,如果一个句子比较长,横跨了两行,那就不好处理,会拆开一些特殊的关键字,但是可以作为一个初步理解BM25方法的简单的程序。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值