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

此外,我们将看看前10个和后10个权重,以便更多地了解发生了什么。

Top 10 weights:

Subject

Frequency

Time frame (seconds)

Weight

[ilug] what howtos for soho system

2

60

8.52

[zzzzteana] the new steve earle

2

120

8.22

[ilug] looking for a file / directory in zip file

2

240

7.92

ouch... [bebergflame]

2

300

7.82

[ilug] serial number in hosts file

2

420

7.685

[ilug] email list management howto

3

720

7.62

should mplayer be build with win32 codecs?

2

660

7.482

[spambayes] all cap or cap word subjects

2

670

7.48

[zzzzteana] save the planet, kill the people

3

1020

7.47

[ilug] got me a crappy laptop

2

720

7.44

Bottom 10 weights:

Subject

Frequency

Time frame (seconds)

Weight

secure sofware key

14

1822200

4.89

[satalk] help with postfix + spamassassin

2

264480

4.88

the war prayer

2

301800

4.82

gecko adhesion finally sussed.

5

767287

4.81

the mime information you requested (last changed 3154 feb 14)

3

504420

4.776

use of base image / delta image for automated recovery from

5

1405800

4.55

sprint delivers the next big thing??

5

1415280

4.55

[razor-users] collision of hashes?

4

1230420

4.51

[ilug] modem problems

2

709500

4.45

tech's major decline

2

747660

4.43

正如你所看到的,最高的权重给予了几乎立即得到电子邮件回复的电子邮件,而最低权重给予具有非常长的时间范围的电子邮件。这允许具有非常低频率的电子邮件仍然基于它们被发送的时间帧被评定为非常重要。有了这个,我们有了2个特征:来自发件人mailsGroupedBySender的电子邮件的数量,以及属于现有线程threadGroupedWithWeights的电子邮件的权重。

让我们继续下一个特征,因为我们希望基于我们的排名值有尽可能多的特征。下一个特征将基于我们刚刚计算的权重排名。这个想法是,不同主题的新电子邮件会送达。然而,很可能它们包含与之前收到的重要主题相似的关键字。 这允许我们在线程(具有相同主题的多个消息)开始之前将电子邮件排序为重要。 为此,我们将关键字的权重指定为该术语出现的主题的权重。如果这个术语出现在多个线程中,我们采用最高的权重作为前导。

这个特征有一个问题,那就是它是停止词。 幸运的是,我们有一个停止词文件,允许我们删除(大多数)英语停止词。 但是,在设计自己的系统时,您应该考虑到可能会看到多种语言,因此您应该删除系统中可能发生的所有语言的停止词。此外,您可能需要小心的从不同的语言中删除停止词,因为一些词在不同的语言之间可能有不同的含义。 至于现在我们坚持删除英语停止词。 这个特征的代码如下:

def getStopWords: List[String] = {
    val source =scala.io.Source
       .fromFile(newFile("/Users/../stopwords.txt"))("latin1")
    val lines =source.mkString.split("\n")
    source.close()
    lines.toList
   }
//Add to top:
val stopWords = getStopWords
val threadTermWeights = threadGroupedWithWeights
    .toArray
    .sortBy(x =>x._4)
    .flatMap(x=> x._1
            .replaceAll("[^a-zA-Z]", "")
           .toLowerCase.split(" ")
           .filter(_.nonEmpty)
            .map(y=> (y,x._4)))
val filteredThreadTermWeights = threadTermWeights
    .groupBy(x=> x._1)
    .map(x =>(x._1, x._2.maxBy(y => y._2)._2))
    .toArray.sortBy(x=> x._1)
    .filter(x =>!stopWords.contains(x._1))

给定这个代码,我们现在有一个关于filteredThreadTermWeights的术语的列表,包括基于现有线程的权重。这些权重可以用于计算新电子邮件主题的权重,即使电子邮件不是对现有线程的回复。

val tdm = trainingData
      .flatMap(x=> x.body.split(" "))
      .filter(x=> x.nonEmpty && !stopWords.contains(x))
      .groupBy(x=> x)
      .map(x =>(x._1, Math.log10(x._2.length + 1)))
      .filter(x=> x._2 != 0)

tdm列表允许我们基于历史数据计算新电子邮件的电子邮件正文的重要性权重。通过这4个特征的准备,我们可以对训练数据进行实际的排序计算。 为此,我们需要找到senderWeight(表示发件人的权重),termWeight(表示主题中的术语的权重),threadGroupWeight(表示线程权重)和commonTermsWeight(表示电子邮件正文的权重) 与每个电子邮件相乘以获得最终排名。 由于我们是相乘而不添加值,我们需要处理小于1的样本。例如,有人发送了1封电子邮件,那么senderWeight将是0.69,这使得那些之前没有发送任何电子邮件的人很不公平,因为他/她会得到一个senderWeight 1。这就是为什么我们拿每个特征的Math.max(值,1)会产生低于1的值。让我们看看代码:

val trainingRanks = trainingData.map(mail => {
      //Determinethe weight of the sender, if it is lower than 1, pick 1 instead
      //This isdone to prevent the feature from having a negative impact
      valsenderWeight = mailsGroupedBySender
       .collectFirst { case (mail.sender, x) =>  Math.max(x,1)}
       .getOrElse(1.0)
      //Determinethe weight of the subject
      valtermsInSubject = mail.subject
       .replaceAll("[^a-zA-Z ]", "")
       .toLowerCase.split(" ")
        .filter(x=>    x.nonEmpty &&
       !stopWords.contains(x)
        )
      valtermWeight = if (termsInSubject.size > 0)
       Math.max(termsInSubject
          .map(x=> {
         tdm.collectFirst { case (y, z) if y == x => z}
           .getOrElse(1.0)
        })
          .sum /termsInSubject.length,1)
      else 1.0
      //Determineif the email is from a thread,
      //and if itis the weight from this thread:
      valthreadGroupWeight: Double = threadGroupedWithWeights
       .collectFirst { case (mail.subject, _, _, weight) => weight}
       .getOrElse(1.0)
      //Determinethe commonly used terms in the email and the weight belonging to it:
      valtermsInMailBody = mail.body
       .replaceAll("[^a-zA-Z ]", "")
       .toLowerCase.split(" ")
        .filter(x=>    x.nonEmpty &&
       !stopWords.contains(x)
        )
      valcommonTermsWeight = if (termsInMailBody.size > 0)
       Math.max(termsInMailBody
          .map(x=> {
         tdm.collectFirst { case (y, z) if y == x => z}
           .getOrElse(1.0)
        })
          .sum /termsInMailBody.length,1)
      else 1.0
      val rank=    termWeight *
       threadGroupWeight *
        commonTermsWeight*
       senderWeight
      (mail, rank)
    })
    valsortedTrainingRanks = trainingRanks
      .sortBy(x=> x._2)
    val median =sortedTrainingRanks(sortedTrainingRanks.length / 2)._2
    val mean =  sortedTrainingRanks
      .map(x =>x._2).sum /
               sortedTrainingRanks.length

正如你所看到d额,我们计算了所有用于训练的电子邮件的排名,并排序它们,得到中位数和平均值。我们采用这个中间值和平均值的原因是要将其变为电子邮件评为优先级或非优先级的决策边界。 在实践中,这通常没有用。实践中获得决策边界的最佳方法是让用户将一组电子邮件标记为优先级与非优先级。然后,您可以使用这些等级来计算决策边界,并另外查看排名功能是否正确。 如果用户最终将电子邮件标记为具有比算法标记为优先级的电子邮件更高排名的优先级,则可能需要重新评估您的特征了。

我们计算决策边界的原因,不是因为排序用户的电子邮件排名,而是时间。 如果你纯粹基于排名排序电子邮件,人们会发现这很讨厌,因为一般人喜欢他们自己的电子邮件排序方式。然而,利用这个决策边界,我们可以先将各个电子邮件标记优先级,如果我们将排名纳入电子邮件客户端,可以将其显示在单独的列表中。

现在让我们来看看测试集中的电子邮件数量是多少。 为此,我们首先要添加以下代码:

val testingRanks = trainingData.map(mail => {
      //mailcontains (full content, date, sender, subject, body)
      //Determinethe weight of the sender
      valsenderWeight = mailsGroupedBySender
       .collectFirst { case (mail.sender, x) =>  Math.max(x,1)}
       .getOrElse(1.0)
      //Determine the weight of the subject
      valtermsInSubject = mail.subject
       .replaceAll("[^a-zA-Z ]", "")
       .toLowerCase.split(" ")
        .filter(x=>    x.nonEmpty &&
       !stopWords.contains(x)
        )
      valtermWeight = if (termsInSubject.size > 0)
       Math.max(termsInSubject
        .map(x=> {
       tdm.collectFirst { case (y, z) if y == x => z}
         .getOrElse(1.0)
      })
        .sum /termsInSubject.length,1)
      else 1.0
      //Determineif the email is from a thread,
      //and if itis the weight from this thread:
      valthreadGroupWeight: Double = threadGroupedWithWeights
       .collectFirst { case (mail.subject, _, _, weight) => weight}
       .getOrElse(1.0)
      //Determinethe commonly used terms in the email and the weight belonging to it:
      valtermsInMailBody = mail.body
       .replaceAll("[^a-zA-Z ]", "")
       .toLowerCase.split(" ")
        .filter(x=>    x.nonEmpty &&
       !stopWords.contains(x)
        )
      valcommonTermsWeight = if (termsInMailBody.size > 0)
       Math.max(termsInMailBody
        .map(x=> {
       tdm.collectFirst { case (y, z) if y == x => z}
         .getOrElse(1.0)
      })
        .sum /termsInMailBody.length,1)
      else 1.0
      val rank=    termWeight *
       threadGroupWeight *
       commonTermsWeight *
       senderWeight
      (mail, rank)
    })
    valpriorityEmails = testingRanks
        .filter(x=> x._2 >= mean)
   println(priorityEmails.length + " ranked as priority")

在实际运行测试代码后,您将看到从测试集中根据优先级排序的电子邮件数量实际上是563.这是测试电子邮件集的45%。 这是一个相当高的值,所以我们可以调整一点决策边界。 然而,此时不应该选择实际中的平均值,我们不会进一步探究到底是多少百分比。相反,我们将查看前10个优先级电子邮件的排名。

请注意,我已移除了部分电子邮件地址,以防止垃圾邮件漫游器检索这些邮件地址。 我们在下表中看到,这十大优先级电子邮件中的大多数是组合在一起的线程,其具有非常高的活动性。例如,排名最高的电子邮件。 此电子邮件是对9分钟前的电子邮件的跟进。 这表明电子邮件线程很重要。

Date

Sender

Subject

Rank

Sat Sep 07 06:45:23 CEST 2002

skip@...

[spambayes] can't write to cvs...

81.11

Sat Sep 07 21:11:36 CEST 2002

tim.one@...

[spambayes] test sets?

79.59

Mon Sep 09 17:46:55 CEST 2002

tim.one@...

[spambayes] deleting "duplicate" spam before training? good idea

79.54

Mon Aug 26 14:43:00 CEST 2002

tomwhore@...

how unlucky can you get?

77.87

Sat Sep 07 06:36:41 CEST 2002

tim.one@...

[spambayes] can't write to cvs...

77.77

Sun Sep 08 21:13:40 CEST 2002

tim.one@...

[spambayes] test sets?

76.3

Thu Sep 05 20:53:00 CEST 2002

felicity@...

[razor-users] problem with razor 2.14 and spamassassin 2.41

75.44

Fri Sep 06 07:09:11 CEST 2002

tim.one@...

[spambayes] all but one testing

72.77

Sat Sep 07 06:40:45 CEST 2002

tim.one@...

[spambayes] maybe change x-spam-disposition to something else...

72.62

Sat Sep 07 05:05:45 CEST 2002

skip@...

[spambayes] maybe change x-spam-disposition to something else...

72.27

另外,我们可以看到tim.one ...在这个表中出现了很多次。 这表明他的所有电子邮件都很重要,或者他发送了如此多的电子邮件,那么排名者会自动将它们列为优先。作为这个例子的最后一步,我们将进一步研究一下:

val timsEmails = testingRanks
    .filter(x =>x._1.sender == "tim.one@...")
    .sortBy(x =>-x._2)
timsEmails
    .foreach(x=> println(  "| " +
                            x._1.emailDate +
                            " |"  +
                            x._1.subject + 
                            " | " +
                            df.format(x._2) + 
                            " |")
                        )

运行此代码后,我们将看到45封电子邮件和最顶的十封:

Date

Subject

Rank

Sun Sep 08 21:36:15 CEST 2002

[spambayes] ditching wordinfo

42.89

Tue Sep 10 18:12:51 CEST 2002

[spambayes] timtest broke?

41.73

Thu Sep 12 04:06:24 CEST 2002

[spambayes] current histograms

41.73

Sun Sep 08 21:46:47 CEST 2002

[spambayes] hammie.py vs. gbayes.py

41.68

Tue Sep 10 04:18:25 CEST 2002

[spambayes] current histograms

40.67

Wed Sep 11 04:46:15 CEST 2002

[spambayes] xtreme training

39.83

Tue Sep 10 19:26:05 CEST 2002

[spambayes] timtest broke?

39.73

Thu Sep 12 01:37:13 CEST 2002

[spambayes] stack.pop() ate my multipart message

38.89

Sat Sep 07 01:06:56 CEST 2002

[spambayes] ditching wordinfo

38.34

Sat Sep 07 00:21:15 CEST 2002

[spambayes] [ann] trained classifier available

8.71

如果我们还能回忆起我们的决策边界是平均值,即25.06,那么我们看到的只有1封电子邮件没有被评为优先级。 这表明一方面我们的决策边界太低了,但另一方面,Tim可能实际上发送了很多重要的电子邮件,所以排名可以低于决策边界。

不幸的是,我们不能提供确切的答案,因为我们不是这个测试数据的所有者。当你没有确切的真相时,验证这样的ranker是相当困难的。 验证和改进它的最常见的方法之一是实际呈现给用户并让他/她自己标记错误。然后可以使用这些校正来改进系统。

总而言之,我们看到了如何从原始数据获取特征,即使数据有“巨大”异常值,以及如何将这些特征组合成最终排名值。 此外,我们尝试了评估这些特征,但由于缺乏数据集的知识,我们不能得出明确的结论。然而,如果你对你所知道的数据做同样的事情,那么这应该让你开始建立自己的排名系统。

Predicting weight based on height (usingOrdinary Least Squares)

在本节中,我们将介绍普通最小二乘法,它是线性回归的一种形式。由于这种技术非常强大,在开始这个例子之前,了解一下回归和常见的陷阱很重要。 我们将在本节中讨论这些问题中的一些,而其他一些在欠拟合和过拟合的部分中写出。

线性回归的思想是在你的训练数据点上设置一个“最优”的回归线。注意,这只能是当你的数据是线性的,并没有巨大的离群值时。 如果不是这种情况,您可以尝试操作您的数据,直到出现这种情况,例如通过采取开方或求数据对数。和往常一样,项目设置时要做的第一件事是导入数据集。为此,我们为您提供以下csv文件和读取此文件的代码:

def getDataFromCSV(file: File): (Array[Array[Double]],Array[Double]) = {
    val source =scala.io.Source.fromFile(file)
    val data =source.getLines().drop(1).map(x => getDataFromString(x)).toArray
    source.close()
    var inputData =data.map(x => x._1)
    var resultData= data.map(x => x._2)
    return(inputData,resultData)
  }
  defgetDataFromString(dataString: String): (Array[Double], Double) = {
    //Split thecomma separated value string into an array of strings
    val dataArray:Array[String] = dataString.split(',')
    var person =1.0
    if(dataArray(0) == "\"Male\"") {
      person = 0.0
    }
    //Extract thevalues from the strings
    //Since the data is in US metrics 
    //inch andpounds we will recalculate this to cm and kilo's
    val data :Array[Double] = Array(person,dataArray(1).toDouble * 2.54)
    val weight:Double = dataArray(2).toDouble * 0.45359237
    //And return theresult in a format that can later easily be used to feed to Smile
    return (data,weight)
  }

请注意,数据读取器会将值从英制转换为公制。这对OLS实现没有大的影响,但由于公制更常用,所以我们更倾向在该系统中提供数据。使用这些方法,我们将数据作为表示数据点的Array [Array [Double]]和表示男性或女性的分类的Array[Double]。 这些格式对于绘制数据和反馈到机器学习算法是有好处的。让我们先看看数据是什么样子。 为此,我们使用以下代码绘制数据。

object LinearRegressionExample extendsSimpleSwingApplication {
  def top = newMainFrame {
    title ="Linear Regression Example"
    val basePath ="/Users/.../OLS_Regression_Example_3.csv"
    val testData =getDataFromCSV(new File(basePath))
    val plotData =(testData._1 zip testData._2).map(x => Array(x._1(1) ,x._2))
    valmaleFemaleLabels = testData._1.map( x=> x(0).toInt)
    val plot =  ScatterPlot.plot(   plotData,
                                    maleFemaleLabels,
                                    '@',
                                   Array(Color.blue, Color.green)
                                 )
   plot.setTitle("Weight and heights for male and females")
    plot.setAxisLabel(0,"Heights")
   plot.setAxisLabel(1,"Weights")
   peer.setContentPane(plot)
    size = newDimension(400, 400)
  }

如果你执行上面的代码,会弹出一个窗口,如下图所示。请注意,当您运行代码时,您可以滚动以放大和缩小绘图。

在这个图中,绿色是女性,蓝色是男性,你可以看到他们的重量和身高有很大的重叠。因此,如果我们忽略男性和女性之间的差异,它仍然会看起来像数据中有一个线性函数(可以在左图中看到)。 然而,当忽略这种差异时,函数将不如当我们结合关于男性和女性的信息时那样精确。在这个例子中,找到这种区别是微不足道的,但是你可能会遇到这些集合不那么明显的数据集。让您意识到这种可能性可能有助于您找到数据中的集合,这可以提高机器学习应用程序的性能。现在我们已经看到了数据,看到确实我们可以想出一个线性回归线来拟合这些数据。smile为我们提供了普通最小二乘算法,我们可以很容易地使用如下:

al olsModel = new OLS(testData._1,testData._2)
With this olsModel we can now predict someone's weight based on length andgender as follows: 
println("Prediction for Male of 1.7M: "+olsModel.predict(Array(0.0,170.0)))
println("Prediction for Female of 1.7M:" +olsModel.predict(Array(1.0,170.0)))
println("Model Error:" + olsModel.error())
and this will give the following results:
Prediction for Male of 1.7M: 79.14538559840447
    Prediction forFemale of 1.7M:70.35580395758966
    ModelError:4.5423150758157185

如果你还能想起分类算法,这里面有一个先验值,解释了一些关于你所建立的模型的性能。由于回归是一种更强的统计方法,所以现在有一个实际的错误值。 该值表示拟合回归线的平均值的范围,因此您可以说,对于这个模型,1.70m男性的预测为79.15kg有 +/- 4.54kg的误差。 注意,如果你删除男性和女性之间的区别,这个错误将增加到5.5428。 换句话说,增加男性和女性之间的区别,在其预测中将模型精度增加+/-1kg。

最后Smile还提供了一些关于你建立的模型的统计信息。RSquared方法给出了来自模型的均方误差(RMSE)除以平均值的RMSE。 该值始终在0和1之间。如果您的模型完全预测了每个数据点,则RSquared将为1,如果模型的性能并不优于平均值函数,则该值将为0.在该字段中,此度量常常乘以100,然后用作表示模型的准确程度。 因为这是一个归一化值,所以它可以用来比较不同模型的性能。

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

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

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

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏流媒体

音视频编码相关名词详解

PAR - pixel aspect ratio(像素宽高比)大多数情况为1:1,就是一个正方形像素,否则为长方形像素

1192
来自专栏携程技术中心

干货 | ElasticSearch相关性打分机制

作者简介 孙咸伟,后端开发一枚,在携程技术中心市场营销研发部负责“携程运动”项目的开发和维护。 携程运动是携程旗下新业务,主要给用户提供羽毛球、游泳等运动项目的...

1.5K8
来自专栏数说工作室

海量文本用 Simhash, 2小时变4秒! | 文本分析:大规模文本处理(2)

这是一个相似匹配的问题(文本相似匹配基础→ 词频与余弦相似度)。但是,亿级数据库,用传统的相似度计算方法太慢了,我们需要一个文本查询方法,可以快速的把一段文本的...

2823
来自专栏数据魔术师

运筹学教学 | 十分钟快速掌握最大流算法(附C++代码及算例)

—“运筹教科书到底能给你啥?” —“算法和实现离教科书有多远?” —“问题解决能力到底从哪来?” 今天刚起床就接到了BOSS的 提·问·三·连 小编表示 收到直...

4945
来自专栏CSDN技术头条

Google核心技术之——PageRank算法scala实现

PageRank算法简述 常言道,看一个人怎样,看他有什么朋友就知道了。也就是说,一个人有着越多牛X朋友的人,他是牛X的概率就越大。将这个知识迁移到网页上就是“...

2556
来自专栏数据小魔方

R语言可视化——用ggplot构造期待已久的雷达图

之前一直苦恼于ggplot函数无法制作雷达图,心想着既然饼图可以通过柱形图+极坐标模拟出来,为啥雷达图不行。 我尝试着用折线图+极坐标来模拟雷达图(之前在制作饼...

3486
来自专栏机器学习人工学weekly

机器学习人工学weekly-2018/9/23

Rosetta: Understanding text in images and videos with machine learning

995
来自专栏大数据文摘

手把手|如何用Python绘制JS地图?

47213
来自专栏华章科技

你真的懂数据分析吗?一文读懂数据分析的流程、基本方法和实践

导读:无论你的工作内容是什么,掌握一定的数据分析能力,都可以帮你更好的认识世界,更好的提升工作效率。数据分析除了包含传统意义上的统计分析之外,也包含寻找有效特征...

1162
来自专栏Android 开发者

Android Smart Linkify 支持机器学习

上半周,我们发布了 Android 9 Pie,这是 Android 的最新版本,它的机器学习应用使您的手机更简单易用。 Android 9 中有一项功能是 S...

1443

扫码关注云+社区