这里会显示出您选择的修订版和当前版本之间的差别。
| 两侧同时换到之前的修订记录 前一修订版 后一修订版 | 前一修订版 | ||
|
zh:courses:textmining2026:ch05 [2026/10/12 15:13] pzczxs [KMeans聚类方法] |
zh:courses:textmining2026:ch05 [2026/10/20 07:27] (当前版本) pzczxs [AP聚类方法] |
||
|---|---|---|---|
| 行 1: | 行 1: | ||
| ====== 第五章:文本聚类 ====== | ====== 第五章:文本聚类 ====== | ||
| ===== 课件 ===== | ===== 课件 ===== | ||
| - | 下载:文本聚类 | + | 下载:{{ :zh:courses:textmining2026:ch05.pptx |文本聚类}} |
| ===== 数据集 ===== | ===== 数据集 ===== | ||
| - | 下载: | + | 下载:{{ :zh:courses:textmining2026:chapter5.zip |}} |
| ===== 工具类 ===== | ===== 工具类 ===== | ||
| 行 980: | 行 980: | ||
| ===== AP聚类方法 ===== | ===== AP聚类方法 ===== | ||
| + | <file java AffinityPropagation.java> | ||
| + | package cn.edu.bjut.textmining.chapter5; | ||
| + | |||
| + | import java.util.ArrayList; | ||
| + | import java.util.Arrays; | ||
| + | import java.util.List; | ||
| + | |||
| + | /** s(i,k)=-||x_i-x_k||²;责任度和可用度采用同步更新与阻尼。 */ | ||
| + | public final class AffinityPropagation { | ||
| + | private AffinityPropagation() {} | ||
| + | |||
| + | public static final class Result { | ||
| + | public final int[] labels; | ||
| + | public final int[] exemplars; | ||
| + | public final int iterations; | ||
| + | public final boolean converged; | ||
| + | public final double preference; | ||
| + | |||
| + | Result( | ||
| + | int[] labels, | ||
| + | int[] exemplars, | ||
| + | int iterations, | ||
| + | boolean converged, | ||
| + | double preference) { | ||
| + | this.labels = labels; | ||
| + | this.exemplars = exemplars; | ||
| + | this.iterations = iterations; | ||
| + | this.converged = converged; | ||
| + | this.preference = preference; | ||
| + | } | ||
| + | } | ||
| + | |||
| + | public static double[][] similarities(double[][] vectors) { | ||
| + | KMeansEngine.validate(vectors); | ||
| + | double[][] s = new double[vectors.length][vectors.length]; | ||
| + | for (int i = 0; i < s.length; i++) { | ||
| + | for (int k = 0; k < s.length; k++) { | ||
| + | for (int j = 0; j < vectors[i].length; j++) { | ||
| + | double delta = vectors[i][j] - vectors[k][j]; | ||
| + | s[i][k] -= delta * delta; | ||
| + | } | ||
| + | } | ||
| + | } | ||
| + | return s; | ||
| + | } | ||
| + | |||
| + | public static double medianPreference(double[][] s) { | ||
| + | if (s.length < 2) { | ||
| + | throw new IllegalArgumentException("AP至少需要两个样本"); | ||
| + | } | ||
| + | double[] values = new double[s.length * (s.length - 1)]; | ||
| + | int p = 0; | ||
| + | for (int i = 0; i < s.length; i++) { | ||
| + | for (int k = 0; k < s.length; k++) { | ||
| + | if (i != k) { | ||
| + | values[p++] = s[i][k]; | ||
| + | } | ||
| + | } | ||
| + | } | ||
| + | Arrays.sort(values); | ||
| + | return (values[(p - 1) / 2] + values[p / 2]) / 2; | ||
| + | } | ||
| + | |||
| + | public static Result fit( | ||
| + | double[][] vectors, double damping, int maxIterations, int stableRounds) { | ||
| + | double[][] s = similarities(vectors); | ||
| + | int n = s.length; | ||
| + | double preference = medianPreference(s); | ||
| + | if (damping < 0.5 || damping >= 1 || maxIterations < 1 || stableRounds < 1) { | ||
| + | throw new IllegalArgumentException("请检查阻尼、迭代上限和稳定轮数"); | ||
| + | } | ||
| + | for (int i = 0; i < n; i++) { | ||
| + | s[i][i] = preference; | ||
| + | } | ||
| + | double[][] r = new double[n][n], a = new double[n][n]; | ||
| + | boolean[] previous = new boolean[n]; | ||
| + | int stable = 0, completed = 0; | ||
| + | boolean converged = false; | ||
| + | for (int iteration = 0; iteration < maxIterations; iteration++) { | ||
| + | completed++; | ||
| + | updateResponsibilities(s, r, a, damping); | ||
| + | updateAvailabilities(r, a, damping); | ||
| + | boolean[] current = new boolean[n]; | ||
| + | int count = 0; | ||
| + | for (int k = 0; k < n; k++) { | ||
| + | current[k] = r[k][k] + a[k][k] > 0; | ||
| + | if (current[k]) { | ||
| + | count++; | ||
| + | } | ||
| + | } | ||
| + | stable = Arrays.equals(previous, current) ? stable + 1 : 0; | ||
| + | previous = current; | ||
| + | if (count > 0 && stable >= stableRounds) { | ||
| + | converged = true; | ||
| + | break; | ||
| + | } | ||
| + | } | ||
| + | List<Integer> chosen = new ArrayList<>(); | ||
| + | for (int k = 0; k < n; k++) { | ||
| + | if (r[k][k] + a[k][k] > 0) { | ||
| + | chosen.add(k); | ||
| + | } | ||
| + | } | ||
| + | if (chosen.isEmpty()) { | ||
| + | throw new IllegalStateException("AP尚未产生代表点,请调整偏好、阻尼或迭代上限"); | ||
| + | } | ||
| + | int[] labels = new int[n], exemplars = new int[chosen.size()]; | ||
| + | for (int c = 0; c < exemplars.length; c++) { | ||
| + | exemplars[c] = chosen.get(c); | ||
| + | } | ||
| + | // 以原始相似度分配给代表点;代表点归入自身簇。 | ||
| + | for (int i = 0; i < n; i++) { | ||
| + | double best = -Double.MAX_VALUE; | ||
| + | for (int c = 0; c < exemplars.length; c++) { | ||
| + | if (s[i][exemplars[c]] > best) { | ||
| + | best = s[i][exemplars[c]]; | ||
| + | labels[i] = c; | ||
| + | } | ||
| + | } | ||
| + | } | ||
| + | for (int c = 0; c < exemplars.length; c++) { | ||
| + | labels[exemplars[c]] = c; | ||
| + | } | ||
| + | return new Result(labels, exemplars, completed, converged, preference); | ||
| + | } | ||
| + | |||
| + | public static void updateResponsibilities( | ||
| + | double[][] s, double[][] r, double[][] a, double damping) { | ||
| + | for (int i = 0; i < s.length; i++) { | ||
| + | for (int k = 0; k < s.length; k++) { | ||
| + | double maxOther = -Double.MAX_VALUE; | ||
| + | for (int j = 0; j < s.length; j++) { | ||
| + | if (j != k) { | ||
| + | maxOther = Math.max(maxOther, a[i][j] + s[i][j]); | ||
| + | } | ||
| + | } | ||
| + | double next = s[i][k] - maxOther; | ||
| + | r[i][k] = damping * r[i][k] + (1 - damping) * next; | ||
| + | } | ||
| + | } | ||
| + | } | ||
| + | |||
| + | public static void updateAvailabilities(double[][] r, double[][] a, double damping) { | ||
| + | for (int k = 0; k < r.length; k++) { | ||
| + | double positiveSum = 0; | ||
| + | for (int i = 0; i < r.length; i++) { | ||
| + | if (i != k) { | ||
| + | positiveSum += Math.max(0, r[i][k]); | ||
| + | } | ||
| + | } | ||
| + | for (int i = 0; i < r.length; i++) { | ||
| + | double next = | ||
| + | i == k | ||
| + | ? positiveSum | ||
| + | : Math.min(0, r[k][k] + positiveSum - Math.max(0, r[i][k])); | ||
| + | a[i][k] = damping * a[i][k] + (1 - damping) * next; | ||
| + | } | ||
| + | } | ||
| + | } | ||
| + | } | ||
| + | </file> | ||
| + | |||
| + | <file java AffinityPropagationDemo.java> | ||
| + | package cn.edu.bjut.textmining.chapter5; | ||
| + | |||
| + | import cn.edu.bjut.textmining.chapter5.support.SampleNews; | ||
| + | |||
| + | /** 在同一TF-IDF空间中传播消息并选取新闻代表。 */ | ||
| + | public class AffinityPropagationDemo { | ||
| + | public static void main(String[] args) { | ||
| + | try { | ||
| + | double[][] vectors = SampleNews.vectors(); | ||
| + | AffinityPropagation.Result result = AffinityPropagation.fit(vectors, 0.7, 300, 20); | ||
| + | System.out.printf( | ||
| + | "偏好值=%.9f,迭代轮数=%d,收敛=%s%n", | ||
| + | result.preference, result.iterations, result.converged); | ||
| + | System.out.print("代表点:"); | ||
| + | for (int i : result.exemplars) { | ||
| + | System.out.print("N" + (i + 1) + " "); | ||
| + | } | ||
| + | System.out.println(); | ||
| + | SampleNews.printClusters(result.labels); | ||
| + | } catch (Exception e) { | ||
| + | e.printStackTrace(); | ||
| + | } | ||
| + | } | ||
| + | } | ||
| + | </file> | ||
| + | |||
| + | ===== 上机作业 ===== | ||
| + | * 完成每类(内部度量法、外部度量法和基于聚类稳定性的方法)聚类性能评价中的至少一种。 | ||
| + | ~~DISCUSSION~~ | ||