• 第一次个人编程作业


    首先我点开了作业博文

    哦~~,任务是计算两篇的文章相似度

    一如往常,我打开浏览器

    先放上github的链接[https://github.com/haha123763/031804108]

    百度方法

    doc2vec,simhash,余弦都有尝试

    一点开百度就看到了: TF-IDF这种方法没有考虑到文字背后的语义关联,但两个文档是相似的情况下,就需要考虑到文档的语义。而doc2vec考虑到文本的语义…………
    我一看还能考虑到语义那自然是一百个精准加两百个有逼格了,好就是它了

    一:首先尝试一下可以情感分析的doc2vec

    doc2vec简介:
    Doc2Vec 也可叫做 paragraph2vec, sentence embeddings,是一种非监督式算法,可以获得 sentences/paragraphs/documents 的向量表达,是 word2vec 的拓展。学出来的向量可以通过计算距离来找 sentences/paragraphs/documents 之间的相似性,可以用于文本聚类,对于有标签的数据,还可以用监督学习的方法进行文本分类,例如经典的情感分析问题。

    处理文本,从网上下载停用词表,读取样本文件内容

    def seg_depart(sentence):
        # 对文档中的每一行进行中文分词
        sentence_depart = jieba.cut(sentence.strip())
        # 创建一个停用词列表
        stopwords = stopwordslist()
        # 输出结果为outstr
        outstr = ''
        # 去停用词
        for word in sentence_depart:
            if word not in stopwords:
                if word != '	':
                    outstr += word
                    outstr += " "
        return outstr
    
    #获取文档内容
    def get_dataset(path):
        inputs=open(path,'r',encoding='UTF-8')
        docs=""
        for line in inputs:
            line_seg = seg_depart(line)
            docs=docs+line_seg
        print(type(docs))
        return docs
    

    然后就是训练模型和测试了,写在一个方法中,用Dictionary将样本文档生成语料库,再用doc2vec将要对比的文档转化为稀疏向量,再来计算相似度

    def ceshi1():
        docs_test = get_dataset(path_sample)      #训练样本的字符串形式
        raw_documents =[docs_test]
        # corpora_documents列表长度就是训练样本的文章个数,可以选择多篇文章一起比较
        corpora_documents = []
        for item_text in raw_documents:
            item_str = list(jieba.cut(item_text))
            corpora_documents.append(item_str)
        # 生成字典和向量语料,用dictionary方法获取词袋(bag-of-words)
        dictionary = corpora.Dictionary(corpora_documents)
        corpus = [dictionary.doc2bow(text) for text in corpora_documents]
    
        similarity = Similarity('-Similarity-index', corpus, num_features=2000)
    
        test_data_1 = get_dataset(path_test)
        test_cut_raw_1 = list(jieba.cut(test_data_1))
        test_corpus_1 = dictionary.doc2bow(test_cut_raw_1)
        similarity.num_best = 5
        print(similarity[test_corpus_1])  # 返回最相似的样本材料,(index_of_document, similarity) tuples
        return str(similarity[test_corpus_1][0][1])
    

    开始运行,为了方便多比较几篇,得到的结果竟然是。。。

    可能大概也许是不太准确的吧。。。。。。


    二:又听说simhash更好,准确度和余弦差不多,但是耗时短,还适合比较长文本,试一下

    simhash是google用来处理海量文本去重的算法。 google出品,你懂的。 simhash最牛逼的一点就是将一个文档,最后转换成一个64位的字节,暂且称之为特征字,然后判断重复只需要判断他们的特征字的距离是不是<n(根据经验这个n一般取值为3),就可以判断两个文档是否相似。
    原理:

    算法过程大概如下:
    1,将Doc进行关键词抽取(其中包括分词和计算权重),抽取出n个(关键词,权重)对, 即图中的(feature, weight)们。 记为 feature_weight_pairs = [fw1, fw2 … fwn],其中 fwn = (feature_n,weight_n`)。
    2,hash_weight_pairs = [ (hash(feature), weight) for feature, weight in feature_weight_pairs ] 生成图中的(hash,weight)们, 此时假设hash生成的位数bits_count = 6(如图);
    3,然后对 hash_weight_pairs 进行位的纵向累加,如果该位是1,则+weight,如果是0,则-weight,最后生成bits_count个数字,如图所示是[13, 108, -22, -5, -32, 55], 这里产生的值和hash函数所用的算法相关。
    4,[13,108,-22,-5,-32,55] -> 110001这个就很简单啦,正1负0。

    然后重点就是 simhash值的海明距离计算,
    二进制串A 和 二进制串B 的海明距离 就是 A xor B 后二进制中1的个数。
    当我们算出所有doc的simhash值之后,需要计算doc A和doc B之间是否相似的条件是:A和B的海明距离是否小于等于n,这个n值根据经验一般取值为3,
    simhash本质上是局部敏感性的hash,和md5之类的不一样。 正因为它的局部敏感性,所以我们可以使用海明距离来衡量simhash值的相似度。

    # 求两篇文章相似度
    def simhash_similarity(text1, text2):
        """
        :param tex1: 文本1
        :param text2: 文本2
        :return: 返回两篇文章的相似度
        """
    
        aa_simhash = Simhash(text1)
        bb_simhash = Simhash(text2)
    
        max_hashbit = max(len(bin(aa_simhash.value)), (len(bin(bb_simhash.value))))
        print("33333333333",max_hashbit)
        # 汉明距离
        distince = aa_simhash.distance(bb_simhash)
          # 相似度的计算
        similar = 1 - distince / max_hashbit
    
        return similar
    

    得到的结果是

    结果竟然这么不靠谱,难道这么长的文章还算是短文本吗


    三:既然余弦和simhash准确度差不多,那我就用余弦吧

    原理:
    余弦相似性:两个向量的夹角越接近于0,其余弦值越接近于1,表明两个向量越相似。
    文本相似度计算大致流程:分词 合并 计算特征值(选取词频作为特征值) 向量化 计算向量夹角余弦值
    首先初始化类,向类中传入要比较的两个文本的字符串形式(已经经过停用词的过滤),并且设置topK=5000,topK表示返回关键词的数量,由于文本比较长,经过试验5000比较合适

    class Similarity():
        def __init__(self, target1, target2, topK=5000):
            self.target1 = target1
            self.target2 = target2
            self.topK = topK
    

    分词

        def vector(self):
            self.vdict1 = {}
            self.vdict2 = {}
            # 分别对文档1,2进行分词,返回 关键词和相应的权重的列表
            # self.target1:待提取关键词的文本  topK:返回关键词的数量,重要性从高到低排序  withWeight:是否同时返回每个关键词的权重 allowPOS=():词性过滤,为空表示不过滤,若提供则仅返回符合词性要求的关键词
            top_keywords1 = jieba.analyse.extract_tags(self.target1, topK=self.topK, withWeight=True)
            top_keywords2 = jieba.analyse.extract_tags(self.target2, topK=self.topK, withWeight=True)
            # 把关键词存储在相应字典,以键值对形式 关键词:权重
            for k, v in top_keywords1:
                self.vdict1[k] = v
            for k, v in top_keywords2:
                self.vdict2[k] = v
    

    合并

        def mix(self):
            # 若key值存在于vdict1.keys()时,返回vdict1[key];若不存在于vdict1.keys()中时,返回0
            # 保证两个字典的key相同
            for key in self.vdict1:
                self.vdict2[key] = self.vdict2.get(key, 0)
            for key in self.vdict2:
                self.vdict1[key] = self.vdict1.get(key, 0)
    

    生成词频向量

    # 计算相对词频
            def mapminmax(vdict):
                # 字典中的最小,最大权重,以及权重差值
                _min = min(vdict.values())
                _max = max(vdict.values())
                _mid = _max - _min
                # print _min, _max, _mid
    
                # 对权重大小进行处理,标准化
                for key in vdict:
                    vdict[key] = (vdict[key] - _min) / _mid
                return vdict
            # 得到标准化的权重大小
            self.vdict1 = mapminmax(self.vdict1)
            self.vdict2 = mapminmax(self.vdict2)
    

    最后计算相似度

        def similar(self):
            self.vector()
            self.mix()
            sum = 0
            # sum是两个字典相同键的值相乘的和
            for key in self.vdict1:
                sum += self.vdict1[key] * self.vdict2[key]
    
            A = sqrt(reduce(lambda x, y: x + y, map(lambda x: x * x, self.vdict1.values())))
            B = sqrt(reduce(lambda x, y: x + y, map(lambda x: x * x, self.vdict2.values())))
            # 返回两个文档的相似度
            return sum / (A * B)
    

    测试一下,得到的结果是

    看上去还挺靠谱的,那这就是我最终的代码了

    性能分析

    我是用pycharm自带的性能分析工具profile,结果如下


    看来去掉停用词占用了不少时间

    代码改进

    这些代码是用余弦方法的基本步骤,我还真不知道怎么改,不过可以改改停用词的过滤,毕竟占了那么多时间,修改了停用词的内容,只剩下标点符号后

    占用时间的确变小了,但是结果相较于之前也增加了1%,对准确度还是有一定影响的
    你以为这就是我关于代码改进的所有修改吗,那你认真看以下上面关于doc2vec和simhash的介绍以及测试,我改的是方法啊!!!!!(改的可谓尽心尽力,屡败不爽)

    单元测试

    测试代码

    import unittest
    import cos3
    path_sample = 'H:sim_0.8orig.txt'
    path_test1 = 'H:sim_0.8orig_0.8_add.txt'
    path_test2 = 'H:sim_0.8orig_0.8_del.txt'
    path_test3 = 'H:sim_0.8orig_0.8_dis_1.txt'
    path_test4="H:sim_0.8orig_0.8_dis_3.txt"
    path_test5="H:sim_0.8orig_0.8_dis_7.txt"
    
    class MyTest(unittest.TestCase):  # 继承unittest.TestCase
        def tearDown(self):
            # 每个测试用例执行之后做操作
            print('111_.....begin')
    
        def setUp(self):
            # 每个测试用例执行之前做操作
            print('22222.....jieshu')
    
    
        def test_1(self):
            print(cos3.ceshi(path_sample,path_test1))
    
        def test_2(self):
            print(cos3.ceshi(path_sample, path_test2))
    
        def test_3(self):
            print(cos3.ceshi(path_sample, path_test3))
    
        def test_4(self):
            print(cos3.ceshi(path_sample, path_test4))
    
        def test_5(self):
            print(cos3.ceshi(path_sample, path_test5))
    
    if __name__ == '__main__':
        unittest.main()  # 运行所有的测试用例
    

    测试结果

    至于覆盖率,用的coverage,虽然不是很美观,但是我只会这一个

    93%的覆盖率我也知足了

    异常处理

    异常主要有:相同文本相似度不是1 不同文本相似度是1 空文本的比较
    创建异常类

    class SameNot1(Exception):
        def __init__(self):
            print("一样的文件,相似度怎么会不一样呢,去改!")
    class NotSAmeBut1(Exception):
        def __init__(self):
            print("不一样的文件,相似度竟然是1,去改!")
    class Empty(Exception):
        def __init__(self):
            print("文档是空的,不用比了,让cpu歇会儿吧")
    

    异常类的测试

    if(t1==""):
            raise Empty
    
    if (result > 0.9999999):
           raise SameNot1
    
    


    PSP表格分析

    Personal Software Process Stages 预估耗时(分钟) 实际耗时(分钟)
    计划 90 90
    估计这个任务需要多少时间 80 80
    开发 660 1600
    需求分析 (包括学习新技术) 80 450
    生成设计文档 70 80
    设计复审 30 40
    代码规范 (为目前的开发制定合适的规范) 40 40
    具体设计 50 70
    具体编码 360 560
    代码复审 20 30
    测试(自我测试,修改代码,提交修改) 40 60
    报告 120 150
    测试报告 50 50
    计算工作量 10 10
    事后总结, 并提出过程改进计划 30 30
    合计 1200 1660

    总结

    这次编程作业我更加坚信了自己的无知,我之前对python的了解仅限于基本语法,对他的各种库一无所知。这次作业查看了大量博客,从寻找方法,到参考代码还有github的使用,pycharm的功能,我学到了很多。以后做作业一定先找好可靠的方法再精心去优化它,而不是始乱终弃,优化结果全靠换方法。

  • 相关阅读:
    固定思维的可怕(转)
    Javascript模块化编程:require.js的用法
    js中将字符串转为JSON的三种方式
    cf 55D 数位dp 好题
    fzu 2113 数位dp
    uestc 250 数位dp(水)
    hdu 3652数位dp
    数位dp 3943 二分法
    hdu 3943 经典数位dp好题
    hdu 4871 树的分治+最短路记录路径
  • 原文地址:https://www.cnblogs.com/huayuaiyinsitan/p/13668060.html
Copyright © 2020-2023  润新知