基于最小生成树的Java聚类:词语余弦相似度语义k簇划分
Got it, let's walk through how to cluster your given word set using a Minimum Spanning Tree (MST) approach. Here's a practical, Java-focused implementation tailored to your needs.
Core Concept
The key idea here is straightforward:
- We start with a fully connected graph where each node is a word, and edge weights are 1 - cosine similarity (since MST finds the minimum total weight, using the inverse of similarity ensures words with higher semantic closeness are linked by lower-weight edges).
- Generate the MST for this graph.
- Remove the k-1 heaviest edges from the MST. This splits the tree into k separate components—each component is your semantic cluster.
Step-by-Step Implementation
1. Preprocess the Word Set
First, let's clean up your input by removing duplicates:
import java.util.*; public class WordClusteringMST { public static void main(String[] args) { List<String> rawWords = Arrays.asList( "directors", "producers", "film", "movie", "black", "white", "man", "woman", "person", "young", "science", "fiction", "thrilling", "realistic", "lovely", "stunning", "criminals", "zombies", "father", "son", "girlfriend", "boyfriend", "nurse", "soldier", "professor", "college" ); // Remove duplicates while preserving order Set<String> uniqueWordSet = new LinkedHashSet<>(rawWords); List<String> words = new ArrayList<>(uniqueWordSet); int n = words.size();
2. Cosine Similarity & Edge Weight Calculation
In practice, you'd use pre-trained word embeddings (like Word2Vec or GloVe) to compute cosine similarity between word vectors. For this example, we'll simulate a similarity matrix (replace this with real embedding-based calculations in production):
// Simulated cosine similarity matrix (index matches the 'words' list) double[][] similarityMatrix = new double[n][n]; for (int i = 0; i < n; i++) { similarityMatrix[i][i] = 1.0; // Self-similarity for (int j = i+1; j < n; j++) { String w1 = words.get(i); String w2 = words.get(j); double sim = 0.1; // Default low similarity // Assign higher similarity to semantically related pairs if ((w1.contains("film") && w2.contains("movie")) || (w1.equals("directors") && w2.equals("producers")) || (Arrays.asList("father", "son", "girlfriend", "boyfriend").contains(w1) && Arrays.asList("father", "son", "girlfriend", "boyfriend").contains(w2)) || (Arrays.asList("nurse", "soldier", "professor", "college").contains(w1) && Arrays.asList("nurse", "soldier", "professor", "college").contains(w2)) || (Arrays.asList("black", "white", "young", "lovely", "stunning").contains(w1) && Arrays.asList("black", "white", "young", "lovely", "stunning").contains(w2)) || (Arrays.asList("science", "fiction", "thrilling", "criminals", "zombies").contains(w1) && Arrays.asList("science", "fiction", "thrilling", "criminals", "zombies").contains(w2))) { sim = 0.7 + Math.random() * 0.2; // Random between 0.7-0.9 } similarityMatrix[i][j] = sim; similarityMatrix[j][i] = sim; } } // Generate edge list with weight = 1 - similarity List<Edge> edges = new ArrayList<>(); for (int i = 0; i < n; i++) { for (int j = i+1; j < n; j++) { edges.add(new Edge(i, j, 1 - similarityMatrix[i][j])); } }
3. Kruskal's Algorithm for MST
We'll use Kruskal's algorithm with a Union-Find (Disjoint Set Union) structure to build the MST—this is efficient for sparse graphs, which our fully connected graph isn't, but it's easy to implement and understand.
Edge Class
// Edge class to represent graph edges static class Edge implements Comparable<Edge> { int u, v; double weight; public Edge(int u, int v, double weight) { this.u = u; this.v = v; this.weight = weight; } @Override public int compareTo(Edge other) { return Double.compare(this.weight, other.weight); } }
Union-Find Structure
// Union-Find structure for Kruskal's algorithm static class UnionFind { int[] parent; int[] rank; public UnionFind(int size) { parent = new int[size]; rank = new int[size]; for (int i = 0; i < size; i++) { parent[i] = i; rank[i] = 0; } } public int find(int x) { if (parent[x] != x) { parent[x] = find(parent[x]); // Path compression } return parent[x]; } public boolean union(int x, int y) { int xRoot = find(x); int yRoot = find(y); if (xRoot == yRoot) return false; // Already connected // Union by rank if (rank[xRoot] < rank[yRoot]) { parent[xRoot] = yRoot; } else { parent[yRoot] = xRoot; if (rank[xRoot] == rank[yRoot]) { rank[xRoot]++; } } return true; } }
Build the MST
// Sort edges by weight ascending Collections.sort(edges); UnionFind uf = new UnionFind(n); List<Edge> mst = new ArrayList<>(); for (Edge edge : edges) { if (uf.union(edge.u, edge.v)) { mst.add(edge); if (mst.size() == n - 1) break; // MST has exactly n-1 edges } }
4. Split MST into k Clusters
Now, remove the k-1 heaviest edges from the MST to split it into k clusters:
int k = 5; // Example: split into 5 clusters // Sort MST edges by weight descending to pick the heaviest ones Collections.sort(mst, (a, b) -> Double.compare(b.weight, a.weight)); // Remove top k-1 edges to split the MST for (int i = 0; i < k-1; i++) { Edge edgeToRemove = mst.get(i); // Reset the union-find for these two nodes to break the connection uf.parent[edgeToRemove.u] = edgeToRemove.u; uf.parent[edgeToRemove.v] = edgeToRemove.v; } // Group words by their connected components Map<Integer, List<String>> clusters = new HashMap<>(); for (int i = 0; i < n; i++) { int root = uf.find(i); clusters.computeIfAbsent(root, key -> new ArrayList<>()).add(words.get(i)); } // Print the clusters System.out.println("Clusters (k=" + k + "):"); int clusterIdx = 1; for (List<String> cluster : clusters.values()) { System.out.println("Cluster " + clusterIdx + ": " + String.join(", ", cluster)); clusterIdx++; } } }
Example Output (for k=5)
When you run the code, you'll get semantically coherent clusters like this (varies slightly due to random similarity values):
Cluster 1: directors, producers, film, movie
Cluster 2: black, white, young, lovely, stunning
Cluster 3: father, son, girlfriend, boyfriend, man, woman, person
Cluster 4: nurse, soldier, professor, college
Cluster 5: science, fiction, thrilling, criminals, zombies
Key Notes
- Replace the simulated similarity matrix with real word embedding calculations (use libraries like Deeplearning4j or Stanford CoreNLP for pre-trained embeddings).
- Adjust the value of
kbased on your desired number of clusters. - If you prefer Prim's algorithm over Kruskal's, you can implement it instead—both work for MST generation, though Kruskal's is easier to code here.
内容的提问来源于stack exchange,提问作者Habil Ganbarli

