機器學習經典算法之Apriori

1、 搞懂關聯規則中的幾個概念html

關聯規則這個概念,最先是由 Agrawal 等人在 1993 年提出的。在 1994 年 Agrawal 等人又提出了基於關聯規則的 Apriori 算法,至今 Apriori 還是關聯規則挖掘的重要算法。python

/*請尊重做者勞動成果,轉載請標明原文連接:*/web

/* https://www.cnblogs.com/jpcflyer/p/11146587.html * /算法

關聯規則挖掘可讓咱們從數據集中發現項與項(item 與 item)之間的關係,它在咱們的生活中有不少應用場景,「購物籃分析」就是一個常見的場景,這個場景能夠從消費者交易記錄中發掘商品與商品之間的關聯關係,進而經過商品捆綁銷售或者相關推薦的方式帶來更多的銷售量。因此說,關聯規則挖掘是個很是有用的技術。數組

我舉一個超市購物的例子,下面是幾名客戶購買的商品列表:瀏覽器

 

什麼是支持度呢?app

支持度是個百分比,它指的是某個商品組合出現的次數與總次數之間的比例。支持度越高,表明這個組合出現的頻率越大。ide

在這個例子中,咱們能看到「牛奶」出現了 4 次,那麼這 5 筆訂單中「牛奶」的支持度就是 4/5=0.8。函數

一樣「牛奶 + 麪包」出現了 3 次,那麼這 5 筆訂單中「牛奶 + 麪包」的支持度就是 3/5=0.6。工具

 

什麼是置信度呢?

它指的就是當你購買了商品 A,會有多大的機率購買商品 B,在上面這個例子中:

置信度(牛奶→啤酒)=2/4=0.5,表明若是你購買了牛奶,有多大的機率會購買啤酒?

置信度(啤酒→牛奶)=2/3=0.67,表明若是你購買了啤酒,有多大的機率會購買牛奶?

咱們能看到,在 4 次購買了牛奶的狀況下,有 2 次購買了啤酒,因此置信度 (牛奶→啤酒)=0.5,而在 3 次購買啤酒的狀況下,有 2 次購買了牛奶,因此置信度(啤酒→牛奶)=0.67。

因此說置信度是個條件概念,就是說在 A 發生的狀況下,B 發生的機率是多少。

 

什麼是提高度呢?

咱們在作商品推薦的時候,重點考慮的是提高度,由於提高度表明的是「商品 A 的出現,對商品 B 的出現機率提高的」程度。

仍是看上面的例子,若是咱們單純看置信度 (可樂→尿布)=1,也就是說可樂出現的時候,用戶都會購買尿布,那麼當用戶購買可樂的時候,咱們就須要推薦尿布麼?

實際上,就算用戶不購買可樂,也會直接購買尿布的,因此用戶是否購買可樂,對尿布的提高做用並不大。咱們能夠用下面的公式來計算商品 A 對商品 B 的提高度:

提高度 (A→B)= 置信度 (A→B)/ 支持度 (B)

這個公式是用來衡量 A 出現的狀況下,是否會對 B 出現的機率有所提高。

因此提高度有三種可能:

提高度 (A→B)>1:表明有提高;

提高度 (A→B)=1:表明有沒有提高,也沒有降低;

提高度 (A→B)<1:表明有降低。

 

2、 Apriori 的工做原理

明白了關聯規則中支持度、置信度和提高度這幾個重要概念,咱們來看下 Apriori 算法是如何工做的。

首先咱們把上面案例中的商品用 ID 來表明,牛奶、麪包、尿布、可樂、啤酒、雞蛋的商品 ID 分別設置爲 1-6,上面的數據表能夠變爲:

Apriori 算法其實就是查找頻繁項集 (frequent itemset) 的過程,因此首先咱們須要定義什麼是頻繁項集。

頻繁項集就是支持度大於等於最小支持度 (Min Support) 閾值的項集,因此小於最小值支持度的項目就是非頻繁項集,而大於等於最小支持度的項集就是頻繁項集。

項集這個概念,英文叫作 itemset,它能夠是單個的商品,也能夠是商品的組合。咱們再來看下這個例子,假設我隨機指定最小支持度是 50%,也就是 0.5。

 

咱們來看下 Apriori 算法是如何運算的。

首先,咱們先計算單個商品的支持度,也就是獲得 K=1 項的支持度:

由於最小支持度是 0.5,因此你能看到商品 四、6 是不符合最小支持度的,不屬於頻繁項集,因而通過篩選商品的頻繁項集就變成:

在這個基礎上,咱們將商品兩兩組合,獲得 k=2 項的支持度:

咱們再篩掉小於最小值支持度的商品組合,能夠獲得:

咱們再將商品進行 K=3 項的商品組合,能夠獲得:

再篩掉小於最小值支持度的商品組合,能夠獲得:

到這裏,你已經和我模擬了一遍整個 Apriori 算法的流程,下面我來給你總結下 Apriori 算法的遞歸流程:

K=1,計算 K 項集的支持度;

篩選掉小於最小支持度的項集;

若是項集爲空,則對應 K-1 項集的結果爲最終結果。

不然 K=K+1,重複 1-3 步。

 

3、 Apriori 的改進算法:FP-Growth 算法

能看到 Apriori 在計算的過程當中有如下幾個缺點:

可能產生大量的候選集。由於採用排列組合的方式,把可能的項集都組合出來了;

每次計算都須要從新掃描數據集,來計算每一個項集的支持度。

 

因此 Apriori 算法會浪費不少計算空間和計算時間,爲此人們提出了 FP-Growth 算法,它的特色是:

建立了一棵 FP 樹來存儲頻繁項集。在建立前對不知足最小支持度的項進行刪除,減小了存儲空間。我稍後會講解如何構造一棵 FP 樹;

整個生成過程只遍歷數據集 2 次,大大減小了計算量。

因此在實際工做中,咱們經常使用 FP-Growth 來作頻繁項集的挖掘,下面我給你簡述下 FP-Growth 的原理。

 

1. 建立項頭表(item header table)

建立項頭表的做用是爲 FP 構建及頻繁項集挖掘提供索引。

這一步的流程是先掃描一遍數據集,對於知足最小支持度的單個項(K=1 項集)按照支持度從高到低進行排序,這個過程當中刪除了不知足最小支持度的項。

項頭表包括了項目、支持度,以及該項在 FP 樹中的鏈表。初始的時候鏈表爲空。

 

2. 構造 FP 樹

FP 樹的根節點記爲 NULL 節點。

整個流程是須要再次掃描數據集,對於每一條數據,按照支持度從高到低的順序進行建立節點(也就是第一步中項頭表中的排序結果),節點若是存在就將計數 count+1,若是不存在就進行建立。同時在建立的過程當中,須要更新項頭表的鏈表。

 

3. 經過 FP 樹挖掘頻繁項集

到這裏,咱們就獲得了一個存儲頻繁項集的 FP 樹,以及一個項頭表。咱們能夠經過項頭表來挖掘出每一個頻繁項集。

具體的操做會用到一個概念,叫「條件模式基」,它指的是以要挖掘的節點爲葉子節點,自底向上求出 FP 子樹,而後將 FP 子樹的祖先節點設置爲葉子節點之和。

我以「啤酒」的節點爲例,從 FP 樹中能夠獲得一棵 FP 子樹,將祖先節點的支持度記爲葉子節點之和,獲得:

你能看出來,相比於原來的 FP 樹,尿布和牛奶的頻繁項集數減小了。這是由於咱們求得的是以「啤酒」爲節點的 FP 子樹,也就是說,在頻繁項集中必定要含有「啤酒」這個項。你能夠再看下原始的數據,其中訂單 1{牛奶、麪包、尿布}和訂單 5{牛奶、麪包、尿布、可樂}並不存在「啤酒」這個項,因此針對訂單 1,尿布→牛奶→麪包這個項集就會從 FP 樹中去掉,針對訂單 5 也包括了尿布→牛奶→麪包這個項集也會從 FP 樹中去掉,因此你能看到以「啤酒」爲節點的 FP 子樹,尿布、牛奶、麪包項集上的計數比原來少了 2。

條件模式基不包括「啤酒」節點,並且祖先節點若是小於最小支持度就會被剪枝,因此「啤酒」的條件模式基爲空。

同理,咱們能夠求得「麪包」的條件模式基爲:

因此能夠求得麪包的頻繁項集爲{尿布,麪包},{尿布,牛奶,麪包}。一樣,咱們還能夠求得牛奶,尿布的頻繁項集,這裏就再也不計算展現。

 

4、 如何使用 Apriori 工具包

Apriori 雖然是十大算法之一,不過在 sklearn 工具包中並無它,也沒有 FP-Growth 算法。這裏教你個方法,來選擇 Python 中可使用的工具包,你能夠經過 https://pypi.org/ 搜索工具包。

這個網站提供的工具包都是 Python 語言的,你能找到 8 個 Python 語言的 Apriori 工具包,具體選擇哪一個呢?建議你使用第二個工具包,即 efficient-apriori。後面我會講到爲何推薦這個工具包。

首先你須要經過 pip install efficient-apriori 安裝這個工具包。

而後看下如何使用它,核心的代碼就是這一行:

1 itemsets, rules = apriori(data, min_support,  min_confidence)

其中 data 是咱們要提供的數據集,它是一個 list 數組類型。min_support 參數爲最小支持度,在 efficient-apriori 工具包中用 0 到 1 的數值表明百分比,好比 0.5 表明最小支持度爲 50%。min_confidence 是最小置信度,數值也表明百分比,好比 1 表明 100%。

 

接下來咱們用這個工具包,跑一下前面講到的超市購物的例子。下面是客戶購買的商品列表:

具體實現的代碼以下:

 1 from efficient_apriori import apriori
 2 # 設置數據集
 3 data = [('牛奶','麪包','尿布'),
 4            ('可樂','麪包', '尿布', '啤酒'),
 5            ('牛奶','尿布', '啤酒', '雞蛋'),
 6            ('麪包', '牛奶', '尿布', '啤酒'),
 7            ('麪包', '牛奶', '尿布', '可樂')]
 8 # 挖掘頻繁項集和頻繁規則
 9 itemsets, rules = apriori(data, min_support=0.5,  min_confidence=1)
10 print(itemsets)
11 print(rules)

運行結果:

1 {1: {('啤酒',): 3, ('尿布',): 5, ('牛奶',): 4, ('麪包',): 4}, 2: {('啤酒', '尿布'): 3, ('尿布', '牛奶'): 4, ('尿布', '麪包'): 4, ('牛奶', '麪包'): 3}, 3: {('尿布', '牛奶', '麪包'): 3}}
2 [{啤酒} -> {尿布}, {牛奶} -> {尿布}, {麪包} -> {尿布}, {牛奶, 麪包} -> {尿布}]

你能從代碼中看出來,data 是個 List 數組類型,其中每一個值均可以是一個集合。實際上你也能夠把 data 數組中的每一個值設置爲 List 數組類型,好比:

1 data = [['牛奶','麪包','尿布'],
2            ['可樂','麪包', '尿布', '啤酒'],
3            ['牛奶','尿布', '啤酒', '雞蛋'],
4            ['麪包', '牛奶', '尿布', '啤酒'],
5            ['麪包', '牛奶', '尿布', '可樂']]

二者的運行結果是同樣的,efficient-apriori 工具包把每一條數據集裏的項式都放到了一個集合中進行運算,並無考慮它們之間的前後順序。由於實際狀況下,同一個購物籃中的物品也不須要考慮購買的前後順序。

而其餘的 Apriori 算法可能會由於考慮了前後順序,出現計算頻繁項集結果不對的狀況。因此這裏採用的是 efficient-apriori 這個工具包。

 

5、 挖掘導演是如何選擇演員的

在實際工做中,數據集是須要本身來準備的,好比咱們要挖掘導演是如何選擇演員的數據狀況,可是並無公開的數據集能夠直接使用。所以咱們須要使用以前講到的 Python 爬蟲進行數據採集。

不一樣導演選擇演員的規則是不一樣的,所以咱們須要先指定導演。數據源咱們選用豆瓣電影。

先來梳理下采集的工做流程。

 

首先咱們先在 https://movie.douban.com 搜索框中輸入導演姓名,好比「甯浩」。

頁面會呈現出來導演以前的全部電影,而後對頁面進行觀察,你能觀察到如下幾個現象:

頁面默認是 15 條數據反饋,第一頁會返回 16 條。由於第一條數據實際上這個導演的概覽,你能夠理解爲是一條廣告的插入,下面纔是真正的返回結果。

每條數據的最後一行是電影的演出人員的信息,第一我的員是導演,其他爲演員姓名。姓名之間用「/」分割。

有了這些觀察以後,咱們就能夠編寫抓取程序了。在代碼講解中你能看出這兩點觀察的做用。抓取程序的目的是爲了生成甯浩導演(你也能夠抓取其餘導演)的數據集,結果會保存在 csv 文件中。完整的抓取代碼以下:

 1 # -*- coding: utf-8 -*-
 2 # 下載某個導演的電影數據集
 3 from efficient_apriori import apriori
 4 from lxml import etree
 5 import time
 6 from selenium import webdriver
 7 import csv
 8 driver = webdriver.Chrome()
 9 # 設置想要下載的導演 數據集
10 director = u'甯浩'
11 # 寫 CSV 文件
12 file_name = './' + director + '.csv'
13 base_url = 'https://movie.douban.com/subject_search?search_text='+director+'&cat=1002&start='
14 out = open(file_name,'w', newline='', encoding='utf-8-sig')
15 csv_write = csv.writer(out, dialect='excel')
16 flags=[]
17 # 下載指定頁面的數據
18 def download(request_url):
19     driver.get(request_url)
20     time.sleep(1)
21     html = driver.find_element_by_xpath("//*").get_attribute("outerHTML")
22     html = etree.HTML(html)
23     # 設置電影名稱,導演演員 的 XPATH
24     movie_lists = html.xpath("/html/body/div[@id='wrapper']/div[@id='root']/div[1]//div[@class='item-root']/div[@class='detail']/div[@class='title']/a[@class='title-text']")
25     name_lists = html.xpath("/html/body/div[@id='wrapper']/div[@id='root']/div[1]//div[@class='item-root']/div[@class='detail']/div[@class='meta abstract_2']")
26     # 獲取返回的數據個數
27     num = len(movie_lists)
28     if num > 15: # 第一頁會有 16 條數據
29         # 默認第一個不是,因此須要去掉
30         movie_lists = movie_lists[1:]
31         name_lists = name_lists[1:]
32     for (movie, name_list) in zip(movie_lists, name_lists):
33         # 會存在數據爲空的狀況
34         if name_list.text is None:
35             continue
36         # 顯示下演員名稱
37         print(name_list.text)
38         names = name_list.text.split('/')
39         # 判斷導演是否爲指定的 director
40         if names[0].strip() == director and movie.text not in flags:
41             # 將第一個字段設置爲電影名稱
42             names[0] = movie.text
43             flags.append(movie.text)
44             csv_write.writerow(names)
45     print('OK') # 表明這頁數據下載成功
46     print(num)
47     if num >= 14: # 有可能一頁會有 14 個電影
48         # 繼續下一頁
49         return True
50     else:
51         # 沒有下一頁
52         return False
53 
54 # 開始的 ID 爲 0,每頁增長 15
55 start = 0
56 while start<10000: # 最多抽取 1 萬部電影
57     request_url = base_url + str(start)
58     # 下載數據,並返回是否有下一頁
59     flag = download(request_url)
60     if flag:
61         start = start + 15
62     else:
63         break
64 out.close()
65 print('finished')

代碼中涉及到了幾個模塊,我簡單講解下這幾個模塊。

在引用包這一段,咱們使用 csv 工具包讀寫 CSV 文件,用 efficient_apriori 完成 Apriori 算法,用 lxml 進行 XPath 解析,time 工具包可讓咱們在模擬後有個適當停留,代碼中我設置爲 1 秒鐘,等 HTML 數據徹底返回後再進行 HTML 內容的獲取。使用 selenium 的 webdriver 來模擬瀏覽器的行爲。

在讀寫文件這一塊,咱們須要事先告訴 python 的 open 函數,文件的編碼是 utf-8-sig(對應代碼:encoding=‘utf-8-sig’),這是由於咱們會用到中文,爲了不編碼混亂。

編寫 download 函數,參數傳入咱們要採集的頁面地址(request_url)。針對返回的 HTML,咱們須要用到以前講到的 Chrome 瀏覽器的 XPath Helper 工具,來獲取電影名稱以及演出人員的 XPath。我用頁面返回的數據個數來判斷當前所處的頁面序號。若是數據個數 >15,也就是第一頁,第一頁的第一條數據是廣告,咱們須要忽略。若是數據個數 =15,表明是中間頁,須要點擊「下一頁」,也就是翻頁。若是數據個數 <15,表明最後一頁,沒有下一頁。

在程序主體部分,咱們設置 start 表明抓取的 ID,從 0 開始最多抓取 1 萬部電影的數據(一個導演不會超過 1 萬部電影),每次翻頁 start 自動增長 15,直到 flag=False 爲止,也就是不存在下一頁的狀況。

你能夠模擬下抓取的流程,得到指定導演的數據,好比我上面抓取的甯浩的數據。這裏須要注意的是,豆瓣的電影數據多是不全的,但基本上夠咱們用。

有了數據以後,咱們就能夠用 Apriori 算法來挖掘頻繁項集和關聯規則,代碼以下:

 1 # -*- coding: utf-8 -*-
 2 from efficient_apriori import apriori
 3 import csv
 4 director = u'甯浩'
 5 file_name = './'+director+'.csv'
 6 lists = csv.reader(open(file_name, 'r', encoding='utf-8-sig'))
 7 # 數據加載
 8 data = []
 9 for names in lists:
10      name_new = []
11      for name in names:
12            # 去掉演員數據中的空格
13            name_new.append(name.strip())
14      data.append(name_new[1:])
15 # 挖掘頻繁項集和關聯規則
16 itemsets, rules = apriori(data, min_support=0.5,  min_confidence=1)
17 print(itemsets)
18 print(rules)

代碼中使用的 apriori 方法和開頭中用 Apriori 獲取購物籃規律的方法相似,好比代碼中都設定了最小支持度和最小置信係數,這樣咱們能夠找到支持度大於 50%,置信係數爲 1 的頻繁項集和關聯規則。

這是最後的運行結果:

1 {1: {('徐崢',): 5, ('黃渤',): 6}, 2: {('徐崢', '黃渤'): 5}}
2 [{徐崢} -> {黃渤}]

 你能看出來,甯浩導演喜歡用徐崢和黃渤,而且有徐崢的狀況下,通常都會用黃渤。你也能夠用上面的代碼來挖掘下其餘導演選擇演員的規律。

相關文章
相關標籤/搜索