K-means(K均值)是一种无监督学习算法,也属于聚类算法,样本集预先不知道所属类别或标签,根据样本之间的距离或者相似程度自动进行分类。在经典k-means聚类算法中,映射是通过样本和样本和个中心之间的最小平方和最小准则来建立的。
求解目标函数是一个NP-hard问题,无法保证得到一个稳定的全局最优解。而经典的K-means(由Stuart Lloyd提出的)聚类算法中,采取迭代优化策略,有效求取局部最优解。算法包括四个步骤:
K-means聚类算法简洁快速,假设均方误差是计算群组分散度的最佳参数,对于满足正态分布的数据聚类效果很好。但其缺点也很明显:
用欧式距离实现图像分割:
import numpy as np
import cv2
#数据导入
def load_data(file_path):
'''导入数据
input: file_path(string):文件的存储位置
output: data(mat):数据
'''
data = []
#读取图片,转码后为矩阵,大小一般为m×n×3
img = cv2.imread(file_path)
#获得图片大小
m,n,_ = img.shape
#把图片展开,铺平
for i in range(m):
for j in range(n):
tmp = []
data.append(img[i,j,])
return np.mat(data),m,n
#定义相似性的度量
def distance(vecA, vecB):
'''计算vecA与vecB之间的欧式距离的平方
input: vecA(mat)A点坐标
vecB(mat)B点坐标
output: dist[0, 0](float)A点与B点距离的平方
'''
dist = (vecA - vecB) * (vecA - vecB).T
return dist[0, 0]
def randCenter(data, k):
'''随机初始化聚类中心
input: data(mat):训练数据
k(int):类别个数
output: centroids(mat):聚类中心
'''
# 属性的个数,也就是load_data里的n
_,n = data.shape
# 初始化k个聚类中心,设为0
centroids = np.mat(np.zeros((k, n)))
# 初始化聚类中心每一维的坐标
for j in range(n):
#求出每种特征值最小值,在图像里就是RGB取值最小值
minJ = np.min(data[:, j])
#特征值取值范围
rangeJ = np.max(data[:, j]) - minJ
# 在最大值和最小值之间随机初始化,公式为:c=min+rand(0,1)×(max-min)
centroids[:, j] = minJ * np.mat(np.ones((k , 1))) \
+ np.random.rand(k, 1) * rangeJ
return centroids
def kmeans(data, k, centroids):
'''根据KMeans算法求解聚类中心
input: data(mat):训练数据
k(int):类别个数
centroids(mat):随机初始化的聚类中心
output: centroids(mat):训练完成的聚类中心
subCenter(mat):每一个样本所属的类别
'''
# m:样本的个数,n:特征的维度
m, n = np.shape(data)
# 初始化每一个样本所属的类别,subCenter用来记录类别与相似度
subCenter = np.mat(np.zeros((m, 2)))
# 判断是否需要重新计算聚类中心
change = True
while change == True:
change = False # 重置
for i in range(m):
# 设置样本与聚类中心之间的最小的距离,初始值为正无穷
minDist = np.inf
minIndex = 0 # 所属的类别
for j in range(k):
# 计算i和每个聚类中心之间的距离
dist = distance(data[i, ], centroids[j, ])
if dist < minDist:
minDist = dist
minIndex = j
# 判断是否需要改变
if subCenter[i, 0] != minIndex: # 需要改变
change = True
subCenter[i, ] = np.mat([minIndex, minDist])
# 重新计算聚类中心
for j in range(k):
sum_all = np.mat(np.zeros((1, n)))
r = 0 # 每个类别中的样本的个数
for i in range(m):
if subCenter[i, 0] == j: # 计算第j个类别
sum_all += data[i, ]
r += 1
for z in range(n):
try:
#所以centroids是一个2维矩阵,每行为对应类别的中心
centroids[j, z] = sum_all[0, z] / r
except:
print(" r is zero" )
return subCenter, centroids
if __name__ == "__main__":
k = 4
file_path = 'city.jpg'
#导入数据
data,m,n = load_data(file_path)
#随机初始化聚类中心
centroids = randCenter(data, k,)
#聚类计算
subCenter, centroids = kmeans(data, k, centroids)
# 保存分割后的图片
new_pic = np.zeros((m*n, 3))
print(new_pic.shape)
for i in range(m*n):
for j in range(k):
if subCenter[i, 0] == j:
new_pic[i, :] = centroids[j, :]
new = np.reshape(new_pic,(m,n,3))
cv2.imwrite('new_pic.jpg', new)
我习惯性用OpenCV,故而导入它读取保存图片。这段程序计算的是RGB像素的中心,图片大小为400*300算法速度比较慢,下面是k=4时分割效果图。