基于LR的新闻多分类(基于spark2.1.0, 附完整代码)

来源:互联网 发布:北京淘宝摄影工作室 编辑:程序博客网 时间:2024/05/02 00:20

原创文章!转载请保留原始文章链接,谢谢!


环境:

  1. Scala2.11.8 + Java1.8.0_112
  2. Spark2.1.0 + HanLP1.3.2

 

完整项目代码见我的GitHub:https://github.com/yhao2014/ckoocML

(因为HanLP分词模型太大,未上传至项目中,需要的请从HanLP发布页下载,然后解压后将data目录整个放到ckoocML\dictionaries\hanlp\目录下即可)

注:GitHub上此部分代码已更改,进行了模块划分,主要分成了预处理类Preprocessor.scala和逻辑回归类LRClassifier.scala,以及基于LR分类的训练及测试LRClassTrainDemo.scala、LRClassPredictDemo.scala。但不影响本博文对LR多分类的实现和解读


主体流程

 

自从引进DataFrame之后,sparkml方面,开始使用DataFrame作为RDD的上层封装,以屏蔽RDD层次的复杂操作,对应用开发者提供简单的DataFrame,以减少开发量。本文以最新的spark2.1.0版本为基础,构建从数据预处理、特征转换、模型训练、数据测试到模型评估的一整套处理流程。另外,经过综合考虑,本文分词方法选用HanLP分词工具(文档丰富、算法公开、代码开源,并且经测试分词效果比较好),数据使用的是从新闻网站爬取的新闻分类数据,数据格式如下:

计算机生成了可选文字:此 电 脑 数 ( G : ) 》 名 称 culture.txt financial.txt military.txt sports.txt test ClaSSneWS 修 改 日 期 2017 / 2 / 1 9 23 : 18 2017 / 2 / 1 9 23 : 14 2017 / 2 / 1 9 23 : 15 2017 / 2 / 1 9 23 : 18 文 本 文 档 文 本 文 档 文 本 文 档 文 本 文 档 115 KB 3,330 KB 引 545 KB 2 , 741 KB

 

计算机生成了可选文字:, 鬍 1 2 3 4 5 6 7 8 9 1B 11 12 15 16 17 18 19 2B 21 2 2 2 3 24 2 5 26 27 28 29 3B 31 3 2 3 3 34 首 页 | 文 化 新 闻 i 第 十 一 届 全 国 优 秀 舞 蹈 节 目 展 演 将 在 武 汉 举 办 i2e16 一 e7 一 es 19 : 25 : 羽 i 新 华 社 北 京 7 月 5 日 电 ( 记 者 周 玮 ) 由 文 化 部 、 湖 北 省 人 民 政 府 主 办 的 第 十 一 首 页 | 文 化 新 闻 i 金 陵 刻 经 处 成 立 15e 周 年 高 僧 大 德 专 家 学 者 纪 念 杨 仁 山 居 士 i2e16 一 e7 一 19 : 12 : 羽 i 南 京 7 月 5 日 电 ( 记 者 申 冉 ) 金 陵 刻 经 处 成 立 15e 周 年 纪 念 活 动 首 页 | 文 化 新 闻 i 河 北 农 民 痴 迷 画 画 被 称 “ 不 务 正 业 " 作 品 远 销 多 个 画 廊 i2e16 一 e7 一 es 17 : 16 : i 邢 台 7 月 5 日 电 ( 张 鹏 翔 李 铁 锤 杜 明 ) “ 我 是 一 个 土 生 土 长 的 农 民 , 首 页 | 文 化 新 闻 i 传 唱 百 年 的 “ 拉 魂 腔 " 在 苏 北 悄 然 复 兴 “ 人 才 荒 ” 制 约 发 展 i2e16 一 e7 一 es 16 巧 4 : 羽 i 南 京 7 月 5 日 电 ( 记 者 刘 林 ) “ 听 的 久 了 , 你 会 觉 得 魂 都 被 ' 摄 丿 走 首 页 | 文 化 新 闻 i 中 国 一 希 腊 文 物 激 光 技 术 联 合 实 验 室 在 故 宫 成 立 i2e16 一 e7 一 es 16 : 32 : 羽 i 北 京 7 月 5 日 电 ( 记 者 应 妮 ) 故 宫 博 物 院 与 希 腊 电 子 结 构 与 激 光 研 究 所 联 合 首 页 | 文 化 新 闻 i 北 京 职 高 生 被 美 国 三 所 名 校 录 取 赴 美 做 志 愿 者 是 人 生 转 折 i2e16 一 e7 一 es 16 : 21 : 羽 i 今 天 , 又 一 届 中 考 生 正 面 临 着 报 志 愿 的 艰 难 选 择 。 而 三 年 前 的 首 页 | 文 化 新 闻 i 北 大 校 长 告 诫 毕 业 生 : 网 络 时 代 不 要 人 云 亦 云 i2e16 一 e7 一 es 15 : 37 : 羽 i 本 报 讯 ( 记 者 张 航 ) 惜 别 燕 园 , 情 系 千 千 结 。 今 天 上 午 , 3185 名 北 大 本 科 生 首 页 | 文 化 新 闻 i 北 大 再 现 “ 一 个 人 的 毕 业 照 " 主 角 将 直 博 读 地 质 学 i2e16 一 e7 一 es 16m4 : 羽 i 将 直 博 攻 读 第 四 纪 地 质 学 专 业 研 究 生 继 两 年 前 的 薛 逸 凡 后 , 北 大 今 年 再 首 页 | 文 化 新 闻 i 蓝 天 野 曾 多 次 捐 款 支 持 公 益 称 还 想 演 戏 、 导 戏 i2e16 一 e7 一 es 14 : 22 : 羽 i 看 蓝 天 野 的 日 程 表 , 你 很 难 想 象 这 是 一 位 89 岁 老 人 的 日 常 生 活 : 北 京 、 台 首 页 | 文 化 新 闻 i 中 国 人 民 抗 日 战 争 纪 念 馆 暂 闭 馆 将 举 办 纪 念 活 动 i2e16 一 e7 一 es 14 : 17 : 羽 i 中 国 人 民 抗 日 战 争 纪 念 馆 闭 馆 公 告 今 年 是 全 民 族 抗 战 爆 发 79 周 年 。 7 月 7 首 页 | 文 化 新 闻 i 平 北 抗 战 纪 念 馆 可 用 手 机 听 讲 解 并 能 分 享 到 朋 友 圈 i2e16 一 e7 一 14 : 16 : 羽 i 本 报 讯 ( 通 讯 员 王 晓 云 ) 位 于 延 庆 区 的 平 北 抗 日 战 争 纪 念 馆 近 日 推 首 页 | 文 化 新 闻 i 网 友 夜 探 杂 书 馆 被 保 安 发 现 高 晓 松 : 窃 书 不 算 偷 i2e16 一 e7 一 es 12 : 32 : 羽 i7 月 5 日 电 近 日 , 有 网 友 在 微 博 称 , 自 己 半 夜 路 过 杂 书 馆 , 想 要 夜 探 宄 首 页 | 文 化 新 闻 i 《 夜 》 作 者 埃 利 威 塞 尔 去 世 曾 获 诺 贝 尔 和 平 奖 i2e16 一 e7 一 es 11 : 14 : 羽 i 京 华 时 报 讯 ( 记 者 田 超 ) 据 美 国 CNN 报 道 , 美 籍 犹 太 裔 作 家 、 诺 贝 尔 和 平 奖 首 页 | 文 化 新 闻 i 《 男 孩 和 世 界 》 : 孩 子 眼 中 的 世 界 是 什 么 样 子 的 i2e16 一 e7 一 es 1B : 48 : 羽 i 巴 西 动 画 电 影 《 男 孩 和 世 界 》 , 呈 现 的 是 孩 子 眼 里 的 大 千 世 界 。 小 乡 村 首 页 | 文 化 新 闻 i 评 论 : 三 本 院 校 拼 “ 考 研 率 ” 错 了 吗 ? i 2 e 16 一 e 7 一 e 5 1B : 14 : 羽 i 给 三 本 院 校 贴 上 “ 考 研 基 地 ” 的 标 签 , 难 免 有 夸 张 成 分 , 难 道 一 本 、 二 本 院 校 就 不 重 首 页 | 文 化 新 闻 i 中 国 民 间 工 艺 : 华 县 皮 影 光 影 间 跳 动 的 精 灵 i2e16 一 e7 一 1B : 1B : 羽 i 在 电 影 还 未 出 现 之 时 , 皮 影 艺 人 们 就 是 夜 幕 降 临 后 最 引 人 注 目 的 “ 魔 术 师 ” 和 首 页 | 文 化 新 闻 i 教 师 做 班 主 任 得 校 长 “ 下 令 " 班 主 任 “ 遇 冷 ” 为 哪 般 i2e16 一 e7 一 es e9:e4 : 羽 i 中 国 教 育 学 会 班 主 任 专 委 会 主 任 委 员 陈 萍 做 班 主 任 培 训 时 , 曾 问 台 下 听 首 页 | 文 化 新 闻 i 京 城 新 增 12 处 地 下 文 物 埋 藏 区 己 增 至 68 处 i2e16 一 e7 一 es 巧 5 : 羽 i 本 报 北 京 电 京 城 的 “ 地 下 宝 藏 ” 己 增 至 68 处 。 日 前 , 北 京 市 公 布 了 第 五 批 地 下 首 页 | 文 化 新 闻 i 澧 县 、 津 市 孟 姜 女 故 事 属 地 之 争 带 来 浪 费 i2e16 一 e7 一 es e7 : 37 : 羽 i 原 标 题 : 警 惕 文 化 名 分 之 争 带 来 浪 费 ( 曝 光 ) 孟 姜 女 哭 长 城 的 故 事 , 是 中 国 四 大 首 页 | 文 化 新 闻 i 专 访 93 岁 著 名 诗 人 屠 岸 : 人 类 不 灭 , 诗 歌 不 亡 i2e16 一 e7 一 es 羽 :e8 : 羽 i 北 京 7 月 5 日 电 ( 上 官 云 ) “ 生 活 中 还 是 要 多 读 经 典 、 多 读 诗 。 ” 近 日 , 93 岁 的 首 页 | 文 化 新 闻 i 世 界 文 化 遗 产 莫 高 窟 酝 酿 “ 智 慧 " 保 护 管 理 新 模 式 i2e16 一 e7 一 e4 23m6 : 羽 i 兰 州 7 月 4 日 电 ( 记 者 冯 志 军 ) 为 提 升 莫 高 窟 管 理 与 服 务 水 平 , 增 强 核 心 首 页 | 文 化 新 闻 i 加 拿 大 佛 教 会 迎 请 中 国 普 陀 山 香 火 i2e16 一 e7 一 e4 21 巧 4 : 舟 山 7 月 4 日 电 ( 记 者 林 波 ) 2e16 年 7 月 4 日 , 农 历 丙 申 年 六 月 初 一 , 加 拿 大 佛 教 耆 宿 、 湛 首 页 | 文 化 新 闻 i 中 俄 蒙 油 画 艺 术 家 年 会 在 内 蒙 古 启 幕 i2e16 一 e7 一 e4 21 : 1B : 羽 i 满 洲 里 7 月 4 日 电 ( 记 者 李 爱 平 ) 4 日 下 午 , 主 题 为 “ 聚 三 国 风 情 , 展 艺 术 精 粹 ” 的 中 俄 首 页 | 文 化 新 闻 i 兰 州 国 际 鼓 文 化 艺 术 周 6 日 启 幕 四 国 民 间 艺 术 亮 相 街 头 i2e16 一 e7 一 e4 19 : 26 : 羽 i 兰 州 7 月 4 日 电 ( 杜 萍 ) 4 日 , 第 四 届 中 国 ( 兰 州 ) 国 际 鼓 文 化 艺 术 周 首 页 | 文 化 新 闻 i 成 都 一 工 地 挖 出 上 千 年 历 史 皇 家 园 林 遗 址 i2e16 一 e7 一 e4 18 巧 2 : i 新 华 社 成 都 7 月 4 日 专 电 ( 记 者 童 方 ) 记 者 4 日 从 成 都 市 文 物 考 古 研 究 所 了 解 至 丨 首 页 | 文 化 新 闻 i 解 秘 中 国 高 端 政 策 研 究 《 透 视 中 国 》 系 列 丛 书 面 世 i2e16 一 e7 一 e4 18m9 : 羽 i 北 京 7 月 4 日 电 ( 记 者 张 量 ) 5 羽 多 万 字 , 11 部 著 作 , 覆 盖 中 国 近 2e 多 年 首 页 | 文 化 新 闻 i 国 内 首 部 寄 宿 概 念 丿 L 童 情 景 剧 《 寄 宿 学 校 》 南 宁 开 拍 i2e16 一 e7 一 e4 16m5 : i 南 宁 7 月 4 日 电 ( 记 者 张 广 权 ) 国 内 首 部 寄 宿 概 念 丿 L 童 情 景 剧 《 寄 宿 学 首 页 | 文 化 新 闻 i 《 安 的 秘 密 》 向 高 校 开 放 导 演 : 没 必 要 刻 意 保 护 版 权 i2e16 一 e7 一 e4 16 : 33 : 羽 i7 月 4 日 电 近 期 , 话 剧 《 安 的 秘 密 》 版 权 面 向 全 国 高 校 学 生 剧 社 开 首 页 | 文 化 新 闻 i 王 斑 出 演 毛 泽 东 : 想 刻 画 一 个 乡 下 书 生 走 上 天 安 门 的 传 奇 i2e16 一 e7 一 e4 14m8 : 羽 i 在 人 艺 舞 台 上 演 了 太 多 像 哈 姆 雷 特 、 周 萍 、 曾 文 清 这 样 的 王 子 、 首 页 | 文 化 新 闻 i 评 论 : 高 校 校 长 鞠 躬 道 歉 只 是 一 个 开 始 i2e16 一 e7 一 e4 13 : 27 : 羽 i 朴 实 的 话 语 , 诚 挚 的 道 歉 , 深 深 的 鞠 躬 一 日 前 , 在 安 庆 师 范 大 学 毕 业 典 礼 上 , 校 首 页 | 文 化 新 闻 i 水 松 石 山 房 主 人 水 墨 展 举 办 探 索 古 典 文 人 精 神 i2e16 一 e7 一 e4 13 : 26 : 羽 i7 月 4 日 电 近 日 , 位 于 金 鱼 胡 同 的 止 观 艺 术 馆 举 办 了 《 权 杖 、 怪 石 、 禅 与 首 页 | 文 化 新 闻 i 北 京 市 公 立 高 中 国 际 部 录 取 线 再 涨 报 名 火 爆 i2e16 一 e7 一 e4 13 : 32 : 羽 i 本 报 讯 ( 记 者 林 艳 武 文 娟 张 昆 龙 ) 北 京 四 中 佳 莲 学 校 565 分 、 十 一 学 校 56 首 页 | 文 化 新 闻 i 《 长 征 》 首 演 阎 维 文 等 共 塑 英 雄 群 像 i2e16 一 e7 一 e4 13 : 16 : 羽 i 为 纪 念 中 国 工 农 红 军 长 征 胜 利 8B 周 年 , 7 月 1 日 晚 , 由 印 青 作 曲 、 邹 静 之 编 剧 、 吕 嘉 首 页 | 文 化 新 闻 i 对 话 赖 声 川 : 家 风 培 育 与 自 我 修 炼 都 需 利 他 精 神 i2e16 一 e7 一 e4 12 : 23 : i 杭 州 7 月 4 日 电 ( 见 习 记 者 陈 丽 莎 ) 近 日 , 著 名 华 人 戏 剧 家 赖 声 川 走 进 杭 开

说明:使用了4个分类的数据(文化、财经、军事和体育),每个分类使用了1000条数据,每行一条数据,有4个字段(分类、标题、日期和内容),使用"\u00EF"作分割符。

 

 

一、数据清洗转换

数据预处理步骤主要进行数据清洗、转换操作。主要代码如下:

计算机生成了可选文字:def clean(filepath: String, spark: SparkSession) : D a t a F r a m e import spark.implicits. spark.sparkContext.textFiIe(fiIePath) . flatMap { line vat textDF val fields 11 Xu@@EF") 1 f (fields.length > 3 ) { vat categoryLine fi elds(@) categories categoryLine.spIit("XXl ' categori · last vat category var label 1 f (category.contains(" 文 化 ") ) label else 1 f (category.contains(" 财 经 ") ) label else 1 f (category.contains(" 军 事 ") ) label else 1 f (category.contains(" 体 育 ") ) label else { } vat title fields(l) vat time fi elds(2) 1 . 0 content) else None title, time, " content") vat content 1 f (label > } else None } . toDF( "label" textDF fi elds(3) 一 1 ) Some(label, tIe"

首先从文件加载数据到RDD,然后按分割符进行切分。因为分类字段爬取下来时没有进行清洗,在这里我们需要将其分类提取出来,然后转换为sparkLR算法可以识别的Double形式,并按分类字段过滤掉未提取到分类或者分类不正确的脏数据,然后转换为DataFrame,并指定每个字段的字段名。

注意:这里必须要添加一行import spark.implicits._,否则不能引用到SparkSQLtoDF方法!

 

 

二、分词

在经过数据预处理之后,我们已经将数据转换为了我们想要的DataFrame格式,并且清洗掉了。接下来我们需要进行分词的操作,将新闻内容切分成一个个词语的形式,以便后续进行停用词去除以及转换为特征向量

计算机生成了可选文字:vat textDF = preprocess (filepath, spark) / / 分 词 vat §月且!!!§.@孓§丆 = new Segmenter(spark) . setSegmentType ( " Standa " ) · re (false) · setInputCoI ( " content") · setOutputCoI ( " tokens " ) segmenter . t r a n s fo r m (textDF) vat segDF

这里我模仿sparklm包下的StopWordsRemover类创建了Segmenter类,用于对数据进行分词,其内部调用了HanLP分词工具。(由于spark自带的StopWordsRemover等使用的闭包仅限于ml包,自定义的类无法调用,故只是采用了与StopWordsRemover类似的使用形式,内部结构并不相同,并且由于以上原因,Segmenter类没有继承Transformer类,故无法进行pipeline管道操作,此缺陷有待解决)

 

Segmenter类具体实现如下:

计算机生成了可选文字:)class SparkSession, vat u 1 d : Stri (g) extends Serializable { private var inputCol private var OLItPUtCOl private var segment 丆 ype private var enableNature " StandardTokenizer" false def setInputCoI(vaIue: String) : this.type this. inputCol this def setoutputCoI(vaIue: Stri (g) : this. type this. outputCol value this def setSegmentType(vaIue: Stri (g) : this. type this.segment 丆 ype value this def enableNature(vaIue: Boolean) : this.enableNature this. type this def this(spark: SparkSession) Identi fi able . randomUTD ( " segment" ) ) this(spark,

计算机生成了可选文字:def transform(dataset: DataFrame) : D a t a F r a m e null var segment: Segment segment 丆 ype match { case "NShortSegment" new MyNShortSegment() segment case "CRFSegment" new MYCRFSegment() segment Case dataset.select(inputcol) . rdd.map { case RO ( line : vat tokens seq() var terms: Seq LTermJ segment 丆 ype match { case "StandardSegment" StandardTokeni zer .segment ( 11 ne) terms case "NLPSegment" NLPTokeni zer .segment ( 11 ne) terms case "IndexSegment" IndexTokeni zer · segment ( 11 ne) terms case "SpeedSegment" SpeedTokeni zer · segment ( 11 ne) terms case "NShortSegment" ne) terms case "CRFSegment" ne) terms Case p ' 一 亡 忉 ( " 分 词 类 型 错 误 ! " ) System.exit(1) Stri ng)

计算机生成了可选文字:terms.map(term = > vat termSeq 1 f (this.enableNature) term.toString else term.word " # (") ) .drop(inPL/tCol + (line, termSeq) import spark.implicits. .toDF(inpL./tCol + " # 1 " vat tokensSet tokens dataset . j n(tokensSet dataset(inputCol) outputCol) tokensSet(inputCol +

主要在transform方法中调用了HanLP相关的分词方法。注意,如果使用NShortSegmentCRFSegment,需要new相应的对象,这里我自己创建了MyNShortSegmentMyCRFSegment类,继承了HanLP中对应的类,并继承了Serializable特质(其实并没有做什么操作~)。主要是因为HanLP没有对它们实现序列化,直接在RDD中使用它们会报错。(当然你也可以对HanLP的源码进行修改,再重新打包。个人觉得比较麻烦,并且不易跟进HanLP发布进度,所以没去弄~

计算机生成了可选文字:package preprocess.utiIs.segment import com. hankcs. hantp.seg.CRF .CRFSegment 女 戽 定 灵 CRE 分 癸 , *XCRFSegment, 尹 荬 刁 钇 女 Created by yogg on 29 7 / 2 / 24 . 一 class MYCRFSegment extends CRFSegment with Serializable(

此外,上面Segmenter代码的最后是使用DataFramejoin操作将原DataFrame与分词后的DataFrame进行了连接,与spark使用的schemaType元数据推断DataFrame结构的方式不同。

 

 

三、去除停用词

分词之后,我们需要对一些常用的无意义词(通常是语气词、连词等),如:“的”、“我们”、“是”等(统称为“停用词”)进行去除。因为这些词没有多大的意义,但是在自然语言中又经常使用,这些词不去掉会强烈的干扰我们对特征的抽取效果。(比如:在体育分类语料中,“的”共出现500次,“足球”共出现300次,那么谁更能代表体育这个分类呢?谁更应该作为特征被保留下来呢?)

去除停用词的操作我们直接调用ml包中的StopWordsRemover类:

计算机生成了可选文字:/ / 去 除 停 用 词 spark · · textFiIe (stopwordPath) .conect() vat $9PY9v@Array new StopWordsRemover() remover · setStopWords (stopwordArray) · setInputCoI ( " tokens " ) · setOutputCoI ( " removed " ) remover.transform (segDF) val removedDF

由于sparkStopWordsRemover类中内置的停用词都是一些英文停用词,而我们在这里处理的是中文语料,故需要加载自己的停用词。这里我使用了HanLPdictionary目录下的stopwords.txt文件提供的停用词。(这里面都是一些基本停用词,如果对停用词要求比较高,可以在网上找几份停用词表进行合并,效果会更好一点)

有兴趣的同学可以进到transform方法中看一看,spark官方的去除停用词方法跟我们常用处理一样,将停用词转换成set,然后调用contains进行判断,然后过滤:

计算机生成了可选文字:1 nce ( " 2 . G). 0 " ) override def transform(dataset: DatasetL_J ) : D a t a F r a m e transformSchema (dataset . schema) vat outputSchema $ ( s 亡 op 0 厂 ) .toSet vat stopWordsSet Lldf { terms: Seq LStri ngl terms. filter(s = > !stopWordsSet.contains(s) ) } else { / / TODO: support user locale (SPARK-15@64) (): Stri ng) = > 1 f (s ! = s.toLowerCase else S val toLower $ ( s 亡 op 0 厂 ) .map(toLower(_)) · toSet vat lower-StopWords Lldf { terms: Seq LStri ngl terms. filter(s = > !lowerStopWords.contains(toLower(s) ) ) outputSchema($ (OLItPLltCOl) ) .metadata vat metadata dataset.select(col ( " * " ) , t(col ($(inputCol))) .as($ (outputCOl) , metadata) )

 

 

四、向量化

由于目前常用的分类、聚类等算法都是基于向量空间模型VSM(即将对象向量化为一个N维向量,映射成N维超空间中的一个点),VSM将数据转换为向量形式,便于对大规模数据进行矩阵操作等,也可以通过计算超空间中两个点之间的距离(一般是余弦距离)来计算两个向量之间的相似度。因此,我们需要将经过处理的语料转换为向量形式,这个过程叫做向量化。

这里我们也调用spark提供的向量化类CountVectorizer类进行向量化操作:

计算机生成了可选文字:/ / 向 量 化 zer new CountVectorizer() · setVocabSi ze ( 2000 ) · setInputCoI ( " removed " ) · setOutputCoI ( " featu res " ) . fit(removedDF) vectori zer .transform(removedDF) vat vectorDF

这里的vocabSize是词汇表大小,即转换为向量之后的向量维度。通过阅读fit方法(训练向量化模型,主要是计算vocabulary词汇表的过程),我们可以看到其逻辑也比较简单:wordcount计算词频 --> 计算文档频率 --> 按文档频率过滤-->取词频最大的vocabSize个词

计算机生成了可选文字:override def fit(dataset: DatasetL_J ) : CountVectorizerModeI transformschema(dataset.schema, logging = true) VOCSize $ (vocabSize) vat input dataset.select($ (inputCol) ) . rdd . LStri ( 0 ) ) vat minDf 1 f ( $ (minDF) > = 1.G)) { $ (minDF) } else { $ (minDF) * input.cache() . cou nt 0 vat wordCounts: RDDL(Str-ing, Long) ] 1 nput. flatMap new OpenHashMapLStri ng, Longl WC { case (tokens) tokens. for-each { W = > wc.changeVaIue(), IL, 计 算 词 频 + IL) (count df2)) . tolnt) (Ordering . by ( WC . m a p { case (word , count) = > (word } . reduceByKey { case ((wc1, df1) , (wc2, (wc1 + wc2, dfl + df2) } . filter { case (word , (wc, d f) ) d f > = minDf— 过 滤 扌 卓 文 档 频 率 小 于 阈 值 的 词 ).map { case (word , (count, dfCount) ) (word , count) ).cache() vat fullVocabSize wordCounts vat vocab = wordCounts . top (math . Ill 讠 n (fullVocabSi , .map( require(vocab.length > 0 , "The . cou nt 0 VOCSi ze) Lower minDF a S necessary. vocabulary size should be 0 . copyVaIues(new CountVectorizerModeI(uid , vocab) .setparent(this))

从这里可以看出,所谓的训练CountVectorizer模型仅仅是对词频进行统计,计算出词频最大的vocabSize个词作为词汇表。下面我们继续看看transform方法:

计算机生成了可选文字:1 nce ( " 2 . G). 0 " ) override def transform(dataset: DatasetL_J ) : transfo . SC , 一 9 且 1 ng 1 f (broadcastDict.isEmpty) { vat dict vocabulary · zi PWi t h I n d e x · toMap D a t a F r a m e true) 进 行 广 播 Some (dataset · sparkSessi O n . sparkContext . broadcast ()i (t) ) b 厂 oadcas 亡 D 讠 c 亡 vat dictBr b 厂 oadcas 亡 D 讠 c 亡 · get vat minTf $ (min 丆 F) zer Lldf { (document: Seq ngJ) vat termCounts new OpenHashMapLInt, Doublel var tokenCount document. for-each { term = > dictBr.vaIue.get(term) match { 将 词 汇 表 添 加 素 引 I 转 换 为 駟 case Some(index) = > termCounts.changeVaIue(index, 1 . 0 , case None = > / / ignore terms not i n the vocabulary tokenCount + = 1 vat effectiveMinTF 1 f (minTf > = ] 一 0 ) minTf else tokenCount * minTf vat effectiveCounts 1 f ( $ (binary) ) { termCounts. filter( 2 > = effectiveMinTF) . ma p ( p = > ( p . 1 , 1 . 0 ) ) .toSeq } else { termCounts. filter( 2 > = effectiveMinTF) .toSeq 按 最 小 词 频 过 滤 , 然 后 如 果 特 征 设 置 取 二 值 特 征 , 则 将 素 引 〉 词 频 映 射 中 的 词 频 设 置 为 Vectors.sparse(dictBr.vaIue.size, effectiveCounts) 向 量 稀 疏 化 , 将 向 量 转 换 为 稀 疏 向 量 形 式 dataset.withCoIumn($ (outputCOl) , vectorizer(col ($(inputCol))))

transform方法也比较简单,将词汇表建立索引并转换为Map -->遍历并保留在词汇表中的词,及其词频 -->转换为稀疏向量形式

 

我们可以将向量化后的数据打印出来看看长什么样儿:

计算机生成了可选文字:/ / 向 量 化 zer new CountVectorizer() · setVocabSi ze ( 2000 ) · setInputCoI ( " removed " ) · setOutputCoI ( " featu res " ) . fit(removedDF) vectori zer .transform(removedDF) vat vectorDF vectorDF .select("1abeV' "features") .show(2@, truncate false)

 

计算机生成了可选文字:label ; features ; ( 2000 , ; ( 2000 , ; ( 2000 , ; ( 2000 , ; ( 2000 , ; ( 2000 , ; ( 2000 , ; ( 2000 , ; ( 2000 , ; ( 2000 , ; ( 2000 , ; ( 2000 , ; ( 2000 , ; ( 2000 , ; ( 2000 , ; ( 2000 , ; ( 2000 , ; ( 2000 , ; ( 2000 , ; ( 2000 , [ 1 , 3 , 6 , 9 , 12 , 20 , 25 , 31, 59 , 62 , 65 , 69 , 71, 77 , 115, 116, 198 , 224 , 229 , 241 , 32L 363 , 374 , 390 , 4 恚 470 , 563 , 565 , 576 , 650 , 724 , 739 , 75L 752 , 777 , 80L 855 , 927 , 940 , 1042 , 1273 , 1362 , 1454 , 151 [ 1 , 2 , 3 , 5 , 8 , 9 , 10 , 11, 12 , 16 , 19 , 21, 24 , 27 , 29 , 38 , 40 , 41, 51, 52 , 54 , 57 , 65 , 67 , 76 , 79 , 87 , 99 , 107 , 112 , 128 , 130 , 146 , 159 , 172 , 175 , 176 , 187 , 189 , 190 , 207 , 210 , 218 , 224 , 228 , 229 , 234 , 247 , 275 , [ 1 , 2 , 3 , 4 , 5 , 6 , 8 , 16 , 17 , 18 , 20 , 33 , 40 , 48 , 51, 71, 73 , 74 , 88 , 10L 132 , 175 , 189 , 212 , 217 , 225 , 252 , 259 , 273 , 306 , 317 , 329 , 382 , 412 , 427 , 4 恚 452 , 467 , 496 , 520 , 522 , 568 , 596 , 608 , 618 , 666 , 675 , [ 0 , 1, 2 , 3 , 4 , 5 , 8 , 9 , 10 , 12 , 13 , 20 , 22 , 25 , 28 , 29 , 30 , 33 , 4 恚 46 , 50 , 51, 56 , 65 , 79 , 89 , 91, 100 , 107 , 112 , 113 , 118 , 127 , 128 , 13L 147 , 16L 17L 173 , 180 , 209 , 210 , 21L 22L 226 , 227 , 23L 232 , 236 , [ 0 , 1, 2 , 3 , 4 , 5 , 8 , 13 , 16 , 17 , 20 , 28 , 40 , 4 恚 46 , 50 , 51, 65 , 67 , 76 , 79 , 85 , 86 , 88 , 90 , 91, 97 , 99 , 100 , 103 , 107 , 115, 123 , 129 , 157 , 17L 175 , 179 , 18L 188 , 193 , 218 , 222 , 225 , 234 , 238 , 242 , 243 , 250 , [ 0 , 1, 3 , 4 , 7 , 10 , 11, 12 , 13 , 15 , 16 , 17 , 18 , 19 , 26 , 27 , 31, 32 , 33 , 36 , 37 , 40 , 42 , 46 , 53 , 58 , 66 , 68 , 74 , 77 , 81, 82 , 85 , 95 , 103 , 108 , 109 , 112 , 113 , 120 , 124 , 138 , 139 , 140 , 14L 15L 155 , 173 , 179 , 183 , [ 1 , 10 , 11, 14 , 29 , 30 , 34 , 59 , 80 , 84 , 97 , 102 , 13L 154 , 169 , 180 , 183 , 198 , 215 , 366 , 369 , 382 , 385 , 392 , 396 , 41L 414 , 466 , 470 , 477 , 506 , 552 , 565 , 57L 717 , 1004 , 1047 , 1107 , H36 , 1535 , 1657 , 1658 ; [ 0 , 1, 3 , 6 , 9 , 15 , 17 , 20 , 21, 22 , 28 , 37 , 38 , 41, 47 , 58 , 60 , 79 , 82 , 104 , 107 , 108 , 111, 116 , 129 , 15L 163 , 170 , 180 , 18L 203 , 227 , 228 , 236 , 246 , 267 , 280 , 300 , 340 , 342 , 38L 393 , 408 , 4 , 574 , 585 , 6 [ 0 , 2 , 3 , 5 , 6 , 8 , 13 , 17 , 25 , 4 恚 46 , 51, 53 , 75 , 79 , 111, 118 , 12L 128 , 129 , 13L 133 , 137 , 143 , 146 , 148 , 188 , 193 , 199 , 207 , 219 , 229 , 247 , 254 , 30L 307 , 31L 363 , 426 , 4 , 51L 550 , 563 , 565 , 585 , 638 , [ 0 , 2 , 3 , 5 , 8 , 10 , 20 , 25 , 29 , 30 , 40 , 42 , 46 , 51, 52 , 53 , 62 , 64 , 65 , 79 , 87 , 95 , 97 , 102 , 111, 113, 115, 117 , 130 , 13L 146 , 182 , 190 , 209 , 214 , 224 , 226 , 227 , 229 , 243 , 32L 363 , 384 , 406 , 423 , 469 , 477 , 5 [ 0 , 1, 2 , 3 , 5 , 8 , 10 , 14 , 21, 23 , 24 , 29 , 30 , 33 , 38 , 42 , 51, 56 , 65 , 79 , 92 , 10L 13L 136 , 137 , 152 , 154 , 167 , 169 , 179 , 182 , 197 , 217 , 219 , 23L 260 , 293 , 298 , 325 , 400 , 425 , 43L 437 , 474 , 504 , 509 , 524 , [ 0 , 1, 2 , 3 , 4 , 5 , 8 , 9 , 10 , 12 , 21, 24 , 30 , 32 , 38 , 45 , 46 , 50 , 51, 52 , 53 , 55 , 57 , 58 , 59 , 64 , 69 , 70 , 76 , 79 , 82 , 83 , 89 , 94 , 95 , 97 , 110, 115, 118 , 129 , 14 恚 145 , 146 , 150 , 152 , 155 , 159 , 160 , 165 , 17L 172 , 1 [ 0 , 1, 2 , 3 , 4 , 5 , 7 , 8 , 12 , 14 , 16 , 17 , 18 , 23 , 24 , 26 , 31, 33 , 34 , 40 , 41, 42 , 45 , 47 , 53 , 56 , 59 , 65 , 68 , 69 , 74 , 83 , 85 , 94 , 102 , 112 , 114 , 117 , 123 , 124 , 133 , 138 , 14L 154 , 166 , 178 , 180 , 193 , 195 , 196 , 197 , [ 0 , 1, 2 , 4 , 5 , 14 , 21, 26 , 29 , 30 , 31, 33 , 45 , 47 , 49 , 61, 71, 74 , 83 , 103 , 122 , 125 , 128 , 133 , 139 , 142 , 156 , 193 , 212 , 219 , 223 , 234 , 241 , 294 , 300 , 343 , 355 , 427 , 429 , 4 , 520 , 633 , 669 , 685 , 728 , 74 恚 7 [ 1 , 2 , 4 , 5 , 6 , 7 , 9 , 11, 14 , 15 , 18 , 20 , 27 , 31, 40 , 62 , 64 , 65 , 68 , 71, 74 , 78 , 83 , 87 , 90 , 92 , 95 , 97 , 98 , 108 , 113 , 118 , 120 , 123 , 137 , 14 恚 146 , 156 , 158 , 160 , 162 , 182 , 183 , 190 , 206 , 217 , 226 , 240 , 249 , 2 [ 1 , 2 , 4 , 5 , 8 , 10 , 13 , 19 , 20 , 21, 26 , 29 , 38 , 39 , 40 , 42 , 4 恚 45 , 51, 53 , 59 , 61, 64 , 65 , 67 , 71, 75 , 80 , 83 , 89 , 113 , 119 , 122 , 123 , 125 , 140 , 146 , 154 , 156 , 159 , 180 , 188 , 190 , 215 , 230 , 234 , 235 , 242 , 254 , [ 1 , 2 , 4 , 5 , 6 , 8 , 9 , 10 , 11, 12 , 15 , 17 , 19 , 20 , 22 , 24 , 37 , 4 恚 47 , 48 , 50 , 53 , 55 , 56 , 60 , 65 , 77 , 84 , 85 , 87 , 91, 104 , 112 , 113 , 118 , 130 , 134 , 138 , 145 , 147 , 156 , 158 , 16L 18L 186 , 187 , 210 , 232 , 237 , 262 , [ 0 , 3 , 4 , 9 , 10 , 11, 17 , 20 , 24 , 25 , 26 , 30 , 45 , 46 , 52 , 55 , 58 , 70 , 71, 73 , 78 , 83 , 95 , 97 , 98 , 10L 103 , 108 , 115, 133 , 142 , 148 , 150 , 154 , 155 , 172 , 174 , 182 , 185 , 204 , 208 , 253 , 264 , 265 , 273 , 289 , 296 , 32 [ 1 , 2 , 3 , 4 , 6 , 7 , 9 , 12 , 13 , 14 , 15 , 17 , 18 , 19 , 20 , 26 , 27 , 28 , 29 , 31, 36 , 38 , 39 , 41, 4 恚 45 , 50 , 54 , 57 , 63 , 64 , 69 , 84 , 85 , 89 , 91, 98 , 10L 103 , 107 , 108 , 109 , 110 , 116 , 117 , 124 , 127 , 128 , 130 , 135 , 138 , 1 [ 1 , 2 , 4 , 5 , 6 , 8 , 9 , 10 , 11, 12 , 13 , 14 , 15 , 17 , 19 , 21, 23 , 24 , 26 , 27 , 29 , 31, 32 , 34 , 38 , 40 , 41, 42 , 45 , 50 , 53 , 55 , 59 , 66 , 68 , 70 , 91, 92 , 95 , 96 , 97 , 100 , 102 , 109 , 110 , 115, 116, 119 , 120 , 122 , 125 , 128 , 1

后面没有显示完,我们取第一条数据看看:

计算机生成了可选文字:1 | 0 0 0 , [ 1,3 悉 , 1 202 , 31, 7t7 · | e . e | (20B, [ 1 3 6 9 12 2B 25 31 59 62 65 69 71 77 115 116 198 224 229 241 321 3 63 374 39e 444 47e 563 丿 565 丿 576 65e 724 丿 739 丿 751 752 丿 777 8e1 855 927 94e 1e42 丿 1273 丿 1362 1454 1515 1666 1679 1762 1778 1975 ] ILI.e,2.e,1.e,1.e,1.e,1.e,1.e,1.e,1.e,2.e 丿 1 . e, 1 . e, 1 . e, 1 . e, 1 . e, 1 . e, 1 . e, 1 . e, 1 . e, 2 . e, 1 . e, 3 . e, 1 . e, 1 . e, 1 . e, 1 . e, 2 . e, 1 . e, 2 . e, 1 . e, 1 . e, 1 . e, 1 . e, 1 . e, 1 . e, 1 . e, 2 . e, 1 . e, 2 . e, 5 . e, 1 . e, 2 . e, 1 . e, 1 . e, 1 . e, 1 . e, 2 . e, 1 . e, 4 . e ] )

可以看到前面是标签,即类别序号,后面是一个稀疏向量,其元素分别代表:向量维度(2000)、特征索引数组(即词汇表中哪些索引号的词出现在该文档中)、词频数组(词汇表中索引词在该文档中出现的次数),例:最后一个元素1975表示词汇表中第1975个词出现在该文档中,出现的次数是4

 

 

五、模型训练

在经过向量化后,数据就可以用来进行分类模型的训练了!这里我们先使用最常用的分类模型——逻辑回归LogisticRegressionspark上提供的LR模型可以实现多分类,正好适用于我们的语料。

下面是分类模型训练的代码:

 

计算机生成了可选文字:/ / LR 分 类 模 型 训 练 t rai n . pe rsi st 0 new LogisticRegression() vat 1 r .setRegParam(@.2) · setEIasti cNetParam ( · 05 ) .setToI(1E—7) · setFeatu resCoI ( " featu res " ) .fit(train) t rai n . u n pe rsi st 0 //LR 预 测 vat predictions Ir.transform(predict) predictions.select(" prediction" "probability") .show(l@@, truncate false)

new一个LogisticRegression时,可以对其参数进行设置,这里大概跟大家说一下:

  • setMaxIter:设置最大迭代次数(默认100),具体迭代过程可能会在不足最大迭代次数时停止(参照下一条)
  • setTol:设置容错(默认1E-6),每次迭代会计算一个误差值,误差值会随着迭代次数的增加逐渐减小,如果误差值小于设置的容错值,则停止迭代优化
  • setRegParam:设置正则化项系数(默认0.0),正则化项主要用于防止过拟合现象,因此,如果你的数据集比较小,特征维数又比较多时,易出现过拟合,此时可以考虑增大正则化项系数
  • setElasticNetParam:正则化范式比(默认0.0),正则化一般有两种范式:L1(Lasso)L2(Ridge)L1一般用于特征的稀疏化,L2一般用于防止过拟合。这里的参数即设置L1范式的占比,默认0.0即只使用L2范式
  • setLabelCol:设置标签列(默认读取“label”列)
  • setFeaturesCol:设置特征列(默认读取“features”列)

还有一个参数是setWeightCol,即设置各特征的权重,默认值是将每个特征权重设置为1.0,这里我们使用默认值就好了,如果对特征有特殊要求,可考虑重新设置对应的权重(如将标题作为一项特征,并且标题重要性更高,可将标题这一特征的权重设置大一点)

注意:由于我们的数据稀疏性本来就很高了(2000维的向量只有少数维度有值),因此切记不要把setElasticNetParam设置得过大!!因为setElasticNetParam越大表示L1正则所占比例越高,对向量稀疏化效果越好,而我们的向量本来就很稀疏了,再稀疏化特征基本都为0了,得到的分类效果跟随机分类没什么区别(不信的话可以把这个值设置大一点,然后把后面说到的预测结果的probability打印出来,可以看到在各类别上的概率差别不大)

 

关于参数的设置,一般根据语料特点和业务场景的不同有所不同,这是一个经验性的东西,没有一个固定的计算公式(所以对数据挖掘和算法工程师来说,调参是一件相当耗时并且头疼的问题)。我们这里暂时使用spark官方example里面的设置,后面再进行调优。

 

这里由于篇幅问题就不跟进去LR算法的源码了,有兴趣的同学可以自行走读源码。

开始训练时,spark默认会打印每次迭代的信息:

计算机生成了可选文字:SegTest 育 10 ℃ 3 / ℃ 4 12 . 17 . 14 10 ℃ 3 / ℃ 4 12 . 17 . 14 10 ℃ 3 / ℃ 4 12 . 17 . 15 10 ℃ 3 / ℃ 4 12 . 17 . 15 睿 10 ℃ 3 / ℃ 4 12 . 17 . 16 10 ℃ 3 / ℃ 4 12 . 17 . 16 而 10 ℃ 3 / ℃ 4 12 . 17 . 18 10 ℃ 3 / ℃ 4 12 . 17 . 18 10 ℃ 3 / ℃ 4 12 . 17 . 19 10 ℃ 3 / ℃ 4 12 . 17 . 19 10 ℃ 3 / ℃ 4 12 . 17 . 20 10 ℃ 3 / ℃ 4 12 . 17 . 20 10 ℃ 3 / ℃ 4 12 . 17 . 22 10 ℃ 3 / ℃ 4 12 . 17 . 22 10 ℃ 3 / ℃ 4 12 . 17 . 23 10 ℃ 3 / ℃ 4 12 . 17 . 23 10 ℃ 3 / ℃ 4 12 . 17 . 26 10 ℃ 3 / ℃ 4 12 . 17 . 26 10 ℃ 3 / ℃ 4 12 . 17 . 28 10 ℃ 3 / ℃ 4 12 . 17 . 28 10 ℃ 3 / ℃ 4 12 . 17 . 29 10 ℃ 3 / ℃ 4 12 . 17 . 29 10 ℃ 3 / ℃ 4 12 . 17 : 33 10 ℃ 3 / ℃ 4 12 . 17 : 33 10 ℃ 3 / ℃ 4 12 . 17 : 33 [Stage 215 . INFO OWLQN INFO OWLQN INFO OWLQN INFO OWLQN INFO OWLQN INFO OWLQN INFO OWLQN INFO OWLQN INFO OWLQN INFO OWLQN INFO OWLQN INFO OWLQN INFO OWLQN INFO OWLQN INFO OWLQN INFO OWLQN INFO OWLQN INFO OWLQN INFO OWLQN INFO OWLQN INFO OWLQN INFO OWLQN INFO OWLQN INFO OWLQN INFO OWLQN Step S i z e : 0 . 5000 Val and Grad Step S i z e : 1. 000 Val and Grad Step S i z e : 1. 000 Val and Grad Step S i z e : 1. 000 Val and Grad Step S i z e : 1. 000 Val and Grad Step S i z e : 1. 000 Val and Grad Step S i z e : 1. 000 Val and Grad Step S i z e : 1. 000 Val and Grad Step S i z e : 0 . 5000 Val and Grad Step S i z e : 1. 000 Val and Grad Step S i z e : 1. 000 Val and Grad Step S i z e : 0 . 2500 Val and Grad Converged because 0 . 717579 仕 el 0 . 717579 仕 el 0 . 717579 仕 el 0 . 717579 仕 el 0 . 717579 仕 el 0 . 717579 仕 el 0 . 717579 仕 el 0 . 717579 仕 el 0 . 717579 仕 el 0 . 717579 仕 el 0 . 717579 仕 el 0 . 717579 仕 el max IteratIOns 2 . 83s 一 07 ) 0 . 000418005 1. 05s 一 07 ) 0 . 000339631 9 . 63s 一 08 ) 0 . 000293660 3 . 05s 一 08 ) 0 . 000425264 2 . 24s 一 08 ) 0 . 000398373 2 . 26s 一 08 ) 0 . 000357659 9 . 14s 一 0 叻 0 . 000306065 1. 09s 一 08 ) 0 . 000302405 2 . 90s 一 08 ) 0 . 000139552 6 . 66s 一 0 叻 0 . 000126241 2 . 12s 一 08 ) 0 . 000113058 3 . 97s 一 09 ) 0 . 000109368 reached ( 189 + 2 ) / 200 〕

这里打印了每次迭代的步长(由算法内部自动设置),以及每次迭代完后计算出的误差值,可以看到我们经过40次迭代后达到迭代次数上线,就停止迭代优化过程了。

 

刚才我们在代码中设置了打印前100个结果,可以看到console中有预测结果的输出:

计算机生成了可选文字:predictionaabel probability ' [ 0 . 5139390536082993 , 0 . 20013061210355457 , 0 . 1323821942887815 , 0 . 15354813999936462 ] ; [ 0 . 8921865578051414 , 0 . 034655753033324264 , 0 . 0328710694720204 恚 0 . 04028661968951374 ] ; [ 0 . 9187174050356536 , 0 . 03710434571101014 , 0 . 02295268093636749 , 0 . 021225568316968875 ] ; [ 0 . 6272179245091338 , 0 . 13902387745453273 , 0 . 1301040352226789 , 0 . 10365416281365432 ] ; [ 0 . 19668560460149395 , 0 . 47034 7452716204 , 0 . 2226797241377162 , 0 . 1102905967336277 ] ; [ 0 . 1755089126626545 , 0 . 17076696810122677 , 0 . 5485675933976063 , 0 . 10515652583851257 ] ; [ 0 . 01610607801636952 , 0 . 01503520101167717 , 0 . 9543407074292088 , 0 . 01451801354274 292 ] ; [ 0 . 03885587346893853 , 0 . 02152683674002253 , 0 . 022845642862697073 , 0 . 9167716469283418 ] ' [ 0 . 13217859399180H5 , 0 . 10580843323260486 , 0 . 13155426620561939 , 0 . 6304587065699745 ] ; [ 0 . 13011924091957724 , 0 . 07920677618109598 , 0 . 08228488108465946 , 0 . 7083891018146674 ] ; [ 0 . 24394892888160133 , 0 . 3711760842362663 , 0 . 19831935777958046 , 0 . 18655562910255194 ] ; [ 0 . 14067738158324952 , 0 . 68996624 的 339039 , 0 . 08080938315494075 , 0 . 08854699032790576 ] ; [ 0 . 08671039041713094 , 0 . 7605784 8546422 , 0 . 0757069000833730L 0 . 07700426364 5387 ] ; [ 0 . 041141646184319466 , 0 . 8860808892759318 , 0 . 038965609114 7156 , 0 . 033811855425341486 ] ; [ 0 . 1940141684 15128 , 0 . 538134 147613715 , 0 . 154 263750189282 , 0 . 1134250417781874 ] ; [ 0 . 23557221693575803 , 0 . 46115778822990167 , 0 . 19017536473231392 , 0 . 11309463010202638 ] ; [ 0 . 16946888456388096 , 0 . 4704763011364086 , 0 . 21215513876480835 , 0 . 14789967553490216 ] ; [ 0 . 08780156023068356 , 0 . 08972175559952765 , 0 . 7492538367898096 , 0 . 07322284737997922 ] ; [ 0 . 2438483755785784 恚 0 . 18499183372139397 , 0 . 33275542903676436 , 0 . 2384043616632633 ] ; [ 0 . 007270851961533085 , 0 . 007780297938894 的 5 , 0 . 011341521929261337 , 0 . 9736073281703111 ] ; [ 0 . 14045929866512333 , 0 . 12257998711176034 , 0 . 101008951252334 , 0 . 6359517629707824 ] ; [ 0 . 046990978497982866 , 0 . 85524 029359823 , 0 . 0660923001683362 , 0 . 031672418397698424 ] ; [ 4 . 675521278211098E 一 恚 0 . 0012212687850390717 , 0 . 9979260451738997 , 3 . 851339132401294E 一

可以看到效果还是蛮不错的!等等,怎么全分对了?不应该吧,往下面找找原来还是有些分错了:

计算机生成了可选文字:; [ 0 . 9798298459256264 , 0 . 005375019611584 28 , 0 . 008015921503715835 , 0 . 00677921295907316 ] ; [ 0 . 37949759429042 , 0 . 12150656129764124 , 0 . 10256854965272202 , 0 . 3964272947592167 ] ; [ 0 . 21391110007587327 , 0 . 389001060813087L 0 . 2121254365294613 , 0 . 18496240258157837 ] ; [ 0 . 029283269273655033 , 0 . 0921083462925594 恚 0 . 8558820709060199 , 0 . 022726313527765565 ] ; [ 0 . 04216869871621747 , 0 . 0413895762068785L 0 . 04293939568915 , 0 . 873502329387754 ] ; [ 0 . 01787881359299539 , 0 . 012976199970300648 , 0 . 013722686840452228 , 0 . 9554222995962517 ] ; [ 0 . 09811314767166059 , 0 . 7472175919034999 , 0 . 0845074575516333 , 0 . 07016180287320616 ] ; [ 0 . 1847228790851088 , 0 . 6520805270303586 , 0 . 10134199046304278 , 0 . 06185460342148986 ] ; [ 0 . 13671313678949773 , 0 . 5997100664H4714 , 0 . 1768792701064118 , 0 . 08669752669261892 ] ; [ 0 . 22085868945293063 , 0 . 5166665067045424 , 0 . 14238116377805166 , 0 . 120093640064 野 52 ] ; [ 0 . 0095337286991386 , 0 . 020737512553476535 , 0 . 00761253664694718 , 0 . 9621162221004376 ] ; [ 0 . 4733824609393107 , 0 . 21170212461354534 , 0 . 14 的 1469206469704 , 0 . 1700007223824 8 ] ; [ 0 . 30313277958350887 , 0 . 3284886936945085 , 0 . 17765293951470557 , 0 . 19072558720727698 ] ' [ 0 . 535418872201607 , 0 . 22175248469092H5 , 0 . 1387473651983794 , 0 . 10408127790909263 ] ; [ 0 . 03984739256428134 , 0 . 910572092338926 , 0 . 025918180962683696 , 0 . 02366233413410916 ] ; [ 0 . 22076177732608163 , 0 . 541H2351460984 恚 0 . 1130795928429984 , 0 . 12504627836993562 ] ; [ 0 . 10957192830624707 , 0 . 13457484231170833 , 0 . 6687896620624717 , 0 . 08706356731957281 ] ; [ 0 . 17554754790100902 , 0 . 19809214810685752 , 0 . 502086239183572L 0 . 12427406480856142 ] ; [ 0 . 606406858588263 , 0 . 12861718828566707 , 0 . 10127637842377263 , 0 . 16369957470229726 ] ; [ 0 . 10213141451829646 , 0 . 10393268856850689 , 0 . 12661919128088892 , 0 . 6673167056323077 ]

不过从这些来看就算是分错了,在概率上和正确类别的概率相差也不是很大,可能是因为文章本身区分度就不太好吧!

 

 

六、模型估计

虽说模型看上去效果不错,但是我们也需要一个量化指标来衡量其效果:这个模型的准确率、召回率和F1(3个指标是评判模型预测能力常用的一组指标,没听过的可以先去了解一下)有多高呢?好在spark提供了用于多分类模型评估的类MulticlassClassificationEvaluator,我们就使用这个来测一测这个模型到底怎么样

具体代码如下:

计算机生成了可选文字:/ / 评 估 效 果 new MulticIassCIassificationEvaIuator() evaluator .setPredictionCoI(" prediction") · setMetri cName ( " accu ) evaluator . evaluate (predi cti ons) accuracy p ' 一 讠 " 置 n ( " \ n \ n 准 确 率 : " + accuracy)

这个类比较蛋疼的是每次必须设置参数setMetricName(默认返回f1)以获取不同的评价指标,能不能一次性返回所有指标呢?通过看MulticlassClassificationEvaluator的源码,我们可以看到其实是可以的:

计算机生成了可选文字:@Si nce ( " 2 . G). 0 " ) override def evaluate(dataset: DatasetL_J ) : Double schema dataset · schema SchemaUtiIs.checkColumn 丆 ype(schema, $ (predictionCol) , SchemaUtiIs.checkNumeric 丆 ype(schema, $ ( I abe I CO I ) ) vat predictionAndLabeIs DoubleType) dataset.select(col ( $ (predictionCol) ) , COI ( $ ( I abe I CO I ) ) .cast(DoubleType) ) . rdd.map { case Row(prediction: Double, label: Double) = > (prediction, label) metrics new MulticIassMetrics(predictionAndLabeIs) $ (metricName) match { = > metrics.weightedFMeasure Case Case Case Case " f 1 " "weightedPrecision" = > metrics.weightedpt-ecision "weightedRecaII" = > metrics.weightedRecal I = > metriCS.CICCL.//-CICJ/ " accuracy

evaluate方法中的这个metrics实际上包含了所有的评价指标,但是头疼的是这东西并没有返回。不过!我们可以自己new这个东东来搞啊,于是自己写了以下代码:

计算机生成了可选文字:/ / 评 估 效 果 vat predictionsRDD predictions.select(" prediction" "label") . rdd .map(case Row(prediction: Double, label: Double) = > (prediction, p ' 一 讠 " 亡 忉 ( s " FI 值 : fl " ) label) } vat vat vat vat vat CS new MulticIassMetrics(predictionsRDD) CS . CICCUFCICY racy ghtedPreci sion CS . weightedpr-ecision weightedRecaII metri CS . weightedRecal I metri CS .weightedFMeasure fl = 评 估 结 果 = " 分 类 正 确 率 : $accuracy") printIn( printIn(s p ' 一 讠 " 置 n ( s " \ n 加 权 准 确 率 : $weightedPrecision") p ' 一 讠 " 置 n ( s " 加 权 召 回 率 : $weightedRecaII")

运行结果如下:

计算机生成了可选文字:评 估 结 果 分 类 正 确 率 : 0 . 9425758818703855 加 权 准 确 率 : 0 . 94363852H497564 加 权 召 回 率 : 0 . 9425758818703855 Fl 值 : 0 . 9428456769668427

 

计算机生成了可选文字:评 估 结 果 分 类 正 确 率 : 0 . 9464138499587799 加 权 准 确 率 : 0 . 9468952608381074 加 权 召 回 率 : 0 . 9464138499587799 Fl 值 : 0 . 9465183524852652

 

计算机生成了可选文字:评 估 结 果 分 类 正 确 率 : 0 . 9368770764119602 加 权 准 确 率 : 0 . 9383245778865026 加 权 召 回 率 : 0 . 9368770764119602 Fl 值 : 0 . 9372170070135939

运行了3次,结果都还不错,看来效果确实还可以。准确率基本能达到93%~94%的样子,这也是因为我们数据的类别区分度本身就比较好,如果选择的数据类别比较相近,分错的概率相对来说就比较大了。

 

整个测试的流程到这里基本结束了,一般数据挖掘的整体流程不外乎上面这些:数据清洗转换-->特征选择 -->向量化 -->模型选择与训练 -->模型测试 -->模型评估

 

但是!这仅仅是常规的处理流程,在使用算法的过程中,往往得到的结果并没有这么理想,这时我们需要对处理过程进行调优,接下来讲讲调优的事儿。

 

(本次测试的完整代码在最后面!

 

 

================分割线 ====================

调优 


下面我们将从以下几个方面来进行调优:

  1. 调整训练集大小
  2. 特征选择
  3. 模型调参

 

 

调整训练集大小

训练集的大小将直接决定我们模型的好坏。一般情况下,用于模型训练的训练集应当越大越好(打个比方,如果让你猜一个东西是什么,是不是给的提示越多,越容易才出来?),如果训练集过小,极易导致过拟合(即模型在训练数据上准确率特别高,几乎都可以分对,但是对于新数据,其预测的准确率并不是很高,这时可以称这个模型的泛化能力差。导致过拟合的原因是数据量太少,训练时模型把个别数据的局部特征当成了全局特征来处理,比如说:如果我们就给模型几片带锯齿边缘的树叶,它可能得到的结果是树叶都带锯齿边缘,那么如果给他一片光滑边缘的树叶,模型可能就把它识别成不是树叶)

因此我们首先尝试增大训练集看看效果会不会提升,这里我们将每个类别的数量从1000增加到2000,然后再运行一遍看看效果:

 

计算机生成了可选文字:评 估 结 果 分 类 正 确 率 : 0 . 9363295880149812 加 权 准 确 率 : 0 . 938686889619531 加 权 召 回 率 : 0 . 9363295880149812 Fl 值 : 0 . 9367454893786222

可以看到效果并没有得到提升,这也可能是由于准确率等本来就很高了,数据集的调整很难再有大的提升。其实,从我们选择的特征维数就可以估计,并不会产生过拟合现象(毕竟我们的维度相对于语料词数来说,还是比较少的)

 

 

特征选择

排除了过拟合,通过查看类别概率分布,发现每个文档在每个类别的概率相差不大,这意味着什么?可以猜测应该是我们的特征对样本数据的区分度不够,也就是说,使用目前选择的特征,无法很好地区分出哪些文档是属于哪个类别的!针对这种情况,我们先做以下两步操作:

  • 过滤有效特征
  • 增大特征维数
  • 更改向量化方式

 

过滤有效特征

一般做特征选择时,都会尽量选择区分度大的特征,也就是容易从特征识别出是属于哪个类别(如从“足球”很容易看出应该是体育相关的文章)我们先把词汇表打印出来看看里面到底是些什么东西:

计算机生成了可选文字:/ / 向 量 化 zer new CountVectorizer() · setVocabSi ze ( 2000 ) · setInputCoI ( " removed " ) · setOutputCoI ( " featu res " ) . fit(removedDF) vectori zer .transform(removedDF) vat vectorDF vectori zer . vocabulary . take ( 200 ) · for-each (printIn)

结果:

计算机生成了可选文字:SegTest 會 17 / ℃ 3 / ℃ 2 中 国 年 记 者 表 示 进 行 发 展 美 国 没 有 企 业 比 赛 文 化

好吧!看来确实需要对词汇表做一些处理了,这里面都是些什么啊!我们可以做下面的操作:

  1. 过滤长度为1的词
  2. 过滤数字

代码实现如下:

计算机生成了可选文字:/ / 向 量 化 zer new CountVectorizer() · setVocabSi ze ( 5000 ) · setInputCoI ( " removed " ) · setOutputCoI ( " featu res " ) vectori ze r . fit(removedDF) vat parentVecModeI vat numPattern vat vocabulary vocabulary) parentVecModeI.vocabuIary. flatMap(term = > 1 f (term.length 1 门 term.matches(numpattern. regex) ) { None } else Some(term) vocabulary . take ( 500 ) · for-each (printIn) new CountVectori zerModeI (Identi fi able . randomUTD("cntVec") , vat vecModeI · setInputCoI ( " removed " ) · setOutputCoI ( " featu res " ) vecModeI. transform (removedDF) vat vectorDF

结果:

计算机生成了可选文字:17 / ℃ 3 / ℃ 2 16 . 中 国 记 者 表 示 进 行 美 国 企 业 比 赛 文 化 问 题 国 家 成 为 北 京 市 场 工 作 经 济 世 界 国 际 活 动 建 设 公 司 今 年 曰 电

分类结果:

计算机生成了可选文字:评 估 结 果 分 类 正 确 率 : 0 . 9402439024390243 加 权 准 确 率 : 0 . 9417815316828562 加 权 召 回 率 : 0 . 9402439024390243 Fl 值 : 0 . 9405247251553857

感觉效果来说还不错,如果准确率本身不高的情况下,相信应该会有较大的提升!

 

增加特征维数

特征区分效果不好,会不会是特征数量太少呢?毕竟我们的语料是新闻长文本,每篇文档按200词计算,2000 * 4 *200 = 1600000,总共大于160万词,就算去除重复词、停用词,好歹10W应该是有的吧,我们此前设置词汇表大小才2000,会不会太小?我们试着把词汇表大小设置为5000看看效果怎么样:

 

计算机生成了可选文字:评 估 结 果 分 类 正 确 率 : 0 . 94 096365173288 加 权 准 确 率 : 0 . 9457162906750769 加 权 召 回 率 : 0 . 94 096365173288 Fl 值 : 0 . 94 012028479578

 

计算机生成了可选文字:评 估 结 果 分 类 正 确 率 : 0 . 9411519198664441 加 权 准 确 率 : 0 . 9419525221542917 加 权 召 回 率 : 0 . 9411519198664442 Fl 值 : 0 . 9413522664 17702

 

计算机生成了可选文字:评 估 结 果 分 类 正 确 率 : 0 . 9438202247191011 加 权 准 确 率 : 0 . 94 78625178551 加 权 召 回 率 : 0 . 9438202247191012 Fl 值 : 0 . 94 辅 065525435168

 

看来效果果然有提升,同时,发现一个问题:多次运行得到的结果波动比较大,这个问题可以先思考一下,在下面我们进行模型调参的时候会讲到这个问题。

好了,我们再试试将特征增加到10000维看看效果:

计算机生成了可选文字:评 估 结 果 分 类 正 确 率 : 0 . 9522088353413655 加 权 准 确 率 : 0 . 9528185624758008 加 权 召 回 率 : 0 . 9522088353413654 Fl 值 : 0 . 9523868817557779

 

计算机生成了可选文字:评 估 结 果 分 类 正 确 率 : 0 . 9525032092426188 加 权 准 确 率 : 0 . 9527807780589744 加 权 召 回 率 : 0 . 9525032092426188 Fl 值 : 0 . 9525809489459771

可以看到各项指标还是有提高,我们再讲特征维度提高到15000

计算机生成了可选文字:评 估 结 果 分 类 正 确 率 : 0 . 9550187421907539 加 权 准 确 率 : 0 . 9554923358456239 加 权 召 回 率 : 0 . 9550187421907539 Fl 值 : 0 . 9551509312368984

 

计算机生成了可选文字:评 估 结 果 分 类 正 确 率 : 0 . 9581064097193129 加 权 准 确 率 : 0 . 9584223095127918 加 权 召 回 率 : 0 . 958106409719313 Fl 值 : 0 . 9581592428368053

 

计算机生成了可选文字:评 估 结 果 分 类 正 确 率 : 0 . 9587451654 0761 加 权 准 确 率 : 0 . 9593293695508748 加 权 召 回 率 : 0 . 9587451654 0761 Fl 值 : 0 . 9588980387508712

 

看来特征维数我们设置为1500010000的效果更好。下面我们为了节约时间直接将维度提升到50000试试:

计算机生成了可选文字:评 估 结 果 分 类 正 确 率 : 0 . 9556299706990372 加 权 准 确 率 : 0 . 9564393148312413 加 权 召 回 率 : 0 . 9556299706990372 Fl 值 : 0 . 9557654238153945

 

计算机生成了可选文字:评 估 结 果 分 类 正 确 率 : 0 . 9550187421907539 加 权 准 确 率 : 0 . 9554923358456239 加 权 召 回 率 : 0 . 9550187421907539 Fl 值 : 0 . 9551509312368984

可以看到准确率等反而出现了下降!

 

大家有时间还可以继续进行测试,其实可以发现随着维度的增加,准确率等先是不断的提高,然后反而会降低。降低就是因为产生了过拟合,特征数量太多,模型的泛化能力下降,此时就可以确定准确率最高时的特征维数比较合适。这里由于篇幅问题,15000~50000中间的就省略就不继续测试了。(注意,模型训练的时间会随着特征维度的增加大幅增加,这是因为中间进行向量计算时,其计算量会因为向量长度增加成几何增长,这也就是我们常说的维度灾难

 

改进向量化方式

在上面流程中,我们使用了根据词频来选择向量的特征,这是一种常用的方法,但是还有另一种更常用的方法——TF-IDF,中文叫做文档-逆文档频率,这里的文档频率其实就是我们上面用到的词频,逆文档频率其实就是预料中文档的总数除以包含该词的文档数,然后再取对数,具体公式如下:

 

计算机生成了可选文字:某 个 词 的 词 频 丆 丆 一 ID 丆 = 丆 丆 寻 D 丆 语 料 库 的 文 档 总 数 •log 该 文 档 中 出 现 次 数 最 多 的 词 的 词 频 包 含 该 词 的 文 档 数 + 1

 

这里词频除以出现次数最多的词的词频是为了做标准化处理,消除不同文档长短带来的影响(也可以除以当前文档词的总数),而求逆文档频率的时候将包含词的文档数+1是为了做平滑处理,防止出现除零的情况。

使用TF-IDF与直接使用词频做特征选择最大的不同是TF-IDF选出来的词的区分度更高,因为TF-IDF越高的词,代表这个词更加为当前文档所独有,因此更能代表这篇文档的属性。

 

由于我们这里的各项指标都已经很高了,将TF改成TF-IDF效果不是很大,故不做此步骤的优化!如果你的分类准确率并不是很高,可以替换成TF-IDF做特征选择,效果应该会有所提升,TF-IDFspark中也已经提供,具体使用可参考exampleml目录下的TfIdfExample.scala注意,该示例中使用了HashingTF来提取词频,但是该过程没有生成我们上述的词汇表,也就是说我们不能针对词频提取的特征进行过滤等操作,推荐把此部分更换为我们上述提到的使用CountVectorizer来做词频计算,然后再使用IDF方法提取IDF

 

模型调参

我们这里使用到的LogisticRegression可以设置的参数在上面已经介绍过了,下面我们将针对这些参数进行调整,看看能否提高模型性能。

 

setMaxIter与setTol

这两个参数我们在上面也介绍过了,主要是用来控制模型迭代的次数。不知各位是否还记得,上面我们发现一个问题:使用40次迭代时,多次测试发现结果波动比较大,其实这个原因很明显:因为迭代次数不够,模型还没有收敛到最优,还处于波动状态,因此才会导致这个问题。如果我们设置迭代次数比较多,误差阈值比较小,这样虽然会延长模型训练的时间,但是训练处的模型会更加稳定,性能也会更优!

我们尝试设置setMaxIter=100,setTol=1E-7,看看结果怎么样(这里还是使用15000个特征,每个分类各2000篇文档):

计算机生成了可选文字:10 ℃ 3 / ℃ 4 12 . 57 . 02 10 ℃ 3 / ℃ 4 12 . 57 . 03 10 ℃ 3 / ℃ 4 12 . 57 . 03 10 ℃ 3 / ℃ 4 12 . 57 . 04 10 ℃ 3 / ℃ 4 12 . 57 . 04 10 ℃ 3 / ℃ 4 12 . 57 . 07 10 ℃ 3 / ℃ 4 12 . 57 . 07 10 ℃ 3 / ℃ 4 12 . 57 . 08 10 ℃ 3 / ℃ 4 12 . 57 . 08 10 ℃ 3 / ℃ 4 12 . 57 . 08 INFO OWLQN INFO OWLQN INFO OWLQN INFO OWLQN INFO OWLQN INFO OWLQN INFO OWLQN INFO OWLQN INFO OWLQN INFO OWLQN Val and Grad Step S i z e : 1. 000 Val and Grad Step S i z e : 1. 000 Val and Grad Step S i z e : 0 . 5000 Val and Grad Step S i z e : 1. 000 Val and Grad Converged because 0 . 714657 0 . 714657 0 . 714657 0 . 714657 0 . 714657 仕 el 仕 el 仕 el 仕 el 仕 el & 93s 一 13 ) 1. 12924s 一 06 1. 00s 一 12 ) 1. 01022s 一 06 1. 63s 一 12 ) 1. 21729s 一 06 3 . 59s 一 13 ) & 96492s 一 07 2 . 75s 一 13 ) 6 . 82411s 一 07 gradient converged

 

计算机生成了可选文字:评 估 结 果 分 类 正 确 率 : 0 . 9593094944512947 加 权 准 确 率 : 0 . 9598118213597087 加 权 召 回 率 : 0 . 9593094944512947 Fl 值 : 0 . 959414 96355637

可以看到这次模型确实收敛了,而且各项指标来看确实有所提高!

 

大家还可以对setRegParam和setElasticNetParam进行测试,这两个参数是控制正则化的,用于减小过拟合现象,这里我们就不进行测试了(如果数据本来就稀疏的情况下,增大setElasticNetParam可能会导致准确率下降!原因我们在上面参数说明的时候已经解释过了)

 

 

================分割线 ====================

 

完整代码

package preprocess

 

importorg.apache.log4j.{Level, Logger}

importorg.apache.spark.ml.classification.LogisticRegression

importorg.apache.spark.ml.feature.{CountVectorizer, CountVectorizerModel,StopWordsRemover}

importorg.apache.spark.ml.util.Identifiable

importorg.apache.spark.mllib.evaluation.MulticlassMetrics

importorg.apache.spark.sql.{DataFrame, Row, SparkSession}

 

/**

  * Created by yhao on 2017/2/11.

  */

objectLRClassificationTest {

 

  def main(args: Array[String]): Unit = {

   Logger.getLogger("org").setLevel(Level.WARN)

    //   HanLP.Config.enableDebug()

 

    val spark = SparkSession

      .builder

      .master("local[2]")

      .appName("Segment Test")

      .getOrCreate()

 

    val filePath ="G:/test/classnews"

    val stopwordPath ="dictionaries/hanlp/data/dictionary/stopwords.txt"

 

 

    //数据清洗、转换

    val textDF = clean(filePath, spark)

 

 

    //分词

    val segmenter = new Segmenter(spark)

     .setSegmentType("StandardSegment")

      .enableNature(false)

      .setInputCol("content")

      .setOutputCol("tokens")

    val segDF = segmenter.transform(textDF)

 

 

    //去除停用词

    val stopwordArray =spark.sparkContext.textFile(stopwordPath).collect()

    val remover = new StopWordsRemover()

      .setStopWords(stopwordArray)

      .setInputCol("tokens")

      .setOutputCol("removed")

    val removedDF = remover.transform(segDF)

 

 

    //向量化

    val vectorizer = new CountVectorizer()

      .setVocabSize(15000)

      .setInputCol("removed")

      .setOutputCol("features")

    val parentVecModel =vectorizer.fit(removedDF)

 

    val numPattern = "[0-9]+".r

    val vocabulary =parentVecModel.vocabulary.flatMap{term =>

      if (term.length == 1 ||term.matches(numPattern.regex)) None else Some(term)

    }

 

    val vecModel = newCountVectorizerModel(Identifiable.randomUID("cntVec"), vocabulary)

    .setInputCol("removed")

    .setOutputCol("features")

    val vectorDF =vecModel.transform(removedDF)

 

    val Array(train, predict) =vectorDF.randomSplit(Array(0.7, 0.3))

 

 

    //LR分类模型训练

    train.persist()

    val lr = new LogisticRegression()

      .setMaxIter(100)

      .setRegParam(0.2)

      .setElasticNetParam(0.05)

      .setLabelCol("label")

      .setFeaturesCol("features")

      .fit(train)

    train.unpersist()

 

 

    //LR预测

    val predictions = lr.transform(predict)

//    predictions.select("prediction","label", "probability").show(100, truncate = false)

 

    //评估效果

    val predictionsRDD =predictions.select("prediction", "label")

      .rdd.

      map { case Row(prediction: Double, label:Double) => (prediction, label) }

 

    val metrics = newMulticlassMetrics(predictionsRDD)

    val accuracy = metrics.accuracy

    val weightedPrecision =metrics.weightedPrecision

    val weightedRecall = metrics.weightedRecall

    val f1 = metrics.weightedFMeasure

 

    println("\n\n=========评估结果==========")

    println(s"分类正确率:$accuracy")

    println(s"\n加权准确率:$weightedPrecision")

    println(s"加权召回率:$weightedRecall")

    println(s"F1值:$f1")

 

    spark.stop()

  }

 

 

  def clean(filePath: String, spark:SparkSession): DataFrame = {

    import spark.implicits._

    val textDF =spark.sparkContext.textFile(filePath).flatMap { line =>

      val fields =line.split("\u00EF")

      if (fields.length > 3) {

        val categoryLine = fields(0)

        val categories =categoryLine.split("\\|")

        val category = categories.last

 

        var label = -1.0

        if (category.contains("文化"))label = 0.0

        else if (category.contains("财经"))label = 1.0

        else if (category.contains("军事"))label = 2.0

        else if (category.contains("体育"))label = 3.0

        else {}

 

        val title = fields(1)

        val time = fields(2)

        val content = fields(3)

        if (label > -1) Some(label, title,time, content) else None

      } else None

    }.toDF("label","title", "time", "content")

 

    textDF

  }

}

 

代码截图

计算机生成了可选文字:object LRCtassificationTest { def main(args: Array[String]) : Unit Logger .getLogger(.org/) .setLevet (Level.WARN) H a n L P · Config. enableDebug() SparkSession vat spark . b u i I de r .master("tocat[2] " ) .appName("Segment Test") .getOrCreate() vat fitePath " G : /test/ctassnews" "dictionaries/bAOLp/data/dictionar-y/>t9PYY9.cA>.txt" vat $9PY9.@Path / / 数 据 清 洗 、 转 换 clean(fitepath, vat textDF / / 分 词 spark) new Segmenter(spark) · setSegmentType ( " Standa " ) · (false) · setInputCot ( " content") · setOutputCot ( " tokens " ) segmenter . t r a n s fo r m (textDF) vat segDF





2 0
原创粉丝点击