Assignment2 word2vec的實現


首先實現word2vec.py中的sigmoid函數,和softmax、負采樣、skip-gram的損失函數和導數,接着實現sgd.py中的sgd優化器,最后運行run.py進行展示。

1word2vec.py

1.1sigmoid函數

 1 def sigmoid(x):
 2     """
 3     Compute the sigmoid function for the input here.
 4     Arguments:
 5     x -- A scalar or numpy array.
 6     Return:
 7     s -- sigmoid(x)
 8     """
 9     ### YOUR CODE HERE (~1 Line)
10     s = 1 / (1 + np.exp(-x))
11     ### END YOUR CODE
12     
13     return s

1.2naiveSoftmaxLossAndGradient函數

 1 def naiveSoftmaxLossAndGradient(centerWordVec, outsideWordIdx, outsideVectors, dataset):
 2     """ Naive Softmax loss & gradient function for word2vec models
 3 
 4     Implement the naive softmax loss and gradients between a center word's 
 5     embedding and an outside word's embedding. This will be the building block
 6     for our word2vec models.
 7 
 8     Arguments:
 9     centerWordVec -- numpy ndarray, center word's embedding
10                     in shape (word vector length, )
11                     (v_c in the pdf handout)
12     outsideWordIdx -- integer, the index of the outside word
13                     (o of u_o in the pdf handout)
14     outsideVectors -- outside vectors is
15                     in shape (num words in vocab, word vector length) 
16                     for all words in vocab (U in the pdf handout)
17     dataset -- needed for negative sampling, unused here.
18 
19     Return:
20     loss -- naive softmax loss
21     gradCenterVec -- the gradient with respect to the center word vector
22                      in shape (word vector length, )
23                      (dJ / dv_c in the pdf handout)
24     gradOutsideVecs -- the gradient with respect to all the outside word vectors
25                     in shape (num words in vocab, word vector length) 
26                     (dJ / dU)
27     """
28     ### YOUR CODE HERE (~6-8 Lines)
29 
30     ### Please use the provided softmax function (imported earlier in this file)
31     ### This numerically stable implementation helps you avoid issues pertaining to integer overflow. 
32     y_hat = softmax(np.dot(outsideVectors, centerWordVec.T))      #(num words, 1)
33     
34     loss = -np.log(y_hat[outsideWordIdx])
35     
36     y = y_hat.copy()
37     y[outsideWordIdx] -= 1
38     gradCenterVec = np.dot(y, outsideVectors)
39     gradOutsideVecs = np.dot(y[:, np.newaxis], centerWordVec[np.newaxis, :])
40     ### END YOUR CODE
41 
42     return loss, gradCenterVec, gradOutsideVecs

1.3negSamplingLossAndGradient函數

先實現getNegativeSamples函數,選取非outsideWordIdx的K個負樣本索引。

 1 def getNegativeSamples(outsideWordIdx, dataset, K):
 2     """ Samples K indexes which are not the outsideWordIdx """
 3 
 4     negSampleWordIndices = [None] * K
 5     for k in range(K):
 6         newidx = dataset.sampleTokenIdx()
 7         while newidx == outsideWordIdx:
 8             newidx = dataset.sampleTokenIdx()
 9         negSampleWordIndices[k] = newidx
10     return negSampleWordIndices

negSamplingLossAndGradient與naiveSoftmaxLossAndGradient不同的是:

  • 只選取K個非外圍詞(負樣本,可能有重復詞),外加1個正確的外圍詞,共K+1個輸出
  • 最后一層使用sigmoid輸出,而不是softmax

Tip:反向傳播得到的是這K+1個詞的梯度,所以需要挨個更新到梯度矩陣中去。

 1 def negSamplingLossAndGradient(centerWordVec, outsideWordIdx, outsideVectors, dataset, K=10):
 2     """ Negative sampling loss function for word2vec models
 3 
 4     Implement the negative sampling loss and gradients for a centerWordVec
 5     and a outsideWordIdx word vector as a building block for word2vec
 6     models. K is the number of negative samples to take.
 7 
 8     Note: The same word may be negatively sampled multiple times. For
 9     example if an outside word is sampled twice, you shall have to
10     double count the gradient with respect to this word. Thrice if
11     it was sampled three times, and so forth.
12 
13     Arguments/Return Specifications: same as naiveSoftmaxLossAndGradient
14     """
1516     # Negative sampling of words is done for you. Do not modify this if you
17     # wish to match the autograder and receive points!
18     negSampleWordIndices = getNegativeSamples(outsideWordIdx, dataset, K)    #選取非outsideWordIdx的K個負樣本索引
19     indices = [outsideWordIdx] + negSampleWordIndices                        #K+1個樣本索引
20 
21     ### YOUR CODE HERE (~10 Lines)
22     ### Please use your implementation of sigmoid in here.
23     gradCenterVec = np.zeros(centerWordVec.shape)
24     gradOutsideVecs = np.zeros(outsideVectors.shape)
25     loss = 0.0
26     
27     u_o = outsideVectors[outsideWordIdx]
28     u_k = outsideVectors[negSampleWordIndices]
29     z1 = sigmoid(np.dot(u_o, centerWordVec))         #z1.shape= ()
30     z2 = sigmoid(-np.dot(u_k, centerWordVec))        #z2.shape= (K,)
31     
32     loss = -np.log(z1) - np.sum(np.log(z2))
33     
34     gradCenterVec += (z1-1.0) * u_o + np.dot((1.0-z2), u_k) 
35     gradOutsideVecs[outsideWordIdx] += (z1-1.0) * centerWordVec
36     
37     for i, negSampleWordIdx in enumerate(negSampleWordIndices):
38         gradOutsideVecs[negSampleWordIdx] += (1.0 - z2[i]) * centerWordVec
39     ### END YOUR CODE
40 
41     return loss, gradCenterVec, gradOutsideVecs

1.4skipgram

遍歷所有的外圍詞,求和損失函數。

 1 def skipgram(currentCenterWord, windowSize, outsideWords, word2Ind,
 2              centerWordVectors, outsideVectors, dataset,
 3              word2vecLossAndGradient=naiveSoftmaxLossAndGradient):
 4     """ Skip-gram model in word2vec
 5 
 6     Implement the skip-gram model in this function.
 7 
 8     Arguments:
 9     currentCenterWord -- a string of the current center word
10     windowSize -- integer, context window size
11     outsideWords -- list of no more than 2*windowSize strings, the outside words
12     word2Ind -- a dictionary that maps words to their indices in
13               the word vector list
14     centerWordVectors -- center word vectors (as rows) is in shape 
15                         (num words in vocab, word vector length) 
16                         for all words in vocab (V in pdf handout)
17     outsideVectors -- outside vectors is in shape 
18                         (num words in vocab, word vector length) 
19                         for all words in vocab (U in the pdf handout)
20     word2vecLossAndGradient -- the loss and gradient function for
21                                a prediction vector given the outsideWordIdx
22                                word vectors, could be one of the two
23                                loss functions you implemented above.
24 
25     Return:
26     loss -- the loss function value for the skip-gram model
27             (J in the pdf handout)
28     gradCenterVec -- the gradient with respect to the center word vector
29                      in shape (word vector length, )
30                      (dJ / dv_c in the pdf handout)
31     gradOutsideVecs -- the gradient with respect to all the outside word vectors
32                     in shape (num words in vocab, word vector length) 
33                     (dJ / dU)
34     """
35     loss = 0.0
36     gradCenterVecs = np.zeros(centerWordVectors.shape)         #(num words, word vector length)
37     gradOutsideVectors = np.zeros(outsideVectors.shape)        #(num words, word vector length)
38 
39     ### YOUR CODE HERE (~8 Lines)
40     currentCenterWordIdx = word2Ind[currentCenterWord]            
41     centerWordVec = centerWordVectors[currentCenterWordIdx]    #中心詞向量
42     
43     for word in outsideWords:                           #遍歷外圍詞,依次計算損失和梯度
44         idx = word2Ind[word]
45         l, gradCenter, gradOutside = word2vecLossAndGradient(centerWordVec, idx, outsideVectors, dataset) 
46         
47         loss += l
48         gradCenterVecs[currentCenterWordIdx] += gradCenter
49         gradOutsideVectors += gradOutside
50         
51     ### END YOUR CODE
52     
53     return loss, gradCenterVecs, gradOutsideVectors

1.5功能測試

 1 #############################################
 2 # Testing functions below. DO NOT MODIFY!   #
 3 #############################################
 4 
 5 def word2vec_sgd_wrapper(word2vecModel, word2Ind, wordVectors, dataset, 
 6                          windowSize, word2vecLossAndGradient=naiveSoftmaxLossAndGradient):
 7     batchsize = 50
 8     loss = 0.0
 9     grad = np.zeros(wordVectors.shape)
10     N = wordVectors.shape[0]
11     centerWordVectors = wordVectors[:int(N/2),:]
12     outsideVectors = wordVectors[int(N/2):,:]
13     for i in range(batchsize):
14         windowSize1 = random.randint(1, windowSize)
15         centerWord, context = dataset.getRandomContext(windowSize1)
16 
17         c, gin, gout = word2vecModel(
18             centerWord, windowSize1, context, word2Ind, centerWordVectors,
19             outsideVectors, dataset, word2vecLossAndGradient
20         )
21         loss += c / batchsize
22         grad[:int(N/2), :] += gin / batchsize
23         grad[int(N/2):, :] += gout / batchsize
24 
25     return loss, grad
26 
27 
28 def test_word2vec():
29     """ Test the two word2vec implementations, before running on Stanford Sentiment Treebank """
30     dataset = type('dummy', (), {})()
31     def dummySampleTokenIdx():
32         return random.randint(0, 4)
33 
34     def getRandomContext(C):
35         tokens = ["a", "b", "c", "d", "e"]
36         return tokens[random.randint(0,4)], \
37             [tokens[random.randint(0,4)] for i in range(2*C)]
38     dataset.sampleTokenIdx = dummySampleTokenIdx
39     dataset.getRandomContext = getRandomContext
40 
41     random.seed(31415)
42     np.random.seed(9265)
43     dummy_vectors = normalizeRows(np.random.randn(10,3))
44     dummy_tokens = dict([("a",0), ("b",1), ("c",2),("d",3),("e",4)])
45 
46     print("==== Gradient check for skip-gram with naiveSoftmaxLossAndGradient ====")
47     gradcheck_naive(lambda vec: word2vec_sgd_wrapper(
48         skipgram, dummy_tokens, vec, dataset, 5, naiveSoftmaxLossAndGradient),
49         dummy_vectors, "naiveSoftmaxLossAndGradient Gradient")
50     
51     grad_tests_softmax(skipgram, dummy_tokens, dummy_vectors, dataset)
52 
53     print("==== Gradient check for skip-gram with negSamplingLossAndGradient ====")
54     gradcheck_naive(lambda vec: word2vec_sgd_wrapper(
55         skipgram, dummy_tokens, vec, dataset, 5, negSamplingLossAndGradient),
56         dummy_vectors, "negSamplingLossAndGradient Gradient")
57 
58     grad_tests_negsamp(skipgram, dummy_tokens, dummy_vectors, dataset, negSamplingLossAndGradient)
59 
60 
61 if __name__ == "__main__":
62     test_word2vec()

==== Gradient check for skip-gram with naiveSoftmaxLossAndGradient ====
Gradient check passed!. Read the docstring of the `gradcheck_naive` method in utils.gradcheck.py to understand what the gradient check does.
======Skip-Gram with naiveSoftmaxLossAndGradient Test Cases======
The first test passed!
The second test passed!
The third test passed!
All 3 tests passed!
==== Gradient check for skip-gram with negSamplingLossAndGradient ====
Gradient check passed!. Read the docstring of the `gradcheck_naive` method in utils.gradcheck.py to understand what the gradient check does.
======Skip-Gram with negSamplingLossAndGradient======
The first test passed!
The second test passed!
The third test passed!
All 3 tests passed!

2sgd.py

 1 def sgd(f, x0, step, iterations, postprocessing=None, useSaved=False, PRINT_EVERY=10):
 2     """ Stochastic Gradient Descent
 3 
 4     Implement the stochastic gradient descent method in this function.
 5 
 6     Arguments:
 7     f -- the function to optimize, it should take a single
 8          argument and yield two outputs, a loss and the gradient
 9          with respect to the arguments
10     x0 -- the initial point to start SGD from
11     step -- the step size for SGD
12     iterations -- total iterations to run SGD for
13     postprocessing -- postprocessing function for the parameters
14                       if necessary. In the case of word2vec we will need to
15                       normalize the word vectors to have unit length.
16     PRINT_EVERY -- specifies how many iterations to output loss
17 
18     Return:
19     x -- the parameter value after SGD finishes
20     """
21     # Anneal learning rate every several iterations
22     ANNEAL_EVERY = 20000
23 
24     if useSaved:
25         start_iter, oldx, state = load_saved_params()
26         if start_iter > 0:
27             x0 = oldx
28             step *= 0.5 ** (start_iter / ANNEAL_EVERY)
29 
30         if state:
31             random.setstate(state)
32     else:
33         start_iter = 0
34 
35     x = x0
36 
37     if not postprocessing:
38         postprocessing = lambda x: x
39 
40     exploss = None
41 
42     for iter in range(start_iter + 1, iterations + 1):
43         # You might want to print the progress every few iterations.
44 
45         loss = None
46         ### YOUR CODE HERE (~2 lines)
47         loss, gradient = f(x)
48         x = x - step * gradient
49         ### END YOUR CODE
50 
51         x = postprocessing(x)
52         if iter % PRINT_EVERY == 0:
53             if not exploss:
54                 exploss = loss
55             else:
56                 exploss = .95 * exploss + .05 * loss
57             print("iter %d: %f" % (iter, exploss))
58 
59         if iter % SAVE_PARAMS_EVERY == 0 and useSaved:
60             save_params(iter, x)
61 
62         if iter % ANNEAL_EVERY == 0:
63             step *= 0.5
64 
65     return x

3run.py

 1 import random
 2 import numpy as np
 3 from utils.treebank import StanfordSentiment
 4 import matplotlib
 5 matplotlib.use('agg')
 6 import matplotlib.pyplot as plt
 7 import time
 8 
 9 from word2vec import *
10 from sgd import *
11 
12 # Check Python Version
13 import sys
14 assert sys.version_info[0] == 3
15 assert sys.version_info[1] >= 5
16 
17 # Reset the random seed to make sure that everyone gets the same results
18 random.seed(314)
19 dataset = StanfordSentiment()
20 tokens = dataset.tokens()
21 nWords = len(tokens)
22 
23 # We are going to train 10-dimensional vectors for this assignment
24 dimVectors = 10
25 
26 # Context size
27 C = 5
28 
29 # Reset the random seed to make sure that everyone gets the same results
30 random.seed(31415)
31 np.random.seed(9265)
32 
33 startTime=time.time()
34 wordVectors = np.concatenate(
35     ((np.random.rand(nWords, dimVectors) - 0.5) /
36        dimVectors, np.zeros((nWords, dimVectors))),
37     axis=0)
38 wordVectors = sgd(
39     lambda vec: word2vec_sgd_wrapper(skipgram, tokens, vec, dataset, C,
40         negSamplingLossAndGradient),
41     wordVectors, 0.3, 40000, None, True, PRINT_EVERY=10)
42 # Note that normalization is not called here. This is not a bug,
43 # normalizing during training loses the notion of length.
44 
45 print("sanity check: cost at convergence should be around or below 10")
46 print("training took %d seconds" % (time.time() - startTime))
47 
48 # concatenate the input and output word vectors
49 wordVectors = np.concatenate(
50     (wordVectors[:nWords,:], wordVectors[nWords:,:]),
51     axis=0)
52 
53 visualizeWords = [
54     "great", "cool", "brilliant", "wonderful", "well", "amazing",
55     "worth", "sweet", "enjoyable", "boring", "bad", "dumb",
56     "annoying", "female", "male", "queen", "king", "man", "woman", "rain", "snow",
57     "hail", "coffee", "tea"]
58 
59 visualizeIdx = [tokens[word] for word in visualizeWords]
60 visualizeVecs = wordVectors[visualizeIdx, :]
61 temp = (visualizeVecs - np.mean(visualizeVecs, axis=0))
62 covariance = 1.0 / len(visualizeIdx) * temp.T.dot(temp)
63 U,S,V = np.linalg.svd(covariance)
64 coord = temp.dot(U[:,0:2])
65 
66 for i in range(len(visualizeWords)):
67     plt.text(coord[i,0], coord[i,1], visualizeWords[i],
68         bbox=dict(facecolor='green', alpha=0.1))
69 
70 plt.xlim((np.min(coord[:,0]), np.max(coord[:,0])))
71 plt.ylim((np.min(coord[:,1]), np.max(coord[:,1])))
72 
73 plt.savefig('word_vectors.png')

渣渣本跑不動,但loss的總體趨勢在減小。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM