我有一个包含工人的数据集,其中包含年龄性别,地址等人口统计信息及其工作地点。我从数据集创建了一个RDD并将其转换为DataFrame。
每个ID都有多个条目。因此,我创建了一个DataFrame,其中只包含工人的ID和他/她曾经工作的各个办公地点。
|----------|----------------|
| **ID** **Office_Loc** |
|----------|----------------|
| 1 |Delhi, Mumbai, |
| | Gandhinagar |
|---------------------------|
| 2 | Delhi, Mandi |
|---------------------------|
| 3 |Hyderbad, Jaipur|
-----------------------------
我想根据办公地点计算每个工人与其他工人之间的余弦相似度。
所以,我遍历了DataFrame的行,从DataFrame中检索了一行:
myIndex = 1
values = (ID_place_df.rdd.zipWithIndex()
.filter(lambda ((l, v), i): i == myIndex)
.map(lambda ((l,v), i): (l, v))
.collect())
然后使用地图
cos_weight = ID_place_df.select("ID","office_location").rdd\
.map(lambda x: get_cosine(values,x[0],x[1]))
计算提取的行与整个DataFrame之间的余弦相似度。
我不认为我的方法是好的,因为我在迭代DataFrame的行,它击败了使用spark的整个目的。 在pyspark有更好的方法吗? 请建议。
答案 0 :(得分:12)
您可以使用mllib
包来计算每行TF-IDF的L2
范数。然后将表与自身相乘得到余弦相似度,作为两乘L2
范数的点积:
<强> 1。 RDD 强>
rdd = sc.parallelize([[1, "Delhi, Mumbai, Gandhinagar"],[2, " Delhi, Mandi"], [3, "Hyderbad, Jaipur"]])
计算TF-IDF
:
documents = rdd.map(lambda l: l[1].replace(" ", "").split(","))
from pyspark.mllib.feature import HashingTF, IDF
hashingTF = HashingTF()
tf = hashingTF.transform(documents)
您可以在HashingTF
中指定要素数,以使要素矩阵更小(列数更少)。
tf.cache()
idf = IDF().fit(tf)
tfidf = idf.transform(tf)
计算L2
规范:
from pyspark.mllib.feature import Normalizer
labels = rdd.map(lambda l: l[0])
features = tfidf
normalizer = Normalizer()
data = labels.zip(normalizer.transform(features))
通过将矩阵与自身相乘来计算余弦相似度:
from pyspark.mllib.linalg.distributed import IndexedRowMatrix
mat = IndexedRowMatrix(data).toBlockMatrix()
dot = mat.multiply(mat.transpose())
dot.toLocalMatrix().toArray()
array([[ 0. , 0. , 0. , 0. ],
[ 0. , 1. , 0.10794634, 0. ],
[ 0. , 0.10794634, 1. , 0. ],
[ 0. , 0. , 0. , 1. ]])
OR:在numpy数组上使用笛卡尔积和函数dot
:
data.cartesian(data)\
.map(lambda l: ((l[0][0], l[1][0]), l[0][1].dot(l[1][1])))\
.sortByKey()\
.collect()
[((1, 1), 1.0),
((1, 2), 0.10794633570596117),
((1, 3), 0.0),
((2, 1), 0.10794633570596117),
((2, 2), 1.0),
((2, 3), 0.0),
((3, 1), 0.0),
((3, 2), 0.0),
((3, 3), 1.0)]
<强> 2。数据帧强>
由于您似乎在使用数据框,因此您可以改为使用spark ml
包:
import pyspark.sql.functions as psf
df = rdd.toDF(["ID", "Office_Loc"])\
.withColumn("Office_Loc", psf.split(psf.regexp_replace("Office_Loc", " ", ""), ','))
计算TF-IDF:
from pyspark.ml.feature import HashingTF, IDF
hashingTF = HashingTF(inputCol="Office_Loc", outputCol="tf")
tf = hashingTF.transform(df)
idf = IDF(inputCol="tf", outputCol="feature").fit(tf)
tfidf = idf.transform(tf)
计算L2
规范:
from pyspark.ml.feature import Normalizer
normalizer = Normalizer(inputCol="feature", outputCol="norm")
data = normalizer.transform(tfidf)
计算矩阵产品:
from pyspark.mllib.linalg.distributed import IndexedRow, IndexedRowMatrix
mat = IndexedRowMatrix(
data.select("ID", "norm")\
.rdd.map(lambda row: IndexedRow(row.ID, row.norm.toArray()))).toBlockMatrix()
dot = mat.multiply(mat.transpose())
dot.toLocalMatrix().toArray()
OR:使用联接和UDF
功能dot
:
dot_udf = psf.udf(lambda x,y: float(x.dot(y)), DoubleType())
data.alias("i").join(data.alias("j"), psf.col("i.ID") < psf.col("j.ID"))\
.select(
psf.col("i.ID").alias("i"),
psf.col("j.ID").alias("j"),
dot_udf("i.norm", "j.norm").alias("dot"))\
.sort("i", "j")\
.show()
+---+---+-------------------+
| i| j| dot|
+---+---+-------------------+
| 1| 2|0.10794633570596117|
| 1| 3| 0.0|
| 2| 3| 0.0|
+---+---+-------------------+
本教程列出了乘以大规模矩阵的不同方法:https://labs.yodas.com/large-scale-matrix-multiplication-with-pyspark-or-how-to-match-two-large-datasets-of-company-1be4b1b2871e
答案 1 :(得分:1)
关于此问题,由于我在pyspark的项目中必须使用余弦相似度,因此我不得不说@MaFF的代码是正确的,的确,当我看到他的代码时我犹豫了一下代码,因为他使用的是向量的L2范数的点积,因此理论说:数学上,它是向量的点积与量级乘积之比两个向量中的一个。
这是我的代码修改后的结果,因此我得出的结论是,SKLearn以不同的方式计算 tfidf ,因此,如果您尝试使用sklearn重播此摘要,您将获得结果不同。
d = [{'id': '1', 'office': 'Delhi, Mumbai, Gandhinagar'}, {'id': '2', 'office': 'Delhi, Mandi'}, {'id': '3', 'office': 'Hyderbad, Jaipur'}]
df_fussion = spark.createDataFrame(d)
df_fussion = df_fussion.withColumn('office', F.split('office', ', '))
from pyspark.ml.feature import HashingTF, IDF
hashingTF = HashingTF(inputCol="office", outputCol="tf")
tf = hashingTF.transform(df_fussion)
idf = IDF(inputCol="tf", outputCol="feature").fit(tf)
data = idf.transform(tf)
@udf
def sim_cos(v1,v2):
try:
p = 2
return float(v1.dot(v2))/float(v1.norm(p)*v2.norm(p))
except:
return 0
result = data.alias("i").join(data.alias("j"), F.col("i.ID") < F.col("j.ID"))\
.select(
F.col("i.ID").alias("i"),
F.col("j.ID").alias("j"),
sim_cos("i.feature", "j.feature").alias("sim_cosine"))\
.sort("i", "j")
result.show()
我还想与您分享一些简单的测试,以证明我对简单的矢量所做的测试是正确的:
亲切的问候,