久久国产成人av_抖音国产毛片_a片网站免费观看_A片无码播放手机在线观看,色五月在线观看,亚洲精品m在线观看,女人自慰的免费网址,悠悠在线观看精品视频,一级日本片免费的,亚洲精品久,国产精品成人久久久久久久

分享

文本匹配開(kāi)山之作--雙塔模型及實(shí)戰(zhàn)

 520jefferson 2021-08-13

NewBeeNLP
永遠(yuǎn)有料,永遠(yuǎn)有趣
199篇原創(chuàng)內(nèi)容
公眾號(hào)

在前面一篇文章中,,總結(jié)了Representation-Based文本匹配模型的改進(jìn)方法,,

其中在一篇論文中提到了使用Pre-train方式來(lái)提高效果,,論文連接如下:

論文中提到的預(yù)訓(xùn)練數(shù)據(jù)均為,,relevant positive Query-Doc 對(duì):

訓(xùn)練的目標(biāo)為最大化當(dāng)前Postive Query-Doc的Softmax條件概率:

論文中提到,,softxmax分母中的 為所有可能的文檔集合,這樣的話(huà)候選文檔集合非常大,,所以論文中做了近似,,「訓(xùn)練時(shí)使用當(dāng)前batch中文檔這個(gè)子集來(lái)代替全集」 ,這種方法稱(chēng)為Sample Softmax,。

TensorFlow中也有這個(gè)方法的API實(shí)現(xiàn),,但是我一直不是很能理解代碼中到底應(yīng)該怎么實(shí)現(xiàn),突然這幾天讀到了文本匹配的開(kāi)山之作 「DSSM」,,我發(fā)現(xiàn)「DSSM」的訓(xùn)練方法與上面那篇論文非常類(lèi)似,,于是研究了一下源碼,有一種豁然開(kāi)朗的感覺(jué),,所以想分享一下,,我對(duì)這種訓(xùn)練方式的理解。DSSM論鏈接如下:

  • Learning deep structured semantic models for web search using clickthrough data.[2]

DSSM論文中的訓(xùn)練數(shù)據(jù)也是Query-Document對(duì),,訓(xùn)練目標(biāo)也為最大化給定Query下點(diǎn)擊Doc的條件概率,,公式如下,和上面說(shuō)的Pre-train任務(wù)基本一致:

極大似然估計(jì)的公式基本一樣,,訓(xùn)練都是Point-wise loss,,具體各個(gè)符號(hào)我在下面仔細(xì)介紹。

DSSM框架簡(jiǎn)要介紹

作為文本匹配方向的開(kāi)山之作,,已經(jīng)有非常多的博客介紹了這個(gè)模型,,這里我就簡(jiǎn)單介紹一下,,重點(diǎn)放在后面訓(xùn)練源碼的閱讀,。

模型結(jié)構(gòu)

圖片

DSSM也是Representation-Based模型,其中Query端 Encoder 和 Doc端 Encoder都是使用 MLP實(shí)現(xiàn),,最后Score計(jì)算使用的是cosine similarity,,后續(xù)模型的改進(jìn)很多都是使用更好的Encoder結(jié)構(gòu)。

輸入

DSSM中輸入并不是單純直接使用 bag-of-word,,從上面結(jié)構(gòu)圖可以看出,,輸入的時(shí)候做了Word Hashing,,在進(jìn)行bag-of-word映射,目的主要如下:

  • 減少詞典的大小,,直接使用原始word詞典非常大(500K),,導(dǎo)致輸入向量的維數(shù)也非常高,使用Word Hashing做分解后,,可以減少詞典大小,,比如letter-trigram(30K)
  • 一定程度解決OOV問(wèn)題
  • 對(duì)拼寫(xiě)錯(cuò)誤也有幫助

Word Hashing的做法類(lèi)似于fast-text中的子詞分解,但是不同點(diǎn)在于

  • fast-text中會(huì)取多個(gè)不同大小窗口對(duì)一個(gè)單詞進(jìn)行分解,,比如2,、3、4,、5,,詞表是這些所有的子詞構(gòu)成的集合
  • Word Hashing只會(huì)取一個(gè)固定大小窗口對(duì)單詞進(jìn)行分解,詞表是這個(gè)固定大小窗口子詞的集合,,比如letter-bigram,,letter-trigram

比如輸入的詞為#good#,我們選「tri-gram」,,則Word-hashing分解后,,#good#的表示則為#go,goo,ood,od#,然后就是輸入的每個(gè)詞都映射為tri-gram bag-of-words 向量,,出現(xiàn)了的位置為1,,否則為0。假設(shè)數(shù)據(jù)集進(jìn)行「tri-gram」分解后,,構(gòu)成的詞表大小為N,,那么Query輸入處理方式如下:

  • 首先將每個(gè)詞進(jìn)行Word Hashing分解
  • 獲得每個(gè)詞的表示,比如 [0,1,1,0,0,0...,0,1] ,維數(shù)為N,,其中在詞表中出現(xiàn)了的位置為1,,否則為0
  • 將Query中所有的詞的表示向量相加可以得到一個(gè)N維向量,「其實(shí)就是bag-of-word表示」(只考慮有沒(méi)有出現(xiàn),,并不考慮出現(xiàn)順序位置)

Doc端輸入的處理也類(lèi)似于上面Query端的處理,,獲得Word-Hashing后的向量表示,作為整個(gè)模型的輸入,。

Encoder層

Query端和Doc端Encoder層處理很簡(jiǎn)單,,就是MLP,計(jì)算公式如下:

可以看出就是標(biāo)準(zhǔn)的全連接層運(yùn)算

相似度Score計(jì)算

DSSM中最后的相似度計(jì)算用的是 cosine similarity,,計(jì)算公式如下:

模型訓(xùn)練好之后,,給定一個(gè)Query我們就可以對(duì)其所有Doc按照這個(gè)計(jì)算出來(lái)的cosine similarity進(jìn)行排序。

訓(xùn)練方式

訓(xùn)練數(shù)據(jù)

DSSM的訓(xùn)練方式是做Point-wise訓(xùn)練,,論文中對(duì)于訓(xùn)練數(shù)據(jù)的描述如下:

The clickthrough logs consist of a list of queries and their clicked documents.

給定的是Query以及對(duì)應(yīng)的點(diǎn)擊Document,,我們需要進(jìn)行極大似然估計(jì),。

訓(xùn)練目標(biāo)

DSSM首先通過(guò)獲得的semantic relevance score計(jì)算在給定Query下Doc的后驗(yàn)概率:

其中 為softmax函數(shù)的平滑因子, 表示所有的待排序的候選文檔集合,,可以看出這個(gè)目標(biāo)其實(shí)和我們一開(kāi)始提到的Pre-train那篇論文的目標(biāo)是一樣的,。我們的候選文檔大小可能會(huì)非常大,論文在實(shí)際訓(xùn)練中,,做法如下:

  • 我們使用 來(lái)表示一個(gè)(Query,Doc)對(duì),,其中 表示這個(gè)Doc是被點(diǎn)擊過(guò)的
  • 使用 和四個(gè)隨機(jī)選取沒(méi)有被點(diǎn)擊過(guò)的Doc來(lái)近似全部文檔集合 ,其中 表示負(fù)樣本

上面就是訓(xùn)練時(shí)候的實(shí)際做法,,對(duì)于每個(gè) ,,我們只需要采樣K個(gè)負(fù)樣本(K可以自己定), ,,這樣softxmax操作我們也只需要在 這個(gè)集合上計(jì)算即可,,論文中還提到,采樣負(fù)樣本方式對(duì)最終結(jié)果沒(méi)有太大影響

In our pilot study, we do not observe any significant difference when different sampling strategies were used to select the unclicked documents.

最后loss選用的就是交叉熵?fù)p失:

訓(xùn)練方式總結(jié)

通過(guò)上面的分析,,我的理解是DSSM和之前說(shuō)的Pre-trian那篇論文,,訓(xùn)練的時(shí)候只需要采樣負(fù)樣本即可,然后softmax操作只在 當(dāng)前正樣本 + 采樣的負(fù)樣本 集合上計(jì)算,,最后用交叉熵?fù)p失即可,。具體負(fù)樣本怎么采樣,我覺(jué)的有兩種方法:

  • 輸入數(shù)據(jù)中就已經(jīng)采樣好負(fù)樣本,,輸入數(shù)據(jù)直接是正樣本 + 負(fù)樣本,,這樣運(yùn)算量會(huì)大些
  • 輸入數(shù)據(jù)batch均為正樣本,負(fù)樣本通過(guò)batch中其他Doc構(gòu)造

DSSM源碼閱讀

我看的DSSM實(shí)現(xiàn)代碼是下面兩個(gè),,其中的不同點(diǎn)就在于上面說(shuō)的負(fù)樣本構(gòu)造不同

  • 訓(xùn)練數(shù)據(jù)中輸入有負(fù)樣本:InsaneLife/dssm[3]

  • 使用一個(gè)batch中其他Doc構(gòu)造負(fù)樣本:LiangHao151941/dssm[4]

訓(xùn)練數(shù)據(jù)中輸入有負(fù)樣本的情況

  • 這部分代碼在https://github.com/InsaneLife/dssm/blob/master/dssm_rnn.py

輸入數(shù)據(jù)

with tf.name_scope('input'):
    # 預(yù)測(cè)時(shí)只用輸入query即可,,將其embedding為向量。
    query_batch = tf.placeholder(tf.int32, shape=[NoneNone], name='query_batch')
    doc_pos_batch = tf.placeholder(tf.int32, shape=[NoneNone], name='doc_positive_batch')
    doc_neg_batch = tf.placeholder(tf.int32, shape=[NoneNone], name='doc_negative_batch')
    query_seq_length = tf.placeholder(tf.int32, shape=[None], name='query_sequence_length')
    pos_seq_length = tf.placeholder(tf.int32, shape=[None], name='pos_seq_length')
    neg_seq_length = tf.placeholder(tf.int32, shape=[None], name='neg_sequence_length')
    on_train = tf.placeholder(tf.bool)
    drop_out_prob = tf.placeholder(tf.float32, name='drop_out_prob')
  • doc_pos_batch , 即是論文中說(shuō)的 $D^+# ,,正樣本輸入
  • doc_neg_batch,,即是論文匯總說(shuō)的 ,負(fù)樣本輸入集合
def pull_batch(data_map, batch_id):
    query_in = data_map['query'][batch_id * query_BS:(batch_id + 1) * query_BS]
    query_len = data_map['query_len'][batch_id * query_BS:(batch_id + 1) * query_BS]
    doc_positive_in = data_map['doc_pos'][batch_id * query_BS:(batch_id + 1) * query_BS]
    doc_positive_len = data_map['doc_pos_len'][batch_id * query_BS:(batch_id + 1) * query_BS]
    doc_negative_in = data_map['doc_neg'][batch_id * query_BS * NEG:(batch_id + 1) * query_BS * NEG]
    doc_negative_len = data_map['doc_neg_len'][batch_id * query_BS * NEG:(batch_id + 1) * query_BS * NEG]

    # query_in, doc_positive_in, doc_negative_in = pull_all(query_in, doc_positive_in, doc_negative_in)
    return query_in, doc_positive_in, doc_negative_in, query_len, doc_positive_len, doc_negative_len

這是準(zhǔn)備每個(gè)batch數(shù)據(jù)的代碼,,其中query_BS為batch_size,,NEG為負(fù)樣本采樣個(gè)數(shù)。

合并正負(fù)樣本與計(jì)算余弦相似度

從論文中可以知道,,我們需要對(duì)「每個(gè)Query」選取 這個(gè)集合做softmax操作,,所以我們計(jì)算出每個(gè)Query正負(fù)樣本的Score之后,需要將同一個(gè)Query正負(fù)樣本其合并到一起,,Score即為softmax輸入的logits,。「由于輸入數(shù)據(jù)中直接有負(fù)樣本」,所以這里不需要我們構(gòu)造負(fù)樣本,,直接把負(fù)樣本輸出的Score concat即可,。下面代碼步驟如下:

  • 先把同一個(gè)Query下pos_doc和neg_doc經(jīng)過(guò)Encoder之后的隱層表示concat到一起
  • 計(jì)算每個(gè)Query與正負(fù)樣本的similarity

計(jì)算出來(lái)的cosine similarity Tensor如下,每一行是一個(gè)Query下正樣本和負(fù)樣本的sim,,這樣我們?cè)?code>axis = 1上做softmax操作即可:

[[query[1]_pos,query[1]_neg[1],query[1]_neg[2],query[1]_neg[3],...],
 [query[2]_pos,query[2]_neg[1],query[2]_neg[2],query[2]_neg[3],...],
 ......,
 [query[n]_pos,query[n]_neg[1],query[n]_neg[2],query[n]_neg[3],...],
]
with tf.name_scope('Merge_Negative_Doc'):
    # 合并負(fù)樣本,,tile可選擇是否擴(kuò)展負(fù)樣本。
    # doc_y = tf.tile(doc_positive_y, [1, 1])
    # 此時(shí)doc_y為單獨(dú)的pos_doc的hidden representation
    doc_y = tf.tile(doc_pos_rnn_output, [11])

    #下面這段代碼就是把同一個(gè)Query下的neg_doc合并到pos_doc,后續(xù)才能計(jì)算score 和 softmax
    for i in range(NEG):
        for j in range(query_BS):
            # slice(input_, begin, size)切片API
            # doc_y = tf.concat([doc_y, tf.slice(doc_negative_y, [j * NEG + i, 0], [1, -1])], 0)
            doc_y = tf.concat([doc_y, tf.slice(doc_neg_rnn_output, [j * NEG + i, 0], [1-1])], 0)

with tf.name_scope('Cosine_Similarity'):
    # Cosine similarity
    # query_norm = sqrt(sum(each x^2))
    query_norm = tf.tile(tf.sqrt(tf.reduce_sum(tf.square(query_rnn_output), 1True)), [NEG + 11])
    # doc_norm = sqrt(sum(each x^2))
    doc_norm = tf.sqrt(tf.reduce_sum(tf.square(doc_y), 1True))

    prod = tf.reduce_sum(tf.multiply(tf.tile(query_rnn_output, [NEG + 11]), doc_y), 1True)
    norm_prod = tf.multiply(query_norm, doc_norm)

    # cos_sim_raw = query * doc / (||query|| * ||doc||)
    cos_sim_raw = tf.truediv(prod, norm_prod)
    # gamma = 20
    cos_sim = tf.transpose(tf.reshape(tf.transpose(cos_sim_raw), [NEG + 1, query_BS])) * 20
    # cos_sim 作為softmax logits輸入

softmax操作與計(jì)算交叉熵?fù)p失

上一步中已經(jīng)計(jì)算出各個(gè)Query對(duì)其正負(fù)樣本的cosine similarity,,這個(gè)將作為softmax輸入的logits,,然后計(jì)算交叉熵?fù)p失即可,「因?yàn)橹挥幸粋€(gè)正樣本,,而且其位置在第一個(gè)」,,所以我們的標(biāo)簽one-hot編碼為:

  • [1,0,0,0,0,0,....,0]

所以我們計(jì)算交叉熵?fù)p失的時(shí)候,「只需要取第一列的概率值即可」

with tf.name_scope('Loss'):
    # Train Loss
    # 轉(zhuǎn)化為softmax概率矩陣,。
    prob = tf.nn.softmax(cos_sim)
    # 只取第一列,,即正樣本列概率。相當(dāng)于one-hot標(biāo)簽為[1,0,0,0,.....,0]
    hit_prob = tf.slice(prob, [00], [-11])
    loss = -tf.reduce_sum(tf.log(hit_prob))
    tf.summary.scalar('loss', loss)

使用一個(gè)batch中其他Doc構(gòu)造負(fù)樣本

上面的方法是在輸入數(shù)據(jù)中直接有負(fù)樣本,,這樣計(jì)算的時(shí)候需要多計(jì)算負(fù)樣本的representation,,在輸入數(shù)據(jù)batch中可以只包含正樣本,然后再選擇同一個(gè)batch中的其他Doc構(gòu)造負(fù)樣本,,這樣可以減少計(jì)算量

  • 這部分代碼在https://github.com/LiangHao151941/dssm/blob/master/single/dssm_v3.py

輸入數(shù)據(jù)

with tf.name_scope('input'):
    # Shape [BS, TRIGRAM_D].
    query_batch = tf.sparse_placeholder(tf.float32, shape=query_in_shape, name='QueryBatch')
    # Shape [BS, TRIGRAM_D]
    doc_batch = tf.sparse_placeholder(tf.float32, shape=doc_in_shape, name='DocBatch')

可以看出這里的輸入數(shù)據(jù)只有 ,,并沒(méi)有負(fù)樣本

構(gòu)造負(fù)樣本并計(jì)算余弦相似度

由于輸入數(shù)據(jù)中沒(méi)有負(fù)樣本,所以使用同一個(gè)batch中的其他Doc做為負(fù)樣本,,由于所有輸入Doc representation在前面已經(jīng)計(jì)算出來(lái)了,,所以不需要額外再算一遍了,下面的代碼就是通過(guò)rotate 輸入 ,,來(lái)構(gòu)造負(fù)樣本,,比如:

  • 輸入為 ,對(duì)于每一個(gè) ,,除了 ,,這個(gè)batch中的其他Doc均為負(fù)樣本
  • 那么對(duì)于 均為視為 ,,可以構(gòu)造負(fù)樣本為
with tf.name_scope('FD_rotate'):
    # Rotate FD+ to produce 50 FD-
    temp = tf.tile(doc_y, [11])

    for i in range(NEG):
        rand = int((random.random() + i) * BS / NEG)
        doc_y = tf.concat(0,
                          [doc_y,
                           tf.slice(temp, [rand, 0], [BS - rand, -1]),
                           tf.slice(temp, [00], [rand, -1])])
with tf.name_scope('Cosine_Similarity'):
    # Cosine similarity
    query_norm = tf.tile(tf.sqrt(tf.reduce_sum(tf.square(query_y), 1True)), [NEG + 11])
    doc_norm = tf.sqrt(tf.reduce_sum(tf.square(doc_y), 1True))

    prod = tf.reduce_sum(tf.mul(tf.tile(query_y, [NEG + 11]), doc_y), 1True)
    norm_prod = tf.mul(query_norm, doc_norm)

    cos_sim_raw = tf.truediv(prod, norm_prod)
    cos_sim = tf.transpose(tf.reshape(tf.transpose(cos_sim_raw), [NEG + 1, BS])) * 20

softmax操作與計(jì)算交叉熵?fù)p失

這一步和前面說(shuō)的是一樣的

with tf.name_scope('Loss'):
    # Train Loss
    prob = tf.nn.softmax((cos_sim))
    hit_prob = tf.slice(prob, [00], [-11])
    loss = -tf.reduce_sum(tf.log(hit_prob)) / BS
    tf.scalar_summary('loss', loss)

總結(jié)

之前一直對(duì)于sampled softmax不太理解,,不知道在實(shí)際訓(xùn)練中如何做,。但是看了DSSM論文和源碼之后,真的有一種撥開(kāi)云霧見(jiàn)月明的感覺(jué),,這種訓(xùn)練方式的核心就在于「構(gòu)造負(fù)樣本」,,這樣一說(shuō)感覺(jué)和Pairwise loss中構(gòu)造pair又有點(diǎn)類(lèi)似,不過(guò)這里構(gòu)造的不止一個(gè)負(fù)樣本,訓(xùn)練目標(biāo)也是pointwise,,這種方式應(yīng)該是不需要用到TensorFlow中的tf.nn.sampled_softmax_loss這個(gè)函數(shù),。

當(dāng)然上面都是個(gè)人理解,最近越來(lái)越覺(jué)得真正要弄懂一個(gè)算法不單要理解數(shù)學(xué)原理,,而且需要去讀懂源碼,,很多在論文中理解不了的信息,在源碼中都會(huì)清晰的展現(xiàn)出來(lái),,這部分我也一直在探索中,,之后有什么心得再分享給大家啦~

一起交流

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶(hù)發(fā)布,,不代表本站觀點(diǎn),。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買(mǎi)等信息,,謹(jǐn)防詐騙,。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào),。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶(hù) 評(píng)論公約

    類(lèi)似文章 更多