詞嵌入向量WordEmbedding的原理和生成方法

WordEmbedding

詞嵌入向量(WordEmbedding)是NLP里面一個(gè)重要的概念惦蚊,我們可以利用WordEmbedding將一個(gè)單詞轉(zhuǎn)換成固定長(zhǎng)度的向量表示器虾,從而便于進(jìn)行數(shù)學(xué)處理。本文將介紹WordEmbedding的使用方式蹦锋,并講解如何通過神經(jīng)網(wǎng)絡(luò)生成WordEmbedding兆沙。

WordEmbedding的使用

使用數(shù)學(xué)模型處理文本語料的第一步就是把文本轉(zhuǎn)換成數(shù)學(xué)表示,有兩種方法莉掂,第一種方法可以通過one-hot矩陣表示一個(gè)單詞挤悉,one-hot矩陣是指每一行有且只有一個(gè)元素為1,其他元素都是0的矩陣巫湘。針對(duì)字典中的每個(gè)單詞装悲,我們分配一個(gè)編號(hào),對(duì)某句話進(jìn)行編碼時(shí)尚氛,將里面的每個(gè)單詞轉(zhuǎn)換成字典里面這個(gè)單詞編號(hào)對(duì)應(yīng)的位置為1的one-hot矩陣就可以了诀诊。比如我們要表達(dá)“the cat sat on the mat”,可以使用如下的矩陣表示阅嘶。

one-hot矩陣表示法

one-hot表示方式很直觀属瓣,但是有兩個(gè)缺點(diǎn),第一讯柔,矩陣的每一維長(zhǎng)度都是字典的長(zhǎng)度抡蛙,比如字典包含10000個(gè)單詞,那么每個(gè)單詞對(duì)應(yīng)的one-hot向量就是1X10000的向量魂迄,而這個(gè)向量只有一個(gè)位置為1粗截,其余都是0,浪費(fèi)空間捣炬,不利于計(jì)算熊昌。第二绽榛,one-hot矩陣相當(dāng)于簡(jiǎn)單的給每個(gè)單詞編了個(gè)號(hào),但是單詞和單詞之間的關(guān)系則完全體現(xiàn)不出來婿屹。比如“cat”和“mouse”的關(guān)聯(lián)性要高于“cat”和“cellphone”灭美,這種關(guān)系在one-hot表示法中就沒有體現(xiàn)出來。

WordEmbedding解決了這兩個(gè)問題昂利。WordEmbedding矩陣給每個(gè)單詞分配一個(gè)固定長(zhǎng)度的向量表示届腐,這個(gè)長(zhǎng)度可以自行設(shè)定,比如300蜂奸,實(shí)際上會(huì)遠(yuǎn)遠(yuǎn)小于字典長(zhǎng)度(比如10000)犁苏。而且兩個(gè)單詞向量之間的夾角值可以作為他們之間關(guān)系的一個(gè)衡量。如下表示:

WordEmbedding表示法

通過簡(jiǎn)單的余弦函數(shù)窝撵,我們就可以計(jì)算兩個(gè)單詞之間的相關(guān)性傀顾,簡(jiǎn)單高效:


兩個(gè)向量相關(guān)性計(jì)算

因?yàn)閃ordEmbedding節(jié)省空間和便于計(jì)算的特點(diǎn),使得它廣泛應(yīng)用于NLP領(lǐng)域碌奉。接下來我們講解如何通過神經(jīng)網(wǎng)絡(luò)生成WordEmbedding短曾。

WordEmbedding的生成

WordEmbedding的生成我們使用tensorflow,通過構(gòu)造一個(gè)包含了一個(gè)隱藏層的神經(jīng)網(wǎng)絡(luò)實(shí)現(xiàn)赐劣。

下面是下載數(shù)據(jù)和加載數(shù)據(jù)的代碼嫉拐,一看就懂。訓(xùn)練數(shù)據(jù)我們使用的是http://mattmahoney.net/dc/enwik8.zip數(shù)據(jù)魁兼,里面是維基百科的數(shù)據(jù)婉徘。

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

接下來是如何構(gòu)建訓(xùn)練數(shù)據(jù)。構(gòu)建訓(xùn)練數(shù)據(jù)主要包括統(tǒng)計(jì)詞頻咐汞,生成字典文件盖呼,并且根據(jù)字典文件給訓(xùn)練源數(shù)據(jù)中的單詞進(jìn)行編號(hào)等工作。我們生成的字典不可能包含所有的單詞化撕,一般我們按照單詞頻率由高到低排序几晤,選擇覆蓋率大于95%的單詞加入詞典就可以了,因?yàn)樵~典越大植阴,覆蓋的場(chǎng)景越大蟹瘾,同時(shí)計(jì)算開銷越大,這是一個(gè)均衡掠手。下面的代碼展示了這個(gè)過程憾朴,首先統(tǒng)計(jì)所有輸入語料的詞頻,選出頻率最高的10000個(gè)單詞加入字典喷鸽。同時(shí)在字典第一個(gè)位置插入一項(xiàng)“UNK"代表不能識(shí)別的單詞众雷,也就是未出現(xiàn)在字典的單詞統(tǒng)一用UNK表示。然后給字典里每個(gè)詞編號(hào),并把源句子里每個(gè)詞表示成在字典中的編號(hào)报腔。我們可以根據(jù)每個(gè)詞的編號(hào)查找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

接下來我們看一下如何將源句子轉(zhuǎn)換成訓(xùn)練過程的輸入和輸出剖淀,這一步是比較關(guān)鍵的纯蛾。有兩種業(yè)界常用的WordEmbedding生成方式,Continuous Bag Of Words (CBOW)方法和skip-gram方法纵隔,我們采用skip-gram方法翻诉。訓(xùn)練的目的是獲得能夠反映任意兩個(gè)單詞之間關(guān)系的單詞向量表示,所以我們的輸入到輸出的映射也要翻譯兩個(gè)單詞之間的關(guān)聯(lián)捌刮。skip-gram的思路是將所有的源句子按固定長(zhǎng)度(比如128個(gè)單詞)分割成很多batch碰煌。對(duì)于每個(gè)batch,從前往后每次選取長(zhǎng)度為skip_window的窗口(我們?cè)O(shè)定skip_window=5)绅作。對(duì)于窗口中的5個(gè)單詞芦圾,我們生成兩個(gè)source-target數(shù)據(jù)對(duì),這兩個(gè)source-target對(duì)的source都是窗口中間的單詞俄认,也就是第三個(gè)單詞个少,然后從另外四個(gè)單詞中隨機(jī)選取兩個(gè)作為兩個(gè)target單詞。然后窗口向后移動(dòng)一個(gè)單詞眯杏,每次向后移動(dòng)一個(gè)位置獲取下5個(gè)單詞夜焦,一共循環(huán)64次,獲取到64X2=128個(gè)source-target對(duì)岂贩,作為一個(gè)batch的訓(xùn)練數(shù)據(jù)茫经。總的思路就是把某個(gè)單詞和附近的單詞組對(duì)萎津,作為輸入和輸出卸伞。這里同一個(gè)source單詞,會(huì)被映射到不同的target單詞锉屈,這樣理論上可以獲取任意兩個(gè)單詞之間的關(guān)系荤傲。

比如對(duì)于句子"cat and dog play balls on the floor",第一個(gè)窗口就是“cat and dog play balls"部念,生成的兩個(gè)source-target對(duì)可能是下面中的任意兩個(gè):
dog -> cat
dog -> and
dog -> balls
dog -> play

第二個(gè)窗口是"and dog play balls on"弃酌,生成的兩個(gè)source-target對(duì)可能是下面中的任意兩個(gè):
play -> and
play -> balls
play -> dog
play -> on

下面是代碼實(shí)現(xiàn):

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 ]

    # 初始化最大長(zhǎng)度為span的雙端隊(duì)列,超過最大長(zhǎng)度后再添加數(shù)據(jù)儡炼,會(huì)從另一端刪除容不下的數(shù)據(jù)
    # 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]  # 需要忽略的詞在當(dāng)前span的位置

        # 更新源單詞為當(dāng)前5個(gè)單詞的中間單詞
        source_word = buffer[skip_window]

        # 隨機(jī)選擇的5個(gè)span單詞中除了源單詞之外的4個(gè)單詞中的兩個(gè)
        for j in range(num_skips):
            while target in targets_to_avoid:  # 隨機(jī)重新從5個(gè)詞中選擇一個(gè)尚未選擇過的詞
                target = random.randint(0, span - 1)
            targets_to_avoid.append(target)

            # batch添加源單詞
            batch[i * num_skips + j] = source_word
            # context添加目標(biāo)單詞妓湘,單詞來自隨機(jī)選擇的5個(gè)span單詞中除了源單詞之外的4個(gè)單詞中的兩個(gè)
            context[i * num_skips + j, 0] = buffer[target]

        # 往雙端隊(duì)列中添加下一個(gè)單詞,雙端隊(duì)列會(huì)自動(dòng)將容不下的數(shù)據(jù)從另一端刪除
        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

接下來是構(gòu)建神經(jīng)網(wǎng)絡(luò)的過程乌询,我們構(gòu)建了一個(gè)包含一個(gè)隱藏層的神經(jīng)網(wǎng)絡(luò)榜贴,該隱藏層包含300個(gè)節(jié)點(diǎn),這個(gè)數(shù)量和我們要構(gòu)造的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矩陣,這個(gè)就是經(jīng)過多步訓(xùn)練后最終我們需要的embedding
  embeddings = tf.Variable(tf.random_uniform([vocabulary_size, embedding_size], -1.0, 1.0))

  # 將輸入序列轉(zhuǎn)換成embedding表示, [batch_size, embedding_size]
  embed = tf.nn.embedding_lookup(embeddings, train_sources)

  # 初始化權(quán)重
  weights = tf.Variable(tf.truncated_normal([embedding_size, vocabulary_size], stddev=1.0 / math.sqrt(embedding_size)))
  biases = tf.Variable(tf.zeros([vocabulary_size]))

  # 隱藏層輸出結(jié)果的計(jì)算, [batch_size, vocabulary_size]
  hidden_out = tf.transpose(tf.matmul(tf.transpose(weights), tf.transpose(embed))) + biases

  # 將label結(jié)果轉(zhuǎn)換成one-hot表示, [batch_size, 1] -> [batch_size, vocabulary_size]
  train_one_hot = tf.one_hot(train_targets, vocabulary_size)

  # 根據(jù)隱藏層輸出結(jié)果和標(biāo)記結(jié)果唬党,計(jì)算交叉熵
  cross_entropy = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=hidden_out, labels=train_one_hot))

  # 隨機(jī)梯度下降進(jìn)行一步反向傳遞
  optimizer = tf.train.GradientDescentOptimizer(1.0).minimize(cross_entropy)

  # 計(jì)算驗(yàn)證數(shù)據(jù)集中的單詞和字典表里所有單詞的相似度鹃共,并在validate過程輸出相似度最高的幾個(gè)單詞
  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)

  # 參數(shù)初始化賦值
  init = tf.global_variables_initializer()

我們首先隨機(jī)初始化embeddings矩陣,通過tf.nn.embedding_lookup函數(shù)將輸入序列轉(zhuǎn)換成WordEmbedding表示作為隱藏層的輸入驶拱。初始化weights和biases霜浴,計(jì)算隱藏層的輸出。然后計(jì)算輸出和target結(jié)果的交叉熵蓝纲,使用GradientDescentOptimizer完成一次反向傳遞阴孟,更新可訓(xùn)練的參數(shù),包括embeddings變量税迷。在Validate過程中永丝,對(duì)測(cè)試數(shù)據(jù)集中的單詞,利用embeddings矩陣計(jì)算測(cè)試單詞和所有其他單詞的相似度箭养,輸出相似度最高的幾個(gè)單詞慕嚷,看看它們相關(guān)性如何,作為一種驗(yàn)證方式毕泌。

通過這個(gè)神經(jīng)網(wǎng)絡(luò)喝检,就可以完成WordEmbedding的訓(xùn)練,繼而應(yīng)用于其他NLP的任務(wù)懈词。

完整代碼可以參考Git Demo Code

參考:
https://www.tensorflow.org/tutorials/word2vec
http://mccormickml.com/2016/04/19/word2vec-tutorial-the-skip-gram-model/
http://adventuresinmachinelearning.com/word2vec-tutorial-tensorflow/

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末蛇耀,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子坎弯,更是在濱河造成了極大的恐慌纺涤,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,884評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件抠忘,死亡現(xiàn)場(chǎng)離奇詭異撩炊,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)崎脉,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,755評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門拧咳,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人囚灼,你說我怎么就攤上這事骆膝。” “怎么了灶体?”我有些...
    開封第一講書人閱讀 158,369評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵阅签,是天一觀的道長(zhǎng)。 經(jīng)常有香客問我蝎抽,道長(zhǎng)政钟,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,799評(píng)論 1 285
  • 正文 為了忘掉前任,我火速辦了婚禮养交,結(jié)果婚禮上精算,老公的妹妹穿的比我還像新娘。我一直安慰自己碎连,他們只是感情好灰羽,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,910評(píng)論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著破花,像睡著了一般谦趣。 火紅的嫁衣襯著肌膚如雪疲吸。 梳的紋絲不亂的頭發(fā)上座每,一...
    開封第一講書人閱讀 50,096評(píng)論 1 291
  • 那天,我揣著相機(jī)與錄音摘悴,去河邊找鬼峭梳。 笑死,一個(gè)胖子當(dāng)著我的面吹牛蹂喻,可吹牛的內(nèi)容都是我干的葱椭。 我是一名探鬼主播,決...
    沈念sama閱讀 39,159評(píng)論 3 411
  • 文/蒼蘭香墨 我猛地睜開眼口四,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼孵运!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起蔓彩,我...
    開封第一講書人閱讀 37,917評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤治笨,失蹤者是張志新(化名)和其女友劉穎逞泄,沒想到半個(gè)月后焚挠,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體深员,經(jīng)...
    沈念sama閱讀 44,360評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡逞敷,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,673評(píng)論 2 327
  • 正文 我和宋清朗相戀三年膀息,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了拉鹃。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片考杉。...
    茶點(diǎn)故事閱讀 38,814評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡矫付,死狀恐怖蹂空,靈堂內(nèi)的尸體忽然破棺而出俯萌,到底是詐尸還是另有隱情,我是刑警寧澤上枕,帶...
    沈念sama閱讀 34,509評(píng)論 4 334
  • 正文 年R本政府宣布咐熙,位于F島的核電站,受9級(jí)特大地震影響姿骏,放射性物質(zhì)發(fā)生泄漏糖声。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,156評(píng)論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望蘸泻。 院中可真熱鬧琉苇,春花似錦、人聲如沸悦施。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽抡诞。三九已至穷蛹,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間昼汗,已是汗流浹背肴熏。 一陣腳步聲響...
    開封第一講書人閱讀 32,123評(píng)論 1 267
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留顷窒,地道東北人蛙吏。 一個(gè)月前我還...
    沈念sama閱讀 46,641評(píng)論 2 362
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像鞋吉,于是被迫代替她去往敵國和親鸦做。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,728評(píng)論 2 351