在人大DMC學習的時候得到了一批某公司音樂下載log數據,有7天的日訪問log文件,共7個文件,每一個文件大概1.2G,還有一個是mp3_rid_info.txt,是音樂id對應的歌曲信息。python
數據格式以下:算法
數據示例以下: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
條件:我手頭的機器不是很給力,ubuntu的虛擬機,32bit,從CPU爲E6600虛擬的主機出來一個核,512MB內存。可是我仍是想試試看,7天的數據難處理,就先處理一天的數據。
預處理
將一樣session的全部項集放在一塊兒,做爲一個「購物車」。
編程目標:從大量的log信息中將同一session的下載歌曲的id歸類。數據庫
# 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()
失敗緣由:數據庫大於2G,而個人系統是32bit的,32bit的系統最多隻能在mongodb裏面存放2G的數據庫。編程
2.shell管道流方案ubuntu
這裏能夠借鑑mapreduce的工做原理,先將一樣session id的記錄歸類,而後將它們收集起來,造成一個一個「購物車」的形式。session
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
(2) reducer,生成項集ide
將剛纔獲取的已經排好序的記錄進行歸類就方便多了,只要用sys.stdin逐行掃描,若session與前一行相同,則加入容器,不然輸出容器裏面全部的id(用逗號分開),並清空容器
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較多,耗時較長
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,選出頻繁項
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
關聯規則抽取
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
歡迎讀者交流討論並提出寶貴意見。