協(xié)同過(guò)濾算法

利用用戶(hù)行為數(shù)據(jù)進(jìn)行推薦(協(xié)同過(guò)濾)

1元镀、用戶(hù)行為數(shù)據(jù)

用戶(hù)行為數(shù)據(jù)在網(wǎng)站上最簡(jiǎn)單的存在形式就是日志,比如用戶(hù)在電子商務(wù)網(wǎng)站中的網(wǎng)頁(yè)瀏覽掌测、購(gòu)買(mǎi)内贮、點(diǎn)擊、評(píng)分和評(píng)論等活動(dòng)汞斧。 用戶(hù)行為在個(gè)性化推薦系統(tǒng)中一般分兩種——顯性反饋行為(explicit feedback)和隱性反饋 行為(implicit feedback)夜郁。顯性反饋行為包括用戶(hù)明確表示對(duì)物品喜好的行為。網(wǎng)站中收集顯性反饋的主要方式就是評(píng)分和喜歡/不喜歡粘勒。隱性反饋行為指的是那些不能明確反應(yīng)用戶(hù)喜好 的行為竞端。最具代表性的隱性反饋行為就是頁(yè)面瀏覽行為。 按照反饋的明確性分庙睡,用戶(hù)行為數(shù)據(jù)可以分為顯性反饋和隱性反饋事富,但按照反饋的方向分, 又可以分為正反饋和負(fù)反饋乘陪。正反饋指用戶(hù)的行為傾向于指用戶(hù)喜歡該物品统台,而負(fù)反饋指用戶(hù)的 行為傾向于指用戶(hù)不喜歡該物品。在顯性反饋中啡邑,很容易區(qū)分一個(gè)用戶(hù)行為是正反饋還是負(fù)反饋贱勃, 而在隱性反饋行為中,就相對(duì)比較難以確定谤逼。

2贵扰、用戶(hù)行為分析

在利用用戶(hù)行為數(shù)據(jù)設(shè)計(jì)推薦算法之前,研究人員首先需要對(duì)用戶(hù)行為數(shù)據(jù)進(jìn)行分析流部,了解 數(shù)據(jù)中蘊(yùn)含的一般規(guī)律戚绕,這樣才能對(duì)算法的設(shè)計(jì)起到指導(dǎo)作用。

(1) 用戶(hù)活躍度和物品流行度

(2) 用戶(hù)活躍度和物品流行度的關(guān)系

一般認(rèn)為贵涵,新用戶(hù)傾向于瀏覽熱門(mén)的物品列肢,因?yàn)樗?們對(duì)網(wǎng)站還不熟悉,只能點(diǎn)擊首頁(yè)的熱門(mén)物品宾茂,而老用戶(hù)會(huì)逐漸開(kāi)始瀏覽冷門(mén)的物品瓷马。如果用橫坐標(biāo)表示用戶(hù)活躍度,縱坐標(biāo)表示具有某個(gè)活躍度的所有用戶(hù)評(píng)過(guò)分的物品的平均流行度跨晴。圖中曲線呈明顯下 降的趨勢(shì)欧聘,這表明用戶(hù)越活躍,越傾向于瀏覽冷門(mén)的物品端盆。


image.png

僅僅基于用戶(hù)行為數(shù)據(jù)設(shè)計(jì)的推薦算法一般稱(chēng)為協(xié)同過(guò)濾算法怀骤。學(xué)術(shù)界對(duì)協(xié)同過(guò)濾算法進(jìn)行了深入研究,提出了很多方法焕妙,比如基于鄰域的方法(neighborhood-based)蒋伦、隱語(yǔ)義模型 (latent factor model)、基于圖的隨機(jī)游走算法(random walk on graph)等焚鹊。在這些方法中痕届, 最著名的、在業(yè)界得到最廣泛應(yīng)用的算法是基于鄰域的方法末患,而基于鄰域的方法主要包含下面兩種算法研叫。

基于用戶(hù)的協(xié)同過(guò)濾算法:這種算法給用戶(hù)推薦和他興趣相似的其他用戶(hù)喜歡的物品

基于物品的協(xié)同過(guò)濾算法:這種算法給用戶(hù)推薦和他之前喜歡的物品相似的物品

3、基于領(lǐng)域的算法

基于鄰域的算法是推薦系統(tǒng)中最基本的算法璧针,該算法不僅在學(xué)術(shù)界得到了深入研究嚷炉,而且在 業(yè)界得到了廣泛應(yīng)用√匠鳎基于鄰域的算法分為兩大類(lèi)申屹,一類(lèi)是基于用戶(hù)的協(xié)同過(guò)濾算法,另一類(lèi)是 基于物品的協(xié)同過(guò)濾算法∽吒椋現(xiàn)在我們所說(shuō)的協(xié)同過(guò)濾独柑,基本上就就是指基于用戶(hù)或者是基于物品的協(xié)同過(guò)濾算法,因此私植,我們可以說(shuō)基于鄰域的算法即是我們常說(shuō)的協(xié)同過(guò)濾算法

(1) 基于用戶(hù)的協(xié)同過(guò)濾算法(UserCF)

基于用戶(hù)的協(xié)同過(guò)濾算法的基本思想是:在一個(gè)在線個(gè)性化推薦系統(tǒng)中忌栅,當(dāng)一個(gè)用戶(hù)A需要個(gè)性化推薦 時(shí),可以先找到和他有相似興趣的其他用戶(hù)曲稼,然后把那些用戶(hù)喜歡的索绪、而用戶(hù)A沒(méi)有聽(tīng)說(shuō)過(guò)的物品推薦給A。

? 從上面的描述中可以看到贫悄,基于用戶(hù)的協(xié)同過(guò)濾算法主要包括兩個(gè)步驟瑞驱。 第一步:找到和目標(biāo)用戶(hù)興趣相似的用戶(hù)集合。 第二步: 找到這個(gè)集合中的用戶(hù)喜歡的窄坦,且目標(biāo)用戶(hù)沒(méi)有聽(tīng)說(shuō)過(guò)的物品推薦給目標(biāo)用戶(hù)唤反。

這里凳寺,步驟1的關(guān)鍵是計(jì)算兩個(gè)用戶(hù)的興趣相似度,協(xié)同過(guò)濾算法主要利用行為的相似度計(jì)算興趣的相似度彤侍。給定用戶(hù)u和用戶(hù)v肠缨,令N(u)表示用戶(hù)u曾經(jīng)有過(guò)正反饋的物品集合,令N(v) 為用戶(hù)v曾經(jīng)有過(guò)正反饋的物品集合盏阶。那么我們可以通過(guò)以下方法計(jì)算用戶(hù)的相似度:


image.png

基于余弦相似度

(2) 基于物品的協(xié)同過(guò)濾算法(itemCF)
與UserCF同理
(3) UserCF和itemCF的比

首先我們提出一個(gè)問(wèn)題晒奕,為什么新聞網(wǎng)站一般使用UserCF,而圖書(shū)名斟、電商網(wǎng)站一般使用ItemCF呢脑慧? 首先回顧一下UserCF算法和ItemCF算法的推薦原理。UserCF給用戶(hù)推薦那些和他有共同興 趣愛(ài)好的用戶(hù)喜歡的物品砰盐,而ItemCF給用戶(hù)推薦那些和他之前喜歡的物品類(lèi)似的物品闷袒。從這個(gè)算 法的原理可以看到,UserCF的推薦結(jié)果著重于反映和用戶(hù)興趣相似的小群體的熱點(diǎn)岩梳,而ItemCF 的推薦結(jié)果著重于維系用戶(hù)的歷史興趣霜运。換句話(huà)說(shuō),UserCF的推薦更社會(huì)化蒋腮,反映了用戶(hù)所在的小型興趣群體中物品的熱門(mén)程度淘捡,而ItemCF的推薦更加個(gè)性化,反映了用戶(hù)自己的興趣傳承池摧。 在新聞網(wǎng)站中焦除,用戶(hù)的興趣不是特別細(xì)化,絕大多數(shù)用戶(hù)都喜歡看熱門(mén)的新聞作彤。個(gè)性化新聞推薦更加強(qiáng)調(diào)抓住 新聞熱點(diǎn)膘魄,熱門(mén)程度和時(shí)效性是個(gè)性化新聞推薦的重點(diǎn),而個(gè)性化相對(duì)于這兩點(diǎn)略顯次要竭讳。因 此创葡,UserCF可以給用戶(hù)推薦和他有相似愛(ài)好的一群其他用戶(hù)今天都在看的新聞,這樣在抓住熱 點(diǎn)和時(shí)效性的同時(shí)绢慢,保證了一定程度的個(gè)性化灿渴。同時(shí),在新聞網(wǎng)站中胰舆,物品的更新速度遠(yuǎn)遠(yuǎn)快于新用戶(hù)的加入速度骚露,而且 對(duì)于新用戶(hù),完全可以給他推薦最熱門(mén)的新聞缚窿,因此UserCF顯然是利大于弊棘幸。

但是,在圖書(shū)倦零、電子商務(wù)和電影網(wǎng)站误续,比如亞馬遜吨悍、豆瓣、Netflix中蹋嵌,ItemCF則能極大地發(fā) 揮優(yōu)勢(shì)畜份。首先,在這些網(wǎng)站中欣尼,用戶(hù)的興趣是比較固定和持久的。一個(gè)技術(shù)人員可能都是在購(gòu)買(mǎi) 技術(shù)方面的書(shū)停蕉,而且他們對(duì)書(shū)的熱門(mén)程度并不是那么敏感愕鼓,事實(shí)上越是資深的技術(shù)人員,他們看 的書(shū)就越可能不熱門(mén)慧起。此外菇晃,這些系統(tǒng)中的用戶(hù)大都不太需要流行度來(lái)輔助他們判斷一個(gè)物品的 好壞,而是可以通過(guò)自己熟悉領(lǐng)域的知識(shí)自己判斷物品的質(zhì)量蚓挤。因此磺送,這些網(wǎng)站中個(gè)性化推薦的 任務(wù)是幫助用戶(hù)發(fā)現(xiàn)和他研究領(lǐng)域相關(guān)的物品。因此灿意,ItemCF算法成為了這些網(wǎng)站的首選算法估灿。 此外,這些網(wǎng)站的物品更新速度不會(huì)特別快缤剧,一天一次更新物品相似度矩陣對(duì)它們來(lái)說(shuō)不會(huì)造成 太大的損失馅袁,是可以接受的。同時(shí)荒辕,從技術(shù)上考慮汗销,UserCF需要維護(hù)一個(gè)用戶(hù)相似度的矩陣,而ItemCF需要維護(hù)一個(gè)物品 相似度矩陣抵窒。從存儲(chǔ)的角度說(shuō)弛针,如果用戶(hù)很多,那么維護(hù)用戶(hù)興趣相似度矩陣需要很大的空間李皇, 同理削茁,如果物品很多,那么維護(hù)物品相似度矩陣代價(jià)較大

下表是對(duì)二者的一個(gè)全面的表較:


image.png

4掉房、算法的實(shí)現(xiàn)如下:(spark)

package cf

import org.apache.spark.rdd.RDD
import org.apache.spark.sql.SparkSession
import breeze.math
import breeze.numerics.{pow,sqrt}
import org.apache.spark.sql.functions._

object CFUserTest {
  case class U(userid:String, itemid:String, rating:String)
  def main(args: Array[String]): Unit = {
    val spark = SparkSession
      .builder()
      .appName("CFUserTest")
      .master("local")
      .config("spark.sql.warehouse.dir","D:\\hadoop\\sparkTest\\data\\u.data")
      .getOrCreate()
    val data = spark.sparkContext.textFile("D:\\hadoop\\sparkTest\\data\\u.data")
    userBaseCF(data,spark)
//計(jì)算用戶(hù)之間的相似度:
def userBaseCF(data:RDD[String],spark:SparkSession) : Unit ={
    import spark.implicits._
    val df_data = data.map(line => line.toString.split("\t")).map(line => U(line(0).trim,line(1).trim,line(2).trim))
      .toDF("userid","itemid","rating").select("userid","itemid","rating")

    val df_data_t = data.map(line => line.toString.split("\t")).map(line => U(line(0).trim,line(1).trim,line(2).trim))      .toDF("userid_1","itemid_1","rating_1").select("userid_1","itemid_1","rating_1")
    df_data.show(false)
    //計(jì)算每個(gè)用戶(hù)用對(duì)所有item打分的平方和付材,再開(kāi)根號(hào)
    val userScoreSum = df_data.rdd.map(x => (x(0).toString,x(2).toString)).groupByKey()
      .mapValues(x => sqrt(x.toArray.map(r => pow(r.toDouble,2)).sum))
    //將rdd userScoreSum  轉(zhuǎn)換為DataFrame,并重新命名列名
    val df_user_sum =userScoreSum.toDF("user_id_sum","rating_sqrt_sum").select("user_id_sum","rating_sqrt_sum")
    df_user_sum.show()

    //對(duì)同一個(gè)item打分的用戶(hù)進(jìn)行join,并進(jìn)行過(guò)濾圃阳,因?yàn)閖oin是做了一個(gè)笛卡爾積厌衔,數(shù)據(jù)存在重復(fù)
    val df_decare = df_data.join(df_data_t,df_data("itemid")===df_data_t("itemid_1"))
      .filter("cast(userid as long) < cast(userid_1 as long)")
    df_decare.show()
    //定義一個(gè)udf計(jì)算兩個(gè)用戶(hù)對(duì)同一個(gè)item打分的乘積
    val product_udf = udf((r1:Int,r2:Int) => r1.toDouble*r2.toDouble)
    //調(diào)用udf函數(shù),添加一列rating_product
    val df_product = df_decare.withColumn("rating_product",product_udf(col("rating"),col("rating_1")))
      .select("userid","userid_1","rating_product")
    df_product.show()
    //以"userid","userid_1"為key進(jìn)行聚合捍岳,并對(duì)value進(jìn)行求和
    val df_group = df_product.groupBy("userid","userid_1").agg("rating_product"->"sum")
      .withColumnRenamed("sum(rating_product)","rating_sum_pro")
    df_group.show()
    //將df_group和df_user_sum富寿,進(jìn)行內(nèi)連接睬隶,目的是將rating_sqrt_sum添加到同一個(gè)DataFrame中方便計(jì)算相似度
    val df_sim_1 = df_group.join(df_user_sum,df_group("userid")===df_user_sum("cast (user_id_sum as string)"))
      .drop("user_id_sum")
    df_sim_1.show()

    //與上一步作用相同
    val df_user_sum1 = df_user_sum.withColumnRenamed("rating_sqrt_sum","rating_sqrt_sum1")
    val df_sim = df_sim_1.join(df_user_sum1,df_sim_1("userid")===df_user_sum1("userid_sum"))
      .drop("userid_sum")
    df_sim.show()
    //定義udf計(jì)算用戶(hù)之間的相似度(根據(jù)cos距離計(jì)算)
    val sim_udf = udf((pro:Double,s1:Double,s2:Double) => pro/(s1.toDouble * s2.toDouble))
    val df_res = df_sim.withColumn("sim",sim_udf(col("rating_sum_pro"),col("rating_sqrt_sum"),col("rating_sqrt_sum1")))
      .select("userid","userid_1","sim")
    df_res.show()
 // 獲取相似物品集合:
  def simUserItem(df_res:DataFrame,df:DataFrame,spark:SparkSession):DataFrame={
     
    import spark.implicits._
    //2.1 以u(píng)ser_id為key進(jìn)行聚合,對(duì)value值中相似度值進(jìn)行降序排列页徐,并取前10個(gè)苏潜,然后對(duì)value進(jìn)行行轉(zhuǎn)列
    val df_nsim = df_res.rdd.map(x=>(x(0).toString,(x(1).toString,x(2).toString)))
      .groupByKey()
      .mapValues{x=>
        x.toArray.sortWith((x,y)=>x._2>y._2).slice(0,10)
      }.flatMapValues(x=>x).toDF("user_id","user_id1_sim")
      .selectExpr("user_id","user_id1_sim._1 as user_id1","user_id1_sim._2 as sim")
   
    val df_user_item1 = df.rdd.map(x=>(x(0).toString,x(1).toString+"_"+x(2).toString))
      .groupByKey().mapValues{x=>
      x.toArray
    }.toDF("user_id_gen_item","item_rating_array")

    val df_user_item2 = df_user_item1.withColumnRenamed("item_rating_array","item_rating_array1")

    //2.2分別為user_id和user_id1攜帶items進(jìn)行過(guò)濾
    val df_gen_item_tmp = df_nsim.join(df_user_item1,
      df_nsim("user_id")===df_user_item1("user_id_gen_item"))
      .drop("user_id_gen_item")
    val df_gen_item = df_gen_item_tmp.join(df_user_item2,
      df_gen_item_tmp("user_id1")===df_user_item2("user_id_gen_item"))
      .drop("user_id_gen_item")

    //2.3用一個(gè)udf過(guò)濾user_id1:item2中被user_id打過(guò)分的item,其中user_id和user_id1是相似用戶(hù)
    val filter_udf = udf{(items1:Seq[String],items2:Seq[String])=>
      val fMap = items1.map{x=>val l = x.split("_")
        (l(0),l(1))
      }.toMap
      items2.filter{x=>
        val l = x.split("_")
        fMap.getOrElse(l(0),"-")=="-"
      }
    }
    val df_filter_item = df_gen_item.withColumn("filtered_item",
      filter_udf(col("item_rating_array"),col("item_rating_array1")))
        .select("user_id","sim","filtered_item")
    //2.4公式計(jì)算 相似度*rating
    val sim_rating_udf = udf{(sim:Double,filter_item:Seq[String])=>
      filter_item.map{x=>val l=x.split("_")
        l(0)+"_"+l(1).toDouble*sim
      }
    }
    val itemSimRating = df_filter_item
      .withColumn("item_product",sim_rating_udf(col("sim"),col("filtered_item")))
      .select("user_id","item_product")

    val df_user_item_score = itemSimRating.select(itemSimRating("user_id"),explode(itemSimRating("item_product")))
      .toDF("user_id","item_product")
      .selectExpr("user_id","split(item_product,'_')[0] as item_id",
        "cast(split(item_product,'_')[1] as double) as score")
    df_user_item_score
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末变勇,一起剝皮案震驚了整個(gè)濱河市恤左,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌搀绣,老刑警劉巖飞袋,帶你破解...
    沈念sama閱讀 206,214評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異链患,居然都是意外死亡巧鸭,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)麻捻,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)纲仍,“玉大人,你說(shuō)我怎么就攤上這事贸毕≈5” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,543評(píng)論 0 341
  • 文/不壞的土叔 我叫張陵明棍,是天一觀的道長(zhǎng)锻拘。 經(jīng)常有香客問(wèn)我,道長(zhǎng)击蹲,這世上最難降的妖魔是什么署拟? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,221評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮歌豺,結(jié)果婚禮上推穷,老公的妹妹穿的比我還像新娘。我一直安慰自己类咧,他們只是感情好馒铃,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,224評(píng)論 5 371
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著痕惋,像睡著了一般区宇。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上值戳,一...
    開(kāi)封第一講書(shū)人閱讀 49,007評(píng)論 1 284
  • 那天议谷,我揣著相機(jī)與錄音,去河邊找鬼堕虹。 笑死卧晓,一個(gè)胖子當(dāng)著我的面吹牛芬首,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播逼裆,決...
    沈念sama閱讀 38,313評(píng)論 3 399
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼郁稍,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了胜宇?” 一聲冷哼從身側(cè)響起耀怜,我...
    開(kāi)封第一講書(shū)人閱讀 36,956評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎桐愉,沒(méi)想到半個(gè)月后财破,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,441評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡仅财,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,925評(píng)論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了碗淌。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片盏求。...
    茶點(diǎn)故事閱讀 38,018評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖亿眠,靈堂內(nèi)的尸體忽然破棺而出碎罚,到底是詐尸還是另有隱情,我是刑警寧澤纳像,帶...
    沈念sama閱讀 33,685評(píng)論 4 322
  • 正文 年R本政府宣布荆烈,位于F島的核電站,受9級(jí)特大地震影響竟趾,放射性物質(zhì)發(fā)生泄漏憔购。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,234評(píng)論 3 307
  • 文/蒙蒙 一岔帽、第九天 我趴在偏房一處隱蔽的房頂上張望玫鸟。 院中可真熱鬧,春花似錦犀勒、人聲如沸屎飘。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,240評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)钦购。三九已至,卻和暖如春褂萧,著一層夾襖步出監(jiān)牢的瞬間押桃,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,464評(píng)論 1 261
  • 我被黑心中介騙來(lái)泰國(guó)打工导犹, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留怨规,地道東北人陌宿。 一個(gè)月前我還...
    沈念sama閱讀 45,467評(píng)論 2 352
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像波丰,于是被迫代替她去往敵國(guó)和親壳坪。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,762評(píng)論 2 345

推薦閱讀更多精彩內(nèi)容