addernet 0.1.0__py3-none-any.whl
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.
- addernet/__init__.py +29 -0
- addernet/addernet.py +203 -0
- addernet/addernet_hdc.py +280 -0
- addernet/build_ext.py +42 -0
- addernet/libaddernet.so +0 -0
- addernet/libaddernet_hdc.so +0 -0
- addernet/src/addernet.c +228 -0
- addernet/src/addernet.h +136 -0
- addernet/src/addernet_hdc.c +273 -0
- addernet/src/addernet_hdc.h +117 -0
- addernet/src/hdc_core.c +145 -0
- addernet/src/hdc_core.h +83 -0
- addernet/src/hdc_core_cuda.c +175 -0
- addernet/src/hdc_cuda_batch.c +552 -0
- addernet-0.1.0.dist-info/METADATA +153 -0
- addernet-0.1.0.dist-info/RECORD +19 -0
- addernet-0.1.0.dist-info/WHEEL +5 -0
- addernet-0.1.0.dist-info/licenses/LICENSE +21 -0
- addernet-0.1.0.dist-info/top_level.txt +1 -0
addernet/__init__.py
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
_HERE = Path(__file__).parent
|
|
6
|
+
|
|
7
|
+
if sys.platform == "darwin":
|
|
8
|
+
_lib_hdc_name = "libaddernet_hdc.dylib"
|
|
9
|
+
_lib_addernet_name = "libaddernet.dylib"
|
|
10
|
+
elif sys.platform == "win32":
|
|
11
|
+
_lib_hdc_name = "addernet_hdc.dll"
|
|
12
|
+
_lib_addernet_name = "addernet.dll"
|
|
13
|
+
else:
|
|
14
|
+
_lib_hdc_name = "libaddernet_hdc.so"
|
|
15
|
+
_lib_addernet_name = "libaddernet.so"
|
|
16
|
+
|
|
17
|
+
_need_build = False
|
|
18
|
+
if not (_HERE / _lib_hdc_name).exists() or not (_HERE / _lib_addernet_name).exists():
|
|
19
|
+
_need_build = True
|
|
20
|
+
|
|
21
|
+
if _need_build:
|
|
22
|
+
from . import build_ext
|
|
23
|
+
build_ext.build()
|
|
24
|
+
|
|
25
|
+
from .addernet import AdderNetLayer
|
|
26
|
+
from .addernet_hdc import AdderNetHDC
|
|
27
|
+
|
|
28
|
+
__version__ = "0.1.0"
|
|
29
|
+
__all__ = ["AdderNetLayer", "AdderNetHDC"]
|
addernet/addernet.py
ADDED
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
AdderNet Python Bindings — ctypes interface to libaddernet.so
|
|
4
|
+
==============================================================
|
|
5
|
+
|
|
6
|
+
Usage:
|
|
7
|
+
from addernet import AdderNetLayer
|
|
8
|
+
|
|
9
|
+
layer = AdderNetLayer(size=256, bias=50, input_min=-50, input_max=200, lr=0.1)
|
|
10
|
+
layer.train(inputs, targets, epochs_raw=1000, epochs_expanded=4000)
|
|
11
|
+
result = layer.predict(37.0)
|
|
12
|
+
layer.save("model.bin")
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
import os
|
|
16
|
+
import ctypes
|
|
17
|
+
import numpy as np
|
|
18
|
+
|
|
19
|
+
# ---- Locate shared library ----
|
|
20
|
+
|
|
21
|
+
_HERE = os.path.dirname(os.path.abspath(__file__))
|
|
22
|
+
_LIB_NAMES = [
|
|
23
|
+
os.path.join(_HERE, "libaddernet.so"),
|
|
24
|
+
os.path.join(_HERE, "libaddernet.dylib"),
|
|
25
|
+
"libaddernet.so",
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
_lib = None
|
|
29
|
+
for _name in _LIB_NAMES:
|
|
30
|
+
try:
|
|
31
|
+
_lib = ctypes.CDLL(_name)
|
|
32
|
+
break
|
|
33
|
+
except OSError:
|
|
34
|
+
continue
|
|
35
|
+
|
|
36
|
+
if _lib is None:
|
|
37
|
+
raise OSError(
|
|
38
|
+
"Cannot find libaddernet.so. "
|
|
39
|
+
"Build it first: cd addernet_lib && make"
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
# ---- Opaque pointer type ----
|
|
43
|
+
|
|
44
|
+
_AnLayerPtr = ctypes.c_void_p
|
|
45
|
+
|
|
46
|
+
# ---- Function signatures ----
|
|
47
|
+
|
|
48
|
+
_lib.an_layer_create.restype = _AnLayerPtr
|
|
49
|
+
_lib.an_layer_create.argtypes = [ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.c_int, ctypes.c_double]
|
|
50
|
+
|
|
51
|
+
_lib.an_layer_free.restype = None
|
|
52
|
+
_lib.an_layer_free.argtypes = [_AnLayerPtr]
|
|
53
|
+
|
|
54
|
+
_lib.an_train.restype = ctypes.c_int
|
|
55
|
+
_lib.an_train.argtypes = [
|
|
56
|
+
_AnLayerPtr,
|
|
57
|
+
ctypes.POINTER(ctypes.c_double),
|
|
58
|
+
ctypes.POINTER(ctypes.c_double),
|
|
59
|
+
ctypes.c_int,
|
|
60
|
+
ctypes.c_int,
|
|
61
|
+
ctypes.c_int,
|
|
62
|
+
]
|
|
63
|
+
|
|
64
|
+
_lib.an_predict.restype = ctypes.c_double
|
|
65
|
+
_lib.an_predict.argtypes = [_AnLayerPtr, ctypes.c_double]
|
|
66
|
+
|
|
67
|
+
_lib.an_predict_batch.restype = ctypes.c_int
|
|
68
|
+
_lib.an_predict_batch.argtypes = [
|
|
69
|
+
_AnLayerPtr,
|
|
70
|
+
ctypes.POINTER(ctypes.c_double),
|
|
71
|
+
ctypes.POINTER(ctypes.c_double),
|
|
72
|
+
ctypes.c_int,
|
|
73
|
+
]
|
|
74
|
+
|
|
75
|
+
_lib.an_save.restype = ctypes.c_int
|
|
76
|
+
_lib.an_save.argtypes = [_AnLayerPtr, ctypes.c_char_p]
|
|
77
|
+
|
|
78
|
+
_lib.an_load.restype = _AnLayerPtr
|
|
79
|
+
_lib.an_load.argtypes = [ctypes.c_char_p]
|
|
80
|
+
|
|
81
|
+
_lib.an_get_offset.restype = ctypes.c_int
|
|
82
|
+
_lib.an_get_offset.argtypes = [_AnLayerPtr, ctypes.POINTER(ctypes.c_double), ctypes.c_int]
|
|
83
|
+
|
|
84
|
+
_lib.an_get_size.restype = ctypes.c_int
|
|
85
|
+
_lib.an_get_size.argtypes = [_AnLayerPtr]
|
|
86
|
+
|
|
87
|
+
_lib.an_get_bias.restype = ctypes.c_int
|
|
88
|
+
_lib.an_get_bias.argtypes = [_AnLayerPtr]
|
|
89
|
+
|
|
90
|
+
_lib.an_get_input_min.restype = ctypes.c_int
|
|
91
|
+
_lib.an_get_input_min.argtypes = [_AnLayerPtr]
|
|
92
|
+
|
|
93
|
+
_lib.an_get_input_max.restype = ctypes.c_int
|
|
94
|
+
_lib.an_get_input_max.argtypes = [_AnLayerPtr]
|
|
95
|
+
|
|
96
|
+
_lib.an_get_lr.restype = ctypes.c_double
|
|
97
|
+
_lib.an_get_lr.argtypes = [_AnLayerPtr]
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
# ---- Python wrapper ----
|
|
101
|
+
|
|
102
|
+
class AdderNetLayer:
|
|
103
|
+
"""
|
|
104
|
+
Python wrapper around the C an_layer struct.
|
|
105
|
+
Provides train / predict / predict_batch / save / load using numpy arrays.
|
|
106
|
+
"""
|
|
107
|
+
|
|
108
|
+
def __init__(self, size=256, bias=50, input_min=-50, input_max=200, lr=0.1,
|
|
109
|
+
_ptr=None):
|
|
110
|
+
if _ptr is not None:
|
|
111
|
+
self._ptr = _ptr
|
|
112
|
+
else:
|
|
113
|
+
self._ptr = _lib.an_layer_create(size, bias, input_min, input_max, lr)
|
|
114
|
+
if not self._ptr:
|
|
115
|
+
raise MemoryError("an_layer_create failed")
|
|
116
|
+
|
|
117
|
+
def __del__(self):
|
|
118
|
+
if hasattr(self, "_ptr") and self._ptr:
|
|
119
|
+
_lib.an_layer_free(self._ptr)
|
|
120
|
+
self._ptr = None
|
|
121
|
+
|
|
122
|
+
def train(self, inputs, targets, epochs_raw=1000, epochs_expanded=4000):
|
|
123
|
+
"""Train on input→target pairs. Accepts lists or numpy arrays."""
|
|
124
|
+
inputs = np.ascontiguousarray(inputs, dtype=np.float64)
|
|
125
|
+
targets = np.ascontiguousarray(targets, dtype=np.float64)
|
|
126
|
+
n = len(inputs)
|
|
127
|
+
if len(targets) != n:
|
|
128
|
+
raise ValueError("inputs and targets must have same length")
|
|
129
|
+
ret = _lib.an_train(
|
|
130
|
+
self._ptr,
|
|
131
|
+
inputs.ctypes.data_as(ctypes.POINTER(ctypes.c_double)),
|
|
132
|
+
targets.ctypes.data_as(ctypes.POINTER(ctypes.c_double)),
|
|
133
|
+
n, epochs_raw, epochs_expanded,
|
|
134
|
+
)
|
|
135
|
+
if ret != 0:
|
|
136
|
+
raise RuntimeError("an_train failed")
|
|
137
|
+
|
|
138
|
+
def predict(self, x):
|
|
139
|
+
"""Single prediction. Returns float."""
|
|
140
|
+
return _lib.an_predict(self._ptr, float(x))
|
|
141
|
+
|
|
142
|
+
def predict_batch(self, inputs):
|
|
143
|
+
"""Batch prediction. Accepts numpy array, returns numpy array."""
|
|
144
|
+
inputs = np.ascontiguousarray(inputs, dtype=np.float64)
|
|
145
|
+
n = len(inputs)
|
|
146
|
+
outputs = np.empty(n, dtype=np.float64)
|
|
147
|
+
_lib.an_predict_batch(
|
|
148
|
+
self._ptr,
|
|
149
|
+
inputs.ctypes.data_as(ctypes.POINTER(ctypes.c_double)),
|
|
150
|
+
outputs.ctypes.data_as(ctypes.POINTER(ctypes.c_double)),
|
|
151
|
+
n,
|
|
152
|
+
)
|
|
153
|
+
return outputs
|
|
154
|
+
|
|
155
|
+
def save(self, path):
|
|
156
|
+
"""Save layer to binary file."""
|
|
157
|
+
ret = _lib.an_save(self._ptr, path.encode("utf-8"))
|
|
158
|
+
if ret != 0:
|
|
159
|
+
raise IOError(f"an_save failed: {path}")
|
|
160
|
+
|
|
161
|
+
@classmethod
|
|
162
|
+
def load(cls, path):
|
|
163
|
+
"""Load layer from binary file. Returns new AdderNetLayer."""
|
|
164
|
+
ptr = _lib.an_load(path.encode("utf-8"))
|
|
165
|
+
if not ptr:
|
|
166
|
+
raise IOError(f"an_load failed: {path}")
|
|
167
|
+
return cls(_ptr=ptr)
|
|
168
|
+
|
|
169
|
+
@property
|
|
170
|
+
def offset_table(self):
|
|
171
|
+
"""Return the offset table as a numpy array."""
|
|
172
|
+
n = _lib.an_get_size(self._ptr)
|
|
173
|
+
buf = np.empty(n, dtype=np.float64)
|
|
174
|
+
_lib.an_get_offset(
|
|
175
|
+
self._ptr,
|
|
176
|
+
buf.ctypes.data_as(ctypes.POINTER(ctypes.c_double)),
|
|
177
|
+
n,
|
|
178
|
+
)
|
|
179
|
+
return buf
|
|
180
|
+
|
|
181
|
+
@property
|
|
182
|
+
def size(self):
|
|
183
|
+
return _lib.an_get_size(self._ptr)
|
|
184
|
+
|
|
185
|
+
@property
|
|
186
|
+
def bias(self):
|
|
187
|
+
return _lib.an_get_bias(self._ptr)
|
|
188
|
+
|
|
189
|
+
@property
|
|
190
|
+
def input_min(self):
|
|
191
|
+
return _lib.an_get_input_min(self._ptr)
|
|
192
|
+
|
|
193
|
+
@property
|
|
194
|
+
def input_max(self):
|
|
195
|
+
return _lib.an_get_input_max(self._ptr)
|
|
196
|
+
|
|
197
|
+
@property
|
|
198
|
+
def lr(self):
|
|
199
|
+
return _lib.an_get_lr(self._ptr)
|
|
200
|
+
|
|
201
|
+
def __repr__(self):
|
|
202
|
+
return (f"AdderNetLayer(size={self.size}, bias={self.bias}, "
|
|
203
|
+
f"range=[{self.input_min},{self.input_max}], lr={self.lr})")
|
addernet/addernet_hdc.py
ADDED
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
#!/usr/bin/env python3
|
|
2
|
+
"""
|
|
3
|
+
AdderNet-HDC Python Bindings — ctypes interface to libaddernet_hdc.so
|
|
4
|
+
======================================================================
|
|
5
|
+
|
|
6
|
+
Usage:
|
|
7
|
+
from addernet_hdc import AdderNetHDC
|
|
8
|
+
|
|
9
|
+
model = AdderNetHDC(n_vars=4, n_classes=3, table_size=256)
|
|
10
|
+
model.train(X, y)
|
|
11
|
+
pred = model.predict(x)
|
|
12
|
+
"""
|
|
13
|
+
|
|
14
|
+
import os
|
|
15
|
+
import ctypes
|
|
16
|
+
import numpy as np
|
|
17
|
+
|
|
18
|
+
# ---- Locate shared library ----
|
|
19
|
+
|
|
20
|
+
_HERE = os.path.dirname(os.path.abspath(__file__))
|
|
21
|
+
_LIB_NAMES = [
|
|
22
|
+
os.path.join(_HERE, "libaddernet_hdc.so"),
|
|
23
|
+
os.path.join(_HERE, "libaddernet_hdc.dylib"),
|
|
24
|
+
"libaddernet_hdc.so",
|
|
25
|
+
]
|
|
26
|
+
|
|
27
|
+
_lib = None
|
|
28
|
+
for _name in _LIB_NAMES:
|
|
29
|
+
try:
|
|
30
|
+
_lib = ctypes.CDLL(_name)
|
|
31
|
+
break
|
|
32
|
+
except OSError:
|
|
33
|
+
continue
|
|
34
|
+
|
|
35
|
+
if _lib is None:
|
|
36
|
+
raise OSError(
|
|
37
|
+
"Cannot find libaddernet_hdc.so. "
|
|
38
|
+
"Build it first: cd addernet_lib && make hdc"
|
|
39
|
+
)
|
|
40
|
+
|
|
41
|
+
# ---- Opaque pointer type ----
|
|
42
|
+
|
|
43
|
+
_AnHdcPtr = ctypes.c_void_p
|
|
44
|
+
|
|
45
|
+
# ---- Function signatures ----
|
|
46
|
+
|
|
47
|
+
_lib.an_hdc_create.restype = _AnHdcPtr
|
|
48
|
+
_lib.an_hdc_create.argtypes = [
|
|
49
|
+
ctypes.c_int, ctypes.c_int, ctypes.c_int,
|
|
50
|
+
ctypes.POINTER(ctypes.c_int),
|
|
51
|
+
]
|
|
52
|
+
|
|
53
|
+
_lib.an_hdc_free.restype = None
|
|
54
|
+
_lib.an_hdc_free.argtypes = [_AnHdcPtr]
|
|
55
|
+
|
|
56
|
+
_lib.an_hdc_train.restype = None
|
|
57
|
+
_lib.an_hdc_train.argtypes = [
|
|
58
|
+
_AnHdcPtr,
|
|
59
|
+
ctypes.POINTER(ctypes.c_double),
|
|
60
|
+
ctypes.POINTER(ctypes.c_int),
|
|
61
|
+
ctypes.c_int,
|
|
62
|
+
]
|
|
63
|
+
|
|
64
|
+
_lib.an_hdc_predict.restype = ctypes.c_int
|
|
65
|
+
_lib.an_hdc_predict.argtypes = [_AnHdcPtr, ctypes.POINTER(ctypes.c_double)]
|
|
66
|
+
|
|
67
|
+
_lib.an_hdc_predict_batch.restype = ctypes.c_int
|
|
68
|
+
_lib.an_hdc_predict_batch.argtypes = [
|
|
69
|
+
_AnHdcPtr,
|
|
70
|
+
ctypes.POINTER(ctypes.c_double),
|
|
71
|
+
ctypes.POINTER(ctypes.c_int),
|
|
72
|
+
ctypes.c_int,
|
|
73
|
+
]
|
|
74
|
+
|
|
75
|
+
_lib.an_hdc_save.restype = ctypes.c_int
|
|
76
|
+
_lib.an_hdc_save.argtypes = [_AnHdcPtr, ctypes.c_char_p]
|
|
77
|
+
|
|
78
|
+
_lib.an_hdc_load.restype = _AnHdcPtr
|
|
79
|
+
_lib.an_hdc_load.argtypes = [ctypes.c_char_p]
|
|
80
|
+
|
|
81
|
+
_lib.hv_seed.restype = None
|
|
82
|
+
_lib.hv_seed.argtypes = [ctypes.c_uint]
|
|
83
|
+
|
|
84
|
+
# HDC primitive bindings (for direct hypervector manipulation)
|
|
85
|
+
|
|
86
|
+
_HV_WORDS = 157 # HDC_WORDS = ceil(10000/64)
|
|
87
|
+
_HV_t = ctypes.c_uint64 * _HV_WORDS
|
|
88
|
+
|
|
89
|
+
_lib.hv_bind.restype = None
|
|
90
|
+
_lib.hv_bind.argtypes = [_HV_t, _HV_t, _HV_t]
|
|
91
|
+
|
|
92
|
+
_lib.hv_bundle.restype = None
|
|
93
|
+
_lib.hv_bundle.argtypes = [_HV_t, ctypes.POINTER(_HV_t), ctypes.c_int]
|
|
94
|
+
|
|
95
|
+
_lib.hv_hamming.restype = ctypes.c_int
|
|
96
|
+
_lib.hv_hamming.argtypes = [_HV_t, _HV_t]
|
|
97
|
+
|
|
98
|
+
_lib.hv_similarity.restype = ctypes.c_float
|
|
99
|
+
_lib.hv_similarity.argtypes = [_HV_t, _HV_t]
|
|
100
|
+
|
|
101
|
+
_lib.hv_random.restype = None
|
|
102
|
+
_lib.hv_random.argtypes = [_HV_t]
|
|
103
|
+
|
|
104
|
+
_lib.hv_copy.restype = None
|
|
105
|
+
_lib.hv_copy.argtypes = [_HV_t, _HV_t]
|
|
106
|
+
|
|
107
|
+
_lib.hv_add_noise.restype = None
|
|
108
|
+
_lib.hv_add_noise.argtypes = [_HV_t, _HV_t, ctypes.c_float]
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
# ---- Python wrapper ----
|
|
112
|
+
|
|
113
|
+
class AdderNetHDC:
|
|
114
|
+
"""
|
|
115
|
+
Multivariate classifier using AdderNet encoding + Hyperdimensional Computing.
|
|
116
|
+
Zero floating-point multiplication at inference.
|
|
117
|
+
"""
|
|
118
|
+
|
|
119
|
+
def __init__(self, n_vars=1, n_classes=2, table_size=256, bias=None,
|
|
120
|
+
seed=42, _ptr=None):
|
|
121
|
+
"""
|
|
122
|
+
Create a new model.
|
|
123
|
+
|
|
124
|
+
Args:
|
|
125
|
+
n_vars: number of input variables
|
|
126
|
+
n_classes: number of output classes
|
|
127
|
+
table_size: encoding table size per variable (power of 2)
|
|
128
|
+
bias: list of bias values per variable (default: table_size//2)
|
|
129
|
+
seed: random seed for reproducibility
|
|
130
|
+
"""
|
|
131
|
+
if _ptr is not None:
|
|
132
|
+
self._ptr = _ptr
|
|
133
|
+
self._n_vars = n_vars
|
|
134
|
+
self._n_classes = n_classes
|
|
135
|
+
self._table_size = table_size
|
|
136
|
+
return
|
|
137
|
+
|
|
138
|
+
_lib.hv_seed(seed)
|
|
139
|
+
|
|
140
|
+
bias_arr = None
|
|
141
|
+
if bias is not None:
|
|
142
|
+
bias_arr = (ctypes.c_int * n_vars)(*bias)
|
|
143
|
+
|
|
144
|
+
self._ptr = _lib.an_hdc_create(n_vars, n_classes, table_size, bias_arr)
|
|
145
|
+
if not self._ptr:
|
|
146
|
+
raise MemoryError("an_hdc_create failed")
|
|
147
|
+
self._n_vars = n_vars
|
|
148
|
+
self._n_classes = n_classes
|
|
149
|
+
self._table_size = table_size
|
|
150
|
+
|
|
151
|
+
def __del__(self):
|
|
152
|
+
if hasattr(self, "_ptr") and self._ptr:
|
|
153
|
+
_lib.an_hdc_free(self._ptr)
|
|
154
|
+
self._ptr = None
|
|
155
|
+
|
|
156
|
+
def train(self, X, y):
|
|
157
|
+
"""
|
|
158
|
+
Train the codebook from labeled data.
|
|
159
|
+
|
|
160
|
+
Args:
|
|
161
|
+
X: n_samples × n_vars array (list of lists or numpy array)
|
|
162
|
+
y: n_samples class labels (int, 0-indexed)
|
|
163
|
+
"""
|
|
164
|
+
X = np.ascontiguousarray(X, dtype=np.float64)
|
|
165
|
+
y = np.ascontiguousarray(y, dtype=np.int32)
|
|
166
|
+
|
|
167
|
+
if X.ndim == 1:
|
|
168
|
+
X = X.reshape(-1, self._n_vars)
|
|
169
|
+
|
|
170
|
+
n = X.shape[0]
|
|
171
|
+
if len(y) != n:
|
|
172
|
+
raise ValueError(f"X has {n} samples but y has {len(y)}")
|
|
173
|
+
|
|
174
|
+
_lib.an_hdc_train(
|
|
175
|
+
self._ptr,
|
|
176
|
+
X.ctypes.data_as(ctypes.POINTER(ctypes.c_double)),
|
|
177
|
+
y.ctypes.data_as(ctypes.POINTER(ctypes.c_int)),
|
|
178
|
+
n,
|
|
179
|
+
)
|
|
180
|
+
|
|
181
|
+
def predict(self, x):
|
|
182
|
+
"""
|
|
183
|
+
Classify one sample.
|
|
184
|
+
|
|
185
|
+
Args:
|
|
186
|
+
x: list or array of n_vars input values
|
|
187
|
+
Returns:
|
|
188
|
+
predicted class label (int)
|
|
189
|
+
"""
|
|
190
|
+
x = np.ascontiguousarray(x, dtype=np.float64)
|
|
191
|
+
return _lib.an_hdc_predict(
|
|
192
|
+
self._ptr,
|
|
193
|
+
x.ctypes.data_as(ctypes.POINTER(ctypes.c_double)),
|
|
194
|
+
)
|
|
195
|
+
|
|
196
|
+
def predict_batch(self, X):
|
|
197
|
+
"""
|
|
198
|
+
Classify multiple samples.
|
|
199
|
+
|
|
200
|
+
Args:
|
|
201
|
+
X: n_samples × n_vars array
|
|
202
|
+
Returns:
|
|
203
|
+
numpy array of predicted class labels
|
|
204
|
+
"""
|
|
205
|
+
X = np.ascontiguousarray(X, dtype=np.float64)
|
|
206
|
+
if X.ndim == 1:
|
|
207
|
+
X = X.reshape(-1, self._n_vars)
|
|
208
|
+
n = X.shape[0]
|
|
209
|
+
outputs = np.empty(n, dtype=np.int32)
|
|
210
|
+
_lib.an_hdc_predict_batch(
|
|
211
|
+
self._ptr,
|
|
212
|
+
X.ctypes.data_as(ctypes.POINTER(ctypes.c_double)),
|
|
213
|
+
outputs.ctypes.data_as(ctypes.POINTER(ctypes.c_int)),
|
|
214
|
+
n,
|
|
215
|
+
)
|
|
216
|
+
return outputs
|
|
217
|
+
|
|
218
|
+
def save(self, path):
|
|
219
|
+
"""Save model to binary file."""
|
|
220
|
+
ret = _lib.an_hdc_save(self._ptr, path.encode("utf-8"))
|
|
221
|
+
if ret != 0:
|
|
222
|
+
raise IOError(f"an_hdc_save failed: {path}")
|
|
223
|
+
|
|
224
|
+
@classmethod
|
|
225
|
+
def load(cls, path):
|
|
226
|
+
"""Load model from binary file."""
|
|
227
|
+
ptr = _lib.an_hdc_load(path.encode("utf-8"))
|
|
228
|
+
if not ptr:
|
|
229
|
+
raise IOError(f"an_hdc_load failed: {path}")
|
|
230
|
+
return cls(_ptr=ptr)
|
|
231
|
+
|
|
232
|
+
@property
|
|
233
|
+
def n_vars(self):
|
|
234
|
+
return self._n_vars
|
|
235
|
+
|
|
236
|
+
@property
|
|
237
|
+
def n_classes(self):
|
|
238
|
+
return self._n_classes
|
|
239
|
+
|
|
240
|
+
@property
|
|
241
|
+
def table_size(self):
|
|
242
|
+
return self._table_size
|
|
243
|
+
|
|
244
|
+
@property
|
|
245
|
+
def codebook(self):
|
|
246
|
+
"""
|
|
247
|
+
Return the trained codebook as a list of numpy uint64 arrays.
|
|
248
|
+
Each array has 157 words (10000 bits).
|
|
249
|
+
|
|
250
|
+
Requires the model to be trained first.
|
|
251
|
+
"""
|
|
252
|
+
_HDC_WORDS = 157
|
|
253
|
+
|
|
254
|
+
# Read the codebook pointer from the an_hdc_model struct.
|
|
255
|
+
# On x86_64, the codebook hv_t* field is at byte offset 32
|
|
256
|
+
# (after n_vars, n_classes, table_size, table_mask, bias*, enc_table*).
|
|
257
|
+
buf = ctypes.string_at(self._ptr, 48) # read first 48 bytes of struct
|
|
258
|
+
cb_addr = int.from_bytes(buf[32:40], byteorder='little')
|
|
259
|
+
if cb_addr == 0:
|
|
260
|
+
raise RuntimeError("Model not trained yet (codebook is NULL)")
|
|
261
|
+
|
|
262
|
+
# Cast the address to a flat uint64 array
|
|
263
|
+
cb = ctypes.cast(
|
|
264
|
+
ctypes.c_void_p(cb_addr),
|
|
265
|
+
ctypes.POINTER(ctypes.c_uint64 * (_HDC_WORDS * self._n_classes))
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
result = []
|
|
269
|
+
flat = cb.contents # the full (HDC_WORDS * n_classes) uint64 array
|
|
270
|
+
for c in range(self._n_classes):
|
|
271
|
+
offset = c * _HDC_WORDS
|
|
272
|
+
hv = np.array([flat[offset + i] for i in range(_HDC_WORDS)],
|
|
273
|
+
dtype=np.uint64)
|
|
274
|
+
result.append(hv)
|
|
275
|
+
return result
|
|
276
|
+
|
|
277
|
+
def __repr__(self):
|
|
278
|
+
return (f"AdderNetHDC(n_vars={self._n_vars}, "
|
|
279
|
+
f"n_classes={self._n_classes}, "
|
|
280
|
+
f"table_size={self._table_size})")
|
addernet/build_ext.py
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import subprocess
|
|
2
|
+
import os
|
|
3
|
+
import sys
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
def build():
|
|
7
|
+
pkg_dir = Path(__file__).parent
|
|
8
|
+
src_dir = pkg_dir / "src"
|
|
9
|
+
|
|
10
|
+
if sys.platform == "darwin":
|
|
11
|
+
lib_name = "libaddernet_hdc.dylib"
|
|
12
|
+
lib_name_addernet = "libaddernet.dylib"
|
|
13
|
+
elif sys.platform == "win32":
|
|
14
|
+
lib_name = "addernet_hdc.dll"
|
|
15
|
+
lib_name_addernet = "addernet.dll"
|
|
16
|
+
else:
|
|
17
|
+
lib_name = "libaddernet_hdc.so"
|
|
18
|
+
lib_name_addernet = "libaddernet.so"
|
|
19
|
+
|
|
20
|
+
out_hdc = pkg_dir / lib_name
|
|
21
|
+
out_addernet = pkg_dir / lib_name_addernet
|
|
22
|
+
|
|
23
|
+
cmd_hdc = [
|
|
24
|
+
"gcc", "-O3", "-march=native", "-fPIC", "-shared",
|
|
25
|
+
str(src_dir / "addernet.c"),
|
|
26
|
+
str(src_dir / "hdc_core.c"),
|
|
27
|
+
str(src_dir / "addernet_hdc.c"),
|
|
28
|
+
"-o", str(out_hdc), "-lm"
|
|
29
|
+
]
|
|
30
|
+
subprocess.run(cmd_hdc, check=True)
|
|
31
|
+
|
|
32
|
+
cmd_addernet = [
|
|
33
|
+
"gcc", "-O3", "-march=native", "-fPIC", "-shared",
|
|
34
|
+
str(src_dir / "addernet.c"),
|
|
35
|
+
"-o", str(out_addernet), "-lm"
|
|
36
|
+
]
|
|
37
|
+
subprocess.run(cmd_addernet, check=True)
|
|
38
|
+
|
|
39
|
+
return str(out_hdc), str(out_addernet)
|
|
40
|
+
|
|
41
|
+
if __name__ == "__main__":
|
|
42
|
+
build()
|
addernet/libaddernet.so
ADDED
|
Binary file
|
|
Binary file
|