新聞中心
本文詳細(xì)介紹了 word2vector 模型的模型架構(gòu),以及 TensorFlow 的實(shí)現(xiàn)過程,包括數(shù)據(jù)準(zhǔn)備、建立模型、構(gòu)建驗(yàn)證集,并給出了運(yùn)行結(jié)果示例。

GitHub 鏈接:https://github.com/adventuresinML/adventures-in-ml-code
Word2Vec softmax 訓(xùn)練器
在接下來的教程中,我將解決的問題是該如何建立一個(gè)深度學(xué)習(xí)模型預(yù)測文本序列。然而,在建立模型之前,我們必須理解一些關(guān)鍵的自然語言處理(NLP)的思想。NLP 的關(guān)鍵思想之一是如何有效地將單詞轉(zhuǎn)換為數(shù)字向量,然后將這些數(shù)字向量「饋送」到機(jī)器學(xué)習(xí)模型中進(jìn)行預(yù)測。本教程將對現(xiàn)在使用的主要技術(shù),即「Word2Vec」進(jìn)行介紹。在討論了相關(guān)的背景材料之后,我們將使用 TensorFlow 實(shí)現(xiàn) Word2Vec 嵌入。要快速了解 TensorFlow,請查看我的 TensorFlow 教程:
http://adventuresinmachinelearning.com/python-tensorflow-tutorial/
我們?yōu)槭裁葱枰?Word2Vec
如果我們想把單詞輸入機(jī)器學(xué)習(xí)模型,除非使用基于樹的方法,否則需要把單詞轉(zhuǎn)換成一些數(shù)字向量。一種直接的方法是使用「獨(dú)熱編碼」方法將單詞轉(zhuǎn)換為稀疏表示,向量中只有一個(gè)元素設(shè)置為 1,其余為 0。我們構(gòu)建分類任務(wù)也采用了相同的方法——詳情請參考該教程:
http://adventuresinmachinelearning.com/neural-networks-tutorial/#setting-up-output
所以,我們可以使用如下的向量表示句子「The cat sat on the mat」:
我們在此將一個(gè)六個(gè)字的句子轉(zhuǎn)換為一個(gè) 6*5 的矩陣,其中 5 是詞匯量(「the」有重復(fù))。然而,在實(shí)際應(yīng)用中,我們希望深度學(xué)習(xí)模型能夠在詞匯量很大(10,000 字以上)的情況下進(jìn)行學(xué)習(xí)。從這里能看到使用「獨(dú)熱碼」表示單詞的效率問題——對這些詞匯建模的任何神經(jīng)網(wǎng)絡(luò)的輸入層至少都有 10,000 個(gè)節(jié)點(diǎn)。不僅如此,這種方法剝離了單詞的所有局部語境——也就是說它會去掉句子中(或句子之間)緊密相連的單詞的信息。
例如,我們可能想看到「United」和「States」靠得很近,或者是「Soviet」和「Union」,或者「食物」和「吃」等等。如果我們試圖以這種方法對自然語言建模,會丟失所有此類信息,這將是一個(gè)很大的疏漏。因此,我們需要使用更高效的方法表示文本數(shù)據(jù),而這種方法可以保存單詞的上下文的信息。這是 Word2Vec 方法發(fā)明的初衷。
Word2Vec 方法
如上文所述,Word2Vec 方法由兩部分組成。首先是將高維獨(dú)熱形式表示的單詞映射成低維向量。例如將 10,000 列的矩陣轉(zhuǎn)換為 300 列的矩陣。這個(gè)過程被稱為詞嵌入。第二個(gè)目標(biāo)是在保留單詞上下文的同時(shí),從一定程度上保留其意義。在 Word2Vec 方法中實(shí)現(xiàn)這兩個(gè)目標(biāo)的方法之一是,輸入一個(gè)詞,然后試著估計(jì)其他詞出現(xiàn)在該詞附近的概率,稱為 skip-gram 方法。還有一種與此相反的被稱為連續(xù)詞袋模型(Continuous Bag Of Words,CBOW)的方法——CBOW 將一些上下文詞語作為輸入,并通過評估概率找出最適合(概率***)該上下文的詞。在本教程中,我們將重點(diǎn)介紹 skip-gram 方法。
什么是 gram?gram 是一個(gè)有 n 個(gè)單詞的組(group),其中 n 是 gram 的窗口大小(window size)。因此,對「The cat sat on the mat」這句話來說,這句話用 3 個(gè) gram 表示的話,是「The cat sat」、「cat sat on」、「sat on the」、「on the mat」。「skip」指一個(gè)輸入詞在不同的上下文詞的情況下,在數(shù)據(jù)集中重復(fù)的次數(shù)(這點(diǎn)會在稍后陳述)。這些 gram 被輸入 Word2Vec 上下文預(yù)測系統(tǒng)。舉個(gè)例子,假設(shè)輸入詞是「cat」——Word2Vec 試圖從提供的輸入字中預(yù)測上下文(「the」,「sat」)。Word2Vec 系統(tǒng)將遍歷所有給出的 gram 和輸入的單詞,并嘗試學(xué)習(xí)適當(dāng)?shù)挠成湎蛄?嵌入),這些映射向量保證了在給定輸入單詞的情況下,正確的上下文單詞能得到更高概率。
什么是 Word2Vec 預(yù)測系統(tǒng)?不過是一種神經(jīng)網(wǎng)絡(luò)。
softmax Word2Vec 方法
從下圖考慮——在這種情況下,我們將假設(shè)「The cat sat on the mat」這個(gè)句子是一個(gè)文本數(shù)據(jù)庫的一部分,而這個(gè)文本數(shù)據(jù)庫的詞匯量非常大——有 10,000 個(gè)字。我們想將其減少到長度為 300 的嵌入。
Word2Vec softmax 訓(xùn)練器
如上表所示,如果我們?nèi)〕觥竎at」這個(gè)詞,它將成為 10,000 個(gè)詞匯中的一個(gè)單詞。因此我們可以將它表示成一個(gè)長度為 10,000 的獨(dú)熱向量。然后將這個(gè)輸入向量連接到一個(gè)具有 300 個(gè)節(jié)點(diǎn)的隱藏層。連接這個(gè)圖層的權(quán)重將成為新的詞向量。該隱藏層中的節(jié)點(diǎn)的激活是加權(quán)輸入的線性總和(不會使用如 sigmoid 或 tanh 這樣的非線性激活函數(shù))。此后這些節(jié)點(diǎn)會饋送到 softmax 輸出層。在訓(xùn)練過程中,我們想要改變這個(gè)神經(jīng)網(wǎng)絡(luò)的權(quán)重,使「cat」周圍的單詞在 softmax 輸出層中輸出的概率更高。例如,如果我們的文本數(shù)據(jù)集有許多蘇斯博士(Dr.Seuss)的書籍,我們希望通過神經(jīng)網(wǎng)絡(luò),像「the」,「sat」和「on」這樣的詞能得到更高概率(給出很多諸如「the cat sat on the mat」這樣的句子)。
通過訓(xùn)練這個(gè)網(wǎng)絡(luò),我們將創(chuàng)建一個(gè) 10,000*300 的權(quán)重矩陣,該矩陣使用有 300 個(gè)節(jié)點(diǎn)的隱藏層與長度為 10,000 的輸入相連接。該矩陣中的每一行都與有 10,000 詞匯的詞匯表的一個(gè)單詞相對應(yīng)——我們通過這種方式有效地將表示單詞的獨(dú)熱向量的長度由 10,000 減少至 300。實(shí)際上,該權(quán)重矩陣可以當(dāng)做查找或編碼單詞的總表。不僅如此,由于我們采用這種方式訓(xùn)練網(wǎng)絡(luò),這些權(quán)值還包含了上下文信息。一旦我們訓(xùn)練了網(wǎng)絡(luò),就意味著我們放棄了 softmax 層并使用 10,000 x 300 的權(quán)重矩陣作為我們的嵌入式查找表。
如何用代碼實(shí)現(xiàn)上述想法?
在 TensorFlow 中實(shí)現(xiàn) softmax Word2Vec 方法
與其他機(jī)器學(xué)習(xí)模型一樣,該網(wǎng)絡(luò)也有兩個(gè)組件——一個(gè)用于將所有數(shù)據(jù)轉(zhuǎn)換為可用格式,另一個(gè)則用于對數(shù)據(jù)進(jìn)行訓(xùn)練、驗(yàn)證和測試。在本教程中,我首先會介紹如何將數(shù)據(jù)收集成可用的格式,然后對模型的 TensorFlow 圖進(jìn)行討論。請注意,在 Github 中可找到本教程的完整代碼。在本例中,大部分代碼都是以這里的 TensorFlow Word2Vec 教程
(https://github.com/tensorflow/tensorflow/blob/r1.2/tensorflow/examples/tutorials/word2vec/word2vec_basic.py)為基礎(chǔ),并對其進(jìn)行了一些個(gè)人修改。
準(zhǔn)備文本數(shù)據(jù)
前面提到的 TensorFlow 教程有幾個(gè)函數(shù),這些函數(shù)可用于提取文本數(shù)據(jù)庫并對其進(jìn)行轉(zhuǎn)換,在此基礎(chǔ)上我們可以小批量(mini-batch)提取輸入詞及其相關(guān) gram,進(jìn)而用于訓(xùn)練 Word2Vec 系統(tǒng)。下面的內(nèi)容會依次介紹這些函數(shù):
- def maybe_download(filename, url, expected_bytes):
- """Download a file if not present, and make sure it's the right size."""
- if not os.path.exists(filename):
- filename, _ = urllib.request.urlretrieve(url + filename, filename)
- statinfo = os.stat(filename)
- if statinfo.st_size == expected_bytes:
- print('Found and verified', filename)
- else:
- print(statinfo.st_size)
- raise Exception('Failed to verify ' + filename + '. Can you get to it with a browser?')
- return filename
該函數(shù)用于檢查是否已經(jīng)從提供的 URL 下載了文件(代碼中的 filename)。如果沒有,使用 urllib.request Python 模塊(該模塊可從給定的 url 中檢索文件),并將該文件下載到本地代碼目錄中。如果文件已經(jīng)存在(即 os.path.exists(filename)返回結(jié)果為真),那么函數(shù)不會再下載文件。接下來,expected_bytes 函數(shù)會對文件大小進(jìn)行檢查,以確保下載文件與預(yù)期的文件大小一致。如果一切正常,將返回至用于提取數(shù)據(jù)的文件對象。為了在本例所用數(shù)據(jù)集中調(diào)用該函數(shù),我們執(zhí)行了下面的代碼:
- url = 'http://mattmahoney.net/dc/'
- filename = maybe_download('text8.zip', url, 31344016)
接下來我們要做的是取用指向已下載文件的文件對象,并使用 Python zipfile 模塊提取數(shù)據(jù)。
- # Read the data into a list of strings.def read_data(filename):"""Extract the first file enclosed in a zip file as a list of words."""with zipfile.ZipFile(filename) as f:
- data = tf.compat.as_str(f.read(f.namelist()[0])).split()return data
使用 zipfile.ZipFile()來提取壓縮文件,然后我們可以使用 zipfile 模塊中的讀取器功能。首先,namelist()函數(shù)檢索該檔案中的所有成員——在本例中只有一個(gè)成員,所以我們可以使用 0 索引對其進(jìn)行訪問。然后,我們使用 read()函數(shù)讀取文件中的所有文本,并傳遞給 TensorFlow 的 as_str 函數(shù),以確保文本保存為字符串?dāng)?shù)據(jù)類型。***,我們使用 split()函數(shù)創(chuàng)建一個(gè)列表,該列表包含文本文件中所有的單詞,并用空格字符分隔。我們可以在這里看到一些輸出:
- vocabulary = read_data(filename)print(vocabulary[:7])['anarchism', 'originated', 'as', 'a', 'term', 'of', 'abuse']
如我們所見,返回的詞匯數(shù)據(jù)包含一個(gè)清晰的單詞列表,將其按照原始文本文件的句子排序?,F(xiàn)在我們已經(jīng)提取了所有的單詞并置入列表,需要對其進(jìn)行進(jìn)一步的處理以創(chuàng)建 skip-gram 批量數(shù)據(jù)。處理步驟如下:
- 提取前 10000 個(gè)最常用的單詞,置入嵌入向量;
- 匯集所有單獨(dú)的單詞,并用唯一的整數(shù)對它們進(jìn)行索引——這一步等同于為單詞創(chuàng)建獨(dú)熱碼。我們將使用一個(gè)字典來完成這一步;
- 循環(huán)遍歷數(shù)據(jù)集中的每個(gè)單詞(詞匯變量),并將其分配給在步驟 2 中創(chuàng)建的***的整數(shù)。這使在單詞數(shù)據(jù)流中進(jìn)行查找或處理操作變得更加容易。
實(shí)現(xiàn)上述行為的代碼如下所示:
- def build_dataset(words, n_words):"""Process raw inputs into a dataset."""
- count = [['UNK', -1]]
- count.extend(collections.Counter(words).most_common(n_words - 1))
- dictdictionary = dict()for word, _ in count:
- dictionary[word] = len(dictionary)
- data = list()
- unk_count = 0for word in words:if word in dictionary:
- index = dictionary[word]else:
- 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
***步是設(shè)置一個(gè)「計(jì)數(shù)器」列表,該列表中存儲在數(shù)據(jù)集中找到一個(gè)單詞的次數(shù)。由于我們的詞匯量僅限于 10,000 個(gè)單詞,因此,不包括在前 10,000 個(gè)最常用單詞中的任何單詞都將標(biāo)記為「UNK」,表示「未知」。然后使用 Python 集合模塊和 Counter()類以及關(guān)聯(lián)的 most_common()函數(shù)對已初始化的計(jì)數(shù)列表進(jìn)行擴(kuò)展。這些設(shè)置用于計(jì)算給定參數(shù)(單詞)中的單詞數(shù)量,然后以列表格式返回 n 個(gè)最常見的單詞。
該函數(shù)的下一部分創(chuàng)建了一個(gè)字典,名為 dictionary,該字典由關(guān)鍵詞進(jìn)行填充,而這些關(guān)鍵詞與每個(gè)***的詞相對應(yīng)。分配給每個(gè)***的關(guān)鍵詞的值只是簡單地將字典的大小以整數(shù)形式進(jìn)行遞增。例如,將 1 賦值給***常用的單詞,2 賦值給第二常用的詞,3 賦值給第三常用的詞,依此類推(整數(shù) 0 被分配給「UNK」詞)。這一步給詞匯表中的每個(gè)單詞賦予了唯一的整數(shù)值——完成上述過程的第二步。
接下來,該函數(shù)將對數(shù)據(jù)集中的每個(gè)單詞進(jìn)行循環(huán)遍歷-——該數(shù)據(jù)集是由 read_data()函數(shù)輸出的。經(jīng)過這一步,我們創(chuàng)建了一個(gè)叫做「data」的列表,該列表長度與單詞量相同。但該列表不是由獨(dú)立單詞組成的單詞列表,而是個(gè)整數(shù)列表——在字典里由分配給該單詞的唯一整數(shù)表示每一個(gè)單詞。因此,對于數(shù)據(jù)集的***個(gè)句子 [『anarchism』, 『originated』, 『as』, 『a』, 『term』, 『of』, 『abuse』],現(xiàn)在在數(shù)據(jù)變量中是這樣的:[5242,3083,12,6,195,2,3136]。這解決了上述第三步。
***,該函數(shù)創(chuàng)建了一個(gè)名為 reverse_dictionary 的字典,它允許我們根據(jù)其唯一的整數(shù)標(biāo)識符來查找單詞,而非根據(jù)單詞查找標(biāo)識符。
建立數(shù)據(jù)的***一點(diǎn)在于,現(xiàn)在要創(chuàng)建一個(gè)包含輸入詞和相關(guān) gram 的數(shù)據(jù)集,這可用于訓(xùn)練 Word2Vec 嵌入系統(tǒng)。執(zhí)行這一步操作的代碼如下:
- data_index = 0# generate batch datadef generate_batch(data, batch_size, num_skips, skip_window):global data_index
- assert batch_size % num_skips == 0assert num_skips <= 2 * skip_window
- batch = np.ndarray(shape=(batch_size), dtype=np.int32)
- context = np.ndarray(shape=(batch_size, 1), dtype=np.int32)
- span = 2 * skip_window + 1 # [ skip_window input_word skip_window ]
- buffer = collections.deque(maxlen=span)for _ in range(span):
- buffer.append(data[data_index])
- data_index = (data_index + 1) % len(data)for i in range(batch_size // num_skips):
- target = skip_window # input word at the center of the buffer
- targets_to_avoid = [skip_window]for j in range(num_skips):while target in targets_to_avoid:
- target = random.randint(0, span - 1)
- targets_to_avoid.append(target)
- batch[i * num_skips + j] = buffer[skip_window] # this is the input word
- context[i * num_skips + j, 0] = buffer[target] # these are the context words
- buffer.append(data[data_index])
- data_index = (data_index + 1) % len(data)# 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, context
該函數(shù)會生成小批量數(shù)據(jù)用于我們的訓(xùn)練中(可在此了解小批量訓(xùn)練:http://adventuresinmachinelearning.com/stochastic-gradient-descent/)。這些小批量包括輸入詞(存儲在批量中)和 gram 中隨機(jī)關(guān)聯(lián)的上下文單詞,這些批量將作為標(biāo)簽對結(jié)果進(jìn)行預(yù)測(存儲在上下文中)。例如,在 gram 為 5 的「the cat sat on the」中,輸入詞即中心詞,也就是「sat」,并且將被預(yù)測的上下文將從這一 gram 的剩余詞中隨機(jī)抽?。篬『the 』,『cat』,『on』,『the』]。在該函數(shù)中,通過 num_skips 定義從上下文中隨機(jī)抽取的單詞數(shù)量。該函數(shù)會使用 skip_window 定義輸入詞周圍抽取的上下文單詞的窗口大小——在上述例子(「the cat sat on the」)中,輸入詞「sat」周圍的 skip_window 的寬度為 2。
在上述函數(shù)中,我們首先將批次和輸出標(biāo)簽定義為 batch_size 的變量。然后定義其廣度的大小(span size),這基本上就是我們要提取輸入詞和上下文的單詞列表的大小。在上述例子的子句「the cat on the」中,廣度是 5 = 2 * skip window + 1。此后還需創(chuàng)建一個(gè)緩沖區(qū):
- buffer = collections.deque(maxlen=span)for _ in range(span):
- buffer.append(data[data_index])
- data_index = (data_index + 1) % len(data)
這個(gè)緩沖區(qū)將會***程度地保留 span 元素,還是一種用于采樣的移動窗口。每當(dāng)有新的單詞索引添加至緩沖區(qū)時(shí),最左方的元素將從緩沖區(qū)中排出,以便為新的單詞索引騰出空間。輸入文本流中的緩沖器被存儲在全局變量 data_index 中,每當(dāng)緩沖器中有新的單詞進(jìn)入時(shí),data_index 遞增。如果到達(dá)文本流的末尾,索引更新的「%len(data)」組件會將計(jì)數(shù)重置為 0。
填寫批量處理和上下文變量的代碼如下所示:
- for i in range(batch_size // num_skips):
- target = skip_window # input word at the center of the buffer
- targets_to_avoid = [skip_window]for j in range(num_skips):while target in targets_to_avoid:
- target = random.randint(0, span - 1)
- targets_to_avoid.append(target)
- batch[i * num_skips + j] = buffer[skip_window] # this is the input word
- context[i * num_skips + j, 0] = buffer[target] # these are the context words
- buffer.append(data[data_index])
- data_index = (data_index + 1) % len(data)
選擇的***個(gè)詞「target」是單詞表最中間的詞,因此這是輸入詞。然后從單詞的 span 范圍中隨機(jī)選擇其他單詞,確保上下文中不包含輸入詞且每個(gè)上下文單詞都是唯一的。batch 變量會反映出重復(fù)的輸入詞(buffer [skip_window]),這些輸入詞會與 context 中的每個(gè)上下文單詞進(jìn)行匹配。
然后返回 batch 變量和 context 變量——現(xiàn)在我們有了從數(shù)據(jù)集中分出批量數(shù)據(jù)的方法。我們現(xiàn)在可以在 TensorFlow 中寫訓(xùn)練 Word2Vec 的代碼了。然而,在此之前,我們要先建立一個(gè)用于測試模型表現(xiàn)的驗(yàn)證集。我們通過測量向量空間中最接近的向量來建立驗(yàn)證集,并使用英語知識以確保這些詞確實(shí)是相似的。這將在下一節(jié)中進(jìn)行具體討論。不過我們可以先暫時(shí)使用另一種方法,從詞匯表最常用的詞中隨機(jī)提取驗(yàn)證單詞,代碼如下所示:
- # We pick a random validation set to sample nearest neighbors. Here we limit the# validation samples to the words that have a low numeric ID, which by# construction are also the most frequent.
- valid_size = 16 # Random set of words to evaluate similarity on.
- valid_window = 100 # Only pick dev samples in the head of the distribution.
- valid_examples = np.random.choice(valid_window, valid_size, replace=False)
上面的代碼從 0 到 100 中隨機(jī)選擇了 16 個(gè)整數(shù)——這些整數(shù)與文本數(shù)據(jù)中最常用的 100 個(gè)單詞的整數(shù)索引相對應(yīng)。我們將通過考察這些詞語來評估相關(guān)單詞與向量空間相關(guān)聯(lián)的過程在我們的學(xué)習(xí)模型中進(jìn)行得如何。到現(xiàn)在為止,我們可以建立 TensorFlow 模型了。
建立 TensorFlow 模型
接下來我將介紹在 TensorFlow 中建立 Word2Vec 詞嵌入器的過程。這涉及到什么內(nèi)容呢?簡單地說,我們需要建立我之前提出的神經(jīng)網(wǎng)絡(luò),該網(wǎng)絡(luò)在 TensorFlow 中使用詞嵌入矩陣作為隱藏層,還包括一個(gè)輸出 softmax 層。通過訓(xùn)練該模型,我們將通過學(xué)習(xí)得到***的詞嵌入矩陣,因此我們將通過學(xué)習(xí)得到一個(gè)簡化的、保留了上下文的單詞到向量的映射。
首先要做的是設(shè)置一些稍后要用的變量——設(shè)置這些變量的目的稍后會變得清楚:
- batch_size = 128
- embedding_size = 128 # Dimension of the embedding vector.
- skip_window = 1 # How many words to consider left and right.
- num_skips = 2 # How many times to reuse an input to generate a context.
接下來,我們設(shè)置一些 TensorFlow 占位符,這些占位符會保存輸入詞(的整數(shù)索引)和我們準(zhǔn)備預(yù)測的上下文單詞。我們還需要創(chuàng)建一個(gè)常量來保存 TensorFlow 中的驗(yàn)證集索引:
- 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)
接下來,我們需要設(shè)置嵌入矩陣變量或張量——這是使用 TensorFlow 中 embedding_lookup()函數(shù)最直接的方法,我會在下文對其進(jìn)行簡短地解釋:
- # 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)
上述代碼的***步是創(chuàng)建嵌入變量,這實(shí)際上是線性隱藏層連接的權(quán)重。我們用 -1.0 到 1 的隨機(jī)均勻分布對變量進(jìn)行初始化。變量大小包括 vocabulary_size 和 embedding_size。vocabulary_size 是上一節(jié)中用來設(shè)置數(shù)據(jù)的 10,000 個(gè)單詞。這是我們輸入的獨(dú)熱向量,在向量中僅有一個(gè)值為「1」的元素是當(dāng)前的輸入詞,其他值都為「0」。embedding_size 是隱藏層的大小,也是新的更小的單詞表示的長度。我們也考慮了可以把這個(gè)張量看作一個(gè)大的查找表——行是詞匯表中的每個(gè)詞,列是每個(gè)詞的新的向量表示。以下一個(gè)簡化的例子(使用虛擬值),其中 vocabulary_size = 7,embedding_size = 3:
正如我們所見,「anarchism」(實(shí)際上由一個(gè)整數(shù)或獨(dú)熱向量表示)現(xiàn)在表示為 [0.5,0.1,-0.1]。我們可以通過查找其整數(shù)索引、搜索嵌入行查找嵌入向量的方法「查找」anarchism:[0.5,0.1,-0.1]。
下面的代碼涉及到 tf.nn.embedding_lookup()函數(shù),在 TensorFlow 的此類任務(wù)中該函數(shù)是一個(gè)很有用的輔助函數(shù):它取一個(gè)整數(shù)索引向量作為輸入——在本例中是訓(xùn)練輸入詞的張量 train_input,并在已給的嵌入張量中「查找」這些索引。
因此,該命令將返回訓(xùn)練批次中每個(gè)給定輸入詞的當(dāng)前嵌入向量。完整的嵌入張量將在訓(xùn)練過程中進(jìn)行優(yōu)化。
接下來,我們必須創(chuàng)建一些權(quán)重和偏差值來連接輸出 softmax 層,并對其進(jìn)行運(yùn)算。如下所示:
- # Construct the variables for the softmax
- weights = tf.Variable(tf.truncated_normal([vocabulary_size, embedding_size],
- stddev=1.0 / math.sqrt(embedding_size)))
- biases = tf.Variable(tf.zeros([vocabulary_size]))
- hidden_out = tf.matmul(embed, tf.transpose(weights)) + biases
因?yàn)闄?quán)重變量連接著隱藏層和輸出層,因此其大小 size(out_layer_size,hidden_layer_size)=(vocabulary_size,embedding_size)。一如以往,偏差值是一維的,且大小與輸出層一致。然后,我們將嵌入變量與權(quán)重相乘(嵌入),再與偏差值相加。接下來可以做 softmax 運(yùn)算,并通過交叉熵?fù)p失函數(shù)來優(yōu)化模型的權(quán)值、偏差值和嵌入。我們將使用 TensorFlow 中的 softmax_cross_entropy_with_logits()函數(shù)簡化這個(gè)過程。然而,如果要使用該函數(shù)的話,我們首先要將上下文單詞和整數(shù)索引轉(zhuǎn)換成獨(dú)熱向量。下面的代碼不僅執(zhí)行了這兩步操作,還對梯度下降進(jìn)行了優(yōu)化:
- # convert train_context to a one-hot format
- train_one_hot = tf.one_hot(train_context, vocabulary_size)
- cross_entropy = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=hidden_out,
- labels=train_one_hot))# Construct the SGD optimizer using a learning rate of 1.0.
- optimizer = tf.train.GradientDescentOptimizer(1.0).minimize(cross_entropy)
接下來,我們需要執(zhí)行相似性評估以檢查模型訓(xùn)練時(shí)的表現(xiàn)。為了確定哪些詞彼此相似,我們需要執(zhí)行某種操作來測量不同詞的詞嵌入向量間的「距離」。在本例中,我們計(jì)算了余弦相似度以度量不同向量間的距離。定義如下:
公式中粗體字母**A**和**B**是需要測量距離的兩個(gè)向量。具有 2 個(gè)下標(biāo)(|| A || 2)的雙平行線是指向量的 L2 范數(shù)。為了得到向量的 L2 范數(shù),可以將向量的每個(gè)維數(shù)(在這種情況下,n = 300,我們的嵌入向量的寬度)平方對其求和后再取平方根:
在 TensorFlow 中計(jì)算余弦相似度的***方法是對每個(gè)向量進(jìn)行歸一化,如下所示:
然后,我們可以將這些歸一化向量相乘得到余弦相似度。我們將之前提過的驗(yàn)證向量或驗(yàn)證詞與嵌入向量中所有的單詞相乘,然后我們可以將之按降序進(jìn)行排列,以得到與驗(yàn)證詞最相似的單詞。
首先,我們分別使用 tf.square(),tf.reduce_sum()和 tf.sqrt()函數(shù)分別計(jì)算每個(gè)向量的 L2 范數(shù)的平方、和以及平方根:
- # convert train_context to a one-hot format
- train_one_hot = tf.one_hot(train_context, vocabulary_size)
- cross_entropy = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=hidden_out,
- labels=train_one_hot))# Construct the SGD optimizer using a learning rate of 1.0.
- optimizer = tf.train.GradientDescentOptimizer(1.0).minimize(cross_entropy)
然后我們就可以使用 tf.nn.embedding_lookup()函數(shù)查找之前提到的驗(yàn)證向量或驗(yàn)證詞:
- valid_embeddings = tf.nn.embedding_lookup(
- normalized_embeddings, valid_dataset)
我們向 embedding_lookup()函數(shù)提供了一個(gè)整數(shù)列表(該列表與我們的驗(yàn)證詞匯表相關(guān)聯(lián)),該函數(shù)對 normalized_embedding 張量按行進(jìn)行查找,返回一個(gè)歸一化嵌入的驗(yàn)證集的子集?,F(xiàn)在我們有了歸一化的驗(yàn)證集張量 valid_embeddings,可將其嵌入完全歸一化的詞匯表(normalized_embedding)以完成相似性計(jì)算:
- similarity = tf.matmul(
- valid_embeddings, normalized_embeddings, transpose_b=True)
該操作將返回一個(gè)(validation_size, vocabulary_size)大小的張量,該張量的每一行指代一個(gè)驗(yàn)證詞,列則指驗(yàn)證詞和詞匯表中其他詞的相似度。
運(yùn)行 TensorFlow 模型
下面的代碼對變量進(jìn)行了初始化并在訓(xùn)練循環(huán)中將初始化的變量饋送入每個(gè)數(shù)據(jù)批次中,每迭代 2,000 次后輸出一次平均損失值。如果在這段代碼中有不能理解的地方,請查看我的 TensorFlow 教程。
- with tf.Session(graphgraph=graph) as session:# We must initialize all variables before we use them.
- init.run()print('Initialized')
- average_loss = 0for step in range(num_steps):
- batch_inputs, batch_context = generate_batch(data,
- batch_size, num_skips, skip_window)
- feed_dict = {train_inputs: batch_inputs, train_context: batch_context}# 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, cross_entropy], feed_dictfeed_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
接下來,我們想要輸出與驗(yàn)證詞相似程度***的單詞——這一步需要通過調(diào)用上面定義的相似性運(yùn)算以及對結(jié)果進(jìn)行排序來達(dá)成(注意,由于計(jì)算量大,因此每迭代 10,000 次執(zhí)行一次該操作):
- # Note that this is expensive (~20% slowdown if computed every 500 steps)if step % 10000 == 0:
- sim = similarity.eval()for i in range(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 range(top_k):
- close_word = reverse_dictionary[nearest[k]]
- log_str = '%s %s,' % (log_str, close_word)print(log_str)
該函數(shù)首先計(jì)算相似性,即給每個(gè)驗(yàn)證詞返回一組余弦相似度的值。然后我們遍歷驗(yàn)證集中的每一個(gè)詞,使用 argsort()函數(shù)輸入相似度的負(fù)值,取前 8 個(gè)最接近的詞并按降序進(jìn)行排列。打印出這 8 個(gè)詞的代碼,我們就可以看到嵌入過程是如何執(zhí)行的了。
***,在完成所有的訓(xùn)練過程的所有迭代之后,我們可以將最終的嵌入結(jié)果定為一個(gè)單獨(dú)的張量供以后使用(比如其他深度學(xué)習(xí)或機(jī)器學(xué)習(xí)過程):
- final_embeddings = normalized_embeddings.eval()
現(xiàn)在我們完成了——真的完成了嗎?Word2Vec 的這個(gè) softmax 方法的代碼被放在了 Github 上——你可以試著運(yùn)行它,但我并不推薦。為什么?因?yàn)樗娴暮苈?/p>
提速——「真正的」Word2Vec 方法
事實(shí)上,使用 softmax 進(jìn)行評估和更新一個(gè)有 10,000 詞的輸出或詞匯表的權(quán)值是非常慢的。我們從 softmax 的定義考慮:
在我們正在處理的內(nèi)容中,softmax 函數(shù)將預(yù)測哪些詞在輸入詞的上下文中具有***的可能性。為了確定這個(gè)概率,softmax 函數(shù)的分母必須評估詞匯表中所有可能的上下文單詞。因此,我們需要 300 * 10,000 = 3M 的權(quán)重,所有這些權(quán)重都需要針對 softmax 輸出進(jìn)行訓(xùn)練。這會降低速度。
NCE(Noise Contrastive Estimation,噪聲對比估計(jì),
http://papers.nips.cc/paper/5021-distributed-representations-of-words-and-phrases-and-their-compositionality.pdf)的速度更快,可以作為替代方案。這個(gè)方法不是用上下文單詞相對于詞匯表中所有可能的上下文單詞的概率,而是隨機(jī)抽樣 2-20 個(gè)可能的上下文單詞,并僅從這些單詞中評估概率。在此不對細(xì)節(jié)進(jìn)行描述,但可以肯定的是,該方法可用于訓(xùn)練模型,且可大大加快訓(xùn)練進(jìn)程。
TensorFlow 已經(jīng)在此幫助過我們,并為我們提供了 NCE 損失函數(shù),即 tf.nn.nce_loss()。我們可以將權(quán)重和偏差變量輸入 tf.nn.nce_loss()。使用該函數(shù)和 NCE,迭代 100 次的時(shí)間從 softmax 的 25 秒減少到不到 1 秒。用以下內(nèi)容替換 softmax:
- # 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]))
- nce_loss = tf.reduce_mean(
- tf.nn.nce_loss(weights=nce_weights,
- biases=nce_biases,
- labels=train_context,
- inputs=embed,
- num_samplednum_sampled=num_sampled,
- num_classes=vocabulary_size))
- optimizer = tf.train.GradientDescentOptimizer(1.0).minimize(nce_loss)
現(xiàn)在我們可以運(yùn)行代碼了。如上所述,每迭代 10,000 次代碼輸出驗(yàn)證詞和 Word2Vec 系統(tǒng)得出的相似詞。您可以在下面看到隨機(jī)初始化和 50,000 次迭代標(biāo)記之間的某些選定驗(yàn)證詞的改進(jìn):
開始:
- 最接近 nine 的詞:heterosexual, scholarly, scandal, serves, humor, realized, cave, himself
- 最接近 this 的詞:contains, alter, numerous, harmonica, nickname, ghana, bogart, Marxist
迭代 10,000 次后:
- 最接近 nine 的詞:zero, one, and, coke, in, UNK, the, jpg
- 最接近 this 的詞:the, a, UNK, killing, meter, afghanistan, ada, Indiana
50,000 次迭代后的最終結(jié)果:
- 最接近 nine 的詞:eight, one, zero, seven, six, two, five, three
- 最接近 this 的詞:that, the, a, UNK, one, it, he, an
通過查看上面的輸出,我們可以首先看到「nine」這個(gè)詞與其他數(shù)字的關(guān)聯(lián)性越來越強(qiáng)(「eight」,「one」,「seven」等)這是有一定道理的。隨著迭代次數(shù)的增加,「this」這個(gè)詞在句子中起到代詞和定冠詞的作用,與其他代詞(「he」,「it」)和其他定冠詞(「the」,「that」等)關(guān)聯(lián)在一起。
總而言之,我們已經(jīng)學(xué)會了如何使用 Word2Vec 方法將大的獨(dú)熱單詞向量減少為小得多的詞嵌入向量,這些向量保留了原始單詞的上下文和含義。這些詞嵌入向量可以作為構(gòu)建自然語言模型的深度學(xué)習(xí)技術(shù)的更加高效和有效的輸入。諸如循環(huán)神經(jīng)網(wǎng)絡(luò)這樣的深度學(xué)習(xí)技術(shù),將在未來占據(jù)主要地位。
原文:http://adventuresinmachinelearning.com/word2vec-tutorial-tensorflow/
【本文是專欄機(jī)構(gòu)“機(jī)器之心”的原創(chuàng)譯文,微信公眾號“機(jī)器之心( id: almosthuman2014)”】
戳這里,看該作者更多好文
新聞名稱:在Python和TensorFlow上構(gòu)建Word2Vec詞嵌入模型
轉(zhuǎn)載來于:http://www.dlmjj.cn/article/dpeihid.html


咨詢
建站咨詢
