hdlib 2.1.0__py3-none-any.whl
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.
- hdlib/__init__.py +16 -0
- hdlib/arithmetic/__init__.py +265 -0
- hdlib/arithmetic/quantum.py +1257 -0
- hdlib/model/__init__.py +12 -0
- hdlib/model/classification.py +1719 -0
- hdlib/model/clustering.py +172 -0
- hdlib/model/graph.py +764 -0
- hdlib/model/regression.py +306 -0
- hdlib/space.py +864 -0
- hdlib/vector.py +608 -0
- hdlib-2.1.0.data/scripts/chopin2.py +473 -0
- hdlib-2.1.0.dist-info/METADATA +137 -0
- hdlib-2.1.0.dist-info/RECORD +16 -0
- hdlib-2.1.0.dist-info/WHEEL +5 -0
- hdlib-2.1.0.dist-info/licenses/LICENSE +21 -0
- hdlib-2.1.0.dist-info/top_level.txt +1 -0
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
"""Similarity-preserving encoding and multi-model regression.
|
|
2
|
+
|
|
3
|
+
It implements the __hdlib.model.regression.RegressionEncoder__ and __hdlib.model.regression.RegressionModel__ class objects
|
|
4
|
+
built according to the Hyperdimensional Computing (HDC) paradigm as described in _Hernández-Cano et al. 2021_ https://doi.org/10.1109/DAC18074.2021.9586284."""
|
|
5
|
+
|
|
6
|
+
import numpy as np
|
|
7
|
+
from scipy.special import softmax
|
|
8
|
+
|
|
9
|
+
from hdlib.vector import Vector
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class RegressionEncoder:
|
|
13
|
+
"""Implements the similarity-preserving encoding from the RegHD article.
|
|
14
|
+
Maps input features to high-dimensional real-valued hypervectors.
|
|
15
|
+
|
|
16
|
+
The encoding function is h_i = cos(F_bar . B_i + b_i) * sin(F_bar . B_i), where F_bar is the input feature vector,
|
|
17
|
+
B_i are random base hypervectors, and b_i are random biases.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
def __init__(self, D: int, n_features: int):
|
|
21
|
+
"""Initializes the RegressionEncoder.
|
|
22
|
+
|
|
23
|
+
Parameters
|
|
24
|
+
----------
|
|
25
|
+
D : int
|
|
26
|
+
Dimensionality of the high-dimensional space (e.g., 10000).
|
|
27
|
+
n_features : int
|
|
28
|
+
Number of features in the input data.
|
|
29
|
+
|
|
30
|
+
Returns
|
|
31
|
+
-------
|
|
32
|
+
RegressionEncoder
|
|
33
|
+
The regression encoder object.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
if D <= 0 or not isinstance(D, int):
|
|
37
|
+
raise ValueError("Dimensionality must be a positive integer.")
|
|
38
|
+
|
|
39
|
+
if n_features <= 0 or not isinstance(n_features, int):
|
|
40
|
+
raise ValueError("Number of features must be a positive integer.")
|
|
41
|
+
|
|
42
|
+
self.D = D
|
|
43
|
+
self.n_features = n_features
|
|
44
|
+
|
|
45
|
+
# Randomly chosen base hypervectors (B_k in paper)
|
|
46
|
+
# These are generated from a normal distribution as suggested by the paper (B_kj ~ N(0,1)).
|
|
47
|
+
# Shape: (n_features, D)
|
|
48
|
+
self.base_hypervectors = np.random.randn(n_features, D)
|
|
49
|
+
|
|
50
|
+
# Random biases b_i ~ U(0, 2pi)
|
|
51
|
+
# Shape: (D,)
|
|
52
|
+
self.biases = np.random.uniform(0, 2 * np.pi, D)
|
|
53
|
+
|
|
54
|
+
def encode(self, feature_vector: np.ndarray) -> np.ndarray:
|
|
55
|
+
"""Encodes a single feature vector into a high-dimensional real-valued hypervector.
|
|
56
|
+
|
|
57
|
+
Parameters
|
|
58
|
+
----------
|
|
59
|
+
feature_vector : np.ndarray
|
|
60
|
+
A 1D numpy array of shape (n_features,) representing the input features.
|
|
61
|
+
|
|
62
|
+
Returns
|
|
63
|
+
-------
|
|
64
|
+
np.ndarray
|
|
65
|
+
A 1D numpy array of shape (D,) representing the encoded high-dimensional real-valued hypervector.
|
|
66
|
+
"""
|
|
67
|
+
|
|
68
|
+
# Ensure feature_vector is a 1D numpy array
|
|
69
|
+
feature_vector = np.asarray(feature_vector).flatten()
|
|
70
|
+
|
|
71
|
+
if feature_vector.shape[0] != self.n_features:
|
|
72
|
+
raise ValueError(f"Input feature vector must have {self.n_features} features, but got {feature_vector.shape[0]}")
|
|
73
|
+
|
|
74
|
+
# Compute dot products: F_bar . B_i for each B_i (column in base_hypervectors)
|
|
75
|
+
# This results in a 1D array of shape (D,)
|
|
76
|
+
dot_products = np.dot(feature_vector, self.base_hypervectors)
|
|
77
|
+
|
|
78
|
+
# Apply the encoding formula: h_i = cos(dot_product + b_i) * sin(dot_product)
|
|
79
|
+
encoded_vector = np.cos(dot_products + self.biases) * np.sin(dot_products)
|
|
80
|
+
|
|
81
|
+
return encoded_vector
|
|
82
|
+
|
|
83
|
+
|
|
84
|
+
class RegressionModel:
|
|
85
|
+
"""Implements the RegHD multi-model regression algorithm.
|
|
86
|
+
|
|
87
|
+
This class handles the training and prediction phases, including the management of cluster and regression models,
|
|
88
|
+
and optional quantization for efficiency.
|
|
89
|
+
"""
|
|
90
|
+
def __init__(
|
|
91
|
+
self,
|
|
92
|
+
D: int,
|
|
93
|
+
n_features: int,
|
|
94
|
+
k_models: int=8,
|
|
95
|
+
learning_rate: float=0.01,
|
|
96
|
+
iterations: int=20,
|
|
97
|
+
binary_threshold: float=0.0
|
|
98
|
+
):
|
|
99
|
+
"""Initializes the RegHDRegressor.
|
|
100
|
+
|
|
101
|
+
Parameters
|
|
102
|
+
----------
|
|
103
|
+
D : int
|
|
104
|
+
Dimensionality of hypervectors.
|
|
105
|
+
n_features : int
|
|
106
|
+
Number of input features.
|
|
107
|
+
k_models : int
|
|
108
|
+
Number of cluster/regression models to use.
|
|
109
|
+
learning_rate : float
|
|
110
|
+
Alpha parameter for model updates.
|
|
111
|
+
iterations : int
|
|
112
|
+
Number of training iterations (epochs).
|
|
113
|
+
binary_threshold : float
|
|
114
|
+
Threshold for binarizing real-valued hypervectors (e.g., 0.0 for bipolar conversion).
|
|
115
|
+
|
|
116
|
+
Returns
|
|
117
|
+
-------
|
|
118
|
+
RegressionModel
|
|
119
|
+
The regression model object.
|
|
120
|
+
"""
|
|
121
|
+
|
|
122
|
+
if D <= 0 or not isinstance(D, int):
|
|
123
|
+
raise ValueError("Dimensionality D must be a positive integer.")
|
|
124
|
+
|
|
125
|
+
if n_features <= 0 or not isinstance(n_features, int):
|
|
126
|
+
raise ValueError("Number of features n_features must be a positive integer.")
|
|
127
|
+
|
|
128
|
+
if k_models <= 0 or not isinstance(k_models, int):
|
|
129
|
+
raise ValueError("Number of models k_models must be a positive integer.")
|
|
130
|
+
|
|
131
|
+
if not isinstance(learning_rate, (int, float)) or learning_rate <= 0:
|
|
132
|
+
raise ValueError("Learning rate must be a positive number.")
|
|
133
|
+
|
|
134
|
+
if iterations <= 0 or not isinstance(iterations, int):
|
|
135
|
+
raise ValueError("Number of iterations must be a positive integer.")
|
|
136
|
+
|
|
137
|
+
if not isinstance(binary_threshold, (int, float)):
|
|
138
|
+
raise ValueError("Binary threshold must be a number.")
|
|
139
|
+
|
|
140
|
+
self.D = D
|
|
141
|
+
self.n_features = n_features
|
|
142
|
+
self.k_models = k_models
|
|
143
|
+
self.learning_rate = learning_rate
|
|
144
|
+
self.iterations = iterations
|
|
145
|
+
self.binary_threshold = binary_threshold
|
|
146
|
+
|
|
147
|
+
# Initialize the encoder for mapping input features to HD space
|
|
148
|
+
self.encoder = RegressionEncoder(D, n_features)
|
|
149
|
+
|
|
150
|
+
# Initialize cluster hypervectors (C_i) and regression model hypervectors (M_i)
|
|
151
|
+
# As per the paper, we maintain both "integer" (float numpy array) and "binary" (Vector)
|
|
152
|
+
# versions for cluster models.
|
|
153
|
+
# Cluster models (binary for similarity search, float for updates)
|
|
154
|
+
self.cluster_models_b = [Vector(vtype="bipolar", size=D) for _ in range(k_models)]
|
|
155
|
+
self.cluster_models_int = [np.zeros(D, dtype=float) for _ in range(k_models)]
|
|
156
|
+
|
|
157
|
+
# Initialize integer models from the binary ones to ensure consistency
|
|
158
|
+
for i in range(self.k_models):
|
|
159
|
+
self.cluster_models_int[i] = self.cluster_models_b[i].vector.astype(float)
|
|
160
|
+
|
|
161
|
+
# Regression models (float for updates, binary for optional quantized prediction)
|
|
162
|
+
self.regression_models_int = [np.zeros(D, dtype=float) for _ in range(k_models)]
|
|
163
|
+
self.regression_models_b = [Vector(vtype="bipolar", vector=np.zeros(D)) for _ in range(k_models)]
|
|
164
|
+
|
|
165
|
+
# Flag to control prediction quantization strategy
|
|
166
|
+
self.quantized_prediction = False
|
|
167
|
+
|
|
168
|
+
def _to_bipolar(self, vec_float: np.ndarray) -> Vector:
|
|
169
|
+
"""Converts a float numpy array to an hdlib Vector (bipolar). Values >= binary_threshold become 1, others become -1.
|
|
170
|
+
"""
|
|
171
|
+
|
|
172
|
+
bipolar_array = np.where(vec_float >= self.binary_threshold, 1, -1)
|
|
173
|
+
|
|
174
|
+
return Vector(vtype="bipolar", vector=bipolar_array)
|
|
175
|
+
|
|
176
|
+
def _calculate_similarity(self, encoded_s_b: Vector, cluster_model_b: Vector) -> float:
|
|
177
|
+
"""Calculates similarity between a binary encoded input hypervector and a
|
|
178
|
+
binary cluster model hypervector. For bipolar vectors, this is based on cosine similarity.
|
|
179
|
+
"""
|
|
180
|
+
|
|
181
|
+
# Using cosine similarity as the primary metric for bipolar vectors in hdlib
|
|
182
|
+
return 1.0 - encoded_s_b.dist(cluster_model_b, method="cosine")
|
|
183
|
+
|
|
184
|
+
def fit(self, X: np.ndarray, y: np.ndarray):
|
|
185
|
+
"""Trains the RegHD regressor using the provided training data.
|
|
186
|
+
|
|
187
|
+
Parameters
|
|
188
|
+
----------
|
|
189
|
+
X : np.ndarray
|
|
190
|
+
Input features, a 2D numpy array of shape (n_samples, n_features).
|
|
191
|
+
y : np.ndarray
|
|
192
|
+
Target values, a 1D numpy array of shape (n_samples,).
|
|
193
|
+
"""
|
|
194
|
+
|
|
195
|
+
X = np.asarray(X, dtype=float)
|
|
196
|
+
y = np.asarray(y, dtype=float)
|
|
197
|
+
|
|
198
|
+
if X.shape[1] != self.n_features:
|
|
199
|
+
raise ValueError(f"Input X must have {self.n_features} features, but got {X.shape[1]}")
|
|
200
|
+
|
|
201
|
+
if X.shape[0] != y.shape[0]:
|
|
202
|
+
raise ValueError("Number of samples in X and y must match.")
|
|
203
|
+
|
|
204
|
+
print(f"Starting RegHD training with D={self.D}, k_models={self.k_models}, iterations={self.iterations}")
|
|
205
|
+
|
|
206
|
+
for iteration in range(self.iterations):
|
|
207
|
+
total_mse = 0.0
|
|
208
|
+
|
|
209
|
+
for i in range(X.shape[0]):
|
|
210
|
+
feature_vector = X[i]
|
|
211
|
+
actual_output = y[i]
|
|
212
|
+
|
|
213
|
+
# 1. Encode input data
|
|
214
|
+
encoded_s_float = self.encoder.encode(feature_vector)
|
|
215
|
+
encoded_s_b = self._to_bipolar(encoded_s_float)
|
|
216
|
+
|
|
217
|
+
# 2. Check similarity with all binary cluster hypervectors
|
|
218
|
+
similarities = np.array([self._calculate_similarity(encoded_s_b, cluster_b) for cluster_b in self.cluster_models_b])
|
|
219
|
+
|
|
220
|
+
# 3. Normalize similarity values using softmax to get confidences
|
|
221
|
+
confidences = softmax(similarities)
|
|
222
|
+
|
|
223
|
+
# 4. Predict output value during training
|
|
224
|
+
predicted_output = 0.0
|
|
225
|
+
for j in range(self.k_models):
|
|
226
|
+
predicted_output += confidences[j] * np.dot(self.regression_models_int[j], encoded_s_float)
|
|
227
|
+
|
|
228
|
+
# 5. Calculate prediction error
|
|
229
|
+
error = actual_output - predicted_output
|
|
230
|
+
total_mse += error**2
|
|
231
|
+
|
|
232
|
+
# 6. Update Regression Models
|
|
233
|
+
for j in range(self.k_models):
|
|
234
|
+
self.regression_models_int[j] += self.learning_rate * confidences[j] * error * encoded_s_float
|
|
235
|
+
|
|
236
|
+
# 7. Update Cluster Models
|
|
237
|
+
l_max_similarity = np.argmax(similarities)
|
|
238
|
+
self.cluster_models_int[l_max_similarity] += (1 - confidences[l_max_similarity]) * encoded_s_float
|
|
239
|
+
|
|
240
|
+
# After each epoch, quantize the updated models
|
|
241
|
+
for j in range(self.k_models):
|
|
242
|
+
self.cluster_models_b[j] = self._to_bipolar(self.cluster_models_int[j])
|
|
243
|
+
|
|
244
|
+
if self.quantized_prediction:
|
|
245
|
+
self.regression_models_b[j] = self._to_bipolar(self.regression_models_int[j])
|
|
246
|
+
|
|
247
|
+
avg_mse = total_mse / X.shape[0]
|
|
248
|
+
|
|
249
|
+
print(f"Iteration {iteration+1}/{self.iterations}, Mean Squared Error (MSE): {avg_mse:.4f}")
|
|
250
|
+
|
|
251
|
+
def predict(self, X_query: np.ndarray) -> np.ndarray:
|
|
252
|
+
"""Predicts output values for new input data.
|
|
253
|
+
|
|
254
|
+
Parameters
|
|
255
|
+
----------
|
|
256
|
+
X_query : np.ndarray
|
|
257
|
+
Input features, a 2D numpy array of shape (n_samples, n_features).
|
|
258
|
+
|
|
259
|
+
Returns
|
|
260
|
+
-------
|
|
261
|
+
np.ndarray
|
|
262
|
+
Predicted target values, a 1D numpy array of shape (n_samples,).
|
|
263
|
+
"""
|
|
264
|
+
|
|
265
|
+
X_query = np.asarray(X_query, dtype=float)
|
|
266
|
+
|
|
267
|
+
if X_query.shape[1] != self.n_features:
|
|
268
|
+
raise ValueError(f"Input X_query must have {self.n_features} features, but got {X_query.shape[1]}")
|
|
269
|
+
|
|
270
|
+
predictions = []
|
|
271
|
+
|
|
272
|
+
for i in range(X_query.shape[0]):
|
|
273
|
+
feature_vector = X_query[i]
|
|
274
|
+
|
|
275
|
+
# 1. Encode input data
|
|
276
|
+
encoded_s_float = self.encoder.encode(feature_vector)
|
|
277
|
+
encoded_s_b = self._to_bipolar(encoded_s_float)
|
|
278
|
+
|
|
279
|
+
# 2. Compute similarity with all binary cluster centers
|
|
280
|
+
similarities = np.array([self._calculate_similarity(encoded_s_b, cluster_b) for cluster_b in self.cluster_models_b])
|
|
281
|
+
|
|
282
|
+
# 3. Normalize similarity values
|
|
283
|
+
confidences = softmax(similarities)
|
|
284
|
+
|
|
285
|
+
# 4. Predict output value
|
|
286
|
+
predicted_output = 0.0
|
|
287
|
+
for j in range(self.k_models):
|
|
288
|
+
if self.quantized_prediction:
|
|
289
|
+
predicted_output += confidences[j] * np.dot(self.regression_models_b[j].vector, encoded_s_b.vector)
|
|
290
|
+
|
|
291
|
+
else:
|
|
292
|
+
predicted_output += confidences[j] * np.dot(self.regression_models_int[j], encoded_s_float)
|
|
293
|
+
|
|
294
|
+
predictions.append(predicted_output)
|
|
295
|
+
|
|
296
|
+
return np.array(predictions)
|
|
297
|
+
|
|
298
|
+
def set_quantized_prediction_mode(self, enable: bool=True):
|
|
299
|
+
"""Sets whether prediction should use binarized regression models.
|
|
300
|
+
"""
|
|
301
|
+
|
|
302
|
+
self.quantized_prediction = enable
|
|
303
|
+
|
|
304
|
+
if enable:
|
|
305
|
+
for j in range(self.k_models):
|
|
306
|
+
self.regression_models_b[j] = self._to_bipolar(self.regression_models_int[j])
|