-
Notifications
You must be signed in to change notification settings - Fork 5.6k
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
add new_layer_cn doc #1029
add new_layer_cn doc #1029
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,36 +2,36 @@ | |
实现新的网络层 | ||
================ | ||
|
||
这份教程指导你在PaddlePaddle中实现一个自定义的网络层。在这里我们使用全连接层作为例子来指导你完成实现新网络层需要的几个步骤。 | ||
这份教程展示了如何在PaddlePaddle中实现一个自定义的网络层。在这里我们使用全连接层作为例子来展示实现新网络层所需要的四个步骤。 | ||
|
||
- 推导该层前向和后向传递的方程。 | ||
- 实现该层的C++类。 | ||
- 写梯度检测的测试单元,以保证梯度的正确计算。 | ||
- 实现该层的python封装。 | ||
1. 推导该层前向和后向传递的方程。 | ||
2. 实现该层的C++类。 | ||
3. 增加梯度检测的单元测试,以保证梯度的正确计算。 | ||
4. 封装该层的Python接口。 | ||
|
||
推导方程 | ||
================ | ||
|
||
首先我们需要推导该网络层的*前向传播*和*后向传播*的方程。前向传播给定输入,计算输出。后向传播给定输出的梯度,计算输入和参数的梯度。 | ||
|
||
下图是一个全链接层的示意图。在全连接层中,每个输出节点都连接到所有的输入节点上。 | ||
下图是一个全连接层的示意图。在全连接层中,每个输出节点都连接到所有的输入节点上。 | ||
|
||
.. image:: FullyConnected.jpg | ||
:align: center | ||
:scale: 60 % | ||
|
||
一个网络层的前向传播部分把输入转化为相应的输出。 | ||
全连接层以一个维度为 :math:`D_i` 稠密的向量作为输入。其用一个尺度为 :math:`D_i \times D_o` 的变换矩阵 :math:`W` 把 :math:`x` 映射到一个维度为 :math:`D_o` 的向量,并在其上再加上维度为 :math:`D_o` 的偏置向量 :math:`b` 。 | ||
全连接层以一个维度为 :math:`D_i` 的稠密向量作为输入,使用一个尺度为 :math:`D_i \times D_o` 的变换矩阵 :math:`W` 把 :math:`x` 映射到一个维度为 :math:`D_o` 的向量,并在乘积结果上再加上维度为 :math:`D_o` 的偏置向量 :math:`b` 。 | ||
|
||
.. math:: | ||
|
||
y = f(W^T x + b) | ||
|
||
其中 :math:`f(.)` 是一个非线性的*激活方程*,例如sigmoid, tanh,以及Relu。 | ||
|
||
变换矩阵 :math:`W` 和偏置向量 :math:`b` 是该网络层的*参数*。一个网络层的参数是在*反向传播*时被训练的。反向传播对所有的参数和输入都计算输出函数的梯度。优化器则用链式法则来对每个参数计算损失函数的梯度。 | ||
变换矩阵 :math:`W` 和偏置向量 :math:`b` 是该网络层的*参数*。一个网络层的参数是在*反向传播*时被训练的。反向传根据输出的梯度,分别计算每个参数的梯度,以及输入的梯度。优化器则用链式法则来对每个参数计算损失函数的梯度。 | ||
|
||
假设我们的损失函数是 :math:`c(y)` ,那么 | ||
假设损失函数是 :math:`c(y)` ,那么 | ||
|
||
.. math:: | ||
|
||
|
@@ -43,9 +43,9 @@ | |
|
||
\frac{\partial y}{\partial z} = \frac{\partial f(z)}{\partial z} | ||
|
||
我们的base layer类可以自动计算上面的导数。 | ||
PaddlePaddle的base layer类可以自动计算上面的导数。 | ||
|
||
因而,对全连接层来说,我们需要计算: | ||
因此,对全连接层来说,我们需要计算: | ||
|
||
.. math:: | ||
|
||
|
@@ -60,23 +60,23 @@ | |
|
||
一个网络层的C++类需要实现初始化,前向和后向。全连接层的实现位于:code:`paddle/gserver/layers/FullyConnectedLayer.h`及:code:`paddle/gserver/layers/FullyConnectedLayer.cpp`。这里我们展示一份简化过的代码。 | ||
|
||
这个类需要继承 :code:`paddle::Layer` 这个基类,并且需要重写以下基类中的虚函数: | ||
这个类需要继承 :code:`paddle::Layer` 这个基类,并且需要重写基类中的以下几个虚函数: | ||
|
||
- 类的构造函数和析构析构函数。 | ||
- 类的构造函数和析构函数。 | ||
- :code:`init` 函数。用于初始化参数和设置。 | ||
- :code:`forward` 。实现网络层的前向传播。 | ||
- :code:`backward` 。实现网络层的后向传播。 | ||
- :code:`prefetch` 。用于确定由参数服务器预取的行相关的参数矩阵。如果该网络层不需要远程稀疏更新的话,你不需要重写该函数。(大多数网络层不需要支持远程稀疏更新) | ||
- :code:`prefetch` 。用来从参数服务器预取参数矩阵相应的行。如果网络层不需要远程稀疏更新,则不需要重写该函数。(大多数网络层不需要支持远程稀疏更新) | ||
|
||
|
||
头文件在下面列出: | ||
头文件如下: | ||
|
||
.. code-block:: c++ | ||
|
||
namespace paddle { | ||
/** | ||
* 全连接层的每个输出都连接到上一层的所有的神经元上。 | ||
* 其用一些学习过的参数做内积并加上偏置(可选)。 | ||
* 它的输入与经过学习的参数做内积并加上偏置(可选)。 | ||
* | ||
* 配置文件接口是fc_layer。 | ||
*/ | ||
|
@@ -101,9 +101,9 @@ | |
}; | ||
} // namespace paddle | ||
|
||
头文件中把参数定位为类的成员变量。我们使用 :code:`Weight` 类作为参数的抽象,它支持多线程更新。该类的实现细节在“实现细节”中由详细介绍。 | ||
头文件中把参数定义为类的成员变量。我们使用 :code:`Weight` 类作为参数的抽象,它支持多线程更新。该类的实现细节在“实现细节”中详细介绍。 | ||
|
||
- :code:`weights_` 是存有变换矩阵的一系列权重。在当前的实现方式下,网络层可以有多个输入。因此,它可能有不止一个权重。每个权重对应一个输入。 | ||
- :code:`weights_` 是存有一系列变换矩阵的权重。在当前的实现方式下,网络层可以有多个输入。因此,它可能有不止一个权重。每个权重对应一个输入。 | ||
- :code:`biases_` 是存有偏置向量的权重。 | ||
|
||
全连接层没有网络层配置的超参数。如果一个网络层需要配置的话,通常的做法是将配置存于 :code:`LayerConfig& config` 中,并在类构建函数中把它放入一个类成员变量里。 | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 构建是构建,初始化是初始化吧? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 哦,这里是我理解错了 |
||
|
@@ -173,7 +173,7 @@ | |
|
||
MatrixPtr outV = getOutputValue(); | ||
|
||
// 对每个输入乘上转化矩阵 | ||
// 对每个输入乘上变换矩阵 | ||
for (size_t i = 0; i != inputLayers_.size(); ++i) { | ||
auto input = getInput(i); | ||
CHECK(input.value) << "The input of 'fc' layer must be matrix"; | ||
|
@@ -193,9 +193,9 @@ | |
|
||
实现后向传播的部分有下面几个步骤。 | ||
|
||
- :code:`backwardActivation()` 计算激活函数的梯度。梯度会就地(不使用额外空间)乘上输出的梯度,并可以通过 :code:`getOutputGrad()` 来获得。 | ||
- 计算偏置的梯度。注意,我们使用 :code:`biases_->getWGrad()` 来得到某个特定参数的梯度矩阵。在一个参数的梯度被更新后,**必须**要调用 :code:`getParameterPtr()->incUpdate(callback);` 。这是用来在多线程和多机上更新参数的。 | ||
- 之后,计算转换矩阵和输入的梯度,并对相应的参数调用 :code:`incUpdate` 。这给了框架一个机会去了解自己是否已经把所有的梯度收集到一个参数中,使得框架可以进行有时间重叠的工作。(例如,网络通信) | ||
- :code:`backwardActivation()` 计算激活函数的梯度。通过 :code:`getOutputGrad()` 来获得输出的梯度,调用该函数后,梯度会就地(不使用额外空间)乘上输出的梯度。 | ||
- 计算偏置的梯度。注意,我们使用 :code:`biases_->getWGrad()` 来得到某个特定参数的梯度矩阵。在一个参数的梯度被更新后,**必须**要调用 :code:`getParameterPtr()->incUpdate(callback);` 。这用于在多线程和多机上更新参数。 | ||
- 最后,计算转换矩阵和输入的梯度,并对相应的参数调用 :code:`incUpdate` 。PaddlePaddle可以通过该机制判断是否已经收集齐所有的梯度,从而可以做一些与计算重叠的工作(例如,网络通信)。 | ||
|
||
|
||
.. code-block:: c++ | ||
|
@@ -208,7 +208,6 @@ | |
if (biases_ && biases_->getWGrad()) { | ||
biases_->getWGrad()->collectBias(*getOutputGrad(), 1); | ||
|
||
/* 加上偏置的梯度 */ | ||
biases_->getParameterPtr()->incUpdate(callback); | ||
} | ||
|
||
|
@@ -238,7 +237,7 @@ | |
} | ||
} | ||
|
||
:code:`prefetch` 函数指出了在训练时需要从参数服务器取出的行。仅在远程稀疏训练时有效。在远程稀疏训练时,完整的参数矩阵被分布式的保存在参数服务器上。当网络层用一个批次做训练时,该批次中,输入仅有一个子集是非零的。因此,该层仅需要这些非零样本位置所对应的转换矩阵的那些行。 :code:`prefetch` 表明了这些行的标号。 | ||
:code:`prefetch` 函数指出了在训练时需要从参数服务器取出的行。仅在远程稀疏训练时有效。使用远程稀疏方式训练时,完整的参数矩阵被分布在不同的参数服务器上。当网络层用一个批次做训练时,该批次的输入中仅有一个子集是非零的。因此,该层仅需要这些非零样本位置所对应的变换矩阵的那些行。 :code:`prefetch` 表明了这些行的标号。 | ||
|
||
大多数层不需要远程稀疏训练函数。这种情况下不需要重写该函数。 | ||
|
||
|
@@ -271,7 +270,7 @@ | |
|
||
写梯度检查单元测试是一个验证新实现的层是否正确的相对简单的办法。梯度检查单元测试通过有限差分法来验证一个层的梯度。首先对输入做一个小的扰动 :math:`\Delta x` ,然后观察到输出的变化为 :math:`\Delta y` ,那么,梯度就可以通过这个方程计算得到 :math:`\frac{\Delta y}{\Delta x }` 。之后,再用这个梯度去和 :code:`backward` 函数得到的梯度去对比,以保证梯度计算的正确性。需要注意的是梯度检查仅仅验证了梯度的计算,并不保证 :code:`forward` 和 :code:`backward` 函数的实现是正确的。你需要一些更复杂的单元测试来保证你实现的网络层是正确的。 | ||
|
||
所有的梯度检测单侧都位于 :code:`paddle/gserver/tests/test_LayerGrad.cpp` 。我们建议你在写新网络层时把测试代码放入新的文件中。下面列出了全连接层的梯度检查单元测试。它包含以下几步: | ||
所有网络层的梯度检查单测都位于 :code:`paddle/gserver/tests/test_LayerGrad.cpp` 。我们建议你在写新网络层时把测试代码放入新的文件中。下面列出了全连接层的梯度检查单元测试。它包含以下几步: | ||
|
||
+ 生成网络层配置。网络层配置包含以下几项: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @luotao1 有的layer可能要添加配置吧,就需要写proto,这个文档里不用说明? |
||
- 偏置参数的大小。(例子中是4096) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 从意义上来说,应该是 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 不是,输出的大小是size of the layer,在下面两行处,这里就是偏置的大小 |
||
|
@@ -294,10 +293,10 @@ | |
- 非零数字的个数,仅对稀疏数据有效。 | ||
- 稀疏数据的格式,仅对稀疏数据有效。 | ||
+ 对每个输入,都需要调用一次 :code:`config.layerConfig.add_inputs();` 。 | ||
+ 调用 :code:`testLayerGrad` 来做梯度检查。它包含下面的参数。 | ||
+ 调用 :code:`testLayerGrad` 来做梯度检查。它包含以下参数。 | ||
- 层和输入的配置。(例子中是 :code:`config` ) | ||
- 输入的类型。(例子中是 :code:`fc` ) | ||
- 梯度检查的批次大小。(例子中是100) | ||
- 网络层的类型。(例子中是 :code:`fc` ) | ||
- 梯度检查的输入数据的批次大小。(例子中是100) | ||
- 输入是否是转置的。大多数层需要设置为 :code:`false` 。(例子中是 :code:`false` ) | ||
- 是否使用权重。有些层或者激活需要做归一化以保证它们的输出的和是一个常数。例如,softmax激活的输出的和总是1。在这种情况下,我们不能通过常规的梯度检查的方式来计算梯度。因此我们采用输出的加权和(非常数)来计算梯度。(例子中是 :code:`true` ,因为全连接层的激活可以是softmax) | ||
|
||
|
@@ -309,7 +308,7 @@ | |
config.biasSize = 4096; | ||
config.layerConfig.set_type("fc"); | ||
config.layerConfig.set_size(4096); | ||
config.layerConfig.set_active_type("sigmoid"); | ||
config.layerConfig.set_active_type("softmax"); | ||
config.layerConfig.set_drop_rate(0.1); | ||
// Setup inputs. | ||
config.inputDefs.push_back( | ||
|
@@ -323,7 +322,7 @@ | |
} | ||
} | ||
|
||
如果你要为了测试而增加新的文件,例如 :code:`paddle/gserver/tests/testFCGrad.cpp` ,你需要把该文件加入 :code:`paddle/gserver/tests/CMakeLists.txt` 中。下面给出了一个例子。当你执行命令 :code:`make tests` 时,所有的单侧都会被执行一次。注意,有些层可能需要高精度来保证梯度检查单侧正确执行。你需要在配置cmake时将 :code:`WITH_DOUBLE` 设置为 `ON` 。 | ||
如果你要为了测试而增加新的文件,例如 :code:`paddle/gserver/tests/testFCGrad.cpp` ,你需要把该文件加入 :code:`paddle/gserver/tests/CMakeLists.txt` 中。下面给出了一个例子。当你执行命令 :code:`make tests` 时,所有的单测都会被执行一次。注意,有些层可能需要高精度来保证梯度检查单测正确执行。你需要在配置cmake时将 :code:`WITH_DOUBLE` 设置为 `ON` 。 | ||
|
||
.. code-block:: bash | ||
|
||
|
@@ -344,7 +343,7 @@ python封装的实现使得我们可以在配置文件中使用新实现的网 | |
- 所有的Python封装都使用 :code:`@config_layer('fc')` 这样的装饰器。网络层的标识符为 :code:`fc` 。 | ||
- 实现构造函数 :code:`__init__` 。 | ||
- 它首先调用基构造函数 :code:`super(FCLayer, self).__init__(name, 'fc', size, inputs=inputs, **xargs)` 。 :code:`FCLayer` 是Python封装的类名。 :code:`fc` 是网络层的标识符。为了封装能够正确工作,这些名字必须要写对。 | ||
- 之后,计算转换矩阵的大小和格式(是否稀疏)。 | ||
- 之后,计算变换矩阵的大小和格式(是否稀疏)。 | ||
|
||
.. code-block:: python | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
反向传根据
->反向传播根据
,掉了一个播
字