我們來看代碼邏輯
至于說取原始數(shù)據(jù)的過程,就不多說了,具體的可以看上面那個案例坷虑,這里就不貼代碼片了甲馋,這里所有表的數(shù)據(jù)都會用到,所以都要獲取猖吴。
第一步摔刁,先進(jìn)行movie候選集的處理,包括Tag預(yù)處理海蔽,合并共屈,以及類目年份的獲取
我們進(jìn)行相似tag合并操作,返回的數(shù)據(jù)形態(tài)是(mvieid,tag)集合党窜,但tag會做提前進(jìn)行預(yù)處理拗引,過程依然跟上次一樣,進(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)varx =0val loop =newBreaks? 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}
}</pre>
我們先將預(yù)處理之后的movie-tag數(shù)據(jù)進(jìn)行統(tǒng)計頻度矾削,直接作為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類別以及抽取電影屬性的年份屬性豁护,其中涉及的正則方法見上一個實例哼凯,這里就不重復(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)行合并楚里,形成電影的處理過的候選集断部,當(dā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()
第二步,我們進(jìn)行用戶畫像屬性的獲取
先通過rating評分表與tags表進(jìn)行關(guān)聯(lián)join沉唠,獲取用戶直接與tag的關(guān)聯(lián)關(guān)系疆虚,這樣評分?jǐn)?shù)據(jù)就可以當(dāng)成單個tag的權(quán)重進(jìn)行計算了,并且通過DataFrame的API操作會更方便满葛,所以可以先將之前處理的tagsSimi轉(zhuǎn)換成DF径簿,然后直接可以使用類似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)
//對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)行類似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)
}
在處理完用戶的興趣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._1val yearList = f._2.map(f=>(f._2,f._3.asInstanceOf[java.math.BigDecimal].doubleValue())).toList.take(10)(userid,yearList)
}
進(jìn)行用戶的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._1val genreList = f._2.map(f=>(f._2,f._3.asInstanceOf[java.math.BigDecimal].doubleValue())).toList.take(10)(userid,genreList)
}
對于每一個用戶來說岳锁,在計算待推薦列表時,都需要移除自身已經(jīng)看過的電影蹦魔,先獲取用戶的觀看列表激率。
<pre style="margin: 0px; padding: 0px; max-width: 100%; box-sizing: border-box !important; word-wrap: break-word !important; font-style: normal; font-variant-ligatures: normal; font-variant-caps: normal; font-weight: 400; letter-spacing: normal; orphans: 2; text-align: start; text-indent: 0px; text-transform: none; word-spacing: 0px; -webkit-text-stroke-width: 0px; text-decoration-style: initial; text-decoration-color: initial; widows: 1; color: rgb(169, 183, 198); font-family: 宋體; font-size: 10.5pt; background-color: rgb(43, 43, 43);">val userMovieGet = ratingData.rdd.map(f=>(f.get(0),f.get(1))).groupByKey()</pre>
第三步,進(jìn)行電影畫像與用戶畫像的匹配計算
在實際的計算過程中勿决,每個同緯度的屬性進(jìn)行相似計算乒躺,最終外層通過權(quán)重模型進(jìn)行打分,然后重新排序低缩,獲取每個用戶的對應(yīng)的待推薦電影TopN嘉冒,記得要移除自身已看過的電影列表。
val portraitBaseReData = userPortraitTag.join(userPortraitYear).join(userPortraitGenre).join(userMovieGet).map{
f=>
val userid = f._1val userTag = f._2._1._1._1val userYear = f._2._1._1._2val userGenre = f._2._1._2//用于做差集計算咆繁,移除已經(jīng)看過的電影val userMovieList = f._2._2.toListval 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)的計算邏輯如下讳推。
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.4genreSimi + 0.3tagSimi + 0.1yearSimi + 0.2rateSimi
score
}
至于每個維度的計算過程,這里就不列了玩般,大同小異银觅,只要邏輯走的通,具體可見源代碼壤短。
第四步设拟,對結(jié)果進(jìn)行存儲。
最后久脯,將計算的結(jié)果保存下來纳胧,同樣,需要先進(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)清晰了,其實說白了就是一個計算用戶畫像的過程摧找,然后畫像與待推薦主體之間的關(guān)聯(lián)性核行。
0.5.3 實操中的注意事項
如上,基于用戶畫像的推薦機(jī)制在實際操作中蹬耘,其實還有很多需要考慮的地方芝雪,并沒有想象中簡單。
比如综苔,用戶的行為并沒有我們想象中靠譜惩系。
所謂沒想象中靠譜是說位岔,一方面用戶的行為數(shù)據(jù),有時候并不是其興趣特點所表現(xiàn)堡牡,這點很顯然抒抬,比如如果系統(tǒng)把一些信息故意放在很顯眼的位置,那么對于一般用戶來說晤柄,不點也得點了擦剑,所以就會造成這種用戶數(shù)據(jù)其實是不那么靠譜的。
另一方面是如果用戶產(chǎn)生了行為數(shù)據(jù)芥颈,但是行為數(shù)據(jù)并不足夠多惠勒,那么這個時候其實這些行為數(shù)據(jù)是有置信度的考量的,行為數(shù)據(jù)不夠產(chǎn)生的描述是有可能形成偏差的浇借,再根據(jù)有偏差的數(shù)據(jù)去做推薦捉撮,那結(jié)果只能是更離譜了。
用戶興趣時效性問題妇垢。
在上面的實驗邏輯中巾遭,我們知道我們并沒有對用戶的行為數(shù)據(jù)做更多的過濾,而實際的操作中闯估,用戶的興趣是有一定時效性的灼舍。舉個例子,我在一年前看電影的記錄涨薪,還適合放到現(xiàn)在做我的畫像分析嗎骑素?不一定的,因為我的興趣可能已經(jīng)隨時間偏移了刚夺,過去我所喜歡的東西献丑,現(xiàn)在我已經(jīng)不喜歡了。
所以侠姑,在一般實際操作的過程中创橄,一定需要分辨用戶的興趣數(shù)據(jù)的有效性,一般情況下莽红,我們會進(jìn)行長期興趣和短期興趣的區(qū)分妥畏,人在一定時間內(nèi)其興趣是固定的,并且在一些很短暫的時間段內(nèi)安吁,比如一兩天醉蚁、甚至是一天內(nèi),其關(guān)注點是有一定意義的鬼店,這個時候其短期興趣就生效了网棍。
所以,我們在實際操作的時候妇智,長期興趣滥玷、短期興趣的具體的應(yīng)用就需要結(jié)合實際的場景的區(qū)分了捌锭,已經(jīng)我們需要注意原始數(shù)據(jù)是否適合做興趣描述的來源數(shù)據(jù),是否已經(jīng)失效罗捎。
冷啟動的問題。
所有涉及到行為數(shù)據(jù)的推薦算法拉盾,都繞不開冷啟動的問題桨菜,即一個用戶是個新手,沒有任何行為記錄留下捉偏,這意味著我們就無法分析其畫像了倒得,這個時候就稱之為該用戶的冷啟動。
在上上個章節(jié)中夭禽,我們有提到過一些解決冷啟動的機(jī)制霞掺,比如基于內(nèi)容推薦(見上個章節(jié)),進(jìn)行熱點內(nèi)容推薦(比如把最熱門的一些電影推給該用戶)讹躯,還比如根據(jù)整體數(shù)據(jù)做關(guān)聯(lián)推薦(這個后面再講)菩彬,方式很多,效果不一潮梯,需要根據(jù)具體情況來看了骗灶,再不行就想辦法在用戶注冊的時候盡可能的收集用戶的靜態(tài)數(shù)據(jù),再根據(jù)用戶的靜態(tài)畫像數(shù)據(jù)來推薦秉馏,總比亂推的好耙旦。
匹配計算的問題。
在上面的例子中萝究,我們其實并沒有做過多匹配計算邏輯的講解免都,只是簡單描述同緯度的進(jìn)行相似計算,然后上層做權(quán)重模型帆竹,其實就是一種很普通的匹配計算的過程绕娘。準(zhǔn)不準(zhǔn),難在于外層權(quán)重的合理性馆揉,具體過程見第二篇文章业舍,這里就不過多闡述。
其實這算是我們有意為之了升酣,如果有些時候沒法讓不同主體(用戶&內(nèi)容)形成同一個維度矩陣的時候舷暮,這個時候其實就要有比較合理的映射機(jī)制了,能讓內(nèi)容與用戶的屬性做關(guān)聯(lián)計算噩茄。
0.5.4 信息補(bǔ)充
寫到這里下面,結(jié)合實際的數(shù)據(jù),Spak工程代碼绩聘,我們成功的從呆板的屬性推薦過渡到基于用戶畫像的推薦沥割,并為推薦附上了個性化的能力耗啦,但實際上基于用戶畫像的個性化推薦依然是有缺陷的,比如他不會做用戶興趣的升級机杜,而實際上一些知識本身就是具有一定的階梯性的帜讲。
舉個例子就很容易理解了,比如椒拗,你對大數(shù)據(jù)的東西很感興趣似将,于是系統(tǒng)根據(jù)你的興趣偏好天天給你推Hadoop、大數(shù)據(jù)各種技術(shù)框架等信息蚀苛,在某個時間段可能是合理在验,比如我對大數(shù)據(jù)領(lǐng)域已經(jīng)熟知了呢?你還給我天天推送大數(shù)據(jù)相關(guān)的信息堵未。
而我實際上是需要尋求大數(shù)據(jù)關(guān)聯(lián)的信息腋舌,甚至是升級的信息,比如基于大數(shù)據(jù)的機(jī)器學(xué)習(xí)渗蟹、數(shù)據(jù)挖掘相關(guān)的東西呀舔,這個機(jī)制是無法做到這一層的携添。所以,學(xué)完了這個,還沒完事梅惯,下個章節(jié)憎蛤,我們將學(xué)習(xí)另一個推薦機(jī)制捞烟,這種推薦機(jī)制可以為你推送一些基于你興趣之外的東西吠卷。
06 經(jīng)典的協(xié)同推薦
接上一個章節(jié),我們大致Get到了一個點岛心,那就是如果要達(dá)到推薦個性化的目的来破,核心還是用戶的行為數(shù)據(jù),只有用戶各自的行為數(shù)據(jù)才能反饋其與其他人所不一樣的特性忘古,從而有針對性的進(jìn)行推薦徘禁。按上個章節(jié)的原話,大致就是這樣的:
實際上基于用戶畫像的個性化推薦依然是有缺陷的髓堪,比如他不會做用戶興趣的升級送朱,而實際上一些知識本身就是具有一定的階梯性的。
舉個例子就很容易理解了干旁,比如驶沼,你對大數(shù)據(jù)的東西很感興趣,于是系統(tǒng)根據(jù)你的興趣偏好天天給你推Hadoop争群、大數(shù)據(jù)各種技術(shù)框架等信息回怜,在某個時間段可能是合理,比如我對大數(shù)據(jù)領(lǐng)域已經(jīng)熟知了呢换薄?你還給我天天推送大數(shù)據(jù)相關(guān)的信息玉雾。
而我實際上是需要尋求大數(shù)據(jù)關(guān)聯(lián)的信息翔试,甚至是升級的信息,比如基于大數(shù)據(jù)的機(jī)器學(xué)習(xí)复旬、數(shù)據(jù)挖掘相關(guān)的東西垦缅,這個機(jī)制是無法做到這一層的。
說白了其實就是基于用戶畫像的推薦驹碍,他無法發(fā)現(xiàn)新知識失都,所謂新知識就是,與你之前的興趣愛好相對比幸冻,推薦的候選集永遠(yuǎn)圈定在你的興趣標(biāo)簽維度內(nèi),做不到認(rèn)知的升級咳焚,而實際上認(rèn)知是會進(jìn)行升級的洽损,特別是隨著你捕獲的知識信息越多的情況下,你就越會對更上層的其他知識感興趣革半,不斷的深入下去碑定。
而基于協(xié)同過濾的推薦,或多或少能解決一點這類問題又官,最起碼能夠結(jié)合本身用戶的行為延刘,讓你觸達(dá)新的知識信息,并且這種遞進(jìn)是通過協(xié)同關(guān)系得到的六敬,意味著是大部分人的共同選擇碘赖,所以還是具有一定合理性的。
0.6.1 協(xié)同的原理拆解
對于基于協(xié)同過濾的推薦外构,可謂是推薦系統(tǒng)中的經(jīng)典推薦算法了普泡,記得好像就是亞馬遜推廣出來的,然后大放光彩审编。協(xié)同過濾又分為基于用戶的協(xié)同(UserCF)撼班、基于物品的協(xié)同(ItemCF),以及基于模型的協(xié)同(ModelCF)垒酬。
基于用戶的協(xié)同過濾推薦(UserCF)砰嘁。
基于用戶的協(xié)同過濾,即我們希望通過用戶之間的關(guān)系來達(dá)到推薦物品的目的勘究,于是矮湘,給某用戶推薦物品,即轉(zhuǎn)換為尋找為這個用戶尋找他的相似用戶口糕,然后相似用戶喜歡的物品板祝,那么也可能是這個用戶喜歡的物品(當(dāng)然會去重)。
來看一個表格:
|
用戶/****物品
|
物品A
|
物品B
|
物品C
|
物品D
|
|
用戶A
|
Y
|
走净?
|
Y
|
券时?
|
|
用戶B
|
|
Y
|
|
|
|
用戶C
|
Y
|
|
Y
|
Y
|
//其中Y表示對應(yīng)用戶喜歡對應(yīng)物品孤里,-表示無交集,橘洞?表示需不需要推薦捌袜。
這是一個最簡單的例子,其實目的很簡單炸枣,我們需要給用戶A推薦物品虏等,而且可以看到,用戶已經(jīng)喜歡了物品A和物品C适肠,其實剩下也就B和D了霍衫,要么是B,要么是D侯养。那么根據(jù)UserCF算法敦跌,我們先計算用戶A與用戶BC之間的相似度,計算相似逛揩,我們前文說了柠傍,要么距離,要么余弦夾角辩稽。
假如我們選擇計算夾角(四維):cosAB=0(90度的夾角)惧笛,cosAC=0.8199(角度自己算吧)。所以相比來說逞泄,我們會發(fā)現(xiàn)用戶A與用戶C的相似度是稍微大一些的患整。于是,我們觀察用戶C都喜歡了哪些物品喷众,然后與用戶的去重并级,然后會發(fā)現(xiàn)該給用戶A推薦物品D。
簡單來講侮腹,UserCF就是如上過程嘲碧,但在實際的過程中,數(shù)據(jù)量肯定不止這么點父阻,于是我們需要做的是為用戶計算出相似用戶列表愈涩,然后在相似用戶中經(jīng)過去重之后,計算一個推薦的物品列表(在計算推薦物品的時候加矛,可以疊加用戶的相似程度進(jìn)一步疊加物品的權(quán)重)履婉。
然后在喜歡物品的表達(dá)形式上,可以是如上的這種二值分類斟览,即Yes Or No毁腿,也可以是帶有評分的程度描述,比如對于某個物品打多少分的這種表現(xiàn)形式。這樣的話已烤,針對于后一種情況鸠窗,我們就需要在求在計算相似度時,加入程度的權(quán)重考量胯究。
基于物品的協(xié)同推薦(ItemCF)
不同于基于用戶的協(xié)同稍计,這里,我們計算的是物品之間的相似度裕循,但是臣嚣,請注意,我們計算物品相似度的時候剥哑,與直接基于物品相似度推薦不同是硅则,我們所用的特征并不是物品的自身屬性,而依然是用戶行為株婴。
|
用戶/****物品
|
物品A
|
物品B
|
物品C
|
|
用戶A
|
Y
|
|
Y
|
|
用戶B
|
Y
|
Y
|
Y
|
|
用戶C
|
Y
|
怎虫?
|
?
|
//其中Y表示對應(yīng)用戶喜歡對應(yīng)物品督暂,-表示無交集,穷吮?表示需不需要推薦逻翁。
同樣,這是一個簡單實例捡鱼。目的也明確八回,我們在知道用戶AB喜歡某些物品情況,以及在用戶C已經(jīng)喜歡物品C的前提下驾诈,為用戶C推薦一個物品缠诅。看表格很簡單嘛乍迄。只有兩個選項管引,要么物品B,要么物品C闯两。那么到底是物品B還是物品C呢褥伴?
我們來計算物品A與其他兩種物品的相似度,計算向量夾角漾狼。對于用戶A重慢,物品A與物品B,則對于AB向量為(1,0),(1,1)逊躁,對于AC向量為(1,1),(1,1)似踱,分別計算夾角cosAB=0.7,cosAC=1。或者用類似關(guān)聯(lián)規(guī)則的方法核芽,計算兩者之間的共現(xiàn)囚戚,例如AB共現(xiàn)1次,AC共現(xiàn)2次狞洋。通過類似這種方式弯淘,我們就知道物品A與物品C在某種程度上是更相似的。
我要說的就是類似共現(xiàn)類做計算的這種方式吉懊,在大規(guī)模數(shù)據(jù)的情況下是很有效的一種方式庐橙,基于統(tǒng)計的方法在數(shù)據(jù)量足夠的時候,更能體現(xiàn)問題的本質(zhì)借嗽。
基于模型的協(xié)同推薦(ModelCF)态鳖。
除了我們熟悉的基于用戶以及基于物品的協(xié)同,還有一類恶导,基于模型的協(xié)同過濾浆竭。基于模型的協(xié)同過濾推薦惨寿,基于樣本的用戶偏好信息邦泄,訓(xùn)練一個模型,然后根據(jù)實時的用戶喜好信息進(jìn)行預(yù)測推薦裂垦。常見的基于模型推薦又有三種:最近鄰模型顺囊,典型如K最近鄰;SVD模型蕉拢,即矩陣分解特碳;圖模型,又稱為社會網(wǎng)絡(luò)圖模型晕换。
最近鄰模型
最近鄰模型午乓,即使用用戶的偏好信息,我們計算當(dāng)前被推薦用戶與其他用戶的距離闸准,然后根據(jù)近鄰進(jìn)行當(dāng)前用戶對于物品的評分預(yù)測益愈。
典型如K最近鄰模型,假如我們使用皮爾森相關(guān)系數(shù)夷家,計算當(dāng)前用戶與其他所有用戶的相似度sim腕唧,然后在K個近鄰中,通過這些相似用戶瘾英,預(yù)測當(dāng)前用戶對于每一個物品的評分枣接,然后重新排序,最終推出M個評分最高的物品推薦出去缺谴。需要注意的是但惶,基于近鄰的協(xié)同推薦耳鸯,較依賴當(dāng)前被推薦用戶的歷史數(shù)據(jù),這樣計算出來的相關(guān)度才更準(zhǔn)確膀曾。
SVD矩陣分解
我們把用戶和物品的對應(yīng)關(guān)系可以看做是一個矩陣X县爬,然后矩陣X可以分解為X=A*B。而滿足這種分解添谊,并且每個用戶對應(yīng)于物品都有評分财喳,必定存在與某組隱含的因子,使得用戶對于物品的評分逼近真實值斩狱,而我們的目標(biāo)就是通過分解矩陣得到這些隱性因子耳高,并且通過這些因子來預(yù)測還未評分的物品。
有兩種方式來學(xué)習(xí)隱性因子所踊,一為交叉最小二乘法泌枪,即ALS;而為隨機(jī)梯度下降法秕岛。首先對于ALS來說碌燕,首先隨機(jī)化矩陣A,然后通過目標(biāo)函數(shù)求得B继薛,然后對B進(jìn)行歸一化處理修壕,反過來求A,不斷迭代遏考,直到A*B滿足一定的收斂條件即停止慈鸠。
對于隨機(jī)梯度下降法來說,首先我們的目標(biāo)函數(shù)是凹函數(shù)或者是凸函數(shù)诈皿,我們通過調(diào)整因子矩陣使得我們的目標(biāo)沿著凹函數(shù)的最小值林束,或者凸函數(shù)的最大值移動像棘,最終到達(dá)移動閾值或者兩個函數(shù)變化絕對值小于閾值時稽亏,停止因子矩陣的變化,得到的函數(shù)即為隱性因子缕题。
使用分解矩陣的方式進(jìn)行協(xié)同推薦截歉,可解釋性較差,但是使用RMSE(均方根誤差)作為評判標(biāo)準(zhǔn)烟零,較容易評判瘪松。
并且,我們使用這種方法時锨阿,需要盡可能的讓用戶覆蓋物品宵睦,即用戶對于物品的歷史評分記錄需要足夠的多,模型才更準(zhǔn)確墅诡。
社會網(wǎng)絡(luò)圖模型
所謂社會網(wǎng)絡(luò)圖模型壳嚎,即我們認(rèn)為每個人之間都是有聯(lián)系的,任何兩個用戶都可以通過某種或者多個物品的購買行為而聯(lián)系起來,即如果一端的節(jié)點是被推薦用戶烟馅,而另一端是其他用戶说庭,他們之間通過若干個物品,最終能聯(lián)系到一起郑趁。
而我們基于社會網(wǎng)絡(luò)圖模型刊驴,即研究用戶對于物品的評分行為,獲取用戶與用戶之間的圖關(guān)系寡润,最終依據(jù)圖關(guān)系的距離捆憎,為用戶推薦相關(guān)的物品。
目前這種協(xié)同推薦使用的較少悦穿。
0.6.2 基于Spark的協(xié)同過濾實踐
老規(guī)矩攻礼,大致過完了理論,我們來走一遭代碼實踐栗柒,數(shù)據(jù)源的解釋不就多說了礁扮,依然還是那份電影數(shù)據(jù),不清楚的見上上上一章節(jié)的的數(shù)據(jù)說明瞬沦,這次我們只用到涉及到評分的數(shù)據(jù)太伊,共100萬條,我們通過評分行為來做協(xié)同過濾逛钻。
截止Spark2.X系列僚焦,Spark的MlLib只實現(xiàn)了基于矩陣分解的協(xié)同(也就是經(jīng)典的基于ALS協(xié)同過濾),沒有實現(xiàn)更常規(guī)的基于物品或者基于用戶的協(xié)同過濾曙痘,但從上面的原理我們知道芳悲,其實基于物品基于用戶的協(xié)同核心就在于構(gòu)建基礎(chǔ)向量矩陣以及計算相似的兩個方面,我這邊也是實現(xiàn)了边坤,但基于篇幅這里名扛,就只介紹基于ALS的實踐過程了,其他兩個案例茧痒,需要的話請聯(lián)系我肮韧。
由于MlLib實現(xiàn)了算法模型,所以從敲代碼的維度上來說旺订,代碼量反而會遠(yuǎn)遠(yuǎn)低于基于用戶弄企、基于物品的協(xié)同,甚至?xí)儆谥暗幕谖锲废嗨苹蛘呋谟脩舢嬒竦耐扑]了区拳,順帶說一句拘领,基于ALS的推薦代碼,其實網(wǎng)上很容易找樱调,算法MlLib中的經(jīng)典算法了约素,很多人都實現(xiàn)了洽瞬,不過萬變不離其宗(變個毛線,API接口就那幾個业汰,參數(shù)也就那幾個伙窃,能怎么變)。
先Hive數(shù)據(jù)表中样漆,將rating評分?jǐn)?shù)據(jù)取出來(當(dāng)然为障,如果你的機(jī)子跑不動,就limit一下簡單取些數(shù)放祟,跑通模型就得啦)鳍怨。
val ratingDataOrc = sparkSession.sql("select userid,movieid,rate,timestame? from mite8.mite_ratings limit 50000")
將取出的評分?jǐn)?shù)據(jù),以時間構(gòu)建Key-value鍵值對跪妥,形成(Int鞋喇,Ratings)格式的數(shù)據(jù),其實這是一個中間處理過程眉撵,方便后續(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ìn)行全局用戶的候選集推薦計算了罐韩,只拿ID=1的用戶當(dāng)成實驗,獲取ID=1的用戶候選推薦列表污朽,先取該用戶的行為數(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,三個比例矾睦,分別進(jìn)行模型訓(xùn)練,數(shù)據(jù)校驗炎功,以及結(jié)果測試枚冗。
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的推薦效果評估,一般我們是以均方根差來離線衡量推薦的準(zhǔn)確度亡问,所以官紫,這里涉及到了ALS參數(shù)調(diào)優(yōu)的問題肛宋,我們通過數(shù)據(jù)來最終確定參數(shù)州藕,并確定最終的Model,分別取ranks酝陈、lambdas床玻、numIters作為調(diào)優(yōu)對象。
var count = 0 //進(jìn)行三層循環(huán)遍歷沉帮,找最佳的Rmse值锈死,對應(yīng)的model for (rank <- ranks; lambda <- lambdas; numIter <- numIters) {
val model = ALS.train(training, rank, numIter, lambda)
//計算均根方差值贫堰,傳入的是model以及校驗數(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其屏,我們基于這個模型,去做最后的推薦缨该,注意需要去除ID=1的用戶已經(jīng)觀看過的電影偎行。
//推薦前十部最感興趣的電影,注意需要剔除該用戶(userid=1)已經(jīng)評分的電影,即去重 val myRatedMovieIds = personalRatingsData.map(f=>f.product).collect().toSet
val candidates = movies.keys.filter(!myRatedMovieIds.contains())
//為用戶1推薦十部movies贰拿,我們只做用戶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)
存儲推薦的結(jié)果蛤袒,主要Row需要先進(jìn)行格式化。
//結(jié)果存儲用戶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ù)測方法妙真,把預(yù)測數(shù)據(jù)初始化model中,并且生成預(yù)測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))
//通過join操作荚守,把相同user-product的value合并成一個(double,double)元組珍德,前者為預(yù)測值,后者為實際值
val predictionsAndRatings = predictions.map{
x => ((x.user, x.product), x.rating)
}.join(dataTmp).values
//均方根誤差能夠很好的反應(yīng)出測量的精密度矗漾,對于偏離過大或者過小的測量值較為敏感
//計算過程為觀測值與真實值偏差的平方菱阵,除于觀測次數(shù)n,然后再取平方根
//reduce方法缩功,執(zhí)行的是值累加操作
math.sqrt(predictionsAndRatings.map(x => (x._1 - x._2) * (x._1 - x._2)).reduce( _ + _ )/n)
}
至此晴及,整個代碼邏輯就結(jié)束了,其實我們不難發(fā)現(xiàn)嫡锌,被框架封裝的算法虑稼,其實使用起來更加的簡單,如果拋開校驗以及優(yōu)化模型的過程势木,總共代碼都沒有幾行蛛倦。
最后再補(bǔ)充一個點。
這里大家可能對為什么協(xié)同能夠發(fā)現(xiàn)新物品啦桌,而基于用戶興趣的畫像推薦不能溯壶,原則上說基于畫像會將思維局限于畫像興趣的偏好內(nèi),但興趣本身就會升級的甫男,這是通過歷史的單個用戶的行為所不能推測的且改。
而基于協(xié)同不一樣,他一方面考慮的用戶的歷史行為板驳,另一方面他參考了該用戶的周圍協(xié)同的行為又跛,而對于大部分人來說,共有的行為軌跡其實很多時候能夠一定程度上體現(xiàn)用戶的自我認(rèn)知若治,以及認(rèn)知升級的過程慨蓝,這意味著物品之間的關(guān)聯(lián)性本身就通過共有的用戶行為天然關(guān)聯(lián)感混,而協(xié)同就是要挖掘這種潛在的關(guān)聯(lián)性,這無關(guān)物品之間的屬性差異礼烈。
所以弧满,從這個維度上說,協(xié)同是容易產(chǎn)生驚喜推薦的一種機(jī)制此熬。
07 從推薦策略算法到系統(tǒng)谱秽,到數(shù)據(jù)架構(gòu),再到產(chǎn)品思維設(shè)計
接推薦系統(tǒng)系列的上一個章節(jié)摹迷,開篇2個章節(jié)我們從概念疟赊,從應(yīng)用場景出發(fā),大概的把推薦系統(tǒng)基礎(chǔ)知識給大伙兒普及了一遍峡碉,接下來的三個章節(jié)近哟,分別由淺到深,從理論到代碼案例講解幾種不同推薦系統(tǒng)的策略或者推薦算法鲫寄,看著整個系列從理論到案例吉执,該有的都有了,其實不然地来,之前就有說過戳玫,推薦策略或者算法與推薦系統(tǒng)是有本質(zhì)的不同的,而這一篇就是要把前面的東西進(jìn)行收攏未斑,從整體上進(jìn)行收官咕宿。
雖然不再從策略維度再進(jìn)一步深入,即這里不會再從理論到代碼案例再深入講策略蜡秽,但是實際上推薦的策略是遠(yuǎn)不止如此的府阀,并且從應(yīng)用以及系統(tǒng)的角度來說,并沒有說固定的策略可言芽突。
** 0.7.1 推薦策略以及算法的百家齊放**
承上试浙,我們講了最基礎(chǔ)的基于內(nèi)容屬性本身的相似關(guān)系進(jìn)行針對物品的推薦,再到基于用戶的興趣屬性進(jìn)行推薦寞蚌,再過渡到基于協(xié)同關(guān)系進(jìn)行推薦田巴,其實這些都算是推薦的策略,說的更技術(shù)點就是推薦的算法挟秤。
而推薦策略的想象力其實無限的壹哺,并不局限于某種固定的策略,只要從業(yè)務(wù)的角度走的通煞聪,其實都是可以的斗躏,當(dāng)然具體的選擇以及搭配問題逝慧,后面我會講到昔脯。
我們來看已經(jīng)歸屬于大騰訊的“起點中文網(wǎng)”的推薦啄糙。
image
PS:截圖是我隨意點擊的一本小說《飛劍問道》,不重要云稚,可以不care隧饼。
從他的推薦理由“喜歡這本書的人還喜歡”來看,顯然是通過用戶之間的閱讀關(guān)聯(lián)性來做的這次推薦静陈,說的更通俗易懂點就是購物籃分析:買這個商品的人還經(jīng)常一起買其他商品燕雁。
是不是邏輯關(guān)系很像?當(dāng)然實際上到底是不是這種推薦策略鲸拥,就需要起點的算法工程師出來講話了拐格,我個人只是從業(yè)務(wù)層往下進(jìn)行追溯從而得出的結(jié)論。你看刑赶,我說的對不對捏浊,策略一層是沒有定式,購物籃分析的邏輯依然是可以用在看文學(xué)站的推薦邏輯上撞叨,沒毛病金踪。
我們再來看一個策略,依然是騰訊的牵敷,騰訊社交廣告一直對外宣稱的技術(shù)“Lookalike”胡岔,翻譯成技術(shù)語言就是“人群擴(kuò)散算法”。簡單的邏輯是枷餐,先找種子用戶靶瘸,然后基于用戶畫像和關(guān)系鏈(這是騰訊強(qiáng)項)挖掘相似用戶,然后再將受眾進(jìn)行擴(kuò)大毛肋。
具體示意圖如下:
image
你覺得這是推薦奕锌?看著更像是廣告投放,但廣告的投放誰說不是一次信息主體的推薦呢村生?本質(zhì)上應(yīng)該是沒有這么大的差距的惊暴,只是一個從業(yè)務(wù)的角度去描述,一個是更偏技術(shù)的角度的說法趁桃。
隨著阿法狗諸如此類的人工智能應(yīng)用的推廣(造勢)辽话,以及近幾年計算能力的大幅提升,使得依賴于大計算能力的神經(jīng)網(wǎng)絡(luò)的相關(guān)算法得以大放光彩卫病,基于神經(jīng)網(wǎng)絡(luò)的一些推薦算法或者策略也是一個大的研究方向油啤。
綜上,不必舉過多的推薦策略或者算法例子了蟀苛,核心想說的就是益咬,其實策略層本身就是百花齊放的狀態(tài),甚至你隨意光顧一些平臺網(wǎng)站帜平,都能遇到不同的策略和算法幽告,甚至是搭配組合梅鹦,沒有限定的策略和算法層,只有不同不適應(yīng)的應(yīng)用場景冗锁,以及從策略到推薦系統(tǒng)層齐唆,其實還是有很多東西的坊谁。
0.7.2 從推薦策略算法到推薦系統(tǒng)
接上面的話題锣披,從策略算法層到系統(tǒng)層,差的有哪些東西:
1.首先是策略并不等于系統(tǒng)糙申,這是明確的叨叙,推薦的整體邏輯也未必是一種邏輯在里頭锭弊,也可能是多種的策略組合。
2.其次擂错,如何選擇策略廷蓉,如何組合策略,如何去評判马昙,如何追蹤效果桃犬,這點尤其重要。
3.對于整個系統(tǒng)級的支撐行楞,在工程化攒暇,以及數(shù)據(jù)架構(gòu)上如何去實現(xiàn),才能支持上層的算法邏輯層子房。
4.產(chǎn)品層的策略對整個推薦系統(tǒng)的影響有多大形用。
如上四個問題都是從推薦系統(tǒng)的角度出發(fā)進(jìn)行分析的,從這里我們知道证杭,光知道策略或者推薦算法田度,是不是離推薦系統(tǒng)的構(gòu)建還差那么好幾個量級,02這個小節(jié)解愤,我們先從1/2兩個小點進(jìn)行分析镇饺。
策略!=系統(tǒng)送讲,這點是明確的奸笤,并且選擇哪些策略去做推薦,本身就有嚴(yán)格的選擇機(jī)制以及評判機(jī)制在里頭的哼鬓。
image
這張圖片很有意思监右,是別人在脈脈上調(diào)侃各大大廠的推薦系統(tǒng)的話,挺有意思异希,另一方面也是可以側(cè)面驗證各大長的一些側(cè)重點(不過有點為鵝廠說好話的嫌疑)健盒,不管,我們隨拿一些實例來看看。
首先是豆瓣(當(dāng)前主頁是魔戒1的主頁):
image
從直觀的的角度講扣癣,同類推薦的因素一定的在惰帽,比如《指環(huán)王》其他,比如哈利波特搏色,加勒比海盜善茎,但諸如大魚券册、角斗士频轿、勇敢的心、與狼共舞這些的邏輯就不得而知了烁焙。
從用戶的角度上看航邢,個人使用豆瓣電影也不算少,但幾乎沒有賬號(但如果說沒有賬號就體系就推不準(zhǔn)骄蝇,那這個公司可以死球去了膳殷,有很多可以做類似唯一用戶的判別的方式,諸如瀏覽器九火、電腦硬件地址赚窃、cookie等等),但從我的個人感知來看岔激,推薦的效果一般般勒极。
再換一個,這是某寶的(當(dāng)前是一個iphoneX的購買主頁):
image
誠如調(diào)侃所言虑鼎,我吃完兩饅頭辱匿,再問我要不要兩饅頭,我搜索iphoneX炫彩,他問我要不要iphone從6到6s到8s匾七,挨個問,也夠累的江兢,反正我是不喜歡這種格調(diào)昨忆。
再回到大騰訊,這是之前文章就涉及到的杉允,騰訊視頻的推薦:
image
當(dāng)前頁面為《海上牧云記》的播放頁面扔嵌,個人是騰訊視頻的中長期會員,再看看他給我推薦的是什么夺颤?大部分都是類似的奇幻古裝劇痢缎,而老實講,我對這種劇著實不喜歡世澜,拍的tmd的假独旷,而我只是好奇點擊進(jìn)去的,So...
再說到騰訊的朋友圈廣告的投放推薦,前段時間一直給我這種孩子都快打醬油的人推婚紗攝影嵌洼,這是幾個意思案疲?
再多的例子這里就不舉了,很多看其推薦的列表大致能猜測一些其推薦的策略重點麻养,其實或多或少還真是與調(diào)侃的有一些相似之處褐啡,那從表面看起來大部分的推薦系統(tǒng)都不像那么高大上,問題出在哪鳖昌,是他們的策略就是這么Low备畦?是他們的算法工程師太菜?
其實核心問題在于推薦系統(tǒng)的評價機(jī)制许昨,在實際的場景中懂盐,一切以效果評價為導(dǎo)向,將策略糕档,甚至是組合推薦的策略往評價得分高的方向進(jìn)行調(diào)整莉恼,對于整個系統(tǒng)來說才是有意義的,并不是說算法高深就一定好速那。
那么俐银,具體什么是合理的評價方式呢,大部分來說都是為了讓用戶的挺溜時間加長端仰,最直接的表現(xiàn)就是點擊轉(zhuǎn)化捶惜,但并不是完全絕對的。以百度的調(diào)侃為例榆俺,你要的是饅頭售躁,人家給你推薦的是饅頭制造機(jī)。
這跟其商業(yè)模式是有一定的關(guān)聯(lián)的茴晋,百度之前的核心就是關(guān)鍵詞競價廣告陪捷,那么,他必然要衡量幾個方面的東西诺擅,第一是關(guān)鍵詞與搜索詞的相關(guān)性市袖,離太遠(yuǎn)太扯淡的不行;第二相關(guān)廣告的競價烁涌。
于是苍碟,他就需要衡量準(zhǔn)與商業(yè)價值之間的關(guān)系了,所以撮执,并不是一味地追求準(zhǔn)確微峰,而是追求在其中最佳的平衡點,能給百度帶來最佳的廣告收益轉(zhuǎn)化抒钱。
再說其他的之前我所體驗的推薦系統(tǒng)蜓肆,他們就一定不準(zhǔn)嗎颜凯?或許以我個人的角度講,他們推薦的并不是很符合我的口味仗扬,但是如果他們是從整體轉(zhuǎn)化進(jìn)行評判呢症概?這種機(jī)制是他們所有目前方案中的最佳轉(zhuǎn)化方案,那為什么不能用早芭?少數(shù)的個體badcase并不會影響整體的策略彼城,也不用管low還是不low,抓住核心問題退个。
當(dāng)然募壕,不可否認(rèn)的是,如果能滿足所有的人的意愿認(rèn)為它很準(zhǔn)帜乞,又能讓整體系統(tǒng)的轉(zhuǎn)化做到最大化司抱,那當(dāng)然最好了筐眷。
所以黎烈,從推薦策略算法到推薦系統(tǒng),最核心的一個東西就是評估機(jī)制匀谣,構(gòu)建起完善并且合理的評估機(jī)制照棋,有利于整體推薦系統(tǒng)的優(yōu)化和改進(jìn)。說到評價武翎,那不得不說的就是AB測試了烈炭,一個好的推薦系統(tǒng),一定是會帶AB測試的宝恶,能夠很好的進(jìn)行策略對比符隙,進(jìn)行流量分配,效果展示等等垫毙。
0.7.2 推薦系統(tǒng)數(shù)據(jù)架構(gòu)
前幾天記得分享過朋友的一篇文章霹疫,核心就是講推薦系統(tǒng)架構(gòu)的。對于整個推薦系統(tǒng)來說综芥,系統(tǒng)的架構(gòu)設(shè)計會嚴(yán)重影響到整個系統(tǒng)的效果與上層應(yīng)用的體驗丽蝎。
在第05個章節(jié)中,記得大致提到過基于用戶畫像推薦的短期興趣與長期興趣膀藐,其實長期興趣的挖掘還好屠阻,基本基于離線的計算結(jié)果依然還是可行的,但是對于很多推薦機(jī)制來說额各,就是短期興趣国觉,更切確的說是你當(dāng)前行為的興趣表現(xiàn)。
這意味著虾啦,我需要實時的對你的瀏覽行為進(jìn)行興趣分析麻诀,然后實時的反饋給你推薦列表蚜枢,這種機(jī)制看似簡單腦殘,但往往很有效针饥,因為他足夠?qū)崟r厂抽,而在段時間內(nèi),人的注意力一般只會放在很垂直的某個點上丁眼,所以往往就很有效筷凤。
但是,看似簡單的機(jī)制苞七,對于這種需要支持實時分析藐守,實時反饋的機(jī)制來說,架構(gòu)的設(shè)計就是一個挑戰(zhàn)蹂风。此外卢厂,在整體的系統(tǒng)構(gòu)建過程中,你需要考慮算法邏輯層可迭代性的問題惠啄,即通過反饋數(shù)據(jù)能夠不斷的自動調(diào)整你的算法策略慎恒,從而讓效果更佳,這些都是需要數(shù)據(jù)架構(gòu)進(jìn)行支撐的撵渡。
此外融柬,就是上面說的AB測試,效果反饋機(jī)制趋距,都是需要集成至整個推薦系統(tǒng)中粒氧,再有承接上層應(yīng)用,你需要考慮好計算的效率與結(jié)果輸出的效率問題节腐,所以緩存的設(shè)計與緩存更新的機(jī)制又是個問題外盯。
從整個架構(gòu)層來說,其實是相對繁雜的翼雀,與我們之前所說的策略算法層饱苟,這是另外一個維度上的東西,需要我們從整體系統(tǒng)級別去考量锅纺。
歸納來說掷空,其實從推薦系統(tǒng)架構(gòu)設(shè)計的角度來說,需要考慮以下幾個因素或者說有以下幾個重要組成部分:
數(shù)據(jù)的輸入層囤锉,承接特征處理層坦弟,作為算法策略層的輸入。
推薦策略以及算法實現(xiàn)層官地,這就是我們說的百家齊放的那層酿傍。
基于推薦算法的結(jié)果候選集,進(jìn)行策略組合驱入、排序以及召回赤炒,最終系統(tǒng)層吐出去的是這部分的結(jié)果氯析,而非算法策略層的直接結(jié)果。
推薦系統(tǒng)的數(shù)據(jù)反饋回收機(jī)制莺褒,不管是隱性的還是顯性反饋掩缓。
AB測試分流機(jī)制。
基于反饋數(shù)據(jù)以及AB測試結(jié)果的算法層動態(tài)迭代部分遵岩,包括基礎(chǔ)的推薦算法你辣,以及排序召回等部分。
部分系統(tǒng)需要考慮實時數(shù)據(jù)的反饋流通應(yīng)用問題尘执。
至于說具體的推薦系統(tǒng)架構(gòu)舍哄,相信大家隨便一搜都能搜到很多架構(gòu)圖,可能略有偏差誊锭,但是個人感覺只要不違背如上的一些基本原則表悬,大體上結(jié)合自身的場景去調(diào)整,是沒有大毛病的丧靡,所以蟆沫,這里就不給具體的架構(gòu)邏輯圖了。
0.7.3 從系統(tǒng)到產(chǎn)品策略層
說完推薦策略窘行,再到推薦系統(tǒng)饥追,再到系統(tǒng)架構(gòu)图仓,這些看似都是與數(shù)據(jù)罐盔、與算法嚴(yán)密相關(guān)的東西,單純的以產(chǎn)品思維角度出發(fā)救崔,你覺得在設(shè)計或者做一個推薦系統(tǒng)時惶看,有什么需要考慮的嗎?這個層面是很多技術(shù)人員很容易忽略的一部分六孵。
其實只要用點心纬黎,就算不太care算法策略,也是大有可為的劫窒,以上面貼過的一張圖為例本今。
image
我們來看他的左上角標(biāo)題“喜歡這本書的人還喜歡”,其實這就是一種推薦解釋主巍,同理冠息,我們可看亞馬遜的推薦解釋“買過這個商品的人還購買了”。
這就是所謂的推薦結(jié)果的可解釋性孕索,人往往信任一些可以解釋逛艰、容易理解的東西,這也就是為何很多推薦系統(tǒng)都愿意去給出這種類似的推薦解釋搞旭,因為這種行為可以提升可信度散怖,而可信度可以增加用戶的點擊轉(zhuǎn)化菇绵,所以,可信度也是推薦系統(tǒng)設(shè)計中的一個重要參考因素镇眷。
從上面這么多推薦案例中咬最,我們不難發(fā)現(xiàn)一個共同特征,那就是右上角的“換一批”按鈕欠动,我們來思考一下這個按鈕存在的意義丹诀。
任何一次用戶點擊這個換一批按鈕,這就意味著我們收集到了用戶的反饋行為翁垂,至于說這個反饋行為到底是正向的還是負(fù)向的铆遭,就得看具體分析了。比如一個用戶一個推薦項都沒有點沿猜,不斷的切換推薦列表枚荣,直至放棄,這顯然你的推薦列表不如人意啼肩,但該用戶又是一個迫切需要推薦場景的用戶橄妆。如果某個用戶,在不斷點擊推薦項的同時祈坠,又在不斷的切換列表害碾,這意味著這個用戶很樂意使用推薦的場景,并且推薦的列表還是可以的赦拘。怕就怕那種不點擊慌随,也不切換的用戶,我們無法獲取到更多的主動反饋了躺同。
說到主動反饋阁猜,另外一個純產(chǎn)品層的設(shè)計思路就是推薦項的主動反饋了,這點也是亞馬遜首創(chuàng)蹋艺,每個推薦項用戶都可以打分剃袍,或者直接評判說喜歡與不喜歡。通過這種方式捎谨,不斷的收集用戶的喜好民效,然后融入策略算法層,才能讓你的推薦系統(tǒng)更加的合理涛救,體驗更好畏邢。
所以,單純的從產(chǎn)品層州叠,也是有很多東西可以去研究的棵红,對于整體推薦系統(tǒng)而言,他就是一個應(yīng)用咧栗,無非是更偏向于數(shù)據(jù)逆甜、算法等維度的一個產(chǎn)品而已虱肄,我們可以從算法層去著手,也可以試圖從產(chǎn)品層去優(yōu)化交煞。
07 總結(jié)補(bǔ)充
到這里咏窿,整個推薦長文基本就結(jié)束了,從整個文章的的邏輯我們知道素征,如果你要架設(shè)一個推薦系統(tǒng)集嵌,那么首先對于推薦系統(tǒng)的一些基本概念需要熟悉,然后了解不同的推薦策略御毅,然后結(jié)合場景分析最佳的一些推薦策略算法根欧,然后基于架構(gòu)的考慮(不同層級的分離),搭建整個推薦系統(tǒng)端蛆,然后從產(chǎn)品的思維角度去優(yōu)化凤粗,最終產(chǎn)出符合你業(yè)務(wù)特征的推薦系統(tǒng)〗穸梗看著容易嫌拣,其實操作起來還是有一定困難的。
推薦系統(tǒng)在一般業(yè)務(wù)規(guī)模小的時候呆躲,其實鳥用不大的异逐,只有在業(yè)務(wù)有了一定規(guī)模之后,那么就到了錙銖必較的階段了插掂,使用推薦哪怕增加了5個點的增長轉(zhuǎn)化灰瞻,也是極好的,更何況可能遠(yuǎn)遠(yuǎn)不止呢燥筷。
目前很多主流推薦系統(tǒng)都是基于用戶的畫像箩祥、興趣愛好推薦的(這是一種相對靠譜,又容易在大規(guī)模用戶場景中使用的策略)肆氓,你越是被他推薦的東西牽著走,你后續(xù)就會越陷入其中底瓣,最終導(dǎo)致了你所獲取的信息一直都是圈定在某個范圍內(nèi)的谢揪,這就是所謂的“信息繭房”。
其實要形成信息繭房一方面是由于推薦機(jī)制導(dǎo)致的捐凭,另一方面跟場景也是有很大關(guān)系的拨扶,比如如果用戶被你所推薦的東西所推動,那么就容易陷入這種狀態(tài)茁肠,如果用戶獲取信息的渠道有多種(比如導(dǎo)航患民、搜索等等),那么就不那么容易垦梆。
典型如今日頭條匹颤,如果在前期你不小心點擊了一些比較low的內(nèi)容仅孩,然后它就越給你推類似的文章,結(jié)果你越看印蓖,它就越推辽慕,于是你所看到的東西都是一大坨類似離譜八卦了。從直觀的角度看赦肃,今日頭條重度依賴于用戶的閱讀行為溅蛉,而頭條又是一個重推薦場景的產(chǎn)品,所以會相對容易陷入“信息繭房”的這種情況他宛。
從目前看船侧,頭條解決這個問題的途徑是,給出熱度頻道厅各,這個邏輯一定程度上降低用戶的興趣偏愛分析勺爱,這樣用戶能夠接觸到信息面就會更廣,進(jìn)而促使用戶能夠調(diào)整其興趣讯检,不斷的更新其興趣琐鲁。
單純從轉(zhuǎn)化的角度看來,短期內(nèi)可能對于系統(tǒng)側(cè)來說是正向的人灼,因為他才不會關(guān)注到底是不是“信息繭房”围段,他只關(guān)注轉(zhuǎn)化有沒有提升,但長期來說投放,對于用戶就是一種損害奈泪。所以,我們在考慮設(shè)計推薦策略算法的時候灸芳,多多少少都會考慮推薦的新穎性涝桅。
但新穎性這東西就是一個雙刃劍,新的東西意味著不確定烙样,不確定意味著可能低的轉(zhuǎn)化冯遂,所以好的推薦系統(tǒng)一定是在確保你興趣的同時,又會考慮新穎谒获,并且這是一種順其自然的推薦信息主體的過渡蛤肌,構(gòu)建起你偏好信息與新信息之間的關(guān)聯(lián)性,讓你同樣有欲望去點擊那些新的東西批狱,不過這就很難是了裸准。
轉(zhuǎn)自:https://mp.weixin.qq.com/s/3UjZFfCcRLRYHEvhWtNFHQ
作者:daos
鏈接:http://www.reibang.com/p/db181f4b5f2b
來源:簡書
簡書著作權(quán)歸作者所有,任何形式的轉(zhuǎn)載都請聯(lián)系作者獲得授權(quán)并注明出處赔硫。