自然语言处理中的文本聚类

       声明:代码的运行环境为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 unicode
    synopses_clean_wiki.append(text)

synopses_wiki = synopses_clean_wiki

genres = 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 unicode
    synopses_clean_imdb.append(text)

synopses_imdb = synopses_clean_imdb

synopses = []

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 stems


def 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 TfidfVectorizer

tfidf_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 KMeans

num_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 MDS

MDS()
# 将二维度平面中绘制的点转化成两个元素(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, dendrogram

linkage_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_clusters

plt.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 line
    corpus.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 KMeans

clf = 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 PCA

pca = 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()

【中文文档聚类结果】

 

  • 8
    点赞
  • 60
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

象在舞

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值