minhash pyspark 源码分析——hash join table是关键
从下面分析可以看出,是先做了hash计算,然后使用hash join table来讲hash值相等的数据合并在一起。然后再使用udf计算距离,最后再filter出满足阈值的数据:
参考:https://github.com/apache/spark/blob/master/mllib/src/main/scala/org/apache/spark/ml/feature/LSH.scala
/**
* Join two datasets to approximately find all pairs of rows whose distance are smaller than
* the threshold. If the [[outputCol]] is missing, the method will transform the data; if the
* [[outputCol]] exists, it will use the [[outputCol]]. This allows caching of the transformed
* data when necessary.
*
* @param datasetA One of the datasets to join.
* @param datasetB Another dataset to join.
* @param threshold The threshold for the distance of row pairs.
* @param distCol Output column for storing the distance between each pair of rows.
* @return A joined dataset containing pairs of rows. The original rows are in columns
* "datasetA" and "datasetB", and a column "distCol" is added to show the distance
* between each pair.
*/
def approxSimilarityJoin(
datasetA: Dataset[_],
datasetB: Dataset[_],
threshold: Double,
distCol: String): Dataset[_] = { val leftColName = "datasetA"
val rightColName = "datasetB"
val explodeCols = Seq("entry", "hashValue")
val explodedA = processDataset(datasetA, leftColName, explodeCols) // If this is a self join, we need to recreate the inputCol of datasetB to avoid ambiguity.
// TODO: Remove recreateCol logic once SPARK-17154 is resolved.
val explodedB = if (datasetA != datasetB) {
processDataset(datasetB, rightColName, explodeCols)
} else {
val recreatedB = recreateCol(datasetB, $(inputCol), s"${$(inputCol)}#${Random.nextString(5)}")
processDataset(recreatedB, rightColName, explodeCols)
} // Do a hash join on where the exploded hash values are equal.
val joinedDataset = explodedA.join(explodedB, explodeCols)
.drop(explodeCols: _*).distinct() // Add a new column to store the distance of the two rows.
val distUDF = udf((x: Vector, y: Vector) => keyDistance(x, y), DataTypes.DoubleType)
val joinedDatasetWithDist = joinedDataset.select(col("*"),
distUDF(col(s"$leftColName.${$(inputCol)}"), col(s"$rightColName.${$(inputCol)}")).as(distCol)
) // Filter the joined datasets where the distance are smaller than the threshold.
joinedDatasetWithDist.filter(col(distCol) < threshold)
}
补充:
sql join 算法 时间复杂度
参考
笔记
sql语句如下:
SELECT T1.name, T2.date
FROM T1, T2
WHERE T1.id=T2.id
AND T1.color='red'
AND T2.type='CAR'
假设T1有m行,T2有n行,那么,普通情况下,应该要遍历T1的每一行的id(m),然后在遍历T2(n)中找出T2.id = T1.id的行进行join。时间复杂度应该是O(m*n)
如果没有索引的话,engine会选择hash join或者merge join进行优化。
hash join是这样的:
- 选择被哈希的表,通常是小一点的表。让我们愉快地假定是T1更小吧。
- T1所有的记录都被遍历。如果记录符合color=’red’,这条记录就会进去哈希表,以id为key,以name为value。
- T2所有的记录被遍历。如果记录符合type=’CAR’,使用这条记录的id去搜索哈希表,所有命中的记录的name的值,都被返回,还带上了当前记录的date的值,这样就可以把两者join起来了。
时间复杂度O(n+m),实现hash表是O(n),hash表查找是O(m),直接将其相加。
merge join是这样的:
1.复制T1(id, name),根据id排序。
2.复制T2(id, date),根据id排序。
3.两个指针指向两个表的最小值。
>1 2<
2 3
2 4
3 5
4.在循环中比较指针,如果match,就返回记录。如果不match,指向较小值的指针指向下一个记录。
>1 2< - 不match, 左指针小,左指针++
2 3
2 4
3 5
1 2< - match, 返回记录,两个指针都++
>2 3
2 4
3 5
1 2 - match, 返回记录,两个指针都++
2 3<
2 4
>3 5
1 2 - 左指针越界,查询结束。
2 3
2 4<
3 5
>
时间复杂度O(n*log(n)+m*log(m))。排序算法的复杂度分别是O(n*log(n))和O(m*log(m)),直接将两者相加。
在这种情况下,使查询更加复杂反而可以加快速度,因为更少的行需要经受join-level的测试?
当然了。
如果原来的query没有where语句,如
SELECT T1.name, T2.date
FROM T1, T2
是更简单的,但是会返回更多的结果并运行更长的时间。
hash函数的补充:
可以看到 hashFunction 涉及到indices 字段下表的计算。另外的distance计算使用了jaccard相似度。
/**
* :: Experimental ::
*
* Model produced by [[MinHashLSH]], where multiple hash functions are stored. Each hash function
* is picked from the following family of hash functions, where a_i and b_i are randomly chosen
* integers less than prime:
* `h_i(x) = ((x \cdot a_i + b_i) \mod prime)`
*
* This hash family is approximately min-wise independent according to the reference.
*
* Reference:
* Tom Bohman, Colin Cooper, and Alan Frieze. "Min-wise independent linear permutations."
* Electronic Journal of Combinatorics 7 (2000): R26.
*
* @param randCoefficients Pairs of random coefficients. Each pair is used by one hash function.
*/
@Experimental
@Since("2.1.0")
class MinHashLSHModel private[ml](
override val uid: String,
private[ml] val randCoefficients: Array[(Int, Int)])
extends LSHModel[MinHashLSHModel] { /** @group setParam */
@Since("2.4.0")
override def setInputCol(value: String): this.type = super.set(inputCol, value) /** @group setParam */
@Since("2.4.0")
override def setOutputCol(value: String): this.type = super.set(outputCol, value) @Since("2.1.0")
override protected[ml] def hashFunction(elems: Vector): Array[Vector] = {
require(elems.numNonzeros > 0, "Must have at least 1 non zero entry.")
val elemsList = elems.toSparse.indices.toList
val hashValues = randCoefficients.map { case (a, b) =>
elemsList.map { elem: Int =>
((1L + elem) * a + b) % MinHashLSH.HASH_PRIME
}.min.toDouble
}
// TODO: Output vectors of dimension numHashFunctions in SPARK-18450
hashValues.map(Vectors.dense(_))
} @Since("2.1.0")
override protected[ml] def keyDistance(x: Vector, y: Vector): Double = {
val xSet = x.toSparse.indices.toSet
val ySet = y.toSparse.indices.toSet
val intersectionSize = xSet.intersect(ySet).size.toDouble
val unionSize = xSet.size + ySet.size - intersectionSize
assert(unionSize > 0, "The union of two input sets must have at least 1 elements")
1 - intersectionSize / unionSize
} @Since("2.1.0")
override protected[ml] def hashDistance(x: Seq[Vector], y: Seq[Vector]): Double = {
// Since it's generated by hashing, it will be a pair of dense vectors.
// TODO: This hashDistance function requires more discussion in SPARK-18454
x.zip(y).map(vectorPair =>
vectorPair._1.toArray.zip(vectorPair._2.toArray).count(pair => pair._1 != pair._2)
).min
} @Since("2.1.0")
override def copy(extra: ParamMap): MinHashLSHModel = {
val copied = new MinHashLSHModel(uid, randCoefficients).setParent(parent)
copyValues(copied, extra)
} @Since("2.1.0")
override def write: MLWriter = new MinHashLSHModel.MinHashLSHModelWriter(this)
}
minhash pyspark 源码分析——hash join table是关键的更多相关文章
- 第九篇:Spark SQL 源码分析之 In-Memory Columnar Storage源码分析之 cache table
/** Spark SQL源码分析系列文章*/ Spark SQL 可以将数据缓存到内存中,我们可以见到的通过调用cache table tableName即可将一张表缓存到内存中,来极大的提高查询效 ...
- Memcached源码分析——hash
以下为memcached中关于使用的hash算法的一点记录 memcached中默认使用的是Bob Jenkins的jenkins_hash算法 以下4段代码均在memcached-1.4.22/ha ...
- hbase源码分析:ERROR: Table already exists问题诊断
问题描述: 重新安装了测试环境的hadoop,所以之前hbase所建的表数据都丢失了,但是zookeeper没有动.在hbase shell中list的时候,看不到之前建的表,但是create tes ...
- 【Spark SQL 源码分析系列文章】
从决定写Spark SQL源码分析的文章,到现在一个月的时间里,陆陆续续差不多快完成了,这里也做一个整合和索引,方便大家阅读,这里给出阅读顺序 :) 第一篇 Spark SQL源码分析之核心流程 第二 ...
- 死磕以太坊源码分析之state
死磕以太坊源码分析之state 配合以下代码进行阅读:https://github.com/blockchainGuide/ 希望读者在阅读过程中发现问题可以及时评论哦,大家一起进步. 源码目录 |- ...
- [源码分析] 带你梳理 Flink SQL / Table API内部执行流程
[源码分析] 带你梳理 Flink SQL / Table API内部执行流程 目录 [源码分析] 带你梳理 Flink SQL / Table API内部执行流程 0x00 摘要 0x01 Apac ...
- java-通过 HashMap、HashSet 的源码分析其 Hash 存储机制
通过 HashMap.HashSet 的源码分析其 Hash 存储机制 集合和引用 就像引用类型的数组一样,当我们把 Java 对象放入数组之时,并非真正的把 Java 对象放入数组中.仅仅是把对象的 ...
- SOFA 源码分析 — 负载均衡和一致性 Hash
前言 SOFA 内置负载均衡,支持 5 种负载均衡算法,随机(默认算法),本地优先,轮询算法,一致性 hash,按权重负载轮询(不推荐,已被标注废弃). 一起看看他们的实现(重点还是一致性 hash) ...
- [转]数据库中间件 MyCAT源码分析——跨库两表Join
1. 概述 2. 主流程 3. ShareJoin 3.1 JoinParser 3.2 ShareJoin.processSQL(...) 3.3 BatchSQLJob 3.4 ShareDBJo ...
随机推荐
- [LeetCode] 119. Pascal's Triangle II 杨辉三角 II
Given an index k, return the kth row of the Pascal's triangle. For example, given k = 3,Return [1,3, ...
- 获取apk的Activity和Package
2.查看包名和activity adb logcat|grep -i activitymanager 获取当前界面的activity 1.adb shell dumpsys window window ...
- [转帖]Helm V2 迁移到 V3 版本
Helm V2 迁移到 V3 版本 -- :: Mr-Liuqx 阅读数 63更多 分类专栏: kubernetes 版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上 ...
- Qt 自定义QTabWidget
思路: QTabWidget的特点:点击不同的选项卡显示不同的窗口.可以将QTabWidget分成两部分: (1).选项卡:点击时要知道点击的是哪个选项.则需要将选项卡和窗口的信息存起来,点击时去这个 ...
- day40——数据库、数据库分类、安装、修改密码、字符集编码、简单语句介绍
day40 详情请看:https://www.cnblogs.com/clschao/articles/9907529.html 数据库 数据库 简而言之可视为电子化的文件柜--存储电子文件的处所,用 ...
- Fully Convolutional Networks for Semantic Segmentation 译文
Fully Convolutional Networks for Semantic Segmentation 译文 Abstract Convolutional networks are powe ...
- Java8时间转换
===java8中时间的各种转换(LocalDateTime)=== 1.将LocalDateTime转为自定义的时间格式的字符串 public static String getDateTimeAs ...
- gitlab 安装、备份与还原及常见设置
gitlab 安装.备份与还原及常见设置 安装 安装过程比较简单,跑在 docker 上,执行命令即可 -v参数后面的值为卷的名称,自动创建数据卷(如果数据卷不存在) https://docs.git ...
- org.apache.ibatis.binding.BindingException: Invalid bound statement (not found): 错误解决
报错信息:org.apache.ibatis.binding.BindingException: Invalid bound statement (not found) 说明:这段报错信息表示 Map ...
- 【转载】JVM结构、GC工作机制详解
文章主要分为以下四个部分 JVM结构.内存分配.垃圾回收算法.垃圾收集器.下面我们一一来看. 一.JVM结构 根据<java虚拟机规范>规定,JVM的基本结构一般如下图所示: 从左图可知, ...