1.K-Means++算法介绍

K-Means++主要解决初始化种子点的问题,其选择初始种子的基本思想是:初始聚类中心之间相互距离要在K-Means算法基础上引入了更智能的初始化步骤,该步骤倾向于选择彼此相距较远的中心点,这一改进使得K-means算法收敛到次优解的可能性很小。K-Means++算法表明,更智能的初始化步骤所需计算量是值得的,因为他可以大大减少寻找最优解所需运行算法的次数。

2.K-Means算法实现步骤

1.取一个中心点C1,从数据集中随机选择一个中心点。

2.计算每个样本与当前已有聚类中心之间的最短距离,用D(x)表示。然后计算每个样本被选为下一个聚类中心的概率,公式如下图所示,最后按轮盘法选出下一个聚类中心:

AGNES聚类算法代码 聚类算法的实例_聚类

3.重复上一步,直到选择了所有K个中心点。

4.初始化聚类中心到此为止,接下来继续通过K-Means算法进行求解。

AGNES聚类算法代码 聚类算法的实例_初始化_02

3.实现实例

数据集请在k-means聚类算法实现下载

# coding: utf-8
import math
import random
from sklearn import datasets
import numpy as np
import pandas as pd

def euler_distance(point1: list, point2: list) -> float:
    """
    计算两点之间的欧拉距离,支持多维
    """
    distance = 0.0
    for a, b in zip(point1, point2):
        distance += math.pow(a - b, 2)
    return math.sqrt(distance)

def get_closest_dist(point, centroids):
    min_dist = math.inf  # 初始设为无穷大
    for i, centroid in enumerate(centroids):
        dist = euler_distance(centroid, point)
        if dist < min_dist:
            min_dist = dist
    return min_dist

def kpp_centers(data_set: list, k: int) -> list:
    """
    从数据集中返回 k 个对象可作为质心
    """
    cluster_centers = []
    cluster_centers.append(random.choice(data_set))#随机返回一个数据记录
    d = [0 for _ in range(len(data_set))]  # 初始化d为0,长度为数据集的长度
    for _ in range(1, k):
        total = 0.0
        for i, point in enumerate(data_set):
            d[i] = get_closest_dist(point, cluster_centers) # 与最近一个聚类中心的距离
            total += d[i]
        total *= random.random()
        for i, di in enumerate(d): # 轮盘法选出下一个聚类中心;
            total -= di
            if total > 0:
                continue
            cluster_centers.append(data_set[i])
            break
    return cluster_centers


def kmeans(data, k, cent):
    '''
    kmeans算法求解聚类中心
    :param data: 训练数据
    :param k: 聚类中心的个数
    :param cent: 随机初始化的聚类中心
    :return: 返回训练完成的聚类中心和每个样本所属的类别
    '''
    m, n = np.shape(data)  # m:样本的个数;n:特征的维度
    subCenter = np.array(np.zeros((m)))  # 初始化每个样本所属的类别
    change = True  # 判断是否需要重新计算聚类中心
    while change == True:
        change = False  # 重置
        for i in range(m):
            minDist = np.inf  # 设置样本与聚类中心的最小距离,初始值为正无穷
            minIndex = 0  # 所属的类别
            for j in range(k):
                # 计算i和每个聚类中心的距离
                dist = euler_distance(list(data[i]), list(cent[j]))
                if dist < minDist:
                    minDist = dist
                    minIndex = j
            # 判断是否需要改变
            if subCenter[i] != minIndex:  # 需要改变
                change = True
                subCenter[i] = minIndex
        # 重新计算聚类中心
        for j in range(k):
            sum_all = np.mat(np.zeros((1, n)))#n是特征的维度
            r = 0  # 每个类别中样本的个数
            for i in range(m):
                if subCenter[i] == j:  # 计算第j个类别
                    sum_all += data[i]
                    r += 1
            print(str(r)+":"+str(j))
            for z in range(n):
                try:
                    cent[j][z] = sum_all[0, z] / r
                except:
                    print("ZeroDivisionError: division by zero")
    return subCenter, cent


if __name__ == "__main__":
    inputfile = 'data/consumption_data.xls'  # 销量及其他属性数据
    data = pd.read_excel(inputfile, index_col='Id')  # 读取数据
    data_zs = 1.0 * (data - data.mean()) / data.std()  # 数据标准化

    cluster_centers = kpp_centers(data_zs.values.tolist(), 4)
    subCenter, cent = kmeans(data_zs.values.tolist(),4,cluster_centers)
    for i in range(500):
        subCenter, cent = kmeans(data_zs.values.tolist(), 4, cent)

    print(subCenter)
    subCenter = pd.Series(subCenter,index = data.index)

    r = pd.concat([data, subCenter], axis=1)  # 横向连接(0是纵向),得到聚类中心对应的类别下的数目
    r.columns = list(data.columns) + ['类别']  # 重命名表头

    print(r)

    from sklearn.manifold import TSNE

    tsne = TSNE(random_state=105)
    tsne.fit_transform(data_zs)  # 进行数据降维
    print(tsne.embedding_)
    tsne = pd.DataFrame(tsne.embedding_, index=data_zs.index)  # 转换数据格式

    import matplotlib.pyplot as plt

    plt.rcParams['font.sans-serif'] = ['SimHei']
    plt.rcParams['axes.unicode_minus'] = False  # 用来正常显示负号
    # 不同类别用不同颜色和样式绘图
    print(r['类别'] == 0)
    d = tsne[r['类别'] == 0]
    plt.plot(d[0], d[1], 'r.')
    d = tsne[r['类别'] == 1]
    plt.plot(d[0], d[1], 'go')
    d = tsne[r['类别'] == 2]
    plt.plot(d[0], d[1], 'b*')
    d = tsne[r['类别'] == 3]
    plt.plot(d[0], d[1], 'y*')
    plt.show()