【Spark+NLP】3、手机短信分类实例详细研究(2)变态详细版

本文是Spark+NLP系列的第二篇,主要介绍如何使用Spark进行手机短信分类。文章详细展示了从数据读取、预处理,到使用Word2Vec转化文本为词向量,再到搭建多层感知器模型进行分类的全过程。通过代码实例,解释了Spark中的数据处理、日志控制、DataFrame操作以及机器学习流程。

接上篇,本文为主体代码。

object SpamMessageClassifier {

object:声明一个单例对象

  def main(args: Array[String]) {

1、这儿,args是一个Array[String]类型的方法参数。也就是说,args是一个String数组。在Scala中,Array是一个具有类型参数指明其元素类型的类(一个真正的类,而不是JAVA中那样)。与之对等的JAVA语法应该类似与下面这样子(假设Java中也有一个Array类):public static void main(Array< String> args) 


    //屏蔽不必要的日志显示在终端上
    Logger.getLogger("org.apache.spark").setLevel(Level.ERROR)
    Logger.getLogger("org.eclipse.jetty.server").setLevel(Level.OFF)

1、我们通常会使用IDE(例如Intellij IDEA)开发Spark应用,而程序调试运行时会在控制台中打印出所有的日志信息。它描述了(伪)集群运行、程序执行的所有行为。在很多情况下,这些信息对于我们来说是无关紧要的,我们更关心的是最终结果,无论是正常输出还是异常停止。

幸运的是,我们可以通过log4j主动控制日志输出的级别。引入log4j.Loggerlog4j.Level,并在对象中设置Logger.getLogger("org").setLevel(Level.ERROR)

以此运行后,控制台只输出ERROR级别信息,并不会错过输出结果和调试报错。

2、log4j定义了8个级别的log(除去OFF和ALL,可以说分为6个级别),优先级从高到低依次为:OFF、FATAL、ERROR、WARN、INFO、DEBUG、TRACE、 ALL。

  • ALL 最低等级的,用于打开所有日志记录。
  • TRACE designates finer-grained informational events than the DEBUG.Since:1.2.12,很低的日志级别,一般不会使用。
  • DEBUG 指出细粒度信息事件对调试应用程序是非常有帮助的,主要用于开发过程中打印一些运行信息。
  • INFO 消息在粗粒度级别上突出强调应用程序的运行过程。打印一些你感兴趣的或者重要的信息,这个可以用于生产环境中输出程序运行的一些重要信息,但是不能滥用,避免打印过多的日志。
  • WARN 表明会出现潜在错误的情形,有些信息不是错误信息,但是也要给程序员的一些提示。
  • ERROR 指出虽然发生错误事件,但仍然不影响系统的继续运行。打印错误和异常信息,如果不想输出太多的日志,可以使用这个级别。
  • FATAL 指出每个严重的错误事件将会导致应用程序的退出。这个级别比较高了。重大错误,这种级别你可以直接停止程序了。
  • OFF 最高等级的,用于关闭所有日志记录。

如果将log level设置在某一个级别上,那么比此级别优先级高的log都能打印出来。例如,如果设置优先级为WARN,那么OFF、FATAL、ERROR、WARN 4个级别的log能正常输出,而INFO、DEBUG、TRACE、 ALL级别的log则会被忽略。Log4j建议只使用四个级别,优先级从高到低分别是ERROR、WARN、INFO、DEBUG。

log4j默认的优先级为ERROR或者WARN(实际上是ERROR)。
 

    //对输入参数个数进行判断,如果输入参数不为1则退出

参数哪来的??
    if (args.length != 1) {
      println("Usage: /path/to/spark/bin/spark-submit --master spark://master:9000 " +
        "--driver-memory 1g --class chapter9.SpamMessageClassifier " +
        "sparklearning.jar SMSDataFilePath")
      sys.exit(1)
    }
//main到此结束

 

 

    //设置应用程序运行环境
    val conf = new SparkConf().setAppName("SpamMessageClassifier")

    val sc = new SparkContext(conf)

设置SparkConf对象,任何spark程序都是SparkContext开始的,

SparkContext的初始化需要一个SparkConf对象,SparkConf包含了Spark集群配置的各种参数。

setAppName:网页端查看时会显示这个名字


    val sqlCtx = new SQLContext(sc)

根据sparkContext创建一个sqlContext

这些貌似都是旧的,现在都用SparkSession和dataFrame了!
    
    //从HDFS读取手机短信作为处理数据源,在此基础上创建DataFrame,该DataFrame包含labelCol、contextCol两个列
    val messageRDD = sc.textFile(args(0)).map(_.split("\t")).map(line => {
      (line(0),line(1).split(" "))
    })

1、sc.textFile()  (加载操作)

path: String 是一个URI,這个URI可以是HDFS、本地文件(全部的节点都可以),或者其他Hadoop支持的文件系统URI返回的是一个字符串类型的RDD,也就是是RDD的内部形式是Iterator[(String)]

知识来源+样例:https://blog.csdn.net/legotime/article/details/51871724

【笔记】spark的sparkContext通过textFile()读取数据生成内存中的RDD

【注意】系统会自动把读取的文本文件中的每一行都生成一个RDD元素

 

2、.map(_.split("\t"))每个RDD元素用\t进行拆分.

\t 的意思是 横向跳到下一制表符位置

3、.map(line => {
      (line(0),line(1).split(" "))

map将函数作用到数据集的每一个元素上,生成一个新的分布式的数据集(RDD)返回

4、该数据集结构非常简单,只有两列,第一列是短信的标签 ,第二列是短信内容,两列之间用制表符 (tab) 分隔。

故:对每个RDD用\t进行分割,前半部分为line(0),值为ham或spm;后半部分为line(1),对line(1)再用空格进行分割,将一段文字变为很多个单独的字的集合。

5、重点参考+代码:https://blog.csdn.net/qq_28743951/article/details/53872829
   

val smsDF = sqlCtx.createDataFrame(messageRDD).toDF("labelCol", "contextCol")

1、创建DataFrame的两种方式+代码:https://www.cnblogs.com/libin2015/p/6782667.html强烈推荐!

2、即:用rdd创建dataframe,该dataframe包含labelCol、contextCol两个列

 

    //将原始的文本标签 (“Ham”或者“Spam”) 转化成数值型的表型
    val labelIndexer = new StringIndexer()

1、StringIndexer 将一列字符串标签编码成一列下标标签,下标范围在[0, 标签数量),顺序是标签的出现频率。所以最经常出现的标签获得的下标就是0。该过程可以使得相应的特征索引化。

    如果输入列是数字的,我们会将其转换成字符串,然后将字符串改为下标。当下游管道组成部分,比如说Estimator 或Transformer 使用将字符串转换成下标的标签时,你必须将组成部分的输入列设置为这个将字符串转换成下标后的列名。

2、在机器学习处理过程中,为了方便相关算法的实现,经常需要把标签数据(一般是字符串)转化成整数

索引,或是在计算结束后将整数索引还原为相应的标签。

3、知识点+代码:https://www.cnblogs.com/SoftwareBuilding/p/9492285.html (用到sparkSession)


      .setInputCol("labelCol")
      .setOutputCol("indexedLabelCol")
      .fit(smsDF)

设置输入列、输出列,并对dataframe进行训练

 

    //使用 Word2Vec 将短信文本转化成数值型词向量
    val word2Vec = new Word2Vec()
      .setInputCol("contextCol")
      .setOutputCol("featuresCol")
      .setVectorSize(100)
      .setMinCount(1)

1、Word2vec是一个Estimator,它采用一系列代表文档的词语来训练word2vecmodel。该模型将每个词语映射到一个固定大小的向量。word2vecmodel使用文档中每个词语的平均数来将文档转换为向量,然后这个向量可以作为预测的特征,来计算文档相似度计算等等。

可重点参考+代码:https://blog.csdn.net/qq_34941023/article/details/71189778

2、​ 本文新建一个Word2Vec,设置相应的参数,这里设置特征向量的维度为100。具体的参数描述可以参见http://spark.apache.org/docs/1.6.2/api/scala/index.html#org.apache.spark.ml.feature.Word2Vec。

3、word2vec工具的基本参数:

  • inputCol:源数据dataframe中存储文本词组的列的名称
  • outputCol:经过处理的数值型特征向量存储列的名称
  • vectorSize:目标数值向量的维度大小,默认100
  • minCount:只有当某个词出现的次数大于等于minCount时,才会被包含到词汇表中,否则会被忽略掉

 

    val layers = Array[Int](100,6,5,2)

scala语法。定义int型数组layers,其中的内容为100,6,5,2

    //使用 MultilayerPerceptronClassifier 训练一个多层感知器模型
    val mpc = new MultilayerPerceptronClassifier()
      .setLayers(layers)

layers是整数型数组,包含多个数字

  • 第一个数:需要与特征向量维度相同
  • 最后的数:需要与训练数据的标签取值个数相同,例如2分类问题取值为2
  • 中间的数:数字个数表示神经网络中隐藏层的层数,每个数字表示该层神经元的个数。

例:(100,6,5,2) :特征向量维度100;是spam和ham的2分类问题;有两个隐藏层,第一个隐藏层包含6个神经元,第二个隐藏层包含5个神经元


      .setBlockSize(512)

blockSize:该参数被前馈神经网络训练器用来将训练样本数据的每个分区都按照blockSize大小分成不同的组,并且每个组内的每个样本都会被叠加成一个响亮,以便于在各种优化算法间传递。

该参数推荐值10-1000,默认128


      .setSeed(1234L)
      .setMaxIter(128)
      .setFeaturesCol("featuresCol")
      .setLabelCol("indexedLabelCol")
      .setPredictionCol("predictionCol")

  • maxIter:优化算法求解的最大迭代次数,默认100
  • featuresCol:输入数据dataframe中指标特征列的名称
  • labelCol:输入数据dataframe中标签列的名称
  • predictionCol:预测结果的列名称

知识点参考+代码:https://liuxiaofei.com.cn/blog/multiple-layer-perceptron-classifier%E5%A4%9A%E5%B1%82%E7%A5%9E%E7%BB%8F%E7%BD%91%E7%BB%9C%E5%88%86%E7%B1%BB-ml/

    //使用 LabelConverter 将预测结果的数值标签转化成原始的文本标签
    val labelConverter = new IndexToString()
      .setInputCol("predictionCol")
      .setOutputCol("predictedLabelCol")
      .setLabels(labelIndexer.labels)

IndexToString正好与StringIndexer相反,将StringIndexer的转换的值再转回原来的初始值

参数名称作用
inputCol设置要进行反索引的列名
outputCol设置字符串保存的列名
labelsStringArrayParam类的参数,用于规定得到的字符串序列

    //将原始文本数据按照 8:2 的比例分成训练和测试数据集
    val Array(trainingData, testData) = smsDF.randomSplit(Array(0.8, 0.2))
    
    //使用pipeline对数据进行处理和模型的训练
    val pipeline = new Pipeline().setStages(Array(labelIndexer, word2Vec, mpc, labelConverter))

1、Pipline是Spark SQL中很好用的一个类,可以组合几个不同的模型,可以有效减少代码量,首先看一下它的文档:
Spark 2.3.2 ScalaDoc - IndexToString可见其只有一个参数值,即stages,其值为一个Array类,其中包含了不同的操作模型。

     即包含了流水线中各阶段名称(把各个pipelineStage封装到一个数组中,作为参数传进来)

2、Pipeline可以通过fit和transform对数据集进行操作:

Estimators 
预测器是学习算法或者其他算法的抽象,用来训练数据。预测器继承fit方法,可以接收一个DataFrame输入,然后产出一个模型。(此模型就是一个转化器transformer) 例如,像逻辑回归算法是一种预测,调用fit方法来训练一个逻辑回归模型。

3、概念梳理

• DataFrame:ML API使用Spark SQL中的DataFrme作为机器学习数据集,可容纳各种类型的数据,如DataFrame可能是存储文本的不同列,特征向量,真正的标签或者预测。       
• 转换器:Transformer是一种算法,可以将一个DataFrame转换成另一个DataFrame。如机器学习模型是一个转换器,可以将特征向量的DataFrame转换成预测结果的DataFrame。 
• 预测器:一个预测是一个算法,可以基于DataFrame产出一个转换器。如机器学习算法是一种预测,训练DataFrame并产生一个模型。       
• 管道/工作流:管道链接多个转换器和预测器生成一个机器学习工作流。  

    val model = pipeline.fit(trainingData)
    val preResultDF = model.transform(testData)

利用model的transform方法,将构建好的数据testData中的某列转换得到一个新的结果的特征向量

    //使用模型对测试数据进行分类处理并在屏幕打印20笔数据
    preResultDF.select("contextCol", "labelCol", "predictedLabelCol").show(20)

    //测试数据集上测试模型的预测精确度
    val evaluator = new MulticlassClassificationEvaluator()
      .setLabelCol("indexedLabelCol")
      .setPredictionCol("predictionCol")
    val predictionAccuracy = evaluator.evaluate(preResultDF)
    println("Testing Accuracy is %2.4f".format(predictionAccuracy * 100) + "%")
    sc.stop
  }
}

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值