eqc-models 0.9.8__py3-none-any.whl → 0.10.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.
- eqc_models-0.10.0.data/platlib/compile_extensions.py +67 -0
- {eqc_models-0.9.8.data → eqc_models-0.10.0.data}/platlib/eqc_models/assignment/setpartition.py +8 -29
- {eqc_models-0.9.8.data → eqc_models-0.10.0.data}/platlib/eqc_models/base/polyeval.c +127 -123
- {eqc_models-0.9.8.data → eqc_models-0.10.0.data}/platlib/eqc_models/base/polyeval.cpython-310-darwin.so +0 -0
- {eqc_models-0.9.8.data → eqc_models-0.10.0.data}/platlib/eqc_models/base/polynomial.py +84 -1
- eqc_models-0.10.0.data/platlib/eqc_models/base.py +115 -0
- eqc_models-0.10.0.data/platlib/eqc_models/combinatorics/setcover.py +93 -0
- eqc_models-0.10.0.data/platlib/eqc_models/communitydetection.py +25 -0
- eqc_models-0.10.0.data/platlib/eqc_models/eqcdirectsolver.py +61 -0
- {eqc_models-0.9.8.data → eqc_models-0.10.0.data}/platlib/eqc_models/graph/base.py +28 -17
- eqc_models-0.10.0.data/platlib/eqc_models/graph/partition.py +148 -0
- eqc_models-0.10.0.data/platlib/eqc_models/graphs.py +28 -0
- eqc_models-0.10.0.data/platlib/eqc_models/maxcut.py +113 -0
- eqc_models-0.10.0.data/platlib/eqc_models/maxkcut.py +185 -0
- eqc_models-0.10.0.data/platlib/eqc_models/ml/classifierqboost.py +628 -0
- eqc_models-0.10.0.data/platlib/eqc_models/ml/cvqboost_hamiltonian.pyx +83 -0
- eqc_models-0.10.0.data/platlib/eqc_models/ml/cvqboost_hamiltonian_c_func.c +68 -0
- eqc_models-0.10.0.data/platlib/eqc_models/ml/cvqboost_hamiltonian_c_func.h +14 -0
- eqc_models-0.10.0.data/platlib/eqc_models/quadraticmodel.py +131 -0
- {eqc_models-0.9.8.data → eqc_models-0.10.0.data}/platlib/eqc_models/sequence/tsp.py +38 -34
- eqc_models-0.10.0.data/platlib/eqc_models/solvers/eqcdirect.py +160 -0
- {eqc_models-0.9.8.data → eqc_models-0.10.0.data}/platlib/eqc_models/solvers/qciclient.py +46 -11
- {eqc_models-0.9.8.data → eqc_models-0.10.0.data}/platlib/eqc_models/utilities/polynomial.py +11 -0
- {eqc_models-0.9.8.dist-info → eqc_models-0.10.0.dist-info}/METADATA +3 -2
- eqc_models-0.10.0.dist-info/RECORD +65 -0
- {eqc_models-0.9.8.dist-info → eqc_models-0.10.0.dist-info}/WHEEL +1 -1
- eqc_models-0.9.8.data/platlib/compile_extensions.py +0 -23
- eqc_models-0.9.8.data/platlib/eqc_models/ml/classifierqboost.py +0 -423
- eqc_models-0.9.8.dist-info/RECORD +0 -52
- {eqc_models-0.9.8.data → eqc_models-0.10.0.data}/platlib/eqc_models/__init__.py +0 -0
- {eqc_models-0.9.8.data → eqc_models-0.10.0.data}/platlib/eqc_models/algorithms/__init__.py +0 -0
- {eqc_models-0.9.8.data → eqc_models-0.10.0.data}/platlib/eqc_models/algorithms/base.py +0 -0
- {eqc_models-0.9.8.data → eqc_models-0.10.0.data}/platlib/eqc_models/algorithms/penaltymultiplier.py +0 -0
- {eqc_models-0.9.8.data → eqc_models-0.10.0.data}/platlib/eqc_models/allocation/__init__.py +0 -0
- {eqc_models-0.9.8.data → eqc_models-0.10.0.data}/platlib/eqc_models/allocation/allocation.py +0 -0
- {eqc_models-0.9.8.data → eqc_models-0.10.0.data}/platlib/eqc_models/allocation/portbase.py +0 -0
- {eqc_models-0.9.8.data → eqc_models-0.10.0.data}/platlib/eqc_models/allocation/portmomentum.py +0 -0
- {eqc_models-0.9.8.data → eqc_models-0.10.0.data}/platlib/eqc_models/assignment/__init__.py +0 -0
- {eqc_models-0.9.8.data → eqc_models-0.10.0.data}/platlib/eqc_models/assignment/qap.py +0 -0
- {eqc_models-0.9.8.data → eqc_models-0.10.0.data}/platlib/eqc_models/base/__init__.py +0 -0
- {eqc_models-0.9.8.data → eqc_models-0.10.0.data}/platlib/eqc_models/base/base.py +0 -0
- {eqc_models-0.9.8.data → eqc_models-0.10.0.data}/platlib/eqc_models/base/constraints.py +0 -0
- {eqc_models-0.9.8.data → eqc_models-0.10.0.data}/platlib/eqc_models/base/operators.py +0 -0
- {eqc_models-0.9.8.data → eqc_models-0.10.0.data}/platlib/eqc_models/base/polyeval.pyx +0 -0
- {eqc_models-0.9.8.data → eqc_models-0.10.0.data}/platlib/eqc_models/base/quadratic.py +0 -0
- {eqc_models-0.9.8.data → eqc_models-0.10.0.data}/platlib/eqc_models/decoding.py +0 -0
- {eqc_models-0.9.8.data → eqc_models-0.10.0.data}/platlib/eqc_models/graph/__init__.py +0 -0
- {eqc_models-0.9.8.data → eqc_models-0.10.0.data}/platlib/eqc_models/graph/hypergraph.py +0 -0
- {eqc_models-0.9.8.data → eqc_models-0.10.0.data}/platlib/eqc_models/graph/maxcut.py +0 -0
- {eqc_models-0.9.8.data → eqc_models-0.10.0.data}/platlib/eqc_models/graph/maxkcut.py +0 -0
- {eqc_models-0.9.8.data → eqc_models-0.10.0.data}/platlib/eqc_models/ml/__init__.py +0 -0
- {eqc_models-0.9.8.data → eqc_models-0.10.0.data}/platlib/eqc_models/ml/classifierbase.py +0 -0
- {eqc_models-0.9.8.data → eqc_models-0.10.0.data}/platlib/eqc_models/ml/classifierqsvm.py +0 -0
- {eqc_models-0.9.8.data → eqc_models-0.10.0.data}/platlib/eqc_models/ml/clustering.py +0 -0
- {eqc_models-0.9.8.data → eqc_models-0.10.0.data}/platlib/eqc_models/ml/clusteringbase.py +0 -0
- {eqc_models-0.9.8.data → eqc_models-0.10.0.data}/platlib/eqc_models/ml/decomposition.py +0 -0
- {eqc_models-0.9.8.data → eqc_models-0.10.0.data}/platlib/eqc_models/ml/forecast.py +0 -0
- {eqc_models-0.9.8.data → eqc_models-0.10.0.data}/platlib/eqc_models/ml/forecastbase.py +0 -0
- {eqc_models-0.9.8.data → eqc_models-0.10.0.data}/platlib/eqc_models/ml/regressor.py +0 -0
- {eqc_models-0.9.8.data → eqc_models-0.10.0.data}/platlib/eqc_models/ml/regressorbase.py +0 -0
- {eqc_models-0.9.8.data → eqc_models-0.10.0.data}/platlib/eqc_models/ml/reservoir.py +0 -0
- {eqc_models-0.9.8.data → eqc_models-0.10.0.data}/platlib/eqc_models/sequence/__init__.py +0 -0
- {eqc_models-0.9.8.data → eqc_models-0.10.0.data}/platlib/eqc_models/solvers/__init__.py +0 -0
- {eqc_models-0.9.8.data → eqc_models-0.10.0.data}/platlib/eqc_models/utilities/__init__.py +0 -0
- {eqc_models-0.9.8.data → eqc_models-0.10.0.data}/platlib/eqc_models/utilities/fileio.py +0 -0
- {eqc_models-0.9.8.data → eqc_models-0.10.0.data}/platlib/eqc_models/utilities/qplib.py +0 -0
- {eqc_models-0.9.8.dist-info → eqc_models-0.10.0.dist-info}/LICENSE.txt +0 -0
- {eqc_models-0.9.8.dist-info → eqc_models-0.10.0.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,628 @@
|
|
|
1
|
+
# (C) Quantum Computing Inc., 2024.
|
|
2
|
+
# Import libs
|
|
3
|
+
import os
|
|
4
|
+
import sys
|
|
5
|
+
import time
|
|
6
|
+
import datetime
|
|
7
|
+
import json
|
|
8
|
+
import gc
|
|
9
|
+
import warnings
|
|
10
|
+
from functools import wraps
|
|
11
|
+
from multiprocessing import shared_memory, Pool, set_start_method, Manager
|
|
12
|
+
from multiprocessing.managers import SharedMemoryManager
|
|
13
|
+
import numpy as np
|
|
14
|
+
from sklearn.tree import DecisionTreeClassifier
|
|
15
|
+
from sklearn.naive_bayes import GaussianNB
|
|
16
|
+
from sklearn.linear_model import LogisticRegression
|
|
17
|
+
from sklearn.gaussian_process import GaussianProcessClassifier
|
|
18
|
+
from sklearn.gaussian_process.kernels import RBF
|
|
19
|
+
|
|
20
|
+
from eqc_models.ml.classifierbase import ClassifierBase
|
|
21
|
+
#from eqc_models.ml.cvqboost_hamiltonian import get_hamiltonian_pyx
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def timer(func):
|
|
25
|
+
@wraps(func)
|
|
26
|
+
def wrapper(*args, **kwargs):
|
|
27
|
+
beg_time = time.time()
|
|
28
|
+
val = func(*args, **kwargs)
|
|
29
|
+
end_time = time.time()
|
|
30
|
+
tot_time = end_time - beg_time
|
|
31
|
+
|
|
32
|
+
print(
|
|
33
|
+
"Runtime of %s: %0.2f seconds!"
|
|
34
|
+
% (
|
|
35
|
+
func.__name__,
|
|
36
|
+
tot_time,
|
|
37
|
+
)
|
|
38
|
+
)
|
|
39
|
+
|
|
40
|
+
return val
|
|
41
|
+
|
|
42
|
+
return wrapper
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class WeakClassifier:
|
|
46
|
+
def __init__(
|
|
47
|
+
self,
|
|
48
|
+
X_train,
|
|
49
|
+
y_train,
|
|
50
|
+
weak_cls_type,
|
|
51
|
+
max_depth=10,
|
|
52
|
+
min_samples_split=100,
|
|
53
|
+
num_jobs=1,
|
|
54
|
+
):
|
|
55
|
+
assert X_train.shape[0] == len(y_train)
|
|
56
|
+
|
|
57
|
+
self.X_train = X_train
|
|
58
|
+
self.y_train = y_train
|
|
59
|
+
|
|
60
|
+
if weak_cls_type == "dct":
|
|
61
|
+
self.clf = DecisionTreeClassifier(
|
|
62
|
+
max_depth=max_depth,
|
|
63
|
+
min_samples_split=min_samples_split,
|
|
64
|
+
random_state=0,
|
|
65
|
+
)
|
|
66
|
+
elif weak_cls_type == "nb":
|
|
67
|
+
self.clf = GaussianNB()
|
|
68
|
+
elif weak_cls_type == "lg":
|
|
69
|
+
self.clf = LogisticRegression(random_state=0)
|
|
70
|
+
elif weak_cls_type == "gp":
|
|
71
|
+
self.clf = GaussianProcessClassifier(
|
|
72
|
+
kernel=1.0 * RBF(1.0),
|
|
73
|
+
random_state=0,
|
|
74
|
+
)
|
|
75
|
+
else:
|
|
76
|
+
assert False, (
|
|
77
|
+
"Unknown weak classifier type <%s>!" % weak_cls_type
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
def train(self):
|
|
81
|
+
self.clf.fit(self.X_train, self.y_train)
|
|
82
|
+
|
|
83
|
+
def predict(self, X):
|
|
84
|
+
return self.clf.predict(X)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
class QBoostClassifier(ClassifierBase):
|
|
88
|
+
"""An implementation of QBoost classifier that uses QCi's Dirac-3.
|
|
89
|
+
|
|
90
|
+
Parameters
|
|
91
|
+
----------
|
|
92
|
+
relaxation_schedule: Relaxation schedule used by Dirac-3;
|
|
93
|
+
default: 2.
|
|
94
|
+
|
|
95
|
+
num_samples: Number of samples used by Dirac-3; default: 1.
|
|
96
|
+
|
|
97
|
+
lambda_coef: A penalty multiplier; default: 0.
|
|
98
|
+
|
|
99
|
+
weak_cls_schedule: Weak classifier schedule. Is either 1, 2,
|
|
100
|
+
or 3; default: 2.
|
|
101
|
+
|
|
102
|
+
weak_cls_type: Type of weak classifier
|
|
103
|
+
- dct: Decison tree classifier
|
|
104
|
+
- nb: Naive Baysian classifier
|
|
105
|
+
- lg: Logistic regression
|
|
106
|
+
- gp: Gaussian process classifier
|
|
107
|
+
|
|
108
|
+
default: dct.
|
|
109
|
+
|
|
110
|
+
weak_max_depth: Max depth of the tree. Applied only when
|
|
111
|
+
weak_cls_type="dct". Default: 10.
|
|
112
|
+
|
|
113
|
+
weak_min_samples_split: The minimum number of samples required
|
|
114
|
+
to split an internal node. Applied only when
|
|
115
|
+
weak_cls_type="dct". Default: 100.
|
|
116
|
+
|
|
117
|
+
Examples
|
|
118
|
+
-----------
|
|
119
|
+
|
|
120
|
+
>>> from sklearn import datasets
|
|
121
|
+
>>> from sklearn.preprocessing import MinMaxScaler
|
|
122
|
+
>>> from sklearn.model_selection import train_test_split
|
|
123
|
+
>>> iris = datasets.load_iris()
|
|
124
|
+
>>> X = iris.data
|
|
125
|
+
>>> y = iris.target
|
|
126
|
+
>>> scaler = MinMaxScaler()
|
|
127
|
+
>>> X = scaler.fit_transform(X)
|
|
128
|
+
>>> for i in range(len(y)):
|
|
129
|
+
... if y[i] == 0:
|
|
130
|
+
... y[i] = -1
|
|
131
|
+
... elif y[i] == 2:
|
|
132
|
+
... y[i] = 1
|
|
133
|
+
>>> X_train, X_test, y_train, y_test = train_test_split(
|
|
134
|
+
... X,
|
|
135
|
+
... y,
|
|
136
|
+
... test_size=0.2,
|
|
137
|
+
... random_state=42,
|
|
138
|
+
... )
|
|
139
|
+
>>> from eqc_models.ml.classifierqboost import QBoostClassifier
|
|
140
|
+
>>> obj = QBoostClassifier(
|
|
141
|
+
... relaxation_schedule=2,
|
|
142
|
+
... num_samples=1,
|
|
143
|
+
... lambda_coef=0.0,
|
|
144
|
+
... )
|
|
145
|
+
>>> from contextlib import redirect_stdout
|
|
146
|
+
>>> import io
|
|
147
|
+
>>> f = io.StringIO()
|
|
148
|
+
>>> with redirect_stdout(f):
|
|
149
|
+
... obj.fit(X_train, y_train)
|
|
150
|
+
... y_train_prd = obj.predict(X_train)
|
|
151
|
+
... y_test_prd = obj.predict(X_test)
|
|
152
|
+
"""
|
|
153
|
+
|
|
154
|
+
def __init__(
|
|
155
|
+
self,
|
|
156
|
+
relaxation_schedule=2,
|
|
157
|
+
num_samples=1,
|
|
158
|
+
lambda_coef=0,
|
|
159
|
+
weak_cls_schedule=2,
|
|
160
|
+
weak_cls_type="lg",
|
|
161
|
+
weak_max_depth=10,
|
|
162
|
+
weak_min_samples_split=100,
|
|
163
|
+
weak_cls_strategy="multi_processing",
|
|
164
|
+
weak_cls_num_jobs=None,
|
|
165
|
+
):
|
|
166
|
+
super(QBoostClassifier).__init__()
|
|
167
|
+
|
|
168
|
+
assert weak_cls_schedule in [1, 2, 3]
|
|
169
|
+
assert weak_cls_type in ["dct", "nb", "lg", "gp"]
|
|
170
|
+
assert weak_cls_strategy in [
|
|
171
|
+
"multi_processing",
|
|
172
|
+
"multi_processing_shm",
|
|
173
|
+
"sequential",
|
|
174
|
+
]
|
|
175
|
+
|
|
176
|
+
self.relaxation_schedule = relaxation_schedule
|
|
177
|
+
self.num_samples = num_samples
|
|
178
|
+
self.lambda_coef = lambda_coef
|
|
179
|
+
self.weak_cls_schedule = weak_cls_schedule
|
|
180
|
+
self.weak_cls_type = weak_cls_type
|
|
181
|
+
self.weak_max_depth = weak_max_depth
|
|
182
|
+
self.weak_min_samples_split = weak_min_samples_split
|
|
183
|
+
self.weak_cls_strategy = weak_cls_strategy
|
|
184
|
+
if weak_cls_num_jobs is None or weak_cls_num_jobs <= 0:
|
|
185
|
+
self.weak_cls_num_jobs = os.cpu_count()
|
|
186
|
+
else:
|
|
187
|
+
self.weak_cls_num_jobs = int(weak_cls_num_jobs)
|
|
188
|
+
|
|
189
|
+
self.h_list = []
|
|
190
|
+
self.ind_list = []
|
|
191
|
+
self.classes_ = None
|
|
192
|
+
|
|
193
|
+
@timer
|
|
194
|
+
def _build_weak_classifiers_sq(self, X, y):
|
|
195
|
+
n_records = X.shape[0]
|
|
196
|
+
n_dims = X.shape[1]
|
|
197
|
+
|
|
198
|
+
assert len(y) == n_records
|
|
199
|
+
|
|
200
|
+
self.h_list = []
|
|
201
|
+
self.ind_list = []
|
|
202
|
+
|
|
203
|
+
num_workers = self.weak_cls_num_jobs
|
|
204
|
+
|
|
205
|
+
tasks = []
|
|
206
|
+
for l in range(n_dims):
|
|
207
|
+
weak_classifier = WeakClassifier(
|
|
208
|
+
X[:, [l]],
|
|
209
|
+
y,
|
|
210
|
+
self.weak_cls_type,
|
|
211
|
+
self.weak_max_depth,
|
|
212
|
+
self.weak_min_samples_split,
|
|
213
|
+
)
|
|
214
|
+
weak_classifier.train()
|
|
215
|
+
self.ind_list.append([l])
|
|
216
|
+
self.h_list.append(weak_classifier)
|
|
217
|
+
|
|
218
|
+
if self.weak_cls_schedule >= 2:
|
|
219
|
+
for i in range(n_dims):
|
|
220
|
+
for j in range(i + 1, n_dims):
|
|
221
|
+
weak_classifier = WeakClassifier(
|
|
222
|
+
X[:, [i, j]],
|
|
223
|
+
y,
|
|
224
|
+
self.weak_cls_type,
|
|
225
|
+
self.weak_max_depth,
|
|
226
|
+
self.weak_min_samples_split,
|
|
227
|
+
)
|
|
228
|
+
weak_classifier.train()
|
|
229
|
+
self.ind_list.append([i, j])
|
|
230
|
+
self.h_list.append(weak_classifier)
|
|
231
|
+
|
|
232
|
+
if self.weak_cls_schedule >= 3:
|
|
233
|
+
for i in range(n_dims):
|
|
234
|
+
for j in range(i + 1, n_dims):
|
|
235
|
+
for k in range(j + 1, n_dims):
|
|
236
|
+
weak_classifier = WeakClassifier(
|
|
237
|
+
X[:, [i, j, k]],
|
|
238
|
+
y,
|
|
239
|
+
self.weak_cls_type,
|
|
240
|
+
self.weak_max_depth,
|
|
241
|
+
self.weak_min_samples_split,
|
|
242
|
+
)
|
|
243
|
+
weak_classifier.train()
|
|
244
|
+
self.ind_list.append([i, j, k])
|
|
245
|
+
self.h_list.append(weak_classifier)
|
|
246
|
+
|
|
247
|
+
return
|
|
248
|
+
|
|
249
|
+
def _train_weak_classifier_mp(
|
|
250
|
+
self,
|
|
251
|
+
indices,
|
|
252
|
+
X_subset,
|
|
253
|
+
y,
|
|
254
|
+
n_records,
|
|
255
|
+
n_dims,
|
|
256
|
+
weak_cls_type,
|
|
257
|
+
weak_max_depth,
|
|
258
|
+
weak_min_samples_split,
|
|
259
|
+
):
|
|
260
|
+
# Train the weak classifier
|
|
261
|
+
weak_classifier = WeakClassifier(
|
|
262
|
+
X_subset,
|
|
263
|
+
y,
|
|
264
|
+
weak_cls_type,
|
|
265
|
+
weak_max_depth,
|
|
266
|
+
weak_min_samples_split,
|
|
267
|
+
)
|
|
268
|
+
weak_classifier.train()
|
|
269
|
+
|
|
270
|
+
return indices, weak_classifier
|
|
271
|
+
|
|
272
|
+
@timer
|
|
273
|
+
def _build_weak_classifiers_mp(self, X, y):
|
|
274
|
+
n_records = X.shape[0]
|
|
275
|
+
n_dims = X.shape[1]
|
|
276
|
+
|
|
277
|
+
assert len(y) == n_records
|
|
278
|
+
|
|
279
|
+
self.h_list = []
|
|
280
|
+
self.ind_list = []
|
|
281
|
+
|
|
282
|
+
num_workers = self.weak_cls_num_jobs
|
|
283
|
+
print(f"Using {num_workers} workers to build weak classifiers.")
|
|
284
|
+
|
|
285
|
+
set_start_method("fork", force=True)
|
|
286
|
+
|
|
287
|
+
tasks = []
|
|
288
|
+
for l in range(n_dims):
|
|
289
|
+
tasks.append(
|
|
290
|
+
(
|
|
291
|
+
[l],
|
|
292
|
+
X[:, [l]],
|
|
293
|
+
y,
|
|
294
|
+
n_records,
|
|
295
|
+
n_dims,
|
|
296
|
+
self.weak_cls_type,
|
|
297
|
+
self.weak_max_depth,
|
|
298
|
+
self.weak_min_samples_split,
|
|
299
|
+
)
|
|
300
|
+
)
|
|
301
|
+
|
|
302
|
+
if self.weak_cls_schedule >= 2:
|
|
303
|
+
for i in range(n_dims):
|
|
304
|
+
for j in range(i + 1, n_dims):
|
|
305
|
+
tasks.append(
|
|
306
|
+
(
|
|
307
|
+
[i, j],
|
|
308
|
+
X[:, [i, j]],
|
|
309
|
+
y,
|
|
310
|
+
n_records,
|
|
311
|
+
n_dims,
|
|
312
|
+
self.weak_cls_type,
|
|
313
|
+
self.weak_max_depth,
|
|
314
|
+
self.weak_min_samples_split,
|
|
315
|
+
)
|
|
316
|
+
)
|
|
317
|
+
|
|
318
|
+
if self.weak_cls_schedule >= 3:
|
|
319
|
+
for i in range(n_dims):
|
|
320
|
+
for j in range(i + 1, n_dims):
|
|
321
|
+
for k in range(j + 1, n_dims):
|
|
322
|
+
tasks.append(
|
|
323
|
+
(
|
|
324
|
+
[i, j, k],
|
|
325
|
+
X[:, [i, j, k]],
|
|
326
|
+
y,
|
|
327
|
+
n_records,
|
|
328
|
+
n_dims,
|
|
329
|
+
self.weak_cls_type,
|
|
330
|
+
self.weak_max_depth,
|
|
331
|
+
self.weak_min_samples_split,
|
|
332
|
+
)
|
|
333
|
+
)
|
|
334
|
+
|
|
335
|
+
# Parallel execution using Pool
|
|
336
|
+
with Pool(processes=num_workers) as pool:
|
|
337
|
+
results = pool.starmap(self._train_weak_classifier_mp, tasks)
|
|
338
|
+
|
|
339
|
+
pool.join()
|
|
340
|
+
pool.close()
|
|
341
|
+
|
|
342
|
+
for indices, weak_classifier in results:
|
|
343
|
+
self.ind_list.append(indices)
|
|
344
|
+
self.h_list.append(weak_classifier)
|
|
345
|
+
|
|
346
|
+
return
|
|
347
|
+
|
|
348
|
+
def _train_weak_classifier_shm(
|
|
349
|
+
self,
|
|
350
|
+
indices,
|
|
351
|
+
shm_X_name,
|
|
352
|
+
shm_y_name,
|
|
353
|
+
shared_list,
|
|
354
|
+
n_records,
|
|
355
|
+
n_dims,
|
|
356
|
+
weak_cls_type,
|
|
357
|
+
weak_max_depth,
|
|
358
|
+
weak_min_samples_split,
|
|
359
|
+
):
|
|
360
|
+
"""Train a weak classifier using shared memory."""
|
|
361
|
+
|
|
362
|
+
shm_X_worker = shared_memory.SharedMemory(name=shm_X_name)
|
|
363
|
+
shm_y_worker = shared_memory.SharedMemory(name=shm_y_name)
|
|
364
|
+
X_shared = np.ndarray(
|
|
365
|
+
(n_records, n_dims), dtype=np.float32, buffer=shm_X_worker.buf
|
|
366
|
+
)
|
|
367
|
+
y_shared = np.ndarray(
|
|
368
|
+
(n_records,), dtype=np.float32, buffer=shm_y_worker.buf
|
|
369
|
+
)
|
|
370
|
+
X_subset = X_shared[:, indices]
|
|
371
|
+
|
|
372
|
+
weak_classifier = WeakClassifier(
|
|
373
|
+
X_subset,
|
|
374
|
+
y_shared,
|
|
375
|
+
weak_cls_type,
|
|
376
|
+
weak_max_depth,
|
|
377
|
+
weak_min_samples_split,
|
|
378
|
+
)
|
|
379
|
+
weak_classifier.train()
|
|
380
|
+
|
|
381
|
+
shared_list.append((indices, weak_classifier))
|
|
382
|
+
|
|
383
|
+
shm_X_worker.close()
|
|
384
|
+
shm_y_worker.close()
|
|
385
|
+
|
|
386
|
+
@timer
|
|
387
|
+
def _build_weak_classifiers_shm(self, X, y):
|
|
388
|
+
n_records = X.shape[0]
|
|
389
|
+
n_dims = X.shape[1]
|
|
390
|
+
|
|
391
|
+
assert len(y) == n_records
|
|
392
|
+
|
|
393
|
+
self.h_list = []
|
|
394
|
+
self.ind_list = []
|
|
395
|
+
|
|
396
|
+
num_workers = self.weak_cls_num_jobs
|
|
397
|
+
print(f"Using {num_workers} workers to build weak classifiers.")
|
|
398
|
+
|
|
399
|
+
set_start_method("fork", force=True)
|
|
400
|
+
|
|
401
|
+
X = np.ascontiguousarray(X, dtype=np.float32)
|
|
402
|
+
y = np.ascontiguousarray(y, dtype=np.float32)
|
|
403
|
+
|
|
404
|
+
with SharedMemoryManager() as shm_manager:
|
|
405
|
+
shm_X = shm_manager.SharedMemory(size=X.nbytes)
|
|
406
|
+
shm_y = shm_manager.SharedMemory(size=y.nbytes)
|
|
407
|
+
|
|
408
|
+
X_shared = np.ndarray(X.shape, dtype=X.dtype, buffer=shm_X.buf)
|
|
409
|
+
y_shared = np.ndarray(y.shape, dtype=y.dtype, buffer=shm_y.buf)
|
|
410
|
+
|
|
411
|
+
np.copyto(X_shared, X)
|
|
412
|
+
np.copyto(y_shared, y)
|
|
413
|
+
|
|
414
|
+
with Manager() as manager:
|
|
415
|
+
shared_list = manager.list()
|
|
416
|
+
tasks = []
|
|
417
|
+
for l in range(n_dims):
|
|
418
|
+
tasks.append(
|
|
419
|
+
(
|
|
420
|
+
[l],
|
|
421
|
+
shm_X.name,
|
|
422
|
+
shm_y.name,
|
|
423
|
+
shared_list,
|
|
424
|
+
n_records,
|
|
425
|
+
n_dims,
|
|
426
|
+
self.weak_cls_type,
|
|
427
|
+
self.weak_max_depth,
|
|
428
|
+
self.weak_min_samples_split,
|
|
429
|
+
)
|
|
430
|
+
)
|
|
431
|
+
|
|
432
|
+
if self.weak_cls_schedule >= 2:
|
|
433
|
+
for i in range(n_dims):
|
|
434
|
+
for j in range(i + 1, n_dims):
|
|
435
|
+
tasks.append(
|
|
436
|
+
(
|
|
437
|
+
[i, j],
|
|
438
|
+
shm_X.name,
|
|
439
|
+
shm_y.name,
|
|
440
|
+
shared_list,
|
|
441
|
+
n_records,
|
|
442
|
+
n_dims,
|
|
443
|
+
self.weak_cls_type,
|
|
444
|
+
self.weak_max_depth,
|
|
445
|
+
self.weak_min_samples_split,
|
|
446
|
+
)
|
|
447
|
+
)
|
|
448
|
+
|
|
449
|
+
if self.weak_cls_schedule >= 3:
|
|
450
|
+
for i in range(n_dims):
|
|
451
|
+
for j in range(i + 1, n_dims):
|
|
452
|
+
for k in range(j + 1, n_dims):
|
|
453
|
+
tasks.append(
|
|
454
|
+
(
|
|
455
|
+
[i, j, k],
|
|
456
|
+
shm_X.name,
|
|
457
|
+
shm_y.name,
|
|
458
|
+
shared_list,
|
|
459
|
+
n_records,
|
|
460
|
+
n_dims,
|
|
461
|
+
self.weak_cls_type,
|
|
462
|
+
self.weak_max_depth,
|
|
463
|
+
self.weak_min_samples_split,
|
|
464
|
+
)
|
|
465
|
+
)
|
|
466
|
+
|
|
467
|
+
with Pool(processes=num_workers) as pool:
|
|
468
|
+
results = pool.starmap(
|
|
469
|
+
self._train_weak_classifier_shm, tasks
|
|
470
|
+
)
|
|
471
|
+
pool.close()
|
|
472
|
+
pool.join()
|
|
473
|
+
|
|
474
|
+
for item in list(shared_list):
|
|
475
|
+
self.ind_list.append(item[0])
|
|
476
|
+
self.h_list.append(item[1])
|
|
477
|
+
|
|
478
|
+
shm_X.close()
|
|
479
|
+
shm_X.unlink()
|
|
480
|
+
shm_y.close()
|
|
481
|
+
shm_y.unlink()
|
|
482
|
+
|
|
483
|
+
def _infer_one_weak_classifier(self, cls_ind, X_subset):
|
|
484
|
+
return self.h_list[cls_ind].predict(X_subset)
|
|
485
|
+
|
|
486
|
+
def _infer_weak_classifiers(self, X):
|
|
487
|
+
n_classifiers = len(self.h_list)
|
|
488
|
+
num_workers = self.weak_cls_num_jobs
|
|
489
|
+
print(f"Using {num_workers} workers for inference.")
|
|
490
|
+
|
|
491
|
+
set_start_method("fork", force=True)
|
|
492
|
+
|
|
493
|
+
tasks = []
|
|
494
|
+
for i in range(n_classifiers):
|
|
495
|
+
tasks.append((i, X[:, self.ind_list[i]]))
|
|
496
|
+
|
|
497
|
+
with Pool(processes=num_workers) as pool:
|
|
498
|
+
results = pool.starmap(self._infer_one_weak_classifier, tasks)
|
|
499
|
+
|
|
500
|
+
return list(results)
|
|
501
|
+
|
|
502
|
+
def fit(self, X, y):
|
|
503
|
+
"""
|
|
504
|
+
Build a QBoost classifier from the training set (X, y).
|
|
505
|
+
|
|
506
|
+
Parameters
|
|
507
|
+
----------
|
|
508
|
+
X : {array-like, sparse matrix} of shape (n_samples, n_features)
|
|
509
|
+
The training input samples.
|
|
510
|
+
|
|
511
|
+
y : array-like of shape (n_samples,)
|
|
512
|
+
The target values.
|
|
513
|
+
|
|
514
|
+
Returns
|
|
515
|
+
-------
|
|
516
|
+
Response of Dirac-3 in JSON format.
|
|
517
|
+
"""
|
|
518
|
+
|
|
519
|
+
assert X.shape[0] == y.shape[0], "Inconsistent sizes!"
|
|
520
|
+
|
|
521
|
+
assert set(y) == {-1, 1}, "Target values should be in {-1, 1}"
|
|
522
|
+
|
|
523
|
+
self.classes_ = set(y)
|
|
524
|
+
|
|
525
|
+
J, C, sum_constraint = self.get_hamiltonian(X, y)
|
|
526
|
+
|
|
527
|
+
assert J.shape[0] == J.shape[1], "Inconsistent hamiltonian size!"
|
|
528
|
+
assert J.shape[0] == C.shape[0], "Inconsistent hamiltonian size!"
|
|
529
|
+
|
|
530
|
+
self.set_model(J, C, sum_constraint)
|
|
531
|
+
|
|
532
|
+
sol, response = self.solve()
|
|
533
|
+
|
|
534
|
+
assert len(sol) == C.shape[0], "Inconsistent solution size!"
|
|
535
|
+
|
|
536
|
+
self.params = self.convert_sol_to_params(sol)
|
|
537
|
+
|
|
538
|
+
assert len(self.params) == len(self.h_list), "Inconsistent size!"
|
|
539
|
+
|
|
540
|
+
return response
|
|
541
|
+
|
|
542
|
+
def predict_raw(self, X: np.array):
|
|
543
|
+
"""
|
|
544
|
+
Predict raw output of the classifier for input X.
|
|
545
|
+
|
|
546
|
+
Parameters
|
|
547
|
+
----------
|
|
548
|
+
X : {array-like, sparse matrix} of shape (n_samples, n_features)
|
|
549
|
+
|
|
550
|
+
Returns
|
|
551
|
+
-------
|
|
552
|
+
y : ndarray of shape (n_samples,)
|
|
553
|
+
The predicted raw output of the classifier.
|
|
554
|
+
"""
|
|
555
|
+
|
|
556
|
+
n_records = X.shape[0]
|
|
557
|
+
n_classifiers = len(self.h_list)
|
|
558
|
+
|
|
559
|
+
y = np.zeros(shape=(n_records), dtype=np.float32)
|
|
560
|
+
h_vals = np.array(
|
|
561
|
+
[
|
|
562
|
+
self.h_list[i].predict(X[:, self.ind_list[i]])
|
|
563
|
+
for i in range(n_classifiers)
|
|
564
|
+
]
|
|
565
|
+
)
|
|
566
|
+
|
|
567
|
+
y = np.tensordot(self.params, h_vals, axes=(0, 0))
|
|
568
|
+
|
|
569
|
+
return y
|
|
570
|
+
|
|
571
|
+
def predict(self, X: np.array):
|
|
572
|
+
"""
|
|
573
|
+
Predict classes for X.
|
|
574
|
+
|
|
575
|
+
Parameters
|
|
576
|
+
----------
|
|
577
|
+
X : {array-like, sparse matrix} of shape (n_samples, n_features)
|
|
578
|
+
|
|
579
|
+
Returns
|
|
580
|
+
-------
|
|
581
|
+
y : ndarray of shape (n_samples,)
|
|
582
|
+
The predicted classes.
|
|
583
|
+
"""
|
|
584
|
+
|
|
585
|
+
y = self.predict_raw(X)
|
|
586
|
+
y = np.sign(y)
|
|
587
|
+
|
|
588
|
+
return y
|
|
589
|
+
|
|
590
|
+
@timer
|
|
591
|
+
def get_hamiltonian(
|
|
592
|
+
self,
|
|
593
|
+
X: np.array,
|
|
594
|
+
y: np.array,
|
|
595
|
+
):
|
|
596
|
+
X = np.array(X, dtype=np.float32)
|
|
597
|
+
y = np.array(y, dtype=np.float32)
|
|
598
|
+
|
|
599
|
+
if self.weak_cls_strategy == "multi_processing":
|
|
600
|
+
self._build_weak_classifiers_mp(X, y)
|
|
601
|
+
elif self.weak_cls_strategy == "multi_processing_shm":
|
|
602
|
+
self._build_weak_classifiers_shm(X, y)
|
|
603
|
+
elif self.weak_cls_strategy == "sequential":
|
|
604
|
+
self._build_weak_classifiers_sq(X, y)
|
|
605
|
+
|
|
606
|
+
print("Built %d weak classifiers!" % len(self.h_list))
|
|
607
|
+
|
|
608
|
+
n_classifiers = len(self.h_list)
|
|
609
|
+
n_records = X.shape[0]
|
|
610
|
+
h_vals = np.array(
|
|
611
|
+
[
|
|
612
|
+
self.h_list[i].predict(X[:, self.ind_list[i]])
|
|
613
|
+
for i in range(n_classifiers)
|
|
614
|
+
]
|
|
615
|
+
)
|
|
616
|
+
|
|
617
|
+
J = np.tensordot(h_vals, h_vals, axes=(1, 1))
|
|
618
|
+
J += np.diag(self.lambda_coef * np.ones((n_classifiers)))
|
|
619
|
+
C = -2.0 * np.tensordot(h_vals, y, axes=(1, 0))
|
|
620
|
+
|
|
621
|
+
# J, C = get_hamiltonian_pyx(y, h_vals, self.lambda_coef, n_records)
|
|
622
|
+
|
|
623
|
+
C = C.reshape((n_classifiers, 1))
|
|
624
|
+
|
|
625
|
+
return J, C, 1.0
|
|
626
|
+
|
|
627
|
+
def convert_sol_to_params(self, sol):
|
|
628
|
+
return np.array(sol)
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
cdef extern from *:
|
|
2
|
+
"""
|
|
3
|
+
#define NPY_NO_DEPRECATED_API NPY_1_7_API_VERSION
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
cdef extern from "stdlib.h":
|
|
7
|
+
void* malloc(size_t size)
|
|
8
|
+
void free(void* ptr)
|
|
9
|
+
|
|
10
|
+
cdef extern from "cvqboost_hamiltonian_c_func.c" nogil:
|
|
11
|
+
void get_hamiltonian_c(
|
|
12
|
+
float **J,
|
|
13
|
+
float *C,
|
|
14
|
+
float **h_vals,
|
|
15
|
+
float *y,
|
|
16
|
+
float lambda_coef,
|
|
17
|
+
int n_records,
|
|
18
|
+
int n_classifiers
|
|
19
|
+
) nogil
|
|
20
|
+
|
|
21
|
+
import os
|
|
22
|
+
os.environ["OMP_PROC_BIND"] = "close"
|
|
23
|
+
os.environ["OMP_PLACES"] = "cores"
|
|
24
|
+
|
|
25
|
+
import numpy as np
|
|
26
|
+
cimport numpy as np
|
|
27
|
+
|
|
28
|
+
def get_hamiltonian_pyx(
|
|
29
|
+
np.ndarray[np.float32_t, ndim=1, mode="c"] y,
|
|
30
|
+
np.ndarray[np.float32_t, ndim=2, mode="c"] h_vals,
|
|
31
|
+
float lambda_coef,
|
|
32
|
+
int n_records
|
|
33
|
+
):
|
|
34
|
+
cdef int n_classifiers = h_vals.shape[0]
|
|
35
|
+
|
|
36
|
+
h_vals = np.ascontiguousarray(h_vals, dtype=np.float32)
|
|
37
|
+
y = np.ascontiguousarray(y, dtype=np.float32)
|
|
38
|
+
|
|
39
|
+
# Allocate J and C as NumPy arrays
|
|
40
|
+
cdef np.ndarray[np.float32_t, ndim=2, mode="c"] J = np.zeros(
|
|
41
|
+
(n_classifiers, n_classifiers),
|
|
42
|
+
dtype=np.float32
|
|
43
|
+
)
|
|
44
|
+
cdef np.ndarray[np.float32_t, ndim=1, mode="c"] C = np.zeros(
|
|
45
|
+
(n_classifiers,),
|
|
46
|
+
dtype=np.float32
|
|
47
|
+
)
|
|
48
|
+
|
|
49
|
+
# Create a C-style array of pointers for J
|
|
50
|
+
cdef float** J_c = <float**>malloc(n_classifiers * sizeof(float*))
|
|
51
|
+
if not J_c:
|
|
52
|
+
raise MemoryError("Failed to allocate memory for J_c.")
|
|
53
|
+
|
|
54
|
+
# Create a C-style array of pointers for h_vals
|
|
55
|
+
cdef float** h_vals_c = <float**>malloc(n_classifiers * sizeof(float*))
|
|
56
|
+
if not h_vals_c:
|
|
57
|
+
free(J_c)
|
|
58
|
+
raise MemoryError("Failed to allocate memory for h_vals_c.")
|
|
59
|
+
|
|
60
|
+
with nogil:
|
|
61
|
+
for i in range(n_classifiers):
|
|
62
|
+
J_c[i] = &J[i, 0]
|
|
63
|
+
for i in range(n_classifiers):
|
|
64
|
+
h_vals_c[i] = &h_vals[i, 0]
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
# Call the C function without the GIL
|
|
68
|
+
with nogil:
|
|
69
|
+
get_hamiltonian_c(
|
|
70
|
+
J_c,
|
|
71
|
+
&C[0],
|
|
72
|
+
h_vals_c,
|
|
73
|
+
&y[0],
|
|
74
|
+
lambda_coef,
|
|
75
|
+
n_records,
|
|
76
|
+
n_classifiers
|
|
77
|
+
)
|
|
78
|
+
|
|
79
|
+
# Free allocated memory
|
|
80
|
+
free(h_vals_c)
|
|
81
|
+
free(J_c)
|
|
82
|
+
|
|
83
|
+
return J, C
|