Spaces:
Sleeping
Sleeping
File size: 6,741 Bytes
d52dd8c | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 | """
embedder.py β Semantic Embedding Vector Generator & Visualizer.
Features:
- Generates per-word and per-sentence embeddings using sentence-transformers
- PCA / t-SNE dimensionality reduction for 2D visualization
- UMAP reduction for cluster visualization (if umap-learn installed)
- Cosine semantic similarity heatmap between sentences
- Word cluster labeling
"""
import numpy as np
import streamlit as st
from typing import List, Dict, Any, Optional, Tuple
@st.cache_resource(show_spinner="π’ Loading embedding model (one-time)...")
def get_embedding_model(model_name: str = "all-MiniLM-L6-v2"):
"""Load and cache the sentence-transformer embedding model."""
from sentence_transformers import SentenceTransformer
return SentenceTransformer(model_name)
def embed_texts(
texts: List[str],
model_name: str = "all-MiniLM-L6-v2",
batch_size: int = 32
) -> np.ndarray:
"""
Embed a list of texts into dense vectors.
Returns ndarray of shape (N, embedding_dim).
"""
if not texts:
return np.array([])
model = get_embedding_model(model_name)
embeddings = model.encode(
texts,
batch_size=batch_size,
show_progress_bar=False,
normalize_embeddings=True,
)
return embeddings
def reduce_pca(embeddings: np.ndarray, n_components: int = 2) -> np.ndarray:
"""Reduce embedding dimensions using PCA."""
from sklearn.decomposition import PCA
if len(embeddings) < n_components:
return embeddings
pca = PCA(n_components=n_components, random_state=42)
return pca.fit_transform(embeddings)
def reduce_tsne(embeddings: np.ndarray, n_components: int = 2, perplexity: int = None) -> np.ndarray:
"""Reduce embedding dimensions using t-SNE."""
from sklearn.manifold import TSNE
n = len(embeddings)
if n < 4:
return reduce_pca(embeddings, n_components)
perp = min(perplexity or 30, n - 1)
tsne = TSNE(
n_components=n_components,
perplexity=perp,
random_state=42,
n_iter=500,
learning_rate="auto",
init="pca"
)
return tsne.fit_transform(embeddings)
def reduce_umap(embeddings: np.ndarray, n_components: int = 2) -> Optional[np.ndarray]:
"""Reduce embedding dimensions using UMAP (if available)."""
try:
import umap
reducer = umap.UMAP(n_components=n_components, random_state=42)
return reducer.fit_transform(embeddings)
except ImportError:
return None
def cosine_similarity_matrix(embeddings: np.ndarray) -> np.ndarray:
"""Compute pairwise cosine similarity matrix (assumes normalized embeddings)."""
norms = np.linalg.norm(embeddings, axis=1, keepdims=True)
normalized = embeddings / np.clip(norms, 1e-8, None)
return normalized @ normalized.T
def cluster_words(embeddings: np.ndarray, n_clusters: int = 5) -> np.ndarray:
"""K-Means cluster the embeddings, return cluster labels."""
from sklearn.cluster import KMeans
n = len(embeddings)
k = min(n_clusters, max(1, n // 3))
kmeans = KMeans(n_clusters=k, random_state=42, n_init=10)
return kmeans.fit_predict(embeddings)
def build_embedding_data(
ocr_results: List[Dict[str, Any]],
text_unit: str = "word", # "word" | "sentence" | "line"
reduction: str = "PCA", # "PCA" | "t-SNE" | "UMAP"
n_clusters: int = 5,
) -> Dict[str, Any]:
"""
Full embedding pipeline:
1. Extract text units from OCR results
2. Generate embeddings
3. Reduce to 2D
4. Cluster
5. Return structured data for plotting
Returns dict with keys:
texts, embeddings, coords_2d, cluster_labels, similarity_matrix
"""
if not ocr_results:
return {}
# ββ Step 1: Extract text units ββ
if text_unit == "word":
texts = [r["text"] for r in ocr_results if r["text"].strip()]
elif text_unit == "sentence":
full = " ".join(r["text"] for r in ocr_results)
# Simple sentence split
import re
sentences = re.split(r"(?<=[.!?])\s+", full)
texts = [s.strip() for s in sentences if len(s.strip()) > 3]
else: # line
# Group words into lines by y-proximity
sorted_results = sorted(ocr_results, key=lambda r: r["y_min"])
lines = []
current_line_y = None
current_line_texts = []
for r in sorted_results:
if current_line_y is None or abs(r["y_min"] - current_line_y) < 20:
current_line_texts.append(r["text"])
current_line_y = r["y_min"]
else:
if current_line_texts:
lines.append(" ".join(current_line_texts))
current_line_texts = [r["text"]]
current_line_y = r["y_min"]
if current_line_texts:
lines.append(" ".join(current_line_texts))
texts = [l for l in lines if l.strip()]
if not texts:
return {}
# ββ Step 2: Embed ββ
embeddings = embed_texts(texts)
if embeddings.size == 0:
return {}
# ββ Step 3: Reduce to 2D ββ
if reduction == "t-SNE" and len(texts) >= 4:
coords_2d = reduce_tsne(embeddings)
elif reduction == "UMAP":
coords_2d = reduce_umap(embeddings)
if coords_2d is None:
coords_2d = reduce_pca(embeddings)
else:
coords_2d = reduce_pca(embeddings)
# ββ Step 4: Cluster ββ
if len(texts) >= 2:
cluster_labels = cluster_words(embeddings, n_clusters)
else:
cluster_labels = np.zeros(len(texts), dtype=int)
# ββ Step 5: Similarity matrix ββ
if len(texts) <= 60:
sim_matrix = cosine_similarity_matrix(embeddings)
else:
sim_matrix = None
# ββ Step 6: Top-N similar pairs ββ
similar_pairs = []
if sim_matrix is not None and len(texts) > 1:
for i in range(len(texts)):
for j in range(i + 1, len(texts)):
similar_pairs.append({
"i": i,
"j": j,
"text_i": texts[i],
"text_j": texts[j],
"similarity": float(sim_matrix[i, j]),
})
similar_pairs.sort(key=lambda x: x["similarity"], reverse=True)
similar_pairs = similar_pairs[:10]
return {
"texts": texts,
"embeddings": embeddings,
"coords_2d": coords_2d,
"cluster_labels": cluster_labels.tolist(),
"similarity_matrix": sim_matrix,
"similar_pairs": similar_pairs,
"embedding_dim": embeddings.shape[1] if len(embeddings.shape) > 1 else 0,
"n_texts": len(texts),
"reduction_method": reduction,
}
|