python中文文本聚类_python进行中文文本聚类实例(TFIDF计算、词袋构建)

花了好几天时间学习了文本聚类,以下记录一下这次的学习,也整理了一些这方面的资料,和大家分享一下,一起交流学习,进步在于不断总结和分享以及相互交流。

文本聚类就是把相似的文档聚集成一簇,通过把文本转换成数值进行聚类,主要分为两个部分,第一个是构建词袋,也就是TF-IDF矩阵,也叫作文档词频矩阵,这个矩阵的每行是一篇文档,每列是一个词,矩阵的某个值代表该词在某篇文档的TF-IDF权重,就是把文档中的每个词转成它在这篇文档的重要性,用TF-IDF来表达这个词的重要程度,从而用数值来替换文本。这一步非常重要,可以说它占整个文本聚类过程的五分之四。关于TF-IDF的介绍。我认为解释的比较易懂的是这篇文档:http://mp.weixin.qq.com/s?__biz=MzA3MDg0MjgxNQ==&mid=2652390921&idx=1&sn=c89606ab9a3d55387305f54a05d26fd5&chksm=84da46d9b3adcfcff0c500c82ea01d1ba3a3d4beb50354cad084863c24b3ab5978bb0d015354&mpshare=1&scene=1&srcid=03255LjRoyvBkfIFoMPxmsBt#rd,这里解释得非常简单易懂了,大家可以阅读参考,(附:这篇文章是我在一个叫 数据挖掘入门与实战的微信公众号看到的,这个公众号经常推送一些数据挖掘的文章,非常实用,感兴趣的伙伴可以关注学习。)我总结一下构建词袋的几个关键步骤,首先是分词,分词是否准确影响聚类效果,关于jieba分词可以参考这篇文章http://www.oschina.net/p/jieba/,然后是计算TF值和IDF值,最后计算TF-IDF值,构建矩阵,这就是构建词袋的过程。

我们已经知道TF-IDF的含义了,下面具体说说代码实现。一开始我不了解已经有现成的库可以直接调用函数就可以计算TF-IDF值,直接可以得到词袋,所以我一开始是自己参考一些资料,根据TF-IDF的含义,一步步编代码计算出来。当我花了很多时间不断地调代码,终于运行正确的时候,突然找到关于scikit-learn库有相应的模块直接调用函数就可以计算。所以我两种方法都试了,以下我把两个方法都介绍一下。

1、直接计算法  import jieba  import os  import pandas as pd  import numpy as np  stopw = [line.strip().decode(‘utf-8’) for line in open(u’F:/自然语言处理/stops.txt’).readlines()]#读取停用词  def del_stop_words(words,stop_words):#定义一个将分词结果过滤掉停用词的函数  result=jieba.cut(words)#分词  new_words = []  for i in result:#对分词结果进行遍历  if i not in stop_words:#如果词语不在停用词表,是我们需要的词  new_words.append(i)#将保留下来的词追加到一个新的list中  return new_words  names = [ os.path.join(u’F:/自然语言处理/document’,h) for h in os.listdir(u’F:/自然语言处理/document’) ]#主要是读取document文件夹下所有的文本文件的路径,其中os.listdir是读取文本文件的名称如‘1.txt’、‘2.txt’  posts = [ open(name).read() for name in names ]#循环读取每个文本文件的内容  docs = []  word_set = set()  以下这个循环的主要作用是获取每个文档的词表,然后把 每个文档的词表取并集,形成word_set。词表就是指该篇文档由哪些词组成。用set函数可以得到每篇文档包含的词语。  for post in posts:  doc = del_stop_words(post,stopw)  docs.append(doc)  word_set |= set(doc)  word_set = list(word_set)#将word_set转为一个list数据类型  docs_vsm = []  以下这两个循环的主要作用是计算每一篇文档的词频,并把所有文档的词频全部放在doc_vsm中,doc_vsm转换成矩阵docs_matrix,这就是一个词频矩阵,每行是一篇文档,列是相应文档里的词,矩阵的某个数值表示该词在某篇文档的频数。  for i in range(13):#我的实验数据一共13篇文档,所以遍历13次,或许有同学会问为什么不是for doc in docs,我参考的博客资料确实这么写,但是我运行测试以后发现有问题,就是最后有一个函数是计算矩阵docs_matrix每一行的和,出现为0的情况,这显然不对,不可能一篇文档的词总数为0,当我写成for i in range(13):的时候,就没有这种问题,13这个数字可以根据您测试的文档数进行更换。  temp_vector = []  for word in word_set:#遍历词表,该词表包含组成文档的所有词  temp_vector.append(docs[i].count(word) * 1.0)#计算每篇文档每个词的词频, temp_vector临时保存一篇文档的词频  docs_vsm.append(temp_vector)#将每篇文档的词频依次追加到docs_vsm数据表中  docs_matrix = np.mat(docs_vsm)  column_sum = [ float(len(np.nonzero(docs_matrix[:,i])[0])) for i in range(docs_matrix.shape[1]) ]#计算包含该词的文档数  column_sum = np.array(column_sum)#转换为数组,因为数组可以方便后面的批量除法计算  column_sum = docs_matrix.shape[0] / column_sum#用文档总数除以包含某个词的文档总数(根据idf的概念)  idf = np.log(column_sum)#取对数  idf = np.diag(idf)#将数组转换为n*n的对角矩阵  以下这个循环主要是根据前面的词频矩阵docs_matrix计算tf值,tf值是词频除以该篇文档的总词数。  for doc_v in docs_matrix:  if doc_v.sum() == 0:  doc_v = doc_v / 1  else:  doc_v = doc_v / (doc_v.sum())  tfidf = np.dot(docs_matrix,idf)#tf*idf  tfidf就是一个词袋了  以上是直接计算的方法,为了说明详细,所以写的注释有点多,为了大家看得更清晰一些,我把代码重新复制一次。  import jieba  import os  import pandas as pd  import numpy as np  stopw = [line.strip().decode(‘utf-8’) for line in open(u’F:/自然语言处理/stops.txt’).readlines()]  def del_stop_words(words,stop_words):  result=jieba.cut(words)  new_words = []  for i in result:  if i not in stop_words:  new_words.append(i)  return new_words  names = [ os.path.join(u’F:/自然语言处理/document’,h) for h in os.listdir(u’F:/自然语言处理/document’) ]  posts = [ open(name).read() for name in names ]  docs = []  word_set = set()  for post in posts:  doc = del_stop_words(post,stopw)  docs.append(doc)  word_set |= set(doc)  word_set = list(word_set)  docs_vsm = []  #print word.encode(“utf-8”)  for i in range(13):  temp_vector = []  for word in word_set:  temp_vector.append(docs[i].count(word) * 1.0)  docs_vsm.append(temp_vector)  docs_matrix = np.mat(docs_vsm)  column_sum = [ float(len(np.nonzero(docs_matrix[:,i])[0])) for i in range(docs_matrix.shape[1]) ]  column_sum = np.array(column_sum)  column_sum = docs_matrix.shape[0] / column_sum  idf = np.log(column_sum)  idf = np.diag(idf)  for doc_v in docs_matrix:  if doc_v.sum() == 0:  doc_v = doc_v / 1  else:  doc_v = doc_v / (doc_v.sum())  tfidf = np.dot(docs_matrix,idf)  2、使用scikit-learn库的feature_extraction.textTrans文本特征抽取模块中的former 和CountVectorizer 相关函数进行计算,比上面的方法简单十倍都不止,但是数据类型要必须满足它的要求,所以在使用这个方法之前要做一些特别的处理,最后形成一个词列表list,list的每个元素是一篇文档的所有词,即每篇文档的所有词构成一个向量,所有的向量构成list。以下是相关代码:  2.1计算TFIDF值之前的前期处理  import re  import os  import sys  import codecs  import jieba  stopw = [line.strip().decode(‘utf-8’) for line in open(u’F:/自然语言处理/stops.txt’).readlines()]  path=u’F:/file/’  ph=u’F:/自然语言处理/document/’  n= 1  while n<=13: #遍历每篇文档  name = “%d” % n  filename = ph + str(name) + “.txt”  resname=path+str(name)+”.txt”  source = open(filename, ‘r’)  result = codecs.open(resname, ‘w’, ‘utf-8’)  line = source.readline() #按行读取  line = line.rstrip(‘\n’)  while line!=”“: #对读取的每行数据进行分词  seglist = jieba.cut(line,cut_all=False)  newword=[]  for i in seglist:  if i not in stopw:  newword.append(i)#去停后的分词结果是以逗号分隔  output = ’ ‘.join(list(newword)) #所以要用join替换成空格拼接  result.write(output + ‘\r\n’) #将该篇文档的某一行文本分词后的结果写入新文档  line = source.readline() #循环读取该篇文档的每行文本  source.close()  result.close()  n = n + 1

result = codecs.open(u’F:/自然语言处理/document/all.txt’, ‘w’, ‘utf-8’)  num = 1  while num <= 13:  name = “%d” % num  fileName = path + str(name) + “.txt”  source = open(fileName, ‘r’)  line = source.readline()  line = line.strip(‘\n’) #去除回车换行符,让所有的词形成一行  line = line.strip(‘\r’)  while line!=”“:  line = line.replace(‘\n’,’ ‘)  line = line.replace(‘\r’,’ ‘) #去除回车换行符,让所有的词形成一行  result.write(line+ ’ ‘) #将每一行写入一个文档并用空格分隔  line = source.readline()  source.close()  num = num + 1  result.close()

主要思路是读取每篇文档进行分词,分词以后将分词结果分别写到新的文本文件中去,我原始文档有13个,最后也会有相应的13个分词结果文件,接着再读取每个分词结果文件到python中,再依次写到一个文件中,这个文件就包含了所有文档的分词结果,这个文件每一行就是一篇文档的分词结果,我这里测试文档是13篇,那么这个文件就只有13行数据,必须这样,否则后面使用函数计算TFIDF会出错。  2.2正式进行计算TF-IDF值  from sklearn.feature_extraction.text import TfidfTransformer  from sklearn.feature_extraction.text import CountVectorizer  corpus = []  for line in open(u’F:/自然语言处理/document/all.txt’, ‘r’).readlines():  #print line  corpus.append(line.strip())#all文件就是存放所有文档分词结果的文件,一共13行,每行代表一篇文本,把它读取到corpus中  vectorizer = CountVectorizer()#将文本中的词语转换为词频矩阵 矩阵元素a[i][j] 表示j词在i类文本下的词频  transformer = TfidfTransformer() #该类会统计每个词语的tf-idf权值  tfidf = transformer.fit_transform(vectorizer.fit_transform(corpus))#第一个fit_transform是计算tf-idf 第二个fit_transform是将文本转为词频矩阵  word = vectorizer.get_feature_names() #获取词袋模型中的所有词语  weight = tfidf.toarray() #将tf-idf矩阵抽取出来,元素w[i][j]表示j词在i类文本中的tf-idf权重  weight就是tfidf矩阵  3、聚类  根据前面计算的tfidf值,我们已经把文本转换成了数值,可以直接进行聚类了。实际上为了测试效果,我使用的文本是已经分好类的,是搜狗实验室的文本数据,我分别从财经、IT、汽车三个类抽取出13篇文章,所以我可以很容易了解到聚类效果。我尝试了所有的聚类方法,聚类效果都不是很好,在其他一些作者写的关于文本聚类的文章里面,也有提到效果不是很好。根据我以前看过的文献资料,应该是最后的TFIDF矩阵是一个高维稀疏矩阵,导致聚类效果很差,解决方法就是降维,目前我还没有继续往下优化,希望对这方面有研究的朋友跟我交流,多多指导。  以下是聚类的有关代码:  from sklearn.cluster import KMeans  kmeans=KMeans(n_clusters=3)  kmeans.fit(weight)  kmeans.labels_#输出k-means聚类的结果  from sklearn import cluster  ms = cluster.MeanShift()  ms.fit_predict(weight)  two_means = cluster.MiniBatchKMeans(n_clusters=3)  two_means.fit_predict(weight)  ward = cluster.AgglomerativeClustering(n_clusters=3, linkage=’ward’)  ward.fit_predict(weight)  spectral = cluster.SpectralClustering(n_clusters=3,eigen_solver=’arpack’,affinity=”nearest_neighbors”)  spectral.fit_predict(weight)  dbscan = cluster.DBSCAN(eps=.2)  dbscan.fit_predict(weight)  affinity_propagation = cluster.AffinityPropagation()  affinity_propagation.fit_predict(weight)  从所有的运行结果看,虽然结果都不太好,总的来说k-means稍微好一些,但是我了解到谱聚类是比较适合高维数据的聚类的,实际运行效果也不好,可能我调参不对,感兴趣的同学都可以尝试一下。  以下给出我参考的几篇博客资料:  http://blog.csdn.net/eastmount/article/details/50473675  http://blog.csdn.net/yyxyyx10/article/details/63685382  这两篇博客写得很好,尤其是第一篇,里面还总结了很多文本挖掘的资料,大家可以参考。

你可能感兴趣的:(python中文文本聚类)