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

add new_layer_cn doc #1029

Merged
merged 5 commits into from
Jan 5, 2017
Merged
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
390 changes: 390 additions & 0 deletions doc/howto/dev/new_layer_cn.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,390 @@
================
实现新的网络层
================

这份教程指导你在PaddlePaddle中实现一个自定义的网络层。在这里我们使用全连接层作为例子来指导你完成实现新网络层需要的几个步骤。
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

来指导你完成实现

==>

来演示实现一个

显得客气一点儿?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

几个步骤 -> 四个步骤


- 推导该层前向和后向传递的方程。
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

既然是几个步骤,可以把 - 改成 1.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

方程合适,还是公式合适呢?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里保持方程吧,方程、等式、表达式概念一致,公式感觉有个“公”,这里的equation不见得general

- 实现该层的C++类。
- 写梯度检测的测试单元,以保证梯度的正确计算。
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

写梯度检测的测试单元,以保证梯度的正确计算。

-> 增加测试单元 -> 单元测试

- 实现该层的python封装。
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

实现该层的python封装。

改成封装python接口


推导方程
================

首先我们需要推导该网络层的*前向传播*和*后向传播*的方程。前向传播给定输入,计算输出。后向传播给定输出的梯度,计算输入和参数的梯度。

下图是一个全链接层的示意图。在全连接层中,每个输出节点都连接到所有的输入节点上。
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

下图是一个全链接层的示意图。

错别字。全链接层 -> 全连接层


.. 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` 。
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

全连接层以一个维度为 :math:D_i 稠密的向量作为输入。其用一个尺度为 :math:D_i \times D_o 的变换矩阵 :math:W 把 :math:x 映射到一个维度为 :math:D_o 的向量,并在其上再加上维度为 :math:D_o 的偏置向量 :math:b

稠密的向量作为输入。其用 -> 的稠密向量作为输入,使用,是同一个主语吧,或者把改成其它代词。
并在其上再 -> 并在乘积结果上

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

是同一主语,不过中间有句号,就又写了一个主语。可以改成这样,用逗号。


.. math::

y = f(W^T x + b)

其中 :math:`f(.)` 是一个非线性的*激活方程*,例如sigmoid, tanh,以及Relu。

变换矩阵 :math:`W` 和偏置向量 :math:`b` 是该网络层的*参数*。一个网络层的参数是在*反向传播*时被训练的。反向传播对所有的参数和输入都计算输出函数的梯度。优化器则用链式法则来对每个参数计算损失函数的梯度。
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The backward pass computes the gradients of the output function with respect to all parameters and inputs.
反向传播对所有的参数和输入都计算输出函数的梯度。

这句话不是很理解。

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这句话是有问题,英文我就不改了,中文应是:反向传播根据output的梯度,分别计算出每个参数的梯度,以及input的梯度。


假设我们的损失函数是 :math:`c(y)` ,那么
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

假设我们的损失函数是

去掉我们的


.. math::

\frac{\partial c(y)}{\partial x} = \frac{\partial c(y)}{\partial y} \frac{\partial y}{\partial x}

假设 :math:`z = f(W^T x + b)` ,那么

.. math::

\frac{\partial y}{\partial z} = \frac{\partial f(z)}{\partial z}

我们的base layer类可以自动计算上面的导数。
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

我们的base layer类可以自动计算上面的导数。

我们的 -> PaddlePaddle的


因而,对全连接层来说,我们需要计算:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

因而 -> 因此


.. math::

\frac{\partial z}{\partial x} = W, \frac{\partial z_j}{\partial W_{ij}} = x_i, \frac{\partial z}{\partial b} = \mathbf 1

其中 :math:`\mathbf 1` 是一个全1的向量, :math:`W_{ij}` 是矩阵 :math:`W` 第i行第j列的数值, :math:`z_j` 是向量 :math:`z` 的第j个值, :math:`x_i` 是向量 :math:`x` 的第i个值。

最后我们使用链式法则计算 :math:`\frac{\partial z}{\partial x}` 以及 :math:`\frac{\partial z}{\partial W}` 。计算的细节将在下面的小节给出。
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

下面的小节 -> 下一个小节

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

本来我写的也是下一小节,但实际上是在下面第三个小节才讲bp的计算细节,就改成了下面的小节。


实现C++类
===================

一个网络层的C++类需要实现初始化,前向和后向。全连接层的实现位于:code:`paddle/gserver/layers/FullyConnectedLayer.h`及:code:`paddle/gserver/layers/FullyConnectedLayer.cpp`。这里我们展示一份简化过的代码。

这个类需要继承 :code:`paddle::Layer` 这个基类,并且需要重写以下基类中的虚函数:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

以下基类中的虚函数 -> 基类中的以下几个虚函数


- 类的构造函数和析构析构函数。
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

重复,析构析构函数 -> 析构函数

- :code:`init` 函数。用于初始化参数和设置。
- :code:`forward` 。实现网络层的前向传播。
- :code:`backward` 。实现网络层的后向传播。
- :code:`prefetch` 。用于确定由参数服务器预取的行相关的参数矩阵。如果该网络层不需要远程稀疏更新的话,你不需要重写该函数。(大多数网络层不需要支持远程稀疏更新)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

用于确定由参数服务器预取的行相关的参数矩阵。

感觉语义不是很清楚。

如果该网络层不需要远程稀疏更新的话,你不需要重写该函数。

改成,如果该网络层不需要远程稀疏更新,则不需要重写该函数。



头文件在下面列出:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

头文件在下面列出 -> 头文件如下


.. code-block:: c++

namespace paddle {
/**
* 全连接层的每个输出都连接到上一层的所有的神经元上。
* 其用一些学习过的参数做内积并加上偏置(可选)。
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

其用一些学习过的参数做内积并加上偏置(可选)

输入和参数的内积吧,这里描述的不清楚,容易误解成参数和参数的内积。另外,使用代词开头。

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

你语法很苛刻啊

*
* 配置文件接口是fc_layer。
*/

class FullyConnectedLayer : public Layer {
protected:
WeightList weights_;
std::unique_ptr<Weight> biases_;

public:
explicit FullyConnectedLayer(const LayerConfig& config)
: Layer(config) {}
~FullyConnectedLayer() {}

bool init(const LayerMap& layerMap, const ParameterMap& parameterMap);

Weight& getWeight(int idx) { return *weights_[idx]; }

void prefetch();
void forward(PassType passType);
void backward(const UpdateCallback& callback = nullptr);
};
} // namespace paddle

头文件中把参数定位为类的成员变量。我们使用 :code:`Weight` 类作为参数的抽象,它支持多线程更新。该类的实现细节在“实现细节”中由详细介绍。
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

定位 -> 定义
中由详细介绍 -> 中详细介绍,去掉


- :code:`weights_` 是存有变换矩阵的一系列权重。在当前的实现方式下,网络层可以有多个输入。因此,它可能有不止一个权重。每个权重对应一个输入。
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

:code:weights_ 是存有变换矩阵的一系列权重。

我理解应该是,一系列变换矩阵的权重?变换矩阵和权重是一一对应的关系吧,这里有点绕。

- :code:`biases_` 是存有偏置向量的权重。

全连接层没有网络层配置的超参数。如果一个网络层需要配置的话,通常的做法是将配置存于 :code:`LayerConfig& config` 中,并在类构建函数中把它放入一个类成员变量里。
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

将配置存于 -> 将配置暂存于
并在类构建函数中把它放入一个类成员变量里 -> 并在类初始化函数中将其保存在一个类成员变量中

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

构建是构建,初始化是初始化吧?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

哦,这里是我理解错了


下面的代码片段实现了 :code:`init` 函数。

- 首先,所有的 :code:`init` 函数必须先调用基类中的函数 :code:`Layer::init(layerMap, parameterMap);` 。该语句会为每个层初始化其所需要的变量和连接。
- 之后初始化所有的权重矩阵 :math:`W` 。当前的实现方式下,网络层可以有多个输入。因此,它可能有不止一个权重。
- 最后,初始化偏置向量。


.. code-block:: c++

bool FullyConnectedLayer::init(const LayerMap& layerMap,
const ParameterMap& parameterMap) {
/* 初始化父类 */
Layer::init(layerMap, parameterMap);

/* 初始化权重表 */
CHECK(inputLayers_.size() == parameters_.size());
for (size_t i = 0; i < inputLayers_.size(); i++) {
// 获得参数尺寸
size_t height = inputLayers_[i]->getSize();
size_t width = getSize();

// 新建一个权重
if (parameters_[i]->isSparse()) {
CHECK_LE(parameters_[i]->getSize(), width * height);
} else {
CHECK_EQ(parameters_[i]->getSize(), width * height);
}
Weight* w = new Weight(height, width, parameters_[i]);

// 将新建的权重加入权重表
weights_.emplace_back(w);
}

/* 初始化biases_ */
if (biasParameter_.get() != NULL) {
biases_ = std::unique_ptr<Weight>(new Weight(1, getSize(), biasParameter_));
}

return true;
}

实现前向传播的部分有下面几个步骤。

- 每个层在其 :code:`forward` 函数的开头必须调用 :code:`Layer::forward(passType);` 。
- 之后使用 :code:`reserveOutput(batchSize, size);` 为输出分配内存。由于我们支持训练数据有不同的批次大小,所以这一步是必要的。 :code:`reserveOutput` 会相应地改变输出的尺寸。为了保证效率,如果需要扩大矩阵,我们会重新分配内存;如果需要缩减矩阵,我们会继续使用现有的内存块。
- 之后使用矩阵运算函数来计算 :math:`\sum_i W_i x + b`。:code:`getInput(i).value` 返回第i个输入矩阵。每个输入都是一个 :math:`batchSize \times dim` 的矩阵,每行表示一个批次中的单个输入。对于我们支持的全部矩阵操作,请参考 :code:`paddle/math/Matrix.h`和:code:`paddle/math/BaseMatrix.h` 。
- 最终,使用 :code:`forwardActivation();` 进行激活操作。这会自动进行网络配置中声明的激活操作。


.. code-block:: c++

void FullyConnectedLayer::forward(PassType passType) {
Layer::forward(passType);

/* 若有必要,为output_申请内存 */
int batchSize = getInput(0).getBatchSize();
int size = getSize();

{
// 设置输出的尺寸
reserveOutput(batchSize, size);
}

MatrixPtr outV = getOutputValue();

// 对每个输入乘上转化矩阵
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

转化矩阵 -> 变换矩阵,和上文保持一致吧

for (size_t i = 0; i != inputLayers_.size(); ++i) {
auto input = getInput(i);
CHECK(input.value) << "The input of 'fc' layer must be matrix";
i == 0 ? outV->mul(input.value, weights_[i]->getW(), 1, 0)
: outV->mul(input.value, weights_[i]->getW(), 1, 1);
}

/* 加上偏置向量 */
if (biases_.get() != NULL) {
outV->addBias(*(biases_->getW()), 1);
}

/* 激活 */ {
forwardActivation();
}
}

实现后向传播的部分有下面几个步骤。

- :code:`backwardActivation()` 计算激活函数的梯度。梯度会就地(不使用额外空间)乘上输出的梯度,并可以通过 :code:`getOutputGrad()` 来获得。
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The gradients will be multiplies in place to the gradients of the output, which can be retrieved using getOutputGrad().

multiplies -> multiplied?

并可以通过 :code:getOutputGrad() 来获得

通过getOutputGrad()获得是输出的梯度,而前半句的主语是激活函数的梯度吧?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这里没错,in place操作的,激活函数的梯度和输出的梯度存在同一个地方。

- 计算偏置的梯度。注意,我们使用 :code:`biases_->getWGrad()` 来得到某个特定参数的梯度矩阵。在一个参数的梯度被更新后,**必须**要调用 :code:`getParameterPtr()->incUpdate(callback);` 。这是用来在多线程和多机上更新参数的。
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

这是用来在多线程和多机上更新参数的。 -> 这将用于多线程和多机环境下的参数更新。

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

可以,“将”字不要了

- 之后,计算转换矩阵和输入的梯度,并对相应的参数调用 :code:`incUpdate` 。这给了框架一个机会去了解自己是否已经把所有的梯度收集到一个参数中,使得框架可以进行有时间重叠的工作。(例如,网络通信)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

之后 -> 最后
转换矩阵 -> 变换矩阵

这给了框架一个机会去了解自己是否已经把所有的梯度收集到一个参数中,使得框架可以进行有时间重叠的工作。(例如,网络通信)

-> PaddlePaddle可以通过该机制判断是否已经收集齐所有的梯度,从而可以做一些与计算重叠的工作(例如,网络通信)。

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

很好



.. code-block:: c++

void FullyConnectedLayer::backward(const UpdateCallback& callback) {
/* 对激活求导 */ {
backwardActivation();
}

if (biases_ && biases_->getWGrad()) {
biases_->getWGrad()->collectBias(*getOutputGrad(), 1);

/* 加上偏置的梯度 */
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Increasing the number of gradient

加上梯度的数量吧?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

此处英文表达有误

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

是加上梯度的数值

biases_->getParameterPtr()->incUpdate(callback);
}

bool syncFlag = hl_get_sync_flag();

for (size_t i = 0; i != inputLayers_.size(); ++i) {
/* 计算当前层权重的梯度 */
if (weights_[i]->getWGrad()) {
MatrixPtr input_T = getInputValue(i)->getTranspose();
MatrixPtr oGrad = getOutputGrad();
{
weights_[i]->getWGrad()->mul(input_T, oGrad, 1, 1);
}
}


/* 计算输入层的偏差 */
MatrixPtr preGrad = getInputGrad(i);
if (NULL != preGrad) {
MatrixPtr weights_T = weights_[i]->getW()->getTranspose();
preGrad->mul(getOutputGrad(), weights_T, 1, 1);
}

{
weights_[i]->getParameterPtr()->incUpdate(callback);
}
}
}

:code:`prefetch` 函数指出了在训练时需要从参数服务器取出的行。仅在远程稀疏训练时有效。在远程稀疏训练时,完整的参数矩阵被分布式的保存在参数服务器上。当网络层用一个批次做训练时,该批次中,输入仅有一个子集是非零的。因此,该层仅需要这些非零样本位置所对应的转换矩阵的那些行。 :code:`prefetch` 表明了这些行的标号。
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

在远程稀疏训练时,完整的参数矩阵被分布式的保存在参数服务器上。

改成 使用远程稀疏方式训练时,完整的参数矩阵被分布在不同的参数服务器中。

该批次中,输入仅有一个子集是非零的 -> 该批次的输入中仅有一个子集是非零的
转换矩阵 -> 变换矩阵


大多数层不需要远程稀疏训练函数。这种情况下不需要重写该函数。

.. code-block:: c++

void FullyConnectedLayer::prefetch() {
for (size_t i = 0; i != inputLayers_.size(); ++i) {
auto* sparseParam =
dynamic_cast<SparsePrefetchRowCpuMatrix*>(weights_[i]->getW().get());
if (sparseParam) {
MatrixPtr input = getInputValue(i);
sparseParam->addRows(input);
}
}
}

最后,使用 :code:`REGISTER_LAYER(fc, FullyConnectedLayer);` 来注册该层。 :code:`fc` 是该层的标识符, :code:`FullyConnectedLayer` 是该层的类名。

.. code-block:: c++

namespace paddle {
REGISTER_LAYER(fc, FullyConnectedLayer);
}

若 :code:`cpp` 被放在 :code:`paddle/gserver/layers` 目录下,其会自动被加入编译列表。


写梯度检查单元测试
===============================

写梯度检查单元测试是一个验证新实现的层是否正确的相对简单的办法。梯度检查单元测试通过有限差分法来验证一个层的梯度。首先对输入做一个小的扰动 :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` 。我们建议你在写新网络层时把测试代码放入新的文件中。下面列出了全连接层的梯度检查单元测试。它包含以下几步:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

所有的梯度检测单侧 -> 所有网络层的梯度检测

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

gradient check unit test梯度检查单测

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

错别字


+ 生成网络层配置。网络层配置包含以下几项:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@luotao1 有的layer可能要添加配置吧,就需要写proto,这个文档里不用说明?

- 偏置参数的大小。(例子中是4096)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

从意义上来说,应该是输出的大小吧,虽然输出和偏置参数的大小是一样的。

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

不是,输出的大小是size of the layer,在下面两行处,这里就是偏置的大小

- 层的类型。(例子中是fc)
- 层的大小。(例子中是4096)
- 激活的类型。(例子中是softmax)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

softmax -> sigmoid,或者312行sigmoid -> softmax

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

我的天,这都被你发现了

- dropout的比例。(例子中是0.1)
+ 配置网络层的输入。在这个例子里,我们仅有一个输入。
- 输入的类型( :code:`INPUT_DATA` ),可以是以下几种:
- :code:`INPUT_DATA` :稠密向量。
- :code:`INPUT_LABEL` :整数。
- :code:`INPUT_DATA_TARGET` :稠密向量,但不用于计算梯度。
- :code:`INPUT_SEQUENCE_DATA` :含有序列信息的稠密向量。
- :code:`INPUT_HASSUB_SEQUENCE_DATA` :含有序列信息和子序列信息的稠密向量。
- :code:`INPUT_SEQUENCE_LABEL` :含有序列信息的整数。
- :code:`INPUT_SPARSE_NON_VALUE_DATA` :0-1稀疏数据。
- :code:`INPUT_SPARSE_FLOAT_VALUE_DATA` :浮点稀疏数据。
- 输入的名字。(例子中是 :code:`layer_0` )
- 输入的大小。(例子中是8192)
- 非零数字的个数,仅对稀疏数据有效。
- 稀疏数据的格式,仅对稀疏数据有效。
+ 对每个输入,都需要调用一次 :code:`config.layerConfig.add_inputs();` 。
+ 调用 :code:`testLayerGrad` 来做梯度检查。它包含下面的参数。
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

下面的参数 -> 以下参数

- 层和输入的配置。(例子中是 :code:`config` )
- 输入的类型。(例子中是 :code:`fc` )
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

输入的类型 -> 网络层的类型

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

原文有误

- 梯度检查的批次大小。(例子中是100)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

梯度检查的批次大小 -> 输入数据的批次大小

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

梯度检查的输入数据的批次大小

- 输入是否是转置的。大多数层需要设置为 :code:`false` 。(例子中是 :code:`false` )
- 是否使用权重。有些层或者激活需要做归一化以保证它们的输出的和是一个常数。例如,softmax激活的输出的和总是1。在这种情况下,我们不能通过常规的梯度检查的方式来计算梯度。因此我们采用输出的加权和(非常数)来计算梯度。(例子中是 :code:`true` ,因为全连接层的激活可以是softmax)

.. code-block:: c++

void testFcLayer(string format, size_t nnz) {
// Create layer configuration.
TestConfig config;
config.biasSize = 4096;
config.layerConfig.set_type("fc");
config.layerConfig.set_size(4096);
config.layerConfig.set_active_type("sigmoid");
config.layerConfig.set_drop_rate(0.1);
// Setup inputs.
config.inputDefs.push_back(
{INPUT_DATA, "layer_0", 8192, nnz, ParaSparse(format)});
config.layerConfig.add_inputs();
LOG(INFO) << config.inputDefs[0].sparse.sparse << " "
<< config.inputDefs[0].sparse.format;
for (auto useGpu : {false, true}) {
testLayerGrad(config, "fc", 100, /* trans */ false, useGpu,
/* weight */ true);
}
}

如果你要为了测试而增加新的文件,例如 :code:`paddle/gserver/tests/testFCGrad.cpp` ,你需要把该文件加入 :code:`paddle/gserver/tests/CMakeLists.txt` 中。下面给出了一个例子。当你执行命令 :code:`make tests` 时,所有的单侧都会被执行一次。注意,有些层可能需要高精度来保证梯度检查单侧正确执行。你需要在配置cmake时将 :code:`WITH_DOUBLE` 设置为 `ON` 。
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

保证梯度检查单侧正确执行 -> 保证梯度检查单测正确执行


.. code-block:: bash

add_unittest_without_exec(test_FCGrad
test_FCGrad.cpp
LayerGradUtil.cpp
TestUtil.cpp)

add_test(NAME test_FCGrad
COMMAND test_FCGrad)


实现python封装
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

python ==> Python,专有名词首字母大写

========================

python封装的实现使得我们可以在配置文件中使用新实现的网络层。所有的python封装都在 :code:`python/paddle/trainer/config_parser.py` 中。全连接层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` 是网络层的标识符。为了封装能够正确工作,这些名字必须要写对。
- 之后,计算转换矩阵的大小和格式(是否稀疏)。
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

转换矩阵 -> 变换矩阵


.. code-block:: python

@config_layer('fc')
class FCLayer(LayerBase):
def __init__(
self,
name,
size,
inputs,
bias=True,
**xargs):
super(FCLayer, self).__init__(name, 'fc', size, inputs=inputs, **xargs)
for input_index in xrange(len(self.inputs)):
input_layer = self.get_input_layer(input_index)
psize = self.config.size * input_layer.size
dims = [input_layer.size, self.config.size]
format = self.inputs[input_index].format
sparse = format == "csr" or format == "csc"
if sparse:
psize = self.inputs[input_index].nnz
self.create_input_parameter(input_index, psize, dims, sparse, format)
self.create_bias_parameter(bias, self.config.size)

在网络配置中,网络层的细节可以通过下面这些代码片段来指定。这个类的参数包括:

- :code:`name` 是网络层实例的名字标识符。
- :code:`type` 是网络层的类型,通过网络层的标识符来指定。
- :code:`size` 是网络层输出的大小。
- :code:`bias` 表明这个层的一个实例是否需要偏置。
- :code:`inputs` 说明这个层的输入,输入是由一个list中的网络层实例的名字组成的。

.. code-block:: python

Layer(
name = "fc1",
type = "fc",
size = 64,
bias = True,
inputs = [Input("pool3")]
)

我们建议你为你的Python封装实现一个“助手”,使得搭模型时更方便。具体可以参考 :code:`python/paddle/trainer_config_helpers/layers.py` 。