由于项目组需要对爬虫获取的文本进行归类,最初使用正则表达式按照想到的规则进行解析分类,后来发现这种方式不够灵活,而且不能穷举所有的可能。所以项目组觉得使用最近比较流行的机器学习相关的知识去处理。
对文本进行分类之前,需要先对文本进行分词,然后将分词转换为特征向量,使用机器学习算法模型对特征向量和已经知道的标签数据进行模型拟合,产生理想的模型,通过理想的模型进行预测未来产生的数据
英文文档已经天然的分好词,根据空格字符就可以做出很精准的分词,只要控制好停用词即可。以下是spark已经为我们封装好的英文分词和停用词API使用介绍。
import org.apache.spark.ml.feature.Tokenizer
import org.apache.spark.sql.functions._
val sentenceDataFrame = spark.createDataFrame(Seq(
(0, "Hi I heard about Spark"),
(1, "I wish Java could use case classes"),
(2, "Logistic,regression,models,are,neat")
)).toDF("id", "sentence")
val tokenizer = new Tokenizer().setInputCol("sentence").setOutputCol("words")
val tokenized = tokenizer.transform(sentenceDataFrame)
tokenized.select("sentence", "words").show(false)
以上代码输出结果
+-----------------------------------+------------------------------------------+
|sentence |words |
+-----------------------------------+------------------------------------------+
|Hi I heard Spark about Spark |[hi, i, heard, spark, about, spark] |
|I wish Java could use case classes |[i, wish, java, could, use, case, classes]|
|Logistic regression models are neat|[logistic, regression, models, are, neat] |
+-----------------------------------+------------------------------------------+
中文分词比英文分词复杂很多,需要根据中文语义和中文词典进行分词。还好关于中文分词的工具很多大牛已经帮我们做好了,我们只要根据API去调用即可。本人在实践中最初使用了IKAnalysis,但是发现IKAnalysis分词器的性能很差,几百万的行的文本分词竟然跑不出来。后来,在同事的推荐下使用了HanNlp中文分词器,解决了性能上面的瓶颈。
TF-IDF是一种统计方法,用以评估一字词对于一个文件集或一个语料库中的其中一份文件的重要程度。字词的重要性随着它在文件中出现的次数成正比增加,但同时会随着它在语料库中出现的频率成反比下降。TF-IDF加权的各种形式常被搜索引擎应用,作为文件与用户查询之间相关程度的度量或评级。
关于TF-IDF的计算公式如下:
T F I D F = T F × I D F TFIDF=TF × IDF TFIDF=TF×IDF
TF(Term Frequency)是一个单词在当前文档中的出现次数除以当前文档总词频,计算公式如下:
T F i , j = N i , j ∑ k N k , j TF_i,_j=\frac{N_i,_j}{\sum_{k}N_k,_j} TFi,j=∑kNk,jNi,j
IDF(Invest Document Frequency) 逆文本频率指数,是等于总的文档数除以包含制定词的文档数。计算公司如下:
I D F = lg ∣ D ∣ ∣ j : t i ∈ d j ∣ IDF=\lg\frac{|D|}{|j:t_i∈d_j|} IDF=lg∣j:ti∈dj∣∣D∣
TF-IDF的值越大说明这个词在文档中的地位越高。如果一个词在所有的文档里面都出现,那么IDF的值等于0,TF-IDF的值也等于0,就算这个词在文档中出现的频率再高,对分析没有什么参考价值。
spark关于TF-IDF也有对应的API,代码
import org.apache.spark.ml.feature.IDF
hahingTF = new HashingTF()
.setInputCol("words")
.setOutputCol("rawFeatures").
setNumFeatures(20) // 设置特征向量数
val featurizedData = hashingTF.transform(wordsData)
val idf = new IDF().setInputCol("rawFeatures").setOutputCol("features")
val idfModel = idf.fit(featurizedData)
val rescaledData = idfModel.transform(featurizedData)
rescaledData.select("label", "features").show()
代码使用HashingTF模型将前面已经分好的word词转化为词频向量,使用rawFeatures字段里面,然后使用IDF模型将rawFeatures字段转化为词向量放在features字段里面。features字段就是我们想要得到的特征向量。代码输出结果如下:
+-----+------------------------------------------+--------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|label|words |rawFeatures |features |
+-----+------------------------------------------+--------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
|0.0 |[hi, i, heard, spark, about, spark] |(262144,[24417,49304,73197,91137,234657],[1.0,1.0,1.0,1.0,2.0]) |(262144,[24417,49304,73197,91137,234657],[0.28768207245178085,0.6931471805599453,0.6931471805599453,0.6931471805599453,1.3862943611198906]) |
|0.0 |[i, wish, java, could, use, case, classes]|(262144,[20719,24417,55551,116873,147765,162369,192310],[1.0,1.0,1.0,1.0,1.0,1.0,1.0])|(262144,[20719,24417,55551,116873,147765,162369,192310],[0.6931471805599453,0.28768207245178085,0.6931471805599453,0.6931471805599453,0.6931471805599453,0.6931471805599453,0.6931471805599453])|
|1.0 |[logistic, regression, models, are, neat] |(262144,[13671,91006,132713,167122,190884],[1.0,1.0,1.0,1.0,1.0]) |(262144,[13671,91006,132713,167122,190884],[0.6931471805599453,0.6931471805599453,0.6931471805599453,0.6931471805599453,0.6931471805599453]) |
+-----+------------------------------------------+--------------------------------------------------------------------------------------+------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+
Word2Vec是一个估计器,它获取表示文档的单词序列,并训练Word2VecModel。该模型将每个单词映射到一个唯一的固定大小向量。Word2VecModel使用文档中所有单词的平均值将每个文档转换为向量;然后这个向量可以用作预测、文档相似度计算等的特征。 以下是关于word2vec的spark使用案例
import org.apache.spark.ml.feature.Word2Vec
import org.apache.spark.ml.linalg.Vector
val word2Vec = new Word2Vec()
.setInputCol("words")
.setOutputCol("features")
.setVectorSize(3) // 设置特征向量数
.setMinCount(0)
val model = word2Vec.fit(tokenized)
val result = model.transform(tokenized)
result.show(false)
代码输出结果:
+-----+-----------------------------------+------------------------------------------+-----------------------------------------------------------------+
|label|sentence |words |features |
+-----+-----------------------------------+------------------------------------------+-----------------------------------------------------------------+
|0.0 |Hi I heard Spark about Spark |[hi, i, heard, spark, about, spark] |[-0.027734022897978623,-0.028992994998892147,0.06874776010711987]|
|0.0 |I wish Java could use case classes |[i, wish, java, could, use, case, classes]|[0.040505675066794665,0.019249096512794495,-0.027486081767295088]|
|1.0 |Logistic regression models are neat|[logistic, regression, models, are, neat] |[0.035547485947608946,0.016720289736986162,0.02818153351545334] |
+-----+-----------------------------------+------------------------------------------+-----------------------------------------------------------------+
关于回归和分类的模型在机器学习中有很多,本人初次接触机器学习,也只是大概知道有这么些模型,关于模型的具体理论和使用,是本人后面学习的目标。这里只是列举以下。
本人使用TF-IDF对分词后的文档进行词向量转化,然后使用神经网络-多层感知器分类。传输层我定义4层,输入层+2层隐藏层+输出层
import org.apache.spark.ml.classification.MultilayerPerceptronClassifier
import org.apache.spark.ml.evaluation.MulticlassClassificationEvaluator
val layers = Array[Int](1000, 60, 50, 10) // 输入一个1000个特征的向量,第一个隐藏层60个节点,第二个隐藏层50个节点,输出10个分类。
val trainer = new MultilayerPerceptronClassifier()
.setLayers(layers)
.setBlockSize(128)
.setSeed(1234L)
.setMaxIter(100)
val model = trainer.fit(trainingDF)
val preceptronDF = model.transform(testDF)
val evaluator = new MulticlassClassificationEvaluator()
.setMetricName("accuracy")
println("测试数据预测准确度 = " + evaluator.evaluate(predictionAndLabels))
由于数据安全性考虑,本人不在这里给出真实数据运行结果。以上参数运行70万行文本进行模型训练,30万行文本进行模型测试,准确度能够达到98%左右。
本人这次接触机器学习,并使用机器学习在项目中实践和应用,我感到成长了很多。虽然这次只是知道机器学习的简单使用,对一些复杂的模型不是很清楚原理,这也将激发我继续学下去的动力。对于机器学习,很多时候还是要多动动手,同事模型选择和预测数据的准备也很重要,选对模型,准备好正确的数据,那么我们就可以行动啦。