hierarchical rnn document, add new config example (#106)

* hierarchical rnn document, add new config example

* update inputs_type of label

* add check for unsupported config

* refine hierarchical document

* refine doc title

* update docs, fix paddle to PaddlePaddle

* follow comments
avx_docs
luotao1 8 years ago committed by emailweixu
parent c2d418dbfd
commit cebdb66768

@ -130,6 +130,12 @@ gru_step_layer
Recurrent Layer Group
=====================
memory
------
.. automodule:: paddle.trainer_config_helpers.layers
:members: memory
:noindex:
recurrent_group
---------------
.. automodule:: paddle.trainer_config_helpers.layers

@ -0,0 +1,66 @@
# 支持双层序列作为输入的Layer
## 概述
在自然语言处理任务中序列是一种常见的数据类型。一个独立的词语可以看作是一个非序列输入或者我们称之为一个0层的序列由词语构成的句子是一个单层序列若干个句子构成一个段落是一个双层的序列。
双层序列是一个嵌套的序列,它的每一个元素,又是一个单层的序列。这是一种非常灵活的数据组织方式,帮助我们构造一些复杂的输入信息。
我们可以按照如下层次定义非序列,单层序列,以及双层序列。
+ 0层序列一个独立的元素类型可以是PaddlePaddle支持的任意输入数据类型
+ 单层序列排成一列的多个元素每个元素是一个0层序列元素之间的顺序是重要的输入信息
+ 双层序列排成一列的多个元素每个元素是一个单层序列称之为双层序列的一个子序列subseqsubseq的每个元素是一个0层序列
在 PaddlePaddle中下面这些Layer能够接受双层序列作为输入完成相应的计算。
## pooling_layer
pooling_layer的使用示例如下详细见<a href = "../../../doc/ui/api/trainer_config_helpers/layers.html#pooling-layer">配置API</a>
```python
seq_pool = pooling_layer(input=layer,
pooling_type=AvgPooling(),
agg_level=AggregateLevel.EACH_SEQUENCE)
```
- `pooling_type` 目前支持两种分别是MaxPooling()和AvgPooling()。
- `agg_level=AggregateLevel.TIMESTEP`时(默认值):
- 作用双层序列经过运算变成一个0层序列或单层序列经过运算变成一个0层序列
- 输入:一个双层序列,或一个单层序列
- 输出一个0层序列即整个输入序列单层或双层的平均值或最大值
- `agg_level=AggregateLevel.EACH_SEQUENCE`时:
- 作用:一个双层序列经过运算变成一个单层序列
- 输入:必须是一个双层序列
- 输出一个单层序列序列的每个元素是原来双层序列每个subseq元素的平均值或最大值
## last_seq 和 first_seq
last_seq的使用示例如下first_seq类似详细见<a href = "../../../doc/ui/api/trainer_config_helpers/layers.html#last-seq">配置API</a>
```python
last = last_seq(input=layer,
agg_level=AggregateLevel.EACH_SEQUENCE)
```
- `agg_level=AggregateLevel.TIMESTEP`时(默认值):
- 作用一个双层序列经过运算变成一个0层序列或一个单层序列经过运算变成一个0层序列
- 输入:一个双层序列或一个单层序列
- 输出一个0层序列即整个输入序列双层或者单层最后一个或第一个元素。
- `agg_level=AggregateLevel.EACH_SEQUENCE`时:
- 作用:一个双层序列经过运算变成一个单层序列
- 输入:必须是一个双层序列
- 输出一个单层序列其中每个元素是双层序列中每个subseq最后一个或第一个元素。
## expand_layer
expand_layer的使用示例如下详细见<a href = "../../../doc/ui/api/trainer_config_helpers/layers.html#expand-layer">配置API</a>
```python
expand = expand_layer(input=layer1,
expand_as=layer2,
expand_level=ExpandLevel.FROM_TIMESTEP)
```
- `expand_level=ExpandLevel.FROM_TIMESTEP`时(默认值):
- 作用一个0层序列经过运算扩展成一个单层序列或者一个双层序列
- 输入layer1必须是一个0层序列是待扩展的数据layer2可以是一个单层序列或者是一个双层序列提供扩展的长度信息
- 输出:一个单层序列,或一个双层序列,输出序列的类型(双层序列,或单层序列)和序列中含有元素的数目同 layer2一致。若输出是单层序列单层序列的每个元素0层序列都是对layer1元素的拷贝若输出是双层序列双层序列每个subseq中每个元素0层序列都是对layer1元素的拷贝
- `expand_level=ExpandLevel.FROM_SEQUENCE`时:
- 作用:一个单层序列经过运算扩展成一个双层序列
- 输入layer1必须是一个单层序列是待扩展的数据layer2必须是一个双层序列提供扩展的长度信息
- 输出一个双层序列序列中含有元素的数目同layer2一致。要求单层序列含有元素的数目0层序列和双层序列含有subseq 的数目一致。单层序列第i个元素0层序列被扩展为一个单层序列构成了输出双层序列的第i个subseq。

File diff suppressed because it is too large Load Diff

@ -0,0 +1,96 @@
# Recurrent Group教程
## 概述
序列数据是自然语言处理任务面对的一种主要输入数据类型。
一句话是由词语构成的序列,多句话进一步构成了段落。因此,段落可以看作是一个嵌套的双层的序列,这个序列的每个元素又是一个序列。
双层序列是PaddlePaddle支持的一种非常灵活的数据组织方式帮助我们更好地描述段落、多轮对话等更为复杂的语言数据。基于双层序列输入我们可以设计搭建一个灵活的、层次化的RNN分别从词语和句子级别编码输入数据同时也能够引入更加复杂的记忆机制更好地完成一些复杂的语言理解任务。
在PaddlePaddle中`recurrent_group`是一种任意复杂的RNN单元用户只需定义RNN在一个时间步内完成的计算PaddlePaddle负责完成信息和误差在时间序列上的传播。
更进一步,`recurrent_group`同样可以扩展到双层序列的处理上。通过两个嵌套的`recurrent_group`分别定义子句级别和词语级别上需要完成的运算最终实现一个层次化的复杂RNN。
目前在PaddlePaddle中能够对双向序列进行处理的有`recurrent_group`和部分Layer具体可参考文档<a href = "hierarchical-layer.html">支持双层序列作为输入的Layer</a>
## 相关概念
### 基本原理
`recurrent_group` 是PaddlePaddle支持的一种任意复杂的RNN单元。使用者只需要关注于设计RNN在一个时间步之内完成的计算PaddlePaddle负责完成信息和梯度在时间序列上的传播。
PaddlePaddle中`recurrent_group`的一个简单调用如下:
``` python
recurrent_group(step, input, reverse)
```
- step一个可调用的函数定义一个时间步之内RNN单元完成的计算
- input输入必须是一个单层序列或者一个双层序列
- reverse是否以逆序处理输入序列
使用`recurrent_group`的核心是设计step函数的计算逻辑。step函数内部可以自由组合PaddlePaddle支持的各种layer完成任意的运算逻辑。`recurrent_group` 的输入即input会成为step函数的输入由于step 函数只关注于RNN一个时间步之内的计算在这里`recurrent_group`替我们完成了原始输入数据的拆分。
### 输入
`recurrent_group`处理的输入序列主要分为以下三种类型:
- **数据输入**:一个双层序列进入`recurrent_group`会被拆解为一个单层序列,一个单层序列进入`recurrent_group`会被拆解为非序列然后交给step函数这一过程对用户是完全透明的。可以有以下两种1通过data_layer拿到的用户输入2其它layer的输出。
- **只读Memory输入**`StaticInput` 定义了一个只读的Memory由`StaticInput`指定的输入不会被`recurrent_group`拆解,`recurrent_group` 循环展开的每个时间步总是能够引用所有输入,可以是一个非序列,或者一个单层序列。
- **序列生成任务的输入**`GeneratedInput`只用于在序列生成任务中指定输入数据。
### 输入示例
序列生成任务大多遵循encoder-decoer架构encoder和decoder可以是能够处理序列的任意神经网络单元而RNN是最流行的选择。
给定encoder输出和当前词decoder每次预测产生下一个最可能的词语。在这种结构中decoder接受两个输入
- 要生成的目标序列是decoder的数据输入也是decoder循环展开的依据`recurrent_group`会对这类输入进行拆解。
- encoder输出可以是一个非序列或者一个单层序列是一个unbounded memorydecoder循环展开的每一个时间步会引用全部结果不应该被拆解这种类型的输入必须通过`StaticInput`指定。关于Unbounded Memory的更多讨论请参考论文 [Neural Turning Machine](https://arxiv.org/abs/1410.5401)。
在序列生成任务中decoder RNN总是引用上一时刻预测出的词的词向量作为当前时刻输入。`GeneratedInput`自动完成这一过程。
### 输出
`step`函数必须返回一个或多个Layer的输出这个Layer的输出会作为整个`recurrent_group` 最终的输出结果。在输出的过程中,`recurrent_group` 会将每个时间步的输出拼接,这个过程对用户也是透明的。
### memory
memory只能在`recurrent_group`中定义和使用。memory不能独立存在必须指向一个PaddlePaddle定义的Layer。引用memory得到这layer上一时刻输出因此可以将memory理解为一个时延操作。
可以显示地指定一个layer的输出用于初始化memory。不指定时memory默认初始化为0。
## 双层RNN介绍
`recurrent_group`帮助我们完成对输入序列的拆分,对输出的合并,以及计算逻辑在序列上的循环展开。
利用这种特性,两个嵌套的`recurrent_group`能够处理双层序列实现词语和句子两个级别的双层RNN结构。
- 单层word-levelRNN每个状态state对应一个词word
- 双层sequence-levelRNN一个双层RNN由多个单层RNN组成每个单层RNN即双层RNN的每个状态对应一个子句subseq
为了描述方便下文以NLP任务为例将含有子句subseq的段落定义为一个双层序列将含有词语的句子定义为一个单层序列那么0层序列即为一个词语。
## 双层RNN的使用
### 训练流程的使用方法
使用 `recurrent_group`需要遵循以下约定:
- **单进单出**:输入和输出都是单层序列。
- 如果有多个输入,不同输入序列含有的词语数必须严格相等。
- 输出一个单层序列,输出序列的词语数和输入序列一致。
- memory在step函数中定义 memory指向一个layer通过引用memory得到这个layer上一个时刻输出形成recurrent 连接。memory的is_seq参数必须为false。如果没有定义memory每个时间步之内的运算是独立的。
- boot_layermemory的初始状态默认初始状为0memory的is_seq参数必须为false。
- **双进双出**:输入和输出都是双层序列。
- 如果有多个输入序列不同输入含有的子句subseq数必须严格相等但子句含有的词语数可以不相等。
- 输出一个双层序列子句subseq数、子句的单词数和指定的一个输入序列一致默认为第一个输入。
- memory在step函数中定义memory指向一个layer通过引用memory得到这个layer上一个时刻的输出形成recurrent连接。定义在外层`recurrent_group` step函数中的memory能够记录上一个subseq 的状态可以是一个单层序列只作为read-only memory也可以是一个词语。如果没有定义memory那么 subseq 之间的运算是独立的。
- boot_layermemory 初始状态可以是一个单层序列只作为read-only memory或一个向量。默认不设置即初始状态为0。
- **双进单出**:目前还未支持,会报错"In hierachical RNN, all out links should be from sequences now"。
### 生成流程的使用方法
使用`beam_search`需要遵循以下约定:
- 单层RNN从一个word生成下一个word。
- 双层RNN即把单层RNN生成后的subseq给拼接成一个新的双层seq。从语义上看也不存在一个subseq直接生成下一个subseq的情况。

@ -16,4 +16,7 @@ PaddlePaddle文档
算法教程
--------
* `RNN配置 <../doc/algorithm/rnn/rnn.html>`_
* `Recurrent Group教程 <algorithm/rnn/rnn-tutorial.html>`_
* `单层RNN示例 <../doc/algorithm/rnn/rnn.html>`_
* `双层RNN示例 <algorithm/rnn/hierarchical-rnn.html>`_
* `支持双层序列作为输入的Layer <algorithm/rnn/hierarchical-layer.html>`_

@ -544,6 +544,12 @@ void RecurrentGradientMachine::forward(const std::vector<Argument>& inArgs,
const std::vector<Argument> inArgs;
std::vector<Argument> outArgs;
frames_[i]->forward(inArgs, &outArgs, passType);
if (hasSubseq) {
for (auto& outFrameLine : outFrameLines_) {
CHECK(outFrameLine.frames[i]->getOutput().sequenceStartPositions)
<< "In hierachical RNN, all out links should be from sequences.";
}
}
}
if (evaluator_ && passType == PASS_TEST) {
this->eval(evaluator_.get());
@ -635,16 +641,15 @@ void RecurrentGradientMachine::createInFrameInfo(int inlinkId,
std::vector<int> sequenceStartPositions;
const int* subSequenceStartPositions = nullptr;
if (hasSubseq) { // for sequenceScatterAgentLayer
subSequenceStartPositions =
input.subSequenceStartPositions->getData(false);
if (hasSubseq) { // for sequenceScatterAgentLayer
subSequenceStartPositions = input.subSequenceStartPositions->getData(false);
inlinkInfo->seqStartPosIndex.clear();
inlinkInfo->seqStartPosIndex.push_back(0); // first seqStartPosIndex = 0
}
// maxSequenceLength_: max topLevelLength in allsamples
for (int i = 0; i < maxSequenceLength_; ++i) {
if (hasSubseq) {
sequenceStartPositions.push_back(0); // first element = 0
sequenceStartPositions.push_back(0); // first element = 0
}
int numSeqs = 0;
for (size_t j = 0; j < numSequences; ++j) {
@ -676,9 +681,9 @@ void RecurrentGradientMachine::createInFrameInfo(int inlinkId,
}
if (hasSubseq) {
// inFrameLine create sequenceStartPositions one time
CHECK_EQ(sequenceStartPositions.size(),
static_cast<size_t>(maxSequenceLength_ +
input.getNumSubSequences()));
CHECK_EQ(
sequenceStartPositions.size(),
static_cast<size_t>(maxSequenceLength_ + input.getNumSubSequences()));
CHECK_EQ(inlinkInfo->seqStartPosIndex.size(),
static_cast<size_t>(maxSequenceLength_ + 1));
createSeqPos(sequenceStartPositions, &inlinkInfo->sequenceStartPositions);
@ -1102,10 +1107,12 @@ size_t RecurrentGradientMachine::beamShrink(std::vector<Path>& newPaths,
newPaths.end(), Path::greaterPath);
newPaths.resize(totalExpandCount + minNewPathSize);
real minPathLogProb = std::min_element(newPaths.end() - minNewPathSize,
newPaths.end())->logProb;
real maxPathLogProb = std::max_element(newPaths.end() - minNewPathSize,
newPaths.end())->logProb;
real minPathLogProb =
std::min_element(newPaths.end() - minNewPathSize, newPaths.end())
->logProb;
real maxPathLogProb =
std::max_element(newPaths.end() - minNewPathSize, newPaths.end())
->logProb;
// Remove the already formed paths that are relatively short
finalPaths_[seqId].erase(

@ -64,6 +64,11 @@ void AverageLayer::forward(PassType passType) {
size_t dim = getSize();
const Argument& input = getInput(0);
CHECK(input.sequenceStartPositions);
if (type_) {
CHECK(input.subSequenceStartPositions)
<< "when trans_type = seq, input must hasSubseq";
}
int64_t newBatchSize =
type_ ? input.getNumSubSequences() : input.getNumSequences();
ICpuGpuVectorPtr startPositions =
@ -75,11 +80,6 @@ void AverageLayer::forward(PassType passType) {
// check
CHECK_EQ(numSequences, (size_t)newBatchSize);
CHECK_EQ(starts[numSequences], input.getBatchSize());
if (type_) {
// when trans_type = seq, input must hasSubseq
CHECK_EQ(input.hasSubseq(), 1UL);
}
CHECK_EQ(dim, input.value->getWidth());
resetOutput(newBatchSize, dim);

@ -91,6 +91,11 @@ void SequenceLastInstanceLayer::forward(PassType passType) {
const Argument& input = getInput(0);
// check
CHECK(input.sequenceStartPositions);
if (type_) {
CHECK(input.subSequenceStartPositions)
<< "when trans_type = seq, input must hasSubseq";
}
auto startPositions =
type_ ? input.subSequenceStartPositions->getVector(false)
: input.sequenceStartPositions->getVector(false);
@ -98,10 +103,6 @@ void SequenceLastInstanceLayer::forward(PassType passType) {
CHECK_EQ(dim, input.value->getWidth());
CHECK_EQ(startPositions->getData()[height], input.getBatchSize());
CHECK_EQ(height, startPositions->getSize() - 1);
if (type_) {
// when trans_type = seq, input must hasSubseq
CHECK_EQ(input.hasSubseq(), 1UL);
}
reserveOutput(height, dim);
const int* starts = startPositions->getData();

@ -21,7 +21,7 @@ from paddle.trainer.PyDataProvider2 import *
def hook(settings, dict_file, **kwargs):
settings.word_dict = dict_file
settings.input_types = [integer_value_sequence(len(settings.word_dict)),
integer_value_sequence(3)]
integer_value(3)]
settings.logger.info('dict len : %d' % (len(settings.word_dict)))
@ -34,14 +34,14 @@ def process(settings, file_name):
words = comment.split()
word_slot = [settings.word_dict[w] for w in words if
w in settings.word_dict]
yield word_slot, [label]
yield word_slot, label
## for hierarchical sequence network
def hook2(settings, dict_file, **kwargs):
settings.word_dict = dict_file
settings.input_types = [integer_value_sub_sequence(len(settings.word_dict)),
integer_value_sub_sequence(3)]
integer_value_sequence(3)]
settings.logger.info('dict len : %d' % (len(settings.word_dict)))
@ -57,7 +57,7 @@ def process2(settings, file_name):
words = comment.split()
word_slot = [settings.word_dict[w] for w in words if
w in settings.word_dict]
label_list.append([label])
label_list.append(label)
word_slot_list.append(word_slot)
else:
yield word_slot_list, label_list

@ -56,9 +56,8 @@ def outer_step(x):
last = last_seq(input=inner_rnn_output, name="outer_rnn_state")
# "return last" should also work. But currently RecurrentGradientMachine
# does not handle it correctly. Current implementation requires that
# all the out links are from sequences. However, it does not report error
# when the out links are not sequences.
# does not handle it, and will report error: In hierachical RNN, all out
# links should be from sequences now.
return inner_rnn_output
out = recurrent_group(

@ -57,9 +57,8 @@ def outer_step(wid, x):
last = last_seq(input=inner_rnn_output, name="outer_rnn_state")
# "return last" should also work. But currently RecurrentGradientMachine
# does not handle it correctly. Current implementation requires that
# all the out links are from sequences. However, it does not report error
# when the out links are not sequences.
# does not handle it, and will report error: In hierachical RNN, all out
# links should be from sequences now.
return inner_rnn_output
out = recurrent_group(

@ -12,7 +12,6 @@ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License. */
#include <gtest/gtest.h>
#include <paddle/utils/Util.h>
#include <paddle/utils/Version.h>
@ -24,7 +23,7 @@ limitations under the License. */
P_DECLARE_int32(seed);
using namespace paddle; // NOLINT
using namespace std; // NOLINT
using namespace std; // NOLINT
class TrainerForTest : public paddle::Trainer {
public:
void startTrain() {
@ -44,11 +43,10 @@ public:
*/
size_t getTotalParameterSize() const {
auto p = const_cast<TrainerForTest*>(this);
auto & params = p->getGradientMachine()->getParameters();
return std::accumulate(params.begin(), params.end(), 0UL,
[](size_t a, const ParameterPtr& p){
return a+p->getSize();
});
auto& params = p->getGradientMachine()->getParameters();
return std::accumulate(
params.begin(), params.end(), 0UL,
[](size_t a, const ParameterPtr& p) { return a + p->getSize(); });
}
};

Loading…
Cancel
Save