写给开发者的机器学习指南(十)

An attempt at rank prediction for topselling books using text regression

在基于高度和性别预测权重的示例中,我们引入了线性回归的概念。但是,有时人们会想要对非数字数据(如文本)应用回归。在这个例子中,我们将展示如何通过试图预测O'Reilly的前100本销售书籍来完成文本回归。 此外,通过此示例,我们还将显示,对于这种特殊情况,使用文本回归是无效的。 原因是数据根本不包含我们的测试数据的信号。然而,这不会使此示例无用,因为在实际使用的数据中可能存在实际信号,然后可以使用此处解释的文本回归检测。

我们在这个例子中使用的数据文件可以在这里下载。 除了Smile库之外,在这个例子中,我们还将使用Scala-csv库处理csv包含逗号的字符串。让我们从获取我们需要的数据开始:

object TextRegression {
  def main(args:Array[String]): Unit = {
    //Get theexample data
      val basePath= "/users/.../TextRegression_Example_4.csv"
      val testData= getDataFromCSV(new File(basePath))
  }
  defgetDataFromCSV(file: File) : List[(String,Int,String)]= {
    val reader =CSVReader.open(file)
    val data =reader.all()
    val documents =data.drop(1).map(x => (x(1),x(3)toInt,x(4)))
    returndocuments
  }
}

我们现在有O'Reilly的前100名销售书的标题,等级和长长的描述。然而,当我们想做某种形式的回归时,我们需要数值数据。 这就是为什么我们将构建一个文档术语矩阵(DTM)。 请注意,此DTM类似于我们在垃圾邮件分类示例中构建的术语文档矩阵(TDM)。 它的不同之处在于,我们存储包含该文档中的术语的文档记录,与存储词语的记录的TDM相反,其中包含该词语可用的文档的列表。我们自己实现了如下:

import java.io.File
import scala.collection.mutable
class DTM {
  var records:List[DTMRecord] = List[DTMRecord]()
  var wordList:List[String] = List[String]()
  defaddDocumentToRecords(documentName: String, rank: Int, documentContent: String)= {
    //Find a recordfor the document
    val record =records.find(x => x.document == documentName)
    if(record.nonEmpty) {
      throw newException("Document already exists in the records")
    }
    var wordRecords= mutable.HashMap[String, Int]()
    valindividualWords = documentContent.toLowerCase.split(" ")
   individualWords.foreach { x =>
      valwordRecord = wordRecords.find(y => y._1 == x)
      if(wordRecord.nonEmpty) {
        wordRecords+= x -> (wordRecord.get._2 + 1)
      }
      else {
        wordRecords+= x -> 1
        wordList =x :: wordList
      }
    }
    records = newDTMRecord(documentName, rank, wordRecords) :: records
  }
  defgetStopWords(): List[String] = {
    val source =scala.io.Source.fromFile(newFile("/Users/.../stopwords.txt"))("latin1")
    val lines =source.mkString.split("\n")
    source.close()
    returnlines.toList
  }
  defgetNumericRepresentationForRecords(): (Array[Array[Double]], Array[Double]) = {
    //First filterout all stop words:
    val StopWords =getStopWords()
    wordList =wordList.filter(x => !StopWords.contains(x))
    var dtmNumeric= Array[Array[Double]]()
    var ranks =Array[Double]()
    records.foreach{ x =>
      //Add therank to the array of ranks
      ranks = ranks:+ x.rank.toDouble
      //And createan array representing all words and their occurrences 
      //for thisdocument:
      vardtmNumericRecord: Array[Double] = Array()
     wordList.foreach { y =>
        valtermRecord = x.occurrences.find(z => z._1 == y)
        if(termRecord.nonEmpty) {
         dtmNumericRecord = dtmNumericRecord :+ termRecord.get._2.toDouble
        }
        else {
         dtmNumericRecord = dtmNumericRecord :+ 0.0
        }
      }
      dtmNumeric =dtmNumeric :+ dtmNumericRecord
    }
    return(dtmNumeric, ranks)
  }
}
class DTMRecord(val document : String,
                valrank : Int,
                varoccurrences : mutable.HashMap[String,Int]
                )

如果你注意一下这个实现,你会看到有一个方法叫做getgetNumericRepresentationForRecords():(Array [Array[Double]],Array [Double])。 此方法返回一个以第一个参数为一个元组的矩阵,其中每行代表一个文档,每个列代表DTM文档的完整词汇表中的一个单词。 注意,第一个表中的双精度表示单词的出现次数。第二个参数是包含属于来自第一个表的记录的所有等级的数组。我们现在可以扩展我们的主代码,使得我们得到所有文档的数字表示如下:

val documen3)tTermMatrix = new DTM()
testData.foreach(x =>documentTermMatrix.addDocumentToRecords(x._1,x._2,x._)

通过从文本到数值的转换,我们可以打开我们的回归工具箱了。我们在预测基于身高的体重的示例中使用了普通最小二乘法(OLS),但是这次我们将使用最小绝对收缩和选择算子(Lasso)回归。 这是因为我们可以给这个回归方法一个特定的lambda,代表一个惩罚值。 该惩罚值允许LASSO算法选择相关特征(字),同时丢弃一些其他特征(字)。

在我们的案例中,Lasso执行的这个特征选择非常有用,因为文档描述中使用了大量的词。 Lasso将尝试使用这些单词的理想子集作为特征,而当应用OLS时,将使用所有单词,并且运行时间将是非常长的。此外,smile的OLS实现检测出秩很低。 这是维度诅咒之一。

然而,我们需要找到一个最佳的lambda,因此,我们应该尝试使用交叉验证几个lambda。 我们将这样做:

for (i <- 0 until cv.k) {
      //Split offthe training datapoints and classifiers from the dataset
      valdpForTraining = numericDTM
        ._1
        .zipWithIndex
        .filter(x=> cv
                   .test(i)
                   .toList
                   .contains(x._2)
                )
        .map(y=> y._1)
      valclassifiersForTraining = numericDTM
        ._2
       .zipWithIndex
        .filter(x=> cv
                   .test(i)
                   .toList
                   .contains(x._2)
                )
        .map(y=> y._1)
      //And thecorresponding subset of data points and their classifiers for testing
      val dpForTesting= numericDTM
        ._1
       .zipWithIndex
        .filter(x=> !cv
                   .test(i)
                   .contains(x._2)
                )
        .map(y=> y._1)
      valclassifiersForTesting = numericDTM
        ._2
       .zipWithIndex
        .filter(x=> !cv
                   .test(i)
                   .contains(x._2)
                )
        .map(y=> y._1)
      //These arethe lambda values we will verify against
      val lambdas:Array[Double] = Array(0.1, 0.25, 0.5, 1.0, 2.0, 5.0)
     lambdas.foreach { x =>
        //Define anew model based on the training data and one of the lambda's
        val model =new LASSO(dpForTraining, classifiersForTraining, x)
        //Compute the RMSE for this model withthis lambda
        val results= dpForTesting.map(y => model.predict(y)) zip classifiersForTesting
        val RMSE =Math
           .sqrt(results
                   .map(x => Math.pow(x._1 - x._2, 2)).sum /
                                 results.length
                       )
       println("Lambda: " + x + " RMSE: " + RMSE)
      }
    }

多次运行此代码使得RMSE在36和51之间变化。这意味着我们将执行的排名预测将至少缺少36个等级。 考虑到我们试图预测前100个排名的事实,它表明该算法执行得很差。 在这种情况下,lambda的差异不明显。但是在实际使用时,在选择lambda值时应该小心:选择的lambda越高,算法的要素数量就越少。这就是为什么交叉验证是重要的,因为要看看算法如何在不同的lambda上执行的。

原文发布于微信公众号 - 鸿的学习笔记(shujuxuexizhilu)

原文发表时间:2016-11-13

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏程序员互动联盟

【编程之美】最短路径

最短路径 任意给定两个数字A和B,通过将A和6个数(7,-7,5,-5,12,-12)做加减运算,运算次数不限,每个数可以被使用多次,求从A到B最少要经过多少次...

41960
来自专栏owent

C++ 新特性学习(六) — 新的字符串编码和伪随机数

使用u””为能至少储存UTF-16的16位元编码,对应’\u’表示16位元的字符。

9410
来自专栏WD学习记录

牛客网 和为S的连续正数序列

小明很喜欢数学,有一天他在做数学作业时,要求计算出9~16的和,他马上就写出了正确答案是100。但是他并不满足于此,他在想究竟有多少种连续的正数序列的和为100...

18010
来自专栏小二的折腾日记

LeetCode-52-N-Queens-II

只返回N皇后问题结果的种数。 因此不需要每一个字符串置位了,只需要判断一个位置的横竖,斜45度和斜135度方向的值即可。依然采用递归的方式,这里需要注意的是,由...

6110
来自专栏小樱的经验随笔

Gym 100952C&&2015 HIAST Collegiate Programming Contest C. Palindrome Again !!【字符串,模拟】

C. Palindrome Again !! time limit per test:1 second memory limit per test:64 meg...

26930
来自专栏人工智能LeadAI

TensorFlow从0到1丨第2篇:TensorFlow核心编程

上一篇Hello, TensorFlow!中的代码还未解释,本篇介绍TensorFlow核心编程的几个基本概念后,那些Python代码就很容易理解了。 与Ten...

41040
来自专栏calmound

hust Dating With Girls

http://acm.sdibt.edu.cn:8080/judge/contest/view.action?cid=573#problem/B 题意:求最大权...

29340
来自专栏人工智能LeadAI

pytorch入门教程 | 第一章:Tensor

1 pytorch安装 安装pytorch之前,需要安装好python,还没安装过python的宝宝请先移步到廖雪峰的python教程,待安装熟悉完之后,再过来...

434100
来自专栏mathor

并查集(Union Find)

 没想到有一天我也能搞懂并查集,orz......实际上本文算是《Algorithms》一书的读后感

22910
来自专栏C语言及其他语言

【每日一题】1426: [蓝桥杯][历届试题]九宫重排

如下面第一个图的九宫格中,放着 1~8 的数字卡片,还有一个格子空着。与空格子相邻的格子中的卡片可以移动到空格中。经过若干次移动,可以形成第二个图所示的局面...

12030

扫码关注云+社区

领取腾讯云代金券