《机器学习系统设计》之应用scikit-learn做文本分类(下)

前言:

    本系列是在作者学习《机器学习系统设计》([美] WilliRichert)过程中的思考与实践,全书通过Python从数据处理,到特征工程,再到模型选择,把机器学习解决问题的过程一一呈现。书中设计的源代码和数据集已上传到我的资源:http://download.csdn.net/detail/solomon1558/8971649

       第3章通过词袋模型+K均值聚类实现相关文本的匹配。本文主要讲解K-均值聚类相关知识以及在20newsgroup数据集上使用K-均值聚类进行测试。

    相关链接:《机器学习系统设计》之应用scikit-learn做文本分类(上)

1. K-均值聚类算法

    K-均值是发现给定数据集的k个簇的算法。簇个数k由用户给定,每一个簇通过其质心(centroid),即簇中所有点的中心来描述。

    K-均值算法的工作流程为:首先随机确定k个初始点作为质心;然后将数据集章的每个点分配到一个簇中,即为每个点找距其最近的质心,并将其分配给该质心对应簇;这一步完成后,每个簇的质心更新为该簇所有点的平均值。经过一定的迭代,当移动量低于一定阈值时,我们就认为聚类已经收敛了。

    上述过程的伪代码表示如下:

创建k个点作为起始质心(一般随机选择)

当任意一个点的簇分配结果发生改变时

   对数据集中每个数据点

      对每个质心

          计算质心与数据点之间的距离

      将数据点分配到距其最近的簇

   对每一个簇,计算簇中所有点的均值并将均值作为中心

    K-均值聚类算法的优缺点:

    优点:算法简单,易于实现

    缺点:可能收敛到局部最小值,在大规模数据集上收敛较慢。

2. K-均值例程

    本小节通过一个简单的例子来验证这个算法,这个例子包含只有两个词语的文档(源代码见附录)

《机器学习系统设计》之应用scikit-learn做文本分类(下)_第1张图片

    经过1次K均值迭代后,以任意3个向量作为起始点,经标签赋予余下的样本,然后更新簇的中心,使之成为该簇中所有数据点的中心点,我们得到以下聚类:

《机器学习系统设计》之应用scikit-learn做文本分类(下)_第2张图片

    由于簇中心的移动,我们必须重新分配簇的标签,并重新计算簇的中心点。在第二轮迭代之后,得到以下聚类:

《机器学习系统设计》之应用scikit-learn做文本分类(下)_第3张图片

    箭头显示了簇中心的移动。程序显示进过5次迭代后,簇中心点不在显著移动。(Scikit默认容许阈值为0.0001)。

    在聚类停止之后,我们只需记录下簇中心及其标识。当每个新文档进来的时候,我们对其向量化,并与所有的簇中心进行比较。我们得到与新文档向量距离最小的簇中心所在的簇,然后把这个簇分配给该新文档。这样新文档的向量只需与同一簇中的向量比较、匹配,大大减少计算量。

3. 在20newsgroup上进行文档匹配

    20newsgroup数据集是机器学习中的一个标准数据集。它包含18828个文档,来自于20个不同的新闻组。如果把每个新闻组看作是一个簇,那么很容易测试出我们寻找相关文档的方法是否有效。

    这个数据集可以从MLComp(http://mlcomp.org/datasets/379)下载,本人已将该数据集资源上传:http://download.csdn.net/detail/solomon1558/9007077

    该资源包含一个原信息文件和3个目录:test、train和raw。测试和训练目录将整个数据集切分为60%的训练和40%的测试文档。

3.1 读取数据

    Scikit已经包含了定制的读取器来读取这个数据集。在读取数据时,可以设置环境变量MLCOMP_DATASETS_HOME,或者通过mlcomp_root参数直接指定路径:

import sklearn.datasets
MLCOMP_DIR = r"E:\py_Space\ML_C3\data" dataset = sklearn.datasets.load_mlcomp("20news-18828", "train", mlcomp_root=MLCOMP_DIR)
print (dataset.filenames) 
print("Number of posts:", len(dataset.filenames))

['E:\\py_Space\\ML_C3\\data\\379\\train\\talk.politics.misc\\17860-178992'

 'E:\\py_Space\\ML_C3\\data\\379\\train\\sci.med\\12836-58920'

 'E:\\py_Space\\ML_C3\\data\\379\\train\\comp.graphics\\871-38995'...,

 'E:\\py_Space\\ML_C3\\data\\379\\train\\sci.space\\14129-61228'

 'E:\\py_Space\\ML_C3\\data\\379\\train\\soc.religion.christian\\15467-20879'

 'E:\\py_Space\\ML_C3\\data\\379\\train\\comp.sys.mac.hardware\\3919-52046']

('Number of posts:', 13180)

    通过设置函数load_mlcomp的第2个参数”train”、”predict”可以选取训练集或测试集。

    为方便起见,把范围限制在某些新闻组中,使整个实验流程更短。我们可以通过设置categories参数实现这一点:

groups = [
    'comp.graphics', 'comp.os.ms-windows.misc', 'comp.sys.ibm.pc.hardware',  'comp.sys.ma c.hardware', 'comp.windows.x', 'sci.space']
dataset = sklearn.datasets.load_mlcomp("20news-18828", "train",  mlcomp_root=MLCOMP_DIR,  categories=groups
                                       )

3.2 对帖子聚类

    由于真实的文本数据中存在很多噪声,甚至包含不合法的字符,这会导致UnicodeDecodeError报错,我们必须让向量化处理器忽略它们:

vectorizer = StemmedTfidfVectorizer(min_df=10, max_df=0.5,  # max_features=1000,  stop_words='english',  #charset_error=None  decode_error='replace'  )
vectorized = vectorizer.fit_transform(dataset.data)
num_samples, num_features = vectorized.shape
print("#samples: %d, #features: %d" % (num_samples, num_features))

#samples: 3414, #features: 4330

    注意书中采用参数charset_error,运行过程报错:

    TypeError: __init__() gotan unexpected keyword argument 'charset_error'

    应该换用decode_error=’replace’代替。

    输出结果显示现有一个大小为3414的文档池,每个文档的特征向量的维度是4430,这个矩阵就是K均值算法的输入。本实验中把簇的大小固定在50:

num_clusters = 50 # sp.unique(labels).shape[0] from sklearn.cluster import KMeans
km = KMeans(n_clusters=num_clusters, init='k-means++', n_init=1,  verbose=1)
clustered = km.fit(vectorized)

    在拟合之后,我们可以从km的成员变量中获得聚类信息。针对每个拟合过的向量,km.labels_都给出了一个对应的标签:

[27 22 43 ..., 1 44 21]

3.3 预测标签

    本小节介绍如何通过km.predict给新文档分配一个簇。

    首先将新文本向量化:

new_post = \
    """Disk drive problems. Hi, I have a problem with my hard disk. After 1 year it is working only sporadically now. I tried to format it, but now it doesn't boot any more. Any ideas? Thanks. """ new_post_vec = vectorizer.transform([new_post])
new_post_label = km.predict(new_post_vec)[0]

    得到新文档的聚类信息后,我们就不需要用new_post_vec和所有的训练文档的向量进行比较。相反,我们只需专注与同一簇中的帖子。从原始数据集中取出它们的索引:

similar_indices = (km.labels_ == new_post_label).nonzero()[0]

    括号中的比较操作可以得到一个布尔型数组,nonzero将这个数组转化为一个更小的数组,它只包含True元素索引。

    然后用similar_indices构建了一个文档列表,以及它们的相似度分值,并对其按相似度升序排序。

similar = []
for i in similar_indices:
    dist = sp.linalg.norm((new_post_vec - vectorized[i]).toarray())
    similar.append((dist, dataset.data[i])) 
similar = sorted(similar)

    打印出最相似的文档(show_at_1),最不相似的文档(show_at_3),以及它们之间的帖子(show_at_3),它们都来自于同一个簇。

show_at_1 = similar[0]
show_at_2 = similar[len(similar) / 2]
show_at_3 = similar[-1]

print(show_at_1)
print(show_at_2)
print(show_at_3)

4. 总结

    从聚类上的预处理,到把有噪文本转化为有意义的简洁向量表示的解决方案,这是一个艰难的过程。其中,我们为最终能够聚类所做的工作占了整个任务的一大部分。但是在这个过程中,我们学习到了很多关于文本处理的知识,以及简单词频统计在有噪声的真实数据上的有效应用。

附录

K-均值聚类例程:

plot_kmeans_exaple.py

# inspired by http://scikit-
# learn.org/dev/auto_examples/cluster/plot_kmeans_digits.html#example-
# cluster-plot-kmeans-digits-py
import os
import scipy as sp
from scipy.stats import norm
from matplotlib import pylab
from sklearn.cluster import KMeans

seed = 2
sp.random.seed(seed)  # to reproduce the data later on
num_clusters = 3

def plot_clustering(x, y, title, mx=None, ymax=None, xmin=None, km=None):
    pylab.figure(num=None, figsize=(8, 6))
    if km:
        pylab.scatter(x, y, s=50, c=km.predict(list(zip(x, y))))
    else:
        pylab.scatter(x, y, s=50)

    pylab.title(title)
    pylab.xlabel("Occurrence word 1")
    pylab.ylabel("Occurrence word 2")
    # pylab.xticks([w*7*24 for w in range(10)], ['week %i'%w for w in range(10)])
    pylab.autoscale(tight=True)
    pylab.ylim(ymin=0, ymax=1)
    pylab.xlim(xmin=0, xmax=1)
    pylab.grid(True, linestyle='-', color='0.75')
    return pylab

xw1 = norm(loc=0.3, scale=.15).rvs(20)
yw1 = norm(loc=0.3, scale=.15).rvs(20)

xw2 = norm(loc=0.7, scale=.15).rvs(20)
yw2 = norm(loc=0.7, scale=.15).rvs(20)

xw3 = norm(loc=0.2, scale=.15).rvs(20)
yw3 = norm(loc=0.8, scale=.15).rvs(20)

x = sp.append(sp.append(xw1, xw2), xw3)
y = sp.append(sp.append(yw1, yw2), yw3)

i = 1
plot_clustering(x, y, "Vectors")
pylab.savefig(os.path.join("..", "1400_03_0%i.png" % i))
#pylab.show()
pylab.clf()

i += 1
#################### 1 iteration ####################

mx, my = sp.meshgrid(sp.arange(0, 1, 0.001), sp.arange(0, 1, 0.001))

km = KMeans(init='random', n_clusters=num_clusters, verbose=1,
            n_init=1, max_iter=1,
            random_state=seed)

km.fit(sp.array(list(zip(x, y))))
print(len(sp.array(list(zip(x, y)))))
#print(sp.array(list(zip(x, y))))

Z = km.predict(sp.c_[mx.ravel(), my.ravel()]).reshape(mx.shape)

plot_clustering(x, y, "Clustering iteration 1", km=km)
pylab.imshow(Z, interpolation='nearest',
           extent=(mx.min(), mx.max(), my.min(), my.max()),
           cmap=pylab.cm.Blues,
           aspect='auto', origin='lower')

c1a, c1b, c1c = km.cluster_centers_
pylab.scatter(km.cluster_centers_[:, 0], km.cluster_centers_[:, 1],
            marker='x', linewidth=2, s=100, color='black')
pylab.savefig(os.path.join("..", "1400_03_0%i.png" % i))
#pylab.show()
pylab.clf()

i += 1

#################### 2 iterations ####################
km = KMeans(init='random', n_clusters=num_clusters, verbose=1,
            n_init=1, max_iter=2,
            random_state=seed)
km.fit(sp.array(list(zip(x, y))))

Z = km.predict(sp.c_[mx.ravel(), my.ravel()]).reshape(mx.shape)

plot_clustering(x, y, "Clustering iteration 2", km=km)
pylab.imshow(Z, interpolation='nearest',
           extent=(mx.min(), mx.max(), my.min(), my.max()),
           cmap=pylab.cm.Blues,
           aspect='auto', origin='lower')

c2a, c2b, c2c = km.cluster_centers_
pylab.scatter(km.cluster_centers_[:, 0], km.cluster_centers_[:, 1],
            marker='x', linewidth=2, s=100, color='black')
# import pdb;pdb.set_trace()
pylab.gca().add_patch(
    pylab.Arrow(c1a[0], c1a[1], c2a[0] - c1a[0], c2a[1] - c1a[1], width=0.1))
pylab.gca().add_patch(
    pylab.Arrow(c1b[0], c1b[1], c2b[0] - c1b[0], c2b[1] - c1b[1], width=0.1))
pylab.gca().add_patch(
    pylab.Arrow(c1c[0], c1c[1], c2c[0] - c1c[0], c2c[1] - c1c[1], width=0.1))

pylab.savefig(os.path.join("..", "1400_03_0%i.png" % i))
pylab.clf()

i += 1

#################### 3 iterations ####################
km = KMeans(init='random', n_clusters=num_clusters, verbose=1,
            n_init=1, max_iter=10,
            random_state=seed)
km.fit(sp.array(list(zip(x, y))))

Z = km.predict(sp.c_[mx.ravel(), my.ravel()]).reshape(mx.shape)

plot_clustering(x, y, "Clustering iteration 10", km=km)
pylab.imshow(Z, interpolation='nearest',
           extent=(mx.min(), mx.max(), my.min(), my.max()),
           cmap=pylab.cm.Blues,
           aspect='auto', origin='lower')

pylab.scatter(km.cluster_centers_[:, 0], km.cluster_centers_[:, 1],
            marker='x', linewidth=2, s=100, color='black')
pylab.savefig(os.path.join("..", "1400_03_0%i.png" % i))
pylab.clf()

i += 1

在20newsgroup上进行文档匹配

rel_post_mlcomp.py

import sklearn.datasets
import scipy as sp

new_post = \
    """Disk drive problems. Hi, I have a problem with my hard disk.
After 1 year it is working only sporadically now.
I tried to format it, but now it doesn't boot any more.
Any ideas? Thanks.
"""

MLCOMP_DIR = r"E:\py_Space\ML_C3\data"
groups = [
    'comp.graphics', 'comp.os.ms-windows.misc', 'comp.sys.ibm.pc.hardware',
    'comp.sys.ma c.hardware', 'comp.windows.x', 'sci.space']
dataset = sklearn.datasets.load_mlcomp("20news-18828", "train",
                                       mlcomp_root=MLCOMP_DIR,
                                       categories=groups
                                       )
print (dataset.filenames)
print (len(dataset.filenames))
print("Number of posts:", len(dataset.filenames))

labels = dataset.target
num_clusters = 50  # sp.unique(labels).shape[0]

import nltk.stem
english_stemmer = nltk.stem.SnowballStemmer('english')

from sklearn.feature_extraction.text import TfidfVectorizer


class StemmedTfidfVectorizer(TfidfVectorizer):
    def build_analyzer(self):
        analyzer = super(TfidfVectorizer, self).build_analyzer()
        return lambda doc: (english_stemmer.stem(w) for w in analyzer(doc))

vectorizer = StemmedTfidfVectorizer(min_df=10, max_df=0.5,
                                    # max_features=1000,
                                    stop_words='english',
                                    #charset_error=None
                                    decode_error='replace'
                                    )
vectorized = vectorizer.fit_transform(dataset.data)
num_samples, num_features = vectorized.shape
print("#samples: %d, #features: %d" % (num_samples, num_features))


from sklearn.cluster import KMeans
km = KMeans(n_clusters=num_clusters, init='k-means++', n_init=1,
            verbose=1)

clustered = km.fit(vectorized)

from sklearn import metrics
print("Homogeneity: %0.3f" % metrics.homogeneity_score(labels, km.labels_))
print("Completeness: %0.3f" % metrics.completeness_score(labels, km.labels_))
print("V-measure: %0.3f" % metrics.v_measure_score(labels, km.labels_))
print("Adjusted Rand Index: %0.3f" %
      metrics.adjusted_rand_score(labels, km.labels_))
print("Adjusted Mutual Information: %0.3f" %
      metrics.adjusted_mutual_info_score(labels, km.labels_))
print(("Silhouette Coefficient: %0.3f" %
       metrics.silhouette_score(vectorized, labels, sample_size=1000)))

new_post_vec = vectorizer.transform([new_post])
new_post_label = km.predict(new_post_vec)[0]

similar_indices = (km.labels_ == new_post_label).nonzero()[0]
print new_post_label
print km.labels_
similar = []
for i in similar_indices:
    dist = sp.linalg.norm((new_post_vec - vectorized[i]).toarray())
    similar.append((dist, dataset.data[i]))

similar = sorted(similar)

show_at_1 = similar[0]
show_at_2 = similar[len(similar) / 2]
show_at_3 = similar[-1]

print(show_at_1)
print(show_at_2)
print(show_at_3)


import pdb
pdb.set_trace()

你可能感兴趣的:(python,k均值,文本聚类,scikit-learn,20newsgroup)