提交 94005393 编写于 作者: S Superjom

Merge branch 'develop' of https://github.com/PaddlePaddle/models into develop

# models简介 # models 简介
[![Documentation Status](https://img.shields.io/badge/docs-latest-brightgreen.svg?style=flat)](https://github.com/PaddlePaddle/models) [![Documentation Status](https://img.shields.io/badge/docs-latest-brightgreen.svg?style=flat)](https://github.com/PaddlePaddle/models)
[![Documentation Status](https://img.shields.io/badge/中文文档-最新-brightgreen.svg)](https://github.com/PaddlePaddle/models) [![Documentation Status](https://img.shields.io/badge/中文文档-最新-brightgreen.svg)](https://github.com/PaddlePaddle/models)
...@@ -6,81 +6,110 @@ ...@@ -6,81 +6,110 @@
PaddlePaddle提供了丰富的运算单元,帮助大家以模块化的方式构建起千变万化的深度学习模型来解决不同的应用问题。这里,我们针对常见的机器学习任务,提供了不同的神经网络模型供大家学习和使用。 PaddlePaddle提供了丰富的运算单元,帮助大家以模块化的方式构建起千变万化的深度学习模型来解决不同的应用问题。这里,我们针对常见的机器学习任务,提供了不同的神经网络模型供大家学习和使用。
## 词向量 ## [词向量](https://github.com/PaddlePaddle/models/tree/develop/word_embedding)
- **介绍** - **介绍**
[词向量](https://github.com/PaddlePaddle/book/blob/develop/04.word2vec/README.cn.md) 是深度学习应用于自然语言处理领域最成功的概念和成果之一,是一种分散式表示(distributed representation)法。分散式表示法用一个更低维度的实向量表示词语,向量的每个维度在实数域取值,都表示文本的某种潜在语法或语义特征。广义地讲,词向量也可以应用于普通离散特征。词向量的学习通常都是一个无监督的学习过程,因此,可以充分利用海量的无标记数据以捕获特征之间的关系,也可以有效地解决特征稀疏、标签数据缺失、数据噪声等问题。 [词向量](https://github.com/PaddlePaddle/book/blob/develop/04.word2vec/README.cn.md) 是深度学习应用于自然语言处理领域最成功的概念和成果之一,是一种分散式表示(distributed representation)法。分散式表示法用一个更低维度的实向量表示词语,向量的每个维度在实数域取值,都表示文本的某种潜在语法或语义特征。广义地讲,词向量也可以应用于普通离散特征。词向量的学习通常都是一个无监督的学习过程,因此,可以充分利用海量的无标记数据以捕获特征之间的关系,也可以有效地解决特征稀疏、标签数据缺失、数据噪声等问题。
然而,在常见词向量学习方法中,模型最后一层往往会遇到一个超大规模的分类问题,是计算性能的瓶颈。在词向量的例子中,我们向大家展示如何使用Hierarchical-Sigmoid 和噪声对比估计(Noise Contrastive Estimation,NCE)来加速词向量的学习。 然而,在常见词向量学习方法中,模型最后一层往往会遇到一个超大规模的分类问题,是计算性能的瓶颈。在词向量的例子中,我们向大家展示如何使用Hierarchical-Sigmoid 和噪声对比估计(Noise Contrastive Estimation,NCE)来加速词向量的学习。
- **应用领域** - **应用领域**
词向量是深度学习方法引入自然语言处理领域的核心技术之一,在大规模无标记语料上训练的词向量常作为各种自然语言处理任务的预训练参数,是一种较为通用的资源,对任务性能的进一步提升有一定的帮助。同时,词嵌入的思想也是深度学习模型处理离散特征的重要方法,有着广泛地借鉴和参考意义。 词向量是深度学习方法引入自然语言处理领域的核心技术之一,在大规模无标记语料上训练的词向量常作为各种自然语言处理任务的预训练参数,是一种较为通用的资源,对任务性能的进一步提升有一定的帮助。同时,词嵌入的思想也是深度学习模型处理离散特征的重要方法,有着广泛地借鉴和参考意义。
词向量是搜索引擎、广告系统、推荐系统等互联网服务背后的常见基础技术之一。 词向量是搜索引擎、广告系统、推荐系统等互联网服务背后的常见基础技术之一。
- **模型配置说明** - **模型配置**
1. [Hsigmoid加速词向量训练](https://github.com/PaddlePaddle/models/blob/develop/word_embedding/hsigmoid_train.py)
2. [噪声对比估计加速词向量训练]()
[word_embedding](https://github.com/PaddlePaddle/models/tree/develop/word_embedding) ## [文本分类](https://github.com/PaddlePaddle/models/tree/develop/text_classification)
## 文本生成
- **介绍** - **介绍**
我们期待有一天机器可以使用自然语言与人们进行交流,像人一样能够撰写高质量的自然语言文本,自动文本生成是实现这一目标的关键技术,可以应用于机器翻译系统、对话系统、问答系统等,为人们带来更加有趣地交互体验,也可以自动撰写新闻摘要,撰写歌词,简单的故事等等。或许未来的某一天,机器能够代替编辑,作家,歌词作者,颠覆这些内容创作领域的工作方式。 文本分类是自然语言处理领域最基础的任务之一,深度学习方法能够免除复杂的特征工程,直接使用原始文本作为输入,数据驱动地最优化分类准确率。我们以情感分类任务为例,提供了基于DNN的非序列文本分类模型,基于CNN和LSTM的序列模型供大家学习和使用。
- **应用领域**
分类是机器学习基础任务之一。文本分类模型在SPAM检测,文本打标签,文本意图识别,文章质量评估,色情暴力文章识别,评论情绪识别,广告物料风险控制等领域都有着广泛的应用。
- **模型配置**
1. [基于 DNN 的文本分类](https://github.com/PaddlePaddle/models/blob/develop/text_classification/text_classification_dnn.py)
2. [基于 CNN 的文本分类](https://github.com/PaddlePaddle/models/blob/develop/text_classification/text_classification_cnn.py)
3. [基于 LSTM 的文本分类](https://github.com/PaddlePaddle/book/blob/develop/06.understand_sentiment/train.py)
基于神经网络生成文本常使用两类方法:1. 语言模型;2. 序列到序列(sequence to sequence)映射模型。在文本生成的例子中,我们为大家展示如何使用以上两种模型来自动生成文本。 ## [序列标注](https://github.com/PaddlePaddle/models/tree/develop/sequence_tagging_for_ner)
特别的,对序列到序列映射模型,我们以机器翻译任务为例提供了多种改进模型,供大家学习和使用,包括: - **介绍**
1. 不带注意力机制的序列到序列映射模型,这一模型是所有序列到序列学习模型的基础。
2. 带注意力机制使用 scheduled sampling 改善生成质量,用来改善RNN模型在文本生成过程中的错误累积问题。 序列标注是自然语言处理中最常见的问题之一。在这一任务中,给定输入序列,模型为序列中每一个元素贴上一个类别标签。随着深度学习的不断探索和发展,利用循环神经网络模型学习输入序列的特征表示,条件随机场(Conditional Random Field, CRF)在特征基础上完成序列标注任务,逐渐成为解决序列标注问题的标配解决方案。深度学习的巨大优势在于:从原始文本中学习,避免复杂的特征工程,只要构建好这样一套深度学习模型,绝大多数序列标注问题都可以直接套用,只需要相应地替换不同问题对应的训练数据。
3. 带外部记忆机制的神经机器翻译,通过增强神经网络的记忆能力,来完成复杂的序列到序列学习任务。
这里,我们以命名实体识别(Named Entity Recognition,NER)任务为例,向大家介绍如何使用 PaddlePaddle 训练一个端到端(End-to-End)的序列标注模型。
- **应用领域** - **应用领域**
文本生成模型实现了两个甚至是多个不定长模型之间的映射,有着广泛地应用,包括机器翻译、智能对话与问答、广告创意语料生成、自动编码(如金融画像编码)、判断多个文本串之间的语义相关性等 序列标注是自然语言处理领域最重要的基础任务之一,有着广泛地应用:自动分词,语言角色标注,命名实体识别,深度问答(Deep QA),关键词提取等问题都可以转化为序列标注问题直接套用本例中的模型
- **模型配置说明** - **模型配置**
[seq2seq](https://github.com/PaddlePaddle/models/tree/develop/seq2seq) | [scheduled_sampling](https://github.com/PaddlePaddle/models/tree/develop/scheduled_sampling) | [external_memory](https://github.com/PaddlePaddle/models/tree/develop/mt_with_external_memory) 1. [命名实体识别](https://github.com/PaddlePaddle/models/blob/develop/sequence_tagging_for_ner/ner.py)
## 排序学习(Learning to Rank, LTR) ## [排序学习](https://github.com/PaddlePaddle/models/tree/develop/ltr)
- **介绍** - **介绍**
排序学习(Learning to Rank,下简称LTR)是信息检索和搜索引擎研究的核心问题之一,通过机器学习方法学习一个分值函数(Scoring Function)对待排序的候选进行打分,再根据分值的高低确定序关系。深度神经网络可以用来建模分值函数,构成各类基于深度学习的LTR模型。 排序学习(Learning to Rank,下简称LTR)是信息检索和搜索引擎研究的核心问题之一,通过机器学习方法学习一个分值函数(Scoring Function)对待排序的候选进行打分,再根据分值的高低确定序关系。深度神经网络可以用来建模分值函数,构成各类基于深度学习的LTR模型。
以信息检索任务为例,给定查询以及检索到的候选文档列表,LTR系统需要按照查询与候选文档的相关性,对候选文档进行打分并排序。LTR学习方法可以分为三种: 以信息检索任务为例,给定查询以及检索到的候选文档列表,LTR系统需要按照查询与候选文档的相关性,对候选文档进行打分并排序。流行的LTR学习方法分为以下三种:
- Pointwise:Pointwise 学习方法将LTR被转化为回归或是分类问题。给定查询以及一个候选文档,模型基于序数进行二分类、多分类或者回归拟合,是一种基础的LTR学习策略。 - [Pointwise](https://github.com/PaddlePaddle/book/blob/develop/05.recommender_system/README.cn.md)
- Pairwise:Pairwise学习方法将排序问题归约为对有序对(ordered pair)的分类,比Pointwise方法更近了一步。模型判断一对候选文档中,哪一个与给定查询更相关,学习的目标为是最小化误分类文档对的数量。理想情况下,如果所有文档对都能被正确的分类,那么原始的候选文档也会被正确的排序。 - Pointwise 学习方法将LTR被转化为回归或是分类问题。
- Listwise:与Pointwise与Pairwise学习方法相比,Listwise方法将给定查询对应的整个候选文档集合列表(list)作为输入,直接对排序结果列表进行优化。Listwise方法在损失函数中考虑了文档排序的位置因素,是前两种方法所不具备的。 - 给定查询以及一个候选文档,模型基于序数进行二分类、多分类或者回归拟合,是一种基础的LTR学习策略。
- Pairwise
- Pairwise学习方法将排序问题归约为对有序对(ordered pair)的分类,比Pointwise方法更近了一步。
- 模型判断一对候选文档中,哪一个与给定查询更相关,学习的目标为是最小化误分类文档对的数量。
- 理想情况下,如果所有文档对都能被正确的分类,那么原始的候选文档也会被正确的排序。
- Listwise
- 与Pointwise与Pairwise 学习方法相比,Listwise方法将给定查询对应的整个候选文档集合列表(list)作为输入,直接对排序结果列表进行优化。Listwise方法在损失函数中考虑了文档排序的位置因素,是前两种方法所不具备的。
Pointwise 学习策略可参考PaddleBook的[推荐系统](https://github.com/PaddlePaddle/book/blob/develop/05.recommender_system/README.cn.md)一节。在这里,我们提供了基于RankLoss 损失函数的Pairwise 排序模型,以及基于LambdaRank 损失函数的ListWise排序模型。 Pointwise 学习策略是PaddleBook中[推荐系统](https://github.com/PaddlePaddle/book/blob/develop/05.recommender_system/README.cn.md)一节介绍过的方法。这里,我们一进步提供了基于 RankLoss 损失函数的 Pairwise 排序模型和基于LambdaRank损失函数的Listwise排序模型。
- **应用领域** - **应用领域**
LTR模型在搜索排序,包括:图片搜索排序、外卖美食搜索排序、App搜索排序、酒店搜索排序等场景中有着广泛的应用。还可以扩展应用于:关键词推荐、各类业务榜单、个性化推荐等任务。 LTR模型在搜索排序,包括:图片搜索排序、外卖美食搜索排序、App搜索排序、酒店搜索排序等场景中有着广泛的应用,还可以扩展应用于:关键词推荐、各类业务榜单、个性化推荐等任务。
- **模型配置说明** - **模型配置说明**
[Pointwise 排序模型](https://github.com/PaddlePaddle/book/blob/develop/05.recommender_system/README.cn.md) 1. [Pointwise 排序模型](https://github.com/PaddlePaddle/book/blob/develop/05.recommender_system/train.py)
| [Pairwise 排序模型](https://github.com/PaddlePaddle/models/tree/develop/ltr) | [Listwise 排序模型](https://github.com/PaddlePaddle/models/tree/develop/ltr) 2. [Pairwise 排序模型](https://github.com/PaddlePaddle/models/blob/develop/ltr/ranknet.py)
3. [Listwise 排序模型](https://github.com/PaddlePaddle/models/blob/develop/ltr/lambda_rank.py)
## 文本分类 ## [文本生成](https://github.com/PaddlePaddle/models/tree/develop/nmt_without_attention)
- **介绍** - **介绍**
文本分类是自然语言处理领域最基础的任务之一,深度学习方法能够免除复杂的特征工程,直接使用原始文本作为输入,数据驱动地最优化分类准确率。我们以情感分类任务为例,提供了基于DNN的非序列文本分类模型,基于CNN和LSTM的序列模型供大家学习和使用。 我们期待有一天机器可以使用自然语言与人们进行交流,像人一样能够撰写高质量的自然语言文本,自动文本生成是实现这一目标的关键技术,可以应用于机器翻译系统、对话系统、问答系统等,为人们带来更加有趣地交互体验,也可以自动撰写新闻摘要,撰写歌词,简单的故事等等。或许未来的某一天,机器能够代替编辑,作家,歌词作者,颠覆这些内容创作领域的工作方式。
基于神经网络生成文本常使用两类方法:1. 语言模型;2. 序列到序列(sequence to sequence)映射模型。在文本生成的例子中,我们为大家展示如何使用以上两种模型来自动生成文本。
特别的,对序列到序列映射模型,我们以机器翻译任务为例提供了多种改进模型,供大家学习和使用,包括:
1. 不带注意力机制的序列到序列映射模型,这一模型是所有序列到序列学习模型的基础。
2. 带注意力机制使用 scheduled sampling 改善生成质量,用来改善RNN模型在文本生成过程中的错误累积问题。
3. 带外部记忆机制的神经机器翻译,通过增强神经网络的记忆能力,来完成复杂的序列到序列学习任务。
- **应用领域** - **应用领域**
分类是机器学习基础任务之一。文本分类模型在SPAM检测,文本打标签,文本意图识别,文章质量评估,色情暴力文章识别,评论情绪识别,广告物料风险控制等领域都有着广泛的应用 文本生成模型实现了两个甚至是多个不定长模型之间的映射,有着广泛地应用,包括机器翻译、智能对话与问答、广告创意语料生成、自动编码(如金融画像编码)、判断多个文本串之间的语义相关性等
- **模型配置说明** - **模型配置**
[text_classification](https://github.com/PaddlePaddle/models/tree/develop/text_classification) 1. [无注意力机制的编码器解码器模型](https://github.com/PaddlePaddle/models/blob/develop/nmt_without_attention/nmt_without_attention.py)
2. [使用 scheduled sampling 改善生成质量]()
3. [ 带外部记忆机制的神经机器翻译]()
## Copyright and License ## Copyright and License
PaddlePaddle is provided under the [Apache-2.0 license](LICENSE). PaddlePaddle is provided under the [Apache-2.0 license](LICENSE).
## 排序学习(LearningToRank) # 排序学习(Learning To Rank)
排序学习技术\[[1](#参考文献1)\]是构建排序模型的机器学习方法,在信息检索自然语言处理,数据挖掘等机器学场景中具有重要作用。排序学习的主要目的是对给定一组文档,对任意查询请求给出反映相关性的文档排序。在本例子中,利用标注过的语料库训练两种经典排序模型RankNet[[4](#参考文献4)\]和LamdaRank[[6](#参考文献6)\],分别可以生成对应的排序模型,能够对任意查询请求,给出相关性文档排序。 排序学习技术\[[1](#参考文献1)\]是构建排序模型的机器学习方法,在信息检索自然语言处理,数据挖掘等机器学场景中具有重要作用。排序学习的主要目的是对给定一组文档,对任意查询请求给出反映相关性的文档排序。在本例子中,利用标注过的语料库训练两种经典排序模型RankNet[[4](#参考文献4)\]和LamdaRank[[6](#参考文献6)\],分别可以生成对应的排序模型,能够对任意查询请求,给出相关性文档排序。
RankNet模型在命令行输入: RankNet模型在命令行输入:
...@@ -11,23 +11,21 @@ python ranknet.py ...@@ -11,23 +11,21 @@ python ranknet.py
LambdaRank模型在命令行输入: LambdaRank模型在命令行输入:
```python ```python
python lambdaRank.py python lambda_rank.py
``` ```
用户只需要使用以上命令就完成排序模型的训练和预测,程序会自动下载内置数据集,无需手动下载。 用户只需要使用以上命令就完成排序模型的训练和预测,程序会自动下载内置数据集,无需手动下载。
## 背景介绍 ## 背景介绍
排序学习技术随着互联网的快速增长而受到越来越多关注,是机器学习中的常见任务之一。一方面人工排序规则不能处理海量规模的候选数据,另一方面无法为不同渠道的候选数据给于合适的权重,因此排序学习在日常生活中应用非常广泛。排序学习起源于信息检索领域,目前仍然是许多信息检索场景中的核心模块,例如搜索引擎搜索结果排序,推荐系统候选集排序,在线广告排序等等。在本例子中,采用文档检索阐述排序学习模型。 排序学习技术随着互联网的快速增长而受到越来越多关注,是机器学习中的常见任务之一。一方面人工排序规则不能处理海量规模的候选数据,另一方面无法为不同渠道的候选数据给于合适的权重,因此排序学习在日常生活中应用非常广泛。排序学习起源于信息检索领域,目前仍然是许多信息检索场景中的核心模块,例如搜索引擎搜索结果排序,推荐系统候选集排序,在线广告排序等等。本例以文档检索任务阐述排序学习模型。
<p align="center"> <p align="center">
<img src="image/search-engine-example.png" width="30%" ><br/> <img src="images/search_engine_example.png" width="30%" ><br/>
图1. 排序模型在文档检索的典型应用搜索引擎中的作用 图1. 排序模型在文档检索的典型应用搜索引擎中的作用
</p> </p>
假定有一组文档S,文档检索任务是依据和请求的相关性,给出文档排列顺序。查询引擎根据查询请求,排序模型会给每个文档打出分数,依据打分情况倒序排列文档,得到查询结果。在训练模型时,给定一条查询,并给出对应的文档最佳排序和得分。在预测时候,给出查询请求,排序模型生成文档排序。常见的排序学习方法划分为以下三类: 假定有一组文档$S$,文档检索任务是依据和请求的相关性,给出文档排列顺序。查询引擎根据查询请求,排序模型会给每个文档打出分数,依据打分情况倒序排列文档,得到查询结果。在训练模型时,给定一条查询,并给出对应的文档最佳排序和得分。在预测时候,给出查询请求,排序模型生成文档排序。常见的排序学习方法划分为以下三类:
- Pointwise 方法 - Pointwise 方法
...@@ -35,24 +33,22 @@ python lambdaRank.py ...@@ -35,24 +33,22 @@ python lambdaRank.py
- Pairwise方法 - Pairwise方法
Pairwise方法是通过近似为分类问题解决排序问题,输入的单条样本为**标签-文档对**。对于一次查询的多个结果文档,组合任意两个文档形成文档对作为输入样本。即学习一个二分类器,对输入的一对文档对AB(Pairwise的由来),根据A相关性是否比B好,二分类器给出分类标签1或0。对所有文档对进行分类,就可以得到一组偏序关系,从而构造文档全集的排序关系。该类方法的原理是对给定的文档全集S,降低排序中的逆序文档对的个数来降低排序错误,从而达到优化排序结果的目的。 Pairwise方法是通过近似为分类问题解决排序问题,输入的单条样本为**标签-文档对**。对于一次查询的多个结果文档,组合任意两个文档形成文档对作为输入样本。即学习一个二分类器,对输入的一对文档对AB(Pairwise的由来),根据A相关性是否比B好,二分类器给出分类标签1或0。对所有文档对进行分类,就可以得到一组偏序关系,从而构造文档全集的排序关系。该类方法的原理是对给定的文档全集$S$,降低排序中的逆序文档对的个数来降低排序错误,从而达到优化排序结果的目的。
- Listwise方法 - Listwise方法
Listwise方法是直接优化排序列表,输入为单条样本为一个**文档排列**。通过构造合适的度量函数衡量当前文档排序和最优排序差值,优化度量函数得到排序模型。由于度量函数很多具有非连续性的性质,优化困难。 Listwise方法是直接优化排序列表,输入为单条样本为一个**文档排列**。通过构造合适的度量函数衡量当前文档排序和最优排序差值,优化度量函数得到排序模型。由于度量函数很多具有非连续性的性质,优化困难。
<p align="center"> <p align="center">
<img src="image/learningToRank.jpg" width="50%" ><br/> <img src="images/learning_to_rank.jpg" width="50%" ><br/>
图2. 排序模型构造的三类方法 图2. 排序模型三类方法
</p> </p>
## 实验数据 ## 实验数据
本例子中的实验数据采用了排序学习中的基准数据[LETOR]([http://research.microsoft.com/en-us/um/beijing/projects/letor/LETOR4.0/Data/MQ2007.rar](http://research.microsoft.com/en-us/um/beijing/projects/letor/LETOR4.0/Data/MQ2007.rar))中语料库,部分来自于Gov2网站的查询请求结果,包含了约1700条查询请求结果文档列表,并对文档相关性做出了人工标注。其中,一条查询含有唯一的查询id,对应于多个具有相关性的文档,构成了一次查询请求结果文档列表。每个文档由一个一维数组的特征向量表示,并对应一个人工标注与查询的相关性分数。 本例中的实验数据采用了排序学习中的基准数据[LETOR]([http://research.microsoft.com/en-us/um/beijing/projects/letor/LETOR4.0/Data/MQ2007.rar](http://research.microsoft.com/en-us/um/beijing/projects/letor/LETOR4.0/Data/MQ2007.rar))语料库,部分来自于Gov2网站的查询请求结果,包含了约1700条查询请求结果文档列表,并对文档相关性做出了人工标注。其中,一条查询含有唯一的查询id,对应于多个具有相关性的文档,构成了一次查询请求结果文档列表。每个文档由一个一维数组的特征向量表示,并对应一个人工标注与查询的相关性分数。
样例在第一次运行的时候会自动下载LETOR MQ2007数据集并缓存,用户无需手动下载。 例在第一次运行的时会自动下载LETOR MQ2007数据集并缓存,无需手动下载。
`mq2007`数据集分别提供了三种类型排序模型的生成格式,需要指定生成格式`format` `mq2007`数据集分别提供了三种类型排序模型的生成格式,需要指定生成格式`format`
...@@ -66,11 +62,11 @@ for label, left_doc, right_doc in pairwise_train_dataset(): ...@@ -66,11 +62,11 @@ for label, left_doc, right_doc in pairwise_train_dataset():
## 模型概览 ## 模型概览
对于排序模型,本样例中提供了Pairwise方法的模型RankNet和ListWise方法的模型LambdaRank,分别代表了两类学习方法。PointWise方法的排序模型退化为回归问题,不予赘述 对于排序模型,本例中提供了Pairwise方法的模型RankNet和Listwise方法的模型LambdaRank,分别代表了两类学习方法。Pointwise方法的排序模型退化为回归问题,请参考PaddleBook中[推荐系统](https://github.com/PaddlePaddle/book/blob/develop/05.recommender_system/README.cn.md)一课
## RankNet排序模型 ## RankNet排序模型
[RankNet](http://icml.cc/2015/wp-content/uploads/2015/06/icml_ranking.pdf)是一种经典的Pairwise的排序学习方法,是典型的前向神经网络排序模型。在文档集合$S$中的第i个文档记做$U_i$,它的文档特征向量记做$x_i$,对于给定的一个文档对$U_i$, $U_j$,RankNet将输入的单个文档特征向量$x$映射到$f(x)$,得到$s_i=f(x_i)$, $s_j=f(x_j)$。将$U_i$相关性比$U_j$好的概率记做$P_{i,j}$,则 [RankNet](http://icml.cc/2015/wp-content/uploads/2015/06/icml_ranking.pdf)是一种经典的Pairwise的排序学习方法,是典型的前向神经网络排序模型。在文档集合$S$中的第$i$个文档记做$U_i$,它的文档特征向量记做$x_i$,对于给定的一个文档对$U_i$, $U_j$,RankNet将输入的单个文档特征向量$x$映射到$f(x)$,得到$s_i=f(x_i)$, $s_j=f(x_j)$。将$U_i$相关性比$U_j$好的概率记做$P_{i,j}$,则
$$P_{i,j}=P(U_{i}>U_{j})=\frac{1}{1+e^{-\sigma (s_{i}-s_{j}))}}$$ $$P_{i,j}=P(U_{i}>U_{j})=\frac{1}{1+e^{-\sigma (s_{i}-s_{j}))}}$$
...@@ -82,7 +78,7 @@ $$C_{i,j}=-\bar{P_{i,j}}logP_{i,j}-(1-\bar{P_{i,j}})log(1-P_{i,j})$$ ...@@ -82,7 +78,7 @@ $$C_{i,j}=-\bar{P_{i,j}}logP_{i,j}-(1-\bar{P_{i,j}})log(1-P_{i,j})$$
$$\bar{P_{i,j}}=\frac{1}{2}(1+S_{i,j})$$ $$\bar{P_{i,j}}=\frac{1}{2}(1+S_{i,j})$$
而$S_{i,j}$ = {+1,0},表示$U_i$和$U_j$组成的Pair的标签,即Ui相关性是否好于Uj 而$S_{i,j}$ = {+1,0},表示$U_i$和$U_j$组成的Pair的标签,即Ui相关性是否好于$U_j$
最终得到了可求导的度量损失函数 最终得到了可求导的度量损失函数
...@@ -96,26 +92,24 @@ $$\lambda _{i,j}=\frac{\partial C}{\partial s_{i}} = \frac{1}{2}(1-S_{i,j})-\fra ...@@ -96,26 +92,24 @@ $$\lambda _{i,j}=\frac{\partial C}{\partial s_{i}} = \frac{1}{2}(1-S_{i,j})-\fra
表示的含义是本轮排序优化过程中文档$U_i$的上升或者下降量。 表示的含义是本轮排序优化过程中文档$U_i$的上升或者下降量。
根据以上推论构造RankNet网络结构,由若干层隐藏层和全连接层构成,如图所示,将文档特征使用隐藏层,全连接层逐层变换,完成了底层特征空间到高层特征空间的变换。其中docA和docB结构对称,分别输入到最终的RankCost层中。 根据以上推论构造RankNet网络结构,由若干层隐藏层和全连接层构成,如图所示,将文档特征使用隐藏层,全连接层逐层变换,完成了底层特征空间到高层特征空间的变换。其中docA和docB结构对称,分别输入到最终的RankCost层中。
<p align="center"> <p align="center">
<img src="image/ranknet.jpg" width="50%" ><br/> <img src="images/ranknet.jpg" width="50%" ><br/>
图3. RankNet网络结构示意图 图3. RankNet网络结构示意图
</p> </p>
- 全连接层(fully connected layer) : 指上一层中的每个节点都连接到下层网络。本例子中同样使用paddle.layer.fc实现,注意输入到RankCost层的全连接层维度为1。 - 全连接层(fully connected layer) : 指上一层中的每个节点都连接到下层网络。本例子中同样使用`paddle.layer.fc`实现,注意输入到RankCost层的全连接层维度为1。
- RankCost层: RankCost层是排序网络RankNet的核心,度量docA相关性是否比docB好,给出预测值并和label比较。使用了交叉熵(cross enctropy)作为度量损失函数,使用梯度下降方法进行优化。细节可见[RankNet](http://icml.cc/2015/wp-content/uploads/2015/06/icml_ranking.pdf)[4] - RankCost层: RankCost层是排序网络RankNet的核心,度量docA相关性是否比docB好,给出预测值并和label比较。使用了交叉熵(cross enctropy)作为度量损失函数,使用梯度下降方法进行优化。细节可见[RankNet](http://icml.cc/2015/wp-content/uploads/2015/06/icml_ranking.pdf)[4]
由于Pairwise中的网络结构是左右对称,可定义一半网络结构,另一半共享网络参数。在PaddlePaddle中允许网络结构中共享连接,具有相同名字的参数将会共享参数状态。使用PaddlePaddle实现RankNet排序模型,定义网络结构的示例代码如下: 由于Pairwise中的网络结构是左右对称,可定义一半网络结构,另一半共享网络参数。在PaddlePaddle中允许网络结构中共享连接,具有相同名字的参数将会共享参数。使用PaddlePaddle实现RankNet排序模型,定义网络结构的示例代码如下:
```python ```python
import paddle.v2 as paddle import paddle.v2 as paddle
def half_ranknet(name_prefix, input_dim): def half_ranknet(name_prefix, input_dim):
""" """
parameter in same name will be shared in paddle framework, parameter with a same name will be shared in PaddlePaddle framework,
these parameters in ranknet can be used in shared state, e.g. left network and right network in detail these parameters in ranknet can be used in shared state, e.g. left network and right network in detail
https://github.com/PaddlePaddle/Paddle/blob/develop/doc/design/api.md https://github.com/PaddlePaddle/Paddle/blob/develop/doc/design/api.md
""" """
...@@ -128,7 +122,7 @@ def half_ranknet(name_prefix, input_dim): ...@@ -128,7 +122,7 @@ def half_ranknet(name_prefix, input_dim):
size=10, size=10,
act=paddle.activation.Tanh(), act=paddle.activation.Tanh(),
param_attr=paddle.attr.Param(initial_std=0.01, name="hidden_w1")) param_attr=paddle.attr.Param(initial_std=0.01, name="hidden_w1"))
# fully connect layer/ output layer # fully connected layer/ output layer
output = paddle.layer.fc( output = paddle.layer.fc(
input=hd1, input=hd1,
size=1, size=1,
...@@ -149,26 +143,24 @@ def ranknet(input_dim): ...@@ -149,26 +143,24 @@ def ranknet(input_dim):
return cost return cost
``` ```
上述结构中使用了和前述图表相同的模型结构,使用了两层隐藏层,分别使用了`hidden_size=10`的全连接层和`hidden_size=1`的全连接层。本例子中的input_dim指输入**单个文档**的特征的维度,label取值为1,0。每条输入样本为`<label>,<docA, docB>`的结构,以docA为例,输入`input_dim`的文档特征,依次变换成10维,1维特征,最终输入到RankCost层中,比较docA和docB在RankCost输出得到预测值。 上述结构中使用了和图3相同的模型结构:两层隐藏层,分别是`hidden_size=10`的全连接层和`hidden_size=1`的全连接层。本例中的input_dim指输入**单个文档**的特征的维度,label取值为1,0。每条输入样本为`<label>,<docA, docB>`的结构,以docA为例,输入`input_dim`的文档特征,依次变换成10维,1维特征,最终输入到RankCost层中,比较docA和docB在RankCost输出得到预测值。
### rankNet模型训练 ### RankNet模型训练
用户运行只需要运行命令: RankNet的训练只需要运行命令:
```python ```python
python ranknet.py python ranknet.py
``` ```
将会自动下载数据,训练RankNet模型,并将每个轮次的模型参数存储下来。 将会自动下载数据,训练RankNet模型,并将每个轮次的模型参数存储下来。
### rankNet模型预测 ### RankNet模型预测
本例子提供了rankNet模型的训练和预测两个部分。完成训练后的模型分为拓扑结构(需要注意rank_cost不是模型拓扑结构的一部分)和模型参数文件两部分。在本例子中复用了ranknet训练时的模型拓扑结构half_ranknet,模型参数从外存中加载。模型预测的输入为单个文档的特征向量,模型会给出相关性得分。将预测得分排序即可得到最终的文档相关性排序结果。
本例提供了rankNet模型的训练和预测两个部分。完成训练后的模型分为拓扑结构(需要注意`rank_cost`不是模型拓扑结构的一部分)和模型参数文件两部分。在本例子中复用了`ranknet`训练时的模型拓扑结构`half_ranknet`,模型参数从外存中加载。模型预测的输入为单个文档的特征向量,模型会给出相关性得分。将预测得分排序即可得到最终的文档相关性排序结果。
## 用户自定义RankNet数据 ## 用户自定义RankNet数据
面的代码使用了paddle内置的排序数据,如果希望使用自定义格式数据,可以参考Paddle内置的`mq2007`数据集,编写一个生成器函数。例如输入数据为如下格式,只包含doc0-doc2三个文档。 述的代码使用了PaddlePaddle内置的排序数据,如果希望使用自定义格式数据,可以参考PaddlePaddle内置的`mq2007`数据集,编写一个新的生成器函数。例如输入数据为如下格式,只包含doc0-doc2三个文档。
\<query_id\> \<relevance_score\> \<feature_vector\>的格式(featureid: feature_value) \<query_id\> \<relevance_score\> \<feature_vector\>的格式(featureid: feature_value)
...@@ -180,7 +172,7 @@ query_id : 2, relevance_score:0, feature_vector 0:0.1, 1:0.4, 2:0.1 #doc0 ...@@ -180,7 +172,7 @@ query_id : 2, relevance_score:0, feature_vector 0:0.1, 1:0.4, 2:0.1 #doc0
..... .....
``` ```
需要将输入样本转换为PairWise的输入格式,例如组合生成格式与mq2007 PairWise格式相同的结构 需要将输入样本转换为Pairwise的输入格式,例如组合生成格式与mq2007 Pairwise格式相同的结构
\<label\> \<docA_feature_vector\>\<docB_feature_vector\> \<label\> \<docA_feature_vector\>\<docB_feature_vector\>
...@@ -191,19 +183,19 @@ query_id : 2, relevance_score:0, feature_vector 0:0.1, 1:0.4, 2:0.1 #doc0 ...@@ -191,19 +183,19 @@ query_id : 2, relevance_score:0, feature_vector 0:0.1, 1:0.4, 2:0.1 #doc0
.... ....
``` ```
注意,一般在PairWise格式的数据中,label=1表示docA和查询的相关性好于docB,事实上label信息隐含在docA和docB组合pair中。如果存在`0 docA docB`,交换顺序构造`1 docB docA`即可。 注意,一般在Pairwise格式的数据中,label=1表示docA和查询的相关性好于docB,事实上label信息隐含在docA和docB组合pair中。如果存在`0 docA docB`,交换顺序构造`1 docB docA`即可。
另外组合所有的pair会有训练数据冗余,因为可以从部分偏序关系恢复文档集上的全序关系。相关研究见[PairWise approach](http://www.machinelearning.org/proceedings/icml2007/papers/139.pdf)[[5](#参考文献5)\],本例不予赘述。 另外组合所有的pair会有训练数据冗余,因为可以从部分偏序关系恢复文档集上的全序关系。相关研究见[PairWise approach](http://www.machinelearning.org/proceedings/icml2007/papers/139.pdf)[[5](#参考文献5)\],本例不予赘述。
```python ```python
# self define data generator # a customized data generator
def gen_pairwise_data(text_line_of_data): def gen_pairwise_data(text_line_of_data):
""" """
return : return :
------ ------
label : np.array, shape=(1) label : np.array, shape=(1)
docA_feature_vector : np.array, shape=(1, feature_dimension) docA_feature_vector : np.array, shape=(1, feature_dimension)
docA_feature_vector : np.array, shape=(1, feature_dimension) docA_feature_vector : np.array, shape=(1, feature_dimension)
""" """
return label, docA_feature_vector, docB_feature_vector return label, docA_feature_vector, docB_feature_vector
``` ```
...@@ -212,7 +204,7 @@ def gen_pairwise_data(text_line_of_data): ...@@ -212,7 +204,7 @@ def gen_pairwise_data(text_line_of_data):
```python ```python
# Define the input data order # Define the input data order
feeding = {"label":0, feeding = { "label":0,
"left/data" :1, "left/data" :1,
"right/data":2} "right/data":2}
``` ```
...@@ -221,7 +213,7 @@ feeding = {"label":0, ...@@ -221,7 +213,7 @@ feeding = {"label":0,
## LambdaRank排序模型 ## LambdaRank排序模型
[LambdaRank](https://papers.nips.cc/paper/2971-learning-to-rank-with-nonsmooth-cost-functions.pdf)[6]是ListWise的排序方法,是Bugers[6]等人从RankNet发展而来,使用构造lambda函数(LambdaRank名字的由来)的方法优化度量标准NDCG(Normalized Discounted Cumulative Gain),每个查询后得到的结果文档列表都单独作为一个训练样本。NDCG是信息论中很衡量文档列表排序质量的标准之一,前K个文档的NDCG得分记做 [LambdaRank](https://papers.nips.cc/paper/2971-learning-to-rank-with-nonsmooth-cost-functions.pdf)\[[6](#参考文献))\]是Listwise的排序方法,是Bugers[6]等人从RankNet发展而来,使用构造lambda函数(LambdaRank名字的由来)的方法优化度量标准NDCG(Normalized Discounted Cumulative Gain),每个查询后得到的结果文档列表都单独作为一个训练样本。NDCG是信息论中很衡量文档列表排序质量的标准之一,前$K$个文档的NDCG得分记做
$$NDCG@K=Z_{k}\sum (2^{rel_{i}})1/log(k+1)$$ $$NDCG@K=Z_{k}\sum (2^{rel_{i}})1/log(k+1)$$
...@@ -234,7 +226,7 @@ $$\lambda _{i,j}=\frac{\partial C}{\partial s_{i}}=-\frac{\sigma }{1+e^{\sigma ( ...@@ -234,7 +226,7 @@ $$\lambda _{i,j}=\frac{\partial C}{\partial s_{i}}=-\frac{\sigma }{1+e^{\sigma (
由以上推导可知,LambdaRank网络结构和RankNet结构非常相似。如图所示 由以上推导可知,LambdaRank网络结构和RankNet结构非常相似。如图所示
<p align="center"> <p align="center">
<img src="image/lambdarank.jpg" width="50%" ><br/> <img src="images/lambdarank.jpg" width="50%" ><br/>
图4. LambdaRank的网络结构示意图 图4. LambdaRank的网络结构示意图
</p> </p>
...@@ -242,13 +234,13 @@ $$\lambda _{i,j}=\frac{\partial C}{\partial s_{i}}=-\frac{\sigma }{1+e^{\sigma ( ...@@ -242,13 +234,13 @@ $$\lambda _{i,j}=\frac{\partial C}{\partial s_{i}}=-\frac{\sigma }{1+e^{\sigma (
- LambdaCost层 : LambdaCost层使用NDCG差值作为Lambda函数,score是一个一维的序列,对于单调训练样本全连接层输出的是1x1的序列,二者的序列长度都等于该条查询得到的文档数量。Lambda函数的构造详细见[LambdaRank](https://papers.nips.cc/paper/2971-learning-to-rank-with-nonsmooth-cost-functions.pdf) - LambdaCost层 : LambdaCost层使用NDCG差值作为Lambda函数,score是一个一维的序列,对于单调训练样本全连接层输出的是1x1的序列,二者的序列长度都等于该条查询得到的文档数量。Lambda函数的构造详细见[LambdaRank](https://papers.nips.cc/paper/2971-learning-to-rank-with-nonsmooth-cost-functions.pdf)
使用Paddle定义LambdaRank网络结构的示例代码如下: 使用PaddlePaddle定义LambdaRank网络结构的示例代码如下:
```python ```python
import paddle.v2 as paddle import paddle.v2 as paddle
def lambdaRank(input_dim): def lambda_rank(input_dim):
""" """
lambdaRank is a ListWise Rank Model, input data and label must be sequence lambda_rank is a ListWise Rank Model, input data and label must be sequence
https://papers.nips.cc/paper/2971-learning-to-rank-with-nonsmooth-cost-functions.pdf https://papers.nips.cc/paper/2971-learning-to-rank-with-nonsmooth-cost-functions.pdf
parameters : parameters :
input_dim, one document's dense feature vector dimension input_dim, one document's dense feature vector dimension
...@@ -278,25 +270,25 @@ def lambdaRank(input_dim): ...@@ -278,25 +270,25 @@ def lambdaRank(input_dim):
return cost, output return cost, output
``` ```
上述结构中使用了和前述图表相同的模型结构,和RankNet相似,分别使用了`hidden_size=10`的全连接层和`hidden_size=1`的全连接层。本例子中的input_dim指输入**单个文档**的特征的维度,label取值为1,0。每条输入样本为label,\<docA, docB\>的结构,以docA为例,输入input_dim的文档特征,依次变换成10维,1维特征,最终输入到LambdaCost层中。需要注意这里的label和data格式为**dense_vector_sequence**,表示一列文档得分或者文档特征组成的**序列** 上述结构中使用了和图3相同的模型结构。和RankNet相似,分别使用了`hidden_size=10``hidden_size=1`的两个全连接层。本例中的input_dim指输入**单个文档**的特征的维度。每条输入样本为label,\<docA, docB\>的结构,以docA为例,输入input_dim的文档特征,依次变换成10维,1维特征,最终输入到LambdaCost层中。需要注意这里的label和data格式为**dense_vector_sequence**,表示一列文档得分或者文档特征组成的**序列**
### lambdaRank模型训练 ### LambdaRank模型训练
用户运行只需要运行命令: 训练LambdaRank模型只需要运行命令:
```python ```python
python lambdaRank.py python lambda_rank.py
``` ```
将会自动下载数据,训练LambdaRank模型,并将每个轮次的模型存储下来,将最终模型存储在文件中 脚本会自动下载数据,训练LambdaRank模型,并将每个轮次的模型存储下来
### lambdaRank模型预测 ### LambdaRank模型预测
lambdaRank模型预测过程和ranknet相同。预测时的模型拓扑结构复用代码中的模型定义,从外存加载对应的参数文件。预测时的输入是文档列表,输出是该文档列表的各个文档相关性打分,根据打分对文档进行重新排序,即可得到最终的文档排序结果。 LambdaRank模型预测过程和RankNet相同。预测时的模型拓扑结构复用代码中的模型定义,从外存加载对应的参数文件。预测时的输入是文档列表,输出是该文档列表的各个文档相关性打分,根据打分对文档进行重新排序,即可得到最终的文档排序结果。
## 自定义 LambdaRank数据 ## 自定义 LambdaRank数据
上面的代码使用了paddle内置的mq2007数据,如果希望使用自定义格式数据,可以参考Paddle内置的`mq2007`数据集,编写一个生成器函数。例如输入数据为如下格式,只包含doc0-doc2三个文档。 上面的代码使用了PaddlePaddle内置的mq2007数据,如果希望使用自定义格式数据,可以参考PaddlePaddle内置的`mq2007`数据集,编写一个生成器函数。例如输入数据为如下格式,只包含doc0-doc2三个文档。
\<query_id\> \<relevance_score\> \<feature_vector\>的格式 \<query_id\> \<relevance_score\> \<feature_vector\>的格式
...@@ -309,7 +301,7 @@ query_id : 2, relevance_score:2, feature_vector 0:0.1, 1:0.4, 2:0.1 #doc1 ...@@ -309,7 +301,7 @@ query_id : 2, relevance_score:2, feature_vector 0:0.1, 1:0.4, 2:0.1 #doc1
..... .....
``` ```
需要转换为ListWise格式,例如 需要转换为Listwise格式,例如
<query_id><relevance_score> <feature_vector> <query_id><relevance_score> <feature_vector>
...@@ -325,24 +317,22 @@ query_id : 2, relevance_score:2, feature_vector 0:0.1, 1:0.4, 2:0.1 #doc1 ...@@ -325,24 +317,22 @@ query_id : 2, relevance_score:2, feature_vector 0:0.1, 1:0.4, 2:0.1 #doc1
**数据格式注意** **数据格式注意**
- 数据中每条样本对应的文档数量都必须大于Lambda_cost层的NDCG_num - 数据中每条样本对应的文档数量都必须大于`lambda_cost`层的NDCG_num
- 单条样本对应的文档都为0,NDCG将会计算无效。文档相关性都为0,那么可以判定该query无效,可以过滤掉。 - 若单条样本对应的文档都为0,文档相关性都为0,NDCG计算无效,那么可以判定该query无效,我们在训练中过滤掉了这样的query。
```python ```python
# self define data generator # self define data generator
def gen_listwise_data(text_all_lines_of_data): def gen_listwise_data(text_all_lines_of_data):
""" """
return : return :
------ ------
label : np.array, shape=(samples_num, ) label : np.array, shape=(samples_num, )
querylist : np.array, shape=(samples_num, feature_dimension) querylist : np.array, shape=(samples_num, feature_dimension)
""" """
return label_list, query_docs_feature_vector_matrix return label_list, query_docs_feature_vector_matrix
``` ```
对应于paddle的输入中,label的`dense_vector_sequence`为得分序列,data的`dense_vector_sequence`为特征向量的序列输入,input_dim为单个文档的一维特征向量维度,与生成器对应,需要在训练模型之前指明输入数据对应关系。 对应于PaddlePaddle输入,`label`的类型为`dense_vector_sequence`,是得分的序列,`data`的类型为`dense_vector_sequence`,是特征向量的序列输入,`input_dim`为单个文档的一维特征向量维度,与生成器对应,需要在训练模型之前指明输入数据对应关系。
```python ```python
# Define the input data order # Define the input data order
...@@ -350,19 +340,15 @@ feeding = {"label":0, ...@@ -350,19 +340,15 @@ feeding = {"label":0,
"data" : 1} "data" : 1}
``` ```
## 总结 ## 总结
LearningToRank是和业务场景结合非常紧密的常用机器学习方法,排序模型构造方法一般可划分为PointWise方法,PairWise方法,ListWise方法,本例子中以LETOR的mq2007数据为例,阐述了PairWise的经典方法RankNet和ListWise方法中的LambdaRank,展示如何使用Paddle框架构造对应的排序模型结构,并提供了自定义数据类型样例。Paddle提供了灵活的编程接口,并可以使用一套代码运行在单机单GPU和多机分布式多GPU情况下,可以实现LearningToRank类型任务。 LTR在实际生活中有着广泛的应用。排序模型构造方法一般可划分为PointWise方法,Pairwise方法,Listwise方法,本例以LETOR的mq2007数据为例子,阐述了Pairwise的经典方法RankNet和Listwise方法中的LambdaRank,展示如何使用PaddlePaddle框架构造对应的排序模型结构,并提供了自定义数据类型样例。PaddlePaddle提供了灵活的编程接口,并可以使用一套代码运行在单机单GPU和多机分布式多GPU下实现LTR类型任务。
## 参考文献 ## 参考文献
1. https://en.wikipedia.org/wiki/Learning_to_rank 1. https://en.wikipedia.org/wiki/Learning_to_rank
2. T.Y. Liu, “Learning to rank for information retrieval,” Foundations and Trends in Information Retrieval, vol.3, no.3, pp.225–331, 2009. 2. Liu T Y. [Learning to rank for information retrieval](http://ftp.nowpublishers.com/article/DownloadSummary/INR-016)[J]. Foundations and Trends® in Information Retrieval, 2009, 3(3): 225-331.
3. H. Li, “Learning to rank for information retrieval and natural language processing,” Synthesis Lectures on Human Language Technologies, 2011, Morgan & Claypool Publishers. 3. Li H. [Learning to rank for information retrieval and natural language processing](http://www.morganclaypool.com/doi/abs/10.2200/S00607ED2V01Y201410HLT026)[J]. Synthesis Lectures on Human Language Technologies, 2014, 7(3): 1-121.
4. Burges, Chris, et al. "Learning to rank using gradient descent." *Proceedings of the 22nd international conference on Machine learning*. ACM, 2005. 4. Burges C, Shaked T, Renshaw E, et al. [Learning to rank using gradient descent](http://machinelearning.wustl.edu/mlpapers/paper_files/icml2005_BurgesSRLDHH05.pdf)[C]//Proceedings of the 22nd international conference on Machine learning. ACM, 2005: 89-96.
5. Cao, Zhe, et al. "Learning to rank: from pairwise approach to listwise approach." *Proceedings of the 24th international conference on Machine learning*. ACM, 2007. 5. Cao Z, Qin T, Liu T Y, et al. [Learning to rank: from pairwise approach to listwise approach](http://machinelearning.wustl.edu/mlpapers/paper_files/icml2007_CaoQLTL07.pdf)[C]//Proceedings of the 24th international conference on Machine learning. ACM, 2007: 129-136.
6. Burges, Christopher JC, Robert Ragno, and Quoc Viet Le. "Learning to rank with nonsmooth cost functions." *NIPS*. Vol. 6. 2006. 6. Burges C J C, Ragno R, Le Q V. [Learning to rank with nonsmooth cost functions](https://papers.nips.cc/paper/2971-learning-to-rank-with-nonsmooth-cost-functions.pdf)[C]//NIPS. 2006, 6: 193-200.
...@@ -4,18 +4,16 @@ import paddle.v2 as paddle ...@@ -4,18 +4,16 @@ import paddle.v2 as paddle
import numpy as np import numpy as np
import functools import functools
#lambdaRank is listwise learning to rank model
def lambda_rank(input_dim):
def lambdaRank(input_dim):
""" """
lambdaRank is a ListWise Rank Model, input data and label must be sequence lambda_rank is a Listwise rank model, the input data and label must be sequences.
https://papers.nips.cc/paper/2971-learning-to-rank-with-nonsmooth-cost-functions.pdf https://papers.nips.cc/paper/2971-learning-to-rank-with-nonsmooth-cost-functions.pdf
parameters : parameters :
input_dim, one document's dense feature vector dimension input_dim, one document's dense feature vector dimension
dense_vector_sequence format format of the dense_vector_sequence:
[[f, ...], [f, ...], ...], f is represent for an float or int number [[f, ...], [f, ...], ...], f is a float or an int number
""" """
label = paddle.layer.data("label", label = paddle.layer.data("label",
paddle.data_type.dense_vector_sequence(1)) paddle.data_type.dense_vector_sequence(1))
...@@ -48,7 +46,7 @@ def lambdaRank(input_dim): ...@@ -48,7 +46,7 @@ def lambdaRank(input_dim):
return cost, output return cost, output
def train_lambdaRank(num_passes): def train_lambda_rank(num_passes):
# listwise input sequence # listwise input sequence
fill_default_train = functools.partial( fill_default_train = functools.partial(
paddle.dataset.mq2007.train, format="listwise") paddle.dataset.mq2007.train, format="listwise")
...@@ -60,7 +58,7 @@ def train_lambdaRank(num_passes): ...@@ -60,7 +58,7 @@ def train_lambdaRank(num_passes):
# mq2007 input_dim = 46, dense format # mq2007 input_dim = 46, dense format
input_dim = 46 input_dim = 46
cost, output = lambdaRank(input_dim) cost, output = lambda_rank(input_dim)
parameters = paddle.parameters.create(cost) parameters = paddle.parameters.create(cost)
trainer = paddle.trainer.SGD( trainer = paddle.trainer.SGD(
...@@ -76,7 +74,7 @@ def train_lambdaRank(num_passes): ...@@ -76,7 +74,7 @@ def train_lambdaRank(num_passes):
if isinstance(event, paddle.event.EndPass): if isinstance(event, paddle.event.EndPass):
result = trainer.test(reader=test_reader, feeding=feeding) result = trainer.test(reader=test_reader, feeding=feeding)
print "\nTest with Pass %d, %s" % (event.pass_id, result.metrics) print "\nTest with Pass %d, %s" % (event.pass_id, result.metrics)
with gzip.open("lambdaRank_params_%d.tar.gz" % (event.pass_id), with gzip.open("lambda_rank_params_%d.tar.gz" % (event.pass_id),
"w") as f: "w") as f:
parameters.to_tar(f) parameters.to_tar(f)
...@@ -88,17 +86,17 @@ def train_lambdaRank(num_passes): ...@@ -88,17 +86,17 @@ def train_lambdaRank(num_passes):
num_passes=num_passes) num_passes=num_passes)
def lambdaRank_infer(pass_id): def lambda_rank_infer(pass_id):
""" """
lambdaRank model inference interface lambda_rank model inference interface
parameters: parameters:
pass_id : inference model in pass_id pass_id : inference model in pass_id
""" """
print "Begin to Infer..." print "Begin to Infer..."
input_dim = 46 input_dim = 46
output = lambdaRank(input_dim) output = lambda_rank(input_dim)
parameters = paddle.parameters.Parameters.from_tar( parameters = paddle.parameters.Parameters.from_tar(
gzip.open("lambdaRank_params_%d.tar.gz" % (pass_id - 1))) gzip.open("lambda_rank_params_%d.tar.gz" % (pass_id - 1)))
infer_query_id = None infer_query_id = None
infer_data = [] infer_data = []
...@@ -119,6 +117,6 @@ def lambdaRank_infer(pass_id): ...@@ -119,6 +117,6 @@ def lambdaRank_infer(pass_id):
if __name__ == '__main__': if __name__ == '__main__':
paddle.init(use_gpu=False, trainer_count=4) paddle.init(use_gpu=False, trainer_count=1)
train_lambdaRank(2) train_lambda_rank(2)
lambdaRank_infer(pass_id=1) lambda_rank_infer(pass_id=1)
TBD # 命名实体识别
命名实体识别(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的标注结果示例:
<div align="center">
<img src="images/ner_label_ins.png" width = "80%" align=center /><br>
图1. BIO标注方法示例
</div>
根据序列标注结果可以直接得到实体边界和实体类别。类似的,分词、词性标注、语块识别、[语义角色标注](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中模型学习到的特征为输入,以标记序列为监督信号,实现序列标注。
<div align="center">
<img src="images/ner_network.png" width = "40%" align=center /><br>
图2. NER模型的网络结构图
</div>
## 数据说明
在本例中,我们使用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.
"""
Conll03 dataset.
"""
import tarfile
import gzip
import itertools
import collections
import re
import numpy as np
__all__ = ['train', 'test', 'get_dict', 'get_embedding']
UNK_IDX = 0
def canonicalize_digits(word):
if any([c.isalpha() for c in word]): return word
word = re.sub("\d", "DG", word)
if word.startswith("DG"):
word = word.replace(",", "") # remove thousands separator
return word
def canonicalize_word(word, wordset=None, digits=True):
word = word.lower()
if digits:
if (wordset != None) and (word in wordset): return word
word = canonicalize_digits(word) # try to canonicalize numbers
if (wordset == None) or (word in wordset): return word
else: return "UUUNKKK" # unknown token
def load_dict(filename):
d = dict()
with open(filename, 'r') as f:
for i, line in enumerate(f):
d[line.strip()] = i
return d
def get_dict(vocab_file='data/vocab.txt', target_file='data/target.txt'):
"""
Get the word and label dictionary.
"""
word_dict = load_dict(vocab_file)
label_dict = load_dict(target_file)
return word_dict, label_dict
def get_embedding(emb_file='data/wordVectors.txt'):
"""
Get the trained word vector.
"""
return np.loadtxt(emb_file, dtype=float)
def corpus_reader(filename='data/train'):
def reader():
sentence = []
labels = []
with open(filename) as f:
for line in f:
if re.match(r"-DOCSTART-.+", line) or (len(line.strip()) == 0):
if len(sentence) > 0:
yield sentence, labels
sentence = []
labels = []
else:
segs = line.strip().split()
sentence.append(segs[0])
# transform from I-TYPE to BIO schema
if segs[-1] != 'O' and (len(labels) == 0 or
labels[-1][1:] != segs[-1][1:]):
labels.append('B' + segs[-1][1:])
else:
labels.append(segs[-1])
f.close()
return reader
def reader_creator(corpus_reader, word_dict, label_dict):
"""
Conll03 train set creator.
The dataset can be obtained according to http://www.clips.uantwerpen.be/conll2003/ner/.
It returns a reader creator, each sample in the reader includes word id sequence, label id sequence and raw sentence for purpose of print.
:return: Training reader creator
:rtype: callable
"""
def reader():
for sentence, labels in corpus_reader():
word_idx = [
word_dict.get(canonicalize_word(w, word_dict), UNK_IDX)
for w in sentence
]
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, mark, label_idx, sentence
return reader
def train(data_file='data/train',
vocab_file='data/vocab.txt',
target_file='data/target.txt'):
return reader_creator(
corpus_reader(data_file),
word_dict=load_dict(vocab_file),
label_dict=load_dict(target_file))
def test(data_file='data/test',
vocab_file='data/vocab.txt',
target_file='data/target.txt'):
return reader_creator(
corpus_reader(data_file),
word_dict=load_dict(vocab_file),
label_dict=load_dict(target_file))
wget http://cs224d.stanford.edu/assignment2/assignment2.zip
unzip assignment2.zip
cp assignment2_release/data/ner/wordVectors.txt data/
cp assignment2_release/data/ner/vocab.txt data/
rm -rf assignment2.zip assignment2_release
B-LOC
I-LOC
B-MISC
I-MISC
B-ORG
I-ORG
B-PER
I-PER
O
-DOCSTART- -X- O O
CRICKET NNP I-NP O
- : O O
LEICESTERSHIRE NNP I-NP I-ORG
TAKE NNP I-NP O
OVER IN I-PP O
AT NNP I-NP O
TOP NNP I-NP O
AFTER NNP I-NP O
INNINGS NNP I-NP O
VICTORY NN I-NP O
. . O O
LONDON NNP I-NP I-LOC
1996-08-30 CD I-NP O
West NNP I-NP I-MISC
Indian NNP I-NP I-MISC
all-rounder NN I-NP O
Phil NNP I-NP I-PER
Simmons NNP I-NP I-PER
took VBD I-VP O
four CD I-NP O
for IN I-PP O
38 CD I-NP O
on IN I-PP O
Friday NNP I-NP O
as IN I-PP O
Leicestershire NNP I-NP I-ORG
beat VBD I-VP O
Somerset NNP I-NP I-ORG
by IN I-PP O
an DT I-NP O
innings NN I-NP O
and CC O O
39 CD I-NP O
runs NNS I-NP O
in IN I-PP O
two CD I-NP O
days NNS I-NP O
to TO I-VP O
take VB I-VP O
over IN I-PP O
at IN B-PP O
the DT I-NP O
head NN I-NP O
of IN I-PP O
the DT I-NP O
county NN I-NP O
championship NN I-NP O
. . O O
Their PRP$ I-NP O
stay NN I-NP O
on IN I-PP O
top NN I-NP O
, , O O
though RB I-ADVP O
, , O O
may MD I-VP O
be VB I-VP O
short-lived JJ I-ADJP O
as IN I-PP O
title NN I-NP O
rivals NNS I-NP O
Essex NNP I-NP I-ORG
, , O O
Derbyshire NNP I-NP I-ORG
and CC I-NP O
Surrey NNP I-NP I-ORG
all DT O O
closed VBD I-VP O
in RP I-PRT O
on IN I-PP O
victory NN I-NP O
while IN I-SBAR O
Kent NNP I-NP I-ORG
made VBD I-VP O
up RP I-PRT O
for IN I-PP O
lost VBN I-NP O
time NN I-NP O
in IN I-PP O
their PRP$ I-NP O
rain-affected JJ I-NP O
match NN I-NP O
against IN I-PP O
Nottinghamshire NNP I-NP I-ORG
. . O O
After IN I-PP O
bowling VBG I-NP O
Somerset NNP I-NP I-ORG
out RP I-PRT O
for IN I-PP O
83 CD I-NP O
on IN I-PP O
the DT I-NP O
opening NN I-NP O
morning NN I-NP O
at IN I-PP O
Grace NNP I-NP I-LOC
Road NNP I-NP I-LOC
, , O O
Leicestershire NNP I-NP I-ORG
extended VBD I-VP O
their PRP$ I-NP O
first JJ I-NP O
innings NN I-NP O
by IN I-PP O
94 CD I-NP O
runs VBZ I-VP O
before IN I-PP O
being VBG I-VP O
bowled VBD I-VP O
out RP I-PRT O
for IN I-PP O
296 CD I-NP O
with IN I-PP O
England NNP I-NP I-LOC
discard VBP I-VP O
Andy NNP I-NP I-PER
Caddick NNP I-NP I-PER
taking VBG I-VP O
three CD I-NP O
for IN I-PP O
83 CD I-NP O
. . O O
-DOCSTART- -X- O O
EU NNP I-NP I-ORG
rejects VBZ I-VP O
German JJ I-NP I-MISC
call NN I-NP O
to TO I-VP O
boycott VB I-VP O
British JJ I-NP I-MISC
lamb NN I-NP O
. . O O
Peter NNP I-NP I-PER
Blackburn NNP I-NP I-PER
BRUSSELS NNP I-NP I-LOC
1996-08-22 CD I-NP O
The DT I-NP O
European NNP I-NP I-ORG
Commission NNP I-NP I-ORG
said VBD I-VP O
on IN I-PP O
Thursday NNP I-NP O
it PRP B-NP O
disagreed VBD I-VP O
with IN I-PP O
German JJ I-NP I-MISC
advice NN I-NP O
to TO I-PP O
consumers NNS I-NP O
to TO I-VP O
shun VB I-VP O
British JJ I-NP I-MISC
lamb NN I-NP O
until IN I-SBAR O
scientists NNS I-NP O
determine VBP I-VP O
whether IN I-SBAR O
mad JJ I-NP O
cow NN I-NP O
disease NN I-NP O
can MD I-VP O
be VB I-VP O
transmitted VBN I-VP O
to TO I-PP O
sheep NN I-NP O
. . O O
Germany NNP I-NP I-LOC
's POS B-NP O
representative NN I-NP O
to TO I-PP O
the DT I-NP O
European NNP I-NP I-ORG
Union NNP I-NP I-ORG
's POS B-NP O
veterinary JJ I-NP O
committee NN I-NP O
Werner NNP I-NP I-PER
Zwingmann NNP I-NP I-PER
said VBD I-VP O
on IN I-PP O
Wednesday NNP I-NP O
consumers NNS I-NP O
should MD I-VP O
buy VB I-VP O
sheepmeat NN I-NP O
from IN I-PP O
countries NNS I-NP O
other JJ I-ADJP O
than IN I-PP O
Britain NNP I-NP I-LOC
until IN I-SBAR O
the DT I-NP O
scientific JJ I-NP O
advice NN I-NP O
was VBD I-VP O
clearer JJR I-ADJP O
. . O O
" " O O
We PRP I-NP O
do VBP I-VP O
n't RB I-VP O
support VB I-VP O
any DT I-NP O
such JJ I-NP O
recommendation NN I-NP O
because IN I-SBAR O
we PRP I-NP O
do VBP I-VP O
n't RB I-VP O
see VB I-VP O
any DT I-NP O
grounds NNS I-NP O
for IN I-PP O
it PRP I-NP O
, , O O
" " O O
the DT I-NP O
Commission NNP I-NP I-ORG
's POS B-NP O
chief JJ I-NP O
spokesman NN I-NP O
Nikolaus NNP I-NP I-PER
van NNP I-NP I-PER
der FW I-NP I-PER
Pas NNP I-NP I-PER
told VBD I-VP O
a DT I-NP O
news NN I-NP O
briefing NN I-NP O
. . O O
He PRP I-NP O
said VBD I-VP O
further JJ I-NP O
scientific JJ I-NP O
study NN I-NP O
was VBD I-VP O
required VBN I-VP O
and CC O O
if IN I-SBAR O
it PRP I-NP O
was VBD I-VP O
found VBN I-VP O
that IN I-SBAR O
action NN I-NP O
was VBD I-VP O
needed VBN I-VP O
it PRP I-NP O
should MD I-VP O
be VB I-VP O
taken VBN I-VP O
by IN I-PP O
the DT I-NP O
European NNP I-NP I-ORG
Union NNP I-NP I-ORG
. . O O
import math
import gzip
import paddle.v2 as paddle
import paddle.v2.evaluator as evaluator
import conll03
import itertools
# init dataset
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)
word_dict, label_dict = conll03.get_dict(vocab_file, target_file)
word_vector_values = conll03.get_embedding(emb_file)
# init hyper-params
word_dict_len = len(word_dict)
label_dict_len = len(label_dict)
mark_dict_len = 2
word_dim = 50
mark_dim = 5
hidden_dim = 300
mix_hidden_lr = 1e-3
default_std = 1 / math.sqrt(hidden_dim) / 3.0
emb_para = paddle.attr.Param(
name='emb', initial_std=math.sqrt(1. / word_dim), is_static=True)
std_0 = paddle.attr.Param(initial_std=0.)
std_default = paddle.attr.Param(initial_std=default_std)
def d_type(size):
return paddle.data_type.integer_value_sequence(size)
def ner_net(is_train):
word = paddle.layer.data(name='word', type=d_type(word_dict_len))
mark = paddle.layer.data(name='mark', type=d_type(mark_dict_len))
word_embedding = paddle.layer.mixed(
name='word_embedding',
size=word_dim,
input=paddle.layer.table_projection(input=word, param_attr=emb_para))
mark_embedding = paddle.layer.mixed(
name='mark_embedding',
size=mark_dim,
input=paddle.layer.table_projection(input=mark, param_attr=std_0))
emb_layers = [word_embedding, mark_embedding]
word_caps_vector = paddle.layer.concat(
name='word_caps_vector', input=emb_layers)
hidden_1 = paddle.layer.mixed(
name='hidden1',
size=hidden_dim,
act=paddle.activation.Tanh(),
bias_attr=std_default,
input=[
paddle.layer.full_matrix_projection(
input=word_caps_vector, param_attr=std_default)
])
rnn_para_attr = paddle.attr.Param(initial_std=0.0, learning_rate=0.1)
hidden_para_attr = paddle.attr.Param(
initial_std=default_std, learning_rate=mix_hidden_lr)
rnn_1_1 = paddle.layer.recurrent(
name='rnn1-1',
input=hidden_1,
act=paddle.activation.Relu(),
bias_attr=std_0,
param_attr=rnn_para_attr)
rnn_1_2 = paddle.layer.recurrent(
name='rnn1-2',
input=hidden_1,
act=paddle.activation.Relu(),
reverse=1,
bias_attr=std_0,
param_attr=rnn_para_attr)
hidden_2_1 = paddle.layer.mixed(
name='hidden2-1',
size=hidden_dim,
bias_attr=std_default,
act=paddle.activation.STanh(),
input=[
paddle.layer.full_matrix_projection(
input=hidden_1, param_attr=hidden_para_attr),
paddle.layer.full_matrix_projection(
input=rnn_1_1, param_attr=rnn_para_attr)
])
hidden_2_2 = paddle.layer.mixed(
name='hidden2-2',
size=hidden_dim,
bias_attr=std_default,
act=paddle.activation.STanh(),
input=[
paddle.layer.full_matrix_projection(
input=hidden_1, param_attr=hidden_para_attr),
paddle.layer.full_matrix_projection(
input=rnn_1_2, param_attr=rnn_para_attr)
])
rnn_2_1 = paddle.layer.recurrent(
name='rnn2-1',
input=hidden_2_1,
act=paddle.activation.Relu(),
reverse=1,
bias_attr=std_0,
param_attr=rnn_para_attr)
rnn_2_2 = paddle.layer.recurrent(
name='rnn2-2',
input=hidden_2_2,
act=paddle.activation.Relu(),
bias_attr=std_0,
param_attr=rnn_para_attr)
hidden_3 = paddle.layer.mixed(
name='hidden3',
size=hidden_dim,
bias_attr=std_default,
act=paddle.activation.STanh(),
input=[
paddle.layer.full_matrix_projection(
input=hidden_2_1, param_attr=hidden_para_attr),
paddle.layer.full_matrix_projection(
input=rnn_2_1,
param_attr=rnn_para_attr), paddle.layer.full_matrix_projection(
input=hidden_2_2, param_attr=hidden_para_attr),
paddle.layer.full_matrix_projection(
input=rnn_2_2, param_attr=rnn_para_attr)
])
output = paddle.layer.mixed(
name='output',
size=label_dict_len,
bias_attr=False,
input=[
paddle.layer.full_matrix_projection(
input=hidden_3, param_attr=std_default)
])
if is_train:
target = paddle.layer.data(name='target', type=d_type(label_dict_len))
crf_cost = paddle.layer.crf(
size=label_dict_len,
input=output,
label=target,
param_attr=paddle.attr.Param(
name='crfw',
initial_std=default_std,
learning_rate=mix_hidden_lr))
crf_dec = paddle.layer.crf_decoding(
size=label_dict_len,
input=output,
label=target,
param_attr=paddle.attr.Param(name='crfw'))
return crf_cost, crf_dec, target
else:
predict = paddle.layer.crf_decoding(
size=label_dict_len,
input=output,
param_attr=paddle.attr.Param(name='crfw'))
return predict
def ner_net_train(data_reader=train_data_reader, num_passes=1):
# define network topology
crf_cost, crf_dec, target = ner_net(is_train=True)
evaluator.sum(name='error', input=crf_dec)
evaluator.chunk(
name='ner_chunk',
input=crf_dec,
label=target,
chunk_scheme='IOB',
num_chunk_types=(label_dict_len - 1) / 2)
# create parameters
parameters = paddle.parameters.create(crf_cost)
parameters.set('emb', word_vector_values)
# create optimizer
optimizer = paddle.optimizer.Momentum(
momentum=0,
learning_rate=2e-4,
regularization=paddle.optimizer.L2Regularization(rate=8e-4),
gradient_clipping_threshold=25,
model_average=paddle.optimizer.ModelAverage(
average_window=0.5, max_average_window=10000), )
trainer = paddle.trainer.SGD(
cost=crf_cost,
parameters=parameters,
update_equation=optimizer,
extra_layers=crf_dec)
reader = paddle.batch(
paddle.reader.shuffle(data_reader, buf_size=8192), batch_size=64)
feeding = {'word': 0, 'mark': 1, 'target': 2}
def event_handler(event):
if isinstance(event, paddle.event.EndIteration):
if event.batch_id % 100 == 0:
print "Pass %d, Batch %d, Cost %f, %s" % (
event.pass_id, event.batch_id, event.cost, event.metrics)
if event.batch_id % 1000 == 0:
result = trainer.test(reader=reader, feeding=feeding)
print "\nTest with Pass %d, Batch %d, %s" % (
event.pass_id, event.batch_id, result.metrics)
if isinstance(event, paddle.event.EndPass):
# save parameters
with gzip.open('params_pass_%d.tar.gz' % event.pass_id, 'w') as f:
parameters.to_tar(f)
result = trainer.test(reader=reader, feeding=feeding)
print "\nTest with Pass %d, %s" % (event.pass_id, result.metrics)
trainer.train(
reader=reader,
event_handler=event_handler,
num_passes=num_passes,
feeding=feeding)
return parameters
def ner_net_infer(data_reader=test_data_reader, model_file='ner_model.tar.gz'):
test_data = []
test_sentences = []
for item in data_reader():
test_data.append([item[0], item[1]])
test_sentences.append(item[-1])
if len(test_data) == 10:
break
predict = ner_net(is_train=False)
lab_ids = paddle.infer(
output_layer=predict,
parameters=paddle.parameters.Parameters.from_tar(gzip.open(model_file)),
input=test_data,
field='id')
flat_data = [word for word in itertools.chain.from_iterable(test_sentences)]
labels_reverse = {}
for (k, v) in label_dict.items():
labels_reverse[v] = k
pre_lab = [labels_reverse[lab_id] for lab_id in lab_ids]
for word, label in zip(flat_data, pre_lab):
print word, label
if __name__ == '__main__':
paddle.init(use_gpu=False, trainer_count=1)
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')
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册