【问题标题】:How to find the nearest neighbors of 1 Billion records with Spark?如何使用 Spark 找到 10 亿条记录的最近邻居?
【发布时间】:2016-08-28 23:11:49
【问题描述】:

给定包含以下信息的 10 亿条记录:

    ID  x1  x2  x3  ... x100
    1   0.1  0.12  1.3  ... -2.00
    2   -1   1.2    2   ... 3
    ...

对于上面的每个 ID,我想根据向量的欧几里德距离 (x1, x2, ..., x100) 找到前 10 个最接近的 ID。

计算这个的最佳方法是什么?

【问题讨论】:

  • 你试过什么?我们要求您向我们展示您迄今为止所做的尝试,当您遇到错误或不理解错误并且文档无法提供帮助时,我们将在这里尝试。此外,包含便于其他用户复制和粘贴到自己环境中的示例数据也很重要,这样他们就可以在自己的环境中进行操作。

标签: apache-spark pyspark spark-dataframe nearest-neighbor euclidean-distance


【解决方案1】:

碰巧,我有一个解决方案,包括将 sklearn 与 Spark 结合使用:https://adventuresindatascience.wordpress.com/2016/04/02/integrating-spark-with-scikit-learn-visualizing-eigenvectors-and-fun/

它的要点是:

  • 集中使用 sklearn 的 k-NN fit() 方法
  • 然后分布式使用 sklearn 的 k-NN kneighbors() 方法

【讨论】:

    【解决方案2】:

    对所有记录与所有记录进行暴力比较是一场失败的战斗。我的建议是使用现成的 k-Nearest Neighbor 算法实现,例如 scikit-learn 提供的算法,然后广播生成的索引和距离数组并走得更远。

    这种情况下的步骤是:

    1- 按照 Bryce 的建议对特征进行矢量化,并让您的矢量化方法返回一个浮点列表(或 numpy 数组),其中包含与您的特征一样多的元素

    2- 让你的 scikit-learn 神经网络适应你的数据:

    nbrs = NearestNeighbors(n_neighbors=10, algorithm='auto').fit(vectorized_data)
    

    3- 对矢量化数据运行经过训练的算法(在您的情况下,训练和查询数据相同)

    distances, indices = nbrs.kneighbors(qpa)
    

    第 2 步和第 3 步将在您的 pyspark 节点上运行,在这种情况下不可并行化。您需要在此节点上有足够的内存。在我的案例中,有 150 万条记录和 4 个功能,需要一两秒钟。

    在我们为 spark 获得良好的 NN 实现之前,我想我们必须坚持使用这些变通方法。如果你想尝试一些新的东西,那就去http://spark-packages.org/package/saurfang/spark-knn

    【讨论】:

    【解决方案3】:

    您没有提供很多细节,但我对这个问题采取的一般方法是:

    1. 将记录转换为像 LabeledPoint 这样的数据结构,以 (ID, x1..x100) 作为标签和特征
    2. 映射每条记录并将该记录与所有其他记录进行比较(这里有很多优化空间)
    3. 创建一些截止逻辑,以便在开始比较 ID = 5 和 ID = 1 时中断计算,因为您已经比较了 ID = 1 和 ID = 5
    4. 一些减少步骤来获得像{id_pair: [1,5], distance: 123}这样的数据结构
    5. 查找每条记录的 10 个最近邻居的另一个映射步骤

    您已经确定了 pyspark,我通常使用 scala 进行此类工作,但每个步骤的一些伪代码可能如下所示:

    # 1. vectorize the features
    def vectorize_raw_data(record)
        arr_of_features = record[1..99]
        LabeledPoint( record[0] , arr_of_features)
    
    # 2,3 + 4 map over each record for comparison
    broadcast_var = [] 
    def calc_distance(record, comparison)
        # here you want to keep a broadcast variable with a list or dictionary of
        # already compared IDs and break if the key pair already exists
        # then, calc the euclidean distance by mapping over the features of
        # the record and subtracting the values then squaring the result, keeping 
        # a running sum of those squares and square rooting that sum
        return {"id_pair" : [1,5], "distance" : 123}    
    
    for record in allRecords:
      for comparison in allRecords:
        broadcast_var.append( calc_distance(record, comparison) )
    
    # 5. map for 10 closest neighbors
    
    def closest_neighbors(record, n=10)
         broadcast_var.filter(x => x.id_pair.include?(record.id) ).takeOrdered(n, distance)
    

    伪代码很糟糕,但我认为它传达了意图。当您将所有记录与所有其他记录进行比较时,这里会有很多洗牌和排序。恕我直言,您希望将密钥对/距离存储在中心位置(例如会更新的广播变量,尽管这很危险),以减少您执行的总欧几里德距离计算。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2013-03-16
      • 2020-11-05
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2013-06-08
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多