# 使用 Skip-Gram 嵌入
在之前的秘籍中,我們在訓練模型之前決定了我們的文本嵌入。使用神經網絡,我們可以使嵌入值成為訓練過程的一部分。我們將探索的第一個這樣的方法叫做 Skip-Gram 嵌入。
## 做好準備
在此秘籍之前,我們沒有考慮與創建單詞嵌入相關的單詞順序。 2013 年初,Tomas Mikolov 和谷歌的其他研究人員撰寫了一篇關于創建解決這個問題的單詞嵌入的論文( [https://arxiv.org/abs/1301.3781](https://arxiv.org/abs/1301.3781) ),他們將他們的方法命名為 word2vec。
基本思想是創建捕獲單詞關系方面的單詞嵌入。我們試圖了解各種單詞如何相互關聯。這些嵌入可能如何表現的一些示例如下:
`king - man + woman = queen`
`India pale ale - hops + malt = stout`
如果我們只考慮它們之間的位置關系,我們可能會實現這樣的數字表示。如果我們能夠分析足夠大的相關文檔來源,我們可能會發現在我們的文本中,`king`,`man`和`queen`這兩個詞在彼此之間相互提及。如果我們也知道`man`和`woman`以不同的方式相關,那么我們可以得出結論,`man`是`king`,因為`woman`是`queen`,依此類推。
為了找到這樣的嵌入,我們將使用一個神經網絡來預測給定輸入字的周圍單詞。我們可以輕松地切換它并嘗試在給定一組周圍單詞的情況下預測目標單詞,但我們將從前面的方法開始。兩者都是 word2vec 過程的變體,但是從目標詞預測周圍詞(上下文)的前述方法稱為 Skip-Gram 模型。在下一個秘籍中,我們將實現另一個方法,從上下文預測目標詞,這稱為連續詞袋方法(CBOW):

圖 4:word2vec 的 Skip-Gram 實現的圖示。 Skip-Gram 預測目標詞的上下文窗口(每側窗口大小為 1)。
對于這個秘籍,我們將在康奈爾大學的一組電影評論數據上實現 Skip-Gram 模型( [http://www.cs.cornell.edu/people/pabo/movie-review-data/](http://www.cs.cornell.edu/people/pabo/movie-review-data/) ])。 word2vec 的 CBOW 方法將在下一個秘籍中實現。
## 操作步驟
對于這個秘籍,我們將創建幾個輔助函數。這些函數將加載數據,正則化文本,生成詞匯表并生成數據批量。只有在這之后我們才開始訓練我們的單詞嵌入。為了清楚起見,我們不是預測任何目標變量,而是我們將擬合單詞嵌入:
1. 首先,我們將加載必要的庫并啟動圖會話:
```py
import tensorflow as tf
import matplotlib.pyplot as plt
import numpy as np
import random
import os
import string
import requests
import collections
import io
import tarfile
import urllib.request
from nltk.corpus import stopwords
sess = tf.Session()
```
1. 然后我們聲明一些模型參數。我們將一次查看 50 對單詞嵌入(批量大小)。每個單詞的嵌入大小將是一個長度為 200 的向量,我們只考慮 10,000 個最常用的單詞(每隔一個單詞將被歸類為未知單詞)。我們將訓練 5 萬代,并每 500 代打印一次。然后我們將聲明一個我們將在 loss 函數中使用的`num_sampled`變量(我們將在后面解釋),并且我們還聲明了我們的 Skip-Gram 窗口大小。在這里,我們將窗口大小設置為 2,因此我們將查看目標每側的周圍兩個單詞。我們將通過名為`nltk`的 Python 包設置我們的停用詞。我們還想要一種方法來檢查我們的單詞嵌入是如何執行的,因此我們將選擇一些常見的電影評論單詞并從每 2,000 次迭代中打印出最近的鄰居單詞:
```py
batch_size = 50
embedding_size = 200
vocabulary_size = 10000
generations = 50000
print_loss_every = 500
num_sampled = int(batch_size/2)
window_size = 2
stops = stopwords.words('english')
print_valid_every = 2000
valid_words = ['cliche', 'love', 'hate', 'silly', 'sad']
```
1. 接下來,我們將聲明我們的數據加載函數,該函數會檢查以確保在下載之前我們沒有下載數據。否則,如果之前保存了數據,它將從磁盤加載數據。使用以下代碼執行此操作:
```py
def load_movie_data():
save_folder_name = 'temp'
pos_file = os.path.join(save_folder_name, 'rt-polarity.pos')
neg_file = os.path.join(save_folder_name, 'rt-polarity.neg')
# Check if files are already downloaded
if os.path.exists(save_folder_name):
pos_data = []
with open(pos_file, 'r') as temp_pos_file:
for row in temp_pos_file:
pos_data.append(row)
neg_data = []
with open(neg_file, 'r') as temp_neg_file:
for row in temp_neg_file:
neg_data.append(row)
else: # If not downloaded, download and save
movie_data_url = 'http://www.cs.cornell.edu/people/pabo/movie-review-data/rt-polaritydata.tar.gz'
stream_data = urllib.request.urlopen(movie_data_url)
tmp = io.BytesIO()
while True:
s = stream_data.read(16384)
if not s:
break
tmp.write(s)
stream_data.close()
tmp.seek(0)
tar_file = tarfile.open(fileobj=tmp, mode='r:gz')
pos = tar_file.extractfile('rt-polaritydata/rt-polarity.pos')
neg = tar_file.extractfile('rt-polaritydata/rt-polarity.neg')
# Save pos/neg reviews
pos_data = []
for line in pos:
pos_data.append(line.decode('ISO-8859-1').encode('ascii',errors='ignore').decode())
neg_data = []
for line in neg:
neg_data.append(line.decode('ISO-8859-1').encode('ascii',errors='ignore').decode())
tar_file.close()
# Write to file
if not os.path.exists(save_folder_name):
os.makedirs(save_folder_name)
# Save files
with open(pos_file, 'w') as pos_file_handler:
pos_file_handler.write(''.join(pos_data))
with open(neg_file, 'w') as neg_file_handler:
neg_file_handler.write(''.join(neg_data))
texts = pos_data + neg_data
target = [1]*len(pos_data) + [0]*len(neg_data)
return(texts, target)
texts, target = load_movie_data()
```
1. 接下來,我們將為文本創建正則化函數。此函數將輸入字符串列表并使其為小寫,刪除標點,刪除數字,刪除額外的空格,并刪除停用詞。使用以下代碼執行此操作:
```py
def normalize_text(texts, stops):
# Lower case
texts = [x.lower() for x in texts]
# Remove punctuation
texts = [''.join(c for c in x if c not in string.punctuation) for x in texts]
# Remove numbers
texts = [''.join(c for c in x if c not in '0123456789') for x in texts]
# Remove stopwords
texts = [' '.join([word for word in x.split() if word not in (stops)]) for x in texts]
# Trim extra whitespace
texts = [' '.join(x.split()) for x in texts]
return(texts)
texts = normalize_text(texts, stops)
```
1. 為了確保我們所有的電影評論都能提供信息,我們應該確保它們足夠長,以包含重要的單詞關系。我們會隨意將其設置為三個或更多單詞:
```py
target = [target[ix] for ix, x in enumerate(texts) if len(x.split()) > 2]
texts = [x for x in texts if len(x.split()) > 2]
```
1. 為了構建我們的詞匯表,我們將創建一個函數來創建一個帶有計數的單詞字典。任何不常見的詞都不會使我們的詞匯量大小被截止,將被標記為`RARE`。使用以下代碼執行此操作:
```py
def build_dictionary(sentences, vocabulary_size):
# Turn sentences (list of strings) into lists of words
split_sentences = [s.split() for s in sentences]
words = [x for sublist in split_sentences for x in sublist]
# Initialize list of [word, word_count] for each word, starting with unknown
count = [['RARE', -1]]
# Now add most frequent words, limited to the N-most frequent (N=vocabulary size)
count.extend(collections.Counter(words).most_common(vocabulary_size-1))
# Now create the dictionary
word_dict = {}
# For each word, that we want in the dictionary, add it, then make it the value of the prior dictionary length
for word, word_count in count:
word_dict[word] = len(word_dict)
return(word_dict)
```
1. 我們需要一個函數將一個句子列表轉換為單詞索引列表,我們可以將它們傳遞給嵌入查找函數。使用以下代碼執行此操作:
```py
def text_to_numbers(sentences, word_dict):
# Initialize the returned data
data = []
for sentence in sentences:
sentence_data = []
# For each word, either use selected index or rare word index
for word in sentence:
if word in word_dict:
word_ix = word_dict[word]
else:
word_ix = 0
sentence_data.append(word_ix)
data.append(sentence_data)
return data
```
1. 現在我們可以實際創建我們的字典并將我們的句子列表轉換為單詞索引列表:
```py
word_dictionary = build_dictionary(texts, vocabulary_size)
word_dictionary_rev = dict(zip(word_dictionary.values(), word_dictionary.keys()))
text_data = text_to_numbers(texts, word_dictionary)
```
1. 從前面的單詞字典中,我們可以查找我們在步驟 2 中選擇的驗證字的索引。使用以下代碼執行此操作:
```py
valid_examples = [word_dictionary[x] for x in valid_words]
```
1. 我們現在將創建一個將返回 Skip-Gram 批次的函數。我們想訓練一對單詞,其中一個單詞是訓練輸入(來自我們窗口中心的目標單詞),另一個單詞是從窗口中選擇的。例如,句子`the cat in the hat`可能導致(輸入,輸出)對,如下所示:(`the`,`in`),(`cat`,`in`),(`the`,`in` ),(`hat`,`in`)如果是目標詞,我們每個方向的窗口大小為 2:
```py
def generate_batch_data(sentences, batch_size, window_size, method='skip_gram'):
# Fill up data batch
batch_data = []
label_data = []
while len(batch_data) < batch_size:
# select random sentence to start
rand_sentence = np.random.choice(sentences)
# Generate consecutive windows to look at
window_sequences = [rand_sentence[max((ix-window_size),0):(ix+window_size+1)] for ix, x in enumerate(rand_sentence)]
# Denote which element of each window is the center word of interest
label_indices = [ix if ix<window_size else window_size for ix,x in enumerate(window_sequences)]
# Pull out center word of interest for each window and create a tuple for each window
if method=='skip_gram':
batch_and_labels = [(x[y], x[:y] + x[(y+1):]) for x,y in zip(window_sequences, label_indices)]
# Make it in to a big list of tuples (target word, surrounding word)
tuple_data = [(x, y_) for x,y in batch_and_labels for y_ in y]
else:
raise ValueError('Method {} not implmented yet.'.format(method))
# extract batch and labels
batch, labels = [list(x) for x in zip(*tuple_data)]
batch_data.extend(batch[:batch_size])
label_data.extend(labels[:batch_size])
# Trim batch and label at the end
batch_data = batch_data[:batch_size]
label_data = label_data[:batch_size]
# Convert to numpy array
batch_data = np.array(batch_data)
label_data = np.transpose(np.array([label_data]))
return batch_data, label_data
```
1. 我們現在可以初始化嵌入矩陣,聲明占位符,并初始化嵌入查找函數。使用以下代碼執行此操作:
```py
embeddings = tf.Variable(tf.random_uniform([vocabulary_size,
embedding_size], -1.0, 1.0))
# Create data/target placeholders
x_inputs = tf.placeholder(tf.int32, shape=[batch_size])
y_target = tf.placeholder(tf.int32, shape=[batch_size, 1])
valid_dataset = tf.constant(valid_examples, dtype=tf.int32)
# Lookup the word embedding:
embed = tf.nn.embedding_lookup(embeddings, x_inputs)
```
1. 損失函數應該是諸如`softmax`之類的東西,它計算預測錯誤單詞類別時的損失。但由于我們的目標有 10,000 個不同的類別,因此非常稀疏。這種稀疏性導致關于模型的擬合或收斂的問題。為了解決這個問題,我們將使用稱為噪聲對比誤差的損失函數。這種 NCE 損失函數通過預測單詞類與隨機噪聲預測將我們的問題轉化為二元預測。 `num_sampled`參數指定批量變成隨機噪聲的程度:
```py
nce_weights = tf.Variable(tf.truncated_normal([vocabulary_size,
embedding_size], stddev=1.0 / np.sqrt(embedding_size)))
nce_biases = tf.Variable(tf.zeros([vocabulary_size]))
loss = tf.reduce_mean(tf.nn.nce_loss(weights=nce_weights,
biases=nce_biases,
inputs=embed,
labels=y_target,
num_sampled=num_sampled,
num_classes=vocabulary_size))
```
1. 現在我們需要創建一種方法來查找附近的單詞到我們的驗證單詞。我們將通過計算驗證集和所有單詞嵌入之間的余弦相似性來完成此操作,然后我們可以為每個驗證字打印出最接近的單詞集。使用以下代碼執行此操作:
```py
norm = tf.sqrt(tf.reduce_sum(tf.square(embeddings), 1, keepdims=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)
```
1. 我們現在聲明我們的優化函數并初始化我們的模型變量:
```py
optimizer = tf.train.GradientDescentOptimizer(learning_rate=1.0).minimize(loss)
init = tf.global_variables_initializer()
sess.run(init)
```
1. 現在我們可以訓練我們的嵌入并在訓練期間打印損失和最接近我們驗證集的單詞。使用以下代碼執行此操作:
```py
loss_vec = []
loss_x_vec = []
for i in range(generations):
batch_inputs, batch_labels = generate_batch_data(text_data, batch_size, window_size)
feed_dict = {x_inputs : batch_inputs, y_target : batch_labels}
# Run the train step
sess.run(optimizer, feed_dict=feed_dict)
# Return the loss
if (i+1) % print_loss_every == 0:
loss_val = sess.run(loss, feed_dict=feed_dict)
loss_vec.append(loss_val)
loss_x_vec.append(i+1)
print("Loss at step {} : {}".format(i+1, loss_val))
# Validation: Print some random words and top 5 related words
if (i+1) % print_valid_every == 0:
sim = sess.run(similarity, feed_dict=feed_dict)
for j in range(len(valid_words)):
valid_word = word_dictionary_rev[valid_examples[j]]
top_k = 5 # number of nearest neighbors
nearest = (-sim[j, :]).argsort()[1:top_k+1]
log_str = "Nearest to {}:".format(valid_word)
for k in range(top_k):
close_word = word_dictionary_rev[nearest[k]]
log_str = "%s %s," % (log_str, close_word)
print(log_str)
```
> 在前面的代碼中,我們在調用`argsort`方法之前采用相似矩陣的否定。我們這樣做是因為我們想要找到從最高相似度值到最低相似度值的索引,而不是相反。
1. 這導致以下輸出:
```py
Loss at step 500 : 13.387781143188477
Loss at step 1000 : 7.240757465362549
Loss at step 49500 : 0.9395825862884521
Loss at step 50000 : 0.30323168635368347
Nearest to cliche: walk, intrigue, brim, eileen, dumber,
Nearest to love: plight, fiction, complete, lady, bartleby,
Nearest to hate: style, throws, players, fearlessness, astringent,
Nearest to silly: delivers, meow, regain, nicely, anger,
Nearest to sad: dizzying, variety, existing, environment, tunney,
```
## 工作原理
我們通過`Skip-Gram`方法在電影評論數據集上訓練了一個 word2vec 模型。我們下載了數據,將單詞轉換為帶有字典的索引,并將這些索引號用作嵌入查找,我們對其進行了訓練,以便附近的單詞可以相互預測。
## 更多
乍一看,我們可能期望驗證集的附近單詞集合是同義詞。事實并非如此,因為很少有同義詞實際上在句子中彼此相鄰。我們真正得到的是預測我們的數據集中哪些單詞彼此接近。我們希望使用這樣的嵌入將使預測更容易。
為了使用這些嵌入,我們必須使它們可重用并保存它們。我們將通過實現 CBOW 嵌入在下一個秘籍中執行此操作。
- TensorFlow 入門
- 介紹
- TensorFlow 如何工作
- 聲明變量和張量
- 使用占位符和變量
- 使用矩陣
- 聲明操作符
- 實現激活函數
- 使用數據源
- 其他資源
- TensorFlow 的方式
- 介紹
- 計算圖中的操作
- 對嵌套操作分層
- 使用多個層
- 實現損失函數
- 實現反向傳播
- 使用批量和隨機訓練
- 把所有東西結合在一起
- 評估模型
- 線性回歸
- 介紹
- 使用矩陣逆方法
- 實現分解方法
- 學習 TensorFlow 線性回歸方法
- 理解線性回歸中的損失函數
- 實現 deming 回歸
- 實現套索和嶺回歸
- 實現彈性網絡回歸
- 實現邏輯回歸
- 支持向量機
- 介紹
- 使用線性 SVM
- 簡化為線性回歸
- 在 TensorFlow 中使用內核
- 實現非線性 SVM
- 實現多類 SVM
- 最近鄰方法
- 介紹
- 使用最近鄰
- 使用基于文本的距離
- 使用混合距離函數的計算
- 使用地址匹配的示例
- 使用最近鄰進行圖像識別
- 神經網絡
- 介紹
- 實現操作門
- 使用門和激活函數
- 實現單層神經網絡
- 實現不同的層
- 使用多層神經網絡
- 改進線性模型的預測
- 學習玩井字棋
- 自然語言處理
- 介紹
- 使用詞袋嵌入
- 實現 TF-IDF
- 使用 Skip-Gram 嵌入
- 使用 CBOW 嵌入
- 使用 word2vec 進行預測
- 使用 doc2vec 進行情緒分析
- 卷積神經網絡
- 介紹
- 實現簡單的 CNN
- 實現先進的 CNN
- 重新訓練現有的 CNN 模型
- 應用 StyleNet 和 NeuralStyle 項目
- 實現 DeepDream
- 循環神經網絡
- 介紹
- 為垃圾郵件預測實現 RNN
- 實現 LSTM 模型
- 堆疊多個 LSTM 層
- 創建序列到序列模型
- 訓練 Siamese RNN 相似性度量
- 將 TensorFlow 投入生產
- 介紹
- 實現單元測試
- 使用多個執行程序
- 并行化 TensorFlow
- 將 TensorFlow 投入生產
- 生產環境 TensorFlow 的一個例子
- 使用 TensorFlow 服務
- 更多 TensorFlow
- 介紹
- 可視化 TensorBoard 中的圖
- 使用遺傳算法
- 使用 k 均值聚類
- 求解常微分方程組
- 使用隨機森林
- 使用 TensorFlow 和 Keras