1、场景
回想项目中遇到的,多商家信息融合的场景。我们如何把多商家的信息进行合理的匹配呢?首先想到的是人工匹配和程序匹配相结合。这样也可以实现,但费心费力,且实际匹配效果很差,如果商家融合信息呈指数级增长,这样的方式就太low了。
那如何把这个流程做的智能化些呢?我们可以引入机器学习中的聚类,它可以将商家相关属性进行聚集,并分类。
2、相关术语
簇: 所有数据点点集合,簇中对象相似。
质心: 簇中所有点的中心(计算所有点的均值).
3、距离计算
采用欧式距离,两点间直线距离.也可采用其他距离计算(后期会做相应的介绍)
4、评价指标: 平方误差和(SSE)
SSE 值越小,表示越接近它们的质心.由于代价函数(SSE)是非凸函数,所以在运用Kmeans算法时,不能保证收敛到一个全局的最优解,我们得到的一般是一个局部的最优解。
为了取得比较好的效果,用不同的初始质心运行,得到多个局部最优解,比较它们的SSE,选取SSE最小的那个。
5、算法
#加载数据
def loadDataSet(fileName):
dataMat = []
fr = open(fileName)
for line in fr.readlines():
curLine = line.strip().split('\t')
ftlLine = map(float,curLine) #数据初始化float类型
dataMat.append(ftlLine)
return dataMat
#欧式距离
def distEclud(vecA, vecB):
return sqrt(sum(power(vecA -vecB,2)))
# power 样本点平方((x1-x2)^2+(y1-y2)^2)
# sqrt 求和后开方
#构建k个随机质心
def randCent(dataSet, k):
n = shape(dataSet)[1] #获得列数
centroids = mat(zeros((k,n))) #k行n列的零阶矩阵
for j in range(n):
minJ = min(dataSet[:,j])
rangeJ = float(max(dataSet[:,j]) - minJ)
centroids[:,j] = minJ + rangeJ * random.rand(k,1)
return centroids
(1)K-均值
- 创建 k 个点作为起始质心(通常是随机选择)
- 当任意一个点的簇分配结果发生改变
- 对数据集中的每个数据点
- 对每个质心
- 计算质心与数据点之间的距离
- 将数据点分配到距其最近的簇
- 对每一个簇, 计算簇中所有点的均值并将均值作为质心
#Kmeans
def kMeans(dataSet, k, distMeas=distEclud, createCent=randCent):
m = shape(dataSet)[0] #样本数
clusterAssment = mat(zeros((m,2))) #获得m*2矩阵(一列簇分类结果,一列误差)
centroids = createCent(dataSet,k) #初始化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:
minDist = distJI; minIndex = j
#该样本划分到距离最近的簇
if clusterAssment[i,0] != minIndex: clusterChanged = True
clusterAssment[i,:] = minIndex,minDist**2
#每轮结束后调整簇心
for cent in range(k):
if clusterAssment[i, 0] != minIndex: clusterChanged = True
clusterAssment[i, :] = minIndex, minDist ** 2 #计算每列平均值
return centroids, clusterAssment
(2)二分K-均值
- 将所有点看成一个簇
- 当簇数目小雨 k 时
- 对于每一个簇
- 计算总误差
- 在给定的簇上面进行 KMeans 聚类(k=2)
- 计算将该簇一分为二之后的总误差
- 选择使得误差最小的那个簇进行划分操作
# k:簇个数 distMeas:距离生成器
def biKmeans(dataSet, k, distMeas=distEclud):
m = shape(dataSet)[0] #数据集矩阵的行数
clusterAssment = mat(zeros((m,2)))
centroid0 = mean(dataSet, axis=0).tolist()[0] # 创建一个初始簇
centList =[centroid0] #create a list with one centroid
for j in range(m): # 计算每个样本点到初始簇的距离
clusterAssment[j,1] = distMeas(mat(centroid0), dataSet[j,:])**2
while (len(centList) < k): # 迭代直到簇的数目等于k
lowestSSE = inf
for i in range(len(centList)):
# 尝试划分每一簇
ptsInCurrCluster = dataSet[nonzero(clusterAssment[:,0].A==i)[0],:]
centroidMat, splitClustAss = kMeans(ptsInCurrCluster, 2, distMeas)
sseSplit = sum(splitClustAss[:,1])
# 剩余数据集的误差
sseNotSplit = sum(clusterAssment[nonzero(clusterAssment[:,0].A!=i)[0],1])
# 记录总误差最小的那个簇
if (sseSplit + sseNotSplit) < lowestSSE:
bestCentToSplit = i
bestNewCents = centroidMat
bestClustAss = splitClustAss.copy()
lowestSSE = sseSplit + sseNotSplit
# 因为二分均值,所以结果簇编号只能是0或1,修改结果簇编号为:原簇号和新增簇号
bestClustAss[nonzero(bestClustAss[:,0].A == 1)[0],0] = len(centList)
bestClustAss[nonzero(bestClustAss[:,0].A == 0)[0],0] = bestCentToSplit
# 更新簇列表centlist和样本点分配簇结果矩阵clusterAssment
centList[bestCentToSplit] = bestNewCents[0,:].tolist()[0]
centList.append(bestNewCents[1,:].tolist()[0])
clusterAssment[nonzero(clusterAssment[:,0].A == bestCentToSplit)[0],:]= bestClustAss
return mat(centList), clusterAssment;
if __name__=="__main__":
dataMat = loadDataSet('testSet.txt')
randCent(mat(dataMat), 3)
#kMeans(mat(dataMat),3,distEclud,randCent)
#biKmeans(mat(dataMat),3,distEclud)
6、优缺点
优点: 容易实现
缺点:可能收敛到局部最小值, 在大规模数据集上收敛较慢,高维空间有局限
7、如何选取K(借鉴)
(1)分析数据
(2)基于变化的算法:即定义一个函数,随着K的改变,认为在正确的K时会产生极值。
(3)基于结构的算法:即比较类内距离、类间距离以确定K。
(4) 基于一致性矩阵的算法:即认为在正确的K时,不同次聚类的结果会更加相似
(5)基于层次聚类:即基于合并或分裂的思想,在一定情况下停止从而获得K
(6)基于采样的算法:即对样本采样,分别做聚类;根据这些结果的相似性确定K。
(7)使用Canopy Method算法进行初始划分
(8)使用BIC算法进行初始划分
目前了解的还比较浅显,后期将不断地优化。