web-dev-qa-db-ja.com

Sparkで10億レコードの最も近い隣人を見つける方法は?

次の情報を含む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を見つけたいと思います。

これを計算するための最良の方法は何ですか?

7
Osiris

すべてのレコードに対してすべてのレコードのブルートフォース比較を実行することは、敗戦です。私の提案は、scikit-learnによって提供されるようなk最近傍アルゴリズムの既製の実装を選択し、結果として得られるインデックスと距離の配列をブロードキャストして、さらに先に進むことです。

この場合の手順は次のとおりです。

1-ブライスが提案したように特徴をベクトル化し、ベクトル化メソッドが特徴と同じ数の要素を持つフロートのリスト(またはnumpy配列)を返すようにします

2-scikit-learnnnをデータに適合させます。

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

3-ベクトル化されたデータに対してトレーニング済みアルゴリズムを実行します(トレーニングデータとクエリデータは同じです)

distances, indices = nbrs.kneighbors(qpa)

手順2と3はpysparkノードで実行され、この場合は並列化できません。このノードには十分なメモリが必要です。 150万件のレコードと4つの機能を備えた私の場合、1〜2秒かかりました。

sparkのNNの適切な実装が得られるまで、これらの回避策に固執する必要があると思います。何か新しいことを試してみたい場合は、 http: //spark-packages.org/package/saurfang/spark-knn

6
architectonic

たまたま、sklearnとSparkを組み合わせることで、これに対する解決策があります: https://adventuresindatascience.wordpress.com/2016/04/02/integrating-spark-with-scikit-learn-visualizing-eigenvectors -and-fun /

その要点は次のとおりです。

  • Sklearnのk-NNfit()メソッドを一元的に使用する
  • ただし、sklearnのk-NN kneighbors()メソッドを分散して使用します
8
xenocyon

あなたは多くの詳細を提供していませんが、この問題に対して私が取る一般的なアプローチは次のとおりです。

  1. レコードを LabeledPoint のようなデータ構造に変換します。ラベルと機能は(ID、x1..x100)です。
  2. 各レコードをマッピングし、そのレコードを他のすべてのレコードと比較します(ここでは最適化の余地がたくさんあります)
  3. ID = 1とID = 5の比較を開始すると、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)

擬似コードはひどいですが、それは意図を伝えていると思います。すべてのレコードを他のすべてのレコードと比較しているため、ここでは多くのシャッフルと並べ替えが行われます。私見では、実行するユークリッド距離の合計計算を減らすために、キーペア/距離を中央の場所に保存します(これは危険ですが更新されるブロードキャスト変数など)。

1
brycemcd