Spark数据挖掘-基于 LSA 隐层语义分析理解APP描述信息(1) - clebeg的个人空间 - 开源中国

标签: | 发表时间:2018-08-11 18:55 | 作者:
出处:https://my.oschina.net


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 模型,尽情期待。



相关 [spark 数据挖掘 lsa] 推荐:

Spark数据挖掘-基于 LSA 隐层语义分析理解APP描述信息(1) - clebeg的个人空间 - 开源中国

- -
Spark数据挖掘-基于 LSA 隐层语义分析理解APP描述信息(1). 结构化数据处理比较直接,然而非结构化数据(比如:文本、语音)处理就比较具有挑战. 对于文本现在比较成熟的技术是搜索引擎,它可以帮助人们从给定的词语中快速找到包含关键词的文本. 但是,一些情况下人们希望找到某一个概念的文本,而不关心文本里面是否包含某个关键词.

Spark数据挖掘-基于 LSA 隐层语义分析理解APP描述信息(2) - clebeg的个人空间 - 开源中国

- -
Spark数据挖掘-基于 LSA 隐层语义分析理解APP描述信息(2). Spark 通过调用 RowMatrix 的 computeSVD 方法会得到三个重要的矩阵 U、S、V , 而且:原始矩阵 近似等于 U * S * V. V: 每一行表示单词,列表示概念,矩阵的值表示单词在概念里面的重要程度.

Latent Semantic Analysis(LSA) - CSDN博客

- -
Latent Semantic Analysis(LSA)中文翻译为潜语义分析,也被叫做Latent Semantic Indexing ( LSI ). 意思是指通过分析一堆(不止一个)文档去发现这些文档中潜在的意思和概念,什么叫潜在的意思. 我第一次看到这个解释,直接懵逼. 假设每个词仅表示一个概念,并且每个概念仅仅被一个词所描述,LSA将非常简单(从词到概念存在一个简单的映射关系).

数据挖掘是神马?

- - 互联网分析
1、数据挖掘需要‘神马样’的流程.  2、哥,有没有详细点的,来个给力的. 4、数据在统计意义上有哪些类型. 9、知道这些工具不知道如何在工作中用呀. 11、还有没有更人性化、智能化的展现. 12、上面这图看起来很给力,背后很复杂吧.  16、转载的留个来源 ,毕竟是我辛苦收集和想出来的,谢谢. 忘记“大数据”,从“中数据”开始.

这就是数据挖掘

- - 互联网分析
当今数据库的容量已经达到上万亿的水平(T)— 1,000,000,000,000个字节. 在这些大量数据的背后隐藏了很多具有决策意义的信息,那么怎么得到这些“知识”呢. 也就是怎样通过一颗颗的树木了解到整个森林的情况. 计 算机科学对这个问题给出的最新回答就是:数据挖掘,在“数据矿山”中找到蕴藏的“知识金块”,帮助企业减少不必要投资的同时提高资金回报.

关于数据挖掘

- - 牛国柱
以下内容来自网络,关于数据挖掘的一些最基本的知识. 数据挖掘是对一系列数据进行分析和挖掘的方法的统称,在精准营销领域,最常用的数据挖掘方法主要包括以下三类:分类、聚类、关联. 分类(Classify)属于预测性模型. 分类模型的构建需要“训练样本”,训练样本中的每一个个体的类别必须是明确的. 分类模型的特征变量一般称为“自变量”,又叫“预测变量”,类别变量称为“目标变量”.

数据挖掘与Taco Bell编程

- everfly - 译言-每日精品译文推荐
来源Data Mining and Taco Bell Programming. Programmer Ted Dziuba suggests an alternative to traditional program that he called "Taco Bell Programming." The Taco Bell chain creates multiple menu items from about eight different ingredients.

使用Weka进行数据挖掘

- - 搜索研发部官方博客
数据挖掘、机器学习这些字眼,在一些人看来,是门槛很高的东西. 诚然,如果做算法实现甚至算法优化,确实需要很多背景知识. 但事实是,绝大多数数据挖掘工程师,不需要去做算法层面的东西. 他们的精力,集中在特征提取,算法选择和参数调优上. 那么,一个可以方便地提供这些功能的工具,便是十分必要的了. 而weka,便是数据挖掘工具中的佼佼者.

数据挖掘 - 分类算法比较

- - IBM developerWorks 中国 : 文档库
随着计算能力、存储、网络的高速发展,人类积累的数据量正以指数速度增长. 对于这些数据,人们迫切希望从中提取出隐藏其中的有用信息,更需要发现更深层次的规律,对决策,商务应用提供更有效的支持. 为了满足这种需求,数据挖掘技术的得到了长足的发展,而分类在数据挖掘中是一项非常重要的任务,目前在商业上应用最多.

数据挖掘分类技术

- - CSDN博客云计算推荐文章
从分类问题的提出至今,已经衍生出了很多具体的分类技术. 下面主要简单介绍四种最常用的分类技术,不过因为原理和具体的算法实现及优化不是本书的重点,所以我们尽量用应用人员能够理解的语言来表述这些技术. 而且我们会在第4章再次给读者讲述分类算法和相关原理. 在我们学习这些算法之前必须要清楚一点,分类算法不会百分百准确.