姓名:崔少杰 ? ? ? 學號:16040510021
轉載自:http://www.reibang.com/p/0bb00eed9c63=有修改
【嵌牛導讀】:詞嵌入向量(WordEmbedding)是NLP里面一個重要的概念,我們可以利用WordEmbedding將一個單詞轉換成固定長度的向量表示,從而便于進行數學處理虐急。本文將介紹WordEmbedding的使用方式,并講解如何通過神經網絡生成WordEmbedding。
【嵌牛鼻子】:WordEmbedding
【嵌牛提問】:詞嵌入向量WordEmbedding在解決問題時有什么巨大的作用囚痴?
【嵌牛正文】:WordEmbedding的使用
使用數學模型處理文本語料的第一步就是把文本轉換成數學表示撬讽,有兩種方法,第一種方法可以通過one-hot矩陣表示一個單詞缎脾,one-hot矩陣是指每一行有且只有一個元素為1祝闻,其他元素都是0的矩陣。針對字典中的每個單詞遗菠,我們分配一個編號联喘,對某句話進行編碼時,將里面的每個單詞轉換成字典里面這個單詞編號對應的位置為1的one-hot矩陣就可以了辙纬。比如我們要表達“the cat sat on the mat”豁遭,可以使用如下的矩陣表示。
one-hot矩陣表示法
one-hot表示方式很直觀贺拣,但是有兩個缺點蓖谢,第一捂蕴,矩陣的每一維長度都是字典的長度,比如字典包含10000個單詞闪幽,那么每個單詞對應的one-hot向量就是1X10000的向量啥辨,而這個向量只有一個位置為1,其余都是0盯腌,浪費空間溉知,不利于計算。第二腕够,one-hot矩陣相當于簡單的給每個單詞編了個號级乍,但是單詞和單詞之間的關系則完全體現不出來。比如“cat”和“mouse”的關聯性要高于“cat”和“cellphone”帚湘,這種關系在one-hot表示法中就沒有體現出來卡者。
WordEmbedding解決了這兩個問題。WordEmbedding矩陣給每個單詞分配一個固定長度的向量表示客们,這個長度可以自行設定崇决,比如300,實際上會遠遠小于字典長度(比如10000)底挫。而且兩個單詞向量之間的夾角值可以作為他們之間關系的一個衡量恒傻。如下表示:
WordEmbedding表示法
通過簡單的余弦函數,我們就可以計算兩個單詞之間的相關性建邓,簡單高效:
兩個向量相關性計算
因為WordEmbedding節(jié)省空間和便于計算的特點盈厘,使得它廣泛應用于NLP領域。接下來我們講解如何通過神經網絡生成WordEmbedding官边。
WordEmbedding的生成
WordEmbedding的生成我們使用tensorflow沸手,通過構造一個包含了一個隱藏層的神經網絡實現。
下面是下載數據和加載數據的代碼注簿,一看就懂契吉。訓練數據我們使用的是http://mattmahoney.net/dc/enwik8.zip數據,里面是維基百科的數據诡渴。
def maybe_download(filename, url):
"""Download a file if not present, and make sure it's the right size."""
if not os.path.exists(filename):
filename, _ = urllib.urlretrieve(url + filename, filename)
return filename
# 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
def collect_data(vocabulary_size=10000):
url = 'http://mattmahoney.net/dc/'
filename = maybe_download('enwik8.zip', url)
vocabulary = read_data(filename)
print(vocabulary[:7])
data, count, dictionary, reverse_dictionary = build_dataset(vocabulary, vocabulary_size)
del vocabulary? # Hint to reduce memory.
return data, count, dictionary, reverse_dictionary
接下來是如何構建訓練數據捐晶。構建訓練數據主要包括統(tǒng)計詞頻,生成字典文件妄辩,并且根據字典文件給訓練源數據中的單詞進行編號等工作惑灵。我們生成的字典不可能包含所有的單詞,一般我們按照單詞頻率由高到低排序眼耀,選擇覆蓋率大于95%的單詞加入詞典就可以了英支,因為詞典越大,覆蓋的場景越大哮伟,同時計算開銷越大干花,這是一個均衡鸯屿。下面的代碼展示了這個過程,首先統(tǒng)計所有輸入語料的詞頻把敢,選出頻率最高的10000個單詞加入字典。同時在字典第一個位置插入一項“UNK"代表不能識別的單詞谅辣,也就是未出現在字典的單詞統(tǒng)一用UNK表示修赞。然后給字典里每個詞編號,并把源句子里每個詞表示成在字典中的編號桑阶。我們可以根據每個詞的編號查找WordEmbedding中的向量表示柏副。
def build_dataset(words, n_words):
"""Process raw inputs into a dataset."""
count = [['UNK', -1]]
#? [['UNK', -1], ['i', 500], ['the', 498], ['man', 312], ...]
count.extend(collections.Counter(words).most_common(n_words - 1))
#? dictionary {'UNK':0, 'i':1, 'the': 2, 'man':3, ...}
dictionary = dict()
for word, _ in count:
dictionary[word] = len(dictionary)
data = list()
unk_count = 0
for 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()))
# data: "I like cat" -> [1, 21, 124]
# count: [['UNK', 349], ['i', 500], ['the', 498], ['man', 312], ...]
# dictionary {'UNK':0, 'i':1, 'the': 2, 'man':3, ...}
# reversed_dictionary: {0:'UNK', 1:'i', 2:'the', 3:'man', ...}
return data, count, dictionary, reversed_dictionary
接下來我們看一下如何將源句子轉換成訓練過程的輸入和輸出,這一步是比較關鍵的蚣录。有兩種業(yè)界常用的WordEmbedding生成方式割择,Continuous Bag Of Words (CBOW)方法和n-gram方法,我們采用n-gram方法萎河。訓練的目的是獲得能夠反映任意兩個單詞之間關系的單詞向量表示荔泳,所以我們的輸入到輸出的映射也要翻譯兩個單詞之間的關聯。n-gram的思路是將所有的源句子按固定長度(比如128個單詞)分割成很多batch虐杯。對于每個batch玛歌,從前往后每次選取長度為skip_window的窗口(我們設定skip_window=5)。對于窗口中的5個單詞擎椰,我們生成兩個source-target數據對支子,這兩個source-target對的source都是窗口中間的單詞,也就是第三個單詞达舒,然后從另外四個單詞中隨機選取兩個作為兩個target單詞值朋。然后窗口向后移動一個單詞,每次向后移動一個位置獲取下5個單詞巩搏,一共循環(huán)64次昨登,獲取到64X2=128個source-target對,作為一個batch的訓練數據贯底「萋猓總的思路就是把某個單詞和附近的單詞組對,作為輸入和輸出丈甸。這里同一個source單詞糯俗,會被映射到不同的target單詞,這樣理論上可以獲取任意兩個單詞之間的關系睦擂。
比如對于句子"cat and dog play balls on the floor"得湘,第一個窗口就是“cat and dog play balls",生成的兩個source-target對可能是下面中的任意兩個:
dog -> cat
dog -> and
dog -> balls
dog -> play
第二個窗口是"and dog play balls on"顿仇,生成的兩個source-target對可能是下面中的任意兩個:
play -> and
play -> balls
play -> dog
play -> on
下面是代碼實現:
def generate_batch(data, 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)
context = np.ndarray(shape=(batch_size, 1), dtype=np.int32)
span = 2 * skip_window + 1? # span含義 -> [ skip_window input_word skip_window ]
# 初始化最大長度為span的雙端隊列淘正,超過最大長度后再添加數據摆马,會從另一端刪除容不下的數據
# buffer: 1, 21, 124, 438, 11
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):? # 128 / 2
# target: 2
target = skip_window? # input word at the center of the buffer
# targets_to_avoid: [2]
targets_to_avoid = [skip_window]? # 需要忽略的詞在當前span的位置
# 更新源單詞為當前5個單詞的中間單詞
source_word = buffer[skip_window]
# 隨機選擇的5個span單詞中除了源單詞之外的4個單詞中的兩個
for j in range(num_skips):
while target in targets_to_avoid:? # 隨機重新從5個詞中選擇一個尚未選擇過的詞
target = random.randint(0, span - 1)
targets_to_avoid.append(target)
# batch添加源單詞
batch[i * num_skips + j] = source_word
# context添加目標單詞,單詞來自隨機選擇的5個span單詞中除了源單詞之外的4個單詞中的兩個
context[i * num_skips + j, 0] = buffer[target]
# 往雙端隊列中添加下一個單詞鸿吆,雙端隊列會自動將容不下的數據從另一端刪除
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
接下來是構建神經網絡的過程囤采,我們構建了一個包含一個隱藏層的神經網絡,該隱藏層包含300個節(jié)點惩淳,這個數量和我們要構造的WordEmbedding維度一致蕉毯。
with graph.as_default():
# 定義輸入輸出
train_sources = tf.placeholder(tf.int32, shape=[batch_size])
train_targets = tf.placeholder(tf.int32, shape=[batch_size, 1])
valid_dataset = tf.constant(valid_examples, dtype=tf.int32)
# 初始化embeddings矩陣,這個就是經過多步訓練后最終我們需要的embedding
embeddings = tf.Variable(tf.random_uniform([vocabulary_size, embedding_size], -1.0, 1.0))
# 將輸入序列轉換成embedding表示, [batch_size, embedding_size]
embed = tf.nn.embedding_lookup(embeddings, train_sources)
# 初始化權重
weights = tf.Variable(tf.truncated_normal([embedding_size, vocabulary_size], stddev=1.0 / math.sqrt(embedding_size)))
biases = tf.Variable(tf.zeros([vocabulary_size]))
# 隱藏層輸出結果的計算, [batch_size, vocabulary_size]
hidden_out = tf.transpose(tf.matmul(tf.transpose(weights), tf.transpose(embed))) + biases
# 將label結果轉換成one-hot表示, [batch_size, 1] -> [batch_size, vocabulary_size]
train_one_hot = tf.one_hot(train_targets, vocabulary_size)
# 根據隱藏層輸出結果和標記結果,計算交叉熵
cross_entropy = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=hidden_out, labels=train_one_hot))
# 隨機梯度下降進行一步反向傳遞
optimizer = tf.train.GradientDescentOptimizer(1.0).minimize(cross_entropy)
# 計算驗證數據集中的單詞和字典表里所有單詞的相似度思犁,并在validate過程輸出相似度最高的幾個單詞
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)
# 參數初始化賦值
init = tf.global_variables_initializer()
我們首先隨機初始化embeddings矩陣代虾,通過tf.nn.embedding_lookup函數將輸入序列轉換成WordEmbedding表示作為隱藏層的輸入。初始化weights和biases激蹲,計算隱藏層的輸出棉磨。然后計算輸出和target結果的交叉熵,使用GradientDescentOptimizer完成一次反向傳遞学辱,更新可訓練的參數乘瓤,包括embeddings變量。在Validate過程中策泣,對測試數據集中的單詞馅扣,利用embeddings矩陣計算測試單詞和所有其他單詞的相似度,輸出相似度最高的幾個單詞着降,看看它們相關性如何差油,作為一種驗證方式无蜂。
通過這個神經網絡件相,就可以完成WordEmbedding的訓練,繼而應用于其他NLP的任務氧腰。