求近义词和类比词

“word2vec 的实现”一节中我们实现了如果使用余弦相似度来寻找近义词,并测试了在一个小规模数据集 PTB 上训练好的 word2vec 模型的效果。这一节我们将使用在大规模语料上预训练的词向量模型,并同时演示如何用它们来寻找类比词。首先导入实验所需的包或模块。

In [1]:
from mxnet import nd
from mxnet.contrib import text
from mxnet.gluon import nn

使用预训练的词向量

MXNet 的contrib.text提供了跟自然语言处理相关的函数和类。下面查看它目前提供的有预训练模型的词向量模型:

In [2]:
text.embedding.get_pretrained_file_names().keys()
Out[2]:
dict_keys(['glove', 'fasttext'])

给定一个模型,我们可以查看它提供了在哪些数据集上训练好的模型。

In [3]:
print(text.embedding.get_pretrained_file_names('glove'))
['glove.42B.300d.txt', 'glove.6B.50d.txt', 'glove.6B.100d.txt', 'glove.6B.200d.txt', 'glove.6B.300d.txt', 'glove.840B.300d.txt', 'glove.twitter.27B.25d.txt', 'glove.twitter.27B.50d.txt', 'glove.twitter.27B.100d.txt', 'glove.twitter.27B.200d.txt']

这里的命名规范大致是“模型.数据集词数.词向量长度.txt”,更多信息可以参考 GloVe [1]和 fastText [2]项目信息。下面我们使用数据集``glove.6B.50d.txt``,它是基于维基百科的一个子集。使用模型名和数据集名可以创建一个词向量实例,创建时会自动下载对应的预训练模型。

In [4]:
glove_6b50d = text.embedding.create(
    'glove', pretrained_file_name='glove.6B.50d.txt')

打印词典大小。其中含有 40 万个词,和一个特殊的未知词符号。

In [5]:
len(glove_6b50d)
Out[5]:
400001

我们可以通过词来得到它在词典中的索引,反之也可以。

In [6]:
glove_6b50d.token_to_idx['beautiful'], glove_6b50d.idx_to_token[3367]
Out[6]:
(3367, 'beautiful')

应用预训练词向量

下面我们以 GloVe 为例,展示预训练词向量的应用。首先,我们定义余弦相似度,并用它表示两个向量之间的相似度。

In [7]:
def cos_sim(x, y):
    return nd.dot(x, y) / (x.norm() * y.norm())

余弦相似度的值域在 -1 到 1 之间。两个余弦相似度越大的向量越相似。

In [8]:
x = nd.array([1, 2])
y = nd.array([10, 20])
z = nd.array([-1, -2])
cos_sim(x, y), cos_sim(x, z)
Out[8]:
(
 [1.]
 <NDArray 1 @cpu(0)>,
 [-1.]
 <NDArray 1 @cpu(0)>)

求近义词

这里重新实现“word2vec 的实现”一节中介绍过的使用余弦相似度来寻找近义词。首先定义一个通过余弦相似度来求 \(k\) 近邻。

In [9]:
def knn(W, x, k):
    cos = nd.dot(W, x.reshape((-1,))) / (
        nd.sum(W * W, axis=1).sqrt() * nd.sum(x * x).sqrt())
    topk = nd.topk(cos, k=k, ret_typ='indices').asnumpy().astype('int32')
    return topk, [cos[i].asscalar() for i in topk]

然后通过预训练好的模型寻找近义词。

In [10]:
def get_similar_tokens(query_token, k, embed):
    topk, cos = knn(embed.idx_to_vec,
                    embed.get_vecs_by_tokens([query_token]), k+2)
    for i, c in zip(topk[2:], cos[2:]):  # 除去输入词和未知词。
        print('similarity=%.3f: %s' % (c, (embed.idx_to_token[i])))

先试一下“chip”的近义词。

In [11]:
get_similar_tokens('chip', 3, glove_6b50d)
similarity=0.856: chips
similarity=0.749: intel
similarity=0.749: electronics

查找“baby”和“beautiful”的近义词。

In [12]:
get_similar_tokens('baby', 3, glove_6b50d)
similarity=0.839: babies
similarity=0.800: boy
similarity=0.792: girl
In [13]:
get_similar_tokens('beautiful', 3, glove_6b50d)
similarity=0.921: lovely
similarity=0.893: gorgeous
similarity=0.830: wonderful

求类比词

除了求近义词以外,我们还可以使用预训练词向量求词与词之间的类比关系。例如,man(男人): woman(女人):: son(儿子) : daughter(女儿)是一个类比例子:“man”之于“woman”相当于“son”之于“daughter”。求类比词问题可以定义为:对于类比关系中的四个词 \(a : b :: c : d\),给定前三个词 \(a\)\(b\)\(c\),求 \(d\)。设词 \(w\) 的词向量为 \(\text{vec}(w)\)。而解类比词的思路是,找到和 \(\text{vec}(c)+\text{vec}(b)-\text{vec}(a)\) 的结果向量最相似的词向量。

In [14]:
def get_analogy(token_a, token_b, token_c, embed):
    vecs = embed.get_vecs_by_tokens([token_a, token_b, token_c])
    x = vecs[1] - vecs[0] + vecs[2]
    topk, cos = knn(embed.idx_to_vec, x, 2)
    return embed.idx_to_token[topk[1]] # 除去未知词。

验证下“男 - 女”类比:

In [15]:
get_analogy('man', 'woman', 'son', glove_6b50d)
Out[15]:
'daughter'

“首都 - 国家”类比:“beijing”(北京)之于“china”(中国)相当于“tokyo”(东京)之于什么?答案应该是“japan”(日本)。

In [16]:
get_analogy('beijing', 'china', 'tokyo', glove_6b50d)
Out[16]:
'japan'

“形容词 - 形容词最高级”类比:“bad”(坏的)之于“worst”(最坏的)相当于“big”(大的)之于什么?答案应该是“biggest”(最大的)。

In [17]:
get_analogy('bad', 'worst', 'big', glove_6b50d)
Out[17]:
'biggest'

“动词一般时 - 动词过去时”类比:“do”(做)之于“did”(做过)相当于“go”(去)之于什么?答案应该是“went”(去过)。

In [18]:
get_analogy('do', 'did', 'go', glove_6b50d)
Out[18]:
'went'

小结

  • 我们可以应用预训练的词向量求近义词和类比词。

练习

  • 测试一下 fastText 的结果。特别的,fastText 有中文词向量(pretrained_file_name=’wiki.zh.vec’)。
  • 如果词典大小特别大,如何提升寻找速度?

扫码直达讨论区

参考文献

[1] GloVe 项目网站. https://nlp.stanford.edu/projects/glove/

[2] fastText 项目网站. https://fasttext.cc/