01 前言
友情提示:據(jù)不完全統(tǒng)計(jì),包含代碼片,通篇大約接近1萬(wàn)字,預(yù)計(jì)閱讀時(shí)間?我也不知道,哈哈,可以當(dāng)成短篇小說(shuō)來(lái)讀了。 02 推薦系統(tǒng)場(chǎng)景需求 本章節(jié)從場(chǎng)景的需求出發(fā),試圖來(lái)分析推薦系統(tǒng)需求的必須性,以及很多平臺(tái)言必談個(gè)性化推薦的現(xiàn)狀。 0.2.1 先從人工智能話(huà)題出發(fā) 說(shuō)推薦系統(tǒng)之前先掰掰人工智能,這個(gè)詞估計(jì)大家能能聽(tīng)得懂,畢竟是風(fēng)口上的名詞,想沒(méi)聽(tīng)過(guò)也難。那么問(wèn)題來(lái)了,你覺(jué)得推薦系統(tǒng)與人工智能有什么關(guān)系? 或許大半的人會(huì)認(rèn)為沒(méi)有半毛錢(qián)的關(guān)系,這讓我想到了前幾天周末在知乎上懟的一個(gè)問(wèn)題,問(wèn)題的核心就是:“現(xiàn)在大數(shù)據(jù)都很low了,大家都是去搞人智能了”。 這典型就是對(duì)于人工智能定義認(rèn)知的問(wèn)題,個(gè)人認(rèn)為人工智能就是一個(gè)偏業(yè)務(wù)的定義,多維度多學(xué)科交叉的概念,壓根兒就不好以技術(shù)維度去對(duì)比去評(píng)判。其核心的三要素就是:算法、計(jì)算能力以及數(shù)據(jù)。 圍繞大量的基礎(chǔ)數(shù)據(jù),對(duì)基礎(chǔ)數(shù)據(jù)進(jìn)行特征處理,然后構(gòu)建有用的業(yè)務(wù)算法模型,然后基于分布式的基礎(chǔ)架構(gòu)計(jì)算能力,將算法模型的用于實(shí)際的生產(chǎn)環(huán)境,以機(jī)器替代人工的作業(yè),以提升效果與效率,達(dá)到機(jī)器智能化的目標(biāo)。 那再回到推薦系統(tǒng)的話(huà)題,在過(guò)去傳統(tǒng)的門(mén)戶(hù)網(wǎng)站或者其他領(lǐng)域,也是有推薦場(chǎng)景的,不過(guò)大部分都是基于編輯或者運(yùn)營(yíng)手動(dòng)進(jìn)行配置推送,隨著對(duì)數(shù)據(jù)、對(duì)算法模型的進(jìn)一步應(yīng)用,才逐漸有算法機(jī)器替代人工進(jìn)行推薦,并且達(dá)到諸如“千人千面”、“個(gè)性化”推薦的效果。 所以,追究其本質(zhì),其實(shí)也是算法模型 計(jì)算過(guò)程 基礎(chǔ)數(shù)據(jù)的流程,并且最終達(dá)到了機(jī)器自動(dòng)化、智能化的效果,從廣義的角度來(lái)說(shuō),或許復(fù)雜一些的推薦系統(tǒng)或許也能納入人工智能的范疇了(真心怕那種一說(shuō)到人工智能=神經(jīng)網(wǎng)絡(luò)的選手)。 0.2.2 推薦與檢索兩種信息獲取的方式 說(shuō)到推薦系統(tǒng),就不得不說(shuō)一下搜索引擎。不管是搜索也好、推薦也好,他們都是信息獲取的一種機(jī)制,核心區(qū)別在于主動(dòng)與被動(dòng)。 搜索引擎是典型的主動(dòng)觸發(fā)的形態(tài),即用戶(hù)已經(jīng)有明確的信息獲取意圖,渴望得到自身既定的目標(biāo)信息,讓后通過(guò)搜索規(guī)則進(jìn)行最終信息的獲取。 比如,你好奇什么是人工智能,那么你就會(huì)用諸如谷歌、或者國(guó)產(chǎn)大百度去搜索,然后獲取到相關(guān)網(wǎng)頁(yè),去點(diǎn)擊查看,最終完成你了解人工智能這個(gè)信息獲取的目的。這就是檢索的機(jī)制,你先要告訴系統(tǒng)你的意圖,然后在給你篩選你要的信息。 而推薦系統(tǒng)則大大的不同,它是一種系統(tǒng)主動(dòng)的行為,對(duì)于用戶(hù)來(lái)說(shuō)是一個(gè)被動(dòng)的行為,被動(dòng)的接受系統(tǒng)推送過(guò)來(lái)的信息。那這樣強(qiáng)扭是不是很尷尬呢?怎么有這么SHA叉的機(jī)制? 其實(shí)不是的,尷尬的是推的不對(duì),東西推對(duì)了就尷尬了,比如你正在瀏覽一個(gè)信息,正在愁這個(gè)信息還沒(méi)解決你的問(wèn)題的時(shí)候,系統(tǒng)啪丟給你幾個(gè)新增的信息,說(shuō)這個(gè)幾個(gè)信息可能能解決的問(wèn)題,你一看我湊,這正是我要的,感謝萬(wàn)能的推薦系統(tǒng)! 所以,推薦核心解決的還是用戶(hù)額外信息獲取的問(wèn)題,以及提升用戶(hù)的進(jìn)一步轉(zhuǎn)化,停留時(shí)間的延長(zhǎng)(只要停留時(shí)間延長(zhǎng),商業(yè)轉(zhuǎn)化機(jī)會(huì)就會(huì)加大,也是粘性提升的體現(xiàn)),而問(wèn)題的核心就是要推的準(zhǔn),推的恰到好處,不然就是反作用。 因?yàn)橥扑]要解決的就是海量信息冗余,用戶(hù)在目的不算很明確的情況,進(jìn)一步幫助其確定目標(biāo),主動(dòng)篩選信息的過(guò)程,推的不好那對(duì)于用戶(hù)來(lái)說(shuō)就更冗余了。 關(guān)于信息的獲取,其實(shí)還有一種常見(jiàn)的形態(tài),那就是結(jié)構(gòu)化導(dǎo)航,比如電商平臺(tái)的分門(mén)別類(lèi)羅列,門(mén)戶(hù)網(wǎng)站的結(jié)構(gòu)化頻道信息。它是通過(guò)把信息進(jìn)行結(jié)構(gòu)化了,構(gòu)建脈絡(luò)結(jié)構(gòu),幫助你去獲取你要的信息。不過(guò),這個(gè)就不在我們的討論范圍內(nèi)了。 0.2.3 推薦系統(tǒng)的場(chǎng)景 說(shuō)了這么多篇邏輯理論的東西,或許很多朋友依然對(duì)推薦系統(tǒng)沒(méi)有一個(gè)很場(chǎng)景化的認(rèn)知,比如具體什么場(chǎng)景?具體什么形態(tài)? 這是我在騰訊視頻上截的圖,這就是典型的視頻推薦場(chǎng)景,我不是鵝廠(chǎng)騰訊視頻業(yè)務(wù)的算法工程師,所以我無(wú)法回答你他們的推薦機(jī)制,但我可以告訴你,當(dāng)時(shí)我的觀(guān)看主體是“地球脈動(dòng)”,結(jié)合推薦列表,大伙兒可以揣摩一下他的推薦機(jī)制。當(dāng)前觀(guān)看的屬性相關(guān)?導(dǎo)演關(guān)聯(lián)性?我的觀(guān)看記錄偏好?從我個(gè)人的評(píng)估來(lái)看,這些因素應(yīng)該都有。 順帶說(shuō)一下的就是,一個(gè)完整的好的推薦系統(tǒng),一定不會(huì)單純的依賴(lài)于某個(gè)推薦算法,雖然這個(gè)系列的后面文章中,我會(huì)講一些推薦機(jī)制或者算法邏輯,甚至附上簡(jiǎn)單的案例代碼,但還是要提前說(shuō)一下這個(gè)問(wèn)題。 我們?cè)賮?lái)看幾個(gè)同樣是騰訊系的產(chǎn)品推薦場(chǎng)景: QQ音樂(lè)平臺(tái)的推薦,分析來(lái)看應(yīng)該跟我當(dāng)前主頁(yè)音樂(lè)的風(fēng)格、以及我的歷史瀏覽相關(guān)。 這是閱文網(wǎng)站的小說(shuō)小說(shuō)推薦,即當(dāng)你瀏覽一本小說(shuō)時(shí),下面會(huì)給這個(gè)推薦列表,從其描述以及個(gè)人分析來(lái)看,好像與個(gè)人的行為相關(guān)性會(huì)小一些,應(yīng)該是基于大盤(pán)用戶(hù)的瀏覽軌跡做的關(guān)聯(lián)分析,進(jìn)而進(jìn)行關(guān)聯(lián)推薦。 最后是電商平臺(tái)的典型案例,即你在瀏覽商品時(shí),一般都有猜你喜歡模塊,并且推薦系統(tǒng)得以大放光彩,成為應(yīng)用領(lǐng)域里典型的應(yīng)用場(chǎng)景,還是得益于亞馬遜。當(dāng)年亞馬遜使用推薦算法幫助其提升了XX(具體多少忘了)的年度利潤(rùn),從此一炮而紅,基本上電商平臺(tái)中的推薦系統(tǒng)就成了標(biāo)配。 0.2.4 推薦系統(tǒng)的一些坑 看了這么多例子,再結(jié)合自己身邊實(shí)際的體驗(yàn),確實(shí)不難發(fā)現(xiàn),各色各樣的產(chǎn)品、平臺(tái),都在打造自己的推薦產(chǎn)品,恨不得用戶(hù)一直點(diǎn)下去,永不跳出。鑒于這種情況,那些尚未為自己產(chǎn)品或者平臺(tái)開(kāi)發(fā)推薦邏輯的,是不是感覺(jué)自己少了個(gè)推薦系統(tǒng),哈哈。 其實(shí)核心還是那句話(huà),推薦本身就是個(gè)雙刃劍,用的不好只會(huì)讓用戶(hù)徒增煩惱,這里所說(shuō)的好不好,不單純是說(shuō)準(zhǔn)不準(zhǔn)的問(wèn)題,準(zhǔn)是前提,即推薦給用戶(hù)切身所需肯定是好的,但這還不夠,你還需要在他需要的時(shí)候給他推,時(shí)機(jī)不對(duì)、場(chǎng)景不對(duì),即使你推的東西再準(zhǔn),那也是瞎比推。 所以,即使你覺(jué)得你少了個(gè)推薦系統(tǒng),也是需要慎重,或許跟完這個(gè)系列會(huì)好點(diǎn)?正如上面說(shuō)的,一些坑還是需要注意的。上面所說(shuō)的推薦時(shí)機(jī)以及場(chǎng)景就不再重復(fù)了。 第一,好的推薦系統(tǒng)一般情況下很依賴(lài)于用戶(hù)的行為數(shù)據(jù),因?yàn)閺挠脩?hù)行為中自然能一窺用戶(hù)的一些偏好所在,但實(shí)際情況是,用戶(hù)的行為數(shù)據(jù)并不是這么容易的,當(dāng)用戶(hù)行為數(shù)據(jù)不夠的時(shí)候,基于用戶(hù)行為的分析結(jié)論就是個(gè)偽命題,甚至?xí)涯銕蝈e(cuò)誤的方向。 第二,用戶(hù)的偏好一定是會(huì)時(shí)間偏移進(jìn)行轉(zhuǎn)變的,所以用戶(hù)行為的有效性又會(huì)是一個(gè)問(wèn)題。 第三,假設(shè)這個(gè)是新用戶(hù)呢?完全沒(méi)有軌跡信息,怎么破。 第四,實(shí)際影響用戶(hù)的選擇的因素太多,我們?nèi)菀紫萑胫饔^(guān)臆斷的誤區(qū),綜合性考慮是一個(gè)完善推薦系統(tǒng)的必須思考的地方。 第五,產(chǎn)品層面的邏輯有時(shí)候比底層算法更有效,典型如上面閱文的截圖例子,“喜歡這本書(shū)的人也喜歡”,這就是一種策略,也是一種推薦解釋?zhuān)山忉屝詴?huì)提升推薦的可信度,諸如還有一些交互方式、產(chǎn)品形態(tài)都是對(duì)推薦轉(zhuǎn)化有影響的。 03 推薦系統(tǒng)的基礎(chǔ)知識(shí) 基于上面章節(jié)的內(nèi)容,我們對(duì)于推薦系統(tǒng)的常見(jiàn)場(chǎng)景有了一個(gè)大概的認(rèn)知,這個(gè)章節(jié),我們從推薦系統(tǒng)本身的基礎(chǔ)知識(shí)進(jìn)行拆解,幫我們從理論上掌握更多關(guān)于推薦系統(tǒng)相關(guān)的知識(shí)。 0.3.1 推薦系統(tǒng)概述 在上個(gè)章節(jié),我們也大致的提到過(guò),需要先明確的一點(diǎn)就是推薦算法或者推薦機(jī)制并不嚴(yán)格等同推薦系統(tǒng),推薦系統(tǒng)是一個(gè)相對(duì)復(fù)雜的業(yè)務(wù)系統(tǒng),里頭涉及到數(shù)據(jù)的處理、架構(gòu)的構(gòu)成、推薦的邏輯機(jī)制,反饋數(shù)據(jù)的回收、效果的跟蹤、AB測(cè)試等等。 并且,很多我們耳熟能詳?shù)耐扑]算法,他只是解決的某種特定情況下的推薦機(jī)制問(wèn)題,而整個(gè)系統(tǒng)很多時(shí)候是復(fù)合了多種算法結(jié)果,綜合呈現(xiàn)的一種結(jié)果。但可以肯定的是,各種理論邏輯、算法機(jī)制是構(gòu)建推薦系統(tǒng)的核心支撐,所以,學(xué)習(xí)推薦系統(tǒng),首先學(xué)習(xí)各種推薦算法并沒(méi)有毛病。
從原始數(shù)據(jù)依賴(lài)的層面來(lái)說(shuō),常見(jiàn)的有基于內(nèi)容屬性的推薦機(jī)制,這種推薦邏輯很簡(jiǎn)單,只是單純的依賴(lài)物品之間的屬性相似來(lái)構(gòu)建推薦關(guān)系,容易理解,有時(shí)間還是有一定效果的,但實(shí)際上很多時(shí)候會(huì)存在這幾種情況,導(dǎo)致了這種原始推薦失效。
所以,由于用戶(hù)行為的不可控,基于內(nèi)容屬性相似的推薦,風(fēng)險(xiǎn)還是挺高的,這是導(dǎo)致了這種原始直接的機(jī)制并不會(huì)得到廣泛的推廣。但與亂推薦相比,還是有一定正向作用的,畢竟用戶(hù)瀏覽的主體是自身選擇的結(jié)果,本身用戶(hù)對(duì)于其選擇的信息主體是有一定偏好性的。
基于物品本身屬性的推薦,其實(shí)與個(gè)性化是沒(méi)有半毛錢(qián)的關(guān)系的,畢竟推薦候選集只跟物品主體有關(guān),與用戶(hù)無(wú)關(guān),就談不上什么個(gè)性化了。 而基于用戶(hù)畫(huà)像(更多人喜歡用基于用戶(hù)標(biāo)簽)的推薦,則更大程度上依賴(lài)于用戶(hù)的畫(huà)像屬性來(lái)推薦,這就體現(xiàn)了用戶(hù)偏好信息,根據(jù)偏好信息來(lái)選擇候選集。 這是一種很通用的做法,并且在大規(guī)模數(shù)據(jù)集情況下,很多實(shí)際的產(chǎn)生過(guò)程中喜歡使用這種機(jī)制。而用戶(hù)的畫(huà)像,或者更具體點(diǎn)用戶(hù)的興趣標(biāo)簽如何構(gòu)建呢?其實(shí)就是依賴(lài)用戶(hù)累積的行為數(shù)據(jù)了,通過(guò)行為數(shù)據(jù)生成用戶(hù)的興趣標(biāo)簽。 這看似是一種相對(duì)靠譜的做法,畢竟如果把用戶(hù)的愛(ài)好都分析清楚了,主動(dòng)給用戶(hù)做推薦不就顯得很個(gè)性化了嗎?在實(shí)際的場(chǎng)景中,首先,并不是所有用戶(hù)的行為都足夠用來(lái)表征其興趣偏好的,即我們會(huì)高估用戶(hù)的行為集合,從而產(chǎn)生有偏差的畫(huà)像屬性,更甚者,如果用戶(hù)完全沒(méi)有行為怎么辦呢? 其次,通常來(lái)說(shuō),用戶(hù)的興趣愛(ài)好是會(huì)隨時(shí)間遷移而改變的,所以,把我用戶(hù)的興趣程度以及其變化并不是一個(gè)容易的事情,更何況用戶(hù)實(shí)際的選擇還會(huì)受很多因素影響,比如,我當(dāng)前查找的一個(gè)信息并不是我之前掌握的信息,那意味著這些信息偏好在我的歷史軌跡中都體現(xiàn)不出來(lái),那單純的通過(guò)我的興趣去推薦就顯得不靠譜了。 但不管怎么說(shuō),根據(jù)用戶(hù)的偏好來(lái)做推薦,大方向肯定是沒(méi)有問(wèn)題的。
協(xié)同過(guò)濾,或許了解過(guò)推薦系統(tǒng)的的朋友,多多少少都能聽(tīng)過(guò)一些,并且協(xié)同推薦可是作為推薦領(lǐng)域典型案例的存在。 協(xié)同過(guò)濾同樣不會(huì)去研究物品的本身屬性,甚至也沒(méi)有空去構(gòu)建用戶(hù)的畫(huà)像標(biāo)簽,正如他的名字描述的一樣,他嚴(yán)重依靠于用戶(hù)的行為以及其周邊用戶(hù)的協(xié)同行為。舉個(gè)例子,為一個(gè)用戶(hù)推薦信息,那么我只需要參考其周邊用戶(hù)在看什么信息,就給他推薦什么信息就好了。 重點(diǎn)在于,如何限定周邊這個(gè)范圍,比如根據(jù)兩個(gè)用戶(hù)的行為,去構(gòu)建相關(guān)關(guān)系,從而判斷用戶(hù)之間的相似程度,把相似用戶(hù)的行為推薦給當(dāng)前用戶(hù),這就是協(xié)同中典型的基于用戶(hù)推薦。 而如果以物品為維度,以用戶(hù)的購(gòu)買(mǎi)或者觀(guān)看記錄為向量,則可以構(gòu)建物品的相似度量,針對(duì)于每一個(gè)待推薦選項(xiàng),用戶(hù)的歷史軌跡就是其向量構(gòu)成,就可以判斷該用戶(hù)歷史的軌跡信息與當(dāng)前的待選物品的向量相關(guān)度了,從而判斷是否要推薦,這就是基于物品的協(xié)同邏輯。 與基于用戶(hù)畫(huà)像的推薦對(duì)比,這種推薦有一定幾率可以發(fā)現(xiàn)新物品,即并不嚴(yán)格依賴(lài)用戶(hù)的興趣。舉個(gè)例子,假設(shè)幾個(gè)信息的層級(jí)是ABC,并且ABC是層級(jí)遞進(jìn)關(guān)系,并不是同一個(gè)東西,對(duì)于一個(gè)用戶(hù)來(lái)說(shuō),他掌握的是A,則意味著他的興趣偏好大多偏向于A(yíng),根據(jù)興趣標(biāo)簽,其實(shí)是很難推薦這種遞進(jìn)相關(guān)的信息。 但是,如果其他用戶(hù)的學(xué)習(xí)軌跡都是A->B->C這種軌跡,這意味著ABC三者之間本身就有前后潛在邏輯關(guān)系存在的,基于協(xié)同,即可為該用戶(hù)在掌握A的基礎(chǔ)上,推薦BC的內(nèi)容,這也是基于興趣所做不到的地方。 當(dāng)前,基于協(xié)同行為的推薦,除了基于物品還有基于用戶(hù),還有其他諸如基于模型的協(xié)同,典型如最近鄰模型、基于矩陣分解、以及基于圖關(guān)系模型的構(gòu)建的推薦機(jī)制。
其實(shí)在我們實(shí)際的操作過(guò)程中,并不會(huì)嚴(yán)格的依賴(lài)于這種條條框框、只要合理即可行,比如我們完全可以把推薦問(wèn)題轉(zhuǎn)化為分類(lèi)問(wèn)題,針對(duì)于每個(gè)待選項(xiàng),他都是YES OR NO的問(wèn)題,即一個(gè)二值分類(lèi)。 又比如在上一篇我們學(xué)習(xí)的一個(gè)場(chǎng)景,即閱文網(wǎng)站的小說(shuō)推薦,還記得他的推薦標(biāo)題嗎?“喜歡這本書(shū)的人還喜歡”,這就是典型的“啤酒與尿布”的解法,即貨架思維,關(guān)聯(lián)銷(xiāo)售,也是可以作為一種推薦機(jī)制而存在的。 在比如微信朋友圈,微信一定是會(huì)研究用戶(hù)的朋友圈關(guān)系的,比如你對(duì)哪類(lèi)朋友點(diǎn)贊、互動(dòng)行為最多,它是不是會(huì)考慮推薦你欣賞的朋友偏好內(nèi)容給你?畢竟微信是一個(gè)典型的熟人社交模型。 所以,推薦算法看似重要,但其實(shí)想想又沒(méi)有這么重要,很多時(shí)候并不是一成不變的,都要我們根據(jù)場(chǎng)景去考慮,最最最重要的是,需要我們用實(shí)際效果來(lái)選擇機(jī)制,也或許是多種機(jī)制共同生效的結(jié)果。 0.3.2 相似度量 在我們上面的推薦算法機(jī)制中,有個(gè)不得不提的操作處理就是各種相似相關(guān)度的計(jì)算,我們來(lái)簡(jiǎn)單分享一下幾種相似或者相關(guān)度量的方式。
最常見(jiàn)的距離度量方式,衡量多維空間中兩點(diǎn)之間的絕對(duì)距離,要求維度的統(tǒng)一。
明氏距離是歐氏距離的擴(kuò)展,是對(duì)多個(gè)距離度量公式的概括性的表述(可以看到,當(dāng)p=2時(shí),其實(shí)就是歐式距離)。
曼哈頓距離來(lái)源于城市區(qū)塊距離,是將多個(gè)維度上的距離進(jìn)行求和后的結(jié)果,即當(dāng)上面的明氏距離中p=1時(shí)得到的距離度量。 //還有其他的一些距離度量,但是都不太常用,最常用的依然是歐式距離度量。
余弦相似度用向量空間中兩個(gè)向量夾角的余弦值作為衡量?jī)蓚€(gè)個(gè)體間差異的大小。相比距離度量,余弦相似度更加注重兩個(gè)向量在方向上的差異,而非距離或長(zhǎng)度上。
即相關(guān)分析中的相關(guān)系數(shù)r,分別對(duì)X和Y基于自身總體標(biāo)準(zhǔn)化后計(jì)算空間向量的余弦?jiàn)A角。基于內(nèi)容的推薦,還有一點(diǎn)需要注意的就是,對(duì)于物品自身屬性,如果屬性值過(guò)少,我們需要適當(dāng)進(jìn)行擴(kuò)大維度,如果維度過(guò)多,則需要進(jìn)行降維。 關(guān)于降維和升維,都是一個(gè)很大的研究方向,大體上可以說(shuō)一下幾種常見(jiàn)的方式。例如降維,我們可以進(jìn)行維度聚類(lèi)、主題抽取,進(jìn)一步把相關(guān)維度進(jìn)行合并,進(jìn)一步減少維度;而對(duì)于升維,我們可以把維度進(jìn)行矩陣化,例如假設(shè)物品X有A和B兩個(gè)維度屬性,那么我們通過(guò)生成A*B矩陣的方式,把維度擴(kuò)充到A*B個(gè)維度。 0.3.3 冷啟動(dòng)問(wèn)題的解決 所謂冷啟動(dòng),即在推薦系統(tǒng)初期時(shí),沒(méi)有任何用戶(hù)與物品的交集信息,即無(wú)用戶(hù)的行為軌跡,無(wú)法通過(guò)類(lèi)似協(xié)同或者用戶(hù)偏好等方式進(jìn)行推薦,這種時(shí)候,我們就稱(chēng)推薦系統(tǒng)處于冷啟動(dòng)狀態(tài)。 這種情況,我們需要盡快的累積起第一批用戶(hù)行為軌跡。我們可以通過(guò)基于內(nèi)容的推薦,或者做一些其他類(lèi)似的操作,快速有效的進(jìn)行物品推薦。一段時(shí)間后,累積到一定的用戶(hù)行為時(shí),整個(gè)系統(tǒng)就能夠正常使用協(xié)同過(guò)濾等方式進(jìn)行推薦了。 但是,針對(duì)于新加入的用戶(hù),或者新加入的物品,同樣也是出于冷啟動(dòng)狀態(tài)的,這個(gè)時(shí)候,我們通過(guò)需要對(duì)這種物品或者用戶(hù)做特殊的處理。 除了基于內(nèi)容屬性的推薦,我們還有其他的一些策略用于彌補(bǔ)這種行為數(shù)據(jù)不足的情況,比如典型的熱度模型,推薦熱點(diǎn)信息這種行為雖然low,但是從整體的反饋來(lái)看,還是有一定效果的,此外,還可以根據(jù)一些統(tǒng)計(jì)學(xué)上的結(jié)論,進(jìn)行基于統(tǒng)計(jì)分析結(jié)論的推薦。 除此之外,我們也可以通過(guò)其他渠道收集用戶(hù)的數(shù)據(jù),比如用戶(hù)注冊(cè)的時(shí)候所填寫(xiě)的個(gè)人資料,這些都是可以作為推薦的原始依賴(lài)數(shù)據(jù)。 0.3.4 馬太效應(yīng) 馬太效應(yīng)或者說(shuō)長(zhǎng)尾效應(yīng),即熱者愈熱,實(shí)際舉例來(lái)說(shuō)就是,在實(shí)際的購(gòu)買(mǎi)場(chǎng)景中,由于你推薦的次數(shù)越多,部分優(yōu)質(zhì)的商品購(gòu)買(mǎi)或者點(diǎn)擊的次數(shù)就越多,形成的用戶(hù)購(gòu)買(mǎi)軌跡就越多,所以得到的推薦機(jī)會(huì)就越多,進(jìn)而產(chǎn)生的推薦也越多,變得越熱。 隨著不斷迭代,子子孫孫無(wú)窮盡也,這樣得到推薦的商品就會(huì)集中在少部分商品中,而大部分長(zhǎng)尾商品是沉寂的,一個(gè)推薦系統(tǒng)如果長(zhǎng)時(shí)間處于長(zhǎng)尾效應(yīng)中,造成推薦疲勞,其推薦效果就會(huì)減弱。 所以,一個(gè)好的推薦系統(tǒng),要考慮到適當(dāng)?shù)耐诰蜷L(zhǎng)尾商品,通過(guò)真的個(gè)性化,把適當(dāng)?shù)拈L(zhǎng)尾商品送到真正需要他們的人手里,在實(shí)際的操作過(guò)程中,我們可以適當(dāng)?shù)倪M(jìn)行熱度降權(quán),從而讓一些中下層的商品得到更多的曝光機(jī)會(huì),當(dāng)然前提是保證點(diǎn)擊率的情況下。 另外一個(gè)場(chǎng)景會(huì)形成馬太效應(yīng)的是熱度模型,即我們的熱度榜單,長(zhǎng)時(shí)間的高居榜首,一定會(huì)獲得更多的點(diǎn)擊,而點(diǎn)擊越多其熱度越高,但我們的信息是需要保持新鮮度的,不然點(diǎn)擊率遲早會(huì)下架的。 所以,我們使用一些機(jī)制讓處于頭部的商品或者信息降權(quán),時(shí)間衰減是一個(gè)比較通用的做法,即隨著時(shí)間的遷移,其整體熱度會(huì)不斷的下降,至于說(shuō)下降的方式,速率就看模型的設(shè)計(jì)了。 0.3.5 AB測(cè)試 關(guān)于推薦的效果,之前我們說(shuō)過(guò)其核心的考核標(biāo)準(zhǔn)就是點(diǎn)擊率,點(diǎn)擊的越多說(shuō)明推薦的越準(zhǔn)確,用戶(hù)的停留時(shí)長(zhǎng)也會(huì)越長(zhǎng),只要把用戶(hù)留在平臺(tái)中,機(jī)會(huì)總是會(huì)有的。其實(shí)就是一層漏斗嘛?這一層的基數(shù)越大,下一層轉(zhuǎn)換的量就會(huì)越高,這也是推薦系統(tǒng)的核心存在意義。 并且之前也說(shuō)到過(guò),一個(gè)不好的推薦系統(tǒng)有時(shí)間反而會(huì)形成反向作用,所以,一個(gè)推薦系統(tǒng)的迭代更新至關(guān)重要。離線(xiàn)的效果評(píng)估一定是要做的,最起碼在離線(xiàn)實(shí)驗(yàn)的階段需要保證當(dāng)前的效果優(yōu)于線(xiàn)上效果,才能進(jìn)行迭代。 但是,實(shí)際情況是復(fù)雜的,對(duì)于推薦的模型來(lái)說(shuō),離線(xiàn)的實(shí)驗(yàn)其實(shí)并沒(méi)有想象中靠譜,那么,就丟到線(xiàn)上去真多真槍的實(shí)驗(yàn)一把,就知道效果了。但是,實(shí)際的生產(chǎn)環(huán)境中,任何一點(diǎn)轉(zhuǎn)化波動(dòng)的影響都是極其嚴(yán)重的,誰(shuí)也不敢拿實(shí)際生產(chǎn)開(kāi)玩笑。 于是,就有了AB測(cè)試機(jī)制的產(chǎn)生,所謂AB測(cè)試機(jī)制,即將流量分為AB兩類(lèi),A流量走原始的舊模型,B流量走新模型,同步測(cè)試同步對(duì)比,效果一目了然。 當(dāng)然,在實(shí)際的AB測(cè)試流程中,首先流量是可以自由分配的,一般情況下新模型在最終確認(rèn)之前流量一定是少量的,隨著模型逐漸被驗(yàn)證,流量比重會(huì)逐漸加大,最終確認(rèn)后流量全部導(dǎo)向新模型,完成新模型的正式上線(xiàn)。 并且,通常,在實(shí)際的環(huán)境中,或許我們會(huì)同時(shí)有十多個(gè)甚至是幾十個(gè)新模型在同時(shí)實(shí)驗(yàn),每個(gè)模型調(diào)整的因子都不一樣,最終選擇最適合的因素進(jìn)行調(diào)整,達(dá)到效果最優(yōu),這也就是AB測(cè)試機(jī)制的魅力所在。 所以,打造一個(gè)好的AB測(cè)試系統(tǒng),首先流量是需要可控的,其次模型的迭代上線(xiàn)是需要高度靈活的,最后,肯定是需要有完整的數(shù)據(jù)回收、數(shù)據(jù)分析對(duì)比機(jī)制存在的。 04 基于內(nèi)容屬性的推薦策略算法 從這個(gè)章節(jié)開(kāi)始,我們將從理論進(jìn)一步過(guò)渡到具體的推薦策略算法,我們先從最簡(jiǎn)單的基于內(nèi)容屬性相關(guān)的策略算法著手。 0.4.1 最簡(jiǎn)單的推薦機(jī)制 如標(biāo)題,既然是“最最最簡(jiǎn)單”的推薦系統(tǒng),其實(shí)也不能說(shuō)是推薦系統(tǒng),之前也說(shuō)了,系統(tǒng)是一個(gè)復(fù)合的完整系統(tǒng),所以這里說(shuō)推薦機(jī)制可能會(huì)更恰當(dāng)些。結(jié)合之前大致陳述的一些推薦機(jī)制,最最最簡(jiǎn)單的推薦機(jī)制,無(wú)疑是基于主體屬性相似或者相關(guān)推薦了,連個(gè)性化都說(shuō)不上,鐵定最最最簡(jiǎn)單了。 說(shuō)到這,說(shuō)不定有些人不愿意干了,既然如此簡(jiǎn)陋的推薦機(jī)制,不看也罷。BUT,真的不要小看基于內(nèi)容相似的推薦,存在即合理。我們?cè)谶M(jìn)行信息獲取時(shí),其實(shí)本身就是具有一定識(shí)別能力的,這意味著我們最終選擇查看的信息都是經(jīng)過(guò)我們大腦大致思考的結(jié)論,這意味著這些信息是有參考價(jià)值的。 并且,在很多時(shí)候,我們是需要同類(lèi)信息進(jìn)行我們獲取到的信息進(jìn)行補(bǔ)全的,完善我們對(duì)目標(biāo)信息的獲取程度。基于這個(gè)考量,基于內(nèi)容屬性的推薦其實(shí)是說(shuō)的過(guò)去的。 別不服,我們來(lái)上例子,還是以前面文章那個(gè)騰訊視頻的推薦場(chǎng)景圖為例。 圖是在使用騰訊視頻觀(guān)看視頻時(shí),我親手截推薦欄位的內(nèi)容,補(bǔ)充一下背景(很重要,請(qǐng)注意):
基于上面我提供的個(gè)人行為數(shù)據(jù),再結(jié)合看這批推薦列表,不難發(fā)現(xiàn),上面有很多的紀(jì)錄片,你覺(jué)得跟我們當(dāng)時(shí)正在瀏覽的內(nèi)容有沒(méi)有關(guān)系?或者你認(rèn)為我行為記錄中很多紀(jì)錄片的記錄?又或者是我是紀(jì)錄片的狂熱者,導(dǎo)致了騰訊視頻給我猛推紀(jì)錄片。 所以,連騰訊視頻都會(huì)考慮基于當(dāng)前瀏覽內(nèi)容的屬性進(jìn)行推薦(并且是大范圍),你還覺(jué)得這種做法十分之LOW嗎?當(dāng)然你也可以認(rèn)為騰訊視頻推的不準(zhǔn),瞎J吧推,也是可以的,我也認(rèn)同,不是非常準(zhǔn)(哈哈,《地球脈動(dòng)》所有我都看過(guò)了,還給我瞎推,上面給推的沒(méi)幾個(gè)有欲望去點(diǎn)的,給騰訊視頻推薦的開(kāi)發(fā)兄弟們打臉了,不好意思)。 我只想表達(dá)的是,這種簡(jiǎn)單的推薦機(jī)制,在整個(gè)推薦系統(tǒng)中真的是不可缺少的部分,很多時(shí)候簡(jiǎn)單并不代表無(wú)效,類(lèi)似上面這種情況,我可以舉出太多有名有姓的實(shí)際案例來(lái),說(shuō)多了沒(méi)意義,所以,咱繼續(xù)。 0.4.2 過(guò)程并沒(méi)有這么簡(jiǎn)單 從直接的推薦機(jī)制來(lái)看,整個(gè)實(shí)現(xiàn)流程看著真的很簡(jiǎn)單,但是在實(shí)際的操作過(guò)程中,還是有一些東西值得探討以及注意的。
之前文章有大致提到過(guò),相似或者相關(guān)計(jì)算還是有很多可以選擇的,他們每一種都有各自的特點(diǎn)以及適應(yīng)性。以相似計(jì)算中使用最多的歐式距離與余弦相似為例,專(zhuān)業(yè)點(diǎn)的說(shuō)法就是余弦?jiàn)A角可以有效規(guī)避個(gè)體相同認(rèn)知中不同程度的差異表現(xiàn),更注重維度之間的差異,而不注重?cái)?shù)值上的差異,而歐式距離則是對(duì)個(gè)體異常數(shù)值會(huì)比較敏感。 這意味著,在我們需要區(qū)分異常樣本時(shí),使用距離計(jì)算會(huì)更恰當(dāng),聚個(gè)栗子,比如電商領(lǐng)域中高價(jià)值與低價(jià)值用戶(hù)的區(qū)分,其實(shí)我們核心是想把他們的差異性拉大的,得以體現(xiàn)出對(duì)比,這個(gè)時(shí)候使用余弦就是不合理的。 在回歸到距離上說(shuō),市面上除了歐式距離,還有好幾種距離度量,諸如馬氏、曼哈頓距離等等,其實(shí)其度量側(cè)重都是不一樣的,我們需要結(jié)合實(shí)際的場(chǎng)景去使用。還有更偏向于相關(guān)度量的皮爾森相關(guān)系數(shù)等。
按照標(biāo)準(zhǔn)流程,假設(shè)有1萬(wàn)個(gè)物品,則對(duì)于每個(gè)物品來(lái)說(shuō),需要與其他物品計(jì)算與其的相似度或者相關(guān)度,然后再排個(gè)序,取TopN形成自身的待推薦列表。那么,簡(jiǎn)單的數(shù)學(xué)題來(lái)了10000*10000=10000萬(wàn)次計(jì)算,這顯然是不合理的。 所以,優(yōu)化這個(gè)過(guò)程是必然的,關(guān)鍵是如何優(yōu)化。核心思想其實(shí)就是初篩嘛!把那些完全沒(méi)啥多大鳥(niǎo)關(guān)系的直接干掉,省掉計(jì)算相似的過(guò)程,節(jié)省資源。如何篩選?一個(gè)比較常見(jiàn)的做法是,尋找核心關(guān)鍵影響因素,保證關(guān)鍵因素的相關(guān)性。 比如,在實(shí)際的生產(chǎn)操作過(guò)程中,很多時(shí)候會(huì)通過(guò)關(guān)鍵屬性是否基本匹配作為判斷依據(jù),或者直接通過(guò)搜索構(gòu)建進(jìn)行檢索初篩,把大致相關(guān)的先過(guò)濾一遍,通過(guò)這種簡(jiǎn)單而粗暴的方式,其實(shí)已經(jīng)能把大部分相關(guān)度很低的候選集給過(guò)濾掉,對(duì)于整體計(jì)算量級(jí)來(lái)說(shuō),計(jì)算復(fù)雜度直接下降。
基于屬性計(jì)算相似,從整體上來(lái)看,其實(shí)一般主體都不止一個(gè)屬性,那么計(jì)算相關(guān)的時(shí)候到底看那個(gè)屬性呢?或者說(shuō)哪些屬性應(yīng)該占有更高的權(quán)重,哪些因素是次要因素。 還是以上面的騰訊視頻的推薦為例,從結(jié)果上來(lái)反推相似推薦的部分(當(dāng)然,實(shí)際情況不詳哈,只是推斷而已),顯然當(dāng)前視頻的類(lèi)別占了很大的權(quán)重(記錄片),除此之外包括導(dǎo)演啊,一些其他特征屬性應(yīng)該也會(huì)參考在內(nèi)的。 回到常規(guī)問(wèn)題,如何確定影響權(quán)重是個(gè)操作難題。最簡(jiǎn)單并且實(shí)際上還挺有效的一種方式就是專(zhuān)家評(píng)判法,即通過(guò)權(quán)威經(jīng)驗(yàn)來(lái)劃定影響因子的權(quán)重,還有就是通過(guò)標(biāo)注的樣本進(jìn)行反向擬合每種因素的占比權(quán)重。除此之外還有一些其他學(xué)術(shù)上的方法,包括什么主成分分析法,層次分析法,還有什么熵權(quán)法,其實(shí)都是找因子影響能力的主次關(guān)系。 最終確定好了影響因素,在實(shí)際上線(xiàn)回收到數(shù)據(jù)之后,依然是需要逐步的進(jìn)行權(quán)重影響調(diào)整的,我們可以通過(guò)結(jié)果的樣本數(shù)據(jù),進(jìn)行LR的回歸擬合,尋找最合適的權(quán)重配比。 0.4.3 最簡(jiǎn)單的推薦策略算法實(shí)踐 說(shuō)了這么多理論,不能光說(shuō)不練,標(biāo)題上寫(xiě)著“附Spark案例”,很多人都是沖著這來(lái)的呢,前面BB了這么多屁話(huà),還不見(jiàn)代碼。來(lái),我們這就上正文。 不過(guò)不用期待過(guò)多,畢竟這只是一個(gè)簡(jiǎn)單的相似計(jì)算的過(guò)程而已,所以權(quán)當(dāng)屬性實(shí)驗(yàn)數(shù)據(jù)以及Spark開(kāi)發(fā)了,高手可以略過(guò)了。
其實(shí)看到這三部分?jǐn)?shù)據(jù)的簡(jiǎn)介,一些老手估計(jì)已經(jīng)知道是什么數(shù)據(jù)了,是的,就是那份有名的電影數(shù)據(jù)集(MovieLens開(kāi)放數(shù)據(jù)),并且取的是完全版的那份,簡(jiǎn)直成了推薦系統(tǒng)的標(biāo)配實(shí)驗(yàn)數(shù)據(jù)了。 三個(gè)文件,其中電影數(shù)據(jù)集共1萬(wàn)多個(gè)電影基礎(chǔ)數(shù)據(jù),評(píng)分?jǐn)?shù)據(jù)集最大共100萬(wàn)條評(píng)分?jǐn)?shù)據(jù),以及10萬(wàn)條的用戶(hù)對(duì)電影的打標(biāo)簽數(shù)據(jù),總大小約為幾百兆,不大,但是用來(lái)做實(shí)驗(yàn)玩玩那是相當(dāng)足夠了。
我們的核心計(jì)算邏輯還是內(nèi)容屬性上的相似嘛,所以核心是看看圍繞電影,有哪些屬性是可以抽取出來(lái)的,并且參與計(jì)算的。 第一,電影的類(lèi)別,基于上面騰訊視頻的考慮,其實(shí)這個(gè)顯然很重要,而電影的類(lèi)別信息存儲(chǔ)于電影數(shù)據(jù)集中,并且是一對(duì)多的關(guān)系,即一個(gè)電影可以對(duì)應(yīng)多個(gè)類(lèi)目,我們需要進(jìn)行切割,由于計(jì)算這個(gè)維度相似的時(shí)候,是多對(duì)多的關(guān)系,天然的計(jì)算相似或者相關(guān)的特征。 第二、電影的播放年份,電影的年份其實(shí)有種潛在的關(guān)聯(lián)關(guān)系,舉個(gè)例子可以說(shuō)明,比如說(shuō)零幾年的電影與現(xiàn)狀的電影風(fēng)格是不同的,當(dāng)時(shí)間跨度有一定差距時(shí),這個(gè)還是蠻明顯的。關(guān)于電影的年份數(shù)據(jù),從數(shù)據(jù)樣本可以知道,它隱藏在電影的名字中,寫(xiě)個(gè)正則過(guò)濾出來(lái)即可。至于說(shuō)如何計(jì)算這個(gè)維度的相關(guān),我們可以用兩者差距來(lái)算相關(guān),比如年份絕對(duì)值越遠(yuǎn),這個(gè)值越小,越近甚至是重疊就越大。 第三,電影的標(biāo)簽,電影本身是沒(méi)有標(biāo)簽屬性的,但它有用戶(hù)對(duì)他打的標(biāo)簽信息,所以我們需要進(jìn)一步處理,把它變成電影的屬性,需要清洗、規(guī)整以及處理。標(biāo)簽本身也是多對(duì)多的關(guān)系,同樣可以計(jì)算相似度,比如歐式或者余弦。 第四、電影的名稱(chēng),名稱(chēng)上進(jìn)行尋找關(guān)聯(lián)性,聽(tīng)上去很扯,但其實(shí)有一定的邏輯在里頭,比如我在視頻網(wǎng)站搜索“三國(guó)”,顯然我期望從名稱(chēng)上尋找三國(guó)相關(guān)題材的視頻,他們就是在名稱(chēng)上建立起關(guān)聯(lián)關(guān)系的,所以,名稱(chēng)從某種程度上來(lái)說(shuō),可以體現(xiàn)相關(guān)性。在計(jì)算相似或者相關(guān)方式上,我們可以進(jìn)行分詞,去除停詞,然后再以詞維度進(jìn)行余弦計(jì)算。 第五、候選集電影的評(píng)分,對(duì)于做推薦來(lái)說(shuō),首先需要保證的推薦的候選集一定是優(yōu)質(zhì)的,從這個(gè)維度上說(shuō),拋開(kāi)其他因素,那么就是整體評(píng)分高的電影是相對(duì)優(yōu)質(zhì)的電影。在處理的過(guò)程中,由于一個(gè)電影對(duì)應(yīng)多個(gè)評(píng)分,所以,我們需要進(jìn)行進(jìn)行歸一計(jì)算,最簡(jiǎn)單的做法就是計(jì)算整體評(píng)分的平均值,作為電影的評(píng)分?jǐn)?shù)據(jù),評(píng)分過(guò)低的數(shù)據(jù)直接從候選集中干掉,又大大的降低了計(jì)算次數(shù)。
Spark2.0之后,不用再構(gòu)建sparkcontext了,以創(chuàng)建一個(gè)復(fù)合多功能的SparkSession替代,可以正常的從HDFS讀取文件,也可以從Hive中獲取DataFrame等等。 val sparkSession = SparkSession .builder() .appName('base-content-Recommand') //spark任務(wù)名稱(chēng) .enableHiveSupport() .getOrCreate() 那三個(gè)表可以先load到Hive中,然后spark直接從Hive中讀取,形成DataFrame。 //從hive中,獲取rating評(píng)分?jǐn)?shù)據(jù)集,最終形成如下格式數(shù)據(jù)(movie,avg_rate) val movieAvgRate = sparkSession.sql('select movieid,round(avg(rate),1) as avg_rate from tx.tx_ratings group by movieid').rdd.map{ f=> (f.get(0),f.get(1)) } //獲取電影的基本屬性數(shù)據(jù),包括電影id,名稱(chēng),以及genre類(lèi)別 val moviesData = sparkSession.sql('select movieid,title,genre from tx.tx_movies').rdd //獲取電影tags數(shù)據(jù),這里取到所有的電影tag val tagsData = sparkSession.sql('select movieid,tag from tx.tx_tags').rdd 先對(duì)tag進(jìn)行處理,很多tag其實(shí)說(shuō)的是同一個(gè)東西,我們需要進(jìn)行一定程度上的合并,這樣才能讓tag更加的合理(有朋友有意見(jiàn)了,就一個(gè)實(shí)驗(yàn)案例而已,搞這么復(fù)雜),舉個(gè)簡(jiǎn)單例子,blood、bloods、bloody其實(shí)都是想說(shuō)這個(gè)電影很血腥暴力,但是不同的人使用的詞不同的(這點(diǎn)大伙兒可以自由查看實(shí)驗(yàn)數(shù)據(jù)),所以我們需要進(jìn)行一定程度上的合并。 val tagsStandardizeTmp = tagsStandardize.collect() val tagsSimi = tagsStandardize.map{ f=> var retTag = f._2 if (f._2.toString.split(' ').size == 1) { var simiTmp = '' val tagsTmpStand = tagsStandardizeTmp .filter(_._2.toString.split(' ').size != 1 ) .filter(f._2.toString.size < _._2.toString.size) .sortBy(_._2.toString.size) var x = 0 val loop = new Breaks tagsTmpStand.map{ tagTmp=> val flag = getEditSize(f._2.toString,tagTmp._2.toString) if (flag == 1){ retTag = tagTmp._2 loop.break() } } ((f._1,retTag),1) } else { ((f._1,f._2),1) } } 其中g(shù)etEditSize是求取,兩個(gè)詞的編輯距離的,編輯距離在一定時(shí)候,進(jìn)行合并,具體邏輯見(jiàn)代碼了,不復(fù)雜。 def getEditSize(str1:String,str2:String): Int ={ if (str2.size > str1.size){ 0 } else { //計(jì)數(shù)器 var count = 0 val loop = new Breaks //以較短的str2為中心,進(jìn)行遍歷,并逐個(gè)比較字符 val lengthStr2 = str2.getBytes().length var i = 0 for ( i <- 1 to lengthStr2 ){ if (str2.getBytes()(i) == str1.getBytes()(i)) { //逐個(gè)匹配字節(jié),相等則計(jì)數(shù)器 1 count = 1 } else { //一旦出現(xiàn)前綴不一致則中斷循環(huán),開(kāi)始計(jì)算重疊度 loop.break() } } //計(jì)算重疊度,當(dāng)前綴重疊度大于等于2/7時(shí),進(jìn)行字符串合并,從長(zhǎng)的往短的合并 if (count.asInstanceOf[Double]/str1.getBytes().size.asInstanceOf[Double] >= (1-0.286)){ 1 }else{ 0 } } } 繼續(xù)對(duì)tag進(jìn)行處理,統(tǒng)計(jì)tag頻度,取TopN個(gè)作為電影對(duì)應(yīng)的tag屬性。 val movieTag = tagsSimi.reduceByKey(_ _).groupBy(k=>k._1._1).map{ f=> (f._1,f._2.map{ ff=> (ff._1._2,ff._2) }.toList.sortBy(_._2).reverse.take(10).toMap) } 接下來(lái)處理年齡、年份和名稱(chēng),這個(gè)會(huì)簡(jiǎn)單點(diǎn),進(jìn)行分詞處理的話(huà),怎么簡(jiǎn)單怎么來(lái)了,直接使用第三方的HanLP進(jìn)行關(guān)鍵詞抽取作為分詞結(jié)果,直接屏蔽了停用詞。 val moviesGenresTitleYear = moviesData.map{ f=> val movieid = f.get(0) val title = f.get(1) val genres = f.get(2).toString.split('|').toList.take(10) val titleWorlds = HanLP.extractKeyword(title.toString, 10).toList val year = movieYearRegex.movieYearReg(title.toString) (movieid,(genres,titleWorlds,year)) } 取年份的正則函數(shù)如下,是個(gè)Java寫(xiě)的精通工具類(lèi)(Scala和Java混寫(xiě),簡(jiǎn)直無(wú)比美妙)。 package utils; import java.util.regex.Matcher; import java.util.regex.Pattern; /** * Desc: 抽取年份公式 */ public class movieYearRegex { private static String moduleType = '.* \\(([1-9][0-9][0-9][0-9])\\).*'; public static void main(String[] args){ System.out.println(movieYearReg('GoldenEye (1995)')); } public static int movieYearReg(String str){ int retYear = 1994; Pattern patternType = Pattern.compile(moduleType); Matcher matcherType = patternType.matcher(str); while (matcherType.find()) { retYear = Integer.parseInt(matcherType.group(1)); } return retYear; } } 通過(guò)join進(jìn)行數(shù)據(jù)合并,生成一個(gè)以電影id為核心的屬性集合。 val movieContent = movieTag.join(movieAvgRate).join(moviesGenresTitleYear).map{ f=> //(movie,tagList,titleList,year,genreList,rate) (f._1,f._2._1._1,f._2._2._2,f._2._2._3,f._2._2._1,f._2._1._2) } 相似計(jì)算開(kāi)始之前,還記得我們之前說(shuō)的嗎,可以進(jìn)行候選集閹割,我們先根據(jù)一些規(guī)則裁剪一下候選集。 val movieConetentTmp = movieContent.filter(f=>f._6.asInstanceOf[java.math.BigDecimal].doubleValue() < 3.5).collect() 然后真正的開(kāi)始計(jì)算相似,使用余弦相似度計(jì)算,取排序之后的Top20作為推薦列表。 val movieContentBase = movieContent.map{ f=> val currentMoiveId = f._1 val currentTagList = f._2 //[(tag,score)] val currentTitleWorldList = f._3 val currentYear = f._4 val currentGenreList = f._5 val currentRate = f._6.asInstanceOf[java.math.BigDecimal].doubleValue() val recommandMovies = movieConetentTmp.map{ ff=> val tagSimi = getCosTags(currentTagList,ff._2) val titleSimi = getCosList(currentTitleWorldList,ff._3) val genreSimi = getCosList(currentGenreList,ff._5) val yearSimi = getYearSimi(currentYear,ff._4) val rateSimi = getRateSimi(ff._6.asInstanceOf[java.math.BigDecimal].doubleValue()) val score = 0.4*genreSimi 0.25*tagSimi 0.1*yearSimi 0.05*titleSimi 0.2*rateSimi (ff._1,score) }.toList.sortBy(k=>k._2).reverse.take(20) (currentMoiveId,recommandMovies) }.flatMap(f=>f._2.map(k=>(f._1,k._1,k._2))).map(f=>Row(f._1,f._2,f._3)) 最后,將結(jié)果存入Hive中,Hive中提前建好結(jié)果表。 //我們先進(jìn)行DataFrame格式化申明 val schemaString2 = 'movieid movieid_recommand score' val schemaContentBase = StructType(schemaString2.split(' ') .map(fieldName=>StructField(fieldName,if (fieldName.equals('score')) DoubleType else StringType,true))) val movieContentBaseDataFrame = sparkSession.createDataFrame(movieContentBase,schemaContentBase) //將結(jié)果存入hive,需要先進(jìn)行臨時(shí)表創(chuàng)建 val userTagTmpTableName = 'mite_content_base_tmp' val userTagTableName = 'mite8.mite_content_base_reco' movieContentBaseDataFrame.registerTempTable(userTagTmpTableName) sparkSession.sql('insert into table ' userTagTableName ' select * from ' userTagTmpTableName) 到這里,基本大的代碼邏輯就完了,可能還有一些邊邊角角的代碼遺漏了,但不妨礙主干了。 05 融合用戶(hù)興趣的推薦才個(gè)性 接上個(gè)章節(jié),我們給了一個(gè)最最最簡(jiǎn)單的推薦系統(tǒng)機(jī)制,即基于內(nèi)容屬性的相似或者相關(guān)推薦,我們知道這種推薦機(jī)制基本只基于內(nèi)容本身的屬性進(jìn)行推薦,與用戶(hù)沒(méi)有半毛錢(qián)關(guān)系,所以,當(dāng)然也就說(shuō)不上個(gè)性化了。 0.5.1 個(gè)性化與用戶(hù)畫(huà)像 在說(shuō)具體的情況之前,我們先來(lái)思考一個(gè)問(wèn)題,什么是個(gè)性化?個(gè)性化一定是與人相關(guān)的,只有人才有個(gè)性,每個(gè)人可能都有自己的個(gè)性,推送的信息如果能滿(mǎn)足用戶(hù)的個(gè)性,才是一個(gè)好的推薦系統(tǒng),才具有足夠的智能。 而今天,我們要討論的就是,如何讓推薦從非智能的過(guò)程演變?yōu)橹獣杂脩?hù)個(gè)性,基于用戶(hù)偏好進(jìn)行推薦,從而變得更“聰明”點(diǎn),也就是智能化。 要實(shí)現(xiàn)推薦個(gè)性化,那么先需要對(duì)用戶(hù)進(jìn)行分析,分析用戶(hù)的偏好,然后根據(jù)偏好來(lái)做推薦,就順其自然了。而要分析用戶(hù)的偏好,那么自然就少不了對(duì)用戶(hù)行為的分析。 所以,核心還是用戶(hù)畫(huà)像的分析,然后我們?cè)倩谟脩?hù)畫(huà)像屬性進(jìn)行推薦,由于用戶(hù)畫(huà)像體現(xiàn)的是每個(gè)用戶(hù)的偏好數(shù)據(jù),所以,不管怎么樣,這種推薦機(jī)制或多或少都是能體現(xiàn)一些個(gè)性化的東西的。 沿著這個(gè)路徑,我們依然是結(jié)合實(shí)際數(shù)據(jù)以及代碼案例來(lái)分解這個(gè)個(gè)性化推薦的過(guò)程。 0.5.2 基于用戶(hù)畫(huà)像的個(gè)性化推薦策略 整個(gè)案例代碼的邏輯是,我們先根據(jù)行為數(shù)據(jù),進(jìn)行用戶(hù)的畫(huà)像描述抽取,然后再結(jié)合用戶(hù)的畫(huà)像數(shù)據(jù)為用戶(hù)進(jìn)行信息推薦,注意,這里與之前的實(shí)例不同的是,我們是基于用戶(hù)進(jìn)行推薦的,而上個(gè)實(shí)例是在瀏覽某個(gè)內(nèi)容的時(shí)候,進(jìn)行相關(guān)內(nèi)容推薦,這里以及進(jìn)化到了根據(jù)人進(jìn)行推薦了。
關(guān)于數(shù)據(jù)源,依然使用的是上個(gè)案例中的實(shí)驗(yàn)數(shù)據(jù),不清楚的見(jiàn)上一個(gè)章節(jié)(最簡(jiǎn)單的推薦系統(tǒng))的原始數(shù)據(jù)說(shuō)明,從上次的數(shù)據(jù)源說(shuō)明情況看,實(shí)際上打標(biāo)行為數(shù)據(jù)有10萬(wàn)條,評(píng)分?jǐn)?shù)據(jù)有100萬(wàn)條,相對(duì)于電影內(nèi)容數(shù)據(jù)實(shí)體來(lái)說(shuō),其實(shí)已經(jīng)算不少了,所以,不用擔(dān)心,針對(duì)于有行為記錄的用戶(hù),或多或少還是能描述出他們各自的一些行為偏好的。
基于上個(gè)小節(jié)的流程圖,所以,在實(shí)踐中,我們首先需要做的就是用戶(hù)興趣標(biāo)簽的提取。我們核心擁有的就是用戶(hù)對(duì)電影的打標(biāo)簽數(shù)據(jù),以及用戶(hù)對(duì)電影的評(píng)分?jǐn)?shù)據(jù)。 所以,從上面兩個(gè)行為數(shù)據(jù)集中,我們可以嘗試提取以下幾個(gè)維度的用戶(hù)偏好數(shù)據(jù):
我們先解決用戶(hù)的偏好標(biāo)簽問(wèn)題,我們已有的是用戶(hù)對(duì)電影的打標(biāo)行為數(shù)據(jù),實(shí)際上這是電影層級(jí)的標(biāo)簽,所以我們需要在這個(gè)基礎(chǔ)上,最終直接為用戶(hù)映射上這些特征標(biāo)簽。 所以,我們需要對(duì)單個(gè)用戶(hù)的所有打標(biāo)數(shù)據(jù)進(jìn)行合并(標(biāo)簽會(huì)做預(yù)處理),然后如果用戶(hù)對(duì)剛好打標(biāo)的電影有評(píng)分的話(huà),還需要帶上評(píng)分權(quán)重,最終合并這些標(biāo)簽,形成用戶(hù)帶權(quán)重(本身的頻度、對(duì)應(yīng)電影的評(píng)分)的標(biāo)簽集,這就是用戶(hù)的一些興趣點(diǎn)。 對(duì)于類(lèi)目偏好,說(shuō)起來(lái)就簡(jiǎn)單了,比如通過(guò)評(píng)分表,我們把對(duì)應(yīng)所有的電影都取出來(lái),然后分析其類(lèi)目,將評(píng)分作為對(duì)應(yīng)類(lèi)目的權(quán)重,然后將類(lèi)目進(jìn)行合并,最終求取用戶(hù)的類(lèi)目偏好合集。 對(duì)于電影年份,過(guò)程與上述取類(lèi)目的過(guò)程類(lèi)似,最終獲取到年份偏好。
假設(shè)在上面的基礎(chǔ)上,我們已經(jīng)獲取了用戶(hù)層級(jí)的畫(huà)像屬性信息,比如偏好的電影類(lèi)別,偏好的特征標(biāo)簽,偏好的某些年份的電影(同個(gè)時(shí)代電影具有一些相同的電影,比如10年前的電影風(fēng)格與現(xiàn)在的儼然不同,年份在某種程度上說(shuō)還是有影響的,雖然很弱)。 接下來(lái),我們需要繪制候選集電影的屬性(取之前,做一些初篩過(guò)濾操作,減少計(jì)算量),對(duì)應(yīng)用戶(hù)的屬性,同樣是三個(gè),其中年份、類(lèi)目都是直接存放于電影表中,唯一需要額外處理的就是特征Tag了,由于不同人對(duì)不同的電影進(jìn)行Tag標(biāo)記,上面進(jìn)行用戶(hù)畫(huà)像繪制的時(shí)候,是以人為維度的,現(xiàn)在已電影為維度,進(jìn)行標(biāo)簽合并,最終同樣可以形成電影維度的標(biāo)簽集。
每個(gè)維度分別進(jìn)行計(jì)算相似度或者相關(guān)度,然后不同維度進(jìn)行合并歸一計(jì)算最終的用戶(hù)與電影的相關(guān)度。最外層我們依然以權(quán)重模型去做,以經(jīng)驗(yàn)來(lái)看,類(lèi)目是最重要的,其次是Tag,最后才是年份屬性,至于最終怎么調(diào)整還是需要根據(jù)實(shí)際反饋數(shù)據(jù)來(lái)做微調(diào),現(xiàn)在就拍腦袋吧。
至于說(shuō)取原始數(shù)據(jù)的過(guò)程,就不多說(shuō)了,具體的可以看上面那個(gè)案例,這里就不貼代碼片了,這里所有表的數(shù)據(jù)都會(huì)用到,所以都要獲取。
我們進(jìn)行相似tag合并操作,返回的數(shù)據(jù)形態(tài)是(mvieid,tag)集合,但tag會(huì)做提前進(jìn)行預(yù)處理,過(guò)程依然跟上次一樣,進(jìn)行編輯距離相近的詞合并。 val tagsStandardizeTmp = tagsStandardize.collect() val tagsSimi = tagsStandardize.map{ f=> var retTag = f._2 if (f._2.toString.split(' ').size == 1) { var simiTmp = '' val tagsTmpStand = tagsStandardizeTmp .filter(_._2.toString.split(' ').size != 1 ) .filter(f._2.toString.size < _._2.toString.size) .sortBy(_._2.toString.size) var x = 0 val loop = new Breaks tagsTmpStand.map{ tagTmp=> val flag = getEditSize(f._2.toString,tagTmp._2.toString) if (flag == 1){ retTag = tagTmp._2 loop.break() } } (f._1,retTag) } else { f } } 我們先將預(yù)處理之后的movie-tag數(shù)據(jù)進(jìn)行統(tǒng)計(jì)頻度,直接作為tag權(quán)重,形成(movie,tagList(tag,score))這種數(shù)據(jù)集形態(tài)。 val movieTagList = tagsSimi.map(f=>((f._1,f._2),1)).reduceByKey(_ _).groupBy(k=>k._1._1).map{ f=> (f._1,f._2.map{ ff=> (ff._1._2,ff._2) }.toList.sortBy(_._2).reverse.take(10).toMap) } 接著進(jìn)行g(shù)enre類(lèi)別以及抽取電影屬性的年份屬性,其中涉及的正則方法見(jiàn)上一個(gè)實(shí)例,這里就不重復(fù)給出了。 val moviesGenresYear = moviesData.rdd.map{ f=> val movieid = f.get(0) val genres = f.get(2) val year = movieYearRegex.movieYearReg(f.get(1).toString) val rate = f.get(3).asInstanceOf[java.math.BigDecimal].doubleValue() (movieid,(genres,year,rate)) } 最終將三種不同的屬性進(jìn)行合并,形成電影的處理過(guò)的候選集,當(dāng)然還有電影的平均評(píng)分rate屬性,這是判斷電影基本水平的標(biāo)志。 val movieContent = movieTagList.join(moviesGenresYear).filter(f=>f._2._2._3 < 2.5).sortBy(f=>f._2._2._3,false).map{ f=> //userid,taglist,genre,year,rate (f._1,f._2._1,f._2._2._1,f._2._2._2,f._2._2._3) }.collect()
先通過(guò)rating評(píng)分表與tags表進(jìn)行關(guān)聯(lián)join,獲取用戶(hù)直接與tag的關(guān)聯(lián)關(guān)系,這樣評(píng)分?jǐn)?shù)據(jù)就可以當(dāng)成單個(gè)tag的權(quán)重進(jìn)行計(jì)算了,并且通過(guò)DataFrame的API操作會(huì)更方便,所以可以先將之前處理的tagsSimi轉(zhuǎn)換成DF,然后直接可以使用類(lèi)似SQL的邏輯關(guān)系了。 val schemaString = 'movieid tag' val schema = StructType(schemaString.split(' ').map(fieldName=>StructField(fieldName,StringType,true))) val tagsSimiDataFrame = sparkSession.createDataFrame(tagsSimi.map(f=>Row(f._1,f._2.toString.trim)),schema) //對(duì)rating(userid,movieid,rate),tags(movieid,tag)進(jìn)行join,以movieid關(guān)聯(lián) //join步驟,將(userId, movieId, rate)與(movieId, tag)按照movieId字段進(jìn)行連接 val tagRateDataFrame = ratingData.join(tagsSimiDataFrame,ratingData('movieid')===tagsSimiDataFrame('movieid'),'inner').select('userid','tag','rate') 接著進(jìn)行類(lèi)似reduce操作,在SQL中就是分組合并,將(userId, tag, rate)中(userId, tag)相同的分?jǐn)?shù)rate相加。 val userPortraitTag = tagRateDataFrame.groupBy('userid','tag').sum('rate').rdd.map{ f=> (f.get(0),f.get(1),f.get(2).asInstanceOf[java.math.BigDecimal].doubleValue()) }.groupBy(f=>f._1).map{ f=> val userid = f._1 val tagList = f._2.toList.sortBy(_._3) .reverse.map(k=>(k._2,k._3)).take(20) (userid,tagList.toMap) } 在處理完用戶(hù)的興趣Tag之后,處理其他屬性,Year屬性。 val userPortraitYear = userYear.rdd.map(f=>(f.get(0),f.get(1),f.get(2))).groupBy(f=>f._1).map{ f=> val userid = f._1 val yearList = f._2.map(f=>(f._2,f._3.asInstanceOf[java.math.BigDecimal].doubleValue())).toList.take(10) (userid,yearList) } 進(jìn)行用戶(hù)的genre偏好處理。 val userPortraitGenre = userGenre.rdd.map(f=>(f.get(0),f.get(1),f.get(2))).groupBy(f=>f._1).map{ f=> val userid = f._1 val genreList = f._2.map(f=>(f._2,f._3.asInstanceOf[java.math.BigDecimal].doubleValue())).toList.take(10) (userid,genreList) } 對(duì)于每一個(gè)用戶(hù)來(lái)說(shuō),在計(jì)算待推薦列表時(shí),都需要移除自身已經(jīng)看過(guò)的電影,先獲取用戶(hù)的觀(guān)看列表。 val userMovieGet = ratingData.rdd.map(f=>(f.get(0),f.get(1))).groupByKey()
在實(shí)際的計(jì)算過(guò)程中,每個(gè)同緯度的屬性進(jìn)行相似計(jì)算,最終外層通過(guò)權(quán)重模型進(jìn)行打分,然后重新排序,獲取每個(gè)用戶(hù)的對(duì)應(yīng)的待推薦電影TopN,記得要移除自身已看過(guò)的電影列表。 val portraitBaseReData = userPortraitTag.join(userPortraitYear).join(userPortraitGenre).join(userMovieGet).map{ f=> val userid = f._1 val userTag = f._2._1._1._1 val userYear = f._2._1._1._2 val userGenre = f._2._1._2 //用于做差集計(jì)算,移除已經(jīng)看過(guò)的電影 val userMovieList = f._2._2.toList val movieRe = movieContent.map{ ff=> val movieid = ff._1 val movieTag = ff._2 val movieGenre = ff._3 val movieYear = ff._4 val movieRate = ff._5 val simiScore = getSimiScore(userTag ,movieTag,userGenre,movieGenre,userYear,movieYear,movieRate) (movieid,simiScore) }.diff(userMovieList).sortBy(k=>k._2).reverse.take(20) (userid,movieRe) }.flatMap(f=>f._2.map(ff=>(f._1,ff._1,ff._2))) 其中函數(shù)getSimiScore相關(guān)的計(jì)算邏輯如下。 def getSimiScore(userTag:Map[Any,Double],movieTag:Map[Any,Int], userGenre:List[(Any,Double)],movieGenre:Any, userYear:List[(Any,Double)],movieYear:Any, movieRate:Double): Double ={ val tagSimi = getCosTags(userTag,movieTag) val genreSimi = getGenreOrYear(userGenre,movieGenre) val yearSimi = getGenreOrYear(userYear,movieYear) val rateSimi = getRateSimi(movieRate) val score = 0.4*genreSimi 0.3*tagSimi 0.1*yearSimi 0.2*rateSimi score } 至于每個(gè)維度的計(jì)算過(guò)程,這里就不列了,大同小異,只要邏輯走的通,具體可見(jiàn)源代碼。
最后,將計(jì)算的結(jié)果保存下來(lái),同樣,需要先進(jìn)行表結(jié)構(gòu)定義。 val schemaPortraitStr = 'userid movieid score' val schemaPortrait = StructType(schemaPortraitStr.split(' ').map(fieldName=>StructField(fieldName,if (fieldName.equals('score')) DoubleType else StringType,true))) val portraitBaseReDataFrame = sparkSession.createDataFrame(portraitBaseReData.map(f=>Row(f._1,f._2,f._3)),schemaPortrait) //將結(jié)果存入hive val portraitBaseReTmpTableName = 'mite_portraitbasetmp' val portraitBaseReTableName = 'mite8.mite_portrait_base_re' portraitBaseReDataFrame.registerTempTable(portraitBaseReTmpTableName) sparkSession.sql('insert into table ' portraitBaseReTableName ' select * from ' portraitBaseReTmpTableName) 至此,所有代碼主體邏輯已經(jīng)清晰了,其實(shí)說(shuō)白了就是一個(gè)計(jì)算用戶(hù)畫(huà)像的過(guò)程,然后畫(huà)像與待推薦主體之間的關(guān)聯(lián)性。 0.5.3 實(shí)操中的注意事項(xiàng) 如上,基于用戶(hù)畫(huà)像的推薦機(jī)制在實(shí)際操作中,其實(shí)還有很多需要考慮的地方,并沒(méi)有想象中簡(jiǎn)單。
所謂沒(méi)想象中靠譜是說(shuō),一方面用戶(hù)的行為數(shù)據(jù),有時(shí)候并不是其興趣特點(diǎn)所表現(xiàn),這點(diǎn)很顯然,比如如果系統(tǒng)把一些信息故意放在很顯眼的位置,那么對(duì)于一般用戶(hù)來(lái)說(shuō),不點(diǎn)也得點(diǎn)了,所以就會(huì)造成這種用戶(hù)數(shù)據(jù)其實(shí)是不那么靠譜的。 另一方面是如果用戶(hù)產(chǎn)生了行為數(shù)據(jù),但是行為數(shù)據(jù)并不足夠多,那么這個(gè)時(shí)候其實(shí)這些行為數(shù)據(jù)是有置信度的考量的,行為數(shù)據(jù)不夠產(chǎn)生的描述是有可能形成偏差的,再根據(jù)有偏差的數(shù)據(jù)去做推薦,那結(jié)果只能是更離譜了。
在上面的實(shí)驗(yàn)邏輯中,我們知道我們并沒(méi)有對(duì)用戶(hù)的行為數(shù)據(jù)做更多的過(guò)濾,而實(shí)際的操作中,用戶(hù)的興趣是有一定時(shí)效性的。舉個(gè)例子,我在一年前看電影的記錄,還適合放到現(xiàn)在做我的畫(huà)像分析嗎?不一定的,因?yàn)槲业呐d趣可能已經(jīng)隨時(shí)間偏移了,過(guò)去我所喜歡的東西,現(xiàn)在我已經(jīng)不喜歡了。 所以,在一般實(shí)際操作的過(guò)程中,一定需要分辨用戶(hù)的興趣數(shù)據(jù)的有效性,一般情況下,我們會(huì)進(jìn)行長(zhǎng)期興趣和短期興趣的區(qū)分,人在一定時(shí)間內(nèi)其興趣是固定的,并且在一些很短暫的時(shí)間段內(nèi),比如一兩天、甚至是一天內(nèi),其關(guān)注點(diǎn)是有一定意義的,這個(gè)時(shí)候其短期興趣就生效了。 所以,我們?cè)趯?shí)際操作的時(shí)候,長(zhǎng)期興趣、短期興趣的具體的應(yīng)用就需要結(jié)合實(shí)際的場(chǎng)景的區(qū)分了,已經(jīng)我們需要注意原始數(shù)據(jù)是否適合做興趣描述的來(lái)源數(shù)據(jù),是否已經(jīng)失效。
所有涉及到行為數(shù)據(jù)的推薦算法,都繞不開(kāi)冷啟動(dòng)的問(wèn)題,即一個(gè)用戶(hù)是個(gè)新手,沒(méi)有任何行為記錄留下,這意味著我們就無(wú)法分析其畫(huà)像了,這個(gè)時(shí)候就稱(chēng)之為該用戶(hù)的冷啟動(dòng)。 在上上個(gè)章節(jié)中,我們有提到過(guò)一些解決冷啟動(dòng)的機(jī)制,比如基于內(nèi)容推薦(見(jiàn)上個(gè)章節(jié)),進(jìn)行熱點(diǎn)內(nèi)容推薦(比如把最熱門(mén)的一些電影推給該用戶(hù)),還比如根據(jù)整體數(shù)據(jù)做關(guān)聯(lián)推薦(這個(gè)后面再講),方式很多,效果不一,需要根據(jù)具體情況來(lái)看了,再不行就想辦法在用戶(hù)注冊(cè)的時(shí)候盡可能的收集用戶(hù)的靜態(tài)數(shù)據(jù),再根據(jù)用戶(hù)的靜態(tài)畫(huà)像數(shù)據(jù)來(lái)推薦,總比亂推的好。
在上面的例子中,我們其實(shí)并沒(méi)有做過(guò)多匹配計(jì)算邏輯的講解,只是簡(jiǎn)單描述同緯度的進(jìn)行相似計(jì)算,然后上層做權(quán)重模型,其實(shí)就是一種很普通的匹配計(jì)算的過(guò)程。準(zhǔn)不準(zhǔn),難在于外層權(quán)重的合理性,具體過(guò)程見(jiàn)第二篇文章,這里就不過(guò)多闡述。 其實(shí)這算是我們有意為之了,如果有些時(shí)候沒(méi)法讓不同主體(用戶(hù)&內(nèi)容)形成同一個(gè)維度矩陣的時(shí)候,這個(gè)時(shí)候其實(shí)就要有比較合理的映射機(jī)制了,能讓內(nèi)容與用戶(hù)的屬性做關(guān)聯(lián)計(jì)算。 0.5.4 信息補(bǔ)充 寫(xiě)到這里,結(jié)合實(shí)際的數(shù)據(jù),Spak工程代碼,我們成功的從呆板的屬性推薦過(guò)渡到基于用戶(hù)畫(huà)像的推薦,并為推薦附上了個(gè)性化的能力,但實(shí)際上基于用戶(hù)畫(huà)像的個(gè)性化推薦依然是有缺陷的,比如他不會(huì)做用戶(hù)興趣的升級(jí),而實(shí)際上一些知識(shí)本身就是具有一定的階梯性的。 舉個(gè)例子就很容易理解了,比如,你對(duì)大數(shù)據(jù)的東西很感興趣,于是系統(tǒng)根據(jù)你的興趣偏好天天給你推Hadoop、大數(shù)據(jù)各種技術(shù)框架等信息,在某個(gè)時(shí)間段可能是合理,比如我對(duì)大數(shù)據(jù)領(lǐng)域已經(jīng)熟知了呢?你還給我天天推送大數(shù)據(jù)相關(guān)的信息。 而我實(shí)際上是需要尋求大數(shù)據(jù)關(guān)聯(lián)的信息,甚至是升級(jí)的信息,比如基于大數(shù)據(jù)的機(jī)器學(xué)習(xí)、數(shù)據(jù)挖掘相關(guān)的東西,這個(gè)機(jī)制是無(wú)法做到這一層的。所以,學(xué)完了這個(gè),還沒(méi)完事,下個(gè)章節(jié),我們將學(xué)習(xí)另一個(gè)推薦機(jī)制,這種推薦機(jī)制可以為你推送一些基于你興趣之外的東西。 06 經(jīng)典的協(xié)同推薦 接上一個(gè)章節(jié),我們大致Get到了一個(gè)點(diǎn),那就是如果要達(dá)到推薦個(gè)性化的目的,核心還是用戶(hù)的行為數(shù)據(jù),只有用戶(hù)各自的行為數(shù)據(jù)才能反饋其與其他人所不一樣的特性,從而有針對(duì)性的進(jìn)行推薦。按上個(gè)章節(jié)的原話(huà),大致就是這樣的:
說(shuō)白了其實(shí)就是基于用戶(hù)畫(huà)像的推薦,他無(wú)法發(fā)現(xiàn)新知識(shí),所謂新知識(shí)就是,與你之前的興趣愛(ài)好相對(duì)比,推薦的候選集永遠(yuǎn)圈定在你的興趣標(biāo)簽維度內(nèi),做不到認(rèn)知的升級(jí),而實(shí)際上認(rèn)知是會(huì)進(jìn)行升級(jí)的,特別是隨著你捕獲的知識(shí)信息越多的情況下,你就越會(huì)對(duì)更上層的其他知識(shí)感興趣,不斷的深入下去。 而基于協(xié)同過(guò)濾的推薦,或多或少能解決一點(diǎn)這類(lèi)問(wèn)題,最起碼能夠結(jié)合本身用戶(hù)的行為,讓你觸達(dá)新的知識(shí)信息,并且這種遞進(jìn)是通過(guò)協(xié)同關(guān)系得到的,意味著是大部分人的共同選擇,所以還是具有一定合理性的。
對(duì)于基于協(xié)同過(guò)濾的推薦,可謂是推薦系統(tǒng)中的經(jīng)典推薦算法了,記得好像就是亞馬遜推廣出來(lái)的,然后大放光彩。協(xié)同過(guò)濾又分為基于用戶(hù)的協(xié)同(UserCF)、基于物品的協(xié)同(ItemCF),以及基于模型的協(xié)同(ModelCF)。
基于用戶(hù)的協(xié)同過(guò)濾,即我們希望通過(guò)用戶(hù)之間的關(guān)系來(lái)達(dá)到推薦物品的目的,于是,給某用戶(hù)推薦物品,即轉(zhuǎn)換為尋找為這個(gè)用戶(hù)尋找他的相似用戶(hù),然后相似用戶(hù)喜歡的物品,那么也可能是這個(gè)用戶(hù)喜歡的物品(當(dāng)然會(huì)去重)。 來(lái)看一個(gè)表格:
//其中Y表示對(duì)應(yīng)用戶(hù)喜歡對(duì)應(yīng)物品,-表示無(wú)交集,?表示需不需要推薦。 這是一個(gè)最簡(jiǎn)單的例子,其實(shí)目的很簡(jiǎn)單,我們需要給用戶(hù)A推薦物品,而且可以看到,用戶(hù)已經(jīng)喜歡了物品A和物品C,其實(shí)剩下也就B和D了,要么是B,要么是D。那么根據(jù)UserCF算法,我們先計(jì)算用戶(hù)A與用戶(hù)BC之間的相似度,計(jì)算相似,我們前文說(shuō)了,要么距離,要么余弦?jiàn)A角。 假如我們選擇計(jì)算夾角(四維):cosAB=0(90度的夾角),cosAC=0.8199(角度自己算吧)。所以相比來(lái)說(shuō),我們會(huì)發(fā)現(xiàn)用戶(hù)A與用戶(hù)C的相似度是稍微大一些的。于是,我們觀(guān)察用戶(hù)C都喜歡了哪些物品,然后與用戶(hù)的去重,然后會(huì)發(fā)現(xiàn)該給用戶(hù)A推薦物品D。 簡(jiǎn)單來(lái)講,UserCF就是如上過(guò)程,但在實(shí)際的過(guò)程中,數(shù)據(jù)量肯定不止這么點(diǎn),于是我們需要做的是為用戶(hù)計(jì)算出相似用戶(hù)列表,然后在相似用戶(hù)中經(jīng)過(guò)去重之后,計(jì)算一個(gè)推薦的物品列表(在計(jì)算推薦物品的時(shí)候,可以疊加用戶(hù)的相似程度進(jìn)一步疊加物品的權(quán)重)。 然后在喜歡物品的表達(dá)形式上,可以是如上的這種二值分類(lèi),即Yes Or No,也可以是帶有評(píng)分的程度描述,比如對(duì)于某個(gè)物品打多少分的這種表現(xiàn)形式。這樣的話(huà),針對(duì)于后一種情況,我們就需要在求在計(jì)算相似度時(shí),加入程度的權(quán)重考量。
不同于基于用戶(hù)的協(xié)同,這里,我們計(jì)算的是物品之間的相似度,但是,請(qǐng)注意,我們計(jì)算物品相似度的時(shí)候,與直接基于物品相似度推薦不同是,我們所用的特征并不是物品的自身屬性,而依然是用戶(hù)行為。
//其中Y表示對(duì)應(yīng)用戶(hù)喜歡對(duì)應(yīng)物品,-表示無(wú)交集,?表示需不需要推薦。 同樣,這是一個(gè)簡(jiǎn)單實(shí)例。目的也明確,我們?cè)谥烙脩?hù)AB喜歡某些物品情況,以及在用戶(hù)C已經(jīng)喜歡物品C的前提下,為用戶(hù)C推薦一個(gè)物品??幢砀窈芎?jiǎn)單嘛。只有兩個(gè)選項(xiàng),要么物品B,要么物品C。那么到底是物品B還是物品C呢? 我們來(lái)計(jì)算物品A與其他兩種物品的相似度,計(jì)算向量夾角。對(duì)于用戶(hù)A,物品A與物品B,則對(duì)于A(yíng)B向量為(1,0),(1,1),對(duì)于A(yíng)C向量為(1,1),(1,1),分別計(jì)算夾角cosAB=0.7,cosAC=1?;蛘哂妙?lèi)似關(guān)聯(lián)規(guī)則的方法,計(jì)算兩者之間的共現(xiàn),例如AB共現(xiàn)1次,AC共現(xiàn)2次。通過(guò)類(lèi)似這種方式,我們就知道物品A與物品C在某種程度上是更相似的。 我要說(shuō)的就是類(lèi)似共現(xiàn)類(lèi)做計(jì)算的這種方式,在大規(guī)模數(shù)據(jù)的情況下是很有效的一種方式,基于統(tǒng)計(jì)的方法在數(shù)據(jù)量足夠的時(shí)候,更能體現(xiàn)問(wèn)題的本質(zhì)。
除了我們熟悉的基于用戶(hù)以及基于物品的協(xié)同,還有一類(lèi),基于模型的協(xié)同過(guò)濾?;谀P偷膮f(xié)同過(guò)濾推薦,基于樣本的用戶(hù)偏好信息,訓(xùn)練一個(gè)模型,然后根據(jù)實(shí)時(shí)的用戶(hù)喜好信息進(jìn)行預(yù)測(cè)推薦。常見(jiàn)的基于模型推薦又有三種:最近鄰模型,典型如K最近鄰;SVD模型,即矩陣分解;圖模型,又稱(chēng)為社會(huì)網(wǎng)絡(luò)圖模型。
最近鄰模型,即使用用戶(hù)的偏好信息,我們計(jì)算當(dāng)前被推薦用戶(hù)與其他用戶(hù)的距離,然后根據(jù)近鄰進(jìn)行當(dāng)前用戶(hù)對(duì)于物品的評(píng)分預(yù)測(cè)。 典型如K最近鄰模型,假如我們使用皮爾森相關(guān)系數(shù),計(jì)算當(dāng)前用戶(hù)與其他所有用戶(hù)的相似度sim,然后在K個(gè)近鄰中,通過(guò)這些相似用戶(hù),預(yù)測(cè)當(dāng)前用戶(hù)對(duì)于每一個(gè)物品的評(píng)分,然后重新排序,最終推出M個(gè)評(píng)分最高的物品推薦出去。需要注意的是,基于近鄰的協(xié)同推薦,較依賴(lài)當(dāng)前被推薦用戶(hù)的歷史數(shù)據(jù),這樣計(jì)算出來(lái)的相關(guān)度才更準(zhǔn)確。
我們把用戶(hù)和物品的對(duì)應(yīng)關(guān)系可以看做是一個(gè)矩陣X,然后矩陣X可以分解為X=A*B。而滿(mǎn)足這種分解,并且每個(gè)用戶(hù)對(duì)應(yīng)于物品都有評(píng)分,必定存在與某組隱含的因子,使得用戶(hù)對(duì)于物品的評(píng)分逼近真實(shí)值,而我們的目標(biāo)就是通過(guò)分解矩陣得到這些隱性因子,并且通過(guò)這些因子來(lái)預(yù)測(cè)還未評(píng)分的物品。 有兩種方式來(lái)學(xué)習(xí)隱性因子,一為交叉最小二乘法,即ALS;而為隨機(jī)梯度下降法。首先對(duì)于A(yíng)LS來(lái)說(shuō),首先隨機(jī)化矩陣A,然后通過(guò)目標(biāo)函數(shù)求得B,然后對(duì)B進(jìn)行歸一化處理,反過(guò)來(lái)求A,不斷迭代,直到A*B滿(mǎn)足一定的收斂條件即停止。 對(duì)于隨機(jī)梯度下降法來(lái)說(shuō),首先我們的目標(biāo)函數(shù)是凹函數(shù)或者是凸函數(shù),我們通過(guò)調(diào)整因子矩陣使得我們的目標(biāo)沿著凹函數(shù)的最小值,或者凸函數(shù)的最大值移動(dòng),最終到達(dá)移動(dòng)閾值或者兩個(gè)函數(shù)變化絕對(duì)值小于閾值時(shí),停止因子矩陣的變化,得到的函數(shù)即為隱性因子。 使用分解矩陣的方式進(jìn)行協(xié)同推薦,可解釋性較差,但是使用RMSE(均方根誤差)作為評(píng)判標(biāo)準(zhǔn),較容易評(píng)判。 并且,我們使用這種方法時(shí),需要盡可能的讓用戶(hù)覆蓋物品,即用戶(hù)對(duì)于物品的歷史評(píng)分記錄需要足夠的多,模型才更準(zhǔn)確。
所謂社會(huì)網(wǎng)絡(luò)圖模型,即我們認(rèn)為每個(gè)人之間都是有聯(lián)系的,任何兩個(gè)用戶(hù)都可以通過(guò)某種或者多個(gè)物品的購(gòu)買(mǎi)行為而聯(lián)系起來(lái),即如果一端的節(jié)點(diǎn)是被推薦用戶(hù),而另一端是其他用戶(hù),他們之間通過(guò)若干個(gè)物品,最終能聯(lián)系到一起。 而我們基于社會(huì)網(wǎng)絡(luò)圖模型,即研究用戶(hù)對(duì)于物品的評(píng)分行為,獲取用戶(hù)與用戶(hù)之間的圖關(guān)系,最終依據(jù)圖關(guān)系的距離,為用戶(hù)推薦相關(guān)的物品。 目前這種協(xié)同推薦使用的較少。 0.6.2 基于Spark的協(xié)同過(guò)濾實(shí)踐 老規(guī)矩,大致過(guò)完了理論,我們來(lái)走一遭代碼實(shí)踐,數(shù)據(jù)源的解釋不就多說(shuō)了,依然還是那份電影數(shù)據(jù),不清楚的見(jiàn)上上上一章節(jié)的的數(shù)據(jù)說(shuō)明,這次我們只用到涉及到評(píng)分的數(shù)據(jù),共100萬(wàn)條,我們通過(guò)評(píng)分行為來(lái)做協(xié)同過(guò)濾。 截止Spark2.X系列,Spark的MlLib只實(shí)現(xiàn)了基于矩陣分解的協(xié)同(也就是經(jīng)典的基于A(yíng)LS協(xié)同過(guò)濾),沒(méi)有實(shí)現(xiàn)更常規(guī)的基于物品或者基于用戶(hù)的協(xié)同過(guò)濾,但從上面的原理我們知道,其實(shí)基于物品基于用戶(hù)的協(xié)同核心就在于構(gòu)建基礎(chǔ)向量矩陣以及計(jì)算相似的兩個(gè)方面,我這邊也是實(shí)現(xiàn)了,但基于篇幅這里,就只介紹基于A(yíng)LS的實(shí)踐過(guò)程了,其他兩個(gè)案例,需要的話(huà)請(qǐng)聯(lián)系我。 由于MlLib實(shí)現(xiàn)了算法模型,所以從敲代碼的維度上來(lái)說(shuō),代碼量反而會(huì)遠(yuǎn)遠(yuǎn)低于基于用戶(hù)、基于物品的協(xié)同,甚至?xí)儆谥暗幕谖锲废嗨苹蛘呋谟脩?hù)畫(huà)像的推薦了,順帶說(shuō)一句,基于A(yíng)LS的推薦代碼,其實(shí)網(wǎng)上很容易找,算法MlLib中的經(jīng)典算法了,很多人都實(shí)現(xiàn)了,不過(guò)萬(wàn)變不離其宗(變個(gè)毛線(xiàn),API接口就那幾個(gè),參數(shù)也就那幾個(gè),能怎么變)。 先Hive數(shù)據(jù)表中,將rating評(píng)分?jǐn)?shù)據(jù)取出來(lái)(當(dāng)然,如果你的機(jī)子跑不動(dòng),就limit一下簡(jiǎn)單取些數(shù),跑通模型就得啦)。 val ratingDataOrc = sparkSession.sql('select userid,movieid,rate,timestame from mite8.mite_ratings limit 50000') 將取出的評(píng)分?jǐn)?shù)據(jù),以時(shí)間構(gòu)建Key-value鍵值對(duì),形成(Int,Ratings)格式的數(shù)據(jù),其實(shí)這是一個(gè)中間處理過(guò)程,方便后續(xù)的數(shù)據(jù)輸入。 val ratings = ratingDataOrc.rdd.map(f => (java.lang.Long.parseLong(f.get(3).toString)%10, Rating(java.lang.Integer.parseInt(f.get(0).toString), java.lang.Integer.parseInt(f.get(1).toString), f.get(2).asInstanceOf[java.math.BigDecimal].doubleValue()))) 這里,鑒于計(jì)算能力,我就不進(jìn)行全局用戶(hù)的候選集推薦計(jì)算了,只拿ID=1的用戶(hù)當(dāng)成實(shí)驗(yàn),獲取ID=1的用戶(hù)候選推薦列表,先取該用戶(hù)的行為數(shù)據(jù)。 val personalRatingsData = ratingDataOrc.where('userid = 1').rdd.map{ f=> Rating(java.lang.Integer.parseInt(f.get(0).toString), java.lang.Integer.parseInt(f.get(1).toString), f.get(2).asInstanceOf[java.math.BigDecimal].doubleValue()) } 基于上上面的K-V中間數(shù)據(jù),我們以取余的方式,將數(shù)據(jù)分成6:2:2,三個(gè)比例,分別進(jìn)行模型訓(xùn)練,數(shù)據(jù)校驗(yàn),以及結(jié)果測(cè)試。 val training = ratings.filter(x => x._1 < 6).values .union(personalRatingsData).repartition(numPartions).persist() val validation = ratings.filter(x => x._1 >=6 && x._1 < 8).values .repartition(numPartions).persist() val test = ratings.filter(x => x._1 > 8).values.persist() ALS的推薦效果評(píng)估,一般我們是以均方根差來(lái)離線(xiàn)衡量推薦的準(zhǔn)確度,所以,這里涉及到了ALS參數(shù)調(diào)優(yōu)的問(wèn)題,我們通過(guò)數(shù)據(jù)來(lái)最終確定參數(shù),并確定最終的Model,分別取ranks、lambdas、numIters作為調(diào)優(yōu)對(duì)象。 var count = 0 //進(jìn)行三層循環(huán)遍歷,找最佳的Rmse值,對(duì)應(yīng)的model for (rank <- ranks; lambda <- lambdas; numIter <- numIters) { val model = ALS.train(training, rank, numIter, lambda) //計(jì)算均根方差值,傳入的是model以及校驗(yàn)數(shù)據(jù) val validationRmse = computeRmse(model, validation, numValidation) count = 1 //選取最佳值,均方根誤差越小越OK if (validationRmse < bestValidationRmse) { bestModel = Some(model) bestValidationRmse = validationRmse bestLambda = lambda bestRank = rank bestNumIter = numIter } } 基于上面最終選擇的參數(shù),輸出Model,我們基于這個(gè)模型,去做最后的推薦,注意需要去除ID=1的用戶(hù)已經(jīng)觀(guān)看過(guò)的電影。 //推薦前十部最感興趣的電影,注意需要剔除該用戶(hù)(userid=1)已經(jīng)評(píng)分的電影,即去重 val myRatedMovieIds = personalRatingsData.map(f=>f.product).collect().toSet val candidates = movies.keys.filter(!myRatedMovieIds.contains(_)) //為用戶(hù)1推薦十部movies,我們只做用戶(hù)ID=1的推薦 val candRDD: RDD[(Int, Int)] = candidates.map((1, _)) val recommendations:RDD[Rating] = bestModel.get.predict(candRDD) val recommendations_ = recommendations.collect().sortBy(-_.rating).take(20) 存儲(chǔ)推薦的結(jié)果,主要Row需要先進(jìn)行格式化。 //結(jié)果存儲(chǔ)用戶(hù)1的推薦結(jié)果 val alsBaseReDataFrame = sparkSession.sparkContext .parallelize(recommendations_.map(f=> (f.user,f.product,f.rating))) .map(f=>Row(f._1,f._2,f._3)) //DataFrame格式化申明 val schemaString = 'userid movieid score' val schemaAlsBase = StructType(schemaString.split(' ') .map(fieldName=>StructField(fieldName,if (fieldName.equals('score')) DoubleType else IntegerType,true))) val movieAlsBaseDataFrame = sparkSession.createDataFrame(alsBaseReDataFrame,schemaAlsBase) //將結(jié)果存入hive val itemBaseReTmpTableName = 'mite_alsbasetmp' val itemBaseReTableName = 'mite8.mite_als_base_re' movieAlsBaseDataFrame.registerTempTable(itemBaseReTmpTableName) sparkSession.sql('insert into table ' itemBaseReTableName ' select * from ' itemBaseReTmpTableName) 最后再補(bǔ)上求均方根差的函數(shù)。 def computeRmse(model:MatrixFactorizationModel,data:RDD[Rating],n:Long):Double = { //調(diào)用model的predict預(yù)測(cè)方法,把預(yù)測(cè)數(shù)據(jù)初始化model中,并且生成預(yù)測(cè)rating val predictions:RDD[Rating] = model.predict((data.map(x => (x.user, x.product)))) val dataTmp = data.map(x => ((x.user, x.product), x.rating)) //通過(guò)join操作,把相同user-product的value合并成一個(gè)(double,double)元組,前者為預(yù)測(cè)值,后者為實(shí)際值 val predictionsAndRatings = predictions.map{ x => ((x.user, x.product), x.rating) }.join(dataTmp).values //均方根誤差能夠很好的反應(yīng)出測(cè)量的精密度,對(duì)于偏離過(guò)大或者過(guò)小的測(cè)量值較為敏感 //計(jì)算過(guò)程為觀(guān)測(cè)值與真實(shí)值偏差的平方,除于觀(guān)測(cè)次數(shù)n,然后再取平方根 //reduce方法,執(zhí)行的是值累加操作 math.sqrt(predictionsAndRatings.map(x => (x._1 - x._2) * (x._1 - x._2)).reduce( _ _ )/n) } 至此,整個(gè)代碼邏輯就結(jié)束了,其實(shí)我們不難發(fā)現(xiàn),被框架封裝的算法,其實(shí)使用起來(lái)更加的簡(jiǎn)單,如果拋開(kāi)校驗(yàn)以及優(yōu)化模型的過(guò)程,總共代碼都沒(méi)有幾行。
這里大家可能對(duì)為什么協(xié)同能夠發(fā)現(xiàn)新物品,而基于用戶(hù)興趣的畫(huà)像推薦不能,原則上說(shuō)基于畫(huà)像會(huì)將思維局限于畫(huà)像興趣的偏好內(nèi),但興趣本身就會(huì)升級(jí)的,這是通過(guò)歷史的單個(gè)用戶(hù)的行為所不能推測(cè)的。 而基于協(xié)同不一樣,他一方面考慮的用戶(hù)的歷史行為,另一方面他參考了該用戶(hù)的周?chē)鷧f(xié)同的行為,而對(duì)于大部分人來(lái)說(shuō),共有的行為軌跡其實(shí)很多時(shí)候能夠一定程度上體現(xiàn)用戶(hù)的自我認(rèn)知,以及認(rèn)知升級(jí)的過(guò)程,這意味著物品之間的關(guān)聯(lián)性本身就通過(guò)共有的用戶(hù)行為天然關(guān)聯(lián),而協(xié)同就是要挖掘這種潛在的關(guān)聯(lián)性,這無(wú)關(guān)物品之間的屬性差異。 所以,從這個(gè)維度上說(shuō),協(xié)同是容易產(chǎn)生驚喜推薦的一種機(jī)制。
接推薦系統(tǒng)系列的上一個(gè)章節(jié),開(kāi)篇2個(gè)章節(jié)我們從概念,從應(yīng)用場(chǎng)景出發(fā),大概的把推薦系統(tǒng)基礎(chǔ)知識(shí)給大伙兒普及了一遍,接下來(lái)的三個(gè)章節(jié),分別由淺到深,從理論到代碼案例講解幾種不同推薦系統(tǒng)的策略或者推薦算法,看著整個(gè)系列從理論到案例,該有的都有了,其實(shí)不然,之前就有說(shuō)過(guò),推薦策略或者算法與推薦系統(tǒng)是有本質(zhì)的不同的,而這一篇就是要把前面的東西進(jìn)行收攏,從整體上進(jìn)行收官。 雖然不再?gòu)牟呗跃S度再進(jìn)一步深入,即這里不會(huì)再?gòu)睦碚摰酱a案例再深入講策略,但是實(shí)際上推薦的策略是遠(yuǎn)不止如此的,并且從應(yīng)用以及系統(tǒng)的角度來(lái)說(shuō),并沒(méi)有說(shuō)固定的策略可言。 0.7.1 推薦策略以及算法的百家齊放 承上,我們講了最基礎(chǔ)的基于內(nèi)容屬性本身的相似關(guān)系進(jìn)行針對(duì)物品的推薦,再到基于用戶(hù)的興趣屬性進(jìn)行推薦,再過(guò)渡到基于協(xié)同關(guān)系進(jìn)行推薦,其實(shí)這些都算是推薦的策略,說(shuō)的更技術(shù)點(diǎn)就是推薦的算法。 而推薦策略的想象力其實(shí)無(wú)限的,并不局限于某種固定的策略,只要從業(yè)務(wù)的角度走的通,其實(shí)都是可以的,當(dāng)然具體的選擇以及搭配問(wèn)題,后面我會(huì)講到。 我們來(lái)看已經(jīng)歸屬于大騰訊的“起點(diǎn)中文網(wǎng)”的推薦。 從他的推薦理由“喜歡這本書(shū)的人還喜歡”來(lái)看,顯然是通過(guò)用戶(hù)之間的閱讀關(guān)聯(lián)性來(lái)做的這次推薦,說(shuō)的更通俗易懂點(diǎn)就是購(gòu)物籃分析:買(mǎi)這個(gè)商品的人還經(jīng)常一起買(mǎi)其他商品。 是不是邏輯關(guān)系很像?當(dāng)然實(shí)際上到底是不是這種推薦策略,就需要起點(diǎn)的算法工程師出來(lái)講話(huà)了,我個(gè)人只是從業(yè)務(wù)層往下進(jìn)行追溯從而得出的結(jié)論。你看,我說(shuō)的對(duì)不對(duì),策略一層是沒(méi)有定式,購(gòu)物籃分析的邏輯依然是可以用在看文學(xué)站的推薦邏輯上,沒(méi)毛病。 我們?cè)賮?lái)看一個(gè)策略,依然是騰訊的,騰訊社交廣告一直對(duì)外宣稱(chēng)的技術(shù)“Lookalike”,翻譯成技術(shù)語(yǔ)言就是“人群擴(kuò)散算法”。簡(jiǎn)單的邏輯是,先找種子用戶(hù),然后基于用戶(hù)畫(huà)像和關(guān)系鏈(這是騰訊強(qiáng)項(xiàng))挖掘相似用戶(hù),然后再將受眾進(jìn)行擴(kuò)大。 具體示意圖如下: 你覺(jué)得這是推薦?看著更像是廣告投放,但廣告的投放誰(shuí)說(shuō)不是一次信息主體的推薦呢?本質(zhì)上應(yīng)該是沒(méi)有這么大的差距的,只是一個(gè)從業(yè)務(wù)的角度去描述,一個(gè)是更偏技術(shù)的角度的說(shuō)法。 隨著阿法狗諸如此類(lèi)的人工智能應(yīng)用的推廣(造勢(shì)),以及近幾年計(jì)算能力的大幅提升,使得依賴(lài)于大計(jì)算能力的神經(jīng)網(wǎng)絡(luò)的相關(guān)算法得以大放光彩,基于神經(jīng)網(wǎng)絡(luò)的一些推薦算法或者策略也是一個(gè)大的研究方向。 綜上,不必舉過(guò)多的推薦策略或者算法例子了,核心想說(shuō)的就是,其實(shí)策略層本身就是百花齊放的狀態(tài),甚至你隨意光顧一些平臺(tái)網(wǎng)站,都能遇到不同的策略和算法,甚至是搭配組合,沒(méi)有限定的策略和算法層,只有不同不適應(yīng)的應(yīng)用場(chǎng)景,以及從策略到推薦系統(tǒng)層,其實(shí)還是有很多東西的。 0.7.2 從推薦策略算法到推薦系統(tǒng) 接上面的話(huà)題,從策略算法層到系統(tǒng)層,差的有哪些東西: 1.首先是策略并不等于系統(tǒng),這是明確的,推薦的整體邏輯也未必是一種邏輯在里頭,也可能是多種的策略組合。 2.其次,如何選擇策略,如何組合策略,如何去評(píng)判,如何追蹤效果,這點(diǎn)尤其重要。 3.對(duì)于整個(gè)系統(tǒng)級(jí)的支撐,在工程化,以及數(shù)據(jù)架構(gòu)上如何去實(shí)現(xiàn),才能支持上層的算法邏輯層。 4.產(chǎn)品層的策略對(duì)整個(gè)推薦系統(tǒng)的影響有多大。 如上四個(gè)問(wèn)題都是從推薦系統(tǒng)的角度出發(fā)進(jìn)行分析的,從這里我們知道,光知道策略或者推薦算法,是不是離推薦系統(tǒng)的構(gòu)建還差那么好幾個(gè)量級(jí),02這個(gè)小節(jié),我們先從1/2兩個(gè)小點(diǎn)進(jìn)行分析。 策略!=系統(tǒng),這點(diǎn)是明確的,并且選擇哪些策略去做推薦,本身就有嚴(yán)格的選擇機(jī)制以及評(píng)判機(jī)制在里頭的。 這張圖片很有意思,是別人在脈脈上調(diào)侃各大大廠(chǎng)的推薦系統(tǒng)的話(huà),挺有意思,另一方面也是可以側(cè)面驗(yàn)證各大長(zhǎng)的一些側(cè)重點(diǎn)(不過(guò)有點(diǎn)為鵝廠(chǎng)說(shuō)好話(huà)的嫌疑),不管,我們隨拿一些實(shí)例來(lái)看看。 首先是豆瓣(當(dāng)前主頁(yè)是魔戒1的主頁(yè)): 從直觀(guān)的的角度講,同類(lèi)推薦的因素一定的在,比如《指環(huán)王》其他,比如哈利波特,加勒比海盜,但諸如大魚(yú)、角斗士、勇敢的心、與狼共舞這些的邏輯就不得而知了。 從用戶(hù)的角度上看,個(gè)人使用豆瓣電影也不算少,但幾乎沒(méi)有賬號(hào)(但如果說(shuō)沒(méi)有賬號(hào)就體系就推不準(zhǔn),那這個(gè)公司可以死球去了,有很多可以做類(lèi)似唯一用戶(hù)的判別的方式,諸如瀏覽器、電腦硬件地址、cookie等等),但從我的個(gè)人感知來(lái)看,推薦的效果一般般。 再換一個(gè),這是某寶的(當(dāng)前是一個(gè)iphoneX的購(gòu)買(mǎi)主頁(yè)): 誠(chéng)如調(diào)侃所言,我吃完兩饅頭,再問(wèn)我要不要兩饅頭,我搜索iphoneX,他問(wèn)我要不要iphone從6到6s到8s,挨個(gè)問(wèn),也夠累的,反正我是不喜歡這種格調(diào)。 再回到大騰訊,這是之前文章就涉及到的,騰訊視頻的推薦: 當(dāng)前頁(yè)面為《海上牧云記》的播放頁(yè)面,個(gè)人是騰訊視頻的中長(zhǎng)期會(huì)員,再看看他給我推薦的是什么?大部分都是類(lèi)似的奇幻古裝劇,而老實(shí)講,我對(duì)這種劇著實(shí)不喜歡,拍的tmd的假,而我只是好奇點(diǎn)擊進(jìn)去的,So... 再說(shuō)到騰訊的朋友圈廣告的投放推薦,前段時(shí)間一直給我這種孩子都快打醬油的人推婚紗攝影,這是幾個(gè)意思? 再多的例子這里就不舉了,很多看其推薦的列表大致能猜測(cè)一些其推薦的策略重點(diǎn),其實(shí)或多或少還真是與調(diào)侃的有一些相似之處,那從表面看起來(lái)大部分的推薦系統(tǒng)都不像那么高大上,問(wèn)題出在哪,是他們的策略就是這么Low?是他們的算法工程師太菜? 其實(shí)核心問(wèn)題在于推薦系統(tǒng)的評(píng)價(jià)機(jī)制,在實(shí)際的場(chǎng)景中,一切以效果評(píng)價(jià)為導(dǎo)向,將策略,甚至是組合推薦的策略往評(píng)價(jià)得分高的方向進(jìn)行調(diào)整,對(duì)于整個(gè)系統(tǒng)來(lái)說(shuō)才是有意義的,并不是說(shuō)算法高深就一定好。 那么,具體什么是合理的評(píng)價(jià)方式呢,大部分來(lái)說(shuō)都是為了讓用戶(hù)的挺溜時(shí)間加長(zhǎng),最直接的表現(xiàn)就是點(diǎn)擊轉(zhuǎn)化,但并不是完全絕對(duì)的。以百度的調(diào)侃為例,你要的是饅頭,人家給你推薦的是饅頭制造機(jī)。 這跟其商業(yè)模式是有一定的關(guān)聯(lián)的,百度之前的核心就是關(guān)鍵詞競(jìng)價(jià)廣告,那么,他必然要衡量幾個(gè)方面的東西,第一是關(guān)鍵詞與搜索詞的相關(guān)性,離太遠(yuǎn)太扯淡的不行;第二相關(guān)廣告的競(jìng)價(jià)。 于是,他就需要衡量準(zhǔn)與商業(yè)價(jià)值之間的關(guān)系了,所以,并不是一味地追求準(zhǔn)確,而是追求在其中最佳的平衡點(diǎn),能給百度帶來(lái)最佳的廣告收益轉(zhuǎn)化。 再說(shuō)其他的之前我所體驗(yàn)的推薦系統(tǒng),他們就一定不準(zhǔn)嗎?或許以我個(gè)人的角度講,他們推薦的并不是很符合我的口味,但是如果他們是從整體轉(zhuǎn)化進(jìn)行評(píng)判呢?這種機(jī)制是他們所有目前方案中的最佳轉(zhuǎn)化方案,那為什么不能用?少數(shù)的個(gè)體badcase并不會(huì)影響整體的策略,也不用管low還是不low,抓住核心問(wèn)題。 當(dāng)然,不可否認(rèn)的是,如果能滿(mǎn)足所有的人的意愿認(rèn)為它很準(zhǔn),又能讓整體系統(tǒng)的轉(zhuǎn)化做到最大化,那當(dāng)然最好了。 所以,從推薦策略算法到推薦系統(tǒng),最核心的一個(gè)東西就是評(píng)估機(jī)制,構(gòu)建起完善并且合理的評(píng)估機(jī)制,有利于整體推薦系統(tǒng)的優(yōu)化和改進(jìn)。說(shuō)到評(píng)價(jià),那不得不說(shuō)的就是AB測(cè)試了,一個(gè)好的推薦系統(tǒng),一定是會(huì)帶AB測(cè)試的,能夠很好的進(jìn)行策略對(duì)比,進(jìn)行流量分配,效果展示等等。 0.7.2 推薦系統(tǒng)數(shù)據(jù)架構(gòu) 前幾天記得分享過(guò)朋友的一篇文章,核心就是講推薦系統(tǒng)架構(gòu)的。對(duì)于整個(gè)推薦系統(tǒng)來(lái)說(shuō),系統(tǒng)的架構(gòu)設(shè)計(jì)會(huì)嚴(yán)重影響到整個(gè)系統(tǒng)的效果與上層應(yīng)用的體驗(yàn)。 在第05個(gè)章節(jié)中,記得大致提到過(guò)基于用戶(hù)畫(huà)像推薦的短期興趣與長(zhǎng)期興趣,其實(shí)長(zhǎng)期興趣的挖掘還好,基本基于離線(xiàn)的計(jì)算結(jié)果依然還是可行的,但是對(duì)于很多推薦機(jī)制來(lái)說(shuō),就是短期興趣,更切確的說(shuō)是你當(dāng)前行為的興趣表現(xiàn)。 這意味著,我需要實(shí)時(shí)的對(duì)你的瀏覽行為進(jìn)行興趣分析,然后實(shí)時(shí)的反饋給你推薦列表,這種機(jī)制看似簡(jiǎn)單腦殘,但往往很有效,因?yàn)樗銐驅(qū)崟r(shí),而在段時(shí)間內(nèi),人的注意力一般只會(huì)放在很垂直的某個(gè)點(diǎn)上,所以往往就很有效。 但是,看似簡(jiǎn)單的機(jī)制,對(duì)于這種需要支持實(shí)時(shí)分析,實(shí)時(shí)反饋的機(jī)制來(lái)說(shuō),架構(gòu)的設(shè)計(jì)就是一個(gè)挑戰(zhàn)。此外,在整體的系統(tǒng)構(gòu)建過(guò)程中,你需要考慮算法邏輯層可迭代性的問(wèn)題,即通過(guò)反饋數(shù)據(jù)能夠不斷的自動(dòng)調(diào)整你的算法策略,從而讓效果更佳,這些都是需要數(shù)據(jù)架構(gòu)進(jìn)行支撐的。 此外,就是上面說(shuō)的AB測(cè)試,效果反饋機(jī)制,都是需要集成至整個(gè)推薦系統(tǒng)中,再有承接上層應(yīng)用,你需要考慮好計(jì)算的效率與結(jié)果輸出的效率問(wèn)題,所以緩存的設(shè)計(jì)與緩存更新的機(jī)制又是個(gè)問(wèn)題。 從整個(gè)架構(gòu)層來(lái)說(shuō),其實(shí)是相對(duì)繁雜的,與我們之前所說(shuō)的策略算法層,這是另外一個(gè)維度上的東西,需要我們從整體系統(tǒng)級(jí)別去考量。
至于說(shuō)具體的推薦系統(tǒng)架構(gòu),相信大家隨便一搜都能搜到很多架構(gòu)圖,可能略有偏差,但是個(gè)人感覺(jué)只要不違背如上的一些基本原則,大體上結(jié)合自身的場(chǎng)景去調(diào)整,是沒(méi)有大毛病的,所以,這里就不給具體的架構(gòu)邏輯圖了。 0.7.3 從系統(tǒng)到產(chǎn)品策略層 說(shuō)完推薦策略,再到推薦系統(tǒng),再到系統(tǒng)架構(gòu),這些看似都是與數(shù)據(jù)、與算法嚴(yán)密相關(guān)的東西,單純的以產(chǎn)品思維角度出發(fā),你覺(jué)得在設(shè)計(jì)或者做一個(gè)推薦系統(tǒng)時(shí),有什么需要考慮的嗎?這個(gè)層面是很多技術(shù)人員很容易忽略的一部分。 其實(shí)只要用點(diǎn)心,就算不太care算法策略,也是大有可為的,以上面貼過(guò)的一張圖為例。 我們來(lái)看他的左上角標(biāo)題“喜歡這本書(shū)的人還喜歡”,其實(shí)這就是一種推薦解釋?zhuān)?,我們可看亞馬遜的推薦解釋“買(mǎi)過(guò)這個(gè)商品的人還購(gòu)買(mǎi)了”。 這就是所謂的推薦結(jié)果的可解釋性,人往往信任一些可以解釋、容易理解的東西,這也就是為何很多推薦系統(tǒng)都愿意去給出這種類(lèi)似的推薦解釋?zhuān)驗(yàn)檫@種行為可以提升可信度,而可信度可以增加用戶(hù)的點(diǎn)擊轉(zhuǎn)化,所以,可信度也是推薦系統(tǒng)設(shè)計(jì)中的一個(gè)重要參考因素。 從上面這么多推薦案例中,我們不難發(fā)現(xiàn)一個(gè)共同特征,那就是右上角的“換一批”按鈕,我們來(lái)思考一下這個(gè)按鈕存在的意義。 任何一次用戶(hù)點(diǎn)擊這個(gè)換一批按鈕,這就意味著我們收集到了用戶(hù)的反饋行為,至于說(shuō)這個(gè)反饋行為到底是正向的還是負(fù)向的,就得看具體分析了。比如一個(gè)用戶(hù)一個(gè)推薦項(xiàng)都沒(méi)有點(diǎn),不斷的切換推薦列表,直至放棄,這顯然你的推薦列表不如人意,但該用戶(hù)又是一個(gè)迫切需要推薦場(chǎng)景的用戶(hù)。如果某個(gè)用戶(hù),在不斷點(diǎn)擊推薦項(xiàng)的同時(shí),又在不斷的切換列表,這意味著這個(gè)用戶(hù)很樂(lè)意使用推薦的場(chǎng)景,并且推薦的列表還是可以的。怕就怕那種不點(diǎn)擊,也不切換的用戶(hù),我們無(wú)法獲取到更多的主動(dòng)反饋了。 說(shuō)到主動(dòng)反饋,另外一個(gè)純產(chǎn)品層的設(shè)計(jì)思路就是推薦項(xiàng)的主動(dòng)反饋了,這點(diǎn)也是亞馬遜首創(chuàng),每個(gè)推薦項(xiàng)用戶(hù)都可以打分,或者直接評(píng)判說(shuō)喜歡與不喜歡。通過(guò)這種方式,不斷的收集用戶(hù)的喜好,然后融入策略算法層,才能讓你的推薦系統(tǒng)更加的合理,體驗(yàn)更好。 所以,單純的從產(chǎn)品層,也是有很多東西可以去研究的,對(duì)于整體推薦系統(tǒng)而言,他就是一個(gè)應(yīng)用,無(wú)非是更偏向于數(shù)據(jù)、算法等維度的一個(gè)產(chǎn)品而已,我們可以從算法層去著手,也可以試圖從產(chǎn)品層去優(yōu)化。 07 總結(jié)補(bǔ)充 到這里,整個(gè)推薦長(zhǎng)文基本就結(jié)束了,從整個(gè)文章的的邏輯我們知道,如果你要架設(shè)一個(gè)推薦系統(tǒng),那么首先對(duì)于推薦系統(tǒng)的一些基本概念需要熟悉,然后了解不同的推薦策略,然后結(jié)合場(chǎng)景分析最佳的一些推薦策略算法,然后基于架構(gòu)的考慮(不同層級(jí)的分離),搭建整個(gè)推薦系統(tǒng),然后從產(chǎn)品的思維角度去優(yōu)化,最終產(chǎn)出符合你業(yè)務(wù)特征的推薦系統(tǒng)。看著容易,其實(shí)操作起來(lái)還是有一定困難的。 推薦系統(tǒng)在一般業(yè)務(wù)規(guī)模小的時(shí)候,其實(shí)鳥(niǎo)用不大的,只有在業(yè)務(wù)有了一定規(guī)模之后,那么就到了錙銖必較的階段了,使用推薦哪怕增加了5個(gè)點(diǎn)的增長(zhǎng)轉(zhuǎn)化,也是極好的,更何況可能遠(yuǎn)遠(yuǎn)不止呢。 目前很多主流推薦系統(tǒng)都是基于用戶(hù)的畫(huà)像、興趣愛(ài)好推薦的(這是一種相對(duì)靠譜,又容易在大規(guī)模用戶(hù)場(chǎng)景中使用的策略),你越是被他推薦的東西牽著走,你后續(xù)就會(huì)越陷入其中,最終導(dǎo)致了你所獲取的信息一直都是圈定在某個(gè)范圍內(nèi)的,這就是所謂的“信息繭房”。 其實(shí)要形成信息繭房一方面是由于推薦機(jī)制導(dǎo)致的,另一方面跟場(chǎng)景也是有很大關(guān)系的,比如如果用戶(hù)被你所推薦的東西所推動(dòng),那么就容易陷入這種狀態(tài),如果用戶(hù)獲取信息的渠道有多種(比如導(dǎo)航、搜索等等),那么就不那么容易。 典型如今日頭條,如果在前期你不小心點(diǎn)擊了一些比較low的內(nèi)容,然后它就越給你推類(lèi)似的文章,結(jié)果你越看,它就越推,于是你所看到的東西都是一大坨類(lèi)似離譜八卦了。從直觀(guān)的角度看,今日頭條重度依賴(lài)于用戶(hù)的閱讀行為,而頭條又是一個(gè)重推薦場(chǎng)景的產(chǎn)品,所以會(huì)相對(duì)容易陷入“信息繭房”的這種情況。 從目前看,頭條解決這個(gè)問(wèn)題的途徑是,給出熱度頻道,這個(gè)邏輯一定程度上降低用戶(hù)的興趣偏愛(ài)分析,這樣用戶(hù)能夠接觸到信息面就會(huì)更廣,進(jìn)而促使用戶(hù)能夠調(diào)整其興趣,不斷的更新其興趣。 單純從轉(zhuǎn)化的角度看來(lái),短期內(nèi)可能對(duì)于系統(tǒng)側(cè)來(lái)說(shuō)是正向的,因?yàn)樗挪粫?huì)關(guān)注到底是不是“信息繭房”,他只關(guān)注轉(zhuǎn)化有沒(méi)有提升,但長(zhǎng)期來(lái)說(shuō),對(duì)于用戶(hù)就是一種損害。所以,我們?cè)诳紤]設(shè)計(jì)推薦策略算法的時(shí)候,多多少少都會(huì)考慮推薦的新穎性。 但新穎性這東西就是一個(gè)雙刃劍,新的東西意味著不確定,不確定意味著可能低的轉(zhuǎn)化,所以好的推薦系統(tǒng)一定是在確保你興趣的同時(shí),又會(huì)考慮新穎,并且這是一種順其自然的推薦信息主體的過(guò)渡,構(gòu)建起你偏好信息與新信息之間的關(guān)聯(lián)性,讓你同樣有欲望去點(diǎn)擊那些新的東西,不過(guò)這就很難是了。 |
|
來(lái)自: melon1024 > 《機(jī)器學(xué)習(xí)》