1. 程式人生 > >推薦演算法初探

推薦演算法初探

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.cnblogs.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的推薦結果,預測該機器在未來可能發生的後續惡意行為,提前預警。

&n