文本聚类算法之一趟聚类(One-pass Cluster)算法的python实现

news/2024/5/20 7:01:59 标签: 机器学习, 聚类

一、算法简介

一趟聚类算法是由蒋盛益教授提出的无监督聚类算法,该算法具有高效、简单的特点。数据集只需要遍历一遍即可完成聚类。算法对超球状分布的数据有良好的识别,对凸型数据分布识别较差。一趟聚类可以在大规模数据,或者二次聚类中,或者聚类与其他算法结合的情况下,发挥其高效、简单的特点;

 

算法流程:

1. 初始时从数据集读入一个新的对象

2. 以这个对象构建一个新的簇

3. 若达到数据集末尾,则转6,否则读入一个新的对象;计算它与每个已有簇之间的距离,并选择与它距离最小的簇。

4. 若最小距离超过给定的阈值r,转2

5. 否则将对象并入该簇,并更新簇心,转3

6. 结束

 

在本算法中,采用的是欧式距离公式计算节点与簇心之间的距离

欧式距离公式:



二、实验

实验数据集:

中国天气的数据集,包括中国各个城市每年的最低和最高温以及该城市的x,y坐标。

实验数据:

https://raw.githubusercontent.com/Hareric/ClusterTool/master/Other/c2.txt


python代码:

# coding=utf-8
import numpy as np
from math import sqrt
import time
import matplotlib.pylab as pl


# 定义一个簇单元
class ClusterUnit:
    def __init__(self):
        self.node_list = []  # 该簇存在的节点列表
        self.node_num = 0  # 该簇节点数
        self.centroid = None  # 该簇质心

    def add_node(self, node, node_vec):
        """
        为本簇添加指定节点,并更新簇心
         node_vec:该节点的特征向量
         node:节点
         return:null
        """
        self.node_list.append(node)
        try:
            self.centroid = (self.node_num * self.centroid + node_vec) / (self.node_num + 1)  # 更新簇心
        except TypeError:
            self.centroid = np.array(node_vec) * 1  # 初始化质心
        self.node_num += 1  # 节点数加1

    def remove_node(self, node):
        # 移除本簇指定节点
        try:
            self.node_list.remove(node)
            self.node_num -= 1
        except ValueError:
            raise ValueError("%s not in this cluster" % node)  # 该簇本身就不存在该节点,移除失败

    def move_node(self, node, another_cluster):
        # 将本簇中的其中一个节点移至另一个簇
        self.remove_node(node=node)
        another_cluster.add_node(node=node)

# cluster_unit = ClusterUnit()
# cluster_unit.add_node(1, [1, 1, 2])
# cluster_unit.add_node(5, [2, 1, 2])
# cluster_unit.add_node(3, [3, 1, 2])
# print cluster_unit.centroid


def euclidian_distance(vec_a, vec_b):
    # 计算向量a与向量b的欧式距离
    diff = vec_a - vec_b
    return sqrt(np.dot(diff, diff))  # dot计算矩阵内积


class OnePassCluster:
    def __init__(self, t, vector_list):
        # t:一趟聚类的阈值
        self.threshold = t  # 一趟聚类的阈值
        self.vectors = np.array(vector_list)
        self.cluster_list = []  # 聚类后簇的列表
        t1 = time.time()
        self.clustering()
        t2 = time.time()
        self.cluster_num = len(self.cluster_list)  # 聚类完成后 簇的个数
        self.spend_time = t2 - t1  # 聚类花费的时间

    def clustering(self):
        self.cluster_list.append(ClusterUnit())  # 初始新建一个簇
        self.cluster_list[0].add_node(0, self.vectors[0])  # 将读入的第一个节点归于该簇
        for index in range(len(self.vectors))[1:]:
            min_distance = euclidian_distance(vec_a=self.vectors[0],
                                              vec_b=self.cluster_list[0].centroid)  # 与簇的质心的最小距离
            min_cluster_index = 0  # 最小距离的簇的索引
            for cluster_index, cluster in enumerate(self.cluster_list[1:]):
                # enumerate会将数组或列表组成一个索引序列
                # 寻找距离最小的簇,记录下距离和对应的簇的索引
                distance = euclidian_distance(vec_a=self.vectors[index],
                                              vec_b=cluster.centroid)
                if distance < min_distance:
                    min_distance = distance
                    min_cluster_index = cluster_index + 1
            if min_distance < self.threshold:  # 最小距离小于阈值,则归于该簇
                self.cluster_list[min_cluster_index].add_node(index, self.vectors[index])
            else:  # 否则新建一个簇
                new_cluster = ClusterUnit()
                new_cluster.add_node(index, self.vectors[index])
                self.cluster_list.append(new_cluster)
                del new_cluster

    def print_result(self, label_dict=None):
        # 打印出聚类结果
        # label_dict:节点对应的标签字典
        print "*******  one-pass cluster result  ***********"
        for index, cluster in enumerate(self.cluster_list):
            print "cluster:%s" % index  # 簇的序号
            print cluster.node_list   # 该簇的节点列表
            if label_dict is not None:
                print " ".join([label_dict[n] for n in cluster.node_list])  # 若有提供标签字典,则输出该簇的标签
            print "node num: %s" % cluster.node_num
            print "-------------"
        print "the number of nodes %s" % len(self.vectors)
        print "the number of cluster %s" % self.cluster_num
        print "spend time %.9fs" % (self.spend_time / 1000)


# 读取测试集
temperature_all_city = np.loadtxt('c2.txt', delimiter=",", usecols=(3, 4))  # 读取聚类特征
xy = np.loadtxt('c2.txt', delimiter=",", usecols=(8, 9))  # 读取各地经纬度
f = open('c2.txt', 'r')
lines = f.readlines()
zone_dict = [i.split(',')[1] for i in lines]  # 读取地区并转化为字典
f.close()

# 构建一趟聚类器
clustering = OnePassCluster(vector_list=temperature_all_city, t=9)
clustering.print_result(label_dict=zone_dict)

# 将聚类结果导出图
fig, ax = pl.subplots()
fig = zone_dict
c_map = pl.get_cmap('jet', clustering.cluster_num)
c = 0
for cluster in clustering.cluster_list:
    for node in cluster.node_list:
        ax.scatter(xy[node][0], xy[node][1], c=c, s=30, cmap=c_map, vmin=0, vmax=clustering.cluster_num)
    c += 1
pl.show()


实验结果:



http://www.niftyadmin.cn/n/1436655.html

相关文章

【牛客网】今日头条2017客户端工程师实习生笔试题

一、[编程题]回文解码 现在有一个字符串&#xff0c;你要对这个字符串进行 n次操作&#xff0c;每次操作给出两个数字&#xff1a;(p, l)表示当前字符串中从下标为 p的字符开始的长度为 l的一个子串。你要将这个子串左右翻转后插在这个子串原来位置的正后方&#xff0c;求最后…

【牛客网】网易有道2017内推编程题

一、[编程题] 洗牌 洗牌在生活中十分常见&#xff0c;现在需要写一个程序模拟洗牌的过程。现在需要洗2n张牌&#xff0c;从上到下依次是第1张&#xff0c;第2张&#xff0c;第3张一直到第2n张。首先&#xff0c;我们把这2n张牌分成两堆&#xff0c;左手拿着第1张到第n张&#…

【牛客网】网易有道2017内推选择题

1、[单选题] 关于数据解析以下说法正确的是: A、XML数据结构有且只有一个根节点&#xff0c;并且不能嵌套 B、JSONObjetWithData:options:error:使用文件流 C、writeJSONObject:toStream:options:error:使用缓冲区数据解析json D、XML解析分为两种&#xff1a;SAX解析和DO…

python初步实现word2vec

一、前言 一开始看到word2vec环境的安装还挺复杂的&#xff0c;安了半天Cygwin也没太搞懂。后来突然发现&#xff0c;我为什么要去安c语言版本的呢&#xff0c;我应该去用python版本的&#xff0c;然后就发现了gensim&#xff0c;安装个gensim的包就可以用word2vec了&#xff…

GRU、LSTM、注意力机制(第八次组会)

GRU、LSTM、注意力机制(第八次组会) 一、 GRU二、 LSTM三、 深度RNN、双向RNN四、 注意力机制一、 GRU 二、 LSTM 三、 深度RNN、双向RNN

django在关闭debug后,静态文件无法加载的解决办法

用django开发了一个网站&#xff0c;现在想用笔记本写代码&#xff0c;用我的台式机看界面。所以需要开启远程访问django。一开始没有关闭debug&#xff0c;遇到了这个问题&#xff1a;DisallowedHost、Invalid http_host header。所以需要将台式机的IP添加进远程主机里面&…

【牛客网】网易2017秋招编程题集合

1、[编程题]回文序列 如果一个数字序列逆置之后跟原序列是一样的就称这样的数字序列为回文序列。例如&#xff1a; {1, 2, 1}, {15, 78, 78, 15} , {112} 是回文序列, {1, 2, 2}, {15, 78, 87, 51} ,{112, 2, 11} 不是回文序列。 现在给出一个数字序列&#xff0c;允许使用一…

2017年书单

时间过的真快&#xff0c;转眼就踏进了我的本命年~2017年注定是不平凡的一年&#xff0c;而我也要在这一年里找工作。对2016年的成绩不是很满意&#xff0c;所以本命年我一定要更加拼命地努力&#xff0c;争取可以找到一份心怡的工作&#xff0c;顺利地毕业。加油加油&#xff…