Fuzzy Clustering详解
第十九次写博客,本人数学基础不是太好,如果有幸能得到读者指正,感激不尽,希望能借此机会向大家学习。这一篇文章是原型聚类中介绍的第三个算法,主要是谈一谈“模糊聚类”(Fuzzy Clustering)。其他有关于原型聚类算法的讨论可以移步到该类算法的导航页《原型聚类算法综述(原型聚类算法开篇)》。
模糊集合
模糊集合论(Fuzzy
Set
Theory):允许对象以0到1之间的某个隶属度属于一个集合;
模糊逻辑(Fuzzy
Logic):允许一个陈述以0到1之间的确定度为真。
下面以天气为例,假设有两个集合分别为“雨天”和“晴天”,那么某一天的降水概率为15%,则他在“雨天”集合中具有0.15的隶属度,而在“晴天”集合中具有0.85的隶属度。
模糊簇
假设数据集
中的每个样本点
为一个
维向量,那么为了确保簇形成“模糊为划分”(Fuzzy
Psuedo-partion),需要满足以下条件:
1) 对于每个样本点
与每个簇
,隶属度(权值)
要满足:
2) 每个样本点 的所有权值之和为1:
3) 每个簇 以非零的权值至少包含一个样本点,且不以权值为1包含所有样本点:
模糊c均值(Fuzzy C-Means)
模糊c均值(Fuzzy C-Means)简称FCM,是K-Means的一种模糊版本,算法伪代码如下图所示,
由上图可以看出,模糊c均值与K-Means通过相似的步骤进行质心与簇划分的交替迭代更新,直到满足迭代停止条件为止。虽然两种算法均可以以最小化总误差平方和SSE为目标,但是在K-Means中,每个样本以0或1的隶属度被划分到对应的簇中,因此总SSE被定义为:
而在模糊聚类中,每个样本都可以以0到1的隶属度被划分到每个簇中,因此在模糊c均值中,总SSE定义为:
可以将上式看做是SSE的加权版本,其中,
是确定权值
影响的指数,他的取值分为以下几种情况:
1)
时,可以简化权值
更新公式;
2)
时,模糊c均值近乎与K-Means相同;
3)
越大,划分结果越模糊。
算法第1行:模糊c均值与K-Means的初始化方法类似,通常对权值
进行随机初始化,但是要保证每个样本点
的所有权值之和为1,因此也会面临局部最优的问题,具体的解决方法与在K-Means中讨论过的相同。
算法第3行:簇质心(原型)的计算与K-Means中的更新方法类似,只不过要考虑到数据集中的所有样本点和他们对应于该簇的隶属度,具体的计算方法如下所示:
同样,如果将隶属度规定为0或1,那么上式会退化为K-Means中的质心更新公式。
算法第4行:在更新模糊伪划分
时,需要保证每个样本点
的所有权值之和为1,更新公式如下所示:
时,上式可以简化为
直观上看,可以将隶属度(权值)
理解为样本点
与簇
的临近度,
越靠近
,
越大,反之越小。
下面是使用模糊c均值进行聚类的一个例子,从这个例子中我们可以看出,该算法产生的最终聚类结果显示出了簇中每个样本点的隶属度,与K-Means相比,他的计算复杂度明显增加,除此之外,他与K-Means有着相同的特点。
代码实现
下面是我自己实现的代码,可以自己手动输入 值,这里默认的是2,下面分别对不同 值进行了聚类实验并可视化出来。
代码细节
"""
@author: Ἥλιος
@CSDN:https://blog.csdn.net/qq_40793975/article/details/82348358
"""
print(__doc__)
import time
import numpy as np
import matplotlib.pyplot as plt
from sklearn import datasets
from sklearn.preprocessing import StandardScaler
from itertools import cycle, islice
# 加载数据集(从文件中)
def load_Data1(filename):
data_set = []
with open(filename) as fi:
for line in fi.readlines():
cur_line = line.strip().split('\t')
flt_line = []
for i in cur_line:
flt_line.append(float(i))
data_set.append(flt_line)
data_mat = np.mat(data_set) # 转化为矩阵形式
return data_mat
# 加载数据集(自建数据集)
def load_Data2(n_samples=1500):
# 带噪声的圆形数据
noisy_circles = datasets.make_circles(n_samples=n_samples, factor=.5, noise=.05)
# 带噪声的月牙形数据
noisy_moons = datasets.make_moons(n_samples=n_samples, noise=.05)
# 随机分布数据
no_structure = np.random.rand(n_samples, 2), np.ones((1, n_samples), dtype=np.int32).tolist()[0]
# 各向异性分布数据(Anisotropicly distributed data)
random_state = 170
X, y = datasets.make_blobs(n_samples=n_samples, random_state=random_state)
transformation = [[0.6, -0.6], [-0.4, 0.8]]
X_aniso = np.dot(X, transformation)
aniso = (X_aniso, y)
# 不同方差的气泡形数据(blobs with varied variances)
varied = datasets.make_blobs(n_samples=n_samples, cluster_std=[1.0, 2.5, 0.5], random_state=random_state)
# 相同方差的气泡形数据
blobs = datasets.make_blobs(n_samples=n_samples, random_state=8)
# 合并数据集
data_sets = [noisy_circles, noisy_moons, no_structure, aniso, varied, blobs]
cluster_nums = [2, 2, 3, 3, 3, 3]
data_mats = []
for i in range(data_sets.__len__()):
X, y = data_sets[i]
X = StandardScaler().fit_transform(X) # 对数据集进行标准化处理
X_mat = np.mat(X)
y_mat = np.mat(y)
data_mats.append((X_mat, y_mat))
# 展示数据集
plt.figure(figsize=(2.5, 14))
plt.subplots_adjust(left=.02, right=.98, bottom=.001, top=.96, wspace=.05, hspace=.01)
for i in range(data_sets.__len__()):
X, y = data_sets[i]
X = StandardScaler().fit_transform(X) # 对数据集进行标准化处理
plt.subplot(len(data_sets), 1, i+1)
if i == 0:
plt.title("Self-built Data Set", size=18)
color = np.array(y) + 1
plt.scatter(X[:, 0], X[:, 1], c=color, s=10)
plt.xlim(-2.5, 2.5)
plt.ylim(-2.5, 2.5)
plt.show()
return data_mats, cluster_nums
# 计算样本点A与B间距离(欧氏距离)
def dist_Euclid(VecA, VecB):
return np.sqrt(np.sum(np.power(VecA-VecB, 2)))
# 对k个聚类中心随机初始化(随机选取数据集中的k个样本点)
def rand_initial_center1(data_mat, k):
(m, n) = np.shape(data_mat)
centroids = np.mat(np.zeros((k, n), dtype=np.float32))
for i in range(k):
index = int(np.random.rand()*m)
centroids[i, :] = data_mat[index, :]
return centroids
# 对k个聚类中心随机初始化(在样本空间范围内随机选取)
def rand_initial_center2(data_mat, k):
n = np.shape(data_mat)[1]
centroids = np.mat(np.zeros((k, n), dtype=np.float32))
for i in range(n):
minJ = np.min(data_mat[:, i])
maxJ = np.max(data_mat[:, i])
centroids[:, i] = np.mat(np.random.rand(k, 1)*(maxJ - minJ)) + minJ
return centroids
# 模糊c均值
def fuzzy_cMeans(data_mat, k, dist_measure=dist_Euclid, p=2.0): # p-决定权重的影响程度 p大于等于1
m, n = np.shape(data_mat)
weight_mat = np.mat(np.random.rand(m, k), dtype=np.float32)
centroids = np.mat(np.zeros((k, 2)), dtype=np.float32)
cluster_assment = np.mat(np.zeros((m, 3)), dtype=np.float32) # 存储每个样本点的簇隶属、簇隶属度和距离所属簇质心的长度
cluster_changed = True
while cluster_changed:
cluster_changed = False
for i in range(k): # E步:更新质心
centroids[i, :] = (np.power(weight_mat[:, i], p).T*data_mat)/np.sum(np.power(weight_mat[:, i], p), axis=0)
for i in range(m): # M步:更新权重
sum_dist_iq = 0 # 用来更新权重的分母
for q in range(k):
dist_iq = dist_measure(data_mat[i, :], centroids[q, :])
sum_dist_iq += pow((1/pow(dist_iq, 2)), (1/(p-1)))
for j in range(k):
dist_ij = dist_measure(data_mat[i, :], centroids[j, :])
weight_mat[i, j] = pow((1/pow(dist_ij, 2)), (1/(p-1))) / sum_dist_iq
for i in range(m): # 更新簇信息
max_weight = 0
max_weight_index = -1
for j in range(k):
cur_weight = weight_mat[i, j]
if cur_weight > max_weight:
max_weight = cur_weight
max_weight_index = j
if max_weight_index != cluster_assment[i, 0]:
cluster_changed = True
cluster_assment[i, 0] = max_weight_index
cluster_assment[i, 1] = max_weight
cluster_assment[i, 2] = dist_measure(data_mat[i, :], centroids[max_weight_index, :])
# # 静态显示
# plt.scatter(data_mat[:, 0].T.A[0], data_mat[:, 1].T.A[0], c=cluster_assment[:, 0].T.A[0])
# plt.scatter(centroids[:, 0].T.tolist()[0], centroids[:, 1].T.tolist()[0], s=100, c='red', marker='x')
# plt.show()
return centroids, cluster_assment
# Fuzzy C-Means
data_mats, cluster_nums = load_Data2()
plt.figure(figsize=(2.5, 14))
plt.subplots_adjust(left=.02, right=.98, bottom=.001, top=.96, wspace=.05, hspace=.01)
for i in range(len(data_mats)):
data_mat = data_mats[i][0] # 获取自建数据集
k = cluster_nums[i] # 获取自建数据集的簇标记
t0 = time.time() # 计算运行时间
centroids, cluster_assment = fuzzy_cMeans(data_mat, k, p=1.05)
t1 = time.time()
color = np.multiply(cluster_assment[:, 0].A + 1, cluster_assment[:, 1].A).T[0] # 获得渐进色图
plt.subplot(len(data_mats), 1, i + 1)
if i == 0:
plt.title("Fuzzy c-Means(Self-programming Implementation)", size=10)
im = plt.scatter(data_mat[:, 0].T.A[0], data_mat[:, 1].T.A[0], c=color, s=10, alpha=0.5)
plt.scatter(centroids[:, 0].T.tolist()[0], centroids[:, 1].T.tolist()[0], s=100, c='red', marker='x')
plt.xlim(-2.5, 2.5)
plt.ylim(-2.5, 2.5)
plt.text(.99, .01, ('%.2fs' % (t1 - t0)).lstrip('0'), transform=plt.gca().transAxes, size=15,
horizontalalignment='right')
plt.colorbar(im)
plt.show()
算法效果
上面两附图从左至右依次是 和 时的聚类效果,可以看出随着 值的增大,聚类簇越来越模糊,当 近似等于1时,该算法类似于K-Means,当 足够大时,聚类效果会变得非常不明显,大家可以去试一下,另外,这里为了方便可视化,将簇标记改为 ,而不是 ,下图也是如此。
上图从左至右依次是:自建数据集和 时的模糊c均值,可以以清楚的看出,两个簇边界上的点以某一概率隶属于某个簇,且该算法的时间复杂度高于K-Means,还有一点需要注意的是,当簇之间的距离比较远时,该算法很容易陷入局部最优。