文档章节

Spark数据挖掘-基于 LSA 隐层语义分析理解APP描述信息(1)

clebeg
 clebeg
发布于 2015/11/19 09:47
字数 2411
阅读 1345
收藏 34

Spark数据挖掘-基于 LSA 隐层语义分析理解APP描述信息(1)

1 前言

结构化数据处理比较直接,然而非结构化数据(比如:文本、语音)处理就比较具有挑战。对于文本现在比较成熟的技术是搜索引擎,它可以帮助人们从给定的词语中快速找到包含关键词的文本。但是,一些情况下人们希望找到某一个概念的文本,而不关心文本里面是否包含某个关键词。这种情况下应该如何是好?
隐语义分析(Latent Semantic Analysis,简称:LSA)是一种寻找更好的理解语料库中词和文档之间关系的自然语言和信息检索的技术。它试图通过语料库提取一系列概念。每个概念对应一系列单词并且通常对应语料库中讨论的一个主题。先抛开数据而言,每一个概念由三个属性构成:

  • 每个文档与概念之间的相关性
  • 每个单词与概念之间的相关性
  • 概念描述数据集变化程度(方差)的重要性得分

比如:LSA可能会发现某个概念和单词“股票”、“炒股”有很高的相关性并且和“互联网金融系列文章”有很高的相关性。通过选择最重要的概念,LSA可以去掉一些噪音数据。 在很多场合都可以使用这种简洁的表示,比如计算词与词、文档与文档、词与文档的相似性。通过LSA得到的关于概念的得分,可以对语料库有更加深入的理解,而不只是简单的计算单词或者共现词。这种相似性度量可以解决同义词查询、文本按照相同主题聚类、给文本添加标签等。 LSA主要用到的技术就是奇异值分解。首先得到词-文档重要性矩阵(一般是TF-IDF矩阵),然后利用svd奇异值分解技术得到原矩阵近似相等的三个矩阵的乘积:SVD,其中 S 可以看出概念与文件的关系,V 表示概念的重要程度,D 表示概念与词的关系。
下面将完整讲述通过爬虫抓取豌豆荚App信息之后,如何利用Spark读取数据,对文本分词、去除噪音词、将数据转换为数字格式、最后计算SVD并且解释如何理解和使用得到的结果。

2 数据集(豌豆荚APP数据)

爬虫不是本文的重点,有兴趣的读者可以查看作者构建的开源爬虫nlp-spider,本文集中抓取的是豌豆荚关于金融理财大类的数据。只提取了三个信息:package_name(包名),description(app 描述信息),categories(类别名),示例如下:

com.zmfz.app  "影视制片过程管理系统,对演员,设备,道具,剧本进行分类管理"   [{level: 1, name: "金融理财"},{level: 2, name: "记账"}]
cn.fa.creditcard  "办信用卡,方便快捷"  [{level: 1, name: "金融理财"},{level: 2, name: "银行"}]

3 数据清洗

public static void clearWandoujiaAppData(
      String categoryFile, //确定哪些类的数据才需要
      String filePath,     //保存抓取数据的文件
      String filedsTerminated //文件的分割符号
) {
  List<String> changeLines;
  File wdj = new File(filePath);
  if (!wdj.exists()) {
      LOGGER.error("file:" + wdj.getAbsolutePath() + " not exists, please check!");
  }
  try {
      List<String> categories = FileUtils.readLines(new File(categoryFile));
      List<String> lines = FileUtils.readLines(wdj, fileEncoding);
      changeLines = new ArrayList<String>(lines.size()*2);
      for (String line : lines) {
          String[] cols = StringUtils.split(line, filedsTerminated);
          //去掉样本中格式错误的
          if (cols.length != 3) {
              LOGGER.warn("line:" + line + ", format error!");
              continue;
          }
          //去掉描述信息为空白、包含乱码、不包含中文、短文本
          if (StringUtils.isBlank(cols[1]) || StringUtils.isEmpty(cols[1])){
              LOGGER.warn("line:" + line + ", content all blank!");
              continue;
          }
          if (StringUtils.contains(cols[1], "?????")){
              LOGGER.warn("line:" + line + ", content contains error code!");
              continue;
          }
          if (!isContainsChinese(cols[1])){
              LOGGER.warn("line:" + line + ", content not contains chinese word!");
              continue;
          }
          if (cols[1].length() <= 10){
              LOGGER.warn("line:" + line + ", content length to short!");
              continue;
          }

          List<String> cates = JsonUtil.jsonParseAppCategories(cols[2], "name");
          if (cates.contains("金融理财")) {
              if (isForClass) {
                  for (String cate : cates) {
                      if (StringUtils.equals(cate, "金融理财"))
                          continue;
                      else {
                          if (categories.contains(cate)) {
                              String[] newLines = new String[]{cols[0], StringUtils.trim(cols[1]), cate};
                              changeLines.add(StringUtil.mkString(newLines, filedsTerminated));
                          }
                      }
                  }
              } else {
                  String[] newLines = new String[]{cols[0], cols[1]};
                  changeLines.add(StringUtil.mkString(newLines, filedsTerminated));
              }
          }
      }
      FileUtils.writeLines(new File(wdj.getParent(), wdj.getName() + ".clear"), changeLines);
  } catch (IOException e) {
      e.printStackTrace();
  }
}

上面会清洗掉不需要的数据,只保留金融理财的数据,注意上面使用的类的来源如下:

  • FileUtils common-io
  • StringUtils common-lang
  • JsonUtil fastjson

4 分词

分词主要使用的是 HanLP(https://github.com/hankcs/HanLP) 这个自然语言处理工具包,下面贴出关键代码:

public static List<String> segContent(String content) {
    List<String> words = new ArrayList<String>(content.length());
    List<Term> terms = HanLP.segment(content);
    for (Term term : terms) {
        String word = term.word;
        //单词必须包含中文而且长度必须大于2
        if (word.length() < 2 || word.matches(".*[a-z|A-Z].*"))
            continue;
        String nature = term.nature.name();
        //词性过滤
        if (nature.startsWith("a") ||
                nature.startsWith("g") ||
                nature.startsWith("n") ||
                nature.startsWith("v")
                ) {
            //停用词去除
            if (!sw.isStopWord(word))
                words.add(word);
        }

    }
    return words;
}

5 Spark加载数据SVD计算

SVD计算之前必须得到一个矩阵,本文使用的是TF-IDF矩阵,TF-IDF矩阵可以理解如下:

  • TF: token frequent 指的是每个单词在文档中出现的频率 = 单词出现的个数/文档中总单词数
  • IDF:inverse document frequent 指的是逆文档频率 = 1/文档频率 = 总文档数量/单词在多少不同文档中出现的次数

TF-IDF = TF*LOG(IDF)

下面给出整个计算的详细流程,代码都有注释,请查看:

object SVDComputer {
  val rootDir = "your_data_dir";
  //本地测试
  def main(args: Array[String]) {
    val conf = new SparkConf().setAppName("SVDComputer").setMaster("local[4]")
    val sc = new SparkContext(conf)
    val rawData = sc.textFile(rootDir + "/your_file_name")
    val tables = rawData.map {
      line =>
        val cols = line.split("your_field_seperator")
        val appId = cols(0)
        val context = cols(1)
        (appId, context.split(" "))
    }
    val numDocs = tables.count()
    //得到每个单词在文章中的次数 -> 计算 tf
    val dtf = docTermFreqs(tables.values)
    val docIds = tables.keys.zipWithIndex().map{case (key, value) => (value, key)}.collect().toMap
    dtf.cache()
    //得到单词在所有文档中出现的不同次数->计算 idf
    val termFreq = dtf.flatMap(_.keySet).map((_, 1)).reduceByKey(_ + _)

    //计算 idf
    val idfs = termFreq.map {
      case (term, count) => (term, math.log(numDocs.toDouble/count))
    }.collect().toMap

    //将词编码 spark 不接受字符串的 id
    val termIds = idfs.keys.zipWithIndex.toMap
    val idTerms = termIds.map{case (term, id) => (id -> term)}
    val bIdfs = sc.broadcast(idfs).value
    val bTermIds = sc.broadcast(termIds).value
    //利用词频(dtf),逆文档频率矩阵(idfs)计算tf-idf

    val vecs = buildIfIdfMatrix(dtf, bIdfs, bTermIds)
    val mat = new RowMatrix(vecs)
    val svd = mat.computeSVD(1000, computeU = true)

    println("Singular values: " + svd.s)
    val topConceptTerms = topTermsInTopConcepts(svd, 10, 10, idTerms)
    val topConceptDocs = topDocsInTopConcepts(svd, 10, 10, docIds)
    for ((terms, docs) <- topConceptTerms.zip(topConceptDocs)) {
      println("Concept terms: " + terms.map(_._1).mkString(", "))
      println("Concept docs: " + docs.map(_._1).mkString(", "))
      println()
    }
    //dtf.take(10).foreach(println)
  }

  /**
    *
    * @param lemmatized
    * @return
    */
  def  docTermFreqs(lemmatized: RDD[Array[String]]):
        RDD[mutable.HashMap[String, Int]] = {
    val dtf = lemmatized.map(terms => {
      val termFreqs = terms.foldLeft(new mutable.HashMap[String, Int]){
        (map, term) => {
          map += term -> (map.getOrElse(term, 0) + 1)
          map
        }
      }
      termFreqs
    })
    dtf
  }

  /**
    * 建立 tf-idf 矩阵
    * @param termFreq
    * @param bIdfs
    * @param bTermIds
    * @return
    */
  def buildIfIdfMatrix(termFreq: RDD[mutable.HashMap[String, Int]],
                       bIdfs: Map[String, Double],
                       bTermIds: Map[String, Int]) = {
    termFreq.map {
      tf =>
        val docTotalTerms = tf.values.sum
        //首先过滤掉没有编码的 term
        val termScores = tf.filter {
          case (term, freq) => bTermIds.contains(term)
        }.map {
          case (term, freq) => (bTermIds(term),
            bIdfs(term) * freq / docTotalTerms)
        }.toSeq
        Vectors.sparse(bTermIds.size, termScores)
    }
  }


  def topTermsInTopConcepts(svd: SingularValueDecomposition[RowMatrix, Matrix], numConcepts: Int,
                            numTerms: Int, termIds: Map[Int, String]): Seq[Seq[(String, Double)]] = {
    val v = svd.V
    val topTerms = new ArrayBuffer[Seq[(String, Double)]]()
    val arr = v.toArray
    for (i <- 0 until numConcepts) {
      val offs = i * v.numRows
      val termWeights = arr.slice(offs, offs + v.numRows).zipWithIndex
      val sorted = termWeights.sortBy(-_._1)
      topTerms += sorted.take(numTerms).map{case (score, id) => (termIds(id), score)}
    }
    topTerms
  }

  def topDocsInTopConcepts(svd: SingularValueDecomposition[RowMatrix, Matrix], numConcepts: Int,
                           numDocs: Int, docIds: Map[Long, String]): Seq[Seq[(String, Double)]] = {
    val u  = svd.U
    val topDocs = new ArrayBuffer[Seq[(String, Double)]]()
    for (i <- 0 until numConcepts) {
      val docWeights = u.rows.map(_.toArray(i)).zipWithUniqueId
      topDocs += docWeights.top(numDocs).map{case (score, id) => (docIds(id), score)}
    }
    topDocs
  }

}

上面代码的运行结果如下所示,只给出了前10个概念最相关的十个单词和十个文档:

Concept terms: 彩票, 记账, 开奖, 理财, 中奖, 大方, 大乐透, 竞彩, 收入, 开发
Concept docs: com.payegis.mobile.energy, audaque.SuiShouJie, com.cyht.dcjr, com.xlltkbyyy.finance, com.zscfappview.jinzheng.wenjiaosuo, com.wukonglicai.app, com.goldheadline.news, com.rytong.bank_cgb.enterprise, com.xfzb.yyd, com.chinamworld.bfa

Concept terms: 茂日, 厕所, 洗浴, 围脖, 邮局, 乐得, 大王, 艺龙, 开开, 茶馆
Concept docs: ylpad.ylpad, com.xh.xinhe, com.jumi, com.zjzx.licaiwang168, com.ss.app, com.yingdong.zhongchaoguoding, com.noahwm.android, com.ylink.MGessTrader_QianShi, com.ssc.P00120, com.monyxApp

Concept terms: 彩票, 开奖, 投注, 中奖, 双色球, 福彩, 号码, 彩民, 排列, 大乐透
Concept docs: ssq.random, com.wukonglicai.app, com.cyht.dcjr, com.tyun.project.app104, com.kakalicai.lingqian, com.wutong, com.icbc.android, com.mzmoney, com.homelinkLicai.activity, com.pingan.lifeinsurance

Concept terms: 开户, 证券, 行情, 股票, 交易, 炒股, 资讯, 基金, 期货, 东兴
Concept docs: com.byp.byp, com.ea.view, com.hmt.jinxiangApp, cn.com.ifsc.yrz, com.cgbsoft.financial, com.eeepay.bpaybox.home.htf, com.gy.amobile.person, wmy.android, me.xiaoqian, cn.eeeeeke.iehejdleieiei

Concept terms: 贷款, 彩票, 开户, 抵押, 证券, 信用, 银行, 申请, 小额, 房贷
Concept docs: com.silupay.silupaymr, com.zscfandroid_guoxinqihuo, com.jin91.preciousmetal, com.manqian.youdan.activity, com.zbar.lib.yijiepay, com.baobei.system, com.caimi.moneymgr, com.thinkive.mobile.account_yzhx, com.qianduan.app, com.bocop.netloan

Concept terms: 支付, 理财, 银行, 信用卡, 刷卡, 金融, 收益, 商户, 硬件, 收款
Concept docs: com.unicom.wopay, com.hexun.futures, com.rapidvalue.android.expensetrackerlite, OTbearStockJY.namespace, gupiao.caopanshou.bigew, com.yucheng.android.yiguan, com.wzlottery, com.zscfappview.shanghaizhongqi, com.wareone.tappmt, com.icbc.echannel

Concept terms: 行情, 理财, 投资, 比特币, 黄金, 汇率, 资讯, 原油, 财经, 贵金属
Concept docs: com.rytong.bankps, com.souyidai.investment.android, com.css.sp2p.invest.activity, com.lotterycc.android.lottery77le, com.sub4.caogurumen, com.feifeishucheng.canuciy, com.hundsun.zjfae, cn.cctvvip, com.mr.yironghui.activity, org.zywx.wbpalmstar.widgetone.uex11328838

Concept terms: 信用卡, 行情, 刷卡, 硬件, 汇率, 比特币, 支付, 交易, 商户, 黄金
Concept docs: com.unicom.wopay, com.hexun.futures, gupiao.caopanshou.bigew, com.rytong.bankps, com.souyidai.investment.android, com.feifeishucheng.canuciy, com.css.sp2p.invest.activity, org.zywx.wbpalmstar.widgetone.uex11328838, com.net.caishi.caishilottery, com.lotterycc.android.lottery77le

Concept terms: 行情, 刷卡, 硬件, 记账, 贷款, 交易, 资讯, 支付, 易贷, 比特币
Concept docs: com.unicom.wopay, com.shengjingbank.mobile.cust, com.rytong.bankps, com.souyidai.investment.android, com.silupay.silupaymr, aolei.sjcp, com.css.sp2p.invest.activity, com.megahub.brightsmart.fso.mtrader.activity, com.manqian.youdan.activity, gupiao.caopanshou.bigew

Concept terms: 刷卡, 硬件, 支付, 汇率, 换算, 贷款, 收款, 商户, 货币, 易贷
Concept docs: com.unicom.wopay, OTbearStockJY.namespace, com.yucheng.android.yiguan, com.wzlottery, com.zscfappview.shanghaizhongqi, com.junanxinnew.anxindainew, com.bitjin.newsapp, com.feifeishucheng.canuciy, org.zywx.wbpalmstar.widgetone.uexYzxShubang, com.qsq.qianshengqian

从上面的结果可以看出,效果还行,这个和语料库太少也有关系。每个概念都比较集中一个主题,比如第一个概念关心的是彩票等。具体应用就不展开了。

6 借题发挥

基于 SVD 的 LSA 技术对理解文档含义还是有局限的,个人认为这方面效果更好的技术应该是 LDA 模型,接下来也有有专门的文章讲解基于 Spark 的 LDA 模型,尽情期待。

个人微信公众号

欢迎关注本人微信公众号,会定时发送关于大数据、机器学习、Java、Linux 等技术的学习文章,而且是一个系列一个系列的发布,无任何广告,纯属个人兴趣。
Clebeg能量集结号

© 著作权归作者所有

clebeg
粉丝 45
博文 40
码字总数 40057
作品 0
广州
程序员
私信 提问
从Hadoop到Spark的架构实践

当下,Spark已经在国内得到了广泛的认可和支持:2014年,Spark Summit China在北京召开,场面火爆;同年,Spark Meetup在北京、上海、深圳和杭州四个城市举办,其中仅北京就成功举办了5次,内...

Emilypz
2015/10/10
1K
0
新手入门:Spark 部署实战入门

Spark简介 整体认识 Apache Spark是一个围绕速度、易用性和复杂分析构建的大数据处理框架。最初在2009年由加州大学伯克利分校的AMPLab开发,并于2010年成为Apache的开源项目之一。 Spark在整...

景龙Edward
2016/07/05
16.2K
5
spark 查看 job history 日志

SPARKHOME/conf 下: spark-defaults.conf 增加如下内容 spark.eventLog.enabled true spark.eventLog.dir hdfs://master:8020/var/log/spark spark.eventLog.compress true spark-env.sh 增加......

stark_summer
2015/06/11
146
0
基于Spark的机器学习实践 (二) - 初识MLlib

1 MLlib概述 1.1 MLlib 介绍 ◆ 是基于Spark core的机器学习库,具有Spark的优点 ◆ 底层计算经过优化,比常规编码效率往往要高 ◆ 实现了多种机器学习算法,可以进行模型训练及预测 1.2 Spark ...

javaedge
04/09
0
0
『 Spark 』3. spark 编程模式

写在前面 本系列是综合了自己在学习spark过程中的理解记录 + 对参考文章中的一些理解 + 个人实践spark过程中的一些心得而来。写这样一个系列仅仅是为了梳理个人学习spark的笔记记录,所以一...

董黎明
2018/06/11
42
0

没有更多内容

加载失败,请刷新页面

加载更多

Centos6.6 安装ffmpeg视频工具

1.安装前置工具 yum -y install gcc cc cl libmpc*//后续失败的话,自己补充自己的缺少的包 2.安装yasm 1)下载wget http://www.tortall.net/projects/yasm/releases/yasm-1.3.0.tar.gz...

小海bug
20分钟前
4
0
电商的支付风控怎么玩?

qwfys
24分钟前
6
0
用什么来做用户行为分析?七个实用工具推荐给你

当企业进入数据化管理阶段之后,就不得不对用户进行行为数据分析,当然其他的包括用户画像、趋势分析等等,都是现在企业经常要进行的营销分析,因此选一个好的数据分析工具是很重要的。 而现...

朕想上头条
25分钟前
5
0
Java mysql连接

import java.sql.DriverManager; import java.sql.SQLException; import com.mysql.jdbc.Connection; public class T { public static void main(String[] args) throws SQLException, Insta......

林词
26分钟前
4
0
Select 选择器 的一些官网没有的用法

一、拼接字符串 <el-option v-for="(item, index) in reagentOptions" :key="index" :label="item.sjlxmc+' '+item.sjbm" :va......

沉迷代码我爱学习
26分钟前
3
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部