kmeas算法,最菜学法,一点一点注释学习

# K-means Algorithm is a clustering algorithm

import matplotlib.pyplot as plt
from matplotlib import colors as mcolors
import math
import random


class Cluster(object):
	def __init__(self, samples):
		if len(samples) == 0:
			raise Exception("错误,样本数为0,为空聚类")
		self.samples = samples
		self.n_dim = samples[0].n_dim

		# 检查是否所有样本点的纬度数都一样
		for sample in samples:  # 这里的 sample 相当于新定义的变量名,类似于int i,再加上for循环
			if sample.n_dim != self.n_dim:
				raise Exception("错误,聚类中的样本点的纬度数不一致")

		self.centroid = self.cal_centroid()  # 初始化聚类中心

	def __repr__(self):
		return str(self.samples)

	def update(self, samples):
		old_centroid = self.centroid
		self.samples = samples
		self.centroid = self.cal_centroid()  # 牛逼!又学了一招https://www.cnblogs.com/IT-LearnHall/p/9426327.html
		# 报错原因1.调用方法时忘记加上括号,导致参数无法传入
		# 一、不带括号时,调用的是这个函数本身 ,是整个函数体,是一个函数对象,不须等该函数执行完成
		# 二、带括号(参数或者无参),调用的是函数的执行结果,须等该函数执行完成的结果

		shift = get_distance(old_centroid, self.centroid)
		return shift

	def cal_centroid(self):  # 对于一组样本点,计算其样本中心点
		n_samples = len(self.samples)
		coords = [sample.coords for sample in self.samples]  # a.coords[i] 包含的是该层维度的具体数值
		unzipped = zip(*coords)
		centroid_coords = [math.fsum(d_list) / n_samples for d_list in unzipped]

		return Sample(centroid_coords)


class Sample(object):
	def __init__(self, coords):
		self.coords = coords  # 参数个数
		self.n_dim = len(coords)  # 维度

	def __repr__(self):
		return str(self.coords)


def get_distance(a, b):
	if a.n_dim != b.n_dim:
		raise Exception("错误!样本点纬度不同")

	acc_diff = 0.0
	for i in range(a.n_dim):
		square_diff = pow(a.coords[i] - b.coords[i], 2)
		acc_diff += square_diff

	distance = math.sqrt(acc_diff)

	return distance


def gen_random_sample(n_dim, low, high):  # 产生随机样本点
	sample = Sample([random.uniform(low, high) for _ in range(n_dim)])
	return sample


def kmeans(samples, k, cutoff):
	init_samples = random.sample(samples, k)  # 初始化K个样本中心点,从samples中选择K个样本点
	clusters = [Cluster([sample]) for sample in init_samples]  # 以init_sample中的样本点作为聚类中心,先创建K个聚类

	num_loop = 0.0  # Q:python 中这里写为0和0.0有什么区别呢?
	while True:
		lists = [[] for _ in clusters]  # 创建K个空列表待用

		num_loop += 1  # 循环次数计数
		for sample in samples:
			min_distance = get_distance(sample, clusters[0].centroid)
			cluster_index = 0

			for i in range(k - 1):
				distance = get_distance(sample, clusters[i + 1].centroid)
				if distance < min_distance:
					min_distance = distance
					cluster_index = i + 1

			lists[cluster_index].append(sample)

		max_shift = 0
		for i in range(k):
			shift = clusters[i].update(lists[i])
			# if shift > max_shift:
			#     max_shift = shift
			# 可再优化:
			max_shift = max(shift, max_shift)

		if max_shift < cutoff:
			print("第{}次迭代后,聚类稳定".format(num_loop))
			break

	return clusters


def run_main():

	# 这些main函数里面的参数名都是自己定义的,和上面的类还有方法都没有关系,只要在对应位置放进去就ok
	n_samples = 1000  # 样本数
	n_feat = 2        # 样本维度
	n_cluster = 100   # 聚类个数 <=> K
	cutoff = 0.2      # 聚类结束标志
	lower = 0         # 样本空间范围
	upper = 200

	samples = [gen_random_sample(n_feat, lower, upper) for _ in range(n_samples)]  # 创造样本列表
	clusters = kmeans(samples, n_cluster, cutoff)  # 对创造出来的样本点列表进行第一次聚类

	for i, c in enumerate(clusters):  # i:簇编号,c:簇集合
		for sample in c.samples:
			print("聚类--{},样本点--{}".format(i, sample))

	plt.subplot()
	color_names = list(mcolors.cnames)  # 元组转化为列表
	for i, c in enumerate(clusters):
		x = []
		y = []
		color = [color_names[i]] * len(c.samples)
		for sample in c.samples:  # 报错原因 2.这里也别忘记了 c. ,因为这里的元素是从当前c簇中选取的
			x.append(sample.coords[0])
			y.append(sample.coords[1])
		plt.scatter(x, y, c=color)  # 必须严格写 c = color,不然就有错误
		# 实际上,c=color 这里不写也可以,采用默认值,但是有一个不好的地方就是,有可能会出现各种相同颜色的簇,不利于分类效果的显示
	plt.show()


if __name__ == '__main__':
	run_main()


借鉴的版本

# K-means Algorithm is a clustering algorithm

import matplotlib.pyplot as plt
from matplotlib import colors as mcolors
import math
import random


class Cluster(object):
    # 类继承了object对象,拥有了好多可操作对象,这些都是类中的高级特性
    # https: // blog.csdn.net / DeepOscar / article / details / 80947155
    """
        聚类
    """

    def __init__(self, samples):
        if len(samples) == 0:
            # 如果聚类中无样本点
            raise Exception("错误:一个空的聚类!")
        # 类中的非静态方法第一个参数是 self 的才可以被实例调用
        # 类中带 self 的参数都是 实例 的,实例对这个参数拥有所有权,即实例中所有的方法都可以使用实例的参数
        # raise 手动抛出一个异常

        # 属于该聚类的样本点
        self.samples = samples

        # 该聚类中样本点的维度
        self.n_dim = samples[0].n_dim  # samples 是一个簇的样本点集合,其中每一个的取值通过[i]来索引查阅

        # 判断该聚类中所有样本点的维度是否相同
        for sample in samples:
            if sample.n_dim != self.n_dim:
                raise Exception("错误: 聚类中样本点的维度不一致!")

        # 设置初始化的聚类中心
        self.centroid = self.cal_centroid()

    def __repr__(self):
        """
            输出对象信息
        """
        return str(self.samples)

    def update(self, samples):
        """
            计算之前的聚类中心和更新后聚类中心的距离
        """

        old_centroid = self.centroid
        self.samples = samples
        self.centroid = self.cal_centroid()
        shift = get_distance(old_centroid, self.centroid)
        return shift

    def cal_centroid(self):
        """
           对于一组样本点计算其中心点
        """
        n_samples = len(self.samples)  # 求样本点总数

        # 获取所有样本点的坐标(特征),并用列表给它装起来[];
        coords = [sample.coords for sample in self.samples]  # 列表; coords : 坐标;
        unzipped = zip(*coords)  # zip <=> 压缩;  zip(*)<=> 解压
        # 计算每个维度的均值

        centroid_coords = [math.fsum(d_list) / n_samples for d_list in unzipped]
        #  math.fsum() 对迭代器里的每个元素进行求和操作

        return Sample(centroid_coords)


class Sample(object):
    """
        样本点类
    """

    def __init__(self, coords):  # coords相当于一个参数,因为这一层是函数,是包在class里面的!
        self.coords = coords  # 样本点包含的坐标: 这里是在 初始化属性 coords 和 n_dim
        self.n_dim = len(coords)  # 样本点维度

    def __repr__(self):  #  如果用户需要自定义类能实现“自我描述”的功能,就必须重写 __repr__() 方法
        """
            输出对象信息
        """
        return str(self.coords)  # 表明在调用该方法时,会返回;;转化成字符串


def get_distance(a, b):
    """
        返回样本点a, b的欧式距离
        参考:https://en.wikipedia.org/wiki/Euclidean_distance#n_dimensions
    """
    if a.n_dim != b.n_dim:
        # 如果样本点维度不同
        raise Exception("错误: 样本点维度不同,无法计算距离!")

    acc_diff = 0.0
    for i in range(a.n_dim):  # 对点坐标的每一个纬度进行遍历,其中a.coords[i]包含的是该层维度的数值
        square_diff = pow((a.coords[i] - b.coords[i]), 2)
        acc_diff += square_diff
    distance = math.sqrt(acc_diff)

    return distance


def gen_random_sample(n_dim, lower, upper):
    """
        生成随机样本
    """
    # 下面 [ ] 中是一个链表推导式
    # 下面的lower和upper是对应这个函数的形参来讲的,所以同名;
    sample = Sample([random.uniform(lower, upper) for _ in range(n_dim)])  # Sample()是一个类,在上一个类中,是样本电类
    return sample
# uniform() 方法将随机生成下一个实数,它在 [x, y) 范围内(左闭右开)
# '_'是一个循环标志,也可以用i,j 等其他字母代替,下面的循环中不会用到,起到的只是控制循环次数的作用


def kmeans(samples, k, cutoff):
    # kmeans函数

    # 随机选k个样本点作为初始聚类中心
    # random.sample(list, 5)  # 从list中随机获取5个元素,作为一个片断返回;(前提是list必须已知);
    # 变形:random.sample(range(0, 9), 4)  => 使得能够满足 从选定范围中选取任意个元素 的需求
    init_samples = random.sample(samples, k)    # random.sample 是一个固定方法,学一下

    # 创建k个聚类,聚类的中心分别为随机初始的样本点
    # 总的clusters结果是一个列表,每个 Cluster类 的参数是一个列表[sample];最后相当于"列表中含列表"?[ [] [] []...]
    clusters = [Cluster([sample]) for sample in init_samples]  # 初始聚类,每类中只含有一个点,最后输出的时候是分类好的样子

    # 迭代循环直到聚类划分稳定
    n_loop = 0
    while True:  # while循环体中使用 True 或者 False ,首字母大写~
        # 初始化一组空列表用于存储每个聚类内的样本点
        lists = [[] for _ in clusters]  # ***************************************

        # 开始迭代
        n_loop += 1
        # 遍历样本集中的每个样本
        for sample in samples:
            # 计算样本点sample和 第一个聚类の中心 的距离
            smallest_distance = get_distance(sample, clusters[0].centroid)
            # 初始化属于聚类 0
            cluster_index = 0

            # 计算和其他聚类中心的距离
            for i in range(k - 1):  # 从0到(k-1),相比于外层的0地址,要再加一,等价于<=>地址从1到k;
                # 计算样本点sample和聚类中心的距离(欧式距离)
                distance = get_distance(sample, clusters[i + 1].centroid)
                # 如果存在更小的距离,更新距离
                if distance < smallest_distance:
                    smallest_distance = distance
                    cluster_index = i + 1  # 这是用来定位的,表明最后会把这个点归类到哪一个样本中心点去,
                    # 这里i因为是第二层循环的缘故,所以0实际对应着下标1,物理实际位置为2

            # 找到最近的聚类中心,更新所属聚类
            lists[cluster_index].append(sample)  # list.append 方法

        # 初始化最大移动距离
        biggest_shift = 0.0

        # 计算本次迭代中,聚类中心移动的距离
        # range(k)是左闭右开:[0,k)
        for i in range(k):  # 对 K 个聚类都更新一下聚类中心,并求出K个聚类中,聚类中心移动距离最大的一个

            # update() (line44) : 计算之前的聚类中心和更新后聚类中心的距离
            # line128:clusters就是由Cluster而定义的列表,所以应该能直接用Cluster类里面的方法吧???
            # update方法 是求取一组样本点的样本中心点,所以参数是一簇样本点;line134,lists结构为[ [][][]... ]的形式
            shift = clusters[i].update(lists[i])  #

            # 记录最大移动距离
            biggest_shift = max(biggest_shift, shift)

        # 如果聚类中心移动的距离小于收敛阈值,即:聚类稳定
        if biggest_shift < cutoff:
            print("第{}次迭代后,聚类稳定。".format(n_loop))
            break  # 跳出while True这一循环体
    # 返回聚类结果
    return clusters


def run_main():
    """
        主函数
    """
    # 样本个数
    n_samples = 1000

    # 特征个数 (特征维度)
    n_feat = 2

    # 特征数值范围
    lower = 0
    upper = 200

    # 聚类个数
    n_cluster = 100

    # 生成随机样本   '_'是个"占位符",循环中不关心具体的元素内容,就是简单的“让它循环这么多次”
    samples = [gen_random_sample(n_feat, lower, upper) for _ in range(n_samples)]

    # 收敛阈值
    cutoff = 0.2

    clusters = kmeans(samples, n_cluster, cutoff)

    # 输出结果 enumerate(sequence, [start=0])
    # 用于将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列,
    # 同时列出数据和数据下标,一般用在 for 循环当中 : (下标,数据)
    #
    # 格式化字符串的函数 str.format()
    # "{1} {0} {1}".format("hello", "world")  # 设置指定位置
    for i, c in enumerate(clusters):  # 按簇,把每一簇内含有的元素都打印出来;enumerate:枚举
        for sample in c.samples:
            print('聚类--{},样本点--{}'.format(i, sample))  # 这里不设置指定位置

    # 可视化结果,直接学就好了
    # https://blog.csdn.net/tefuirnever/article/details/88944438
    # c可以是一个RGB或RGBA二维行数组
    plt.subplot()  # subplot() 函数允许你在同一图中绘制不同的东西:matplotlib.pyplot
    color_names = list(mcolors.cnames)  # list() 方法用于将 元组 转换为 列表 ,所以这里现在是一个列表 [ i ]
    for i, c in enumerate(clusters):
        x = []  # 学习python中数组的定义方式
        y = []
        # random.choice
        color = [color_names[i]] * len(c.samples)
        # 这里按照当前簇c中的元素个数,对color列表进行增殖
        # 以期在plt.scatter函数中,对应的是一组x和对应的一组y,则带入一个颜色列表,方能使得最后画图时,同一组点都是同颜色的
        # 用一个列表list1乘一个数字n 会得到一个新的列表list2, 这个列表的元素是list1的元素重复n次

        for sample in c.samples:
            x.append(sample.coords[0])  # 用于在列表末尾添加新的对象
            y.append(sample.coords[1])  # coords是坐标,后面的 [] 是指维度
        plt.scatter(x, y, c=color)  # 绘制散点图,其中X、Y是相同长度的数组序列;  一簇一簇地画
    plt.show()


if __name__ == '__main__':
    run_main()

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值