PyHierarchicalTsetlinMachineCUDA 0.2.2__tar.gz → 0.2.5__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.
- {PyHierarchicalTsetlinMachineCUDA-0.2.2 → PyHierarchicalTsetlinMachineCUDA-0.2.5}/PKG-INFO +1 -1
- {PyHierarchicalTsetlinMachineCUDA-0.2.2 → PyHierarchicalTsetlinMachineCUDA-0.2.5}/PyHierarchicalTsetlinMachineCUDA/kernels.py +48 -6
- {PyHierarchicalTsetlinMachineCUDA-0.2.2 → PyHierarchicalTsetlinMachineCUDA-0.2.5}/PyHierarchicalTsetlinMachineCUDA/tm.py +197 -21
- PyHierarchicalTsetlinMachineCUDA-0.2.5/PyHierarchicalTsetlinMachineCUDA/utils.py +175 -0
- {PyHierarchicalTsetlinMachineCUDA-0.2.2 → PyHierarchicalTsetlinMachineCUDA-0.2.5}/PyHierarchicalTsetlinMachineCUDA.egg-info/PKG-INFO +1 -1
- {PyHierarchicalTsetlinMachineCUDA-0.2.2 → PyHierarchicalTsetlinMachineCUDA-0.2.5}/PyHierarchicalTsetlinMachineCUDA.egg-info/SOURCES.txt +2 -5
- {PyHierarchicalTsetlinMachineCUDA-0.2.2 → PyHierarchicalTsetlinMachineCUDA-0.2.5}/PyHierarchicalTsetlinMachineCUDA.egg-info/requires.txt +3 -0
- PyHierarchicalTsetlinMachineCUDA-0.2.5/README.md +192 -0
- {PyHierarchicalTsetlinMachineCUDA-0.2.2 → PyHierarchicalTsetlinMachineCUDA-0.2.5}/setup.py +3 -2
- PyHierarchicalTsetlinMachineCUDA-0.2.2/README.md +0 -107
- {PyHierarchicalTsetlinMachineCUDA-0.2.2 → PyHierarchicalTsetlinMachineCUDA-0.2.5}/LICENSE +0 -0
- {PyHierarchicalTsetlinMachineCUDA-0.2.2 → PyHierarchicalTsetlinMachineCUDA-0.2.5}/PyHierarchicalTsetlinMachineCUDA/__init__.py +0 -0
- {PyHierarchicalTsetlinMachineCUDA-0.2.2 → PyHierarchicalTsetlinMachineCUDA-0.2.5}/PyHierarchicalTsetlinMachineCUDA/tools.py +0 -0
- {PyHierarchicalTsetlinMachineCUDA-0.2.2 → PyHierarchicalTsetlinMachineCUDA-0.2.5}/PyHierarchicalTsetlinMachineCUDA.egg-info/dependency_links.txt +0 -0
- {PyHierarchicalTsetlinMachineCUDA-0.2.2 → PyHierarchicalTsetlinMachineCUDA-0.2.5}/PyHierarchicalTsetlinMachineCUDA.egg-info/top_level.txt +0 -0
- {PyHierarchicalTsetlinMachineCUDA-0.2.2 → PyHierarchicalTsetlinMachineCUDA-0.2.5}/setup.cfg +0 -0
|
@@ -215,10 +215,13 @@ code_update = """
|
|
|
215
215
|
int or_group_vote_sum = 0;
|
|
216
216
|
for (int or_addend = 0; or_addend < number_of_or_group_addends; ++or_addend) {
|
|
217
217
|
// Aggregate votes from each child node through addition
|
|
218
|
+
|
|
219
|
+
int previous_or_group_vote_sum = or_group_vote_sum;
|
|
218
220
|
or_group_vote_sum += child_input[or_group_node*number_of_or_group_addends + or_addend];
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
221
|
+
if (or_group_vote_sum < 0) {
|
|
222
|
+
printf("OR* OVERFLOW %d -> %d\\n", previous_or_group_vote_sum, or_group_vote_sum);
|
|
223
|
+
or_group_vote_sum = previous_or_group_vote_sum;
|
|
224
|
+
}
|
|
222
225
|
}
|
|
223
226
|
|
|
224
227
|
// Store or group vote sum as node output
|
|
@@ -237,7 +240,14 @@ code_update = """
|
|
|
237
240
|
int and_group_vote_product = 1;
|
|
238
241
|
for (int and_factor = 0; and_factor < number_of_and_group_factors; ++and_factor) {
|
|
239
242
|
// Aggregate votes from each child node through multiplication
|
|
243
|
+
|
|
244
|
+
int previous_and_group_vote_product = and_group_vote_product;
|
|
240
245
|
and_group_vote_product *= child_input[and_group_node*number_of_and_group_factors + and_factor];
|
|
246
|
+
|
|
247
|
+
if (and_group_vote_product < 0) {
|
|
248
|
+
printf("AND OVERFLOW %d -> %d\\n", previous_and_group_vote_product, and_group_vote_product);
|
|
249
|
+
and_group_vote_product = previous_and_group_vote_product;
|
|
250
|
+
}
|
|
241
251
|
}
|
|
242
252
|
|
|
243
253
|
// Store and group product as node output
|
|
@@ -273,11 +283,15 @@ code_update = """
|
|
|
273
283
|
int or_alternatives_vote_sum = 0;
|
|
274
284
|
for (int or_alternative = 0; or_alternative < number_of_or_alternatives; ++or_alternative) {
|
|
275
285
|
// Aggregate same input or alternatives through summation
|
|
286
|
+
|
|
287
|
+
|
|
288
|
+
int previous_or_alternatives_vote_sum = or_alternatives_vote_sum;
|
|
276
289
|
or_alternatives_vote_sum += child_input[or_alternatives_node * number_of_or_alternatives + or_alternative];
|
|
277
290
|
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
291
|
+
if (or_alternatives_vote_sum < 0) {
|
|
292
|
+
printf("OR OVERFLOW %d -> %d\\n", previous_or_alternatives_vote_sum, or_alternatives_vote_sum);
|
|
293
|
+
or_alternatives_vote_sum = previous_or_alternatives_vote_sum;
|
|
294
|
+
}
|
|
281
295
|
}
|
|
282
296
|
|
|
283
297
|
// Store vote sum as node output
|
|
@@ -483,3 +497,31 @@ code_transform = """
|
|
|
483
497
|
}
|
|
484
498
|
}
|
|
485
499
|
"""
|
|
500
|
+
|
|
501
|
+
code_clauses = """
|
|
502
|
+
extern "C" __global__ void get_ta_states(const unsigned int* global_ta_state, unsigned int* unpacked_states) {
|
|
503
|
+
int index = blockIdx.x * blockDim.x + threadIdx.x;
|
|
504
|
+
int stride = blockDim.x * gridDim.x;
|
|
505
|
+
|
|
506
|
+
for (unsigned long long i = index; i < CLAUSES * COMPONENTS * LITERALS_PER_LEAF; i += stride) {
|
|
507
|
+
unsigned long long clause = i / (COMPONENTS * LITERALS_PER_LEAF);
|
|
508
|
+
unsigned long long comp = (i / LITERALS_PER_LEAF) % COMPONENTS;
|
|
509
|
+
unsigned long long ta_idx = i % LITERALS_PER_LEAF;
|
|
510
|
+
|
|
511
|
+
int chunk = ta_idx / 32;
|
|
512
|
+
int bit_pos = ta_idx % 32;
|
|
513
|
+
|
|
514
|
+
unsigned int state = 0;
|
|
515
|
+
for (int b = 0; b < STATE_BITS; ++b) {
|
|
516
|
+
unsigned int plane = global_ta_state[
|
|
517
|
+
(clause * COMPONENTS * TA_CHUNKS_PER_LEAF * STATE_BITS) +
|
|
518
|
+
(comp * TA_CHUNKS_PER_LEAF * STATE_BITS) +
|
|
519
|
+
(chunk * STATE_BITS) + b
|
|
520
|
+
];
|
|
521
|
+
if (plane & (1U << bit_pos)) state |= (1U << b);
|
|
522
|
+
}
|
|
523
|
+
|
|
524
|
+
unpacked_states[(clause * COMPONENTS * LITERALS_PER_LEAF) + (comp * LITERALS_PER_LEAF) + ta_idx] = state;
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
"""
|
|
@@ -18,6 +18,7 @@
|
|
|
18
18
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
19
19
|
# SOFTWARE.
|
|
20
20
|
|
|
21
|
+
from collections import deque
|
|
21
22
|
import numpy as np
|
|
22
23
|
|
|
23
24
|
import PyHierarchicalTsetlinMachineCUDA.kernels as kernels
|
|
@@ -26,6 +27,7 @@ import pycuda.curandom as curandom
|
|
|
26
27
|
import pycuda.driver as cuda
|
|
27
28
|
import pycuda.autoinit
|
|
28
29
|
from pycuda.compiler import SourceModule
|
|
30
|
+
from pycuda import gpuarray
|
|
29
31
|
|
|
30
32
|
from time import time
|
|
31
33
|
|
|
@@ -37,11 +39,10 @@ VANILLA_TM = 0
|
|
|
37
39
|
WEIGHTED_TM = 1
|
|
38
40
|
COALESCED_TM = 2
|
|
39
41
|
|
|
40
|
-
g = curandom.XORWOWRandomNumberGenerator()
|
|
41
42
|
|
|
42
43
|
class CommonTsetlinMachine():
|
|
43
44
|
|
|
44
|
-
def __init__(self, number_of_clauses, T, s, q=1.0, hierarchy_structure=None, boost_true_positive_feedback=1, number_of_state_bits=8, append_negated=True, grid=(16*13,1,1), block=(128,1,1)):
|
|
45
|
+
def __init__(self, number_of_clauses, T, s, q=1.0, hierarchy_structure=None, boost_true_positive_feedback=1, number_of_state_bits=8, append_negated=True, grid=(16*13,1,1), block=(128,1,1), seed=None):
|
|
45
46
|
self.number_of_clauses = number_of_clauses
|
|
46
47
|
self.number_of_state_bits = number_of_state_bits
|
|
47
48
|
self.T = int(T)
|
|
@@ -95,6 +96,12 @@ class CommonTsetlinMachine():
|
|
|
95
96
|
if (self.hierarchy_structure[d][0] == OR_GROUP or self.hierarchy_structure[d][0] == AND_GROUP):
|
|
96
97
|
self.number_of_literal_chunks *= self.hierarchy_structure[d][1]
|
|
97
98
|
|
|
99
|
+
self.np_rng = np.random.default_rng(seed)
|
|
100
|
+
if seed is not None:
|
|
101
|
+
self.cuda_rng = curandom.XORWOWRandomNumberGenerator(seed_getter=lambda N: gpuarray.to_gpu(self.np_rng.integers(1, 2**30, size=N).astype(np.int32)))
|
|
102
|
+
else:
|
|
103
|
+
self.cuda_rng = curandom.XORWOWRandomNumberGenerator()
|
|
104
|
+
|
|
98
105
|
self.cuda_modules()
|
|
99
106
|
|
|
100
107
|
self.first = True
|
|
@@ -151,6 +158,9 @@ class CommonTsetlinMachine():
|
|
|
151
158
|
self.prepare_encode_hierarchy = mod_encode.get_function("prepare_encode_hierarchy")
|
|
152
159
|
self.encode_hierarchy = mod_encode.get_function("encode_hierarchy")
|
|
153
160
|
|
|
161
|
+
mod_clauses = SourceModule(parameters + kernels.code_clauses, no_extern_c=True)
|
|
162
|
+
self.kernel_get_ta_states = mod_clauses.get_function("get_ta_states")
|
|
163
|
+
|
|
154
164
|
def encode_X(self, X, encoded_X_hierarchy_gpu):
|
|
155
165
|
number_of_examples = X.shape[0]
|
|
156
166
|
|
|
@@ -171,8 +181,8 @@ class CommonTsetlinMachine():
|
|
|
171
181
|
# GPU memory for accumulating votes, level by level
|
|
172
182
|
self.hierarchy_votes = []
|
|
173
183
|
for d in range(1, self.depth):
|
|
174
|
-
self.hierarchy_votes.append(cuda.mem_alloc(self.number_of_clauses*int(self.hierarchy_size[d])*
|
|
175
|
-
self.hierarchy_votes.append(cuda.mem_alloc(self.number_of_clauses*
|
|
184
|
+
self.hierarchy_votes.append(cuda.mem_alloc(self.number_of_clauses*int(self.hierarchy_size[d])*8))
|
|
185
|
+
self.hierarchy_votes.append(cuda.mem_alloc(self.number_of_clauses*8))
|
|
176
186
|
|
|
177
187
|
# GPU memory for storing hierarchy structure
|
|
178
188
|
self.hierarchy_structure_factors_gpu = cuda.mem_alloc((self.depth-1)*4)
|
|
@@ -236,10 +246,10 @@ class CommonTsetlinMachine():
|
|
|
236
246
|
None # To be updated
|
|
237
247
|
|
|
238
248
|
def initialize_weights_and_ta_states(self):
|
|
239
|
-
self.prepare_weights(
|
|
249
|
+
self.prepare_weights(self.cuda_rng.state, np.int32(self.tm_type), np.int32(self.number_of_outputs), self.clause_weights_gpu, self.class_sum_gpu, grid=self.grid, block=self.block)
|
|
240
250
|
cuda.Context.synchronize()
|
|
241
251
|
|
|
242
|
-
self.prepare_hierarchy(
|
|
252
|
+
self.prepare_hierarchy(self.cuda_rng.state, np.int32(self.number_of_outputs), self.ta_state_hierarchy_gpu, self.clause_weights_gpu, self.class_sum_gpu, grid=self.grid, block=self.block)
|
|
243
253
|
cuda.Context.synchronize()
|
|
244
254
|
|
|
245
255
|
def evaluate_hierarchy(self, encoded_X_hierarchy, e):
|
|
@@ -351,7 +361,7 @@ class CommonTsetlinMachine():
|
|
|
351
361
|
self.update_hierarchy.prepared_call(
|
|
352
362
|
self.grid,
|
|
353
363
|
self.block,
|
|
354
|
-
|
|
364
|
+
self.cuda_rng.state,
|
|
355
365
|
np.int32(self.number_of_outputs),
|
|
356
366
|
self.ta_state_hierarchy_gpu,
|
|
357
367
|
self.clause_weights_gpu,
|
|
@@ -371,7 +381,7 @@ class CommonTsetlinMachine():
|
|
|
371
381
|
self.update_weights.prepared_call(
|
|
372
382
|
self.grid,
|
|
373
383
|
self.block,
|
|
374
|
-
|
|
384
|
+
self.cuda_rng.state,
|
|
375
385
|
np.int32(self.tm_type),
|
|
376
386
|
np.int32(self.number_of_outputs),
|
|
377
387
|
self.clause_weights_gpu,
|
|
@@ -402,9 +412,102 @@ class CommonTsetlinMachine():
|
|
|
402
412
|
|
|
403
413
|
return class_sum
|
|
404
414
|
|
|
415
|
+
def get_ta_states(self) -> np.ndarray:
|
|
416
|
+
"""
|
|
417
|
+
Get state value for each TA.
|
|
418
|
+
Returns: Numpy array of shape (number_of_clauses, number_of_clause_components, number_of_literals_per_leaf)
|
|
419
|
+
"""
|
|
420
|
+
# Mem Allocation
|
|
421
|
+
ta_states_gpu = gpuarray.to_gpu(
|
|
422
|
+
np.zeros((self.number_of_clauses, self.hierarchy_size[1], self.number_of_literals_per_leaf), dtype=np.uint32)
|
|
423
|
+
)
|
|
424
|
+
|
|
425
|
+
# Calculate grid size based on the kernel
|
|
426
|
+
total = self.number_of_clauses * self.hierarchy_size[1] * self.number_of_literals_per_leaf
|
|
427
|
+
grid = (((total + self.block[0] - 1) // self.block[0]), 1, 1)
|
|
428
|
+
self.kernel_get_ta_states(self.ta_state_hierarchy_gpu, ta_states_gpu, block=self.block, grid=grid)
|
|
429
|
+
|
|
430
|
+
# Copy back to CPU
|
|
431
|
+
return ta_states_gpu.get()
|
|
432
|
+
|
|
433
|
+
def get_literals(self):
|
|
434
|
+
"""
|
|
435
|
+
Get included literals for each clause.
|
|
436
|
+
Returns: Numpy array of shape (number_of_clauses, number_of_clause_components, number_of_literals_per_leaf)
|
|
437
|
+
"""
|
|
438
|
+
return (self.get_ta_states() >= (1 << (self.number_of_state_bits - 1))).astype(np.uint8)
|
|
439
|
+
|
|
440
|
+
def map_ta_id_to_feature_id(self):
|
|
441
|
+
"""
|
|
442
|
+
Return an array of shape(number_of_clause_components, number_of_literals_per_leaf). That is the total number of TAs in a clause. Maps each TA id to a feature_id in the input. In each component, the first half of the TAs correspond to the positive features, and the second half correspond to the negated features.
|
|
443
|
+
"""
|
|
444
|
+
# BFS top-down traversal
|
|
445
|
+
q = deque()
|
|
446
|
+
q.append((self.depth - 1, 0, 0)) # (level, node_id, group_id)
|
|
447
|
+
|
|
448
|
+
comp_grps = -1 * np.ones(self.hierarchy_size[1], dtype=np.int32)
|
|
449
|
+
while q:
|
|
450
|
+
level, node_id, group_id = q.popleft()
|
|
451
|
+
|
|
452
|
+
if level == 0:
|
|
453
|
+
# This is the leaf component
|
|
454
|
+
comp_grps[node_id] = group_id
|
|
455
|
+
continue
|
|
456
|
+
|
|
457
|
+
n_children = self.hierarchy_structure[level][1]
|
|
458
|
+
is_alt = (self.hierarchy_structure[level][0] == OR_ALTERNATIVES)
|
|
459
|
+
for child_pos in range(n_children):
|
|
460
|
+
child_id = node_id * n_children + child_pos
|
|
461
|
+
if is_alt:
|
|
462
|
+
# All children share the same features
|
|
463
|
+
child_group_id = group_id
|
|
464
|
+
else:
|
|
465
|
+
# Features are partitioned among the children
|
|
466
|
+
child_group_id = group_id * n_children + child_pos
|
|
467
|
+
|
|
468
|
+
q.append((level - 1, child_id, child_group_id))
|
|
469
|
+
|
|
470
|
+
# map each TA in a component to a feature
|
|
471
|
+
half = self.number_of_literals_per_leaf // 2
|
|
472
|
+
lit_ids = np.arange(self.number_of_literals_per_leaf)
|
|
473
|
+
local_feats = lit_ids % half if self.append_negated else lit_ids
|
|
474
|
+
fmap = comp_grps[:, None] * (half if self.append_negated else self.number_of_literals_per_leaf) + local_feats[None, :]
|
|
475
|
+
|
|
476
|
+
return fmap
|
|
477
|
+
|
|
478
|
+
def calc_hierarchy_votes(self, X):
|
|
479
|
+
"""
|
|
480
|
+
Get the clause activation information for each sample in X.
|
|
481
|
+
"""
|
|
482
|
+
assert not self.first, "Model must be trained before getting activations."
|
|
483
|
+
|
|
484
|
+
number_of_examples = X.shape[0]
|
|
485
|
+
encoded_X_hierarchy_test_gpu = gpuarray.empty((number_of_examples, self.number_of_literal_chunks), dtype=np.uint32)
|
|
486
|
+
self.encode_X(X, encoded_X_hierarchy_test_gpu.gpudata)
|
|
487
|
+
|
|
488
|
+
class_sum = np.ascontiguousarray(np.zeros((self.number_of_outputs, number_of_examples))).astype(np.int32)
|
|
489
|
+
class_sum_example = np.ascontiguousarray(np.zeros(self.number_of_outputs)).astype(np.int32)
|
|
490
|
+
hierarchy_votes = []
|
|
491
|
+
for e in range(number_of_examples):
|
|
492
|
+
self.evaluate_hierarchy(encoded_X_hierarchy_test_gpu.gpudata, e)
|
|
493
|
+
|
|
494
|
+
cuda.memcpy_dtoh(class_sum_example, self.class_sum_gpu)
|
|
495
|
+
class_sum[:, e] = class_sum_example
|
|
496
|
+
|
|
497
|
+
hierarchy_votes_example = []
|
|
498
|
+
for d in range(self.depth):
|
|
499
|
+
temp_arr = np.empty(self.number_of_clauses*int(self.hierarchy_size[d+1]), dtype=np.int32)
|
|
500
|
+
cuda.memcpy_dtoh(temp_arr, self.hierarchy_votes[d])
|
|
501
|
+
hierarchy_votes_example.append(temp_arr.reshape((self.number_of_clauses, int(self.hierarchy_size[d+1]))))
|
|
502
|
+
|
|
503
|
+
hierarchy_votes.append(hierarchy_votes_example)
|
|
504
|
+
|
|
505
|
+
class_sum = np.clip(class_sum.reshape((self.number_of_outputs, number_of_examples)), -self.T, self.T)
|
|
506
|
+
return hierarchy_votes, class_sum
|
|
507
|
+
|
|
405
508
|
def print_hierarchy(self, print_ta_state=False):
|
|
406
509
|
for i in range(self.number_of_clauses):
|
|
407
|
-
print("\
|
|
510
|
+
print("\nCLAUSE #%d: " % (i), end='')
|
|
408
511
|
|
|
409
512
|
previous_index = np.ones((self.depth-1), dtype=np.int32)*-1
|
|
410
513
|
for j in range(self.hierarchy_size[1]):
|
|
@@ -457,11 +560,53 @@ class CommonTsetlinMachine():
|
|
|
457
560
|
print(l[0], end = '')
|
|
458
561
|
|
|
459
562
|
print(")" * (self.depth - 1))
|
|
563
|
+
|
|
564
|
+
def save(self) -> dict:
|
|
565
|
+
ta_state_hierarchy = np.empty(self.number_of_clauses*self.hierarchy_size[0]*self.number_of_state_bits, dtype=np.uint32)
|
|
566
|
+
cuda.memcpy_dtoh(ta_state_hierarchy, self.ta_state_hierarchy_gpu)
|
|
567
|
+
|
|
568
|
+
clause_weights = np.empty(self.number_of_outputs*self.number_of_clauses, dtype=np.int32)
|
|
569
|
+
cuda.memcpy_dtoh(clause_weights, self.clause_weights_gpu)
|
|
570
|
+
|
|
571
|
+
return {
|
|
572
|
+
'ta_state_hierarchy': ta_state_hierarchy,
|
|
573
|
+
'clause_weights': clause_weights,
|
|
574
|
+
'number_of_outputs': self.number_of_outputs,
|
|
575
|
+
'min_y': self.min_y,
|
|
576
|
+
'max_y': self.max_y,
|
|
577
|
+
'params': {
|
|
578
|
+
'number_of_clauses': self.number_of_clauses,
|
|
579
|
+
'T': self.T,
|
|
580
|
+
's': self.s,
|
|
581
|
+
'q': self.q,
|
|
582
|
+
'hierarchy_structure': self.hierarchy_structure,
|
|
583
|
+
'boost_true_positive_feedback': self.boost_true_positive_feedback,
|
|
584
|
+
'number_of_state_bits': self.number_of_state_bits,
|
|
585
|
+
'append_negated': self.append_negated,
|
|
586
|
+
},
|
|
587
|
+
'negative_clauses': self.negative_clauses,
|
|
588
|
+
'tm_type': self.tm_type,
|
|
589
|
+
'flip_polarity': self.flip_polarity,
|
|
590
|
+
'weighted_clauses': getattr(self, 'weighted_clauses', None),
|
|
591
|
+
}
|
|
592
|
+
|
|
593
|
+
def load(self, state_dict: dict):
|
|
594
|
+
self.number_of_outputs = state_dict['number_of_outputs']
|
|
595
|
+
self.min_y = state_dict['min_y']
|
|
596
|
+
self.max_y = state_dict['max_y']
|
|
597
|
+
|
|
598
|
+
self.allocate_gpu_memory()
|
|
599
|
+
|
|
600
|
+
cuda.memcpy_htod(self.ta_state_hierarchy_gpu, state_dict['ta_state_hierarchy'])
|
|
601
|
+
cuda.memcpy_htod(self.clause_weights_gpu, state_dict['clause_weights'])
|
|
602
|
+
|
|
603
|
+
self.first = False
|
|
604
|
+
|
|
460
605
|
|
|
461
606
|
class MultiOutputTsetlinMachine(CommonTsetlinMachine):
|
|
462
|
-
def __init__(self, number_of_clauses, T, s, q=1.0, boost_true_positive_feedback=1, number_of_state_bits=8, append_negated=True, grid=(16*13,1,1), block=(128,1,1)):
|
|
607
|
+
def __init__(self, number_of_clauses, T, s, q=1.0, boost_true_positive_feedback=1, number_of_state_bits=8, append_negated=True, grid=(16*13,1,1), block=(128,1,1), seed=None):
|
|
463
608
|
self.negative_clauses = 1
|
|
464
|
-
super().__init__(number_of_clauses, T, s, q=q, boost_true_positive_feedback=boost_true_positive_feedback, number_of_state_bits=number_of_state_bits, append_negated=append_negated, grid=grid, block=block)
|
|
609
|
+
super().__init__(number_of_clauses, T, s, q=q, boost_true_positive_feedback=boost_true_positive_feedback, number_of_state_bits=number_of_state_bits, append_negated=append_negated, grid=grid, block=block, seed=seed)
|
|
465
610
|
|
|
466
611
|
def fit(self, X, Y, epochs=100, incremental=False):
|
|
467
612
|
X = X.reshape(X.shape[0], X.shape[1], 1)
|
|
@@ -485,12 +630,12 @@ class MultiOutputTsetlinMachine(CommonTsetlinMachine):
|
|
|
485
630
|
return (self.score(X) >= 0).astype(np.uint32).transpose()
|
|
486
631
|
|
|
487
632
|
class MultiClassCoalescedTsetlinMachine(CommonTsetlinMachine):
|
|
488
|
-
def __init__(self, number_of_clauses, T, s, hierarchy_structure=((AND_GROUP, 1)), q=1.0, boost_true_positive_feedback=1, number_of_state_bits=8, append_negated=True, grid=(16*13,1,1), block=(128,1,1)):
|
|
633
|
+
def __init__(self, number_of_clauses, T, s, hierarchy_structure=((AND_GROUP, 1)), q=1.0, boost_true_positive_feedback=1, number_of_state_bits=8, append_negated=True, grid=(16*13,1,1), block=(128,1,1), seed=None):
|
|
489
634
|
self.negative_clauses = 1
|
|
490
635
|
self.tm_type = COALESCED_TM
|
|
491
636
|
self.flip_polarity = 1
|
|
492
637
|
|
|
493
|
-
super().__init__(number_of_clauses, T, s, hierarchy_structure=hierarchy_structure, q=q, boost_true_positive_feedback=boost_true_positive_feedback, number_of_state_bits=number_of_state_bits, append_negated=append_negated, grid=grid, block=block)
|
|
638
|
+
super().__init__(number_of_clauses, T, s, hierarchy_structure=hierarchy_structure, q=q, boost_true_positive_feedback=boost_true_positive_feedback, number_of_state_bits=number_of_state_bits, append_negated=append_negated, grid=grid, block=block, seed=seed)
|
|
494
639
|
|
|
495
640
|
def fit(self, X, Y, epochs=100, incremental=False):
|
|
496
641
|
X = X.reshape(X.shape[0], X.shape[1], 1)
|
|
@@ -517,7 +662,7 @@ class MultiClassCoalescedTsetlinMachine(CommonTsetlinMachine):
|
|
|
517
662
|
return np.argmax(self.score(X), axis=0)
|
|
518
663
|
|
|
519
664
|
class MultiClassTsetlinMachine:
|
|
520
|
-
def __init__(self, number_of_clauses, T, s, weighted_clauses=False, hierarchy_structure=((AND_GROUP, 1)), q=1.0, boost_true_positive_feedback=1, number_of_state_bits=8, append_negated=True, grid=(16*13,1,1), block=(128,1,1)):
|
|
665
|
+
def __init__(self, number_of_clauses, T, s, weighted_clauses=False, hierarchy_structure=((AND_GROUP, 1)), q=1.0, boost_true_positive_feedback=1, number_of_state_bits=8, append_negated=True, grid=(16*13,1,1), block=(128,1,1), seed=None):
|
|
521
666
|
self.number_of_clauses = number_of_clauses
|
|
522
667
|
self.T = T
|
|
523
668
|
self.s = s
|
|
@@ -529,19 +674,20 @@ class MultiClassTsetlinMachine:
|
|
|
529
674
|
self.append_negated = append_negated
|
|
530
675
|
self.grid = grid
|
|
531
676
|
self.block = block
|
|
677
|
+
self.seed = np.random.randint(1, 2**30) if seed is None else seed
|
|
532
678
|
|
|
533
679
|
self.configured = False
|
|
534
680
|
|
|
535
681
|
def fit(self, X, Y, epochs=100, incremental=False):
|
|
536
682
|
self.number_of_outputs = int(np.max(Y) + 1)
|
|
537
|
-
|
|
683
|
+
|
|
538
684
|
if not self.configured:
|
|
539
685
|
self.tms = []
|
|
540
686
|
for i in range(self.number_of_outputs):
|
|
541
|
-
self.tms.append(TsetlinMachine(self.number_of_clauses, self.T, self.s, weighted_clauses=self.weighted_clauses, hierarchy_structure=self.hierarchy_structure, q=self.q, boost_true_positive_feedback=self.boost_true_positive_feedback, number_of_state_bits=self.number_of_state_bits, append_negated=self.append_negated, grid=self.grid, block=self.block))
|
|
687
|
+
self.tms.append(TsetlinMachine(self.number_of_clauses, self.T, self.s, weighted_clauses=self.weighted_clauses, hierarchy_structure=self.hierarchy_structure, q=self.q, boost_true_positive_feedback=self.boost_true_positive_feedback, number_of_state_bits=self.number_of_state_bits, append_negated=self.append_negated, grid=self.grid, block=self.block, seed=self.seed+i))
|
|
542
688
|
|
|
543
689
|
self.configured = True
|
|
544
|
-
|
|
690
|
+
|
|
545
691
|
encoded_Y = np.empty(Y.shape[0], dtype = np.int32)
|
|
546
692
|
|
|
547
693
|
for epoch in range(epochs):
|
|
@@ -569,8 +715,38 @@ class MultiClassTsetlinMachine:
|
|
|
569
715
|
def predict(self, X):
|
|
570
716
|
return np.argmax(self.score(X), axis=0)
|
|
571
717
|
|
|
718
|
+
def save(self) -> dict:
|
|
719
|
+
return {
|
|
720
|
+
'number_of_outputs': self.number_of_outputs,
|
|
721
|
+
'tms': [t.save() for t in self.tms],
|
|
722
|
+
'params': {
|
|
723
|
+
'number_of_clauses': self.number_of_clauses,
|
|
724
|
+
'T': self.T,
|
|
725
|
+
's': self.s,
|
|
726
|
+
'q': self.q,
|
|
727
|
+
'weighted_clauses': self.weighted_clauses,
|
|
728
|
+
'hierarchy_structure': self.hierarchy_structure,
|
|
729
|
+
'boost_true_positive_feedback': self.boost_true_positive_feedback,
|
|
730
|
+
'number_of_state_bits': self.number_of_state_bits,
|
|
731
|
+
'append_negated': self.append_negated,
|
|
732
|
+
'grid': self.grid,
|
|
733
|
+
'block': self.block,
|
|
734
|
+
},
|
|
735
|
+
}
|
|
736
|
+
|
|
737
|
+
def load(self, state_dict: dict):
|
|
738
|
+
self.number_of_outputs = state_dict['number_of_outputs']
|
|
739
|
+
|
|
740
|
+
self.tms = []
|
|
741
|
+
for tm_state in state_dict['tms']:
|
|
742
|
+
t = TsetlinMachine(self.number_of_clauses, self.T, self.s, weighted_clauses=self.weighted_clauses, hierarchy_structure=self.hierarchy_structure, q=self.q, boost_true_positive_feedback=self.boost_true_positive_feedback, number_of_state_bits=self.number_of_state_bits, append_negated=self.append_negated, grid=self.grid, block=self.block)
|
|
743
|
+
t.load(tm_state)
|
|
744
|
+
self.tms.append(t)
|
|
745
|
+
|
|
746
|
+
self.configured = True
|
|
747
|
+
|
|
572
748
|
class TsetlinMachine(CommonTsetlinMachine):
|
|
573
|
-
def __init__(self, number_of_clauses, T, s, weighted_clauses=False, hierarchy_structure=((AND_GROUP, 1)), q=1.0, boost_true_positive_feedback=1, number_of_state_bits=8, append_negated=True, grid=(16*13,1,1), block=(128,1,1)):
|
|
749
|
+
def __init__(self, number_of_clauses, T, s, weighted_clauses=False, hierarchy_structure=((AND_GROUP, 1)), q=1.0, boost_true_positive_feedback=1, number_of_state_bits=8, append_negated=True, grid=(16*13,1,1), block=(128,1,1), seed=None):
|
|
574
750
|
self.negative_clauses = 1
|
|
575
751
|
self.flip_polarity = 0
|
|
576
752
|
|
|
@@ -579,7 +755,7 @@ class TsetlinMachine(CommonTsetlinMachine):
|
|
|
579
755
|
else:
|
|
580
756
|
self.tm_type = VANILLA_TM
|
|
581
757
|
|
|
582
|
-
super().__init__(number_of_clauses, T, s, hierarchy_structure=hierarchy_structure, q=q, boost_true_positive_feedback=boost_true_positive_feedback, number_of_state_bits=number_of_state_bits, append_negated=append_negated, grid=grid, block=block)
|
|
758
|
+
super().__init__(number_of_clauses, T, s, hierarchy_structure=hierarchy_structure, q=q, boost_true_positive_feedback=boost_true_positive_feedback, number_of_state_bits=number_of_state_bits, append_negated=append_negated, grid=grid, block=block, seed=seed)
|
|
583
759
|
|
|
584
760
|
def fit(self, X, Y, epochs=100, incremental=False):
|
|
585
761
|
X = X.reshape(X.shape[0], X.shape[1], 1)
|
|
@@ -603,11 +779,11 @@ class TsetlinMachine(CommonTsetlinMachine):
|
|
|
603
779
|
return (self.score(X) >= 0).astype(np.int32)
|
|
604
780
|
|
|
605
781
|
class RegressionTsetlinMachine(CommonTsetlinMachine):
|
|
606
|
-
def __init__(self, number_of_clauses, T, s, hierarchy_structure=((AND_GROUP, 1)), boost_true_positive_feedback=1, number_of_state_bits=8, append_negated=True, grid=(16*13,1,1), block=(128,1,1)):
|
|
782
|
+
def __init__(self, number_of_clauses, T, s, hierarchy_structure=((AND_GROUP, 1)), boost_true_positive_feedback=1, number_of_state_bits=8, append_negated=True, grid=(16*13,1,1), block=(128,1,1), seed=None):
|
|
607
783
|
self.negative_clauses = 0
|
|
608
784
|
self.flip_polarity = 0
|
|
609
785
|
|
|
610
|
-
super().__init__(number_of_clauses, T, s, hierarchy_structure=hierarchy_structure, boost_true_positive_feedback=boost_true_positive_feedback, number_of_state_bits=number_of_state_bits, append_negated=append_negated, grid=grid, block=block)
|
|
786
|
+
super().__init__(number_of_clauses, T, s, hierarchy_structure=hierarchy_structure, boost_true_positive_feedback=boost_true_positive_feedback, number_of_state_bits=number_of_state_bits, append_negated=append_negated, grid=grid, block=block, seed=seed)
|
|
611
787
|
|
|
612
788
|
def fit(self, X, Y, epochs=100, incremental=False):
|
|
613
789
|
X = X.reshape(X.shape[0], X.shape[1], 1)
|
|
@@ -0,0 +1,175 @@
|
|
|
1
|
+
from collections import deque
|
|
2
|
+
|
|
3
|
+
import networkx as nx
|
|
4
|
+
import numpy as np
|
|
5
|
+
|
|
6
|
+
from .tm import OR_ALTERNATIVES, CommonTsetlinMachine
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
def make_hierarchy_graph(G: nx.Graph, hier: list[tuple[int, int]]):
|
|
10
|
+
"""Create Graph from the hierarchy."""
|
|
11
|
+
# BFS
|
|
12
|
+
q = deque()
|
|
13
|
+
q.append((len(hier), 0)) # (level, idx)
|
|
14
|
+
while q:
|
|
15
|
+
level, idx = q.popleft()
|
|
16
|
+
G.add_node((level, idx), label=hier[level - 1][0], op=hier[level - 1][0])
|
|
17
|
+
if level == 1:
|
|
18
|
+
continue
|
|
19
|
+
branching = hier[level - 1][1]
|
|
20
|
+
for cid in range(idx * branching, (idx + 1) * branching):
|
|
21
|
+
q.append((level - 1, cid))
|
|
22
|
+
G.add_edge((level, idx), (level - 1, cid))
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def clause_to_nx(
|
|
26
|
+
tm: CommonTsetlinMachine,
|
|
27
|
+
clause_idx: int,
|
|
28
|
+
feature_names: list[str] | None = None,
|
|
29
|
+
negation_prefix: str = '¬',
|
|
30
|
+
clause_literals: np.ndarray | None = None,
|
|
31
|
+
ta_to_fid_mapping=None,
|
|
32
|
+
):
|
|
33
|
+
"""
|
|
34
|
+
Create a networkx Graph for a single clause.
|
|
35
|
+
Args:
|
|
36
|
+
`tm`: The model.
|
|
37
|
+
`clause_idx`: Index of the clause to visualize.
|
|
38
|
+
`feature_names`: Optional list of feature names for labeling. If None, defaults to 'x0', 'x1', ...
|
|
39
|
+
`negation_prefix`: Prefix for negated features (default: '¬').
|
|
40
|
+
`clause_literals`: Optional pre-extracted literals for the clause, must have shape (n_components, literals_per_component). If None, literals will be extracted from the model.
|
|
41
|
+
`ta_to_fid_mapping`: Optional mapping from (component_id, literal_id) to feature_id. If None, it will be obtained from the model.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
assert tm.hierarchy_structure is not None, (
|
|
45
|
+
'Hierarchy structure not defined in the model. Are you sure tm belongs to CommonTsetlinMachine or its subclasses?'
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
if feature_names is None:
|
|
49
|
+
feature_names = [f'x{i}' for i in range(tm.number_of_features_hierarchy)]
|
|
50
|
+
|
|
51
|
+
literals = (
|
|
52
|
+
clause_literals
|
|
53
|
+
if clause_literals is not None
|
|
54
|
+
else tm.get_literals()[clause_idx]
|
|
55
|
+
)
|
|
56
|
+
ta_to_fid = (
|
|
57
|
+
ta_to_fid_mapping
|
|
58
|
+
if ta_to_fid_mapping is not None
|
|
59
|
+
else tm.map_ta_id_to_feature_id()
|
|
60
|
+
)
|
|
61
|
+
n_comp = tm.hierarchy_size[1]
|
|
62
|
+
lits_per_comp = tm.number_of_literals_per_leaf
|
|
63
|
+
|
|
64
|
+
G = nx.Graph()
|
|
65
|
+
make_hierarchy_graph(G, tm.hierarchy_structure)
|
|
66
|
+
|
|
67
|
+
feat_per_comp = lits_per_comp // 2 if tm.append_negated else lits_per_comp
|
|
68
|
+
|
|
69
|
+
# Add included literals to leaf components
|
|
70
|
+
for comp_id in range(n_comp):
|
|
71
|
+
for fid in range(feat_per_comp):
|
|
72
|
+
pos_lit = literals[comp_id, fid]
|
|
73
|
+
if pos_lit:
|
|
74
|
+
G.add_node(
|
|
75
|
+
(0, comp_id * lits_per_comp + fid),
|
|
76
|
+
label=feature_names[ta_to_fid[comp_id, fid]],
|
|
77
|
+
)
|
|
78
|
+
G.add_edge((1, comp_id), (0, comp_id * lits_per_comp + fid))
|
|
79
|
+
|
|
80
|
+
if tm.append_negated:
|
|
81
|
+
neg_lit = literals[comp_id, feat_per_comp + fid]
|
|
82
|
+
if neg_lit:
|
|
83
|
+
lab = f'{negation_prefix}{feature_names[ta_to_fid[comp_id, fid]]}'
|
|
84
|
+
G.add_node(
|
|
85
|
+
(0, comp_id * lits_per_comp + feat_per_comp + fid), label=lab
|
|
86
|
+
)
|
|
87
|
+
G.add_edge(
|
|
88
|
+
(1, comp_id), (0, comp_id * lits_per_comp + feat_per_comp + fid)
|
|
89
|
+
)
|
|
90
|
+
|
|
91
|
+
return G
|
|
92
|
+
|
|
93
|
+
|
|
94
|
+
def clause_bank_to_nx(
|
|
95
|
+
tm: CommonTsetlinMachine,
|
|
96
|
+
feature_names: list[str] | None = None,
|
|
97
|
+
negation_prefix: str = '¬',
|
|
98
|
+
):
|
|
99
|
+
"""
|
|
100
|
+
Create a networkx Graph for the entire clause bank.
|
|
101
|
+
Args:
|
|
102
|
+
`tm`: The model.
|
|
103
|
+
`feature_names`: Optional list of feature names for labeling. If None, defaults to 'x0', 'x1', ...
|
|
104
|
+
`negation_prefix`: Prefix for negated features (default: '¬').
|
|
105
|
+
"""
|
|
106
|
+
literals = tm.get_literals()
|
|
107
|
+
ta_to_fid = tm.map_ta_id_to_feature_id()
|
|
108
|
+
G = nx.Graph()
|
|
109
|
+
cb_root = 'CB_ROOT'
|
|
110
|
+
G.add_node(cb_root, label=OR_ALTERNATIVES, op=OR_ALTERNATIVES)
|
|
111
|
+
for ci in range(tm.number_of_clauses):
|
|
112
|
+
clause_G = clause_to_nx(
|
|
113
|
+
tm, ci, feature_names, negation_prefix, literals[ci], ta_to_fid
|
|
114
|
+
)
|
|
115
|
+
mapping = {node: (ci, node) for node in clause_G.nodes}
|
|
116
|
+
G = nx.compose(G, nx.relabel_nodes(clause_G, mapping))
|
|
117
|
+
G.add_edge(cb_root, (ci, (tm.depth, 0)))
|
|
118
|
+
|
|
119
|
+
return G
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
def active_path_graph(
|
|
123
|
+
tm: CommonTsetlinMachine,
|
|
124
|
+
X: np.ndarray,
|
|
125
|
+
feature_names: list[str] | None = None,
|
|
126
|
+
negation_prefix: str = '¬',
|
|
127
|
+
):
|
|
128
|
+
"""
|
|
129
|
+
Show the activated path, and vote calculation for a single sample `X`.
|
|
130
|
+
Args:
|
|
131
|
+
`tm`: The model.
|
|
132
|
+
`X`: A single sample with shape (n_features,).
|
|
133
|
+
`feature_names`: Optional list of feature names for labeling. If None, defaults to 'x0', 'x1', ...
|
|
134
|
+
`negation_prefix`: Prefix for negated features (default: '¬').
|
|
135
|
+
Returns:
|
|
136
|
+
A list of networkx Graphs for each clause, with 'active' and 'vote' attributes on nodes and edges, and class sums for each class.
|
|
137
|
+
"""
|
|
138
|
+
assert X.ndim == 1, 'X must be a single sample with shape (n_features,)'
|
|
139
|
+
|
|
140
|
+
hierarchy_votes, class_sums = tm.calc_hierarchy_votes(X.reshape(1, -1))
|
|
141
|
+
hierarchy_votes = hierarchy_votes[0]
|
|
142
|
+
class_sums = class_sums[0]
|
|
143
|
+
|
|
144
|
+
literals = tm.get_literals()
|
|
145
|
+
ta_to_fid = tm.map_ta_id_to_feature_id()
|
|
146
|
+
lits_per_comp = tm.number_of_literals_per_leaf
|
|
147
|
+
feat_per_comp = lits_per_comp // 2 if tm.append_negated else lits_per_comp
|
|
148
|
+
|
|
149
|
+
G = [
|
|
150
|
+
clause_to_nx(tm, ci, feature_names, negation_prefix, literals[ci], ta_to_fid)
|
|
151
|
+
for ci in range(tm.number_of_clauses)
|
|
152
|
+
]
|
|
153
|
+
for ci in range(tm.number_of_clauses):
|
|
154
|
+
g = G[ci]
|
|
155
|
+
|
|
156
|
+
for node in g.nodes():
|
|
157
|
+
level, idx = node
|
|
158
|
+
if level == 0:
|
|
159
|
+
comp_id = idx // lits_per_comp
|
|
160
|
+
local_lit = idx % lits_per_comp
|
|
161
|
+
fid = ta_to_fid[comp_id, local_lit]
|
|
162
|
+
is_neg = (local_lit >= feat_per_comp) if tm.append_negated else False
|
|
163
|
+
g.nodes[node]['active'] = (X[fid] == 0) if is_neg else (X[fid] == 1)
|
|
164
|
+
g.nodes[node]['vote'] = 1 if g.nodes[node]['active'] else None
|
|
165
|
+
else:
|
|
166
|
+
vote = hierarchy_votes[level - 1][ci, idx]
|
|
167
|
+
g.nodes[node]['active'] = vote > 0
|
|
168
|
+
g.nodes[node]['vote'] = vote
|
|
169
|
+
|
|
170
|
+
for u, v in g.edges():
|
|
171
|
+
g.edges[u, v]['active'] = g.nodes[u].get('active', False) and g.nodes[
|
|
172
|
+
v
|
|
173
|
+
].get('active', False)
|
|
174
|
+
|
|
175
|
+
return G, class_sums
|
|
@@ -5,12 +5,9 @@ PyHierarchicalTsetlinMachineCUDA/__init__.py
|
|
|
5
5
|
PyHierarchicalTsetlinMachineCUDA/kernels.py
|
|
6
6
|
PyHierarchicalTsetlinMachineCUDA/tm.py
|
|
7
7
|
PyHierarchicalTsetlinMachineCUDA/tools.py
|
|
8
|
+
PyHierarchicalTsetlinMachineCUDA/utils.py
|
|
8
9
|
PyHierarchicalTsetlinMachineCUDA.egg-info/PKG-INFO
|
|
9
10
|
PyHierarchicalTsetlinMachineCUDA.egg-info/SOURCES.txt
|
|
10
11
|
PyHierarchicalTsetlinMachineCUDA.egg-info/dependency_links.txt
|
|
11
12
|
PyHierarchicalTsetlinMachineCUDA.egg-info/requires.txt
|
|
12
|
-
PyHierarchicalTsetlinMachineCUDA.egg-info/top_level.txt
|
|
13
|
-
pyhierarchicaltsetlinmachinecuda.egg-info/PKG-INFO
|
|
14
|
-
pyhierarchicaltsetlinmachinecuda.egg-info/SOURCES.txt
|
|
15
|
-
pyhierarchicaltsetlinmachinecuda.egg-info/dependency_links.txt
|
|
16
|
-
pyhierarchicaltsetlinmachinecuda.egg-info/top_level.txt
|
|
13
|
+
PyHierarchicalTsetlinMachineCUDA.egg-info/top_level.txt
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
# Hierarchical Tsetlin Machine in CUDA
|
|
2
|
+
Implements the [Hierarchical Tsetlin Machine](https://github.com/cair/HierarchicalTsetlinMachine) in CUDA.
|
|
3
|
+
|
|
4
|
+
<p align="center">
|
|
5
|
+
<img width="60%" src="https://github.com/cair/PyHierarchicalTsetlinMachineCUDA/blob/main/figures/Visualization_voting.png">
|
|
6
|
+
</p>
|
|
7
|
+
|
|
8
|
+
## Installation
|
|
9
|
+
|
|
10
|
+
```bash
|
|
11
|
+
pip install PyHierarchicalTsetlinMachineCUDA
|
|
12
|
+
```
|
|
13
|
+
or
|
|
14
|
+
```bash
|
|
15
|
+
git clone git@github.com:cair/PyHierarchicalTsetlinMachineCUDA.git
|
|
16
|
+
cd PyHierarchicalTsetlinMachineCUDA
|
|
17
|
+
python ./setup.py sdist
|
|
18
|
+
pip install dist/PyHierarchicalTsetlinMachineCUDA-0.2.2.tar.gz
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Examples
|
|
22
|
+
|
|
23
|
+
### Noisy Parity Demo
|
|
24
|
+
|
|
25
|
+
#### Code: NoisyParityDemo.py
|
|
26
|
+
|
|
27
|
+
```python
|
|
28
|
+
from PyHierarchicalTsetlinMachineCUDA.tm import TsetlinMachine
|
|
29
|
+
import numpy as np
|
|
30
|
+
from time import time
|
|
31
|
+
import PyHierarchicalTsetlinMachineCUDA.tm as tm
|
|
32
|
+
|
|
33
|
+
clauses = 32
|
|
34
|
+
s = 25.0
|
|
35
|
+
T = 250
|
|
36
|
+
|
|
37
|
+
train_data = np.loadtxt("./examples/NoisyParityTrainingData.txt").astype(np.uint32)
|
|
38
|
+
X_train = train_data[:,0:-1]
|
|
39
|
+
Y_train = train_data[:,-1]
|
|
40
|
+
|
|
41
|
+
test_data = np.loadtxt("./examples/NoisyParityTestingData.txt").astype(np.uint32)
|
|
42
|
+
X_test = test_data[:,0:-1]
|
|
43
|
+
Y_test = test_data[:,-1]
|
|
44
|
+
|
|
45
|
+
tm = TsetlinMachine(clauses, T, s, number_of_state_bits=8, boost_true_positive_feedback=0, hierarchy_structure=((tm.AND_GROUP, 3), (tm.OR_ALTERNATIVES, 3), (tm.AND_GROUP, 2), (tm.OR_ALTERNATIVES, 3), (tm.AND_GROUP, 2)))
|
|
46
|
+
|
|
47
|
+
print("\nAccuracy over 1000 epochs:\n")
|
|
48
|
+
for e in range(1000):
|
|
49
|
+
start_training = time()
|
|
50
|
+
tm.fit(X_train, Y_train, epochs=10, incremental=True)
|
|
51
|
+
stop_training = time()
|
|
52
|
+
|
|
53
|
+
start_testing = time()
|
|
54
|
+
result = 100*(tm.predict(X_test) == Y_test).mean()
|
|
55
|
+
stop_testing = time()
|
|
56
|
+
|
|
57
|
+
tm.print_hierarchy()
|
|
58
|
+
|
|
59
|
+
print("\n#%d Accuracy: %.2f%% Training: %.2fs Testing: %.2fs" % (e+1, result, stop_training-start_training, stop_testing-start_testing))
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
#### Output
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
python ./examples/NoisyParityData.py
|
|
66
|
+
python ./examples/NoisyParityDemo.py
|
|
67
|
+
|
|
68
|
+
Accuracy over 1000 epochs:
|
|
69
|
+
|
|
70
|
+
#1 Accuracy: 51.19% Training: 21.88s Testing: 1.37s
|
|
71
|
+
#2 Accuracy: 51.94% Training: 21.91s Testing: 1.37s
|
|
72
|
+
#3 Accuracy: 53.46% Training: 21.91s Testing: 1.37s
|
|
73
|
+
...
|
|
74
|
+
CLAUSE 1: (((((x0 ∧ x1) ∨ (x0 ∧ x1) ∨ (¬x0 ∧ ¬x1)) ∧ ((¬x0 ∧ ¬x1) ∨ (x0 ∧ x1) ∨ (x0 ∧ x1))) ∨ (((¬x0 ∧ ¬x1) ∨ (¬x0 ∧ ¬x1) ∨ (x0 ∧ x1)) ∧ ((x0 ∧ x1) ∨ (x0 ∧ x1) ∨ (¬x0 ∧ ¬x1))) ∨ (((x0 ∧ x1) ∨ (x0 ∧ x1) ∨ (¬x0 ∧ ¬x1)) ∧ (¬x0 ∨ (x0 ∧ x1) ∨ (¬x0 ∧ ¬x1)))) ∧ ((((x0 ∧ ¬x1) ∨ (x0 ∧ ¬x1) ∨ (x1 ∧ ¬x0)) ∧ ((x0 ∧ x1) ∨ (¬x0 ∧ ¬x1) ∨ (¬x0 ∧ ¬x1))) ∨ (((x1 ∧ ¬x0) ∨ (x0 ∧ ¬x1) ∨ (x0 ∧ ¬x1)) ∧ ((x0 ∧ x1) ∨ (¬x0 ∧ ¬x1) ∨ (x0 ∧ x1))) ∨ (((x1 ∧ ¬x0) ∨ (x1 ∧ ¬x0) ∨ (x0 ∧ ¬x1)) ∧ ((x0 ∧ x1) ∨ (¬x0 ∧ ¬x1) ∨ (¬x0 ∧ ¬x1)))))
|
|
75
|
+
...
|
|
76
|
+
#778 Accuracy: 99.71% Training: 32.57s Testing: 2.18s
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Hex Demo
|
|
80
|
+
|
|
81
|
+
<p align="center">
|
|
82
|
+
<img width="60%" src="https://github.com/cair/PyHierarchicalTsetlinMachineCUDA/blob/hex_description/figures/Hex-board-11x11.png">
|
|
83
|
+
</p>
|
|
84
|
+
<p align="center">
|
|
85
|
+
Source: https://en.wikipedia.org/wiki/Hex_(board_game)
|
|
86
|
+
</p>
|
|
87
|
+
|
|
88
|
+
#### Code: HexDemo.py
|
|
89
|
+
|
|
90
|
+
```python
|
|
91
|
+
from PyHierarchicalTsetlinMachineCUDA.tm import TsetlinMachine
|
|
92
|
+
import numpy as np
|
|
93
|
+
from time import time
|
|
94
|
+
import PyHierarchicalTsetlinMachineCUDA.tm as tm
|
|
95
|
+
import argparse
|
|
96
|
+
|
|
97
|
+
def default_args(**kwargs):
|
|
98
|
+
parser = argparse.ArgumentParser()
|
|
99
|
+
parser.add_argument("--epochs", default=1000, type=int)
|
|
100
|
+
parser.add_argument("--clauses", default=2000, type=int)
|
|
101
|
+
parser.add_argument("--T", default=9000, type=int)
|
|
102
|
+
parser.add_argument("--s", default=44.0, type=float)
|
|
103
|
+
parser.add_argument("--q", default=1.0, type=float)
|
|
104
|
+
parser.add_argument("--boost", default=1, type=int)
|
|
105
|
+
parser.add_argument("--number_of_state_bits", default=7, type=int)
|
|
106
|
+
parser.add_argument("--or_alternatives", default=60, type=int)
|
|
107
|
+
|
|
108
|
+
args = parser.parse_args()
|
|
109
|
+
for key, value in kwargs.items():
|
|
110
|
+
if key in args.__dict__:
|
|
111
|
+
setattr(args, key, value)
|
|
112
|
+
return args
|
|
113
|
+
|
|
114
|
+
args = default_args()
|
|
115
|
+
|
|
116
|
+
data = np.loadtxt("./examples/hex_data.txt").astype(np.uint32)
|
|
117
|
+
X_train = data[:int(len(data)*0.8),0:-1]
|
|
118
|
+
Y_train = data[:int(len(data)*0.8),-1]
|
|
119
|
+
|
|
120
|
+
X_test = data[int(len(data)*0.8):,0:-1]
|
|
121
|
+
Y_test = data[int(len(data)*0.8):,-1]
|
|
122
|
+
|
|
123
|
+
tsetlin_machine = TsetlinMachine(args.clauses, args.T, args.s, weighted_clauses=False, number_of_state_bits=args.number_of_state_bits, boost_true_positive_feedback=args.boost, hierarchy_structure=((tm.AND_GROUP, 72), (tm.OR_ALTERNATIVES, args.or_alternatives), (tm.AND_GROUP, 4)))
|
|
124
|
+
|
|
125
|
+
print("\nAccuracy over %d epochs:\n" % (args.epochs))
|
|
126
|
+
for e in range(args.epochs):
|
|
127
|
+
start_training = time()
|
|
128
|
+
for b in range(10):
|
|
129
|
+
tsetlin_machine.fit(X_train[b*len(Y_train)//10:(b+1)*len(Y_train)//10], Y_train[b*len(Y_train)//10:(b+1)*len(Y_train)//10], epochs=1, incremental=True)
|
|
130
|
+
stop_training = time()
|
|
131
|
+
|
|
132
|
+
start_testing = time()
|
|
133
|
+
result_testing = 100*(tsetlin_machine.predict(X_test) == Y_test).mean()
|
|
134
|
+
stop_testing = time()
|
|
135
|
+
|
|
136
|
+
result_training = 100*(tsetlin_machine.predict(X_train) == Y_train).mean()
|
|
137
|
+
|
|
138
|
+
print("#%d Training Accuracy: %.2f%% Testing Accuracy: %.2f%% Training Time: %.2fs Testing Time: %.2fs" % (e+1, result_training, result_testing, stop_training-start_training, stop_testing-start_testing))
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
#### Output
|
|
142
|
+
|
|
143
|
+
```bash
|
|
144
|
+
cd ./examples
|
|
145
|
+
make
|
|
146
|
+
./hex
|
|
147
|
+
cd ..
|
|
148
|
+
python3 ./examples/HexDemo.py
|
|
149
|
+
|
|
150
|
+
Accuracy over 1000 epochs:
|
|
151
|
+
|
|
152
|
+
#1 Training Accuracy: 97.71% Testing Accuracy: 97.26% Training Time: 36.00s Testing Time: 5.65s
|
|
153
|
+
#2 Training Accuracy: 99.11% Testing Accuracy: 98.61% Training Time: 33.65s Testing Time: 5.65s
|
|
154
|
+
#3 Training Accuracy: 99.53% Testing Accuracy: 98.92% Training Time: 33.00s Testing Time: 5.65s
|
|
155
|
+
#4 Training Accuracy: 99.74% Testing Accuracy: 99.18% Training Time: 32.66s Testing Time: 5.67s
|
|
156
|
+
#5 Training Accuracy: 99.84% Testing Accuracy: 99.35% Training Time: 32.40s Testing Time: 5.67s
|
|
157
|
+
...
|
|
158
|
+
#446 Training Accuracy: 100.00% Testing Accuracy: 99.45% Training Time: 32.09s Testing Time: 6.09s
|
|
159
|
+
#447 Training Accuracy: 100.00% Testing Accuracy: 99.46% Training Time: 32.08s Testing Time: 6.09s
|
|
160
|
+
#448 Training Accuracy: 100.00% Testing Accuracy: 99.45% Training Time: 32.10s Testing Time: 6.09s
|
|
161
|
+
#449 Training Accuracy: 100.00% Testing Accuracy: 99.47% Training Time: 32.06s Testing Time: 6.07s
|
|
162
|
+
#450 Training Accuracy: 100.00% Testing Accuracy: 99.45% Training Time: 31.96s Testing Time: 6.08s
|
|
163
|
+
...
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
## Paper
|
|
167
|
+
|
|
168
|
+
_A Tsetlin Machine for Logical Learning and Reasoning With AND-OR Hierarchies_. Ole-Christoffer Granmo, et al., 2026. (Forthcoming)
|
|
169
|
+
|
|
170
|
+
## Licence
|
|
171
|
+
|
|
172
|
+
MIT License
|
|
173
|
+
|
|
174
|
+
Copyright (c) 2026 Ole-Christoffer Granmo and the University of Agder
|
|
175
|
+
|
|
176
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
177
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
178
|
+
in the Software without restriction, including without limitation the rights
|
|
179
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
180
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
181
|
+
furnished to do so, subject to the following conditions:
|
|
182
|
+
|
|
183
|
+
The above copyright notice and this permission notice shall be included in all
|
|
184
|
+
copies or substantial portions of the Software.
|
|
185
|
+
|
|
186
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
187
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
188
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
189
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
190
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
191
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
192
|
+
SOFTWARE.
|
|
@@ -2,7 +2,7 @@ from setuptools import *
|
|
|
2
2
|
|
|
3
3
|
setup(
|
|
4
4
|
name='PyHierarchicalTsetlinMachineCUDA',
|
|
5
|
-
version='0.2.
|
|
5
|
+
version='0.2.5',
|
|
6
6
|
author='Ole-Christoffer Granmo',
|
|
7
7
|
author_email='ole.granmo@uia.no',
|
|
8
8
|
url='https://github.com/cair/PyHierarchicalTsetlinMachineCUDA/',
|
|
@@ -16,8 +16,9 @@ setup(
|
|
|
16
16
|
'pycuda',
|
|
17
17
|
'scipy',
|
|
18
18
|
'scikit-learn',
|
|
19
|
+
'networkx',
|
|
19
20
|
],
|
|
20
21
|
extras_require={
|
|
21
|
-
'examples': ['tensorflow'],
|
|
22
|
+
'examples': ['tensorflow', 'matplotlib', 'pygraphviz'],
|
|
22
23
|
}
|
|
23
24
|
)
|
|
@@ -1,107 +0,0 @@
|
|
|
1
|
-
# Hierarchical Tsetlin Machine in CUDA
|
|
2
|
-
Implements the [Hierarchical Tsetlin Machine](https://github.com/cair/HierarchicalTsetlinMachine) in CUDA.
|
|
3
|
-
|
|
4
|
-
<p align="center">
|
|
5
|
-
<img width="70%" src="https://github.com/cair/PyHierarchicalTsetlinMachineCUDA/blob/main/figures/Clause_Plot_Mayur_Shende.png">
|
|
6
|
-
</p>
|
|
7
|
-
|
|
8
|
-
## Installation
|
|
9
|
-
|
|
10
|
-
```bash
|
|
11
|
-
pip install pyhierarchicaltsetlinmachinecuda
|
|
12
|
-
```
|
|
13
|
-
or
|
|
14
|
-
```bash
|
|
15
|
-
git clone git@github.com:cair/PyHierarchicalTsetlinMachineCUDA.git
|
|
16
|
-
cd PyHierarchicalTsetlinMachineCUDA
|
|
17
|
-
python ./setup.py sdist
|
|
18
|
-
pip install dist/pyhierarchicaltsetlinmachinecuda-0.2.1.tar.gz
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
## Examples
|
|
22
|
-
|
|
23
|
-
### Noisy Parity Demo
|
|
24
|
-
|
|
25
|
-
#### Code: NoisyParityDemo.py
|
|
26
|
-
|
|
27
|
-
```python
|
|
28
|
-
from PyHierarchicalTsetlinMachineCUDA.tm import TsetlinMachine
|
|
29
|
-
import numpy as np
|
|
30
|
-
from time import time
|
|
31
|
-
import PyHierarchicalTsetlinMachineCUDA.tm as tm
|
|
32
|
-
|
|
33
|
-
train_data = np.loadtxt("./examples/NoisyParityTrainingData.txt").astype(np.uint32)
|
|
34
|
-
X_train = train_data[:,0:-1]
|
|
35
|
-
Y_train = train_data[:,-1]
|
|
36
|
-
|
|
37
|
-
test_data = np.loadtxt("./examples/NoisyParityTestingData.txt").astype(np.uint32)
|
|
38
|
-
X_test = test_data[:,0:-1]
|
|
39
|
-
Y_test = test_data[:,-1]
|
|
40
|
-
|
|
41
|
-
tm = TsetlinMachine(32, 3000, 30.1,
|
|
42
|
-
number_of_state_bits=8,
|
|
43
|
-
boost_true_positive_feedback=0,
|
|
44
|
-
hierarchy_structure=(
|
|
45
|
-
(tm.AND_GROUP, 3),
|
|
46
|
-
(tm.OR_ALTERNATIVES, 10),
|
|
47
|
-
(tm.AND_GROUP, 2),
|
|
48
|
-
(tm.OR_ALTERNATIVES, 2),
|
|
49
|
-
(tm.AND_GROUP, 2)
|
|
50
|
-
)
|
|
51
|
-
)
|
|
52
|
-
|
|
53
|
-
print("\nAccuracy over 500 epochs:\n")
|
|
54
|
-
for i in range(500):
|
|
55
|
-
start_training = time()
|
|
56
|
-
tm.fit(X_train, Y_train, epochs=10, incremental=True)
|
|
57
|
-
stop_training = time()
|
|
58
|
-
|
|
59
|
-
start_testing = time()
|
|
60
|
-
result = 100*(tm.predict(X_test) == Y_test).mean()
|
|
61
|
-
stop_testing = time()
|
|
62
|
-
|
|
63
|
-
print("#%d Accuracy: %.2f%% Training: %.2fs Testing: %.2fs" % (i+1, result, stop_training-start_training, stop_testing-start_testing))
|
|
64
|
-
```
|
|
65
|
-
|
|
66
|
-
#### Output
|
|
67
|
-
|
|
68
|
-
```bash
|
|
69
|
-
python ./examples/NoisyParityData.py
|
|
70
|
-
python ./examples/NoisyParityDemo.py
|
|
71
|
-
|
|
72
|
-
#1 Accuracy: 58.54% Training: 22.36s Testing: 1.39s
|
|
73
|
-
#2 Accuracy: 61.03% Training: 22.40s Testing: 1.39s
|
|
74
|
-
#3 Accuracy: 72.43% Training: 22.42s Testing: 1.39s
|
|
75
|
-
...
|
|
76
|
-
#88 Accuracy: 99.87% Training: 24.39s Testing: 1.51s
|
|
77
|
-
#89 Accuracy: 99.38% Training: 24.40s Testing: 1.52s
|
|
78
|
-
#90 Accuracy: 99.94% Training: 24.39s Testing: 1.52s
|
|
79
|
-
```
|
|
80
|
-
|
|
81
|
-
### Paper
|
|
82
|
-
|
|
83
|
-
_A Tsetlin Machine for Logical Learning and Reasoning With AND-OR Hierarchies_. Ole-Christoffer Granmo, et al., 2026. (Forthcoming)
|
|
84
|
-
|
|
85
|
-
## Licence
|
|
86
|
-
|
|
87
|
-
MIT License
|
|
88
|
-
|
|
89
|
-
Copyright (c) 2026 Ole-Christoffer Granmo and the University of Agder
|
|
90
|
-
|
|
91
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
92
|
-
of this software and associated documentation files (the "Software"), to deal
|
|
93
|
-
in the Software without restriction, including without limitation the rights
|
|
94
|
-
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
95
|
-
copies of the Software, and to permit persons to whom the Software is
|
|
96
|
-
furnished to do so, subject to the following conditions:
|
|
97
|
-
|
|
98
|
-
The above copyright notice and this permission notice shall be included in all
|
|
99
|
-
copies or substantial portions of the Software.
|
|
100
|
-
|
|
101
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
102
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
103
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
104
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
105
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
106
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
107
|
-
SOFTWARE.
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|