去年暑假使用了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)
代码中的注释并不多,稍后如果有时间的话我会对改动的地方重新注释。