引言

在这一小节中。我将介绍基于数据(函数式)的方法来构建数据应用。这里会介绍monadic设计来创建动态工作流,利用依赖注入这种高级函数式特性来构建轻便的计算工作流。

建模过程

在统计学和概率论中,一个模型通过描写叙述从一个系统中观察到的数据来表达不论什么形式的不确定性。模型使得我们能够用来判断规则,进行预測,从数据中学习实用的东西。

对于有经验的Scala程序猿而言,模型经常和monoid联系起来。monoid是一些观測的集合。当中的操作是实现模型所需的函数。

关于模型的特征

模型特征的选择是从可用变量中发现最小集合来构建模型的过程。数据中经常包括多余和不相干的特征,这些多余特征并不能提供不论什么实用信息。所以须要通过特征选择将实用的特征挑选出来。

特征选择包括两个详细步骤

  • 搜索新的特征子集
  • 通过某种评分机制来评估特征子集

观測数据是一组隐含特征(也称为隐含变量。latent variables)的间接測量。他们可能是噪声。也可能包括高度的相关性和冗余。

直接使用原始观測进行预測任务经常得到不准确的结果。使用从观測数据提取的全部特征又带来了计算代价。特征抽取能够通过去除冗余或不相关的特征来降低特征数量或维度。

设计工作流

首先,所选的数学模型是从原始输入数据中抽取知识的。那么模型的选择中须要考虑以下几个方面:

  • 业务需求,比方预測结果的精确度
  • 训练数据和算法的可用性
  • 专业领域的相关知识

然后。从project角度出发。须要选择一种计算调度框架来处理数据。这须要考虑以下几个方面:

  • 可用资源,如CPU、内存、IO带宽
  • 实现策略,如迭代和递归计算
  • 响应整个过程的需求。如计算时间、中间结果的显示

以下的图标给出了计算模型的工作流程:



在这个流程图中,下游的数据转换(data transformation)的參数须要依据上游数据转换的输出进行配置。Scala的高阶函数很适合实现可配置的数据转换。

计算框架

创建足够灵活和可重用的框架的目的是为了更好地适应不同工作流程,支持各种类型的机器学习算法。

Scala通过特质(traits)语法实现了丰富的语言特性,能够通过以下的设计层级来构建复杂的程序框架:

管道操作符(The pipe operator)

数据转换是对数据进行分类、训练验证模型、结果可视化等每一个步骤环节的基础。定义一个符号。表示不同类型的数据转换,而不暴露算法实现的内部状态。

而管道操作符就是用来表示数据转换的。

trait PipeOperator[-T, +U] {
def |>(data: T): Option[U]
}

|>操作符将类型为T的数据转换成类型为U的数据,返回一个Option来处理中间的错误和异常。

单子化数据转换(Monadic data transformation)

接下来须要创建单子化的设计(monadic design)来实现管道操作(pipe operator)。通过单子化设计来包装类_FCT_FCT类的方法代表了传统Scala针对集合的高阶函数子集。

class _FCT[+T](val _fct: T) {
def map[U](c: T => U): _FCT[U] = new _FCT[U]( c(_fct)) def flatMap[U](f: T =>_FCT[U]): _FCT[U] = f(_fct) def filter(p: T =>Boolean): _FCT[T] =
if( p(_fct) ) new _FCT[T](_fct) else zeroFCT(_fct) def reduceLeft[U](f: (U,T) => U)(implicit c: T=> U): U =
f(c(_fct),_fct) def foldLeft[U](zero: U)(f: (U, T) => U)(implicit c: T=> U): U =
f(c(_fct), _fct) def foreach(p: T => Unit): Unit = p(_fct)
}

最后。Transform类将PipeOperator实例作为參数输入,自己主动调用其操作符。像这样:

class Transform[-T, +U](val op: PipeOperator[T, U]) extends _FCT[Function[T, Option[U]]](op.|>) {
def |>(data: T): Option[U] = _fct(data)
}

或许你会对数据转换Transform的单子化表示背后的原因表示怀疑。毕竟本来能够通过PipeOperator的实现来创建不论什么算法。

原因是Transform含有丰富的方法,使得开发人员能够创建丰富的工作流。

以下的代码片段描写叙述的是使用单子化方法来进行数据转换组合:

val op = new PipeOperator[Int, Double] {
def |> (n: Int):Option[Double] =Some(Math.sin(n.toDouble))
}
def g(f: Int =>Option[Double]): (Int=> Long) = {
(n: Int) => {
f(n) match {
case Some(x) => x.toLong
case None => -1L
}
}
}
val gof = new Transform[Int,Double](op).map(g(_))

这里使用函数g作为现有的数据转换来扩展op。

依赖注入(Dependency injection)

一个由可配置的数据转换构成的工作流在其不同的流程阶段都须要动态的模块化。蛋糕模式(Cake Pattern)是使用混入特质(mix-in traits)来满足可配置计算工作流的一种高级类组合模式。

Scala通过特质这一语法特性使得开发人员能够使用一种灵活的、可重用的方法来创建和管理模块,特质是可嵌套的、可混入类中的、可堆叠的、可继承的。

val myApp = new Classification with Validation with PreProcessing {
val filter = ..
}
val myApp = new Clustering with Validation with PreProcessing {
val filter = ..
}

对于上面两个应用来说。都须要数据的预处理和验证模块,在代码中都反复定义了filter方法,使得代码反复、缺乏灵活性。当特质在组合中存在依赖性时。这个问题凸现出来。

混入的线性化

在混入的特质中。方法调用遵循从右到左的顺序:

- trait B extends A

- trait C extends A

- class M extends N with C with B

Scala编译器依照M => B => C => A => N的线性顺序来实现

trait PreProcessingWithValidation extends PreProcessing {
self: Validation =>
val filter = ..
} val myApp = new Classification with PreProcessingWithValidation {
val validation: Validation
}

在PreProcessingWithValidation中使用self类型来解决上述问题。

(tips:原书的内容在这里我没怎么搞清楚,不知道是通过自身类型混入了Validation后filter方法详细是怎么实现的,以及实例化Classification时混入PreProcessingWithValidation难道不须要混入Validation吗?我表示疑问)

工作流模块

由PipeOperator定义的数据转换动态地嵌入了通过抽象val定义的模块中。以下我们定义工作流的三个阶段:

trait PreprocModule[-T, +U] { val preProc: PipeOperator[T, U] }
trait ProcModule[-T, +U] { val proc: PipeOperator[T, U] }
trait PostprocModule[-T, +U] { val postProc: PipeOperator[T, U] }

上面的特质(模块)仅包括一个抽象值,蛋糕模式的一个特点是用模块内部封装的类型初始化抽象值来运行严格的模块化:

trait ProcModule[-T, +U] {
val proc: PipeOperator [T, U]
class Classification[-T, +U] extends PipeOperator [T,U] { }
}

构建框架的一个目的是同意开发人员能够从不论什么工作流中独立创建数据转换(继承自PipeOperator)。

工作流工厂

接下来就是将不同的模块写入一个工作流中。通过上一小节中的三个特质的堆叠作为自身引用来实现:

class WorkFlow[T, U, V, W] {
self: PreprocModule[T,U] with ProcModule[U,V] with PostprocModule[V,W] => def |> (data: T): Option[W] = {
preProc |> data match {
case Some(input) => {
proc |> input match {
case Some(output) => postProc |> output
case None => { … }
}
}
case None => { … }
}
}
}

以下介绍怎样详细地实现一个工作流。

首先通过继承PipeOperator来定义集中数据转换:

class Sampler(val samples: Int) extends PipeOperator[Double => Double, DblVector] {
override def |> (f: Double => Double): Option[DblVector] =
Some(Array.tabulate(samples)(n => f(n.toDouble/samples)) )
} class Normalizer extends PipeOperator[DblVector, DblVector] {
override def |> (data: DblVector): Option[DblVector] =
Some(Stats[Double](data).normalize)
} class Reducer extends PipeOperator[DblVector, Int] {
override def |> (data: DblVector): Option[Int] =
Range(0, data.size) find(data(_) == 1.0)
}



工作流工厂由这个UML类图描写叙述。

终于通过动态地初始化抽象值preProc、proc和postProc来实例化工作流。

val dataflow = new Workflow[Double => Double, DblVector, DblVector, Int]
with PreprocModule[Double => Double, DblVector]
with ProcModule[DblVector, DblVector]
with PostprocModule[DblVector, Int] {
val preProc: PipeOperator[Double => Double,DblVector] = new Sampler(100) //1
val proc: PipeOperator[DblVector,DblVector]= new Normalizer //1
val postProc: PipeOperator[DblVector,Int] = new Reducer//1
} dataflow |> ((x: Double) => Math.log(x+1.0)+Random.nextDouble) match {
case Some(index) => …

參考资料

《Scala for Machine Learning》Chapter 2

转载请注明作者Jason Ding及其出处

jasonding.top

Github博客主页(http://blog.jasonding.top/)

CSDN博客(http://blog.csdn.net/jasonding1354)

简书主页(http://www.jianshu.com/users/2bd9b48f6ea8/latest_articles)

Google搜索jasonding1354进入我的博客主页

【Scala-ML】使用Scala构建机器学习工作流的更多相关文章

  1. 使用 ML Pipeline 构建机器学习工作流

    http://www.ibm.com/developerworks/cn/opensource/os-cn-spark-practice5/

  2. spark ml pipeline构建机器学习任务

    一.关于spark ml pipeline与机器学习一个典型的机器学习构建包含若干个过程 1.源数据ETL 2.数据预处理 3.特征选取 4.模型训练与验证 以上四个步骤可以抽象为一个包括多个步骤的流 ...

  3. 入门系列之Scikit-learn在Python中构建机器学习分类器

    欢迎大家前往腾讯云+社区,获取更多腾讯海量技术实践干货哦~ 本文由信姜缘 发表于云+社区专栏 介绍 机器学习是计算机科学.人工智能和统计学的研究领域.机器学习的重点是训练算法以学习模式并根据数据进行预 ...

  4. Scala学习(一)--Scala基础学习

    Scala基础学习 摘要: 在篇主要内容:如何把Scala当做工业级的便携计算器使用,如何用Scala处理数字以及其他算术操作.在这个过程中,我们将介绍一系列重要的Scala概念和惯用法.同时你还将学 ...

  5. java.lang.String cannot be cast to scala.runtime.Nothing Scala中的Nothing类型

    经常在写Rdd的时候, 如:  val OWNER_ID=row.getAs("OWNER_ID")  等, 运行是可能会报异常 : java.lang.String cannot ...

  6. 三、使用maven创建scala工程(scala和java混一起)

    本文先叙述如何配置eclipse中maven+scala的开发环境,之后,叙述如何实现spark的本地运行.最后,成功运行scala编写的spark程序. 刚开始我的eclipse+maven环境是配 ...

  7. 解锁云原生 AI 技能 - 开发你的机器学习工作流

    按照上篇文章<解锁云原生 AI 技能 | 在 Kubernetes 上构建机器学习系统>搭建了一套 Kubeflow Pipelines 之后,我们一起小试牛刀,用一个真实的案例,学习如何 ...

  8. 解锁云原生 AI 技能|在 Kubernetes 上构建机器学习系统

    本系列将利用阿里云容器服务,帮助您上手 Kubeflow Pipelines. 介绍 机器学习的工程复杂度,除了来自于常见的软件开发问题外,还和机器学习数据驱动的特点相关.而这就带来了其工作流程链路更 ...

  9. 使用Flask构建机器学习模型API

    1. Python环境设置和Flask基础 使用"Anaconda"创建一个虚拟环境.如果你需要在Python中创建你的工作流程,并将依赖项分离出来,或者共享环境设置," ...

随机推荐

  1. RPA(Robotic Process Automation)的概要介绍

    最近因为公司业务的需要,开始关注RPA的内容,奈何国内相关的信息太少,只能硬着头皮啃英文了. 下面记录的内容作为学习笔记,有不对的地方请大家指教. 首先RPA(Robotic Process Auto ...

  2. rewirte 规则

    Nginx Rewrite Rewirte 规则也称为规则重写,主要功能是实现浏览器访问 HTTP URL 的跳转,其正则 表达式是基于 Perl 语言.通常而言,几乎所有的 WEB 服务器均可以支持 ...

  3. 7. ZooKeeper的stat结构

    ZooKeeper命名空间中的每个znode都有一个与之关联的stat结构,类似于Unix/Linux文件系统中文件的stat结构. znode的stat结构中的字段显示如下,各自的含义如下: cZx ...

  4. [转载] Java并发编程:Lock

    转载自http://www.cnblogs.com/dolphin0520/p/3923167.html 以下是本文目录大纲: 一.synchronized的缺陷 二.java.util.concur ...

  5. linux如何查看端口被谁占用

    1.查看端口是否被占用 [guosong@alice48 main]$ netstat -nlp|grep 6184 (Not all processes could be identified, n ...

  6. ASP.NET Core 认证与授权[5]:初识授权

    经过前面几章的姗姗学步,我们了解了在 ASP.NET Core 中是如何认证的,终于来到了授权阶段.在认证阶段我们通过用户令牌获取到用户的Claims,而授权便是对这些的Claims的验证,如:是否拥 ...

  7. JavaScript数据可视化编程学习(二)Flotr2,雷达图

    一.雷达图 使用雷达图显示多维数据. 如果你有多维的数据要展示,那么雷达图就是一种非常有效的可视化方法. 由于雷达图不常用,比较陌生,所以向用户解释的时候有一些难度.注意使用雷达图会增加用户认知负担. ...

  8. View处理常用方法封装

    处理View常用的一些方法:Drawable和Bitmap互相转换,Bitmap改变大小,dp.px互相转换,sp.px互相转换,根据Id查找Drawable,获取屏幕大小等方法. import an ...

  9. window.location.href跳转至空白页

    现象:window.location.href = "XXX"调到了空白页,但是将XXX在窗口地址栏输入就会可以访问到. 原因:就是XXX前缀没有加上"http://&q ...

  10. 如何清除PHP中不需要的Layout模板

    最简单的办法就是在需要清除的静态页的最前端加上   {__NOLAYOUT__}  就可以清除所有的layout效果, 继而设置自己的css.js效果.