ML入门6.0 手写K-Means 聚类 (K-Means Clustering)
K-Means 简介
聚类算法通常被归类于无监督学习中,由于训练样本是未知的,目标是通过对无对无标记训练样本的学习来揭示数据的内在规律和性质,为进一步的数据分析提供基础。聚类试图将数据集中的样本划分为若干个不相交的子集,每个子集被称为一个簇,通过这样的划分,每个簇内的样本可能拥有相同或者类似的特征,从而对应于特定的类别。
k均值聚类是最著名的划分聚类算法,由于简洁和效率使得他成为所有聚类算法中最广泛使用的。给定一个数据点集合和需要的聚类数目k,k由用户指定,k均值算法根据某个距离函数反复把数据分入k个聚类中。 百度百科
K-Means应用场景十分广泛,如:物品传输优化,识别犯罪地点,客户分类,乘车数据分析,天文数据分析等
原理简介
聚类问题输入:数据集合
聚类问题输出:簇
聚类指标—距离
闵可夫斯基距离:
d i s t ( x i , x j ) = ( ∑ u = 1 n ∣ x i u − x j u ∣ p ) dist(x_{i},x_{j}) = (\sum_{u=1}^{n}|x_{iu} - x_{ju}|^{p}) dist(xi,xj)=(∑u=1n∣xiu−xju∣p)
当p=1时就是曼哈顿距离,p=2时就是欧式距离
Jaccard距离:
d i s t ( A , B ) = ∣ A ⋂ B ∣ ∣ A ⋃ B ∣ dist(A,B) = \frac{|A \bigcap B|}{|A \bigcup B|} dist(A,B)=∣A⋃B∣∣A⋂B∣
K-Means算法步骤
- 选择初始化的 k 个样本作为初始聚类中心, a = ( a 1 , a 2 , . . . , a k ) a = (a_{1}, a_{2},...,a_{k} ) a=(a1,a2,...,ak)
- 针对数据集中每个样本 x i x_{i} xi计算它到 k 个聚类中心的距离并将其分到距离最小的聚类中心所对应的类中;(其中 x i = ( x i 1 , x i 2 , … , x i n ) x_{i} = (x_{i1}, x_{i2},…,x_{in} ) xi=(xi1,xi2,…,xin))
- 针对每个类别 a j a_{j} aj , 重新计算它的聚类中心 a j = 1 m ∑ i = 1 m x i a_{j} = \frac{1}{m}\sum{i=1}^{m} x_{i} aj=m1∑i=1mxi
- 重复上面 2 3 两步操作,直到达到某个中止条件(迭代次数、最小误差变化等)
实现代码
Func1: loadDataSet(fileName) 加载数据集
def loadDataSet(fileName): # general function to parse tab -delimited floats
"""
加载数据
:param fileName:
:return: datamat
"""
dataMat = [] # assume last column is target value
fr = open(fileName)
for line in fr.readlines():
curLine = line.strip().split('\t')
fltLine = map(float, curLine) # map all elements to float()
dataMat.append(fltLine)
return dataMat
Func2: distEclud(vecA, vecB) 计算距离
def distEclud(vecA, vecB):
"""
计算欧式距离
:param vecA: 向量A
:param vecB: 向量B
:return: 距离
"""
return sqrt(sum(power(vecA - vecB, 2))) # la.norm(vecA-vecB)
Func3: randCent(dataSet, k)给出随机的聚类中心
def randCent(dataSet, k):
"""
随机聚类中心
:param dataSet: 数据集
:param k: 中心个数
:return: 中心点
"""
n = shape(dataSet)[1]
centroids = mat(zeros((k, n))) # create centroid mat
for j in range(n): # create random cluster centers, within bounds of each dimension
minJ = min(dataSet[:, j])
rangeJ = float(max(dataSet[:, j]) - minJ)
centroids[:, j] = mat(minJ + rangeJ * random.rand(k, 1))
return centroids
Func4: Kmeans(dataSet, k, distMeas=distEclud, createCent=randCent) kmeans算法实现
def kMeans(dataSet, k, distMeas=distEclud, createCent=randCent):
"""
Kmeans算法实现
:param dataSet:数据集
:param k: 中心个数
:param distMeas: 距离算法
:param createCent: 得到中心的算法
:return:
"""
m = shape(dataSet)[0]
clusterAssment = mat(zeros((m, 2))) # create mat to assign data points
# to a centroid, also holds SE of each point
centroids = createCent(dataSet, k)
clusterChanged = True
while clusterChanged:
clusterChanged = False
for i in range(m): # for each data point assign it to the closest centroid
minDist = inf;
minIndex = -1
for j in range(k):
distJI = distMeas(centroids[j, :], dataSet[i, :])
if distJI < minDist:
minDist = distJI;
minIndex = j
if clusterAssment[i, 0] != minIndex: clusterChanged = True
clusterAssment[i, :] = minIndex, minDist ** 2
print(centroids)
for cent in range(k): # recalculate centroids
ptsInClust = dataSet[nonzero(clusterAssment[:, 0].A == cent)[0]] # get all the point in this cluster
centroids[cent, :] = mean(ptsInClust, axis=0) # assign centroid to mean
return centroids, clusterAssment
Func5: clusterClubs(numClust=5) 对数据进行聚类并绘制相关图像
def clusterClubs(numClust=5):
"""
测试K-means并绘图
:param numClust:簇的个数
:return: 图像
"""
print("clusterClubs")
datList = []
for line in open('places.txt').readlines():
lineArr = line.split('\t')
datList.append([float(lineArr[4]), float(lineArr[3])])
datMat = mat(datList)
myCentroids, clustAssing = biKmeans(datMat, numClust, distMeas=distSLC)
fig = plt.figure()
rect = [0.1, 0.1, 0.8, 0.8]
scatterMarkers = ['s', 'o', '^', '8', 'p', \
'd', 'v', 'h', '>', '<']
axprops = dict(xticks=[], yticks=[])
ax0 = fig.add_axes(rect, label='ax0', **axprops)
imgP = plt.imread('Portland.png')
ax0.imshow(imgP)
ax1 = fig.add_axes(rect, label='ax1', frameon=False)
for i in range(numClust):
ptsInCurrCluster = datMat[nonzero(clustAssing[:, 0].A == i)[0], :]
markerStyle = scatterMarkers[i % len(scatterMarkers)]
ax1.scatter(ptsInCurrCluster[:, 0].flatten().A[0], ptsInCurrCluster[:, 1].flatten().A[0], marker=markerStyle,
s=90)
ax1.scatter(myCentroids[:, 0].flatten().A[0], myCentroids[:, 1].flatten().A[0], marker='+', s=300)
plt.show()
运行结果
优缺点
优点
容易理解,聚类效果不错,虽然是局部最优, 但往往局部最优就够了;
处理大数据集的时候,该算法可以保证较好的伸缩性;
当簇近似高斯分布的时候,效果非常不错;
算法复杂度低。
缺点
K 值需要人为设定,不同 K 值得到的结果不一样;
对初始的簇中心敏感,不同选取方式会得到不同结果;
对异常值敏感;
样本只能归为一类,不适合多分类任务;
不适合太离散的分类、样本类别不平衡的分类、非凸形状的分类。