1 # Smooth Support Vector Clustering之实现
2
3 import copy
4 import numpy
5 from matplotlib import pyplot as plt
6
7 numpy.random.seed(0)
8
9
10 def ring(type1Size=100, type2Size=100):
11 theta1 = numpy.random.uniform(0, 2 * numpy.pi, type1Size)
12 theta2 = numpy.random.uniform(0, 2 * numpy.pi, type2Size)
13
14 R1 = numpy.random.uniform(0, 0.3, type1Size).reshape((-1, 1))
15 R2 = numpy.random.uniform(0.7, 1, type2Size).reshape((-1, 1))
16 X1 = R1 * numpy.hstack((numpy.cos(theta1).reshape((-1, 1)), numpy.sin(theta1).reshape((-1, 1))))
17 X2 = R2 * numpy.hstack((numpy.cos(theta2).reshape((-1, 1)), numpy.sin(theta2).reshape((-1, 1))))
18
19 # R1 = numpy.random.uniform(0, 0.3, type1Size).reshape((-1, 1))
20 # X1 = R1 * numpy.hstack((numpy.cos(theta1).reshape((-1, 1)), numpy.sin(theta1).reshape((-1, 1)))) - 0.5
21 # X2 = R1 * numpy.hstack((numpy.cos(theta2).reshape((-1, 1)), numpy.sin(theta2).reshape((-1, 1)))) + 0.5
22
23 X = numpy.vstack((X1, X2))
24 return X
25
26
27 # 生成聚类数据
28 X = ring(300, 300)
29
30
31 class SSVC(object):
32
33 def __init__(self, X, c=1, mu=1, beta=300, sampleCnt=30):
34 '''
35 X: 聚类数据集, 1行代表一个样本
36 '''
37 self.__X = X # 聚类数据集
38 self.__c = c # 误差项权重系数
39 self.__mu = mu # gaussian kernel参数
40 self.__beta = beta # 光滑华参数
41 self.__sampleCnt = sampleCnt # 邻接矩阵采样数
42
43 self.__A = X.T
44
45
46 def get_clusters(self, IDPs, SVs, alpha, R, KAA=None):
47 '''
48 构造邻接矩阵, 进行簇划分
49 注意, 此处仅需关注IDPs、SVs
50 '''
51 if KAA is None:
52 KAA = self.get_KAA()
53
54 if IDPs.shape[0] == 0 and SVs.shape[0] == 0:
55 raise Exception(">>> Both IDPs and SVs are empty! <<<")
56 elif IDPs.shape[0] == 0 and SVs.shape[0] != 0:
57 currX = SVs
58 elif IDPs.shape[0] != 0 and SVs.shape[0] == 0:
59 currX = IDPs
60 else:
61 currX = numpy.vstack((IDPs, SVs))
62 adjMat = self.__build_adjMat(currX, alpha, R, KAA)
63
64 idxList = list(range(currX.shape[0]))
65 clusters = self.__get_clusters(idxList, adjMat)
66
67 clustersOri = list()
68 for idx, cluster in enumerate(clusters):
69 print('Size of cluster {} is about {}'.format(idx, len(cluster)))
70 clusterOri = list(currX[idx, :] for idx in cluster)
71 clustersOri.append(numpy.array(clusterOri))
72 return clustersOri
73
74
75 def get_IDPs_SVs_BSVs(self, alpha, R):
76 '''
77 获取IDPs: Interior Data Points
78 获取SVs: Support Vectors
79 获取BSVs: Bounded Support Vectors
80 '''
81 X = self.__X
82 KAA = self.get_KAA()
83
84 epsilon = 3.e-3 * R
85 IDPs, SVs, BSVs = list(), list(), list()
86
87 for x in X:
88 hVal = self.get_hVal(x, alpha, KAA)
89 if hVal >= R + epsilon:
90 BSVs.append(x)
91 elif numpy.abs(hVal - R) < epsilon:
92 SVs.append(x)
93 else:
94 IDPs.append(x)
95 return numpy.array(IDPs), numpy.array(SVs), numpy.array(BSVs)
96
97
98 def get_hVal(self, x, alpha, KAA=None):
99 '''
100 计算x与超球球心在特征空间中的距离
101 '''
102 A = self.__A
103 mu = self.__mu
104
105 x = numpy.array(x).reshape((-1, 1))
106 KAx = self.__get_KAx(A, x, mu)
107 if KAA is None:
108 KAA = self.__get_KAA(A, mu)
109 term1 = self.__calc_gaussian(x, x, mu)
110 term2 = -2 * numpy.matmul(alpha.T, KAx)[0, 0]
111 term3 = numpy.matmul(numpy.matmul(alpha.T, KAA), alpha)[0, 0]
112 hVal = numpy.sqrt(term1 + term2 + term3)
113 return hVal
114
115
116 def get_KAA(self):
117 A = self.__A
118 mu = self.__mu
119
120 KAA = self.__get_KAA(A, mu)
121 return KAA
122
123
124 def optimize(self, maxIter=3000, epsilon=1.e-9):
125 '''
126 maxIter: 最大迭代次数
127 epsilon: 收敛判据, 梯度趋于0则收敛
128 '''
129 A = self.__A
130 c = self.__c
131 mu = self.__mu
132 beta = self.__beta
133
134 alpha, R = self.__init_alpha_R((A.shape[1], 1))
135 KAA = self.__get_KAA(A, mu)
136 JVal = self.__calc_JVal(KAA, c, beta, alpha, R)
137 grad = self.__calc_grad(KAA, c, beta, alpha, R)
138 D = self.__init_D(KAA.shape[0] + 1)
139
140 for i in range(maxIter):
141 print("iterCnt: {:3d}, JVal: {}".format(i, JVal))
142 if self.__converged1(grad, epsilon):
143 return alpha, R, True
144
145 dCurr = -numpy.matmul(D, grad)
146 ALPHA = self.__calc_ALPHA_by_ArmijoRule(alpha, R, JVal, grad, dCurr, KAA, c, beta)
147
148 delta = ALPHA * dCurr
149 alphaNew = alpha + delta[:-1, :]
150 RNew = R + delta[-1, -1]
151 JValNew = self.__calc_JVal(KAA, c, beta, alphaNew, RNew)
152 if self.__converged2(delta, JValNew - JVal, epsilon ** 2):
153 return alpha, R, True
154
155 gradNew = self.__calc_grad(KAA, c, beta, alphaNew, RNew)
156 DNew = self.__update_D_by_BFGS(delta, gradNew - grad, D)
157
158 alpha, R, JVal, grad, D = alphaNew, RNew, JValNew, gradNew, DNew
159 else:
160 if self.__converged1(grad, epsilon):
161 return alpha, R, True
162 return alpha, R, False
163
164
165 def __update_D_by_BFGS(self, sk, yk, D):
166 rk = 1 / numpy.matmul(yk.T, sk)[0, 0]
167
168 term1 = rk * numpy.matmul(sk, yk.T)
169 term2 = rk * numpy.matmul(yk, sk.T)
170 I = numpy.identity(term1.shape[0])
171 term3 = numpy.matmul(I - term1, D)
172 term4 = numpy.matmul(term3, I - term2)
173 term5 = rk * numpy.matmul(sk, sk.T)
174
175 DNew = term4 + term5
176 return DNew
177
178
179 def __calc_ALPHA_by_ArmijoRule(self, alphaCurr, RCurr, JCurr, gCurr, dCurr, KAA, c, beta, C=1.e-4, v=0.5):
180 i = 0
181 ALPHA = v ** i
182 delta = ALPHA * dCurr
183 alphaNext = alphaCurr + delta[:-1, :]
184 RNext = RCurr + delta[-1, -1]
185 JNext = self.__calc_JVal(KAA, c, beta, alphaNext, RNext)
186 while True:
187 if JNext <= JCurr + C * ALPHA * numpy.matmul(dCurr.T, gCurr)[0, 0]: break
188 i += 1
189 ALPHA = v ** i
190 delta = ALPHA * dCurr
191 alphaNext = alphaCurr + delta[:-1, :]
192 RNext = RCurr + delta[-1, -1]
193 JNext = self.__calc_JVal(KAA, c, beta, alphaNext, RNext)
194 return ALPHA
195
196
197 def __converged1(self, grad, epsilon):
198 if numpy.linalg.norm(grad, ord=numpy.inf) <= epsilon:
199 return True
200 return False
201
202
203 def __converged2(self, delta, JValDelta, epsilon):
204 val1 = numpy.linalg.norm(delta, ord=numpy.inf)
205 val2 = numpy.abs(JValDelta)
206 if val1 <= epsilon or val2 <= epsilon:
207 return True
208 return False
209
210
211 def __init_D(self, n):
212 D = numpy.identity(n)
213 return D
214
215
216 def __init_alpha_R(self, shape):
217 '''
218 alpha、R之初始化
219 '''
220 alpha, R = numpy.zeros(shape), 0
221 return alpha, R
222
223
224 def __calc_grad(self, KAA, c, beta, alpha, R):
225 grad_alpha = numpy.zeros((KAA.shape[0], 1))
226 grad_R = 0
227
228 term = numpy.matmul(numpy.matmul(alpha.T, KAA), alpha)[0, 0]
229 z = list(numpy.sqrt(KAA[idx, idx] - 2 * numpy.matmul(KAA[idx:idx+1, :], alpha)[0, 0] + term) for idx in range(KAA.shape[0]))
230 term1 = numpy.matmul(KAA, alpha)
231 y = list(term1 - KAA[:, idx:idx+1] for idx in range(KAA.shape[0]))
232
233 for i in range(KAA.shape[0]):
234 term2 = self.__p(z[i] - R, beta) * self.__s(z[i] - R, beta)
235 grad_alpha += term2 * y[i] / z[i]
236 grad_R += term2
237 grad_alpha *= c
238 grad_R = R - c * grad_R
239
240 grad = numpy.vstack((grad_alpha, [[grad_R]]))
241 return grad
242
243
244 def __calc_JVal(self, KAA, c, beta, alpha, R):
245 term = numpy.matmul(numpy.matmul(alpha.T, KAA), alpha)[0, 0]
246 z = list(numpy.sqrt(KAA[idx, idx] - 2 * numpy.matmul(KAA[idx:idx+1, :], alpha)[0, 0] + term) for idx in range(KAA.shape[0]))
247
248 term1 = R ** 2 / 2
249 term2 = sum(self.__p(zi - R, beta) ** 2 for zi in z) * c / 2
250 JVal = term1 + term2
251 return JVal
252
253
254 def __p(self, x, beta):
255 term = x * beta
256 if term > 10:
257 val = x + numpy.log(1 + numpy.exp(-term)) / beta
258 else:
259 val = numpy.log(numpy.exp(term) + 1) / beta
260 return val
261
262
263 def __s(self, x, beta):
264 term = x * beta
265 if term > 10:
266 val = 1 / (numpy.exp(-beta * x) + 1)
267 else:
268 term1 = numpy.exp(term)
269 val = term1 / (1 + term1)
270 return val
271
272
273 def __d(self, x, beta):
274 term = x * beta
275 if term > 10:
276 term1 = numpy.exp(-term)
277 val = beta * term1 / (1 + term1) ** 2
278 else:
279 term1 = numpy.exp(term)
280 val = beta * term1 / (term1 + 1) ** 2
281 return val
282
283
284 def __calc_gaussian(self, x1, x2, mu):
285 val = numpy.exp(-mu * numpy.linalg.norm(x1 - x2) ** 2)
286 # val = numpy.sum(x1 * x2)
287 return val
288
289
290 def __get_KAA(self, A, mu):
291 KAA = numpy.zeros((A.shape[1], A.shape[1]))
292 for rowIdx in range(KAA.shape[0]):
293 for colIdx in range(rowIdx + 1):
294 x1 = A[:, rowIdx:rowIdx+1]
295 x2 = A[:, colIdx:colIdx+1]
296 val = self.__calc_gaussian(x1, x2, mu)
297 KAA[rowIdx, colIdx] = KAA[colIdx, rowIdx] = val
298 return KAA
299
300
301 def __get_KAx(self, A, x, mu):
302 KAx = numpy.zeros((A.shape[1], 1))
303 for rowIdx in range(KAx.shape[0]):
304 x1 = A[:, rowIdx:rowIdx+1]
305 val = self.__calc_gaussian(x1, x, mu)
306 KAx[rowIdx, 0] = val
307 return KAx
308
309
310 def __get_segment(self, xi, xj, sampleCnt):
311 lamdaList = numpy.linspace(0, 1, sampleCnt + 2)
312 segmentList = list(lamda * xi + (1 - lamda) * xj for lamda in lamdaList[1:-1])
313 return segmentList
314
315
316 def __build_adjMat(self, X, alpha, R, KAA):
317 sampleCnt = self.__sampleCnt
318
319 adjMat = numpy.zeros((X.shape[0], X.shape[0]))
320 for i in range(adjMat.shape[0]):
321 for j in range(i+1, adjMat.shape[1]):
322 xi = X[i, :]
323 xj = X[j, :]
324 xs = self.__get_segment(xi, xj, sampleCnt)
325 for x in xs:
326 dist = self.get_hVal(x, alpha, KAA)
327 if dist > R:
328 adjMat[i, j] = adjMat[j, i] = 0
329 break
330 else:
331 adjMat[i, j] = adjMat[j, i] = 1
332
333 numpy.savetxt("adjMat.txt", adjMat, fmt="%d")
334 return adjMat
335
336
337 def __get_clusters(self, idxList, adjMat):
338 clusters = list()
339 while idxList:
340 idxCurr = idxList.pop(0)
341 queue = [idxCurr]
342 cluster = list()
343 while queue:
344 idxPop = queue.pop(0)
345 cluster.append(idxPop)
346 for idx in copy.deepcopy(idxList):
347 if adjMat[idxPop, idx] == 1:
348 idxList.remove(idx)
349 queue.append(idx)
350 clusters.append(cluster)
351
352 return clusters
353
354
355 class SSVCPlot(object):
356
357 def profile_plot(self, X, ssvcObj, alpha, R):
358 surfX1 = numpy.linspace(-1.1, 1.1, 100)
359 surfX2 = numpy.linspace(-1.1, 1.1, 100)
360 surfX1, surfX2 = numpy.meshgrid(surfX1, surfX2)
361
362 KAA = ssvcObj.get_KAA()
363 surfY = numpy.zeros(surfX1.shape)
364 for rowIdx in range(surfY.shape[0]):
365 for colIdx in range(surfY.shape[1]):
366 surfY[rowIdx, colIdx] = ssvcObj.get_hVal((surfX1[rowIdx, colIdx], surfX2[rowIdx, colIdx]), alpha, KAA)
367
368 IDPs, SVs, BSVs = ssvcObj.get_IDPs_SVs_BSVs(alpha, R)
369 clusters = ssvcObj.get_clusters(IDPs, SVs, alpha, R, KAA)
370
371 fig = plt.figure(figsize=(10, 3))
372 ax1 = plt.subplot(1, 2, 1)
373 ax2 = plt.subplot(1, 2, 2)
374
375 ax1.contour(surfX1, surfX2, surfY, levels=36)
376 ax1.scatter(X[:, 0], X[:, 1], marker="o", color="red", s=5)
377 ax1.set(xlim=[-1.1, 1.1], ylim=[-1.1, 1.1], xlabel="$x_1$", ylabel="$x_2$")
378
379 if BSVs.shape[0] != 0:
380 ax2.scatter(BSVs[:, 0], BSVs[:, 1], color="k", s=5, label="$BSVs$")
381 for idx, cluster in enumerate(clusters):
382 ax2.scatter(cluster[:, 0], cluster[:, 1], s=3, label="$cluster\ {}$".format(idx))
383 ax2.set(xlim=[-1.1, 1.1], ylim=[-1.1, 1.1], xlabel="$x_1$", ylabel="$x_2$")
384 ax2.legend()
385
386 fig.tight_layout()
387 fig.savefig("profile.png", dpi=100)
388
389
390
391 if __name__ == "__main__":
392 ssvcObj = SSVC(X, c=100, mu=7, beta=300)
393 alpha, R, tab = ssvcObj.optimize()
394 spObj = SSVCPlot()
395 spObj.profile_plot(X, ssvcObj, alpha, R)
396