Spark - 加速笛卡尔积

时间:2018-01-05 14:05:56

标签: apache-spark pyspark combinations

有没有一种正确的方法来加速两个数据集之间的笛卡尔积?我使用了350k元素的数据集,我想得到它的组合(n取2)。

我使用经典策略在Spark中找到2组合:

words_comb = dataset.cartesian(dataset).filter(lambda x: x[0] < x[1])

我正在使用Databricks框架,并且需要超过45分钟才能解决(45分钟时火花驱动程序停在Databricks中......)。我们都同意这个特定问题的瓶颈是数据集的笛卡儿积这一事实,其时间复杂度为O(n ^ 2)。

有没有办法改善这个?有没有更好的方法解决这个问题?

(谢客)

2 个答案:

答案 0 :(得分:2)

  

我需要构建一个图G =(N,F),其中F(n)是具有edit_distance(n,s)= 1的单词s的子集S作为图像的函数。 hae从所有单词组合开始,然后我过滤了所有不满足edit_distance = 1约束的单词对。

你的方法效率低得离谱。 n 平均长度的字 s 或多或少 O(n 2 s 2 n 2 edit_distance来电)。与此同时,您的数据很小(根据the comment为4.1MB)并且分布式处理及其开销并不是很有用。你应该重新考虑你的方法。

我的建议是使用有效的查找结构(例如TrieBWT),这可以促进不匹配的高效搜索。构建索引,使用整个数据集,如果需要,使用线程来并行化搜索。

答案 1 :(得分:0)

It's possible to get rid of Cartesian product, without using a special data structure.

I will demonstrate the method by an example with pyspark

Suppose you have a data set of (user, query).

input:

users_queries_rdd = sc.parallelize([
     ('u1', 'q1'), ('u1', 'q2'), ('u1', 'q3'), ('u1', 'q4'),
     ('u2', 'q2'), ('u2', 'q4'), ('u2', 'q5'),
     ('u3', 'q1'), ('u3', 'q2'), ('u3', 'q4')
 ])

You would like to count the occurrences of 2 queries for different users.

expected output:
[(('q4', 'q2'), 3),
 (('q5', 'q2'), 1),
 (('q3', 'q1'), 1),
 (('q5', 'q4'), 1),
 (('q4', 'q1'), 2),
 (('q3', 'q2'), 1),
 (('q2', 'q1'), 2),
 (('q4', 'q3'), 1)]

方法1-使用笛卡尔积:

pair_queries_count_rdd = users_queries_rdd\
.cartesian(users_queries_rdd)\
.filter(lambda line: line[0] > line[1])\
.filter(lambda line: line[0][0] == line[1][0])\
.map(lambda line: (line[0][1], line[1][1]))\
.map(lambda line: (line, 1))\
.reduceByKey(add)

方法2-去除笛卡尔积:

pair_queries_count_rdd_no_cartesian = users_queries_rdd\
                        .map(lambda line: (line[0], [line[1]]))\
                        .reduceByKey(add)\
                        .map(lambda line: tuple(combinations(line[1], 2)))\
                        .flatMap(lambda line: [(x, 1) for x in line])\
                        .reduceByKey(add)

说明:

方法1:

1.1 .cartesian(users_queries_rdd)
    在rdd到自身之间创建笛卡尔乘积。     它会生成n ^ 2个组合。

1.2 .filter(lambda line:line [0]> line [1])
    如果包含(q1,q2),则(q2,q1)将被过滤掉。

1.3 .filter(lambda line:line [0] [0] == line [1] [0])
    按用户对查询进行分组(汇总)。

1.4 .map(lambda line:(line [0] [1],line [1] [1]))
    忽略用户列。仅保留查询对。

1.5 .map(lambda line:(line,1))
    每行将(q [i],q [j])映射到((q [i],q [j]),1)

1.6 .reduceByKey(add)
    计算每个查询对的出现次数。

方法2:

2.1 .map(lambda line:(line [0],[line [1]]))
    将每行的(u [i],q [j])映射到(u [i],[q [j]])(查询被封装到列表中)

2.2 .reduceByKey(add)
    为每个用户创建一个包含所有查询的列表。

2.3 .map(lambda line:tuple(combinations(line [1],2)))
    对于每个用户,请省略“用户”列,并创建其所有查询的组合

2.4 .flatMap(lambda line:[(x,1)for x in line])
    平面映射将所有键都平面化:(q [i],q [j])映射到((q [i],q [j]),1)

2.5 .reduceByKey(add)
    计算每个查询对的出现次数。

我们假设每个用户的查询数量相对较少(恒定数量)。 因此,方法2的效率为O(n)

由于每个用户都使用iter.combinations函数,因此这一假设至关重要。

在大多数实际情况下,方法2更为有效。