“Programming Collective Intelligence”中的皮尔逊算法有什么问题?

5 投票
4 回答
3236 浏览
提问于 2025-04-15 16:15

这个函数来自《编程集体智慧》这本书,目的是计算p1和p2之间的皮尔逊相关系数,这个系数的值应该在-1到1之间。

如果两个评论者对某些项目的评分非常相似,这个函数应该返回1,或者接近1。

但是在使用真实用户数据时,我有时会得到奇怪的结果。在下面的例子中,数据集critics2应该返回1,但实际上却返回了0。

有没有人能发现问题所在?

(这不是关于“编程集体智慧”中这个python函数有什么问题的重复问题)

from __future__ import division
from math import sqrt

def sim_pearson(prefs,p1,p2):
    si={}
    for item in prefs[p1]: 
        if item in prefs[p2]: si[item]=1
    if len(si)==0: return 0
    n=len(si)
    sum1=sum([prefs[p1][it] for it in si])
    sum2=sum([prefs[p2][it] for it in si])
    sum1Sq=sum([pow(prefs[p1][it],2) for it in si])
    sum2Sq=sum([pow(prefs[p2][it],2) for it in si]) 
    pSum=sum([prefs[p1][it]*prefs[p2][it] for it in si])
    num=pSum-(sum1*sum2/n)
    den=sqrt((sum1Sq-pow(sum1,2)/n)*(sum2Sq-pow(sum2,2)/n))
    if den==0: return 0
    r=num/den
    return r

critics = {
    'user1':{
        'item1': 3,
        'item2': 5,
        'item3': 5,
        },
    'user2':{
        'item1': 4,
        'item2': 5,
        'item3': 5,
        }
}
critics2 = {
    'user1':{
        'item1': 5,
        'item2': 5,
        'item3': 5,
        },
    'user2':{
        'item1': 5,
        'item2': 5,
        'item3': 5,
        }
}
critics3 = {
    'user1':{
        'item1': 1,
        'item2': 3,
        'item3': 5,
        },
    'user2':{
        'item1': 5,
        'item2': 3,
        'item3': 1,
        }
}

print sim_pearson(critics, 'user1', 'user2', )
result: 1.0 (expected)
print sim_pearson(critics2, 'user1', 'user2', )
result: 0 (unexpected)
print sim_pearson(critics3, 'user1', 'user2', )
result: -1 (expected)

4 个回答

0

这个算法的结果是正确的。0表示它们之间没有关联(或者说,至少根据你所知道的情况,你无法判断它们之间的关系)。

通常来说(这取决于你把这个算法用在哪个领域),你可以把-0.9到0.09之间的所有值都看作是“没有明显的关联”。

3

如果你在维基百科上查一下皮尔逊相关系数,你会发现它的公式是用一系列数据中每个数据与这个系列的平均值之间的差来计算的。当系列中的所有数据都一样时,就会出现除以零的情况,这样计算就会失败。

如果这样说更清楚的话,你可以使用下面的代码:

def simplified_sim_pearson(p1, p2):
    n = len(p1)
    assert (n != 0)
    sum1 = sum(p1)
    sum2 = sum(p2)
    m1 = float(sum1) / n
    m2 = float(sum2) / n
    p1mean = [(x - m1) for x in p1]
    p2mean = [(y - m2) for y in p2]
    numerator = sum(x * y for x, y in zip(p1mean, p2mean))
    denominator = math.sqrt(sum(x * x for x in p1mean) * sum(y * y for y in p2mean))
    return numerator / denominator if denominator else 0

def sim_pearson(prefs,p1,p2):
    p1 = prefs[p1]
    p2 = prefs[p2]
    si = set(p1.keys()).intersection(set(p2.keys()))
    p1_x = [p1[k] for k in sorted(si)]
    p2_x = [p2[k] for k in sorted(si)]
    return simplified_sim_pearson(p1_x, p2_x)



critics = {
    'user1':{
        'item1': 3,
        'item2': 5,
        'item3': 5,
        },
    'user2':{
        'item1': 4,
        'item2': 5,
        'item3': 5,
        }
}
critics2 = {
    'user1':{
        'item1': 5,
        'item2': 5,
        'item3': 5,
        },
    'user2':{
        'item1': 5,
        'item2': 5,
        'item3': 5,
        }
}
critics3 = {
    'user1':{
        'item1': 1,
        'item2': 3,
        'item3': 5,
        },
    'user2':{
        'item1': 5,
        'item2': 3,
        'item3': 1,
        }
}

print sim_pearson(critics, 'user1', 'user2', )
print sim_pearson(critics2, 'user1', 'user2', )
print sim_pearson(critics3, 'user1', 'user2', )

顺便提一下,使用Excel来确定正确答案是验证大部分计算结果的好方法。在这种情况下,你可以使用correl函数。

11

你的结果没有问题。你试图通过三个点画一条线。在第二种情况下,你的三个点的坐标都是一样的,也就是说实际上只有一个点。你不能说这些点是相关的还是反相关的,因为通过一个点可以画出无数条线(在你的代码中,den等于零)。

撰写回答