Detecting Abuse at Scale: Locality Sensitive Hashing at Uber Engineering (2017)
原文链接:https://www.uber.com/en-GB/blog/lsh/
PS:图片都加载不出来了o(╥﹏╥)o
译文:
Uber在全球每天会有500多万次出行交易,对于Uber工程师来说保证数据的准确性非常重要。元数据和整合的数据如果使用得当的话,可以快速检测平台的滥用情况,从垃圾邮件到虚假账户和交易诈骗。放大正确数据的信号可以使检测更加准确可靠。
为了解决系统中的这一挑战,优步工程公司和Databricks共同为Apache Spark2.1贡献了局部敏感哈希(LSH)这一技术。LSH是一种随机算法和哈希技术,通常用于聚类和近似最近邻居搜索等大规模机器学习任务。
这篇文章中我们将展示Uber是如何使用LSH这个强大的工具来检测Uber中的虚假出行信息。
Why LSH?
在Uber使用LSH之前,我们一直使用一个复杂度为
N
2
N^2
N2的算法来筛选出行信息。尽管这个方法准确率很高,但是过于耗时,计算起来十分依赖硬件。
LSH的整体思想是使用一系列函数(称为LSH族函数)将数据点散列到桶中,这样相邻的数据点很有可能会落入同一个桶中,而彼此距离较远的数据点很大可能映射到不同的桶中。这可以使识别具有重叠的行程更加容易。
作为参考,LSH是具有丰富应用的多用途技术,包括:
- 近似重复检测:LSH通常用于大量文档、网页和文件的去重。
- 全基因关联研究:生物学家通常使用LSH去识别基因组数据库中类似的基因。
- 大规模图像搜索:谷歌使用LSH和PageRank构建他们的图像搜索技术VisualRank。
- 音频/视频指纹:在多媒体技术中,LSH被广泛用作A/V数据的指纹技术。
LSH at Uber
Uber中LSH主要应用在基于空间特性检测类似的出行信息,以此来识别存在欺诈行为的司机。Uber工程师在2016年的Spark峰会上提出了这个使用场景,讨论了我们对于在Spark框架上使用LSH来公开所有行程并筛选存在欺诈行为的记录的开发动机。我们的动机有三:
- Spark是Uber运营不可或缺的一部分,我们许多的内部团队现在都在使用Spark来处理各种各样的复杂数据,包括机器学习、空间数据处理、时间序列的计算、分析和预测以及特别数据科学探索。事实上,Uber几乎使用了所有的Spark组件,如MLlib、Spark SQL、Spark Streaming,以及YARN和Mesos上的直接RDD处理等。由于我们的基础设施和攻击是围绕Spark构建的,Uber工程师可以比较轻松的创建和管理Spark应用。
- Spark使得在进行任何实际机器学习之前需要做的数据清理和特征工程更加高效,使数字运算速度更快。Uber的大量数据使得通过基本方法来解决这个问题变得不可扩展和低速。
- 我们不需要这个方程的精确解,所以没必要去专门购买和维护新的硬件。在这种情况下,一个近似的结果就足够我们去判断潜在的欺诈行为,足够我们用来解决问题。LSH可以在消耗一些精度的情况下节省大量的硬件资源。
出于这些原因,对于我们的业务目标来说,在Spark上部署LSH来解决我们这个问题是一个正确的选择。
在高层次上,LSH的使用具有三个步骤。
首先,我们通过将行程信息分解为大小相等的区域段来为每个行程创建一个特征向量。
然后,我们使用Jaccard距离,通过MinHash对向量进行哈希操作。
最后,我们批量进行相似性连接或实时使用K近邻搜索。
相比于我们原本使用的暴力方法相比,我们的数据集使Spark的完成速度快了整整一个数量级(从
N
2
N^2
N2方法的大约55小时到使用LSH的4小时)
API Tutorial
为了更好地展示LSH的工作原理,我们将在维基百科提取WEX数据集上使用MinHashLSH来查找类似文章的示例。
每个LSH族都链接到其度量空间。在Spark 2.1中,有两个LSH估计量:
- 欧氏距离的BucketedRandomProjectionLSH
- Jaccard距离的MinHashLSH
在本文的使用场景中,我们使用MinHashLSH,因为我们将使用单词计数的实值特征向量。
Load Raw Data 加载原始数据
首先,我们需要启动一个EMR(Elastic MapReduce)集群,并将WEX数据集装载为EBS(Elastical Block Store)卷。有关此过程的其他详细信息,请参阅AWS关于EMR和EBS的文档。
在设置文本环境之后,我们根据EMR集群大小将WEX数据的样本上传到HDFS。在Spark shell中,我们在HDFS中加载示例数据:
// Read RDD from HDFS
import org.apache.spark.ml.feature._
import org.apache.spark.ml.linalg._
import org.apache.spark.sql.types._
val df = spark.read.option(“delimiter”,”t”).csv(“/user/hadoop/testdata.tsv”)
val dfUsed = df.select(col(“_c1”).as(“title”), col(“_c4”).as(“content”)).filter(col(“content”) !== null)
dfUsed.show()
【图1加载不出来,之后的图片也是】
图1显示了我们之前代码的结果,按标题和主题来显示文章。我们将使用这些内容作为哈希键值,并在以下实验中找到类似的维基百科文章。
Prepare Feature Vectors 准备特征向量
Minhash是一种非常常见的估算两个集合相似程度的LSH技术。在Spark中实现MinhashLSH时,我们将每个集合表示为一个二进制的稀疏向量。在这一步,我们将维基百科的文章内容转换为向量。
通过下面特征工程的代码,我们将文章内容切割为单词(Tokenizer),根据单词数量创建特征向量(CountVectorizer),并且将内容为空的文章删除:
·// Tokenize the wiki content
val tokenizer = new Tokenizer().setInputCol(“content”).setOutputCol(“words”)
val wordsDf = tokenizer.transform(dfUsed)
// Word count to vector for each wiki content
val vocabSize = 1000000
val cvModel: CountVectorizerModel = new CountVectorizer().setInputCol(“words”).setOutputCol(“features”).setVocabSize(vocabSize)
.setMinDF(10).fit(wordsDf)
val isNoneZeroVector = udf({v: Vector => v.numNonzeros > 0}, DataTypes.BooleanType)
val vectorizedDf = cvModel.transform(wordsDf).filter(isNoneZeroVector(col(“features”))).select(col(“title”), col(“features”))
vectorizedDf.show()
Fit and Query an LSH Model
为了使用MinhashLSH,首先我们使用以下命令在我们的特征化数据上拟合MinhashLSH模型:
val mh = new MinHashLSH().setNumHashTables(3).setInputCol(“features”).setOutputCol(“hashValues”)
val model = mh.fit(vectorizedDf)
我们可以使用LSH模型进行多种类型的查询,但是考虑到本教程的目的,我们首先在数据集上进行特征转换:
model.transform(vectorizedDf).show()
这条指令将为我们生成哈希值,用于之后的手动连接和特征生成。
接下来,我们将进行近似近邻搜索去找到最接近目标的数据点。为了便于展示,我们搜索那些与短语“united states”大致匹配的文章。
val key = Vectors.sparse(vocabSize, Seq((cvModel.vocabulary.indexOf(“united”), 1.0), (cvModel.vocabulary.indexOf(“states”), 1.0)))
val k = 40
model.approxNearestNeighbors(vectorizedDf, key, k).show()
最后,我们执行近似的相似性连接,以在同一数据集中找到相似的文章对。
// Self Join
val threshold = 0.8
model.approxSimilarityJoin(vectorizedDf, vectorizedDf, threshold).filter(“distCol != 0”).show()
请注意,虽然我们在下面使用自连接,但我们也可以连接不同数据集以获得相同的结果。
图5展示了如何设置哈希表的数量。对于近似的最近邻指令和近似的相似性连接,哈希表的数量要在运行时间和假阳性之间权衡。哈希表数量的增加可以提高准确性,但是也会增加程序的通信成本和运行时间。默认情况下,哈希表的数量设置为1.
Performance Tests
为了衡量性能,我们在WEX数据集上对MinhashLSH进行基准测试。使用AWS云,我们使用16个executors执行近似的最近邻搜索和相似性连接。
如下表所示,我们可以看到,在哈希表数量为5的情况下,近似最近邻居的运行速度比全扫描快2倍,而近似相似性连接的运行速度是输出行数和哈希表数量的3-5倍。
我们的实验还表明,尽管运行时间很短,与暴力方法相比,LSH算法仍然保持很高的精度。同时,近似最近邻搜索在40条返回行的情况下实现了85%的准确率,近似性连接成功地找到93%的相邻对。这种速度和准确性的权衡使LSH称为一种强大的工具,可以用于检测海量数据中的欺诈行为。
Next Steps
虽然我们的LSH模型帮助Uber识别了欺诈司机行为,但我们的工作还远未完成。在LSH的初始实现过程中,我们计划在未来的版本中部署一些功能。高优先级功能包括:
SPARK-18450:除了指定完成搜索所需的哈希表数量外,这个新功能还使用LSH来定义每个哈希表中的哈希函数数量。这一变化也将为AND/OR化合物扩增提供充分支持。
SPARK-18082和SPARK-18083:我们还想实现其他LSH系列。这两个更新将实现对两个数据点之间的汉明距离的比特采样,以及对机器学习任务中常用的余弦距离的随机投影符号。
SPARK-18454:第三个特性将改进近似最近邻居搜索的API。这种新的多探针相似性搜索可以在不需要大量哈希表的情况下提高搜索质量。