使用三重損失和孿生神經網絡訓練大型類目的嵌入表示
大型網站類目目錄的數量很大,一般都無法進行手動標記,所以理解大型目錄的內容對在線業務來說是一個重大挑戰,并且這使得對于新產品發現就變得非常困難,但這個問題可以通過使用自監督神經網絡模型來解決。
在過去我們一直使用人工在系統中進行產品的標記,這樣的確可以解決問題但是卻耗費了很多人力的成本。如果能夠創建一種機器學習為基礎的通用的方式,在語義上自動的關聯產品,并深入了解現有的目錄內容,就可以將產品推薦、搜索、促銷活動和運營情報變為自動化的操作。
在這篇文章中,描述了一種通過在網站內部的用戶搜索數據上使用自監督學習技術來訓練高質量的可推廣嵌入的方法。除此以外本位還列舉了一些替代方法,并詳細介紹了我們所選解決方案的模型訓練和評估過程和各個方法的優略對比。
大型網站的類目問題
對于大型網站來說,網站目錄的內容并不是恒定的,如果我們添加了新的類目,對于機器學習來說則需要訓練新的模型,有沒有一種能夠在不建立定制模型的情況下處理目錄的方法呢?首先,了解類目的內容對于運營業務和面向消費者內容搜索很重要,因為:
- 類目可以根據消費者的已知偏好進行新的推薦
- 當消費者與新商店互動時,將產品品推薦給他們
- 在搜索查詢時推薦更相關商店和產品
- 自動推薦類似消費者最近訂單歷史的促銷
- 了解消費者在搜索后購買哪些物品
等等。。

在同一潛在空間中查詢(綠色)和產品(黃色)表示的示例。我們想學習一個嵌入表示形式,其中相同顏色的線具有高余弦相似性(之間的角度很小),而不同顏色的界線則具有很小的余弦相似性(它們之間的大角度)。這意味著我們需要將查詢和項目編碼到同一空間中,并為它們兩個都學習高質量的表示。
在搜索的背景下,我們還希望能夠創建一個可以與產品和商鋪嵌入進行比較的可查詢嵌入。模型需要將查詢和產品置于相同的潛在空間(上圖1)以使其可比。一旦將查詢“mexican”和產品“taco”進行嵌入,嵌入空間就可以告訴我們二者是相關的(余弦相似度)。我們還需要將商鋪和購買者嵌入在同一潛在空間中, 這使我們能夠使用嵌入方式包括目錄知識來進行商鋪的推薦。

通過定義消費者嵌入(藍色)為他們的商品嵌入(綠色)的平均值,我們可以了解消費者的不同偏好。在上圖中,經常購買墨西哥菜的消費者比經常購買亞洲菜的消費者更接近墨西哥菜。同時購買這兩種食品的消費者將會在墨西哥食品和亞洲食品集群之間形成一種嵌入。
所以我們要解決的問題是如何使用有限的標記數據有效地在非常罕見的類上訓練,這就需要利用自監督的方法來訓練嵌入。但在開始之前,我們先回顧一下傳統的技術來訓練嵌入,這樣可以讓我們理解為什么它們不能解決問題。
構建嵌入的技術的回顧
對于上面的用例來說,傳統的方法包括對條目id進行Word2vec訓練或對深度學習分類器進行訓練并取最后一層線性層的輸出。在自然語言處理(NLP)中,對BERT這樣的大型預訓練模型進行微調也變得很常見。但是對于不斷發展的大而稀疏的目錄問題,我們將一一介紹這些方法:
方案1:在實體id上嵌入Word2vec
可以使用訪問或購買等客戶行為對任意一組實體id進行Word2vec嵌入訓練。這些嵌入通過假設在同一會話中與客戶交互的實體彼此相關來學習id之間的關系,這與Word2vec分布假設類似。商店和客戶的行為定期訓練這類嵌入,以便在推薦和其他個性化應用程序中使用。見下圖

Word2vec嵌入在為大型目錄保留語義相似性方面存在一些缺陷。當新實體添加到目錄中時,它們需要定期進行再訓練。如果每天都要添加數百萬個產品,每天重新訓練這些嵌入在計算上是非常昂貴的。使用這種方法訓練的嵌入容易出現稀疏性問題,因為很少與客戶交互的id沒有得到很好的訓練。
方案2:基于監督任務的深度神經網絡訓練嵌入
深度神經網絡在分類任務上的訓練誤差較低,可以學習到高質量的目標類表示。網絡最后一層隱藏層的輸出可以被視為原始輸入的嵌入。對于多樣化和大型高質量的標記數據集,這種方法可以非常有效地學習高質量的嵌入,并可以在分類任務中重用。

這種訓練方法并不總是保證底層嵌入具有良好的度量特性。因為我們的優先級是下游應用程序的易用性,希望這些嵌入可以輕松地使用簡單的指標,如余弦相似度進行比較。由于這種方法是需要監督的,學習度量的質量在很大程度上取決于訓練集標注的質量。我們需要確保數據集具有良好的負樣本,以確保模型能夠學會區分密切相關的標簽。對于數據樣本有限的稀有類,這個問題就變得尤其嚴重。所以無監督的解決方案可以通過從未標記的數據自動生成樣本并學習標簽的表示來規避這個問題。
方案3:微調一個預先訓練好的語言模型,比如BERT
隨著最近在大型語料庫上訓練大型NLP模型方面取得的進展,通過遷移學習對這些模型進行微調學習針對特定任務的嵌入已經成為一種流行的方法(下圖5中的示例架構)。BERT是一種流行的預訓練模型,這種方法可以使用開源庫直接實現,并且可以克服數據稀疏的問題,并且作為一個非常良好的基線模型。

雖然BERT嵌入是在基線上的一個顯著改進,但由于模型規模的原因它的訓練和推理非常耗時。即使使用蒸餾模型(如DistilBERT或ELECTRA),也可能比小得多的定制模型慢得多。另外就是如果有足夠多的領域特定數據,即使它是未標記的,與預先訓練的語言模型相比,自監督方法對于特定任務具有更好的度量屬性。
通過自監督學習訓練嵌入
在調研了上述方法之后,我們使用自我監督的方法基于類目名稱和搜索查詢來訓練嵌入。通過使用子詞信息,如字符級信息,這些嵌入也可以推廣到訓練數據中沒有出現的文本。
為了保證良好的度量性能,使用了帶有三重損失的孿生神經網絡(也稱為Siamese Neural Network)架構。三重損失試圖在潛在空間中把相似的樣本擠到一起,把不同的樣本分開。孿生神經網絡可以確保用于查詢和產品文本的編碼以一種保持相似示例之間距離的方式嵌入到相同的潛在空間中。
構建數據集
為了訓練三重損失,我們需要一個結構為<anchor, positive, negative>的數據集。將anchor定義為原始查詢文本,并將查詢的“相關”和“不相關”分別視為positive和 negative。
為了構造這個數據集(下圖6中的示例),需要開發一組啟發式方法來制定訓練任務。使用以下啟發式方法分別確定正訓練樣本和負訓練樣本對應的相關和不相關項目:
如果用戶搜索了查詢Q,然后在同一個會話中立即購買了X,并且X是購物車中最貴的商品,那么商品X與查詢Q相關
這種對于正樣本的啟發式方法確保我們只取購物車中的主要商品,我們認為它可能是最相關的
如果X是在查詢R中購買的,Q和R的Levenshtein距離是> 5,那么商品X對于查詢Q來說是不相關的
這種針對負樣本的啟發式方法保證了為類似查詢購買的商品(例如“burger”和“burgers”)不會被視為無關的,生成高質量的負樣本對于防止模式崩潰是至關重要的。在這個例子中,即使是文本中這種簡單的啟發式和自然的變化對于訓練來說也足夠了,但是可能還有更好的方法需要我們去研究。
我們還對輸入進行了最小的規一化,將所有字符串小寫并刪除標點符號。這使得經過訓練的模型能夠適應拼寫錯誤和其他語言的自然變化。
為了確保模型可以推廣到詞匯表外標記的樣本,我們使用字符三元組序列來處理輸入。我們試驗了多種替代標記方案(單詞ngram、字節對編碼、WordPiece和單詞+字符ngram),但發現三元組具有相似或更好的預測性能,可以更快地訓練。
模型的架構
該模型是一個孿生網絡(下圖8),它使用由深神經網絡組成的編碼器和輸出最終嵌入的線性層。所有權重都在編碼器之間共享。由于權重是在編碼器之間共享的,所以所有頭部的編碼都進入相同的潛在空間。編碼器的輸出用于計算三重損失。
損失計算如下:
L(a, p, n, margin) = max(d(a, p) -d(a, n) + margin, 0)
對于“Mexican”查詢(紅色),三重損失試圖將positive(黃色)的嵌入靠近,并將negative(灰色)分開。
神經網絡代碼樣本。在此處抽取編碼器的詳細信息,以說明如何計算前向傳播和損失。
class SiameseNetwork(torch.nn.Module): def __init__(self, learning_rate, transforms, model, **kwargs): super().__init__() self.learning_rate = learning_rate self.transforms = transforms self._encoder = model(**kwargs) self.loss = torch.nn.TripletMarginLoss(margin=1.0, p=2) def configure_optimizers(self): return torch.optim.Adam(self.parameters(), lr=self.learning_rate) def _loss(self, anchor, pos, neg): return self.loss(anchor, pos, neg) def forward(self, anchor, seq1, seq2): anchor = self._encoder(anchor) emb1 = self._encoder(seq1) emb2 = self._encoder(seq2) return anchor, emb1, emb2
實際的編碼器體系結構是雙向LSTM,然后是線性層。LSTM負責將一系列字符的處理到向量中。

下面是編碼器的代碼
class LSTMEncoder(torch.nn.Module): def __init__(self, output_dim, n_layers=1, vocab_size=None, embedding_dim=None, embeddings=None, bidirectional=False, freeze=True, dropout=0.1): super().__init__() if embeddings is None: self.embedding = torch.nn.Embedding(vocab_size, embedding_dim) else: _, embedding_dim = embeddings.shape self.embedding = torch.nn.Embedding.from_pretrained(embeddings=embeddings, padding_idx=0, freeze=freeze) self.lstm = torch.nn.LSTM(embedding_dim, output_dim, num_layers=n_layers, bidirectional=bidirectional, dropout=dropout, batch_first=True) self.directions = 2 if bidirectional else 1 self._projection = torch.nn.Sequential( torch.nn.Dropout(dropout), torch.nn.Linear(output_dim * self.directions, output_dim), torch.nn.BatchNorm1d(output_dim), torch.nn.ReLU(), torch.nn.Linear(output_dim, output_dim), torch.nn.BatchNorm1d(output_dim), torch.nn.ReLU(), torch.nn.Linear(output_dim, output_dim, bias=False), ) def forward(self, x): embedded = self.embedding(x) # [batch size, sent len, emb dim] output, (hidden, cell) = self.lstm(embedded) hidden = einops.rearrange(hidden, '(layer dir) b c -> layer b (dir c)', dir=self.directions) return self._projection(hidden[-1])
模型評估
根據定性指標(如對嵌入UMAP投影的評估)和定量指標(如基線f1分)來評估模型。通過觀察嵌入的UMAP投影來評估定性結果(下圖)。可以看到相似的類被投射到彼此附近,這意味著嵌入可以很好地捕捉語義相似性。

考慮到定性評估的良好結果,我們還在一些基線分類任務上對模型進行了更嚴格的基準測試,這樣可以了解嵌入的質量以及在其他內部模型中使用它們的潛在收益。

模型的f1評分比基線(FastText分類器)提高了約23%。這是一個巨大的提升,特別是因為Siamese神經網絡是在零樣本分類任務上評估的,而基線是在標記數據上訓練的。
使用這些嵌入作為下游分類任務的特征,可以顯著提高樣本效率。在訓練標記模型時,使用FastText分類器訓練同樣精確的模型需要超過現有標記數據三倍數據量。這表明,學習得到的表征攜帶了關于文本內容的大量信息。
嵌入應用程序示例
為了改善對客戶的內容推薦,我們通過客戶最近購買的店鋪類別來推薦相似的其他店鋪。如果沒有嵌入則需要構建一個專門的模型,該模型考慮到<consumer_id,last_store_id>,并嘗試預測每個候選store_id上購買率。但是我們這里使用我們已經生成的嵌入,這就需要2個步驟:
- 在已經生成的嵌入中檢索與last_store_id最相似的店鋪
- 使用排名器,為每個客戶進行篩選后的店鋪進行個性化排名。
由于通過余弦相似度計算非常快速的,并且我們不需要為排名收集任何其他數據,下圖是這一過程的詳細信息。通過平均每個商鋪中商品嵌入,還可以簡單的生成一個商鋪的語義嵌入,并且可以在批處理過程中完成,減少實時系統負載。

總結
自監督方法通常特別有助于在快速增長的目錄中開發立即可重用的ML產品。雖然其他ML方法可能更適合于特殊任務,但自監督嵌入仍然可以為需要高質量文本數據表示的任務添加強大的基線。與現成的嵌入方法(如FastText或BERT)相比,通常特定于領域的嵌入方法更適合于內部應用程序(如搜索和推薦)。