Spark 3.0 - 5.ML Pipeline 实战之电影影评情感分析

news2025/1/12 0:50:12

目录

 

一.引言

二.Stage1 - 数据准备

1.数据样式

2.读取数据

3.平均得分与 Top 5

4.训练集、测试集划分

三.Stage-2 - Comment 分词

1.Tokenizer 🙅🏻‍♀️

2.JieBa 分词 🙆🏻‍♀️

2.1 Jieba 分词示例

2.2 自定义 Jieba 分词 Transformer

四.Stage-3 HashingTF 向量化

五.Stage-4 通过 LR 构建 Pipeline

六.Stage-5 模型存储与复用

1.模型存储与加载

2.预测评论情感

七.总结


一.引言

上一文中讲到了如何构建 Pipeline - Estimator 训练模型以及通过 Pipeline - Transfomer 预测数据,本文基于用户豆瓣影评与评分构建二分类模型判断评论属于正向或者负向,属于基础的 NLP 二分类问题,构建该模型需要如下 Stage:

Stage1 - 数据清洗并构建 DataFrame

Stage2 - 分词工具对用户影评进行分词

Stage3 - HashingTF 将分词向量化供后续训练

Stage4 - LogisticRegression 实现二分类模型

Stage5 - 存储 Pipeline Model 并读取模型预测

后续将基于这 5 个 Stage 介绍。

- 星际穿越剧照 

二.Stage1 - 数据准备

1.数据样式

首先看下原始数据的样式:

数据共分为3列,分别为 Movie [影名]、Score [评分] 与 Comment [评论],由于本文的目标是用 LR 实现评论情感的二分类,所以我们需要人工指定一个 Label,0 代表负向评价,1 代表正向评价,基于一般认知,我们将 Score > 3 的影评认定为正向,即 Label 为 1,<= 3 则认为负向,Label = 0。

2.读取数据

使用 sc 将数据读取为 RDD 随后调用隐式转换切换为 DF,这里 positive 就是上面提到的,> 3 的 comment Label = 1.0,<= 3 的 Label 为 0.0。

    val commentAndLabel =  spark.sparkContext.textFile(inputPath).mapPartitions(partition => {

      partition.map(line => {
        try {
          val info = line.split("##")
          val movieName = info(0)
          val score = info(1)
          val positive = if (score != "null" && score.toInt > 3) 1.0 else 0.0
          val oriComment = info(2)
          (movieName, score, positive, comment, oriComment)
        } catch {
          case _: Throwable =>
            null
        }
      })

    }).filter(_ != null)

    val data = spark.createDataFrame(commentAndLabel)
      .toDF("movie", "score", "label", "oriComment")

3.平均得分与 Top 5

基于上述数据 DataFrame,我们先用前面的 Spark Sql 语句统计下每个 Movie 的平均分并查看当前的 Top 5 电影。

    // 平均分 Map
    data.createOrReplaceTempView("MovieComment")
    val avgScore = spark.sql("select movie, avg(score) from MovieComment group by movie")
      .collect()
      .map(row => (row.getString(0), row.getDouble(1)))
      .toMap

    println(s"Total Movie Num: ${avgScore.size}")

    avgScore.toArray.sortBy(-_._2).slice(0, 5).zipWithIndex.foreach{ case (movieInfo, index) => {
      println(s"Top${index + 1} - <<${movieInfo._1}>> Avg: ${avgScore.apply(movieInfo._1)}")
    }}

 共有 255 部电影,其中排名最高的是陈导的霸王别姬,平均分达到了 4.74 🌟🌟🌟🌟🌟

4.训练集、测试集划分

对数据有整体了解后,我们首先划分训练集与数据集,前面提到过 randomSplit 函数可以配合比例轻松实现划分,这里采用雪碧的比例。

    // 划分训练集、测试集
    val trainAndTestRatio = Array(0.8, 0.2)
    val pipelineData = data.randomSplit(trainAndTestRatio, 99)
    val trainData = pipelineData(0)
    val testData = pipelineData(1)

    // 相关统计
    println(s"AllSamples: ${commentAndLabel.count()} TrainSample: ${trainData.count} TestSamples: ${testData.count()}")

可以看到 255 部电影共包含 50590 条 comment 数据,其中训练样本 4w+,测试样本 1w+。基本的 Dataframe 已经搞定,下面我们整理 Transformer 与 Estimator 并构建 Pipeline。 

AllSamples: 50590 TrainSample: 40373 TestSamples: 10217

三.Stage-2 - Comment 分词

1.Tokenizer 🙅🏻‍♀️

先拿上篇文章使用的 Tokenizer 试试水:

    val tokenizer = new Tokenizer()
      .setInputCol("comment")
      .setOutputCol("output")
    tokenizer.transform(trainData).select("movie", "comment", "output").show(10)

完了,BBQ 了呀,Tokenizer 只能分割空格隔开的语句,当前场景下无明显空格分隔符,所以都是一整句当做一个词,因此放弃该方案。 

2.JieBa 分词 🙆🏻‍♀️

Java 有很多分词工具,这里选择之前 python 也用到过的 JieBa,还有 ikanalyzer、Ansj 等等,大家也可以多多尝试。

<!-- 开源中文分词器 Jieba -->
<dependency>
    <groupId>com.huaban</groupId>
    <artifactId>jieba-analysis</artifactId>
    <version>1.0.2</version>
</dependency>
<!-- 开源中文分词器 Ansj -->
<dependency>
	<groupId>org.ansj</groupId>
	<artifactId>ansj_seg</artifactId>
	<version>5.1.6</version>
</dependency>
<!-- 开源中文分词器 IK -->
<dependency>
     <groupId>com.janeluo</groupId>
     <artifactId>ikanalyzer</artifactId>
     <version>2012_u6</version>
</dependency>

2.1 Jieba 分词示例

    val jiebaTokenizer = new JiebaTokenizer()
      .setInputCol("comment")
      .setOutputCol("output")
    jiebaTokenizer.transform(trainData).select("movie", "comment", "output").show(10)

这里 JiebaTokenizer 为我们自定义的分词器,内部调用 Jieba 实现分词处理并过滤 StopWords,这下看下来比上面好多了,但是有一些人名或者物品识别也不是太好,这与分词器的内部中文词库大小相关联,这里我们先凑乎用一下。

2.2 自定义 Jieba 分词 Transformer

  val jieba = new JiebaSegmenter()
  jieba.sentenceProcess(text)

初始化分词器并调用 setenceProcess 方法即可实现分词效果,但是官方未提供原生 jiebaTokenizer,所以只能将分词步骤提前到数据准备阶段的 RDD MapPartition 中才能达到分词的效果:

这样虽然简单,但是违背了 Pipeline 的初衷,我们不得不把第一步 Tokenizer 分词的 Stage 从 Pipeline 中提出,为了 Pipeline 的统一性,我们继承 org.apache.spark.ml.UnaryTransformer 自定义实现 JiebaTokenizer Transformer:

import com.huaban.analysis.jieba.JiebaSegmenter
import org.apache.spark.annotation.Since
import org.apache.spark.ml.UnaryTransformer
import org.apache.spark.ml.feature.{HashingTF, Tokenizer}
import org.apache.spark.ml.param.Param
import org.apache.spark.ml.util.{DefaultParamsReadable, DefaultParamsWritable, Identifiable, MLReader, MLWriter}
import org.apache.spark.sql.types._

class JiebaTokenizer(override val uid: String)
  extends UnaryTransformer[String, Array[String], JiebaTokenizer] with DefaultParamsWritable with java.io.Serializable {

  lazy val jieba = new JiebaSegmenter()

  def this() = this(Identifiable.randomUID("JiebaTokenizer"))

  val inputPath = "./stopwords.txt"

  val stopWords = scala.io.Source.fromFile(inputPath)
    .getLines().toSet

  override protected def outputDataType: DataType = new ArrayType(StringType, true)

  override protected def validateInputType(inputType: DataType): Unit = {
    require(inputType == DataTypes.StringType,
      s"Input type must be string type but got $inputType."
    )
  }

  override protected def createTransformFunc: String => Array[String] = {
    parseContent
  }

  /**
    * Jieba 分词
    */
  private def parseContent(text: String): Array[String] = {
    if (text == null || text.isEmpty) {
      return Array.empty[String]
    }
    jieba.sentenceProcess(text).toArray().map(_.toString).filter(str => !stopWords.contains(str))
  }

}

object JiebaTokenizer extends DefaultParamsReadable[JiebaTokenizer] {

  override def load(path: String): JiebaTokenizer = {
    super.load(path)
  }

}

关于如何在 Spark ML 中继承 UnaryTransformer 实现自定义 Transformer 博主会在下一篇文章详细讲解一下每个函数的使用方法与解释,有需要的同学可以关注下~

四.Stage-3 HashingTF 向量化

HashingTF 负责将原始分词文本进行词频统计并 Hash 得到数组索引,这里读取前面 Jieba 分词生成的 output Col 并将新的结果输出至 vector Col,需要注意 numFeatures 的设置,实际场景下应该基于自己分词后去重的分词词库大小来决定该参数,这里由于我们评论五花八门,所以不考虑去重词库大小,直接设置为 20w。

    val hashingTF = new HashingTF()
      .setInputCol("output")
      .setOutputCol("vector")
      .setNumFeatures(200000)

    hashingTF.transform(jiebaTokenizer.transform(trainData)).select("movie", "comment", "vector").show(10)

通过两步 Transformer,向量化数据终于搞定,下面搭配 Estimator - LR 即可构成完整 Pipeline。 

五.Stage-4 通过 LR 构建 Pipeline

LR 前两篇文章都有过讲解,这里不再赘述,直接生成 Pipeline:

    val lr = new LogisticRegression()

    val pipeline = new Pipeline()
      .setStages(Array(jiebaTokenizer, hashingTF, lr))

    val paramMap = ParamMap(lr.maxIter -> 20, lr.regParam -> 0.01)
      .put(jiebaTokenizer.inputCol -> "comment", jiebaTokenizer.outputCol -> "words")
      .put(hashingTF.numFeatures -> 200000, hashingTF.inputCol -> "words", hashingTF.outputCol -> "features")

    // 调用fit()函数,训练数据
    val model = pipeline.fit(trainData, paramMap)

paramMap 中分别为 JiebaTokenizer、HashingTF 与 LR 配置相关参数,原始 comment 将先转化为 words 列,随后转化为 features 列,配合最先生成的情感 Label 供 LR 训练模型。

六.Stage-5 模型存储与复用

经过一系列操作,我们的 Pipeline Model 终于构建完毕,下面将训练好的模型存储,并在需要使用的时候 load 加载完成预测。

1.模型存储与加载

    println(s"Start Save Model: ${System.currentTimeMillis()}")
    val output = "./output"
    model.write.overwrite().save(output)
    val newModel = PipelineModel.load(output)
    println(s"End Save Model: ${System.currentTimeMillis()}")

采用 model.write 实现模型存储,如果需要覆盖之前的模型可以增加 overwrite 选项,读取模型则是通过 org.apache.spark.ml.PipelineModel 类实现。

2.预测评论情感

上面留了 20% 的数据作为测试集,下面测试下我们的情感模型效果如何:

    // 在测试集上进行预测
    newModel.transform(testData.sample(0.1, 99))
      .select("movie", "comment", "score", "probability", "prediction")
      .collect()
      .foreach { case Row(movie: String, comment: String, score: String, prob: Vector, prediction: Double) =>
        println(s"($movie, $comment) --> real=$score avg=${avgScore(movie)} prob=$prob, prediction=$prediction")
      }

分别打印电影名与原始评论作为文本信息,打印真实分数与平均分数作为当前电影的真实评价,最终打印预测概率,0 为负向评论代表用户不喜欢该电影,1 为正向评论代表用户喜欢该电影,理想情况下,预测概率应该与用户的原始评价分有关 🌟🌟🌟🌟🌟,因为给的星越高,代表评价越高,评论里的描述也越正向,反之同理。

 这里简单挑几个样本看看模型效果如何:

A.为什么又是“一个崽的神奇冒险”,那么点儿耍帅镜头想糊弄谁呢?

real=3,avg=2.17,加上略带消极的评论,预测为 0 基本正确

B.还不错,真实事件

real=3,avg=1.56,单看分数应该是烂片无疑了,但是由于评论正向,所以预测为 1,虽然与label不符合,但是感觉也可以算正确

C.无聊透顶,近乎儿戏,仍旧是个人目前为止最讨厌的一部漫威

负向评论无疑,real=2,预测为 0,预测正确

D.难得看到漫威拍严肃题材,一个时刻生活在谎言中的封闭国家,一个遭受外来文化冲击的封建体制,在超英片中已经是很有追求了

中肯中带有一些正向,real 也达到了 4 分,但是正向的概率却只有 0.04,这里失真比较严重

七.总结

上面通过 Spark ML Pipeline 构建了简单的 NLP 情感分类模型,可以看到评论场景下,一些评论与实际打分存在差异,可能用户不喜欢却依旧打了高分,或者存在粉丝刷榜随意打高分的情况,这些在真实场景下都会对模型带来噪声,影响模型的学习。除此之外,这里除了通过简单的二分类实现情感分析,还可以使用多分类模型预测电影评星 [1-5] 或者使用线性、多项式回归预测电影得分,这涉及到多分类和回归的知识,后面有机会我们也会介绍。

It takes a strong man to save himself, and a great man to save another.

最后留下自己最喜欢的🎬 <<肖申克的救赎>> ,大家有最喜欢的电影也可以留在评论区,一起分享 (*^▽^*)

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/24876.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

系统设计 system design 干货笔记

参考大佬的博客 https://www.lecloud.net/post/9246290032/scalability-for-dummies-part-3-cache 参考的github https://github.com/donnemartin/system-design-primer#step-2-review-the-scalability-article scalability 1 Clone 每台服务器都包含完全相同的代码库&#…

SOLIDWORKS 2023 3D Creator 云端结构设计新功能

3DEXPERIENCE平台更新版本已经与大家见面&#xff0c;今天微辰三维与大家分享3D Creator 云端结构设计新功能&#xff0c;让我们先一起来看看视频—— SOLIDWORKS 2023 3D 云端结构设计新功能点击观看3D Creator 云端结构设计新功能 如今&#xff0c;我们的设计生产工作不仅要面…

Linux进阶-Makefile

make工具&#xff1a;找出修改过的文件&#xff0c;根据依赖关系&#xff0c;找出受影响的相关文件&#xff0c;最后按照规则单独编译这些文件。 Makefile文件&#xff1a;记录依赖关系和编译规则。 Makefile本质&#xff1a;无论多么复杂的语法&#xff0c;都是为了更好地解决…

m认知无线电网络中频谱感知的按需路由算法matlab仿真

目录 1.算法概述 2.仿真效果预览 3.MATLAB部分代码预览 4.完整MATLAB程序 1.算法概述 使用无线电用户的频率范围在 9kHz 到 275GHz[3]&#xff0c;由于无线通信环境中的干扰、信道衰落和无线电收发设备自身属性等的影响&#xff0c;大部分无线电设备只能工作在 50GHz 以下。…

融媒体解决方案-最新全套文件

融媒体解决方案-最新全套文件一、建设背景二、建设思路三、建设方案二、获取 - 融媒体全套最新解决方案合集一、建设背景 随着互联网的快速发展&#xff0c;社会已步入全媒体时代&#xff0c;各媒体机构积极探索传统媒体转型之路。 为巩固壮大主流思想舆论&#xff0c;不断提…

对数的应用:放缩x轴或者y轴以更好地表达函数的结果

对数尺度的作用 yAxnyAx^nyAxn 在实验中 AAA 和 nnn 都是未知数&#xff0c;现在我想求出 AAA 和 nnn假设 n1.5,A1n1.5, A1n1.5,A1&#xff0c;那么我们可以做个图看看 x np.linspace(1,10,10) y 1 * x**3 plt.plot(y)如果我做实验恰好得到一些点&#xff0c;那么我很难知道…

【全志T113-S3_100ask】14-1 linux采集usb摄像头实现拍照(FFmpeg、fswebcam)

【全志T113-S3_100ask】14-1 linux采集usb摄像头实现拍照背景&#xff08;一&#xff09;FFmpeg1、简介&#xff1a;2、交叉编译FFmpeg3、测试&#xff08;二&#xff09;fswebcam1、背景2、交叉编译fswebcam3、测试背景 在开发板上有一个csi转dvp接口的摄像头&#xff0c;但是…

前端入门到放弃(VUE、ES6,简单到不得了)

VSCode 使用 1、安装常用插件 切换到插件标签页 安装一下基本插件 2、创建项目 vscode 很轻量级&#xff0c;本身没有新建项目的选项&#xff0c;创建一个空文件夹就可以当做一个项目 3、创建网页 创建文件&#xff0c;命名为 index.html 快捷键 !快速创建网页模板 h1 回…

精益管理学会|什么是ECRS改善方法?

ECRS是IE工程改善、精益生產管理改善的四大法宝。 针对现有的生产线进行改善时&#xff0c;常见的做法是对现有的生产线进行绘制各工站的工时山积表如下圖所見&#xff0c;然后对各工站的动作单元进行ECRS 改善。 E&#xff1a;不需要的可进行 Eliminate &#xff08;取消&…

Telegraf-Influxdb-Grafana容器化部署拓展(Https、AD域、告警集成)并监控Cisco设备指标

前言&#xff1a; 还记得在去年的笔记中提到过使用python的pysnmp模块&#xff0c;配合Influxdb&#xff0c;Grafana收集Cisco设备指标。链接如下&#xff1a;https://blog.csdn.net/tushanpeipei/article/details/117329794 。在该实例中&#xff0c;我们通过python编写脚本收…

第一节 Maven核心程序解压与配置

1、Maven 官网地址 首页&#xff1a; Maven – Maven Repositories (apache.org)https://maven.apache.org/repositories/index.html下载页面&#xff1a; Maven – Download Apache Mavenhttps://maven.apache.org/download.cgi下载链接&#xff1a; 具体下载地址&#xff…

【微信小程序】列表渲染wx:for

&#x1f3c6;今日学习目标&#xff1a;第十二期——列表渲染wx:for &#x1f603;创作者&#xff1a;颜颜yan_ ✨个人主页&#xff1a;颜颜yan_的个人主页 ⏰预计时间&#xff1a;20分钟 &#x1f389;专栏系列&#xff1a;我的第一个微信小程序 文章目录前言效果图< block…

同花顺_代码解析_交易系统_J01_08

本文通过对同花顺中现成代码进行解析&#xff0c;用以了解同花顺相关策略设计的思想 目录 J_01 MACD系统 J_02 布林带系统 J_03 趋向指标 J_04 乖离系统 J_05 KDJ系统 J_07 容量比率系统 J_08 威廉系统 J_01 MACD系统 分析MACD柱状线&#xff0c;由绿变红(负变正)&…

Bootstrap实例(四)

目录&#xff1a; &#xff08;1&#xff09;bootstrtap实例&#xff08;轮廓&#xff09; &#xff08;2&#xff09;bootstrap三列布局 &#xff08;3&#xff09;bootstrap&#xff08;标签页&#xff09; &#xff08;4&#xff09;bootstrap&#xff08;end&#xff0…

56.Django的admin后台管理使用方法

准备 Django框架提供了一个自动化后台管理功能&#xff0c;对网站数据的后台维护&#xff0c;仅仅需要进行非常简单的配置和编写极少的代码即可实现。 首先创建一个Django项目admin_study&#xff0c;然后创建连接数据库&#xff0c;在数据库中创建好表数据库后&#xff0c;进…

[附源码]java毕业设计小区物业管理系统

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…

头歌-信息安全技术-用Python实现自己的区块链、支持以太坊的云笔记服务器端开发、编写并测试用于保存云笔记的智能合约、支持以太坊的云笔记小程序开发基础

头歌-信息安全技术-用Python实现自己的区块链、支持以太坊的云笔记服务器端开发、编写并测试用于保存云笔记的智能合约、支持以太坊的云笔记小程序开发基础一、用Python实现自己的区块链1、任务描述2、评测步骤(1)打开终端&#xff0c;输入两行代码即可评测通过二、支持以太坊的…

MySQL的高阶学习:索引、B+树

1.索引 索引是一种数据结构&#xff0c;如果没有索引&#xff0c;查找一个数据就需要从第一页开始全局检索直至找到需要的数据&#xff0c;有了索引可以先在目录中根据拼音查找到该数据所在的页数&#xff0c;因此通过索引可以大大减少了查询时间。 索引有两种存储类型&#xf…

金融科技赋能 互融云手机回租系统 实现资产全流程在线运营管理

在共享单车、充电宝等共享商业的兴起与成熟之后&#xff0c;“信用租赁”的模式悄然诞生&#xff0c;租房、租衣、租数码等已成常态。信用租赁系统的出现&#xff0c;带活了一大批租赁经济&#xff0c;尤其是手机行业。 伴随手机零售业的增长以及新品发布速度的提高&#xff0…

CY8C5888AXQ-LP096 CY8C5888AXI-LP096,IC MCU 32BIT

PSoC 5LP是一种真正的可编程嵌入式片上系统&#xff0c;集成了可配置的模拟和数字外设&#xff0c;内存和单芯片上的微控制器。PSoC 5LP架构通过以下方式提高性能&#xff1a; 32位Arm Cortex-M3核心加上DMA控制器和数字滤波处理器&#xff0c;最高可达80mhz 超低功率&#xff…