PyHierarchicalTsetlinMachineCUDA 0.2.5__tar.gz → 0.2.6__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.2.5 → PyHierarchicalTsetlinMachineCUDA-0.2.6}/PKG-INFO +1 -1
  2. {PyHierarchicalTsetlinMachineCUDA-0.2.5 → PyHierarchicalTsetlinMachineCUDA-0.2.6}/PyHierarchicalTsetlinMachineCUDA/kernels.py +99 -19
  3. {PyHierarchicalTsetlinMachineCUDA-0.2.5 → PyHierarchicalTsetlinMachineCUDA-0.2.6}/PyHierarchicalTsetlinMachineCUDA/tm.py +37 -15
  4. {PyHierarchicalTsetlinMachineCUDA-0.2.5 → PyHierarchicalTsetlinMachineCUDA-0.2.6}/PyHierarchicalTsetlinMachineCUDA.egg-info/PKG-INFO +1 -1
  5. {PyHierarchicalTsetlinMachineCUDA-0.2.5 → PyHierarchicalTsetlinMachineCUDA-0.2.6}/PyHierarchicalTsetlinMachineCUDA.egg-info/requires.txt +1 -0
  6. {PyHierarchicalTsetlinMachineCUDA-0.2.5 → PyHierarchicalTsetlinMachineCUDA-0.2.6}/README.md +5 -5
  7. {PyHierarchicalTsetlinMachineCUDA-0.2.5 → PyHierarchicalTsetlinMachineCUDA-0.2.6}/setup.py +2 -1
  8. {PyHierarchicalTsetlinMachineCUDA-0.2.5 → PyHierarchicalTsetlinMachineCUDA-0.2.6}/LICENSE +0 -0
  9. {PyHierarchicalTsetlinMachineCUDA-0.2.5 → PyHierarchicalTsetlinMachineCUDA-0.2.6}/PyHierarchicalTsetlinMachineCUDA/__init__.py +0 -0
  10. {PyHierarchicalTsetlinMachineCUDA-0.2.5 → PyHierarchicalTsetlinMachineCUDA-0.2.6}/PyHierarchicalTsetlinMachineCUDA/tools.py +0 -0
  11. {PyHierarchicalTsetlinMachineCUDA-0.2.5 → PyHierarchicalTsetlinMachineCUDA-0.2.6}/PyHierarchicalTsetlinMachineCUDA/utils.py +0 -0
  12. {PyHierarchicalTsetlinMachineCUDA-0.2.5 → PyHierarchicalTsetlinMachineCUDA-0.2.6}/PyHierarchicalTsetlinMachineCUDA.egg-info/SOURCES.txt +0 -0
  13. {PyHierarchicalTsetlinMachineCUDA-0.2.5 → PyHierarchicalTsetlinMachineCUDA-0.2.6}/PyHierarchicalTsetlinMachineCUDA.egg-info/dependency_links.txt +0 -0
  14. {PyHierarchicalTsetlinMachineCUDA-0.2.5 → PyHierarchicalTsetlinMachineCUDA-0.2.6}/PyHierarchicalTsetlinMachineCUDA.egg-info/top_level.txt +0 -0
  15. {PyHierarchicalTsetlinMachineCUDA-0.2.5 → PyHierarchicalTsetlinMachineCUDA-0.2.6}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: PyHierarchicalTsetlinMachineCUDA
3
- Version: 0.2.5
3
+ Version: 0.2.6
4
4
  Summary: Hierarchical Tsetlin Machine Architecture.
5
5
  Home-page: https://github.com/cair/PyHierarchicalTsetlinMachineCUDA/
6
6
  Author: Ole-Christoffer Granmo
@@ -159,7 +159,7 @@ code_update = """
159
159
  }
160
160
 
161
161
  // Evaluate example
162
- __global__ void evaluate_leaves(unsigned int *global_ta_state, int *component_weights, int *global_component_output, int depth, int *hierarchy_structure_factors, int *hierarchy_structure_alternatives, int *X, int example)
162
+ __global__ void evaluate_leaves(unsigned int *global_ta_state, int *component_weights, int *global_component_output, int depth, int *hierarchy_structure_factors, int *hierarchy_structure_type, int *X, int example)
163
163
  {
164
164
  int index = blockIdx.x * blockDim.x + threadIdx.x;
165
165
  int stride = blockDim.x * gridDim.x;
@@ -168,35 +168,43 @@ code_update = """
168
168
 
169
169
  // Evaluate each clause component (leaf) in separate threads
170
170
  for (int clause_component = index; clause_component < CLAUSES*COMPONENTS; clause_component += stride) {
171
+ int clause = clause_component / COMPONENTS;
171
172
  int component = clause_component % COMPONENTS;
172
173
 
173
- // Get state of current clause component
174
- unsigned int *ta_state = &global_ta_state[clause_component*TA_CHUNKS_PER_LEAF*STATE_BITS];
175
-
176
174
  int component_remainder = component;
175
+ int feature_chunk_base = 0;
177
176
  int ta_chunk_base = 0;
178
- int size = 1;
177
+ int size_feature_chunk_base = 1;
178
+ int size_ta_chunk_base = 1;
179
179
  for (int d = 0; d < depth-1; ++d) {
180
180
  int depth_d_node_index = component_remainder % hierarchy_structure_factors[d];
181
181
  component_remainder = component_remainder / hierarchy_structure_factors[d];
182
182
 
183
- if (hierarchy_structure_alternatives[d] == 0) {
184
- ta_chunk_base += size * depth_d_node_index * TA_CHUNKS_PER_LEAF;
185
- size *= hierarchy_structure_factors[d];
183
+ if (hierarchy_structure_type[d] != 1) {
184
+ feature_chunk_base += size_feature_chunk_base * depth_d_node_index * TA_CHUNKS_PER_LEAF;
185
+ size_feature_chunk_base *= hierarchy_structure_factors[d];
186
+ }
187
+
188
+ if (hierarchy_structure_type[d] != 2) {
189
+ ta_chunk_base += size_ta_chunk_base * depth_d_node_index * TA_CHUNKS_PER_LEAF;
190
+ size_ta_chunk_base *= hierarchy_structure_factors[d];
186
191
  }
187
192
  }
188
193
 
194
+ // Get state of current ta team component
195
+ unsigned int *ta_state = &global_ta_state[clause*COMPONENTS*TA_CHUNKS_PER_LEAF*STATE_BITS + ta_chunk_base*STATE_BITS];
196
+
189
197
  // Evaluate clause component
190
198
  int component_output = 1;
191
199
  for (int ta_chunk = 0; ta_chunk < TA_CHUNKS_PER_LEAF-1; ++ta_chunk) {
192
200
  // Compare the TA state of the component (leaf) against the corresponding part of the feature vector
193
- if ((ta_state[ta_chunk*STATE_BITS + STATE_BITS - 1] & Xi[ta_chunk_base + ta_chunk]) != ta_state[ta_chunk*STATE_BITS + STATE_BITS - 1]) {
201
+ 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]) {
194
202
  component_output = 0;
195
203
  break;
196
204
  }
197
205
  }
198
206
 
199
- if ((ta_state[(TA_CHUNKS_PER_LEAF-1)*STATE_BITS + STATE_BITS - 1] & Xi[ta_chunk_base + TA_CHUNKS_PER_LEAF-1] & FILTER_HIERARCHICAL) != (ta_state[(TA_CHUNKS_PER_LEAF-1)*STATE_BITS + STATE_BITS - 1] & FILTER_HIERARCHICAL)) {
207
+ 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)) {
200
208
  component_output = 0;
201
209
  }
202
210
 
@@ -213,9 +221,15 @@ code_update = """
213
221
  for (int or_group_node = index; or_group_node < CLAUSES*number_of_or_group_nodes; or_group_node += stride) {
214
222
  // Add OR addends
215
223
  int or_group_vote_sum = 0;
224
+ int max_vote_sum = 0;
225
+
216
226
  for (int or_addend = 0; or_addend < number_of_or_group_addends; ++or_addend) {
217
227
  // Aggregate votes from each child node through addition
218
228
 
229
+ if (child_input[or_group_node*number_of_or_group_addends + or_addend] > max_vote_sum) {
230
+ max_vote_sum = child_input[or_group_node*number_of_or_group_addends + or_addend];
231
+ }
232
+
219
233
  int previous_or_group_vote_sum = or_group_vote_sum;
220
234
  or_group_vote_sum += child_input[or_group_node*number_of_or_group_addends + or_addend];
221
235
  if (or_group_vote_sum < 0) {
@@ -224,8 +238,8 @@ code_update = """
224
238
  }
225
239
  }
226
240
 
227
- // Store or group vote sum as node output
228
- or_group_node_output[or_group_node] = or_group_vote_sum;
241
+ // or_group_node_output[or_group_node] = or_group_vote_sum;
242
+ or_group_node_output[or_group_node] = max_vote_sum;
229
243
  }
230
244
  }
231
245
 
@@ -272,6 +286,58 @@ code_update = """
272
286
  }
273
287
  }
274
288
 
289
+ __global__ void propagate_or_group_false_truth_values(curandState *state, int *child_input, int *group_node_output, int number_of_group_nodes, int number_of_group_node_children)
290
+ {
291
+ int index = blockIdx.x * blockDim.x + threadIdx.x;
292
+ int stride = blockDim.x * gridDim.x;
293
+
294
+ int non_zero_children[361];
295
+
296
+ /* Copy state to local memory for efficiency */
297
+ curandState localState = state[index];
298
+
299
+ // If a group node is false, all children are made false.
300
+ for (int group_node = index; group_node < CLAUSES*number_of_group_nodes; group_node += stride) {
301
+ int number_of_non_zero_children = 0;
302
+
303
+ if (group_node_output[group_node] == -1) {
304
+ for (int or_addend = 0; or_addend < number_of_group_node_children; ++or_addend) {
305
+ child_input[group_node*number_of_group_node_children + or_addend] = -1;
306
+ }
307
+ } else if (group_node_output[group_node] == 0) {
308
+ for (int or_addend = 0; or_addend < number_of_group_node_children; ++or_addend) {
309
+ if (child_input[group_node*number_of_group_node_children + or_addend] > 0) {
310
+ child_input[group_node*number_of_group_node_children + or_addend] = 0;
311
+ }
312
+ }
313
+ } else {
314
+ for (int or_addend = 0; or_addend < number_of_group_node_children; ++or_addend) {
315
+ if (child_input[group_node*number_of_group_node_children + or_addend] > 0) {
316
+ non_zero_children[number_of_non_zero_children] = or_addend;
317
+ number_of_non_zero_children++;
318
+ }
319
+ }
320
+ }
321
+
322
+ if (group_node_output[group_node] != -1) {
323
+ int selected_child;
324
+ if (number_of_non_zero_children > 0) {
325
+ selected_child = non_zero_children[curand(&localState) % number_of_non_zero_children];
326
+ } else {
327
+ selected_child = curand(&localState) % number_of_group_node_children;
328
+ }
329
+
330
+ for (int or_addend = 0; or_addend < number_of_group_node_children; ++or_addend) {
331
+ if (selected_child != or_addend) {
332
+ child_input[group_node*number_of_group_node_children + or_addend] = -1;
333
+ }
334
+ }
335
+ }
336
+ }
337
+
338
+ state[index] = localState;
339
+ }
340
+
275
341
  __global__ void evaluate_or_alternatives(int *child_input, int *or_alternatives_node_output, int number_of_or_alternatives_nodes, int number_of_or_alternatives)
276
342
  {
277
343
  int index = blockIdx.x * blockDim.x + threadIdx.x;
@@ -316,7 +382,7 @@ code_update = """
316
382
  }
317
383
 
318
384
  // Update state of Tsetlin Automata team
319
- __global__ void update_hierarchy(curandState *state, int number_of_outputs, unsigned int *global_ta_state, int *clause_weights, int *component_output, int depth, int *hierarchy_structure_factors, int *hierarchy_structure_alternatives, int *class_sum, int *X, int *y, int example)
385
+ __global__ void update_hierarchy(curandState *state, int number_of_outputs, unsigned int *global_ta_state, int *clause_weights, int *component_output, int depth, int *hierarchy_structure_factors, int *hierarchy_structure_type, int *class_sum, int *X, int *y, int example)
320
386
  {
321
387
  int index = blockIdx.x * blockDim.x + threadIdx.x;
322
388
  int stride = blockDim.x * gridDim.x;
@@ -328,25 +394,39 @@ code_update = """
328
394
 
329
395
  // Calculate clause output first
330
396
  for (int clause_component = index; clause_component < CLAUSES*COMPONENTS; clause_component += stride) {
397
+ if (component_output[clause_component] == -1) {
398
+ continue;
399
+ }
400
+
331
401
  int clause = clause_component / COMPONENTS;
332
402
  int component = clause_component % COMPONENTS;
333
403
 
334
404
  // Get state of current clause component
335
- unsigned int *ta_state = &global_ta_state[clause_component*TA_CHUNKS_PER_LEAF*STATE_BITS];
405
+ // This one must be updated for reuse of TAs within OR group
336
406
 
337
407
  int component_remainder = component;
408
+ int feature_chunk_base = 0;
338
409
  int ta_chunk_base = 0;
339
- int size = 1;
410
+ int size_feature_chunk_base = 1;
411
+ int size_ta_chunk_base = 1;
340
412
  for (int d = 0; d < depth-1; ++d) {
341
413
  int depth_d_node_index = component_remainder % hierarchy_structure_factors[d];
342
414
  component_remainder = component_remainder / hierarchy_structure_factors[d];
343
415
 
344
- if (hierarchy_structure_alternatives[d] == 0) {
345
- ta_chunk_base += size * depth_d_node_index * TA_CHUNKS_PER_LEAF;
346
- size *= hierarchy_structure_factors[d];
416
+ if (hierarchy_structure_type[d] != 1) {
417
+ feature_chunk_base += size_feature_chunk_base * depth_d_node_index * TA_CHUNKS_PER_LEAF;
418
+ size_feature_chunk_base *= hierarchy_structure_factors[d];
419
+ }
420
+
421
+ if (hierarchy_structure_type[d] != 2) {
422
+ ta_chunk_base += size_ta_chunk_base * depth_d_node_index * TA_CHUNKS_PER_LEAF;
423
+ size_ta_chunk_base *= hierarchy_structure_factors[d];
347
424
  }
348
425
  }
349
426
 
427
+ // Get state of current ta team component
428
+ unsigned int *ta_state = &global_ta_state[clause*COMPONENTS*TA_CHUNKS_PER_LEAF*STATE_BITS + ta_chunk_base*STATE_BITS];
429
+
350
430
  for (unsigned long long class_id = 0; class_id < number_of_outputs; ++class_id) {
351
431
  int local_class_sum = class_sum[class_id];
352
432
  if (local_class_sum > THRESHOLD) {
@@ -355,7 +435,7 @@ code_update = """
355
435
  local_class_sum = -THRESHOLD;
356
436
  }
357
437
 
358
- update_component_hierarchy(&localState, number_of_outputs, &clause_weights[class_id*CLAUSES + clause], ta_state, component_output[clause_component], &Xi[ta_chunk_base], y[example*number_of_outputs + class_id], local_class_sum);
438
+ update_component_hierarchy(&localState, number_of_outputs, &clause_weights[class_id*CLAUSES + clause], ta_state, component_output[clause_component], &Xi[feature_chunk_base], y[example*number_of_outputs + class_id], local_class_sum);
359
439
  }
360
440
  }
361
441
 
@@ -28,6 +28,7 @@ import pycuda.driver as cuda
28
28
  import pycuda.autoinit
29
29
  from pycuda.compiler import SourceModule
30
30
  from pycuda import gpuarray
31
+ import sys
31
32
 
32
33
  from time import time
33
34
 
@@ -64,11 +65,13 @@ class CommonTsetlinMachine():
64
65
 
65
66
  # Represents hierarchy structure for transfer to GPU
66
67
  self.hierarchy_structure_factors = [0] * (self.depth - 1)
67
- self.hierarchy_structure_alternatives = [0] * (self.depth - 1)
68
+ self.hierarchy_structure_type = [0] * (self.depth - 1)
68
69
  for d in range(1, self.depth):
69
70
  self.hierarchy_structure_factors[d-1] = self.hierarchy_structure[d][1]
70
71
  if self.hierarchy_structure[d][0] == OR_ALTERNATIVES:
71
- self.hierarchy_structure_alternatives[d-1] = 1
72
+ self.hierarchy_structure_type[d-1] = 1
73
+ elif self.hierarchy_structure[d][0] == OR_GROUP:
74
+ self.hierarchy_structure_type[d-1] = 2
72
75
 
73
76
  # Calculates total number of features spanned by the hierarchy
74
77
  self.number_of_features_hierarchy = 1
@@ -147,6 +150,9 @@ class CommonTsetlinMachine():
147
150
  self.propagate_and_group_false_truth_values = mod_update.get_function("propagate_and_group_false_truth_values")
148
151
  self.propagate_and_group_false_truth_values.prepare("PPii")
149
152
 
153
+ self.propagate_or_group_false_truth_values = mod_update.get_function("propagate_or_group_false_truth_values")
154
+ self.propagate_or_group_false_truth_values.prepare("PPPii")
155
+
150
156
  self.evaluate_or_groups = mod_update.get_function("evaluate_or_groups")
151
157
  self.evaluate_or_groups.prepare("PPii")
152
158
 
@@ -189,8 +195,8 @@ class CommonTsetlinMachine():
189
195
  cuda.memcpy_htod(self.hierarchy_structure_factors_gpu, np.array(self.hierarchy_structure_factors, dtype=np.int32))
190
196
 
191
197
  # GPU memory for storing hierarchy structure
192
- self.hierarchy_structure_alternatives_gpu = cuda.mem_alloc((self.depth-1)*4)
193
- cuda.memcpy_htod(self.hierarchy_structure_alternatives_gpu, np.array(self.hierarchy_structure_alternatives, dtype=np.int32))
198
+ self.hierarchy_structure_type_gpu = cuda.mem_alloc((self.depth-1)*4)
199
+ cuda.memcpy_htod(self.hierarchy_structure_type_gpu, np.array(self.hierarchy_structure_type, dtype=np.int32))
194
200
 
195
201
  # GPU memory for storing Tsetlin Automata states
196
202
  self.ta_state_hierarchy_gpu = cuda.mem_alloc(self.number_of_clauses*self.hierarchy_size[0]*self.number_of_state_bits*4)
@@ -266,7 +272,7 @@ class CommonTsetlinMachine():
266
272
  self.hierarchy_votes[0],
267
273
  self.depth,
268
274
  self.hierarchy_structure_factors_gpu,
269
- self.hierarchy_structure_alternatives_gpu,
275
+ self.hierarchy_structure_type_gpu,
270
276
  encoded_X_hierarchy,
271
277
  np.int32(e)
272
278
  )
@@ -320,6 +326,10 @@ class CommonTsetlinMachine():
320
326
  cuda.Context.synchronize()
321
327
 
322
328
  def _fit(self, X, encoded_Y, epochs=100, incremental=False):
329
+ if self.number_of_features_hierarchy != X.shape[1]:
330
+ print("The number of features spanned by hierarchy does not align with the input data.")
331
+ sys.exit(-1)
332
+
323
333
  number_of_examples = X.shape[0]
324
334
 
325
335
  if self.first:
@@ -347,15 +357,27 @@ class CommonTsetlinMachine():
347
357
  # Propagates the root value and any intermittent node values back to the leaves.
348
358
  # The purpose is to determine which leaves only has True nodes on the path from leaf to root.
349
359
  for d in range(self.depth-1, 0, -1):
350
- self.propagate_and_group_false_truth_values.prepared_call(
351
- self.grid,
352
- self.block,
353
- self.hierarchy_votes[d-1],
354
- self.hierarchy_votes[d],
355
- self.hierarchy_size[d + 1],
356
- self.hierarchy_structure[d][1]
357
- )
358
- cuda.Context.synchronize()
360
+ if self.hierarchy_structure[d][0] != OR_GROUP:
361
+ self.propagate_and_group_false_truth_values.prepared_call(
362
+ self.grid,
363
+ self.block,
364
+ self.hierarchy_votes[d-1],
365
+ self.hierarchy_votes[d],
366
+ self.hierarchy_size[d + 1],
367
+ self.hierarchy_structure[d][1]
368
+ )
369
+ cuda.Context.synchronize()
370
+ else:
371
+ self.propagate_or_group_false_truth_values.prepared_call(
372
+ self.grid,
373
+ self.block,
374
+ self.cuda_rng.state,
375
+ self.hierarchy_votes[d-1],
376
+ self.hierarchy_votes[d],
377
+ self.hierarchy_size[d + 1],
378
+ self.hierarchy_structure[d][1]
379
+ )
380
+ cuda.Context.synchronize()
359
381
 
360
382
  # Updates the clause components (leaves) based on the propagated truth values
361
383
  self.update_hierarchy.prepared_call(
@@ -368,7 +390,7 @@ class CommonTsetlinMachine():
368
390
  self.hierarchy_votes[0],
369
391
  self.depth,
370
392
  self.hierarchy_structure_factors_gpu,
371
- self.hierarchy_structure_alternatives_gpu,
393
+ self.hierarchy_structure_type_gpu,
372
394
  self.class_sum_gpu,
373
395
  encoded_X_hierarchy_training_gpu,
374
396
  Y_gpu,
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: PyHierarchicalTsetlinMachineCUDA
3
- Version: 0.2.5
3
+ Version: 0.2.6
4
4
  Summary: Hierarchical Tsetlin Machine Architecture.
5
5
  Home-page: https://github.com/cair/PyHierarchicalTsetlinMachineCUDA/
6
6
  Author: Ole-Christoffer Granmo
@@ -149,11 +149,11 @@ python3 ./examples/HexDemo.py
149
149
 
150
150
  Accuracy over 1000 epochs:
151
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
152
+ #1 Training Accuracy: 97.98% Testing Accuracy: 97.46% Training Time: 37.02s Testing Time: 5.74s
153
+ #2 Training Accuracy: 99.19% Testing Accuracy: 98.70% Training Time: 34.44s Testing Time: 5.76s
154
+ #3 Training Accuracy: 99.49% Testing Accuracy: 98.92% Training Time: 33.60s Testing Time: 5.75s
155
+ #4 Training Accuracy: 99.65% Testing Accuracy: 99.05% Training Time: 33.15s Testing Time: 5.78s
156
+ #5 Training Accuracy: 99.73% Testing Accuracy: 99.10% Training Time: 32.86s Testing Time: 5.76s
157
157
  ...
158
158
  #446 Training Accuracy: 100.00% Testing Accuracy: 99.45% Training Time: 32.09s Testing Time: 6.09s
159
159
  #447 Training Accuracy: 100.00% Testing Accuracy: 99.46% Training Time: 32.08s Testing Time: 6.09s
@@ -2,7 +2,7 @@ from setuptools import *
2
2
 
3
3
  setup(
4
4
  name='PyHierarchicalTsetlinMachineCUDA',
5
- version='0.2.5',
5
+ version='0.2.6',
6
6
  author='Ole-Christoffer Granmo',
7
7
  author_email='ole.granmo@uia.no',
8
8
  url='https://github.com/cair/PyHierarchicalTsetlinMachineCUDA/',
@@ -16,6 +16,7 @@ setup(
16
16
  'pycuda',
17
17
  'scipy',
18
18
  'scikit-learn',
19
+ 'scikit-image',
19
20
  'networkx',
20
21
  ],
21
22
  extras_require={