提交 13ff59e9 编写于 作者: C Cao Ying 提交者: GitHub

Merge pull request #387 from lcy-seso/fix_transpose_in_hsigmoid_nce

fix the bug that parameters are note transposed.
...@@ -57,7 +57,7 @@ def train_data(filename, word_dict, n): ...@@ -57,7 +57,7 @@ def train_data(filename, word_dict, n):
图2. 网络配置结构 图2. 网络配置结构
</p> </p>
代码实现如下: 代码如下:
```python ```python
def ngram_lm(hidden_size, embed_size, dict_size, gram_num=4, is_train=True): def ngram_lm(hidden_size, embed_size, dict_size, gram_num=4, is_train=True):
...@@ -86,31 +86,44 @@ def ngram_lm(hidden_size, embed_size, dict_size, gram_num=4, is_train=True): ...@@ -86,31 +86,44 @@ def ngram_lm(hidden_size, embed_size, dict_size, gram_num=4, is_train=True):
param_attr=paddle.attr.Param( param_attr=paddle.attr.Param(
initial_std=1. / math.sqrt(embed_size * 8), learning_rate=1)) initial_std=1. / math.sqrt(embed_size * 8), learning_rate=1))
if is_train == True: return paddle.layer.hsigmoid(
cost = paddle.layer.hsigmoid( input=hidden_layer,
input=hidden_layer, label=target_word,
label=target_word, param_attr=paddle.attr.Param(name="sigmoid_w"),
num_classes=dict_size, bias_attr=paddle.attr.Param(name="sigmoid_b"))
param_attr=paddle.attr.Param(name="sigmoid_w"),
bias_attr=paddle.attr.Param(name="sigmoid_b"))
return cost
else:
prediction = paddle.layer.fc(
size=dict_size - 1,
input=hidden_layer,
act=paddle.activation.Sigmoid(),
bias_attr=paddle.attr.Param(name="sigmoid_b"),
param_attr=paddle.attr.Param(name="sigmoid_w"))
return prediction
``` ```
需要注意,在预测阶段,我们需要对hsigmoid参数做一次转置,这里输出的类别数为词典大小减1,对应非叶节点的数量。 需要注意在 PaddlePaddle 中,hsigmoid 层将可学习参数存储为一个 `[类别数目 - 1 × 隐层向量宽度]` 大小的矩阵。预测时,需要将 hsigmoid 层替换为全连接运算**并固定以 `sigmoid` 为激活**。预测时输出一个宽度为`[batch_size x 类别数目 - 1]` 维度的矩阵(`batch_size = 1`时退化为一个向量)。矩阵行向量的每一维计算了一个输入向量属于一个内部结点的右孩子的概率。**全连接运算在加载 hsigmoid 层学习到的参数矩阵时,需要对参数矩阵进行一次转置**。代码片段如下:
```python
return paddle.layer.mixed(
size=dict_size - 1,
input=paddle.layer.trans_full_matrix_projection(
hidden_layer, param_attr=paddle.attr.Param(name="sigmoid_w")),
act=paddle.activation.Sigmoid(),
bias_attr=paddle.attr.Param(name="sigmoid_b"))
```
上述代码片段中的 `paddle.layer.mixed` 必须以 PaddlePaddle 中 `paddle.layer.×_projection` 为输入。`paddle.layer.mixed` 将多个 `projection` (输入可以是多个)计算结果求和作为输出。`paddle.layer.trans_full_matrix_projection` 在计算矩阵乘法时会对参数$W$进行转置。
## 训练阶段 ## 训练阶段
训练比较简单,直接运行``` python train.py ```。程序第一次运行会检测用户缓存文件夹中是否包含imikolov数据集,如果未包含,则自动下载。运行过程中,每100个iteration会打印模型训练信息,主要包含训练损失和测试损失,每个pass会保存一次模型。 训练比较简单,直接运行``` python train.py ```。程序第一次运行会检测用户缓存文件夹中是否包含imikolov数据集,如果未包含,则自动下载。运行过程中,每100个iteration会打印模型训练信息,主要包含训练损失和测试损失,每个pass会保存一次模型。
## 预测阶段 ## 预测阶段
预测时,直接运行``` python infer.py ```,程序会首先load模型,然后按照batch方式进行预测,并打印预测结果。预测阶段最重要的就是根据概率得到编码路径,然后遍历路径获取最终的预测类别,这部分逻辑如下: 在命令行运行 :
```bash
python infer.py \
--model_path "models/XX" \
--batch_size 1 \
--use_gpu false \
--trainer_count 1
```
参数含义如下:
- `model_path`:指定训练好的模型所在的路径。必选。
- `batch_size`:一次预测并行的样本数目。可选,默认值为 `1`
- `use_gpu`:是否使用 GPU 进行预测。可选,默认值为 `False`
- `trainer_count` : 预测使用的线程数目。可选,默认为 `1`**注意:预测使用的线程数目必选大于一次预测并行的样本数目**
预测阶段根据多个二分类概率得到编码路径,遍历路径获取最终的预测类别,逻辑如下:
```python ```python
def decode_res(infer_res, dict_size): def decode_res(infer_res, dict_size):
...@@ -143,6 +156,7 @@ def decode_res(infer_res, dict_size): ...@@ -143,6 +156,7 @@ def decode_res(infer_res, dict_size):
return predict_lbls return predict_lbls
``` ```
预测程序的输入数据格式与训练阶段相同,如have a dream that one,程序会根据have a dream that生成一组概率,通过对概率解码生成预测词,one作为真实词,方便评估。解码函数的输入是一个batch样本的预测概率以及词表的大小,里面的循环是对每条样本的输出概率进行解码,解码方式就是按照左0右1的准则,不断遍历路径,直至到达叶子节点。需要注意的是,本文选用的数据集需要较长的时间训练才能得到较好的结果,预测程序选用第一轮的模型,仅为展示方便,学习效果不能保证。 预测程序的输入数据格式与训练阶段相同,如`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). 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).
...@@ -99,7 +99,7 @@ def train_data(filename, word_dict, n): ...@@ -99,7 +99,7 @@ def train_data(filename, word_dict, n):
图2. 网络配置结构 图2. 网络配置结构
</p> </p>
代码实现如下: 代码如下:
```python ```python
def ngram_lm(hidden_size, embed_size, dict_size, gram_num=4, is_train=True): def ngram_lm(hidden_size, embed_size, dict_size, gram_num=4, is_train=True):
...@@ -128,31 +128,44 @@ def ngram_lm(hidden_size, embed_size, dict_size, gram_num=4, is_train=True): ...@@ -128,31 +128,44 @@ def ngram_lm(hidden_size, embed_size, dict_size, gram_num=4, is_train=True):
param_attr=paddle.attr.Param( param_attr=paddle.attr.Param(
initial_std=1. / math.sqrt(embed_size * 8), learning_rate=1)) initial_std=1. / math.sqrt(embed_size * 8), learning_rate=1))
if is_train == True: return paddle.layer.hsigmoid(
cost = paddle.layer.hsigmoid( input=hidden_layer,
input=hidden_layer, label=target_word,
label=target_word, param_attr=paddle.attr.Param(name="sigmoid_w"),
num_classes=dict_size, bias_attr=paddle.attr.Param(name="sigmoid_b"))
param_attr=paddle.attr.Param(name="sigmoid_w"),
bias_attr=paddle.attr.Param(name="sigmoid_b"))
return cost
else:
prediction = paddle.layer.fc(
size=dict_size - 1,
input=hidden_layer,
act=paddle.activation.Sigmoid(),
bias_attr=paddle.attr.Param(name="sigmoid_b"),
param_attr=paddle.attr.Param(name="sigmoid_w"))
return prediction
``` ```
需要注意,在预测阶段,我们需要对hsigmoid参数做一次转置,这里输出的类别数为词典大小减1,对应非叶节点的数量。 需要注意在 PaddlePaddle 中,hsigmoid 层将可学习参数存储为一个 `[类别数目 - 1 × 隐层向量宽度]` 大小的矩阵。预测时,需要将 hsigmoid 层替换为全连接运算**并固定以 `sigmoid` 为激活**。预测时输出一个宽度为`[batch_size x 类别数目 - 1]` 维度的矩阵(`batch_size = 1`时退化为一个向量)。矩阵行向量的每一维计算了一个输入向量属于一个内部结点的右孩子的概率。**全连接运算在加载 hsigmoid 层学习到的参数矩阵时,需要对参数矩阵进行一次转置**。代码片段如下:
```python
return paddle.layer.mixed(
size=dict_size - 1,
input=paddle.layer.trans_full_matrix_projection(
hidden_layer, param_attr=paddle.attr.Param(name="sigmoid_w")),
act=paddle.activation.Sigmoid(),
bias_attr=paddle.attr.Param(name="sigmoid_b"))
```
上述代码片段中的 `paddle.layer.mixed` 必须以 PaddlePaddle 中 `paddle.layer.×_projection` 为输入。`paddle.layer.mixed` 将多个 `projection` (输入可以是多个)计算结果求和作为输出。`paddle.layer.trans_full_matrix_projection` 在计算矩阵乘法时会对参数$W$进行转置。
## 训练阶段 ## 训练阶段
训练比较简单,直接运行``` python train.py ```。程序第一次运行会检测用户缓存文件夹中是否包含imikolov数据集,如果未包含,则自动下载。运行过程中,每100个iteration会打印模型训练信息,主要包含训练损失和测试损失,每个pass会保存一次模型。 训练比较简单,直接运行``` python train.py ```。程序第一次运行会检测用户缓存文件夹中是否包含imikolov数据集,如果未包含,则自动下载。运行过程中,每100个iteration会打印模型训练信息,主要包含训练损失和测试损失,每个pass会保存一次模型。
## 预测阶段 ## 预测阶段
预测时,直接运行``` python infer.py ```,程序会首先load模型,然后按照batch方式进行预测,并打印预测结果。预测阶段最重要的就是根据概率得到编码路径,然后遍历路径获取最终的预测类别,这部分逻辑如下: 在命令行运行 :
```bash
python infer.py \
--model_path "models/XX" \
--batch_size 1 \
--use_gpu false \
--trainer_count 1
```
参数含义如下:
- `model_path`:指定训练好的模型所在的路径。必选。
- `batch_size`:一次预测并行的样本数目。可选,默认值为 `1`。
- `use_gpu`:是否使用 GPU 进行预测。可选,默认值为 `False`。
- `trainer_count` : 预测使用的线程数目。可选,默认为 `1`。**注意:预测使用的线程数目必选大于一次预测并行的样本数目**。
预测阶段根据多个二分类概率得到编码路径,遍历路径获取最终的预测类别,逻辑如下:
```python ```python
def decode_res(infer_res, dict_size): def decode_res(infer_res, dict_size):
...@@ -185,7 +198,8 @@ def decode_res(infer_res, dict_size): ...@@ -185,7 +198,8 @@ def decode_res(infer_res, dict_size):
return predict_lbls return predict_lbls
``` ```
预测程序的输入数据格式与训练阶段相同如have a dream that one程序会根据have a dream that生成一组概率通过对概率解码生成预测词one作为真实词方便评估解码函数的输入是一个batch样本的预测概率以及词表的大小里面的循环是对每条样本的输出概率进行解码解码方式就是按照左0右1的准则不断遍历路径直至到达叶子节点需要注意的是本文选用的数据集需要较长的时间训练才能得到较好的结果预测程序选用第一轮的模型仅为展示方便学习效果不能保证 预测程序的输入数据格式与训练阶段相同`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). 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).
......
import os import os
import logging import logging
import gzip import gzip
import click
import paddle.v2 as paddle import paddle.v2 as paddle
from network_conf import ngram_lm from network_conf import ngram_lm
...@@ -51,10 +52,29 @@ def infer_a_batch(batch_ins, idx_word_dict, dict_size, inferer): ...@@ -51,10 +52,29 @@ def infer_a_batch(batch_ins, idx_word_dict, dict_size, inferer):
for w in ins]) + " -> " + predict_words[i]) for w in ins]) + " -> " + predict_words[i])
def infer(model_path, batch_size): @click.command("infer")
assert os.path.exists(model_path), "trained model does not exist." @click.option(
"--model_path",
paddle.init(use_gpu=False, trainer_count=1) default="",
help="The path of the trained model for generation.")
@click.option(
"--batch_size",
default=1,
help="The number of testing examples in one forward batch in inferring.")
@click.option(
"--use_gpu", default=False, help="Whether to use GPU in inference or not.")
@click.option(
"--trainer_count",
default=1,
help="Whether to use GPU in inference or not.")
def infer(model_path, batch_size, use_gpu, trainer_count):
assert os.path.exists(model_path), "The trained model does not exist."
assert (batch_size and trainer_count and batch_size >= trainer_count), (
"batch_size and trainer_count must both be greater than 0. "
"And batch_size must be equal to "
"or greater than trainer_count.")
paddle.init(use_gpu=use_gpu, trainer_count=trainer_count)
word_dict = paddle.dataset.imikolov.build_dict(min_word_freq=2) word_dict = paddle.dataset.imikolov.build_dict(min_word_freq=2)
dict_size = len(word_dict) dict_size = len(word_dict)
prediction_layer = ngram_lm( prediction_layer = ngram_lm(
...@@ -79,4 +99,4 @@ def infer(model_path, batch_size): ...@@ -79,4 +99,4 @@ def infer(model_path, batch_size):
if __name__ == "__main__": if __name__ == "__main__":
infer("models/hsigmoid_batch_00010.tar.gz", 20) infer()
import math import math
import paddle.v2 as paddle import paddle.v2 as paddle
from paddle.v2.layer import parse_network
def ngram_lm(hidden_size, embed_size, dict_size, gram_num=4, is_train=True): def ngram_lm(hidden_size, embed_size, dict_size, gram_num=4, is_train=True):
...@@ -30,18 +31,28 @@ def ngram_lm(hidden_size, embed_size, dict_size, gram_num=4, is_train=True): ...@@ -30,18 +31,28 @@ def ngram_lm(hidden_size, embed_size, dict_size, gram_num=4, is_train=True):
initial_std=1. / math.sqrt(embed_size * 8), learning_rate=1)) initial_std=1. / math.sqrt(embed_size * 8), learning_rate=1))
if is_train == True: if is_train == True:
cost = paddle.layer.hsigmoid( return paddle.layer.hsigmoid(
input=hidden_layer, input=hidden_layer,
label=target_word, label=target_word,
num_classes=dict_size, num_classes=dict_size,
param_attr=paddle.attr.Param(name="sigmoid_w"), param_attr=paddle.attr.Param(name="sigmoid_w"),
bias_attr=paddle.attr.Param(name="sigmoid_b")) bias_attr=paddle.attr.Param(name="sigmoid_b"))
return cost
else: else:
prediction = paddle.layer.fc( return paddle.layer.mixed(
size=dict_size - 1, size=dict_size - 1,
input=hidden_layer, input=paddle.layer.trans_full_matrix_projection(
hidden_layer, param_attr=paddle.attr.Param(name="sigmoid_w")),
act=paddle.activation.Sigmoid(), act=paddle.activation.Sigmoid(),
bias_attr=paddle.attr.Param(name="sigmoid_b"), bias_attr=paddle.attr.Param(name="sigmoid_b"))
param_attr=paddle.attr.Param(name="sigmoid_w"))
return prediction
if __name__ == "__main__":
# this is to test and debug the network topology defination.
# please set the hyper-parameters as needed.
print(parse_network(
ngram_lm(
hidden_size=512,
embed_size=512,
dict_size=1024,
gram_num=4,
is_train=False)))
...@@ -16,7 +16,13 @@ def main(save_dir="models"): ...@@ -16,7 +16,13 @@ def main(save_dir="models"):
paddle.init(use_gpu=False, trainer_count=1) paddle.init(use_gpu=False, trainer_count=1)
word_dict = paddle.dataset.imikolov.build_dict(min_word_freq=2) word_dict = paddle.dataset.imikolov.build_dict(min_word_freq=2)
dict_size = len(word_dict) dict_size = len(word_dict)
adam_optimizer = paddle.optimizer.Adam(
learning_rate=3e-3,
regularization=paddle.optimizer.L2Regularization(8e-4))
cost = ngram_lm(hidden_size=256, embed_size=32, dict_size=dict_size) cost = ngram_lm(hidden_size=256, embed_size=32, dict_size=dict_size)
parameters = paddle.parameters.create(cost)
def event_handler(event): def event_handler(event):
if isinstance(event, paddle.event.EndPass): if isinstance(event, paddle.event.EndPass):
...@@ -35,10 +41,6 @@ def main(save_dir="models"): ...@@ -35,10 +41,6 @@ def main(save_dir="models"):
"Pass %d, Batch %d, Cost %f, Test Cost %f" % "Pass %d, Batch %d, Cost %f, Test Cost %f" %
(event.pass_id, event.batch_id, event.cost, result.cost)) (event.pass_id, event.batch_id, event.cost, result.cost))
parameters = paddle.parameters.create(cost)
adam_optimizer = paddle.optimizer.Adam(
learning_rate=3e-3,
regularization=paddle.optimizer.L2Regularization(8e-4))
trainer = paddle.trainer.SGD(cost, parameters, adam_optimizer) trainer = paddle.trainer.SGD(cost, parameters, adam_optimizer)
trainer.train( trainer.train(
......
...@@ -17,7 +17,7 @@ J^h(\theta )=E_{ P_d^h }\left[ \log { P^h(D=1|w,\theta ) } \right] +kE_{ P_n }\ ...@@ -17,7 +17,7 @@ J^h(\theta )=E_{ P_d^h }\left[ \log { P^h(D=1|w,\theta ) } \right] +kE_{ P_n }\
$$ $$
\\\\\qquad =E_{ P_d^h }\left[ \log { \sigma (\Delta s_\theta(w,h)) } \right] +kE_{ P_n }\left[ \log (1-\sigma (\Delta s_\theta(w,h))) \right]$$ \\\\\qquad =E_{ P_d^h }\left[ \log { \sigma (\Delta s_\theta(w,h)) } \right] +kE_{ P_n }\left[ \log (1-\sigma (\Delta s_\theta(w,h))) \right]$$
总体上来说,NCE 是通过构造逻辑回归(logistic regression),对正样例和负样例做二分类,对于每一个样本,将自身的预测词 label 作为正样例,同时采样出 $k$ 个其他词 label 作为负样例,从而只需要计算样本在这 $k+1$ 个 label 上的概率。相比原始的 `softmax ` 分类需要计算每个类别的分数,然后归一化得到概率,节约了大量的计算时间。 简单来讲,NCE 是通过构造逻辑回归(logistic regression),对正样例和负样例做二分类,对于每一个样本,将自身的预测词 label 作为正样例,同时采样出 $k$ 个其他词 label 作为负样例,从而只需要计算样本在这 $k+1$ 个 label 上的概率。相比原始的 `softmax ` 分类需要计算每个类别的分数,然后归一化得到概率,节约了大量的计算时间。
## 实验数据 ## 实验数据
本文采用 Penn Treebank (PTB) 数据集([Tomas Mikolov预处理版本](http://www.fit.vutbr.cz/~imikolov/rnnlm/simple-examples.tgz))来训练语言模型。PaddlePaddle 提供 [paddle.dataset.imikolov](https://github.com/PaddlePaddle/Paddle/blob/develop/python/paddle/v2/dataset/imikolov.py) 接口来方便调用这些数据,如果当前目录没有找到数据它会自动下载并验证文件的完整性。并提供大小为5的滑动窗口对数据做预处理工作,方便后期处理。语料语种为英文,共有42068句训练数据,3761句测试数据。 本文采用 Penn Treebank (PTB) 数据集([Tomas Mikolov预处理版本](http://www.fit.vutbr.cz/~imikolov/rnnlm/simple-examples.tgz))来训练语言模型。PaddlePaddle 提供 [paddle.dataset.imikolov](https://github.com/PaddlePaddle/Paddle/blob/develop/python/paddle/v2/dataset/imikolov.py) 接口来方便调用这些数据,如果当前目录没有找到数据它会自动下载并验证文件的完整性。并提供大小为5的滑动窗口对数据做预处理工作,方便后期处理。语料语种为英文,共有42068句训练数据,3761句测试数据。
...@@ -52,7 +52,7 @@ N-gram 神经概率语言模型详细网络结构见图1: ...@@ -52,7 +52,7 @@ N-gram 神经概率语言模型详细网络结构见图1:
在模型文件`network_conf.py`中 NCE 调用代码如下: 在模型文件`network_conf.py`中 NCE 调用代码如下:
```python ```python
cost = paddle.layer.nce( return paddle.layer.nce(
input=hidden_layer, input=hidden_layer,
label=next_word, label=next_word,
num_classes=dict_size, num_classes=dict_size,
...@@ -73,22 +73,34 @@ NCE 层的一些重要参数解释如下: ...@@ -73,22 +73,34 @@ NCE 层的一些重要参数解释如下:
| act | 使用何种激活函数| 根据 NCE 的原理,这里应该使用 sigmoid 函数 | | act | 使用何种激活函数| 根据 NCE 的原理,这里应该使用 sigmoid 函数 |
## 预测 ## 预测
1. 首先修改 `infer.py` 脚本的 `main` 函数指定需要测试的模型。 1. 在命令行运行 :
2. 需要注意的是,**预测和训练的计算逻辑不同**,需要以一个全连接层:`paddle.layer.fc`替换训练使用的`paddle.train.nce`层, 并直接加载NCE学习到的参数,代码如下: ```bash
python infer.py \
```python --model_path "models/XX" \
prediction = paddle.layer.fc( --batch_size 1 \
size=dict_size, --use_gpu false \
act=paddle.activation.Softmax(), --trainer_count 1
bias_attr=paddle.attr.Param(name="nce_b"), ```
input=hidden_layer, 参数含义如下:
param_attr=paddle.attr.Param(name="nce_w")) - `model_path`:指定训练好的模型所在的路径。必选。
``` - `batch_size`:一次预测并行的样本数目。可选,默认值为 `1`
3. 运行 `python infer.py`。程序首先会加载指定的模型,然后按照 batch 大小依次进行预测,并打印预测结果。预测的输出格式如下: - `use_gpu`:是否使用 GPU 进行预测。可选,默认值为 `False`
- `trainer_count` : 预测使用的线程数目。可选,默认为 `1`**注意:预测使用的线程数目必选大于一次预测并行的样本数目**
2. 需要注意的是:**预测和训练的计算逻辑不同**。预测使用全连接矩阵乘法后接`softmax`激活,输出基于各类别的概率分布,需要替换训练中使用的`paddle.train.nce`层。在PaddlePaddle中,NCE层将可学习参数存储为一个 `[类别数目 × 上一层输出向量宽度]` 大小的矩阵,预测时,**全连接运算在加载NCE层学习到参数时,需要进行转置**,代码如下:
```python
return paddle.layer.mixed(
size=dict_size,
input=paddle.layer.trans_full_matrix_projection(
hidden_layer, param_attr=paddle.attr.Param(name="nce_w")),
act=paddle.activation.Sigmoid(),
bias_attr=paddle.attr.Param(name="nce_b"))
```
上述代码片段中的 `paddle.layer.mixed` 必须以 PaddlePaddle 中 `paddle.layer.×_projection` 为输入。`paddle.layer.mixed` 将多个 `projection` (输入可以是多个)计算结果求和作为输出。`paddle.layer.trans_full_matrix_projection` 在计算矩阵乘法时会对参数$W$进行转置。
3. 预测的输出格式如下:
```text ```text
0.6734 their may want to move 0.6734 their may want to move
``` ```
每一行是一条预测结果,内部以“\t”分隔,共计3列: 每一行是一条预测结果,内部以“\t”分隔,共计3列:
......
...@@ -59,7 +59,7 @@ J^h(\theta )=E_{ P_d^h }\left[ \log { P^h(D=1|w,\theta ) } \right] +kE_{ P_n }\ ...@@ -59,7 +59,7 @@ J^h(\theta )=E_{ P_d^h }\left[ \log { P^h(D=1|w,\theta ) } \right] +kE_{ P_n }\
$$ $$
\\\\\qquad =E_{ P_d^h }\left[ \log { \sigma (\Delta s_\theta(w,h)) } \right] +kE_{ P_n }\left[ \log (1-\sigma (\Delta s_\theta(w,h))) \right]$$ \\\\\qquad =E_{ P_d^h }\left[ \log { \sigma (\Delta s_\theta(w,h)) } \right] +kE_{ P_n }\left[ \log (1-\sigma (\Delta s_\theta(w,h))) \right]$$
总体上来说,NCE 是通过构造逻辑回归(logistic regression),对正样例和负样例做二分类,对于每一个样本,将自身的预测词 label 作为正样例,同时采样出 $k$ 个其他词 label 作为负样例,从而只需要计算样本在这 $k+1$ 个 label 上的概率。相比原始的 `softmax ` 分类需要计算每个类别的分数,然后归一化得到概率,节约了大量的计算时间。 简单来讲,NCE 是通过构造逻辑回归(logistic regression),对正样例和负样例做二分类,对于每一个样本,将自身的预测词 label 作为正样例,同时采样出 $k$ 个其他词 label 作为负样例,从而只需要计算样本在这 $k+1$ 个 label 上的概率。相比原始的 `softmax ` 分类需要计算每个类别的分数,然后归一化得到概率,节约了大量的计算时间。
## 实验数据 ## 实验数据
本文采用 Penn Treebank (PTB) 数据集([Tomas Mikolov预处理版本](http://www.fit.vutbr.cz/~imikolov/rnnlm/simple-examples.tgz))来训练语言模型。PaddlePaddle 提供 [paddle.dataset.imikolov](https://github.com/PaddlePaddle/Paddle/blob/develop/python/paddle/v2/dataset/imikolov.py) 接口来方便调用这些数据,如果当前目录没有找到数据它会自动下载并验证文件的完整性。并提供大小为5的滑动窗口对数据做预处理工作,方便后期处理。语料语种为英文,共有42068句训练数据,3761句测试数据。 本文采用 Penn Treebank (PTB) 数据集([Tomas Mikolov预处理版本](http://www.fit.vutbr.cz/~imikolov/rnnlm/simple-examples.tgz))来训练语言模型。PaddlePaddle 提供 [paddle.dataset.imikolov](https://github.com/PaddlePaddle/Paddle/blob/develop/python/paddle/v2/dataset/imikolov.py) 接口来方便调用这些数据,如果当前目录没有找到数据它会自动下载并验证文件的完整性。并提供大小为5的滑动窗口对数据做预处理工作,方便后期处理。语料语种为英文,共有42068句训练数据,3761句测试数据。
...@@ -94,7 +94,7 @@ N-gram 神经概率语言模型详细网络结构见图1: ...@@ -94,7 +94,7 @@ N-gram 神经概率语言模型详细网络结构见图1:
在模型文件`network_conf.py`中 NCE 调用代码如下: 在模型文件`network_conf.py`中 NCE 调用代码如下:
```python ```python
cost = paddle.layer.nce( return paddle.layer.nce(
input=hidden_layer, input=hidden_layer,
label=next_word, label=next_word,
num_classes=dict_size, num_classes=dict_size,
...@@ -115,22 +115,34 @@ NCE 层的一些重要参数解释如下: ...@@ -115,22 +115,34 @@ NCE 层的一些重要参数解释如下:
| act | 使用何种激活函数| 根据 NCE 的原理,这里应该使用 sigmoid 函数 | | act | 使用何种激活函数| 根据 NCE 的原理,这里应该使用 sigmoid 函数 |
## 预测 ## 预测
1. 首先修改 `infer.py` 脚本的 `main` 函数指定需要测试的模型。 1. 在命令行运行 :
2. 需要注意的是,**预测和训练的计算逻辑不同**,需要以一个全连接层:`paddle.layer.fc`替换训练使用的`paddle.train.nce`层, 并直接加载NCE学习到的参数,代码如下: ```bash
python infer.py \
```python --model_path "models/XX" \
prediction = paddle.layer.fc( --batch_size 1 \
size=dict_size, --use_gpu false \
act=paddle.activation.Softmax(), --trainer_count 1
bias_attr=paddle.attr.Param(name="nce_b"), ```
input=hidden_layer, 参数含义如下:
param_attr=paddle.attr.Param(name="nce_w")) - `model_path`:指定训练好的模型所在的路径。必选。
``` - `batch_size`:一次预测并行的样本数目。可选,默认值为 `1`。
3. 运行 `python infer.py`。程序首先会加载指定的模型,然后按照 batch 大小依次进行预测,并打印预测结果。预测的输出格式如下: - `use_gpu`:是否使用 GPU 进行预测。可选,默认值为 `False`。
- `trainer_count` : 预测使用的线程数目。可选,默认为 `1`。**注意:预测使用的线程数目必选大于一次预测并行的样本数目**。
2. 需要注意的是:**预测和训练的计算逻辑不同**。预测使用全连接矩阵乘法后接`softmax`激活,输出基于各类别的概率分布,需要替换训练中使用的`paddle.train.nce`层。在PaddlePaddle中,NCE层将可学习参数存储为一个 `[类别数目 × 上一层输出向量宽度]` 大小的矩阵,预测时,**全连接运算在加载NCE层学习到参数时,需要进行转置**,代码如下:
```python
return paddle.layer.mixed(
size=dict_size,
input=paddle.layer.trans_full_matrix_projection(
hidden_layer, param_attr=paddle.attr.Param(name="nce_w")),
act=paddle.activation.Sigmoid(),
bias_attr=paddle.attr.Param(name="nce_b"))
```
上述代码片段中的 `paddle.layer.mixed` 必须以 PaddlePaddle 中 `paddle.layer.×_projection` 为输入。`paddle.layer.mixed` 将多个 `projection` (输入可以是多个)计算结果求和作为输出。`paddle.layer.trans_full_matrix_projection` 在计算矩阵乘法时会对参数$W$进行转置。
3. 预测的输出格式如下:
```text ```text
0.6734 their may want to move 0.6734 their may want to move
``` ```
每一行是一条预测结果,内部以“\t”分隔,共计3列: 每一行是一条预测结果,内部以“\t”分隔,共计3列:
......
import os import os
import gzip import gzip
import click
import numpy as np import numpy as np
import paddle.v2 as paddle import paddle.v2 as paddle
...@@ -14,13 +15,33 @@ def infer_a_batch(inferer, test_batch, id_to_word): ...@@ -14,13 +15,33 @@ def infer_a_batch(inferer, test_batch, id_to_word):
" ".join([id_to_word[w] for w in res[0]]))) " ".join([id_to_word[w] for w in res[0]])))
def infer(model_path, batch_size): @click.command("infer")
assert os.path.exists(model_path), "the trained model does not exist." @click.option(
"--model_path",
default="",
help="The path of the trained model for generation.")
@click.option(
"--batch_size",
default=1,
help="The number of testing examples in one forward batch in inferring.")
@click.option(
"--use_gpu", default=False, help="Whether to use GPU in inference or not.")
@click.option(
"--trainer_count",
default=1,
help="Whether to use GPU in inference or not.")
def infer(model_path, batch_size, use_gpu, trainer_count):
assert os.path.exists(model_path), "The trained model does not exist."
assert (batch_size and trainer_count and batch_size >= trainer_count), (
"batch_size and trainer_count must both be greater than 0. "
"And batch_size must be equal to "
"or greater than trainer_count.")
word_to_id = paddle.dataset.imikolov.build_dict() word_to_id = paddle.dataset.imikolov.build_dict()
id_to_word = dict((v, k) for k, v in word_to_id.items()) id_to_word = dict((v, k) for k, v in word_to_id.items())
dict_size = len(word_to_id) dict_size = len(word_to_id)
paddle.init(use_gpu=False, trainer_count=1) paddle.init(use_gpu=use_gpu, trainer_count=trainer_count)
# load the trained model. # load the trained model.
with gzip.open(model_path) as f: with gzip.open(model_path) as f:
...@@ -44,4 +65,4 @@ def infer(model_path, batch_size): ...@@ -44,4 +65,4 @@ def infer(model_path, batch_size):
if __name__ == "__main__": if __name__ == "__main__":
infer("models/model_pass_00000_00020.tar.gz", 10) infer()
import math import math
import paddle.v2 as paddle import paddle.v2 as paddle
from paddle.v2.layer import parse_network
def ngram_lm(hidden_size, emb_size, dict_size, gram_num=4, is_train=True): def ngram_lm(hidden_size, emb_size, dict_size, gram_num=4, is_train=True):
...@@ -13,7 +15,6 @@ def ngram_lm(hidden_size, emb_size, dict_size, gram_num=4, is_train=True): ...@@ -13,7 +15,6 @@ def ngram_lm(hidden_size, emb_size, dict_size, gram_num=4, is_train=True):
emb_layers.append( emb_layers.append(
paddle.layer.embedding( paddle.layer.embedding(
input=word, size=emb_size, param_attr=embed_param_attr)) input=word, size=emb_size, param_attr=embed_param_attr))
next_word = paddle.layer.data( next_word = paddle.layer.data(
name="__target_word__", type=paddle.data_type.integer_value(dict_size)) name="__target_word__", type=paddle.data_type.integer_value(dict_size))
...@@ -26,7 +27,7 @@ def ngram_lm(hidden_size, emb_size, dict_size, gram_num=4, is_train=True): ...@@ -26,7 +27,7 @@ def ngram_lm(hidden_size, emb_size, dict_size, gram_num=4, is_train=True):
param_attr=paddle.attr.Param(initial_std=1. / math.sqrt(emb_size * 8))) param_attr=paddle.attr.Param(initial_std=1. / math.sqrt(emb_size * 8)))
if is_train: if is_train:
cost = paddle.layer.nce( return paddle.layer.nce(
input=hidden_layer, input=hidden_layer,
label=next_word, label=next_word,
num_classes=dict_size, num_classes=dict_size,
...@@ -35,13 +36,22 @@ def ngram_lm(hidden_size, emb_size, dict_size, gram_num=4, is_train=True): ...@@ -35,13 +36,22 @@ def ngram_lm(hidden_size, emb_size, dict_size, gram_num=4, is_train=True):
act=paddle.activation.Sigmoid(), act=paddle.activation.Sigmoid(),
num_neg_samples=25, num_neg_samples=25,
neg_distribution=None) neg_distribution=None)
return cost
else: else:
prediction = paddle.layer.fc( return paddle.layer.mixed(
size=dict_size, size=dict_size,
act=paddle.activation.Softmax(), input=paddle.layer.trans_full_matrix_projection(
bias_attr=paddle.attr.Param(name="nce_b"), hidden_layer, param_attr=paddle.attr.Param(name="nce_w")),
input=hidden_layer, act=paddle.activation.Sigmoid(),
param_attr=paddle.attr.Param(name="nce_w")) bias_attr=paddle.attr.Param(name="nce_b"))
return prediction if __name__ == "__main__":
# this is to test and debug the network topology defination.
# please set the hyper-parameters as needed.
print(parse_network(
ngram_lm(
hidden_size=256,
emb_size=256,
dict_size=1024,
gram_num=4,
is_train=True)))
...@@ -28,6 +28,11 @@ def train(model_save_dir): ...@@ -28,6 +28,11 @@ def train(model_save_dir):
if event.batch_id and not event.batch_id % 10: if event.batch_id and not event.batch_id % 10:
logger.info("Pass %d, Batch %d, Cost %f" % logger.info("Pass %d, Batch %d, Cost %f" %
(event.pass_id, event.batch_id, event.cost)) (event.pass_id, event.batch_id, event.cost))
save_path = os.path.join(model_save_dir,
"model_pass_%05d.tar.gz" % event.pass_id)
logger.info("Save model into %s ..." % save_path)
with gzip.open(save_path, "w") as f:
parameters.to_tar(f)
if isinstance(event, paddle.event.EndPass): if isinstance(event, paddle.event.EndPass):
result = trainer.test( result = trainer.test(
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册