on
+Ground Truth Output: the
+Predict Output: the
+
+```
+
+每一个短线表示一次的预测,第二行显示第几条测试样例,并给出输入的4个单词,第三行为真实的标签,第四行为预测的标签。
+
+## 参考文献
+1. Mnih A, Kavukcuoglu K. [Learning word embeddings efficiently with noise-contrastive estimation](https://papers.nips.cc/paper/5165-learning-word-embeddings-efficiently-with-noise-contrastive-estimation.pdf)[C]//Advances in neural information processing systems. 2013: 2265-2273.
+
+2. Morin, F., & Bengio, Y. (2005, January). [Hierarchical Probabilistic Neural Network Language Model](http://www.iro.umontreal.ca/~lisa/pointeurs/hierarchical-nnlm-aistats05.pdf). In Aistats (Vol. 5, pp. 246-252).
+
+3. Mnih A, Teh Y W. [A Fast and Simple Algorithm for Training Neural Probabilistic Language Models](http://xueshu.baidu.com/s?wd=paperuri%3A%280735b97df93976efb333ac8c266a1eb2%29&filter=sc_long_sign&tn=SE_xueshusource_2kduw22v&sc_vurl=http%3A%2F%2Farxiv.org%2Fabs%2F1206.6426&ie=utf-8&sc_us=5770715420073315630)[J]. Computer Science, 2012:1751-1758.
diff --git a/nce_cost/images/network_conf.png b/nce_cost/images/network_conf.png
new file mode 100644
index 0000000000000000000000000000000000000000..749f8a365db1e1c18d829a460de7c45b27892d19
Binary files /dev/null and b/nce_cost/images/network_conf.png differ
diff --git a/nce_cost/infer.py b/nce_cost/infer.py
new file mode 100644
index 0000000000000000000000000000000000000000..53e3aef45fc02ac008caa7102836ac47915be1fc
--- /dev/null
+++ b/nce_cost/infer.py
@@ -0,0 +1,70 @@
+# -*- encoding:utf-8 -*-
+import numpy as np
+import glob
+import gzip
+import paddle.v2 as paddle
+from nce_conf import network_conf
+
+
+def main():
+ paddle.init(use_gpu=False, trainer_count=1)
+ word_dict = paddle.dataset.imikolov.build_dict()
+ dict_size = len(word_dict)
+
+ prediction_layer = network_conf(
+ is_train=False,
+ hidden_size=128,
+ embedding_size=512,
+ dict_size=dict_size)
+
+ models_list = glob.glob('./models/*')
+ models_list = sorted(models_list)
+
+ with gzip.open(models_list[-1], 'r') as f:
+ parameters = paddle.parameters.Parameters.from_tar(f)
+
+ idx_word_dict = dict((v, k) for k, v in word_dict.items())
+ batch_size = 64
+ batch_ins = []
+ ins_iter = paddle.dataset.imikolov.test(word_dict, 5)
+
+ infer_data = []
+ infer_data_label = []
+ for item in paddle.dataset.imikolov.test(word_dict, 5)():
+ infer_data.append((item[:4]))
+ infer_data_label.append(item[4])
+ # Choose 100 samples from the test set to show how to infer.
+ if len(infer_data_label) == 100:
+ break
+
+ feeding = {
+ 'firstw': 0,
+ 'secondw': 1,
+ 'thirdw': 2,
+ 'fourthw': 3,
+ 'fifthw': 4
+ }
+
+ predictions = paddle.infer(
+ output_layer=prediction_layer,
+ parameters=parameters,
+ input=infer_data,
+ feeding=feeding,
+ field=['value'])
+
+ for i, (prob, data,
+ label) in enumerate(zip(predictions, infer_data, infer_data_label)):
+ print '--------------------------'
+ print "No.%d Input: " % (i+1) + \
+ idx_word_dict[data[0]] + ' ' + \
+ idx_word_dict[data[1]] + ' ' + \
+ idx_word_dict[data[2]] + ' ' + \
+ idx_word_dict[data[3]]
+ print 'Ground Truth Output: ' + idx_word_dict[label]
+ print 'Predict Output: ' + idx_word_dict[prob.argsort(
+ kind='heapsort', axis=0)[-1]]
+ print
+
+
+if __name__ == '__main__':
+ main()
diff --git a/nce_cost/nce_conf.py b/nce_cost/nce_conf.py
new file mode 100644
index 0000000000000000000000000000000000000000..962a9ccc80906bc2272245d0e297142397ffb024
--- /dev/null
+++ b/nce_cost/nce_conf.py
@@ -0,0 +1,61 @@
+# -*- encoding:utf-8 -*-
+import math
+import paddle.v2 as paddle
+
+
+def network_conf(hidden_size, embedding_size, dict_size, is_train):
+
+ first_word = paddle.layer.data(
+ name="firstw", type=paddle.data_type.integer_value(dict_size))
+ second_word = paddle.layer.data(
+ name="secondw", type=paddle.data_type.integer_value(dict_size))
+ third_word = paddle.layer.data(
+ name="thirdw", type=paddle.data_type.integer_value(dict_size))
+ fourth_word = paddle.layer.data(
+ name="fourthw", type=paddle.data_type.integer_value(dict_size))
+ next_word = paddle.layer.data(
+ name="fifthw", type=paddle.data_type.integer_value(dict_size))
+
+ embed_param_attr = paddle.attr.Param(
+ name="_proj", initial_std=0.001, learning_rate=1, l2_rate=0)
+ first_embedding = paddle.layer.embedding(
+ input=first_word, size=embedding_size, param_attr=embed_param_attr)
+ second_embedding = paddle.layer.embedding(
+ input=second_word, size=embedding_size, param_attr=embed_param_attr)
+ third_embedding = paddle.layer.embedding(
+ input=third_word, size=embedding_size, param_attr=embed_param_attr)
+ fourth_embedding = paddle.layer.embedding(
+ input=fourth_word, size=embedding_size, param_attr=embed_param_attr)
+
+ context_embedding = paddle.layer.concat(input=[
+ first_embedding, second_embedding, third_embedding, fourth_embedding
+ ])
+
+ hidden_layer = paddle.layer.fc(
+ input=context_embedding,
+ size=hidden_size,
+ act=paddle.activation.Tanh(),
+ bias_attr=paddle.attr.Param(learning_rate=1),
+ param_attr=paddle.attr.Param(
+ initial_std=1. / math.sqrt(embedding_size * 8), learning_rate=1))
+
+ if is_train == True:
+ cost = paddle.layer.nce(
+ input=hidden_layer,
+ label=next_word,
+ num_classes=dict_size,
+ param_attr=paddle.attr.Param(name='nce_w'),
+ bias_attr=paddle.attr.Param(name='nce_b'),
+ act=paddle.activation.Sigmoid(),
+ num_neg_samples=25,
+ neg_distribution=None)
+ return cost
+ else:
+ with paddle.layer.mixed(
+ size=dict_size,
+ act=paddle.activation.Softmax(),
+ bias_attr=paddle.attr.Param(name='nce_b')) as prediction:
+ prediction += paddle.layer.trans_full_matrix_projection(
+ input=hidden_layer, param_attr=paddle.attr.Param(name='nce_w'))
+
+ return prediction
diff --git a/nce_cost/train.py b/nce_cost/train.py
new file mode 100644
index 0000000000000000000000000000000000000000..a8b437c1dd9bfc89fd03598b9a4201693c3074d7
--- /dev/null
+++ b/nce_cost/train.py
@@ -0,0 +1,52 @@
+# -*- encoding:utf-8 -*-
+import paddle.v2 as paddle
+import gzip
+
+from nce_conf import network_conf
+
+
+def main():
+ paddle.init(use_gpu=False, trainer_count=1)
+ word_dict = paddle.dataset.imikolov.build_dict()
+ dict_size = len(word_dict)
+
+ cost = network_conf(
+ is_train=True, hidden_size=128, embedding_size=512, dict_size=dict_size)
+
+ parameters = paddle.parameters.create(cost)
+ adagrad = paddle.optimizer.Adam(learning_rate=1e-4)
+ trainer = paddle.trainer.SGD(cost, parameters, adagrad)
+
+ def event_handler(event):
+ if isinstance(event, paddle.event.EndIteration):
+ if event.batch_id % 1000 == 0:
+ print "Pass %d, Batch %d, Cost %f" % (
+ event.pass_id, event.batch_id, event.cost)
+
+ if isinstance(event, paddle.event.EndPass):
+ result = trainer.test(
+ paddle.batch(paddle.dataset.imikolov.test(word_dict, 5), 64))
+ print "Test here.. Pass %d, Cost %f" % (event.pass_id, result.cost)
+
+ model_name = "./models/model_pass_%05d.tar.gz" % event.pass_id
+ print "Save model into %s ..." % model_name
+ with gzip.open(model_name, 'w') as f:
+ parameters.to_tar(f)
+
+ feeding = {
+ 'firstw': 0,
+ 'secondw': 1,
+ 'thirdw': 2,
+ 'fourthw': 3,
+ 'fifthw': 4
+ }
+
+ trainer.train(
+ paddle.batch(paddle.dataset.imikolov.train(word_dict, 5), 64),
+ num_passes=1000,
+ event_handler=event_handler,
+ feeding=feeding)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/nmt_without_attention/README.md b/nmt_without_attention/README.md
index 38361bbfbc3e029de872eba967a17453c5e7dac1..a54b715102574dae1b619997a1ed7a2bfc14131c 100644
--- a/nmt_without_attention/README.md
+++ b/nmt_without_attention/README.md
@@ -91,11 +91,11 @@ PaddleBook中[机器翻译](https://github.com/PaddlePaddle/book/blob/develop/08
```python
#### Decoder
encoder_last = paddle.layer.last_seq(input=encoded_vector)
-with paddle.layer.mixed(
+encoder_last_projected = paddle.layer.mixed(
size=decoder_size,
- act=paddle.activation.Tanh()) as encoder_last_projected:
- encoder_last_projected += paddle.layer.full_matrix_projection(
- input=encoder_last)
+ act=paddle.activation.Tanh(),
+ input=paddle.layer.full_matrix_projection(input=encoder_last))
+
# gru step
def gru_decoder_without_attention(enc_vec, current_word):
'''
@@ -112,10 +112,12 @@ def gru_decoder_without_attention(enc_vec, current_word):
context = paddle.layer.last_seq(input=enc_vec)
- with paddle.layer.mixed(size=decoder_size * 3) as decoder_inputs:
- decoder_inputs +=paddle.layer.full_matrix_projection(input=context)
- decoder_inputs += paddle.layer.full_matrix_projection(
- input=current_word)
+ decoder_inputs = paddle.layer.mixed(
+ size=decoder_size * 3,
+ input=[
+ paddle.layer.full_matrix_projection(input=context),
+ paddle.layer.full_matrix_projection(input=current_word)
+ ])
gru_step = paddle.layer.gru_step(
name='gru_decoder',
@@ -125,24 +127,24 @@ def gru_decoder_without_attention(enc_vec, current_word):
output_mem=decoder_mem,
size=decoder_size)
- with paddle.layer.mixed(
- size=target_dict_dim,
- bias_attr=True,
- act=paddle.activation.Softmax()) as out:
- out += paddle.layer.full_matrix_projection(input=gru_step)
+ out = paddle.layer.mixed(
+ size=target_dict_dim,
+ bias_attr=True,
+ act=paddle.activation.Softmax(),
+ input=paddle.layer.full_matrix_projection(input=gru_step))
return out
```
在模型训练和测试阶段,解码器的行为有很大的不同:
- **训练阶段**:目标翻译结果的词向量`trg_embedding`作为参数传递给单步逻辑`gru_decoder_without_attention()`,函数`recurrent_group()`循环调用单步逻辑执行,最后计算目标翻译与实际解码的差异cost并返回;
-- **测试阶段**:解码器根据最后一个生成的词预测下一个词,`GeneratedInputV2()`自动取出模型预测出的概率最高的$k$个词的词向量传递给单步逻辑,`beam_search()`函数调用单步逻辑函数`gru_decoder_without_attention()`完成柱搜索并作为结果返回。
+- **测试阶段**:解码器根据最后一个生成的词预测下一个词,`GeneratedInput()`自动取出模型预测出的概率最高的$k$个词的词向量传递给单步逻辑,`beam_search()`函数调用单步逻辑函数`gru_decoder_without_attention()`完成柱搜索并作为结果返回。
训练和生成的逻辑分别实现在如下的`if-else`条件分支中:
```python
decoder_group_name = "decoder_group"
-group_input1 = paddle.layer.StaticInputV2(input=encoded_vector, is_seq=True)
+group_input1 = paddle.layer.StaticInput(input=encoded_vector, is_seq=True)
group_inputs = [group_input1]
if not generating:
trg_embedding = paddle.layer.embedding(
@@ -166,7 +168,7 @@ if not generating:
return cost
else:
- trg_embedding = paddle.layer.GeneratedInputV2(
+ trg_embedding = paddle.layer.GeneratedInput(
size=target_dict_dim,
embedding_name='_target_language_embedding',
embedding_size=word_vector_dim)
diff --git a/nmt_without_attention/index.html b/nmt_without_attention/index.html
new file mode 100644
index 0000000000000000000000000000000000000000..35177ee5a679fe4f826dfd219721ef2e36b7df83
--- /dev/null
+++ b/nmt_without_attention/index.html
@@ -0,0 +1,419 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+# 神经网络机器翻译模型
+
+## 背景介绍
+机器翻译利用计算机将源语言转换成目标语言的同义表达,是自然语言处理中重要的研究方向,有着广泛的应用需求,其实现方式也经历了不断地演化。传统机器翻译方法主要基于规则或统计模型,需要人为地指定翻译规则或设计语言特征,效果依赖于人对源语言与目标语言的理解程度。近些年来,深度学习的提出与迅速发展使得特征的自动学习成为可能。深度学习首先在图像识别和语音识别中取得成功,进而在机器翻译等自然语言处理领域中掀起了研究热潮。机器翻译中的深度学习模型直接学习源语言到目标语言的映射,大为减少了学习过程中人的介入,同时显著地提高了翻译质量。本例介绍在PaddlePaddle中如何利用循环神经网络(Recurrent Neural Network, RNN)构建一个端到端(End-to-End)的神经网络机器翻译(Neural Machine Translation, NMT)模型。
+
+## 模型概览
+基于 RNN 的神经网络机器翻译模型遵循编码器-解码器结构,其中的编码器和解码器均是一个循环神经网络。将构成编码器和解码器的两个 RNN 沿时间步展开,得到如下的模型结构图:
+
+
图 1. 编码器-解码器框架
+
+神经机器翻译模型的输入输出可以是字符,也可以是词或者短语。不失一般性,本例以基于词的模型为例说明编码器/解码器的工作机制:
+
+- **编码器**:将源语言句子编码成一个向量,作为解码器的输入。解码器的原始输入是表示词的 `id` 序列 $w = {w_1, w_2, ..., w_T}$,用独热(One-hot)码表示。为了对输入进行降维,同时建立词语之间的语义关联,模型为热独码表示的单词学习一个词嵌入(Word Embedding)表示,也就是常说的词向量,关于词向量的详细介绍请参考 PaddleBook 的[词向量](https://github.com/PaddlePaddle/book/blob/develop/04.word2vec/README.cn.md)一章。最后 RNN 单元逐个词地处理输入,得到完整句子的编码向量。
+
+- **解码器**:接受编码器的输入,逐个词地解码出目标语言序列 $u = {u_1, u_2, ..., u_{T'}}$。每个时间步,RNN 单元输出一个隐藏向量,之后经 `Softmax` 归一化计算出下一个目标词的条件概率,即 $P(u_i | w, u_1, u_2, ..., u_{t-1})$。因此,给定输入 $w$,其对应的翻译结果为 $u$ 的概率则为
+
+$$ P(u_1,u_2,...,u_{T'} | w) = \prod_{t=1}^{t={T'}}p(u_t|w, u_1, u_2, u_{t-1})$$
+
+以中文到英文的翻译为例,源语言是中文,目标语言是英文。下面是一句源语言分词后的句子
+
+```
+祝愿 祖国 繁荣 昌盛
+```
+
+对应的目标语言英文翻译结果为:
+
+```
+Wish motherland rich and powerful
+```
+
+在预处理阶段,准备源语言与目标语言互译的平行语料数据,并分别构建源语言和目标语言的词典;在训练阶段,用这样成对的平行语料训练模型;在模型测试阶段,输入中文句子,模型自动生成对应的英语翻译,然后将生成结果与标准翻译对比进行评估。在机器翻译领域,BLEU 是最流行的自动评估指标之一。
+
+### RNN 单元
+RNN 的原始结构用一个向量来存储隐状态,然而这种结构的 RNN 在训练时容易发生梯度弥散(gradient vanishing),对于长时间的依赖关系难以建模。因此人们对 RNN 单元进行了改进,提出了 LSTM\[[1](#参考文献)] 和 GRU\[[2](#参考文献)],这两种单元以门来控制应该记住的和遗忘的信息,较好地解决了序列数据的长时依赖问题。以本例所用的 GRU 为例,其基本结构如下:
+
+
+
+图 2. GRU 单元
+
+
+可以看到除了隐含状态以外,GRU 内部还包含了两个门:更新门(Update Gate)、重置门(Reset Gate)。在每一个时间步,门限和隐状态的更新由图 2 右侧的公式决定。这两个门限决定了状态以何种方式更新。
+
+### 双向编码器
+在上述的基本模型中,编码器在顺序处理输入句子序列时,当前时刻的状态只包含了历史输入信息,而没有未来时刻的序列信息。而对于序列建模,未来时刻的上下文同样包含了重要的信息。可以使用如图 3 所示的这种双向编码器来同时获取当前时刻输入的上下文:
+
+
+图 3. 双向编码器结构示意图
+
+
+图 3 所示的双向编码器\[[3](#参考文献)\]由两个独立的 RNN 构成,分别从前向和后向对输入序列进行编码,然后将两个 RNN 的输出合并在一起,作为最终的编码输出。
+在 PaddlePaddle 中,双向编码器可以很方便地调用相关 APIs 实现:
+
+```python
+#### Encoder
+src_word_id = paddle.layer.data(
+ name='source_language_word',
+ type=paddle.data_type.integer_value_sequence(source_dict_dim))
+# source embedding
+src_embedding = paddle.layer.embedding(
+ input=src_word_id, size=word_vector_dim)
+# use bidirectional_gru
+encoded_vector = paddle.networks.bidirectional_gru(
+ input=src_embedding,
+ size=encoder_size,
+ fwd_act=paddle.activation.Tanh(),
+ fwd_gate_act=paddle.activation.Sigmoid(),
+ bwd_act=paddle.activation.Tanh(),
+ bwd_gate_act=paddle.activation.Sigmoid(),
+ return_seq=True)
+```
+
+### 柱搜索(Beam Search) 算法
+训练完成后的生成阶段,模型根据源语言输入,解码生成对应的目标语言翻译结果。解码时,一个直接的方式是取每一步条件概率最大的词,作为当前时刻的输出。但局部最优并不一定能得到全局最优,即这种做法并不能保证最后得到的完整句子出现的概率最大。如果对解的全空间进行搜索,其代价又过大。为了解决这个问题,通常采用柱搜索(Beam Search)算法。柱搜索是一种启发式的图搜索算法,用一个参数 $k$ 控制搜索宽度,其要点如下:
+
+**1**. 在解码的过程中,始终维护 $k$ 个已解码出的子序列;
+
+**2**. 在中间时刻 $t$, 对于 $k$ 个子序列中的每个序列,计算下一个词出现的概率并取概率最大的前 $k$ 个词,组合得到 $k^2$ 个新子序列;
+
+**3**. 取 **2** 中这些组合序列中概率最大的前 $k$ 个以更新原来的子序列;
+
+**4**. 不断迭代下去,直至得到 $k$ 个完整的句子,作为翻译结果的候选。
+
+关于柱搜索的更多介绍,可以参考 PaddleBook 中[机器翻译](https://github.com/PaddlePaddle/book/blob/develop/08.machine_translation/README.cn.md)一章中[柱搜索](https://github.com/PaddlePaddle/book/blob/develop/08.machine_translation/README.cn.md#柱搜索算法)一节。
+
+
+### 无注意力机制的解码器
+PaddleBook中[机器翻译](https://github.com/PaddlePaddle/book/blob/develop/08.machine_translation/README.cn.md)的相关章节中,已介绍了带注意力机制(Attention Mechanism)的 Encoder-Decoder 结构,本例则介绍的是不带注意力机制的 Encoder-Decoder 结构。关于注意力机制,读者可进一步参考 PaddleBook 和参考文献\[[3](#参考文献)]。
+
+对于流行的RNN单元,PaddlePaddle 已有很好的实现均可直接调用。如果希望在 RNN 每一个时间步实现某些自定义操作,可使用 PaddlePaddle 中的`recurrent_layer_group`。首先,自定义单步逻辑函数,再利用函数 `recurrent_group()` 循环调用单步逻辑函数处理整个序列。本例中的无注意力机制的解码器便是使用`recurrent_layer_group`来实现,其中,单步逻辑函数`gru_decoder_without_attention()`相关代码如下:
+
+```python
+#### Decoder
+encoder_last = paddle.layer.last_seq(input=encoded_vector)
+encoder_last_projected = paddle.layer.mixed(
+ size=decoder_size,
+ act=paddle.activation.Tanh(),
+ input=paddle.layer.full_matrix_projection(input=encoder_last))
+
+# gru step
+def gru_decoder_without_attention(enc_vec, current_word):
+ '''
+ Step function for gru decoder
+ :param enc_vec: encoded vector of source language
+ :type enc_vec: layer object
+ :param current_word: current input of decoder
+ :type current_word: layer object
+ '''
+ decoder_mem = paddle.layer.memory(
+ name='gru_decoder',
+ size=decoder_size,
+ boot_layer=encoder_last_projected)
+
+ context = paddle.layer.last_seq(input=enc_vec)
+
+ decoder_inputs = paddle.layer.mixed(
+ size=decoder_size * 3,
+ input=[
+ paddle.layer.full_matrix_projection(input=context),
+ paddle.layer.full_matrix_projection(input=current_word)
+ ])
+
+ gru_step = paddle.layer.gru_step(
+ name='gru_decoder',
+ act=paddle.activation.Tanh(),
+ gate_act=paddle.activation.Sigmoid(),
+ input=decoder_inputs,
+ output_mem=decoder_mem,
+ size=decoder_size)
+
+ out = paddle.layer.mixed(
+ size=target_dict_dim,
+ bias_attr=True,
+ act=paddle.activation.Softmax(),
+ input=paddle.layer.full_matrix_projection(input=gru_step))
+ return out
+```
+
+在模型训练和测试阶段,解码器的行为有很大的不同:
+
+- **训练阶段**:目标翻译结果的词向量`trg_embedding`作为参数传递给单步逻辑`gru_decoder_without_attention()`,函数`recurrent_group()`循环调用单步逻辑执行,最后计算目标翻译与实际解码的差异cost并返回;
+- **测试阶段**:解码器根据最后一个生成的词预测下一个词,`GeneratedInput()`自动取出模型预测出的概率最高的$k$个词的词向量传递给单步逻辑,`beam_search()`函数调用单步逻辑函数`gru_decoder_without_attention()`完成柱搜索并作为结果返回。
+
+训练和生成的逻辑分别实现在如下的`if-else`条件分支中:
+
+```python
+decoder_group_name = "decoder_group"
+group_input1 = paddle.layer.StaticInput(input=encoded_vector, is_seq=True)
+group_inputs = [group_input1]
+if not generating:
+ trg_embedding = paddle.layer.embedding(
+ input=paddle.layer.data(
+ name='target_language_word',
+ type=paddle.data_type.integer_value_sequence(target_dict_dim)),
+ size=word_vector_dim,
+ param_attr=paddle.attr.ParamAttr(name='_target_language_embedding'))
+ group_inputs.append(trg_embedding)
+
+ decoder = paddle.layer.recurrent_group(
+ name=decoder_group_name,
+ step=gru_decoder_without_attention,
+ input=group_inputs)
+
+ lbl = paddle.layer.data(
+ name='target_language_next_word',
+ type=paddle.data_type.integer_value_sequence(target_dict_dim))
+ cost = paddle.layer.classification_cost(input=decoder, label=lbl)
+
+ return cost
+else:
+
+ trg_embedding = paddle.layer.GeneratedInput(
+ size=target_dict_dim,
+ embedding_name='_target_language_embedding',
+ embedding_size=word_vector_dim)
+ group_inputs.append(trg_embedding)
+
+ beam_gen = paddle.layer.beam_search(
+ name=decoder_group_name,
+ step=gru_decoder_without_attention,
+ input=group_inputs,
+ bos_id=0,
+ eos_id=1,
+ beam_size=beam_size,
+ max_length=max_length)
+
+ return beam_gen
+```
+
+## 数据准备
+本例所用到的数据来自[WMT14](http://www-lium.univ-lemans.fr/~schwenk/cslm_joint_paper/),该数据集是法文到英文互译的平行语料。用[bitexts](http://www-lium.univ-lemans.fr/~schwenk/cslm_joint_paper/data/bitexts.tgz)作为训练数据,[dev+test data](http://www-lium.univ-lemans.fr/~schwenk/cslm_joint_paper/data/dev+test.tgz)作为验证与测试数据。在PaddlePaddle中已经封装好了该数据集的读取接口,在首次运行的时候,程序会自动完成下载,用户无需手动完成相关的数据准备。
+
+## 模型的训练与测试
+
+在定义好网络结构后,就可以进行模型训练与测试了。根据用户运行时传递的参数是`--train` 还是 `--generate`,Python 脚本的 `main()` 函数分别调用函数`train()`和`generate()`来完成模型的训练与测试。
+
+### 模型训练
+模型训练阶段,函数 `train()` 依次完成了如下的逻辑:
+
+**a) 由网络定义,解析网络结构,初始化模型参数**
+
+```
+# initialize model
+cost = seq2seq_net(source_dict_dim, target_dict_dim)
+parameters = paddle.parameters.create(cost)
+```
+
+**b) 设定训练过程中的优化策略、定义训练数据读取 `reader`**
+
+```
+# define optimize method and trainer
+optimizer = paddle.optimizer.RMSProp(
+ learning_rate=1e-3,
+ gradient_clipping_threshold=10.0,
+ regularization=paddle.optimizer.L2Regularization(rate=8e-4))
+trainer = paddle.trainer.SGD(
+ cost=cost, parameters=parameters, update_equation=optimizer)
+# define data reader
+wmt14_reader = paddle.batch(
+ paddle.reader.shuffle(
+ paddle.dataset.wmt14.train(source_dict_dim), buf_size=8192),
+ batch_size=55)
+```
+
+**c) 定义事件句柄,打印训练中间结果、保存模型快照**
+
+```
+# define event_handler callback
+def event_handler(event):
+ if isinstance(event, paddle.event.EndIteration):
+ if event.batch_id % 100 == 0 and event.batch_id > 0:
+ with gzip.open('models/nmt_without_att_params_batch_%d.tar.gz' %
+ event.batch_id, 'w') as f:
+ parameters.to_tar(f)
+
+ if event.batch_id % 10 == 0:
+ print "\nPass %d, Batch %d, Cost%f, %s" % (
+ event.pass_id, event.batch_id, event.cost, event.metrics)
+ else:
+ sys.stdout.write('.')
+ sys.stdout.flush()
+```
+
+**d) 开始训练**
+
+```
+# start to train
+trainer.train(
+ reader=wmt14_reader, event_handler=event_handler, num_passes=2)
+```
+
+启动模型训练的十分简单,只需在命令行窗口中执行
+
+```
+python nmt_without_attention_v2.py --train
+```
+
+输出样例为
+
+```
+Pass 0, Batch 0, Cost 267.674663, {'classification_error_evaluator': 1.0}
+.........
+Pass 0, Batch 10, Cost 172.892294, {'classification_error_evaluator': 0.953895092010498}
+.........
+Pass 0, Batch 20, Cost 177.989329, {'classification_error_evaluator': 0.9052488207817078}
+.........
+Pass 0, Batch 30, Cost 153.633665, {'classification_error_evaluator': 0.8643803596496582}
+.........
+Pass 0, Batch 40, Cost 168.170543, {'classification_error_evaluator': 0.8348183631896973}
+```
+
+
+### 模型测试
+模型测试阶段,函数`generate()`执行了依次如下逻辑:
+
+**a) 加载测试样本**
+
+```
+# load data samples for generation
+gen_creator = paddle.dataset.wmt14.gen(source_dict_dim)
+gen_data = []
+for item in gen_creator():
+ gen_data.append((item[0], ))
+```
+
+**b) 初始化模型,执行`infer()`为每个输入样本生成`beam search`的翻译结果**
+
+```
+beam_gen = seq2seq_net(source_dict_dim, target_dict_dim, True)
+with gzip.open(init_models_path) as f:
+ parameters = paddle.parameters.Parameters.from_tar(f)
+# prob is the prediction probabilities, and id is the prediction word.
+beam_result = paddle.infer(
+ output_layer=beam_gen,
+ parameters=parameters,
+ input=gen_data,
+ field=['prob', 'id'])
+```
+
+**c) 加载源语言和目标语言词典,将`id`序列表示的句子转化成原语言并输出结果**
+
+```
+# get the dictionary
+src_dict, trg_dict = paddle.dataset.wmt14.get_dict(source_dict_dim)
+
+# the delimited element of generated sequences is -1,
+# the first element of each generated sequence is the sequence length
+seq_list = []
+seq = []
+for w in beam_result[1]:
+ if w != -1:
+ seq.append(w)
+ else:
+ seq_list.append(' '.join([trg_dict.get(w) for w in seq[1:]]))
+ seq = []
+
+prob = beam_result[0]
+for i in xrange(len(gen_data)):
+ print "\n*******************************************************\n"
+ print "src:", ' '.join([src_dict.get(w) for w in gen_data[i][0]]), "\n"
+ for j in xrange(beam_size):
+ print "prob = %f:" % (prob[i][j]), seq_list[i * beam_size + j]
+```
+
+模型测试的执行与模型训练类似,只需执行
+
+```
+python nmt_without_attention_v2.py --generate
+```
+则自动为测试数据生成了对应的翻译结果。
+设置beam search的宽度为3,输入某个法文句子
+
+```
+src:
Elles connaissent leur entreprise mieux que personne .
+```
+
+其对应的英文翻译结果为
+
+```
+prob = -3.754819: They know their business better than anyone .
+prob = -4.445528: They know their businesses better than anyone .
+prob = -5.026885: They know their business better than anybody .
+```
+
+* `prob`表示生成句子的得分,随之其后则是翻译生成的句子;
+* `` 表示句子的开始,``表示一个句子的结束,如果出现了在词典中未包含的词,则用``替代。
+
+至此,我们在 PaddlePaddle 上实现了一个初步的机器翻译模型。我们可以看到,PaddlePaddle 提供了灵活丰富的API供大家选择和使用,使得我们能够很方便完成各种复杂网络的配置。机器翻译本身也是个快速发展的领域,各种新方法新思想在不断涌现。在学习完本例后,读者若有兴趣和余力,可基于 PaddlePaddle 平台实现更为复杂、性能更优的机器翻译模型。
+
+
+## 参考文献
+[1] Sutskever I, Vinyals O, Le Q V. [Sequence to Sequence Learning with Neural Networks](https://arxiv.org/abs/1409.3215)[J]. 2014, 4:3104-3112.
+
+[2]Cho K, Van Merriënboer B, Gulcehre C, et al. [Learning phrase representations using RNN encoder-decoder for statistical machine translation](http://www.aclweb.org/anthology/D/D14/D14-1179.pdf)[C]. Proceedings of the 2014 Conference on Empirical Methods in Natural Language Processing (EMNLP), 2014: 1724-1734.
+
+[3] Bahdanau D, Cho K, Bengio Y. [Neural machine translation by jointly learning to align and translate](https://arxiv.org/abs/1409.0473)[C]. Proceedings of ICLR 2015, 2015
+
+
+
+
+
+
diff --git a/nmt_without_attention/nmt_without_attention.py b/nmt_without_attention/nmt_without_attention.py
index e5a4e1b602226da802c5903d83c0d963ae37bd44..5a61b525e67f7d07f66ae8cc5064c0244bc0b6f3 100644
--- a/nmt_without_attention/nmt_without_attention.py
+++ b/nmt_without_attention/nmt_without_attention.py
@@ -16,7 +16,7 @@ def seq2seq_net(source_dict_dim, target_dict_dim, generating=False):
'''
Define the network structure of NMT, including encoder and decoder.
- :param source_dict_dim: size of source dictionary
+ :param source_dict_dim: size of source dictionary
:type source_dict_dim : int
:param target_dict_dim: size of target dictionary
:type target_dict_dim: int
@@ -41,11 +41,11 @@ def seq2seq_net(source_dict_dim, target_dict_dim, generating=False):
return_seq=True)
#### Decoder
encoder_last = paddle.layer.last_seq(input=encoded_vector)
- with paddle.layer.mixed(
- size=decoder_size,
- act=paddle.activation.Tanh()) as encoder_last_projected:
- encoder_last_projected += paddle.layer.full_matrix_projection(
- input=encoder_last)
+ encoder_last_projected = paddle.layer.mixed(
+ size=decoder_size,
+ act=paddle.activation.Tanh(),
+ input=paddle.layer.full_matrix_projection(input=encoder_last))
+
# gru step
def gru_decoder_without_attention(enc_vec, current_word):
'''
@@ -63,10 +63,12 @@ def seq2seq_net(source_dict_dim, target_dict_dim, generating=False):
context = paddle.layer.last_seq(input=enc_vec)
- with paddle.layer.mixed(size=decoder_size * 3) as decoder_inputs:
- decoder_inputs += paddle.layer.full_matrix_projection(input=context)
- decoder_inputs += paddle.layer.full_matrix_projection(
- input=current_word)
+ decoder_inputs = paddle.layer.mixed(
+ size=decoder_size * 3,
+ input=[
+ paddle.layer.full_matrix_projection(input=context),
+ paddle.layer.full_matrix_projection(input=current_word)
+ ])
gru_step = paddle.layer.gru_step(
name='gru_decoder',
@@ -76,15 +78,15 @@ def seq2seq_net(source_dict_dim, target_dict_dim, generating=False):
output_mem=decoder_mem,
size=decoder_size)
- with paddle.layer.mixed(
- size=target_dict_dim,
- bias_attr=True,
- act=paddle.activation.Softmax()) as out:
- out += paddle.layer.full_matrix_projection(input=gru_step)
+ out = paddle.layer.mixed(
+ size=target_dict_dim,
+ bias_attr=True,
+ act=paddle.activation.Softmax(),
+ input=paddle.layer.full_matrix_projection(input=gru_step))
return out
decoder_group_name = "decoder_group"
- group_input1 = paddle.layer.StaticInputV2(input=encoded_vector, is_seq=True)
+ group_input1 = paddle.layer.StaticInput(input=encoded_vector, is_seq=True)
group_inputs = [group_input1]
if not generating:
@@ -109,7 +111,7 @@ def seq2seq_net(source_dict_dim, target_dict_dim, generating=False):
return cost
else:
- trg_embedding = paddle.layer.GeneratedInputV2(
+ trg_embedding = paddle.layer.GeneratedInput(
size=target_dict_dim,
embedding_name='_target_language_embedding',
embedding_size=word_vector_dim)
@@ -194,7 +196,7 @@ def generate(source_dict_dim, target_dict_dim, init_models_path):
beam_gen = seq2seq_net(source_dict_dim, target_dict_dim, True)
with gzip.open(init_models_path) as f:
parameters = paddle.parameters.Parameters.from_tar(f)
- # prob is the prediction probabilities, and id is the prediction word.
+ # prob is the prediction probabilities, and id is the prediction word.
beam_result = paddle.infer(
output_layer=beam_gen,
parameters=parameters,
@@ -244,10 +246,10 @@ def main():
target_language_dict_dim = 30000
if generating:
- # shoud pass the right generated model's path here
+ # modify this path to speicify a trained model.
init_models_path = 'models/nmt_without_att_params_batch_1800.tar.gz'
if not os.path.exists(init_models_path):
- print "Cannot find models for generation"
+ print "trained model cannot be found."
exit(1)
generate(source_language_dict_dim, target_language_dict_dim,
init_models_path)
diff --git a/sequence_tagging_for_ner/index.html b/sequence_tagging_for_ner/index.html
new file mode 100644
index 0000000000000000000000000000000000000000..b7c6c8994abdbcd80ff7347960d984e5528311a1
--- /dev/null
+++ b/sequence_tagging_for_ner/index.html
@@ -0,0 +1,314 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+# 命名实体识别
+
+命名实体识别(Named Entity Recognition,NER)又称作“专名识别”,是指识别文本中具有特定意义的实体,主要包括人名、地名、机构名、专有名词等,是自然语言处理研究的一个基础问题。NER任务通常包括实体边界识别、确定实体类别两部分,可以将其作为序列标注问题解决。
+
+序列标注可以分为Sequence Classification、Segment Classification和Temporal Classification三类[[1](#参考文献)],本例只考虑Segment Classification,即对输入序列中的每个元素在输出序列中给出对应的标签。对于NER任务,由于需要标识边界,一般采用[BIO方式](http://book.paddlepaddle.org/07.label_semantic_roles/)定义的标签集,如下是一个NER的标注结果示例:
+
+
+
+图1. BIO标注方法示例
+
+
+根据序列标注结果可以直接得到实体边界和实体类别。类似的,分词、词性标注、语块识别、[语义角色标注](http://book.paddlepaddle.org/07.label_semantic_roles/index.cn.html)等任务同样可通过序列标注来解决。
+
+由于序列标注问题的广泛性,产生了[CRF](http://book.paddlepaddle.org/07.label_semantic_roles/index.cn.html)等经典的序列模型,这些模型大多只能使用局部信息或需要人工设计特征。随着深度学习研究的发展,循环神经网络(Recurrent Neural Network,RNN等序列模型能够处理序列元素之间前后关联问题,能够从原始输入文本中学习特征表示,而更加适合序列标注任务,更多相关知识可参考PaddleBook中[语义角色标注](https://github.com/PaddlePaddle/book/blob/develop/07.label_semantic_roles/README.cn.md)一课。
+
+使用神经网络模型解决问题的思路通常是:前层网络学习输入的特征表示,网络的最后一层在特征基础上完成最终的任务;对于序列标注问题,通常:使用基于RNN的网络结构学习特征,将学习到的特征接入CRF完成序列标注。实际上是将传统CRF中的线性模型换成了非线性神经网络。沿用CRF的出发点是:CRF使用句子级别的似然概率,能够更好的解决标记偏置问题[[2](#参考文献)]。本例也将基于此思路建立模型。虽然,这里以NER任务作为示例,但所给出的模型可以应用到其他各种序列标注任务中。
+
+## 模型说明
+
+NER任务的输入是"一句话",目标是识别句子中的实体边界及类别,我们参照论文\[[2](#参考文献)\]仅对原始句子进行了一些预处理工作:将每个词转换为小写,并将原词是否大写另作为一个特征,共同作为模型的输入。按照上述处理序列标注问题的思路,可构造如下结构的模型(图2是模型结构示意图):
+
+1. 构造输入
+ - 输入1是句子序列,采用one-hot方式表示
+ - 输入2是大写标记序列,标记了句子中每一个词是否是大写,采用one-hot方式表示;
+2. one-hot方式的句子序列和大写标记序列通过词表,转换为实向量表示的词向量序列;
+3. 将步骤2中的2个词向量序列作为双向RNN的输入,学习输入序列的特征表示,得到新的特性表示序列;
+4. CRF以步骤3中模型学习到的特征为输入,以标记序列为监督信号,实现序列标注。
+
+
+
+图2. NER模型的网络结构图
+
+
+
+## 数据说明
+
+在本例中,我们使用CoNLL 2003 NER任务中开放出的数据集。该任务(见[此页面](http://www.clips.uantwerpen.be/conll2003/ner/))只提供了标注工具的下载,原始Reuters数据由于版权原因需另外申请免费下载。在获取原始数据后可参照标注工具中README生成所需数据文件,完成后将包括如下三个数据文件:
+
+| 文件名 | 描述 |
+|---|---|
+| eng.train | 训练数据 |
+| eng.testa | 验证数据,可用来进行参数调优 |
+| eng.testb | 评估数据,用来进行最终效果评估 |
+
+为保证本例的完整性,我们从中抽取少量样本放在`data/train`和`data/test`文件中,作为示例使用;由于版权原因,完整数据还请大家自行获取。这三个文件数据格式如下:
+
+```
+ U.N. NNP I-NP I-ORG
+ official NN I-NP O
+ Ekeus NNP I-NP I-PER
+ heads VBZ I-VP O
+ for IN I-PP O
+ Baghdad NNP I-NP I-LOC
+ . . O O
+```
+
+其中第一列为原始句子序列(第二、三列分别为词性标签和句法分析中的语块标签,这里暂时不用),第四列为采用了I-TYPE方式表示的NER标签(I-TYPE和BIO方式的主要区别在于语块开始标记的使用上,I-TYPE只有在出现相邻的同类别实体时对后者使用B标记,其他均使用I标记),句子之间以空行分隔。
+
+原始数据需要进行数据预处理才能被PaddlePaddle处理,预处理主要包括下面几个步骤:
+
+1. 从原始数据文件中抽取出句子和标签,构造句子序列和标签序列;
+2. 将I-TYPE表示的标签转换为BIO方式表示的标签;
+3. 将句子序列中的单词转换为小写,并构造大写标记序列;
+4. 依据词典获取词对应的整数索引。
+
+我们将在`conll03.py`中完成以上预处理工作(使用方法将在后文给出):
+
+```python
+# import conll03
+# conll03.corpus_reader函数完成上面第1步和第2步.
+# conll03.reader_creator函数完成上面第3步和第4步.
+# conll03.train和conll03.test函数可以获取处理之后的每条样本来供PaddlePaddle训练和测试.
+```
+
+预处理完成后,一条训练样本包含3个部分:句子序列、首字母大写标记序列、标注序列。下表是一条训练样本的示例。
+
+| 句子序列 | 大写标记序列 | 标注序列 |
+|---|---|---|
+| u.n. | 1 | B-ORG |
+| official | 0 | O |
+| ekeus | 1 | B-PER |
+| heads | 0 | O |
+| for | 0 | O |
+| baghdad | 1 | B-LOC |
+| . | 0 | O |
+
+另外,本例依赖的数据还包括:word词典、label词典和预训练的词向量三个文件。label词典已附在`data`目录中,对应于`data/target.txt`;word词典和预训练的词向量来源于[Stanford CS224d](http://cs224d.stanford.edu/)课程作业,请先在该示例所在目录下运行`data/download.sh`脚本进行下载,完成后会将这两个文件一并放入`data`目录下,分别对应`data/vocab.txt`和`data/wordVectors.txt`。
+
+## 使用说明
+
+本示例给出的`conll03.py`和`ner.py`两个Python脚本分别提供了数据相关和模型相关接口。
+
+### 数据接口使用
+
+`conll03.py`提供了使用CoNLL 2003数据的接口,各主要函数的功能已在数据说明部分进行说明。结合我们提供的接口和文件,可以按照如下步骤使用CoNLL 2003数据:
+
+1. 定义各数据文件、词典文件和词向量文件路径;
+2. 调用`conll03.train`和`conll03.test`接口。
+
+对应如下代码:
+
+```python
+import conll03
+
+# 修改以下变量为对应文件路径
+train_data_file = 'data/train' # 训练数据文件的路径
+test_data_file = 'data/test' # 测试数据文件的路径
+vocab_file = 'data/vocab.txt' # 输入句子对应的字典文件的路径
+target_file = 'data/target.txt' # 标签对应的字典文件的路径
+emb_file = 'data/wordVectors.txt' # 预训练的词向量参数的路径
+
+# 返回训练数据的生成器
+train_data_reader = conll03.train(train_data_file, vocab_file, target_file)
+# 返回测试数据的生成器
+test_data_reader = conll03.test(test_data_file, vocab_file, target_file)
+```
+
+### 模型接口使用
+
+`ner.py`提供了以下两个接口分别进行模型训练和预测:
+
+1. `ner_net_train(data_reader, num_passes)`函数实现了模型训练功能,参数`data_reader`表示训练数据的迭代器、`num_passes`表示训练pass的轮数。训练过程中每100个iteration会打印模型训练信息。我们同时在模型配置中加入了chunk evaluator,会输出当前模型对语块识别的Precision、Recall和F1值。chunk evaluator 的详细使用说明请参照[文档](http://www.paddlepaddle.org/develop/doc/api/v2/config/evaluators.html#chunk)。每个pass后会将模型保存为`params_pass_***.tar.gz`的文件(`***`表示pass的id)。
+
+2. `ner_net_infer(data_reader, model_file)`函数实现了预测功能,参数`data_reader`表示测试数据的迭代器、`model_file`表示保存在本地的模型文件,预测过程会按如下格式打印预测结果:
+
+ ```
+ U.N. B-ORG
+ official O
+ Ekeus B-PER
+ heads O
+ for O
+ Baghdad B-LOC
+ . O
+ ```
+ 其中第一列为原始句子序列,第二列为BIO方式表示的NER标签。
+
+### 运行程序
+
+本例另在`ner.py`中提供了完整的运行流程,包括数据接口的使用和模型训练、预测。根据上文所述的接口使用方法,使用时需要将`ner.py`中如下的数据设置部分中的各变量修改为对应文件路径:
+
+```python
+# 修改以下变量为对应文件路径
+train_data_file = 'data/train' # 训练数据文件的路径
+test_data_file = 'data/test' # 测试数据文件的路径
+vocab_file = 'data/vocab.txt' # 输入句子对应的字典文件的路径
+target_file = 'data/target.txt' # 标签对应的字典文件的路径
+emb_file = 'data/wordVectors.txt' # 预训练的词向量参数的路径
+```
+
+各接口的调用已在`ner.py`中提供:
+
+```python
+# 训练数据的生成器
+train_data_reader = conll03.train(train_data_file, vocab_file, target_file)
+# 测试数据的生成器
+test_data_reader = conll03.test(test_data_file, vocab_file, target_file)
+
+# 模型训练
+ner_net_train(data_reader=train_data_reader, num_passes=1)
+# 预测
+ner_net_infer(data_reader=test_data_reader, model_file='params_pass_0.tar.gz')
+```
+
+为运行序列标注模型除适当调整`num_passes`和`model_file`两参数值外,无需再做其它修改(也可根据需要自行调用各接口,如只使用预测功能)。完成修改后,运行本示例只需在`ner.py`所在路径下执行`python ner.py`即可。该示例程序会执行数据读取、模型训练和保存、模型读取及新样本预测等步骤。
+
+### 自定义数据和任务
+
+前文提到本例中的模型可以应用到其他序列标注任务中,这里以词性标注任务为例,给出使用其他数据,并应用到其他任务的操作方法。
+
+假定有如下格式的原始数据:
+
+```
+U.N. NNP
+official NN
+Ekeus NNP
+heads VBZ
+for IN
+Baghdad NNP
+. .
+```
+
+第一列为原始句子序列,第二列为词性标签序列,两列之间以“\t”分隔,句子之间以空行分隔。
+
+为使用PaddlePaddle和本示例提供的模型,可参照`conll03.py`并根据需要自定义数据接口,如下:
+
+1. 参照`conll03.py`中的`corpus_reader`函数,定义接口返回句子序列和标签序列生成器;
+
+ ```python
+ # 实现句子和对应标签的抽取,传入数据文件路径,返回句子和标签序列生成器。
+ def corpus_reader(filename):
+ def reader():
+ sentence = []
+ labels = []
+ with open(filename) as f:
+ for line in f:
+ if len(line.strip()) == 0:
+ if len(sentence) > 0:
+ yield sentence, labels
+ sentence = []
+ labels = []
+ else:
+ segs = line.strip().split()
+ sentence.append(segs[0])
+ labels.append(segs[-1])
+ f.close()
+
+ return reader
+ ```
+
+2. 参照`conll03.py`中的`reader_creator`函数,定义接口返回id化的句子和标签序列生成器。
+
+ ```python
+ # 传入corpus_reader返回的生成器、dict类型的word词典和label词典,返回id化的句子和标签序列生成器。
+ def reader_creator(corpus_reader, word_dict, label_dict):
+ def reader():
+ for sentence, labels in corpus_reader():
+ word_idx = [
+ word_dict.get(w, UNK_IDX) # 若使用小写单词,请使用w.lower()
+ for w in sentence
+ ]
+ # 若使用首字母大写标记,请去掉以下注释符号,并在yield语句的word_idx后加上mark
+ # mark = [
+ # 1 if w[0].isupper() else 0
+ # for w in sentence
+ # ]
+ label_idx = [label_dict.get(w) for w in labels]
+ yield word_idx, label_idx, sentence # 加上sentence方便预测时打印
+ return reader
+ ```
+
+自定义了数据接口后,要使用本示例中的模型,只需在调用模型训练和预测接口`ner_net_train`和`ner_net_infer`时传入调用`reader_creator`返回的生成器即可。另外需要注意,这里给出的数据接口定义去掉了`conll03.py`一些预处理(使用原始句子,而非转换成小写单词加上大写标记),`ner.py`中的模型相关接口也需要进行一些调整:
+
+1. 修改网络结构定义接口`ner_net`中大写标记相关内容:
+
+ 删去`mark`和`mark_embedding`两个变量;
+
+2. 修改模型训练接口`ner_net_train`中大写标记相关内容:
+
+ 将变量`feeding`定义改为`feeding = {'word': 0, 'target': 1}`;
+
+3. 修改预测接口`ner_net_infer`中大写标记相关内容:
+
+ 将`test_data.append([item[0], item[1]])`改为`test_data.append([item[0]])`。
+
+如果要继续使用NER中的特征预处理(小写单词、大写标记),请参照上文`reader_creator`代码段给出的注释进行修改,此时`ner.py`中的模型相关接口不必进行修改。
+
+## 参考文献
+
+1. Graves A. [Supervised Sequence Labelling with Recurrent Neural Networks](http://www.cs.toronto.edu/~graves/preprint.pdf)[J]. Studies in Computational Intelligence, 2013, 385.
+2. Collobert R, Weston J, Bottou L, et al. [Natural Language Processing (Almost) from Scratch](http://www.jmlr.org/papers/volume12/collobert11a/collobert11a.pdf)[J]. Journal of Machine Learning Research, 2011, 12(1):2493-2537.
+
+
+
+
+
+
diff --git a/text_classification/index.html b/text_classification/index.html
new file mode 100644
index 0000000000000000000000000000000000000000..3ee660d8471269bfebf2444fb7c4a97deb550561
--- /dev/null
+++ b/text_classification/index.html
@@ -0,0 +1,302 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+# 文本分类
+文本分类是机器学习中的一项常见任务,主要目的是根据一条文本的内容,判断该文本所属的类别。在本例子中,我们利用有标注的语料库训练二分类DNN和CNN模型,完成对输入文本的分类任务。
+
+DNN与CNN模型之间最大的区别在于:
+
+- DNN不属于序列模型,大多使用基本的全连接结构,只能接受固定维度的特征向量作为输入。
+
+- CNN属于序列模型,能够提取一个局部区域之内的特征,能够处理变长的序列输入。
+
+举例来说,情感分类是一项常见的文本分类任务,在情感分类中,我们希望训练一个模型来判断句子中表现出的情感是正向还是负向。例如,"The apple is not bad",其中的"not bad"是决定这个句子情感的关键。
+
+- 对于DNN模型来说,只能知道句子中有一个"not"和一个"bad",但两者之间的顺序关系在输入时已经丢失,网络不再有机会学习序列之间的顺序信息。
+
+- CNN模型接受文本序列作为输入,保留了"not bad"之间的顺序信息。因此,在大多数文本分类任务上,CNN模型的表现要好于DNN。
+
+## 实验数据
+本例子的实验在[IMDB数据集](http://ai.stanford.edu/%7Eamaas/data/sentiment/aclImdb_v1.tar.gz)上进行。IMDB数据集包含了来自IMDB(互联网电影数据库)网站的5万条电影影评,并被标注为正面/负面两种评价。数据集被划分为train和test两部分,各2.5万条数据,正负样本的比例基本为1:1。样本直接以英文原文的形式表示。
+
+## DNN模型
+
+**DNN的模型结构入下图所示:**
+
+
+
+图1. DNN文本分类模型
+
+
+**可以看到,模型主要分为如下几个部分:**
+
+- **词向量层**:IMDB的样本由原始的英文单词组成,为了更好地表示不同词之间语义上的关系,首先将英文单词转化为固定维度的向量。训练完成后,词与词语义上的相似程度可以用它们的词向量之间的距离来表示,语义上越相似,距离越近。关于词向量的更多信息请参考PaddleBook中的[词向量](https://github.com/PaddlePaddle/book/tree/develop/04.word2vec)一节。
+
+- **最大池化层**:最大池化在时间序列上进行,池化过程消除了不同语料样本在单词数量多少上的差异,并提炼出词向量中每一下标位置上的最大值。经过池化后,词向量层输出的向量序列被转化为一条固定维度的向量。例如,假设最大池化前向量的序列为`[[2,3,5],[7,3,6],[1,4,0]]`,则最大池化的结果为:`[7,4,6]`。
+
+- **全连接隐层**:经过最大池化后的向量被送入两个连续的隐层,隐层之间为全连接结构。
+
+
+- **输出层**:输出层的神经元数量和样本的类别数一致,例如在二分类问题中,输出层会有2个神经元。通过Softmax激活函数,输出结果是一个归一化的概率分布,和为1,因此第$i$个神经元的输出就可以认为是样本属于第$i$类的预测概率。
+
+**通过PaddlePaddle实现该DNN结构的代码如下:**
+
+```python
+import paddle.v2 as paddle
+
+def fc_net(dict_dim, class_dim=2, emb_dim=28):
+ """
+ dnn network definition
+
+ :param dict_dim: size of word dictionary
+ :type input_dim: int
+ :params class_dim: number of instance class
+ :type class_dim: int
+ :params emb_dim: embedding vector dimension
+ :type emb_dim: int
+ """
+
+ # input layers
+ data = paddle.layer.data("word",
+ paddle.data_type.integer_value_sequence(dict_dim))
+ lbl = paddle.layer.data("label", paddle.data_type.integer_value(class_dim))
+
+ # embedding layer
+ emb = paddle.layer.embedding(input=data, size=emb_dim)
+ # max pooling
+ seq_pool = paddle.layer.pooling(
+ input=emb, pooling_type=paddle.pooling.Max())
+
+ # two hidden layers
+ hd_layer_size = [28, 8]
+ hd_layer_init_std = [1.0 / math.sqrt(s) for s in hd_layer_size]
+ hd1 = paddle.layer.fc(
+ input=seq_pool,
+ size=hd_layer_size[0],
+ act=paddle.activation.Tanh(),
+ param_attr=paddle.attr.Param(initial_std=hd_layer_init_std[0]))
+ hd2 = paddle.layer.fc(
+ input=hd1,
+ size=hd_layer_size[1],
+ act=paddle.activation.Tanh(),
+ param_attr=paddle.attr.Param(initial_std=hd_layer_init_std[1]))
+
+ # output layer
+ output = paddle.layer.fc(
+ input=hd2,
+ size=class_dim,
+ act=paddle.activation.Softmax(),
+ param_attr=paddle.attr.Param(initial_std=1.0 / math.sqrt(class_dim)))
+
+ cost = paddle.layer.classification_cost(input=output, label=lbl)
+
+ return cost, output, lbl
+
+```
+该DNN模型默认对输入的语料进行二分类(`class_dim=2`),embedding的词向量维度默认为28(`emd_dim=28`),两个隐层均使用Tanh激活函数(`act=paddle.activation.Tanh()`)。
+
+需要注意的是,该模型的输入数据为整数序列,而不是原始的英文单词序列。事实上,为了处理方便我们一般会事先将单词根据词频顺序进行id化,即将单词用整数替代, 也就是单词在字典中的序号。这一步一般在DNN模型之外完成。
+
+## CNN模型
+
+**CNN的模型结构如下图所示:**
+
+
+
+图2. CNN文本分类模型
+
+
+**可以看到,模型主要分为如下几个部分:**
+
+- **词向量层**:与DNN中词向量层的作用一样,将英文单词转化为固定维度的向量,利用向量之间的距离来表示词之间的语义相关程度。如图2中所示,将得到的词向量定义为行向量,再将语料中所有的单词产生的行向量拼接在一起组成矩阵。假设词向量维度为5,语料“The cat sat on the read mat”包含7个单词,那么得到的矩阵维度为7*5。关于词向量的更多信息请参考PaddleBook中的[词向量](https://github.com/PaddlePaddle/book/tree/develop/04.word2vec)一节。
+
+- **卷积层**: 文本分类中的卷积在时间序列上进行,即卷积核的宽度和词向量层产出的矩阵一致,卷积沿着矩阵的高度方向进行。卷积后得到的结果被称为“特征图”(feature map)。假设卷积核的高度为$h$,矩阵的高度为$N$,卷积的步长为1,则得到的特征图为一个高度为$N+1-h$的向量。可以同时使用多个不同高度的卷积核,得到多个特征图。
+
+- **最大池化层**: 对卷积得到的各个特征图分别进行最大池化操作。由于特征图本身已经是向量,因此这里的最大池化实际上就是简单地选出各个向量中的最大元素。各个最大元素又被拼接在一起,组成新的向量,显然,该向量的维度等于特征图的数量,也就是卷积核的数量。举例来说,假设我们使用了四个不同的卷积核,卷积产生的特征图分别为:`[2,3,5]`、`[8,2,1]`、`[5,7,7,6]`和`[4,5,1,8]`,由于卷积核的高度不同,因此产生的特征图尺寸也有所差异。分别在这四个特征图上进行最大池化,结果为:`[5]`、`[8]`、`[7]`和`[8]`,最后将池化结果拼接在一起,得到`[5,8,7,8]`。
+
+- **全连接与输出层**:将最大池化的结果通过全连接层输出,与DNN模型一样,最后输出层的神经元个数与样本的类别数量一致,且输出之和为1。
+
+**通过PaddlePaddle实现该CNN结构的代码如下:**
+
+```python
+import paddle.v2 as paddle
+
+def convolution_net(dict_dim, class_dim=2, emb_dim=28, hid_dim=128):
+ """
+ cnn network definition
+
+ :param dict_dim: size of word dictionary
+ :type input_dim: int
+ :params class_dim: number of instance class
+ :type class_dim: int
+ :params emb_dim: embedding vector dimension
+ :type emb_dim: int
+ :params hid_dim: number of same size convolution kernels
+ :type hid_dim: int
+ """
+
+ # input layers
+ data = paddle.layer.data("word",
+ paddle.data_type.integer_value_sequence(dict_dim))
+ lbl = paddle.layer.data("label", paddle.data_type.integer_value(2))
+
+ #embedding layer
+ emb = paddle.layer.embedding(input=data, size=emb_dim)
+
+ # convolution layers with max pooling
+ conv_3 = paddle.networks.sequence_conv_pool(
+ input=emb, context_len=3, hidden_size=hid_dim)
+ conv_4 = paddle.networks.sequence_conv_pool(
+ input=emb, context_len=4, hidden_size=hid_dim)
+
+ # fc and output layer
+ output = paddle.layer.fc(
+ input=[conv_3, conv_4], size=class_dim, act=paddle.activation.Softmax())
+
+ cost = paddle.layer.classification_cost(input=output, label=lbl)
+
+ return cost, output, lbl
+```
+
+该CNN网络的输入数据类型和前面介绍过的DNN一致。`paddle.networks.sequence_conv_pool`为PaddlePaddle中已经封装好的带有池化的文本序列卷积模块,该模块的`context_len`参数用于指定卷积核在同一时间覆盖的文本长度,即图2中的卷积核的高度;`hidden_size`用于指定该类型的卷积核的数量。可以看到,上述代码定义的结构中使用了128个大小为3的卷积核和128个大小为4的卷积核,这些卷积的结果经过最大池化和结果拼接后产生一个256维的向量,向量经过一个全连接层输出最终预测结果。
+
+## 自定义数据
+本样例中的代码通过`Paddle.dataset.imdb.train`接口使用了PaddlePaddle自带的样例数据,在第一次运行代码时,PaddlePaddle会自动下载并缓存所需的数据。如果希望使用自己的数据进行训练,需要自行编写数据读取接口。
+
+编写数据读取接口的关键在于实现一个Python生成器,生成器负责从原始输入文本中解析出一条训练样本,并组合成适当的数据形式传送给网络中的data layer。例如在本样例中,data layer需要的数据类型为`paddle.data_type.integer_value_sequence`,本质上是一个Python list。因此我们的生成器需要完成:从文件中读取数据, 以及转换成适当形式的Python list,这两件事情。
+
+假设原始数据的格式为:
+
+```
+PaddlePaddle is good 1
+What a terrible weather 0
+```
+每一行为一条样本,样本包括了原始语料和标签,语料内部单词以空格分隔,语料和标签之间用`\t`分隔。对以上格式的数据,可以使用如下自定义的数据读取接口为PaddlePaddle返回训练数据:
+
+```python
+def encode_word(word, word_dict):
+ """
+ map word to id
+
+ :param word: the word to be mapped
+ :type word: str
+ :param word_dict: word dictionary
+ :type word_dict: Python dict
+ """
+
+ if word_dict.has_key(word):
+ return word_dict[word]
+ else:
+ return word_dict['
']
+
+def data_reader(file_name, word_dict):
+ """
+ Reader interface for training data
+
+ :param file_name: data file name
+ :type file_name: str
+ :param word_dict: word dictionary
+ :type word_dict: Python dict
+ """
+
+ def reader():
+ with open(file_name, "r") as f:
+ for line in f:
+ ins, label = line.strip('\n').split('\t')
+ ins_data = [int(encode_word(w, word_dict)) for w in ins.split(' ')]
+ yield ins_data, int(label)
+ return reader
+```
+
+`word_dict`是字典,用来将原始的单词字符串转化为在字典中的序号。可以用`data_reader`替换原先代码中的`Paddle.dataset.imdb.train`接口用以提供自定义的训练数据。
+
+## 运行与输出
+
+本部分以上文介绍的DNN网络为例,介绍如何利用样例中的`text_classification_dnn.py`脚本进行DNN网络的训练和对新样本的预测。
+
+`text_classification_dnn.py`中的代码分为四部分:
+
+- **fc_net函数**:定义dnn网络结构,上文已经有说明。
+
+- **train\_dnn\_model函数**:模型训练函数。定义优化方式、训练输出等内容,并组织训练流程。每完成一个pass的训练,程序都会将当前的模型参数保存在硬盘上,文件名为:`dnn_params_pass***.tar.gz`,其中`***`表示pass的id,从0开始计数。本函数接受一个整数类型的参数,表示训练pass的总轮数。
+
+- **dnn_infer函数**:载入已有模型并对新样本进行预测。函数开始运行后会从当前路径下寻找并读取指定名称的参数文件,加载其中的模型参数,并对test数据集中的样本进行预测。
+
+- **main函数**:主函数
+
+要运行本样例,直接在`text_classification_dnn.py`所在路径下执行`python text_classification_dnn.py`即可,样例会自动依次执行数据集下载、数据读取、模型训练和保存、模型读取、新样本预测等步骤。
+
+预测的输出形式为:
+
+```
+[ 0.99892634 0.00107362] 0
+[ 0.00107638 0.9989236 ] 1
+[ 0.98185927 0.01814074] 0
+[ 0.31667888 0.68332112] 1
+[ 0.98853314 0.01146684] 0
+```
+
+每一行表示一条样本的预测结果。前两列表示该样本属于0、1这两个类别的预测概率,最后一列表示样本的实际label。
+
+在运行CNN模型的`text_classification_cnn.py`脚本中,网络模型定义在`convolution_net`函数中,模型训练函数名为`train_cnn_model`,预测函数名为`cnn_infer`。其他用法和`text_classification_dnn.py`是一致的。
+
+
+
+
+
+
diff --git a/word_embedding/index.html b/word_embedding/index.html
new file mode 100644
index 0000000000000000000000000000000000000000..83f6809d669d9ec6e0dd002f414ba8247068e270
--- /dev/null
+++ b/word_embedding/index.html
@@ -0,0 +1,227 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+# Hsigmoid加速词向量训练
+## 背景介绍
+在自然语言处理领域中,传统做法通常使用one-hot向量来表示词,比如词典为['我', '你', '喜欢'],可以用[1,0,0]、[0,1,0]和[0,0,1]这三个向量分别表示'我'、'你'和'喜欢'。这种表示方式比较简洁,但是当词表很大时,容易产生维度爆炸问题;而且任意两个词的向量是正交的,向量包含的信息有限。为了避免或减轻one-hot表示的缺点,目前通常使用词向量来取代one-hot表示,词向量也就是word embedding,即使用一个低维稠密的实向量取代高维稀疏的one-hot向量。训练词向量的方法有很多种,神经网络模型是其中之一,包括CBOW、Skip-gram等,这些模型本质上都是一个分类模型,当词表较大即类别较多时,传统的softmax将非常消耗时间。PaddlePaddle提供了Hsigmoid Layer、NCE Layer,来加速模型的训练过程。本文主要介绍如何使用Hsigmoid Layer来加速训练,词向量相关内容请查阅PaddlePaddle Book中的[词向量章节](https://github.com/PaddlePaddle/book/tree/develop/04.word2vec)。
+
+## Hsigmoid Layer
+Hsigmoid Layer引用自论文\[[1](#参考文献)\],Hsigmoid指Hierarchical-sigmoid,原理是通过构建一个分类二叉树来降低计算复杂度,二叉树中每个叶子节点代表一个类别,每个非叶子节点代表一个二类别分类器。例如我们一共有4个类别分别是0、1、2、3,softmax会分别计算4个类别的得分,然后归一化得到概率。当类别数很多时,计算每个类别的概率非常耗时,Hsigmoid Layer会根据类别数构建一个平衡二叉树,如下:
+
+
+
+图1. (a)为平衡二叉树,(b)为根节点到类别1的路径
+
+
+二叉树中每个非叶子节点是一个二类别分类器(sigmoid),如果类别是0,则取左子节点继续分类判断,反之取右子节点,直至达到叶节点。按照这种方式,每个类别均对应一条路径,例如从root到类别1的路径编码为0、1。训练阶段我们按照真实类别对应的路径,依次计算对应分类器的损失,然后综合所有损失得到最终损失。预测阶段,模型会输出各个非叶节点分类器的概率,我们可以根据概率获取路径编码,然后遍历路径编码就可以得到最终预测类别。传统softmax的计算复杂度为N(N为词典大小),Hsigmoid可以将复杂度降至log(N),详细理论细节可参照论文\[[1](#参考文献)\]。
+
+## 数据准备
+### PTB数据
+本文采用Penn Treebank (PTB)数据集([Tomas Mikolov预处理版本](http://www.fit.vutbr.cz/~imikolov/rnnlm/simple-examples.tgz)),共包含train、valid和test三个文件。其中使用train作为训练数据,valid作为测试数据。本文训练的是5-gram模型,即用每条数据的前4个词来预测第5个词。PaddlePaddle提供了对应PTB数据集的python包[paddle.dataset.imikolov](https://github.com/PaddlePaddle/Paddle/blob/develop/python/paddle/v2/dataset/imikolov.py) ,自动做数据的下载与预处理。预处理会把数据集中的每一句话前后加上开始符号\
以及结束符号\,然后依据窗口大小(本文为5),从头到尾每次向右滑动窗口并生成一条数据。如"I have a dream that one day"可以生成\ I have a dream、I have a dream that、have a dream that one、a dream that one day、dream that one day \,PaddlePaddle会把词转换成id数据作为预处理的输出。
+
+### 自定义数据
+用户可以使用自己的数据集训练模型,自定义数据集最关键的地方是实现reader接口做数据处理,reader需要产生一个迭代器,迭代器负责解析文件中的每一行数据,返回一个python list,例如[1, 2, 3, 4, 5],分别是第一个到第四个词在字典中的id,PaddlePaddle会进一步将该list转化成`paddle.data_type.inter_value`类型作为data layer的输入,一个封装样例如下:
+
+```python
+def reader_creator(filename, word_dict, n):
+ def reader():
+ with open(filename) as f:
+ UNK = word_dict['']
+ for l in f:
+ l = [''] + l.strip().split() + ['']
+ if len(l) >= n:
+ l = [word_dict.get(w, UNK) for w in l]
+ for i in range(n, len(l) + 1):
+ yield tuple(l[i - n:i])
+ return reader
+
+
+def train_data(filename, word_dict, n):
+ """
+ Reader interface for training data.
+
+ It returns a reader creator, each sample in the reader is a word ID tuple.
+
+ :param filename: path of data file
+ :type filename: str
+ :param word_dict: word dictionary
+ :type word_dict: dict
+ :param n: sliding window size
+ :type n: int
+ """
+ return reader_creator(filename, word_dict, n)
+```
+
+## 网络结构
+本文通过训练N-gram语言模型来获得词向量,具体地使用前4个词来预测当前词。网络输入为词在字典中的id,然后查询词向量词表获取词向量,接着拼接4个词的词向量,然后接入一个全连接隐层,最后是Hsigmoid层。详细网络结构见图2:
+
+
+
+图2. 网络配置结构
+
+
+代码实现如下:
+
+```python
+import math
+import paddle.v2 as paddle
+
+
+def network_conf(hidden_size, embed_size, dict_size, is_train=True):
+ first_word = paddle.layer.data(
+ name='firstw', type=paddle.data_type.integer_value(dict_size))
+ second_word = paddle.layer.data(
+ name='secondw', type=paddle.data_type.integer_value(dict_size))
+ third_word = paddle.layer.data(
+ name='thirdw', type=paddle.data_type.integer_value(dict_size))
+ fourth_word = paddle.layer.data(
+ name='fourthw', type=paddle.data_type.integer_value(dict_size))
+ target_word = paddle.layer.data(
+ name='fifthw', type=paddle.data_type.integer_value(dict_size))
+
+ embed_param_attr = paddle.attr.Param(
+ name="_proj", initial_std=0.001, learning_rate=1, l2_rate=0)
+ embed_first_word = paddle.layer.embedding(
+ input=first_word, size=embed_size, param_attr=embed_param_attr)
+ embed_second_word = paddle.layer.embedding(
+ input=second_word, size=embed_size, param_attr=embed_param_attr)
+ embed_third_word = paddle.layer.embedding(
+ input=third_word, size=embed_size, param_attr=embed_param_attr)
+ embed_fourth_word = paddle.layer.embedding(
+ input=fourth_word, size=embed_size, param_attr=embed_param_attr)
+
+ embed_context = paddle.layer.concat(input=[
+ embed_first_word, embed_second_word, embed_third_word, embed_fourth_word
+ ])
+
+ hidden_layer = paddle.layer.fc(
+ input=embed_context,
+ size=hidden_size,
+ act=paddle.activation.Sigmoid(),
+ layer_attr=paddle.attr.Extra(drop_rate=0.5),
+ bias_attr=paddle.attr.Param(learning_rate=2),
+ param_attr=paddle.attr.Param(
+ initial_std=1. / math.sqrt(embed_size * 8), learning_rate=1))
+
+ if is_train == True:
+ cost = paddle.layer.hsigmoid(
+ input=hidden_layer,
+ label=target_word,
+ num_classes=dict_size,
+ param_attr=paddle.attr.Param(name='sigmoid_w'),
+ bias_attr=paddle.attr.Param(name='sigmoid_b'))
+ return cost
+ else:
+ with paddle.layer.mixed(
+ size=dict_size - 1,
+ act=paddle.activation.Sigmoid(),
+ bias_attr=paddle.attr.Param(name='sigmoid_b')) as prediction:
+ prediction += paddle.layer.trans_full_matrix_projection(
+ input=hidden_layer,
+ param_attr=paddle.attr.Param(name='sigmoid_w'))
+ return prediction
+```
+
+需要注意,在预测阶段,我们需要对hsigmoid参数做一次转置,这里输出的类别数为词典大小减1,对应非叶节点的数量。
+
+## 训练阶段
+训练比较简单,直接运行``` python hsigmoid_train.py ```。程序第一次运行会检测用户缓存文件夹中是否包含imikolov数据集,如果未包含,则自动下载。运行过程中,每100个iteration会打印模型训练信息,主要包含训练损失和测试损失,每个pass会保存一次模型。
+
+## 预测阶段
+预测时,直接运行``` python hsigmoid_predict.py ```,程序会首先load模型,然后按照batch方式进行预测,并打印预测结果。预测阶段最重要的就是根据概率得到编码路径,然后遍历路径获取最终的预测类别,这部分逻辑如下:
+
+```python
+def decode_res(infer_res, dict_size):
+ """
+ Inferring probabilities are orginized as a complete binary tree.
+ The actual labels are leaves (indices are counted from class number).
+ This function travels paths decoded from inferring results.
+ If the probability >0.5 then go to right child, otherwise go to left child.
+
+ param infer_res: inferring result
+ param dict_size: class number
+ return predict_lbls: actual class
+ """
+ predict_lbls = []
+ infer_res = infer_res > 0.5
+ for i, probs in enumerate(infer_res):
+ idx = 0
+ result = 1
+ while idx < len(probs):
+ result <<= 1
+ if probs[idx]:
+ result |= 1
+ if probs[idx]:
+ idx = idx * 2 + 2 # right child
+ else:
+ idx = idx * 2 + 1 # left child
+
+ predict_lbl = result - dict_size
+ predict_lbls.append(predict_lbl)
+ return predict_lbls
+```
+
+预测程序的输入数据格式与训练阶段相同,如have a dream that one,程序会根据have a dream that生成一组概率,通过对概率解码生成预测词,one作为真实词,方便评估。解码函数的输入是一个batch样本的预测概率以及词表的大小,里面的循环是对每条样本的输出概率进行解码,解码方式就是按照左0右1的准则,不断遍历路径,直至到达叶子节点。需要注意的是,本文选用的数据集需要较长的时间训练才能得到较好的结果,预测程序选用第一轮的模型,仅为展示方便,学习效果不能保证。
+## 参考文献
+1. Morin, F., & Bengio, Y. (2005, January). [Hierarchical Probabilistic Neural Network Language Model](http://www.iro.umontreal.ca/~lisa/pointeurs/hierarchical-nnlm-aistats05.pdf). In Aistats (Vol. 5, pp. 246-252).
+
+
+
+
+
+