推薦算法初探

1. 推薦算法簡介

0x1:關于推薦的幾個小故事

在開始討論抽象具體的算法和公式之前,筆者希望先通過幾個小故事,來幫助讀者朋友建立一個對推薦算法的感性理解。同時我們也可以更好地體會到在現實復雜世界中,推薦是一項非常復雜的事情,現在的最新的推薦算法可能只模擬了其中30%不到的程度。

1. 150年前美國西部小鎮的生活情形

假想150年前一個美國小鎮的生活情形,大家都互相認識:

百貨店某天進了一批布料,店員注意到這批布料中某個特定毛邊的樣式很可能會引起Clancey夫人的高度興趣,因為他知道Clancey夫人喜歡亮花紋樣,于是他在心里記著等Clancey夫人下次光顧時將該布料拿給她看看。

Chow Winkler告訴酒吧老板Wilson先生,他考慮將多余的雷明頓(Renmington)來福槍出售,Wilson先生將這則消息告訴Bud Barclay,因為他知道Bud正在尋求一把好槍。

Valquez警長及其下屬知道Lee Pye是需要重點留意的對象,因為Lee Pye喜歡喝酒,并且性格暴躁、身體強壯

100年前的小鎮生活都與人和人之間的聯系有關。人們知道你的喜好、健康和婚姻狀況。不管是好是壞,大家得到的都是個性化的體驗。那時,這種高度個性化的社區生活占據了當時世界上的大部分角落。

請注意,這里每個人都對其他每個人的個性和近期情況非常了解,因此可以很精準地推薦和每個人的個性和近期需求最匹配的物品,這是一種面向人本身和物理關聯度的關聯度推薦策略

2. 一次愉快的公司團隊Outing

筆者公司每年都會提供員工1次免費帶薪outing的機會,今年8月,我們團隊決定去從未去過的緬甸出游,我們購買了機票和預定酒店,愉快地出發了。

當天晚上到了曼德勒,我們決定去吃一個當地網紅的本地菜餐館,到了餐館,服務員上來點菜,因為從未來過緬甸,菜單呢,也是一堆的緬甸語看不懂,因此我們讓服務員給你們推薦一些菜,這個時候問題來了,服務員該如何給我們推薦呢?

我們來列舉一下服務員要考慮的所有因素:

  • 顧客是7個人,考慮到7人都是年輕大男生,推薦的菜量要足夠,不然不夠吃,但也不能太多,不然吃不完肯定會造成客戶體驗不好
  • 顧客看外貌是中國來的,還一臉興奮的表情,看起來是第一次來緬甸,那最好推薦一些最優特色的當地菜
  • 顧客7人都是男生,除了采之外,最好再推薦一些當地的啤酒
  • 在可能的情況下,盡量推薦一些貴的菜,增加餐館營收
  • 顧客中有一個人提出不太能吃辣,推薦的菜中,將30%的菜換成不那么辣的菜

請注意這個場景中,服務員對這批顧客是沒有任何背景知識了解的,既不是熟人,也不是熟客。他只能依靠有限的信息,結合自己的經驗來給出一個主觀推薦。換句話說,這是一個冷啟動問題

3. 啤酒與尿布

Tom是一家超市的老板,近日,為了進一步提升超市的銷售額,他學習了一些統計學知識,打算利用統計學知識對近半年的銷售流水進行分析。

經過了一系列的數據清洗、統計聚類、關聯分析后,Tom發現,有一些商品,總是成對地出現在receipt上,例如:

  • 啤酒、尿布
  • 牛奶、全麥面包
  • 棉手套、棉頭套
  • 拿鐵咖啡、每日日報
  • ....

老板決定,以后在顧客結賬的時候,會根據顧客的已購商品,選擇性地推薦一些“常見搭配”給顧客,例如某個顧客買了啤酒,就選擇性地問問他是否還需要買尿布,如果某個顧客買了牛奶,就問問今日的報紙要不要買一份看看呀,如此等等。通過這個舉措,Tom發現,超市的營收上升了40%。

請注意這個場景中,超市老板對顧客的購買推薦是基于歷史上其他顧客的購買記錄,通過統計關聯提取得到的一種關聯信息,我們稱之為基于關聯規則推薦策略

4. 書商的智慧

我們去逛書店的時候,常常會發現,書店會把同一類型的書放在一起售賣,例如:

  • 路遙的《人生》、路遙的《平凡的世界》
  • 阿西莫夫的《銀河帝國:基地7部曲》、劉慈欣的《三體》

這么做的原因在于,每本書盡管風格各不相同,但總體上可以按照一定的屬性維度進行分類,例如:

  • 青春
    • 校園
    • 愛情
    • 叛逆
    • 網絡
    • 爆笑 
  • 小說作品集
    • 世界名著
    • 外國小說
    • 中國古典小說
    • 武俠小說 

請注意這個場景中,書商對書進行聚類關聯,將相似的暢銷書放在一起捆綁銷售,是基于下面這條假設,喜歡某類書的顧客,也有同樣喜歡同屬于該類的其他書。這是一種內容關聯推薦策略

5. 市場調研的智慧

Lily準備在大學城周圍開一家奶茶店,擺在眼前的第一件事就是要搞清楚,這一片區域中的大學生都喜歡哪些類型的奶茶。

為了解決這個問題,Lily先建立了一個基本假設:物以類聚人以群分,同一個專業/班級內的學生的喜好是彼此接近的,同時也是會隨著相處的時間逐漸靠攏的,因此,只要從每個專業中隨機抽取1-2名學生,進行市場調研,總會匯總調研結果,就能基本得到該區域內大學生的奶茶喜好了。

在這個例子中,奶茶店老板的推薦策略是,對未知的客戶的推薦,可以先尋找到與該客戶最相似的“同類客戶群”,然后用這個同類客戶群的喜好來給新的顧客進行推薦。這是一種用戶關聯推薦策略

0x2:群體智慧的推薦算法

推薦算法本質上提取的是大規模數據集中的相關性統計信息,因此更適合大數據場景。

Relevant Link:     

https://lianhaimiao.github.io/2018/01/06/%E6%8E%A8%E8%8D%90%E7%B3%BB%E7%BB%9F%E6%96%B9%E6%B3%95%E5%B0%8F%E7%BB%93-%E4%B8%8A/ 
https://www.bobinsun.cn/ai/2019/04/17/Recommender-system/ 
https://www.zhihu.com/question/19971859
http://www.guidetodatamining.com/

 

2. 協同過濾推薦算法

0x1:什么是協同過濾思想

所謂協同過濾,就是利用群體的行為來給每個單個個體做綜合決策(推薦),屬于群體智慧編程的范疇。 

對于推薦系統來說,通過用戶的持續協同作用,最終給用戶的推薦會越來越準。 

具體來說,協同過濾的思路是通過群體的行為來找到某種相似性(用戶之間的相似性或者標的物之間的相似性),通過該相似性來為用戶做決策和推薦

現實生活中有很多協同過濾的案例及思想體現,例如人類喜歡追求相親中的“門當戶對”,其實也是一種協同過濾思想的反射,門當戶對實際上是建立了相親男女的一種“相似度”(家庭背景、出身、生活習慣、為人處世、消費觀、甚至價值觀可能會相似),給自己找一個門當戶對的伴侶就是一種“過濾”,當雙方”門當戶對“時,各方面的習慣及價值觀會更相似,未來幸福的概率也會更大。 如果整個社會具備這樣的傳統和風氣,通過”協同進化“作用,大家會越來越認同這種方式。 

抽象地說,協同過濾利用了兩個非常樸素的自然哲學思想: 

  • “群體的智慧”:群體的智慧的底層原理是統計學規律,是一種朝向平衡穩定態發展的動態過程。
  • “相似的物體具備相似的性質”:這個道理在物理學和化學中體現的非常明顯,越相似的物體在物理結構和化學結構上就越相似。這個道理對于抽象的虛擬數據世界也是成立的。

筆者思考

將協同過濾和關聯規則挖掘進行互相對比,我們會發現,itemCF是頻繁項挖掘FPGrowth的一種通用表示,頻繁項挖掘的本質上挖掘的也是一種相似性統計模式。下面我們來類比一下它們二者思想的共通點:

  • itemCF:歷史評價行為矩陣的列向量視角,通過度量每個record中不同列之間的距離,來度量item之前的相似度
  • FPGrowth:本質也是列向量視角,但FPGrowth可以看成是是簡化版的itemCF,在FPGrowth中,不同列之間的距離度量簡化為0/1兩種狀態(是否出現),FPGrowth中所謂的頻繁共現集,本質上是尋找一個距離相近的列向量集合

0x2:協同過濾的矩陣統一視角

盡管協同過濾大體上可以分為userCF和itemCF,但其實如果我們用二維矩陣來進行抽象,可以將它們二者看做同一個框架下兩種不同算法形式。

以一個百貨店的購買歷史記錄為例,行為用戶id,列為該用戶對每個商品的購買評價分,

我們可以看到,

  • 行視角代表了每個用戶對所有商品的評分情況,可以理解為對每個用戶進行了一個特征向量化,每個屬性對應了一個商品
  • 列視角代表了每個商品被所有用戶的評分情況,可以理解為對每個商品進行了一個特征向量化,每個屬性對應一個用戶
  • 矩陣中存在缺失值,所以這是一個稀疏矩陣

可以看到,所謂的“user-based collaborative filtering”和“item-based collaborative filtering”本質上可以理解為對用戶歷史購買行為的二維矩陣,分別進行行向量和列向量的相似度關聯挖掘。

0x3:user-based collaborative filtering - 基于用戶的推薦:愛你所愛

1. 算法主要思想

基于用戶對物品的喜好找到相似鄰居用戶,然后將鄰居用戶喜歡的物品推薦給目標用戶,即所謂的愛你所愛。

上圖中,用戶A喜歡物品A和物品C,用戶C也喜歡物品A、物品C,同時還喜歡物品D。

從這些用戶的歷史喜好信息中,我們可以發現用戶A和用戶C的口味和偏好是比較類似的,同時用戶C還喜歡物品D,那么我們可以推斷用戶A可能也喜歡物品D,因此可以將物品D推薦給用戶A。

2. 算法簡要實現思路

將一個用戶對所有物品的打分(行視角)作為一個向量(Vector),計算用戶之間的相似度,找到top K-近鄰鄰居后,根據所有鄰居的相似度權重以及他們對物品的評分,為目標用戶生成一個排序的物品列表作為推薦列表。

3. 算法實現示例

import codecs 
from math import sqrt

users = {"Angelica": {"Blues Traveler": 3.5, "Broken Bells": 2.0,
                      "Norah Jones": 4.5, "Phoenix": 5.0,
                      "Slightly Stoopid": 1.5,
                      "The Strokes": 2.5, "Vampire Weekend": 2.0},
         
         "Bill":{"Blues Traveler": 2.0, "Broken Bells": 3.5,
                 "Deadmau5": 4.0, "Phoenix": 2.0,
                 "Slightly Stoopid": 3.5, "Vampire Weekend": 3.0},
         
         "Chan": {"Blues Traveler": 5.0, "Broken Bells": 1.0,
                  "Deadmau5": 1.0, "Norah Jones": 3.0, "Phoenix": 5,
                  "Slightly Stoopid": 1.0},
         
         "Dan": {"Blues Traveler": 3.0, "Broken Bells": 4.0,
                 "Deadmau5": 4.5, "Phoenix": 3.0,
                 "Slightly Stoopid": 4.5, "The Strokes": 4.0,
                 "Vampire Weekend": 2.0},
         
         "Hailey": {"Broken Bells": 4.0, "Deadmau5": 1.0,
                    "Norah Jones": 4.0, "The Strokes": 4.0,
                    "Vampire Weekend": 1.0},
         
         "Jordyn":  {"Broken Bells": 4.5, "Deadmau5": 4.0,
                     "Norah Jones": 5.0, "Phoenix": 5.0,
                     "Slightly Stoopid": 4.5, "The Strokes": 4.0,
                     "Vampire Weekend": 4.0},
         
         "Sam": {"Blues Traveler": 5.0, "Broken Bells": 2.0,
                 "Norah Jones": 3.0, "Phoenix": 5.0,
                 "Slightly Stoopid": 4.0, "The Strokes": 5.0},
         
         "Veronica": {"Blues Traveler": 3.0, "Norah Jones": 5.0,
                      "Phoenix": 4.0, "Slightly Stoopid": 2.5,
                      "The Strokes": 3.0}
        }



class recommender:

    def __init__(self, data, k=1, metric='pearson', n=5):
        """ initialize recommender
        currently, if data is dictionary the recommender is initialized
        to it.
        For all other data types of data, no initialization occurs
        k is the k value for k nearest neighbor
        metric is which distance formula to use
        n is the maximum number of recommendations to make"""
        self.k = k
        self.n = n
        self.username2id = {}
        self.userid2name = {}
        self.productid2name = {}
        # for some reason I want to save the name of the metric
        self.metric = metric
        if self.metric == 'pearson':
            self.fn = self.pearson
        #
        # if data is dictionary set recommender data to it
        #
        if type(data).__name__ == 'dict':
            self.data = data

    def convertProductID2name(self, id):
        """Given product id number return product name"""
        if id in self.productid2name:
            return self.productid2name[id]
        else:
            return id


    def userRatings(self, id, n):
        """Return n top ratings for user with id"""
        print ("Ratings for " + self.userid2name[id])
        ratings = self.data[id]
        print(len(ratings))
        ratings = list(ratings.items())
        ratings = [(self.convertProductID2name(k), v)
                   for (k, v) in ratings]
        # finally sort and return
        ratings.sort(key=lambda artistTuple: artistTuple[1],
                     reverse = True)
        ratings = ratings[:n]
        for rating in ratings:
            print("%s\t%i" % (rating[0], rating[1]))
        

        

    def loadBookDB(self, path=''):
        """loads the BX book dataset. Path is where the BX files are
        located"""
        self.data = {}
        i = 0
        #
        # First load book ratings into self.data
        #
        f = codecs.open(path + "BX-Book-Ratings.csv", 'r', 'utf8')
        for line in f:
            i += 1
            #separate line into fields
            fields = line.split(';')
            user = fields[0].strip('"')
            book = fields[1].strip('"')
            rating = int(fields[2].strip().strip('"'))
            if user in self.data:
                currentRatings = self.data[user]
            else:
                currentRatings = {}
            currentRatings[book] = rating
            self.data[user] = currentRatings
        f.close()
        #
        # Now load books into self.productid2name
        # Books contains isbn, title, and author among other fields
        #
        f = codecs.open(path + "BX-Books.csv", 'r', 'utf8')
        for line in f:
            i += 1
            #separate line into fields
            fields = line.split(';')
            isbn = fields[0].strip('"')
            title = fields[1].strip('"')
            author = fields[2].strip().strip('"')
            title = title + ' by ' + author
            self.productid2name[isbn] = title
        f.close()
        #
        #  Now load user info into both self.userid2name and
        #  self.username2id
        #
        f = codecs.open(path + "BX-Users.csv", 'r', 'utf8')
        for line in f:
            i += 1
            #print(line)
            #separate line into fields
            fields = line.split(';')
            userid = fields[0].strip('"')
            location = fields[1].strip('"')
            if len(fields) > 3:
                age = fields[2].strip().strip('"')
            else:
                age = 'NULL'
            if age != 'NULL':
                value = location + '  (age: ' + age + ')'
            else:
                value = location
            self.userid2name[userid] = value
            self.username2id[location] = userid
        f.close()
        print(i)
                
        
    def pearson(self, rating1, rating2):
        sum_xy = 0
        sum_x = 0
        sum_y = 0
        sum_x2 = 0
        sum_y2 = 0
        n = 0
        for key in rating1:
            if key in rating2:
                n += 1
                x = rating1[key]
                y = rating2[key]
                sum_xy += x * y
                sum_x += x
                sum_y += y
                sum_x2 += pow(x, 2)
                sum_y2 += pow(y, 2)
        if n == 0:
            return 0
        # now compute denominator
        denominator = (sqrt(sum_x2 - pow(sum_x, 2) / n)
                       * sqrt(sum_y2 - pow(sum_y, 2) / n))
        if denominator == 0:
            return 0
        else:
            return (sum_xy - (sum_x * sum_y) / n) / denominator


    def computeNearestNeighbor(self, username):
        """creates a sorted list of users based on their distance to
        username"""
        distances = []
        for instance in self.data:
            if instance != username:
                distance = self.fn(self.data[username],
                                   self.data[instance])
                distances.append((instance, distance))
        # sort based on distance -- closest first
        distances.sort(key=lambda artistTuple: artistTuple[1],
                       reverse=True)
        return distances

    def recommend(self, user):
       """Give list of recommendations"""
       recommendations = {}
       # first get list of users  ordered by nearness
       nearest = self.computeNearestNeighbor(user)
       #
       # now get the ratings for the user
       #
       userRatings = self.data[user]
       #
       # determine the total distance
       totalDistance = 0.0
       for i in range(self.k):
          totalDistance += nearest[i][1]
       # now iterate through the k nearest neighbors
       # accumulating their ratings
       for i in range(self.k):
          # compute slice of pie 
          weight = nearest[i][1] / totalDistance
          # get the name of the person
          name = nearest[i][0]
          # get the ratings for this person
          neighborRatings = self.data[name]
          # get the name of the person
          # now find bands neighbor rated that user didn't
          for artist in neighborRatings:
             if not artist in userRatings:
                if artist not in recommendations:
                   recommendations[artist] = (neighborRatings[artist]
                                              * weight)
                else:
                   recommendations[artist] = (recommendations[artist]
                                              + neighborRatings[artist]
                                              * weight)
       # now make list from dictionary
       recommendations = list(recommendations.items())
       recommendations = [(self.convertProductID2name(k), v)
                          for (k, v) in recommendations]
       # finally sort and return
       recommendations.sort(key=lambda artistTuple: artistTuple[1],
                            reverse = True)
       # Return the first n items
       return recommendations[:self.n]


if __name__ == "__main__":
     r = recommender(users)

     print r.recommend('Jordyn')
     print r.recommend('Hailey')

0x4:item-based collaborative filtering - 基于項目的推薦:聽群眾的,沒錯!

1. 算法主要思想 

基于用戶對物品的喜好找到相似的物品,然后根據用戶的歷史喜好,推薦相似的物品給目標用戶。

上圖中,物品A被用戶A/B/C喜歡,物品C被用戶A/B喜歡。

從這些用戶的歷史喜好可以分析出物品A和物品C是比較類似的,喜歡物品A的人都喜歡物品C,基于這個數據可以推斷用戶C很有可能也喜歡物品C,所以系統會將物品C推薦給用戶C。

2. 算法簡要實現思路

將所有用戶對某一個物品的喜好作為一個向量來計算物品之間的相似度,得到物品的相似物品后,根據用戶歷史的喜好預測目標用戶還沒有涉及的物品,計算得到一個排序的物品列表作為推薦。 

0x5:相似度計算 - Similarity Metrics Computing

這里討論的相似度計算方法不僅用于協同過濾推薦算法,對文章之后討論的其他算法(例如內容過濾推薦)也同樣有用,但是為了行文的連貫性,筆者將這部分內容放置在這里。

首先需要明白的是,不管是基于協同過濾,還是內容過濾,我們的計算對象本質上都是向量(Vector),相似度計算算法是一個輔助工具算法,目標是度量不同向量之間的相關聯程度。

下面介紹三種主流的相似度計算策略,它們是兩種非常不同的思路,但同時也有一些內在的底層聯系,

1. 基于向量距離的相似度計算 - 明氏距離(Minkowski Distance)

基于向量距離的相似度計算算法思路非常簡單明了,通過計算兩個向量的數值距離,距離越近相似度越大。

一般地,我們用明氏距離(Minkowski Distance)來度量兩個向量間的距離,

其中,

  • r=1時,上式就是曼哈頓距離
  • r=2時,上式就是歐氏距離,歐幾里德距離就是平面上兩個點的距離
  • r=∞時,上式就是上確界距離(supermum distance)

值得注意的是,歐幾里德距離計算相似度是所有相似度計算里面最簡單、最易理解的方法。它以經過人們一致評價的物品為坐標軸,然后將參與評價的人繪制到坐標系上,并計算他們彼此之間的直線距離。

2. 基于余弦相似度的相似度計算 - Cosine 相似度(Cosine Similarity) 

在余弦相似度公式中,向量的等比例放縮是不影響最終公式結果的,余弦相似度公式比較的是不同向量之間的夾角。公式如下,

相比距離度量,余弦相似度更加注重兩個向量在方向上的差異,而非距離或長度上。

從圖上可以看出

  • 距離度量衡量的是空間各點間的絕對距離,跟各個點所在的位置坐標(即個體特征維度的數值)直接相關;
  • 而余弦相似度衡量的是空間向量的夾角,更加的是體現在方向上的差異,而不是位置。如果保持A點的位置不變,B點朝原方向遠離坐標軸原點,那么這個時候余弦相似度cosθ是保持不變的,因為夾角不變,而A、B兩點的距離顯然在發生改變,這就是歐氏距離和余弦相似度的不同之處

根據歐氏距離和余弦相似度各自的計算方式和衡量特征,分別適用于不同的數據分析模型:

  • 歐氏距離能夠體現個體數值特征的絕對差異,所以更多的用于需要從維度的數值大小中體現差異的分析,如使用用戶行為指標分析用戶價值的相似度或差異
  • 而余弦相似度更多的是從方向上區分差異,而對絕對的數值不敏感,更多的用于使用用戶對內容評分來區分用戶興趣的相似度和差異,同時修正了用戶間可能存在的度量標準不統一的問題(因為余弦相似度對絕對數值不敏感)

3. 基于相關性的相似度計算 - 皮爾森相關系數(Pearson Correlation Coefficient)

皮爾森相關系數一般用于計算兩個變量間聯系的緊密程度(相關度),它的取值在 [-1,+1] 之間。

從公式可以看到,皮爾遜系數就是相對兩個向量都先進行中心化(centered)后再計算余弦相似度。

中心化的意思是,對每個向量,先計算所有元素的平均值avg,然后向量中每個維度的值都減去這個avg,得到的這個向量叫做被中心化的向量。機器學習,數據挖掘要計算向量余弦相似度的時候,由于向量經常在某個維度上有數據的缺失,所以預處理階段都要對所有維度的數值進行中心化處理。

從統一理論的角度來說,皮爾遜相關系數是余弦相似度在維度值缺失情況下的一種改進

更進一步的,我們從數理統計中的協方差的角度來理解一下皮爾森相關系數。

協方差(Covariance)是一個反映兩個隨機變量相關程度的指標,如果一個變量跟隨著另一個變量同時變大或者變小,那么這兩個變量的協方差就是正值,反之相反,公式如下:

而Pearson相關系數公式如下:

由公式可知,Pearson相關系數是用協方差除以兩個變量的標準差得到的,雖然協方差能反映兩個隨機變量的相關程度(協方差大于0的時候表示兩者正相關,小于0的時候表示兩者負相關),但是協方差值的大小并不能很好地度量兩個隨機變量的關聯程度。

例如,現在二維空間中分布著一些數據,我們想知道數據點坐標X軸和Y軸的相關程度。如果X與Y的數據分布的比較離散,這樣會導致求出的協方差值較大,用這個值來度量相關程度是不合理的,如下圖:

為了更好的度量兩個隨機變量的相關程度,Pearson相關系數在協方差的基礎上除以了兩個隨機變量的標準差,這樣就綜合了因為隨機變量自身的方差增加導致的協方差增加。

容易得出,pearson是一個介于-1和1之間的值,

  • 當兩個變量的線性關系增強時,相關系數趨于1或-1
    • 當一個變量增大,另一個變量也增大時,表明它們之間是正相關的,相關系數大于0
    • 如果一個變量增大,另一個變量卻減小,表明它們之間是負相關的,相關系數小于0
  • 如果相關系數等于0,表明它們之間不存在線性相關關系

4. 三種相似度計算算法的區別   

上述三種相似度計算的區別在于,定義“什么叫相似”的標準不同,其實也可以理解為在量綱是否一致的不同環境下,采取的不同相似度評價策略,

  • 量綱一致環境:不同用戶對好壞事物的評價是在均值區間內的,舉個例子,在一個成熟的觀影市場中,觀眾對《復聯》系列的評價總體在9.5~9.8分之間,而對《無極》的評價總體在5~6分之間,這種影評環境就叫量綱一致環境,指的是市場中每個評價個體(用戶)都是一個客觀實體。
  • 量綱不一致環境:還是舉一個例子說明,在某小鎮中,居民對電影的普遍接受度較低,因此他們對所有電影的評價都普遍降低,而不管該電影實際的好壞,在這種情況下,該小鎮居民的評價數據和大城市中主流觀影市場的評價數據之間,就存在量綱不一致問題。 

Relevant Link:   

http://www.guidetodatamining.com/
https://www.autumnlj.com/charlesblc/p/8336765.html
https://my.oschina.net/dillan/blog/164263
http://www.woshipm.com/pd/2384774.html
https://www.jishuwen.com/d/2h2A 
https://www.zhihu.com/question/19971859

 

3. 內容過濾推薦算法(Content-Based Recommendations)

0x1:算法主要思想介紹

所謂基于內容的推薦算法(Content-Based Recommendations)是基于標的物相關信息(通過特征工程方式得到特征向量)來構建推薦算法模型,為用戶提供推薦服務。

這里的標的物相關信息可以是對標的物文字描述的metadata信息、標簽、用戶評論、人工標注的信息等。更一般地說,我們可以基于特征工程技術,對標的物進行特征提取,得到降維后的特征向量后,使用聚類和近似相似性搜索等機器學習手段進行關聯性推薦。這其實就是傳統機器學習中的“feature engineering+machine learning”的流程。

廣義的標的物相關信息不限于文本信息,圖片、語音、視頻等都可以作為內容推薦的信息來源,只不過這類信息處理成本較大,處理的時間及存儲成本也相對更高。

蘋果、香蕉;和櫻桃西瓜都是水果,物品本身在特征維度上有相似度

從上圖中可以看出,內容過濾推薦算法的推薦策略和推薦效果,很大程度取決于對標的物的特征工程方案。現在主流的特征提取策略有兩類,分別是:

0x2:基于內容的推薦算法的優勢與缺點

1. 優點

  • 符合用戶的需求愛好:該算法完全基于用戶的歷史興趣來為用戶推薦,推薦的標的物也是跟用戶歷史興趣相似的,所以推薦的內容一定是符合用戶的口味的。
  • 直觀易懂,可解釋性強:基于內容的推薦算法基于用戶的興趣為用戶推薦跟他興趣相似的標的物,原理簡單,容易理解。同時,由于是基于用戶歷史興趣推薦跟興趣相似的標的物,用戶也非常容易接受和認可。
  • 解決冷啟動問題:所謂冷啟動問題是指該用戶只有很少的歷史購買行為,或其購買的商品是一個很少被其他用戶購買的商品,這種情況會影響協同過濾的效果。但是基于內容推薦沒有這個問題,只要用戶有一個操作行為,就可以基于內容為用戶做推薦,不依賴其他用戶行為。同時對于新入庫的標的物,只要它具備metadata信息等標的物相關信息,就可以利用基于內容的推薦算法將它分發出去。因此,對于強依賴于UGC內容的產品(如抖音、快手等),基于內容的推薦可以更好地對標的物提供方進行流量扶持。
  • 算法實現相對簡單:基于內容的推薦可以基于標簽維度做推薦,也可以將標的物嵌入向量空間中,利用相似度做推薦,不管哪種方式,算法實現較簡單,有現成的開源的算法庫供開發者使用,非常容易落地到真實的業務場景中。
  • 對于小眾領域也能有比較好的推薦效果(本質就是冷啟動問題):對于冷門小眾的標的物,用戶行為少,協同過濾等方法很難將這類內容分發出去,而基于內容的算法受到這種情況的影響相對較小。
  • 非常適合標的物快速增長的有時效性要求的產品(本質就是冷啟動問題):對于標的物增長很快的產品,如今日頭條等青青青在線日本無碼不卡資訊類APP,基本每天都有幾十萬甚至更多的標的物入庫,另外標的物時效性也很強。新標的物一般用戶行為少,協同過濾等算法很難將這些大量實時產生的新標的物推薦出去,這時就可以采用基于內容的推薦算法更好地分發這些內容。

2. 缺點 

  • 推薦范圍狹窄,新穎性不強:由于該類算法只依賴于單個用戶的行為為用戶做推薦,推薦的結果會聚集在用戶過去感興趣的標的物類別上,如果用戶不主動關注其他類型的標的物,很難為用戶推薦多樣性的結果,也無法挖掘用戶深層次的潛在興趣。特別是對于新用戶,只有少量的行為,為用戶推薦的標的物較單一。
  • 需要知道相關的內容信息且處理起來較難(依賴特征工程):內容信息主要是文本、視頻、音頻,處理起來費力,相對難度較大,依賴領域知識。同時這些信息更容易有更大概率含有噪音,增加了處理難度。另外,對內容理解的全面性、完整性及準確性會影響推薦的效果。
  • 較難將長尾標的物分發出去:基于內容的推薦需要用戶對標的物有操作行為,長尾標的物一般操作行為非常少,只有很少用戶操作,甚至沒有用戶操作。由于基于內容的推薦只利用單個用戶行為做推薦,所以更難將它分發給更多的用戶。
  • 推薦精準度不太高:一個很簡單的道理是,你花9000多買了一個iphone后,不太可能短期內再買一個iphone或其他手機,相反更有可能需要買一個手機殼。

Relevant Link:  

https://zhuanlan.zhihu.com/p/72860695  

 

4. 混合推薦算法

目前研究和應用最多的是內容推薦和協同過濾推薦的組合。最簡單的做法就是分別用基于內容的方法和協同過濾推薦方法去產生一個推薦預測結果,然后用線性組合、加權組合等方式綜合其結果。本章我們分別討論。

0x1:加權式:加權多種推薦技術結果

0x2:切換式:根據問題背景和實際情況或要求決定變換采用不同的推薦技術

0x3:混雜式:同時采用多種推薦技術給出多種推薦結果為用戶提供參考

0x4:特征組合:組合來自不同推薦數據源的特征被另一種推薦算法所采用

0x5:層疊式:先用一種推薦技術產生一種粗糙的推薦結果,第二種推薦技術在此推薦結果的基礎上進一步作出更精確的推薦 

0x6:特征補充:一種技術產生附加的特征信息嵌入到另一種推薦技術的特征輸入中

0x7:級聯式:用一種推薦方法產生的模型作為另一種推薦方法的輸入

Relevant Link:  

https://core.ac.uk/download/pdf/41441354.pdf 
https://lianhaimiao.github.io/2018/01/06/%E6%8E%A8%E8%8D%90%E7%B3%BB%E7%BB%9F%E6%96%B9%E6%B3%95%E5%B0%8F%E7%BB%93-%E4%B8%8A/ 

  

5. 從推薦算法中得到的思考

在調研和學習推薦算法的相關資料的時候,筆者腦子里萌生了一個想法,網絡安全領域中,對未來可能發生的攻擊行為是否可以做到提前預測呢?

形成這種想法的基本假設是這樣的:

每次攻擊入侵事件都可以分成如下幾個動作

  • “前序動作”:所謂的前序動作,一般指遭受到一些端口掃描、暴力破解、漏洞存在性和試探性poc掃描等行為
  • “IOC動作”:即下載/啟動惡意程序
  • “后序動作”:對外蠕蟲攻擊、持久化等操作

如果我們收集所有歷史上曾經遭受過入侵的系統日志,將其組織成一個“machine-behavior”二維矩陣。通過itemCF挖掘會發現,“前序動作”,“IOC動作”,“后序動作”這三類行為日志在所有日志中是彼此接近的,換句話說,它們是彼此之間滿足推薦條件的,當一個機器上發生了“前序動作”,“IOC動作”,或“后序動作”中的任何一個時候,就極有可能發生其他的后續異常事件。

基于這個想法,我們就可以得到一個“未來風險預測器”,當一臺主機已經發生了一些“前序動作”的時候,我們可以根據itemCF的推薦結果,預測該機器未來可能還會遭受到哪些惡意事件。

具體來說,我們需要構建一個“machine-behavior”二維矩陣,行向量為每個機器在某個時間段內的每一條進程日志,列向量為特定進程出現的次數。

檢測的思路是,如果入侵機器在某時間段內出現了“wget/curl/su/kill”等指令,我們就可以基于itemCF進行入侵預測,例如當出現一部分進程特征的時候,根據itemCF的推薦結果,預測該機器在未來可能發生的后續惡意行為,提前預警。

 

posted @ 2019-10-11 20:55 鄭瀚Andrew.Hann 閱讀(...) 評論(...) 編輯 收藏