wordviz 0.1.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
wordviz-0.1.1/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 Elena Zen
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
wordviz-0.1.1/PKG-INFO ADDED
@@ -0,0 +1,82 @@
1
+ Metadata-Version: 2.3
2
+ Name: wordviz
3
+ Version: 0.1.1
4
+ Summary: A lightweight Python package for loading, analyzing, and visualizing word embeddings.
5
+ License: MIT
6
+ Author: Elena Zen
7
+ Author-email: elenazen563@gmail.com
8
+ Requires-Python: >=3.11
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: Programming Language :: Python :: 3.11
12
+ Classifier: Programming Language :: Python :: 3.12
13
+ Classifier: Programming Language :: Python :: 3.13
14
+ Requires-Dist: adjusttext (>=1.3.0,<2.0.0)
15
+ Requires-Dist: gensim (>=4.3.3,<5.0.0)
16
+ Requires-Dist: ipykernel (>=6.29.5,<7.0.0)
17
+ Requires-Dist: jupyter (>=1.1.1,<2.0.0)
18
+ Requires-Dist: matplotlib (>=3.10.1,<4.0.0)
19
+ Requires-Dist: numpy (>=1.18.5,<2.0)
20
+ Requires-Dist: plotly (>=6.0.1,<7.0.0)
21
+ Requires-Dist: scikit-learn (>=1.6.1,<2.0.0)
22
+ Requires-Dist: scipy (>=1.7.0,<1.14.0)
23
+ Requires-Dist: seaborn (>=0.13.2,<0.14.0)
24
+ Requires-Dist: umap-learn (>=0.5.7,<0.6.0)
25
+ Description-Content-Type: text/markdown
26
+
27
+ # WordViz
28
+
29
+ **WordViz** is a Python visualization library designed for exploring and visualizing word embeddings. Built on top of popular libraries such as `matplotlib`, `plotly`, and `gensim`, WordViz provides intuitive tools for analyzing embeddings through clustering, similarity exploration, and dimensionality reduction, all wrapped in interactive and customizable plots.
30
+ With WordViz, users can gain insights into the structure of their word embeddings, making it a valuable tool for researchers and practitioners in natural language processing.
31
+
32
+ ## Main Features
33
+
34
+ - Load and explore pretrained embeddings (e.g., GloVe, FastText)
35
+ - Select from a variety of available embeddings
36
+ - Visualize embeddings in 2D with flexible dimensionality reduction options
37
+ - Identify and plot the most similar words to a given token
38
+ - Visualize clusters of related words
39
+ - Interactive plots powered by `plotly`
40
+ - Support for both light and dark themes
41
+
42
+
43
+ ## Installation
44
+
45
+ Install the latest version from PyPI:
46
+
47
+ ```bash
48
+ pip install wordviz
49
+ ```
50
+
51
+
52
+ ## Usage
53
+
54
+ You can load and manage embeddings though the `EmbeddingLoader` class, and then visualize them with the `Visualizer` class.
55
+
56
+ ```python
57
+ from wordviz.loading import EmbeddingLoader
58
+ from wordviz.plotting import Visualizer
59
+
60
+ loader = EmbeddingLoader()
61
+ loader.load_from_file('path/to/your/embedding/file', 'word2vec')
62
+
63
+ vis = Visualizer(loader)
64
+ vis.plot_embeddings()
65
+ ```
66
+
67
+ You can explore all functionalities through the example notebook provided in the `docs/` folder:
68
+
69
+ 👉 [View example notebook](docs/example.ipynb)
70
+
71
+
72
+ ## Contributing
73
+
74
+ This project was created as part of my Bachelor's Degree thesis. For now, it remains a personal project and is not yet open to public collaboration.
75
+ However, it will be further developed and eventually opened to contributions.
76
+
77
+ In the meantime, if you want to suggest features or report bugs, feel free to contact me directly.
78
+
79
+
80
+ ## License
81
+
82
+ This project is licensed under the MIT License.
@@ -0,0 +1,56 @@
1
+ # WordViz
2
+
3
+ **WordViz** is a Python visualization library designed for exploring and visualizing word embeddings. Built on top of popular libraries such as `matplotlib`, `plotly`, and `gensim`, WordViz provides intuitive tools for analyzing embeddings through clustering, similarity exploration, and dimensionality reduction, all wrapped in interactive and customizable plots.
4
+ With WordViz, users can gain insights into the structure of their word embeddings, making it a valuable tool for researchers and practitioners in natural language processing.
5
+
6
+ ## Main Features
7
+
8
+ - Load and explore pretrained embeddings (e.g., GloVe, FastText)
9
+ - Select from a variety of available embeddings
10
+ - Visualize embeddings in 2D with flexible dimensionality reduction options
11
+ - Identify and plot the most similar words to a given token
12
+ - Visualize clusters of related words
13
+ - Interactive plots powered by `plotly`
14
+ - Support for both light and dark themes
15
+
16
+
17
+ ## Installation
18
+
19
+ Install the latest version from PyPI:
20
+
21
+ ```bash
22
+ pip install wordviz
23
+ ```
24
+
25
+
26
+ ## Usage
27
+
28
+ You can load and manage embeddings though the `EmbeddingLoader` class, and then visualize them with the `Visualizer` class.
29
+
30
+ ```python
31
+ from wordviz.loading import EmbeddingLoader
32
+ from wordviz.plotting import Visualizer
33
+
34
+ loader = EmbeddingLoader()
35
+ loader.load_from_file('path/to/your/embedding/file', 'word2vec')
36
+
37
+ vis = Visualizer(loader)
38
+ vis.plot_embeddings()
39
+ ```
40
+
41
+ You can explore all functionalities through the example notebook provided in the `docs/` folder:
42
+
43
+ 👉 [View example notebook](docs/example.ipynb)
44
+
45
+
46
+ ## Contributing
47
+
48
+ This project was created as part of my Bachelor's Degree thesis. For now, it remains a personal project and is not yet open to public collaboration.
49
+ However, it will be further developed and eventually opened to contributions.
50
+
51
+ In the meantime, if you want to suggest features or report bugs, feel free to contact me directly.
52
+
53
+
54
+ ## License
55
+
56
+ This project is licensed under the MIT License.
@@ -0,0 +1,31 @@
1
+ [tool.poetry]
2
+ name = "wordviz"
3
+ version = "0.1.1"
4
+ description = "A lightweight Python package for loading, analyzing, and visualizing word embeddings."
5
+ authors = ["Elena Zen <elenazen563@gmail.com>"]
6
+ license = "MIT"
7
+ readme = "README.md"
8
+
9
+ [tool.poetry.dependencies]
10
+ python = ">=3.11"
11
+ matplotlib = ">=3.10.1,<4.0.0"
12
+ numpy = ">=1.18.5,<2.0"
13
+ gensim = ">=4.3.3,<5.0.0"
14
+ scipy = ">=1.7.0,<1.14.0"
15
+ scikit-learn = ">=1.6.1,<2.0.0"
16
+ plotly = ">=6.0.1,<7.0.0"
17
+ seaborn = ">=0.13.2,<0.14.0"
18
+ adjusttext = "^1.3.0"
19
+ umap-learn = ">=0.5.7,<0.6.0"
20
+ ipykernel = ">=6.29.5,<7.0.0"
21
+ jupyter = ">=1.1.1,<2.0.0"
22
+
23
+ [tool.poetry.group.dev.dependencies]
24
+ pytest = "^8.3.5"
25
+ black = "^25.1.0"
26
+ isort = "^6.0.1"
27
+ sphinx = "^8.2.3"
28
+
29
+ [build-system]
30
+ requires = ["poetry-core>=2.0.0,<3.0.0"]
31
+ build-backend = "poetry.core.masonry.api"
File without changes
@@ -0,0 +1,44 @@
1
+ import numpy as np
2
+ from sklearn.cluster import KMeans, DBSCAN
3
+ from typing import Tuple, Optional
4
+ from .dim_reduction import reduce_dim
5
+
6
+
7
+ def create_clusters(vectors: np.ndarray, n_clusters: int = 5, method: str = 'kmeans') -> Tuple[np.ndarray, Optional[np.ndarray], np.ndarray]:
8
+ '''
9
+ Performs clustering on embeddings for visualization purposes.
10
+ If the input vectors have more than 2 dimensions, dimensionality reduction is applied first.
11
+
12
+ Parameters:
13
+ -----------
14
+ vectors : np.ndarray
15
+ Array of embeddings to cluster.
16
+ n_clusters : int, default=5
17
+ Number of clusters to generate (used only for k-means).
18
+ method : str, default='kmeans'
19
+ Clustering method to use ('kmeans' or 'dbscan').
20
+
21
+ Returns:
22
+ --------
23
+ labels : np.ndarray
24
+ Cluster labels assigned to each vector.
25
+ centers : np.ndarray or None
26
+ Coordinates of cluster centers (only for k-means; None for dbscan).
27
+ reduced_emb : np.ndarray
28
+ 2D reduced embeddings used for clustering and plotting.
29
+ '''
30
+
31
+ if vectors.shape[1] > 2:
32
+ reduced_emb = reduce_dim(vectors)
33
+ else:
34
+ reduced_emb = vectors
35
+
36
+ match method:
37
+ case 'kmeans':
38
+ clustering = KMeans(n_clusters=n_clusters, random_state=0, n_init="auto").fit(reduced_emb)
39
+ centers = clustering.cluster_centers_
40
+ case 'dbscan':
41
+ clustering = DBSCAN(eps=0.5, min_samples=n_clusters).fit(reduced_emb)
42
+ centers = None
43
+
44
+ return clustering.labels_, centers, reduced_emb
@@ -0,0 +1,76 @@
1
+ import umap
2
+ import numpy as np
3
+ from sklearn.decomposition import PCA
4
+ from sklearn.manifold import TSNE
5
+ from sklearn.manifold import Isomap
6
+ from sklearn.manifold import MDS
7
+ import warnings
8
+
9
+ def reduce_dim(vectors: np.ndarray, method: str = 'pca', n_dimensions: int = 2, dist: str = 'euclidean', **kwargs) -> np.ndarray:
10
+ '''
11
+ Applies dimensionality reduction for visualization to input vectors using a specified method.
12
+
13
+ Parameters:
14
+ -----------
15
+ vectors: array
16
+ Input high-dimensional data to reduce.
17
+ method: str, default:'pca'
18
+ Dimensionality reduction algorithm to apply. Options are:
19
+ - 'pca': Principal Component Analysis
20
+ - 'tsne': t-Distributed Stochastic Neighbor Embedding
21
+ - 'umap': Uniformed Manifold Approximation and Projection
22
+ - 'isomap': Isometric Mapping
23
+ - 'mds': Multidimensional Scaling
24
+ n_dimensions: int, default:2
25
+ Number of dimensions for output. Must be 2 or 3
26
+ dist : str, default='euclidean'
27
+ Distance metric for methods that require a distance matrix (MDS). Ignored for other methods.
28
+ **kwargs : dict
29
+ Additional parameters passed to the selected dimensionality reduction algorithm.
30
+
31
+ Returns:
32
+ --------
33
+ np.ndarray
34
+ Embedding reduced at specified dimensions.
35
+ '''
36
+
37
+ if n_dimensions not in [2, 3]:
38
+ raise ValueError('n_dimensions has to be 2 or 3')
39
+
40
+ if (method in ['isomap', 'tsne', 'mds']) and vectors.shape[0] > 5000:
41
+ warnings.warn(f"Warning: {method} is a computational heavy method, loading very large embeddings without subsetting may result in execution times so long that the process may never complete.")
42
+
43
+ default_params = {
44
+ 'pca': {},
45
+ 'tsne': {'random_state': 42, 'perplexity': 30},
46
+ 'umap': {'n_neighbors': 15, 'min_dist': 0.1, 'random_state': 42},
47
+ 'isomap': {'n_neighbors': 5},
48
+ 'mds': {'metric':True, 'n_init':3, 'max_iter':300, 'random_state':42, 'dissimilarity': 'euclidean'}
49
+ }
50
+
51
+ if method in default_params:
52
+ params = {**default_params[method.lower()], **kwargs}
53
+ else:
54
+ raise ValueError(f"Method {method} not supported. Choose between 'pca', 'tsne', 'umap' or 'isomap'")
55
+
56
+ match method.lower():
57
+ case 'pca':
58
+ reducer = PCA(n_components=n_dimensions, **params)
59
+ case 'tsne':
60
+ reducer = TSNE(n_components=n_dimensions, **params)
61
+ case 'umap':
62
+ reducer = umap.UMAP(n_components=n_dimensions, **params)
63
+ case 'isomap':
64
+ reducer = Isomap(n_components=n_dimensions, **params)
65
+ case 'mds':
66
+ reducer = MDS(n_components=n_dimensions,
67
+ dissimilarity='euclidean' if dist == 'euclidean' else 'precomputed', **params)
68
+ if dist != 'precomputed' and dist != 'euclidean':
69
+ from sklearn.metrics import pairwise_distances
70
+ vectors = pairwise_distances(vectors, metric=dist)
71
+
72
+ reduced_emb = reducer.fit_transform(vectors)
73
+
74
+ return reduced_emb
75
+
76
+
@@ -0,0 +1,269 @@
1
+ import os
2
+ import shutil
3
+ from gensim.models import KeyedVectors
4
+ from gensim.scripts.glove2word2vec import glove2word2vec
5
+ from gensim.models.fasttext import load_facebook_model
6
+ import numpy as np
7
+ import zipfile
8
+ from pathlib import Path
9
+ import urllib.request
10
+
11
+
12
+ class EmbeddingLoader:
13
+ def __init__(self):
14
+ self.embeddings_raw = None
15
+ self.embeddings = None
16
+ self.tokens = None
17
+ self.dimension = None
18
+ self.embeddings_subset = None
19
+ self.tokens_subset = None
20
+
21
+ self.available_pretrained = {
22
+ 'glove': {
23
+ 'en': {
24
+ 'wiki': {
25
+ '100d': {
26
+ 'url': 'https://nlp.stanford.edu/data/glove.6B.zip',
27
+ 'filename': 'glove.6B.100d.txt'
28
+ }
29
+ },
30
+ 'cc': {
31
+ '300d': {
32
+ 'url': 'https://nlp.stanford.edu/data/glove.42B.300d.zip',
33
+ 'filename': 'glove.42B.300d.txt'
34
+ }
35
+ },
36
+ 'twitter': {
37
+ '100d': {
38
+ 'url': 'https://nlp.stanford.edu/data/glove.twitter.27B.zip',
39
+ 'filename': 'glove.twitter.27B.100d.txt'
40
+ }
41
+ }
42
+ }
43
+ },
44
+ 'fasttext': {
45
+ 'it': {
46
+ 'cc': {
47
+ '300d': {
48
+ 'url': 'https://dl.fbaipublicfiles.com/fasttext/vectors-crawl/cc.it.300.bin.gz'
49
+ }
50
+ }
51
+ }
52
+ }
53
+ }
54
+
55
+ def get_cache_dir(self):
56
+ cache_dir = Path.home() / ".wordviz_cache"
57
+ cache_dir.mkdir(parents=True, exist_ok=True)
58
+ return cache_dir
59
+
60
+
61
+ def _validate_file(self, path):
62
+ '''checks if path argument leads to a valid file name and returns if it is binary'''
63
+ valid_ext = ['.bin', '.txt', '.vec']
64
+
65
+ if not isinstance(path, str):
66
+ path = str(path)
67
+ _, ext = os.path.splitext(path.lower())
68
+
69
+ if path is None:
70
+ raise ValueError('File path is required')
71
+
72
+ if not isinstance(path, str):
73
+ raise TypeError('The file path must be a string')
74
+
75
+ if not os.path.exists(path):
76
+ raise FileNotFoundError(f"Invalid file path {path}: the file does not exist")
77
+
78
+ if ext not in valid_ext:
79
+ raise ValueError(f'Invalid file extension {ext}. Valid extensions are: {','.join(valid_ext)}')
80
+
81
+ binary = True if ext == '.bin' else False
82
+
83
+ return binary
84
+
85
+
86
+ def load_from_file(self, path: str, format: str) -> np.ndarray:
87
+ '''
88
+ Loads word embeddings from a file in .txt, .vec, or .bin format.
89
+
90
+ Parameters
91
+ -----------
92
+ path : str
93
+ Path to the embedding file.
94
+ format : str
95
+ Format of the embedding model: 'word2vec', 'fasttext', or 'glove'.
96
+
97
+ Returns
98
+ --------
99
+ np.ndarray
100
+ Loaded embedding matrix.
101
+
102
+ Notes:
103
+ ------
104
+ - For GloVe files, they are first converted to word2vec format.
105
+ - FastText binary files are supported via Facebook's native loader.
106
+ - Loaded tokens are stored in self.tokens.
107
+ - Embedding matrix is stored in self.embeddings.
108
+ '''
109
+
110
+ binary = self._validate_file(path)
111
+
112
+ match format:
113
+ case 'word2vec':
114
+ self.embeddings_raw = KeyedVectors.load_word2vec_format(path, binary=binary)
115
+ case 'fasttext':
116
+ if binary:
117
+ self.embeddings_raw = load_facebook_model(path)
118
+ else:
119
+ self.embeddings_raw = KeyedVectors.load_word2vec_format(path, binary=False)
120
+ case 'glove':
121
+ glove2word2vec(path, "glove_w2v.txt")
122
+ if not os.path.exists("glove_w2v.txt"):
123
+ raise RuntimeError("GloVe to Word2Vec conversion failed.")
124
+
125
+ self.embeddings_raw = KeyedVectors.load_word2vec_format("glove_w2v.txt")
126
+
127
+ self.tokens = list(self.embeddings_raw.index_to_key)
128
+ self.dimension = self.embeddings_raw.vector_size
129
+
130
+ words = self.embeddings_raw.index_to_key
131
+ self.embeddings = np.array([self.embeddings_raw.get_vector(word) for word in words])
132
+ print("Embedding loaded from file")
133
+
134
+ return self.embeddings
135
+
136
+
137
+ def download_zip(self, url, filename):
138
+ '''downloads zip file from url'''
139
+ zip_path = self.get_cache_dir() / filename
140
+ if not zip_path.exists():
141
+ print(f"Downloading {filename}...")
142
+ urllib.request.urlretrieve(url, zip_path)
143
+ else:
144
+ print(f"{filename} already exists in cache.")
145
+ return zip_path
146
+
147
+
148
+ def export_embedding(self, source_path, dest_folder):
149
+ '''saves locally pretrained embeddings file'''
150
+ os.makedirs(dest_folder, exist_ok=True)
151
+ filename = os.path.basename(source_path)
152
+ dest_path = os.path.join(dest_folder, filename)
153
+ shutil.copy(source_path, dest_path)
154
+ print(f"File saved in {dest_path}.")
155
+
156
+
157
+ def load_pretrained(self, model: str, lang: str, source: str, dimension: str, save_file: bool = False, export_dir: str = None) -> np.ndarray:
158
+ '''
159
+ Downloads and loads a pretrained embedding model from an online source.
160
+
161
+ Parameters
162
+ -----------
163
+ model : str
164
+ Name of the embedding model ('word2vec', 'fasttext', etc.).
165
+ lang : str
166
+ Language code of the embedding ('en', 'it').
167
+ source : str
168
+ Data source ('wiki', 'cc').
169
+ dimension : str or int
170
+ Embedding dimensionality (e.g., '300').
171
+ save_file : bool, default=False
172
+ If True, saves the embedding to the specified export directory.
173
+ export_dir : str, optional
174
+ Path to the directory where the file will be exported (used if save_file=True).
175
+
176
+ Returns
177
+ --------
178
+ np.ndarray
179
+ Loaded embedding matrix (n_words x dimension).
180
+ '''
181
+
182
+ option = self.available_pretrained[model][lang][source][dimension]
183
+ url = option['url']
184
+ filename = option['filename']
185
+ zip_filename = url.split("/")[-1]
186
+
187
+ zip_path = self.download_zip(url, zip_filename)
188
+
189
+ dest_dir = self.get_cache_dir() / model / lang / source / dimension
190
+ dest_dir.mkdir(parents=True, exist_ok=True)
191
+ file_path = dest_dir / filename
192
+
193
+ if not file_path.exists():
194
+ with zipfile.ZipFile(zip_path, 'r') as z:
195
+ print(f"Extracting {filename}...")
196
+ z.extract(filename, path=dest_dir)
197
+
198
+ self.embeddings = self.load_from_file(file_path, model)
199
+
200
+ if save_file:
201
+ if export_dir is None:
202
+ raise ValueError("Must specify export_dir to save file.")
203
+ self.export_embedding(file_path, export_dir)
204
+
205
+ return self.embeddings
206
+
207
+
208
+ def list_available_pretrained(self):
209
+ '''prints a list of pretrained embeddings provided by the package'''
210
+ print('model | lang | source | dim')
211
+ for model, langs in self.available_pretrained.items():
212
+ for lang, sources in langs.items():
213
+ for source, dims in sources.items():
214
+ for dim in dims:
215
+ print(f"{model} | {lang} | {source} | {dim}")
216
+
217
+
218
+ def get_embedding(self, word):
219
+ '''returns corresponding embeddings using KeyedVectors object for a string given by the user'''
220
+ if isinstance(word, str):
221
+ if word in self.embeddings_raw:
222
+ return self.embeddings_raw[word]
223
+
224
+
225
+ def subset(self, n: int = 1000, strategy: str = 'first', random_seed: int = None):
226
+ '''
227
+ Create a subset of the current embeddings and tokens. Useful for speeding up visualizations or
228
+ managing memory with large embedding spaces.
229
+
230
+ Parameters
231
+ -----------
232
+ n : int, default=1000
233
+ Number of embeddings to retain. If n exceeds the total number of available embeddings, all are retained.
234
+ strategy : str, default='first'
235
+ Selection strategy:
236
+ - 'first': select the first n embeddings in original order.
237
+ - 'random': select n random embeddings.
238
+ random_seed : int, optional
239
+ Seed for reproducible random sampling (only used if strategy is 'random').
240
+
241
+ Updates
242
+ --------
243
+ self.tokens_subset : list of str
244
+ List of selected token strings.
245
+ self.embeddings_subset : np.ndarray
246
+ Corresponding selected embedding vectors.
247
+ '''
248
+ if n > len(self.tokens):
249
+ print('n is larger than the embedding size, the subset size will be equal to the full size')
250
+
251
+ if strategy == 'first':
252
+ indices = list(range(min(n, len(self.tokens))))
253
+ elif strategy == 'random':
254
+ rng = np.random.default_rng(random_seed)
255
+ indices = rng.choice(len(self.tokens), size=min(n, len(self.tokens)), replace=False).tolist()
256
+ else:
257
+ raise ValueError("strategy has to be 'first' o 'random'")
258
+
259
+ self.tokens_subset = [self.tokens[i] for i in indices]
260
+ self.embeddings_subset = self.embeddings[indices]
261
+
262
+
263
+ def use_subset(self, n: int = 1000):
264
+ '''returns embedding subset. If None, creates 1000 words subset and returns it.'''
265
+
266
+ if self.embeddings_subset is None:
267
+ self.subset(n)
268
+
269
+ return self.embeddings_subset, self.tokens_subset
@@ -0,0 +1,488 @@
1
+ from adjustText import adjust_text
2
+ import matplotlib.pyplot as plt
3
+ import numpy as np
4
+ import plotly.express as px
5
+ import plotly.graph_objects as go
6
+ import seaborn as sns
7
+ from sklearn.cluster import KMeans
8
+ from sklearn.metrics import pairwise_distances
9
+ from scipy.stats import gaussian_kde
10
+ import warnings
11
+ from .clustering import create_clusters
12
+ from .dim_reduction import reduce_dim
13
+ from .similarity import n_most_similar
14
+
15
+
16
+ class Visualizer:
17
+ def __init__(self, loader):
18
+ self.loader = loader
19
+ self.tokens = loader.tokens
20
+ self.embeddings = loader.embeddings
21
+
22
+
23
+ def get_theme(self, theme='light1'):
24
+ themes = {
25
+ 'light1': {
26
+ 'bg' : '#f5f5fa',
27
+ 'points' : '#66c2a5',
28
+ 'target' : '#e78ac3',
29
+ 'grid' : '#cccccc',
30
+ 'text' : '#1a1a1a',
31
+ 'scale': 'Viridis'
32
+ },
33
+ 'dark1': {
34
+ 'bg' : '#1a1a1a',
35
+ 'points' : '#fc8d62',
36
+ 'target' : '#a6d854',
37
+ 'grid' : '#444444',
38
+ 'text' : '#f0f0f0',
39
+ 'scale': 'Plasma'
40
+ }
41
+ }
42
+ return themes.get(theme, themes['light1'])
43
+
44
+
45
+ def _setup_plot(self, theme, grid, title):
46
+ '''base private function to config matplotlib plot'''
47
+ colors = self.get_theme(theme)
48
+
49
+ fig, ax = plt.subplots(figsize=(10, 8))
50
+
51
+ fig.patch.set_facecolor(colors['bg'])
52
+ ax.set_facecolor(colors['bg'])
53
+
54
+ if grid:
55
+ ax.grid(True, linestyle='--', color=colors['grid'], alpha=0.6)
56
+ ax.set_axisbelow(True)
57
+ ax.tick_params(left=True, bottom=True, labelleft=False, labelbottom=False, color=(0.5, 0.5, 0.5, 0.4))
58
+ else:
59
+ plt.xticks([])
60
+ plt.yticks([])
61
+
62
+ for spine in ax.spines.values():
63
+ spine.set_visible(False)
64
+
65
+ if title is not None:
66
+ plt.title(title, fontsize=12, fontweight='bold', color=colors['text'])
67
+
68
+ return fig, ax, colors
69
+
70
+
71
+ def map_colors(self, labels):
72
+ '''automatizes color and legend label mapping for clustering applied to embeddings'''
73
+ unique_classes = list(set(labels))
74
+ palette = sns.color_palette("Set2", n_colors=len(unique_classes))
75
+ class_to_color = dict(zip(unique_classes, palette))
76
+ colors = [class_to_color[label] for label in labels]
77
+ legend_labels = {label: (class_to_color[label], f'Cluster {label+1}') for label in unique_classes}
78
+
79
+ return colors, legend_labels
80
+
81
+
82
+ def select_sparse_labels(self, embeddings, n):
83
+ '''uses clustering to select n distributed labels to visualize'''
84
+ kmeans = KMeans(n_clusters=n, random_state=0).fit(embeddings)
85
+ centers = kmeans.cluster_centers_
86
+ indices = []
87
+
88
+ for center in centers:
89
+ idx = np.argmin(np.linalg.norm(embeddings - center, axis=1))
90
+ indices.append(idx)
91
+
92
+ return indices
93
+
94
+
95
+ def plot_embeddings(self, red_method: str = 'pca', grid: bool = True, theme: str = 'light1', title: str = None, nlabels: int = 0, use_subset: bool = False):
96
+ '''
97
+ Creates a simple static 2D scatterplot of the embeddings.
98
+
99
+ Parameters
100
+ -----------
101
+ red_method : str, default='pca'
102
+ Dimensionality reduction method to apply ('pca', 'tsne', 'umap', etc.).
103
+ grid : bool, default=True
104
+ If True, displays a background grid on the plot.
105
+ theme : str, default='light1'
106
+ Color theme to apply.
107
+ title : str, optional
108
+ Title to display on the plot.
109
+ nlabels : int, default=0
110
+ Number of word labels to display. If 0, no labels are shown.
111
+ use_subset : bool, default=False
112
+ If True, uses the embedding subset instead of the full embeddings.
113
+
114
+ Returns
115
+ --------
116
+ fig : matplotlib.figure.Figure
117
+ ax : matplotlib.axes.Axes
118
+ '''
119
+
120
+ if use_subset:
121
+ emb, tokens = self.loader.use_subset()
122
+ else:
123
+ emb = self.embeddings
124
+ tokens = self.tokens
125
+
126
+ reduced_emb = reduce_dim(emb, method=red_method)
127
+
128
+ fig, ax, colors = self._setup_plot(theme, grid, title)
129
+ ax.scatter(reduced_emb[:, 0], reduced_emb[:, 1], c=colors['points'], alpha=0.5, s=14, marker='o')
130
+
131
+ texts = []
132
+ if nlabels > 0:
133
+ sparse_indices = self.select_sparse_labels(reduced_emb, nlabels)
134
+ for i in sparse_indices:
135
+ texts.append(ax.text(reduced_emb[i, 0], reduced_emb[i, 1], tokens[i],
136
+ color=colors['text'], fontsize=9, alpha=1, ha='center', va='bottom'))
137
+
138
+ adjust_text(texts, ax=ax, expand=(1.2, 2), arrowprops=dict(arrowstyle='-', color='k'))
139
+ plt.rcParams['figure.dpi'] = 600
140
+ plt.show()
141
+ return fig, ax
142
+
143
+
144
+ def plot_similarity(self, target_word: str, dist: str = 'cosine', n: int = 10, red_method: str = 'umap', grid: bool = True, theme: str = 'light1', title: str = None):
145
+ '''
146
+ Creates a scatterplot showing the most similar words to a target word.
147
+
148
+ Parameters
149
+ -----------
150
+ target_word : str
151
+ The word for which to find and plot the most similar words.
152
+ dist : str, default='cosine'
153
+ Distance metric to use when computing word similarity.
154
+ n : int, default=10
155
+ Number of similar words to display.
156
+ red_method : str, default='umap'
157
+ Dimensionality reduction method to apply ('pca', 'tsne', 'umap', etc.).
158
+ grid : bool, default=True
159
+ If True, displays a background grid on the plot.
160
+ theme : str, default='light1'
161
+ Color theme to apply to the plot.
162
+ title : str, optional
163
+ Title to display. If None, a default title will be generated.
164
+
165
+ Returns
166
+ --------
167
+ fig : matplotlib.figure.Figure
168
+ ax : matplotlib.axes.Axes
169
+ '''
170
+ similar_words, similar_vecs, _ = n_most_similar(self.loader, target_word, dist, n)
171
+ target_vec = self.loader.get_embedding(target_word)
172
+ vectors = np.vstack([target_vec.reshape(1, -1), similar_vecs])
173
+ words = [target_word] + similar_words
174
+
175
+ reduced_emb = reduce_dim(vectors, method=red_method)
176
+
177
+ if title is None:
178
+ title = f"Top {n} words similar to '{target_word}'"
179
+
180
+ fig, ax, colors = self._setup_plot(theme, grid, title)
181
+
182
+ texts = []
183
+ ax.scatter(reduced_emb[0, 0], reduced_emb[0, 1], c=colors['target'], alpha=0.5, s=20, marker='o')
184
+ texts.append(ax.text(reduced_emb[0, 0], reduced_emb[0, 1], target_word,
185
+ color=colors['text'], fontsize=9, fontweight='bold', alpha=1, ha='center', va='bottom'))
186
+
187
+ ax.scatter(reduced_emb[1:, 0], reduced_emb[1:, 1], c=colors['points'], alpha=0.5, s=20, marker='o')
188
+ for i, word in enumerate(similar_words):
189
+ texts.append(ax.text(reduced_emb[i+1, 0], reduced_emb[i+1, 1], word,
190
+ color=colors['text'], fontsize=9, alpha=1, ha='center', va='bottom'))
191
+
192
+
193
+ plt.rcParams['figure.dpi'] = 600
194
+ plt.show()
195
+
196
+ return fig, ax
197
+
198
+
199
+ def plot_topography(self, dist: str = 'cosine', red_method: str = 'isomap', use_subset: bool = True, grid: bool = True, theme: str = 'light1', title: str = None):
200
+ '''
201
+ Plots word embeddings in a topographical map using dimensionality reduction to maintain word distances in the representation. Allows to visualize word density in the space.
202
+
203
+ Parameters
204
+ -----------
205
+ dist : str, default='cosine'
206
+ The distance metric to use for word similarity. Options include 'cosine', 'euclidean', etc.
207
+ red_method : str, default='isomap'
208
+ The dimensionality reduction method to use for visualizing the word embeddings, maintaining word distances. Options include 'umap', 'isomap', and 'mds'.
209
+ use_subset : bool, default=True
210
+ If True, uses a subset of the embeddings for visualization. This is recommended in this plot for larger embeddings.
211
+ grid : bool, default=True
212
+ If True, shows grid lines on the plot.
213
+ theme : str, default='light1'
214
+ The plot theme to use, which controls the colors of the plot.
215
+ title : str, optional
216
+ Title of the plot. If not provided, a default title is used.
217
+
218
+ Returns
219
+ --------
220
+ fig : plotly.graph_objs.Figure
221
+ '''
222
+
223
+ if use_subset:
224
+ emb, tokens = self.loader.use_subset()
225
+ else:
226
+ emb = self.embeddings
227
+ tokens = self.tokens
228
+
229
+ reduced_emb = reduce_dim(emb, red_method, dist=dist)
230
+
231
+ x = reduced_emb[:, 0]
232
+ y = reduced_emb[:, 1]
233
+ fig = go.Figure()
234
+
235
+ colors = self.get_theme(theme)
236
+
237
+ # calculate coordinates for contour plot
238
+ x_grid, y_grid = np.meshgrid(np.linspace(x.min() - 0.5, x.max() + 0.5, 100),
239
+ np.linspace(y.min() - 0.5, y.max() + 0.5, 100))
240
+
241
+ kde = gaussian_kde([x, y], bw_method=0.2)
242
+ z_grid = kde([x_grid.flatten(), y_grid.flatten()]).reshape(x_grid.shape)
243
+ z_grid = np.log1p(z_grid)
244
+
245
+ # add contour
246
+ fig.add_trace(go.Contour(
247
+ z=z_grid,
248
+ x=x_grid[0],
249
+ y=y_grid[:, 0],
250
+ colorscale=colors['scale'],
251
+ opacity=0.8,
252
+ contours=dict(
253
+ showlabels=False,
254
+ start=z_grid.min(),
255
+ end=z_grid.max(),
256
+ size=(z_grid.max() - z_grid.min()) / 15),
257
+ colorbar=dict(title="Density"),
258
+ hoverinfo='skip'
259
+ ))
260
+
261
+ # add points
262
+ fig.add_trace(go.Scatter(
263
+ x=x, y=y,
264
+ mode='markers',
265
+ marker=dict(
266
+ size=5,
267
+ color='rgba(255, 255, 255, 0.5)',
268
+ line=dict(width=1, color='rgba(0, 0, 0, 0.8)')
269
+ ),
270
+ text=tokens,
271
+ hovertemplate='%{text}<extra></extra>',
272
+ showlegend=False
273
+ ))
274
+
275
+ fig.update_traces(
276
+ hoverlabel=dict(
277
+ bgcolor=colors['bg'],
278
+ font=dict(color=colors['text'])
279
+ )
280
+ )
281
+
282
+ fig.update_layout(
283
+ width=900,
284
+ height=700,
285
+ title=title if title else "Word Embedding Topography",
286
+ title_x=0.5,
287
+ title_xanchor='center',
288
+ plot_bgcolor=colors['bg'],
289
+ paper_bgcolor=colors['bg'],
290
+ font=dict(color=colors['text']),
291
+ xaxis=dict(showgrid=grid, zeroline=False, showticklabels=False, title=""),
292
+ yaxis=dict(showgrid=grid, zeroline=False, showticklabels=False, title="")
293
+ )
294
+
295
+ return fig
296
+
297
+
298
+ def similarity_heatmap(self, dist: str = 'cosine', use_subset: bool = True, n: int = 500, theme: str = 'light1', title: bool = None):
299
+ '''
300
+ Creates a heatmap showing pairwise distances between word embeddings.
301
+
302
+ Parameters
303
+ -----------
304
+ dist : str, default='cosine'
305
+ Distance metric to use for computing similarity between embeddings.
306
+ use_subset : bool, default=True
307
+ If True, uses a subset of the embeddings. Otherwise, uses the full set.
308
+ n : int, optional
309
+ Number of embeddings to subset. Ignored if a subset already exists and use_subset is True.
310
+ theme : str, default='light1'
311
+ Plot color theme to use.
312
+ title : str, optional
313
+ Title for the heatmap. If None, a default title is assigned.
314
+
315
+ Returns
316
+ --------
317
+ fig : plotly.graph_objects.Figure
318
+ '''
319
+
320
+ if use_subset:
321
+ if n:
322
+ self.loader.subset(n)
323
+ emb = self.loader.embeddings_subset
324
+ tokens = self.loader.tokens_subset
325
+ else:
326
+ emb, tokens = self.loader.use_subset()
327
+ else:
328
+ emb = self.embeddings
329
+ tokens = self.tokens
330
+
331
+ if emb.shape[0] > 500:
332
+ warnings.warn(f"Warning: loading more than 500 embeddings without subsetting will generate more than one heatmap and may result in longer execution times. Consider subsetting before or setting n < 500.")
333
+
334
+ distances = pairwise_distances(emb, metric=dist)
335
+
336
+ colors = self.get_theme(theme)
337
+
338
+ fig = px.imshow(distances, x=tokens, y=tokens, text_auto=True, color_continuous_scale=colors['scale'])
339
+ fig.update_layout(
340
+ width=800, height=800,
341
+ title=title if title else "Word Embedding Similarity Heatmap",
342
+ title_x=0.5,
343
+ title_xanchor='center',
344
+ plot_bgcolor=colors['bg'],
345
+ paper_bgcolor=colors['bg'],
346
+ font=dict(color=colors['text']))
347
+ fig.update_coloraxes(colorbar_title='Distance')
348
+ fig.update_traces(
349
+ hovertemplate="Word 1: %{x}<br>Word 2: %{y}<br>Distance: %{z}<extra></extra>",
350
+ hoverlabel=dict(
351
+ bgcolor=colors['bg'],
352
+ font=dict(color=colors['text']) ))
353
+
354
+ return fig
355
+
356
+
357
+ def plot_clusters(self, n_clusters=5, method='kmeans', red_method='pca', show_centers=False, grid=True, theme='light1', title=None, nlabels=0, use_subset=False):
358
+ '''
359
+ Creates a 2D scatterplot of clustered embeddings using a clustering algorithm.
360
+
361
+ Parameters:
362
+ -----------
363
+ n_clusters : int, default=5
364
+ Number of clusters to generate.
365
+ method : str, default='kmeans'
366
+ Clustering method to use ('kmeans' or others supported by create_clusters).
367
+ red_method : str, default='pca'
368
+ Dimensionality reduction method to apply before plotting.
369
+ show_centers : bool, default=False
370
+ If True, displays cluster centers on the plot.
371
+ grid : bool, default=True
372
+ Whether to display grid lines.
373
+ theme : str, default='light1'
374
+ Plot color theme.
375
+ title : str, optional
376
+ Title of the plot. If None, no title is shown.
377
+ nlabels : int, default=0
378
+ Number of token labels to display on the plot.
379
+ use_subset : bool, default=False
380
+ If True, uses the embedding subset instead of the full embeddings.
381
+
382
+ Returns:
383
+ --------
384
+ fig : matplotlib.figure.Figure
385
+ ax : matplotlib.axes.Axes
386
+ '''
387
+
388
+ if use_subset:
389
+ emb, tokens = self.loader.use_subset()
390
+ else:
391
+ emb = self.embeddings
392
+ tokens = self.tokens
393
+
394
+ reduced_emb = reduce_dim(emb, method=red_method)
395
+
396
+ clusters, centers, reduced_emb = create_clusters(reduced_emb, n_clusters=n_clusters, method=method)
397
+ clusters_colors, legend_labels = self.map_colors(clusters)
398
+
399
+ fig, ax, colors = self._setup_plot(theme, grid, title)
400
+ ax.scatter(reduced_emb[:, 0], reduced_emb[:, 1], c=clusters_colors, alpha=0.5, s=14, marker='o')
401
+
402
+ if show_centers and centers is not None:
403
+ for i in range(n_clusters):
404
+ ax.scatter(centers[i, 0], centers[i, 1], edgecolors="grey", color=colors['text'], s=40, alpha=0.8, marker='o')
405
+
406
+ legend_elements = [plt.Line2D([0], [0], marker='o',
407
+ color=color,
408
+ label=label_text,
409
+ markerfacecolor=color,
410
+ markersize=8,
411
+ linestyle='None')
412
+ for label, (color, label_text) in legend_labels.items()]
413
+
414
+ texts=[]
415
+ if nlabels > 0:
416
+ sparse_indices = self.select_sparse_labels(reduced_emb, nlabels)
417
+ for i in sparse_indices:
418
+ texts.append(ax.text(reduced_emb[i, 0], reduced_emb[i, 1], tokens[i],
419
+ color=colors['text'], fontsize=9, alpha=1, ha='center', va='bottom'))
420
+
421
+ ax.legend(handles=legend_elements, facecolor=colors['bg'], labelcolor=colors['text'])
422
+ adjust_text(texts, ax=ax, expand=(1.2, 2), arrowprops=dict(arrowstyle='-', color=colors['text']))
423
+ plt.rcParams['figure.dpi'] = 600
424
+ plt.show()
425
+ return fig, ax
426
+
427
+
428
+ def interactive_embeddings(self, red_method='pca', grid=True, theme='light1', title=None, use_subset=False):
429
+ '''
430
+ Creates an interactive 2D scatterplot of embeddings using Plotly.
431
+
432
+ Parameters:
433
+ -----------
434
+ red_method : str, default='pca'
435
+ Dimensionality reduction method to apply before plotting.
436
+ grid : bool, default=True
437
+ Whether to display grid lines.
438
+ theme : str, default='light1'
439
+ Plot color theme.
440
+ title : str, optional
441
+ Title of the plot. If None, no title is shown.
442
+ use_subset : bool, default=False
443
+ If True, uses the embedding subset instead of the full embeddings.
444
+
445
+ Returns:
446
+ --------
447
+ fig : plotly.graph_objects.Figure
448
+ '''
449
+
450
+ if use_subset:
451
+ emb, tokens = self.loader.use_subset()
452
+ else:
453
+ emb = self.embeddings
454
+ tokens = self.tokens
455
+
456
+ reduced_emb = reduce_dim(emb, method=red_method)
457
+
458
+ colors = self.get_theme(theme)
459
+
460
+ fig = px.scatter(reduced_emb, reduced_emb[:, 0], reduced_emb[:, 1], color_discrete_sequence=[colors['points']])
461
+ fig.update_traces(
462
+ text=tokens,
463
+ textposition='top center',
464
+ hovertemplate='%{text}<extra></extra>',
465
+ hoverlabel=dict(
466
+ bgcolor=colors['bg'],
467
+ font=dict(color=colors['text'])),
468
+ marker=dict(size=6, opacity=0.6, line=dict(width=0))
469
+ )
470
+ fig.update_layout(
471
+ height=600,
472
+ title=title if title else "Word Embedding Interactive Plot",
473
+ title_x=0.5,
474
+ title_xanchor='center',
475
+ plot_bgcolor=colors['bg'],
476
+ paper_bgcolor=colors['bg'],
477
+ font=dict(color=colors['text']),
478
+ xaxis=dict(showticklabels=False, showgrid=grid, gridcolor=colors['grid'], zeroline=False),
479
+ yaxis=dict(showticklabels=False, showgrid=grid, gridcolor=colors['grid'], zeroline=False),
480
+ xaxis_title=None,
481
+ yaxis_title=None
482
+ )
483
+
484
+ return fig
485
+
486
+
487
+
488
+
@@ -0,0 +1,193 @@
1
+ import numpy as np
2
+ from scipy.spatial.distance import cityblock, euclidean, cosine, chebyshev, canberra, braycurtis
3
+ from scipy.stats import pearsonr, spearmanr
4
+ from sklearn.metrics import pairwise_distances
5
+ from typing import List, Tuple
6
+ import warnings
7
+ from wordviz.loading import EmbeddingLoader
8
+
9
+ def word_distance(loader: EmbeddingLoader, word1: str, word2: str, dist: str = 'cosine') -> float:
10
+ '''
11
+ Computes distance between two words given by user.
12
+
13
+ Parameters
14
+ -----------
15
+ loader: EmbeddingLoader
16
+ Object used to load embeddings
17
+ word1, word2: str
18
+ Word to compute distance between
19
+ dist: str, default='cosine'
20
+ Type of distance to use:
21
+ - 'braycurtis'
22
+ - 'canberra'
23
+ - 'chebyshev'
24
+ - 'cosine'
25
+ - 'dot'
26
+ - 'euclidean'
27
+ - 'manhattan'
28
+ - 'pearson'
29
+ - 'pearson'
30
+
31
+ Returns
32
+ --------
33
+ distance: float
34
+ '''
35
+ words = loader.tokens
36
+
37
+ missing = [w for w in (word1, word2) if w not in words]
38
+ if missing:
39
+ raise ValueError(f"Word(s) not in vocabulary: {', '.join(missing)}")
40
+
41
+ vec1 = loader.get_embedding(word1)
42
+ vec2 = loader.get_embedding(word2)
43
+ emb_matrix = loader.embeddings
44
+
45
+ match dist:
46
+ case 'braycurtis':
47
+ distance = braycurtis(vec1, vec2)
48
+ case 'canberra':
49
+ distance = canberra(vec1, vec2)
50
+ case 'chebyshev':
51
+ distance = chebyshev(vec1, vec2)
52
+ case 'cosine':
53
+ distance = cosine(vec1, vec2)
54
+ case 'dot':
55
+ distance = -np.dot(vec1, vec2)
56
+ case 'euclidean':
57
+ distance = euclidean(vec1, vec2)
58
+ case 'manhattan':
59
+ distance = cityblock(vec1, vec2)
60
+ case 'pearson':
61
+ pearson_corr, _ = pearsonr(vec1, vec2)
62
+ distance = 1 - pearson_corr
63
+ case 'spearman':
64
+ spearman_corr, _ = spearmanr(vec1, vec2)
65
+ distance = 1 - spearman_corr
66
+
67
+ return distance
68
+
69
+
70
+ def n_most_similar2(loader: EmbeddingLoader, target_word: str, dist: str = 'cosine', n: int = 10) -> Tuple[List[str], np.ndarray, List[float]]:
71
+ '''
72
+ Finds the n most similar words to a given target word using a specified distance metric.
73
+
74
+ Parameters
75
+ -----------
76
+ loader : EmbeddingLoader
77
+ An instance of the embedding loader containing word vectors.
78
+ target_word : str
79
+ The word for which to find the most similar neighbors.
80
+ dist : str, default='cosine'
81
+ The distance metric to use. Options include 'cosine', 'euclidean', etc.
82
+ n : int, default=10
83
+ The number of most similar words to retrieve.
84
+
85
+ Returns
86
+ --------
87
+ words : list of str
88
+ The most similar words found.
89
+ vectors : np.ndarray
90
+ Embedding vectors corresponding to the most similar words.
91
+ distances : list of float
92
+ Distances from the target word to each of the most similar words.
93
+ '''
94
+ words = loader.tokens
95
+
96
+ if target_word not in words:
97
+ raise ValueError(f'{target_word} is not in vocabulary')
98
+
99
+ topn_vectors = {}
100
+
101
+ for word in words:
102
+ if word == target_word:
103
+ continue
104
+
105
+ distance = word_distance(loader, word, target_word, dist)
106
+ if len(topn_vectors) < n:
107
+ topn_vectors[word] = distance
108
+ else:
109
+ max_dist_word = max(topn_vectors, key=topn_vectors.get)
110
+ if distance < topn_vectors[max_dist_word]:
111
+ del topn_vectors[max_dist_word]
112
+ topn_vectors[word] = distance
113
+
114
+ vectors = np.array([loader.get_embedding(word) for word in topn_vectors])
115
+ words = list(topn_vectors.keys())
116
+ distances = list(topn_vectors.values())
117
+
118
+ return words, vectors, distances
119
+
120
+ def n_most_similar(loader: EmbeddingLoader, target_word: str, dist: str = 'cosine', n: int = 10) -> Tuple[List[str], np.ndarray, List[float]]:
121
+ '''
122
+ Findsspairwise the n most similar words to a given target word using a specified distance metric.
123
+
124
+ Parameters
125
+ -----------
126
+ loader : EmbeddingLoader
127
+ An instance of the embedding loader containing word vectors.
128
+ target_word : str
129
+ The word for which to find the most similar neighbors.
130
+ dist : str, default='cosine'
131
+ The distance metric to use. Options include 'cosine', 'euclidean', etc.
132
+ n : int, default=10
133
+ The number of most similar words to retrieve.
134
+
135
+ Returns
136
+ --------
137
+ words : list of str
138
+ The most similar words found.
139
+ vectors : np.ndarray
140
+ Embedding vectors corresponding to the most similar words.
141
+ distances : list of float
142
+ Distances from the target word to each of the most similar words.
143
+ '''
144
+ words = loader.tokens
145
+
146
+ if target_word not in words:
147
+ raise ValueError(f'{target_word} is not in vocabulary')
148
+
149
+ # Ottieni il vettore target
150
+ target_vector = loader.get_embedding(target_word)
151
+ target_index = words.index(target_word)
152
+
153
+ # Prepara gli indici di tutte le parole tranne la parola target
154
+ word_indices = list(range(len(words)))
155
+ word_indices.remove(target_index)
156
+
157
+ # Sottoinsiemi di parole e vettori corrispondenti
158
+ filtered_words = [words[i] for i in word_indices]
159
+
160
+ # Per gestire vocabolari molto grandi, possiamo elaborare per batch
161
+ batch_size = 10000
162
+ all_distances = []
163
+ all_indices = []
164
+
165
+ for i in range(0, len(filtered_words), batch_size):
166
+ batch_words = filtered_words[i:i+batch_size]
167
+ batch_vectors = np.array([loader.get_embedding(word) for word in batch_words])
168
+
169
+ # Calcola le distanze pairwise tra il vettore target e tutti i vettori batch
170
+ # Reshape target_vector per ottenere una matrice 1 x dimensione
171
+ distances = pairwise_distances(
172
+ target_vector.reshape(1, -1),
173
+ batch_vectors,
174
+ metric=dist
175
+ ).flatten()
176
+
177
+ all_distances.extend(distances)
178
+ all_indices.extend(range(i, min(i+batch_size, len(filtered_words))))
179
+
180
+ # Seleziona gli indici delle n distanze minori
181
+ if len(all_distances) <= n:
182
+ top_n_indices = np.argsort(all_distances)
183
+ else:
184
+ top_n_indices = np.argpartition(all_distances, n-1)[:n]
185
+ # Ordina questi n indici per distanza
186
+ top_n_indices = top_n_indices[np.argsort(np.array(all_distances)[top_n_indices])]
187
+
188
+ # Ottieni le parole, le distanze e i vettori corrispondenti
189
+ result_words = [filtered_words[all_indices[i]] for i in top_n_indices]
190
+ result_distances = [all_distances[i] for i in top_n_indices]
191
+ result_vectors = np.array([loader.get_embedding(word) for word in result_words])
192
+
193
+ return result_words, result_vectors, result_distances