您的位置:首页 > 编程语言 > Python开发

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

2017-03-25 22:48 1091 查看
花了好几天时间学习了文本聚类,以下记录一下这次的学习,也整理了一些这方面的资料,和大家分享一下,一起交流学习,进步在于不断总结和分享以及相互交流。
文本聚类就是把相似的文档聚集成一簇,通过把文本转换成数值进行聚类,主要分为两个部分,第一个是构建词袋,也就是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()

以下这个循环的主要作用是
4000
获取每个文档的词表,然后把 每个文档的词表取并集,形成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

这两篇博客写得很好,尤其是第一篇,里面还总结了很多文本挖掘的资料,大家可以参考。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息