Spark:HanLP+Word2Vec+LSH实现文本推荐(kotlin)

文本推荐的基本流程就是首先对目标本文进行关键词提取,接着把关键词转成词向量,再计算词向量的相似性进行推荐。这三个步骤都有现成的模型和算法来实现,本文介绍的就是基于spark用hanlp+word2vec+lsh实现文本推荐。

下面先介绍每个步骤所用的模型和算法。

1.HanLP:提取中文文本的关键词

1.HanLP是一系列模型与算法组成的NLP工具包,目标是普及自然语言处理在生产环境中的应用。

2.HanLP工具包功能非常丰富,中文分词,词性标注,命名实体识别,关键词提取,自动摘要,短语提取,拼音转换,简繁转换,文本推荐.

3.这些功能的很多都是基于机器学习深度学习的模型,只不过已经训练好,我们直接调用即可

4.附上HanLP github链接: https://github.com/hankcs/HanLP

2.Word2Vec:把提取后的关键词转成词向量

1.Word2Vec是Google开源的一款用于词向量计算的工具,该工具得到的训练结果——词向量(word embedding),可以很好地度量词与词之间的相似性。

2.word2vec算法的背后其实是一个浅层神经网络,另外word2vec是一个计算word vector的开源工具并不是一个算法或者模型,可以理解为动词形式 “把word 转成 vector”,背后是word vector的CBoW模型和Skip-gram模型做了转换工作。

3.Word2Vec的发展历程以及内部算法其实算比较比较复杂,如果展开来讲可能一篇博客都讲不完,笔者也不能完全理解,简而言之,word2vec背后是一个神经网络模型用来预测目标语句出现的概率返回词库中的向量,然后计算输入向量和返回向量的相似度再归一化。Word2Vec主要用于机器翻译和推荐系统领域。

4.这里贴上一份比较详细的word2vec介绍的文章 https://www.cnblogs.com/guoyaohua/p/9240336.html

3.LSH:将向量进行哈希分桶,使得原语义上相似的文本大概率被哈希到同一个桶中,同个桶内的文本可以认为是大概率是相似的

LSH:局部敏感哈希算法,是一种针对海量高维数据的快速最近邻查找算法,主要有如下用法

1.近似重复的检测: LSH 通常用于对大量文档,网页和其他文件进行去重处理。
2.全基因组的相关研究:生物学家经常使用 LSH 在基因组数据库中鉴定相似的基因表达。
3.大规模的图片搜索: Google 使用 LSH 和 PageRank 来构建他们的图片搜索技术VisualRank。
4.音频/视频指纹识别:在多媒体技术中,LSH 被广泛用于 A/V 数据的指纹识别。

在推荐系统等应用中,经常会遇到的一个问题就是面临着海量的高维数据,查找最近邻。如果使用线性查找对于高维数据计算量和耗时都是灾难性的。为了解决这样的问题,出现了一种特殊的hash函数,使得2个相似度很高的数据以较高的概率映射成同一个hash值,而令2个相似度很低的数据以极低的概率映射成同一个hash值。我们把这样的函数,叫做LSH(局部敏感哈希),注意是这样的函数不是一种。

LSH中的哈希函数的数学定义

我们将这样的一族hash函数 H={h:S→U} 称为是(r1,r2,p1,p2)敏感的,如果对于任意H中的函数h,满足以下2个条件:
1.如果d(O1,O2)<r1d(O1,O2)<r1,那么Pr[h(O1)=h(O2)]≥p1Pr[h(O1)=h(O2)]≥p1
2.如果d(O1,O2)>r2d(O1,O2)>r2,那么Pr[h(O1)=h(O2)]≤p2
其中,O1,O2∈S,表示两个具有多维属性的数据对象,d(O1,O2)为2个对象的相异程度,也就是1 - 相似度

为了避免陷入数学漩涡,概括一下就是,LSH的哈希函数必须满足这样一个功能,当两个目标值足够相似时,映射为同一hash值的概率足够大,而足够不相似时,映射为同一hash值的概率足够小。

针对不同的相似度测量方法,局部敏感哈希的算法也不同,我们主要看看在两种最常用的相似度下,两种不同的LSH(spark也支持这两种模型)

1.使用Jaccard系数度量数据相似度时的min-hash ,对应spark的MinHashLSH

2.使用欧氏距离度量数据相似度时的P-stable hash,对应spark的BucketedRandomProjectionLSH

两种方法都是将高维数据降维到低维数据,且在一定程度上还能保持原数据的相似度不变,LSH的哈希相似度不变性也是概率性的不是确定性的,好在LSH的设计能够通过相应的参数控制出现这种错误的概率,这也是LSH为什么被广泛应用的原因

min-hash (通过度量Jaccard系数)

Jaccard系数主要用来解决的是非对称二元属性相似度的度量问题,常用的场景是度量2个集合之间的相似度,就是2个集合的交集比2个集合的并集(很容易理解吧)

下面我们来简单举个例子理解min-hash的原理

假设有4个文档,每个文档有相应的词项{w1,w2,…,w7},若某个文档存在这个词项,则标为1,否则标0.那么我们就可以得到如下矩阵1

接着我们进行一个随机的行置换可以得到如下矩阵2

可以确定的是,对这个矩阵按行进行多次置换,每次置换之后,统计每一列(其实对应的就是每个文档)第一个不为0的位置(行号),这样每次统计的结果能构成一个与文档数等大的向量,这个向量,我们称之为签名向量。比如文档1的签名向量 [1,2,1,2],文档2的签名向量 [1,1,2,1]

min-hash的思想就是如果两个文档足够相似,那也就是说这两个文档中有很多元素是共有的,换句话说,这样置换之后统计出来的签名向量,这些文档所对应的签名向量的相应的元素,值相同的概率就很高。概括一下就是如果两个文档相似,那么分词后含有的词项应该交集很多,那么进行随机置换后文档所在列的第一个不为0的位置很大概率是相同的,能理解吧

通过多次置换,求取向量,构建了一组hash函数。也就是最终得到了一个签名矩阵. 为了控制相似度与映射概率之间的关系,我们需要按下面的操作进行,一共三步

(1) 将signature matrix水平分割成一些区块(记为band),每个band包含了signature matrix中的r行。需要注意的是,同一列的每个band都是属于同一个文档的。


(2) 对每个band计算hash值,hash函数没有特殊要求,MD5,SHA1等等均可。但需要将这些hash值做处理,使之成为事先设定好的hash桶的tag,然后把这些band“扔”进hash桶中。如下图所示。

(3) 如果某两个文档的同一水平方向上的band,映射成了同一hash值(如果你选的hash函数比较安全,抗碰撞性好,那这基本说明这两个band是一样的),我们就将这两个文档映射到同一个hash bucket中,也就是认为这两个文档是足够相近的。

其中bands 和 rows 就是可以调节的两个参数,通过调节b和r两个参数可以得到一个很好的效果。

P-stable hash

不同的相似度判别方法,对应着不同的LSH,那对于最常见的Lp范数下的欧几里得空间,应该用怎样的LSH呢

P-stable hash涉及到p稳定分布的概念,这里不说数学自行百度,概括一下,p稳定分布一个重要的应用,就是可以估计给定向量vv在欧式空间下的p范数的长度,也就是||v||p||v||p。

p-stable 分布LSH函数族构造

1.将空间中的一条直线分成长度为r的,等长的若干段

2.通过一种映射函数(也就是我们要用的hash函数),将空间中的点映射到这条直线上,给映射到同一段的点赋予相同的hash值。不难理解,若空间中的两个点距离较近,他们被映射到同一段的概率也就越高。

3.可以得到这样一个结论:空间中两个点距离:近到一定程度时,应该被hash成同一hash值,而向量点积的性质,正好保持了这种局部敏感性。因此,可以用点积来设计hash函数族。

接下来我们来看看具体的代码实现 spark+kotlin

首选创建SparkSession,提交到spark平台的不需要设置master,顺便贴一下spark参数

val spark = SparkSession.builder().appName("NlpW2VectorLsh").enableHiveSupport().getOrCreate()

顺便提一下提交spark任务的一些常见参数,hive-site.xml定义了hive表,如果缺少此文件无法操作hive表

--master yarn
--deploy-mode cluster
--driver-memory 8G
--executor-memory 8G
--num-executors 8
--files /usr/local/spark/conf/hive-site.xml

调用类方法获取推荐结果,再把推荐结果保存在hdfs文件。
"\u0001"是hdfs的列分隔符(根据平台而定)
dataDS需要分区写入不然很大概率内存不够卡死

val dataDS = NlpW2VectorLsh().run(spark, DATA_HDFS_URL)LOGGER.warn("NlpVectorLsh dataDS success..........")dataDS.mapPartitions(MapPartitionsFunction<Row, String> { listRow ->val result = mutableListOf<String>()listRow.forEach { row ->val hiveRowText = row.getString(0) + "\u0001" +row.getString(1) + "\u0001" +row.getString(2) + "\u0001" +row.getString(3) + "\u0001" +row.getDouble(4)result.add(hiveRowText)}return@MapPartitionsFunction result.iterator()}, Encoders.javaSerialization(String::class.java)).rdd().saveAsTextFile(SAVE_HDFS_URL)LOGGER.warn("NlpVectorLsh saveAsTextFile success.............")spark.sql(LOAD_SPARK_SQL)LOGGER.warn("NlpVectorLsh load sql success.............")

再来看下run方法实现了什么
定制词库,在分词前对词库进行调整,此调整适用全局

/*** 动态修改NLP词典* CustomDictionary是一份全局的用户自定义词典,可以随时增删,影响全部分词器*/private fun nlpDictionary(){// 动态增减词典CustomDictionary.add("和田玉")// 强行插入CustomDictionary.insert("白富美", "nz 1024")// 删除词语(注释掉试试)CustomDictionary.remove("攻城狮")}

接着从hdfs文件读入数据,在读入数据的时候顺便进行分词和数据清洗

//获取样本数据 Nlp分词val dataDF = this.createDataset(spark, hdfsUrl)LOGGER.warn("NlpVectorLsh createDataset success..........")

用获取到的data训练word2vec模型
vectorSize是词向量的维度,minCount是忽略小于该词频的词向量

 /*** 训练word2vec模型*/fun trainingWord2Vector(spark: SparkSession, data: Dataset<Row>, vectorSize: Int, minCount: Int): Word2VecModel {val word2Vec = Word2Vec().setInputCol("words").setOutputCol("wordvec").setVectorSize(vectorSize).setMinCount(minCount)return word2Vec.fit(data)}

用训练好的word2vec模型把文本转向量

//文本转向量val vectorDF = w2vModel.transform(dataDF)LOGGER.warn("NlpVectorLsh word2Vector transform success..........")

训练LSH模型出来,这里的两个参数直接影响推荐的精度,先弄清楚这些参数的意义然后再去调整

/*** 创建LSH模型 用模型哈希分桶*/fun trainingLSH2hashModel(w2vDf: Dataset<Row>, bucketLength: Double, numHashTables: Int): BucketedRandomProjectionLSHModel {//桶长度可用于控制哈希桶的平均大小(因此也可用于控制桶的数量)。// 较大的桶长度(即,更少的桶)增加了将特征哈希到相同桶的概率(增加真实和假阳性的数量)val brp = BucketedRandomProjectionLSH().setBucketLength(bucketLength).setNumHashTables(numHashTables).setInputCol("wordvec").setOutputCol("hashes")return brp.fit(w2vDf)}

用训练好的LSH模型把词向量都进行哈希分桶

     //LSH 哈希分桶val lshHashDF = lshModel.transform(vectorDF).cache()LOGGER.warn("NlpVectorLsh lSH2hashModel transform success..........")

获取推荐结果呢有两种方法,approxSimilarityJoin 和 approxNearestNeighbors
approxSimilarityJoin是连表获取所有相似对,会有较多的重复,数据量也很大,用该方法在计算的时候要进行分区计算,approxNearestNeighbors是获取指定个数的最近邻的结果,为了避免再重新计算哈希,传入的数据应该是哈希后的数据,比如我们可以用以下方法来避免再次进行哈希运算

   //传入转化好的特征向量lshHashDF可以避免重计算哈希val recommendDF = lshModel.approxSimilarityJoin(lshHashDF, lshHashDF,0.015).select(col("datasetA.productSeq").alias("originSeq"),col("datasetA.productName").alias("originName"),col("datasetB.productSeq").alias("recommendSeq"),col("datasetB.productName").alias("recommendName"),col("distCol").alias("distance")).filter("distance > 0")LOGGER.warn("NlpVectorLsh recommendDF success..........")

大概整个流程就是如此
这里引入一下LSH在识别欺诈行为的性能测试结果

为了衡量性能,我们在WEX数据集上测试了MinHashLSH的实现。使用AWS云,我们使用16个executors(m3.xlarge 实例)执行WEX数据集样本的近似最近邻搜索和近似相似连接。

使用numHashTables = 5,近似最近邻的速度比完全扫描快2倍。在numHashTables = 3的情况下,近似相似连接比完全连接和过滤要快3-5倍。

在上面的表格中,我们可以看到哈希表的数量被设置为5时,近似最近邻的运行速度完全扫描快2倍;根据不同的输出行和哈希表数量,近似相似连接的运行速度快了3到5倍。

我们的实验结果还表明,尽管当前算法的运行时间很短,但与暴力方法的结果相比仍有较高的精度。近似最近邻搜索对于40个返回行达到了85%的正确率,而我们的近似相似连接成功地找到了93%的邻近行。这种速度与精度的折中算法,证明了LSH能从每天TB级数据中检测欺诈行为的强大能力。

参考文献

[1] Datar M, Immorlica N, Indyk P, et al. Locality-sensitive hashing scheme based on p-stable distributions[C]//Proceedings of the twentieth annual symposium on Computational geometry. ACM, 2004: 253-262.

[2]LSH和p-stable LSH http://blog.sina.com.cn/s/blog_67914f2901019p3v.html

[3]E2LSH源码分析–p稳定分布LSH算法初探
http://blog.csdn.net/jasonding1354/article/details/38237353

Spark:HanLP+Word2Vec+LSH实现文本推荐(kotlin)相关推荐

  1. spark Word2Vec+LSH相似文本推荐(scala)

    在上一篇博客,我们使用spark CountVectorizer与IDF进行了关键词提取,博客地址: spark CountVectorizer+IDF提取中文关键词(scala) 本篇博客在上一篇博 ...

  2. 使用Spark+Hanlp进行简单的文本处理(中)-Kmeans文本聚类

    文本聚类 1. TFIDF TFIDF全程叫做term frequency–inverse document frequency,即文本频率与逆文档频率指数, TFIDF就是为了表征一个token的重 ...

  3. 使用Word2Vec完成基于文本相似度的推荐

    使用 Word2Vec 完成基于文本相似度的推荐 之前的基于文本相似度的推荐使用的是one-hot的词向量,虽然可以使用稀疏向量来存储里面的非0值,但是以这种形式的词向量存在很多问题: 稀疏的向量表达 ...

  4. hanlp 词频统计_10.HanLP实现k均值--文本聚类

    AI 人工智能 10.HanLP实现k均值--文本聚类 10. 文本聚类 正所谓物以类聚,人以群分.人们在获取数据时需要整理,将相似的数据归档到一起,自动发现大量样本之间的相似性,这种根据相似性归档的 ...

  5. 情感分析的新方法,使用word2vec对微博文本进行情感分析和分类

    向AI转型的程序员都关注了这个号??? 大数据挖掘DT数据分析  公众号: datadw 情感分析是一种常见的自然语言处理(NLP)方法的应用,特别是在以提取文本的情感内容为目标的分类方法中.通过这种 ...

  6. word2vec词向量 文本分类实现(TensorFlow版,算法TextCNN)

    之前也写过word2vec词向量文本分类实现,不过那是基于Keras. 今天来写下tensoflow版的代码. 再来感受下它的魅力. tensorflow比Keras更接近底层,可以更方便让我们理解W ...

  7. Hadoop学习系列之Hadoop、Spark学习路线(很值得推荐)

    Hadoop学习系列之Hadoop.Spark学习路线(很值得推荐) 文章出自:http://www.cnblogs.com/zlslch/p/5448857.html 1 Java基础: 视频方面: ...

  8. python3spark文本分类_如何用Spark深度集成Tensorflow实现文本分类?

    本篇知识点:Tensorflow编程 CNN相关知识 PySpark相关知识 因为例子较为复杂,我们会假设你不但学习了[Tensorflow基础],而且还自己主动扩展了TF相关的知识,并且根据里面的推 ...

  9. Spark MLlib — Word2Vec

    Word2vec 是 Google 在 2013 年开源的一款将词表征为实数值向量的高效工具.能够将单词映射到K维向量空间,同时由于算法考虑了每个单词的上下文环境,因此词向量表示同时具有语义特性.本文 ...

最新文章

  1. 盘点一下数据库的误操作有哪些后悔药?
  2. suse linux 软件包安装,SUSE Linux 11系统rpm包离线安装GCC
  3. PostgreSQL 10.1 手册_部分 III. 服务器管理_第 32 章 回归测试_32.5. 测试覆盖检查
  4. ubuntu/Linux下 提示“conda:未找到命令”
  5. 计算机强势专业大学,计算机专业实力最强的6所大学,认可度高,竞争优势大,值得选择...
  6. 澄海哪里学机器人编程_少年学机器人编程
  7. 【设计模式:单例模式】单例模式02:懒汉模式
  8. CCSP2020比赛太原理工学子再创佳绩
  9. python设计计算器的目的_python的第一个作业:计算器的后台实现
  10. Pycharm主题颜色设置
  11. 通用能力-《即兴演讲》-樊登读书总结
  12. 软件测试必学的16个高频数据库操作及命令
  13. 财联社专访数说故事创始人徐亚波,谈商业模式与企业数字化
  14. Wordpress限制游客访问权限(免插件) 实现禁止游客访问功能
  15. OPPOR9Plus系列通刷刷机包精简包_OPPOR9Plus线刷包救砖包_OPPOR9Plus纯净刷机包_OPPOR9Plus刷机教程下载
  16. linux打包解压工具,打包压缩、解压缩工具详解
  17. C/C++编码准则,可借鉴的东西还挺多!
  18. OpenCV2.4.13 文本分割(水平垂直,直方图投影)
  19. 如何写《软件需求规格说明书》
  20. 如何购买一只基金?【赢利模型】

热门文章

  1. Linux设备相关信息查询
  2. 谷歌僵尸账号注销登录破解挑战 FB僵尸账号苹果僵尸账号找回登录破解挑战
  3. 局域网组网 实验7 MPLS VNP
  4. 拳王虚拟项目公社:引流高手最怕泄露的“引流4大秘诀”
  5. python二叉树广度遍历_黄哥Python: 二叉树的广度优先搜索的二种方法
  6. Excel2013每次打开都提示重新安装/正在配置
  7. 一体计算机屏幕只脚,这台嵌在显示器支架里的电脑 手撕笔记本 脚踹一体机!...
  8. 中国融资租赁行业市场投资分析与前景战略规划建议报告2022~2028年
  9. 系统动力学复习博客(二)——液压与气动系统
  10. C#WMI 操作 【转载】