You cannot select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

11 KiB

36 | 文本聚类:如何过滤冗余的新闻?

你好,我是黄申。

前两节,我讲了向量空间模型,以及如何在信息检索领域中运用向量空间模型。向量空间模型提供了衡量向量之间的距离或者相似度的机制,而这种机制可以衡量查询和被查询数据之间的相似程度,而对于文本检索来说,查询和文档之间的相似程度可作为文档的相关性。

实际上,除了文档的相关性,距离或者相似度还可以用在机器学习的算法中。今天,我们就来聊聊如何在聚类算法中使用向量空间模型,并最终实现过滤重复文章。

聚类算法

在概率统计模块中我们介绍了分类Classification/Categorization和回归Regression这两种监督式学习Supervised Learning。监督式学习通过训练资料学习并建立一个模型并依此模型对新的实例进行预测。

不过在实际场景中我们常常会遇到另一种更为复杂的情况。这时候不存在任何关于样本的先验知识而是需要机器在没人指导的情形下去将很多东西进行归类。由于缺乏训练样本这种学习被称为“非监督学习”Unsupervised Learning也就是我们通常所说的聚类Clustering。在这种学习体系中系统必须通过一种有效的方法发现样本的内在相似性并把数据对象以群组Cluster的形式进行划分。

谈到相似性,你可能已经想到了利用特征向量和向量空间模型,这确实是可行的方法。不过,为了让你全面了解在整个非监督式学习中,如何运用向量空间,让我先从一个具体的聚类算法开始。

这个算法的名称是K均值K-Means聚类算法它让我们可以在一个任意多的数据上得到一个事先定好群组数量K的聚类结果。这种算法的中心思想是尽量最大化总的群组内相似度同时尽量最小化群组之间的相似度。群组内或群组间的相似度是通过各个成员和群组质心相比较来确定的。想法很简单但是在样本数量达到一定规模后希望通过排列组合所有的群组划分来找到最大总群组内的相似度几乎是不可能的。于是人们提出如下的求近似解的方法。

  1. 从N个数据对象中随机选取k个对象作为质心这里每个群组的质心定义是群组内所有成员对象的平均值。因为是第一轮所以第i个群组的质心就是第i个对象而且这时候我们只有这一个组员。

  2. 对剩余的对象,测量它和每个质心的相似度,并把它归到最近的质心所属的群组。这里我们可以说距离,也可以说相似度,只是两者呈现反比关系。

  3. 重新计算已经得到的各个群组的质心。这里质心的计算是关键,如果使用特征向量来表示的数据对象,那么最基本的方法是取群组内成员的特征向量,将它们的平均值作为质心的向量表示。

  4. 迭代上面的第2步和第3步直至新的质心与原质心相等或相差之值小于指定阈值算法结束。

我以二维空间为例子,画张图来展示一下数据对象聚类的过程。

在这张图中,( a )、( b )、( c )三步分别展示了质心和群组逐步调整的过程。我们一一来看。(a)步骤是选择初始质心质心用不同颜色的x表示( b )步骤开始进行聚类,把点分配到最近的质心所在的组;( c )步骤重新计算每个群组的质心你会发现x的位置发生了改变。之后就是如此重复进入下一轮聚类。

总的来说K均值算法是通过不断迭代、调整K个聚类质心的算法。而质心或者群组的中心点是通过求群组所包含的成员之平均值来计算的。

使用向量空间进行聚类

明白了K均值聚类算法的核心思想再来理解向量空间模型在其中的运用就不难了。我还是以文本聚类为例讲讲如何使用向量空间模型和聚类算法去除重复的新闻。

我们在看新闻的时候一般都希望不断看到新的内容。可是由于现在的报道渠道非常丰富经常会出现热点新闻霸占版面的情况。假如我们不想总是看到重复的新闻应该怎么办呢有一种做法就是对新闻进行聚类那么内容非常类似的文章就会被聚到同一个分组然后对每个分组我们只选择1到2篇显示就够了。

基本思路确定后,我们可以把整个方法分为三个主要步骤。

第一步,把文档集合都转换成向量的形式。这块我上一节讲过了,你要是不记得了,可以自己回去复习一下。

第二步使用K均值算法对文档集合进行聚类。这个算法的关键是如何确定数据对象和分组质心之间的相似度。针对这点我们有两个点需要关注。

  • 使用向量空间中的距离或者夹角余弦度量,计算两个向量的相似度。

  • 计算质心的向量。K均值里质心是分组里成员的平均值。所以我们需要求分组里所有文档向量的平均值。求法非常直观就是分别为每维分量求平均值我把具体的计算公式列在这里

其中,x\_{i}表示向量的第i个分量x\_{ij}表示第j个向量的第i个分量,而j=1,2,…,n表示属于某个分组的所有向量。

第三步,在每个分类中,选出和质心最接近的几篇文章作为代表。而其他的文章作为冗余的内容过滤掉。

下面我使用Python里的sklearn库来展示使用欧氏距离的K均值算法。

Python中的K均值算法

在尝试下面的代码之前你需要看看自己的机器上是不是已经安装了scikit-learn。Scikit-learn是Python常用的机器学习库它提供了大量的机器学习算法的实现和相关的文档甚至还内置了一些公开数据集是我们实践机器学习算法的好帮手。

首先我使用sklearn库中的CountVectorizer对一个测试的文档集合构建特征也就是词典。这个测试集合有7句话2句关于篮球2句关于电影还有3句关于游戏。具体代码如下

from sklearn.feature_extraction.text import CountVectorizer

#模拟文档集合
corpus = ['I like great basketball game',
          'This video game is the best action game I have ever played',
          'I really really like basketball',
          'How about this movie? Is the plot great?',
          'Do you like RPG game?',
          'You can try this FPS game',
          'The movie is really great, so great! I enjoy the plot']

#把文本中的词语转换为词典和相应的向量
vectorizer = CountVectorizer()
vectors = vectorizer.fit_transform(corpus)

#输出所有的词条(所有维度的特征)
print('所有的词条(所有维度的特征)')
print(vectorizer.get_feature_names())
print('\n')

#输出(文章ID, 词条ID) 词频
print('(文章ID, 词条ID) 词频')
print(vectors)
print('\n')

从运行的结果中,你可以看到,整个词典里包含了哪些词,以及每个词在每个文档里的词频。

这里我们希望使用比词频tf更好的tf-idf机制TfidfTransformer可以帮助我们做到这点代码和注释如下

from sklearn.feature_extraction.text import TfidfTransformer

#构建tfidf的值
transformer = TfidfTransformer()
tfidf = transformer.fit_transform(vectorizer.fit_transform(corpus))

# 输出每个文档的向量
tfidf_array = tfidf.toarray()
words = vectorizer.get_feature_names()

for i in range(len(tfidf_array)):
    print ("*********第", i + 1, "个文档中所有词语的tf-idf*********")
    # 输出向量中每个维度的取值
    for j in range(len(words)):
        print(words[j], ' ', tfidf_array[i][j])
    print('\n')

运行的结果展示了每个文档中每个词的tfidf权重你可以自己手动验算一下。

最后我们就可以进行K均值聚类了。由于有篮球、电影和游戏3个类别我选择的K是3并在KMeans的构造函数中设置n_clusters为3。

from sklearn.cluster import KMeans

#进行聚类,在我这个版本里默认使用的是欧氏距离
clusters = KMeans(n_clusters=3)
s = clusters.fit(tfidf_array)

#输出所有质心点,可以看到质心点的向量是组内成员向量的平均值
print('所有质心点的向量')
print(clusters.cluster_centers_)
print('\n')

#输出每个文档所属的分组
print('每个文档所属的分组')
print(clusters.labels_)

#输出每个分组内的文档
dict = {}
for i in range(len(clusters.labels_)):
    label = clusters.labels_[i]
    if label not in dict.keys():
        dict[label] = []
        dict[label].append(corpus[i])
    else:
        dict[label].append(corpus[i])
print(dict)

为了帮助你的理解我输出了每个群组的质心也就是其中成员向量的平均值。最后我也输出了3个群组中所包含的句子。在我机器上的运行结果显示系统可以把属于3个话题的句子区分开来。如下所示

{2: ['I like great basketball game', 'I really really like basketball'], 0: ['This video game is the best action game I have ever played', 'Do you like RPG game?', 'You can try this FPS game'], 1: ['How about this movie? Is the plot great?', 'The movie is really great, so great! I enjoy the plot']}

不过由于KMeans具体的实现可能不一样而且初始质心的选择也有一定随机性所以你看到的结果可能稍有不同。

总结

这一节,我介绍了如何在机器学习的聚类算法中,使用向量空间模型。在聚类中,数据对象之间的相似度是很关键的。如果我们把样本转换为向量,然后使用向量空间中的距离或者夹角余弦,就很自然的能获得这种相似度,所以向量空间模型和聚类算法可以很容易的结合在一起。

为了给你加深印象我介绍了一个具体的K均值算法以及向量空间模型在其中所起到的作用并通过Python的sklearn代码演示了几个关键的步骤。

向量空间模型和K均值算法的结合虽然简单易懂但是一开始怎样选择这个群组的数量是个关键问题。我今天演示的测试数据很小而且主题划分的也非常明显。所以我选择聚类的数量为3。

可是在实际项目中对于一个新的数据集合选择多少比较合适呢如果这个K值取得太大群组可能切分太细每个之间区别不大。如果K值取得太小群组的粒度又太粗造成群组内差异比较明显。对非监督式的学习来说这个参数确实难以得到准确预估。我们可以事先在一个较小的数据集合上进行尝试然后根据结果和应用场景确定一个经验值。

思考题

今天我使用的是sklearn里的KMeans包它使用了向量间的欧氏距离来进行聚类。你可以尝试实现自己的K均值聚类并使用向量间的夹角余弦作为相似度的度量。

欢迎留言和我分享,也欢迎你在留言区写下今天的学习笔记。你可以点击“请朋友读”,把今天的内容分享给你的好友,和他一起精进。