[阿里DIN]從論文原始碼學習 之 embedding_lookup

羅西的思考發表於2020-10-25

[阿里DIN]從論文原始碼學習 之 embedding_lookup

0x00 摘要

Deep Interest Network(DIN)是阿里媽媽精準定向檢索及基礎演算法團隊在2017年6月提出的。其針對電子商務領域(e-commerce industry)的CTR預估,重點在於充分利用/挖掘使用者歷史行為資料中的資訊。

本系列文章解讀論文以及原始碼,順便梳理一些深度學習相關概念和TensorFlow的實現。

本文通過DIN原始碼 https://github.com/mouna99/dien 分析,來深入展開看看embedding層原理 以及 embedding_lookup如何使用。

0x01 DIN程式碼

1.1 Embedding概念

我們首先簡要提一下Embedding概念及作用。

Embedding譯為“嵌入”,被翻譯為“向量化”。主要作用:將稀疏向量轉化為稠密向量,便於上層神經網路的處理。例如,做一個推薦使用者看視訊的推薦系統,該模型的輸入可能是使用者屬性(看視訊,yoghurt搜尋詞,使用者年齡,性別等等)的Embedding向量,模型的輸出是多分類的softmax層,預測的是使用者看了哪個視訊。

定義:用一個低維稠密的向量“表示”一個物件。 物件可以是一個詞,一個商品,一部電影等等。“表示”:意味著Embedding向量能夠表達相應物件的某些特徵、向量之間的距離,可以反應物件之間的相似性。

Embedding對深度學習推薦系統的重要性:

  • 在輸入層和全連線層之間使用Embedding層將高維稀疏特徵向量轉換成低維稠密特徵向量;
  • 可以引入任何資訊進行編碼,本身包含大量有價值的資訊;
  • 通過計算使用者和物品的 Embedding 相似度,Embedding 可以直接作為推薦系統或計算廣告系統的召回層或者召回方法之一;

1.2 在DIN中的使用

下面是一個縮減版的程式碼,可以看到,DIN是用self.mid_batch_ph作為id,在self.uid_embeddings_var中查詢變數。

在DIN中,我們只有這一處初始化 embeddings 的地方,沒有找到迭代更新的程式碼,這會給初學者帶來一些困擾。

class Model(object):
    def __init__(self, n_uid, n_mid, n_cat, EMBEDDING_DIM, HIDDEN_SIZE, ATTENTION_SIZE, use_negsampling = False):
        with tf.name_scope('Inputs'):
            self.mid_his_batch_ph = tf.placeholder(tf.int32, [None, None], name='mid_his_batch_ph')
            self.cat_his_batch_ph = tf.placeholder(tf.int32, [None, None], name='cat_his_batch_ph')
            self.uid_batch_ph = tf.placeholder(tf.int32, [None, ], name='uid_batch_ph')
            self.mid_batch_ph = tf.placeholder(tf.int32, [None, ], name='mid_batch_ph')
            self.cat_batch_ph = tf.placeholder(tf.int32, [None, ], name='cat_batch_ph')

        # Embedding layer
        with tf.name_scope('Embedding_layer'):
            self.uid_embeddings_var = tf.get_variable("uid_embedding_var", [n_uid, EMBEDDING_DIM])
            self.uid_batch_embedded = tf.nn.embedding_lookup(self.uid_embeddings_var, self.uid_batch_ph)

            self.mid_embeddings_var = tf.get_variable("mid_embedding_var", [n_mid, EMBEDDING_DIM])
            self.mid_batch_embedded = tf.nn.embedding_lookup(self.mid_embeddings_var, self.mid_batch_ph)
            self.mid_his_batch_embedded = tf.nn.embedding_lookup(self.mid_embeddings_var, self.mid_his_batch_ph)

            self.cat_embeddings_var = tf.get_variable("cat_embedding_var", [n_cat, EMBEDDING_DIM])
            self.cat_batch_embedded = tf.nn.embedding_lookup(self.cat_embeddings_var, self.cat_batch_ph)
            self.cat_his_batch_embedded = tf.nn.embedding_lookup(self.cat_embeddings_var, self.cat_his_batch_ph)

1.3 問題

於是我們遇到幾個問題:

  • embedding層在這裡起到什麼作用?
  • embedding_lookup究竟用來做什麼?
  • 如何更新mid_embeddings_var這樣的embedding層?

下面就讓我們一一研究。

0x02 相關概念

2.1 one-hot編碼

one-hot編碼是保證每個樣本中的單個特徵只有1位處於狀態1,其他的都是0。

具體編碼舉例如下,把語料庫中,杭州、上海、寧波、北京每個都對應一個向量,向量中只有一個值為1,其餘都為0,我們得到如下。

杭州 [0,0,0,0,0,0,0,1,0,……,0,0,0,0,0,0,0]
上海 [0,0,0,0,1,0,0,0,0,……,0,0,0,0,0,0,0]
寧波 [0,0,0,1,0,0,0,0,0,……,0,0,0,0,0,0,0]
北京 [0,0,0,0,0,0,0,0,0,……,1,0,0,0,0,0,0]

這就是獨熱編碼。

優勢:計算方便快捷、表達能力強。

缺點

  • 每一維包含資訊量太少;
  • 維度會隨著城市數量的增加而增加,可能造成維度爆炸,計算複雜度過高。過於稀疏時,過度佔用資源。比如統計全球城市,那麼對應矩陣維度就太大了。

2.2 轉換

因為獨熱編碼有使用上的困難,所以在實踐中,人們會對其進行轉換,我們大致講解如下:

比如用one-hot編碼來表示4個梁山好漢。

李逵   [0 0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0]
劉唐   [0 0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
武松   [0 0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0]
魯智深 [0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0] 

這樣編碼的優勢是:所有梁山好漢,都能在一個一維的陣列裡用 0 1 表示出來。不同的好漢絕對不一樣,一點重複都沒有,表達本徵的能力極強。

缺點是:因為其完全獨立,表達關聯特徵的能力幾乎為0。你從這個稀疏矩陣無法看出來這四人中有任何聯絡,因為每個人都只有一個1,而且這個1的位置彼此完全不同。

但實際上,這幾個好漢都是有內在聯絡的。

  • 4個好漢武力值都不錯。
  • 武松和魯智深兩個人都是出家人,都做過官。魯智深是提轄,官階大些。武松是都頭,官階略小。
  • 李逵和劉唐兩個人都是二貨。

所以我們構建如下矩陣:

        二  出  官   武
        貨  家  階   力
李逵    [1   0   0   0.5]
劉唐    [1   0   0   0.4]
武松    [0   1   0.5 0.8]
魯智深  [0   1   0.75 0.8] 

由此我們將四位好漢同 "二貨","出家","官階","武力" 這幾個特徵關聯起來,我們可以認為:

  • 李逵 = 1.0 二貨 + 0 出家 + 0 官階 + 0.5 武力
  • 劉唐 = 1.0 二貨 + 0 出家 + 0 官階 + 0.4 武力
  • 武松 = 0 二貨 + 1 出家 + 0.5 官階 + 0.8 武力
  • 魯智深 = 0 二貨 + 1 出家 + 0.75 官階 + 0.8 武力

於是乎,我們把好漢的one-hot編碼,從稀疏態變成了密集態,並且讓相互獨立向量變成了有內在聯絡的關係向量。這就得倒了 Embedding層。

2.3 Embedding層

2.3.1 意義

Embedding的意義是對於高維、稀疏的id類特徵,通過將單個id(可能是某個詞的id,也可能是某個商品的id)對映成一個稠密向量,變id特徵的“精確匹配”為embedding向量的“模糊查詢”,從而降低了特徵的維度和計算複雜度,提升演算法的擴充套件能力

Embedding最重要的屬性是:越“相似”的實體,Embedding之間的距離越小。以word2vec模型為例,如果兩個詞的上下文幾乎相同,就意味著它們的輸出值幾乎相同,在模型收斂的前提下,兩個詞在Embedding層的輸出值一定非常相近。在推薦系統裡,可以計算實體間的餘弦相似度,召回相似度高的商品作為備選推薦商品,這是Embedding內在屬性的一種簡單的應用。

Embedding層把我們的稀疏矩陣,通過一些線性變換(比如用全連線層進行轉換,也稱為查表操作),變成了一個密集矩陣,這個密集矩陣用了N(例子中N=4)個特徵來表徵所有的好漢。在這個密集矩陣中,表象上代表著密集矩陣跟單個好漢的一一對應關係,實際上還蘊含了大量的好漢與好漢之間的內在關係(如:我們得出的李逵跟劉唐的關係)。它們之間的關係,用嵌入層學習來的引數進行表徵。這個從稀疏矩陣到密集矩陣的過程,叫做embedding,很多人也把它叫做查表,因為它們之間也是一個一一對映的關係

這種對映關係在反向傳播的過程中一直在更新。因此能在多次epoch後,使得這個關係變成相對成熟,即:正確的表達整個語義以及各個語句之間的關係。這個成熟的關係,就是embedding層的所有權重引數

Embedding是NPL領域最重要的發明之一,他把獨立的向量一下子就關聯起來了。這就相當於什麼呢,相當於你是你爸的兒子,你爸是A的同事,B是A的兒子,似乎跟你是八竿子才打得著的關係。結果你一看B,是你的同桌。Embedding層就是用來發現這個祕密的武器。

Embedding最大的劣勢是無法解釋每個維度的含義,這也是複雜機器學習模型的通病

2.3.2 常規作用

Embedding除了把獨立向量聯絡起來之外,還有兩個作用:降維,升維。

embedding層 降維的原理就是矩陣乘法。

比如一個 1 x 4 的矩陣,乘以一個 4 x 3 的矩陣,得倒一個 1 x 3 的矩陣。4 x 3 的矩陣縮小了 1 / 4。

\[\left[\begin{matrix} 0 & 0 & 1 & 0 \end{matrix} \right] \times \left[ \begin{matrix} 1 & 2 & 3 \\ 4 & 5 & 6 \\ 7 & 8 & 9 \\ 10 & 11 & 12 \end{matrix} \right] = \left[\begin{matrix} 7 & 8 & 9 \end{matrix} \right] \]

假如我們有一個100W X 10W的矩陣,用它乘上一個10W X 20的矩陣,我們可以把它降到100W X 20,瞬間量級降了。

升維可以理解為:前面有一幅圖畫,你離遠了看不清楚,離近了看就可以看清楚細節。當對低維的資料進行升維時,可能把一些其他特徵給放大了,或者把籠統的特徵給分開了。同時這個embedding是一直在學習在優化的,就使得整個拉近拉遠的過程慢慢形成一個良好的觀察點。

2.3.3 如何生成

生成Embedding的方法可以歸類為三種,分別是矩陣分解,無監督建模和有監督建模

矩陣分解

矩陣分解是將兩種實體間的關係矩陣分解為兩個Embedding矩陣,得到每一種實體的Embedding,比如在推薦系統裡,我們已知使用者與商品的共現矩陣,通過矩陣分解可以得到每個使用者的Embedding和每個商品的Embedding。

無監督建模

無監督建模是生成Embedding的常用方法,按組織方式可以將資料分為序列和圖兩類,針對序列資料生成Embedding常採用word2vec或類似演算法(item2vec, doc2vec等),針對圖資料生成Embedding的演算法稱為Graph Embedding,這類演算法包括deepwalk、node2vec、struc2vec等,它們大多采用隨機遊走方式生成序列,底層同樣也是word2vec演算法。

有監督建模

有監督建模也可以用於生成Embedding,主要分為兩類,一類是因子分解機及其衍生演算法,包括FM、FFM、DeepFM等,另一類是圖卷積演算法,包括GCN、GraphSAGE、GAT等,這些模型中都包含Embedding層,在建模有監督問題時,每個實體在Embedding層的輸出向量可以作為這個實體的Embedding使用。

2.4 Embedding與深度學習推薦系統的結合

2.4.1 重要性

為什麼說Embedding技術對於深度學習如此重要,甚至可以說是深度學習的“基本核心操作”呢?原因主要有以下:

  • 在深度學習網路中作為Embedding層,完成從高維稀疏特徵向量到低維稠密特徵向量的轉換(比如Wide&Deep、DIN等模型)。推薦場景中大量使用One-hot編碼對類別、id型特徵進行編碼,導致樣本特徵向量極度稀疏,而深度學習的結構特點使其不利於稀疏特徵向量的處理,因此幾乎所有的深度學習推薦模型都會由Embedding層負責將高維稀疏特徵向量轉換成稠密低維特徵向量。
  • 作為預訓練的Embedding特徵向量,與其他特徵向量連線後,一同輸入深度學習網路進行訓練(比如FNN模型)。Embedding本身就是極其重要的特徵向量。相比傳統方法產生的特徵向量,Embedding的表達能力更強,特別是Graph Embedding技術被提出後,Embedding幾乎可以引入任何資訊進行編碼,使其本身就包含大量有價值的資訊。在此基礎上,Embedding向量往往會與其他推薦系統特徵連線後一同輸入後續深度學習網路進行訓練。
  • 通過計算使用者和物品的Embedding相似度,Embedding可以直接作為推薦系統的召回層或者召回策略之一(比如Youtube推薦模型等)。Embedding對物品、使用者相似度的計算是常用的推薦系統召回層技術。在區域性敏感雜湊(Locality-Sensitive Hashing)等快速最近鄰搜尋技術應用於推薦系統後,Embedding更適用於對海量備選物品進行快速“篩選”,過濾出幾百到幾千量級的物品交由深度學習網路進行“精排”。

2.4.2 預訓練方法

由於Embedding預訓練開銷巨大,一般讓Embedding的預訓練往往獨立於深度學習網路進行。

Embedding層進行低頻訓練就可以了,上層神經網路為抓住最新的資料整體趨勢,需要進行高頻訓練、實時訓練。

更徹底的Embedding訓練方法就是固定Embedding層權重,僅更新上層神經網路權重。

模型部署,只需要將使用者Embedding和物品Embedding儲存到線上記憶體資料庫,通過內積運算再排列得到物品的排列,再取Top N 的物品,即可得到召回的候選集合,這就是利用Embedding作為召回層的過程。沒必要部署整個深度神經網路來完成從原始特徵向量到最終輸出的預測過程。

0x03 embedding_lookup

embedding_lookup 函式在 DIN 實現了完成高維稀疏特徵向量到低維稠密特徵向量的轉換。其接收的是類別特徵的one-hot向量,轉換的目標是低維的Embedding向量。本質上就求解一個m × n 維的權重矩陣的過程,其列向量就是相應維度的one-hot特徵的Embedding向量。

3.1 函式說明

在NLP領域中,通常都會先將文字轉換成普通整數編碼,然後再用embedding層進行可更新向量編碼。Tensorflow提供給了embedding_lookup函式來進行轉換。比如從one_hot到矩陣編碼的轉換過程需要在embedding進行查詢:

one_hot * embedding_weights = embedding_code

TensorFlow 的 embedding_lookup(params, ids) 函式的目的是按照ids從params這個矩陣中拿向量(行),所以ids就是這個矩陣索引(行號),需要int型別。即按照ids順序返回params中的第ids行。比如說,ids=[1,3,2],就是返回params中第1,3,2行。返回結果為由params的1,3,2行組成的tensor。

 embedding_lookup(
     params,   # embedding_params 對應的轉換向量
     ids,      # inputs_ids,標記著要查詢的id
     partition_strategy='mod',   #分割方式 
     name=None,
     validate_indices=True, # deprecated
     max_norm=None
 )

引數和返回值如下:

  • params: 由一個tensor或者多個tensor組成的列表(多個tensor組成時,每個tensor除了第一個維度其他維度需相等)。
  • ids: 一個整型的tensor,ids的每個元素代表要在params中取的每個元素的第0維的邏輯index。
  • partition_strategy: 邏輯index是由partition_strategy指定,partition_strategy用來設定ids的切分方式,目前有兩種切分方式’div’和’mod’,預設是’mod’。
  • 返回值: 是一個dense tensor,返回的shape為shape(ids)+shape(params)[1:]。

3.2 函式本質

3.2.1 全連線層

embedding_lookup是一種特殊的全連線層的實現方法,其針對 輸入是超高維 one hot向量的情況。

神經網路處理不了onehot編碼。embedding_lookup雖然是隨機化地對映成向量,看起來資訊量相同,但其實卻更加超平面可分。

問題本質只是做一次常規的線性變換而已,Z = WX + b。由於輸入是One-Hot Encoding 的原因,WX 的矩陣乘法看起來就像是取了Weights矩陣中對應的一列,看起來就像是在查表。等於說變相的進行了一次矩陣相乘運算,其實就是一次線性變換。

embedding_lookup不是簡單的查表,id對應的向量是可以訓練的,訓練引數個數應該是 category num * embedding size,也就是說lookup是一個特殊的“全連線層”

3.2.2 對映向量

一般做自然語言相關的。需要把每個詞都對映成向量,這個向量可以是word2vec預訓練好的,也可以是在網路裡訓練的。在網路裡需要先把詞的id轉換成對應的向量,這個函式就是做這件事的。

假設embedding權重矩陣是一個[vocab_size, embed_size]的稠密矩陣W,vocab_size是需要embed的所有item的個數(比如:所有詞的個數,所有商品的個數),embed_size是對映後的向量長度。

所謂embedding_lookup(W, id1),可以想像成一個只在id1位為1的[1, vocab_size]的one_hot向量,與[vocab_size, embed_size]的W矩陣相乘,結果是一個[1, embed_size]的向量,它就是id1對應的embedding向量,實際上就是W矩陣的第id1行

但是,以上過程只是前代,因為W一般是隨機初始化的,是待優化的變數。因此,embedding_lookup除了要完成以上矩陣相乘的過程(實現成“抽取id對應的行”),還要完成自動求導,以實現對W的更新。

embedding_lookup一般在NLP中用得比較多,將一個[batchsize, sequence_len]的輸入,對映成[batchsize, sequence_len, embed_size]的矩陣。而在推薦/搜尋領域,我們往往需要先embedding, 再將embedding後的多個向量合併成一個向量(即pooling過程)。比如,使用者過去一週用過3次微信,1次支付寶,那我們將使用者過去一週的app使用習慣表示成:使用者app使用習慣向量 = 3 * 微信向量 + 1 * 支付寶向量

3.3 函式示例

3.3.1 示例

示例程式碼如下:

import numpy as np
import tensorflow as tf

sess = tf.InteractiveSession()

embedding = tf.Variable(np.identity(6, dtype=np.int32))
input_ids = tf.placeholder(dtype=tf.int32, shape=[None])
input_embedding = tf.nn.embedding_lookup(embedding, input_ids)

sess.run(tf.global_variables_initializer())
print("====== the embedding ====== ")
print(sess.run(embedding) )
print("====== the input_embedding ====== ")
print(sess.run(input_embedding, feed_dict={input_ids: [4, 0, 2]}))

3.3.2 輸出

輸出如下

====== the embedding ====== 
[[1 0 0 0 0 0]
 [0 1 0 0 0 0]
 [0 0 1 0 0 0]
 [0 0 0 1 0 0]
 [0 0 0 0 1 0]
 [0 0 0 0 0 1]]
====== the input_embedding ====== 
[[0 0 0 0 1 0]
 [1 0 0 0 0 0]
 [0 0 1 0 0 0]]

3.3.3 解釋

從以上可以看出:

embedding將變數表現成了one-hot形式,簡單來說是建立了一個embedding詞典;

====== the embedding ====== 
[[1 0 0 0 0 0]
 [0 1 0 0 0 0]
 [0 0 1 0 0 0]
 [0 0 0 1 0 0]
 [0 0 0 0 1 0]
 [0 0 0 0 0 1]]

input_embedding = tf.nn.embedding_lookup(embedding, input_ids)就是把input_ids中給出的tensor表現成embedding中的形式;

簡單來說是通過輸入的input_ids查詢上部的字典得到embedding後的值。而字典是可以由使用者隨意建立的,例中給出的是一個one-hot字典,還可以自由建立其他字典,例如使用正態分佈或均勻分佈產生(0,1)的隨機數建立任意維度的embedding字典。

====== the input_embedding ====== 
[[0 0 0 0 1 0]
 [1 0 0 0 0 0]
 [0 0 1 0 0 0]]

3.4 DIN應用

回到DIN程式碼,從註釋中可以看出DIN在此構建user,item的embedding lookup table,將輸入資料轉換為對應的embedding,就是把稀疏特徵轉換為稠密特徵。具體就是用各種變數作為id,在對應的embeddings_var中查詢變數。比如用self.mid_batch_ph作為id,在self.uid_embeddings_var中查詢變數。

class Model(object):
    def __init__(self, n_uid, n_mid, n_cat, EMBEDDING_DIM, HIDDEN_SIZE, ATTENTION_SIZE, use_negsampling = False):
        with tf.name_scope('Inputs'):
            self.mid_his_batch_ph = tf.placeholder(tf.int32, [None, None], name='mid_his_batch_ph')
            self.cat_his_batch_ph = tf.placeholder(tf.int32, [None, None], name='cat_his_batch_ph')
            self.uid_batch_ph = tf.placeholder(tf.int32, [None, ], name='uid_batch_ph')
            self.mid_batch_ph = tf.placeholder(tf.int32, [None, ], name='mid_batch_ph')
            self.cat_batch_ph = tf.placeholder(tf.int32, [None, ], name='cat_batch_ph')

        # Embedding layer
        with tf.name_scope('Embedding_layer'):
            # shape: [U, H/2], user_id的embedding weight. U是user_id的hash bucket size,即user count
            self.uid_embeddings_var = tf.get_variable("uid_embedding_var", [n_uid, EMBEDDING_DIM])
            # 從uid embedding weight 中取出 uid embedding vector
            self.uid_batch_embedded = tf.nn.embedding_lookup(self.uid_embeddings_var, self.uid_batch_ph)

            # shape: [I, H/2], item_id的embedding weight. I是item_id的hash bucket size,即movie count
            self.mid_embeddings_var = tf.get_variable("mid_embedding_var", [n_mid, EMBEDDING_DIM])
            # 從mid embedding weight 中取出 uid embedding vector
            self.mid_batch_embedded = tf.nn.embedding_lookup(self.mid_embeddings_var, self.mid_batch_ph)
            # 從mid embedding weight 中取出 mid history embedding vector,是正樣本
            # 注意 self.mid_his_batch_ph這樣的變數 儲存使用者的歷史行為序列, 大小為 [B, T],所以在進行 embedding_lookup 時,輸出大小為 [B, T, H/2]; 
            self.mid_his_batch_embedded = tf.nn.embedding_lookup(self.mid_embeddings_var, self.mid_his_batch_ph)
            # 從mid embedding weight 中取出 mid history embedding vector,是負樣本
            if self.use_negsampling:
                self.noclk_mid_his_batch_embedded = tf.nn.embedding_lookup(self.mid_embeddings_var, self.noclk_mid_batch_ph)

            # shape: [C, H/2], cate_id的embedding weight. C是cat_id的hash bucket size
            self.cat_embeddings_var = tf.get_variable("cat_embedding_var", [n_cat, EMBEDDING_DIM])
            # 從 cid embedding weight 中取出 cid history embedding vector,是正樣本
            # 比如cat_embeddings_var 是(1601, 18),cat_batch_ph 是(?,),則cat_batch_embedded 就是 (?, 18)
            self.cat_batch_embedded = tf.nn.embedding_lookup(self.cat_embeddings_var, self.cat_batch_ph)
            # 從 cid embedding weight 中取出 cid embedding vector,是正樣本
            self.cat_his_batch_embedded = tf.nn.embedding_lookup(self.cat_embeddings_var, self.cat_his_batch_ph)
            # 從 cid embedding weight 中取出 cid history embedding vector,是負樣本
            if self.use_negsampling:
                self.noclk_cat_his_batch_embedded = tf.nn.embedding_lookup(self.cat_embeddings_var, self.noclk_cat_batch_ph)

至此我們解決了關於embedding的前兩個問題:

  • embedding層起到什麼作用?
  • embedding_lookup究竟用來做什麼?

對於第三個問題:如何更新mid_embeddings_var這樣的embedding層?我們將在下文進行講解,敬請期待。

0xFF 參考

tf.nn.embedding_lookup中關於partition_strategy引數詳解

深度學習中 Embedding層兩大作用的個人理解

深入理解 Embedding層的本質

tf.nn.embedding_lookup函式原理?

Embedding原理和Tensorflow-tf.nn.embedding_lookup()

求通俗講解下tensorflow的embedding_lookup介面的意思?

embedding_lookup的學習筆記

tf.nn.embedding_lookup

Logit究竟是個啥?——離散選擇模型之三

用NumPy手工打造 Wide & Deep

詳解 Wide & Deep 結構背後的動機

見微知著,你真的搞懂Google的Wide&Deep模型了嗎?

Embedding技術在深度學習推薦系統中的應用

深度學習推薦系統 | Embedding,從哪裡來,到哪裡去

Embedding技術在深度學習推薦系統中的應用

深度學習推薦系統中各類流行的Embedding方法(上)

深度學習推薦系統中各類流行的Embedding方法(下)

相關文章