Skip to content

Commit 846d0b5

Browse files
authored
Add sparsity option to random hypervectors (#47)
* Add sparsity option to random hypervectors * Update random hypervector creation * Remove unused import
1 parent 789509e commit 846d0b5

File tree

2 files changed

+31
-100
lines changed

2 files changed

+31
-100
lines changed

torchhd/functional.py

Lines changed: 21 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ def identity_hv(
3737
num_embeddings: int,
3838
embedding_dim: int,
3939
*,
40-
out=None,
4140
dtype=None,
4241
device=None,
4342
requires_grad=False,
@@ -49,7 +48,6 @@ def identity_hv(
4948
Args:
5049
num_embeddings (int): the number of hypervectors to generate.
5150
embedding_dim (int): the dimensionality of the hypervectors.
52-
out (Tensor, optional): the output tensor.
5351
dtype (``torch.dtype``, optional): the desired data type of returned tensor. Default: if ``None``, uses a global default (see ``torch.set_default_tensor_type()``).
5452
device (``torch.device``, optional): the desired device of returned tensor. Default: if ``None``, uses the current device for the default tensor type (see torch.set_default_tensor_type()). ``device`` will be the CPU for CPU tensor types and the current CUDA device for CUDA tensor types.
5553
requires_grad (bool, optional): If autograd should record operations on the returned tensor. Default: ``False``.
@@ -67,7 +65,6 @@ def identity_hv(
6765
return torch.ones(
6866
num_embeddings,
6967
embedding_dim,
70-
out=out,
7168
dtype=dtype,
7269
device=device,
7370
requires_grad=requires_grad,
@@ -78,8 +75,8 @@ def random_hv(
7875
num_embeddings: int,
7976
embedding_dim: int,
8077
*,
78+
sparsity=0.5,
8179
generator=None,
82-
out=None,
8380
dtype=None,
8481
device=None,
8582
requires_grad=False,
@@ -91,38 +88,38 @@ def random_hv(
9188
Args:
9289
num_embeddings (int): the number of hypervectors to generate.
9390
embedding_dim (int): the dimensionality of the hypervectors.
91+
sparsity (float, optional): the expected fraction of elements to be +1. Default: ``0.5``.
9492
generator (``torch.Generator``, optional): a pseudorandom number generator for sampling.
95-
out (Tensor, optional): the output tensor.
9693
dtype (``torch.dtype``, optional): the desired data type of returned tensor. Default: if ``None``, uses a global default (see ``torch.set_default_tensor_type()``).
9794
device (``torch.device``, optional): the desired device of returned tensor. Default: if ``None``, uses the current device for the default tensor type (see torch.set_default_tensor_type()). ``device`` will be the CPU for CPU tensor types and the current CUDA device for CUDA tensor types.
9895
requires_grad (bool, optional): If autograd should record operations on the returned tensor. Default: ``False``.
9996
10097
Examples::
10198
102-
>>> functional.random_hv(2, 3)
103-
tensor([[ 1., -1., -1.],
104-
[ -1., 1., -1.]])
99+
>>> functional.random_hv(2, 5)
100+
tensor([[-1., 1., -1., -1., 1.],
101+
[ 1., -1., -1., -1., -1.]])
102+
>>> functional.random_hv(2, 5, sparsity=0.9)
103+
tensor([[ 1., 1., 1., -1., 1.],
104+
[ 1., 1., 1., 1., 1.]])
105+
>>> functional.random_hv(2, 5, dtype=torch.long)
106+
tensor([[ 1, -1, 1, 1, 1],
107+
[ 1, 1, -1, -1, 1]])
105108
106109
"""
107110
if dtype is None:
108111
dtype = torch.get_default_dtype()
109112

110-
selection = torch.randint(
111-
0,
112-
2,
113-
size=(num_embeddings * embedding_dim,),
114-
generator=generator,
115-
dtype=torch.long,
116-
device=device,
117-
)
118-
119-
if out is not None:
120-
out = out.view(num_embeddings * embedding_dim)
121-
122-
options = torch.tensor([1, -1], dtype=dtype, device=device)
123-
hv = torch.index_select(options, 0, selection, out=out)
124-
hv.requires_grad = requires_grad
125-
return hv.view(num_embeddings, embedding_dim)
113+
select = torch.empty(
114+
(
115+
num_embeddings,
116+
embedding_dim,
117+
),
118+
dtype=torch.bool,
119+
).bernoulli_(1.0 - sparsity, generator=generator)
120+
result = torch.where(select, -1, +1).to(dtype=dtype, device=device)
121+
result.requires_grad = requires_grad
122+
return result
126123

127124

128125
def level_hv(
@@ -131,7 +128,6 @@ def level_hv(
131128
*,
132129
randomness=0.0,
133130
generator=None,
134-
out=None,
135131
dtype=None,
136132
device=None,
137133
requires_grad=False,
@@ -146,7 +142,6 @@ def level_hv(
146142
embedding_dim (int): the dimensionality of the hypervectors.
147143
randomness (float, optional): r-value to interpolate between level at ``0.0`` and random-hypervectors at ``1.0``. Default: ``0.0``.
148144
generator (``torch.Generator``, optional): a pseudorandom number generator for sampling.
149-
out (Tensor, optional): the output tensor.
150145
dtype (``torch.dtype``, optional): the desired data type of returned tensor. Default: if ``None``, uses a global default (see ``torch.set_default_tensor_type()``).
151146
device (``torch.device``, optional): the desired device of returned tensor. Default: if ``None``, uses the current device for the default tensor type (see torch.set_default_tensor_type()). ``device`` will be the CPU for CPU tensor types and the current CUDA device for CUDA tensor types.
152147
requires_grad (bool, optional): If autograd should record operations on the returned tensor. Default: ``False``.
@@ -164,7 +159,6 @@ def level_hv(
164159
hv = torch.zeros(
165160
num_embeddings,
166161
embedding_dim,
167-
out=out,
168162
dtype=dtype,
169163
device=device,
170164
)
@@ -219,7 +213,6 @@ def circular_hv(
219213
*,
220214
randomness=0.0,
221215
generator=None,
222-
out=None,
223216
dtype=None,
224217
device=None,
225218
requires_grad=False,
@@ -234,7 +227,6 @@ def circular_hv(
234227
embedding_dim (int): the dimensionality of the hypervectors.
235228
randomness (float, optional): r-value to interpolate between circular at ``0.0`` and random-hypervectors at ``1.0``. Default: ``0.0``.
236229
generator (``torch.Generator``, optional): a pseudorandom number generator for sampling.
237-
out (Tensor, optional): the output tensor.
238230
dtype (``torch.dtype``, optional): the desired data type of returned tensor. Default: if ``None``, uses a global default (see ``torch.set_default_tensor_type()``).
239231
device (``torch.device``, optional): the desired device of returned tensor. Default: if ``None``, uses the current device for the default tensor type (see torch.set_default_tensor_type()). ``device`` will be the CPU for CPU tensor types and the current CUDA device for CUDA tensor types.
240232
requires_grad (bool, optional): If autograd should record operations on the returned tensor. Default: ``False``.
@@ -252,7 +244,6 @@ def circular_hv(
252244
hv = torch.zeros(
253245
num_embeddings,
254246
embedding_dim,
255-
out=out,
256247
dtype=dtype,
257248
device=device,
258249
)

torchhd/tests/test_functional.py

Lines changed: 10 additions & 70 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,6 @@ def test_value(self):
1616
hv = functional.identity_hv(4, 85)
1717
assert (hv == 1).min().item()
1818

19-
def test_out(self):
20-
buffer = torch.empty(3, 52)
21-
hv = functional.identity_hv(3, 52, out=buffer)
22-
23-
assert buffer.data_ptr() == hv.data_ptr()
24-
assert hv.dim() == 2
25-
assert hv.size(0) == 3
26-
assert hv.size(1) == 52
27-
2819
def test_device(self):
2920
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
3021
hv = functional.identity_hv(3, 52, device=device)
@@ -53,23 +44,17 @@ def test_requires_grad(self):
5344

5445
def test_integration(self):
5546
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
56-
buffer = torch.empty(6, 10000, dtype=torch.float16)
5747
hv = functional.identity_hv(
58-
6, 10000, out=buffer, dtype=torch.float16, requires_grad=True, device=device
48+
6, 10000, dtype=torch.float16, requires_grad=True, device=device
5949
)
6050

61-
assert buffer.data_ptr() == hv.data_ptr()
6251
assert hv.dim() == 2
6352
assert hv.size(0) == 6
6453
assert hv.size(1) == 10000
6554
assert hv.requires_grad == True
6655
assert hv.dtype == torch.float16
6756
assert hv.device == device
6857

69-
with pytest.raises(RuntimeError):
70-
buffer = torch.empty(6, 10000, dtype=torch.float)
71-
hv = functional.identity_hv(6, 10000, out=buffer, dtype=torch.float16)
72-
7358

7459
class TestRandom_hv:
7560
def test_shape(self):
@@ -102,15 +87,6 @@ def test_generator(self):
10287

10388
assert (hv1 == hv2).min().item()
10489

105-
def test_out(self):
106-
buffer = torch.empty(3, 52)
107-
hv = functional.random_hv(3, 52, out=buffer)
108-
109-
assert buffer.data_ptr() == hv.data_ptr()
110-
assert hv.dim() == 2
111-
assert hv.size(0) == 3
112-
assert hv.size(1) == 52
113-
11490
def test_device(self):
11591
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
11692
hv = functional.random_hv(3, 52, device=device)
@@ -142,23 +118,17 @@ def test_requires_grad(self):
142118

143119
def test_integration(self):
144120
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
145-
buffer = torch.empty(6, 10000, dtype=torch.float)
146121
hv = functional.random_hv(
147-
6, 10000, out=buffer, dtype=torch.float, requires_grad=True, device=device
122+
6, 10000, dtype=torch.float, requires_grad=True, device=device
148123
)
149124

150-
assert buffer.data_ptr() == hv.data_ptr()
151125
assert hv.dim() == 2
152126
assert hv.size(0) == 6
153127
assert hv.size(1) == 10000
154128
assert hv.requires_grad == True
155129
assert hv.dtype == torch.float
156130
assert hv.device == device
157131

158-
with pytest.raises(RuntimeError):
159-
buffer = torch.empty(6, 10000, dtype=torch.float)
160-
hv = functional.random_hv(6, 10000, out=buffer, dtype=torch.float16)
161-
162132

163133
class TestLevel_hv:
164134
def test_shape(self):
@@ -180,9 +150,9 @@ def test_value(self):
180150
sim = functional.cosine_similarity(hv[0], hv[1].unsqueeze(0))
181151
assert sim.abs().item() > 0.98
182152
sim = functional.cosine_similarity(hv[0], hv[24].unsqueeze(0))
183-
assert sim.abs().item() > 0.49
153+
assert sim.abs().item() > 0.47
184154
sim = functional.cosine_similarity(hv[0], hv[24].unsqueeze(0))
185-
assert sim.abs().item() < 0.51
155+
assert sim.abs().item() < 0.52
186156
sim = functional.cosine_similarity(hv[40], hv[41].unsqueeze(0))
187157
assert sim.abs().item() > 0.98
188158

@@ -197,15 +167,6 @@ def test_generator(self):
197167

198168
assert (hv1 == hv2).min().item()
199169

200-
def test_out(self):
201-
buffer = torch.empty(20, 52)
202-
hv = functional.level_hv(20, 52, out=buffer)
203-
204-
assert buffer.data_ptr() == hv.data_ptr()
205-
assert hv.dim() == 2
206-
assert hv.size(0) == 20
207-
assert hv.size(1) == 52
208-
209170
def test_device(self):
210171
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
211172
hv = functional.level_hv(3, 52, device=device)
@@ -237,23 +198,17 @@ def test_requires_grad(self):
237198

238199
def test_integration(self):
239200
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
240-
buffer = torch.empty(6, 10000, dtype=torch.float)
241201
hv = functional.level_hv(
242-
6, 10000, out=buffer, dtype=torch.float, requires_grad=True, device=device
202+
6, 10000, dtype=torch.float, requires_grad=True, device=device
243203
)
244204

245-
assert buffer.data_ptr() == hv.data_ptr()
246205
assert hv.dim() == 2
247206
assert hv.size(0) == 6
248207
assert hv.size(1) == 10000
249208
assert hv.requires_grad == True
250209
assert hv.dtype == torch.float
251210
assert hv.device == device
252211

253-
with pytest.raises(RuntimeError):
254-
buffer = torch.empty(6, 10000, dtype=torch.float)
255-
hv = functional.level_hv(6, 10000, out=buffer, dtype=torch.float16)
256-
257212

258213
class TestCircular_hv:
259214
def test_shape(self):
@@ -277,13 +232,13 @@ def test_value(self):
277232
sim = functional.cosine_similarity(hv[0], hv[49].unsqueeze(0))
278233
assert sim.abs().item() > 0.95
279234
sim = functional.cosine_similarity(hv[0], hv[12].unsqueeze(0))
280-
assert sim.abs().item() > 0.49
235+
assert sim.abs().item() > 0.47
281236
sim = functional.cosine_similarity(hv[0], hv[37].unsqueeze(0))
282-
assert sim.abs().item() > 0.49
237+
assert sim.abs().item() > 0.47
283238
sim = functional.cosine_similarity(hv[0], hv[12].unsqueeze(0))
284-
assert sim.abs().item() < 0.52
239+
assert sim.abs().item() < 0.54
285240
sim = functional.cosine_similarity(hv[0], hv[37].unsqueeze(0))
286-
assert sim.abs().item() < 0.52
241+
assert sim.abs().item() < 0.54
287242
sim = functional.cosine_similarity(hv[40], hv[41].unsqueeze(0))
288243
assert sim.abs().item() > 0.96
289244

@@ -298,15 +253,6 @@ def test_generator(self):
298253

299254
assert (hv1 == hv2).min().item()
300255

301-
def test_out(self):
302-
buffer = torch.empty(20, 52)
303-
hv = functional.circular_hv(20, 52, out=buffer)
304-
305-
assert buffer.data_ptr() == hv.data_ptr()
306-
assert hv.dim() == 2
307-
assert hv.size(0) == 20
308-
assert hv.size(1) == 52
309-
310256
def test_device(self):
311257
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
312258
hv = functional.circular_hv(3, 52, device=device)
@@ -338,23 +284,17 @@ def test_requires_grad(self):
338284

339285
def test_integration(self):
340286
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
341-
buffer = torch.empty(6, 10000, dtype=torch.float)
342287
hv = functional.circular_hv(
343-
6, 10000, out=buffer, dtype=torch.float, requires_grad=True, device=device
288+
6, 10000, dtype=torch.float, requires_grad=True, device=device
344289
)
345290

346-
assert buffer.data_ptr() == hv.data_ptr()
347291
assert hv.dim() == 2
348292
assert hv.size(0) == 6
349293
assert hv.size(1) == 10000
350294
assert hv.requires_grad == True
351295
assert hv.dtype == torch.float
352296
assert hv.device == device
353297

354-
with pytest.raises(RuntimeError):
355-
buffer = torch.empty(6, 10000, dtype=torch.float)
356-
hv = functional.circular_hv(6, 10000, out=buffer, dtype=torch.float16)
357-
358298

359299
class TestBind:
360300
def test_value(self):

0 commit comments

Comments
 (0)