声明:代码的运行环境为Python3。Python3与Python2在一些细节上会有所不同,希望广大读者注意。本博客以代码为主,代码中会有详细的注释。相关文章将会发布在我的个人博客专栏《Python自然语言处理》,欢迎大家关注。
聚类是典型的无监督学习方法,在自然语言处理中,聚类也是至关重要的。
【英文文档的聚类】
'''
英文文档的聚类
'''import numpy as np
import pandas as pd
import nltk
from bs4 import BeautifulSoup
import re
import os
import codecs
from sklearn import feature_extraction
import mpld3# 加载影片数据
titles = open('F:/title_list.txt').read().split('\n')
titles = titles[:100] # 取前一百个影片名
print(titles[:10]) # 前 10 个片名links = open('F:/link_list_imdb.txt').read().split('\n')
links = links[:100] # 取前一百个影片介绍synopses_wiki = open('F:/synopses_list_wiki.txt', encoding='utf-8').read().split('\n BREAKS HERE')
synopses_wiki = synopses_wiki[:100] # 取前一百个来自wiki的简介# 数据清洗,获取html代码中的文本内容
synopses_clean_wiki = []
for text in synopses_wiki:text = BeautifulSoup(text, 'lxml').getText() # 获取html中的文本信息# strips html formatting and converts to unicodesynopses_clean_wiki.append(text)synopses_wiki = synopses_clean_wikigenres = open('F:/genres_list.txt').read().split('\n')
genres = genres[:100]
# 打印出读进来的相关文本
print(str(len(titles)) + ' titles')
print(str(len(links)) + ' links')
print(str(len(synopses_wiki)) + ' synopses')
print(str(len(genres)) + ' genres')synopses_imdb = open('F:/synopses_list_imdb.txt', encoding='utf-8').read().split('\n BREAKS HERE')
synopses_imdb = synopses_imdb[:100]synopses_clean_imdb = []for text in synopses_imdb:text = BeautifulSoup(text, 'html.parser').getText()# strips html formatting and converts to unicodesynopses_clean_imdb.append(text)synopses_imdb = synopses_clean_imdbsynopses = []for i in range(len(synopses_wiki)): # 合并wiki和imdb中的内容item = synopses_wiki[i] + synopses_imdb[i]synopses.append(item)# 为每个项目生成索引的全集(在本例中它只是排名),以后我将使用这个得分
ranks = []for i in range(0, len(titles)):ranks.append(i)# 载入 nltk 的英文停用词作为“stopwords”变量
stopwords = nltk.corpus.stopwords.words('english')
print(stopwords[:10])# 载入 nltk 的 SnowballStemmer 作为“stemmer”变量
from nltk.stem.snowball import SnowballStemmer # 返回词语的原型,去掉ing等stemmer = SnowballStemmer("english")# 这里定义了一个分词器(tokenizer)和词干分析器(stemmer),它们会输出给定文本词干化后的词集合
def tokenize_and_stem(text): # 词干分析器,返回词语的原型# 首先分句,接着分词,而标点也会作为词例存在tokens = [word for sent in nltk.sent_tokenize(text) for word in nltk.word_tokenize(sent)]filtered_tokens = []# 过滤所有不含字母的词例(例如:数字、纯标点)for token in tokens:if re.search('[a-zA-Z]', token):filtered_tokens.append(token)stems = [stemmer.stem(t) for t in filtered_tokens]return stemsdef tokenize_only(text): # 分词器,仅分词# 首先分句,接着分词,而标点也会作为词例存在tokens = [word.lower() for sent in nltk.sent_tokenize(text) for word in nltk.word_tokenize(sent)]filtered_tokens = []# 过滤所有不含字母的词例(例如:数字、纯标点)for token in tokens:if re.search('[a-zA-Z]', token):filtered_tokens.append(token)return filtered_tokens# 扩充列表后变成了非常庞大的二维(flat)词汇表
totalvocab_stemmed = []
totalvocab_tokenized = []
for i in synopses:allwords_stemmed = tokenize_and_stem(i) # 对每个电影的剧情简介进行分词和词干化totalvocab_stemmed.extend(allwords_stemmed) # 扩充“totalvocab_stemmed”列表allwords_tokenized = tokenize_only(i)totalvocab_tokenized.extend(allwords_tokenized)vocab_frame = pd.DataFrame({'words': totalvocab_tokenized}, index=totalvocab_stemmed) # 转换为数据框
print('there are ' + str(vocab_frame.shape[0]) + ' items in vocab_frame')print(vocab_frame.head())# 定义向量化参数
from sklearn.feature_extraction.text import TfidfVectorizertfidf_vectorizer = TfidfVectorizer(max_df=0.8, max_features=200000,min_df=0.2, stop_words='english',use_idf=True, tokenizer=tokenize_and_stem, ngram_range=(1,3)) # max_df:用于描述一个词在文档中出现的频率,此处为80%,频率大于这个数,说明该词意义不大;min_df:此处和max_df相对,当一个词出现的频率在二者之间的时候才会被计算tfidf;ngram_range:设置一元模型、二元模型等等
# 在语句前面加:%time 可以在控制台输出运行时间
tfidf_matrix = tfidf_vectorizer.fit_transform(synopses) # 向量化剧情简介文本
print(tfidf_matrix.shape)
terms = tfidf_vectorizer.get_feature_names() # 获取特征from sklearn.metrics.pairwise import cosine_similarity # 计算余弦相似度
dist = 1 - cosine_similarity(tfidf_matrix) # 计算余弦相似度# k-means聚类
from sklearn.cluster import KMeansnum_clusters = 5
km = KMeans(n_clusters=num_clusters)
km.fit(tfidf_matrix)
clusters = km.labels_.tolist()from sklearn.externals import joblib# 注释语句用来存储你的模型
# 因为我已经从 pickle 载入过模型了
# joblib.dump(km, 'doc_cluster.pkl')
km = joblib.load('doc_cluster.pkl')
clusters = km.labels_.tolist()films = {'title': titles, 'rank': ranks, 'synopsis': synopses, 'cluster': clusters, 'genre': genres}
frame = pd.DataFrame(films, index=[clusters], columns=['rank', 'title', 'cluster', 'genre'])
frame['cluster'].value_counts()grouped = frame['rank'].groupby(frame['cluster']) # 为了凝聚(aggregation),由聚类分类。
grouped.mean() # 每个聚类的平均排名(1 到 100)from __future__ import print_function# 按离质心的距离排列聚类中心,由近到远
order_centroids = km.cluster_centers_.argsort()[:, ::-1]for i in range(num_clusters):print("Cluster %d words:" % i, end='')for ind in order_centroids[i, :6]: # 每个聚类选 6 个词print(' %s' % vocab_frame.ix[terms[ind].split(' ')].values.tolist()[0][0].encode('utf-8', 'ignore'), end=',')print() # 空行print() # 空行print("Cluster %d titles:" % i, end='')for title in frame.ix[i]['title'].values.tolist():print(' %s,' % title, end='')print() # 空行print() # 空行# 多维尺度分析MDS
import os # 为了使用 os.path.basename 函数
import matplotlib.pyplot as plt
import matplotlib as mpl
from sklearn.manifold import MDSMDS()
# 将二维度平面中绘制的点转化成两个元素(components)
# 设置为“precomputed”是因为我们提供的是距离矩阵
# 我们可以将“random_state”具体化来达到重复绘图的目的
mds = MDS(n_components=2, dissimilarity="precomputed", random_state=1)
pos = mds.fit_transform(dist) # 形如 (n_components, n_samples)
xs, ys = pos[:, 0], pos[:, 1]##可视化聚类
# 用字典设置每个聚类的颜色
cluster_colors = {0: '#1b9e77', 1: '#d95f02', 2: '#7570b3', 3: '#e7298a', 4: '#66a61e'}
# 用字典设置每个聚类名称
cluster_names = {0: 'Family, home, war',1: 'Police, killed, murders',2: 'Father, New York, brothers',3: 'Dance, singing, love',4: 'Killed, soldiers, captain'}# 用 MDS 后的结果加上聚类编号和绘色创建 DataFrame
df = pd.DataFrame(dict(x=xs, y=ys, label=clusters, title=titles))# 聚类归类
groups = df.groupby('label')# 设置绘图
fig, ax = plt.subplots(figsize=(17, 9)) # 设置大小
ax.margins(0.05) # 可选项,只添加 5% 的填充(padding)来自动缩放(auto scaling)。# 对聚类进行迭代并分布在绘图上
# 我用到了 cluster_name 和 cluster_color 字典的“name”项,这样会返回相应的 color 和 label
for name, group in groups:ax.plot(group.x, group.y, marker='o', linestyle='', ms=12,label=cluster_names[name], color=cluster_colors[name],mec='none')ax.set_aspect('auto')ax.tick_params(axis='x', # 使用 x 坐标轴which='both', # 同时使用主刻度标签(major ticks)和次刻度标签(minor ticks)bottom='off', # 取消底部边缘(bottom edge)标签top='off', # 取消顶部边缘(top edge)标签labelbottom='off')ax.tick_params(axis='y', # 使用 y 坐标轴which='both', # 同时使用主刻度标签(major ticks)和次刻度标签(minor ticks)left='off', # 取消底部边缘(bottom edge)标签top='off', # 取消顶部边缘(top edge)标签labelleft='off')ax.legend(numpoints=1) # 图例(legend)中每项只显示一个点# 在坐标点为 x,y 处添加影片名作为标签(label)
for i in range(len(df)):ax.text(df.ix[i]['x'], df.ix[i]['y'], df.ix[i]['title'], size=8)plt.show() # 展示绘图# 以下注释语句可以保存需要的绘图
# plt.savefig('clusters_small_noaxes.png', dpi=200)
plt.close()# 层次聚类
from scipy.cluster.hierarchy import ward, dendrogramlinkage_matrix = ward(dist) # 聚类算法处理之前计算得到的距离,用 linkage_matrix 表示
fig, ax = plt.subplots(figsize=(15, 20)) # 设置大小
ax = dendrogram(linkage_matrix, orientation="right", labels=titles)
plt.tick_params(axis='x', # 使用 x 坐标轴which='both', # 同时使用主刻度标签(major ticks)和次刻度标签(minor ticks)bottom='off', # 取消底部边缘(bottom edge)标签top='off', # 取消顶部边缘(top edge)标签labelbottom='off')plt.tight_layout() # 展示紧凑的绘图布局
# 注释语句用来保存图片
plt.savefig('ward_clusters.png', dpi=200) # 保存图片为 ward_clustersplt.close()
【英文文档聚类结果】
【中文文档聚类】
'''
中文聚类
'''import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import codecs
from scipy import ndimage
from sklearn import manifold, datasets
from sklearn import feature_extraction
from sklearn.feature_extraction.text import TfidfTransformer
from sklearn.feature_extraction.text import CountVectorizer
from sklearn.feature_extraction.text import HashingVectorizer####第一步 计算TFIDF##### 文档预料 空格连接
corpus = []# 读取预料 一行预料为一个文档
for line in open('F:/01_All_BHSpider_Content_Result.txt', 'r', encoding='utf8').readlines():# print linecorpus.append(line.strip())
# print corpus
# 将文本中的词语转换为词频矩阵 矩阵元素a[i][j] 表示j词在i类文本下的词频
vectorizer = CountVectorizer()# 该类会统计每个词语的tf-idf权值
transformer = TfidfTransformer()# 第一个fit_transform是计算tf-idf 第二个fit_transform是将文本转为词频矩阵
tfidf = transformer.fit_transform(vectorizer.fit_transform(corpus))# 获取词袋模型中的所有词语
word = vectorizer.get_feature_names()# 将tf-idf矩阵抽取出来,元素w[i][j]表示j词在i类文本中的tf-idf权重
weight = tfidf.toarray()# 打印特征向量文本内容
print('Features length:' + str(len(word)))
resName = "BHTfidf_Result.txt"
result = codecs.open(resName, 'w', 'utf-8')
for j in range(len(word)):result.write(word[j] + ' ')
result.write('\r\n\r\n')# 打印每类文本的tf-idf词语权重,第一个for遍历所有文本,第二个for便利某一类文本下的词语权重
for i in range(len(weight)):# print u"-------这里输出第", i, u"类文本的词语tf-idf权重------"for j in range(len(word)):# print weight[i][j],result.write(str(weight[i][j]) + ' ')result.write('\r\n\r\n')
result.close()####第二步 聚类Kmeans####
print('Start Kmeans:')
from sklearn.cluster import KMeansclf = KMeans(n_clusters=4) # 景区 动物 人物 国家
s = clf.fit(weight)
print(s)# 中心点
print(clf.cluster_centers_)# 每个样本所属的簇
label = [] # 存储1000个类标 4个类
print(clf.labels_)
i = 1
while i <= len(clf.labels_):print(i, clf.labels_[i - 1])label.append(clf.labels_[i - 1])i = i + 1# 用来评估簇的个数是否合适,距离越小说明簇分的越好,选取临界点的簇个数 958.137281791
print(clf.inertia_)####第三步 图形输出 降维####
from sklearn.decomposition import PCApca = PCA(n_components=2) # 输出两维
newData = pca.fit_transform(weight) # 载入N维
print(newData)# 5A景区
x1 = []
y1 = []
i = 0
while i < 400:x1.append(newData[i][0])y1.append(newData[i][1])i += 1# 动物
x2 = []
y2 = []
i = 400
while i < 600:x2.append(newData[i][0])y2.append(newData[i][1])i += 1# 人物
x3 = []
y3 = []
i = 600
while i < 800:x3.append(newData[i][0])y3.append(newData[i][1])i += 1# 国家
x4 = []
y4 = []
i = 800
while i < 1000:x4.append(newData[i][0])y4.append(newData[i][1])i += 1# 四种颜色 红 绿 蓝 黑
plt.plot(x1, y1, 'or')
plt.plot(x2, y2, 'og')
plt.plot(x3, y3, 'ob')
plt.plot(x4, y4, 'ok')
plt.show()
【中文文档聚类结果】