音樂下載日誌的關聯分析

在人大DMC學習的時候得到了一批某公司音樂下載log數據,有7天的日訪問log文件,共7個文件,每一個文件大概1.2G,還有一個是mp3_rid_info.txt,是音樂id對應的歌曲信息。python

數據格式以下:算法

*共6個字段,以{]分隔,utf-8編碼;
*ESID:session id
*PHONE: 手機號,空
*UID:user id
*TIME
*TYPE:不一樣行爲,d下載,s搜索,v查看
*VALUE:歌曲名,若是是數字,到mp3_rid_info.txt文件找對應的歌曲名和歌手

數據示例以下:mongodb

_qQaHMAb9aH{]{]{]20110701 000000{]v{]4854453
4qQaHxxNa84{]{]HQaHxvNwoA{]20110701 000000{]v{]4899081
VQS55L0lfa2HqQajg3zOQEO3z3GQ33zQ3{]{]VQS5WL2v_wDNfojjj3z3O2O3z3GQ33z2O{]20110701 000000{]v{]4854453
4qQaHtCw7gK{]{]4qQaHtCw7gK{]20110701 000000{]v{]109720
HqQaHYE14lp{]{]7aQaHojc6ju{]20110701 000000{]d{]12947091
VQS5WLwGxWg4qQajV3zOQEO3zG2QGGz2O{]{]VQS5WLwGxWg4qQajV3zOQEO3zG2QGGz2O{]20110701 000000{]v{]95073
Y1VqQajs80L5Jj-{]{]VQS5WLKQwrW5COajI3zVGVO3zOOZE3z2O{]20110701 000000{]v{]2652954
KM4qQajwobL5YDq{]{]XLueQaj7obL5bjt{]20110701 000000{]d{]19171739
VQS5WLWu4VE2qQajK3zOQEO3z_3QQ2z2O{]{]VQS5WLoqjIHFKQaj93zOQEO3z_3QQ2z2O{]20110701 000000{]s{]劉承俊
4qQaHiEnsi5{]{]zqQaHFFxBwL{]20110701 000000{]d{]5922248
VQS5WL-yEGT4qQajT3zOQEO3zVO3ez2O{]{]gdAB1c3a4x4sheoOam{]20110701 000000{]d{]11776465
3qHqQajc80L5bsV{]{]3qHqQajc80L5bsV{]20110701 000000{]v{]4904109
VQS5WLuuo-04qQajQ3zOQEO3zO_GOGz2O{]{]VQS5WLz8N7CfqQaj13zOQEO3zO_GOGz2O{]20110701 000000{]s{]黃家駒
yxHqQajB80L5oJB{]{]1lDNFjjm80L5w7y{]20110701 000000{]v{]1899686
IqQaHJi6YbK{]{]IqQaH3wxlaU{]20110701 000000{]s{]厲志歌曲
v-n3EqQa4eerAqmQaB{]{]{]20110701 000000{]v{]1023
U9fqQajfoxyi2mJ{]{]dZbr3aj2oxyixcc{]20110701 000000{]v{]3459364
7G2SjP05164qQaarG3_OG3O3oWLW7b{]{]X6GaHRq8VrA{]20110701 000000{]v{]1937019
p84qQajpobL54O0{]{]DFIqQaj5obL5lmQ{]20110701 000000{]d{]18900639
H64qQajgobL5BZv{]{]pHc1Qaj7obL5xfz{]20110701 000000{]d{]6445841
4qQaHVaIRa5{]{]4qQaHVaIRa5{]20110701 000000{]d{]12067314
VQS05L4JUDr4qQajP3zOQEO3zZ33Ez_O{]{]VQS05L4JUDr4qQajP3zOQEO3zZ33Ez_O{]20110701 000000{]d{]36028283
VQS55LNqP-GnqQaj83zOQEO3zVO3ezQ3{]{]ThlE3ajl80L5NKt{]20110701 000000{]s{]刀郎
hqQaHwOcF9M{]{]_qQaHnEIQ2B{]20110701 000000{]d{]30389904
4qQaHilUFEB{]{]U4JjHXDdSCQ{]20110701 000000{]d{]36227787
rZnqQajFoxyi2ZA{]{]n3tcQajtoxyiKfX{]20110701 000000{]v{]4126779
4qQaHKpbaNa{]{]4qQaHKpbaNa{]20110701 000000{]s{]我這個你不愛的人+迪克牛
VQS5WLZDENS4qQajU3zOQEO3zOG2E3z2O{]{]VQS5WLNCYrbHqQajb3zOQEO3zOG2E3z2O{]20110701 000000{]s{]最幸福的人
fqQaHEv1cpS{]{]BpQaHjADVEj{]20110701 000000{]s{]黃小琥
4qQaHDUEMXY{]{]CBvAJKQa4ecVpsQa0{]20110701 000000{]s{]少女時代
jJucbdfqQaad80L5aCn_-u{]{]jJucbdfqQaad80L5aCn_u{]20110701 000000{]v{]1023
sELFnqQa4480JOvQax{]{]sELFnqQa4480JOvQax{]20110701 000000{]v{]1993325

 

這些數據如何用來作分析呢,我考慮了一下,能夠作推薦、用戶活躍度變化的分析、歌曲或者用戶的聚類。不過,剛拿到數據的時候,我也沒想到這麼多,正好當時在學習頻繁項集,就拿這個來練習吧。因爲我比較習慣用python做數據分析,就選擇python了。shell

頻繁項集主要用於購物車內商品關聯分析,這裏把歌曲做爲商品,每一個session id同樣的項集做爲一個「購物車」。

條件:我手頭的機器不是很給力,ubuntu的虛擬機,32bit,從CPU爲E6600虛擬的主機出來一個核,512MB內存。可是我仍是想試試看,7天的數據難處理,就先處理一天的數據。

預處理
    將一樣session的全部項集放在一塊兒,做爲一個「購物車」。
    編程目標:從大量的log信息中將同一session的下載歌曲的id歸類。數據庫


     1.mongodb方案
     逐行匹配後插入mongodb,而後用mongodb的mapreduce功能進行處理。
     代碼以下(mogodbdump.mp3):
# coding=UTF-8
import re
import sys
import fileinput
import inspect
from pymongo import Connection
import bson
reload(sys)
sys.setdefaultencoding("utf-8")
 
linereg=re.compile(r"([^ ]+)\{\](\d*)\{\]([^ ]*)\{\](\d{8} \d{6})\{\]([dsv])\{\]([^ ]+)")
 
class recordItem:#記錄類,包含各字段
    def __init__(self,*groups):
        self.sessionid,self.phone,self.uid,self.time,self.typ,self.value=groups
        try:
            self.value=self.value.decode("utf-8")
        except UnicodeDecodeError:
            try:
                self.value=self.value.decode("gbk")
            except UnicodeDecodeError:
                self.value=self.value
 
class visitLogFile():#該類爲一個生成器,每一個元素即爲每一個記錄
    def __init__(self,filename):
        self.fd=fileinput.input(filename)
 
    def close(self):
        self.fd.close()
 
    def __iter__(self):
        for line in self.fd:
            if line:
                line=line.rstrip("\n")
                line=line.strip()
                m=re.match(linereg,line)
                if not m:
                    try:
                        line=line.decode("utf-8")
                    except UnicodeDecodeError:
                        try:
                            line=line.decode("gbk")
                        except UnicodeDecodeError:
                            print "shit!",fileinput.lineno()
                    print line,fileinput.lineno()
                else:
                    try:
                        record=recordItem(*m.groups())
                        yield record
                    except GeneratorExit:
                        pass
                    except Exception as e:
                        print "GENERATOR ERROR:",line,fileinput.fileno()
 
def prop(obj):
    pr={}
    for name in dir(obj):
        value=getattr(obj,name)
        if not name.startswith("__") and not inspect.ismethod(value):
            pr[name]=value
    return pr
 
if __name__ == "__main__":  
    conn=Connection()
    db=conn.easou
    collection=db.visit
 
    vlf=visitLogFile("visit.txt.20110701.2")#以文件名做爲參數
    for item in vlf:#遍歷生成器,並將每條記錄寫進mogodb
        try:
            collection.insert(prop(item))
        except bson.errors.InvalidStringData:
            print "Encode Error",item
    vlf.close()
View Code

 

失敗緣由:數據庫大於2G,而個人系統是32bit的,32bit的系統最多隻能在mongodb裏面存放2G的數據庫。編程

     2.shell管道流方案ubuntu

    這裏能夠借鑑mapreduce的工做原理,先將一樣session id的記錄歸類,而後將它們收集起來,造成一個一個「購物車」的形式。session

     (1) mapper
    將全部session id同樣的記錄歸在一塊兒,便於後續的reducer收集處理。
    經過sys.stdin逐行讀取,匹配的方式提取出各字段。
    若是字段4爲「d」,則輸出第0個字段和第5個字段。
    代碼以下(mapvisit.py):
import sys
import re
reload(sys)
sys.setdefaultencoding("utf-8")
 
linereg=re.compile(r"([^ ]+)\{\](\d*)\{\]([^ ]*)\{\](\d{8} \d{6})\{\]([dsv])\{\]([^ ]+)")#匹配字符串
 
def read_input(file):
    for line in file:
        line=line.strip()
        if not line=="":
            m=re.match(linereg,line)
            if m:
                match=m.groups()
                if match[4]=="d":
                    try:
                        value=match[5].decode("utf-8")
                    except UnicodeDecodeError:
                        try:
                            value=match[5].decode("gbk")
                        except UnicodeDecodeError:
                            value=match[5]
                    yield match[0]+"\t"+value#輸出session id與歌曲id
 
input=read_input(sys.stdin)
 
for item in input:
    print item

  用法:cat visit.txt.2011xxxx.2 | python mapvisit.py | sort > sorted.xxxx.txt app

    這裏,shell的sort能夠以行爲單位進行排序,sort仍是挺給力的,117MB的數據,大概幾分鐘就排好了。  

   (2) reducer,生成項集ide

    將剛纔獲取的已經排好序的記錄進行歸類就方便多了,只要用sys.stdin逐行掃描,若session與前一行相同,則加入容器,不然輸出容器裏面全部的id(用逗號分開),並清空容器

     代碼以下(genCollection.py):
import sys
 
def read_input(file):
    for line in file:
        line=line.rstrip()
        yield line
 
input=read_input(sys.stdin)
prev=""#存放前一個記錄的session id
collection=[]#用於臨時存放統一購物車的項的容器
for item in input:
    groups=item.split("\t")
    session=groups[0]
    value=groups[1]
    if not session==prev:#若是與前一個記錄的session id不同,那麼輸出並把容器清空
        if not len(collection)==0:
            coll=set(collection)
            coll=",".join([x for x in coll])
            print coll
        collection=[]
    collection.append(value)#將當前記錄放入容器
    prev=session
if not len(collection)==0:#最後的處理
    coll=set(collection)
    coll=",".join([x for x in coll])
    print coll

  用法:cat sorted.xxxx.txt | python genCollection.py > ck.xxxx.txt

     這樣輸出的文件就是一個個「購物車」了,示例以下,每一行表明一個「購物車」,由歌曲的id構成,用「,」分隔:

25821471
23888779,23888780
19323097
13005242
20837081
26011932
30389910
17682189
13014949,25704721,11957138
8865282
12072426
5180610
6570888
30389910,8770990
25724699
8561271
15451360,16386868
17618286
36186443
22469762
11513471
36151688
12300387
12041000
36168455
6318481
13018096,33361116,20135287,30389912
36314621,8254907,7741279,301796,36481093,25775400
36478533
36484454,36488370,36484452
9737456
36492246
36283045
36435458
22033394
36263322
36486287
20868410

  

生成C1及其頻數 

    接下來就能夠對購物車進行Apriori分詞了。其實這個過程自動化生成Ck,並掃描就能夠了,不過爲了觀察從小到大的各元祖的頻繁度,仍是一步一步來吧。若是支持度設置太高,可能都沒法生成頻繁的二元組,若是設置太低,可能須要機器跑好長時間才能出結果。
    方案一:
    掃描一遍整個「購物車」數據集,提取出C1。
    再次掃描一遍數據集,掃描每一個「購物車」時,將C1中的元素逐個判斷,是不是該「購物車」的子集,若是是,則將相應的C1對應的出現次數加1
    缺點:C1較多,耗時較長

    方案二:
    掃描的同時,將每一個購物車的元素做爲字典的鍵,值爲出現的次數,每掃描到一個元素,將字典中該元素對應的值加一。掃描結束後,根據值排序,輸出到文件
     代碼以下(genC1num.py):
import sys
from operator import itemgetter
 
def read_input(file):
        for line in file:
                line=line.rstrip()
                yield line
 
C1={}#用於存放各一元組及其頻數
input=read_input(sys.stdin)
for line in input:
        transaction=line.strip().split(",")
        if not len(transaction)==0:
                for item in transaction:
                        if not C1.has_key(item):
                C1[item]=1
            else:
                C1[item]+=1
 
sCnt=sorted(C1.iteritems(), key=itemgetter(1), reverse=True)#按照字典的值進行排序
for item in sCnt:
    print item[0]+"\t"+str(item[1])

  用法:cat ck.xxxx.txt | python genC1num.py > C1num.py

用Apriori算法生成Ck,選出頻繁項

    經過Ck-1中知足支持度的項集生成Ck的候選項集。掃描每一數據集,遍歷Ck的候選項集,若是是此數據集的子集,則相應的字典加一。最後將每一項集及其的數量排序後輸出。
     代碼以下(apriori.py):
import sys
from operator import itemgetter
 
def genCandidate(F):#經過知足支持度的Ck-1項集生成候選的Ck項集
    C=[]
    k=len(F[0])+1
    print "k="+str(k)
    length=len(F)
    for i in range(length):
        for j in range(i+1,length):
            L1=list(F[i])[:k-2]
            L2=list(F[j])[:k-2]
            L1.sort()
            L2.sort()
            if L1==L2:
                C.append(F[i]|F[j])
    return C
 
def scanD(D,Ck):#掃描每一「購物車」,統計每一候選項集出現的頻率
        ssCnt={}
        i=0
        for tid in D:
                i+=1
                for can in Ck:
                        if can.issubset(tid):
                                if not ssCnt.has_key(can):
                                        ssCnt[can]=1
                                else:
                                        ssCnt[can]+=1
 
                if i%1000==0:#用於觀察進度
                        print str(i)+" lines scaned!"
        sCnt=sorted(ssCnt.iteritems(), key=itemgetter(1), reverse=True)
    return sCnt,ssCnt
 
def read_input(file):
        for line in file:
                line=line.rstrip()
                yield line.split(",")
 
fd=open("C2num.txt","r")#操做Ck-1項集的文件,能夠按照須要修改文件名
ck1=[]#存放Ck-1項集
while True:
    line=fd.readline()
    if not line:
        break
    item=line.split("\t")
    if int(item[1])<487:
        break
    ck1.append(item[0].split(","))
 
ck1=map(frozenset,ck1)
ck=genCandidate(ck1)
fd.close()
print "Length of Ck is "+str(len(ck))
print "Load Ck completely!"
 
input=read_input(sys.stdin)
sCnt,ssCnt=scanD(input,ck)
 
fdout=open("C3num.txt","w")#生成Ck項集的文件,能夠按照須要修改文件名
for item in sCnt:
    ss=""
    for i in item[0]:
        ss+=i+","
    ss=ss.rstrip(",")
    ss+="\t"+str(item[1])+"\n"
    fdout.write(ss)
fdout.close()

  用法:cat ck.xxxx.txt| python apriori.py > C3num.txt

     循環此步驟,直到Ck中沒有知足支持度的項集。在本數據集中,到C3就沒有,知足支持度的項集了。所以接下來的分析中主要圍繞C1和C2進行分析。

 

關聯規則抽取

    獲取頻繁項集之後,咱們就能夠進行關聯規則的抽取,按照信任度的公式P->H=support(PH)/support(P)。在抽取的同時,按照這樣的原則:若是某條規則不知足最小可信度要求,那麼該規則的全部本身也不會知足最小信任度的要求。
    能夠先從一個頻繁項集開始,接着建立一個規則列表,其中規則右邊包含一個元素,而後對這些規則進行測試。接下來合併全部剩餘規則來建立一個新的規則列表,其中規則右邊包含兩個元素。在這裏,因爲只有一元組和二元組兩種頻繁項集,因此抽取的規則比較簡單。
  代碼以下(relationExtraction.py):
def loadCk(filename,supportData):#加載Ck的函數
    Ck=[]
    fd=open(filename,"r")
    while True:
        line=fd.readline()
        if not line:break
        line=line.rstrip()
        item=line.split("\t")
        if int(item[1])<487:break
        Ck.append(item[0].split(","))
        supportData[frozenset(item[0].split(","))]=int(item[1])
    return map(frozenset,Ck)
 
def generateRules(L,supportData):#抽取關聯規則的函數
    bigRuleList=[]
    for i in range(1,len(L)):
        for freqset in L[i]:
            H1=[frozenset([item]) for item in freqset]
            calcConf(freqset,H1,supportData,bigRuleList)
 
def calcConf(freqset,H,supportData,bigRuleList):
    for conseq in H:
        conf=float(supportData[freqset])/supportData[freqset-conseq]
        bigRuleList.append((freqset-conseq,conseq,conf))
        if conf>0.1:#可信度的閾值爲0.1,能夠按照需求改變
            print ",".join(freqset-conseq)+"\t"+",".join(conseq)+"\t"+str(conf)
            #print freqset-conseq+"\t"+conseq+"\t"+conf
 
retlist=[]
supportData={}
retlist.append(loadCk("C1num.txt",supportData))#一元組的加載
retlist.append(loadCk("C2num.txt",supportData))#二元組的加載
 
generateRules(retlist,supportData)

  用法:python relationExtraction.py > relation.txt

    抽取的關聯規則以下(左邊->右邊 信任度):

36435459    36455065    0.100081699346
36259037    26032040    0.100420838775
36435458    36455064    0.102110885046
36314621    36163849    0.102863822326
36314622    36488369    0.103251231527
36455066    36435460    0.104193971166
36314621    36488368    0.108240794857
36314623    36163851    0.11100049776
36494430    36455066    0.111133685494
36481096    36273013    0.114648033126
36280476    36280477    0.115893297467
36481094    36481093    0.12092463923
36273013    36481096    0.123432711062
36435460    36455066    0.127506014435
36314623    36488370    0.135390741663
30389910    30389896    0.145206766917
30389896    30389910    0.159196290572
35979647    26032038    0.178885630499
17818175    36314621    0.179292929293
17818177    36314623    0.185461956522
36280477    36280476    0.195463137996
36280476    36163849    0.219905850706
36280477    36163851    0.239697542533
36481093    36481094    0.24720021852

思考

    從大量的數據中抽取的關聯規則特別少,緣由是同一session id下載的歌曲不少都是隻有一首歌。是否是應該考慮不以session做爲單位進行頻繁項集的抽取,而是以用戶做爲單位進行抽取。並且,有些id對應同一首歌,這樣一樣會被抽取爲關聯度較大的規則,這是沒有意義的,做爲噪聲須要避免。若是是處理多天的數據,可能就須要多臺機器並行處理了,針對此還須要稍微改進一下如今的算法。
    同時,關聯規則的抽取只是一個小的方面,還有不少方面能夠對這些數據進行抽取,期待之後的工做能將此作的更好。

 

我的博客地址:http://gujianbo.1kapp.com/ 

新浪微博:http://weibo.com/gujianbobo

歡迎讀者交流討論並提出寶貴意見。

相關文章
相關標籤/搜索