美文网首页
利用TF-IDF进行句子相似度计算

利用TF-IDF进行句子相似度计算

作者: 烛之文 | 来源:发表于2020-03-02 17:21 被阅读0次

    1 前言

    在NLP机器学习任务中,一个首要的步骤就是将词向量化,也称为词编码。对于词编码,目前主要存在两类方法,一是词袋方法,二是分布式表示;前者又称为 one-hot 编码,是传统的经典方法。分布式表示(distribution representation)是现在流行的方法,也是深度学习必要步骤,对应着embedding layer。

    今天主要分享如何利用词袋的方法向量化,然后做句子相似度计算任务。虽然词袋方法已不是主流,但它背后的思想也是分布式表示方法的基础,理解它的原理,有助于我们更好理解现在各种各样新的embedding方法。

    2 词袋方法

    基本思想:
    step1:先统计语料库生成一个有序词表,假设词表大小为N(即词的数量);
    step2:接着初始化一个长度N的零向量(v=[0,0,...,0]),长度N代表着词和句子向量后的维度;
    step3:最后,对一个词,检索它在词表的索引,然后将索引对应的初始向量维度值变为1,生成一个one-hot向量即为该词的向量,若词的索引为3,则词向量为v=[0,0,0,1,0,...,0],第4维度为1,其他维度都为0;对于一个句子,遍历句子中的每个词,按词编码的方式将初始向量对应的维度变为1,若句子有3个词,对应词表的索引为0,1,3,这句子向量为v=[1,1,0,1,0,...,0],第0,1,3维度为1,其他维度都为0。

    举个例子:假设有个有序词表为['临床','表现','及','实验室','检查','即可','做出','诊断'],长度为8。对于‘临床’词来说,在词表的索引为0,则词向量为v=[1,0,0,0,0,0,0,0]; 对于‘实验室’词来说,在词表的索引为3,则词向量为v=[0,0,0,1,0,0,0,0];对于“检查做出诊断为:”句子来说,分词后为“检查/做出/诊断/为/:”,每个词在词表对应的索引为4,6,7,后面两个词不在词表中,不考虑,最后句子向量为v=[0,0,0,0,1,0,1,1]。在实际情况,一般词表很大,大到可能百万级别,当然也会去掉没太意义的词,如停用词或标点符号。

    基于TF-IDF词袋方法

    今天实现句子向量化,是基于TF-IDF的词袋方法。该方法很简单,就是在词袋基本思想上,将向量中的1值用该词的tf-idf的值代替,这样的好处就是一句话中不同词的权重是不一样的,重要的词所在维度取得值应该更大些,而tf-idf值就是衡量一个词的重要性。

    同样,也有将1用词的词频(tf值)来代替,与基于TF-IDF词袋方法是一致的,但TF-IDF的值比TF值更具有代表性。

    词袋方法缺陷

    不管词袋方法如何优化,但有一个明显的缺陷:就是编码后的句子向量失去了原有词的顺序,换句话来说就是,丢弃了词的上下文信息,而这在很多NLP任务中是很重要的信息,尤其序列标注任务。也是因为这个问题,才有了现在主流的分布式表征方法,后续详细介绍分布式表征的词向量方法。

    3 相似度计算

    在进行句子相似度计算时,有很多可选方法。本文选择了最基本的余弦(cosine)相似度方法,其背后的思想是计算两个句子向量的夹角,夹角越小,相似度越高。假定V_1V_2是两个句子的向量,维度都为n,即V_1=(x_1,x_2,..,x_n)V_2=(y_1,y_2,..,y_n),它们的余弦值等于
    cos(V_1,V_2)=\frac{\sum_{i=1}^{n}{(x_i*y_i)}}{\sqrt{\sum_{i=1}^{n}{(x_i)^2}}*\sqrt{\sum_{i=1}^{n}{(y_i)^2}}}=\frac{V_1.V_2}{||V_1||*||V_2||}

    4 实验

    任务说明:利用TF-IDF词袋方法,进行句子相似度计算。

    实验数据:使用上一篇“TF-IDF的理论与实践“(https://www.jianshu.com/p/c55c6cae24ad)中同样的语料库file_corpus,然后从语料库中切分句子,取出现句子频率最高的前10000句子样本集。选取5个样本句子,然后利用相似度来计算出与样本句子最相似的句子。

    4.1 生成句子样本集

    引入所用的包:

    import codecs
    import re
    import jieba
    from scipy import spatial
    import json
    

    句子切分

    def get_sentence(corpus_dir,sentence_dir):
        
        re_han= re.compile(u"([\u4E00-\u9FD5a-zA-Z0-9]+)")  #the method of cutting text to sentence
        
        file_corpus=codecs.open(corpus_dir,'r',encoding='utf-8')
        file_sentence=codecs.open(sentence_dir,'w',encoding='utf-8')
    
        st=dict()
        for _,line in enumerate(file_corpus):
            line=line.strip()
            blocks=re_han.split(line)
            for blk in blocks:
                if re_han.match(blk) and len(blk)>10:
                    st[blk]=st.get(blk,0)+1
    
        st=sorted(st.items(),key=lambda x:x[1],reverse=True)
        for s in st[:10000]:
            file_sentence.write(s[0]+'\n')
    
        file_corpus.close()
        file_sentence.close()
    

    4.2 相似度计算

    class ComputeSimilarity():
        def __init__(self,tf_idf_dir):
            self.tf_idf_dir=tf_idf_dir
            self.tf_idf=self.load_dict()
            self.vocab=list(self.tf_idf.keys())
            self.N=len(self.vocab)   
            
        def load_dict(self):
            tf_idf=dict()
            with codecs.open(tf_idf_dir,'r',encoding='utf-8') as f:
                for line in f:
                    line=line.split('\t')
                    try:
                        assert len(line)==2
                        tf_idf[line[0]]=float(line[1])
                    except:
                        pass
            return tf_idf
            
        def similarity(self,s1,s2,use_idf=True):
            v1=np.zeros((1,self.N))
            v2=np.zeros((1,self.N))
    
            if use_idf:
                for w in jieba.cut(s1):
                    if w in self.tf_idf:
                        v=self.tf_idf[w]
                        i=self.vocab.index(w)
                        v1[:,i]+=v
    
                for w in jieba.cut(s2):
                    if w in self.tf_idf:
                        v=self.tf_idf[w]
                        i=self.vocab.index(w)
                        v2[:,i]+=v
            else:
                for w in jieba.cut(s1):
                    if w in self.tf_idf:
                        i=self.vocab.index(w)
                        v1[:,i]=1
    
                for w in jieba.cut(s2):
                    if w in self.tf_idf:
                        i=self.vocab.index(w)
                        v2[:,i]=1
    
            sim=1-spatial.distance.cosine(v1,v2)
    
            return sim
    

    4.3 样本测试

    def test(tf_idf_dir,sentence_dir,test_dir):
        
        cs=ComputeSimilarity(tf_idf_dir)
        ssm=cs.similarity
        
        test_data=[u'临床表现及实验室检查即可做出诊断',
                   u'面条汤等容易消化吸收的食物为佳',
                   u'每天应该摄入足够的维生素A',
                   u'视患者情况逐渐恢复日常活动',
                   u'术前1天开始预防性运用广谱抗生素']
        
        model_list=['use_idf','unuse_idf']
        
        file_sentence=codecs.open(sentence_dir,'r',encoding='utf-8')
        train_data=file_sentence.readlines()
        
        for model in model_list:
            if model=='use_idf':
                use_idf=True
            else:
                use_idf=False 
                
            dataset=dict()
            result=dict()
            for s1 in test_data:
                dataset[s1]=dict()
                for s2 in train_data:
                    s2=s2.strip()
                    if s1!=s2:
                        sim=ssm(s1,s2,use_idf=use_idf)
                        dataset[s1][s2]=dataset[s1].get(s2,0)+sim
            for r in dataset:
                top=sorted(dataset[r].items(),key=lambda x:x[1],reverse=True)
                result[r]=top[0]
                
            with codecs.open(test_dir,'a',encoding='utf-8') as f:
                f.write('--------------The result of %s method------------------\n '%model)
                
                f.write(json.dumps(result, ensure_ascii=False, indent=2, sort_keys=False))
                f.write('\n\n')
    
        file_sentence.close()
    

    执行代码

    if __name__=="__main__":
        sentence_dir=r'E:\jianshu\data\file_sentence.txt'
        tf_idf_dir=r'E:\jianshu\data\tf_idf.txt'
        test_dir=r'E:\jianshu\data\test_result.txt'
        test(tf_idf_dir,sentence_dir,test_dir)
    

    5 结果分析

    将跑出的test_result.txt的结果用表格形式展示出来,如下图:

    运行结果

    从5条样例数据来看,在二者匹配结果一致的情况下(第2,4,5句子),使用tf_idf的方法(use_idf)计算的相似度更高些;通过第1个句子对比看,二者匹配差异在“实验室”这个词,这个词的tf_idf影响了最终的结果,显示use_idf优于后者;通过第2个句子对比看,应该unuse_idf结果更好些,出现这种情况的原因是词“维生素“的tf_idf很高的词且匹配句子重复出现,就导致这个句子匹配度提升,这种情形下是不合理的。

    整体来说,tf_idf相似度计算会更合理些,但也不是完美的,当句子的匹配中涉及语义理解的含义时,往往这两种方法都不能很好处理,这时候可以利用word2vec或者bert能得到一定程度的处理,这类方法下次再介绍。

    相关文章

      网友评论

          本文标题:利用TF-IDF进行句子相似度计算

          本文链接:https://www.haomeiwen.com/subject/fdnuzctx.html