PyHierarchicalTsetlinMachineCUDA 0.3.0__tar.gz → 0.3.2__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.
Files changed (15) hide show
  1. {PyHierarchicalTsetlinMachineCUDA-0.3.0 → PyHierarchicalTsetlinMachineCUDA-0.3.2}/PKG-INFO +1 -1
  2. {PyHierarchicalTsetlinMachineCUDA-0.3.0 → PyHierarchicalTsetlinMachineCUDA-0.3.2}/PyHierarchicalTsetlinMachineCUDA/kernels.py +74 -157
  3. {PyHierarchicalTsetlinMachineCUDA-0.3.0 → PyHierarchicalTsetlinMachineCUDA-0.3.2}/PyHierarchicalTsetlinMachineCUDA/tm.py +37 -52
  4. {PyHierarchicalTsetlinMachineCUDA-0.3.0 → PyHierarchicalTsetlinMachineCUDA-0.3.2}/PyHierarchicalTsetlinMachineCUDA/utils.py +30 -8
  5. {PyHierarchicalTsetlinMachineCUDA-0.3.0 → PyHierarchicalTsetlinMachineCUDA-0.3.2}/PyHierarchicalTsetlinMachineCUDA.egg-info/PKG-INFO +1 -1
  6. {PyHierarchicalTsetlinMachineCUDA-0.3.0 → PyHierarchicalTsetlinMachineCUDA-0.3.2}/README.md +2 -2
  7. {PyHierarchicalTsetlinMachineCUDA-0.3.0 → PyHierarchicalTsetlinMachineCUDA-0.3.2}/setup.py +1 -1
  8. {PyHierarchicalTsetlinMachineCUDA-0.3.0 → PyHierarchicalTsetlinMachineCUDA-0.3.2}/LICENSE +0 -0
  9. {PyHierarchicalTsetlinMachineCUDA-0.3.0 → PyHierarchicalTsetlinMachineCUDA-0.3.2}/PyHierarchicalTsetlinMachineCUDA/__init__.py +0 -0
  10. {PyHierarchicalTsetlinMachineCUDA-0.3.0 → PyHierarchicalTsetlinMachineCUDA-0.3.2}/PyHierarchicalTsetlinMachineCUDA/tools.py +0 -0
  11. {PyHierarchicalTsetlinMachineCUDA-0.3.0 → PyHierarchicalTsetlinMachineCUDA-0.3.2}/PyHierarchicalTsetlinMachineCUDA.egg-info/SOURCES.txt +0 -0
  12. {PyHierarchicalTsetlinMachineCUDA-0.3.0 → PyHierarchicalTsetlinMachineCUDA-0.3.2}/PyHierarchicalTsetlinMachineCUDA.egg-info/dependency_links.txt +0 -0
  13. {PyHierarchicalTsetlinMachineCUDA-0.3.0 → PyHierarchicalTsetlinMachineCUDA-0.3.2}/PyHierarchicalTsetlinMachineCUDA.egg-info/requires.txt +0 -0
  14. {PyHierarchicalTsetlinMachineCUDA-0.3.0 → PyHierarchicalTsetlinMachineCUDA-0.3.2}/PyHierarchicalTsetlinMachineCUDA.egg-info/top_level.txt +0 -0
  15. {PyHierarchicalTsetlinMachineCUDA-0.3.0 → PyHierarchicalTsetlinMachineCUDA-0.3.2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: PyHierarchicalTsetlinMachineCUDA
3
- Version: 0.3.0
3
+ Version: 0.3.2
4
4
  Summary: Hierarchical Tsetlin Machine Architecture.
5
5
  Home-page: https://github.com/cair/PyHierarchicalTsetlinMachineCUDA/
6
6
  Author: Ole-Christoffer Granmo
@@ -94,8 +94,14 @@ code_update = """
94
94
 
95
95
  int sign = (*clause_weight >= 0) - (*clause_weight < 0);
96
96
 
97
- float absolute_prediction_error = fabsf(y - class_sum);
98
- if (curand_uniform(localState) <= 1.0*absolute_prediction_error/(2*THRESHOLD)) {
97
+ float update_probability;
98
+ #if CONSTANT_UPDATE_P == 1
99
+ update_probability = 1.0;
100
+ #else
101
+ update_probability = fabsf(y - class_sum)/(2*THRESHOLD);
102
+ #endif
103
+
104
+ if (curand_uniform(localState) <= update_probability) {
99
105
  if (target*sign > 0) {
100
106
  if (clause_output && abs(*clause_weight) < INT_MAX) {
101
107
  (*clause_weight) += sign;
@@ -124,8 +130,14 @@ code_update = """
124
130
 
125
131
  int sign = (*clause_weight >= 0) - (*clause_weight < 0);
126
132
 
127
- float absolute_prediction_error = fabsf(y - class_sum);
128
- if (curand_uniform(localState) <= 1.0*absolute_prediction_error/(2*THRESHOLD)) {
133
+ float update_probability;
134
+ #if CONSTANT_UPDATE_P == 1
135
+ update_probability = 1.0;
136
+ #else
137
+ update_probability = fabsf(y - class_sum)/(2*THRESHOLD);
138
+ #endif
139
+
140
+ if (curand_uniform(localState) <= update_probability) {
129
141
  if (target*sign > 0) {
130
142
  // Type I Feedback
131
143
  for (int ta_chunk = 0; ta_chunk < TA_CHUNKS_PER_LEAF; ++ta_chunk) {
@@ -199,31 +211,19 @@ code_update = """
199
211
 
200
212
  // Evaluate clause component
201
213
 
202
- #if LOG_SCALE == 1
203
- float component_output = 0;
204
- #else
205
- float component_output = 1;
206
- #endif
214
+ float component_output = 1;
207
215
 
208
216
  for (int ta_chunk = 0; ta_chunk < TA_CHUNKS_PER_LEAF-1; ++ta_chunk) {
209
217
  // Compare the TA state of the component (leaf) against the corresponding part of the feature vector
210
218
  if ((ta_state[ta_chunk*STATE_BITS + STATE_BITS - 1] & Xi[feature_chunk_base + ta_chunk]) != ta_state[ta_chunk*STATE_BITS + STATE_BITS - 1]) {
211
- #if LOG_SCALE == 1
212
- component_output = NEG_INFINITY;
213
- #else
214
- component_output = 0;
215
- #endif
219
+ component_output = 0;
216
220
 
217
221
  break;
218
222
  }
219
223
  }
220
224
 
221
225
  if ((ta_state[(TA_CHUNKS_PER_LEAF-1)*STATE_BITS + STATE_BITS - 1] & Xi[feature_chunk_base + TA_CHUNKS_PER_LEAF-1] & FILTER_HIERARCHICAL) != (ta_state[(TA_CHUNKS_PER_LEAF-1)*STATE_BITS + STATE_BITS - 1] & FILTER_HIERARCHICAL)) {
222
- #if LOG_SCALE == 1
223
- component_output = NEG_INFINITY;
224
- #else
225
- component_output = 0;
226
- #endif
226
+ component_output = 0;
227
227
  }
228
228
 
229
229
  global_component_output[clause_component] = component_output;
@@ -262,17 +262,12 @@ code_update = """
262
262
  for (int and_group_node = index; and_group_node < CLAUSES*number_of_and_group_nodes; and_group_node += stride) {
263
263
  // Multiply and factors
264
264
 
265
- #if LOG_SCALE == 1
266
- float and_group_vote_product = 0;
267
- #else
268
- float and_group_vote_product = 1;
269
- #endif
270
-
265
+ float and_group_vote_product = 1;
271
266
  for (int and_factor = 0; and_factor < number_of_and_group_factors; ++and_factor) {
272
267
  // Aggregate votes from each child node through multiplication
273
268
 
274
- #if LOG_SCALE == 1
275
- and_group_vote_product += child_input[and_group_node*number_of_and_group_factors + and_factor];
269
+ #if AND_GROUP_NORMALIZATION == 1
270
+ and_group_vote_product *= powf(child_input[and_group_node*number_of_and_group_factors + and_factor], 1.0/number_of_and_group_factors);
276
271
  #else
277
272
  and_group_vote_product *= child_input[and_group_node*number_of_and_group_factors + and_factor];
278
273
  #endif
@@ -289,24 +284,14 @@ code_update = """
289
284
  int stride = blockDim.x * gridDim.x;
290
285
 
291
286
  // If a group node is false, all children are made false.
292
- for (int group_node = index; group_node < CLAUSES*number_of_group_nodes; group_node += stride) {
293
- #if LOG_SCALE == 1
294
- if (group_node_output[group_node] == NEG_INFINITY) {
295
- for (int and_factor = 0; and_factor < number_of_group_node_children; ++and_factor) {
296
- if (child_input[group_node*number_of_group_node_children + and_factor] >= 0) {
297
- child_input[group_node*number_of_group_node_children + and_factor] = NEG_INFINITY;
298
- }
299
- }
300
- }
301
- #else
302
- if (group_node_output[group_node] == 0) {
303
- for (int and_factor = 0; and_factor < number_of_group_node_children; ++and_factor) {
304
- if (child_input[group_node*number_of_group_node_children + and_factor] > 0) {
305
- child_input[group_node*number_of_group_node_children + and_factor] = 0;
306
- }
287
+ for (int group_node = index; group_node < CLAUSES*number_of_group_nodes; group_node += stride) {
288
+ if (group_node_output[group_node] == 0) {
289
+ for (int and_factor = 0; and_factor < number_of_group_node_children; ++and_factor) {
290
+ if (child_input[group_node*number_of_group_node_children + and_factor] > 0) {
291
+ child_input[group_node*number_of_group_node_children + and_factor] = 0;
307
292
  }
308
293
  }
309
- #endif
294
+ }
310
295
 
311
296
  if (group_node_output[group_node] == -1) {
312
297
  for (int and_factor = 0; and_factor < number_of_group_node_children; ++and_factor) {
@@ -321,63 +306,50 @@ code_update = """
321
306
  int index = blockIdx.x * blockDim.x + threadIdx.x;
322
307
  int stride = blockDim.x * gridDim.x;
323
308
 
324
- int non_zero_children[MAX_NUMBER_OF_OR_GROUP_CHILDREN];
309
+ int child_input_sum;
325
310
 
326
311
  /* Copy state to local memory for efficiency */
327
312
  curandState localState = state[index];
328
313
 
329
314
  // If a group node is false, all children are made false.
330
315
  for (int group_node = index; group_node < CLAUSES*number_of_group_nodes; group_node += stride) {
331
- int number_of_non_zero_children = 0;
332
-
333
- #if LOG_SCALE == 1
334
- if (group_node_output[group_node] == -1) {
335
- for (int or_addend = 0; or_addend < number_of_group_node_children; ++or_addend) {
336
- child_input[group_node*number_of_group_node_children + or_addend] = -1;
337
- }
338
- } else if (group_node_output[group_node] == NEG_INFINITY) {
339
- for (int or_addend = 0; or_addend < number_of_group_node_children; ++or_addend) {
340
- if (child_input[group_node*number_of_group_node_children + or_addend] >= 0) {
341
- child_input[group_node*number_of_group_node_children + or_addend] = NEG_INFINITY;
342
- }
343
- }
344
- } else {
345
- for (int or_addend = 0; or_addend < number_of_group_node_children; ++or_addend) {
346
- if (child_input[group_node*number_of_group_node_children + or_addend] >= 0) {
347
- non_zero_children[number_of_non_zero_children] = or_addend;
348
- number_of_non_zero_children++;
349
- }
350
- }
316
+ if (group_node_output[group_node] == -1) {
317
+ for (int or_addend = 0; or_addend < number_of_group_node_children; ++or_addend) {
318
+ child_input[group_node*number_of_group_node_children + or_addend] = -1;
351
319
  }
352
- #else
353
- if (group_node_output[group_node] == -1) {
354
- for (int or_addend = 0; or_addend < number_of_group_node_children; ++or_addend) {
355
- child_input[group_node*number_of_group_node_children + or_addend] = -1;
356
- }
357
- } else if (group_node_output[group_node] == 0) {
358
- for (int or_addend = 0; or_addend < number_of_group_node_children; ++or_addend) {
359
- if (child_input[group_node*number_of_group_node_children + or_addend] > 0) {
360
- child_input[group_node*number_of_group_node_children + or_addend] = 0;
361
- }
320
+ } else if (group_node_output[group_node] == 0) {
321
+ for (int or_addend = 0; or_addend < number_of_group_node_children; ++or_addend) {
322
+ if (child_input[group_node*number_of_group_node_children + or_addend] > 0) {
323
+ child_input[group_node*number_of_group_node_children + or_addend] = 0;
362
324
  }
363
- } else {
364
- for (int or_addend = 0; or_addend < number_of_group_node_children; ++or_addend) {
365
- if (child_input[group_node*number_of_group_node_children + or_addend] > 0) { // Or pick the largest.... picking randomly when a tie
366
- non_zero_children[number_of_non_zero_children] = or_addend;
367
- number_of_non_zero_children++;
368
- }
325
+ }
326
+ } else {
327
+ child_input_sum = 0;
328
+ for (int or_addend = 0; or_addend < number_of_group_node_children; ++or_addend) {
329
+ if (child_input[group_node*number_of_group_node_children + or_addend] > 0) {
330
+ child_input_sum += child_input[group_node*number_of_group_node_children + or_addend];
369
331
  }
370
332
  }
371
- #endif
333
+ }
372
334
 
335
+ // Skip node if "turned off" (-1)
373
336
  if (group_node_output[group_node] != -1) {
374
337
  int selected_child;
375
- if (number_of_non_zero_children > 0) {
376
- selected_child = non_zero_children[curand(&localState) % number_of_non_zero_children];
338
+ if (group_node_output[group_node] > 0) {
339
+ int random_child_value = curand(&localState) % child_input_sum;
340
+ child_input_sum = 0;
341
+ for (int or_addend = 0; or_addend < number_of_group_node_children; ++or_addend) {
342
+ child_input_sum += child_input[group_node*number_of_group_node_children + or_addend];
343
+ if (child_input_sum > random_child_value) {
344
+ selected_child = or_addend;
345
+ break;
346
+ }
347
+ }
377
348
  } else {
378
349
  selected_child = curand(&localState) % number_of_group_node_children;
379
350
  }
380
351
 
352
+ // "Turn off" the sub-hierarchies that were not selected
381
353
  for (int or_addend = 0; or_addend < number_of_group_node_children; ++or_addend) {
382
354
  if (selected_child != or_addend) {
383
355
  child_input[group_node*number_of_group_node_children + or_addend] = -1;
@@ -397,39 +369,15 @@ code_update = """
397
369
 
398
370
  // Add up the votes from the children of each OR node
399
371
  for (int or_alternatives_node = index; or_alternatives_node < CLAUSES*number_of_or_alternatives_nodes; or_alternatives_node += stride) {
400
- #if LOG_SCALE == 1
401
- float or_alternatives_vote_max = NEG_INFINITY;
402
- for (int or_alternative = 0; or_alternative < number_of_or_alternatives; ++or_alternative) {
403
- if (child_input[or_alternatives_node * number_of_or_alternatives + or_alternative] > or_alternatives_vote_max) {
404
- or_alternatives_vote_max = child_input[or_alternatives_node * number_of_or_alternatives + or_alternative];
405
- }
406
- }
407
-
408
- if (or_alternatives_vote_max != NEG_INFINITY) {
409
- // Sum up votes from each or alternative
410
- float or_alternatives_vote_sum = 0;
411
- for (int or_alternative = 0; or_alternative < number_of_or_alternatives; ++or_alternative) {
412
- // Aggregates or alternatives through summation
413
-
414
- or_alternatives_vote_sum += exp2f(child_input[or_alternatives_node * number_of_or_alternatives + or_alternative] - or_alternatives_vote_max);
415
- }
416
-
417
- // Store vote sum as node output
418
- or_alternatives_node_output[or_alternatives_node] = or_alternatives_vote_max + log2f(or_alternatives_vote_sum);
419
- } else {
420
- or_alternatives_node_output[or_alternatives_node] = NEG_INFINITY;
421
- }
422
- #else
423
- // Sum up votes from each or alternative
424
- int or_alternatives_vote_sum = 0;
425
- for (int or_alternative = 0; or_alternative < number_of_or_alternatives; ++or_alternative) {
426
- // Aggregate same input or alternatives through summation
427
- or_alternatives_vote_sum += child_input[or_alternatives_node * number_of_or_alternatives + or_alternative];
428
- }
372
+ // Sum up votes from each or alternative
373
+ int or_alternatives_vote_sum = 0;
374
+ for (int or_alternative = 0; or_alternative < number_of_or_alternatives; ++or_alternative) {
375
+ // Aggregate same input or alternatives through summation
376
+ or_alternatives_vote_sum += child_input[or_alternatives_node * number_of_or_alternatives + or_alternative];
377
+ }
429
378
 
430
- // Store vote sum as node output
431
- or_alternatives_node_output[or_alternatives_node] = or_alternatives_vote_sum;
432
- #endif
379
+ // Store vote sum as node output
380
+ or_alternatives_node_output[or_alternatives_node] = or_alternatives_vote_sum;
433
381
  }
434
382
  }
435
383
 
@@ -450,41 +398,18 @@ code_update = """
450
398
  int stride = blockDim.x * gridDim.x;
451
399
 
452
400
  // Add up the votes from each clause
453
- #if LOG_SCALE == 1
454
- // Add up the votes from each clause
455
- for (int class_id = index; class_id < number_of_outputs; class_id += stride) {
456
- float clause_output_max = NEG_INFINITY;
457
- for (int clause = 0; clause < CLAUSES; ++clause) {
458
- if (clause_output[clause] > clause_output_max) {
459
- clause_output_max = clause_output[clause];
460
- }
461
- }
462
-
463
- if (clause_output_max != NEG_INFINITY) {
464
- float weighted_clause_output_sum = 0;
465
- for (int clause = 0; clause < CLAUSES; ++clause) {
466
- weighted_clause_output_sum += clause_weights[class_id*CLAUSES + clause] * exp2f(clause_output[clause] - clause_output_max);
467
- }
468
401
 
469
- if (log2f(fabs(weighted_clause_output_sum)) + clause_output_max >= log2f(THRESHOLD)) {
470
- float sign = (1 - 2 * (weighted_clause_output_sum < 0));
471
- class_sum[class_id] = sign*THRESHOLD;
472
- } else {
473
- class_sum[class_id] = weighted_clause_output_sum * exp2f(clause_output_max);
474
- }
475
- } else {
476
- class_sum[class_id] = 0;
477
- }
478
- }
479
- #else
480
- for (int clause = index; clause < CLAUSES; clause += stride) {
481
- if (clause_output[clause]) {
482
- for (int class_id = 0; class_id < number_of_outputs; ++class_id) {
402
+ for (int clause = index; clause < CLAUSES; clause += stride) {
403
+ if (clause_output[clause]) {
404
+ for (int class_id = 0; class_id < number_of_outputs; ++class_id) {
405
+ #if BINARY_INFERENCE == 0
483
406
  atomicAdd(&class_sum[class_id], (float) clause_weights[class_id*CLAUSES + clause] * clause_output[clause]);
484
- }
485
- }
407
+ #else
408
+ atomicAdd(&class_sum[class_id], (float) clause_weights[class_id*CLAUSES + clause]);
409
+ #endif
410
+ }
486
411
  }
487
- #endif
412
+ }
488
413
  }
489
414
 
490
415
  __global__ void rescale_final(int number_of_outputs, float *clause_output_max, float *class_sum)
@@ -563,11 +488,7 @@ code_update = """
563
488
  local_class_sum = -THRESHOLD;
564
489
  }
565
490
 
566
- #if LOG_SCALE == 1
567
- update_component_hierarchy(&localState, number_of_outputs, &clause_weights[class_id*CLAUSES + clause], ta_state, component_output[clause_component] != NEG_INFINITY, &Xi[feature_chunk_base], y[example*number_of_outputs + class_id], local_class_sum);
568
- #else
569
- update_component_hierarchy(&localState, number_of_outputs, &clause_weights[class_id*CLAUSES + clause], ta_state, component_output[clause_component] > 0, &Xi[feature_chunk_base], y[example*number_of_outputs + class_id], local_class_sum);
570
- #endif
491
+ update_component_hierarchy(&localState, number_of_outputs, &clause_weights[class_id*CLAUSES + clause], ta_state, component_output[clause_component] > 0, &Xi[feature_chunk_base], y[example*number_of_outputs + class_id], local_class_sum);
571
492
  }
572
493
  }
573
494
 
@@ -592,11 +513,7 @@ code_update = """
592
513
  local_class_sum = -THRESHOLD;
593
514
  }
594
515
 
595
- #if LOG_SCALE == 1
596
- update_clause_weight(&localState, tm_type, number_of_outputs, &clause_weights[class_id*CLAUSES + clause], clause_output[clause] != NEG_INFINITY, y[example*number_of_outputs + class_id], local_class_sum);
597
- #else
598
- update_clause_weight(&localState, tm_type, number_of_outputs, &clause_weights[class_id*CLAUSES + clause], clause_output[clause] > 0, y[example*number_of_outputs + class_id], local_class_sum);
599
- #endif
516
+ update_clause_weight(&localState, tm_type, number_of_outputs, &clause_weights[class_id*CLAUSES + clause], clause_output[clause] > 0, y[example*number_of_outputs + class_id], local_class_sum);
600
517
  }
601
518
  }
602
519
 
@@ -25,7 +25,7 @@ import PyHierarchicalTsetlinMachineCUDA.kernels as kernels
25
25
 
26
26
  import pycuda.curandom as curandom
27
27
  import pycuda.driver as cuda
28
- import pycuda.autoinit
28
+ import pycuda.autoprimaryctx
29
29
  from pycuda.compiler import SourceModule
30
30
  from pycuda import gpuarray
31
31
  from pycuda.gpuarray import GPUArray
@@ -44,13 +44,16 @@ COALESCED_TM = 2
44
44
 
45
45
  class CommonTsetlinMachine():
46
46
 
47
- def __init__(self, number_of_clauses, T, s, q=1.0, log_scale=False, 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):
47
+ def __init__(self, number_of_clauses, T, s, q=1.0, no_clipping=False, constant_update_p=0, binary_inference=False, and_group_normalization=False, 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):
48
48
  self.number_of_clauses = number_of_clauses
49
49
  self.number_of_state_bits = number_of_state_bits
50
- self.T = int(T)
50
+ self.T = float(T)
51
51
  self.s = s
52
52
  self.q = q
53
- self.log_scale = log_scale
53
+ self.no_clipping = no_clipping
54
+ self.constant_update_p = constant_update_p
55
+ self.binary_inference = binary_inference
56
+ self.and_group_normalization = and_group_normalization
54
57
  self.hierarchy_structure = hierarchy_structure
55
58
  self.depth = len(hierarchy_structure)
56
59
 
@@ -97,12 +100,6 @@ class CommonTsetlinMachine():
97
100
  if self.hierarchy_structure[d][0] != OR_GROUP: # OR_GROUP reuses the same TA
98
101
  self.number_of_ta_teams = self.hierarchy_structure[self.depth - d - 1][1] * self.number_of_ta_teams
99
102
 
100
- # Identifies the larget number of OR_GROUP children (for allocating local memory)
101
- self.max_number_of_or_group_children = 0
102
- for d in range(1, self.depth):
103
- if self.hierarchy_structure[d][0] == OR_GROUP and self.max_number_of_or_group_children < self.hierarchy_structure[d][1]:
104
- self.max_number_of_or_group_children = self.hierarchy_structure[d][1]
105
-
106
103
  # Calculates number of literal chunks overall for the feature vector (ignores OR- and AND alternatives)
107
104
  self.number_of_literal_chunks = self.number_of_literal_chunks_per_leaf
108
105
  for d in range(self.depth - 1, 0, -1):
@@ -133,19 +130,19 @@ class CommonTsetlinMachine():
133
130
  #define S %f
134
131
  #define THRESHOLD %d
135
132
  #define Q %f
136
- #define LOG_SCALE %d
133
+ #define BINARY_INFERENCE %d
134
+ #define CONSTANT_UPDATE_P %d
135
+ #define AND_GROUP_NORMALIZATION %d
137
136
 
138
137
  #define AND_GROUP %d
139
138
  #define AND_ALTERNATIVES %d
140
139
  #define OR_GROUP %d
141
140
  #define OR_ALTERNATIVES %d
142
141
 
143
- #define MAX_NUMBER_OF_OR_GROUP_CHILDREN %d
144
-
145
142
  #define NEGATIVE_CLAUSES %d
146
143
  #define FLIP_POLARITY %d
147
- """ % (self.number_of_clauses, self.depth, self.hierarchy_size[1], self.number_of_ta_teams, self.number_of_literals_per_leaf, self.number_of_literal_chunks_per_leaf, self.number_of_literal_chunks, self.number_of_state_bits, self.boost_true_positive_feedback, self.s, self.T, self.q, self.log_scale, AND_GROUP, AND_ALTERNATIVES, OR_GROUP, OR_ALTERNATIVES, self.max_number_of_or_group_children, self.negative_clauses, self.flip_polarity)
148
-
144
+ """ % (self.number_of_clauses, self.depth, self.hierarchy_size[1], self.number_of_ta_teams, self.number_of_literals_per_leaf, self.number_of_literal_chunks_per_leaf, self.number_of_literal_chunks, self.number_of_state_bits, self.boost_true_positive_feedback, self.s, self.T, self.q, self.binary_inference, self.constant_update_p, self.and_group_normalization, AND_GROUP, AND_ALTERNATIVES, OR_GROUP, OR_ALTERNATIVES, self.negative_clauses, self.flip_polarity)
145
+
149
146
  mod_prepare = SourceModule(parameters + kernels.code_header + kernels.code_prepare, no_extern_c=True)
150
147
  self.prepare_weights = mod_prepare.get_function("prepare_weights")
151
148
  self.prepare_hierarchy = mod_prepare.get_function("prepare_hierarchy")
@@ -337,15 +334,6 @@ class CommonTsetlinMachine():
337
334
  self.clause_output_max[:] = np.finfo(np.float32).min
338
335
  cuda.memcpy_htod(self.clause_output_max_gpu, self.clause_output_max)
339
336
 
340
- # if self.log_scale:
341
- # self.max_clause_output.prepared_call(
342
- # self.grid,
343
- # self.block,
344
- # np.int32(self.number_of_outputs),
345
- # self.hierarchy_votes[self.depth-1],
346
- # self.clause_output_max_gpu
347
- # )
348
-
349
337
  # Adds up the votes from each clause (hierarchy root)
350
338
  self.evaluate_final.prepared_call(
351
339
  self.grid,
@@ -356,15 +344,6 @@ class CommonTsetlinMachine():
356
344
  self.class_sum_gpu
357
345
  )
358
346
 
359
- # if self.log_scale:
360
- # self.rescale_final.prepared_call(
361
- # self.grid,
362
- # self.block,
363
- # np.int32(self.number_of_outputs),
364
- # self.clause_output_max_gpu,
365
- # self.class_sum_gpu
366
- # )
367
-
368
347
  def _fit(self, X, encoded_Y):
369
348
  if self.number_of_features_hierarchy != X.shape[1]:
370
349
  print("The number of features spanned by hierarchy does not align with the input data.")
@@ -453,15 +432,18 @@ class CommonTsetlinMachine():
453
432
  encoded_X_hierarchy_test_gpu = cuda.mem_alloc(int(number_of_examples * self.number_of_literal_chunks * 4))
454
433
  self.encode_X(X, encoded_X_hierarchy_test_gpu)
455
434
 
456
- class_sum = np.ascontiguousarray(np.zeros((self.number_of_outputs, number_of_examples))).astype(np.int32)
435
+ class_sum = np.ascontiguousarray(np.zeros((self.number_of_outputs, number_of_examples))).astype(np.float32)
457
436
 
458
437
  for e in range(number_of_examples):
459
438
  self.evaluate_hierarchy(encoded_X_hierarchy_test_gpu, e)
460
439
 
461
440
  cuda.memcpy_dtoh(self.class_sum, self.class_sum_gpu)
462
- class_sum[:, e] = self.class_sum.astype(np.int32)
441
+ class_sum[:, e] = self.class_sum
463
442
 
464
- class_sum = np.clip(class_sum.reshape((self.number_of_outputs, number_of_examples)), -self.T, self.T)
443
+ if self.no_clipping:
444
+ class_sum = class_sum.reshape((self.number_of_outputs, number_of_examples))
445
+ else:
446
+ class_sum = np.clip(class_sum.reshape((self.number_of_outputs, number_of_examples)), -self.T, self.T)
465
447
 
466
448
  return class_sum
467
449
 
@@ -508,7 +490,7 @@ class CommonTsetlinMachine():
508
490
  continue
509
491
 
510
492
  n_children = self.hierarchy_structure[level][1]
511
- is_alt = (self.hierarchy_structure[level][0] == OR_ALTERNATIVES)
493
+ is_alt = self.hierarchy_structure[level][0] in (OR_ALTERNATIVES, AND_ALTERNATIVES)
512
494
  for child_pos in range(n_children):
513
495
  child_id = node_id * n_children + child_pos
514
496
  if is_alt:
@@ -607,12 +589,12 @@ class CommonTsetlinMachine():
607
589
  if self.ta_action(i, j, k):
608
590
  if (not self.append_negated) or k < self.number_of_literals_per_leaf // 2:
609
591
  if print_ta_state:
610
- l.append("x%d(%d)" % (feature_base + k, self.ta_state(i, j, k)))
592
+ l.append("x%d[%d]" % (feature_base + k, self.ta_state(i, j, k)))
611
593
  else:
612
594
  l.append("x%d" % (feature_base + k,))
613
595
  else:
614
596
  if print_ta_state:
615
- l.append("¬x%d(%d)" % (feature_base + k - self.number_of_literals_per_leaf // 2, self.ta_state(i, j, k)))
597
+ l.append("¬x%d[%d]" % (feature_base + k - self.number_of_literals_per_leaf // 2, self.ta_state(i, j, k)))
616
598
  else:
617
599
  l.append("¬x%d" % (feature_base + k - self.number_of_literals_per_leaf // 2,))
618
600
 
@@ -666,9 +648,9 @@ class CommonTsetlinMachine():
666
648
 
667
649
 
668
650
  class MultiOutputTsetlinMachine(CommonTsetlinMachine):
669
- def __init__(self, number_of_clauses, T, s, q=1.0, log_scale=False, boost_true_positive_feedback=1, number_of_state_bits=8, append_negated=True, grid=(16*13,1,1), block=(128,1,1), seed=None):
651
+ def __init__(self, number_of_clauses, T, s, q=1.0, no_clipping=False, constant_update_p=0, binary_inference=False, and_group_normalization=False, boost_true_positive_feedback=1, number_of_state_bits=8, append_negated=True, grid=(16*13,1,1), block=(128,1,1), seed=None):
670
652
  self.negative_clauses = 1
671
- super().__init__(number_of_clauses, T, s, q=q, log_scale=log_scale, 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)
653
+ super().__init__(number_of_clauses, T, s, q=q, no_clipping=no_clipping, constant_update_p=constant_update_p, binary_inference=binary_inference, and_group_normalization=and_group_normalization, 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)
672
654
 
673
655
  def fit(self, X, Y):
674
656
  X = X.reshape(X.shape[0], X.shape[1], 1)
@@ -692,12 +674,12 @@ class MultiOutputTsetlinMachine(CommonTsetlinMachine):
692
674
  return (self.score(X) >= 0).astype(np.uint32).transpose()
693
675
 
694
676
  class MultiClassCoalescedTsetlinMachine(CommonTsetlinMachine):
695
- def __init__(self, number_of_clauses, T, s, q=1.0, log_scale=False, 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):
677
+ def __init__(self, number_of_clauses, T, s, q=1.0, no_clipping=False, constant_update_p=0, binary_inference=False, and_group_normalization=False, 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):
696
678
  self.negative_clauses = 1
697
679
  self.tm_type = COALESCED_TM
698
680
  self.flip_polarity = 1
699
681
 
700
- super().__init__(number_of_clauses, T, s, q=q, log_scale=log_scale, 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)
682
+ super().__init__(number_of_clauses, T, s, q=q, no_clipping=no_clipping, constant_update_p=constant_update_p, binary_inference=binary_inference, and_group_normalization=and_group_normalization, 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)
701
683
 
702
684
  def fit(self, X, Y):
703
685
  X = X.reshape(X.shape[0], X.shape[1], 1)
@@ -724,12 +706,15 @@ class MultiClassCoalescedTsetlinMachine(CommonTsetlinMachine):
724
706
  return np.argmax(self.score(X), axis=0)
725
707
 
726
708
  class MultiClassTsetlinMachine:
727
- def __init__(self, number_of_clauses, T, s, q=1.0, log_scale=False, weighted_clauses=False, 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):
709
+ def __init__(self, number_of_clauses, T, s, q=1.0, no_clipping=False, constant_update_p=0, binary_inference=False, and_group_normalization=False, weighted_clauses=False, 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):
728
710
  self.number_of_clauses = number_of_clauses
729
711
  self.T = T
730
712
  self.s = s
731
713
  self.q = q
732
- self.log_scale = log_scale
714
+ self.no_clipping = no_clipping
715
+ self.constant_update_p = constant_update_p
716
+ self.binary_inference = binary_inference
717
+ self.and_group_normalization = and_group_normalization
733
718
  self.weighted_clauses = weighted_clauses
734
719
  self.hierarchy_structure = hierarchy_structure
735
720
  self.boost_true_positive_feedback = boost_true_positive_feedback
@@ -747,7 +732,7 @@ class MultiClassTsetlinMachine:
747
732
  if not self.configured:
748
733
  self.tms = []
749
734
  for i in range(self.number_of_outputs):
750
- self.tms.append(TsetlinMachine(self.number_of_clauses, self.T, self.s, q=self.q, log_scale=self.log_scale, weighted_clauses=self.weighted_clauses, hierarchy_structure=self.hierarchy_structure, 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))
735
+ self.tms.append(TsetlinMachine(self.number_of_clauses, self.T, self.s, q=self.q, no_clipping=self.no_clipping, constant_update_p=self.constant_update_p, binary_inference=self.binary_inference, and_group_normalization=self.and_group_normalization, weighted_clauses=self.weighted_clauses, hierarchy_structure=self.hierarchy_structure, 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))
751
736
 
752
737
  self.configured = True
753
738
 
@@ -768,7 +753,7 @@ class MultiClassTsetlinMachine:
768
753
  return
769
754
 
770
755
  def score(self, X):
771
- class_sums = np.empty((self.number_of_outputs, X.shape[0]), dtype=np.int32)
756
+ class_sums = np.empty((self.number_of_outputs, X.shape[0]), dtype=np.float32)
772
757
  for i in range(self.number_of_outputs):
773
758
  class_sums[i,:] = self.tms[i].score(X)
774
759
 
@@ -798,7 +783,7 @@ class MultiClassTsetlinMachine:
798
783
 
799
784
  def print_hierarchy(self, print_ta_state=False):
800
785
  for i in range(self.number_of_outputs):
801
- print("CLASS %d\n", i)
786
+ print("\nCLASS %d\n" % (i,))
802
787
  self.tms[i].print_hierarchy(print_ta_state = print_ta_state)
803
788
 
804
789
  def load(self, state_dict: dict):
@@ -813,7 +798,7 @@ class MultiClassTsetlinMachine:
813
798
  self.configured = True
814
799
 
815
800
  class TsetlinMachine(CommonTsetlinMachine):
816
- def __init__(self, number_of_clauses, T, s, q=1.0, log_scale=False, weighted_clauses=False, 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):
801
+ def __init__(self, number_of_clauses, T, s, q=1.0, no_clipping=False, constant_update_p=0, binary_inference=False, and_group_normalization=False, weighted_clauses=False, 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):
817
802
  self.negative_clauses = 1
818
803
  self.flip_polarity = 0
819
804
 
@@ -822,7 +807,7 @@ class TsetlinMachine(CommonTsetlinMachine):
822
807
  else:
823
808
  self.tm_type = VANILLA_TM
824
809
 
825
- super().__init__(number_of_clauses, T, s, q=q, log_scale=log_scale, 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)
810
+ super().__init__(number_of_clauses, T, s, q=q, no_clipping=no_clipping, constant_update_p=constant_update_p, binary_inference=binary_inference, and_group_normalization=and_group_normalization, 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)
826
811
 
827
812
  def fit(self, X, Y):
828
813
  X = X.reshape(X.shape[0], X.shape[1], 1)
@@ -846,11 +831,11 @@ class TsetlinMachine(CommonTsetlinMachine):
846
831
  return (self.score(X) >= 0).astype(np.int32)
847
832
 
848
833
  class RegressionTsetlinMachine(CommonTsetlinMachine):
849
- def __init__(self, number_of_clauses, T, s, log_scale=False, 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):
834
+ def __init__(self, number_of_clauses, T, s, and_group_normalization=False, 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):
850
835
  self.negative_clauses = 0
851
836
  self.flip_polarity = 0
852
837
 
853
- super().__init__(number_of_clauses, T, s, log_scale=log_scale, 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)
838
+ super().__init__(number_of_clauses, T, s, and_group_normalization=and_group_normalization, 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)
854
839
 
855
840
  def fit(self, X, Y):
856
841
  X = X.reshape(X.shape[0], X.shape[1], 1)
@@ -3,17 +3,31 @@ from collections import deque
3
3
  import networkx as nx
4
4
  import numpy as np
5
5
 
6
- from .tm import OR_ALTERNATIVES, CommonTsetlinMachine
6
+ from .tm import AND_ALTERNATIVES, AND_GROUP, OR_ALTERNATIVES, OR_GROUP, CommonTsetlinMachine
7
7
 
8
+ DEFAULT_NODE_TYPE_LABELS: dict[int, str] = {
9
+ AND_GROUP: ' ∧* ',
10
+ AND_ALTERNATIVES: ' ∧ ',
11
+ OR_GROUP: ' ∨* ',
12
+ OR_ALTERNATIVES: ' ∨ ',
13
+ }
8
14
 
9
- def make_hierarchy_graph(G: nx.Graph, hier: list[tuple[int, int]]):
15
+
16
+ def make_hierarchy_graph(
17
+ G: nx.Graph,
18
+ hier: list[tuple[int, int]],
19
+ node_type_labels: dict[int, str] | None = None,
20
+ ):
10
21
  """Create Graph from the hierarchy."""
22
+ node_type_labels = {**DEFAULT_NODE_TYPE_LABELS, **(node_type_labels or {})}
23
+
11
24
  # BFS
12
25
  q = deque()
13
26
  q.append((len(hier), 0)) # (level, idx)
14
27
  while q:
15
28
  level, idx = q.popleft()
16
- G.add_node((level, idx), label=hier[level - 1][0], op=hier[level - 1][0])
29
+ op = hier[level - 1][0]
30
+ G.add_node((level, idx), label=node_type_labels.get(op, op), op=op)
17
31
  if level == 1:
18
32
  continue
19
33
  branching = hier[level - 1][1]
@@ -29,6 +43,7 @@ def clause_to_nx(
29
43
  negation_prefix: str = '¬',
30
44
  clause_literals: np.ndarray | None = None,
31
45
  ta_to_fid_mapping=None,
46
+ node_type_labels: dict[int, str] | None = None,
32
47
  ):
33
48
  """
34
49
  Create a networkx Graph for a single clause.
@@ -39,6 +54,7 @@ def clause_to_nx(
39
54
  `negation_prefix`: Prefix for negated features (default: '¬').
40
55
  `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
56
  `ta_to_fid_mapping`: Optional mapping from (component_id, literal_id) to feature_id. If None, it will be obtained from the model.
57
+ `node_type_labels`: Optional dict overriding the display symbol for one or more hierarchy node types (`AND_GROUP`/`AND_ALTERNATIVES`/`OR_GROUP`/`OR_ALTERNATIVES`). Merged over `DEFAULT_NODE_TYPE_LABELS`; unset keys fall back to the default.
42
58
  """
43
59
 
44
60
  assert tm.hierarchy_structure is not None, (
@@ -62,7 +78,7 @@ def clause_to_nx(
62
78
  lits_per_comp = tm.number_of_literals_per_leaf
63
79
 
64
80
  G = nx.Graph()
65
- make_hierarchy_graph(G, tm.hierarchy_structure)
81
+ make_hierarchy_graph(G, tm.hierarchy_structure, node_type_labels)
66
82
 
67
83
  feat_per_comp = lits_per_comp // 2 if tm.append_negated else lits_per_comp
68
84
 
@@ -95,6 +111,7 @@ def clause_bank_to_nx(
95
111
  tm: CommonTsetlinMachine,
96
112
  feature_names: list[str] | None = None,
97
113
  negation_prefix: str = '¬',
114
+ node_type_labels: dict[int, str] | None = None,
98
115
  ):
99
116
  """
100
117
  Create a networkx Graph for the entire clause bank.
@@ -102,15 +119,18 @@ def clause_bank_to_nx(
102
119
  `tm`: The model.
103
120
  `feature_names`: Optional list of feature names for labeling. If None, defaults to 'x0', 'x1', ...
104
121
  `negation_prefix`: Prefix for negated features (default: '¬').
122
+ `node_type_labels`: Optional dict overriding the display symbol for one or more hierarchy node types. Merged over `DEFAULT_NODE_TYPE_LABELS`; unset keys fall back to the default.
105
123
  """
124
+ node_type_labels = {**DEFAULT_NODE_TYPE_LABELS, **(node_type_labels or {})}
125
+
106
126
  literals = tm.get_literals()
107
127
  ta_to_fid = tm.map_ta_id_to_feature_id()
108
128
  G = nx.Graph()
109
129
  cb_root = 'CB_ROOT'
110
- G.add_node(cb_root, label=OR_ALTERNATIVES, op=OR_ALTERNATIVES)
130
+ G.add_node(cb_root, label=node_type_labels.get(OR_ALTERNATIVES, OR_ALTERNATIVES), op=OR_ALTERNATIVES)
111
131
  for ci in range(tm.number_of_clauses):
112
132
  clause_G = clause_to_nx(
113
- tm, ci, feature_names, negation_prefix, literals[ci], ta_to_fid
133
+ tm, ci, feature_names, negation_prefix, literals[ci], ta_to_fid, node_type_labels
114
134
  )
115
135
  mapping = {node: (ci, node) for node in clause_G.nodes}
116
136
  G = nx.compose(G, nx.relabel_nodes(clause_G, mapping))
@@ -124,6 +144,7 @@ def active_path_graph(
124
144
  X: np.ndarray,
125
145
  feature_names: list[str] | None = None,
126
146
  negation_prefix: str = '¬',
147
+ node_type_labels: dict[int, str] | None = None,
127
148
  ):
128
149
  """
129
150
  Show the activated path, and vote calculation for a single sample `X`.
@@ -132,6 +153,7 @@ def active_path_graph(
132
153
  `X`: A single sample with shape (n_features,).
133
154
  `feature_names`: Optional list of feature names for labeling. If None, defaults to 'x0', 'x1', ...
134
155
  `negation_prefix`: Prefix for negated features (default: '¬').
156
+ `node_type_labels`: Optional dict overriding the display symbol for one or more hierarchy node types. Merged over `DEFAULT_NODE_TYPE_LABELS`; unset keys fall back to the default.
135
157
  Returns:
136
158
  A list of networkx Graphs for each clause, with 'active' and 'vote' attributes on nodes and edges, and class sums for each class.
137
159
  """
@@ -139,7 +161,7 @@ def active_path_graph(
139
161
 
140
162
  hierarchy_votes, class_sums = tm.calc_hierarchy_votes(X.reshape(1, -1))
141
163
  hierarchy_votes = hierarchy_votes[0]
142
- class_sums = class_sums[0]
164
+ class_sums = class_sums[:, 0]
143
165
 
144
166
  literals = tm.get_literals()
145
167
  ta_to_fid = tm.map_ta_id_to_feature_id()
@@ -147,7 +169,7 @@ def active_path_graph(
147
169
  feat_per_comp = lits_per_comp // 2 if tm.append_negated else lits_per_comp
148
170
 
149
171
  G = [
150
- clause_to_nx(tm, ci, feature_names, negation_prefix, literals[ci], ta_to_fid)
172
+ clause_to_nx(tm, ci, feature_names, negation_prefix, literals[ci], ta_to_fid, node_type_labels)
151
173
  for ci in range(tm.number_of_clauses)
152
174
  ]
153
175
  for ci in range(tm.number_of_clauses):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: PyHierarchicalTsetlinMachineCUDA
3
- Version: 0.3.0
3
+ Version: 0.3.2
4
4
  Summary: Hierarchical Tsetlin Machine Architecture.
5
5
  Home-page: https://github.com/cair/PyHierarchicalTsetlinMachineCUDA/
6
6
  Author: Ole-Christoffer Granmo
@@ -11,7 +11,7 @@ or
11
11
  git clone git@github.com:cair/PyHierarchicalTsetlinMachineCUDA.git
12
12
  cd PyHierarchicalTsetlinMachineCUDA
13
13
  python ./setup.py sdist
14
- pip install dist/PyHierarchicalTsetlinMachineCUDA-0.2.8.tar.gz
14
+ pip install dist/PyHierarchicalTsetlinMachineCUDA-0.3.1.tar.gz
15
15
  ```
16
16
 
17
17
  ## Examples
@@ -79,7 +79,7 @@ CLAUSE 1: (((((x0 ∧ x1) ∨ (x0 ∧ x1) ∨ (¬x0 ∧ ¬x1)) ∧ ((x3 ∧ x4)
79
79
  ### Hex Demo
80
80
 
81
81
  <p align="center">
82
- <img width="60%" src="https://github.com/cair/PyHierarchicalTsetlinMachineCUDA/blob/hex_description/figures/Hex-board-11x11.png">
82
+ <img width="60%" src="https://github.com/cair/PyHierarchicalTsetlinMachineCUDA/blob/main/figures/Hex-board-11x11.png">
83
83
  </p>
84
84
  <p align="center">
85
85
  Source: https://en.wikipedia.org/wiki/Hex_(board_game)
@@ -2,7 +2,7 @@ from setuptools import *
2
2
 
3
3
  setup(
4
4
  name='PyHierarchicalTsetlinMachineCUDA',
5
- version='0.3.0',
5
+ version='0.3.2',
6
6
  author='Ole-Christoffer Granmo',
7
7
  author_email='ole.granmo@uia.no',
8
8
  url='https://github.com/cair/PyHierarchicalTsetlinMachineCUDA/',