日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

推荐算法注意点和DeepFM工程化实现

發布時間:2024/10/8 编程问答 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 推荐算法注意点和DeepFM工程化实现 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

?PaperWeekly 原創 ·?作者|賁忠奇

學校|混沌大學推薦算法工程師

研究方向|推薦算法、反作弊


緣起

今年疫情期間開始優化公司的推薦系統,因為 DeepFM 具有使用線性特征、低階交叉特征和高階特征的優點,決定采用此算法試試能否提高線下的 auc 和線上的 CTR 預估。

DeepFM 算法介紹詳見 [1],在 DeepFM 工程化的時候,遇到了特征稀疏、一列多值和共享權重的情況,主要參考石塔西的實現。那我為什么要繼續炒冷飯呢?因為石塔西實現的 TensorFlow 框架用的是高階 api,顯得靈活性低一些。

主要存在兩個問題,在實際過程中,無法保存 auc 最優的模型,early stopping 也不能保證停在效果最好的階段;在線上預測階段是不能按照文件的方式去讀取。主要是針對以上兩個問題,進行改造,實現了工程化上線。

效果方面:點擊率 PV 提升了 2.67%;點擊率 UV 提升了 3.64%;平均點擊數提升了 4.53%。推薦系統實際工程中需要注意樣本、特征、算法等方方面面的問題,下面開始介紹整個項目。


項目背景

混沌大學 APP(以下簡稱 APP)是一個提供哲科思維和創新商業的課程在線學習軟件,在線視頻學習是 APP 提供的最重要的業務功能。APP 內提供上百門十幾分鐘至幾小時不等的長視頻課程,為了用戶更快的發現合適自己的課程,以及擁有更好的學習體驗,APP 提供了課程推薦的功能。

▲ 算法頁面

推薦模式

由于 app 推薦的物品通常是十幾分鐘到幾小時的長視頻課程學習,且課程數量相對較少,學員存在斷續觀看、重復觀看的需求;并且幾百門課如果采用 feed 流方式(推薦視頻不重復)很容易就把所有課程內容刷沒了。因此沒有使用 feed 流的推薦模式,而是使用了 topN 推薦。


算法架構

由于推薦的物品課程總量僅為百級,因此沒有做召回而實現了排序+重排序。為了解決有限的計算資源與實效性之間的矛盾,采用了實時異步的架構,即在用戶有行為時去為用戶預測一個新的課程列表并緩存,在用戶請求時,才將這個緩存列表發給用戶,即拉模式(pull),整體架構如下:

▲ 工程架構

下面從樣本、特征工程、算法改造、重排序、探索位介紹整個項目流程。

樣本

模型學習的內容是樣本,模型效果的上限在樣本,所以樣本的設計和選擇非常重要。如何選擇正負樣本?樣本沖突了怎么辦?如何使得樣本表達的內容豐富,受到噪聲干擾比較小?在線系統的特征獲取比離線訓練某些特征數據有延遲怎么辦?

面對以上問題,整個樣本的生成采取寧缺毋濫的原則,對于不確定是否正確的數據,均不采納。例如簡單的樣本時間錯誤(數據生成時間早于動作時間或數據生成時間與動作時間相差太遠等),這種數據剔除掉。下面重點講正負樣本的選擇、樣本沖突、樣本下采樣和數據穿透問題。

4.1 正負樣本選擇

正樣本的選擇相對比較容易,點擊即為正。負樣本的選擇相對復雜一點:課程曝光,并不代表用戶注意到了。因此選擇用戶在推薦列表最下面的一個點擊位置以上的曝光作為負樣本區域。

例如以下展示列表和點擊動作情況(最下一個點擊位為7),使用 3、7 為正樣本,1、2、4、5、6 為負樣本。而 8、9、10 等位置雖然曝光但用戶可能并未看到,丟棄該數據。正負樣本的比例一般設置為 1:1 或者 2:3。

▲ 一條請求數據返回的樣本列表點擊情況

4.2 樣本沖突

課程可以重復推薦,這樣就存在用戶選擇前后矛盾的情況,即對于同一個課程上次用戶選擇點擊,而這次選擇不點擊,或者反過來上次選擇不點擊,這次選擇點擊。

對于一個時效性要求較高的系統,將這兩種情況的數據都作為樣本加入系統,可以增加模型對時效性特征的理解。由于用戶可能某個時間段未關注、或者被其他課程干擾、或者某個時刻的心理狀態等等問題存在。

在寧缺毋濫的原則下,選擇了一刀切去掉負樣本的方式,即?24 小時之內,存在正負樣本沖突的,僅保留正樣本。同時如果 24 小時之內有多個負樣本,僅保留最后也就是最新的負樣本。

4.3 樣本下采樣

由于用戶不同天以及天內對推薦系統的使用頻率不同,會導致整個樣本數據就會傾向于樣本時間段內使用頻率加高的那些樣本用戶,這對其他的用戶是不公平的。同理,不同的課程的每天曝光量也有很大差別。因此分別以用戶和課程為單位對整體樣本進行下采樣,使得樣本數量的比例更加平滑一些。

4.4 數據穿透問題

推薦系統需要考慮數據穿透的問題,即特征數據一定要選取樣本發生時刻之前的,如果選取了樣本時刻之后的特征,相當于在學習階段,讓模型知道了標準答案,使得模型僅學會了對答案進行抄襲,而在上線預測時,標準答案還存在于尚未發生的未來的,模型此時得不到標準答案,預測結果就很差。

由于 CTR 預估對實時性要求高,實際過程中存在另外一種數據穿透的情況,線上數據延遲帶來的穿透。

即,在離線訓練階段,如果選取樣本時刻之前的所有特征,這些特征本來是沒有穿透的,但是上線階段,由于前端、后臺、數據系統等等存在一些延遲,最近時間的一些特征在預測時并沒有流入推薦系統,導致線上預測階段,模型拿到的是殘缺的數據。這樣會導致模型離線階段效果還不錯,線上階段預測效果就不好的情況。

面對這個問題,在算法離線訓練階段,就不考慮樣本最近一段時間的特征數據,即讓模型只使用殘缺的數據學習,逼迫模型從殘缺的數據中發掘數據關聯,即不依賴線上容易發生延遲的數據部分。這個處理會使得線下 AUC 指標降低,即降低了算法上限,但在數據延遲的情況下有更好的健壯性。


特征工程

特征延用舊算法中的大部分,只有 word2vec 類型的特征沒有加入。本身這些文本類別的特征都能在 embedding 層被編碼表示,也加入課程屬于的類別、用戶側點擊、搜索、分享等特征做成長興趣和短興趣兩種。對于數據歸一化,分桶等常規操作就不介紹了。

由于 DeepFM 輸入數據需要有索引和值的需求,所以將數據分為4類。一類是連續的單值:課程的播放比率,下載次數,距今時間等;一類是連續的一列多值:用戶側老師的刻畫,課程標簽等;一類是離散單值:是否有研習社權限,課程是否免費;一類是離散的一列多值:用戶和課程的聯系,課程的多個老師等。


DeepFM

在實際使用的 DeepFM 算法的過程中,有些特征會比較稀疏,并且存在一列多值的情況和權重共享的要求,需要解決這三個方面的實際問題。

1. 稀疏要求:課程、用戶標簽、課程標簽、老師、關鍵詞都很多并且稀疏,可以采用編碼方式,自動轉成 embedding;

2. 一列多值:比如說用戶點擊過的課程是一個序列;用戶可以有多個標簽;用戶可以和課程建立多個聯系等,將同一列多個元素的 embeding 做累加;

3. 權重共享:用戶對課程點擊或者觀看時長都是對課程的刻畫;用戶畫像和課程畫像共用同一個詞表。

正好看到石塔西的代碼,能夠解決以上的問題,但是還存在兩個問題需要解決:

1. 在實際過程中,無法保存 auc 最優的模型,early stopping 也不能保證停在效果最好的階段;

2. 在線上預測階段是不能按照文件的方式去讀取。

因此需要對模型訓練部分和模型預測部分的數據輸入進行修改和優化,將 TensorFlow 高階的 API 改成低階的 API,實現更加靈活的控制。

原文采用 tf_utils.to_sparse_input_and_drop_ignore_values(dense_Xs[k]) 將稠密文件矩陣轉成稀疏矩陣。按照輸出的格式采用 sparse_element(下面代碼中有)方法和 tf.compat.v1.SparseTensorValue 構建起一行一行的讀入方式便于線上實時預測使用:

class?FeaturePredictV2(object):@classmethoddef?sparse_element(cls,suffix,?flag,?max_tokens):#構造稀疏特征,按照行處理成需要的格式values_lst?=?[]indices_lst?=?[]row_length?=?len(suffix)for?i?in?range(row_length):for?j?in?range(len(suffix[i])):values_lst.append(suffix[i][j])indices_lst.append([i,?j])indices?=?np.array(indices_lst,?dtype=np.int)values?=?np.array(values_lst,?dtype=np.int32)?if?flag?else?np.array(values_lst,?dtype=np.float32)dense_shape?=?np.array([row_length,?max_tokens])return?[indices,?values,?dense_shape]@classmethoddef?get_processed_data(cls,data_pre):#數據讀入并處理X?=?{}name_list?=?[t[0]?for?t?in?COLUMNS_MAX_TOKENS]df?=?pd.DataFrame(data_pre,?columns=name_list)for?colname,?max_tokens?in?COLUMNS_MAX_TOKENS:kvpairs?=?[i.split(",")[:max_tokens]?for?i?in?df[colname]]ids?=?[]vals?=?[]for?lines?in?kvpairs:id?=?[]val?=?[]try:for?line?in?lines:splited?=?line.split(":")id.append(splited[0])val.append(splited[1])except?Exception?as?err:print('get_processed_data?error?{}'.format(sys.exc_info()))print(err)traceback.print_exc()ids.append(id)vals.append(val)ids_elements?=?cls.sparse_element(ids,?1,?max_tokens)vals_elements?=?cls.sparse_element(vals,?0,?max_tokens)X[colname?+?"_ids"]?=?tf.compat.v1.SparseTensorValue(ids_elements[0],?ids_elements[1],?ids_elements[2])X[colname?+?"_values"]?=?tf.compat.v1.SparseTensorValue(vals_elements[0],?vals_elements[1],?vals_elements[2])return?Xif?__name__?==?'__main__':data_pre?=?[]with?open('data/train/train_st.dat')?as?f:for?line?in?f.readlines()[:3]:line_list?=?line.split("?")[1:]processed_line?=?[i.rstrip("\n")?for?i?in?line_list]#?print(processed_line)data_pre.append(processed_line)print(FeaturePredictV2.get_processed_data(data_pre))

模型訓練部分保存 auc 最高的部分:主要是構圖和保存模型兩段代碼。

構圖部分如下,將原代碼中高階 API 的核心部分用低階 API 重新寫成,將 sess、init、features、labels、train_graph、cost、predictions、embedding_table 靈活提取出來,可以供整個過程的使用。

?def?build_model(self,?params):"""構圖:param?name::param?params::return:"""train_graph?=?tf.Graph()with?train_graph.as_default():features?=?{}for?c,?max_tokens?in?COLUMNS_MAX_TOKENS:features[c?+?"_ids"]?=?tf.compat.v1.sparse_placeholder(tf.int32,?shape=[None,?max_tokens])features[c?+?"_values"]?=?tf.compat.v1.sparse_placeholder(tf.float32,?shape=[None,?max_tokens])labels?=?tf.compat.v1.placeholder(tf.int32,?shape=[None,?1])x?=?tf.compat.v1.placeholder(tf.int32)y?=?tf.compat.v1.placeholder(tf.int32)for?featname,?featvalues?in?features.items():if?not?isinstance(featvalues,?tf.SparseTensor):raise?TypeError("feature[{}]?isn't?SparseTensor".format(featname))embedding_table?=?self.build_embedding_table(params)linear_logits?=?self.output_logits_from_linear(features,?embedding_table,?params)bi_interact_logits,?fields_embeddings?=?self.output_logits_from_bi_interaction(features,?embedding_table,params)dnn_logits?=?tf.cond(tf.less(x,?y),?lambda:?self.f1(fields_embeddings,?params),lambda:?self.f2(fields_embeddings,?params))general_bias?=?tf.compat.v1.get_variable(name='general_bias',?shape=[1],?initializer=tf.constant_initializer(0.0))#?logits?=?linear_logits?+?bi_interact_logitslogits?=?linear_logits?+?bi_interact_logits?+?dnn_logitslogits?=?tf.nn.bias_add(logits,?general_bias)??#?bias_add,獲取broadcasting的便利#?del?featureslogits?=?tf.reshape(logits,?shape=[-1])predictions?=?tf.sigmoid(logits)labels?=?tf.cast(labels,?tf.float32)labels?=?tf.reshape(labels,?shape=[-1])cost?=?tf.reduce_mean(tf.nn.sigmoid_cross_entropy_with_logits(logits=logits,?labels=labels))optimizer?=?params['optimizer'].minimize(cost,?global_step=tf.compat.v1.train.get_global_step())#?auc?=tf.compat.v1.metrics.auc(labels,?predictions)sess?=?tf.compat.v1.Session(graph=train_graph)init?=?tf.group(tf.compat.v1.global_variables_initializer(),?tf.compat.v1.local_variables_initializer())return?Struct(sess=sess,?init=init,?features=features,?labels=labels,?train_graph=train_graph,?cost=cost,predictions=predictions,?x=x,?y=y,optimizer=optimizer,?embedding_table=embedding_table)

訓練保存 auc 最優模型部分:

在訓練的迭代過程中,會有多個模型的產生,高階 API 雖然可以保存最優的幾個或者比較優秀的,但是在每晚的訓練模型中我們需要找到最優的。因此提取高階 API 的思路采用低階 API 的方式重新寫成想要的,如果 auc 高于之前最高的就當前的覆蓋之前最好的模型,只保留一個最優的模型。

?def?train(self,?train_data,?test_data,?save_path=model_dir):train_len_data?=?len(train_data)train_total_batch?=?train_len_data?//?self.batch_sizeprint("train_data?length:{},toal_batch?of?training?data:{}".format(train_len_data,?train_total_batch))test_len_data?=?len(test_data)test_total_batch?=?test_len_data?//?self.batch_sizeprint("test_data?length:{},toal_batch?of?testing?data:{}".format(test_len_data,?test_total_batch))self.model?=?self.build_model(self.param)self.model.sess.run(self.model.init)max_score?=?0.0fl_train?=?Feature_load(self.train_eval,?self.num_epochs,?self.batch_size,?self.train_shuffle)train_input?=?fl_train.input_fn(train_data)fl_eval?=?Feature_load(self.train_eval,?self.num_epochs,?self.batch_size,?self.test_shuffle)test_input?=?fl_eval.input_fn(test_data)for?epoch?in?range(self.num_epochs):loss_static?=?[]all_predictions=[]all_labels=[]for?i?in?range(train_total_batch):train_feed_dict?=?self.get_input_data(train_input,?True)#?loss,?_?=?self.model.sess.run([self.model.cost,?self.model.optimizer],?feed_dict=train_feed_dict)loss,?_,predictions,labels?=?self.model.sess.run([self.model.cost,?self.model.optimizer,?self.model.predictions,self.model.labels],feed_dict=train_feed_dict)#?print(auc)loss_static.append(loss)all_predictions.extend(predictions)all_labels.extend(labels)avg_loss?=?float(sum(loss_static))?/?len(loss_static)r?=?EvaMgr(['AUC'])test_auc_entity=r.evaluate(all_labels,all_predictions)test_auc?=test_auc_entity['AUC']print("Epoch:{}/{}".format(epoch,?self.num_epochs),"Train?loss:{:.3f}?Train?AUC:{:.3f}".format(avg_loss,test_auc))eva,?avg_test_loss?=?self.evaluate(test_total_batch,?test_input,?['AUC',?'Acc',?'NE'])score?=?eva['AUC']print("Epoch:{}/{}?Test?loss:{:.3f}?Test?AUC:?{:.3f}".format(epoch,?self.num_epochs,?avg_test_loss,?score))print(eva)if?score?>?max_score:max_score?=?scoreprint('saving?model,acc:'?+?str(score))if?not?os.path.exists(save_path):os.mkdir(save_path)self.save(saved_model)

以上的三段代碼還需要其他才能跑起來,讀者可以自行構造一些數據并結合石塔西公開的代碼,由于公司限制不能公開全部代碼,但是把主要的思路和改造部分貼了出來,供大家參考。


重排序

推薦系統線上預測通常對于相似的課程會得到比較相似的得分,在推薦列表中展示時會表現出相鄰的課程通常會比較相似。

因此,對 CTR 預估的推薦列表進行了重排序,如果相鄰的兩個課程有相同的教師或者學籍,則對排名考慮的課程與后續課程互換位置進行微調,使得展示列表更加平衡。經過測試,調整后的列表指標上與調整前沒有明顯差異,但是展示效果更好。


探索位

推薦系統與熱單都會傾向不再推質量較低的,大部分用戶不太喜歡或者曝光少的的課程。長期會導致一些課程得不到曝光的機會,甚至徹底沉沒。

為了解決該問題,增加了一個探索推薦位,強制抽選一個該用戶曝光最少的課程(曝光比例越低,被選中的概率越大),放在前十位推薦的課程中。該方法會在一定程度上降低點擊率,但是會增加課程的整體曝光度,降低馬太效應,提升課程多樣性和新穎性。


線上下線一致性驗證

CTR 預估的 bug 相對于業務系統其實比較難以發現和測試,因為 CTR 預估的 bug 通常僅導致線上指標的下降而非功能的缺失和錯誤。?

算法模型通常分為離線訓練階段和上線預測階段,離線階段需要批量對數據進行特征處理,而預測階段需要對數據單個特征處理,基于處理速度和計算性能的考慮,以及兩個階段對于特征數據時間的處理不同,通常會使用兩套不同的代碼。

離線代碼可以通過離線指標保證其正確性,而在線代碼如果有 bug 很難通過在線指標進行驗證,這是由于:

1. 在線指標同時受很多其他因素影響;

2. 在線指標需要積累一定的數據才能統計,此時才發現問題,實際上有 bug 的代碼已經運行了一段時間。

解決這個問題的方法:

1. 對于特征處理離線部分和在線部分盡可能多的使用相同的子函數(雖然整體流程不一致,但是部分子過程是相同的),對于不一致的部分例如樣本時間的處理,可以作為子函數參數傳遞;

2. 在上線之前,分別使用離線部分和在線部分的代碼對相同的樣本進行預測,如果樣本的浮點數得分完全相同(小數點后若干位),則可以認為在線部分沒有 bug。



評價指標

評價指標對推薦系統有重要意義,是衡量推薦算法優劣的重要依據,根據業務場景需要采用了四個評價指標來衡量 CTR 預估,并以前三個為主,第四個做參考。

1. 平均點擊率 PV = 總點擊 PV/總展示 PV
2. 平均點擊率 UV = 總點擊 UV/總展示 UV
3. 平均點擊數 = 總點擊 PV/總展示 UV
4. 平均觀看時長 = 總播放時長/播放 UV



效果

▲ 點擊率增量指標



深入一步

在點擊率提升后就做推薦系統的多目標優化,現在做點擊和時長的優化,具體的思想工具參見這篇文章 [4],全面總結了多目標的各種情況。由于點擊和時長相關關系還是很明顯,所以采用阿里的 ESMM。


參考文獻

[1]?https://zhuanlan.zhihu.com/p/248895172

[2] https://www.ijcai.org/Proceedings/2017/0239.pdf

[3] https://zhuanlan.zhihu.com/p/48057256

[4]?https://zhuanlan.zhihu.com/p/183760759

更多閱讀

#投 稿?通 道#

?讓你的論文被更多人看到?

如何才能讓更多的優質內容以更短路徑到達讀者群體,縮短讀者尋找優質內容的成本呢?答案就是:你不認識的人。

總有一些你不認識的人,知道你想知道的東西。PaperWeekly 或許可以成為一座橋梁,促使不同背景、不同方向的學者和學術靈感相互碰撞,迸發出更多的可能性。?

PaperWeekly 鼓勵高校實驗室或個人,在我們的平臺上分享各類優質內容,可以是最新論文解讀,也可以是學習心得技術干貨。我們的目的只有一個,讓知識真正流動起來。

?????來稿標準:

? 稿件確系個人原創作品,來稿需注明作者個人信息(姓名+學校/工作單位+學歷/職位+研究方向)?

? 如果文章并非首發,請在投稿時提醒并附上所有已發布鏈接?

? PaperWeekly 默認每篇文章都是首發,均會添加“原創”標志

?????投稿郵箱:

? 投稿郵箱:hr@paperweekly.site?

? 所有文章配圖,請單獨在附件中發送?

? 請留下即時聯系方式(微信或手機),以便我們在編輯發布時和作者溝通

????

現在,在「知乎」也能找到我們了

進入知乎首頁搜索「PaperWeekly」

點擊「關注」訂閱我們的專欄吧

關于PaperWeekly

PaperWeekly 是一個推薦、解讀、討論、報道人工智能前沿論文成果的學術平臺。如果你研究或從事 AI 領域,歡迎在公眾號后臺點擊「交流群」,小助手將把你帶入 PaperWeekly 的交流群里。

總結

以上是生活随笔為你收集整理的推荐算法注意点和DeepFM工程化实现的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。