推薦系統(tǒng)入門(mén)實(shí)踐(3)召回之youtube-dnn

DNN召回

這里指的是youtube的Deep Neural Network for YouTube Recommendation論文里提到的模型,論文里同時(shí)提出了召回和排序,這里先只介紹召回精刷。大廠畢竟還是nb啊揭措,看起來(lái)其實(shí)挺簡(jiǎn)單胆数,為什么別人提不出來(lái)呢?細(xì)節(jié)滿滿黎泣,意味著坑也滿滿恕刘。1.論文鏈接 2.簡(jiǎn)書(shū)上的靠譜分享 3.我之前在簡(jiǎn)書(shū)上的簡(jiǎn)單一提

直接上圖:

image.png

模型很簡(jiǎn)單,先看輸入抒倚。左下藍(lán)色的是item embedding褐着,是用item的id從random初始化的emb_matrix里look_up出來(lái)的,一個(gè)用戶(或者說(shuō)一條樣本更準(zhǔn)確些)的若干個(gè)item平均一下托呕,進(jìn)入dnn含蓉;
綠色的是用戶搜索的query,跟item本質(zhì)上沒(méi)什么不同项郊,也是平均一下進(jìn)入dnn馅扣;
再其他concat到一起的是各種各樣的特征,這里主要是用戶的特征着降。想一下為什么不用item的特征差油?因?yàn)閕tem這么多,你怎么知道加哪個(gè)item的特征叭蛋厌殉!當(dāng)然也許是有什么巧妙特征的,只是實(shí)踐里沒(méi)這么麻煩過(guò)侈咕。有了輸入公罕,經(jīng)過(guò)三層網(wǎng)絡(luò),就是輸出耀销,把這個(gè)模型當(dāng)做是超大規(guī)模的分類(lèi)模型楼眷,每一個(gè)item可以理解為一個(gè)類(lèi)。


問(wèn)題來(lái)了熊尉。

輸入的emb直接做均值合理嗎罐柳?
我隱約記得這是論文里指出的,好像是嘗試過(guò)add/concat效果都不如average狰住。在實(shí)踐中還是用了加權(quán)平均(但沒(méi)有跟直接平均對(duì)照試驗(yàn)過(guò)张吉,每次改動(dòng)的地方都不止一個(gè)),權(quán)重的設(shè)計(jì)思想就是跟(觀看后的)天數(shù)成反比催植、跟觀看完成率成正比肮蛹,公式是:

 weight_avg_watch = tf.exp(-self.batch_watchDays/7.0) * self.batch_watchProp 

那求均值的視頻/query個(gè)數(shù)要固定嗎勺择?
其實(shí)沒(méi)必要固定。在實(shí)踐中伦忠,為了簡(jiǎn)單起見(jiàn)省核,訓(xùn)練的時(shí)候樣本都是固定長(zhǎng)度的,而推理(后面會(huì)再說(shuō)一下)的時(shí)候就是不固定長(zhǎng)度的了昆码,有多少算多少(但設(shè)置max)气忠,然后求均值。

有梯度消失或梯度爆炸的問(wèn)題嗎赋咽?
這是有的旧噪,一看三層relu就知道有問(wèn)題了,實(shí)踐中改成了leakyrelu冬耿,有改善舌菜。其他的方法比如clip、BN亦镶、lr decay等都可以嘗試日月。

對(duì)時(shí)間怎么建模?example age是什么缤骨?
example age就是target item在“當(dāng)前”的年齡( the age of the training example )爱咬,沒(méi)有那么多彎彎繞,因?yàn)閥outube用戶對(duì)新視頻更加偏好绊起,即使相關(guān)性不太好也可以.

超大規(guī)模的分類(lèi)精拟,計(jì)算資源夠用嗎?
超大規(guī)模的分類(lèi)虱歪,實(shí)踐中通常是幾百萬(wàn)蜂绎,計(jì)算資源開(kāi)銷(xiāo)太大,光算loss都要半年笋鄙,所以不能這樣干师枣。我們用采樣的方式構(gòu)造負(fù)樣本,TensorFlow牛逼萧落,tf.nn.log_uniform_candidate_sampler這個(gè)函數(shù)的思想就是給定正樣本践美,在剩下的樣本里,越是靠前的越容易被抽出來(lái)當(dāng)負(fù)樣本找岖,這叫按照 log-uniform (Zipfian) 分布采樣陨倡。一般采五六百吧。注:vocab是按照頻率排的许布,因此越靠前兴革,就是越熱門(mén)的item。vocab怎么得到的蜜唾?可以在產(chǎn)出樣本的時(shí)候順便產(chǎn)出杂曲。貼一段代碼:

import org.apache.spark.ml.feature.{CountVectorizer, CountVectorizerModel}
...
...
    val watchDF = sqlContext.createDataFrame(sampleWatch).toDF("uid", "watch")
    val watchCvModel: CountVectorizerModel = new CountVectorizer()
      .setInputCol("watch")
      .setOutputCol("watch_fea")
      .setVocabSize(watchVocabSize)
      .setMinDF(watchVocabMinFreq)
      .fit(watchDF)
    val watchVocab = watchCvModel.vocabulary.zipWithIndex
    val formatWatchVocab = watchVocab.map(x => s"${x._1}\t${x._2}")
    sc.parallelize(formatWatchVocab, 1).saveAsTextFile(outputWatchVocab)

那直接用展示未點(diǎn)擊的來(lái)做負(fù)樣本不好嗎箕昭?
不好。一個(gè)是麻煩解阅。另一個(gè)是,展示未點(diǎn)擊的泌霍,其實(shí)是排序模型認(rèn)為很好的選項(xiàng)了货抄,直接用label=0來(lái)打擊它并不好,一般用戶對(duì)它是有一定的興趣的朱转。還有就是蟹地,要predicting future watch,而不是predicting held-out watch藤为,不要偷窺未來(lái)怪与。

要如何構(gòu)造樣本?上面兩段說(shuō)明了負(fù)樣本如何選擇缅疟,其實(shí)就是為了在這里說(shuō)樣本如何選擇分别。負(fù)樣本是采樣得到的,那構(gòu)造樣本的時(shí)候只要記錄input和正樣本就好了存淫。正樣本是什么耘斩?點(diǎn)擊的?觀看的桅咆?還是觀看完成度高于某個(gè)值的括授?這里需要根據(jù)業(yè)務(wù)來(lái)定,比如長(zhǎng)視頻岩饼,甚至還需要你將用戶分段的觀看累計(jì)起來(lái)荚虚。input是什么?我們有了用戶的觀看或者行為歷史籍茧,既可以一個(gè)用戶產(chǎn)出多條樣本版述,也可以一個(gè)用戶產(chǎn)出一條樣本,這兩個(gè)選項(xiàng)又分別有多種不同的方法硕糊。比如院水,一個(gè)用戶的正向行為list是abcdefg,那我的樣本可以是(abcd->e,abcd->f,abcd->g,bcde->f,bcde->g)简十。這個(gè)選擇比較自由檬某,也沒(méi)有對(duì)比過(guò)優(yōu)劣,應(yīng)該是差不多的螟蝙。

用戶特征選什么比較好恢恼?
用戶特征使用靜態(tài)特征(年齡、性別胰默、居住地...)比較好场斑,因?yàn)榻y(tǒng)計(jì)特征本質(zhì)上只是區(qū)分了用戶是否活躍漓踢,這不是我們想要的÷┮或者其他各種途徑得到的用戶emb特征喧半,也可以加進(jìn)去試試。

有什么其他的改進(jìn)嗎青责?

  • 例如加入attention挺据,這個(gè)實(shí)踐中效果有限,也有分享說(shuō)效果很好脖隶,應(yīng)該是業(yè)務(wù)不同導(dǎo)致的扁耐。
  • 例如將item的某些屬性(uploader、tag等)與item的emb concat到一起产阱,再計(jì)算平均值婉称。這其實(shí)相當(dāng)于又加了若干個(gè)query。
  • 使用LSTM替換average是否有效构蹬?基本沒(méi)效果王暗。

網(wǎng)絡(luò)結(jié)構(gòu)的代碼可見(jiàn) youtube-dnn-recall-structure.py


問(wèn)題又來(lái)了。

上面一節(jié)說(shuō)的主要是訓(xùn)練怎燥。這一節(jié)主要說(shuō)線上服務(wù)瘫筐。

那我要怎么提供線上服務(wù)啊铐姚?

  • 這其實(shí)還有一個(gè)內(nèi)部隱含的問(wèn)題策肝,就是誰(shuí)是item的emb,誰(shuí)是user的emb隐绵?我們看模型結(jié)構(gòu)示意圖之众,覺(jué)得item emb不是輸入的嗎?其實(shí)不是依许。網(wǎng)絡(luò)上有人將輸入的emb當(dāng)做item棺禾,也取得了不錯(cuò)的效果,這是有可能的峭跳,但膘婶,確實(shí)不太對(duì)。說(shuō)結(jié)論蛀醉,user的emb是最后一層隱藏層h(激活后)悬襟,item的emb就是這個(gè)隱藏層到softmax之前的權(quán)重矩陣W。為什么呢拯刁?因?yàn)閥 = Wh + b脊岳,如此才能顯示出W和h之間的聯(lián)系(這里其實(shí)有個(gè)問(wèn)題,b存在的意義是什么?實(shí)踐中前人的代碼里寫(xiě)了b割捅,但我覺(jué)得可能不需要b吧奶躯,只是后來(lái)沒(méi)做實(shí)驗(yàn)了。)亿驾。另外嘹黔,input的emb其實(shí)可以用其他方式得到的emb來(lái)初始化,例如item2vec的emb莫瞬;并且参淹,input的emb可以與W共享,同時(shí)更新乏悄,這樣就不用矛盾選擇哪個(gè)emb了。
  • 線上召回可以有兩種恳不,一是II召回檩小,也就是用item emb計(jì)算相似度,得到倒排烟勋,進(jìn)而召回规求,這里可以看出我們?cè)谏弦还?jié)的優(yōu)化中concat了uploader等屬性的好處,那就是同一個(gè)up的item之間天然的具有一定相似度卵惦;二是UI召回阻肿,也就是,將user存到couchbase/Aerospike里沮尿,將item用faiss訓(xùn)練為一個(gè)index丛塌,然后線上根據(jù)user查找相近的item。

加一個(gè)問(wèn)題畜疾,UI召回里的相似度怎么算赴邻?為什么?II呢啡捶?
UI召回的相似度是用內(nèi)積的姥敛,而不是余弦,這是因?yàn)榫W(wǎng)絡(luò)訓(xùn)練的時(shí)候就是內(nèi)積計(jì)算瞎暑。II召回的相似度彤敛,內(nèi)積、余弦了赌、歐氏距離墨榄,都可以嘗試,可以根據(jù)實(shí)際情況來(lái)決定揍拆,我在實(shí)踐中是都用過(guò)渠概,效果差不多,都還不錯(cuò)。但從理解上來(lái)說(shuō)播揪,余弦可能更科學(xué)一點(diǎn)贮喧,畢竟是同一空間?

再加一個(gè)問(wèn)題猪狈,softmax的W要不要?dú)w一化箱沦?hidden要不要?dú)w一化?也就是說(shuō)雇庙,user和item的emb需要?dú)w一化嗎谓形?從上文的理解來(lái)說(shuō),都行疆前,這個(gè)意思是寒跳,你在訓(xùn)練的時(shí)候怎么做的,在用的時(shí)候就怎么做竹椒。如果你網(wǎng)絡(luò)里歸一了童太,使用的時(shí)候就歸一,如果沒(méi)有胸完,那就不用书释,不然會(huì)起反效果(試驗(yàn)過(guò)的)。

插一個(gè)問(wèn)題赊窥,faiss訓(xùn)練index的具體原理爆惧?faiss是一種高效的k-means聚類(lèi)實(shí)現(xiàn),facebook 牛逼(破音)锨能!具體的我也沒(méi)太了解過(guò)扯再。安裝可以看久遠(yuǎn)的過(guò)去,使用可以看簡(jiǎn)書(shū)大佬的分享址遇。貼幾句使用示例吧:

    index = faiss.index_factory(dim, factory)
    index.nprobe = nprobe
    index.train(embedding_all)
    index.add_with_ids(embedding_all, fid_all)
    faiss.write_index(index, out_index)

好了叔收,現(xiàn)在知道item的emb要訓(xùn)練index索引了,當(dāng)然這個(gè)item emb在訓(xùn)練結(jié)束時(shí)候可以存下來(lái)傲隶。user的emb要怎么弄饺律?一種方法是python來(lái)load模型然后推理一次,一種方法是手寫(xiě)前向然后推理跺株。python的優(yōu)點(diǎn)是簡(jiǎn)單复濒,手寫(xiě)一般是用scala/java,優(yōu)點(diǎn)是可以為后續(xù)的實(shí)時(shí)化做準(zhǔn)備乒省。貼一小段代碼:

import breeze.linalg.{DenseMatrix, DenseVector, normalize}
...
...
  def feedForward(x: DenseVector[Double],
                  w: Array[DenseMatrix[Double]],
                  b: Array[DenseVector[Double]]): DenseVector[Double] = {
    val layer0 = w(0) * x + b(0)
    val relu0 = layer0.map { e => if (e > 0) e else 0.0 }
    val layer1 = w(1) * relu0 + b(1)
    val relu1 = layer1.map { e => if (e > 0) e else 0.0 }
    val layer2 = w(2) * relu1 + b(2)
    val relu2 = layer2.map { e => if (e > 0) e else 0.2 * e }
    relu2
  }

dnn召回的效果指標(biāo)都挺不錯(cuò)巧颈,展示占比也不低,是主要的召回源之一袖扛。實(shí)踐中砸泛,user的emb存到cb十籍,通常只存最新的,而item的index需要訓(xùn)練新舊兩個(gè)版本唇礁,避免cb沒(méi)刷完時(shí)user找不到item勾栗。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市盏筐,隨后出現(xiàn)的幾起案子围俘,更是在濱河造成了極大的恐慌,老刑警劉巖琢融,帶你破解...
    沈念sama閱讀 206,378評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件界牡,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡漾抬,警方通過(guò)查閱死者的電腦和手機(jī)宿亡,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)纳令,“玉大人她混,你說(shuō)我怎么就攤上這事〔幢” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,702評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵毯欣,是天一觀的道長(zhǎng)馒过。 經(jīng)常有香客問(wèn)我,道長(zhǎng)酗钞,這世上最難降的妖魔是什么腹忽? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,259評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮砚作,結(jié)果婚禮上窘奏,老公的妹妹穿的比我還像新娘。我一直安慰自己葫录,他們只是感情好着裹,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著米同,像睡著了一般骇扇。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上面粮,一...
    開(kāi)封第一講書(shū)人閱讀 49,036評(píng)論 1 285
  • 那天少孝,我揣著相機(jī)與錄音,去河邊找鬼熬苍。 笑死稍走,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播婿脸,決...
    沈念sama閱讀 38,349評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼粱胜,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了盖淡?” 一聲冷哼從身側(cè)響起年柠,我...
    開(kāi)封第一講書(shū)人閱讀 36,979評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎褪迟,沒(méi)想到半個(gè)月后冗恨,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,469評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡味赃,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評(píng)論 2 323
  • 正文 我和宋清朗相戀三年掀抹,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片心俗。...
    茶點(diǎn)故事閱讀 38,059評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡傲武,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出城榛,到底是詐尸還是另有隱情揪利,我是刑警寧澤,帶...
    沈念sama閱讀 33,703評(píng)論 4 323
  • 正文 年R本政府宣布狠持,位于F島的核電站疟位,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏喘垂。R本人自食惡果不足惜甜刻,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望正勒。 院中可真熱鬧得院,春花似錦、人聲如沸章贞。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,262評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)鸭限。三九已至就谜,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間里覆,已是汗流浹背丧荐。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留喧枷,地道東北人虹统。 一個(gè)月前我還...
    沈念sama閱讀 45,501評(píng)論 2 354
  • 正文 我出身青樓弓坞,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親车荔。 傳聞我的和親對(duì)象是個(gè)殘疾皇子渡冻,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評(píng)論 2 345