这两天琢磨了下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()就变成在全局环境执行。