本人仅以 PaddlePaddle 深度学习 101 官网教程为指导,添加个人理解和笔记,仅作为学习练习使用,若有错误,还望批评指教。–ZJ
原文地址: PaddlePaddle 官网| PaddlePaddle 深度学习 101
环境:
- Python 2.7
- Ubuntu 16.04
本教程源代码目录在book/word2vec, 初次使用请参考PaddlePaddle安装教程,更多内容请参考本教程的视频课堂。
本章我们介绍词的向量表征,也称为 word embedding。
在这些互联网服务里,我们经常要比较两个词或者两段文本之间的相关性。为了做这样的比较,我们往往先要把词表示成计算机适合处理的方式。
One-hot vector虽然自然,但是用处有限。
比如,在互联网广告系统里,如果用户输入的 query 是“母亲节”,而有一个广告的关键词是“康乃馨”。
虽然按照常理,我们知道这两个词之间是有联系的——母亲节通常应该送给母亲一束康乃馨;
但是这两个词对应的one-hot vectors之间的距离度量,无论是欧氏距离还是余弦相似度(cosine similarity),由于其向量正交,都认为这两个词毫无相关性。
在机器学习领域里,各种“知识”被各种模型表示,词向量模型(word embedding model) 就是其中的一类。通过词向量模型可将一个 one-hot vector 映射到一个维度更低的实数向量(embedding vector),
如 embedding(母亲节)=[0.3,4.2,−1.5,...],embedding(康乃馨)=[0.2,5.6,−2.3,...] e m b e d d i n g ( 母 亲 节 ) = [ 0.3 , 4.2 , − 1.5 , . . . ] , e m b e d d i n g ( 康 乃 馨 ) = [ 0.2 , 5.6 , − 2.3 , . . . ] 。
在这个映射到的实数向量表示中,希望两个语义(或用法)上相似的词对应的词向量“更像”,这样如“母亲节”和“康乃馨”的对应词向量的余弦相似度就不再为零了。
词向量模型可以是概率模型、共生矩阵 (co-occurrence matrix) 模型或神经元网络模型。
在用神经网络求词向量之前,传统做法是统计一个词语的共生矩阵 X X 。 X X 是一个 |V|×|V| | V | × | V | 大小的矩阵, Xij X i j 表示在所有语料中,词汇表V
(vocabulary)中第 i 个词和第 j 个词同时出现的词数, |V| | V | 为词汇表的大小。对 X X 做矩阵分解(如奇异值分解,Singular Value Decomposition [5]),得到的 U U 即视为所有词的词向量:
但这样的传统做法有很多问题:
1) 由于很多词没有出现,导致矩阵极其稀疏,因此需要对词频做额外处理来达到好的矩阵分解效果;
2) 矩阵非常大,维度太高(通常达到 106∗106 10 6 ∗ 10 6 的数量级);
3) 需要手动去掉停用词(如 although, a,…),不然这些频繁出现的词也会影响矩阵分解的效果。
基于神经网络的模型不需要计算存储一个在全语料上统计的大表,而是通过学习语义信息得到词向量,因此能很好地解决以上问题。在本章里,我们将展示基于神经网络训练词向量的细节,以及如何用 PaddlePaddle 训练一个词向量模型。
本章中,当词向量训练好后,我们可以用数据可视化算法 t-SNE[4]画出词语特征在二维上的投影(如下图所示)。从图中可以看出,语义相关的词语(如a, the, these; big, huge)在投影上距离很近,语意无关的词(如 say, business; decision, japan)在投影上的距离很远。
另一方面,我们知道两个向量的余弦值在 [−1,1] [ − 1 , 1 ] 的区间内:
similarity: 0.899180685161
please input two words: big huge
please input two words: from company
similarity: -0.0997506977351
以上结果可以通过运行`calculate_dis.py`, 加载字典里的单词和对应训练特征结果得到,我们将在[应用模型](#应用模型)中详细描述用法。 ## 模型概览 在这里我们介绍三个训练词向量的模型:N-gram 模型,CBOW 模型和 Skip-gram 模型,它们的中心思想都是通过上下文得到一个词出现的概率。对于 N-gram 模型,我们会先介绍语言模型的概念,并在之后的[训练模型](#训练模型)中,带大家用 PaddlePaddle 实现它。而后两个模型,是近年来最有名的神经元词向量模型,由 Tomas Mikolov 在 Google 研发
训练数据 | 验证数据 | 测试数据 |
ptb.train.txt | ptb.valid.txt | ptb.test.txt |
42068句 | 3370句 | 3761句 |
本章训练的是 5-gram 模型,表示在 PaddlePaddle 训练时,每条数据的前 4 个词用来预测第 5个词。PaddlePaddle 提供了对应PTB数据集的 python 包paddle.dataset.imikolov
,自动做数据的下载与预处理,方便大家使用。
预处理会把数据集中的每一句话前后加上开始符号
以及结束符号
。然后依据窗口大小(本教程中为 5),从头到尾每次向右滑动窗口并生成一条数据。
如”I have a dream that one day” 一句提供了5条数据:
I have a dream
I have a dream that
have a dream that one
a dream that one day
dream that one day
最后,每个输入会按其单词次在字典里的位置,转化成整数的索引序列,作为 PaddlePaddle 的输入。
本配置的模型结构如下图所示:
首先,加载所需要的包:
import math
import paddle.v2 as paddle
然后,定义参数:
embsize = 32 # 词向量维度
hiddensize = 256 # 隐层维度
N = 5 # 训练5-Gram
用于保存和加载word_dict和embedding table的函数
# save and load word dict and embedding table
def save_dict_and_embedding(word_dict, embeddings):
with open("word_dict", "w") as f:
for key in word_dict:
f.write(key + " " + str(word_dict[key]) + "\n")
with open("embedding_table", "w") as f:
numpy.savetxt(f, embeddings, delimiter=',', newline='\n')
def load_dict_and_embedding():
word_dict = dict()
with open("word_dict", "r") as f:
for line in f:
key, value = line.strip().split(" ")
word_dict[key] = int(value)
embeddings = numpy.loadtxt("embedding_table", delimiter=",")
return word_dict, embeddings
接着,定义网络结构:
def wordemb(inlayer):
wordemb = paddle.layer.table_projection(
input=inlayer,
size=embsize,
param_attr=paddle.attr.Param(
name="_proj",
initial_std=0.001,
learning_rate=1,
l2_rate=0,
sparse_update=True))
return wordemb
paddle.init(use_gpu=False, trainer_count=3) # 初始化PaddlePaddle
word_dict = paddle.dataset.imikolov.build_dict()
dict_size = len(word_dict)
# 每个输入层都接受整形数据,这些数据的范围是[0, dict_size)
firstword = paddle.layer.data(
name="firstw", type=paddle.data_type.integer_value(dict_size))
secondword = paddle.layer.data(
name="secondw", type=paddle.data_type.integer_value(dict_size))
thirdword = paddle.layer.data(
name="thirdw", type=paddle.data_type.integer_value(dict_size))
fourthword = paddle.layer.data(
name="fourthw", type=paddle.data_type.integer_value(dict_size))
nextword = paddle.layer.data(
name="fifthw", type=paddle.data_type.integer_value(dict_size))
Efirst = wordemb(firstword)
Esecond = wordemb(secondword)
Ethird = wordemb(thirdword)
Efourth = wordemb(fourthword)
contextemb = paddle.layer.concat(input=[Efirst, Esecond, Ethird, Efourth])
hidden1 = paddle.layer.fc(input=contextemb,
size=hiddensize,
act=paddle.activation.Sigmoid(),
layer_attr=paddle.attr.Extra(drop_rate=0.5),
bias_attr=paddle.attr.Param(learning_rate=2),
param_attr=paddle.attr.Param(
initial_std=1. / math.sqrt(embsize * 8),
learning_rate=1))
|V|
个词的生成概率。predictword = paddle.layer.fc(input=hidden1,
size=dict_size,
bias_attr=paddle.attr.Param(learning_rate=2),
act=paddle.activation.Softmax())
classification_cost
函数。cost = paddle.layer.classification_cost(input=predictword, label=nextword)
然后,指定训练相关的参数:
parameters = paddle.parameters.create(cost)
adagrad = paddle.optimizer.AdaGrad(
learning_rate=3e-3,
regularization=paddle.optimizer.L2Regularization(8e-4))
trainer = paddle.trainer.SGD(cost, parameters, adagrad)
下一步,我们开始训练过程。paddle.dataset.imikolov.train()
和paddle.dataset.imikolov.test()
分别做训练和测试数据集。这两个函数各自返回一个reader——PaddlePaddle中的reader是一个Python函数,每次调用的时候返回一个Python generator。
paddle.batch
的输入是一个reader,输出是一个batched reader —— 在PaddlePaddle里,一个reader每次yield一条训练数据,而一个batched reader每次yield一个minbatch。
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 isinstance(event, paddle.event.EndPass):
result = trainer.test(
paddle.batch(
paddle.dataset.imikolov.test(word_dict, N), 32))
print "Pass %d, Testing metrics %s" % (event.pass_id, result.metrics)
with open("model_%d.tar"%event.pass_id, 'w') as f:
trainer.save_parameter_to_tar(f)
trainer.train(
paddle.batch(paddle.dataset.imikolov.train(word_dict, N), 32),
num_passes=100,
event_handler=event_handler)
Pass 0, Batch 0, Cost 7.870579, {'classification_error_evaluator': 1.0}, Testing metrics {'classification_error_evaluator': 0.999591588973999}
Pass 0, Batch 100, Cost 6.136420, {'classification_error_evaluator': 0.84375}, Testing metrics {'classification_error_evaluator': 0.8328699469566345}
Pass 0, Batch 200, Cost 5.786797, {'classification_error_evaluator': 0.8125}, Testing metrics {'classification_error_evaluator': 0.8328542709350586}
...
训练过程是完全自动的,event_handler里打印的日志类似如上所示:
经过30个pass,我们将得到平均错误率为classification_error_evaluator=0.735611。
训练完成之后,我们可以把词典和embedding table单独保存下来,后面可以直接使用
# save word dict and embedding table
embeddings = parameters.get("_proj").reshape(len(word_dict), embsize)
save_dict_and_embedding(word_dict, embeddings)
训练模型后,我们可以加载模型参数,用训练出来的词向量初始化其他模型,也可以将模型查看参数用来做后续应用。
PaddlePaddle训练出来的参数可以直接使用parameters.get()
获取出来。例如查看单词apple
的词向量,即为
embeddings = parameters.get("_proj").reshape(len(word_dict), embsize)
print embeddings[word_dict['apple']]
[-0.38961065 -0.02392169 -0.00093231 0.36301503 0.13538605 0.16076435
-0.0678709 0.1090285 0.42014077 -0.24119169 -0.31847557 0.20410083 0.04910378 0.19021918 -0.0122014 -0.04099389 -0.16924137 0.1911236
-0.10917275 0.13068172 -0.23079982 0.42699069 -0.27679482 -0.01472992 0.2069038 0.09005053 -0.3282454 0.12717034 -0.24218646 0.25304323
0.19072419 -0.24286366]
获得到的embedding为一个标准的numpy矩阵。我们可以对这个numpy矩阵进行修改,然后赋值回去。
def modify_embedding(emb):
# Add your modification here.
pass
modify_embedding(embeddings)
parameters.set("_proj", embeddings)
两个向量之间的距离可以用余弦值来表示,余弦值在 [−1,1] [ − 1 , 1 ] 的区间内,向量间余弦值越大,其距离越近。这里我们在calculate_dis.py
中实现不同词语的距离度量。
用法如下:
from scipy import spatial
emb_1 = embeddings[word_dict['world']]
emb_2 = embeddings[word_dict['would']]
print spatial.distance.cosine(emb_1, emb_2)
0.99375076448
本教程 由 PaddlePaddle 创作,采用 知识共享 署名-相同方式共享 4.0 国际 许可协议进行许可。