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

Paddle Python API 设计文档(初稿) #1069

Closed
jacquesqiao opened this issue Jan 5, 2017 · 10 comments
Closed

Paddle Python API 设计文档(初稿) #1069

jacquesqiao opened this issue Jan 5, 2017 · 10 comments

Comments

@jacquesqiao
Copy link
Member

jacquesqiao commented Jan 5, 2017

一个典型的训练过程

gradient_machine.startPass()
updater.startPass()
for each_batch in data:
    gradient_machine.startBatch()
    updater.startBatch()

    gradient_machine.train()

    updater.finishBatch()
    gradient_machine.finishBatch()
updater.finishPass()
gradient_machine.finishPass()

用一个类似调用链的东西,把操作分离开。比如上面的例子可以被拆成两个RunnerChainItems.

  • GradientMachineOperations
  • UpdaterOperations.

一些核心概念

  • Runner
  • RunnerItem
  • RunnerBuilder

Runner

Runner主要利用GradientMachine层面暴露出来的API,将原来Trainer.cpp的逻辑封装起来,Runner中包含很多个RunnerItem,每个RunnerItem完成Trainer中的部分逻辑,用户可以循环调用Runner的run_pass,Runner内部通过一个一个的RunnerItem完成之前各个组件的功能,比如updater,gradientmachine的forward/backward,parameter save/load等操作,用户无需关心。

Runner的实现

class Runner(object):
    def add_item(self, item):
        """
        Add a runner item to runner.
        """
    def run_one_pass(self):
        """
        Run one pass for runner. The parent argument will passed to context.
        """

构造一个runner的过程

    runner = Runner()

    runner.add_item(ItemA())
    runner.add_item(ItemB())

    with runner:
        runner.run_one_pass()

RunnerItem

RunnerItem is an item in Runner. Runner will composite the
RunnerItems together and invoke the first RunnerChainItem's methods.
And Runner will pass the next chain item's method as next_callback.
If current chain item is the last item. A default next_callback will be
passed.

Context is a global object shared by items.

class RunnerItem(object):
    """
    RunnerItem is an item in Runner. Runner will composite the
    RunnerItems together and invoke the first RunnerChainItem's methods.
    And Runner will pass the next chain item's method as `next_callback`.
    If current chain item is the last item. A default next_callback will be
    passed.

    Context is a global object shared by items.
    """

    def __init__(self):
        pass

    def initialize(self, context, next_callback):
        """
        initialize method. It will be invoked when Runner start to run.

        :param context: a global object shared by items.
        :type context: RunnerContext
        :param next_callback: next item's initialize method.
        :type next_callback: callable
        :return: None
        :rtype: None
        """
        next_callback(context)

    def finalize(self, next_callback):
        """
        Finalize method. It will be invoked when Runner complete run, and clean
        some state in RunnerItem.

        :param next_callback: next item's initialize method.
        :type next_callback: callable
        :return: None
        :rtype: None
        """
        next_callback()

    def on_pass_begin(self, next_callback):
        """
        Pass Begin Method. Invoked when a pass begins.

        :param next_callback: next item's initialize method.
        :type next_callback: callable
        :return: None
        :rtype: None
        """

        next_callback()

    def on_pass_end(self, next_callback):
        """
        Pass End Method. Invoked when a pass ends.

        :param next_callback: next item's initialize method.
        :type next_callback: callable
        :return: None
        :rtype: None
        """
        next_callback()

    def on_batch_begin(self, next_callback):
        """
        Batch Begin Method. Invoked when a batch begins. Return true if there is
        no more batch could be processed.

        :param next_callback: next item's initialize method.
        :type next_callback: callable
        :return: True if no more batch could be processed.
        :rtype: bool
        """
        return next_callback()

    def on_batch_end(self, next_callback):
        """
        Batch End Method. Invoked when a batch ends. Return true if there is
        no more batch could be processed.

        :param next_callback: next item's initialize method.
        :type next_callback: callable
        :return: True if no more batch could be processed.
        :rtype: bool
        """
        return next_callback()

已经实现的RunnerItem

  • CreateGradientMachine
  • BasicLocalParameterUpdaterOps
  • BasicGradientMachineTrainOps
  • BasicGradientMachineTestOps
  • SaveParamsOnPassEnd
  • Counter

RunnerBuilder

将build Runner的过程封装起来,用with_std_local_trainer等辅助函数方式组装一个可以运行的Runner

import paddle.trainer.PyDataProvider2 as dp
from paddle.trainer_config_helpers import *

import mnist_provider
from py_paddle.trainer import *


@network(
    inputs={
        'pixel': dp.dense_vector(784),
        'label': dp.integer_value(10),
    },
    learning_rate=1e-4,
    learning_method=AdamOptimizer(),
    batch_size=1000,
    model_average=ModelAverage(average_window=0.5),
    regularization=L2Regularization(rate=0.5))
def mnist_network(pixel, label):
    hidden1 = fc_layer(input=pixel, size=200)
    hidden2 = fc_layer(input=hidden1, size=200)
    inference = fc_layer(input=hidden2, size=10, act=SoftmaxActivation())
    cost = classification_cost(input=inference, label=label)
    return cost


def main():
    mnist = mnist_network()
    runner = RunnerBuilder(
        network=mnist, device_count=2).with_std_local_trainer(
            method=mnist_provider.process,
            file_list=['./data/raw_data/train']).with_std_tester(
                method=mnist_provider.process,
                file_list=['./data/raw_data/t10k']).build()
    with runner:
        for _ in xrange(2):
            runner.run_one_pass()


if __name__ == '__main__':
    main()
@jacquesqiao
Copy link
Member Author

jacquesqiao commented Jan 5, 2017

@wangkuiyi
Copy link
Collaborator

wangkuiyi commented Jan 5, 2017

有不少看不明白的地方:

  1. 为什么要Pass这个概念?我看到有 startPass, finishPass 之类的函数。怎么定义pass的起止?batch learning可以定义为扫描一遍数据文件;如果是online learning怎么定义?
  2. gradient machine和 updater 分别是用来做什么的?gradient machine是用来算gradient的吗?updator是用来update model parameters的?那么他们的操作对象model为什么没有在这个例子里体现出来呢?我们训练完了之后,不是应该需要 model.save(...) 这样的操作吗?
  3. 为什么有一个Runner?它是用来“跑”什么的?如果是跑训练过程,为什么不叫Trainer?
  4. Runner是不是实现了一个 training loop,是一个比 gradient machine 和 updater 更高一层次的API?
  5. 为什么需要RunnerItem?几个RunnerItem都不能顾名思义。比如 BasicLocalParameterUpdaterOps,何谓basic?怎么就不算basic?
  6. RunnerBuilder是干什么的?如果只是创建一个runner用的,一个函数 create_runner 是不是就行了?

@jacquesqiao
Copy link
Member Author

我先回答部分概念问题:
1,startPass, finishPass Paddle 内部逻辑中的概念,之前裸写api时需要用到,现在会被封装到runnerItem中,所以用户看不到了。目前还没有对online learning的操作。
2,按我的理解,现在没有一个叫model的东西,model在gradient machine中,构造gradient machine的时候,初始化了相关layer和parameter,gradient machine负责通过forward/backward算gradient,然后通过callback调用updater来跟新parameter。
3,runner其实类似之前的trainer,可以train/test/evaluate,取决于里面有哪些item。也类似tf的session
4,runner是把paddle的各个组件串起来了,比如gmachine,updater,evaluater等,确实是一个更高层次的api。
5,RunnerItem是为了提供一个公共的结构,然后之前的各个组件,比如updater,gmacine的各种操作都是成对出现的,按照这个结构实现之后,只要被add到trainer的chain中,run_padd的时候就可以自动按照顺序执行一些逻辑,比如start_pass/stop_pass等。

@reyoung
Copy link
Collaborator

reyoung commented Jan 5, 2017

为什么要Pass这个概念?我看到有 startPass, finishPass 之类的函数。怎么定义pass的起止?batch learning可以定义为扫描一遍数据文件;如果是online learning怎么定义?

一个pass = 训练过所有数据。Pass的结束就是所有数据训练结束。Online Learning这个概念,可以整体是一个Pass。

gradient machine和 updater 分别是用来做什么的?gradient machine是用来算gradient的吗?updator是用来update model parameters的?那么他们的操作对象model为什么没有在这个例子里体现出来呢?我们训练完了之后,不是应该需要 model.save(...) 这样的操作吗?

Paddle的模型主体分为两个方面,一个是神经网络结构(protobuf),一个是神经网络的参数。神经网络参数的save是在某一个RunnerItem中做的。

当然,也可以手动获得这些参数。

为什么有一个Runner?它是用来“跑”什么的?如果是跑训练过程,为什么不叫Trainer?

Runner是一个比Trainer更广义的概念。只要是Run一个有Pass--Batch关系的东西,都可以叫做Runner。并且Runner可以嵌套。

常见的Runner是,外层是一个训练过程,训练所有的数据。内层嵌套一个Runner用作测试过程,在训练的Runner Pass结束的时候,调用内层嵌套的Runner,进行测试或者Evaluate。

这都是可以自定义的。

Runner是不是实现了一个training loop,是一个比 gradient machine 和 updater 更高一层次的API?
为什么需要RunnerItem?几个RunnerItem都不能顾名思义。比如 BasicLocalParameterUpdaterOps,何谓basic?怎么就不算basic?

RunnerItem是一个Runner的某一层操作。类似于中间件。这些中间件可以相互组合,形成不同的Runner。例如,面对集群训练的时候,只需要将LocalParameterUpdater的Item替换成Remote的即可。又例如,在每隔100个Batch之后测试全量数据,也是一种RunnerItem。

通过这种中间件的组合,用户可以更方便的自定义训练过程。

RunnerBuilder是干什么的?如果只是创建一个runner用的,一个函数create_runner 是不是就行了?

有意思的地方就是这个Builder。因为各种任务的训练过程可能不同,所以通过组合RunnerItem可以复用非常多的逻辑。

@reyoung
Copy link
Collaborator

reyoung commented Jan 5, 2017

这个文档并不完善,我再把整体思路整理一下,说明为什么要用Runner,RunnerItem这种抽象吧。

其实主体是复用代码。

@jacquesqiao jacquesqiao self-assigned this Jan 5, 2017
@wangkuiyi
Copy link
Collaborator

wangkuiyi commented Jan 5, 2017

好。我建议我们明天开一个VC讨论。欢迎更多同学加入。

这个设计会影响到今后很长时间Paddle用户的体验。一定得做好。

做一个好的设计不容易。不仅是有一个“做法”,而且得能说明白为什么我们的“做法”是最好的。

很多时候,“好”不容易定义,所以大家会用“简洁”来衡量,是以谓之 Occam's Razor。或者Rob Pike称为“Less is exponentially more“。

要能设计出最简洁的做法,首先对系统里重要概念得能拿得住。比如NN的核心是“模型”,不管训练还是inference都要用到。TF把模型描述成一个很通用的compute flow graph,但是我们看到太通用会带来很多麻烦——说白了就是不够简洁——这是我们得避免的。我不确定我们定义模型的时候,是不是应该考虑到 @hedaoyuan 的 Function 概念,以及这样是否会让我们的模型定义更简洁。

在此基础上,我们要做两件事:trainng 和 inference。超出这两件事儿的,我们应该倾向于砍掉。所以当我看到“runner是一个比trainer更通用的概念”这样的描述,就会就raze的冲动。:-) 我赞同得有一个trainer,trainer貌似要能执行一个training algorithm,这个algorithm在循环里要等调用 training data的 fetch 方法来获得训练数据(minibatch)。我以为我们最好提供几个algorithms的实现即可。

考虑到有些用户可能需要定义自己的algorithm,自己写trainng loop,那我们确实就得暴露更多底层facilities,比如 gradient machine 和 updater。它俩如何简洁地和model以及training data交互,会是另一层要考虑的问题。

@wangkuiyi wangkuiyi self-assigned this Jan 5, 2017
@hedaoyuan
Copy link
Contributor

Function的概念是可以对外的,单纯把Function的概念引入到Python中Code实现上也部分很复杂。这里有几点问题:

  1. 当前的Function的结构上还不是很完善,参数的数据结构部分也都还没实现好,现在如果直接考虑Python中怎么调用Function来计算还不行;
  2. 当前Function的设计实现主要是为了优化Layer的结构(C++部分的代码),其中的一些数据结构设计上,主要也是为了如何便于在C++的Neural Network、Layer、Computation API之间做数据传递;
  3. Python API和数据结构的实现上可以更多的考虑如何便于使用Paddle,如何便于配置Neural Network计算;最后可以根据Python API的数据结构来修改Function的数据结构,或者二者之间做一定的适配。

@reyoung
Copy link
Collaborator

reyoung commented Jan 5, 2017

我建议我们明天开一个VC讨论。欢迎更多同学加入。

这太好了。之所以写这个Experimental的code其实是因为很多东西我也想不明白,只是边写边想。并且,确实要把这个东西做的『简单』、『好用』,思考起来非常困难。所以,也期待大家帮忙,把这事情搞的好一些。

Function我也调查了一下,但是现阶段本身Function的API还在持续变动中,目前暴露可能比较麻烦。

至于很多PR,写的第一版可能只是一种整理思考的过程,完全可以废弃。之所以写code,可能只是写code思考比写文档来思考更快一些。所以,如果完全废弃,也完全没必要担心 :-)

@jacquesqiao
Copy link
Member Author

这个文档更多的是面向paddle开发者,而不是使用者,所以应该从用户的角度重新设计一下。

@jacquesqiao
Copy link
Member Author

this design is discarded

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

4 participants