zoukankan      html  css  js  c++  java
  • 基于python语言使用余弦相似性算法进行文本相似度分析

    编写此脚本的目的:

      本人从事软件测试工作,近两年发现项目成员总会提出一些内容相似的问题,导致开发抱怨。一开始想搜索一下是否有此类工具能支持查重的工作,但并没找到,因此写了这个工具。通过从纸上谈兵到着手实践,还是发现很多大大小小的问题(一定要动手去做喔!),总结起来就是理解清楚参考资料、按需设计、多角度去解决问题。

    脚本进行相似度分析的基本过程:

      1、获取Bug数据。读取excel表,获取到“BugID”和“Bug内容”

      2、获取指定格式的Bug关键字集合。使用“jieba包”,采用“搜索模式”,对Bug内容进行分词,获取到分词表后,使用“正则表达式”过滤,拿到词语(词语长度>=2),提出掉单个字、符号、数字等非关键字

      3、计算词频(TF)。在步骤2中获取到关键字总量,在本步骤,则对筛选后的关键字进行频率统计,最后得出“TF值(关键字出现频率/总词数)”

      4、获取词逆文档频率(IDF)(也可以理解为权重)。在本步骤,有个重要的前提就是“语料库”,这里我没有使用开放、通用的语料库,而是使用本项目的“测试用例步骤、约束条件”等内容,唯一的原因就是“很适应Bug内容的语言场景”,在此模块,也是采用分词,然后利用参考资料中的计算公式,获取到每个“关键词”的IDF值,并存至名称为“get_IDF_value.txt”的文件中(一开始没放到这里,导致出现重复、多次进行本步骤计算,几kb的文本内容还能接受,几百kb的就......当时跑了一夜都没完!)

      5、获取TF_IDF值,并据此对Bug关键字进行倒序排列,然后硬性截取所有Bug排位前50%的关键字,并形成集合,然后以冒泡的形式,从第一个Bug开始,进行“相似度计算”(公式见参考资料),最终将相似度大于阀值的Bug,以形式“Bug编号_1(被比对对象)-Bug编号_2(比对对象)”打印到名称为“bug_compare_result.xls”的Excel表中

    过程中一些特殊点的处理:

      1、“所有Bug排位前50%的关键字,并形成集合”之所以创建该列表是为减少单个列表或字典在多个函数使用的频率,肯定是可以减少脚本问题频率的。

      2、语料库的选择是为适应使用需求

      3、得到TF_IDF值的目的只是为了获取到“排位前50%的Bug关键字”

      4、逆文档频率(IDF)值会存储至“get_IDF_value.txt”文件中,每次运行脚本时会提醒是否更改语料库

    说明:

      1、代码友好性差,技术能力有限。当然可塑性很高 ~ ~

      2、出于练习的目的,独立编写“IDF计算”的脚本,并放到另一路径下

    源码:(纯生肉,通过代码注释还是能看到本人设计过程的心态)

    sameText.py

    --------------------sameText.py--------------------------

    #-*- coding:utf-8 -*-
    import jieba
    import re
    import CorpusDatabase.getIDF as getIDF
    import math
    import xlrd
    import xlwt
    import time
    #######获取原始bug数据
    re_rule_getTF = re.compile(r"^([u4e00-u9fa5]){2,20}")#选择中文开头,且文字长度为2-20,一定要记住哇
    Top_List = []#将分词的列表和TF_IDF值前50%的列表合于此处
    Compare_CosResult = []#相似度大于??(可调整)的问题号
    BugID_List = []#用于存放BugID
    getIDF_get_IDF_value = {}#存储语料库中各关键词的权重
    xiangsidu = 0.8
    ###########
    def read_Originalbase(filename):
        Original_Bug_Dict = {}
        book = xlrd.open_workbook(filename)
        sheet_name = book.sheet_by_index(0)
        nrows = sheet_name.nrows
        for i in range(nrows):
            Original_Bug_Dict[sheet_name.row_values(i)[0]] = sheet_name.row_values(i)[1]
        return Original_Bug_Dict
    
    ###########词频统计###################
    def get_TF_value(test_String_one):
        word_counts = 0#统计除空、单字、符号以外的总词数
        counts = {}
        counts_two = {}
        words_re_TF = []
        words = jieba.lcut_for_search(test_String_one)
    #这边得加段正则表达式的,筛除掉words非中文且单字的内容,并构建words_re_TF
        for i in words:
            if re.match(re_rule_getTF,i):
                words_re_TF.append(i)
        #统计每个分词的出现次数,即总词数量counts
        Top_List.append(words_re_TF)#全局的作用列表,很关键
        for word in words_re_TF:
                word_counts = word_counts + 1
                counts[word] = counts.get(word,0) + 1#为关键字出现次数进行统计,从0开始,get中第一个为key,0为赋给其对应的value
        #获取每个关键字在整句话中出现的频率,即最终词频
        for count in counts:
            counts_two[count] = counts_two.get(count,(counts[count]/float(word_counts)))
        return counts_two
    ###########获取TF_TDF值,并向量化#############
    def get_TF_IDF_Key(test_String_two):
        global getIDF_get_IDF_value
        TestString_TF_value = {} 
        TF_IDF_value = {}
        The_Last_TF_IDF_List = []
        Corpus_weight_value = getIDF_get_IDF_value#此部分直接定义一个存储语料库权重的全局变量吧
        TestString_TF_value = get_TF_value(test_String_two)
        for i in TestString_TF_value:
            balance_value = 1
            for j in Corpus_weight_value:
                if i == j:
                    TF_IDF_value[i] = float(TestString_TF_value[i]) * float(Corpus_weight_value[j])
                elif i != j and balance_value != len(Corpus_weight_value):#遍历列表过程中,判断是否与TF的值一样,且是否遍历到最后一个元素了
                    balance_value = balance_value + 1#计算遍历次数
                    continue
                else:
                    TF_IDF_value[i] = float(TestString_TF_value[i]) * 1#对于语料库中不存在的词,权重默认为1
        The_Last_TF_IDF_List = sorted(TF_IDF_value,key = TF_IDF_value.__getitem__,reverse=True)
        The_Last_TF_IDF_List = The_Last_TF_IDF_List[0:int((len(The_Last_TF_IDF_List)/2) + 0.5)]#默认取一半关键字
        Top_List.append(The_Last_TF_IDF_List)#全局的作用列表,很关键
        return The_Last_TF_IDF_List
    def cosine_result(the_Top_List):#计算余弦相似度
        global Compare_CosResult
        global BugID_List
        global xiangsidu
        cos_result = 0.0
        keymix = []
        compare_one = []
        compare_two = []
        control_i = 0
        for i in range(0,len(the_Top_List) - 1,2):
            control_j = control_i + 1
            for j in range(i + 2,len(the_Top_List) - 1,2):
                keymix = list(set(the_Top_List[i+1] + the_Top_List[j+1]))#去掉重复的关键字
                compare_one = the_Top_List[i]
                compare_two = the_Top_List[j]
                compare_one_dict = {}
                compare_two_dict = {}
                fenzi_value = 0.0
                fenmu_value = 1.0
                fenmu_value_1 = 0.0
                fenmu_value_2 = 0.0
                for word_one in compare_one:
                    for unit_keymix_one in keymix:
                        if unit_keymix_one == word_one:
                            compare_one_dict[unit_keymix_one] = compare_one_dict.get(unit_keymix_one,0) + 1
                        else:
                            compare_one_dict[unit_keymix_one] = compare_one_dict.get(unit_keymix_one,0) + 0
                for word_two in compare_two:
                    for unit_keymix_two in keymix:
                        if unit_keymix_two == word_two:
                            compare_two_dict[unit_keymix_two] = compare_two_dict.get(unit_keymix_two,0) + 1
                        else:
                            compare_two_dict[unit_keymix_two] = compare_two_dict.get(unit_keymix_two,0) + 0
    ###########计算余弦相似度##################
                for k in compare_one_dict:
                    fenzi_value = fenzi_value + compare_one_dict[k] * compare_two_dict[k]
                    fenmu_value_1 = fenmu_value_1 + math.pow(compare_one_dict[k],2)
                    fenmu_value_2 = fenmu_value_2 + math.pow(compare_two_dict[k],2)
                    fenmu_value = math.sqrt(fenmu_value_1) * math.sqrt(fenmu_value_2)
                cos_result = fenzi_value / fenmu_value
                if cos_result >= xiangsidu:#调控值
                    Compare_CosResult.append(str(int(BugID_List[control_i])) + '-' + str(int(BugID_List[control_j])))#浮点数转整数再转字符串
                control_j = control_j + 1
            control_i = control_i + 1
        return 0
    #########将最终结果写入到新的xls文件中#########
    def write_BugBase(bug_base,CosResult_list):
        global xiangsidu
        key_list = list(bug_base.keys())
        value_list = list(bug_base.values())
        newbook = xlwt.Workbook(encoding = "utf-8", style_compression = 0)
        sheet_name = newbook.add_sheet('sheet1',cell_overwrite_ok = True)
        for i in range(0,len(key_list)):
            bug_com_key_list = []
            if i == 0:
                sheet_name.write(i,0,key_list[i])
                sheet_name.write(i,1,value_list[i])
                sheet_name.write(i,2,u"相似问题(相似度大于%.1f)"%(xiangsidu))
            else:
                re_CosResult = re.compile(r"^((%s)-d{0,3}$)"%str(int(key_list[i])))
                sheet_name.write(i,0,key_list[i])
                sheet_name.write(i,1,value_list[i])
                for bug_com_key in CosResult_list:
                    if re.match(re_CosResult,bug_com_key):########
                        bug_com_key_list.append(bug_com_key)########
                    else:
                        continue
                for j in range(len(bug_com_key_list)):
                    sheet_name.write(i,j + 2,bug_com_key_list[j] + "
    ")#重复写入            
        newbook.save("bug_compare_result.xls")
    
    def main():
        global getIDF_get_IDF_value
        user_message = input("是否更改了语料库,请输入 Y or N:")
        starttime = time.time()
        if str(user_message) == "Y" or str(user_message) == "y":
            getIDF_get_IDF_value = getIDF.get_IDF_value()
            getIDF_file = open('get_IDF_value.txt','w')
            getIDF_file.write(str(getIDF_get_IDF_value))
            getIDF_file.close()
        else:
            getIDF_file = open('get_IDF_value.txt','r')
            getIDF_get_IDF_value = eval(getIDF_file.read())
            getIDF_file.close()
        ##########
        Bug_base = read_Originalbase('bug.xls')
        for main_key_value_1 in Bug_base.keys():
            if main_key_value_1 == "Bug编号":
                continue
            else:
                BugID_List.append(main_key_value_1)
        for main_key_value_2 in Bug_base.values():
            if main_key_value_2 == "Bug标题":
                continue
            else:
                get_TF_IDF_Key(main_key_value_2)#字符串
        cosine_result(Top_List)
        write_BugBase(Bug_base,Compare_CosResult)
        endtime = time.time()
        print("共用时:%d秒!"%(endtime - starttime))
        
    
    if __name__ == '__main__':
        main()

    -----------------------------------------------------------------

    getIDF.py

    ---------------------getIDF.py-------------------------------

    #-*- coding:utf-8 -*-
    import os
    import re
    import jieba
    import math
    import re
    ######正则表达式规则######
    re_rule_getIDF = re.compile(r"^([u4e00-u9fa5]){2,20}")#选择中文开头,且文字长度为2-20,一定要记住哇
    #读取语料库,并计算出语料库中所有关键词的权重
    def get_IDF_value():
        Corpus_file_list = []#获取所有语料库文件,以列表形式保存
        Corpus_text_list = []
        words_re_IDF = []
        Corpus_word_counts = 0
        Corpus_counts = {}
        Corpus_counts_two = {}
        for root,dirs,files in os.walk('.',topdown=False):
            for name in files:
                str_value = ""
                str_value = os.path.join(root,name)
                Corpus_file_list.append(str_value)
        for i in Corpus_file_list:
            if i == '.\CorpusDatabase\__pycache__\getIDF.cpython-36.pyc' or i == '.\CorpusDatabase\__pycache__\__init__.cpython-36.pyc' or i == '.\CorpusDatabase\getIDF.py' or i == '.\CorpusDatabase\getIDF.pyc' or i == '.\CorpusDatabase\__init__.py' or i == '.\CorpusDatabase\__init__.pyc' or i == '.\sameText.py' or i == '.\bug.xls' or i == '.\bug_compare_result.xls' or i == '.\get_IDF_value.txt':
                continue
            else:
                file_object = open(i,'r',encoding = 'UTF-8')#避免出现编码问题,open文件时使用UTF-8编码
                file_content = file_object.readlines()
                for j in file_content:
                    Corpus_text_list.append(j)
        #对语料库进行分词,统计总词数和每个词出现频率,最终计算出权重
        for split_words in Corpus_text_list:
            words = jieba.lcut_for_search(split_words)
    #这边得加段正则表达式的,筛除掉words非中文、单个汉字的内容,并构建words_re_IDF
            for k in words:
                if re.match(re_rule_getIDF,k):
                    words_re_IDF.append(k)
            for word in words_re_IDF:
                   Corpus_word_counts = Corpus_word_counts + 1
                   Corpus_counts[word] = Corpus_counts.get(word,0) + 1
        for count in Corpus_counts:
            Corpus_counts_two[count] = Corpus_counts_two.get(count,(math.log(Corpus_word_counts / (float(Corpus_counts[count] + 1)))))
        return Corpus_counts_two

     -------------------------------------------------------

    文档结构:

    NLP为主文件名,文件中包括:

      ——bug.xls(存有BugID和Bug内容的原始文件)

      ——bug_compare_result.xls(存储比对结果的文件,由程序生成)

      ——get_IDF_value.txt(存储IDF值的文件)

      ——sameText.py(主脚本文件)

      ——CorpusDatabase

        ——__init__.py(空脚本文件)

        ——getIDF.py(计算IDF值的文件)

        ——OAcase.txt(语料库)

        ——__pycache__(程序自动生成,不用关注)

    Bug原文件样式:

    最终结果:

    相似的问题会从第3列开始写入

    参考资料:

    http://www.ruanyifeng.com/blog/2013/03/tf-idf.html

    http://www.ruanyifeng.com/blog/2013/03/cosine_similarity.html

  • 相关阅读:
    1036 Boys vs Girls (25 分)
    1090 Highest Price in Supply Chain (25 分)
    1106 Lowest Price in Supply Chain (25 分)dfs
    设计模式-解释器模式
    设计模式-命令模式
    设计模式-责任链模式
    设计模式-代理模式
    设计模式-享元模式
    设计模式-外观模式
    设计模式-装饰器模式
  • 原文地址:https://www.cnblogs.com/Gogo-ouchen/p/11155731.html
Copyright © 2011-2022 走看看