一个点,如何找到
它所属的簇?
随机选取 Jain 中的 p313,按真实函数调用顺序,查看每个中间集合、每条 MST 边和每一次分配依据。图中红环始终指向同一个样本。
固定一次随机抽样,先看执行路线
fit_predict → fit → _validate
本页跟踪 p313:Python 索引 313,MATLAB / 人工行号 314。用 np.random.default_rng(20260926).integers(373) 从全部样本均匀抽取一次,没有按聚类结果筛选,也没有重抽。
prune_mean = "all" · vote_rule = "strict" · core_fallback = "kmnn"
_validate(X) 检查二维形状、有限数值、整数 K、1 ≤ K < N 和选项合法性。本例全部通过;此函数不改变样本,也不使用真实类别。所有后续点号均是 从 0 开始的索引。
这是带有全局依赖的算法:近邻需搜索全部点,δ 需比较全部密度,分配顺序需比较全部未标记点。本页在每一阶段突出 p313,同时保留必要的全局状态。
查看实际代码 · dpc_mstsc.py:32
def _validate(self, X):
X = np.asarray(X, dtype=float)
if X.ndim != 2 or X.shape[0] < 2 or X.shape[1] < 1:
raise ValueError("X must be a 2D matrix with at least two samples and one feature")
if not np.isfinite(X).all():
raise ValueError("X must contain only finite values")
if isinstance(self.K, (bool, np.bool_)) or not isinstance(self.K, (int, np.integer)):
raise ValueError("K must be an integer")
if not 1 <= self.K < len(X):
raise ValueError("K must satisfy 1 <= K < n_samples")
if not np.isfinite(self.lam) or self.lam <= 0:
raise ValueError("lam must be finite and positive (paper range: [0.25, 0.5])")
if self.prune_mean not in ("all", "incident"):
raise ValueError("prune_mean must be 'all' or 'incident'")
if self.vote_rule not in ("strict", "plurality"):
raise ValueError("vote_rule must be 'strict' or 'plurality'")
if self.core_fallback not in ("error", "kmnn"):
raise ValueError("core_fallback must be error or kmnn")
return X
查看实际代码 · dpc_mstsc.py:209
def fit_predict(self, X):
return self.fit(X).labels_
查看实际代码 · dpc_mstsc.py:188
def fit(self, X):
X = self._validate(X)
# Clear prior fitted results even if the new fit fails during core discovery.
self.labels_ = self.centers_ = self.skeletons_ = self.n_clusters_ = None
self.X_normalized_ = self._minmax_normalize(X)
self.D_ = cdist(self.X_normalized_, self.X_normalized_)
self._build_neighborhoods(self.D_)
self._find_kfmnn()
self._pruned_mst_density(self.D_)
self._compute_delta_gamma(self.D_)
self._build_skeletons()
labels = np.full(len(X), -1, dtype=int)
for c, skeleton in enumerate(self.skeletons_):
labels[skeleton] = c
self.skeleton_labels_ = labels.copy()
self.labels_before_refinement_ = self._attractiveness_assignment(self.D_, labels)
self.labels_ = (self._label_refinement(self.labels_before_refinement_) if self.refine
else self.labels_before_refinement_.copy())
self.n_output_clusters_ = len(np.unique(self.labels_))
return self
从原始坐标得到实际计算空间
_minmax_normalize(X) + fit 中的 cdist
分别缩放每个特征。所有近邻、MST 边权和吸引力距离,都来自归一化后的欧氏空间;它与原始空间的邻居关系不必完全相同。
d(i,j) = √[(x′i1 − x′j1)² + (x′i2 − x′j2)²]
| 特征 | 全局最小值 | 全局最大值 | 跨度 | p313 原值 | 归一化结果 |
|---|---|---|---|---|---|
| 1 | 0.75 | 41.3 | 40.55 | 36.7 | 0.88655980 |
| 2 | 2.95 | 27.85 | 24.9 | 9.15 | 0.24899598 |
cdist 生成 373 × 373 距离矩阵。对角线 d(i,i)=0;选近邻时才在临时行副本中将自身距离置为 ∞,原距离矩阵不被改变。
展开 p313 到全部样本的距离(按距离排序)
| 距离排序 | 点索引 | 距离 |
|---|---|---|
| 0 | 313 | 0.00000000 |
| 1 | 310 | 0.02130112 |
| 2 | 315 | 0.02459598 |
| 3 | 314 | 0.02622065 |
| 4 | 311 | 0.02648052 |
| 5 | 304 | 0.03303350 |
| 6 | 309 | 0.03595552 |
| 7 | 317 | 0.04083640 |
| 8 | 312 | 0.04140252 |
| 9 | 320 | 0.04203038 |
| 10 | 316 | 0.04238922 |
| 11 | 321 | 0.04362044 |
| 12 | 303 | 0.04468932 |
| 13 | 308 | 0.04479654 |
| 14 | 297 | 0.05317267 |
| 15 | 302 | 0.05349964 |
| 16 | 296 | 0.06155280 |
| 17 | 318 | 0.06302573 |
| 18 | 306 | 0.06711725 |
| 19 | 322 | 0.06778780 |
| 20 | 319 | 0.06828423 |
| 21 | 301 | 0.07045398 |
| 22 | 323 | 0.07070515 |
| 23 | 295 | 0.07441092 |
| 24 | 305 | 0.07561966 |
| 25 | 300 | 0.07631518 |
| 26 | 294 | 0.08074532 |
| 27 | 324 | 0.08242329 |
| 28 | 298 | 0.08526856 |
| 29 | 325 | 0.09177221 |
| 30 | 292 | 0.09255057 |
| 31 | 291 | 0.09273017 |
| 32 | 293 | 0.09765733 |
| 33 | 299 | 0.09842447 |
| 34 | 290 | 0.10277487 |
| 35 | 326 | 0.10467944 |
| 36 | 268 | 0.10717415 |
| 37 | 289 | 0.11075054 |
| 38 | 331 | 0.11273032 |
| 39 | 267 | 0.11423877 |
| 40 | 332 | 0.11444872 |
| 41 | 330 | 0.11486436 |
| 42 | 329 | 0.12124299 |
| 43 | 288 | 0.12183167 |
| 44 | 327 | 0.12251478 |
| 45 | 346 | 0.12686374 |
| 46 | 287 | 0.12696493 |
| 47 | 333 | 0.13028519 |
| 48 | 269 | 0.13427399 |
| 49 | 328 | 0.13699640 |
| 50 | 345 | 0.14253725 |
| 51 | 270 | 0.14304925 |
| 52 | 284 | 0.14373216 |
| 53 | 266 | 0.14453302 |
| 54 | 347 | 0.14471890 |
| 55 | 285 | 0.14553119 |
| 56 | 283 | 0.14767661 |
| 57 | 350 | 0.14795275 |
| 58 | 286 | 0.14937718 |
| 59 | 271 | 0.15204935 |
| 60 | 335 | 0.15532485 |
| 61 | 334 | 0.15594146 |
| 62 | 344 | 0.16049891 |
| 63 | 265 | 0.16251974 |
| 64 | 272 | 0.16282417 |
| 65 | 348 | 0.16297332 |
| 66 | 282 | 0.16304241 |
| 67 | 351 | 0.16325677 |
| 68 | 273 | 0.16904012 |
| 69 | 349 | 0.17011033 |
| 70 | 336 | 0.17181914 |
| 71 | 343 | 0.17225675 |
| 72 | 264 | 0.17323094 |
| 73 | 281 | 0.17428794 |
| 74 | 280 | 0.17857476 |
| 75 | 352 | 0.18660251 |
| 76 | 263 | 0.19081320 |
| 77 | 279 | 0.19365370 |
| 78 | 247 | 0.19545685 |
| 79 | 278 | 0.19826983 |
| 80 | 274 | 0.19856414 |
| 81 | 262 | 0.19956060 |
| 82 | 277 | 0.20765635 |
| 83 | 341 | 0.20957427 |
| 84 | 342 | 0.21270535 |
| 85 | 340 | 0.21276258 |
| 86 | 261 | 0.21359644 |
| 87 | 339 | 0.21543930 |
| 88 | 246 | 0.21646023 |
| 89 | 337 | 0.21672303 |
| 90 | 276 | 0.22159370 |
| 91 | 338 | 0.22240363 |
| 92 | 275 | 0.22412742 |
| 93 | 353 | 0.23391630 |
| 94 | 257 | 0.23907920 |
| 95 | 245 | 0.24196338 |
| 96 | 354 | 0.24276952 |
| 97 | 244 | 0.24600317 |
| 98 | 254 | 0.24946138 |
| 99 | 355 | 0.25062837 |
| 100 | 260 | 0.25105021 |
| 101 | 255 | 0.25192468 |
| 102 | 256 | 0.25248724 |
| 103 | 259 | 0.25452361 |
| 104 | 258 | 0.25707748 |
| 105 | 366 | 0.26047354 |
| 106 | 365 | 0.26265911 |
| 107 | 356 | 0.26318299 |
| 108 | 253 | 0.26404462 |
| 109 | 243 | 0.26437531 |
| 110 | 307 | 0.26656560 |
| 111 | 222 | 0.26829392 |
| 112 | 224 | 0.27121026 |
| 113 | 357 | 0.27179354 |
| 114 | 252 | 0.27187751 |
| 115 | 223 | 0.27534952 |
| 116 | 248 | 0.27560009 |
| 117 | 251 | 0.27746454 |
| 118 | 225 | 0.27815813 |
| 119 | 249 | 0.28159681 |
| 120 | 242 | 0.28267692 |
| 121 | 358 | 0.28316314 |
| 122 | 203 | 0.28491486 |
| 123 | 250 | 0.28596863 |
| 124 | 221 | 0.28933895 |
| 125 | 364 | 0.29232952 |
| 126 | 363 | 0.29392447 |
| 127 | 226 | 0.29573031 |
| 128 | 367 | 0.29780623 |
| 129 | 362 | 0.30167296 |
| 130 | 227 | 0.30194545 |
| 131 | 202 | 0.30221233 |
| 132 | 369 | 0.30452568 |
| 133 | 228 | 0.30886211 |
| 134 | 361 | 0.30932313 |
| 135 | 204 | 0.31254026 |
| 136 | 241 | 0.31312836 |
| 137 | 368 | 0.31334212 |
| 138 | 201 | 0.31748806 |
| 139 | 240 | 0.32047863 |
| 140 | 371 | 0.32077317 |
| 141 | 229 | 0.32162128 |
| 142 | 190 | 0.32250132 |
| 143 | 200 | 0.32523529 |
| 144 | 372 | 0.32693613 |
| 145 | 359 | 0.32707805 |
| 146 | 230 | 0.32917091 |
| 147 | 189 | 0.33121630 |
| 148 | 370 | 0.33152823 |
| 149 | 360 | 0.33413256 |
| 150 | 238 | 0.33694116 |
| 151 | 239 | 0.34042553 |
| 152 | 220 | 0.34044909 |
| 153 | 205 | 0.34065837 |
| 154 | 92 | 0.34344423 |
| 155 | 199 | 0.34424688 |
| 156 | 232 | 0.34552337 |
| 157 | 237 | 0.34785667 |
| 158 | 233 | 0.34824537 |
| 159 | 231 | 0.34966062 |
| 160 | 198 | 0.35112115 |
| 161 | 219 | 0.35427959 |
| 162 | 184 | 0.35986005 |
| 163 | 187 | 0.36137198 |
| 164 | 215 | 0.36390838 |
| 165 | 217 | 0.36408203 |
| 166 | 218 | 0.36452825 |
| 167 | 196 | 0.36670417 |
| 168 | 183 | 0.36697019 |
| 169 | 197 | 0.36707512 |
| 170 | 182 | 0.36714376 |
| 171 | 236 | 0.36746960 |
| 172 | 188 | 0.37033147 |
| 173 | 216 | 0.37121777 |
| 174 | 235 | 0.37145437 |
| 175 | 234 | 0.37178134 |
| 176 | 206 | 0.37193267 |
| 177 | 96 | 0.37443194 |
| 178 | 195 | 0.37508270 |
| 179 | 172 | 0.37757369 |
| 180 | 185 | 0.37972830 |
| 181 | 213 | 0.38037373 |
| 182 | 207 | 0.38236850 |
| 183 | 171 | 0.38250250 |
| 184 | 193 | 0.38392901 |
| 185 | 214 | 0.38729000 |
| 186 | 173 | 0.38747257 |
| 187 | 95 | 0.38813030 |
| 188 | 194 | 0.39067532 |
| 189 | 211 | 0.39411102 |
| 190 | 192 | 0.39479309 |
| 191 | 181 | 0.39548418 |
| 192 | 212 | 0.39553868 |
| 193 | 208 | 0.39810959 |
| 194 | 210 | 0.39824068 |
| 195 | 209 | 0.39829023 |
| 196 | 165 | 0.39852170 |
| 197 | 93 | 0.40048954 |
| 198 | 180 | 0.40287585 |
| 199 | 191 | 0.40352523 |
| 200 | 164 | 0.40456357 |
| 201 | 179 | 0.40607623 |
| 202 | 94 | 0.40620345 |
| 203 | 174 | 0.41114197 |
| 204 | 163 | 0.41195960 |
| 205 | 186 | 0.41393699 |
| 206 | 178 | 0.41444508 |
| 207 | 162 | 0.41447842 |
| 208 | 175 | 0.41503087 |
| 209 | 149 | 0.41800597 |
| 210 | 156 | 0.42680387 |
| 211 | 176 | 0.42788373 |
| 212 | 155 | 0.42910458 |
| 213 | 161 | 0.42956449 |
| 214 | 91 | 0.42981446 |
| 215 | 177 | 0.43173886 |
| 216 | 150 | 0.43432924 |
| 217 | 157 | 0.43543182 |
| 218 | 168 | 0.43770107 |
| 219 | 158 | 0.43973975 |
| 220 | 169 | 0.44375213 |
| 221 | 170 | 0.44429210 |
| 222 | 160 | 0.44796541 |
| 223 | 159 | 0.44940545 |
| 224 | 140 | 0.45183717 |
| 225 | 142 | 0.45273981 |
| 226 | 81 | 0.45337034 |
| 227 | 139 | 0.45415185 |
| 228 | 167 | 0.45420858 |
| 229 | 154 | 0.45735676 |
| 230 | 82 | 0.45988523 |
| 231 | 166 | 0.46107232 |
| 232 | 138 | 0.46619739 |
| 233 | 90 | 0.46706799 |
| 234 | 141 | 0.46770279 |
| 235 | 153 | 0.46927829 |
| 236 | 145 | 0.46939992 |
| 237 | 80 | 0.47023506 |
| 238 | 144 | 0.47273243 |
| 239 | 137 | 0.47300034 |
| 240 | 152 | 0.47413540 |
| 241 | 151 | 0.47471847 |
| 242 | 143 | 0.47736949 |
| 243 | 146 | 0.47893133 |
| 244 | 79 | 0.48117001 |
| 245 | 136 | 0.48545385 |
| 246 | 147 | 0.48755363 |
| 247 | 89 | 0.49037765 |
| 248 | 148 | 0.49239482 |
| 249 | 78 | 0.49352890 |
| 250 | 135 | 0.49430095 |
| 251 | 88 | 0.49739100 |
| 252 | 133 | 0.49942118 |
| 253 | 134 | 0.50142739 |
| 254 | 129 | 0.50372265 |
| 255 | 128 | 0.50451349 |
| 256 | 127 | 0.50584060 |
| 257 | 83 | 0.50614321 |
| 258 | 125 | 0.50743623 |
| 259 | 124 | 0.50830449 |
| 260 | 126 | 0.51163731 |
| 261 | 130 | 0.51298893 |
| 262 | 87 | 0.51387422 |
| 263 | 123 | 0.51544490 |
| 264 | 84 | 0.51936298 |
| 265 | 132 | 0.52108612 |
| 266 | 122 | 0.52112367 |
| 267 | 131 | 0.52195287 |
| 268 | 118 | 0.52415901 |
| 269 | 117 | 0.53123731 |
| 270 | 120 | 0.53141932 |
| 271 | 119 | 0.53284851 |
| 272 | 77 | 0.53331436 |
| 273 | 121 | 0.53331805 |
| 274 | 85 | 0.53749648 |
| 275 | 116 | 0.53808957 |
| 276 | 110 | 0.53811692 |
| 277 | 112 | 0.54000923 |
| 278 | 115 | 0.54011490 |
| 279 | 111 | 0.54405478 |
| 280 | 86 | 0.54674161 |
| 281 | 114 | 0.54810791 |
| 282 | 109 | 0.55003788 |
| 283 | 113 | 0.55151166 |
| 284 | 107 | 0.55336916 |
| 285 | 108 | 0.55551010 |
| 286 | 105 | 0.56115490 |
| 287 | 106 | 0.56779229 |
| 288 | 104 | 0.57303462 |
| 289 | 76 | 0.57380097 |
| 290 | 71 | 0.57644155 |
| 291 | 70 | 0.58227475 |
| 292 | 101 | 0.58502011 |
| 293 | 102 | 0.58974687 |
| 294 | 103 | 0.59060437 |
| 295 | 100 | 0.59170363 |
| 296 | 69 | 0.61152806 |
| 297 | 99 | 0.61270515 |
| 298 | 68 | 0.62471001 |
| 299 | 75 | 0.63006931 |
| 300 | 98 | 0.63209198 |
| 301 | 62 | 0.63557512 |
| 302 | 67 | 0.64620797 |
| 303 | 97 | 0.64629796 |
| 304 | 72 | 0.65048761 |
| 305 | 61 | 0.66124481 |
| 306 | 60 | 0.66512425 |
| 307 | 65 | 0.67119519 |
| 308 | 66 | 0.67189979 |
| 309 | 73 | 0.67448168 |
| 310 | 74 | 0.68712582 |
| 311 | 64 | 0.68994591 |
| 312 | 59 | 0.69134455 |
| 313 | 63 | 0.70112085 |
| 314 | 58 | 0.71113380 |
| 315 | 49 | 0.74433357 |
| 316 | 48 | 0.75278449 |
| 317 | 14 | 0.75856202 |
| 318 | 47 | 0.76187765 |
| 319 | 56 | 0.76476041 |
| 320 | 44 | 0.76592560 |
| 321 | 40 | 0.76663307 |
| 322 | 57 | 0.76953209 |
| 323 | 46 | 0.77225582 |
| 324 | 43 | 0.77293490 |
| 325 | 15 | 0.77482896 |
| 326 | 41 | 0.78137834 |
| 327 | 55 | 0.78140801 |
| 328 | 45 | 0.78159415 |
| 329 | 51 | 0.78555973 |
| 330 | 42 | 0.78564708 |
| 331 | 50 | 0.79138043 |
| 332 | 52 | 0.79906657 |
| 333 | 3 | 0.80166425 |
| 334 | 38 | 0.80483043 |
| 335 | 31 | 0.81642926 |
| 336 | 30 | 0.81675927 |
| 337 | 5 | 0.81860612 |
| 338 | 39 | 0.82075090 |
| 339 | 16 | 0.82365537 |
| 340 | 4 | 0.82652307 |
| 341 | 28 | 0.83080946 |
| 342 | 29 | 0.83424921 |
| 343 | 17 | 0.84913826 |
| 344 | 6 | 0.85485131 |
| 345 | 53 | 0.86099904 |
| 346 | 2 | 0.86165813 |
| 347 | 7 | 0.87190431 |
| 348 | 54 | 0.87555694 |
| 349 | 18 | 0.88792435 |
| 350 | 8 | 0.89272373 |
| 351 | 37 | 0.89460314 |
| 352 | 20 | 0.90051558 |
| 353 | 36 | 0.90410636 |
| 354 | 13 | 0.90890804 |
| 355 | 19 | 0.92201112 |
| 356 | 21 | 0.92207511 |
| 357 | 1 | 0.92362763 |
| 358 | 9 | 0.92406736 |
| 359 | 27 | 0.93427348 |
| 360 | 35 | 0.93792748 |
| 361 | 32 | 0.94176462 |
| 362 | 0 | 0.94484539 |
| 363 | 34 | 0.94710689 |
| 364 | 33 | 0.95062724 |
| 365 | 12 | 0.95625544 |
| 366 | 10 | 0.96282985 |
| 367 | 22 | 0.96486384 |
| 368 | 11 | 0.97300049 |
| 369 | 25 | 0.98722066 |
| 370 | 23 | 0.98970202 |
| 371 | 24 | 0.99115065 |
| 372 | 26 | 0.99181098 |
查看实际代码 · dpc_mstsc.py:52
@staticmethod
def _minmax_normalize(X):
span = np.ptp(X, axis=0)
return (X - X.min(axis=0)) / np.where(span == 0, 1, span)
得到 KNN、互近邻、二阶邻居和共享计数
_build_neighborhoods(D)
先排除自身,选最小的 14 个距离;边界平票按点索引打破。对每个邻居 j,再检查 p313 是否也出现在 j 的 KNN 中。两边都成立,才是互近邻。
唯一的单向邻居为 p297:它属于 p313 的 KNN,但它的 14 个近邻不包含 p313。因此 |MNN(p313)| = 13。
| KNN 排名 | j | d(i,j) | 互近邻? | |MNN(j)| | j ∈ S? | SNN(i,j) |
|---|---|---|---|---|---|---|
| 1 | 310 | 0.02130112 | 是 | 12 | 否 | 12 |
| 2 | 315 | 0.02459598 | 是 | 14 | 是 | 9 |
| 3 | 314 | 0.02622065 | 是 | 14 | 是 | 9 |
| 4 | 311 | 0.02648052 | 是 | 14 | 是 | 11 |
| 5 | 304 | 0.03303350 | 是 | 14 | 是 | 6 |
| 6 | 309 | 0.03595552 | 是 | 10 | 否 | 11 |
| 7 | 317 | 0.04083640 | 是 | 14 | 是 | 8 |
| 8 | 312 | 0.04140252 | 是 | 12 | 否 | 9 |
| 9 | 320 | 0.04203038 | 是 | 11 | 否 | 8 |
| 10 | 316 | 0.04238922 | 是 | 14 | 是 | 8 |
| 11 | 321 | 0.04362044 | 是 | 7 | 否 | 9 |
| 12 | 303 | 0.04468932 | 是 | 13 | 否 | 3 |
| 13 | 308 | 0.04479654 | 是 | 10 | 否 | 9 |
| 14 | 297 | 0.05317267 | 否 | 13 | 否 | 3 |
SNN(i,j) = |KNN(i) ∩ KNN(j)|
SNN 是共享样本的数量,不要求 i 与 j 本身互为近邻。代码用稀疏 0/1 邻接矩阵 B 构建 B @ B.T;每个元素等于一个集合交集大小。
展开二阶邻居的具体到达路径 i → j → k
| 二阶点 k | 可作为中间点的 j |
|---|---|
| 290 | 297 |
| 291 | 297 |
| 292 | 303, 297 |
| 293 | 297 |
| 294 | 304, 303, 308, 297 |
| 295 | 304, 303, 308, 297 |
| 296 | 304, 309, 303, 308, 297 |
| 297 | 310, 304, 309, 303, 308 |
| 298 | 303, 297 |
| 300 | 304, 303, 297 |
| 301 | 304, 303, 297 |
| 302 | 304, 303, 308, 297 |
| 303 | 310, 304, 309, 308, 297 |
| 304 | 310, 311, 309, 321, 303, 308, 297 |
| 305 | 303 |
| 306 | 304, 303 |
| 308 | 310, 311, 304, 309, 312, 303, 297 |
| 309 | 310, 315, 314, 311, 304, 317, 312, 308 |
| 310 | 315, 314, 311, 304, 309, 317, 312, 320, 316, 321, 308 |
| 311 | 310, 315, 314, 304, 309, 317, 312, 320, 316, 321, 308 |
| 312 | 310, 315, 314, 311, 309, 317, 320, 316, 321, 308 |
| 314 | 310, 315, 311, 309, 317, 312, 320, 316, 321, 308 |
| 315 | 310, 314, 311, 309, 317, 312, 320, 316, 321, 308 |
| 316 | 310, 315, 314, 311, 309, 317, 312, 320, 321 |
| 317 | 310, 315, 314, 311, 309, 312, 320, 316, 321 |
| 318 | 310, 315, 314, 311, 309, 317, 312, 320, 316, 321 |
| 319 | 315, 314, 311, 317, 312, 320, 316, 321 |
| 320 | 310, 315, 314, 311, 317, 312, 316, 321 |
| 321 | 315, 314, 311, 320, 316 |
| 322 | 315, 314, 317, 320, 316, 321 |
| 323 | 315, 314, 317, 312, 320, 316, 321 |
| 324 | 317, 312, 320, 316 |
展开 14 个近邻各自与目标点共享的样本
| 邻居 j | 共享样本索引 |
|---|---|
| 310 | 297, 303, 304, 308, 309, 311, 312, 314, 315, 316, 317, 320 |
| 315 | 309, 310, 311, 312, 314, 316, 317, 320, 321 |
| 314 | 309, 310, 311, 312, 315, 316, 317, 320, 321 |
| 311 | 304, 308, 309, 310, 312, 314, 315, 316, 317, 320, 321 |
| 304 | 297, 303, 308, 309, 310, 311 |
| 309 | 297, 303, 304, 308, 310, 311, 312, 314, 315, 316, 317 |
| 317 | 309, 310, 311, 312, 314, 315, 316, 320 |
| 312 | 308, 309, 310, 311, 314, 315, 316, 317, 320 |
| 320 | 310, 311, 312, 314, 315, 316, 317, 321 |
| 316 | 310, 311, 312, 314, 315, 317, 320, 321 |
| 321 | 304, 310, 311, 312, 314, 315, 316, 317, 320 |
| 303 | 297, 304, 308 |
| 308 | 297, 303, 304, 309, 310, 311, 312, 314, 315 |
| 297 | 303, 304, 308 |
查看实际代码 · dpc_mstsc.py:57
def _build_neighborhoods(self, D):
n, k = len(D), self.K
self.K_eff_ = k
knn = np.empty((n, k), dtype=int)
for i in range(n):
row = D[i].copy()
row[i] = np.inf # Exclude identity, not zero-distance duplicates.
cutoff = np.partition(row, k - 1)[k - 1]
closer = np.flatnonzero(row < cutoff)
tied = np.flatnonzero(row == cutoff)[:k - len(closer)]
chosen = np.concatenate((closer, tied))
knn[i] = chosen[np.lexsort((chosen, row[chosen]))]
self.knn_ = knn
adjacency = csr_matrix((np.ones(n*k, dtype=np.int64),
(np.repeat(np.arange(n), k), knn.ravel())),
shape=(n, n))
mutual = adjacency.multiply(adjacency.T).tocsr()
self.mnn_ = [mutual.indices[mutual.indptr[i]:mutual.indptr[i+1]].copy()
for i in range(n)]
self.knn2_ = [set(knn[knn[i]].ravel().tolist()) - {i} for i in range(n)]
# Sparse product avoids the original dense O(n^3) integer product.
self.snn_ = (adjacency @ adjacency.T).toarray()
核心筛选失败在哪里?为何需要显式回退?
_find_kfmnn()
SF = {i ∈ S : MNN(i) ⊆ S}
第一层筛选要求一个点的全部 14 个近邻都是互近邻。第二层还要求这些互近邻本身都通过第一层筛选。p313 只有 13 个互近邻,第一层就未通过,所以不属于 S,也不属于严格 SF。
No K-FMNN core points for this K; paper skeleton initialization is undefined. Choose another K; no K-MNN fallback is applied.
为了演示后续完整路径,本页显式启用 core_fallback="kmnn",实际初始化集合改为 S(114 个点)。这是扩展策略,不能等同于论文严格 K-FMNN 流程已经复现成功。p313 仍不是核心点。
展开所有 K-MNN 候选及其未满足条件的互近邻
| S 中候选 | 其互近邻中不属于 S 的点 |
|---|---|
| 6 | 0, 1, 2, 4, 5, 13, 15, 16 |
| 7 | 0, 1, 2, 4, 5, 13, 15, 16 |
| 8 | 0, 4, 5, 11, 12, 13, 15, 16 |
| 9 | 0, 11, 12, 13, 16, 19, 20, 21 |
| 10 | 11, 12, 13, 19, 20, 21, 22, 23 |
| 17 | 11, 12, 13, 16, 19, 20, 21, 28 |
| 18 | 11, 12, 13, 16, 19, 20, 21, 28 |
| 30 | 29, 31, 38, 39, 40, 41, 43 |
| 37 | 26, 27, 32, 33, 34, 35, 36, 38, 39, 50, 51, 52, 53, 54 |
| 42 | 28, 29, 31, 40, 41, 43 |
| 44 | 29, 31, 40, 41, 43 |
| 45 | 29, 31, 38, 40, 41, 43 |
| 46 | 29, 31, 38, 41, 43 |
| 47 | 29, 31, 40, 41, 43 |
| 48 | 29, 31, 40, 41, 43 |
| 49 | 31, 40, 41, 43, 60 |
| 58 | 43, 60, 62, 63, 64 |
| 59 | 60, 62, 63, 64, 65, 66 |
| 61 | 60, 62, 63, 64, 65, 66, 67, 69, 70, 71, 72 |
| 68 | 60, 62, 63, 64, 65, 66, 67, 69, 70, 71, 72 |
| 76 | 67, 69, 70, 71, 72, 77, 84, 85, 86, 87, 88 |
| 83 | 70, 71, 77, 78, 79, 80, 82, 84, 85, 86, 91 |
| 89 | 77, 78, 79, 80, 82, 84, 85, 86, 87, 88, 91 |
| 90 | 77, 78, 79, 80, 82, 84, 85, 86, 87, 88, 91, 94 |
| 107 | 100, 101, 106, 109, 113, 114, 115, 118 |
| 108 | 100, 101, 109, 113, 114, 115, 118 |
| 116 | 106, 113, 114, 115, 118, 123, 124 |
| 117 | 109, 113, 114, 115, 118, 123, 124 |
| 119 | 109, 114, 115, 118, 123, 124, 125 |
| 120 | 109, 114, 118, 123, 124, 125, 126 |
| 121 | 109, 110, 123, 124, 125, 126, 127 |
| 122 | 109, 110, 118, 123, 124, 125, 126, 127 |
| 129 | 110, 111, 126, 127, 128, 131, 132, 137, 138, 139 |
| 130 | 110, 111, 112, 127, 128, 131, 132, 137, 138 |
| 133 | 112, 132, 140, 145, 146, 147, 148 |
| 134 | 111, 112, 131, 132, 137, 140 |
| 135 | 111, 112, 131, 132, 137, 140 |
| 136 | 132, 137, 139, 140, 142, 145 |
| 141 | 137, 139, 140, 142, 145, 146, 147, 148 |
| 143 | 140, 142, 145, 146, 147, 148, 149 |
| 144 | 140, 142, 145, 146, 147, 148, 149 |
| 150 | 142, 145, 146, 149, 154 |
| 155 | 142, 145, 149, 154, 171 |
| 156 | 149, 154, 171, 172 |
| 157 | 149, 151, 152, 154 |
| 158 | 151, 152, 154, 160 |
| 159 | 151, 152, 153, 154, 160, 166, 167, 168, 169 |
| 161 | 152, 153, 154, 160, 173 |
| 162 | 154, 160, 171, 172, 173 |
| 163 | 149, 171, 172, 173, 182 |
| 164 | 149, 171, 172, 173, 182 |
| 165 | 171, 172, 173, 182, 183 |
| 176 | 166, 167, 168, 169, 170, 174, 175, 177, 178, 179, 185, 186 |
| 180 | 168, 169, 170, 173, 174, 175, 178, 179, 184, 185, 187, 188 |
| 181 | 168, 169, 170, 173, 174, 175, 179, 183, 184, 185, 187, 188 |
| 191 | 177, 178, 186, 192, 193, 195, 196, 209, 210 |
| 194 | 186, 192, 193, 195, 196, 209, 210 |
| 197 | 188, 192, 193, 195, 196, 198, 199, 205 |
| 206 | 192, 193, 195, 196, 205, 209, 210 |
| 207 | 192, 193, 195, 196, 209, 210, 211 |
| 208 | 186, 192, 193, 195, 209, 210, 211 |
| 213 | 209, 210, 211, 212, 214 |
| 215 | 205, 209, 210, 211, 220 |
| 216 | 209, 210, 211, 212, 214, 220, 231, 232, 234 |
| 217 | 211, 212, 214, 220, 231, 232, 234, 235 |
| 218 | 211, 212, 214, 230, 231, 232, 234, 235, 236 |
| 219 | 211, 214, 220, 229, 230, 231, 232, 234 |
| 233 | 228, 229, 230, 231, 232, 234, 235, 236, 237, 238, 239 |
| 242 | 240, 241, 243, 249, 250 |
| 251 | 241, 243, 248, 249, 250 |
| 252 | 243, 248, 249, 250, 257 |
| 253 | 225, 243, 244, 250, 257 |
| 254 | 225, 243, 244, 245, 257, 261 |
| 255 | 243, 244, 245, 257, 261 |
| 256 | 243, 250, 257, 260, 261 |
| 258 | 248, 249, 250, 257, 260 |
| 259 | 248, 249, 250, 257, 260, 275 |
| 270 | 264, 265, 266, 268, 269, 271, 283, 287 |
| 272 | 262, 263, 264, 265, 266, 269, 271, 283 |
| 273 | 262, 264, 265, 269, 271, 278, 283, 285 |
| 274 | 257, 261, 262, 264, 275, 276, 277, 278, 279, 280 |
| 281 | 276, 277, 278, 279, 280, 283, 285, 286, 287 |
| 282 | 271, 277, 278, 280, 283, 285, 286, 287 |
| 284 | 265, 269, 271, 283, 285, 286, 287 |
| 289 | 268, 283, 285, 287, 288, 294 |
| 290 | 268, 269, 287, 288, 294 |
| 291 | 268, 269, 287, 288, 294, 297, 302 |
| 292 | 268, 287, 288, 294, 297, 301, 302 |
| 293 | 268, 287, 288, 294, 297, 299, 301 |
| 295 | 268, 294, 297, 302, 303, 308 |
| 296 | 268, 294, 297, 301, 302, 303, 308 |
| 298 | 288, 297, 299, 300, 301, 302, 303 |
| 304 | 294, 297, 300, 301, 302, 303, 306, 308, 309, 310, 313 |
| 307 | 243, 244, 249, 250, 257 |
| 311 | 308, 309, 310, 312, 313, 319, 320, 321 |
| 314 | 309, 310, 312, 313, 319, 320, 321, 322 |
| 315 | 309, 310, 312, 313, 319, 320, 321, 322 |
| 316 | 310, 312, 313, 319, 320, 321, 322, 324 |
| 317 | 309, 310, 312, 313, 319, 320, 322, 324 |
| 318 | 309, 310, 312, 319, 322, 324, 325, 326 |
| 323 | 319, 320, 322, 324, 325, 326 |
| 330 | 324, 325, 326, 327, 328, 329, 334, 335, 346 |
| 331 | 324, 325, 326, 327, 328, 329, 334, 346, 347 |
| 332 | 324, 325, 326, 327, 329, 346, 347, 350 |
| 333 | 326, 327, 328, 329, 334, 346, 347, 348 |
| 343 | 334, 335, 336, 340, 341, 342, 347, 348, 349, 351, 352 |
| 344 | 328, 334, 335, 336, 346, 347, 348, 349, 351, 352 |
| 345 | 334, 346, 347, 348, 349, 350, 351, 352 |
| 356 | 338, 342, 353, 354, 355, 357, 358, 359, 361 |
| 362 | 357, 358, 359, 360, 361, 368, 369, 371, 372 |
| 363 | 358, 359, 360, 361, 367, 368, 369, 371, 372 |
| 364 | 358, 359, 360, 361, 367, 368, 369, 371, 372 |
| 365 | 338, 340, 353, 354, 355, 357, 358, 367, 369 |
| 366 | 337, 338, 339, 340, 341, 354, 355, 367, 369 |
查看实际代码 · dpc_mstsc.py:80
def _find_kfmnn(self):
full = np.array([len(v) == self.K for v in self.mnn_])
self.S_ = np.flatnonzero(full)
self.SF_ = np.array([i for i in self.S_ if full[self.mnn_[i]].all()], dtype=int)
self.strict_SF_ = self.SF_.copy()
self.used_fallback_ = False
if not len(self.SF_) and self.core_fallback == "kmnn" and len(self.S_):
self.SF_ = self.S_.copy()
self.used_fallback_ = True
if not len(self.SF_):
raise ValueError("No K-FMNN core points for this K; paper skeleton initialization "
"is undefined. Choose another K; no K-MNN fallback is applied.")
在这个点的局部完全图上逐步构建 MST
_pruned_mst_density(D) 内调用 _mst(sub)
局部节点按 [i] + KNN(i) 排列,共 15 个节点。完全图有 15×14/2 = 105 条候选无向边,但 MST 最终只有 14 条边。边权是归一化欧氏距离,不是目标点到所有邻居的星形连线。
_mst 使用 Prim:从 p313 开始;key[v] 是当前树到未入树节点 v 的最短边长,parent[v] 保存该边在树中的端点。每次选择 key 最小的节点,加入边,再更新剩余节点的 key 与 parent。
拖动滑块观察真实选边顺序。蓝绿色实线是已经加入树的边;橙色虚线是尚未入树节点的当前最短连接候选。下方表中的边序与滑块一致。
展开 14 次选边记录
| 加边顺序 | 父节点 → 新节点 | 边权 |
|---|---|---|
| 1 | 313 → 310 | 0.02130112 |
| 2 | 310 → 311 | 0.01229799 |
| 3 | 310 → 309 | 0.01493218 |
| 4 | 311 → 312 | 0.01534886 |
| 5 | 311 → 314 | 0.01597584 |
| 6 | 314 → 315 | 0.00766594 |
| 7 | 314 → 317 | 0.01489644 |
| 8 | 315 → 316 | 0.01823623 |
| 9 | 309 → 308 | 0.01885116 |
| 10 | 316 → 320 | 0.02862198 |
| 11 | 320 → 321 | 0.01178197 |
| 12 | 313 → 304 | 0.03303350 |
| 13 | 304 → 303 | 0.01211113 |
| 14 | 303 → 297 | 0.01493218 |
查看实际代码 · dpc_mstsc.py:93
@staticmethod
def _mst(sub):
"""Dense Prim; returns local endpoint pairs and weights, including zero edges."""
n = len(sub)
used = np.zeros(n, dtype=bool)
key = np.full(n, np.inf)
parent = np.full(n, -1, dtype=int)
key[0] = 0
edges, weights = [], []
for _ in range(n):
u = int(np.argmin(np.where(used, np.inf, key)))
used[u] = True
if parent[u] >= 0:
edges.append((parent[u], u))
weights.append(key[u])
improve = (~used) & (sub[u] < key)
key[improve] = sub[u, improve]
parent[improve] = u
return np.asarray(edges, dtype=int).reshape(-1, 2), np.asarray(weights)
剪除异常边,再将保留边转换成密度
_pruned_mst_density(D)
本页采用 prune_mean="all":先计算全部 14 条 MST 边的平均权重,再检查每条边的相对偏差。不是只删除长边:异常短的边也可能被删除。
保留条件:|w − w̄| / w̄ < 0.35
等价区间:0.01114223 < w < 0.02314156
ρ313 = (1 / 14) × Σ保留边 exp(−w) = 0.77362114
本点保留 11 条边,删除 3 条。所有保留边都参与求和,即使剪枝后不再与目标点相连;代码没有额外截取目标点所在的连通分量。
| 边序 | 边端点 | w | |w−w̄|/w̄ | 判断 | exp(−w)/14 的实际贡献 |
|---|---|---|---|---|---|
| 1 | 313–310 | 0.02130112 | 0.24263537 | 保留 | 0.06992315 |
| 2 | 310–311 | 0.01229799 | 0.28257695 | 保留 | 0.07055552 |
| 3 | 310–309 | 0.01493218 | 0.12890720 | 保留 | 0.07036991 |
| 4 | 311–312 | 0.01534886 | 0.10459954 | 保留 | 0.07034060 |
| 5 | 311–314 | 0.01597584 | 0.06802347 | 保留 | 0.07029651 |
| 6 | 314–315 | 0.00766594 | 0.55279500 | 删除 | 0.00000000 |
| 7 | 314–317 | 0.01489644 | 0.13099226 | 保留 | 0.07037243 |
| 8 | 315–316 | 0.01823623 | 0.06383977 | 保留 | 0.07013779 |
| 9 | 309–308 | 0.01885116 | 0.09971251 | 保留 | 0.07009467 |
| 10 | 316–320 | 0.02862198 | 0.66970930 | 删除 | 0.00000000 |
| 11 | 320–321 | 0.01178197 | 0.31267968 | 保留 | 0.07059194 |
| 12 | 313–304 | 0.03303350 | 0.92706235 | 删除 | 0.00000000 |
| 13 | 304–303 | 0.01211113 | 0.29347801 | 保留 | 0.07056871 |
| 14 | 303–297 | 0.01493218 | 0.12890720 | 保留 | 0.07036991 |
论文式(6)对均值的文字表述存在歧义。本例采用“全部局部 MST 边均值”的解释;另一可用选项是 incident。本页的数字严格对应当前 all 配置,没有混合两种定义。
查看实际代码 · dpc_mstsc.py:113
def _pruned_mst_density(self, D):
rho = np.zeros(len(D))
for i in range(len(D)):
nodes = np.r_[i, self.knn_[i]]
edges, weights = self._mst(D[np.ix_(nodes, nodes)])
reference = weights if self.prune_mean == "all" else weights[(edges == 0).any(axis=1)]
mean = reference.mean()
# Eq. 6 is undefined at mean=0: retain zero edges, reject positive ones.
keep = (np.abs(weights - mean) < self.lam * mean) if mean > 0 else (weights == 0)
rho[i] = np.exp(-weights[keep]).sum() / self.K
self.rho_ = rho
计算严格高密度距离 δ 和决策值 γ
_compute_delta_gamma(D)
先对全部 373 个样本重复上一阶段,得到完整密度向量。再从满足 rho[j] > rho[i] 的样本中选距离最近者;相同密度不算更高密度。
最近的严格高密度点:p309,ρ309 = 0.84351529
δ313 = d(313, 309) = 0.03595552
γ313 = ρ313 × δ313 = 0.02781595
本点有 36 个严格高密度候选。若一个点没有任何严格高密度候选,它的 δ 才取该行最大距离。
| 最近的高密度候选 j | ρ(j) | d(i,j) |
|---|---|---|
| 309 | 0.84351529 | 0.03595552 |
| 317 | 0.84267848 | 0.04083640 |
| 312 | 0.84276265 | 0.04140252 |
| 320 | 0.84290051 | 0.04203038 |
| 316 | 0.84290051 | 0.04238922 |
| 318 | 0.77391011 | 0.06302573 |
| 326 | 0.84418359 | 0.10467944 |
| 330 | 0.84378575 | 0.11486436 |
查看实际代码 · dpc_mstsc.py:125
def _compute_delta_gamma(self, D):
n = len(D)
self.delta_ = np.zeros(n)
self.nneigh_ = np.full(n, -1, dtype=int)
for i in range(n):
higher = np.flatnonzero(self.rho_ > self.rho_[i])
if len(higher):
j = higher[np.argmin(D[i, higher])]
self.delta_[i], self.nneigh_[i] = D[i, j], j
else:
self.delta_[i] = D[i].max()
self.gamma_ = self.rho_ * self.delta_
先在全局构建骨架,目标点暂时保持未标记
_build_skeletons()
实际候选集合为回退后的 114 个 K-MNN 点。每次选剩余候选中密度最高者作为新中心,从该中心开始 BFS;只把一阶或二阶邻域内仍在候选集合中的点加入队列。入队时就从剩余集合删除,避免重复入队。
| 骨架标签 | 中心索引 | 中心密度 | 初始骨架点数 |
|---|---|---|---|
| C0 | 116 | 0.91805261 | 90 |
| C1 | 10 | 0.69208486 | 24 |
骨架建成后,skeleton_labels_[313] = −1。灰色 −1 表示尚未分配,不是算法最终判定的噪声。114 个核心点已有标签,剩余 259 个点进入吸引力传播。
查看实际代码 · dpc_mstsc.py:138
def _build_skeletons(self):
remaining = set(self.SF_.tolist())
skeletons, centers = [], []
while remaining:
cen = min(remaining, key=lambda i: (-self.rho_[i], i))
centers.append(cen)
queue, group = deque([cen]), []
remaining.remove(cen)
while queue:
p = queue.popleft()
group.append(p)
reachable = (set(self.knn_[p]) | self.knn2_[p]) & remaining
for q in sorted(reachable):
remaining.remove(q)
queue.append(q)
skeletons.append(np.asarray(group, dtype=int))
self.skeletons_ = skeletons
self.centers_ = np.asarray(centers, dtype=int)
self.n_clusters_ = len(skeletons)
比较每个簇的吸引力,等待全局轮到这个点
_attractiveness_assignment(D, skeleton_labels)
对每个未标记点 i 与每个簇 c,累加该簇所有已标记点 j 的贡献。j 可以是初始骨架点,也可以是刚刚被吸收的非核心点。共享邻居为零的点对,贡献就是零。
A(i,c) = Σj ∈ Cc w(i,j)
(i*,c*) = arg max全部未标记 i、全部簇 c A(i,c)
目标点并不是按输入行号、密度或距离次序被分配。它在全局第 93 轮才成为最大吸引力对应的点,被分给 C0。
| 阶段 | A(i,C0) | A(i,C1) |
|---|---|---|
| 仅有初始骨架 | 67.54413846 | 0.00000000 |
| 第 93 轮分配之前 | 115.20015089 | 0.00000000 |
滑块 t 表示已完成 t 次分配:t=0 只有骨架,t=92 是目标点分配前,t=93 时它已获得标签。实线小图跟踪它等待期间的两个吸引力值;分配后不再把它作为候选评分。
在决定该点归属的一刻,共有 26 个已标记点对它提供正贡献。下面的图与表固定在目标点分配前,不随上方全局时间滑块改变。
SNN = 12,|Δρ| = 0.00073340,d = 0.02130112
w(313,310) = 12 × 0.99926687 × 0.97892414 = 11.73847757
展开分配时全部正贡献与共享样本明细
| j | 所属簇 | SNN | |Δρ| | d(i,j) | 贡献 | 共享样本索引 |
|---|---|---|---|---|---|---|
| 310 | C0 | 12 | 0.00073340 | 0.02130112 | 11.73847757 | 297, 303, 304, 308, 309, 311, 312, 314, 315, 316, 317, 320 |
| 311 | C0 | 11 | 0.00054777 | 0.02648052 | 10.70667077 | 304, 308, 309, 310, 312, 314, 315, 316, 317, 320, 321 |
| 309 | C0 | 11 | 0.06989415 | 0.03595552 | 9.89515850 | 297, 303, 304, 308, 310, 311, 312, 314, 315, 316, 317 |
| 315 | C0 | 9 | 0.00063193 | 0.02459598 | 8.77578887 | 309, 310, 311, 312, 314, 316, 317, 320, 321 |
| 314 | C0 | 9 | 0.00063193 | 0.02622065 | 8.76154273 | 309, 310, 311, 312, 315, 316, 317, 320, 321 |
| 318 | C0 | 8 | 0.00028897 | 0.06302573 | 7.50918423 | 309, 310, 311, 312, 314, 315, 316, 317 |
| 308 | C0 | 9 | 0.13978033 | 0.04479654 | 7.48310422 | 297, 303, 304, 309, 310, 311, 312, 314, 315 |
| 317 | C0 | 8 | 0.06905735 | 0.04083640 | 7.16743460 | 309, 310, 311, 312, 314, 315, 316, 320 |
| 316 | C0 | 8 | 0.06927937 | 0.04238922 | 7.15472479 | 310, 311, 312, 314, 315, 317, 320, 321 |
| 304 | C0 | 6 | 0.21049428 | 0.03303350 | 4.70314622 | 297, 303, 308, 309, 310, 311 |
| 323 | C0 | 5 | 0.14160962 | 0.07070515 | 4.04355048 | 314, 315, 316, 317, 320 |
| 306 | C0 | 4 | 0.28348626 | 0.06711725 | 2.81705172 | 297, 303, 304, 321 |
| 295 | C0 | 4 | 0.28023418 | 0.07441092 | 2.80568935 | 297, 303, 304, 308 |
| 296 | C0 | 4 | 0.35061931 | 0.06155280 | 2.64884116 | 297, 303, 304, 308 |
| 305 | C0 | 3 | 0.21291755 | 0.07561966 | 2.24807676 | 297, 303, 304 |
| 303 | C0 | 3 | 0.28202387 | 0.04468932 | 2.16387177 | 297, 304, 308 |
| 302 | C0 | 3 | 0.27941232 | 0.05349964 | 2.15049992 | 297, 303, 304 |
| 294 | C0 | 3 | 0.28023418 | 0.08074532 | 2.09097987 | 297, 303, 304 |
| 297 | C0 | 3 | 0.35061931 | 0.05317267 | 2.00334905 | 303, 304, 308 |
| 300 | C0 | 3 | 0.35160593 | 0.07631518 | 1.95558852 | 297, 303, 304 |
| 301 | C0 | 3 | 0.42101174 | 0.07045398 | 1.83518734 | 297, 303, 304 |
| 299 | C0 | 2 | 0.35203759 | 0.09842447 | 1.27466719 | 297, 303 |
| 298 | C0 | 2 | 0.42094042 | 0.08526856 | 1.20555278 | 297, 303 |
| 292 | C0 | 1 | 0.28042343 | 0.09255057 | 0.68868314 | 297 |
| 291 | C0 | 1 | 0.28042343 | 0.09273017 | 0.68855946 | 297 |
| 293 | C0 | 1 | 0.28101512 | 0.09765733 | 0.68476987 | 297 |
每吸收一个点 p,只更新它所属簇的一列:A[:, c] += contribution[:, p]。这与重新计算各簇所有贡献之和等价。本页的跟踪重放已与实际函数输出逐项校验。
零距离不同样本仍可贡献吸引力,因为 exp(0)=1;只将自身对自身的对角贡献置零。本例没有重复坐标。
查看实际代码 · dpc_mstsc.py:158
def _attractiveness_assignment(self, D, labels):
labels = labels.copy()
contribution = self.snn_ * np.exp(-np.abs(self.rho_[:, None] - self.rho_[None, :]) - D)
np.fill_diagonal(contribution, 0)
scores = np.column_stack([contribution[:, s].sum(axis=1) for s in self.skeletons_])
pending = labels < 0
self.zero_attraction_assignments_ = 0
while pending.any():
ids = np.flatnonzero(pending)
sub = scores[ids]
pos, cluster = np.unravel_index(np.argmax(sub), sub.shape)
point = ids[pos]
if sub[pos, cluster] == 0:
self.zero_attraction_assignments_ += 1
labels[point] = cluster
pending[point] = False
scores[:, cluster] += contribution[:, point]
return labels
以传播后的邻居标签做一次同步多数投票
_label_refinement(labels)
所有非核心点都完成分配后,才开始精修。对每个点读取同一份传播结果,不把循环内刚改过的标签用于后面的点。本页设置 vote_rule="strict",要求唯一胜者的票数严格大于 K/2=7。
本点的 14 个邻居全部投给 C0,满足严格多数;但它原本已属于 C0,因此标签不变。全数据本轮发生标签变化的点数为 0。
| 邻居 j | 传播完成后的标签 |
|---|---|
| 310 | C0 |
| 315 | C0 |
| 314 | C0 |
| 311 | C0 |
| 304 | C0 |
| 309 | C0 |
| 317 | C0 |
| 312 | C0 |
| 320 | C0 |
| 316 | C0 |
| 321 | C0 |
| 303 | C0 |
| 308 | C0 |
| 297 | C0 |
Algorithm 2 已为全部样本分配标签,因此这里没有遗留的 −1,也没有触发“未标记点归最近已标记点”的步骤。代码不会凭空额外执行一次噪声判定。
查看实际代码 · dpc_mstsc.py:177
def _label_refinement(self, labels):
result = labels.copy()
for i, neighbors in enumerate(self.knn_):
counts = np.bincount(labels[neighbors], minlength=self.n_clusters_)
winners = np.flatnonzero(counts == counts.max())
if len(winners) == 1:
winner = winners[0]
if self.vote_rule == "plurality" or counts[winner] > self.K / 2:
result[i] = winner
return result
把这个点的整条计算路径串起来
fit / fit_predict 的最终输出
“非核心点”不意味着密度一定低;核心资格由互近邻结构决定。本点可以有较高的密度,却因一个不互惠的近邻而未通过 K-MNN 条件。密度、邻域结构与吸引力是不同阶段使用的不同信息。
| 中间变量 | p313 的结果 |
|---|---|
| X[i] | [36.7, 9.15] |
| X_normalized_[i] | [0.8865598027127005, 0.24899598393574296] |
| |KNN(i)| / |MNN(i)| / |KNN²(i)| | 14 / 13 / 32 |
| i ∈ S / i ∈ strict_SF | False / False |
| rho_[i] | 0.77362114 |
| delta_[i] | 0.03595552 |
| gamma_[i] | 0.02781595 |
| nneigh_[i] | 309 |
| skeleton_labels_[i] | -1 |
| labels_before_refinement_[i] | 0 |
| labels_[i] | 0 |
追踪一致性检查与文件指纹
- Prim endpoints and weights match _mst exactly
- Retained-edge contributions equal rho[target]
- BFS order and centers match _build_skeletons
- Propagation replay equals labels_before_refinement_
- Contributor sums equal target attractiveness at assignment
- Refinement replay equals labels_
数据 SHA-256:c61fbbf821d5b20d6a9ab211a1fba1fb2a60feca3ff682903f998f6de73ba8f1
算法源文件 SHA-256:e48c6e0b6bb910ec8f3a817a30d9879215ce3f3470b2ddc209e24cebbe1e2d2a
复现命令:python build_walkthrough.py。HTML 自带全部图片、数值和交互脚本,离线可直接打开;walkthrough_trace.json 保存全部可审计状态,walkthrough_assets/ 保存独立 SVG 图。