基于spark的文本相似性匹配

基于pyspark的英文文本相似度匹配

原文本文件是txt格式的多文本,数量大约三万个,项目需求是对这些文本进行相似度的匹配,找出哪些文本之间内容是相似的。

文本内容类似如下:

From: kedz@bigwpi.WPI.EDU (John Kedziora)
Subject: Motorcycle wanted.
Organization: Worcester Polytechnic Institute
Lines: 11
Expires: 5/1/93
NNTP-Posting-Host: bigwpi.wpi.edu

Followup-To:kedz@wpi.wpi.edu
Distribution: ne
Organization: Worcester Polytechnic Institute
Keywords:

I am looking for an inexpensive motorcycle, nothing fancy, have to be able to do all maintinence my self. looking in the <$400 range.

if you can help me out, GREAT!, please reply by e-mail.

文本的关键信息主要是标题(subject)和正文部分。

项目环境

linux环境
hadoop2.7.1
spark2.4.5
开发语言:python

项目思路

数据预处理
把一万多个文本数据进行清洗,提取出有效信息(标题+正文),把有效信息合并为一个大的txt文件,合并后文件的每一行与原来的单个txt文件的信息对应。

剔除停用词
剔除原文本里很多的英文常用词,例如am,is,going等,这些常用词汇很容易出现,但是没有办法成为一个句子的代表性词汇,故而必须去掉。

特征工程
首先统计单行句子里的每一个词的TFIDF值,把值比较大的前15个词提出来作为句子的关键词,然后把这些关键词转为word2vec向量,word2vec向量将作为单行文本的特征。

使用LSH模型进行距离筛选
实际上起初我打算使用传统的机器学习方法,构建logistic回归模型或者神经网络模型,做成一个分类模型,但是由word2vec向量构成的训练模型效果很差(三层神经网络的准确率只有百分之十五),在这个方向失败之后,回到LSH模型做出相似性匹配。
LSH即Locality Sensitive has,局部敏感哈希,主要用来解决海量数据的相似性检索。由spark的官方文档翻译为:LSH的一般思想是使用一系列函数将数据点哈希到桶中,使得彼此接近的数据点在相同的桶中具有高概率,而数据点是远离彼此很可能在不同的桶中。spark中LSH支持欧式距离与Jaccard距离。这里我们使用的是欧式距离。由在相同的桶中的数据认为是高概率相似的。

模型结构

数据输入,剔除停用词

StopWords = [] #停用词

with open(r"/usr/local/chinesestopwords.txt") as f:
    for i in f.readlines():
        i = i.strip('\n')
        StopWords.append(i)
def f(x):
    rel = {}
    rel['index'] = str(x[0])
    seg_list = jieba.cut(str(x[1]))
    rel['words'] =[word for word in list(seg_list) if word not in StopWords]
    return rel  

train_path = "file:///usr/local/chinese.txt"
train_file = spark.sparkContext.textFile(train_path)
sqc = SQLContext(spark.sparkContext)

rdd = train_file.map(lambda line:line.split('==')).map(lambda p:Row(**f(p))).toDF()

获取keyword列

#首先使用CountVectorizer,将words转化为featurn词频向量
cv = CountVectorizer(inputCol='words',outputCol='feature',vocabSize=10000,minDF=2)
#IDF输入逆词频向量
idf=IDF(inputCol='feature',outputCol='features')

idfPipeline =  Pipeline().setStages([cv,idf])
idfPipelineModel = idfPipeline.fit(rdd)
idfDF = idfPipelineModel.transform(rdd)

cvModel = idfPipelineModel.stages[0]

voc = cvModel.vocabulary

idfDF.show()
def getWords(vec):
    list1 = []
    list2 = []
    for i in vec.indices:
        list1.append(voc[int(i)])
    for j in vec.values:
        list2.append(j)
    res = list(zip(list1,list2))
    res = sorted(res,key=lambda x: x[1],reverse=True)
    ret = []
    if(len(res)<25):
        ret = [a[0] for a in res]
    else:
        ret = [res[k][0] for k in range(25)]
    return ret
getKeyWordsFun = udf(lambda vec:getWords(vec))
keyWordsDf = idfDF.withColumn('keywords',getKeyWordsFun(col("features")))
keyWordsDf.drop("words").drop("features").drop("feature")

keyword转为word2vec向量

keyWordsDf = keyWordsDf.withColumn('keywords',split(keyWordsDf['keywords'],",/s*").cast(ArrayType(StringType())))
word2Vec = Word2Vec(inputCol='keywords',outputCol='wordvec',vectorSize=15,minCount=0)
wvModel = word2Vec.fit(keyWordsDf)
w2vDf = wvModel.transform(keyWordsDf)

构建LSH模型

brp = BucketedRandomProjectionLSH(bucketLength=4.0,numHashTables=10,inputCol='wordvec',outputCol='hashes')
brpModel = brp.fit(w2vDf)
tsDf = brpModel.transform(w2vDf)
#使用LSH模型获取每个文档的相似文档(欧式距离)
brpDf = brpModel.approxSimilarityJoin(tsDf,tsDf,0.3,"EduclideanDistance")

sparksql相似性文档查找

getIdFun = udf(lambda row:str(row[0]))
corrDf1 = brpDf.withColumn("id",getIdFun(col("datasetA")))
corrDf = corrDf1.withColumn("id_sim",getIdFun(col("datasetB"))).drop("datasetA").drop("datasetB").drop("EduclideanDistance")
corrDf.createOrReplaceTempView("test")
result = sqc.sql("select id,concat_ws(',',collect_set(id_sim)) as sim from test where id!=id_sim group by id")
corrDf.show()
result.show()
result.repartition(1).write.format("csv").save("file:///usr/local/result_info")

项目结果

在这里插入图片描述实际上中文文本的相似性匹配也可以使用这套模型框架,只是多了一个中文分词过程,可以用jieba分词实现。

项目里遇到的困难点

因为项目是基于pyspark开发,使用python语言,对于udf(新增列)的方法,不能像scala一样直接调用库函数,我借用lambda的思想,用lambda做出了实现。
例如getKeyWordsFun = udf(lambda vec:getWords(vec))
getWords()方法就对如何提选出15个关键词做了具体实现。

用这种方式作出实现之后,又出现了新的问题,我们通过上面的方法得到的keywords列是纯粹的Stringtype格式,但是因为下面要调用
word2Vec = Word2Vec(inputCol=‘keywords’,outputCol=‘wordvec’,vectorSize=15,minCount=0)
keywords列是输入,但是因为Word2Vec模型的输入不能是单纯的Stringtype格式,程序会错误。所以在调用Word2Vec模型前需要cast转型,转为ArrayType(StringType())格式:
keyWordsDf = keyWordsDf.withColumn(‘keywords’,split(keyWordsDf[‘keywords’],",/s*").cast(ArrayType(StringType())))

其次在程序运行时发生的一个错误是JVM OOM错误,这是spark非常典型的一个错误,一般是由于数据倾斜使某分区过大或者创建的对象过多造成的,解决这个问题的方法不尽相同,如果是多个key过大,可以重新分区减小单个分区的数据量;如果单个key值数据量太大,只能就这个key值重新划分。在这里因为我们大多调用了模型,选择扩大JVM内存值,也成功解决了这个问题。

  • 0
    点赞
  • 0
    评论
  • 3
    收藏
  • 一键三连
    一键三连
  • 扫一扫,分享海报

©️2021 CSDN 皮肤主题: 游动-白 设计师:白松林 返回首页
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、C币套餐、付费专栏及课程。

余额充值