Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Python wrapper follows tf and pytorch's concept #3566

Closed
wants to merge 2 commits into from

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吧!

@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
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants