python代码实现K-means算法

0. 前言

     上一篇博客中,通过调用机器学习库sklearn实现了K-means算法;由于都被封装好了,虽然对算法思想清楚,但是内部实现的过程还需要拆开来自己实现以下可能才比较清楚,所以下面不通过调用本来的库来实现该算法。

1. python实现K-means

  1. 加载数据集
    首先,我们需要准备一个数据集。这里我们从文件加载数据集,此处附上该文件的网盘下载地址:testSet数据集    提取码:4pg1
    load_data_set()函数,实现将文本文件导入到一个列表中。
def load_data_set(fileName):
    """加载数据集"""
    dataSet = []  # 初始化一个空列表
    fr = open(fileName)
    for line in fr.readlines():
        # 按tab分割字段,将每行元素分割为list的元素
        curLine = line.strip().split('\t')
        # 其中map(float, curLine)表示把列表的每个值用float函数转成float型,并返回迭代器
        fltLine = map(float, curLine)
        dataSet.append(fltLine)
    return dataSet

     文本文件的每一行为tab分隔的浮点数,每一个列表会被添加到dataSet中,最后返回dataSet。这个返回值是一个包含许多其他列表的列表。
在这里插入图片描述

  1. 计算距离
    此处实现计算两个向量的欧氏距离
def distance_euclidean(vector1, vector2):
    """计算欧氏距离"""
    return sqrt(sum(power(vector1-vector2, 2)))  # 返回两个向量的距离
  1. 构建簇质心
    为给定的数据集构建一个包含k个随机质心的集合。随机质心要在整个数据集的边界内,可以通过找到数据集每一维的最小和最大值来完成。然后生成0到1.0之间的随机数并通过取值范围和最小值,确保随机点在数据的边界之内。
def rand_center(dataSet, k):
    """构建一个包含K个随机质心的集合"""
    n = shape(dataSet)[1]  # 获取样本特征值
    # 初始化质心,创建(k,n)个以0填充的矩阵
    centroids = mat(zeros((k, n)))  # 每个质心有n个坐标值,总共要k个质心
    # 遍历特征值
    for j in range(n):
        # 计算每一列的最小值
        minJ = min(dataSet[:, j])
        # 计算每一列的范围值
        rangeJ = float(max(dataSet[:, j]) - minJ)
        # 计算每一列的质心,并将其赋给centroids
        centroids[:, j] = minJ + rangeJ * random.rand(k, 1)
    return centroids   # 返回质心

验证一下rand_center()函数能否正常运行,结合以上3部分代码:
在验证过程中,出现了一个错误:
TypeError: unsupported operand type(s) for -: ‘map’ and ‘map’
解决办法:将第一个加载数据集函数的fltLine = map(float, curLine)更改为fltLine = list(map(float, curLine))。因为在rangeJ = float(max(dataSet[:, j]) - minJ)是2个map类型的数据在相减,fltLine = map(float, curLine)在python2返回的是list型数据,在python3返回map型数据。
更改后的3部分总的代码:

from numpy import *


def load_data_set(fileName):
    """加载数据集"""
    dataSet = []  # 初始化一个空列表
    fr = open(fileName)
    for line in fr.readlines():
        # 按tab分割字段,将每行元素分割为list的元素
        curLine = line.strip().split('\t')
        # 用list函数把map函数返回的迭代器遍历展开成一个列表
        # 其中map(float, curLine)表示把列表的每个值用float函数转成float型,并返回迭代器
        fltLine = list(map(float, curLine))
        dataSet.append(fltLine)
    return dataSet


def distance_euclidean(vector1, vector2):
    """计算欧氏距离"""
    return sqrt(sum(power(vector1-vector2, 2)))  # 返回两个向量的距离


def rand_center(dataSet, k):
    """构建一个包含K个随机质心的集合"""
    n = shape(dataSet)[1]  # 获取样本特征值
    # 初始化质心,创建(k,n)个以0填充的矩阵
    centroids = mat(zeros((k, n)))  # 每个质心有n个坐标值,总共要k个质心
    # 遍历特征值
    for j in range(n):
        # 计算每一列的最小值
        minJ = min(dataSet[:, j])
        # 计算每一列的范围值
        rangeJ = float(max(dataSet[:, j]) - minJ)
        # 计算每一列的质心,并将其赋给centroids
        centroids[:, j] = minJ + rangeJ * random.rand(k, 1)
    return centroids   # 返回质心


datMat = mat(load_data_set('testSet.txt'))
print(datMat.shape)  # 80个样本,二维数据集
print("第一列最小:", min(datMat[:, 0]))  # 取所有集合第0个数据中的最小,即第一维中的最小
print("第二列最小:", min(datMat[:, 1]))
print("第一列最大:", max(datMat[:, 0]))
print("第二列最大:", max(datMat[:, 1]))
# 随机生成4个min-max之间的值
print(rand_center(datMat, 4))

在这里插入图片描述

  1. K-means聚类算法
    k_means()函数接收4个输入参数,其中数据集和簇的数目是必选参数。该函数最开始要确定数据集中数据点的总数,接着创建一个矩阵来存储每个点的簇分配结果。簇分配结果矩阵clusterAssment包含两列:一列记录簇索引值,一列存储误差;即第一列存放该数据所属中心点,第二列是该数据到中心点的距离。再“计算质心-分配-重新计算”反复迭代,直至所有数据点的簇分配结果不再改变。
def k_means(dataSet,k,distMeas = distance_euclidean,creatCent = rand_center):
    """K-means聚类算法"""
    m = shape(dataSet)[0] # 行数
    # 建立簇分配结果矩阵,第一列存放该数据所属中心点,第二列是该数据到中心点的距离
    clusterAssment = mat(zeros((m, 2)))
    centroids = creatCent(dataSet, k) # 质心,即聚类点
    # 用来判定聚类是否收敛
    clusterChanged = True
    while clusterChanged:
        clusterChanged = False
        for i in range(m):  # 把每一个数据划分到离他最近的中心点
            minDist = inf # 无穷大
            minIndex = -1 #初始化
            for j in range(k):
                # 计算各点与新的聚类中心的距离
                distJI = distMeas(centroids[j,:],dataSet[i,:])
                if distJI < minDist:
                    # 如果第i个数据点到第j中心点更近,则将i归属为j
                    minDist = distJI
                    minIndex = j
            # 如果分配发生变化,则需要继续迭代
            if clusterAssment[i,0] != minIndex:
                clusterChanged = True
            # 并将第i个数据点的分配情况存入字典
            clusterAssment[i,:] = minIndex,minDist**2
        print(centroids)
        for cent in range(k):  # 重新计算中心点
            # 去第一列等于cent的所有列
            ptsInClust = dataSet[nonzero(clusterAssment[:, 0].A == cent)[0]]
            # 算出这些数据的中心点
            centroids[cent, :] = mean(ptsInClust, axis=0)
    return centroids, clusterAssment
  1. 完整代码
from numpy import *


def load_data_set(fileName):
    """加载数据集"""
    dataSet = []  # 初始化一个空列表
    fr = open(fileName)
    for line in fr.readlines():
        # 按tab分割字段,将每行元素分割为list的元素
        curLine = line.strip().split('\t')
        # 用list函数把map函数返回的迭代器遍历展开成一个列表
        # 其中map(float, curLine)表示把列表的每个值用float函数转成float型,并返回迭代器
        fltLine = list(map(float, curLine))
        dataSet.append(fltLine)
    return dataSet

def distance_euclidean(vector1, vector2):
    """计算欧氏距离"""
    return sqrt(sum(power(vector1-vector2, 2)))  # 返回两个向量的距离


def rand_center(dataSet, k):
    """构建一个包含K个随机质心的集合"""
    n = shape(dataSet)[1]  # 获取样本特征值


    # 初始化质心,创建(k,n)个以0填充的矩阵
    centroids = mat(zeros((k, n)))  # 每个质心有n个坐标值,总共要k个质心
    # 遍历特征值
    for j in range(n):
        # 计算每一列的最小值
        minJ = min(dataSet[:, j])
        # 计算每一列的范围值
        rangeJ = float(max(dataSet[:, j]) - minJ)
        # 计算每一列的质心,并将其赋给centroids
        centroids[:, j] = minJ + rangeJ * random.rand(k, 1)
    return centroids   # 返回质心


def k_means(dataSet,k,distMeas = distance_euclidean,creatCent = rand_center):
    """K-means聚类算法"""
    m = shape(dataSet)[0] # 行数
    # 建立簇分配结果矩阵,第一列存放该数据所属中心点,第二列是该数据到中心点的距离
    clusterAssment = mat(zeros((m, 2)))
    centroids = creatCent(dataSet, k) # 质心,即聚类点
    # 用来判定聚类是否收敛
    clusterChanged = True
    while clusterChanged:
        clusterChanged = False
        for i in range(m):  # 把每一个数据划分到离他最近的中心点
            minDist = inf # 无穷大
            minIndex = -1 #初始化
            for j in range(k):
                # 计算各点与新的聚类中心的距离
                distJI = distMeas(centroids[j,:],dataSet[i,:])
                if distJI < minDist:
                    # 如果第i个数据点到第j中心点更近,则将i归属为j
                    minDist = distJI
                    minIndex = j
            # 如果分配发生变化,则需要继续迭代
            if clusterAssment[i,0] != minIndex:
                clusterChanged = True
            # 并将第i个数据点的分配情况存入字典
            clusterAssment[i,:] = minIndex,minDist**2
        print(centroids)
        for cent in range(k):  # 重新计算中心点
            # 去第一列等于cent的所有列
            ptsInClust = dataSet[nonzero(clusterAssment[:, 0].A == cent)[0]]
            # 算出这些数据的中心点
            centroids[cent, :] = mean(ptsInClust, axis=0)
    return centroids, clusterAssment


datMat = mat(load_data_set('testSet.txt'))
myCentroids, clusterAssing = k_means(datMat, 4)
print(myCentroids)
print(clusterAssing)

运行结果:

在这里插入图片描述
在这里插入图片描述

2. 图形化展示

from numpy import *
from matplotlib import pyplot as plt

def load_data_set(fileName):
    """加载数据集"""
    dataSet = []  # 初始化一个空列表
    fr = open(fileName)
    for line in fr.readlines():
        # 按tab分割字段,将每行元素分割为list的元素
        curLine = line.strip().split('\t')
        # 用list函数把map函数返回的迭代器遍历展开成一个列表
        # 其中map(float, curLine)表示把列表的每个值用float函数转成float型,并返回迭代器
        fltLine = list(map(float, curLine))
        dataSet.append(fltLine)
    return dataSet

def distance_euclidean(vector1, vector2):
    """计算欧氏距离"""
    return sqrt(sum(power(vector1-vector2, 2)))  # 返回两个向量的距离


def rand_center(dataSet, k):
    """构建一个包含K个随机质心的集合"""
    n = shape(dataSet)[1]  # 获取样本特征值


    # 初始化质心,创建(k,n)个以0填充的矩阵
    centroids = mat(zeros((k, n)))  # 每个质心有n个坐标值,总共要k个质心
    # 遍历特征值
    for j in range(n):
        # 计算每一列的最小值
        minJ = min(dataSet[:, j])
        # 计算每一列的范围值
        rangeJ = float(max(dataSet[:, j]) - minJ)
        # 计算每一列的质心,并将其赋给centroids
        centroids[:, j] = minJ + rangeJ * random.rand(k, 1)
    return centroids   # 返回质心


def k_means(dataSet,k,distMeas = distance_euclidean,creatCent = rand_center):
    """K-means聚类算法"""
    m = shape(dataSet)[0] # 行数
    # 建立簇分配结果矩阵,第一列存放该数据所属中心点,第二列是该数据到中心点的距离
    clusterAssment = mat(zeros((m, 2)))
    centroids = creatCent(dataSet, k) # 质心,即聚类点
    # 用来判定聚类是否收敛
    clusterChanged = True
    while clusterChanged:
        clusterChanged = False
        for i in range(m):  # 把每一个数据划分到离他最近的中心点
            minDist = inf # 无穷大
            minIndex = -1 #初始化
            for j in range(k):
                # 计算各点与新的聚类中心的距离
                distJI = distMeas(centroids[j,:],dataSet[i,:])
                if distJI < minDist:
                    # 如果第i个数据点到第j中心点更近,则将i归属为j
                    minDist = distJI
                    minIndex = j
            # 如果分配发生变化,则需要继续迭代
            if clusterAssment[i,0] != minIndex:
                clusterChanged = True
            # 并将第i个数据点的分配情况存入字典
            clusterAssment[i,:] = minIndex,minDist**2
        print(centroids)
        for cent in range(k):  # 重新计算中心点
            # 去第一列等于cent的所有列
            ptsInClust = dataSet[nonzero(clusterAssment[:, 0].A == cent)[0]]
            # 算出这些数据的中心点
            centroids[cent, :] = mean(ptsInClust, axis=0)
    return centroids, clusterAssment


# datMat = mat(load_data_set('testSet.txt'))
# myCentroids, clusterAssing = k_means(datMat, 4)
# print(myCentroids)
# print(clusterAssing)
datMat = mat(load_data_set('testSet.txt'))
myCentroids, clusterAssing = k_means(datMat, 4)
plt.scatter(array(datMat)[:, 0], array(datMat)[:, 1], c=array(clusterAssing)[:, 0].T)
plt.scatter(myCentroids[:, 0].tolist(), myCentroids[:, 1].tolist(), c="r")
plt.show()

效果图:
在这里插入图片描述

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值