去年暑假使用了TFIDF對一些文本做了處理,本次遇到的文本文件由於量太大,所以全年使用的算法源代碼計算速度太慢,問題主要出在重復的循環比對詞語的問題上。在設立集合比對的任務中,字典的速度是最快的,同時可以提供一個值作為出現次數。這樣一篇文檔的詞袋模型可以使用字典數據結構完整的表現出來而且速度不會太慢。
先前的代碼使用的是某一篇博客的,源網址在先前的博客中引用過,這里直接將源代碼貼出
#-*- coding:utf-8 -*- import math import os import fileinput TEXT = 0 #某類別的文檔數目 SUCCEED = 0 Docs = [] Path = "ciku".decode('utf8').encode('cp936') #綜合分析,path地址實際上是存放訓練數據的一個文件夾,文件夾內包含眾多txt文件 w_Path = "TFIDF".decode('utf8').encode('cp936') ##計算權重函數,tf為某詞在文章中出現的次數,df為包含該詞的文檔數,max文章中出現次數最多的詞條數 ##返回值為TF-IDF權重 ##把特征向量按權重進行排序 def sort(terms,TF_IDF): for i in range(0,len(terms)):#range生成一個0到len(terms)的數字序列 m = i for j in range(i+1,len(terms)): if TF_IDF[j]>TF_IDF[m]: m = j if i!=m: temp = terms[i] terms[i] = terms[m] terms[m] = temp v = TF_IDF[i] TF_IDF[i] = TF_IDF[m] TF_IDF[m] = v def save_words(path): global Docs , TEXT terms = [] fp = open(path,"r") while True: line = fp.readline() if not line : break terms.append(line) fp.close() Docs.append(terms) TEXT += 1 def GenerateIDF(path):#計算詞的IDF,具體的計算方法是取文檔總數除以出現這個詞的文檔數量之后的商取對數 global Docs , TEXT terms = [] IDF = [] idf = 0.0 fp = open(path,"r") #綜合前邊的分析在這里path指向的是一個單行的詞匯表,但是這樣的組織形式和多篇文章的計算方法存在差別?? while True: line = fp.readline() if not line : break flag = 0 for i in range(0,len(terms)):#這個循環的意思是對比兩個相同的詞匯表,如果固定一個詞,這個詞與已有的詞匯庫作對比,如果未發現相同的,則將這個詞放入到terms,類似一個蛇頭吃蛇尾的算法 if line == terms[i]:#使用了一個文件的次創造了兩個詞集,一個是不重復的詞集 flag = 1 if flag == 0: terms.append(line)#這一句的意思是創造一個terms list在這個list中均為不重復的term,也就是建立了一張詞匯表 fp.close() for j in range(0,len(terms)):#計算文檔中是否存在這個詞,存在則df自加1 df = 0 for i in range(0,len(Docs)): #在這里terms與df為對應關系, #對應於每個詞的idf值,方法是固定總的詞,拿每篇文章的詞與其對比, #如發現相同則循環終止,df自加1 flag = 0 doc = Docs[i] for k in range(0,len(doc)):#設置一個flag來檢測循環終止 if terms[j] == doc[k]:#在這里Docs為一個二維的矩陣,每一行代表一篇文章,doc為其某一行,即代表一篇文章,遍歷單篇文章如發現文章穿線terms則此時中斷且df自加1 flag = 1 break if flag == 1: df += 1 idf = math.log(float(TEXT)/float(df)+0.01)#文章總數與出現本詞的文章總數比值取對數 IDF.append(idf)#TEXT為文章總數 df為出現這個詞的文檔總數 return IDF,terms def GenerateTF(path,terms):#計算TF值,具體的TF計算方法為某個詞在一篇文章中出現的次數 all_terms = [] TF = [] terms_count = len(terms)#term總數量,這個terms實際是一個全部詞匯的不重復的詞匯集 fp = open(path,"r")#打開數據集 while True:#使用按行讀取生成一個terms的詞集 line = fp.readline() if not line : break all_terms.append(line) for i in terms:#固定i比較terms中某個詞和總的all_terms命中次數,即出現的次數 tf = 0 for j in all_terms: if i == j: tf +=1 TF.append(float(tf)/float(terms_count)) fp.close() return TF def save_weight(TF,IDF,terms,path): global SUCCEED TF_IDF = [] top = 200 if len(TF)<top: top = len(TF) for i in range(0,len(TF)): TF_IDF.append(float(TF[i])*float(IDF[i])) fp = open(path,"w+")#這里的path為傳入的w_Path地址,用來存儲相應的 sort(terms,TF_IDF)#使用詞與TF_IDF作為輸入進入到分類函數中 for i in range(0,top): string = terms[i].strip()+" "+str(TF_IDF[i])+'\n' fp.write(string) SUCCEED += 1 fp.close() def read_dir(path,w_path): global SUCCEED file_list = [] files = os.listdir(path)#遍歷文件路徑,輸出目錄下的文件名和文件夾名 print "please wait......" for f in files: file_list.append(f) #file_list為文件名,與os.listdir對應 r_name = path + '/' + f #數據集的目錄加具體的文件路徑 save_words(r_name)#TEXT加1,且terms成為完整路徑,函數實現讀取詞語的功能 print "sum of docs is:%d"%TEXT #TEXT為讀取的詞語的總數 for i in file_list:#對每一個訓練文件名,加上完整的路徑,輸出為name來為后邊的函數服務 print i name = path + '/' + i w_name = w_path + '/' + i IDF,terms = GenerateIDF(name)#在這里輸出一個詞語不重復的terms詞匯表,和每個詞語的IDF值 TF = GenerateTF(name,terms)#根據輸出的詞匯表來計算每個詞的TF值 save_weight(TF,IDF,terms,w_name) print "succeed:%d"%SUCCEED #將文檔中的詞提取出來,放入list,便於后期計算 def get_word(input_line): line = input_line.replace() if __name__=="__main__": print("main") read_dir(Path,w_Path) print "-------------------Finished-----------------------"
在代碼中,我對關鍵的語句進行了標注。
計算總體步驟是,通過規定兩個dir,一個輸出一個輸入,以行排列的單詞文件輸入到算法中輸出,單個單詞的TFIDF數值,為其后的數據分析提供支撐。
在IDF的計算中,主要是源代碼中使用循環對某個單詞是否出現在文件中進行查找,在每一個文檔中計算每一個詞的IDF值都會重復這個比對的循環過程,造成大量的運行時間浪費,在代碼的改進中,我使用一個兩級的字典文件表示整個數據集,最外層的字典是48480個文件,每一個鍵值對的鍵是編號(從1到48480),值為一個文檔字典,比如編號為1的鍵其對應的值為文檔1全部詞與出現個數的詞典。這樣對整個數據集的表示和查找通過使用字典結構的方法可以獲得大比例的速度提升。例如在計算IDF中如果需要計算某個單詞是否出現在某個編號的文檔中,直接使用字典結構的has_key()即可。雖然還是帶來了一定的時間復雜度(如果需要進一步的提升效率可以使用中間緩存的方法建立單詞出現文檔數的緩存列表,相信可以進一步大規模提升效率)。字典結構的更改主要出現在需要遍歷單詞和文檔的函數中,其他部分保持不變,具體的改動請參考下方的代碼
#coding:utf-8 #先構建字典,將所有的文檔分詞的詞語建立一個字典,key是單詞,value是出現的次數,方便隨后計算頻率, #在每一行的字典中加入一個鍵‘total’,值為所有詞的個數。只有將原代碼中所有的對文件的操作改為對字典的操作, #文件的操作作為對每一個字典的行進行操作 from zhon.hanzi import punctuation import re import string import os import math import sys reload(sys) sys.setdefaultencoding('utf8') path = 'ciku'.decode('utf8').encode('cp936') path1 = 'stop_word.txt'.decode('utf8').encode('cp936') w_path = 'TFIDF'.decode('utf8').encode('cp936') def sort(terms,TF_IDF): for i in range(0,len(terms)):#range生成一個0到len(terms)的數字序列 m = i for j in range(i+1,len(terms)): if TF_IDF[j]>TF_IDF[m]: m = j if i!=m: temp = terms[i] terms[i] = terms[m] terms[m] = temp v = TF_IDF[i] TF_IDF[i] = TF_IDF[m] TF_IDF[m] = v def GenerateIDF(index): # 計算詞的IDF,具體的計算方法是取文檔總數除以出現這個詞的文檔數量之后的商取對數 terms = [] IDF = [] idf = 0.0 for key in dict_all[index]: if key != 'total': terms.append(key) for j in range(0, len(terms)): # 計算文檔中是否存在這個詞,存在則df自加1 df = 0 for key1 in range(1,48481): dict_sample = dict_all[str(key1)] if dict_sample.has_key(terms[j]): df = df + 1 idf = math.log(float(48480) / float(df) + 0.01) # 文章總數與出現本詞的文章總數比值取對數 IDF.append(idf) # TEXT為文章總數 df為出現這個詞的文檔總數 return IDF, terms def GenerateTF(index, terms): # 計算TF值,具體的TF計算方法為某個詞在一篇文章中出現的次數 all_terms = [] TF = [] dict_sample = dict_all[str(index)] for i in terms: # 固定i比較terms中某個詞和總的all_terms命中次數,即出現的次數 TF.append(float(dict_sample[i]) / float(dict_sample['total'])) return TF def save_weight(TF,IDF,terms,path): TF_IDF = [] top = 200 if len(TF)<top: top = len(TF) for i in range(0,len(TF)): TF_IDF.append(float(TF[i])*float(IDF[i])) fp = open(path,"w+")#這里的path為傳入的w_Path地址,用來存儲相應的 sort(terms,TF_IDF)#使用詞與TF_IDF作為輸入進入到分類函數中 for i in range(0,top): string = terms[i].strip()+" "+str(TF_IDF[i])+'\n' fp.write(string) fp.close() file_list = [] files = os.listdir(path)#遍歷文件路徑,輸出目錄下的文件名和文件夾名 print "please wait......" dict_all = {} stop_word = [] stop_file = open(path1,'r') for line1 in stop_file.readlines(): line = line1.replace('\n','') stop_word.append(line) for f in sorted(files): print f file_list.append(f) #file_list為文件名,與os.listdir對應 r_name = path + '/' + f #數據集的目錄加具體的文件路徑 num = f.replace('.txt','') content1 = open(r_name,'r') term_list = [] for line1 in content1.readlines(): line = line1.replace('\n','') symol = punctuation+string.punctuation+'1234567890' + ' ' term = re.sub(ur"[%s]+" % symol, "", line.decode("utf-8")) if term == ' ' or len(term) == 0 or term in stop_word: continue term_list.append(term) set1 = set(term_list) dict1 = {} total = 0 for term in set1: dict1[term] = term_list.count(term) total = term_list.count(term)+ total dict1['total'] = total dict_all[num] = dict1 #for key in dict1: #print key +':'+str(dict1[key]) for i in file_list: # 對每一個訓練文件名,加上完整的路徑,輸出為name來為后邊的函數服務 print i name = path + '/' + i w_name = w_path + '/' + i num_wenjian = i.replace('.txt','') IDF, terms = GenerateIDF(num_wenjian) # 在這里輸出一個詞語不重復的terms詞匯表,和每個詞語的IDF值 TF = GenerateTF(num_wenjian, terms) # 根據輸出的詞匯表來計算每個詞的TF值 save_weight(TF, IDF, terms, w_name)
代碼中的注釋並不多,稍后如果有時間的話我會對改動的地方重新注釋。