Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
PaddlePaddle
models
提交
4eb5346e
M
models
项目概览
PaddlePaddle
/
models
大约 1 年 前同步成功
通知
222
Star
6828
Fork
2962
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
602
列表
看板
标记
里程碑
合并请求
255
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
M
models
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
602
Issue
602
列表
看板
标记
里程碑
合并请求
255
合并请求
255
Pages
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
提交
Issue看板
提交
4eb5346e
编写于
6月 02, 2017
作者:
Z
zhaopu7
提交者:
GitHub
6月 02, 2017
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
Add files via upload
上级
5fea977f
变更
3
隐藏空白更改
内联
并排
Showing
3 changed file
with
330 addition
and
39 deletion
+330
-39
language_model/README.md
language_model/README.md
+249
-1
language_model/data_util.py
language_model/data_util.py
+50
-24
language_model/lm_ngram.py
language_model/lm_ngram.py
+31
-14
未找到文件。
language_model/README.md
浏览文件 @
4eb5346e
TBD
# 语言模型
## 简介
语言模型即 Language Model,简称LM,它是一个概率分布模型,简单来说,就是用来计算一个句子的概率的模型。给定句子(词语序列):
<br>
<div
align=
center
><img
src=
'img/s.png'
/></div>
它的概率可以表示为:
<br><br>
<div
align=
center
><img
src=
'img/ps.png'
/>
(式1)
</div>
语言模型可以计算(式1)中的P(S)及其中间结果。
**利用它可以确定哪个词序列的可能性更大,或者给定若干个词,可以预测下一个最可能出现的词语。**
## 应用场景
**语言模型被应用在多个领域**
,如:
*
**自动写作**
:语言模型可以根据上文生成下一个词,递归下去可以生成整个句子、段落、篇章。
*
**QA**
:语言模型可以根据Question生成Answer。
*
**机器翻译**
:当前主流的机器翻译模型大多基于Encoder-Decoder模式,其中Decoder就是一个语言模型,用来生成目标语言。
*
**拼写检查**
:语言模型可以计算出词语序列的概率,一般在拼写错误处序列的概率会骤减,可以用来识别拼写错误并提供改正候选集。
*
**词性标注、句法分析、语音识别......**
## 关于本例
Language Model 常见的实现方式有 N-Gram、RNN、seq2seq。本例中实现了基于N-Gram、RNN的语言模型。
**本例的文件结构如下**
:
*
data_util.py:实现了对语料的读取以及词典的建立、保存和加载。
*
lm_rnn.py:实现了基于rnn的语言模型的定义、训练以及做预测。
*
lm_ngram.py:实现了基于n-gram的语言模型的定义、训练以及做预测。
**注:**
*一般情况下基于N-Gram的语言模型不如基于RNN的语言模型效果好,所以实际使用时建议使用基于RNN的语言模型,本例中也将着重介绍基于RNN的模型,简略介绍基于N-Gram的模型。*
## RNN 语言模型
### 简介
RNN是一个序列模型,基本思路是:在时刻t,将前一时刻t-1的隐藏层输出h
<small>
t-1
</small>
和t时刻的词向量x
<small>
t
</small>
一起输入到隐藏层从而得到时刻t的特征表示h
<small>
t
</small>
,然后用这个特征表示得到t时刻的预测输出ŷ ,如此在时间维上递归下去,如下图所示:
<div
align=
center
><img
src=
'img/rnn_str.png'
width=
'500px'
/></div>
可以看出RNN善于使用上文信息、历史知识,具有“记忆”功能。理论上RNN能实现“长依赖”(即利用很久之前的知识),但在实际应用中发现效果并不理想,于是出现了很多RNN的变种,如常用的LSTM和GRU,它们对传统RNN的cell进行了改进,弥补了RNN的不足,下图是LSTM的示意图:
<div
align=
center
><img
src=
'img/lstm.png'
width=
'500px'
/></div>
本例中即使用了LSTM、GRU。
### 模型结构
lm_rnn.py 中的 lm() 函数定义了模型的结构。解析如下:
*
1,首先,在
\_\_
main
\_\_
中定义了模型的参数变量。
```python
# -- config : model --
rnn_type = 'gru' # or 'lstm'
emb_dim = 200
hidden_size = 200
num_passs = 2
num_layer = 2
```
其中 rnn\_type 用于配置rnn cell类型,可以取‘lstm’或‘gru’;hidden\_size配置unit个数;num\_layer配置RNN的层数;num\_passs配置训练的轮数;emb_dim配置embedding的dimension。
*
2,将输入的词(或字)序列映射成向量,即embedding。
```python
data = paddle.layer.data(name="word", type=paddle.data_type.integer_value_sequence(vocab_size))
target = paddle.layer.data("label", paddle.data_type.integer_value_sequence(vocab_size))
emb = paddle.layer.embedding(input=data, size=emb_dim)
```
*
3,根据配置实现RNN层,将上一步得到的embedding向量序列作为输入。
```python
if rnn_type == 'lstm':
rnn_cell = paddle.networks.simple_lstm(
input=emb, size=hidden_size)
for _ in range(num_layer - 1):
rnn_cell = paddle.networks.simple_lstm(
input=rnn_cell, size=hidden_size)
elif rnn_type == 'gru':
rnn_cell = paddle.networks.simple_gru(
input=emb, size=hidden_size)
for _ in range(num_layer - 1):
rnn_cell = paddle.networks.simple_gru(
input=rnn_cell, size=hidden_size)
```
*
4,实现输出层(使用softmax归一化计算单词的概率,将output结果返回)、定义模型的cost(多类交叉熵损失函数)。
```python
# fc and output layer
output = paddle.layer.fc(
input=[rnn_cell], size=vocab_size, act=paddle.activation.Softmax())
# loss
cost = paddle.layer.classification_cost(input=output, label=target)
```
### 训练模型
lm
\_
rnn.py 中的 train() 方法实现了模型的训练,流程如下:
*
1,准备输入数据:本例中使用的是标准PTB数据,调用data
\_
util.py中的build
\_
vocab()方法建立词典,并使用save
\_
vocab()方法将词典持久化,以备复用(当语料量大时生成词典比较耗时,所以这里把第一次生成的词典保存下来复用)。然后使用data
\_
util.py中的train
\_
data()、test
\_
data()方法建立train
\_
reader和test
\_
reader用来实现对train数据和test数据的读取。
*
2,初始化模型:包括模型的结构、参数、优化器(demo中使用的是Adam)以及训练器trainer。如下:
```python
# network config
cost, _ = lm(len(word_id_dict), emb_dim, rnn_type, hidden_size, num_layer)
# create parameters
parameters = paddle.parameters.create(cost)
# create optimizer
adam_optimizer = paddle.optimizer.Adam(
learning_rate=1e-3,
regularization=paddle.optimizer.L2Regularization(rate=1e-3),
model_average=paddle.optimizer.ModelAverage(average_window=0.5))
# create trainer
trainer = paddle.trainer.SGD(
cost=cost, parameters=parameters, update_equation=adam_optimizer)
```
*
3,定义回调函数event_handler来跟踪训练过程中loss的变化,并在每轮时结束保存模型的参数:
```python
# define event_handler callback
def event_handler(event):
if isinstance(event, paddle.event.EndIteration):
if event.batch_id % 100 == 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()
# save model each pass
if isinstance(event, paddle.event.EndPass):
result = trainer.test(reader=ptb_reader)
print("\nTest with Pass %d, %s" % (event.pass_id, result.metrics))
with gzip.open(model_file_name_prefix + str(event.pass_id) + '.tar.gz', 'w') as f:
parameters.to_tar(f)
```
*
4,开始train模型:
```
python
trainer
.
train
(
reader
=
ptb_reader
,
event_handler
=
event_handler
,
num_passes
=
num_passs
)
```
### 生成文本
lm
\_
rnn.py中的predict()方法实现了做prediction、生成文本。流程如下:
*
1,首先加载并缓存词典和模型,其中加载train好的模型参数方法如下:
```
python
parameters
=
paddle
.
parameters
.
Parameters
.
from_tar
(
gzip
.
open
(
model_file_name
))
```
*
2,生成文本,本例中生成文本的方式是启发式图搜索算法beam search,即lm
\_
rnn.py中的
\_
generate
\_
with
\_
beamSearch()方法。
### <font color='red'>使用此demo</font>
本例中使用的是标准的PTB数据,如果用户要实现自己的model,则只需要做如下适配工作:
#### 语料适配
*
清洗语料:去除空格、tab、乱码,根据需要去除数字、标点符号、特殊符号等。
*
编码格式:utf-8,本例中已经对中文做了适配。
*
内容格式:每个句子占一行;每行中的各词之间使用一个空格分开。
*
按需要配置lm
\_
rnn.py中
\_\_
main
\_\_
函数中对于data的配置:
```
python
# -- config : data --
train_file
=
'data/ptb.train.txt'
test_file
=
'data/ptb.test.txt'
vocab_file
=
'data/vocab_cn.txt'
# the file to save vocab
vocab_max_size
=
3000
min_sentence_length
=
3
max_sentence_length
=
60
```
其中,vocab
\_
max
\_
size定义了词典的最大长度,如果语料中出现的不同词的个数大于这个值,则根据各词的词频倒序排,取top(vocab
\_
max
\_
size)个词纳入词典。
*注:需要注意的是词典越大生成的内容越丰富但训练耗时越久,一般中文分词之后,语料中不同的词能有几万乃至几十万,如果vocab\_max\_size取值过小则导致\<UNK\>占比过高,如果vocab\_max\_size取值较大则严重影响训练速度(对精度也有影响),所以也有“按字”训练模型的方式,即:把每个汉字当做一个词,常用汉字也就几千个,使得字典的大小不会太大、不会丢失太多信息,但汉语中同一个字在不同词中语义相差很大,有时导致模型效果不理想。建议用户多试试、根据实际情况选择是“按词训练”还是“按字训练”。*
#### 模型适配
根据语料的大小按需调整模型的
\_\_
main
\_\_
中定义的参数。
然后运行 python lm_rnn.py即可训练模型、做prediction。
## n-gram 语言模型
n-gram模型也称为n-1阶马尔科夫模型,它有一个有限历史假设:当前词的出现概率仅仅与前面n-1个词相关。因此 (式1) 可以近似为:
<div
align=
center
><img
src=
'img/ps2.png'
/></div>
一般采用最大似然估计(Maximum Likelihood Estimation,MLE)的方法对模型的参数进行估计。当n取1、2、3时,n-gram模型分别称为unigram、bigram和trigram语言模型。一般情况下,n越大、训练语料的规模越大,参数估计的结果越可靠,但由于模型较简单、表达能力不强以及数据稀疏等问题。一般情况下用n-gram实现的语言模型不如rnn、seq2seq效果好。
### 模型结构
lm
\_
ngram.py中的lm()定义了模型的结构,大致如下:
*
1,demo中n取5,将前四个词分别做embedding,然后连接起来作为特征向量。
*
2,后接DNN的hidden layer。
*
3,将DNN的输出通过softmax layer做分类,得到下个词在词典中的概率分布。
*
4,模型的loss采用交叉熵,用Adam optimizer对loss做优化。
图示如下:
<div
align=
center
><img
src=
'img/ngram.png'
width=
'400px'
/></div>
### 模型训练
lm
\_
ngram.py中的train()方法实现了模型的训练,过程和RNN LM类似,简介如下:
*
1,准备输入数据:使用的是标准PTB数据,调用data
\_
util.py中的build
\_
vocab()方法建立词典,并使用save
\_
vocab()方法将词典持久化,使用data
\_
util.py中的train
\_
data()、test
\_
data()方法建立train
\_
reader和test
\_
reader用来实现对train数据和test数据的读取。
*
2,初始化模型:包括模型的结构、参数、优化器(demo中使用的是Adam)以及trainer。
*
3,定义回调函数event_handler来跟踪训练过程中loss的变化,并在每轮时结束保存模型的参数。
*
4,使用trainer开始train模型。
### 生成文本
lm
\_
ngram.py中的
\_\_
main
\_\_
方法中对prediction(生成文本)做了简单的实现。流程如下:
*
1,首先加载词典和模型:
```
python
# prepare model
word_id_dict
=
reader
.
load_vocab
(
vocab_file
)
# load word dictionary
_
,
output_layer
=
lm
(
len
(
word_id_dict
),
emb_dim
,
hidden_size
,
num_layer
)
# network config
model_file_name
=
model_file_name_prefix
+
str
(
num_passs
-
1
)
+
'.tar.gz'
parameters
=
paddle
.
parameters
.
Parameters
.
from_tar
(
gzip
.
open
(
model_file_name
))
# load parameters
```
*
2,根据4(n-1)个词的上文预测下一个单词并打印:
```python
# generate
text = 'the end of the' # use 4 words to predict the 5th word
input = [[word_id_dict.get(w, word_id_dict['<UNK>']) for w in text.split()]]
predictions = paddle.infer(
output_layer=output_layer,
parameters=parameters,
input=input,
field=['value'])
id_word_dict = dict([(v, k) for k, v in word_id_dict.items()]) # dictionary with type {id : word}
predictions[-1][word_id_dict['<UNK>']] = -1 # filter <UNK>
next_word = id_word_dict[np.argmax(predictions[-1])]
print(next_word.encode('utf-8'))
```
*注:这里展示了另一种做预测的方法,即使用paddle.infer方法。RNN的实例中使用的是paddle.inference.Inference接口。*
\ No newline at end of file
language_model/data_util.py
浏览文件 @
4eb5346e
# coding=utf-8
import
numpy
as
np
import
collections
import
os
# config
train_file
=
'data/ptb.train.txt'
test_file
=
'data/ptb.test.txt'
vocab_max_size
=
3000
min_sentence_length
=
3
max_sentence_length
=
60
# -- function --
def
build_vocab
():
def
save_vocab
(
word_id_dict
,
vocab_file_name
):
"""
save vocab.
:param word_id_dict: dictionary with content of '{word, id}', 'word' is string type , 'id' is int type.
:param vocab_file_name: vocab file name.
"""
f
=
open
(
vocab_file_name
,
'w'
)
for
(
k
,
v
)
in
word_id_dict
.
items
():
f
.
write
(
k
.
encode
(
'utf-8'
)
+
'
\t
'
+
str
(
v
)
+
'
\n
'
)
print
(
'save vocab to '
+
vocab_file_name
)
f
.
close
()
def
load_vocab
(
vocab_file_name
):
"""
load vocab from file
:param vocab_file_name: vocab file name.
:return: dictionary with content of '{word, id}', 'word' is string type , 'id' is int type.
"""
if
not
os
.
path
.
isfile
(
vocab_file_name
):
raise
Exception
(
'vocab file does not exist!'
)
dict
=
{}
for
line
in
open
(
vocab_file_name
):
if
len
(
line
)
<
2
:
continue
kv
=
line
.
decode
(
'utf-8'
).
strip
().
split
(
'
\t
'
)
dict
[
kv
[
0
]]
=
int
(
kv
[
1
])
return
dict
def
build_vocab
(
file_name
,
vocab_max_size
):
"""
build vacab.
:param vocab_max_size: vocab's max size.
:return: dictionary with content of '{word, id}', 'word' is string type , 'id' is int type.
"""
words
=
[]
for
line
in
open
(
train_fil
e
):
for
line
in
open
(
file_nam
e
):
words
+=
line
.
decode
(
'utf-8'
,
'ignore'
).
strip
().
split
()
counter
=
collections
.
Counter
(
words
)
...
...
@@ -29,16 +53,16 @@ def build_vocab():
word_id_dict
[
'<EOS>'
]
=
1
return
word_id_dict
def
_read_by_fixed_length
(
file_name
,
sentence_len
=
10
):
def
_read_by_fixed_length
(
file_name
,
word_id_dict
,
sentence_len
=
10
):
"""
create reader, each sample with fixed length.
:param file_name: file name.
:param word_id_dict: dictionary with content of '{word, id}', 'word' is string type , 'id' is int type.
:param sentence_len: each sample's length.
:return: data reader.
"""
def
reader
():
word_id_dict
=
build_vocab
()
words
=
[]
UNK
=
word_id_dict
[
'<UNK>'
]
for
line
in
open
(
file_name
):
...
...
@@ -53,15 +77,17 @@ def _read_by_fixed_length(file_name, sentence_len=10):
yield
ids
[
start
:
start
+
sentence_len
],
ids
[
start
+
1
:
start
+
sentence_len
+
1
]
return
reader
def
_read_by_line
(
file_name
):
def
_read_by_line
(
file_name
,
min_sentence_length
,
max_sentence_length
,
word_id_dict
):
"""
create reader, each line is a sample.
:param file_name: file name.
:param min_sentence_length: sentence's min length.
:param max_sentence_length: sentence's max length.
:param word_id_dict: dictionary with content of '{word, id}', 'word' is string type , 'id' is int type.
:return: data reader.
"""
def
reader
():
word_id_dict
=
build_vocab
()
UNK
=
word_id_dict
[
'<UNK>'
]
for
line
in
open
(
file_name
):
words
=
line
.
decode
(
'utf-8'
,
'ignore'
).
strip
().
split
()
...
...
@@ -74,17 +100,17 @@ def _read_by_line(file_name):
yield
ids
[:],
target
[:]
return
reader
def
_reader_creator_for_NGram
(
file_name
,
N
):
def
_reader_creator_for_NGram
(
file_name
,
N
,
word_id_dict
):
"""
create reader for ngram.
:param file_name: file name.
:param N: ngram's n.
:param word_id_dict: dictionary with content of '{word, id}', 'word' is string type , 'id' is int type.
:return: data reader.
"""
assert
N
>=
2
def
reader
():
word_id_dict
=
build_vocab
()
words
=
[]
UNK
=
word_id_dict
[
'<UNK>'
]
for
line
in
open
(
file_name
):
...
...
@@ -95,14 +121,14 @@ def _reader_creator_for_NGram(file_name, N):
yield
tuple
(
ids
[
i
:
i
+
N
])
return
reader
def
train_data
():
return
_read_by_line
(
train_file
)
def
train_data
(
train_file
,
min_sentence_length
,
max_sentence_length
,
word_id_dict
):
return
_read_by_line
(
train_file
,
min_sentence_length
,
max_sentence_length
,
word_id_dict
)
def
test_data
():
return
_read_by_line
(
test_file
)
def
test_data
(
test_file
,
min_sentence_length
,
max_sentence_length
,
word_id_dict
):
return
_read_by_line
(
test_file
,
min_sentence_length
,
max_sentence_length
,
word_id_dict
)
def
train_data_for_NGram
(
N
):
return
_reader_creator_for_NGram
(
train_file
,
N
)
def
train_data_for_NGram
(
train_file
,
N
,
word_id_dict
):
return
_reader_creator_for_NGram
(
train_file
,
N
,
word_id_dict
)
def
test_data_for_NGram
(
N
):
return
_reader_creator_for_NGram
(
test_file
,
N
)
def
test_data_for_NGram
(
test_file
,
N
,
word_id_dict
):
return
_reader_creator_for_NGram
(
test_file
,
N
,
word_id_dict
)
language_model/lm_ngram.py
浏览文件 @
4eb5346e
...
...
@@ -3,7 +3,7 @@ import sys
import
paddle.v2
as
paddle
import
data_util
as
reader
import
gzip
import
generate_text
as
generator
import
numpy
as
np
def
lm
(
vocab_size
,
emb_dim
,
hidden_size
,
num_layer
):
"""
...
...
@@ -13,7 +13,7 @@ def lm(vocab_size, emb_dim, hidden_size, num_layer):
:param emb_dim: embedding vector's dimension.
:param hidden_size: size of unit.
:param num_layer: layer number.
:return: cost and output of model.
:return: cost and output
layer
of model.
"""
assert
emb_dim
>
0
and
hidden_size
>
0
and
vocab_size
>
0
and
num_layer
>
0
...
...
@@ -73,19 +73,20 @@ def train():
:return: none, but this function will save the training model each epoch.
"""
# load word dictionary
print
(
'load dictionary...'
)
word_id_dict
=
reader
.
build_vocab
()
# prepare word dictionary
print
(
'prepare vocab...'
)
word_id_dict
=
reader
.
build_vocab
(
train_file
,
vocab_max_size
)
# build vocab
reader
.
save_vocab
(
word_id_dict
,
vocab_file
)
# save vocab
# define data reader
train_reader
=
paddle
.
batch
(
paddle
.
reader
.
shuffle
(
reader
.
train_data_for_NGram
(
N
),
buf_size
=
65536
),
reader
.
train_data_for_NGram
(
train_file
,
N
,
word_id_dict
),
buf_size
=
65536
),
batch_size
=
32
)
test_reader
=
paddle
.
batch
(
paddle
.
reader
.
shuffle
(
reader
.
test_data_for_NGram
(
N
),
buf_size
=
65536
),
reader
.
test_data_for_NGram
(
train_file
,
N
,
word_id_dict
),
buf_size
=
65536
),
batch_size
=
8
)
# network config
...
...
@@ -133,11 +134,9 @@ def train():
print
(
"Training finished."
)
if
__name__
==
'__main__'
:
# -- config --
paddle
.
init
(
use_gpu
=
False
,
trainer_count
=
1
)
# -- config : model --
emb_dim
=
200
hidden_size
=
200
num_passs
=
2
...
...
@@ -145,18 +144,36 @@ if __name__ == '__main__':
N
=
5
model_file_name_prefix
=
'lm_ngram_pass_'
# -- config : data --
train_file
=
'data/chinese.txt'
test_file
=
'data/chinese.txt'
vocab_file
=
'data/vocab_cn.txt'
# the file to save vocab
vocab_max_size
=
3000
min_sentence_length
=
3
max_sentence_length
=
60
# -- train --
paddle
.
init
(
use_gpu
=
False
,
trainer_count
=
1
)
train
()
# -- predict --
text
=
'the end of the'
# use 4 words to predict the 5th word
# prepare model
word_id_dict
=
reader
.
build_vocab
(
)
# load word dictionary
_
,
output
=
lm
(
len
(
word_id_dict
),
emb_dim
,
hidden_size
,
num_layer
)
# network config
word_id_dict
=
reader
.
load_vocab
(
vocab_file
)
# load word dictionary
_
,
output
_layer
=
lm
(
len
(
word_id_dict
),
emb_dim
,
hidden_size
,
num_layer
)
# network config
model_file_name
=
model_file_name_prefix
+
str
(
num_passs
-
1
)
+
'.tar.gz'
parameters
=
paddle
.
parameters
.
Parameters
.
from_tar
(
gzip
.
open
(
model_file_name
))
# load parameters
# generate
text
=
'the end of the'
input
=
[[
word_id_dict
.
get
(
w
,
word_id_dict
[
'<UNK>'
])
for
w
in
text
.
split
()]]
print
(
generator
.
next_word
(
output
,
parameters
,
word_id_dict
,
input
))
predictions
=
paddle
.
infer
(
output_layer
=
output_layer
,
parameters
=
parameters
,
input
=
input
,
field
=
[
'value'
])
id_word_dict
=
dict
([(
v
,
k
)
for
k
,
v
in
word_id_dict
.
items
()])
# dictionary with type {id : word}
predictions
[
-
1
][
word_id_dict
[
'<UNK>'
]]
=
-
1
# filter <UNK>
next_word
=
id_word_dict
[
np
.
argmax
(
predictions
[
-
1
])]
print
(
next_word
.
encode
(
'utf-8'
))
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录