相信大家一定了解多分类问题了,这里就不过多阐述了。前面说到的垃圾邮件识别是一个二分类问题,多分类就是多个类别。这次呢,我们使用20news-group来简单介绍如何实现这个多分类问题。
我们先来介绍一下20news-group吧,它其实就是一个20个类别得新闻组,比如说体育、娱乐、国事...。下面给出下载地址,官网上解释很清楚。目录结构和上篇文章中得垃圾邮件是一样得,这里就不上图了。
下载地址:http://qwone.com/~jason/20Newsgroups/
一般情况下,SVM有两种多分类方法,一种是one-vs-all,一种是pairwise。
one-vs-all: 假设我们有4个类,A\B\C\D,他的流程是:将A设为正类,然后BCD为一类,这就是一个二分类,记为PA,然后就是B为一类,ACD为一类,记为PB,依次类推。然后最后就有PA,PB,PC,PD的四个概率,然后取最大的。但是这种方法有种缺陷,训练集是1:K-1,这种情况下存在biased.因而不是很实用.
pairwise:也称one-vs-one,同样假设ABCD4个类,它是怎么操作的呢,他也是构建了多个二分类,但是是这样的,AB,AC,AD,BC,BD,CD一共六个。训练结果,在测试的时候则是分别对6个分类器进行测试,然后就是通过投票的形式,获取最终的类别。
投票过程:
最开始A=B=C=D=0
AB:假设AB分类器中,分类为A,则A=A+1,B=0,反之B=B+1,A=0
AC:同理,假设为A,A=A+1,C=0,反之
。。。
· CD:假设结果为C,则C=C+1,D=0,反之
最后取数最大的哪一个作为分类结果。
这个方法虽然好,但是当类别非常多的时候,那么就会有n(n-1)/2个二分类,计算代价是非常昂贵的。
接下来我们就通过Spark来实现一下one-vs-all的方式,Spark中也只实现了该方式。这里使用的是pipeline的方式来处理数据,以及模型的建立同时还能够通过保存模型的方式,这里我们没有对数据进行过多的预处理,使用的是tf-idf,这里也没有进行stopword的处理,效果上肯定还可以在进行提升。
import org.apache.spark.ml.feature.{HashingTF, IDF, Tokenizer}
import org.apache.spark.ml.classification.{LogisticRegression, OneVsRest}
import org.apache.spark.ml.evaluation.MulticlassClassificationEvaluator
import org.apache.spark.ml.feature.IndexToString;
import org.apache.spark.ml.feature.StringIndexer
import org.apache.spark.ml.Pipeline
import org.apache.spark.ml.PipelineModel
import org.apache.spark.ml.PipelineStage
val dataDF = spark.sparkContext
.wholeTextFiles("file:///opt/data/20_newsgroups/*")
.map{case(p,c) =>(p.split("/").takeRight(2).head,c)}
.toDF("label","context")
val Array(trainData,validationData,testData) = dataDF.randomSplit(Array(0.7,0.2,0.1))
val Array(trainData,validationData,testData) = dataDF.randomSplit(Array(0.7,0.2,0.1))
val tokenizer = new Tokenizer().setInputCol("context").setOutputCol("words")
val hashingTF = new HashingTF().setInputCol("words").setOutputCol("rawFeatures")
val idf = new IDF().setInputCol("rawFeatures").setOutputCol("features")
val labelIndexer = new StringIndexer().setInputCol("label").setOutputCol("indexedLabel").fit(dataDF)
// 构建one-vs-all的基分类器
val classifier = new LogisticRegression().setMaxIter(100).setTol(1E-6).setFitIntercept(true)
// 初始化one-va-all模型
val ovr = new OneVsRest().setClassifier(classifier).setFeaturesCol("features").setLabelCol("indexedLabel")
val labelConverter = new IndexToString().setInputCol("prediction").setOutputCol("predictionLabel").setLabels(labelIndexer.labels)
val pipeline = new Pipeline().setStages(Array(tokenizer,hashingTF,idf,labelIndexer,ovr,labelConverter))
val model = pipeline.fit(trainData)
val predictionResultDF = model.transform(validationData)
val evaluator = new MulticlassClassificationEvaluator().setLabelCol("indexedLabel").setPredictionCol("prediction").setMetricName("accuracy")
val predictionAccuracy = evaluator.evaluate(predictionResultDF)
println("Test set accuracy = " + predictionAccuracy)
// save
model.write.overwrite().save("/tmp/saveModelonevsall")
//测试
val sameModel = PipelineModel.load("/tmp/saveModelonevsall")
val out = sameModel.transform(testData)
evaluator.evaluate(out)
迭代了100次,所以或有这么多,所以前面就要先缓存下数据,这样就会比较快了。
看样子我们保存的模型还是有效果的。
我们再来看看使用NB的效果如何,我们知道nb可以做二分类也可以做多分类,且在词袋模型中效果还不错,对数据量的要求也不是特别的大。我们来试试。
我们只需要更改pipeline就可以了,并不需要重写代码,只需要定义模型的实现,然后再管道中实现,我们只需要在原有的spark-shell下添加一下代码就可以了。当然也是可以移植到idea编译器中的哈
import org.apache.spark.ml.classification.NaiveBayes
val nb = new NaiveBayes().setFeaturesCol("features").setLabelCol("indexedLabel").setSmoothing(0.1)
val nbPipeline = new Pipeline().setStages(Array(tokenizer,hashingTF,idf,labelIndexer,nb,labelConverter))
val nbModel = nbPipeline.fit(trainData)
val predictionNB = nbModel.transform(validationData)
evaluator.evaluate(predictionNB)
从结果上来看比one-vs-all效果是要好那么一点
然而更重要的是 NB在整个模型的构建中只花费了5s,然而one-vs-all至少运行了几分钟
我们也可以试着去修改平滑度参数,查看准确率的变化,这个参数主要是防止过拟合的。
将学习率改成0.5
val nb = new NaiveBayes().setFeaturesCol("features").setLabelCol("indexedLabel").setSmoothing(0.5)
val nbPipeline = new Pipeline().setStages(Array(tokenizer,hashingTF,idf,labelIndexer,nb,labelConverter))
val nbModel = nbPipeline.fit(trainData)
val predictionNB = nbModel.transform(validationData)
evaluator.evaluate(predictionNB)
效果还是有点的。其他的就慢慢调试吧,这里就不演示了。当然这个平滑度也不是越大越好,越大容易欠拟合,越小容易过拟合。
好了,今天的分享就到这里结束啦。
更多文章请关注公众号: