Spaces:
Sleeping
Sleeping
| import numpy as np | |
| import hdbscan | |
| import umap | |
| from sklearn.metrics import silhouette_score | |
| from app.core.parser import ParsedLog | |
| class Clusterer: | |
| def __init__(self, min_cluster_size: int = 5): | |
| self.min_cluster_size = min_cluster_size | |
| def reduce(self, embeddings: np.ndarray) -> np.ndarray: | |
| reducer = umap.UMAP( | |
| n_components=2, | |
| n_neighbors=15, | |
| min_dist=0.1, | |
| random_state=42 | |
| ) | |
| return reducer.fit_transform(embeddings) | |
| def cluster(self, embeddings_2d: np.ndarray) -> np.ndarray: | |
| clusterer = hdbscan.HDBSCAN( | |
| min_cluster_size=self.min_cluster_size, | |
| metric='euclidean', | |
| prediction_data=True | |
| ) | |
| clusterer.fit(embeddings_2d) | |
| self.clusterer_ = clusterer | |
| return clusterer.labels_ | |
| def get_probabilities(self) -> np.ndarray: | |
| return self.clusterer_.probabilities_ | |
| def silhouette(self, embeddings_2d: np.ndarray, labels: np.ndarray) -> float: | |
| mask = labels != -1 | |
| if len(set(labels[mask])) < 2 or mask.sum() < 2: | |
| return 0.0 | |
| return float(silhouette_score(embeddings_2d[mask], labels[mask])) | |
| def run(self, embeddings: np.ndarray) -> dict: | |
| print("Reducing dimensions with UMAP...") | |
| coords_2d = self.reduce(embeddings) | |
| print("Clustering with HDBSCAN...") | |
| labels = self.cluster(coords_2d) | |
| probs = self.get_probabilities() | |
| n_clusters = len(set(labels)) - (1 if -1 in labels else 0) | |
| n_noise = int((labels == -1).sum()) | |
| score = self.silhouette(coords_2d, labels) | |
| print(f"Found {n_clusters} clusters, {n_noise} anomalies, silhouette={score:.3f}") | |
| return { | |
| "coords_2d": coords_2d, | |
| "labels": labels, | |
| "probabilities": probs, | |
| "n_clusters": n_clusters, | |
| "n_noise": n_noise, | |
| "silhouette_score": score, | |
| } |