詞向量也稱(chēng)為詞嵌入,是指將詞轉(zhuǎn)換成為向量的形式。
為何需要詞向量
對(duì)于非結(jié)構(gòu)化的數(shù)據(jù):音頻,圖片,文字。前面兩種的數(shù)據(jù)存儲(chǔ)方式是天然高維和高密度的,而且數(shù)據(jù)天然的就非常具有實(shí)際意義(相近的數(shù)據(jù)表示顏色或者音頻接近),幾乎可以直接進(jìn)入模型進(jìn)行處理。但是對(duì)于文字來(lái)說(shuō)不同的詞如果采用類(lèi)似LabelEncoder來(lái)做的,不同的詞ID取值接近并不能有實(shí)際的意義表示。而如果采用類(lèi)似OneHot編碼則會(huì)導(dǎo)致向量維度過(guò)高(詞匯量少說(shuō)也要幾萬(wàn)),也過(guò)于稀疏,同時(shí)也依然難以在數(shù)值上表示出不同詞之間的關(guān)系。
所以我們希望能找到一種詞與向量的映射關(guān)系,使得向量維度不需要過(guò)大,而且詞向量在向量空間中所表示的點(diǎn)具有實(shí)際的意義,也就是相似含義的詞在空間中的距離更近。
Word2Vec就是一個(gè)可以達(dá)到上述要求的一種方法,它可以從原始文本(語(yǔ)料庫(kù))中讀取詞語(yǔ)然后生成詞向量。word2vec從實(shí)現(xiàn)方法來(lái)看分為兩個(gè)大的框架:一、Hierarchical Softmax模型框架;二、Negative Sampling模型框架。
Hierarchical Softmax模型框架
模型大致由輸入層、投影層和輸出層構(gòu)成。
其中Hierarchical Softmax模型的輸出層由語(yǔ)料庫(kù)中詞出現(xiàn)的頻數(shù)當(dāng)作權(quán)值構(gòu)造出的哈夫曼樹(shù)作為輸出。具體實(shí)現(xiàn)由CBOW模型(Continuous Bag-of-Words Model)或者Skip-gram模型來(lái)完成。
假設(shè)詞w的上下文窗口長(zhǎng)度skip_window為c,那么對(duì)于模型的每一次迭代計(jì)算有【w之前c個(gè)詞,w,w之后c個(gè)詞】
- CBOW模型實(shí)現(xiàn)
CBOW考慮的主要思想是要P( w | Context(w) )的概率最大化,所以接下來(lái)看CBOW模型主要就是看如何定義和計(jì)算這個(gè)概率。(當(dāng)然對(duì)于語(yǔ)言模型來(lái)說(shuō),實(shí)際的目標(biāo)函數(shù)通常是對(duì)語(yǔ)料庫(kù)中的每個(gè)詞的概率P( w | Context(w) )取對(duì)數(shù)再累加)
輸入層: 2c個(gè)詞向量
投影層:2c個(gè)詞向量的累加
輸出層:哈夫曼樹(shù)(重點(diǎn)是詞w所在的葉子節(jié)點(diǎn),以及w到根節(jié)點(diǎn)的路徑)
接下來(lái)就是重點(diǎn)了,也就是怎么計(jì)算P( w | Context(w) )
- 2c個(gè)上下文詞向量的累加,與根節(jié)點(diǎn)的參數(shù)(系數(shù)theta_0和常數(shù)項(xiàng)bias_0)計(jì)算概率P(0)
- 計(jì)算使用了Sigmoid函數(shù),假設(shè)計(jì)算結(jié)果為S(0)
- 根據(jù)上面計(jì)算得到的S0以及詞w所在路徑對(duì)應(yīng)的分支,決定P(0)=S(0)或者P0=1-S(0)
- 繼續(xù)使用2c個(gè)上下文詞向量的累加向量與路徑中下一個(gè)節(jié)點(diǎn)的參數(shù)計(jì)算概率P(1)
- 一直計(jì)算到w所在的葉子節(jié)點(diǎn)直接相連的上面那個(gè)節(jié)點(diǎn)P(h)
-
P( w | Context(w) ) = P(0) * P(1) * ... * P(h)
計(jì)算過(guò)程圖示(簡(jiǎn)書(shū)不好寫(xiě)公式,所以省略了很多公式記號(hào))
然后對(duì)P取對(duì)數(shù),求梯度,可以得到兩個(gè)部分的更新:
-
詞w的路徑中各個(gè)非葉節(jié)點(diǎn)的參數(shù)(系數(shù)theta_i和常數(shù)項(xiàng)bias_i)的更新
非葉節(jié)點(diǎn)的系數(shù)更新 -
詞w的上下文詞向量的更新(所有w的上下文窗內(nèi)詞統(tǒng)一更新)
所有w的上下文詞向量統(tǒng)一更新
最后當(dāng)把語(yǔ)料庫(kù)遍歷一遍或者幾遍后就得到了全部詞的詞向量。
- Skip-gram模型實(shí)現(xiàn)
Skip-gram考慮的主要思想是要P( Context(w) | w )的概率最大化,所以接下來(lái)看Skip-gram模型主要就是看如何定義和計(jì)算這個(gè)概率。(當(dāng)然對(duì)于語(yǔ)言模型來(lái)說(shuō),實(shí)際的目標(biāo)函數(shù)通常是對(duì)語(yǔ)料庫(kù)中的每個(gè)詞的概率P( Context(w) | w )取對(duì)數(shù)再累加)
輸入層:詞w的向量
投影層:依舊是詞w的向量
輸出層:哈夫曼樹(shù)(重點(diǎn)是詞w的上下文窗內(nèi)2c個(gè)詞所在的葉子節(jié)點(diǎn),以及各自到根節(jié)點(diǎn)的路徑)
接下來(lái)的重點(diǎn)就是怎么定義和計(jì)算P( Context(w) | w )
- 對(duì)于詞w的上下文窗內(nèi)的一個(gè)詞u(1):
- 計(jì)算P( u(1) | w ),其計(jì)算過(guò)程和前面的類(lèi)似,都是先計(jì)算詞向量w與u(1)到根路徑中非葉節(jié)點(diǎn)的系數(shù)Sigmoid函數(shù)值,然后根據(jù)每個(gè)具體的分支得到P(i),然后將P(i)累乘得到。
- 遍歷詞w的上下文窗內(nèi)的每個(gè)詞u(i),都計(jì)算得到P( u(i) | w )
-
P( Context(w) | w ) = P(u(1) | w) * P(u(2) | w) * ... * P(u(2c) | w)
計(jì)算過(guò)程簡(jiǎn)易圖示
有了P( Context(w) | w )的計(jì)算,就可以通過(guò)取對(duì)數(shù)然后求梯度來(lái)對(duì)兩個(gè)部分的參數(shù)更新:
-
每個(gè)上下文詞u(i)對(duì)應(yīng)的到根路徑的節(jié)點(diǎn)的參數(shù)的更新:
每個(gè)上下文詞u所在路徑的非葉節(jié)點(diǎn)參數(shù)更新 -
詞w的向量的更新:
中心詞w的詞向量更新
同樣,把語(yǔ)料庫(kù)遍歷幾遍后就可以得到全部詞的詞向量。
- 總結(jié)
- CBOW模型的一次更新是:輸入2c個(gè)詞向量的累加,然后對(duì)中心詞w上的路徑節(jié)點(diǎn)系數(shù)進(jìn)行更新,然后對(duì)所有的上下文詞的詞向量進(jìn)行整體一致更新。
- Skip-gram模型的一次更新是:輸入中心詞w的詞向量,然后對(duì)每個(gè)上下文詞u(i)所在的路徑上的節(jié)點(diǎn)系數(shù)進(jìn)行更新,然后對(duì)詞w的詞向量進(jìn)行單獨(dú)更新。
可見(jiàn)CBOW的一次更新計(jì)算量要小,Skip-gram模型計(jì)算量大,而且更新的系數(shù)也多。
Negative Sampling模型框架
Negative Sampling模型的輸出層顧名思義,由對(duì)指定詞的負(fù)采樣來(lái)作為輸出(與Hierarchical Softmax最大的不同就是用負(fù)采樣替代了哈夫曼樹(shù),這樣也就改變了條件概率的計(jì)算過(guò)程)。具體實(shí)現(xiàn)也是由兩個(gè)算法模型CBOW和Skip-gram來(lái)實(shí)現(xiàn)。
為了解決新加入的概念帶來(lái)的困擾,我們先看下負(fù)采樣
- 負(fù)采樣
負(fù)采樣的算法思路其實(shí)還是比較簡(jiǎn)單,就是利用不同詞在語(yǔ)料中出現(xiàn)的頻次多少來(lái)決定被采樣到的概率。
簡(jiǎn)單說(shuō)就是每個(gè)詞由一個(gè)線段構(gòu)成(線段的長(zhǎng)度由詞頻決定),所有的詞構(gòu)成一個(gè)大的線段,然后在這個(gè)總線段上用非常細(xì)的刻度來(lái)進(jìn)行劃分,采樣的時(shí)候就是在這個(gè)細(xì)刻度的劃分中隨機(jī)選取一個(gè),看其屬于哪個(gè)詞的線段內(nèi)就表示本次采用選到了哪個(gè)詞。
多說(shuō)一句,方便接下來(lái)的算法理解,其實(shí)負(fù)采樣的作用就是采用出一些“負(fù)”詞(與采樣詞不同即為負(fù)),使得原來(lái)在哈夫曼樹(shù)中需要用到的非葉節(jié)點(diǎn)參數(shù)以及分支選擇的地方都替換成負(fù)采樣出來(lái)的詞。
- CBOW模型實(shí)現(xiàn)
輸入層: 2c個(gè)詞向量
投影層:2c個(gè)詞向量的累加
輸出層:負(fù)采樣詞集(重點(diǎn)是詞w的負(fù)詞詞集的參數(shù)(θ),負(fù)詞的概率永遠(yuǎn)是1-Sigmoid函數(shù)值)
接下來(lái)我們考慮的重點(diǎn)不是P( w | Context(w) )而是替換成g( w ):
其中:
把上面的公式用通俗的語(yǔ)言表達(dá)就是:
- 輸入詞w的上下文詞向量的累加:X_w
- 對(duì)詞w進(jìn)行負(fù)采樣(得到一組非w的負(fù)詞)
- X_w與詞w的輔助向量(可訓(xùn)練的參數(shù)θ)的Sigmoid函數(shù)值越大越好
- X_w與詞w的負(fù)詞的輔助向量(可訓(xùn)練的參數(shù)θ)的Sigmoid函數(shù)值越小越好
也就是說(shuō)每個(gè)詞除了有自己的詞向量之外,還有一個(gè)輔助向量θ
有了g( w )的定義后,就可以計(jì)算梯度,然后更新兩個(gè)部分的參數(shù):
-
每個(gè)詞包括w及其采樣得到的負(fù)詞詞集的參數(shù)θ更新:
-
詞w的上下文詞向量的更新(所有上下文詞一致更新):
最后把語(yǔ)料庫(kù)遍歷幾遍后,就可以得到全部的詞向量。
- Skip-gram模型實(shí)現(xiàn)
輸入層:詞w的向量
投影層:依舊是詞w的向量
輸出層:每個(gè)上下文詞u的負(fù)采樣(重點(diǎn)是詞u的負(fù)詞詞集的參數(shù)(θ),負(fù)詞的概率永遠(yuǎn)是1-Sigmoid函數(shù)值)
接下來(lái)的重點(diǎn)不是P( Context(w) | w ),而是G
這里v(w)為詞w的詞向量。
通俗講解:
- 輸入詞w的詞向量
- 對(duì)于詞w的上下文詞u(i)進(jìn)行負(fù)采樣得到詞集z(i),計(jì)算g( u(i) )
- 計(jì)算P( z(i)_j | w ),z(i)_j = u(i)時(shí)使用v(w)和z的θ的Sigmoid函數(shù)值,否則1 - Sigmoid函數(shù)值
- g( u(i) ) = 對(duì)上面j的遍歷后的累積
- 對(duì)上下文詞u(i)進(jìn)行遍歷,得到每個(gè)g( u(i) ),最后累積就是G
有了G之后就可以計(jì)算梯度進(jìn)行參數(shù)更新:
- 每個(gè)負(fù)采樣出來(lái)的詞系數(shù)θ的更新
- 詞w的詞向量更新
同樣把語(yǔ)料庫(kù)遍歷幾遍后可以得到所有的詞向量。
算法整體總結(jié)
- Hierarchical Softmax主要是通過(guò)哈夫曼樹(shù)來(lái)計(jì)算,其中用到了非葉節(jié)點(diǎn)的系數(shù)θ。
- Negative Sampling主要是對(duì)詞進(jìn)行負(fù)采樣,其中每個(gè)詞除了有自己的詞向量外還有輔助向量系數(shù)θ。
- CBOW的思想是在Context(w)基礎(chǔ)上讓w的條件概率越大越好,輸入是w的上下文詞向量累加,更新也是上下文的詞向量一致更新。同時(shí)對(duì)于輔助向量θ的更新個(gè)數(shù)較少
- Skip-gram的思想是在w的條件上讓Context(w)的條件概率越大越好,輸入是w的詞向量,更新的也是w的詞向量。同時(shí)對(duì)輔助向量θ的更新個(gè)數(shù)較多
所以CBOW看起來(lái)更新的更平滑,適合小量文本集上的詞向量構(gòu)建,Skip-gram每次更新都更加有針對(duì)性,所以對(duì)于大文本集上表現(xiàn)更好。
接下來(lái)的TF實(shí)踐,主要使用的就是Negative Sampling框架下的Skip-gram算法。
TensorFlow實(shí)踐
在TensorFlow的教學(xué)文檔中有一個(gè)關(guān)于詞向量的基礎(chǔ)代碼實(shí)踐:word2vec_basic.py。接下來(lái)提到的代碼也是圍繞這個(gè)進(jìn)行。
-
- 讀取語(yǔ)料構(gòu)成輸入數(shù)據(jù)集。
- 讀語(yǔ)料文檔
讀取的細(xì)節(jié)代碼就不細(xì)究了,這里想說(shuō)下,因?yàn)檎Z(yǔ)料第一次是需要下載的,如果直接用代碼下載的話會(huì)比較慢,建議先用迅雷把文檔下載下來(lái)text8.zip. 然后放入
from tempfile import gettempdir gettempdir()
顯示的臨時(shí)文件夾中(程序中默認(rèn)在這個(gè)文件夾中尋找語(yǔ)料文件,也可以手動(dòng)修改成別的文件夾),再運(yùn)行代碼。
- 構(gòu)建輸入數(shù)據(jù)
def build_dataset(words, n_words): """Process raw inputs into a dataset.""" count = [['UNK', -1]] # 統(tǒng)計(jì)單詞和詞頻的二維列表:[[單詞,詞頻], ... ,[單詞,詞頻]] count.extend(collections.Counter(words).most_common(n_words - 1)) dictionary = dict() # 單詞和對(duì)應(yīng)的索引,不常出現(xiàn)的詞(排名在49999之后的),統(tǒng)統(tǒng)索引為0,用'NUK'表示 for word, _ in count: dictionary[word] = len(dictionary) data = list() # 語(yǔ)料中的單詞轉(zhuǎn)成索引的列表 unk_count = 0 for word in words: index = dictionary.get(word, 0) if index == 0: # dictionary['UNK'] unk_count += 1 data.append(index) count[0][1] = unk_count reversed_dictionary = dict(zip(dictionary.values(), dictionary.keys())) # 索引->單詞 return data, count, dictionary, reversed_dictionary
輸入:words是語(yǔ)料庫(kù)中的詞組list,以及n_words最大詞數(shù)目的限制。
輸出:data是語(yǔ)料文本中詞的Index的list,count是[[詞,詞頻], ... ,]組成的二維list,dictionary是詞->索引,reversed_dictionary是索引->詞。 - 為Skip-gram模型產(chǎn)生batch輸入
def generate_batch(batch_size, num_skips, skip_window):
global data_index
assert batch_size % num_skips == 0
assert num_skips <= 2 * skip_window
batch = np.ndarray(shape=(batch_size), dtype=np.int32)
labels = np.ndarray(shape=(batch_size, 1), dtype=np.int32)
span = 2 * skip_window + 1 # [ skip_window target skip_window ] 前后skip窗長(zhǎng)加上中心詞自己后的個(gè)數(shù)
buffer = collections.deque(maxlen=span) # 雙端隊(duì)列,并設(shè)置最大長(zhǎng)度
if data_index + span > len(data):
data_index = 0
buffer.extend(data[data_index:data_index + span]) # 接續(xù)上次讀入的位置,讀入span長(zhǎng)度的文本內(nèi)容
data_index += span
for i in range(batch_size // num_skips): # 分塊總共采樣batch_size個(gè),其中每塊隨機(jī)選取上下文的詞num_skips次,每一塊的中心詞固定
context_words = [w for w in range(span) if w != skip_window] # 得到不包含中心詞的位置索引[0,1,3,4],假如skip窗長(zhǎng)為2
words_to_use = random.sample(context_words, num_skips) # 得到隨機(jī)選取的作為上下文的詞的位置
for j, context_word in enumerate(words_to_use):
batch[i * num_skips + j] = buffer[skip_window] # 中心詞
labels[i * num_skips + j, 0] = buffer[context_word] # 上下文詞
if data_index == len(data):
buffer[:] = data[:span]
data_index = span
else:
buffer.append(data[data_index]) # 繼續(xù)向后讀入一個(gè)詞,相當(dāng)于讀取下一塊,中心詞也向后偏移一個(gè)
data_index += 1
# Backtrack a little bit to avoid skipping words in the end of a batch
data_index = (data_index + len(data) - span) % len(data)
return batch, labels
輸入:batch_size為一個(gè)batch的大小,num_skips為每個(gè)中心詞選取上下文詞的次數(shù)(要保證batch_size能整除num_skips,因?yàn)閎atch_size // num_skips是一個(gè)batch中會(huì)偏移向后取詞的個(gè)數(shù)),skip_window是中心詞的上下文詞的范圍(比如skip_window=2是指中心詞的前面2個(gè)詞和后面2個(gè)詞共4個(gè)詞作為這個(gè)中心詞的上下文詞集)
輸出:batch是shape=(batch_size,)的中心詞Index的np數(shù)組,labels是shape=(batch_size,1)的上下文詞Index的np數(shù)組
- 構(gòu)造Skip-gram模型
使用TF構(gòu)造任何模型核心都是定義計(jì)算loss的公式以及具體計(jì)算中使用的優(yōu)化方法。
- 構(gòu)造Skip-gram模型
with graph.as_default():
# Input data.
train_inputs = tf.placeholder(tf.int32, shape=[batch_size])
train_labels = tf.placeholder(tf.int32, shape=[batch_size, 1])
valid_dataset = tf.constant(valid_examples, dtype=tf.int32)
# Ops and variables pinned to the CPU because of missing GPU implementation
with tf.device('/cpu:0'):
# Look up embeddings for inputs.
embeddings = tf.Variable(
tf.random_uniform([vocabulary_size, embedding_size], -1.0, 1.0))
embed = tf.nn.embedding_lookup(embeddings, train_inputs)
# Construct the variables for the NCE loss
nce_weights = tf.Variable(
tf.truncated_normal([vocabulary_size, embedding_size],
stddev=1.0 / math.sqrt(embedding_size)))
nce_biases = tf.Variable(tf.zeros([vocabulary_size]))
# Compute the average NCE loss for the batch.
# tf.nce_loss automatically draws a new sample of the negative labels each
# time we evaluate the loss.
# Explanation of the meaning of NCE loss:
# http://mccormickml.com/2016/04/19/word2vec-tutorial-the-skip-gram-model/
loss = tf.reduce_mean(
tf.nn.nce_loss(weights=nce_weights,
biases=nce_biases,
labels=train_labels,
inputs=embed,
num_sampled=num_sampled,
num_classes=vocabulary_size))
# Construct the SGD optimizer using a learning rate of 1.0.
optimizer = tf.train.GradientDescentOptimizer(1.0).minimize(loss)
# Compute the cosine similarity between minibatch examples and all embeddings.
norm = tf.sqrt(tf.reduce_sum(tf.square(embeddings), 1, keep_dims=True))
normalized_embeddings = embeddings / norm
valid_embeddings = tf.nn.embedding_lookup(
normalized_embeddings, valid_dataset)
similarity = tf.matmul(
valid_embeddings, normalized_embeddings, transpose_b=True)
# Add variable initializer.
init = tf.global_variables_initializer()
-
tf.nn.embedding_lookup
這個(gè)函數(shù)功能是根據(jù)輸入的Index查找對(duì)應(yīng)的向量,然后返回Tensor出來(lái)。喂給下面的計(jì)算NCE損失作為Input用。-
tf.nn.nce_loss
這個(gè)函數(shù)是重中之重,因?yàn)樗苯幼鳛閘oss的計(jì)算(再套一個(gè)tf.reduce_mean而已)。理解這個(gè)函數(shù)需要用到上面我們講到Negative Sampling框架下的Skip-gram模型算法。
這里我們?cè)倩仡櫹耂kip-gram模型都用到了哪些變量來(lái)計(jì)算:- 中心詞w的詞向量
- 上下文詞u的輔助系數(shù)θ(以及bias)
- 對(duì)每個(gè)上下文詞u進(jìn)行負(fù)采樣得到的其他詞u_neg的輔助系數(shù)θ
然后我們?cè)倏聪潞瘮?shù)tf.nn.nce_loss都有哪些輸入:
- weights:shape=[vocabulary_size, embedding_size]的Tensor,是全部詞典的輔助系數(shù)θ
- biases:shape=[vocabulary_size]的Tensor,是全部詞典的偏置項(xiàng)bias
- labels:中心詞w對(duì)應(yīng)的上下文詞u的Index
- inputs:中心詞w的詞向量
- num_sampled:每次對(duì)于一個(gè)上下文詞要采樣多少個(gè)負(fù)詞
- num_classes:詞典的大小(詞的類(lèi)別個(gè)數(shù))
通過(guò)對(duì)比可以發(fā)現(xiàn)tf.nn.nce_loss的輸入正好涵蓋了前面講到Skip-gram模型時(shí)用到的計(jì)算變量。然后具體內(nèi)部的實(shí)現(xiàn)細(xì)節(jié)可以通過(guò)源碼或者查考其他資料。(與上面寫(xiě)的計(jì)算過(guò)程略有不同,除了計(jì)算Sigmoid的概率值之外,還計(jì)算了交叉熵?fù)p失,以及最后按行求和)
計(jì)算余弦相似度
最后還對(duì)詞向量做了正則化(方便后面計(jì)算余弦相似度,直接使用矩陣乘積即可,因?yàn)槌龜?shù)已經(jīng)被歸一化了),然后對(duì)隨機(jī)選取的詞與全字典進(jìn)行余弦相似度計(jì)算。另外需要注意的是不能用GPU來(lái)搭建模型,因?yàn)閠f.nn.sampled_softmax_loss使用了GPU不支持的op
-
- 開(kāi)始訓(xùn)練
with tf.Session(graph=graph) as session:
# We must initialize all variables before we use them.
init.run()
print('Initialized')
average_loss = 0
for step in xrange(num_steps):
batch_inputs, batch_labels = generate_batch(batch_size, num_skips, skip_window)
feed_dict = {train_inputs: batch_inputs, train_labels: batch_labels}
# We perform one update step by evaluating the optimizer op (including it
# in the list of returned values for session.run()
_, loss_val = session.run([optimizer, loss], feed_dict=feed_dict)
average_loss += loss_val
if step % 2000 == 0:
if step > 0:
average_loss /= 2000
# The average loss is an estimate of the loss over the last 2000 batches.
print('Average loss at step ', step, ': ', average_loss)
average_loss = 0
# Note that this is expensive (~20% slowdown if computed every 500 steps)
if step % 10000 == 0:
sim = similarity.eval()
for i in xrange(valid_size):
valid_word = reverse_dictionary[valid_examples[i]]
top_k = 8 # number of nearest neighbors
nearest = (-sim[i, :]).argsort()[1:top_k + 1]
log_str = 'Nearest to %s:' % valid_word
for k in xrange(top_k):
close_word = reverse_dictionary[nearest[k]]
log_str = '%s %s,' % (log_str, close_word)
print(log_str)
final_embeddings = normalized_embeddings.eval()
訓(xùn)練過(guò)程比較簡(jiǎn)單,就是從generate_batch中讀取數(shù)據(jù),然后設(shè)置好feed_dict后run得到loss,除了每隔2000打印一次平均loss外,還會(huì)每隔10000打印一次隨機(jī)選取的驗(yàn)證詞中余弦相似度最接近的詞語(yǔ)。
最后通過(guò)normalized_embeddings.eval()得到正則化后的詞向量final_embeddings
- 畫(huà)圖展示詞向量
def plot_with_labels(low_dim_embs, labels, filename):
assert low_dim_embs.shape[0] >= len(labels), 'More labels than embeddings'
plt.figure(figsize=(18, 18)) # in inches
for i, label in enumerate(labels):
x, y = low_dim_embs[i, :]
plt.scatter(x, y)
plt.annotate(label,
xy=(x, y),
xytext=(5, 2),
textcoords='offset points',
ha='right',
va='bottom')
plt.savefig(filename)
try:
# pylint: disable=g-import-not-at-top
from sklearn.manifold import TSNE
import matplotlib.pyplot as plt
tsne = TSNE(perplexity=30, n_components=2, init='pca', n_iter=5000, method='exact')
plot_only = 500
low_dim_embs = tsne.fit_transform(final_embeddings[:plot_only, :])
labels = [reverse_dictionary[i] for i in xrange(plot_only)]
plot_with_labels(low_dim_embs, labels, os.path.join(gettempdir(), 'tsne.png'))
except ImportError as ex:
print('Please install sklearn, matplotlib, and scipy to show embeddings.')
print(ex)
通過(guò)使用t-SNE降維,來(lái)畫(huà)圖展示詞向量: