提交 db379811 编写于 作者: Y Yu Yang 提交者: GitHub

Merge pull request #606 from luotao1/doc3

refine dataprovider related rst
DataProvider的介绍
==================
DataProvider是PaddlePaddle负责提供数据的模块。其作用是将数据传入内存或显存,让神经网络可以进行训练或预测。用户可以通过简单使用Python接口 `PyDataProvider2 <pydataprovider2.html>`_ ,来自定义传数据的过程。如果有更复杂的使用,或者需要更高的效率,用户也可以在C++端自定义一个 ``DataProvider`` 。
PaddlePaddle需要用户在网络配置(trainer_config.py)中定义使用哪种DataProvider,并且在DataProvider中实现如何访问训练文件列表(train.list)或测试文件列表(test.list)。
- train.list和test.list存放在本地(推荐直接存放到训练目录,以相对路径引用)。一般情况下,两者均为纯文本文件,其中每一行对应一个数据文件地址:
- 如果数据文件存于本地磁盘,这个地址则为它的绝对路径或相对路径(相对于PaddlePaddle程序运行时的路径)。
- 地址也可以为hdfs文件路径,或者数据库连接路径等。
- 由于这个地址会被DataProvider使用,因此,如何解析该地址也是用户自定义DataProvider时需要考虑的地方。
- 如果没有设置test.list,或设置为None,那么在训练过程中不会执行测试操作;否则,会根据命令行参数指定的测试方式,在训练过程中进行测试,从而防止过拟合。
PaddlePaddle的数据提供(DataProvider)介绍
========================================
数据提供(DataProvider)是PaddlePaddle负责提供数据的模块。其作用是将训练数据传入内存或者显存,让神经网络可以进行训练。简单的使用,用户可以使用Python的 :code:`PyDataProvider` 来自定义传数据的过程。如果有更复杂的使用,或者需要更高的效率,用户也可以在C++端自定义一个 :code:`DataProvider` 。
PaddlePaddle需要用户在网络配置(trainer_config.py)中定义使用哪种DataProvider及其参数,训练文件列表(train.list)和测试文件列表(test.list)。
其中,train.list和test.list均为本地的两个文件(推荐直接放置到训练目录,以相对路径引用)。如果test.list不设置,或者设置为None,那么在训练过程中,不会执行测试操作。否则,会根据命令行参数指定的测试方式,在训练过程中进行测试,从而防止过拟合。
一般情况下,train.list和test.list为纯文本文件,一行对应一个数据文件,数据文件存放在本地磁盘中。将文件的绝对路径或相对路径(相对于PaddlePaddle程序运行时的路径)写在train.list和test.list中。当然,train.list和test.list也可以放置hdfs文件路径,或者数据库连接地址等等。
用户在DataProvider中需要实现如何访问其中每一个文件。DataProvider的具体用法和如何实现一个新的DataProvider,请参考下述文章:
.. toctree::
pydataprovider2.rst
write_new_dataprovider.rst
......@@ -5,5 +5,6 @@ define_py_data_sources2(
test_list=None,
module='mnist_provider',
obj='process')
img = data_layer(name='pixel', size=784)
label = data_layer(name='label', size=10)
from paddle.trainer.PyDataProvider2 import *
# Define a py data provider
@provider(input_types=[dense_vector(28 * 28), integer_value(10)])
def process(settings, filename): # settings is not used currently.
f = open(filename, 'r') # open one of training file
for line in f: # read each line
label, pixel = line.split(';')
# get features and label
pixels_str = pixel.split(' ')
pixels_float = []
for each_pixel_str in pixels_str:
pixels_float.append(float(each_pixel_str))
# give data to paddle.
yield pixels_float, int(label)
f.close() # close file
PyDataProvider2的使用
=====================
PyDataProvider是PaddlePaddle使用Python提供数据的推荐接口。使用该接口用户可以只关注如何
从文件中读取每一条数据,而不用关心数据如何传输给PaddlePaddle,数据如何存储等等。该数据
接口使用多线程读取数据,并提供了简单的Cache功能。
PyDataProvider2是PaddlePaddle使用Python提供数据的推荐接口。该接口使用多线程读取数据,并提供了简单的Cache功能;同时可以使用户只关注如何从文件中读取每一条数据,而不用关心数据如何传输,如何存储等等。
.. contents::
简单的使用场景
--------------
MNIST的使用场景
---------------
我们以MNIST手写识别为例,来说明PyDataProvider2的简单使用场景。
这里以MNIST手写识别为例,来说明简单的PyDataProvider如何使用。MNIST是一个包含有
70,000张灰度图片的数字分类数据集。对于MNIST而言,标签是0-9的数字,而特征即为
28*28的像素灰度值。这里我们使用简单的文本文件表示MNIST图片,样例数据如下。
样例数据
++++++++
MNIST是一个包含有70,000张灰度图片的数字分类数据集。样例数据 ``mnist_train.txt`` 如下:
.. literalinclude:: mnist_train.txt
其数据使用;间隔,第一段数据为这张图片的label,第二段数据为这个图片的像素值。
首先我们将这个数据文件(例如文件名是'mnist_train.txt')写入train.list。那么
train.list即为
其中每行数据代表一张图片,行内使用 ``;`` 分成两部分。第一部分是图片的标签,为0-9中的一个数字;第二部分是28*28的图片像素灰度值。 对应的 ``train.list`` 即为这个数据文件的名字:
.. literalinclude:: train.list
那么对应的dataprovider既为
dataprovider的使用
++++++++++++++++++
.. literalinclude:: mnist_provider.py
:linenos:
.. literalinclude:: mnist_provider.dict.py
其中第一行是引入PaddlePaddle的PyDataProvider2包。主要函数是process函数。process函数
具有两个参数,第一个参数是 settings 。这个参数在这个样例里没有使用,具
体可以参考 settings 。第二个参数是filename,这个参数被PaddlePaddle进程传入,为
train.list中的一行(即train.list若干数据文件路径的某一个路径)。
- 首先,引入PaddlePaddle的PyDataProvider2包。
- 其次,定义一个Python的 `Decorator <http://www.learnpython.org/en/Decorators>`_ `@provider`_ 。用于将下一行的数据输入函数标记成一个PyDataProvider2,同时设置它的input_types属性。
:code:`@provider` 是一个Python的 `Decorator <http://www.learnpython.org/en/Decorators>`_
。这行的作用是设置DataProvider的一些属性,并且标记process函数是一个DataProvider。
如果不了解 `Decorator <http://www.learnpython.org/en/Decorators>`_ 是什么也没关系,
只需要知道这只是一个标记属性的方法就可以了。
- `input_types`_:设置这个PyDataProvider2返回什么样的数据。本例根据网络配置中 ``data_layer`` 的名字,显式指定返回的是一个28*28维的稠密浮点数向量和一个[0-9]的10维整数标签。
属性 `input_types`_ 是设置这个DataProvider返回什么样的数据。这里设置的是返回一个
28*28的稠密向量和一个[0-9],10维的整数值。 `input_types`_ 具体可以设置成什么其他格
式,请参考 `input_types`_ 的文档。
.. literalinclude:: mnist_config.py
:lines: 9-10
process函数是实现数据输入的主函数,在这个函数中,实现了打开文本文件,从文本文件中读取
每一行,并将每行转换成和 `input_types`_ 一致的特征,并在23行返回给PaddlePaddle进程。需要注意
的是, 返回的顺序需要和 `input_types`_ 中定义的顺序一致。
- 注意:如果用户不显示指定返回数据的对应关系,那么PaddlePaddle会根据layer的声明顺序,来确定对应关系。但这个关系可能不正确,所以推荐使用显式指定的方式来设置input_types。
- 最后,实现数据输入函数(如本例的 ``process`` 函数)。
同时,返回数据在PaddlePaddle中是仅仅返回一条完整的训练样本,并且使用关键词 :code:`yield` 。
在PyDataProvider中,可以为一个数据文件返回多条训练样本(就像这个样例一样),只需要在
process函数调用多次 :code:`yield` 即可。 :code:`yield` 是Python的一个关键词,相关的概
念是 :code:`generator` 。使用这个关键词,可以在一个函数里,多次返回变量。
- 该函数的功能是:打开文本文件,读取每一行,将行中的数据转换成与input_types一致的格式,然后返回给PaddlePaddle进程。注意,
在训练配置里,只需要使用一行代码即可以设置训练引用这个DataProvider。这个设置为
- 返回的顺序需要和input_types中定义的顺序一致。
- 返回时,必须使用Python关键词 ``yield`` ,相关概念是 ``generator`` 。
- 一次yield调用,返回一条完整的样本。如果想为一个数据文件返回多条样本,只需要在函数中调用多次yield即可(本例中使用for循环进行多次调用)。
.. literalinclude:: mnist_config.py
- 该函数具有两个参数:
- settings:在本例中没有使用,具体可以参考 `init_hook`_ 中的说明。
- filename:为 ``train.list`` 或 ``test.list`` 中的一行,即若干数据文件路径的某一个。
这里说明了训练数据是 'train.list',而没有测试数据。引用的DataProvider是 'mnist_provider'
这个模块中的 'process' 函数。
网络配置中的调用
++++++++++++++++
同时,根据模型配置文件中 :code:`data_layer` 的名字,用户也可以显式指定返回的数据对应关系。例如:
在网络配置里,只需要一行代码就可以调用这个PyDataProvider2,如,
.. literalinclude:: mnist_provider.dict.py
:linenos:
.. literalinclude:: mnist_config.py
:lines: 1-7
如果用户不指定返回数据的对应关系,那么PaddlePaddle会粗略的根据layer的声明顺序,
来确定对应关系。这个对应关系可能不正确。所以推荐使用显式指定返回值和数据对应关系。
训练数据是 ``train.list`` ,没有测试数据,调用的PyDataProvider2是 ``mnist_provider`` 模块中的 ``process`` 函数。
至此,简单的PyDataProvider样例就说明完毕了。对于用户来说,讲数据发送给PaddlePaddle,仅仅需要
知道如何从 **一个文件** 里面读取 **一条** 样本。而PaddlePaddle进程帮助用户做了
小结
+++++
* 将数据组合成Batch训练
* Shuffle训练数据
* 多线程数据读取
至此,简单的PyDataProvider2样例就说明完毕了。对用户来说,仅需要知道如何从 **一个文件** 中读取 **一条样本** ,就可以将数据传送给PaddlePaddle了。而PaddlePaddle则会帮用户做以下工作:
* 将数据组合成Batch进行训练
* 对训练数据进行Shuffle
* 多线程的数据读取
* 缓存训练数据到内存(可选)
* CPU->GPU双缓存
是不是很简单呢?
序列模型数据提供
----------------
时序模型的使用场景
------------------
样例数据
++++++++
序列模型是指数据的某一维度是一个序列形式,即包含时间步信息。所谓时间步信息,
不一定和时间有关系,只是说明数据的顺序是重要的。例如,文本信息就是一个序列
数据。
时序模型是指数据的某一维度是一个序列形式,即包含时间步信息。所谓时间步信息,不一定和时间有关系,只是说明数据的顺序是重要的。例如,文本信息就是一个序列数据。
这里举例的数据是英文情感分类的数据。数据是给一段英文文本,分类成正面情绪和
负面情绪两类(用0和1表示)。样例数据为
本例采用英文情感分类的数据,即将一段英文文本数据,分类成正面情绪和负面情绪两类(用0和1表示)。样例数据 ``sentimental_train.txt`` 如下:
.. literalinclude:: sentimental_train.txt
这里,DataProvider可以是
.. literalinclude:: sentimental_provider.py
这个序列模型比较复杂。主要是增加了初始化机制。其中 :code:`on_init` 函数是使用
`@provider`_ 中的 `init_hook`_ 配置参数配置给DataProvider的。这个函数会在
DataProvider创建的时候执行。这个初始化函数具有如下参数:
* 第一个参数是 settings 对象。
* 其他参数均使用key word argument形式传入。有部分参数是Paddle自动生成的,
参考 `init_hook`_ 。这里的 :code:`dictionary` 是从训练配置传入的dict对象。
即从单词字符串到单词id的字典。
dataprovider的使用
++++++++++++++++++
传入这个变量的方式为
相对MNIST而言,这个dataprovider较复杂,主要原因是增加了初始化机制 `init_hook`_。本例的 ``on_init`` 函数就是根据该机制配置的,它会在dataprovider创建的时候执行。
.. literalinclude:: sentimental_config.py
- 其中 ``input_types`` 和在 `@provider`_ 中配置的效果一致。本例中的输入特征是词ID的序列,因此使用 ``integer_value_sequence`` 类型来设置。
- 将 ``dictionary`` 存入settings对象,在 ``process`` 函数中使用。 dictionary是从网络配置中传入的dict对象,即一个将单词字符串映射到单词ID的字典。
这个声明基本上和mnist的样例一致。除了
* 在配置中读取了字典
* 在声明DataProvider的时候传入了dictionary作为参数。
.. literalinclude:: sentimental_provider.py
在 :code:`on_init` 函数中,配置了 `input_types` 。这个和在 `@provider`_ 中配置
`input_types` 效果一致,但是在 `on_init` 中配置 `input_types` 是在运行时执行的,所以
可以根据不同的数据配置不同的输入类型。这里的输入特征是词id的序列,所以将 :code:`seq_type`
设置成了序列(同时,也可以使用 :code:`integer_sequence` 类型来设置)。
网络配置中的调用
++++++++++++++++
同时,将字典存入了settings 对象。这个字典可以在 :code:`process` 函数中使用。 :code:`process`
函数中的 settings 和 :code:`on_init` 中的settings 是同一个对象。
调用这个PyDataProvider2的方法,基本上和MNIST样例一致,除了
而在 :code:`process` 函数中,基本的处理逻辑也和mnist逻辑一致。依次返回了文件中的每条数据。
* 在配置中需要读取外部字典。
* 在声明DataProvider的时候传入dictionary作为参数。
至此,基本的PyDataProvider使用介绍完毕了。具体DataProvider还具有什么功能,请参考下节reference。
.. literalinclude:: sentimental_config.py
:emphasize-lines: 12-14
参考(Reference)
---------------
......@@ -127,41 +109,38 @@ DataProvider创建的时候执行。这个初始化函数具有如下参数:
@provider
+++++++++
:code:`@provider` 是一个Python的 `Decorator`_ ,他可以将某一个函数标记成一个PyDataProvider。它包含的参数有:
* `input_types`_ 是数据输入格式。具体有哪些格式,参考 `input_types`_ 。
* should_shuffle 是个DataProvider是不是要做shuffle,如果不设置的话,训练的时候默认shuffle,
测试的时候默认不shuffle。
* min_pool_size 是设置DataProvider在内存中最小暂存的数据条数。这个也是PaddlePaddle所能够保证的shuffle粒度。
设置成-1的话,会预先读取全部数据到内存中。
* pool_size 是设置DataProvider在内存中暂存的数据条数。设置成-1的话,即不在乎内存暂存多少条数据。
* can_over_batch_size 表示是否允许Paddle暂存略微多余pool_size的数据。这样做可以避免很多死锁问题。
一般推荐设置成True
* calc_batch_size 传入的是一个函数,这个函数以一条数据为参数,返回batch_size的大小。默认情况下一条数据
是一个batch size,但是有时为了计算均衡性,可以将一条数据设置成多个batch size
* cache 是数据缓存的策略,参考 `cache`_
* init_hook 是初始化时调用的函数,参考 `init_hook`_
* check 设置成true的话,会根据input_types检查数据的合法性。
* check_fail_continue 如果设置成true的话,即使在check中数据不合法,也会扔到这条数据,继续训练。 如果
check是false的话,没有作用。
``@provider`` 是一个Python的 `Decorator`_ ,可以将某一个函数标记成一个PyDataProvider2。如果不了解 `Decorator`_ 是什么也没关系,只需知道这是一个标记属性的方法就可以了。它包含的属性参数如下:
* input_types:数据输入格式。具体的格式说明,请参考 `input_types`_ 。
* should_shuffle:是不是要对数据做Shuffle。训练时默认shuffle,测试时默认不shuffle。
* min_pool_size:设置内存中最小暂存的数据条数,也是PaddlePaddle所能够保证的shuffle粒度。如果为-1,则会预先读取全部数据到内存中。
* pool_size: 设置内存中暂存的数据条数。如果为-1(默认),则不在乎内存暂存多少条数据。如果设置,则推荐大于训练时batch size的值,并且在内存足够的情况下越大越好。
* can_over_batch_size:是否允许暂存略微多余pool_size的数据。由于这样做可以避免很多死锁问题,一般推荐设置成True。
* calc_batch_size:可以传入一个函数,用于自定义每条数据的batch size(默认为1)。
* cache: 数据缓存的策略,具体请参考 `cache`_ 。
* init_hook:初始化时调用的函数,具体请参考 `init_hook`_ 。
* check:如果为true,会根据input_types检查数据的合法性。
* check_fail_continue:如果为true,那么当check出数据不合法时,会扔到这条数据,继续训练或预测。(对check=false的情况,没有作用)
input_types
+++++++++++
PaddlePaddle的数据包括四种主要类型,和三种序列模式。其中,四种数据类型是
PaddlePaddle的数据包括四种主要类型,和三种序列模式。
四种数据类型:
* dense_vector 表示稠密的浮点数向量。
* sparse_binary_vector 表示稀疏的零一向量,即大部分值为0,有值的位置只能取1
* sparse_float_vector 表示稀疏的向量,即大部分值为0,有值的部分可以是任何浮点数
* integer 表示整数标签。
* dense_vector:稠密的浮点数向量。
* sparse_binary_vector:稀疏的01向量,即大部分值为0,但有值的地方必须为1。
* sparse_float_vector:稀疏的向量,即大部分值为0,但有值的部分可以是任何浮点数。
* integer:整数标签。
而三种序列模式为
三种序列模式:
* SequenceType.NO_SEQUENCE 即不是一条序列
* SequenceType.SEQUENCE 即是一条时间序列
* SequenceType.SUB_SEQUENCE 即是一条时间序列,且序列的每一个元素还是一个时间序列。
* SequenceType.NO_SEQUENCE:不是一条序列
* SequenceType.SEQUENCE:是一条时间序列
* SequenceType.SUB_SEQUENCE: 是一条时间序列,且序列的每一个元素还是一个时间序列。
不同的数据类型和序列模式返回的格式不同,列表如下
不同的数据类型和序列模式返回的格式不同,列表如下
+----------------------+---------------------+-----------------------------------+------------------------------------------------+
| | NO_SEQUENCE | SEQUENCE | SUB_SEQUENCE |
......@@ -177,31 +156,32 @@ PaddlePaddle的数据包括四种主要类型,和三种序列模式。其中
其中,f代表一个浮点数,i代表一个整数。
注意:对sparse_binary_vector和sparse_float_vector,PaddlePaddle存的是有值位置的索引。例如,
- 对一个5维非序列的稀疏01向量 ``[0, 1, 1, 0, 0]`` ,类型是sparse_binary_vector,返回的是 ``[1, 2]`` 。
- 对一个5维非序列的稀疏浮点向量 ``[0, 0.5, 0.7, 0, 0]`` ,类型是sparse_float_vector,返回的是 ``[(1, 0.5), (2, 0.7)]`` 。
init_hook
+++++++++
init_hook可以传入一个函数。这个函数在初始化的时候会被调用。这个函数的参数是:
init_hook可以传入一个函数。该函数在初始化的时候会被调用,其参数如下:
* 第一个参数是 settings 对象。这个对象和process的第一个参数一致。具有的属性有
* settings.input_types 设置输入类型。参考 `input_types`_
* settings.logger 一个logging对象
* 其他参数都使用key word argument传入。这些参数包括paddle定义的参数,和用户传入的参数。
* Paddle定义的参数包括:
* is_train bool参数,表示这个DataProvider是训练用的DataProvider或者测试用的
DataProvider
* file_list 所有文件列表。
* 用户定义的参数使用args在训练配置中设置。
* 第一个参数是settings对象,它和数据传入函数的第一个参数(如本例中 ``process`` 函数的 ``settings`` 参数)必须一致。该对象具有以下两个属性:
* settings.input_types:数据输入格式,具体请参考 `input_types`_ 。
* settings.logger:一个logging对象。
* 其他参数使用 ``kwargs`` (key word arguments)传入,包括以下两种:
* PaddlePaddle定义的参数: 1)is_train:bool型参数,表示用于训练或预测;2)file_list:所有文件列表。
* 用户定义的参数:使用args在网络配置中设置。
注意,PaddlePaddle保留添加参数的权力,所以init_hook尽量使用 :code:`**kwargs` , 来接受不使用的
函数来保证兼容性。
注意:PaddlePaddle保留添加参数的权力,因此init_hook尽量使用 ``**kwargs`` 来接受不使用的函数以保证兼容性。
cache
+++++
DataProvider提供了两种简单的Cache策略。他们是
PyDataProvider2提供了两种简单的Cache策略:
* CacheType.NO_CACHE 不缓存任何数据,每次都会从python端读取数据
* CacheType.CACHE_PASS_IN_MEM 第一个pass会从python端读取数据,剩下的pass会直接从内存里
* CacheType.NO_CACHE:不缓存任何数据,每次都会从python端读取数据
* CacheType.CACHE_PASS_IN_MEM:第一个pass会从python端读取数据,剩下的pass会直接从内存里
读取数据。
......@@ -211,15 +191,9 @@ DataProvider提供了两种简单的Cache策略。他们是
可能的内存泄露问题
++++++++++++++++++
PaddlePaddle将train.list中的每一行,都传递给process函数,从而生成多个generator。
即如果train.list中,有100个训练文件,即会生成100个generator。这个本身不是一个很
严重的问题。
PaddlePaddle将train.list中的每一行都传递给process函数,从而生成多个generator。当训练数据非常多时,就会生成非常多的generator。
但是,如果在训练时,每一条训练数据都是一个文件,并且,训练数据非常多的情况下,就
会生成多个generator。每个generator在没有调用的时候,是几乎不占内存的。但是,当调
用过一次的时候,generator便会存下当前的上下文(Context)。而这个Context可能会非常
大。并且,generator至少调用两次才会知道是否停止。所以,即使在process里面只会有一
个yield,也需要两次随机选择到同样的generator的时候,才会释放该段内存。
虽然每个generator在没有调用的时候,是几乎不占内存的;但当调用过一次后,generator便会存下当前的上下文(Context),而这个Context可能会非常大。并且,generator至少需要调用两次才会知道是否停止。所以,即使process函数里面只有一个yield,也需要两次随机选择到相同generator的时候,才会释放该段内存。
.. code-block:: python
......@@ -230,11 +204,10 @@ PaddlePaddle将train.list中的每一行,都传递给process函数,从而生
tmp = next(f) # 调用一次,返回0
tmp = next(f) # 调用第二次的时候,才会Stop Iteration
而如果按顺序调用这些generator就不会出现这个问题。
由于顺序调用这些generator不会出现上述问题,因此有两种解决方案:
所以最佳实践推荐不要将每一个样本都放入train.list。而是将样本的地址放入另一个文本
文件,train.list写入那个文本文件的地址。 或者在python generator的上下文中尽量留
下非常少的变量引用。例如
1. **最佳推荐**:将样本的地址放入另一个文本文件,train.list写入那个文本文件的地址。即不要将每一个样本都放入train.list。
2. 在generator的上下文中尽量留下非常少的变量引用,例如
.. code-block:: python
......@@ -245,13 +218,10 @@ PaddlePaddle将train.list中的每一行,都传递给process函数,从而生
def process(fn):
yield real_process(fn)
这个问题是PyDataProvider读数据时候的逻辑问题,基本上不能整体修正。
注意:这个问题是PyDataProvider读数据时候的逻辑问题,很难整体修正。
内存不够用的情况
++++++++++++++++
PyDataProvider2会尽量使用内存。所以如果对于内存比较小的机器,推荐设置
:code:`pool_size` 变量,而这个变量推荐大于训练的batch size,并且在内存足够
的情况下越大越好。
PyDataProvider2会尽可能多的使用内存。因此,对于内存较小的机器,推荐使用 ``pool_size`` 变量来设置内存中暂存的数据条。具体请参考 `@provider`_ 中的说明。
......@@ -8,19 +8,16 @@ def on_init(settings, dictionary, **kwargs):
# set input types in runtime. It will do the same thing as
# @provider(input_types) will do, but it is set dynamically during runtime.
settings.input_types = [
settings.input_types = {
# The text is a sequence of integer values, and each value is a word id.
# The whole sequence is the sentences that we want to predict its
# sentimental.
integer_value(
len(dictionary), seq_type=SequenceType), # text input
'data': integer_value_sequence(len(dictionary)), # text input
'label': integer_value(2) # label positive/negative
}
# label positive/negative
integer_value(2)
]
# save dictionary as settings.dictionary. It will be used in process
# method.
# save dictionary as settings.dictionary.
# It will be used in process method.
settings.dictionary = dictionary
......
自定义一个DataProvider
====================
TBD
\ No newline at end of file
......@@ -8,8 +8,8 @@
.. toctree::
:maxdepth: 1
data_provider/index.rst
data_provider/dataprovider.rst
data_provider/pydataprovider2.rst
命令及命令行参数
================
......@@ -23,9 +23,8 @@
* `参数分类 <../../doc/ui/cmd_argument/argument_outline.html>`_
* `参数描述 <../../doc/ui/cmd_argument/detail_introduction.html>`_
预测
====
=======
.. toctree::
:maxdepth: 1
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册