机器学习实战(一):Document clustering 文档聚类

机器学习实战(一):Document clustering 文档聚类

在这里插入图片描述

更多Ai资讯:公主号AiCharm
在这里插入图片描述

1. 简介

  文档聚类是指根据文档的文本和语义背景将其归入不同的组别。它是一种无监督的技术,因为我们没有文件的标签,它在信息检索和搜索引擎中得到了应用。

  为了根据文档的内容进行分类,我决定使用K-手段算法。由于项目是没有标签的,这显然是一个无监督的学习问题,最好的解决方案之一应该是K-Means。当然,我们可以使用不同的算法,如高斯混合模型,甚至深度学习方法,如自动编码器。我将使用python与Jupyter笔记本,将代码和结果与文档结合起来。

  我在Anaconda环境下开发代码,并使用了以下依赖:

  • Pandas 库用于数据处理
  • Sklearn库用于机器学习和预处理
  • Matplotlib 库用于绘图
  • Ntlk库用于自然语言算法
  • BeautifulSoup库用于从 xml 文件中解析文本并删除类别

2.数据解析

  函数parseXML使用xml.etree.ElementTree来解析数据。我决定只使用项目的标题和描述来进行聚类,这与语义学最相关。由于描述不是原始文本,我们用BeautifulSoup库提取文本,我已经提到过。此外,我们还放弃了那些描述非常小的项目,因为它们影响了最终的聚类。我们可以认为它们都属于一个额外的聚类。当然,还有一些方法可以包括它们,但我暂时没有使用它们。

import xml.etree.ElementTree as ET
import pandas as pd

import nltk
from sklearn.cluster import KMeans
from sklearn.externals import joblib
from sklearn.feature_extraction.text import TfidfVectorizer
from sklearn.metrics.pairwise import cosine_similarity

nltk.download('punkt')
from bs4 import BeautifulSoup
from nltk import SnowballStemmer
import re

def parseXML(xmlfile):

    tree = ET.parse(xmlfile)
    root = tree.getroot()

    titles=[]
    descriptions=[]

    for item in root.findall('./channel/item'):

        for child in item:

            if(child.tag=='title' ):
                titles.append(child.text)

            if (child.tag == 'description' ):

                soup = BeautifulSoup(str(child.text).encode('utf8','ignore'), "lxml")
                strtext=soup.text.replace(u'\xa0', u' ').replace('\n',' ')

                descriptions.append(strtext)

    return titles,descriptions

#remove items with short descriptions
bef_titles,bef_descriptions = parseXML('data.source.rss-feeds.xml')
print('Count of items before dropping:' ,len(bef_titles))

titles=[]
descriptions=[]
for i in range(len(bef_titles)):

    if ( len(bef_descriptions[i]) > 500):
        titles.append(bef_titles[i])
        descriptions.append(bef_descriptions[i])

print('Count of items after:' ,len(titles))

[nltk_data] Downloading package punkt to
[nltk_data]     C:\Users\sergi\AppData\Roaming\nltk_data...
[nltk_data]   Package punkt is already up-to-date!
Count of items before dropping: 1662
Count of items after: 1130

3. 符号化和词根化

  下一步是将文本标记为单词,删除任何形态词缀,并删除冠词和介词等常用词。这可以通过ntlk的内置功能来完成。最后,我们得到两个不同的词汇表(一个标记化和词干化,一个只有标记化),我们将它们合并到一个pandas数据框架中。

def tokenize_and_stem(text):

    #tokenize
    tokens = [word for sent in nltk.sent_tokenize(text) for word in nltk.word_tokenize(sent)]
    filtered_tokens = []

    #keep only letters
    for token in tokens:
        if re.search('[a-zA-Z]', token):
            filtered_tokens.append(token)
    #stemming
    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
# nltk's English stopwords and stemmer
stemmer = SnowballStemmer("english")

#create steam and tokenized voucabularies
totalvocab_stemmed = []
totalvocab_tokenized = []

for i in descriptions:
    allwords_stemmed = tokenize_and_stem(i)
    totalvocab_stemmed.extend(allwords_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')
there are 481437 items in vocab_frame

4. 词向量化

  在我们将数据加载到K-手段算法之前,必须对其进行向量化。最流行的技术是Tdidf向量器,它根据文档中的单词频率创建一个矩阵,这就是我们要使用的技术。值得一提的是,作为未来的工作,word2vec和doc2vec可能会更有效地表示项目之间的关系。

#Tf-idf


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))

tfidf_matrix = tfidf_vectorizer.fit_transform(descriptions)
print('Td idf Matrix shape: ',tfidf_matrix.shape)

terms = tfidf_vectorizer.get_feature_names()

#calculate the distance matrix . I will use them in the visualization of the cluster.
dist = 1 - cosine_similarity(tfidf_matrix)
Td idf Matrix shape:  (1130, 74)

5.K means

  实际的聚类发生在这里,K means在Td-idf矩阵的基础上产生5个聚类。我们可以很容易地预测,这将不是一个最佳的解决方案,因为它只考虑到了文件中每个词的频率。

num_clusters = 5

km = KMeans(n_clusters=num_clusters)
km.fit(tfidf_matrix)

clusters = km.labels_.tolist()

  为了展示集群,我创建了一个由集群索引的pandas Dataframe。每个聚类的前6个词呈现在下面。我们注意到,这个聚类远非完美,因为有些词在一个以上的聚类中。另外,集群的语义内容之间也没有明确的区别。我们可以很容易地看到,与工作有关的词汇包括在多个聚类中。

items = { 'title': titles, 'description': descriptions}

frame = pd.DataFrame(items, index = [clusters] , columns = [ 'title','cluster'])


print("Top terms per cluster:")

# sort cluster centers by proximity to centroid
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]:  # replace 6 with n words per cluster
        print(' %s' % vocab_frame.ix[terms[ind].split(' ')].values.tolist()[0][0], end=',')


    print()

    #print("Cluster %d titles:" % i, end='')
    #for title in frame.ix[i]['title'].values.tolist():
       #print(' %s,' % title, end='')
Top terms per cluster:
Cluster 0 words: labour, employability, european, social, work, eu,
Cluster 1 words: occupational, sectors, skill, employability, services, workers,
Cluster 2 words: skill, job, labour, develop, market, cedefop,
Cluster 3 words: education, training, learning, vocational, education, cedefop,
Cluster 4 words: rates, unemployment, area, employability, increasingly, stated,

6.绘图

  为了实现聚类的可视化,我们应该首先降低其维度。我们用sklearn.manifold库中的t-SNE(t-Distributed Stochastic Neighbor Embedding)来实现。另一种方法是使用PCA或MDS(Multi-Demiensional Scaling)。

  绘图是用matplotlib库完成的。

import os  # for os.path.basename

import matplotlib.pyplot as plt
import matplotlib as mpl

from sklearn.manifold import TSNE


tsne = TSNE(n_components=2, verbose=1, perplexity=40, n_iter=300)
# dist is the distance matrix
pos = tsne.fit_transform(dist)

xs, ys = pos[:, 0], pos[:, 1]


cluster_colors = {0: '#1b9e77', 1: '#d95f02', 2: '#7570b3', 3: '#e7298a', 4: '#66a61e'}

cluster_names = {0: 'A',1: 'B',  2: 'C', 3: 'D',  4: 'E'}
[t-SNE] Computing pairwise distances...
[t-SNE] Computing 121 nearest neighbors...
[t-SNE] Computed conditional probabilities for sample 1000 / 1130
[t-SNE] Computed conditional probabilities for sample 1130 / 1130
[t-SNE] Mean sigma: 1.785805
[t-SNE] KL divergence after 100 iterations with early exaggeration: 0.947952
[t-SNE] Error after 125 iterations: 0.947952
%matplotlib inline

df = pd.DataFrame(dict(x=xs, y=ys, label=clusters, title=titles))

groups = df.groupby('label')

fig, ax = plt.subplots(figsize=(16,8) )


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.legend(numpoints=1)

#we do not present the tiles of items to not make the graph overwhelming
#for i in range(len(df)):
    #ax.text(df.ix[i]['x'], df.ix[i]['y'], df.ix[i]['title'], size=4)


plt.show()

在这里插入图片描述

  我们可以发现结果并不像我们最初想象的那样糟糕。虽然有一些部分的重叠,但各组是相当有区别的。然而,毫无疑问,我们可以进一步优化它们。

  我们应该注意,只有几个字的项目没有在图中显示出来。我还注意到,有一些项目是用不同于英语的语言写的。我们目前没有处理它们,因此,它们的分类实际上是随机的。图中有一些错位的点。

  此外,在数据清理和预处理方面还有很多工作要做。一种方法是优化tdidf矢量化的参数,使用doc2vec进行矢量化。或者我们可以使用另一种技术,如亲和传播、频谱聚类或最近的方法,如HDBSCAN和变异自动编码器。

更多Ai资讯:公主号AiCharm
在这里插入图片描述

  • 1
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

AiCharm

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

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

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

打赏作者

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

抵扣说明:

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

余额充值