Word Similarity and Analogy

:label:sec_synonyms

In :numref:sec_word2vec_pretraining, we trained a word2vec model on a small dataset, and applied it to find semantically similar words for an input word. In practice, word vectors that are pretrained on large corpora can be applied to downstream natural language processing tasks, which will be covered later in :numref:chap_nlp_app. To demonstrate semantics of pretrained word vectors from large corpora in a straightforward way, let us apply them in the word similarity and analogy tasks.

```{.python .input} from d2l import mxnet as d2l from mxnet import np, npx import os

npx.set_np()

  1. ```{.python .input}
  2. #@tab pytorch
  3. from d2l import torch as d2l
  4. import torch
  5. from torch import nn
  6. import os

Loading Pretrained Word Vectors

Below lists pretrained GloVe embeddings of dimension 50, 100, and 300, which can be downloaded from the GloVe website. The pretrained fastText embeddings are available in multiple languages. Here we consider one English version (300-dimensional “wiki.en”) that can be downloaded from the fastText website.

```{.python .input}

@tab all

@save

d2l.DATA_HUB[‘glove.6b.50d’] = (d2l.DATA_URL + ‘glove.6B.50d.zip’, ‘0b8703943ccdb6eb788e6f091b8946e82231bc4d’)

@save

d2l.DATA_HUB[‘glove.6b.100d’] = (d2l.DATA_URL + ‘glove.6B.100d.zip’, ‘cd43bfb07e44e6f27cbcc7bc9ae3d80284fdaf5a’)

@save

d2l.DATA_HUB[‘glove.42b.300d’] = (d2l.DATA_URL + ‘glove.42B.300d.zip’, ‘b5116e234e9eb9076672cfeabf5469f3eec904fa’)

@save

d2l.DATA_HUB[‘wiki.en’] = (d2l.DATA_URL + ‘wiki.en.zip’, ‘c1816da3821ae9f43899be655002f6c723e91b88’)

  1. To load these pretrained GloVe and fastText embeddings, we define the following `TokenEmbedding` class.
  2. ```{.python .input}
  3. #@tab all
  4. #@save
  5. class TokenEmbedding:
  6. """Token Embedding."""
  7. def __init__(self, embedding_name):
  8. self.idx_to_token, self.idx_to_vec = self._load_embedding(
  9. embedding_name)
  10. self.unknown_idx = 0
  11. self.token_to_idx = {token: idx for idx, token in
  12. enumerate(self.idx_to_token)}
  13. def _load_embedding(self, embedding_name):
  14. idx_to_token, idx_to_vec = ['<unk>'], []
  15. data_dir = d2l.download_extract(embedding_name)
  16. # GloVe website: https://nlp.stanford.edu/projects/glove/
  17. # fastText website: https://fasttext.cc/
  18. with open(os.path.join(data_dir, 'vec.txt'), 'r') as f:
  19. for line in f:
  20. elems = line.rstrip().split(' ')
  21. token, elems = elems[0], [float(elem) for elem in elems[1:]]
  22. # Skip header information, such as the top row in fastText
  23. if len(elems) > 1:
  24. idx_to_token.append(token)
  25. idx_to_vec.append(elems)
  26. idx_to_vec = [[0] * len(idx_to_vec[0])] + idx_to_vec
  27. return idx_to_token, d2l.tensor(idx_to_vec)
  28. def __getitem__(self, tokens):
  29. indices = [self.token_to_idx.get(token, self.unknown_idx)
  30. for token in tokens]
  31. vecs = self.idx_to_vec[d2l.tensor(indices)]
  32. return vecs
  33. def __len__(self):
  34. return len(self.idx_to_token)

Below we load the 50-dimensional GloVe embeddings (pretrained on a Wikipedia subset). When creating the TokenEmbedding instance, the specified embedding file has to be downloaded if it was not yet.

```{.python .input}

@tab all

glove_6b50d = TokenEmbedding(‘glove.6b.50d’)

  1. Output the vocabulary size. The vocabulary contains 400000 words (tokens) and a special unknown token.
  2. ```{.python .input}
  3. #@tab all
  4. len(glove_6b50d)

We can get the index of a word in the vocabulary, and vice versa.

```{.python .input}

@tab all

glove_6b50d.token_to_idx[‘beautiful’], glove_6b50d.idx_to_token[3367]

  1. ## Applying Pretrained Word Vectors
  2. Using the loaded GloVe vectors,
  3. we will demonstrate their semantics
  4. by applying them
  5. in the following word similarity and analogy tasks.
  6. ### Word Similarity
  7. Similar to :numref:`subsec_apply-word-embed`,
  8. in order to find semantically similar words
  9. for an input word
  10. based on cosine similarities between
  11. word vectors,
  12. we implement the following `knn`
  13. ($k$-nearest neighbors) function.
  14. ```{.python .input}
  15. def knn(W, x, k):
  16. # Add 1e-9 for numerical stability
  17. cos = np.dot(W, x.reshape(-1,)) / (
  18. np.sqrt(np.sum(W * W, axis=1) + 1e-9) * np.sqrt((x * x).sum()))
  19. topk = npx.topk(cos, k=k, ret_typ='indices')
  20. return topk, [cos[int(i)] for i in topk]

```{.python .input}

@tab pytorch

def knn(W, x, k):

  1. # Add 1e-9 for numerical stability
  2. cos = torch.mv(W, x.reshape(-1,)) / (
  3. torch.sqrt(torch.sum(W * W, axis=1) + 1e-9) *
  4. torch.sqrt((x * x).sum()))
  5. _, topk = torch.topk(cos, k=k)
  6. return topk, [cos[int(i)] for i in topk]
  1. Then, we
  2. search for similar words
  3. using the pretrained word vectors
  4. from the `TokenEmbedding` instance `embed`.
  5. ```{.python .input}
  6. #@tab all
  7. def get_similar_tokens(query_token, k, embed):
  8. topk, cos = knn(embed.idx_to_vec, embed[[query_token]], k + 1)
  9. for i, c in zip(topk[1:], cos[1:]): # Exclude the input word
  10. print(f'cosine sim={float(c):.3f}: {embed.idx_to_token[int(i)]}')

The vocabulary of the pretrained word vectors in glove_6b50d contains 400000 words and a special unknown token. Excluding the input word and unknown token, among this vocabulary let us find three most semantically similar words to word “chip”.

```{.python .input}

@tab all

get_similar_tokens(‘chip’, 3, glove_6b50d)

  1. Below outputs similar words
  2. to "baby" and "beautiful".
  3. ```{.python .input}
  4. #@tab all
  5. get_similar_tokens('baby', 3, glove_6b50d)

```{.python .input}

@tab all

get_similar_tokens(‘beautiful’, 3, glove_6b50d)

  1. ### Word Analogy
  2. Besides finding similar words,
  3. we can also apply word vectors
  4. to word analogy tasks.
  5. For example,
  6. man”:“woman”::“son”:“daughter
  7. is the form of a word analogy:
  8. man is to woman as son is to daughter”.
  9. Specifically,
  10. the word analogy completion task
  11. can be defined as:
  12. for a word analogy
  13. $a : b :: c : d$, given the first three words $a$, $b$ and $c$, find $d$.
  14. Denote the vector of word $w$ by $\text{vec}(w)$.
  15. To complete the analogy,
  16. we will find the word
  17. whose vector is most similar
  18. to the result of $\text{vec}(c)+\text{vec}(b)-\text{vec}(a)$.
  19. ```{.python .input}
  20. #@tab all
  21. def get_analogy(token_a, token_b, token_c, embed):
  22. vecs = embed[[token_a, token_b, token_c]]
  23. x = vecs[1] - vecs[0] + vecs[2]
  24. topk, cos = knn(embed.idx_to_vec, x, 1)
  25. return embed.idx_to_token[int(topk[0])] # Remove unknown words

Let us verify the “male-female” analogy using the loaded word vectors.

```{.python .input}

@tab all

get_analogy(‘man’, ‘woman’, ‘son’, glove_6b50d)

  1. Below completes a
  2. capital-country analogy:
  3. beijing”:“china”::“tokyo”:“japan”.
  4. This demonstrates
  5. semantics in the pretrained word vectors.
  6. ```{.python .input}
  7. #@tab all
  8. get_analogy('beijing', 'china', 'tokyo', glove_6b50d)

For the “adjective-superlative adjective” analogy such as “bad”:“worst”::“big”:“biggest”, we can see that the pretrained word vectors may capture the syntactic information.

```{.python .input}

@tab all

get_analogy(‘bad’, ‘worst’, ‘big’, glove_6b50d)

  1. To show the captured notion
  2. of past tense in the pretrained word vectors,
  3. we can test the syntax using the
  4. "present tense-past tense" analogy: do”:“did”::“go”:“went”.
  5. ```{.python .input}
  6. #@tab all
  7. get_analogy('do', 'did', 'go', glove_6b50d)

Summary

  • In practice, word vectors that are pretrained on large corpora can be applied to downstream natural language processing tasks.
  • Pretrained word vectors can be applied to the word similarity and analogy tasks.

Exercises

  1. Test the fastText results using TokenEmbedding('wiki.en').
  2. When the vocabulary is extremely large, how can we find similar words or complete a word analogy faster?

:begin_tab:mxnet Discussions :end_tab:

:begin_tab:pytorch Discussions :end_tab: