TensorFlowOnSpark 源码解析,Javascript的this关键字

这两天琢磨了下spark-deep-learning和spark-sklearn两个项目,但是感觉都不尽人如意。在training时,都需要把数据broadcast到各个节点进行并行训练,基本就失去实用价值了(tranning数据都会大于单节点内存的好么),而且spark-deep-learning目前还没有实现和tf cluster的结合。所以这个时候转向了开源已久的yahoo的TensorFlowOnSpark项目。简单了过了下他的源码,大致理清楚了原理,这里算是记录下来,也希望能帮到读者。

TensorFlowOnSpark 代码运行剖析

从项目中打开examples/mnist/spark/mnist_spark/mnist_dist.py,

第一步通过pyspark创建SparkContext,这个过程其实就启动了Spark cluster,至于如何通过python启动spark 并且进行相互通讯

第二步是接受一些命令行参数,这个我就不贴了。

第三步是使用标准的pyspark API 从HDFS获取图片数据,构成一个

接着就是开始进入正题,启动tf cluster了:

cluster = TFCluster.run(sc, mnist_dist.map_fun, args, args.cluster_size, num_ps, args.tensorboard, TFCluster.InputMode.SPARK)

TFCluster.run 里的sc 就是sparkcontext,mnist_dist.map_fun函数则包含了你的tensorflow业务代码,在这个示例里就是minist的模型代码,模型代码具体细节代码我们会晚点说。我们先看看TFCluster.run方法:

上面是确定parameter server和worker的数目,这两个概念是和tf相关的。

接着会启动一个Server:

在driver端启动一个Server,主要是为了监听待会spark executor端启动的tf workerhttps://www.yaoruanwen.com/n/463996.html,进行协调。

上面的代码获取完整的启动tf cluster所需要的信息。建议大家可以去google下如何手动配置tf cluster,然后就能更深入理解TensorFlowOnSpark是如何预先收集好哪些参数。

上面的第一段代码其实是为了确保启动cluster_size个task,每个task对应一个partition,每个partition其实只有一个元素,就是worker的编号。通过对partition进行foreatch来启动对应的tf worker(包含ps)。倒数第二行代码我们又看到了,前面的那个server了,它会阻塞代码往下执行,直到所有tf worker都启动为止。

到这里我们也可以看到,一个spark executor可能会启动多个tf worker。

现在我们进入 TFSparkNode.run看看,这里面包含了具体如何启动tf worker的逻辑,记得这些代码已经在executor执行了。

首先定义了一个函数_mapfn,他的参数是一个iter,这个iter 没啥用,就是前面的worker编号,只有一个元素。该函数里主要作用其实就是启动tf worker(PS)的,并且运行用户的代码的:

启动的过程中会启动一个client,连接我们前面说的Server,报告自己成功启动了。

这里会判断是ps还是worker。如果是后台运行,则通过multiprocessing.Process直接运行我们前年提到的mnist_dist.map_fun方法,而mnist_dist.map_fun其实包含了tf session的逻辑代码。当然这个时候模型虽然启动了,但是因为在获取数据时使用了queue.get(block=Truehttps://www.yaoruanwen.com/n/464003.html) 时,这个时候还没有数据进来,所以会被阻塞住。值得注意的是,这里的代码会发送给spark起的python worker里执行。

在获得cluster对象后,我们就可以调用train方法做真实的训练了,本质上就是开始喂数据:

进入 cluster.train看下,会进入如下代码:

这里会把数据按partition的方式喂给每个TF worker(通过调用train方法):

这里会拿到tf的queue,然后通过iter(也就是实际的spark rdd包含的训练数据)往里面放,如果放满了就会阻塞。

直至,大致流程就完成了。现在我们回过头来看我们的业务代码mnist_dist.map_fun,该方法其实是在每个tf worker上执行的:

简单的做了判定,如果是ps则停止在这,否则执行构建模型的工作。在with tf.device… 里面就是开始定义模型什么的了,标准的tf 代码了:

当然,在TensorFlowOnSpark的示例代码里,使用了Supervisor:

TFNode.DataFeedhttps://www.yaoruanwen.com/n/464005.html提供了一个便捷的获取批量数据的方式,让你不用操心queue的事情。

在训练达到必要的数目后,你可以停止训练:

现在整个流程应该是比较清晰了。
this 是函数运行时,在函数体内部自动生成的一个对象,只能在函数体内部使用。

教科书般的解释,字都认识,怎么连在一起还是不知道啥意思呢?

1 this的值究竟是什么呢?

函数的不同场合,this有不同值。

总的来说,this就是函数运行时所在的环境对象。

1.1 简单函数调用

函数的最通常用法,属全局性调用,因此this就代表全局对象。

看下面案例

1.2 作为对象方法的调用

函数还可以作为某个对象的方法调用,这时this就指这个上级对象。

记住一条:当function被作为method调用时,this指向调用对象。另外,JavaScript并不是OO的,而是object based的一种语言。

1.3 构造函数

所谓构造函数,就是通过这个函数,可以生成一个新对象。这时,this就指这个新对象。

上面两套代码等效 可以写class test,但本质上new test(https://www.yaoruanwen.com/n/464010.html)的时候,还是test构造函数,差不多,class主要是向java之类的语言抄的,可以直接当java的类用,但本质上test还是个构造函数,因为js一开始就没有class 只能用构造函数,函数test运行时,内部会自动有一个this对象可以使用。

运行结果为1。为了表明这时this不是全局对象,我们对代码做一些改变:

运行结果为2,表明全局变量x的值根本没变。

1.4 apply 调用

apply()是函数的一个方法,作用是改变函数的调用对象。它的第一个参数就表示改变后的调用这个函数的对象。因此,这时this指的就是这第一个参数。

apply()的参数为空时,默认调用全局对象。因此,这时的运行结果为0,证明this指的是全局对象。

如果把最后一行代码修改为

运行结果就变成了1,证明了这时thishttps://www.yaoruanwen.com/n/464013.html代表的是对象obj。

2 深入内存分析

学懂 JavaScript 语言,一个标志就是理解下面两种写法,可能有不一样的结果。

上面代码中,虽然obj.foo和foo指向同一个函数,但是执行结果可能不一样。请看下面的例子。

对于obj.foo()来说,foo运行在obj环境,所以this指向obj

对于foo()来说,foo运行在全局环境,所以this指向全局环境。所以,两者的运行结果不一样。

为什么会这样?函数的运行环境到底是谁决定的?为什么obj.foo()就是在obj环境执行,而一旦var foo = obj.foo,foo()就变成全局环境执行了?

带着灵魂的思考,我们深入解析下

2.1 内存布局

上面的代码将一个对象赋值给变量obj.

JS 引擎会先在内存里面,生成一个对象{ foo: 5 },然后把这个对象的内存地址赋值给变量obj。

变量obj是一个地址(referencehttps://www.yaoruanwen.com/n/464017.html)。后面如果要读取obj.foo,引擎先从obj拿到内存地址,然后再从该地址读出原始的对象,返回它的foo属性。

原始的对象以字典结构保存,每一个属性名都对应一个属性描述对象。举例来说,上面例子的foo属性,实际上是以下面的形式保存的。

{

foo: {

[[value]]: 5

[[writable]]: true

[[enumerable]]: true

[[configurable]]: true

}

}

注意,foo属性的值保存在属性描述对象的value属性里面。

3 函数

这样的结构是很清晰的,问题在于属性的值可能是一个函数。

引擎会将函数单独保存在内存中,然后再将函数的地址赋值给foo属性的value属性

{ foo: {

[[value]]: https://www.yaoruanwen.com/n/464024.html函数的地址

}

}

由于函数是一个单独的值,所以它可以在不同的环境(上下文)执行。

4 环境变量

JavaScript 允许在函数体内部,引用当前环境的其他变量。

上面代码中,函数体里面使用了变量x。该变量由运行环境提供。

现在问题就来了,由于函数可以在不同的运行环境执行,所以需要有一种机制,能够在函数体内部获得当前的运行环境(context)。所以,this就出现了,它的设计目的就是在函数体内部,指代函数当前的运行环境。

上面代码中,函数体里面的this.x就是指当前运行环境的x。

var f = function () { console.log(this.x);

}var x = 1;var obj = { f: f, x: 2,

};// 单独执行f() // 1// obj 环境执行obj.f() // 2

上面代码中,函数f在全局环境执行,https://www.yaoruanwen.com/n/464027.htmlthis.x指向全局环境的x。

在obj环境执行,this.x指向obj.x。

回到本文开头提出的问题,obj.foo()是通过obj找到foo,所以就是在obj环境执行。一旦var foo = obj.foo,变量foo就直接指向函数本身,所以foo()就变成在全局环境执行。

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值