【问题标题】:Calculate euclidean distance from dicts (sklearn)计算字典的欧几里得距离(sklearn)
【发布时间】:2016-07-14 08:55:52
【问题描述】:

我的代码中已经计算了两个dictionaries,如下所示:

X = {'a': 10, 'b': 3, 'c': 5, ...}
Y = {'a': 8, 'c': 3, 'e': 8, ...}

实际上它们包含来自维基文本的单词,但这应该可以说明我的意思。它们不一定包含相同的键。

最初我想像这样使用sklearn的成对度量:

from sklearn.metrics.pairwise import pairwise_distances

obama = wiki[wiki['name'] == 'Barack Obama']['tf_idf'][0]
biden = wiki[wiki['name'] == 'Joe Biden']['tf_idf'][0]

obama_biden_distance = pairwise_distances(obama, biden, metric='euclidean', n_jobs=2)[0][0]

但是,这会产生错误:

--------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-124-7ff03bd40683> in <module>()
      6 biden = wiki[wiki['name'] == 'Joe Biden']['tf_idf'][0]
      7 
----> 8 obama_biden_distance = pairwise_distances(obama, biden, metric='euclidean', n_jobs=2)[0][0]

/home/xiaolong/development/anaconda3/envs/coursera_ml_clustering_and_retrieval/lib/python3.4/site-packages/sklearn/metrics/pairwise.py in pairwise_distances(X, Y, metric, n_jobs, **kwds)
   1205         func = partial(distance.cdist, metric=metric, **kwds)
   1206 
-> 1207     return _parallel_pairwise(X, Y, func, n_jobs, **kwds)
   1208 
   1209 

/home/xiaolong/development/anaconda3/envs/coursera_ml_clustering_and_retrieval/lib/python3.4/site-packages/sklearn/metrics/pairwise.py in _parallel_pairwise(X, Y, func, n_jobs, **kwds)
   1058     ret = Parallel(n_jobs=n_jobs, verbose=0)(
   1059         fd(X, Y[s], **kwds)
-> 1060         for s in gen_even_slices(Y.shape[0], n_jobs))
   1061 
   1062     return np.hstack(ret)

AttributeError: 'dict' object has no attribute 'shape'

对我来说,这看起来像是试图访问 shape 属性,而 dict 没有。我猜它需要numpy 数组。我如何转换字典,以便 sklearn 函数将计算正确的距离,假设 0 值,如果字典没有某个键,另一个字典有?

【问题讨论】:

    标签: python numpy dictionary scikit-learn euclidean-distance


    【解决方案1】:

    你为什么不直接从你的稀疏表示中做呢?

    In [1]: import math
    
    In [2]: Y = {'a': 8, 'c':3,'e':8}
    
    In [3]: X = {'a':10, 'b':3, 'c':5}
    
    In [4]: math.sqrt(sum((X.get(d,0) - Y.get(d,0))**2 for d in set(X) | set(Y)))
    Out[4]: 9.0
    

    【讨论】:

    • 有超过 50 万个数据点,我想让 sklearn 完成繁重的工作。同样在每个数组中,不存在的键也需要为零,否则欧几里德距离是错误的。
    • @Zelphir 哎呀!你是对的。嗯,使用余弦距离?呵呵。但实际上,您仍然可以通过使用带有默认值的get 方法对其进行修改以使其工作。
    • @Zelphir 连同集合并集而不是交集。我之前对大量文档的大型词汇表和语料库使用了相同的方法,而且速度相当快。
    • 我会试一试 :) 看看运行时是否可以接受。
    • 哇,您的编辑让您的代码比我的更智能!我也试试。
    【解决方案2】:

    您可以从创建一个包含所有字典键的列表开始(请务必注意,此列表必须进行排序):

    X = {'a': 10, 'b': 3, 'c': 5}
    Y = {'a': 8, 'c': 3, 'e': 8}
    data = [X, Y]
    words = sorted(list(reduce(set.union, map(set, data))))
    

    这在 Python 2 中运行良好,但如果您使用的是 Python 3,则需要添加句子 from functools import reduce(感谢 @Zelphir 发现这一点)。如果您不想导入functools 模块,您可以将上面sn-p 的最后一行替换为以下代码:

    words = set(data[0])
    for d in data[1:]:
        words = words | set(d)
    words = sorted(list(words))
    

    无论您选择哪种方法,words 列表都可以设置一个矩阵,其中每一行对应一个字典(样本),这些字典(特征)的值被放置在与其键对应的列。

    feats = zip(*[[d.get(w, 0) for d in data] for w in words])
    

    这个矩阵可以传递给scikit的函数pairwise_distance

    from sklearn.metrics.pairwise import pairwise_distances as pd
    dist = pd(feats, metric='euclidean')
    

    以下交互式会话演示了它的工作原理:

    In [227]: words
    Out[227]: ['a', 'b', 'c', 'e']
    
    In [228]: feats
    Out[228]: [(10, 3, 5, 0), (8, 0, 3, 8)]
    
    In [229]: dist
    Out[229]: 
    array([[ 0.,  9.],
           [ 9.,  0.]])
    

    最后,您可以将上面的代码包装成一个函数来计算任意数量字典的成对距离:

    def my_func(data, metric='euclidean'):
        words = set(data[0])
        for d in data[1:]:
            words = words | set(d)
        words = sorted(list(words))
        feats = zip(*[[d.get(w, 0) for d in data] for w in words])
        return pd(feats, metric=metric)
    

    我已经避免调用 reduce 以便包装器可以跨版本工作。

    演示:

    In [237]: W = {'w': 1}
    
    In [238]: Z = {'z': 1}
    
    In [239]: my_func((X, Y, W, Z), 'cityblock')
    Out[239]: 
    array([[  0.,  15.,  19.,  19.],
           [ 15.,   0.,  20.,  20.],
           [ 19.,  20.,   0.,   2.],
           [ 19.,  20.,   2.,   0.]])
    

    【讨论】:

    • 另外需要注意的是reduce需要从functools导入。
    • 需要注意的是reduce需要从functools导入。早些时候我忘记重命名该函数并意外地运行了另一个函数两次,这让我相信你的答案中的函数对我来说与接受的答案中的另一个函数一样。但是,当我更改名称并再次尝试时,我遇到了一些问题,我无法让您的函数运行:/也许我在某个地方犯了错误。
    • 为什么你认为reduce()需要从functools导入?我的代码中唯一的导入语句是from sklearn.metrics.pairwise import pairwise_distances as pd(顺便说一句,在您的问题中您提到您希望使用它来计算成对距离)。其他函数要么是内置函数,即reduce()map()list()set()zip()sorted(),要么是内置类方法,例如set.union()dict.get() .看看docs.python.org/2/library/functions.html#reduce 说服自己...
    • 无意冒犯,但这是 2016 年; ) 我在 3 年前改用 Python。我没有使用 Python 2,并尽可能避免使用它。在 Python 3 中,reduce 被移到了functools,因为很多人误用了它。就像现在一样,sum 无需导入即可使用,因此人们不会陷入使用reduce 来计算总和的陷阱。至少这是我在某处读到的关于 reduce 已被移动的解释。
    • 对不起,我的错! :( 我还没有转到第 3 版,并且我无意识地认为 Python 2 仍然被大多数人视为默认设置。我将编辑我的答案并将对 reduce 的调用替换为 for 循环,以便跨版本工作的代码。感谢您的反馈!
    【解决方案3】:

    您似乎想使用X.get(search_string,0),如果找不到,它将输出值或0。如果您有很多搜索字符串,您可以使用 [X.get(s,0) for s in list_of_strings] 来推送输出列表。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2020-11-29
      • 2018-02-14
      • 2013-04-07
      • 2021-01-31
      • 2015-09-23
      • 1970-01-01
      • 2017-07-07
      相关资源
      最近更新 更多