今天我们要学习的是Spark中的分类算法中的贝叶斯模型以及SVC模型,这里还是通过一个小实例来实现。就是垃圾邮件识别。
首先我们还是先来简单的介绍一下算法吧。
朴素贝叶斯模型
首先我们先假设各个向量之间是相互独立的。朴素贝叶斯(简称NB)就是在这个假设条件下的。朴素贝叶斯属于生成式模型,它的收敛速度将快于判别式模型比如逻辑回归,所以你只需要较少的训练数据即可。即使贝叶斯条件独立假设不成立,NB分类器在实践中任然表现很出色。但是它的主要缺点是它不能学习特征之间的相互作用,举个简单的例子就是,比如你喜欢动作和爱情两种一起类型的电影,但是他不能学习出你不喜欢他们单独类型电影。只有动作或者只有爱情,这里是相互独立的条件概率。
其实呢,我对贝叶斯的一个简单理解,他就是一个条件概率,已知什么的条件下求这个结果的一个概率,概率大的就是这个结果。这就是我的土理解。
然而对于贝叶斯我们记住下面这个公式就行了,其实也不用记住啦,知道原理就行啦。
首先我们先了解条件概率:
P(A|B) = P(AB) / P(B)
相信大家对这个公式肯定不陌生吧,高中知识哈。
P(A|B):一个条件概率,已知事件B得情况下求A得概率
P(AB):事件AB的联合概率
P(B) : 事件B发生的概率
然后就就也有
P(B|A) = P(AB) / P(A)
P(AB) = P(B|A) * P(A) = P(A|B) * P(B)
再然后就有了:
P(A|B) = P(B|A) * P(A) / P(B)
然后再一拓展:就变成了
P(A|B1B2....Bn) = P(B1B2...Bn|A) * P(A) / P(B1B2...Bn)
这就是贝叶斯的推导过程。
再来个暴击式的理解贝叶斯:
建模:
P(X,Y) = P(X|Y)P(Y)
判别:
P(Y|X)=P(X,Y)/P(X)
接下来介绍它的优缺点:
优点:
1.朴素贝叶斯模型发源于古典数学理论,有着坚实的数学基础,以及稳定的分类效率;
2.对小规模数据表现很好,能够处理多分类任务
3.对缺失数据不太敏感,算法也比较简单,常用于文本分类
4.对大量数据训练时和查询时具有较高的速度。即使使用大规模的训练集,针对每个项目通常也只会对相对较少的特征数,并且对项目的训练也仅仅是特征概率的数学运算而已。
5.支持增量式运算,既可以实时的对新增的样本进行训练;
6.朴素贝叶斯对结果容易解释。条件概率
缺点:
1.需要计算先验概率;
2.分类决策存在错误率
3.对输入数据的表达形式敏感
4.由于使用了样本属性独立性假设,所以样本属性有关联时效果不好
应用领域:
文本分类、欺诈检测中使用较多
SVM模型
SVM呢分为两种一种是SVC是一个分类模型,另一种就是SVR是一个回归模型。这里的svc呢只能使用二分类中,当然可不可以实现多分类呢,也是可以的,这里就是实现了多个svc模型,其实他还是一个二分类,只是多个二分类而已。在Spark中都实现了的,二分类的是linerSVC模型,多分类就是one-vs-all模型。这里呢先介绍svc,明天介绍one-vs-all模型。
这里我使用在李航老师的概率统计上面学习道德知识来介绍下SVM。
支持向量机SVM 是一种二类分类模型,他的基础模型是定义在特征空间上的间隔最大的线性分类器,间隔最大使他有别于感知机;之前我们也学习过感知机,感知机的结果是有多个的,这里呢我们是找的间隔最大的哪一个平面,是寻求的唯一解。支持向量机还包括核技巧,这使它成为实质上的非线性分类器。核方法--核函数,就是将非线性转换成线性的一种手段,将数据从低位空间映射到高维空间。SVM的学习策略就是间隔最大化,可形式化为一个求解凸二次规划的问题。也等价于正则化的合页损失函数的最小化问题。SVM的学习算法是求解凸二次规划的最优化算法。利用间隔最大化。
线性可分支持向量机:
一般的,当训练数据线性可分的时候,存在无数个分离超平面可将两类数据正确分开,感知机利用误分类最小的策略,求得分离超平面,不过这时的解有无穷个,线性可分支持向量机利用间隔最大化求最优分离平面,这时,解是唯一的。
和感知机一样使用一个超平面 W*X + b = 0
以及相应的分类决策函数 f(x) = sign(w*x + b)
称为线性可分支持向量机。
SVM采用的是间隔最大策略,与感知机不同的是感知机使用的是误差最小化。
间隔最大化
支持向量机学习的基本想法就是求解能够正确划分训练数据集并且几何间隔最大的分离超平面。对线性可分数据集而言,线性可分分离超平面有无穷个(等价于感知机),但是几何间隔最大的分离超平面是唯一的,这里的间隔最大化又称与硬间隔最大化。
来自李航老师的统计学习,推荐学习
W相当于法向量。
优点:
1.可以解决高维问题,特大的特征空间
2.能够处理得线性特征的相互作用
3.解决小样本下的机器学习问题
4.无需依赖整个数据
5.泛化能力强
6.无局部最小值问题(相对于神经网络这些算法)
缺点:
1.当样本多时,效率不是很高(大样本反而不合适)
2.对非线性问题没有通用的解决方案,有时候很难找到一个合适的核函数
3.对缺失数据敏感
对于核函数的选择也是有技巧的,(libsvm中自带了几种核函数:线性核,多项式核,RBF以及sigmoid)
第一:如果样本数量小于特征数,没必要选择非线性核,使用线性核就可以
第二:如果样本数量大于特征数,该情况可以使用非线性核,将样本映射到更高维度,一般可以得到更好的结果。
第三:如果样本数目和特征数目相等,使用非线性核
对于第一种情况,可以先降维,在进行使用非线性核
svm的应用领域:
文本分类,图片识别 主要用于二分类领域
接下来我们在Spark中使用实例来进行介绍:
实战:垃圾邮件识别
首先我们先启动Spark-shell
./spark-shell --num-executors 2 --executor-memory 2G
这里呢,我设置了一下这个executor-memory为2G。
然后我们来看看这个数据集长什么样哈。
这里呢,我有三个文件夹,train,test,classify。分别是训练集和测试集以及最后的离线测试。然后train里面就分为ham和spam两个文件夹,分别对应正常邮件和垃圾邮件,这文件夹下嘛,就是一些txt文本了,里面的内容自然就是邮件内容。
大概就是这么个数据吧。
这里我们使用道德特征转换是TF-IDF转换。
同样我们先导入我们需要的包,这里就和Java类似了哈
import org.apache.spark.ml.feature.{HashingTF, IDF, Tokenizer}
import org.apache.spark.ml.classification.NaiveBayes
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
然后导入Spark中自带的额隐式转换,不然的话会报错的。
import spark.implicits._
载入数据:
val dataDF = spark.sparkContext
.wholeTextFiles("file:///opt/data/youjian/youjian/train/*")
.map{case(p,c) =>(p.split("/").takeRight(2).head,c)}
.toDF("label","context")
val dtestDF = spark.sparkContext
.wholeTextFiles("file:///opt/data/youjian/youjian/test/*")
.map{case(p,c) =>(p.split("/")
.takeRight(2).head,c)}
.toDF("label","context")
特征转换,数据清洗吧,以及模型的建立。这里使用pipeline的方式
// 数据拆分,分词。中文就不行了
val tokenizer = new Tokenizer().setInputCol("context").setOutputCol("words")
// tf
val hashingTF = new HashingTF().setInputCol("words").setOutputCol("rawFeatures")
// idf
val idf = new IDF().setInputCol("rawFeatures").setOutputCol("features")
// Index转换,将String转换成Int
val labelIndexer = new StringIndexer().setInputCol("label").setOutputCol("indexedLabel").fit(dataDF)
//建立NB模型
val nb = new NaiveBayes().setFeaturesCol("features").setLabelCol("indexedLabel").setSmoothing(0.1)
//. int转Stringlabel
val labelConverter = new IndexToString().setInputCol("prediction").setOutputCol("predictionLabel").setLabels(labelIndexer.labels)
使用管道去构建训练模型
val pipeline = new Pipeline().setStages(Array(tokenizer,hashingTF,idf,labelIndexer,nb,labelConverter))
val model = pipeline.fit(dataDF)
可以看到整个过程只花费了6s,这个时间还可以优化的哈。
测试模型
val predictionResultDF = model.transform(dtestDF)
val evaluator = new MulticlassClassificationEvaluator().setLabelCol("indexedLabel").setPredictionCol("prediction").setMetricName("accuracy")
val predictionAccuracy = evaluator.evaluate(predictionResultDF)
println("Test set accuracy = " + predictionAccuracy)
模型的保存
model.write.overwrite().save("/tmp/saveModel")
加载我们保存的模型,然后训练数据
val unlineDF = spark.sparkContext.wholeTextFiles("file:///opt/data/youjian/youjian/classfiy/*").map{case(p,c) =>(p.split("/").takeRight(1).head,c)}.toDF("label","context")
val sameModel = PipelineModel.load("/tmp/saveModel")
val out = sameModel.transform(unlineDF)
结果还是可以的。这里就没有截取所有的了。
再然后我们就通过SVM来试一下呢。通过pipeline的方式我们的改动还是很小的,只需要重新构建一个模型就可以了。
import org.apache.spark.ml.classification.LinearSVC
val lsvc = new LinearSVC().setMaxIter(100).setRegParam(0.1).setFeaturesCol("features").setLabelCol("indexedLabel")
val pipelineSVC = new Pipeline().setStages(Array(tokenizer,hashingTF,idf,labelIndexer,lsvc,labelConverter))
val modelSvc = pipelineSVC.fit(dataDF)
val predictionSvc = modelSvc.transform(dtestDF)
val evaluatorSvc = new MulticlassClassificationEvaluator().setLabelCol("indexedLabel").setPredictionCol("prediction").setMetricName("accuracy")
val predictionSvcAccuracy = evaluatorSvc.evaluate(predictionSvc)
println("Test set accuracy = " + predictionSvcAccuracy)
就是这么方便。
看起来效果还不错,但是运行时间,
svm使用了差不多5分钟的时间迭代100次。然而贝叶斯只用了6s。这就是差距啊。
然后呢今天的分享就结束了,谢谢大家!
更多文章请关注公众号: