关联规则挖掘(南京大学复杂数据结构挖掘课程作业)
項目github地址:點我跳轉
1 實驗說明
1.1 任務
本次實驗的主要任務是對給定數據集進行關聯規則挖掘。通過改變support和confidence的值,比較Aprioir、FP-growth和最基本的窮舉法之間的差別,在進行比較時,主要著眼于產生的頻繁項集的數目,算法運行過程中的內存消耗和時間消耗。
1.2 數據集
本次實驗的數據集總共有兩個,分別是GroceryStore和UNIX_usage。
GroceryStore數據集包含某商店一個月內的交易記錄,數據存儲的格式為csv,每一行是一條交易信息,購買的商品名稱被“{”和“}”包圍著。此數據集總共有9835條交易記錄,涉及169種商品。
UNIX_usage數據集總共包含9個文件,每個文件都是真實用戶的命令行輸入歷史,其中第0個文件和第1個文件是同一用戶在不同平臺和項目中產生的命令行記錄,其余的7個文件都是來自不同用戶的命令行歷史記錄。出于保護隱私的考慮,移除了原始文件中的文件名、用戶名、文件結構等所有可能暴漏個人信息的部分,而是用<1>來代替此部分。在一個session的開始和結束會包含**SOF**和**EOF**,而之前在命令行中用空格分隔的參數部分,在此處會單獨占據一行。下面是兩個session的原始記錄。
# Start session 1 cd ~/private/docs ls -laF | more cat foo.txt bar.txt zorch.txt > somewhere exit # End session 1# Start session 2 cd ~/games/ xquake & fg vi scores.txt mailx john_doe@somewhere.com exit # End session 2在我們拿到的數據中,這兩個session用如下形式來表示。
**SOF** cd <1> # one "file name" argument ls -laF | more cat <3> # three "file" arguments > <1> exit **EOF** **SOF** cd <1> xquake & fg vi <1> mailx <1> exit **EOF**2 代碼實現
2.1 Apriori
你 Apriori算法的核心是對先驗知識的運用,具體來說是利用“頻繁項集的所有非空子集也一定是頻繁的”這條規則來減少搜索空間。我們利用kkk頻繁項集的集合LkL_kLk?生成k+1k+1k+1頻繁項集的集合Lk+1L_{k+1}Lk+1?,在生成的過程中,會有一個中間步驟,即k+1k+1k+1頻繁項集的候選集合Ck+1C_{k+1}Ck+1?。按照常規的做法,此時應該掃描一遍數據庫,判斷Ck+1C_{k+1}Ck+1?中的哪個項集是頻繁的,哪個是非頻繁的。但是我們現在考慮一下剛才提到的先驗知識,如果Ck+1C_{k+1}Ck+1?集合中的某個k+1k+1k+1項集ccc是頻繁的,那么它的所有子集中長度為kkk的集合,即kkk項集,也一定是頻繁的。所有的kkk頻繁項集都在LkL_kLk?中,因此可以通過判斷ccc的子集中的kkk項集是否出現在LkL_kLk?中來減少搜索空間。此算法的偽代碼如Algotithm1所示。
整個算法在實現的時候,由四個函數構成。apriori為主函數,find_frequent_1_itemset用于從原始數據庫中尋找1頻繁項集,apriori_gen用于從kkk頻繁項集中生成k+1k+1k+1候選頻繁項集,上文中提到過,生成的過程中會進行剪枝,而has_infrequent_subset函數就是用于判斷是否滿足剪枝條件的。對候選集初步剪枝之后,需要去掃描一遍數據庫,以找出那些真正的k+1k+1k+1項集,此操作由subset函數輔助完成。
對于計算出來的每個kkk頻繁項集,都是用list對象進行存儲。比如
[item1item_1item1?, item2item_2item2?, …, itemkitem_kitemk?, frequency]
此list對象共包含k+1k+1k+1個元素,前kkk個元素對應于kkk頻繁項集,最后一個元素是此kkk頻繁項集在數據庫中出現的次數。
2.2 Dummy
dummy方法是一種窮盡搜索的方法,雖然使用起來效率低,但是實現較為簡單。算法偽代碼如Algorithm2所示。
窮盡搜索,即嘗試每一種可能的項集組合。先由itemsetitemsetitemset生成所有只含一個元素的子集,即1頻繁候選項集,然后掃描數據庫,找出1頻繁項集;再由itemsetitemsetitemset生成所有只含兩個元素的子集,即2頻繁候選項集,然后掃描數據庫,找出2頻繁項集。依此類推,直到不能從kkk頻繁候選項集中找出kkk頻繁項集時結束。
2.3 FP-growth
FP-growth方法比Apriori方法在空間消耗和時間消耗上都有所提升,尤其是當設定的支持度比較低時,這種提升尤為明顯。
首先介紹一下FP-Tree的結構。FP-Tree是一棵索引樹,每個節點包含五個字段,name字段表示項目的名稱,frequency字段表示此項目的頻數,parent字段表示此項目的父節點索引,children字段表示此項目的子節點索引,因為一個項目可能有多個子節點,所以children字段為list類型,nextit字段表示下一個和此項目同名的節點索引號。
在FPTree這個類中,除了有nodes字段用來存儲樹的節點外,還有headtables字段用來存儲每個項目的頭節點索引。headtables是一個dic}類型,每個元素的key為項目名稱,value是此項目第一次在FPTree中出現時的索引。headtables相當于一個頭指針表,通過它,可以訪問到某個項目在FPTree中的所有節點。
FP-growth算法的偽代碼如Algorithm3所示。
按照FP-growth算法,首先需要掃描一遍數據庫,找出所有的頻繁1項集,然后再按照每個項集出現的次數逆序排序。之后再掃描一遍數據庫,此時需要對數據庫中的數據做一些修改,首先將那些沒有出現在頻繁1項集中的item從數據庫中移除,再按照頻繁1項集中每個item的順序,對數據庫中的每條transaction進行排序。上述操作完成之后,就可以用數據庫中的數據來構建FP-Tree了。構建樹的過程,其實就是把一個個結點插入到樹中的過程。為了表述方便,假設現在我們要把一條包含n個item的交易記錄ttt插入到FP樹中,因為ttt有n個item,所以相當于是要插入n個結點。為了找到插入的位置,我們首先需要對現在的FP樹進行遞歸搜索。搜索過程從根節點開始,即最初把根節點當作當前結點。如果根節點存在與t[0]t[0]t[0](交易記錄中的第一個item)相同的子節點n0n_0n0?,則遞歸的去搜索n0n_0n0?是否存在與t[1]t[1]t[1]相同的子節點,依此類推,直到找不到相同的子節點或ttt中的所有item在FP樹中都有相同的結點。找到插入的位置之后,插入操作相對比較簡單,只是需要注意一下,在插入結點的同時要構建headtablesheadtablesheadtables。
到目前為止,關于數據庫的FP樹已經構建出來了,接下來要做的是根據FP樹生成所有的頻繁項集。將FP樹中出現的所有的item按照頻數升序排序,然后遍歷這些item,遍歷過程由for循環完成,每當遍歷到一個item時,將當前的item加入到前綴序列中,然后將此前綴序列作為頻繁項集加入到總的頻繁項集集合LLL中。之后計算當前item所對應的條件模式基,若此條件模式基不為空,則由其繼續構造FP樹,然后遞歸的由此FP樹生成所有的頻繁項集。
利用FP-growth方法生成的頻繁項集和用Apriori方法生成的相比,在表示形式上是相同,都是用list存儲,前n-1個元素表示頻繁項集中的item,最后一個元素表示此頻繁項集的頻數。
2.4 Association Rule
按照上述的方法,已經把所有的頻繁項集都找出來了,接下來要做的就是從這些頻繁項集中挖掘關聯規則。對于一個頻繁項集fififi來說,我們需要計算出它所有的非空真子集,然后遍歷這些集合,對于fififi的某個真子集sss來說,如果滿足:
support(l)support(s)≥confidence\frac{support(l)}{support(s)} \ge confidencesupport(s)support(l)?≥confidence
則輸出規則s?(l?s)s \Rightarrow (l-s)s?(l?s)
3 實驗方案
在進行實驗設計時,主要從四個方面切入。
- 比較不同的數據集產生的頻繁項集的數量有什么特點。
- 對于相同的數據集,使用不同的頻繁項集生成算法,在時間消耗上各自的表現如何。
- 對于相同的數據集,使用不同的頻繁項集生成算法,在空間消耗上各自的表現如何。
- 尋找一些有趣的關聯規則,并對這些規則進行討論。
對于第一個方面,我們可以變換不同的support,然后用本次實驗提供的Groceries和UNIX_usage這兩個數據集進行實驗。
對于第二個方面,我們可以使用同一數據集(我選用的是Groceries),變換不同的support,分別使用dummy、Aprioir、FP-growth算法進行實驗,來查看不同算法上的時間消耗。
對于第三個方面,實驗方案與探究時間消耗的方案大致相同,只不過我們用mprof run命令代替python命令來記錄內存使用情況,當程序運行結束之后,會在當前目錄下生成mprofile_xxxxxxxxxxx.data文件,里面存儲了相同時間跨度的時間點上內存的使用情況,使用mprof list可以查看文件對應的索引(0,1,2 …),然后通過執行mprof plot file_index加載對應文件數據來繪制圖形
mprof plot 0對于第四個方面,我們可以變換不同的support和confidence,并且根據lift來查看生成的關聯規則。
4 實驗結果
4.1 頻繁項集的數量
在support設置為0.01的情況下,在兩個數據集上進行頻繁項集數量的比較。
對于Groceries數據集,不同的頻繁項集的數量如表1所示。
| k | 1 | 2 | 3 | 4 |
| 數量 | 88 | 213 | 32 | 0 |
對于UNIX_usage數據集,不同的頻繁項集的數量如表2所示。
| k | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 |
| 數量 | 61 | 329 | 781 | 1086 | 1004 | 635 | 289 | 91 | 19 | 2 | 0 |
Groceries數據集中共有9835條transaction,UNIX_usage數據集中共有5058條session。從表1和表2的對比中可以看出,UNIX_usage數據集最多可以產生10頻繁項集,而Groceries數據集最多只能產生3頻繁項集。從數量關系上來看,雖然UNIX_usage的數據要比Groceries少,但是前者的k頻繁項集的數目要遠多于后者。出現這種現象的原因,與兩個數據集本身的特性有關。UNIX_usage數據集是由真實用戶的命令行記錄構成的,我們在輸入cd <1>命令后,可能會習慣性的再輸入ls命令查看當前目錄的所有文件。
4.2 頻繁項集生成算法的時間性能
用Groceries數據進行實驗,來評判dummy、Apriori、FP-growth這三個頻繁項集生成算法的時間性能。進行實驗的設備為個人筆記本電腦,重要參數為:2 GHz 4 核 Intel Core i5 處理器,16 GB 3733 MHz LPDDR4X 內存。分別設置support為0.1,0.01和0.001,生成所有頻繁項集所用的時間如表3所示。
dummy算法進行的是窮盡搜索,假設數據集中總共有n種不同的item,那么總共有CnkC^k_nCnk?個候選k頻繁項集,因此要想找到所有的k頻繁項集,需要掃描數據庫CnkC^k_nCnk?遍。在進行實驗時,發現對于三種不同的support,dummy算法都無法在20分鐘內結束,因此表3中dummy算法的執行時間用-代替,表示未測得其執行時間。
Apriori算法相較于dummy算法在時間性能上有了很大的提升。相較于dummy的窮盡搜索,Apriori用了一些剪枝的方法,因此減少了搜索空間,提高了執行效率。根據先驗知識,“k頻繁項集的所有非空子集也是頻繁的”,我們可以由k頻繁項集生成候選(k+1)頻繁項集,相較于dummy算法會產生Cnk+1C^{k+1}_nCnk+1?個候選(k+1)頻繁項集,此方法會減少候選頻繁項集的數量。除此之外,我們還可以再做一次剪枝操作。如果一個(k+1)項集是頻繁的,那么它的所有k子集也一定是頻繁的,也就是說它的所有k子集一定在k頻繁項集的集合里面,假若存在某個k子集不滿足此條件,那么這個候選(k+1)頻繁項集也一定不是頻繁的。通過這兩步剪枝操作,可以盡可能的減少候選頻繁項集的數目,從而減少掃描數據庫的次數(掃描數據庫非常耗時)。
FP-growth算法和Apriori算法的思想不同,它通過“樹”這種數據結構,使得在生成頻繁項集的時候,只需要掃描兩次數據庫即可。第一次掃描數據庫,找出所有的1頻繁項集,第二次掃描數據庫,構建FP樹,之后的所有操作,都是針對FP樹進行的。
FP-growth算法克服了Apriori算法需要多次掃描數據庫的缺點,因此當生成的頻繁項集較多時,FP-growth的執行時間要遠少于Apriori。從表3中可以看出,當support為0.1時,FP-growth算法和Apriori算法的執行時間都是毫秒級的,且兩者的差異不明顯。當support減小為0.01時,生成的頻繁項集的數量會增加,此時就能看出FFP-growth算法的優勢了,它的時間消耗僅為Apriori算法的110\frac{1}{10}101?。當support繼續減小到0.001時,FP-growth算法的優勢表現的更為明顯,生成同樣的頻繁項集,FP-growth算法的時間消耗僅為Apriori算法的1100\frac{1}{100}1001?,時間性能上提升了兩個數量級。
當support減小時,生成的頻繁項集的數量會增多。對于Apriori算法來說,它的時間主要是消耗在頻繁掃描數據庫上,而對于FP-growth算法來說,它的時間主要是消耗在遞歸構建FP樹上。因為掃描數據庫是一件非常耗時的工作,所以FP-grow算法的執行時間要遠小于Apriori。
4.3 頻繁項集生成算法的空間性能
與時間性能的實驗類似,仍然使用Groceries數據集評估不同算法的空間性能。由之前的實驗已經知道,dummy算法即使在support較小的情況下也很難在短時間內終止,因此本實驗只考慮Apriori和FP-growth算法的空間性能。對于不同的support,兩種算法占用內存的峰值如表4所示。
從表4中可以看出,隨著support的減小,兩種算法所占用的內存都在增加。對于Apriori算法,當support減小時,需要更多的內存空間來存儲候選頻繁項集。對于FP-growth算法,當support減小時,需要更多的內存空間來遞歸生成FP樹。從實驗結果中可以發現一個有趣的現象,當support較大時,FP-growth算法的空間效率要低于Apriori,但是當support較小時,前者的空間效率卻又優于后者。
4.4 挖掘關聯規則
4.4.1 挖掘Groceries
在進行關聯規則挖掘時,首先需要選擇合適的support和confidence。若參數的值設的太小,則容易產生大量的關聯規則,從而很難從中發現有價值的規則。同理,若參數的值設的太大,則可能會忽略掉一些有意義的關聯規則。經過多次嘗試,發現對于Groceries數據,support=0.01,confidence=0.5可以產生15條關聯規則。所有的這些規則如表5所示。
在表5中,除了我們熟悉的support和confidence之外,還引入了一個新的指標:lift。lift指標的計算公式為:
lift(A→B)=p(B∣A)p(B)=confidencesupportlift(A \rightarrow B) = \frac{p(B|A)}{p(B)} = \frac{confidence}{support}lift(A→B)=p(B)p(B∣A)?=supportconfidence?
它的含義是:在已知AAA的前提下BBB發生的概率,和沒有先驗條件時BBB單獨發生的概率之間的相對關系。若lift<1,則說明AAA的發生對BBB的發生起著抑制作用;若lift=1,則說明AAA的發生對BBB是否發生沒有任何影響,即AAA和BBB相互獨立;若lift>1,則說明AAA的發生對BBB的發生起著促進作用。
從表5中可以看出,每一條強關聯規則的lift都大于1,且都大于20,這說明這些規則的關聯性較強。在后續進行商品布局時,可以參考上述規則設計每種商品的擺放位置。比如說將yogurt、curd和whole milk擺放在一起,根據過往的統計規律,當顧客購買了yogurt和curd之后,有58.24%的概率會購買whole milk。
觀察挖掘出來的所有關聯規則,可以發現規則的左部出現最頻繁的是root/other vegetables,規則的右部出現最頻繁的是whole milk,但是強關聯規則中卻沒有\par \noindent vegetables—>whole milk這條規則。另一個有趣的現象是,挖掘出來的這些規則都是來自3頻繁項集,由表1可知,當support為0.01時,有221個2頻繁項集,有32個3頻繁項集,也就是說2頻繁項集的數目要遠多于3頻繁項集,但是卻不能從2頻繁項集中挖掘出強關聯規則。
4.4.2 挖掘UNIX_usage
在1.2節中,我們對UNIX_usage數據集進行了介紹。每個session相當于Groceries數據集的一個transaction,但是每行數據卻不能等同于Groceries中的一個item。UNIX_usage數據集文件中的每行數據是一個unix命令或者命令的參數,如果我們直接對原始的數據集進行挖掘,那么可能會挖掘到一些含有參數的規則,而這種規則是沒有實際意義的,因為我們要找的是命令于命令之間的聯系。為了避免這種現象的發生,在數據挖掘之前,首先對原始數據集進行清洗,即把那些命令參數過濾掉,只保留真正的命令。
同Groceries數據集一樣,我們首先需要找到合適的support和confidence,經過多次嘗試,最終選定support=0.1,confidence=0.8。UNIX_usage數據集的關聯性比較高,即便選擇了限制性比較強的參數,得到的關聯規則的confidence仍較高。在此設定下,總共有11條關聯規則,如表6所示。
從表6中,我們可以看到一些符合事實的關聯規則,比如cd, vi ?\Rightarrow? ls,它表示在一個session中,如果執行了cd和vi命令,那么有95.74%的概率也執行了ls命令。這是非常符合人的直覺的,首先用cd命令進入某個指定的文件夾,然后用vi命令編輯文件,最后用ls命令查看一下這個文件。
如果將UNIX_usage的關聯規則和Groceries的關聯規則做類比,那么會發現一些違反事實的規則。比如cd, exit ?\Rightarrow? ls,按照之前的理解,這條關聯規則應該解釋成執行了cd和exit這兩條命令之后,在后續的過程中執行ls的概率為94.34%,但是這一解釋顯然不符合常識。在執行exit之后,當前session就已經結束了,后續也不會再執行其他命令,當然也不可能執行ls命令了。
出現上述現象的原因是UNIX_usage數據集和Groceries數據集本質上并不相同。Groceries數據集是transaction的集合,每個transaction由若干商品名構成,且若干商品名之間是無序的;UNIX_usage數據集是session的集合,每個session由若干命令構成,且若干命令之間是有序的,可以先執行ls再執行exit,但是反過來卻不行。
5 總結
關聯規則的挖掘過程分為兩步:第一步找到所有的頻繁項集,第二步從這些頻繁項集中生成強關聯規則。第二步相對來說比較簡單,只需要遍歷每個頻繁項集的所有非空子集,然后判斷confidence是否滿足要求即可。第一步要困難許多,這種困難也在本次實驗中有所體現。當用dummy方法找所有的頻繁項集時,即使是support設置成0.1,也無法在規定的時間內完成。為了應對這種挑戰,人們發明了各種各樣的方法來尋找頻繁項集,其中最具代表性的便是Apriori算法和FP-growth算法。Apriori算法的精髓是運用先驗知識來減少搜索空間,從而提高執行效率,相比于dummy算法,它有了質的提升。但是人們對于這種算法仍然不是特別滿意,這主要歸因于它的兩個最主要的缺點:當support較小時會生成大量的候選頻繁項集和多次重復掃描數據庫。為了克服這兩個缺點,人們提出了FP-growth算法。這種方法無論support多小,都只需要掃描兩遍數據庫,且不會產生候選頻繁項集。FP-growth算法的核心思想是用到了“樹”這種數據結構,將原始數據庫構建成一棵樹,這樣后續的所有操作都只需在這棵樹上進行。
窮盡搜索方法dummy在實際場景中的應用性不強,因為它會暴力窮舉所有可能的候選頻繁項集。對于含有n種item的數據集,至多可以產生2n?12^n-12n?1個候選項集,所以dummy方法的時間復雜度是O(2n)O(2^n)O(2n)級別的。同時由于窮舉所有的候選頻繁項集,所以空間復雜度也很高。Apriori方法相較于dummy方法,減少了候選頻繁項集的數目,因此提高了時間和空間的效率。FP-growth算法沒有從候選頻繁項集的角度考慮,而是直接生成頻繁項集,因此又進一步地提高了時空效率。
Groceries數據集來自于真實顧客的購物記錄,它的關聯規則的置信度(confidence)并不算特別高,但是由于support也較小,所以lift較大,也就是說如果購買了關聯規則左邊的商品,那么對關聯規則右邊的商品的銷售也有極大的促進作用。UNIX_usage數據集來自于真實用戶的命令行歷史記錄,處于隱私保護的需求,替換了一些敏感內容。這個數據集的關聯性比較高,置信度(confidence)最高可達1.0,但是因為support也較高,所以lift整體上不如Groceries。在用程序挖掘出關聯規則之后,并不能直接應用這些規則來解決現實問題,因為有部分規則并不符合實際情況,所以還需要再人工篩選一次。
總結
以上是生活随笔為你收集整理的关联规则挖掘(南京大学复杂数据结构挖掘课程作业)的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: dbcp链接池
- 下一篇: 建筑八大员培训湖北质量员培训古建筑施工中