知乎搜索框背后的Query理解和语义召回技术
一只小狐貍帶你解鎖 煉丹術&NLP?秘籍
前言
隨著用戶規模和產品的發展, 知乎搜索面臨著越來越大的 query 長尾化挑戰,query 理解是提升搜索召回質量的關鍵。本次分享將介紹知乎搜索在 query term weighting,同義詞擴展,query 改寫,以及語義召回等方向上的實踐方法和落地情況。
知乎搜索的歷史
首先回顧下知乎搜索的歷史。現有的知乎搜索16年開始內部研發,17年中完全替換外部支持的系統。算法團隊從 18 年初開始成立,規模5人左右,之后開始了快速的迭代過程。
主要進展有: 18年 4 月份上線 Term Weight;6 月份上線自研的 Rust 引擎并已對外開源;后續上線一系列的算法模型如糾錯、語義相關性,以及今天會重點分享的語義索引和 query 改寫;今年下半年工作主要集中在排序側,主要為用 DNN 替換了樹排序模型,并在此基礎上繼續上線了若干項收益不錯的優化。由于今天分享的內容主要是在 NLP 范圍,排序側的內容可以后續有機會再分享。
知乎是一個問答類型的社區平臺,內容以用戶生產的問題和回答為主。問答網站的屬性導致了知乎搜索 query 詞的長尾化。實際上在大搜領域中問答類 query 也是相對滿意度比較差的一類,而目前各類垂搜分流了原來大搜的流量,當中知乎搜索承擔了一部分相對比較難處理的 query。
從我們內部實際數據來看,近一年知乎搜索的長尾化趨勢也越來越明顯。如上圖右上部分是頭腰尾部搜索比例,可以看到長尾 query 增長趨勢明顯。但在我們搜索團隊的努力下,用戶的搜索滿意度并沒有隨著 query 難度的增加而下降。
長尾 query 特點
長尾 query 的多樣性對于搜索系統來說是一個很大的挑戰,原因有:
??存在輸入錯誤。例如上圖中的錯誤 query "塞爾維雅" ( 塞爾維亞 ),對于這種錯誤我們希望系統能夠自動的糾錯;
??存在表達冗余。例如輸入 "孫子兵法智慧的現代意義",在這個語境下,"智慧" 是一個無關緊要的詞。如果強制去匹配 "智慧" 的話,反而匹配不出真正想要的結果;
??存在語義鴻溝。比如 "高跟鞋消音",其中 "消音" 這個詞的表達較少見,使得同時包含 "高跟鞋" 和 "消音"?文檔較少。而類似的表達如 "高跟鞋聲音大如何消除"、"高跟鞋消聲" 等可能較多。用戶輸入的 query 和用戶生產內容之間存在了語義鴻溝。其他類型的難點還有表達不完整,意圖不明等等。
我們先通過圖右邊這個query:"iPhone 手機價格多少" 來介紹如何解決上述問題:
??對于輸入錯誤,比如說用戶輸入的 query 是 iPhone,但是這個詞輸錯了,會通過糾錯模塊將其糾正為正確的 query;
??對于表達冗余,通過計算每一個詞的重要程度也就是 term weight,來確定參與倒排索引求交操作的詞。先對 query 進行分詞,切成 iPhone、手機、價格、多少,之后判斷各詞對于表達意圖更重要,重要的詞會在檢索時參與倒排索引的求交操作,不那么重要的詞不嚴格要求一定在文檔中出現;
??解決語義鴻溝的問題。需要對原始 query 做同義詞擴展,比如 "iPhone" 和 "蘋果" 是同義詞,"價格" 和 "售價" 是同義詞。
所以在傳統的搜索領域中的查詢模塊,往往包含這些子任務:糾錯、分詞、緊密度、同義詞、詞權重,以及其他的如實體詞識別、意圖識別等等。這些查詢理解會生成一個結構化的數據,檢索模塊就可以通過結構化的查詢串去召回相關的文檔。
知乎搜索的召回和排序
知乎的召回系統主要分為兩部分:
??基于詞的傳統的倒排索引召回;
??基于向量的向量索引的召回機制。
如圖所示,實際召回中有三個隊列,基于倒排索引的隊列占兩個,分別是原始 Query 隊列和改寫 Query 隊列。改寫隊列是用翻譯模型去生成一個表達更適合檢索的 query,然后再去做 query 理解和索引召回。而第三個隊列是基于 embedding 的索引。首先把所有 doc、原始 query 都轉為向量,再通過空間最近鄰的 KNN-Search,找到與 query 最相似的文檔。
最終參與排序的就是這三個隊列,并進行合并和精排,在精排之后會進行上下文排序。下面來介紹下各個模塊具體的實現方法。
Query Term Weight
對于 term weight,最簡單的實現方式是用 IDF 逆文檔頻率算出,即看這個詞在語料里的出現次數,如果次數較少,則認為其包含的信息量是相對較多。
這個方法的局限是無法根據 query 上下文語境動態適應,因為其在不同的上下文中 weight 是一致的。所以我們再通過統計點擊數據中進行調整。如圖,如果用戶的 query 包含 a,b 兩個 term,并且點擊了 Doc1 和 Doc4,其中 Doc1 包含 a,b, Doc4 只包含a,即 a 出現2次,b 出現1次。一個樸素的想法就是 a 的權重就是1,b 是0.5,這樣就可以從歷史日志里把每個 query 里的 term 權重統計出來。
這個方法對于從未出現過的 query 就無法從歷史數據中統計得到。所以我們從 query 粒度泛化到 gram 粒度,按 ngram 來聚合。比如 a,b 就是一個 bigram,類似于 mapreduce 的 map 過程,首先會把 query 中的 ngram 先 map 出來,再發送到 reduce 里面統計。這樣就能統計出每個 ngram 下較置信的權重。
對于 ngram,在實際操作中需要注意以下幾點:
??詞表較大,往往在十億量級以上;
??字典更新較慢;
??無法處理長距離依賴。基本最多到 3gram,多 gram 詞不僅會使詞表更加膨脹,詞與詞的依賴關系也將更難捕捉到;
??無法解決過于稀疏的詞,過于長尾的詞會退化成 idf。
我們的解法是把 ngram 基于詞典的泛化方法變為基于 embedding 的模型泛化方法。目標是想得到根據 query 語境動態自適應的 term weight。如果可以獲取跟 query 上下文相關的動態詞向量,那么在詞向量的基礎上再接 MLP 就可以預測出詞權重了。目前的方法是通過 term 詞本身的詞向量減去 query 所有詞 pooling 之后的詞向量獲得,今后也會嘗試替換成 ELMo 或者 Transformer 的結構以獲得更好的效果。
同義詞擴展
對于同義詞擴展策略,首先需要一個同義詞詞表。現在有可以直接應用的公開數據集。但是數據集的詞與知乎搜索里的詞可能由于分詞粒度不一致而不匹配,并且同義詞無法保證更新。所以我們主要用內部語料進行數據挖掘,主要方向為:
??User logs: session
用戶搜索日志,利用用戶搜索一個 query 后改寫了的新 query,把兩者當作相關關系,用對齊的工具將兩者同義詞找出;
??Query logs: click
類似的,利用兩個 query 點擊的相同 doc 構建 query 關系;
? Doc:pretrain embedding
Doc 本身的語料。主要利用知乎內部數據去訓練 word embedding 的模型,用 embedding 的值計算相關性。
Word2vec 這一類的 embedding 計算方法,核心假設是擁有相似上下文的詞相似。需要注意該方法無法區分同義詞和反義詞/同位詞,例如最大/最小,蘋果/香蕉。我們這里利用監督學習的信號,對訓練完成的詞向量空間進行微調。
??外部數據
用一些規則如百科里 "XXX 又名XXX" 句式進行挖掘。
Query 改寫
接下來分享的是 query 改寫部分。為什么要做 query 改寫?
在傳統搜索里,一個 query 進來需要做多個子任務的處理。假如每個任務只能做到90%,那么積累起來的損失會非常大。我們需要一個方法一次性完成多個任務去減小損失,比如把 "蘋果手機價格多少" 直接改寫為 "蘋果手機售價"。
用翻譯方法可以實現這個需求,即把改寫問題看成 src 和 target 語言為同一種語言的翻譯問題,翻譯模型用的是谷歌的 NMT 結構。
對于這個任務來說,訓練語料的挖掘比修改模型結構更為重要,我們通過用戶行為日志來挖掘訓練語料。比如從 query 到 title,點擊同一 doc 的不同 query,都可形成平行語料。
最后我們用來訓練模型的語料用的是 Query -> Query 的平行語料,因為兩者在同一個語義空間,長度也基本接近。如果 query 翻譯成 title,則會因為 title 長度通常過長而加大訓練復雜度。具體方法是:
??語言模型的過濾
目的是把罕見的表達換為常見的表達。用 ngram 模型算一個得分,把較罕見的放在前面;
??進行相關性過濾
考慮到點擊日志噪聲比較多,通過相關性過濾掉噪聲;
??確定切詞粒度
用的是 BPE 的 Subword 方法。未用知乎內部的切詞是因為未登錄詞無法預測,同時詞表較大,訓練速度較慢。
Query 改寫 – 強化學習
具體來說,改寫模型是拿共現數據做訓練語料,但不能保證改寫出來的結果一定可以召回有用的文檔。我們參考了谷歌 2018 ICLR 的工作,為了將用戶輸入的 query 改寫為更容易檢索出答案的 query,把 QA 系統當做一個 environment,改寫模型當做 agent,CNN 是對所有召回結果做選擇的模塊。
在我們的場景中,將圖中灰色框里的 QA 對應一套倒排索引,CNN 對應在線排序服務模塊。
首先會把原始 query 的召回結果和改寫 query 的召回結果一并送入排序服務模塊,然后得到 topK 的排序;之后統計這 topK 結果里有多少是改寫 query 召回隊列里的結果,將其作為 reward。
接下來,我們用強化學習 policy gradient 方法來 finetune 改寫模型。利用強化學習的一個好處是,不再需要對齊文本語料,只需要隨機的 query 即可。在利用強化學習訓練時遇到的問題之一是 reward 稀疏問題。我們發送了很多 NMT 模型的結果給線上系統,但在返回來的 topK 列表里并不包含改寫隊列的召回。這導致了訓練的過程非常的緩慢,也對線上系統造成了很大的開銷。
為了緩解 reward 稀疏問題,參照 alphago 里面的模式,即由策略網絡,價值網絡和蒙特卡洛樹搜索三個組件完成。我們可以把 NMT 當成是一個策略網絡,然后這里主要添加了價值網絡。價值網絡用來評估一次改寫完成以后,其實際能從系統當中拿到多少 reward。他的輸入是兩個拼接在一起的 query,輸出是一個浮點值。用強化學習的經驗來學習這個價值網絡,然后將其后面的訓練過程中來加速訓練。
最后的優化效果非常明顯,在加入價值網絡以后 reward 比例提升了50%以上。同時,這個價值網絡還可以用于在線改寫觸發 rerank。如下圖所示,線上覆蓋率有了明顯提升, top20 包含 rewrite 比例的 query 數量占到了24%,query doc 數量占到了4%。在改寫中我們還訓練了從 title 到 query 的模型,解決了檢索中的重要字段 clicked query 的覆蓋率有限和展現偏置兩個問題。Clicked query 字段是記錄點擊過這個文檔的 query,就是說盡管這個 doc 不含 query 里的詞,但如果有別的用戶搜了類似的 query 所點擊的 doc,也會將其召回。
記錄 clicked query 會遇到點擊噪音和展示偏置的問題。因為有點擊的文檔比沒有點擊的文檔要多一個字段,使得在召回時有一定優勢,從而形成正向循環,導致了展示偏置。
通過訓練從 title 到 query 的模型,替換掉了 clicked query。這樣把每個文檔都寫有這個字段。雖然模型的效果沒有 query 到 query 的模型好,但因為這個模型建在索引里所以容錯率會更高。
以上就是基于詞的一些算法階段的過程,最后分享一下語義召回。
語義召回
語義召回就是把 query 和 doc 都表示為向量,然后在向量空間中做最近鄰的搜索。相對于圖像領域,在 NLP 領域還不夠成熟,因為用戶輸入的表達可能差一個字含義完全不同提高了語義召回難度。
在模型上需要訓練深度語義模型,主要分為兩種:
??基于 interaction 的深度語義模型
Interaction based 用于判斷兩個文檔的相似性。該方法提前算出 query 里每個詞和 titile 里每個詞的相似性,得到相似性矩陣。之后直接用例如 CNN 圖像卷積的方法處理該矩陣,計算相似度。
??基于 representation 的深度語義模型
Representation 的意思是先分別將 query 和 title 進行網絡的處理,分別得到向量,再預測兩者相似度的分數。
按照經驗來說,interaction based model 效果會好于 representation based model。但是在向量召回這個任務當中,我們需要離線計算好所有文檔的 embedding 并建成向量索引,所以語義召回采用的的是 representation based 模型。
語義召回 – BERT 模型
在 NLP 領域,做 embedding 召回難度大,原因為:
??Query 長尾,難以捕捉細粒度的差異
??需要泛化
??過多誤召回傷害用戶體驗
目前BERT 模型帶領 NLP 進入了新的時代,借鑒經典的孿生網絡結構,我們用 BERT 模型分別做 query 和 doc 的 encoder,接著用 pooling 之后的 query doc cosine 距離當做輸出,最后通過 pairwise 損失函數訓練模型。在 pooling 方法上,我們也嘗試過不同層 pooling,或者在多層上增加 attention 聚合 BERT 多層結果,效果和最后一層所有 token 做 average pooling 相當。損失函數方面,因為我們是做召回階段的模型,所以參考過雅虎的方法,他們認為在一輪排序階段使用 pointwise 損失函數的效果會更好,但是在我們的數據上 pairwise 還是會稍好一些。
在 BERT 模型的基礎上,我們還做了兩項優化:
??優化預訓練模型, mask token pretrain 這里主要是參考 百度 ernie 的做法,在知乎的語料上,用中文的分詞粒度的數據繼續做了 mask token pretrain,使得預訓練模型能夠更好的適應中文的詞關系
??數據增強,增加一些對于模型來說更難區分的負樣本具體采樣方法為:
用原始的腳本數據去微調一個模型,計算 doc 的 embedding 和 query 的 embedding。對 doc embedding 進行聚類,此時 doc 和 query 的 embedding 就在同一空間里,所以 query embedding 就可以對應到某一個類別。這時候即可從 doc 里直接采樣一些負樣本了。向量召回上線以后效果提升明顯。如圖右上方的 case,雖然 query 錯誤,但仍召回了正確結果。
后續方向
以上是知乎搜索最近一年在召回側的一些工作,后續的其他方向有:
??GAN 在 query 改寫的應用因為之前用的是真實系統計算 reward,代價較高,所以考慮用 gan 來代替真實系統去計算 reward,這樣樣本數據的計算會速度非常快。
??預訓練方面嘗試把預訓練模型做小以便用于更多場景中。目前由于參數規模大,時間開銷大,導致無法廣泛運用。
?進一步探索強化學習在其他場景的應用如應用于 Query 的 term weight 中。
可
能
喜
歡
ACL2020 | FastBERT:放飛BERT的推理速度
ACL2020 | 對話數據集Mutual:論對話邏輯,BERT還差的很遠
LayerNorm是Transformer的最優解嗎?
如何優雅地編碼文本中的位置信息?三種positioanl encoding方法簡述
在大廠和小廠做算法有什么不同?
夕小瑤的賣萌屋
_
關注&星標小夕,帶你解鎖AI秘籍
訂閱號主頁下方「撩一下」有驚喜哦
總結
以上是生活随笔為你收集整理的知乎搜索框背后的Query理解和语义召回技术的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: GPT-3诞生,Finetune也不再必
- 下一篇: 模型训练慢和显存不够怎么办?GPU加速混