pyhierarchicaltsetlinmachinecuda 0.2.0__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.0/LICENSE +21 -0
- pyhierarchicaltsetlinmachinecuda-0.2.0/PKG-INFO +12 -0
- pyhierarchicaltsetlinmachinecuda-0.2.0/PyHierarchicalTsetlinMachineCUDA/__init__.py +1 -0
- pyhierarchicaltsetlinmachinecuda-0.2.0/PyHierarchicalTsetlinMachineCUDA/graph.py +133 -0
- pyhierarchicaltsetlinmachinecuda-0.2.0/PyHierarchicalTsetlinMachineCUDA/kernels.py +510 -0
- pyhierarchicaltsetlinmachinecuda-0.2.0/PyHierarchicalTsetlinMachineCUDA/tm.py +502 -0
- pyhierarchicaltsetlinmachinecuda-0.2.0/PyHierarchicalTsetlinMachineCUDA/tools.py +43 -0
- pyhierarchicaltsetlinmachinecuda-0.2.0/README.md +33 -0
- pyhierarchicaltsetlinmachinecuda-0.2.0/pyhierarchicaltsetlinmachinecuda.egg-info/PKG-INFO +12 -0
- pyhierarchicaltsetlinmachinecuda-0.2.0/pyhierarchicaltsetlinmachinecuda.egg-info/SOURCES.txt +12 -0
- pyhierarchicaltsetlinmachinecuda-0.2.0/pyhierarchicaltsetlinmachinecuda.egg-info/dependency_links.txt +1 -0
- pyhierarchicaltsetlinmachinecuda-0.2.0/pyhierarchicaltsetlinmachinecuda.egg-info/top_level.txt +1 -0
- pyhierarchicaltsetlinmachinecuda-0.2.0/setup.cfg +4 -0
- pyhierarchicaltsetlinmachinecuda-0.2.0/setup.py +14 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ole-Christoffer Granmo and the University of Agder
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: pyhierarchicaltsetlinmachinecuda
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Hierarchical Tsetlin Machine Architecture.
|
|
5
|
+
Home-page: https://github.com/cair/PyHierarchicalTsetlinMachineCUDA/
|
|
6
|
+
Author: Ole-Christoffer Granmo
|
|
7
|
+
Author-email: ole.granmo@uia.no
|
|
8
|
+
License: MIT
|
|
9
|
+
Keywords: pattern-recognition cuda machine-learning interpretable-machine-learning rule-based-machine-learning propositional-logic tsetlin-machine regression convolution classification multi-layer
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
|
|
12
|
+
Hierarchical Tsetlin Machine Architecture.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# Copyright (c) 2024 Ole-Christoffer Granmo
|
|
2
|
+
|
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
|
8
|
+
# furnished to do so, subject to the following conditions:
|
|
9
|
+
|
|
10
|
+
# The above copyright notice and this permission notice shall be included in all
|
|
11
|
+
# copies or substantial portions of the Software.
|
|
12
|
+
|
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
19
|
+
# SOFTWARE.
|
|
20
|
+
|
|
21
|
+
import numpy as np
|
|
22
|
+
from scipy.sparse import coo_matrix
|
|
23
|
+
|
|
24
|
+
class Graph():
|
|
25
|
+
def __init__(self):
|
|
26
|
+
self.node_name_id = {}
|
|
27
|
+
self.node_id_name = {}
|
|
28
|
+
self.node_edges = {}
|
|
29
|
+
self.node_features = {}
|
|
30
|
+
self.edge_counter = 0
|
|
31
|
+
self.feature_counter = 0
|
|
32
|
+
|
|
33
|
+
def add_node(self, node_name):
|
|
34
|
+
self.node_name_id[node_name] = len(self.node_name_id)
|
|
35
|
+
self.node_id_name[self.node_name_id[node_name]] = node_name
|
|
36
|
+
self.node_features[node_name] = []
|
|
37
|
+
self.node_edges[node_name] = []
|
|
38
|
+
|
|
39
|
+
def add_edge(self, node_name_1, node_name_2, edge_type=0):
|
|
40
|
+
self.node_edges[node_name_1].append((node_name_2, edge_type))
|
|
41
|
+
self.edge_counter += 1
|
|
42
|
+
|
|
43
|
+
def add_feature(self, node_name, symbols):
|
|
44
|
+
if type(symbols) == tuple:
|
|
45
|
+
symbols = " ".join(symbols)
|
|
46
|
+
self.node_features[node_name].append(symbols)
|
|
47
|
+
self.feature_counter += 1
|
|
48
|
+
|
|
49
|
+
class Graphs():
|
|
50
|
+
def __init__(self):
|
|
51
|
+
self.edge_type_id = {}
|
|
52
|
+
self.graphs = []
|
|
53
|
+
self.encoded = False
|
|
54
|
+
|
|
55
|
+
def add(self, graph):
|
|
56
|
+
self.graphs.append(graph)
|
|
57
|
+
|
|
58
|
+
def encode(self, hypervectors = {}, hypervector_size=1024, hypervector_bits=3):
|
|
59
|
+
self.hypervectors = hypervectors
|
|
60
|
+
|
|
61
|
+
self.hypervector_size = hypervector_size
|
|
62
|
+
self.hypervector_bits = hypervector_bits
|
|
63
|
+
|
|
64
|
+
global_feature_counter = 0
|
|
65
|
+
global_edge_counter = 0
|
|
66
|
+
for graph in self.graphs:
|
|
67
|
+
global_feature_counter += graph.feature_counter * self.hypervector_bits
|
|
68
|
+
global_edge_counter += len(graph.node_name_id) + graph.edge_counter*2
|
|
69
|
+
|
|
70
|
+
indexes = np.arange(self.hypervector_size, dtype=np.uint32)
|
|
71
|
+
|
|
72
|
+
feature_row = np.empty(global_feature_counter, dtype=np.uint32)
|
|
73
|
+
feature_col = np.empty(global_feature_counter, dtype=np.uint32)
|
|
74
|
+
feature_data = np.empty(global_feature_counter, dtype=np.uint32)
|
|
75
|
+
|
|
76
|
+
edge_row = np.empty(global_edge_counter, dtype=np.uint32)
|
|
77
|
+
edge_col = np.empty(global_edge_counter, dtype=np.uint32)
|
|
78
|
+
edge_data = np.empty(global_edge_counter, dtype=np.uint32)
|
|
79
|
+
|
|
80
|
+
self.node_count = np.empty(len(self.graphs), dtype=np.uint32)
|
|
81
|
+
|
|
82
|
+
feature_position = 0
|
|
83
|
+
edge_position = 0
|
|
84
|
+
for i in range(len(self.graphs)):
|
|
85
|
+
graph = self.graphs[i]
|
|
86
|
+
self.node_count[i] = len(graph.node_name_id)
|
|
87
|
+
local_edge_position = 0
|
|
88
|
+
for j in range(len(graph.node_name_id)):
|
|
89
|
+
node_name = graph.node_id_name[j]
|
|
90
|
+
|
|
91
|
+
for symbols in graph.node_features[node_name]:
|
|
92
|
+
if symbols not in self.hypervectors:
|
|
93
|
+
self.hypervectors[symbols] = np.random.choice(indexes, size=(self.hypervector_bits), replace=False)
|
|
94
|
+
|
|
95
|
+
for k in range(self.hypervector_bits):
|
|
96
|
+
feature_row[feature_position] = i
|
|
97
|
+
feature_col[feature_position] = self.hypervectors[symbols][k] + j*self.hypervector_size
|
|
98
|
+
feature_data[feature_position] = 1
|
|
99
|
+
feature_position += 1
|
|
100
|
+
|
|
101
|
+
edge_row[edge_position] = i
|
|
102
|
+
edge_col[edge_position] = local_edge_position
|
|
103
|
+
edge_data[edge_position] = len(graph.node_edges[node_name])
|
|
104
|
+
edge_position += 1
|
|
105
|
+
local_edge_position += 1
|
|
106
|
+
for edge in graph.node_edges[graph.node_id_name[j]]:
|
|
107
|
+
edge_row[edge_position] = i
|
|
108
|
+
edge_col[edge_position] = local_edge_position
|
|
109
|
+
edge_data[edge_position] = graph.node_name_id[edge[0]]
|
|
110
|
+
edge_position += 1
|
|
111
|
+
local_edge_position += 1
|
|
112
|
+
|
|
113
|
+
edge_row[edge_position] = i
|
|
114
|
+
edge_col[edge_position] = local_edge_position
|
|
115
|
+
|
|
116
|
+
if edge[1] not in self.edge_type_id:
|
|
117
|
+
self.edge_type_id[edge[1]] = len(self.edge_type_id)
|
|
118
|
+
edge_data[edge_position] = self.edge_type_id[edge[1]]
|
|
119
|
+
edge_position += 1
|
|
120
|
+
local_edge_position += 1
|
|
121
|
+
|
|
122
|
+
self.X = coo_matrix((feature_data, (feature_row, feature_col))).tocsr()
|
|
123
|
+
self.X.sum_duplicates()
|
|
124
|
+
|
|
125
|
+
self.edges = coo_matrix((edge_data, (edge_row, edge_col))).tocsr()
|
|
126
|
+
|
|
127
|
+
self.max_node_count = self.node_count.max()
|
|
128
|
+
|
|
129
|
+
self.signature = np.concatenate((self.X.indptr, self.X.indices, self.edges.indptr, self.edges.indices, self.edges.data))
|
|
130
|
+
|
|
131
|
+
self.encoded = True
|
|
132
|
+
|
|
133
|
+
return
|
|
@@ -0,0 +1,510 @@
|
|
|
1
|
+
# Copyright (c) 2026 Ole-Christoffer Granmo and the University of Agder
|
|
2
|
+
|
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
|
8
|
+
# furnished to do so, subject to the following conditions:
|
|
9
|
+
|
|
10
|
+
# The above copyright notice and this permission notice shall be included in all
|
|
11
|
+
# copies or substantial portions of the Software.
|
|
12
|
+
|
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
19
|
+
# SOFTWARE.
|
|
20
|
+
|
|
21
|
+
# This code implements the Convolutional Tsetlin Machine from paper arXiv:1905.09688
|
|
22
|
+
# https://arxiv.org/abs/1905.09688
|
|
23
|
+
|
|
24
|
+
code_header = """
|
|
25
|
+
#include <curand_kernel.h>
|
|
26
|
+
|
|
27
|
+
#define INT_SIZE 32ULL
|
|
28
|
+
|
|
29
|
+
#define LITERAL_LEAVES (LITERAL_CHUNKS / TA_CHUNKS_PER_LEAF)
|
|
30
|
+
|
|
31
|
+
#if (LITERALS_PER_LEAF % 32 != 0)
|
|
32
|
+
#define FILTER_HIERARCHICAL (~(0xffffffff << (LITERALS_PER_LEAF % INT_SIZE)))
|
|
33
|
+
#else
|
|
34
|
+
#define FILTER_HIERARCHICAL 0xffffffff
|
|
35
|
+
#endif
|
|
36
|
+
|
|
37
|
+
#define VANILLA_TM 0
|
|
38
|
+
|
|
39
|
+
#define WEIGHTED_TM 1
|
|
40
|
+
|
|
41
|
+
#define COALESCED_TM 2
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
code_update = """
|
|
45
|
+
extern "C"
|
|
46
|
+
{
|
|
47
|
+
// Increment the states of each of those 32 Tsetlin Automata flagged in the active bit vector.
|
|
48
|
+
__device__ inline void inc(unsigned int *ta_state, int chunk, unsigned int active)
|
|
49
|
+
{
|
|
50
|
+
unsigned int carry, carry_next;
|
|
51
|
+
int id = chunk*STATE_BITS;
|
|
52
|
+
carry = active;
|
|
53
|
+
for (int b = 0; b < STATE_BITS; ++b) {
|
|
54
|
+
if (carry == 0)
|
|
55
|
+
break;
|
|
56
|
+
|
|
57
|
+
carry_next = ta_state[id + b] & carry; // Sets carry bits (overflow) passing on to next bit
|
|
58
|
+
ta_state[id + b] = ta_state[id + b] ^ carry; // Performs increments with XOR
|
|
59
|
+
carry = carry_next;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
if (carry > 0) {
|
|
63
|
+
for (int b = 0; b < STATE_BITS; ++b) {
|
|
64
|
+
ta_state[id + b] |= carry;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
// Decrement the states of each of those 32 Tsetlin Automata flagged in the active bit vector.
|
|
70
|
+
__device__ inline void dec(unsigned int *ta_state, int chunk, unsigned int active)
|
|
71
|
+
{
|
|
72
|
+
unsigned int carry, carry_next;
|
|
73
|
+
int id = chunk*STATE_BITS;
|
|
74
|
+
carry = active;
|
|
75
|
+
for (int b = 0; b < STATE_BITS; ++b) {
|
|
76
|
+
if (carry == 0)
|
|
77
|
+
break;
|
|
78
|
+
carry_next = (~ta_state[id + b]) & carry; // Sets carry bits (overflow) passing on to next bit
|
|
79
|
+
ta_state[id + b] = ta_state[id + b] ^ carry; // Performs increments with XOR
|
|
80
|
+
carry = carry_next;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (carry > 0) {
|
|
84
|
+
for (int b = 0; b < STATE_BITS; ++b) {
|
|
85
|
+
ta_state[id + b] &= ~carry;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
__device__ inline void update_clause_weight(curandState *localState, int tm_type, int number_of_outputs, int *clause_weight, int clause_output, int y, int class_sum)
|
|
91
|
+
{
|
|
92
|
+
int target = 1 - 2*(class_sum > y);
|
|
93
|
+
|
|
94
|
+
if (target == -1 && curand_uniform(localState) > 1.0*Q/max(1, number_of_outputs-1)) {
|
|
95
|
+
return;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
int sign = (*clause_weight >= 0) - (*clause_weight < 0);
|
|
99
|
+
|
|
100
|
+
int absolute_prediction_error = abs(y - class_sum);
|
|
101
|
+
if (curand_uniform(localState) <= 1.0*absolute_prediction_error/(2*THRESHOLD)) {
|
|
102
|
+
if (target*sign > 0) {
|
|
103
|
+
if (clause_output && abs(*clause_weight) < INT_MAX) {
|
|
104
|
+
(*clause_weight) += sign;
|
|
105
|
+
}
|
|
106
|
+
} else if (target*sign < 0 && clause_output) {
|
|
107
|
+
// Type II Feedback
|
|
108
|
+
|
|
109
|
+
if (*clause_weight < -1 || *clause_weight > 1 || tm_type == COALESCED_TM) {
|
|
110
|
+
(*clause_weight) -= sign;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
#if NEGATIVE_CLAUSES == 0
|
|
114
|
+
if (*clause_weight < 1) {
|
|
115
|
+
*clause_weight = 1;
|
|
116
|
+
}
|
|
117
|
+
#endif
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
__device__ inline void update_component_hierarchy(curandState *localState, int number_of_outputs, int *clause_weight, unsigned int *ta_state, int component_output, int *X, int y, int class_sum)
|
|
123
|
+
{
|
|
124
|
+
int target = 1 - 2*(class_sum > y);
|
|
125
|
+
|
|
126
|
+
if (target == -1 && curand_uniform(localState) > 1.0*Q/max(1, number_of_outputs-1)) {
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
int sign = (*clause_weight >= 0) - (*clause_weight < 0);
|
|
132
|
+
|
|
133
|
+
int absolute_prediction_error = abs(y - class_sum);
|
|
134
|
+
if (curand_uniform(localState) <= 1.0*absolute_prediction_error/(2*THRESHOLD)) {
|
|
135
|
+
if (target*sign > 0) {
|
|
136
|
+
// Type I Feedback
|
|
137
|
+
for (int ta_chunk = 0; ta_chunk < TA_CHUNKS_PER_LEAF; ++ta_chunk) {
|
|
138
|
+
// Generate random bit values
|
|
139
|
+
unsigned int la_feedback = 0;
|
|
140
|
+
for (int b = 0; b < INT_SIZE; ++b) {
|
|
141
|
+
if (curand_uniform(localState) <= 1.0/S) {
|
|
142
|
+
la_feedback |= (1 << b);
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (component_output) {
|
|
147
|
+
#if BOOST_TRUE_POSITIVE_FEEDBACK == 1
|
|
148
|
+
inc(ta_state, ta_chunk, X[ta_chunk]);
|
|
149
|
+
#else
|
|
150
|
+
inc(ta_state, ta_chunk, X[ta_chunk] & (~la_feedback));
|
|
151
|
+
#endif
|
|
152
|
+
|
|
153
|
+
dec(ta_state, ta_chunk, (~X[ta_chunk]) & la_feedback);
|
|
154
|
+
} else {
|
|
155
|
+
dec(ta_state, ta_chunk, la_feedback);
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
} else if (target*sign < 0 && component_output) {
|
|
159
|
+
// Type II Feedback
|
|
160
|
+
|
|
161
|
+
for (int ta_chunk = 0; ta_chunk < TA_CHUNKS_PER_LEAF; ++ta_chunk) {
|
|
162
|
+
inc(ta_state, ta_chunk, (~X[ta_chunk]) & (~ta_state[ta_chunk*STATE_BITS + STATE_BITS - 1]));
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
// Evaluate example
|
|
169
|
+
__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)
|
|
170
|
+
{
|
|
171
|
+
int index = blockIdx.x * blockDim.x + threadIdx.x;
|
|
172
|
+
int stride = blockDim.x * gridDim.x;
|
|
173
|
+
|
|
174
|
+
int *Xi = &X[(unsigned long long)example*LITERAL_CHUNKS];
|
|
175
|
+
|
|
176
|
+
// Evaluate each clause component (leaf) in separate threads
|
|
177
|
+
for (int clause_component = index; clause_component < CLAUSES*COMPONENTS; clause_component += stride) {
|
|
178
|
+
int clause = clause_component / COMPONENTS;
|
|
179
|
+
int component = clause_component % COMPONENTS;
|
|
180
|
+
|
|
181
|
+
// Get state of current clause component
|
|
182
|
+
unsigned int *ta_state = &global_ta_state[clause_component*TA_CHUNKS_PER_LEAF*STATE_BITS];
|
|
183
|
+
|
|
184
|
+
int component_remainder = component;
|
|
185
|
+
int ta_chunk_base = 0;
|
|
186
|
+
int size = 1;
|
|
187
|
+
for (int d = 0; d < depth-1; ++d) {
|
|
188
|
+
int depth_d_node_index = component_remainder % hierarchy_structure_factors[d];
|
|
189
|
+
component_remainder = component_remainder / hierarchy_structure_factors[d];
|
|
190
|
+
|
|
191
|
+
if (hierarchy_structure_alternatives[d] == 0) {
|
|
192
|
+
ta_chunk_base += size * depth_d_node_index * TA_CHUNKS_PER_LEAF;
|
|
193
|
+
size *= hierarchy_structure_factors[d];
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if (clause == -1 && d == depth-2) {
|
|
197
|
+
printf("%d: %d (%d) (%d = %d) %d\\n", d, component, depth_d_node_index, ta_chunk_base, (component % (LITERAL_CHUNKS / TA_CHUNKS_PER_LEAF))*TA_CHUNKS_PER_LEAF, component_remainder);
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Evaluate clause component
|
|
202
|
+
int component_output = 1;
|
|
203
|
+
for (int ta_chunk = 0; ta_chunk < TA_CHUNKS_PER_LEAF-1; ++ta_chunk) {
|
|
204
|
+
// Compare the TA state of the component (leaf) against the corresponding part of the feature vector
|
|
205
|
+
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]) {
|
|
206
|
+
component_output = 0;
|
|
207
|
+
break;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
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)) {
|
|
212
|
+
component_output = 0;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
global_component_output[clause_component] = component_output;
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
__global__ void evaluate_or_groups(int *child_input, int *or_group_node_output, int number_of_or_group_nodes, int number_of_or_group_addends)
|
|
220
|
+
{
|
|
221
|
+
int index = blockIdx.x * blockDim.x + threadIdx.x;
|
|
222
|
+
int stride = blockDim.x * gridDim.x;
|
|
223
|
+
|
|
224
|
+
// Add up the votes of each OR node child
|
|
225
|
+
for (int or_group_node = index; or_group_node < CLAUSES*number_of_or_group_nodes; or_group_node += stride) {
|
|
226
|
+
// Add OR addends
|
|
227
|
+
int or_group_vote_sum = 0;
|
|
228
|
+
for (int or_addend = 0; or_addend < number_of_or_group_addends; ++or_addend) {
|
|
229
|
+
// Aggregate votes from each child node through addition
|
|
230
|
+
or_group_vote_sum += child_input[or_group_node*number_of_or_group_addends + or_addend];
|
|
231
|
+
//if (child_input[or_group_node*number_of_or_group_addends + or_addend]) {
|
|
232
|
+
// or_group_vote_sum = 1;
|
|
233
|
+
//}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
// Store or group vote sum as node output
|
|
237
|
+
or_group_node_output[or_group_node] = or_group_vote_sum;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
__global__ void evaluate_and_groups(int *child_input, int *and_group_node_output, int number_of_and_group_nodes, int number_of_and_group_factors)
|
|
242
|
+
{
|
|
243
|
+
int index = blockIdx.x * blockDim.x + threadIdx.x;
|
|
244
|
+
int stride = blockDim.x * gridDim.x;
|
|
245
|
+
|
|
246
|
+
// Multiply the votes from the children of each AND node
|
|
247
|
+
for (int and_group_node = index; and_group_node < CLAUSES*number_of_and_group_nodes; and_group_node += stride) {
|
|
248
|
+
// Multiply and factors
|
|
249
|
+
int and_group_vote_product = 1;
|
|
250
|
+
for (int and_factor = 0; and_factor < number_of_and_group_factors; ++and_factor) {
|
|
251
|
+
// Aggregate votes from each child node through multiplication
|
|
252
|
+
and_group_vote_product *= child_input[and_group_node*number_of_and_group_factors + and_factor];
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
// Store and group product as node output
|
|
256
|
+
and_group_node_output[and_group_node] = and_group_vote_product;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
__global__ void propagate_and_group_false_truth_values(int *child_input, int *group_node_output, int number_of_group_nodes, int number_of_group_node_children)
|
|
261
|
+
{
|
|
262
|
+
int index = blockIdx.x * blockDim.x + threadIdx.x;
|
|
263
|
+
int stride = blockDim.x * gridDim.x;
|
|
264
|
+
|
|
265
|
+
// If a group node is false, all children are made false.
|
|
266
|
+
for (int group_node = index; group_node < CLAUSES*number_of_group_nodes; group_node += stride) {
|
|
267
|
+
if (group_node_output[group_node] == 0) {
|
|
268
|
+
for (int and_factor = 0; and_factor < number_of_group_node_children; ++and_factor) {
|
|
269
|
+
if (child_input[group_node*number_of_group_node_children + and_factor] > 0) {
|
|
270
|
+
child_input[group_node*number_of_group_node_children + and_factor] = 0;
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
__global__ void evaluate_or_alternatives(int *child_input, int *or_alternatives_node_output, int number_of_or_alternatives_nodes, int number_of_or_alternatives)
|
|
278
|
+
{
|
|
279
|
+
int index = blockIdx.x * blockDim.x + threadIdx.x;
|
|
280
|
+
int stride = blockDim.x * gridDim.x;
|
|
281
|
+
|
|
282
|
+
// Add up the votes from the children of each OR node
|
|
283
|
+
for (int or_alternatives_node = index; or_alternatives_node < CLAUSES*number_of_or_alternatives_nodes; or_alternatives_node += stride) {
|
|
284
|
+
// Sum up votes from each or alternative
|
|
285
|
+
int or_alternatives_vote_sum = 0;
|
|
286
|
+
for (int or_alternative = 0; or_alternative < number_of_or_alternatives; ++or_alternative) {
|
|
287
|
+
// Aggregate same input or alternatives through summation
|
|
288
|
+
or_alternatives_vote_sum += child_input[or_alternatives_node * number_of_or_alternatives + or_alternative];
|
|
289
|
+
|
|
290
|
+
//if (child_input[or_alternatives_node * number_of_or_alternatives + or_alternative]) {
|
|
291
|
+
// or_alternatives_vote_sum = 1;
|
|
292
|
+
//}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// Store vote sum as node output
|
|
296
|
+
or_alternatives_node_output[or_alternatives_node] = or_alternatives_vote_sum;
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
__global__ void evaluate_final(int number_of_outputs, int *child_input, int *clause_weights, int *class_sum)
|
|
301
|
+
{
|
|
302
|
+
int index = blockIdx.x * blockDim.x + threadIdx.x;
|
|
303
|
+
int stride = blockDim.x * gridDim.x;
|
|
304
|
+
|
|
305
|
+
// Add up the votes from each clause
|
|
306
|
+
for (int clause = index; clause < CLAUSES; clause += stride) {
|
|
307
|
+
if (child_input[clause]) {
|
|
308
|
+
for (int class_id = 0; class_id < number_of_outputs; ++class_id) {
|
|
309
|
+
int clause_weight = clause_weights[class_id*CLAUSES + clause];
|
|
310
|
+
atomicAdd(&class_sum[class_id], clause_weight * child_input[clause]);
|
|
311
|
+
}
|
|
312
|
+
}
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
// Update state of Tsetlin Automata team
|
|
317
|
+
__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)
|
|
318
|
+
{
|
|
319
|
+
int index = blockIdx.x * blockDim.x + threadIdx.x;
|
|
320
|
+
int stride = blockDim.x * gridDim.x;
|
|
321
|
+
|
|
322
|
+
/* Copy state to local memory for efficiency */
|
|
323
|
+
curandState localState = state[index];
|
|
324
|
+
|
|
325
|
+
int *Xi = &X[(unsigned long long)example*LITERAL_CHUNKS];
|
|
326
|
+
|
|
327
|
+
// Calculate clause output first
|
|
328
|
+
for (int clause_component = index; clause_component < CLAUSES*COMPONENTS; clause_component += stride) {
|
|
329
|
+
int clause = clause_component / COMPONENTS;
|
|
330
|
+
int component = clause_component % COMPONENTS;
|
|
331
|
+
|
|
332
|
+
// Get state of current clause component
|
|
333
|
+
unsigned int *ta_state = &global_ta_state[clause_component*TA_CHUNKS_PER_LEAF*STATE_BITS];
|
|
334
|
+
|
|
335
|
+
int component_remainder = component;
|
|
336
|
+
int ta_chunk_base = 0;
|
|
337
|
+
int size = 1;
|
|
338
|
+
for (int d = 0; d < depth-1; ++d) {
|
|
339
|
+
int depth_d_node_index = component_remainder % hierarchy_structure_factors[d];
|
|
340
|
+
component_remainder = component_remainder / hierarchy_structure_factors[d];
|
|
341
|
+
|
|
342
|
+
if (hierarchy_structure_alternatives[d] == 0) {
|
|
343
|
+
ta_chunk_base += size * depth_d_node_index * TA_CHUNKS_PER_LEAF;
|
|
344
|
+
size *= hierarchy_structure_factors[d];
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
for (unsigned long long class_id = 0; class_id < number_of_outputs; ++class_id) {
|
|
349
|
+
int local_class_sum = class_sum[class_id];
|
|
350
|
+
if (local_class_sum > THRESHOLD) {
|
|
351
|
+
local_class_sum = THRESHOLD;
|
|
352
|
+
} else if (local_class_sum < -THRESHOLD) {
|
|
353
|
+
local_class_sum = -THRESHOLD;
|
|
354
|
+
}
|
|
355
|
+
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);
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
state[index] = localState;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// Update state of Tsetlin Automata team
|
|
363
|
+
__global__ void update_weights(curandState *state, int tm_type, int number_of_outputs, int *clause_weights, int *clause_output, int *class_sum, int *y, int example)
|
|
364
|
+
{
|
|
365
|
+
int index = blockIdx.x * blockDim.x + threadIdx.x;
|
|
366
|
+
int stride = blockDim.x * gridDim.x;
|
|
367
|
+
|
|
368
|
+
/* Copy state to local memory for efficiency */
|
|
369
|
+
curandState localState = state[index];
|
|
370
|
+
|
|
371
|
+
for (unsigned long long clause = index; clause < CLAUSES; clause += stride) {
|
|
372
|
+
for (unsigned long long class_id = 0; class_id < number_of_outputs; ++class_id) {
|
|
373
|
+
int local_class_sum = class_sum[class_id];
|
|
374
|
+
if (local_class_sum > THRESHOLD) {
|
|
375
|
+
local_class_sum = THRESHOLD;
|
|
376
|
+
} else if (local_class_sum < -THRESHOLD) {
|
|
377
|
+
local_class_sum = -THRESHOLD;
|
|
378
|
+
}
|
|
379
|
+
update_clause_weight(&localState, tm_type, number_of_outputs, &clause_weights[class_id*CLAUSES + clause], clause_output[clause], y[example*number_of_outputs + class_id], local_class_sum);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
state[index] = localState;
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
"""
|
|
387
|
+
|
|
388
|
+
code_prepare = """
|
|
389
|
+
extern "C"
|
|
390
|
+
{
|
|
391
|
+
__global__ void prepare_weights(curandState *state, int tm_type, int number_of_outputs, int *clause_weights, int *class_sum)
|
|
392
|
+
{
|
|
393
|
+
int index = blockIdx.x * blockDim.x + threadIdx.x;
|
|
394
|
+
int stride = blockDim.x * gridDim.x;
|
|
395
|
+
|
|
396
|
+
curandState localState = state[index];
|
|
397
|
+
|
|
398
|
+
for (unsigned long long clause = index; clause < CLAUSES; clause += stride) {
|
|
399
|
+
for (unsigned long long class_id = 0; class_id < number_of_outputs; ++class_id) {
|
|
400
|
+
#if NEGATIVE_CLAUSES == 1
|
|
401
|
+
if (tm_type == COALESCED_TM) {
|
|
402
|
+
clause_weights[class_id*CLAUSES + clause] = 1 - 2 * (curand(&localState) % 2);
|
|
403
|
+
} else {
|
|
404
|
+
if (class_id*CLAUSES <= clause && clause < (class_id+1)*CLAUSES) {
|
|
405
|
+
clause_weights[class_id*CLAUSES + clause] = 1 - 2 * (clause % 2);
|
|
406
|
+
} else {
|
|
407
|
+
clause_weights[class_id*CLAUSES + clause] = 0;
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
#else
|
|
411
|
+
clause_weights[class_id*CLAUSES + clause] = 1;
|
|
412
|
+
#endif
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
state[index] = localState;
|
|
417
|
+
}
|
|
418
|
+
|
|
419
|
+
__global__ void prepare_hierarchy(curandState *state, int number_of_outputs, unsigned int *global_ta_state, int *clause_weights, int *class_sum)
|
|
420
|
+
{
|
|
421
|
+
int index = blockIdx.x * blockDim.x + threadIdx.x;
|
|
422
|
+
int stride = blockDim.x * gridDim.x;
|
|
423
|
+
|
|
424
|
+
curandState localState = state[index];
|
|
425
|
+
|
|
426
|
+
// Evaluate each clause component (leaf) in separate threads
|
|
427
|
+
for (int clause_component = index; clause_component < CLAUSES*COMPONENTS; clause_component += stride) {
|
|
428
|
+
// Get state of current clause component
|
|
429
|
+
unsigned int *ta_state = &global_ta_state[clause_component*TA_CHUNKS_PER_LEAF*STATE_BITS];
|
|
430
|
+
for (int ta_chunk = 0; ta_chunk < TA_CHUNKS_PER_LEAF; ++ta_chunk) {
|
|
431
|
+
for (int b = 0; b < STATE_BITS-1; ++b) {
|
|
432
|
+
ta_state[ta_chunk*STATE_BITS + b] = ~0;
|
|
433
|
+
}
|
|
434
|
+
ta_state[ta_chunk*STATE_BITS + STATE_BITS - 1] = 0;
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
for (unsigned long long clause = index; clause < CLAUSES; clause += stride) {
|
|
439
|
+
for (unsigned long long class_id = 0; class_id < number_of_outputs; ++class_id) {
|
|
440
|
+
#if NEGATIVE_CLAUSES == 1
|
|
441
|
+
clause_weights[class_id*CLAUSES + clause] = 1 - 2 * (curand(&localState) % 2);
|
|
442
|
+
#else
|
|
443
|
+
clause_weights[class_id*CLAUSES + clause] = 1;
|
|
444
|
+
#endif
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
state[index] = localState;
|
|
449
|
+
}
|
|
450
|
+
}
|
|
451
|
+
"""
|
|
452
|
+
|
|
453
|
+
code_encode = """
|
|
454
|
+
#include <curand_kernel.h>
|
|
455
|
+
|
|
456
|
+
extern "C"
|
|
457
|
+
{
|
|
458
|
+
__global__ void prepare_encode_hierarchy(unsigned int *X, unsigned int *encoded_X, int number_of_literal_chunks, int number_of_examples)
|
|
459
|
+
{
|
|
460
|
+
int index = blockIdx.x * blockDim.x + threadIdx.x;
|
|
461
|
+
int stride = blockDim.x * gridDim.x;
|
|
462
|
+
|
|
463
|
+
for (unsigned long long i = index; i < number_of_examples * number_of_literal_chunks; i += stride) {
|
|
464
|
+
encoded_X[i] = 0;
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
|
|
468
|
+
__global__ void encode_hierarchy(unsigned int *X, unsigned int *encoded_X, int number_of_features, int number_of_literal_chunks, int number_of_leaves, int number_of_features_per_leaf, int number_of_literal_chunks_per_leaf, int append_negated, int number_of_examples)
|
|
469
|
+
{
|
|
470
|
+
int index = blockIdx.x * blockDim.x + threadIdx.x;
|
|
471
|
+
int stride = blockDim.x * gridDim.x;
|
|
472
|
+
|
|
473
|
+
unsigned int *Xi;
|
|
474
|
+
unsigned int *encoded_Xi;
|
|
475
|
+
|
|
476
|
+
for (unsigned long long i = index; i < number_of_examples; i += stride) {
|
|
477
|
+
Xi = &X[i*number_of_features];
|
|
478
|
+
encoded_Xi = &encoded_X[i*number_of_literal_chunks];
|
|
479
|
+
|
|
480
|
+
for (int j = 0; j < number_of_features / number_of_features_per_leaf; ++j) {
|
|
481
|
+
for (int k = 0; k < number_of_features_per_leaf; ++k) {
|
|
482
|
+
if (Xi[j*number_of_features_per_leaf + k] == 1) {
|
|
483
|
+
int leaf_chunk_nr = k / 32;
|
|
484
|
+
int leaf_chunk_pos = k % 32;
|
|
485
|
+
encoded_Xi[j*number_of_literal_chunks_per_leaf + leaf_chunk_nr] |= (1 << leaf_chunk_pos);
|
|
486
|
+
} else if (append_negated) {
|
|
487
|
+
int leaf_chunk_nr = (k + number_of_features_per_leaf) / 32;
|
|
488
|
+
int leaf_chunk_pos = (k + number_of_features_per_leaf) % 32;
|
|
489
|
+
encoded_Xi[j*number_of_literal_chunks_per_leaf + leaf_chunk_nr] |= (1 << leaf_chunk_pos);
|
|
490
|
+
}
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
}
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
"""
|
|
497
|
+
|
|
498
|
+
code_transform = """
|
|
499
|
+
extern "C"
|
|
500
|
+
{
|
|
501
|
+
// Transform examples
|
|
502
|
+
__global__ void transform(unsigned int *global_ta_state, int *X, int *transformed_X)
|
|
503
|
+
{
|
|
504
|
+
int index = blockIdx.x * blockDim.x + threadIdx.x;
|
|
505
|
+
int stride = blockDim.x * gridDim.x;
|
|
506
|
+
|
|
507
|
+
// To be updated
|
|
508
|
+
}
|
|
509
|
+
}
|
|
510
|
+
"""
|
|
@@ -0,0 +1,502 @@
|
|
|
1
|
+
# Copyright (c) 2026 Ole-Christoffer Granmo and the University of Agder
|
|
2
|
+
|
|
3
|
+
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
4
|
+
# of this software and associated documentation files (the "Software"), to deal
|
|
5
|
+
# in the Software without restriction, including without limitation the rights
|
|
6
|
+
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
7
|
+
# copies of the Software, and to permit persons to whom the Software is
|
|
8
|
+
# furnished to do so, subject to the following conditions:
|
|
9
|
+
|
|
10
|
+
# The above copyright notice and this permission notice shall be included in all
|
|
11
|
+
# copies or substantial portions of the Software.
|
|
12
|
+
|
|
13
|
+
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
14
|
+
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
15
|
+
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
16
|
+
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
17
|
+
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
18
|
+
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
19
|
+
# SOFTWARE.
|
|
20
|
+
|
|
21
|
+
# This code implements the Convolutional Tsetlin Machine from paper arXiv:1905.09688
|
|
22
|
+
# https://arxiv.org/abs/1905.09688
|
|
23
|
+
|
|
24
|
+
import numpy as np
|
|
25
|
+
|
|
26
|
+
import PyHierarchicalTsetlinMachineCUDA.kernels as kernels
|
|
27
|
+
|
|
28
|
+
import pycuda.curandom as curandom
|
|
29
|
+
import pycuda.driver as cuda
|
|
30
|
+
import pycuda.autoinit
|
|
31
|
+
from pycuda.compiler import SourceModule
|
|
32
|
+
|
|
33
|
+
from time import time
|
|
34
|
+
|
|
35
|
+
OR_GROUP = "OR a group of children"
|
|
36
|
+
OR_ALTERNATIVES = "Create multiple OR alternatives for a sub hierarchy"
|
|
37
|
+
AND_GROUP = "AND a group of children"
|
|
38
|
+
|
|
39
|
+
VANILLA_TM = 0
|
|
40
|
+
WEIGHTED_TM = 1
|
|
41
|
+
COALESCED_TM = 2
|
|
42
|
+
|
|
43
|
+
g = curandom.XORWOWRandomNumberGenerator()
|
|
44
|
+
|
|
45
|
+
class CommonTsetlinMachine():
|
|
46
|
+
|
|
47
|
+
def __init__(self, number_of_clauses, T, s, tm_type=VANILLA_TM, 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)):
|
|
48
|
+
self.number_of_clauses = number_of_clauses
|
|
49
|
+
self.number_of_state_bits = number_of_state_bits
|
|
50
|
+
self.T = int(T)
|
|
51
|
+
self.s = s
|
|
52
|
+
self.tm_type = tm_type
|
|
53
|
+
self.q = q
|
|
54
|
+
self.hierarchy_structure = hierarchy_structure
|
|
55
|
+
self.depth = len(hierarchy_structure)
|
|
56
|
+
|
|
57
|
+
self.boost_true_positive_feedback = boost_true_positive_feedback
|
|
58
|
+
self.append_negated = append_negated
|
|
59
|
+
self.grid = grid
|
|
60
|
+
self.block = block
|
|
61
|
+
|
|
62
|
+
# Calculates the number of nodes at each level of the hierarchy
|
|
63
|
+
self.hierarchy_size = [0] * (self.depth + 1)
|
|
64
|
+
self.hierarchy_size[self.depth] = 1
|
|
65
|
+
for d in range(self.depth - 1):
|
|
66
|
+
self.hierarchy_size[self.depth - d - 1] = self.hierarchy_structure[self.depth - d - 1][1] * self.hierarchy_size[self.depth - d]
|
|
67
|
+
|
|
68
|
+
# Represents hierarchy structure for transfer to GPU
|
|
69
|
+
self.hierarchy_structure_factors = [0] * (self.depth - 1)
|
|
70
|
+
self.hierarchy_structure_alternatives = [0] * (self.depth - 1)
|
|
71
|
+
for d in range(1, self.depth):
|
|
72
|
+
self.hierarchy_structure_factors[d-1] = self.hierarchy_structure[d][1]
|
|
73
|
+
if self.hierarchy_structure[d][0] == OR_ALTERNATIVES:
|
|
74
|
+
self.hierarchy_structure_alternatives[d-1] = 1
|
|
75
|
+
|
|
76
|
+
# Calculates total number of features spanned by the hierarchy
|
|
77
|
+
self.number_of_features_hierarchy = 1
|
|
78
|
+
for d in range(self.depth - 1, -1, -1):
|
|
79
|
+
if (self.hierarchy_structure[d][0] == OR_GROUP or self.hierarchy_structure[d][0] == AND_GROUP):
|
|
80
|
+
self.number_of_features_hierarchy *= self.hierarchy_structure[d][1]
|
|
81
|
+
|
|
82
|
+
# Calculates literal chunks per leaf
|
|
83
|
+
self.number_of_features_per_leaf = self.hierarchy_structure[0][1]
|
|
84
|
+
if self.append_negated:
|
|
85
|
+
self.number_of_literals = self.number_of_features_hierarchy * 2
|
|
86
|
+
self.number_of_literals_per_leaf = self.number_of_features_per_leaf * 2
|
|
87
|
+
self.number_of_literal_chunks_per_leaf = int((self.number_of_literals_per_leaf - 1) / 32 + 1)
|
|
88
|
+
else:
|
|
89
|
+
self.number_of_literals = self.number_of_features_hierarchy
|
|
90
|
+
self.number_of_literals_per_leaf = self.number_of_features_per_leaf
|
|
91
|
+
self.number_of_literal_chunks_per_leaf = int((self.number_of_literals_per_leaf - 1) / 32 + 1)
|
|
92
|
+
|
|
93
|
+
# Calculates the number of literal chunks for the full hierarchy
|
|
94
|
+
self.hierarchy_size[0] = self.number_of_literal_chunks_per_leaf * self.hierarchy_size[1]
|
|
95
|
+
|
|
96
|
+
# Calculates number of literal chunks overall for the feature vector (ignores OR alternatives)
|
|
97
|
+
self.number_of_literal_chunks = self.number_of_literal_chunks_per_leaf
|
|
98
|
+
for d in range(self.depth - 1, 0, -1):
|
|
99
|
+
if (self.hierarchy_structure[d][0] == OR_GROUP or self.hierarchy_structure[d][0] == AND_GROUP):
|
|
100
|
+
self.number_of_literal_chunks *= self.hierarchy_structure[d][1]
|
|
101
|
+
|
|
102
|
+
self.cuda_modules()
|
|
103
|
+
|
|
104
|
+
self.first = True
|
|
105
|
+
|
|
106
|
+
def cuda_modules(self):
|
|
107
|
+
parameters = """
|
|
108
|
+
#define CLAUSES %d
|
|
109
|
+
#define DEPTH %d
|
|
110
|
+
#define COMPONENTS %d
|
|
111
|
+
#define LITERALS_PER_LEAF %d
|
|
112
|
+
#define TA_CHUNKS_PER_LEAF %d
|
|
113
|
+
#define LITERAL_CHUNKS %d
|
|
114
|
+
#define STATE_BITS %d
|
|
115
|
+
#define BOOST_TRUE_POSITIVE_FEEDBACK %d
|
|
116
|
+
#define S %f
|
|
117
|
+
#define THRESHOLD %d
|
|
118
|
+
#define Q %f
|
|
119
|
+
|
|
120
|
+
#define NEGATIVE_CLAUSES %d
|
|
121
|
+
""" % (self.number_of_clauses, self.depth, self.hierarchy_size[1], 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.negative_clauses)
|
|
122
|
+
|
|
123
|
+
mod_prepare = SourceModule(parameters + kernels.code_header + kernels.code_prepare, no_extern_c=True)
|
|
124
|
+
self.prepare_weights = mod_prepare.get_function("prepare_weights")
|
|
125
|
+
self.prepare_hierarchy = mod_prepare.get_function("prepare_hierarchy")
|
|
126
|
+
|
|
127
|
+
mod_update = SourceModule(parameters + kernels.code_header + kernels.code_update, no_extern_c=True)
|
|
128
|
+
self.update_hierarchy = mod_update.get_function("update_hierarchy")
|
|
129
|
+
self.update_hierarchy.prepare("PiPPPiPPPPPi")
|
|
130
|
+
|
|
131
|
+
self.update_weights = mod_update.get_function("update_weights")
|
|
132
|
+
self.update_weights.prepare("PiiPPPPi")
|
|
133
|
+
|
|
134
|
+
self.evaluate_leaves = mod_update.get_function("evaluate_leaves")
|
|
135
|
+
self.evaluate_leaves.prepare("PPPiPPPi")
|
|
136
|
+
|
|
137
|
+
self.evaluate_final = mod_update.get_function("evaluate_final")
|
|
138
|
+
self.evaluate_final.prepare("iPPP")
|
|
139
|
+
|
|
140
|
+
self.evaluate_and_groups = mod_update.get_function("evaluate_and_groups")
|
|
141
|
+
self.evaluate_and_groups.prepare("PPii")
|
|
142
|
+
|
|
143
|
+
self.propagate_and_group_false_truth_values = mod_update.get_function("propagate_and_group_false_truth_values")
|
|
144
|
+
self.propagate_and_group_false_truth_values.prepare("PPii")
|
|
145
|
+
|
|
146
|
+
self.evaluate_or_groups = mod_update.get_function("evaluate_or_groups")
|
|
147
|
+
self.evaluate_or_groups.prepare("PPii")
|
|
148
|
+
|
|
149
|
+
self.evaluate_or_alternatives = mod_update.get_function("evaluate_or_alternatives")
|
|
150
|
+
self.evaluate_or_alternatives.prepare("PPii")
|
|
151
|
+
|
|
152
|
+
# CUDA modules for encoding input data
|
|
153
|
+
mod_encode = SourceModule(kernels.code_encode, no_extern_c=True)
|
|
154
|
+
self.prepare_encode_hierarchy = mod_encode.get_function("prepare_encode_hierarchy")
|
|
155
|
+
self.encode_hierarchy = mod_encode.get_function("encode_hierarchy")
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def encode_X(self, X, encoded_X_hierarchy_gpu):
|
|
159
|
+
number_of_examples = X.shape[0]
|
|
160
|
+
|
|
161
|
+
# Allocates GPU memory for input data
|
|
162
|
+
Xm = np.ascontiguousarray(X.flatten()).astype(np.uint32)
|
|
163
|
+
X_gpu = cuda.mem_alloc(Xm.nbytes)
|
|
164
|
+
cuda.memcpy_htod(X_gpu, Xm)
|
|
165
|
+
|
|
166
|
+
# Prepares for leaf encoding of the input data
|
|
167
|
+
self.prepare_encode_hierarchy(X_gpu, encoded_X_hierarchy_gpu, np.int32(self.number_of_literal_chunks), np.int32(number_of_examples), grid=self.grid, block=self.block)
|
|
168
|
+
cuda.Context.synchronize()
|
|
169
|
+
|
|
170
|
+
# Encodes the input data split across the leaves
|
|
171
|
+
self.encode_hierarchy(X_gpu, encoded_X_hierarchy_gpu, np.int32(self.number_of_features_hierarchy), np.int32(self.number_of_literal_chunks), np.int32(self.hierarchy_size[1]), np.int32(self.number_of_features_per_leaf), np.int32(self.number_of_literal_chunks_per_leaf), np.int32(self.append_negated), np.int32(number_of_examples), grid=self.grid, block=self.block)
|
|
172
|
+
cuda.Context.synchronize()
|
|
173
|
+
|
|
174
|
+
def allocate_gpu_memory(self):
|
|
175
|
+
# GPU memory for accumulating votes, level by level
|
|
176
|
+
self.hierarchy_votes = []
|
|
177
|
+
for d in range(1, self.depth):
|
|
178
|
+
self.hierarchy_votes.append(cuda.mem_alloc(self.number_of_clauses*int(self.hierarchy_size[d])*4))
|
|
179
|
+
self.hierarchy_votes.append(cuda.mem_alloc(self.number_of_clauses*4))
|
|
180
|
+
|
|
181
|
+
# GPU memory for storing hierarchy structure
|
|
182
|
+
self.hierarchy_structure_factors_gpu = cuda.mem_alloc((self.depth-1)*4)
|
|
183
|
+
cuda.memcpy_htod(self.hierarchy_structure_factors_gpu, np.array(self.hierarchy_structure_factors, dtype=np.int32))
|
|
184
|
+
|
|
185
|
+
# GPU memory for storing hierarchy structure
|
|
186
|
+
self.hierarchy_structure_alternatives_gpu = cuda.mem_alloc((self.depth-1)*4)
|
|
187
|
+
cuda.memcpy_htod(self.hierarchy_structure_alternatives_gpu, np.array(self.hierarchy_structure_alternatives, dtype=np.int32))
|
|
188
|
+
|
|
189
|
+
# GPU memory for storing Tsetlin Automata states
|
|
190
|
+
self.ta_state_hierarchy_gpu = cuda.mem_alloc(self.number_of_clauses*self.hierarchy_size[0]*self.number_of_state_bits*4)
|
|
191
|
+
self.clause_weights_gpu = cuda.mem_alloc(self.number_of_outputs*self.number_of_clauses*4)
|
|
192
|
+
self.component_weights_gpu = cuda.mem_alloc(self.number_of_clauses*self.hierarchy_size[1]*4) # Only positive weights...
|
|
193
|
+
self.class_sum_gpu = cuda.mem_alloc(self.number_of_outputs*4)
|
|
194
|
+
|
|
195
|
+
def ta_action(self, clause, leaf, ta):
|
|
196
|
+
ta_state_hierarchy = np.empty(self.number_of_clauses*self.hierarchy_size[1]*self.number_of_literal_chunks_per_leaf*self.number_of_state_bits, dtype=np.uint32)
|
|
197
|
+
cuda.memcpy_dtoh(ta_state_hierarchy, self.ta_state_hierarchy_gpu)
|
|
198
|
+
ta_state_hierarchy = self.ta_state_hierarchy.reshape((self.number_of_clauses, self.hierarchy_size[1], self.number_of_literal_chunks_per_leaf, self.number_of_state_bits))
|
|
199
|
+
|
|
200
|
+
return (ta_state[clause, leaf, ta // 32, self.number_of_state_bits-1] & (1 << (ta % 32))) > 0
|
|
201
|
+
|
|
202
|
+
def get_state(self):
|
|
203
|
+
# To be updated
|
|
204
|
+
ta_state_hierarchy = np.empty(self.number_of_clauses*self.hierarchy_size[1]*self.number_of_literal_chunks_per_leaf*self.number_of_state_bits, dtype=np.uint32)
|
|
205
|
+
cuda.memcpy_dtoh(self.ta_state_hierarchy, self.ta_state_hierarchy_gpu)
|
|
206
|
+
clause_weights = np.empty(self.number_of_outputs*self.number_of_clauses).astype(np.int32)
|
|
207
|
+
cuda.memcpy_dtoh(self.clause_weights, self.clause_weights_gpu)
|
|
208
|
+
return((self.ta_state_hierarchy, self.clause_weights, self.number_of_outputs, self.number_of_clauses, self.hierarchy_structure, self.boost_true_positive_feedback, self.number_of_state_bits, self.append_negated, self.min_y, self.max_y))
|
|
209
|
+
|
|
210
|
+
def set_state(self, state):
|
|
211
|
+
# To be updated
|
|
212
|
+
self.number_of_outputs = state[2]
|
|
213
|
+
self.number_of_clauses = state[3]
|
|
214
|
+
self.hierarchy_structure = state[4]
|
|
215
|
+
self.boost_true_positive_feedback = state[5]
|
|
216
|
+
self.number_of_state_bits = state[6]
|
|
217
|
+
self.append_negated = state[7]
|
|
218
|
+
self.min_y = state[8]
|
|
219
|
+
self.max_y = state[9]
|
|
220
|
+
|
|
221
|
+
self.ta_state_hierarchy_gpu = cuda.mem_alloc(self.number_of_clauses*self.hierarchy_size[0]*self.number_of_state_bits*4)
|
|
222
|
+
self.clause_weights_gpu = cuda.mem_alloc(self.number_of_outputs*self.number_of_clauses*4)
|
|
223
|
+
cuda.memcpy_htod(self.ta_state_hierarchy_gpu, state[0])
|
|
224
|
+
cuda.memcpy_htod(self.clause_weights_gpu, state[1])
|
|
225
|
+
|
|
226
|
+
# Transform input data for processing at next layer
|
|
227
|
+
def transform(self, X):
|
|
228
|
+
None # To be updated
|
|
229
|
+
|
|
230
|
+
def initialize_weights_and_ta_states(self):
|
|
231
|
+
self.prepare_weights(g.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)
|
|
232
|
+
cuda.Context.synchronize()
|
|
233
|
+
|
|
234
|
+
self.prepare_hierarchy(g.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)
|
|
235
|
+
cuda.Context.synchronize()
|
|
236
|
+
|
|
237
|
+
def evaluate_hierarchy(self, encoded_X_hierarchy, e):
|
|
238
|
+
# Initializes class sums to zero
|
|
239
|
+
class_sum = np.ascontiguousarray(np.zeros(self.number_of_outputs)).astype(np.int32)
|
|
240
|
+
cuda.memcpy_htod(self.class_sum_gpu, class_sum)
|
|
241
|
+
|
|
242
|
+
# Evaluates all the hierarchy leaves in parallel
|
|
243
|
+
self.evaluate_leaves.prepared_call(
|
|
244
|
+
self.grid,
|
|
245
|
+
self.block,
|
|
246
|
+
self.ta_state_hierarchy_gpu,
|
|
247
|
+
self.component_weights_gpu,
|
|
248
|
+
self.hierarchy_votes[0],
|
|
249
|
+
self.depth,
|
|
250
|
+
self.hierarchy_structure_factors_gpu,
|
|
251
|
+
self.hierarchy_structure_alternatives_gpu,
|
|
252
|
+
encoded_X_hierarchy,
|
|
253
|
+
np.int32(e)
|
|
254
|
+
)
|
|
255
|
+
cuda.Context.synchronize()
|
|
256
|
+
|
|
257
|
+
# Propagates votes bottom-up in the hierarchy, starting from the clause components (leaves)
|
|
258
|
+
for d in range(1, self.depth):
|
|
259
|
+
if (self.hierarchy_structure[d][0] == AND_GROUP):
|
|
260
|
+
self.evaluate_and_groups.prepared_call(
|
|
261
|
+
self.grid,
|
|
262
|
+
self.block,
|
|
263
|
+
self.hierarchy_votes[d-1],
|
|
264
|
+
self.hierarchy_votes[d],
|
|
265
|
+
self.hierarchy_size[d + 1],
|
|
266
|
+
self.hierarchy_structure[d][1]
|
|
267
|
+
)
|
|
268
|
+
cuda.Context.synchronize()
|
|
269
|
+
elif self.hierarchy_structure[d][0] == OR_GROUP:
|
|
270
|
+
self.evaluate_or_groups.prepared_call(
|
|
271
|
+
self.grid,
|
|
272
|
+
self.block,
|
|
273
|
+
self.hierarchy_votes[d-1],
|
|
274
|
+
self.hierarchy_votes[d],
|
|
275
|
+
self.hierarchy_size[d + 1],
|
|
276
|
+
self.hierarchy_structure[d][1]
|
|
277
|
+
)
|
|
278
|
+
cuda.Context.synchronize()
|
|
279
|
+
elif self.hierarchy_structure[d][0] == OR_ALTERNATIVES:
|
|
280
|
+
self.evaluate_or_alternatives.prepared_call(
|
|
281
|
+
self.grid,
|
|
282
|
+
self.block,
|
|
283
|
+
self.hierarchy_votes[d-1],
|
|
284
|
+
self.hierarchy_votes[d],
|
|
285
|
+
self.hierarchy_size[d + 1],
|
|
286
|
+
self.hierarchy_structure[d][1]
|
|
287
|
+
)
|
|
288
|
+
cuda.Context.synchronize()
|
|
289
|
+
else:
|
|
290
|
+
printf("Unknown node type!")
|
|
291
|
+
sys.exit()
|
|
292
|
+
|
|
293
|
+
# Adds up the votes from each clause (hierarchy root)
|
|
294
|
+
self.evaluate_final.prepared_call(
|
|
295
|
+
self.grid,
|
|
296
|
+
self.block,
|
|
297
|
+
np.int32(self.number_of_outputs),
|
|
298
|
+
self.hierarchy_votes[self.depth-1],
|
|
299
|
+
self.clause_weights_gpu,
|
|
300
|
+
self.class_sum_gpu
|
|
301
|
+
)
|
|
302
|
+
cuda.Context.synchronize()
|
|
303
|
+
|
|
304
|
+
def _fit(self, X, encoded_Y, epochs=100, incremental=False):
|
|
305
|
+
number_of_examples = X.shape[0]
|
|
306
|
+
|
|
307
|
+
if self.first:
|
|
308
|
+
# Allocates memory and prepares weights and Tsetlin automata states on first run
|
|
309
|
+
self.allocate_gpu_memory()
|
|
310
|
+
|
|
311
|
+
self.initialize_weights_and_ta_states()
|
|
312
|
+
|
|
313
|
+
self.first = False
|
|
314
|
+
elif not incremental:
|
|
315
|
+
# Re-initializes weights and Tsetlin automata states if training is not incremental
|
|
316
|
+
self.initialize_weights_and_ta_states()
|
|
317
|
+
|
|
318
|
+
# Allocates GPU memory for training data
|
|
319
|
+
encoded_X_hierarchy_training_gpu = cuda.mem_alloc(int(number_of_examples * self.number_of_literal_chunks * 4))
|
|
320
|
+
Y_gpu = cuda.mem_alloc(encoded_Y.nbytes)
|
|
321
|
+
|
|
322
|
+
self.encode_X(X, encoded_X_hierarchy_training_gpu)
|
|
323
|
+
cuda.memcpy_htod(Y_gpu, encoded_Y)
|
|
324
|
+
|
|
325
|
+
for epoch in range(epochs):
|
|
326
|
+
for e in range(number_of_examples):
|
|
327
|
+
self.evaluate_hierarchy(encoded_X_hierarchy_training_gpu, e)
|
|
328
|
+
|
|
329
|
+
# Propagates the root value and any intermittent node values back to the leaves.
|
|
330
|
+
# The purpose is to determine which leaves only has True nodes on the path from leaf to root.
|
|
331
|
+
for d in range(self.depth-1, 0, -1):
|
|
332
|
+
self.propagate_and_group_false_truth_values.prepared_call(
|
|
333
|
+
self.grid,
|
|
334
|
+
self.block,
|
|
335
|
+
self.hierarchy_votes[d-1],
|
|
336
|
+
self.hierarchy_votes[d],
|
|
337
|
+
self.hierarchy_size[d + 1],
|
|
338
|
+
self.hierarchy_structure[d][1]
|
|
339
|
+
)
|
|
340
|
+
cuda.Context.synchronize()
|
|
341
|
+
|
|
342
|
+
# Updates the clause components (leaves) based on the propagated truth values
|
|
343
|
+
self.update_hierarchy.prepared_call(
|
|
344
|
+
self.grid,
|
|
345
|
+
self.block,
|
|
346
|
+
g.state,
|
|
347
|
+
np.int32(self.number_of_outputs),
|
|
348
|
+
self.ta_state_hierarchy_gpu,
|
|
349
|
+
self.clause_weights_gpu,
|
|
350
|
+
self.hierarchy_votes[0],
|
|
351
|
+
self.depth,
|
|
352
|
+
self.hierarchy_structure_factors_gpu,
|
|
353
|
+
self.hierarchy_structure_alternatives_gpu,
|
|
354
|
+
self.class_sum_gpu,
|
|
355
|
+
encoded_X_hierarchy_training_gpu,
|
|
356
|
+
Y_gpu,
|
|
357
|
+
np.int32(e)
|
|
358
|
+
)
|
|
359
|
+
cuda.Context.synchronize()
|
|
360
|
+
|
|
361
|
+
# Updates the clause weights
|
|
362
|
+
if (self.tm_type in [WEIGHTED_TM, COALESCED_TM]):
|
|
363
|
+
self.update_weights.prepared_call(
|
|
364
|
+
self.grid,
|
|
365
|
+
self.block,
|
|
366
|
+
g.state,
|
|
367
|
+
np.int32(self.tm_type),
|
|
368
|
+
np.int32(self.number_of_outputs),
|
|
369
|
+
self.clause_weights_gpu,
|
|
370
|
+
self.hierarchy_votes[self.depth-1],
|
|
371
|
+
self.class_sum_gpu,
|
|
372
|
+
Y_gpu,
|
|
373
|
+
np.int32(e)
|
|
374
|
+
)
|
|
375
|
+
cuda.Context.synchronize()
|
|
376
|
+
return
|
|
377
|
+
|
|
378
|
+
def _score(self, X):
|
|
379
|
+
number_of_examples = X.shape[0]
|
|
380
|
+
|
|
381
|
+
encoded_X_hierarchy_test_gpu = cuda.mem_alloc(int(number_of_examples * self.number_of_literal_chunks * 4))
|
|
382
|
+
self.encode_X(X, encoded_X_hierarchy_test_gpu)
|
|
383
|
+
|
|
384
|
+
class_sum = np.ascontiguousarray(np.zeros((self.number_of_outputs, number_of_examples))).astype(np.int32)
|
|
385
|
+
class_sum_example = np.ascontiguousarray(np.zeros(self.number_of_outputs)).astype(np.int32)
|
|
386
|
+
|
|
387
|
+
for e in range(number_of_examples):
|
|
388
|
+
self.evaluate_hierarchy(encoded_X_hierarchy_test_gpu, e)
|
|
389
|
+
|
|
390
|
+
cuda.memcpy_dtoh(class_sum_example, self.class_sum_gpu)
|
|
391
|
+
class_sum[:, e] = class_sum_example
|
|
392
|
+
|
|
393
|
+
class_sum = np.clip(class_sum.reshape((self.number_of_outputs, number_of_examples)), -self.T, self.T)
|
|
394
|
+
|
|
395
|
+
return class_sum
|
|
396
|
+
|
|
397
|
+
class MultiOutputTsetlinMachine(CommonTsetlinMachine):
|
|
398
|
+
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)):
|
|
399
|
+
self.negative_clauses = 1
|
|
400
|
+
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)
|
|
401
|
+
|
|
402
|
+
def fit(self, X, Y, epochs=100, incremental=False):
|
|
403
|
+
X = X.reshape(X.shape[0], X.shape[1], 1)
|
|
404
|
+
|
|
405
|
+
self.number_of_outputs = Y.shape[1]
|
|
406
|
+
self.patch_dim = (X.shape[1], 1, 1)
|
|
407
|
+
|
|
408
|
+
self.max_y = None
|
|
409
|
+
self.min_y = None
|
|
410
|
+
|
|
411
|
+
encoded_Y = np.where(Y == 1, self.T, -self.T).astype(np.int32)
|
|
412
|
+
self._fit(X, encoded_Y, epochs = epochs, incremental = incremental)
|
|
413
|
+
|
|
414
|
+
return
|
|
415
|
+
|
|
416
|
+
def score(self, X):
|
|
417
|
+
X = X.reshape(X.shape[0], X.shape[1], 1)
|
|
418
|
+
return self._score(X)
|
|
419
|
+
|
|
420
|
+
def predict(self, X):
|
|
421
|
+
return (self.score(X) >= 0).astype(np.uint32).transpose()
|
|
422
|
+
|
|
423
|
+
class MultiClassTsetlinMachine(CommonTsetlinMachine):
|
|
424
|
+
def __init__(self, number_of_clauses, T, s, tm_type=VANILLA_TM, 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)):
|
|
425
|
+
self.negative_clauses = 1
|
|
426
|
+
super().__init__(number_of_clauses, T, s, tm_type=tm_type, 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)
|
|
427
|
+
|
|
428
|
+
def fit(self, X, Y, epochs=100, incremental=False):
|
|
429
|
+
X = X.reshape(X.shape[0], X.shape[1], 1)
|
|
430
|
+
|
|
431
|
+
self.number_of_outputs = int(np.max(Y) + 1)
|
|
432
|
+
self.patch_dim = (X.shape[1], 1, 1)
|
|
433
|
+
|
|
434
|
+
self.max_y = None
|
|
435
|
+
self.min_y = None
|
|
436
|
+
|
|
437
|
+
encoded_Y = np.empty((Y.shape[0], self.number_of_outputs), dtype = np.int32)
|
|
438
|
+
for i in range(self.number_of_outputs):
|
|
439
|
+
encoded_Y[:,i] = np.where(Y == i, self.T, -self.T)
|
|
440
|
+
|
|
441
|
+
self._fit(X, encoded_Y, epochs = epochs, incremental = incremental)
|
|
442
|
+
|
|
443
|
+
return
|
|
444
|
+
|
|
445
|
+
def score(self, X):
|
|
446
|
+
X = X.reshape(X.shape[0], X.shape[1], 1)
|
|
447
|
+
return self._score(X)
|
|
448
|
+
|
|
449
|
+
def predict(self, X):
|
|
450
|
+
return np.argmax(self.score(X), axis=0)
|
|
451
|
+
|
|
452
|
+
class TsetlinMachine(CommonTsetlinMachine):
|
|
453
|
+
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)):
|
|
454
|
+
self.negative_clauses = 1
|
|
455
|
+
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)
|
|
456
|
+
|
|
457
|
+
def fit(self, X, Y, epochs=100, incremental=False):
|
|
458
|
+
X = X.reshape(X.shape[0], X.shape[1], 1)
|
|
459
|
+
|
|
460
|
+
self.number_of_outputs = 1
|
|
461
|
+
self.patch_dim = (X.shape[1], 1, 1)
|
|
462
|
+
|
|
463
|
+
self.max_y = None
|
|
464
|
+
self.min_y = None
|
|
465
|
+
|
|
466
|
+
encoded_Y = np.where(Y == 1, self.T, -self.T).astype(np.int32)
|
|
467
|
+
|
|
468
|
+
self._fit(X, encoded_Y, epochs = epochs, incremental = incremental)
|
|
469
|
+
|
|
470
|
+
return
|
|
471
|
+
|
|
472
|
+
def score(self, X):
|
|
473
|
+
X = X.reshape(X.shape[0], X.shape[1], 1)
|
|
474
|
+
return self._score(X)[0,:]
|
|
475
|
+
|
|
476
|
+
def predict(self, X):
|
|
477
|
+
return int(self.score(X) >= 0)
|
|
478
|
+
|
|
479
|
+
class RegressionTsetlinMachine(CommonTsetlinMachine):
|
|
480
|
+
def __init__(self, number_of_clauses, T, s, boost_true_positive_feedback=1, number_of_state_bits=8, append_negated=True, grid=(16*13,1,1), block=(128,1,1)):
|
|
481
|
+
self.negative_clauses = 0
|
|
482
|
+
super().__init__(number_of_clauses, T, s, boost_true_positive_feedback=boost_true_positive_feedback, number_of_state_bits=number_of_state_bits, append_negated=append_negated, grid=grid, block=block)
|
|
483
|
+
|
|
484
|
+
def fit(self, X, Y, epochs=100, incremental=False):
|
|
485
|
+
X = X.reshape(X.shape[0], X.shape[1], 1)
|
|
486
|
+
|
|
487
|
+
self.number_of_outputs = 1
|
|
488
|
+
self.patch_dim = (X.shape[1], 1, 1)
|
|
489
|
+
|
|
490
|
+
self.max_y = np.max(Y)
|
|
491
|
+
self.min_y = np.min(Y)
|
|
492
|
+
|
|
493
|
+
encoded_Y = ((Y - self.min_y)/(self.max_y - self.min_y)*self.T).astype(np.int32)
|
|
494
|
+
|
|
495
|
+
self._fit(X, encoded_Y, epochs = epochs, incremental = incremental)
|
|
496
|
+
|
|
497
|
+
return
|
|
498
|
+
|
|
499
|
+
def predict(self, X):
|
|
500
|
+
X = X.reshape(X.shape[0], X.shape[1], 1)
|
|
501
|
+
|
|
502
|
+
return 1.0*(self._score(X)[0,:])*(self.max_y - self.min_y)/(self.T) + self.min_y
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import numpy as np
|
|
2
|
+
|
|
3
|
+
from sklearn.preprocessing import FunctionTransformer
|
|
4
|
+
from sklearn.preprocessing import KBinsDiscretizer
|
|
5
|
+
|
|
6
|
+
import warnings
|
|
7
|
+
warnings.filterwarnings("ignore", category=UserWarning)
|
|
8
|
+
|
|
9
|
+
# From https://github.com/WojciechMigda/Tsetlini/blob/main/lib/examples/california-housing/src/produce_dataset_alt.py
|
|
10
|
+
|
|
11
|
+
def _as_bits(x, nbits):
|
|
12
|
+
s = '1' * x + '0' * (nbits - x)
|
|
13
|
+
return np.array([int(c) for c in s])
|
|
14
|
+
|
|
15
|
+
def _unpack_bits(a, nbits):
|
|
16
|
+
if len(a.shape) > 2:
|
|
17
|
+
raise ValueError("_unpack_bits: input array cannot have more than 2 dimensions, got {}".format(len(a.shape)))
|
|
18
|
+
|
|
19
|
+
a = np.clip(a, 0, nbits)
|
|
20
|
+
a_ = np.empty_like(a, dtype=np.uint64)
|
|
21
|
+
np.rint(a, out=a_, casting='unsafe')
|
|
22
|
+
F = np.frompyfunc(_as_bits, 2, 1)
|
|
23
|
+
rv = np.stack(F(a_.ravel(), nbits)).reshape(a.shape[0], -1)
|
|
24
|
+
return rv
|
|
25
|
+
|
|
26
|
+
class Booleanizer:
|
|
27
|
+
def __init__(self, max_bits_per_feature = 25):
|
|
28
|
+
self.max_bits_per_feature = max_bits_per_feature
|
|
29
|
+
|
|
30
|
+
self.kbd = KBinsDiscretizer(n_bins=max_bits_per_feature+1, encode='ordinal', strategy='quantile')
|
|
31
|
+
|
|
32
|
+
return
|
|
33
|
+
|
|
34
|
+
def fit(self, X):
|
|
35
|
+
self.kbd_fitted = self.kbd.fit(X)
|
|
36
|
+
|
|
37
|
+
return
|
|
38
|
+
|
|
39
|
+
def transform(self, X):
|
|
40
|
+
X_transformed = self.kbd_fitted.transform(X).astype(int)
|
|
41
|
+
|
|
42
|
+
pre = FunctionTransformer(_unpack_bits, validate=False, kw_args={'nbits': self.max_bits_per_feature})
|
|
43
|
+
return pre.fit_transform(X_transformed)
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# PyHierarchicalTsetlinMachineCUDA
|
|
2
|
+
Implements the Hierarchical Tsetlin Machine in CUDA
|
|
3
|
+
|
|
4
|
+
## Installation
|
|
5
|
+
|
|
6
|
+
```bash
|
|
7
|
+
python ./setup.py sdist
|
|
8
|
+
pip install dist/PyHierarchicalTsetlinMachineCUDA-0.2.0.tar.gz
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Licence
|
|
12
|
+
|
|
13
|
+
MIT License
|
|
14
|
+
|
|
15
|
+
Copyright (c) 2026 Ole-Christoffer Granmo and the University of Agder
|
|
16
|
+
|
|
17
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
18
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
19
|
+
in the Software without restriction, including without limitation the rights
|
|
20
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
21
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
22
|
+
furnished to do so, subject to the following conditions:
|
|
23
|
+
|
|
24
|
+
The above copyright notice and this permission notice shall be included in all
|
|
25
|
+
copies or substantial portions of the Software.
|
|
26
|
+
|
|
27
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
28
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
29
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
30
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
31
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
32
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
33
|
+
SOFTWARE.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: pyhierarchicaltsetlinmachinecuda
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Hierarchical Tsetlin Machine Architecture.
|
|
5
|
+
Home-page: https://github.com/cair/PyHierarchicalTsetlinMachineCUDA/
|
|
6
|
+
Author: Ole-Christoffer Granmo
|
|
7
|
+
Author-email: ole.granmo@uia.no
|
|
8
|
+
License: MIT
|
|
9
|
+
Keywords: pattern-recognition cuda machine-learning interpretable-machine-learning rule-based-machine-learning propositional-logic tsetlin-machine regression convolution classification multi-layer
|
|
10
|
+
License-File: LICENSE
|
|
11
|
+
|
|
12
|
+
Hierarchical Tsetlin Machine Architecture.
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
setup.py
|
|
4
|
+
PyHierarchicalTsetlinMachineCUDA/__init__.py
|
|
5
|
+
PyHierarchicalTsetlinMachineCUDA/graph.py
|
|
6
|
+
PyHierarchicalTsetlinMachineCUDA/kernels.py
|
|
7
|
+
PyHierarchicalTsetlinMachineCUDA/tm.py
|
|
8
|
+
PyHierarchicalTsetlinMachineCUDA/tools.py
|
|
9
|
+
pyhierarchicaltsetlinmachinecuda.egg-info/PKG-INFO
|
|
10
|
+
pyhierarchicaltsetlinmachinecuda.egg-info/SOURCES.txt
|
|
11
|
+
pyhierarchicaltsetlinmachinecuda.egg-info/dependency_links.txt
|
|
12
|
+
pyhierarchicaltsetlinmachinecuda.egg-info/top_level.txt
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
|
pyhierarchicaltsetlinmachinecuda-0.2.0/pyhierarchicaltsetlinmachinecuda.egg-info/top_level.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
PyHierarchicalTsetlinMachineCUDA
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
from setuptools import *
|
|
2
|
+
|
|
3
|
+
setup(
|
|
4
|
+
name='pyhierarchicaltsetlinmachinecuda',
|
|
5
|
+
version='0.2.0',
|
|
6
|
+
author='Ole-Christoffer Granmo',
|
|
7
|
+
author_email='ole.granmo@uia.no',
|
|
8
|
+
url='https://github.com/cair/PyHierarchicalTsetlinMachineCUDA/',
|
|
9
|
+
license='MIT',
|
|
10
|
+
description='Hierarchical Tsetlin Machine Architecture.',
|
|
11
|
+
long_description='Hierarchical Tsetlin Machine Architecture.',
|
|
12
|
+
keywords ='pattern-recognition cuda machine-learning interpretable-machine-learning rule-based-machine-learning propositional-logic tsetlin-machine regression convolution classification multi-layer',
|
|
13
|
+
packages=['PyHierarchicalTsetlinMachineCUDA']
|
|
14
|
+
)
|