【问题标题】:Cosine similarity using TFIDF使用 TFIDF 的余弦相似度
【发布时间】:2016-08-14 16:04:54
【问题描述】:

在 SO 和网络上有几个问题描述了如何在两个字符串之间,甚至在以 TFIDF 作为权重的两个字符串之间获取cosine similarity。但是像 scikit 的 linear_kernel 这样的函数的输出让我有点困惑。

考虑以下代码:

import pandas as pd
from sklearn.feature_extraction.text import TfidfVectorizer

a = ['hello world', 'my name is', 'what is your name?']
b = ['my name is', 'hello world', 'my name is what?']

df = pd.DataFrame(data={'a':a, 'b':b})
df['ab'] = df.apply(lambda x : x['a'] + ' ' + x['b'], axis=1)
print(df.head())

                    a                 b                                   ab
0         hello world        my name is               hello world my name is
1          my name is       hello world               my name is hello world
2  what is your name?  my name is what?  what is your name? my name is what?

问题: 我想有一列是a 中的字符串和b 中的字符串之间的余弦相似度。

我尝试了什么

我在ab上训练了一个TFIDF分类器,以便包含所有单词:

clf = TfidfVectorizer(ngram_range=(1, 1), stop_words='english')
clf.fit(df['ab'])

然后我得到了ab 列的稀疏 TFIDF 矩阵:

tfidf_a = clf.transform(df['a'])
tfidf_b = clf.transform(df['b'])

现在,如果我使用其他人推荐的 scikit 的 linear_kernel,我会返回一个 (nfeatures,nfeatures) 的 Gram 矩阵,如他们的文档中所述。

from sklearn.metrics.pairwise import linear_kernel
linear_kernel(tfidf_a,tfidf_b)

array([[ 0.,  1.,  0.],
       [ 0.,  0.,  0.],
       [ 0.,  0.,  0.]])

但是我需要的是一个简单的向量,其中第一个元素是a的第一行和b的第一行之间的cosin_sim,第二个元素是cos_sim(a[1],b[ 1]),等等。

使用 python3,scikit-learn 0.17。

【问题讨论】:

    标签: python tf-idf cosine-similarity


    【解决方案1】:
    dfs = {}
    idfs = {}
    speeches = {}
    speechvecs = {}
    total_word_counts = {}
    
    def tokenize(doc):
        tokens = mytokenizer.tokenize(doc)
        lowertokens = [token.lower() for token in tokens]
        filteredtokens = [stemmer.stem(token) for token in lowertokens if not token in sortedstopwords]
        return filteredtokens
    
    def incdfs(tfvec):
        for token in set(tfvec):
            if token not in dfs:
                dfs[token]=1
                total_word_counts[token] = tfvec[token]
            else:
                dfs[token] += 1
                total_word_counts[token] += tfvec[token]
    
    
    def calctfidfvec(tfvec, withidf):
        tfidfvec = {}
        veclen = 0.0
    
        for token in tfvec:
            if withidf:
                tfidf = (1+log10(tfvec[token])) * getidf(token)
            else:
                tfidf = (1+log10(tfvec[token]))
            tfidfvec[token] = tfidf 
            veclen += pow(tfidf,2)
    
        if veclen > 0:
            for token in tfvec: 
                tfidfvec[token] /= sqrt(veclen)
    
        return tfidfvec
    
    def cosinesim(vec1, vec2):
        commonterms = set(vec1).intersection(vec2)
        sim = 0.0
        for token in commonterms:
            sim += vec1[token]*vec2[token]
    
        return sim
    
    def query(qstring):
        qvec = getqvec(qstring.lower())
        scores = {filename:cosinesim(qvec,tfidfvec) for filename, tfidfvec in speechvecs.items()}  
        return max(scores.items(), key=operator.itemgetter(1))[0]
    
    def docdocsim(filename1,filename2):
        return cosinesim(gettfidfvec(filename1),gettfidfvec(filename2))
    

    【讨论】:

    • 虽然这段代码 sn-p 可以解决问题,但它没有解释为什么或如何回答这个问题。请include an explanation for your code,因为这确实有助于提高您的帖子质量。请记住,您是在为将来的读者回答问题,而这些人可能不知道您提出代码建议的原因。
    • 我发现这段代码是自记录的,我什至不知道 python。
    • 我觉得在余弦相似度的某个地方应该有一个余弦函数,但没有。为什么?
    【解决方案2】:

    我认为您的示例有点下降,因为您的 TfidfVectorizer 过滤掉了您的大部分单词,因为您有 stop_words = 'english' 参数(您在示例中包含了几乎所有停用词)。我已经删除了它并使你的矩阵变得密集,这样我们就可以看到发生了什么。如果你做了这样的事情怎么办?

    import pandas as pd
    from sklearn.feature_extraction.text import TfidfVectorizer
    from scipy import spatial
    
    a = ['hello world', 'my name is', 'what is your name?']
    b = ['my name is', 'hello world', 'my name is what?']
    
    df = pd.DataFrame(data={'a':a, 'b':b})
    df['ab'] = df.apply(lambda x : x['a'] + ' ' + x['b'], axis=1)
    
    clf = TfidfVectorizer(ngram_range=(1, 1))
    clf.fit(df['ab'])
    
    tfidf_a = clf.transform(df['a']).todense()
    tfidf_b = clf.transform(df['b']).todense()
    
    row_similarities = [1 - spatial.distance.cosine(tfidf_a[x],tfidf_b[x]) for x in range(len(tfidf_a)) ]
    row_similarities
    
    [0.0, 0.0, 0.72252389079716417]
    

    这显示了每行之间的距离。我并不完全同意你如何构建完整的语料库,但这个例子根本没有优化,所以我暂时不谈。希望这会有所帮助。

    【讨论】:

    • 谢谢,这行得通。你为什么不支持我如何构建完整的语料库?
    • 因为通常有比使用 .apply 完成此类任务更好的方法。这里有 6 个文档,两列 3 行,有两个单独的文档(a 和 b),还是有 3 个文档(每行一个)。计算 TFIDF 中的频率很重要,我不确定您现在构建 ab 的方式是否反映了您的意图。
    猜你喜欢
    • 2017-03-26
    • 2014-09-17
    • 1970-01-01
    • 2020-08-12
    • 2011-01-01
    • 2018-10-26
    • 2017-12-12
    • 2013-05-24
    相关资源
    最近更新 更多