MindSpore——詳解推薦模型的原理與實踐
相信大家都在淘寶購買過相關的商品。比如某一天用戶購買了一臺臺式主機后,淘寶會立刻給該用戶推送與電腦相關的商品,例如電腦顯示屏,鍵盤和鼠標等等。這些推薦的物品完全是淘寶根據大量的點擊數據去學習到的。
傳統的推薦系統以協同過濾為代表,而隨著深度學習的快速發展,越來越多的研究者嘗試應用深度學習技術到推薦系統中。在此我們主要介紹其中的Wide&Deep網絡。
總體流程
在此我們以APP商店中的推薦系統為例,其整體流程可以如下圖所示
給定一個查詢,這個查詢可能是用戶相關的特征,推薦系統首先會從數據庫中檢索到查詢相關的APP,由于APP的數量非常巨大,因此我們可以取最相關的100個檢索結果作為候選APP,這一過程通常叫作粗排。
然后將候選的100個APP送入排序模型中,此處的排序模型就是我們下面將要介紹的Wide&Deep模型,這一過程也被叫作精排。
排序完成后,我們可以將點擊概率最高的APP放置于用戶最容易注意到的地方。無論用戶是否點擊了我們的推薦結果,我們都可以構造一個新的日志文件。在累積了一定數量的日志文件后,就可以繼續微調排序模型,提高模型的準確程度。
Wide&Deep模型自從被提出后就引起來非常大的關注。正如其名,此模型主要可以分成兩個部分,Wide部分和Deep部分。
Wide模型
它的設計目的是為了記住數據中特定的特征組合方式。例如前面介紹購買電腦的場景,購買了電腦主機的用戶,再次購買顯示器,鍵盤等物件的概率特別高。因此可以將用戶最近是否購買了電腦作為輸入模型的輸入,也就是特征。
假設當前wide網絡只有一個特征,當該特征取1時,y=wx可以得到y=0.9(假設w的值是0.9),當該特征取0時,y=0。y輸出值越大,會增大模型對應用戶點擊概率的估計。
回到我們APP應用推薦的場景中,可以看到圖右側的Cross Product Transformation,就是我們的wide部分的輸入。Cross Product Transformation是指特征交叉,即將User Installed App和Impression APP進行組合。
例如用戶手機已經安裝了微信,且當前的待估計的APP為QQ,那么這個組合特征就是(User Installed App=’微信’, ?Impression APP=’QQ’)。
Deep模型
介紹完Wide部分,我們重新回來再看下Deep部分。Deep部分的設計是為了模型具有較好的泛化能力,在輸入的數據沒有在訓練集中出現時,它依然能夠保持相關性較好的輸出。
在下圖中,Deep模型輸入都是一些含義不是非常明顯的特征,例如設備類型,用戶統計數據等類別特征(Categorical Features)。類別特征一般屬于高維特征。
例如手機的種類可能存在成千上萬個,因此我們通常把這些類別特征通過嵌入(Embedding)的方式,映射成低維空間的參數向量。這個向量可以被認為表示了原先這個類別特征的信息。對于連續特征,其數值本身就具備一定的含義,因此可以直接將和其他嵌入向量進行拼接。
在拼接完成后,可以得到大致為1200維度的向量。將其作為三層全連接層網絡的輸入,并且選擇Relu作為激活函數,其中每層的輸出維度分別為[1024, 512, 256]。
在廣告點擊率預估的場景中,模型的輸出是一個0~1之間的值,表示當前候選APP被點擊的概率。因此可以采用邏輯回歸函數,將Wide&Deep部分的輸出壓縮到0~1在之間。
首先,Deep部分的輸出是一個256維度的向量,可以通過一個線性變換將其映射為維度為1的值,然后和Wide部分的輸出進行求和,將求和后的結果輸入到邏輯回歸函數中。
在介紹完模型之后,現在開始動手實踐了,由于谷歌公司并沒有將其在論文中使用的APP商店數據集進行公開,因此我們現在采用Criteo公司發布在Kaggle的廣告點擊率預估數據集Criteo。
在模型定義前,我們需要對數據進行預處理。數據處理的目的是將數據集中的特征取值映射為數值id,并且去除一些出現次數過少的特征值,避免特征值出現次數過少,導致當特征值對應的參數向量的更新次數過少,影響模型的精度。Criteo數據集由13類連續特征和26列類別特征,已經通過哈希方式映射為了32位數值。對應的標簽(label)的取值為0和1。
還記得之前提到過的低維嵌入過程嗎?映射操作是為了模型中的嵌入向量查找而準備的。例如,輸入數據中的A,B,C被映射為了0,1,2。而0,1,2分別表示在嵌入矩陣中(Embedding Table)的第0行,第1行和第2行。
將Criteo數據集下載后解壓,可以看到train.txt和test.txt文件。查看train.txt中的文件,可以看到其中某些行存在缺失。
我們可以調用
model_zoo/wide_and_deep/src/preprocess_data.py
進行數據的下載和處理,對于缺失值,可以將其標記為OOV(Out Of Vocabulary)對應的id。
介紹完數據處理后,我們在此開始定義模型。模型的核心代碼在mindspore倉庫下
model_zoo/wide_and_deep/src/wide_and_deep.py
在MindSpore中,網絡的定義方式和Pytorch比較接近,先定義定義的操作,然后再construct函數中對調用對應的操作對輸入進行處理。
首先我們再回憶下Wide&Deep網絡,它由一個Wide部分和Deep部分。其中Wide部分是一個線性網絡。
在實現上,我們將線性網絡中的權重視為維度為1,通過嵌入矩陣查找的方式即可獲得輸入x對應的權重,然后將其和輸入的mask相乘,將結果求和。
值得注意的是,我們將連續特征和類別特征進行等同處理,因此這里的mask是為了將連續特征和類別特征進行區而設計的。連續特征mask中的值即為連續特征值,類別特征mask中的值為1。Wide部分的核心代碼如下所示,我們定義個名為self.wide_w的權重,它的形狀為[詞表大小,1]。
class WideDeepModel(nn.Cell): ? ?def __init__(self, config): ? ? ? ?super(WideDeepModel, self).__init__() ? ? ? ?… ? ? ? ?init_acts = [('Wide_w', [self.vocab_size, 1], self.emb_init), ? ? ? ? ? ? ? ? ? ? ('V_l2', [self.vocab_size, self.emb_dim], self.emb_init), ? ? ? ? ? ? ? ? ? ? ('Wide_b', [1], self.emb_init)] ? ? ? ?var_map = init_var_dict(self.init_args, init_acts) ? ? ? ?self.wide_w = var_map["Wide_w"] ? ? ? ?self.wide_b = var_map["Wide_b"] ? ? ? ?self.embeddinglookup = nn.EmbeddingLookup() ? ? ? ?self.mul = P.Mul() ? ? ? ?self.reduce_sum = P.ReduceSum(keep_dims=False) ? ? ? ?self.reshape = P.Reshape() ? ? ? ?self.square = P.Square()
def construct(self, id_hldr, wt_hldr): ? ? ? ?mask = self.reshape(wt_hldr, (self.batch_size, self.field_size, 1)) ? ? ? ?# Wide layer ? ? ? ?wide_id_weight = self.embeddinglookup(self.wide_w, id_hldr, 0) ? ? ? ?wx = self.mul(wide_id_weight, mask) ? ? ? ?wide_out = self.reshape(self.reduce_sum(wx, 1) + self.wide_b, (-1, 1)) ? ? ? ?out = wide_out
class WideDeepModel(nn.Cell): ? ?def __init__(self, config): ? ? ? ?super(WideDeepModel, self).__init__() ? ? ? ?… ? ? ? ?init_acts = [('Wide_w', [self.vocab_size, 1], self.emb_init), ? ? ? ? ? ? ? ? ? ? ('V_l2', [self.vocab_size, self.emb_dim], self.emb_init), ? ? ? ? ? ? ? ? ? ? ('Wide_b', [1], self.emb_init)] ? ? ? ?var_map = init_var_dict(self.init_args, init_acts) ? ? ? ?self.wide_w = var_map["Wide_w"] ? ? ? ?self.wide_b = var_map["Wide_b"] ? ? ? ?self.embedding_table = var_map["V_l2"] ? ? ? ?self.dense_layer_1 = DenseLayer(self.all_dim_list[0], ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?self.all_dim_list[1], ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?self.weight_bias_init, ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?self.deep_layer_act, ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?convert_dtype=True, drop_out=config.dropout_flag) ? ? ? ?self.dense_layer_2 = DenseLayer(self.all_dim_list[1], ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?self.all_dim_list[2], ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?self.weight_bias_init, ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?self.deep_layer_act, ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?convert_dtype=True, drop_out=config.dropout_flag) ? ? ? ?self.dense_layer_3 = DenseLayer(self.all_dim_list[2], ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?self.all_dim_list[3], ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?self.weight_bias_init, ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?self.deep_layer_act, ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?convert_dtype=True, drop_out=config.dropout_flag) ? ? ? ?self.dense_layer_4 = DenseLayer(self.all_dim_list[3], ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?self.all_dim_list[4], ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?self.weight_bias_init, ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?self.deep_layer_act, ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?convert_dtype=True, drop_out=config.dropout_flag) ? ? ? ?self.dense_layer_5 = DenseLayer(self.all_dim_list[4], ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?self.all_dim_list[5], ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?self.weight_bias_init, ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?self.deep_layer_act, ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?use_activation=False, convert_dtype=True, drop_out=config.dropout_flag)
self.embeddinglookup = nn.EmbeddingLookup() ? ? ? ?self.mul = P.Mul() ? ? ? ?self.reduce_sum = P.ReduceSum(keep_dims=False) ? ? ? ?self.reshape = P.Reshape() ? ? ? ?self.square = P.Square() ? ? ? ?self.shape = P.Shape() ? ? ? ?self.tile = P.Tile() ? ? ? ?self.concat = P.Concat(axis=1) ? ? ? ?self.cast = P.Cast()
def construct(self, id_hldr, wt_hldr): ? ? ? ?""" ? ? ? ?Args: ? ? ? ? ? ?id_hldr: batch ids; ? ? ? ? ? ?wt_hldr: batch weights; ? ? ? ?""" ? ? ? ?mask = self.reshape(wt_hldr, (self.batch_size, self.field_size, 1)) ? ? ? ?# Wide layer ? ? ? ?wide_id_weight = self.embeddinglookup(self.wide_w, id_hldr, 0) ? ? ? ?wx = self.mul(wide_id_weight, mask) ? ? ? ?wide_out = self.reshape(self.reduce_sum(wx, 1) + self.wide_b, (-1, 1)) ? ? ? ?# Deep layer ? ? ? ?deep_id_embs = self.embeddinglookup(self.embedding_table, id_hldr, 0) ? ? ? ?vx = self.mul(deep_id_embs, mask) ? ? ? ?deep_in = self.reshape(vx, (-1, self.field_size * self.emb_dim)) ? ? ? ?deep_in = self.dense_layer_1(deep_in) ? ? ? ?deep_in = self.dense_layer_2(deep_in) ? ? ? ?deep_in = self.dense_layer_3(deep_in) ? ? ? ?deep_in = self.dense_layer_4(deep_in) ? ? ? ?deep_out = self.dense_layer_5(deep_in) ? ? ? ?out = wide_out + deep_out ? ? ? ?return out, self.embedding_table
model_zoo/wide_and_deep/train_and_eval.py
def test_train_eval(config): ? ?""" ? ?test_train_eval ? ?""" ? ?data_path = config.data_path ? ?batch_size = config.batch_size ? ?epochs = config.epochs ? ?ds_train = create_dataset(data_path, train_mode=True, epochs=epochs, batch_size=batch_size) ? ?ds_eval = create_dataset(data_path, train_mode=False, epochs=epochs + 1, batch_size=batch_size) ? ?print("ds_train.size: {}".format(ds_train.get_dataset_size())) ? ?print("ds_eval.size: {}".format(ds_eval.get_dataset_size()))
net_builder = ModelBuilder()
train_net, eval_net = net_builder.get_net(config) ? ?train_net.set_train() ? ?auc_metric = AUCMetric()
model = Model(train_net, eval_network=eval_net, metrics={"auc": auc_metric})
eval_callback = EvalCallBack(model, ds_eval, auc_metric, config)
callback = LossCallBack(config=config) ? ?ckptconfig = CheckpointConfig(save_checkpoint_steps=ds_train.get_dataset_size(), keep_checkpoint_max=5) ? ?ckpoint_cb = ModelCheckpoint(prefix='widedeep_train', directory=config.ckpt_path, config=ckptconfig)
out = model.eval(ds_eval) ? ?print("=====" * 5 + "model.eval() initialized: {}".format(out)) ? ?model.train(epochs, ds_train, ? ? ? ? ? ? ? ?callbacks=[TimeMonitor(ds_train.get_dataset_size()), eval_callback, callback, ckpoint_cb])
一旦訓練完成后,就可以裝載模型參數進行評估。評估網絡和訓練網絡類似,只不過輸出經過了一個Sigmoid層。
class PredictWithSigmoid(nn.Cell): ? ?def __init__(self, network): ? ? ? ?super(PredictWithSigmoid, self).__init__() ? ? ? ?self.network = network ? ? ? ?self.sigmoid = P.Sigmoid()
def construct(self, batch_ids, batch_wts, labels): ? ? ? ?logits, _, _, = self.network(batch_ids, batch_wts) ? ? ? ?pred_probs = self.sigmoid(logits) ? ? ? ?return logits, pred_probs, labels
采用AUC作為評價指標。AUC廣泛的應用在分類模型的評估中,可以較好的反映模型學習的好壞,其值在0~1之間,值越高,模型的性能越好。
本文內容介紹了推薦系統的原理和實踐代碼。首先講述了推薦系統在我們生活中的應用場景,并且介紹了推薦系統的核心原理。然后詳細介紹了Wide&Deep網絡以及相關的代碼實踐,期望可以幫助大家入門
參考文獻
[1] Cheng H T, Koc L, Harmsen J, et al. Wide & deep learning for recommender systems[C]//Proceedings of the 1st workshop on deep learning for recommender systems. 2016: 7-10.
[2]https://www.mindspore.cn/tutorial/-zhCN/master/advanced_use/customized_debugging_information.html
昇騰
版權聲明:本文內容由網絡用戶投稿,版權歸原作者所有,本站不擁有其著作權,亦不承擔相應法律責任。如果您發現本站中有涉嫌抄襲或描述失實的內容,請聯系我們jiasou666@gmail.com 處理,核實后本網站將在24小時內刪除侵權內容。