使用TFIDF计算余弦相似度

4

有几个在stackoverflow和网上的问题描述如何计算两个字符串的余弦相似度,甚至是在两个带有TFIDF权重的字符串之间。但像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的第一行之间的余弦相似度,第二个元素是cos_sim(a[1],b[1]),以此类推。

使用Python3和scikit-learn 0.17。

2个回答

3

我认为你的例子存在一些问题,因为你的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]

这显示了每行之间的距离。我对您构建完整语料库的方法并不完全赞同,但是这个示例并没有优化,所以现在我会暂时不做评论。希望这能有所帮助。

谢谢,这个有效。你为什么不支持我如何构建完整语料库的方式? - David
因为通常有更好的方法来完成这种任务,而不是使用.apply。这里有6个文档,两列中有3行,是两个单独的文件(a和b),还是3个文档(每行一个)。这对于计算TFIDF中的频率很重要,我不确定您现在构建ab的方式是否反映了您想做的事情。 - flyingmeatball

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))

1
虽然这段代码片段可能解决了问题,但它并没有解释为什么或者如何回答这个问题。请在您的代码中包含解释,因为这真的有助于提高您的帖子质量。记住,您正在为未来的读者回答问题,而这些人可能不知道您的代码建议的原因。 - Scott Weldon
我发现这段代码自我说明,即使我不懂Python。 - Seth
我觉得在余弦相似度中应该有一个余弦函数,但实际上却没有。为什么呢? - Adam Bajger

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接