Skip to content

Conversation

@Superjomn
Copy link
Contributor

@Superjomn Superjomn commented Aug 18, 2017

一个简单的wrapper实现,但只实现了核心的部分,整理下一些想法:

基于的大前提

  • python wrapper 不需要以 caffe2 为参考对象,而应该以更流行的 tf 或 pytorch 为参考的基础
    • caffe2 的python语法并不流行;c++ 部分参考之,因为实现简单加速开发;python wrapper 工作量不大,参考 caffe2 没有必要的理由
    • 我们最终面向的是用户,当下 TF 和 pytorch 占主流,不可否认这两个平台的受众占大多数(更流行)
      • 一些基本的思想已经潜移默化成了行业的潮流,比如输入输出必然是 tensor(mxnet也是,流行度前三的平台的选择)
      • 拓扑通过 input/output 的argument 自动创建,而非 caffe2 中的 model.add_op(xxx) 或 net.add_op(xxx)
    • v2 反而比较类似 tf, 每个 layer 类似一个 function,通过 cost 自动推导拓扑等,类似 caffe2 显式有个net,感觉并不一定需要

具体实现细节:

  • 以 op 的inputs 和 outputs 来自动推断拓扑,而非基于 op, 比如 net.add(op) 或者 model.add(op)
  • inputs 和 outputs 必须为 Var , 而非字符串, typeof(input 应该是 Var 而非 str,实现可以是 str 但提供给用户的是概念,Var 里面有更多一些逻辑
  • 根据 target 自动推断涉及的子图,DFS抽取出对应的最小子图, 动态创建 NetOp 来run,除了支持原始的 paddle.trainer.train 之外,提供 paddle.trainer.run(targets, ...) 接口来执行类似 tf.Session.run 的逻辑,子图的执行更自然
  • 所有的 layer 和 op 等 sub-module 全部折叠到 paddle 下(类似tf),比如 paddle.layer.fc 也可以用
import xxx as pd fc_out = pd.layer.fc(xxx) fc_out = pd.fc(xxx)

模块

  • var.py variable 的封装,提供 Var, Var 会统一所有 op, layer 的 inputs 和 outputs 格式
  • op.py 包含所有 op 的实现
    • pybind 的所有 op 都会有 python 的封装以支持更 user-friendly 的语法
  • layer.py layer实现
  • topology.py DFS 子图推断相关
  • session.py 提供Session 存储所有创建的 Var 和 Op, 提供类似 tf.run 的接口
    • 类似 tf.Session, Session 全局只需要一份,可以用 g_session 隐藏掉

只增加了 Var 新概念

由于所有的 oplayer 的 inputs 和 outputs 都统一成了 Var

MINIST 使用示例

兼容 v2 的方式

# all the layer, op namespace are imported into pd for short # the old absolute path should also work # for example, pd.data -> paddle.layer.data import paddle.v2 as pd images = pd.data(name='pixe', type=pd.dense_vector(784)) label = pd.data(name='label', type=pd.integer_value(10)) prediction = pd.fc(input=images, size=10, act=...) cost = pd.cross_entropy(prediction, label) optimizer = pd.Momentum(...) # following are different styles should supported # v2 style, seems that hard to support multiple sub-model parameters = pd.parameters.create(cost) trainer = pd.SGD(cost=[cost], parameters=parameters, update_equation=optimizer) trainer.train(reader=..., num_passes=5)

v2 的方式貌似很难支持多个sub-model 的执行(不支持子图),必须往 paddle.trainer 里 添加新的接口,这里是此demo核心概念应该支持的另外一种写法

# all the layer, op namespace are imported into pd for short # the old absolute path should also work # for example, pd.data -> paddle.layer.data import paddle.v2 as pd images = pd.data(name='pixe', type=pd.dense_vector(784)) label = pd.data(name='label', type=pd.integer_value(10)) prediction = pd.fc(input=images, size=10, act=...) cost = pd.cross_entropy(prediction, label) optimizer = pd.Momentum(...) # style with new features borrowed from tf and pytorch trainer = pd.SGD() # same as global_variables_initializer trainer.init_parameters() # train a sub-model if there has more than one sub-models has different costs like gan trainer.train(targets=[cost], update_equation=optimizer, reader=...)

infer

# just forward run is supported # borrowed from tf, forward run a sub-model whose end point is targets trainer.run(targets=[cost], reader=...)

最后的想法

  • python wrapper 最好能多参考写model同学的建议,特别是比较熟悉/用过 tf, pytorch, mxnet, caffe2 等
  • 写 python wrapper 的 developer, 和用 python wrapper 的user,想法可能差异很大
  • 我们最终面向的用户是同一群人,客观现实,他们在使用的习惯,概念上已经被非常主流的框架同化了
  • 如果提供非主流的用法,可能没有很多的小白用户接受再教育,小白是从其他平台或者了解其他平台用法的过来的,不免会带入其他平台的一些使用习惯
@zchen0211
Copy link
Contributor

I like Chunwei's idea. Caffe2 has good c++ implementation, but tf has better and more friendly user experience. We can learn from both :)

@helinwang
Copy link
Contributor

helinwang commented Aug 18, 2017

非常赞同我们应该更像TF和PyTorch,以及用DAG图自动追溯应该运行哪些OP。

有几点想讨论的:

  1. 所有的 layer 和 op 等 sub-module 全部折叠到 paddle 下(类似tf),比如 paddle.layer.fc 也可以用
    import xxx as pd
    fc_out = pd.layer.fc(xxx)
    fc_out = pd.fc(xxx)

    同一个概念有两个使用方式(pd.layer.fc, pd.fc)有些奇怪,能否只有pd.op.fc,更高层的我们可以起名叫pd.layer.lstmpd.op.*, pd.layer.*都应该返回Var

  2. 除了支持原始的 paddle.trainer.train 之外,提供 paddle.trainer.run(targets, ...)

    paddle.trainer.run很奇怪:trainer是负责训练的,为何还能run,来做inference。
    要不要我们还是删掉trainer,加一个session的概念,sess.run(init_target); sess.run(targets, input_dict) # target can be an optimizer,或者更高层的helper:paddle.train(sess, targets, reader)

  3. topology.py DFS 子图推断相关

    可以考虑子图推断写在cpp里面,Python只负责把整个图传给cpp。

  4. 可以用 g_session 隐藏掉

    建议尽量不要有任何全局变量。全局变量让代码难读,使用起来也让多个network instance难以同时跑(比如要是都依赖一个全局变量怎么办)。

  5. 兼容V2

    V2的设计中optimizer并不是图的一部分,从trainer = pd.SGD(cost=[cost], parameters=parameters, update_equation=optimizer)可以看出(如果是图的一部分的话就不需要指定cost,直接指定optimizer即可)。重构之后我理解一切都是图的一部分(包括optimizer), 由于不少概念有区别, 继续完全支持V2可能会让用户使用起来不符合intuition,要不要考虑不需要兼容V2 API,我们在V2 API上面做一点修改,让其变的更好用。

@helinwang
Copy link
Contributor

请对模型最熟悉的@lcy-seso 老师review一下这个PR吧!

@helinwang helinwang requested a review from lcy-seso August 18, 2017 18:28
@Superjomn
Copy link
Contributor Author

现在大家的结论是,v2 是必须要兼容的,一来向后兼容语法;二来正好当成高层的接口用,比如 keras 之于 tf @helinwang

@wangkuiyi
Copy link
Collaborator

类似 caffe2 显式有个net,感觉并不一定需要

赞同

@wangkuiyi
Copy link
Collaborator

wangkuiyi commented Aug 20, 2017

Var这个概念是不是类似Caffe2的BlobReference了?我们确实需要这样一个概念,才能让 paddle.layer.XXX 函数可以被functional的形式调用:

paddle.layer.mse(paddle.layer.softmax(paddle.layer.fc(... 

不过Caffe2的BlobReference只包含 name 和 Net 两个fields,为什么Var里需要这么多内容呢?

@wangkuiyi
Copy link
Collaborator

是说 目前 v2 API 设计里 trainer 和拓扑绑定了:

trainer = pd.SGD(cost=[cost], parameters=parameters, update_equation=optimizer) trainer.train(reader=..., num_passes=5) 

所以不能训练多个子图吗?我支持不绑定trainer和拓扑,不过即使绑定貌似也是可以训练多个子图的——为每个子图创建一个trainer。

@Superjomn
Copy link
Contributor Author

Caffe2 的一些概念,比如 BlobReferenceModel 都是类似 C structure without methods,就是一些数据的集合;代码看起来比较简单,但用户上手和使用时不一定方便:

  • 概念与正常的编程语言(比如python)不太一致,如果说 op 都是 python functions, 那所有的变量应该叫 Variable/Argument 等,BlogReference 不直观(感觉是从 developer 的角度命名的)。
  • 变量这个概念感觉用一个 class 的界面把该有的 data 和 method 封装并呈现出来;tf 和 pytorch 里基本的输入输出都是 tensor,明确的概念;而 tensor 对应的方法都是以 OOP 的方式封装好的,而非 caffe2 里一个结构体,然后另外提供一些 helper。

这里 Var 里的一堆代码,是为了对应 class in C++,把 Variable 这个概念封装起来,并且直接提供用户必须的一些 methods,比如 Var.val() ,当用户好奇一个 Var 里具体的值时,可以用这个接口得到 python numpy 表示的值。

另外,Var 是用来推导拓扑的基础,在这个 design里,Op 对应到编程语言里的 function, Var 对应到数据; 通过 Var 的调用关系来推导拓扑。

@wangkuiyi

@Superjomn
Copy link
Contributor Author

所以不能训练多个子图吗?我支持不绑定trainer和拓扑,不过即使绑定貌似也是可以训练多个子图的——为每个子图创建一个trainer。

TF 的语法界面跟一个编程语言很像, tf.Variable 对应变量/数据, op 对应无状态的函数;尽管由于实现机制的束缚无法像 pytorch 那样完全给出一个近python语法的界面,但相比其他平台(mxnet, caffe2)已经很贴近常规编程语言的语法。

TF里用户只需要关注变量:

  • a = tf.Variable() 创建一个变量
  • b = tf.Sigmoid(a) 用 op 处理变量
  • session = tf.Session(); session.run([a, b]) 同时计算a, b变量对应的子图,得到 a, b这两个变量通过模型执行完的结果
    • 这里 [a,b] 对应到一个有 a, b 两个终点的最小公共子图; 而如果创建两个 trainer, 则对应到两个子图
    • session.run([a,b])可以跑一次图得到两个结果; trainer.train(a); trainer.train(b) 会跑两次图,如果两个图有交集,交集的节点会跑两次
    • ** 类似 session.run([vars...]) 的语法可以很自然的表示最小公共子图;而创建两个trainer不行 **

@wangkuiyi

@Superjomn
Copy link
Contributor Author

Superjomn commented Aug 20, 2017

对比 TF minist democaffe2 minist demo

从一个只懂DL理论和 python 编程语言的小白眼里,TF 的概念基本能从经验推导:

  • Variable 对应python里的变量
  • Session 对应linux 里一次登录后的系统上下文
  • op 对应一堆函数
  • 一个模型的书写以 Variable 为中心,placeholder 做输入,调用不同的 op 对 variable 做处理,最终 session.run 得到 variable 的结果

相比于 caffe2,多了一些概念,也比较不直观,除去CNN的几行,参数初始化、SGD等细节上,TF只用了两个函数 tf.global_variables_initializer().run() , train_step = tf.train.GradientDescentOptimizer(0.5).minimize(cross_entropy) ,caffe2里也需要用户增加如下更多代码:

 for param in model.params: param_grad = model.param_to_grad[param] model.WeightedSum([param, ONE, param_grad, LR], param)
model.AddGradientOperators([loss]) # do a simple stochastic gradient descent ITER = brew.iter(model, "iter") # set the learning rate schedule LR = model.LearningRate( ITER, "LR", base_lr=-0.1, policy="step", stepsize=1, gamma=0.999 ) # ONE is a constant value that is used in the gradient update. We only need # to create it once, so it is explicitly placed in param_init_net. ONE = model.param_init_net.ConstantFill([], "ONE", shape=[1], value=1.0) # Now, for each parameter, we do the gradient updates. for param in model.params: # Note how we get the gradient of each parameter - ModelHelper keeps # track of that. param_grad = model.param_to_grad[param] # The update is a simple weighted sum: param = param + param_grad * LR model.WeightedSum([param, ONE, param_grad, LR], param)

@wangkuiyi

@lcy-seso
Copy link
Contributor

lcy-seso commented Aug 23, 2017

  • 子图在带控制流的网络 (GAN 或者 RL,或者动态构建网络)中才会有用,若干个子图肯定不可能是完全独立去forward,子图之间有连接关系;

  • GAN 只 forward/backward 一部分网络;或者依次 forward/backward 这两部分网络,对 trainer 的要求也比较简单。

  • 我理解 trainer 控制着整个任务定义的各个子图按照什么样的逻辑去何依次计算。一个子图之内的网络:自底向上forward,自顶向下backward。我觉得 trainer 不需要和 topology 绑定在一起,trainer 控制如何处理子图之间的连接。

  • 我觉得v2 不支持子图最主要的原因是没有 trainer (对应了paddle 现在的gradient machine)能够非常通用的“解释/执行”控制流,无法以通用的方式处理子图之间如何连,v2 的 trainer 目前只能依次forward/backward 所有的layer。

  • 对更广义的通用动态网络,用户需要有办法描述子图如何连接(conditional op),trainer 必须能能够处理子图之间如何依次计算,这一步会出现不是依次计算,但是这种情况可能会比较复杂,或者可以考虑以 GAN 为基本目标;

@Superjomn Superjomn closed this Oct 24, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

5 participants