binboost 0.2.1__tar.gz → 0.2.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {binboost-0.2.1 → binboost-0.2.2}/PKG-INFO +1 -1
- {binboost-0.2.1 → binboost-0.2.2}/binboost/__init__.py +1 -1
- {binboost-0.2.1 → binboost-0.2.2}/binboost/binboost.py +7 -7
- binboost-0.2.2/binboost/multiclass.py +78 -0
- {binboost-0.2.1 → binboost-0.2.2}/binboost/rule.py +71 -72
- {binboost-0.2.1 → binboost-0.2.2}/binboost.egg-info/PKG-INFO +1 -1
- {binboost-0.2.1 → binboost-0.2.2}/binboost.egg-info/SOURCES.txt +1 -0
- {binboost-0.2.1 → binboost-0.2.2}/pyproject.toml +1 -1
- {binboost-0.2.1 → binboost-0.2.2}/setup.py +1 -1
- {binboost-0.2.1 → binboost-0.2.2}/LICENSE +0 -0
- {binboost-0.2.1 → binboost-0.2.2}/README.md +0 -0
- {binboost-0.2.1 → binboost-0.2.2}/binboost/binarizer.py +0 -0
- {binboost-0.2.1 → binboost-0.2.2}/binboost/loss.py +0 -0
- {binboost-0.2.1 → binboost-0.2.2}/binboost.egg-info/dependency_links.txt +0 -0
- {binboost-0.2.1 → binboost-0.2.2}/binboost.egg-info/requires.txt +0 -0
- {binboost-0.2.1 → binboost-0.2.2}/binboost.egg-info/top_level.txt +0 -0
- {binboost-0.2.1 → binboost-0.2.2}/setup.cfg +0 -0
|
@@ -9,7 +9,7 @@ class BinBoost:
|
|
|
9
9
|
"""
|
|
10
10
|
BinBoost: Gradient Boosting Berbasis Aturan Logika Adaptif.
|
|
11
11
|
|
|
12
|
-
Algoritma klasifikasi biner yang membangun ensemble aturan logika
|
|
12
|
+
Algoritma klasifikasi biner yang membangun ensemble aturan logika
|
|
13
13
|
murni (AND, OR, XOR) dengan binarisasi fitur numerik adaptif
|
|
14
14
|
berbasis gradien pada setiap iterasi boosting.
|
|
15
15
|
|
|
@@ -18,7 +18,7 @@ class BinBoost:
|
|
|
18
18
|
n_estimators : int, default=100
|
|
19
19
|
Jumlah iterasi boosting.
|
|
20
20
|
|
|
21
|
-
learning_rate : float, default=0.
|
|
21
|
+
learning_rate : float, default=0.2
|
|
22
22
|
Faktor penyusutan kontribusi setiap aturan.
|
|
23
23
|
|
|
24
24
|
loss : str, default='logistic'
|
|
@@ -33,7 +33,7 @@ class BinBoost:
|
|
|
33
33
|
focal_alpha : float, default=0.25
|
|
34
34
|
Parameter alpha pada Focal Loss. Hanya berlaku saat loss='focal'.
|
|
35
35
|
|
|
36
|
-
max_rule_length : int, default=
|
|
36
|
+
max_rule_length : int, default=4
|
|
37
37
|
Jumlah maksimum fitur dalam satu aturan.
|
|
38
38
|
|
|
39
39
|
operators : list, default=['AND', 'OR']
|
|
@@ -59,7 +59,7 @@ class BinBoost:
|
|
|
59
59
|
min_samples_rule : int or float, default=5
|
|
60
60
|
Jumlah minimum sampel yang harus memenuhi sebuah aturan.
|
|
61
61
|
|
|
62
|
-
lambda0 : float, default=
|
|
62
|
+
lambda0 : float, default=3.0
|
|
63
63
|
Konstanta regularisasi dasar untuk bobot Newton adaptif (λ_R = lambda0 · L / sqrt(n_R+1)).
|
|
64
64
|
|
|
65
65
|
binarize_strategy : str, default='gradient'
|
|
@@ -122,12 +122,12 @@ class BinBoost:
|
|
|
122
122
|
def __init__(
|
|
123
123
|
self,
|
|
124
124
|
n_estimators=100,
|
|
125
|
-
learning_rate=0.
|
|
125
|
+
learning_rate=0.2,
|
|
126
126
|
loss='logistic',
|
|
127
127
|
poly_epsilon=1.0,
|
|
128
128
|
focal_gamma=2.0,
|
|
129
129
|
focal_alpha=0.25,
|
|
130
|
-
max_rule_length=
|
|
130
|
+
max_rule_length=4,
|
|
131
131
|
operators=None,
|
|
132
132
|
use_xor=False,
|
|
133
133
|
rule_complexity_penalty=0.0,
|
|
@@ -135,7 +135,7 @@ class BinBoost:
|
|
|
135
135
|
beam_width=5,
|
|
136
136
|
subsample=0.8,
|
|
137
137
|
min_samples_rule=5,
|
|
138
|
-
lambda0=
|
|
138
|
+
lambda0=3.0,
|
|
139
139
|
binarize_strategy='gradient',
|
|
140
140
|
n_thresholds='auto',
|
|
141
141
|
n_iter_no_change=None,
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# multiclass.py
|
|
2
|
+
import numpy as np
|
|
3
|
+
from binboost import BinBoost
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class BinBoostOvR:
|
|
7
|
+
def __init__(self, mode='multiclass', **binboost_params):
|
|
8
|
+
self.mode = mode
|
|
9
|
+
self.binboost_params = binboost_params
|
|
10
|
+
|
|
11
|
+
def fit(self, X, y):
|
|
12
|
+
X = np.asarray(X, dtype=np.float64)
|
|
13
|
+
y = np.asarray(y)
|
|
14
|
+
self.classes_ = np.unique(y)
|
|
15
|
+
self.n_classes_ = len(self.classes_)
|
|
16
|
+
if self.n_classes_ < 2:
|
|
17
|
+
raise ValueError("Minimal harus ada 2 kelas berbeda pada label y.")
|
|
18
|
+
|
|
19
|
+
self.estimators_ovr_ = []
|
|
20
|
+
for kelas in self.classes_:
|
|
21
|
+
y_bin = (y == kelas).astype(np.float64)
|
|
22
|
+
model_k = BinBoost(**self.binboost_params)
|
|
23
|
+
model_k.fit(X, y_bin)
|
|
24
|
+
self.estimators_ovr_.append(model_k)
|
|
25
|
+
return self
|
|
26
|
+
|
|
27
|
+
def _raw_scores(self, X):
|
|
28
|
+
return np.column_stack([m.predict_proba(X)[:, 1] for m in self.estimators_ovr_])
|
|
29
|
+
|
|
30
|
+
def predict_proba(self, X):
|
|
31
|
+
raw = self._raw_scores(X)
|
|
32
|
+
if self.mode == 'multilabel':
|
|
33
|
+
# Tidak dinormalisasi — tiap kelas independen
|
|
34
|
+
return raw
|
|
35
|
+
# mode='multiclass' — normalisasi supaya tiap baris berjumlah 1
|
|
36
|
+
total = raw.sum(axis=1, keepdims=True)
|
|
37
|
+
semua_nol = (total.flatten() <= 1e-10)
|
|
38
|
+
total_aman = np.where(total <= 1e-10, 1.0, total)
|
|
39
|
+
probs = raw / total_aman
|
|
40
|
+
if semua_nol.any():
|
|
41
|
+
probs[semua_nol] = 1.0 / self.n_classes_
|
|
42
|
+
return probs
|
|
43
|
+
|
|
44
|
+
def predict(self, X):
|
|
45
|
+
if self.mode == 'multilabel':
|
|
46
|
+
# Tiap kelas pakai threshold Youden J miliknya sendiri
|
|
47
|
+
preds = np.column_stack([m.predict(X) for m in self.estimators_ovr_])
|
|
48
|
+
return preds
|
|
49
|
+
probs = self.predict_proba(X)
|
|
50
|
+
idx = np.argmax(probs, axis=1)
|
|
51
|
+
return self.classes_[idx]
|
|
52
|
+
|
|
53
|
+
def score(self, X, y):
|
|
54
|
+
y = np.asarray(y)
|
|
55
|
+
preds = self.predict(X)
|
|
56
|
+
return np.mean(preds == y)
|
|
57
|
+
|
|
58
|
+
@property
|
|
59
|
+
def rule_summary_(self):
|
|
60
|
+
rows = []
|
|
61
|
+
for kelas, model_k in zip(self.classes_, self.estimators_ovr_):
|
|
62
|
+
for r in model_k.rule_summary_:
|
|
63
|
+
r = dict(r)
|
|
64
|
+
r['kelas'] = kelas
|
|
65
|
+
rows.append(r)
|
|
66
|
+
return rows
|
|
67
|
+
|
|
68
|
+
@property
|
|
69
|
+
def feature_usage_(self):
|
|
70
|
+
usage_total = {}
|
|
71
|
+
for model_k in self.estimators_ovr_:
|
|
72
|
+
for k, v in model_k.feature_usage_.items():
|
|
73
|
+
usage_total[k] = usage_total.get(k, 0) + v
|
|
74
|
+
return usage_total
|
|
75
|
+
|
|
76
|
+
@property
|
|
77
|
+
def n_rules_(self):
|
|
78
|
+
return sum(m.n_rules_ for m in self.estimators_ovr_)
|
|
@@ -2,22 +2,26 @@
|
|
|
2
2
|
import numpy as np
|
|
3
3
|
|
|
4
4
|
class Rule:
|
|
5
|
-
# Satu aturan logika dalam ensemble BinBoost
|
|
6
|
-
def __init__(self, features, operators, weight, feature_names=None):
|
|
7
|
-
# 1. Simpan komponen aturan
|
|
8
|
-
# a. Indeks fitur yang terlibat dalam aturan
|
|
5
|
+
# Satu aturan logika dalam ensemble BinBoost, dengan dukungan negasi (NOT) per fitur
|
|
6
|
+
def __init__(self, features, operators, weight, negations=None, feature_names=None):
|
|
9
7
|
self.features = features
|
|
10
|
-
# b. Daftar operator logika antar fitur
|
|
11
8
|
self.operators = operators
|
|
12
|
-
# c. Bobot optimal hasil perhitungan w_m*
|
|
13
9
|
self.weight = weight
|
|
10
|
+
# Daftar boolean sepanjang features: True berarti fitur itu dipakai dalam bentuk negasi (NOT)
|
|
11
|
+
self.negations = negations if negations is not None else [False] * len(features)
|
|
14
12
|
self.feature_names = feature_names
|
|
15
13
|
|
|
14
|
+
def _kolom(self, X_bin, idx_dalam_features):
|
|
15
|
+
fitur_idx = self.features[idx_dalam_features]
|
|
16
|
+
kolom = X_bin[:, fitur_idx].astype(bool)
|
|
17
|
+
if self.negations[idx_dalam_features]:
|
|
18
|
+
kolom = ~kolom
|
|
19
|
+
return kolom
|
|
20
|
+
|
|
16
21
|
def evaluate(self, X_bin):
|
|
17
|
-
|
|
18
|
-
result = X_bin[:, self.features[0]].astype(bool)
|
|
22
|
+
result = self._kolom(X_bin, 0)
|
|
19
23
|
for i, op in enumerate(self.operators):
|
|
20
|
-
next_col = X_bin
|
|
24
|
+
next_col = self._kolom(X_bin, i + 1)
|
|
21
25
|
if op == 'AND':
|
|
22
26
|
result = result & next_col
|
|
23
27
|
elif op == 'OR':
|
|
@@ -27,12 +31,12 @@ class Rule:
|
|
|
27
31
|
return result.astype(np.float64)
|
|
28
32
|
|
|
29
33
|
def to_string(self, feature_names=None):
|
|
30
|
-
# Kembalikan representasi teks aturan yang dapat dibaca manusia
|
|
31
34
|
names = feature_names if feature_names is not None else self.feature_names
|
|
32
35
|
if names is None:
|
|
33
|
-
|
|
36
|
+
base_parts = [f"X{f}" for f in self.features]
|
|
34
37
|
else:
|
|
35
|
-
|
|
38
|
+
base_parts = [str(names[f]) for f in self.features]
|
|
39
|
+
parts = [f"NOT {p}" if neg else p for p, neg in zip(base_parts, self.negations)]
|
|
36
40
|
if len(parts) == 1:
|
|
37
41
|
return parts[0]
|
|
38
42
|
expr = parts[0]
|
|
@@ -45,11 +49,9 @@ class Rule:
|
|
|
45
49
|
|
|
46
50
|
|
|
47
51
|
class BeamSearchRuleFinder:
|
|
48
|
-
# Pencari aturan terbaik menggunakan beam search tervektorisasi berbasis numpy
|
|
49
|
-
|
|
50
52
|
def __init__(self, operators, max_rule_length, beam_width,
|
|
51
53
|
feature_selection_threshold, min_samples_rule,
|
|
52
|
-
rule_complexity_penalty, ohe_groups=None):
|
|
54
|
+
rule_complexity_penalty, ohe_groups=None, allow_negation=True):
|
|
53
55
|
self.operators = operators
|
|
54
56
|
self.max_rule_length = max_rule_length
|
|
55
57
|
self.beam_width = beam_width
|
|
@@ -57,8 +59,8 @@ class BeamSearchRuleFinder:
|
|
|
57
59
|
self.min_samples_rule = min_samples_rule
|
|
58
60
|
self.rule_complexity_penalty = rule_complexity_penalty
|
|
59
61
|
self.ohe_groups = ohe_groups
|
|
62
|
+
self.allow_negation = allow_negation # False -> perilaku identik versi sebelum ini
|
|
60
63
|
|
|
61
|
-
# Bangun peta indeks fitur ke grup OHE untuk pencarian constraint yang cepat
|
|
62
64
|
self._feature_to_group = {}
|
|
63
65
|
if ohe_groups is not None:
|
|
64
66
|
for gid, members in ohe_groups.items():
|
|
@@ -66,8 +68,6 @@ class BeamSearchRuleFinder:
|
|
|
66
68
|
self._feature_to_group[f] = gid
|
|
67
69
|
|
|
68
70
|
def _select_features(self, X_bin, gradients):
|
|
69
|
-
# 1. Pilih fitur kandidat berdasarkan skor korelasi gradien secara tervektorisasi
|
|
70
|
-
# a. Hitung dot product gradien dengan semua kolom sekaligus dalam satu operasi
|
|
71
71
|
dot_products = np.abs(X_bin.T @ gradients)
|
|
72
72
|
col_sums = X_bin.sum(axis=0) + 1e-10
|
|
73
73
|
scores = dot_products / col_sums
|
|
@@ -77,29 +77,23 @@ class BeamSearchRuleFinder:
|
|
|
77
77
|
return selected
|
|
78
78
|
|
|
79
79
|
def _compute_scores_batch(self, H_batch, gradients):
|
|
80
|
-
# 1. Hitung bobot optimal dan skor untuk banyak kandidat aturan sekaligus
|
|
81
|
-
# a. H_batch berukuran (n_kandidat, n_sampel)
|
|
82
80
|
dot_gh = H_batch @ gradients
|
|
83
81
|
dot_hh = (H_batch * H_batch).sum(axis=1)
|
|
84
82
|
valid = dot_hh > 1e-10
|
|
85
83
|
w = np.where(valid, dot_gh / np.where(dot_hh > 1e-10, dot_hh, 1.0), 0.0)
|
|
86
|
-
# b. Kurangi gradien dengan prediksi berbobot untuk semua kandidat sekaligus
|
|
87
84
|
residual = gradients[np.newaxis, :] - w[:, np.newaxis] * H_batch
|
|
88
85
|
scores = (residual * residual).sum(axis=1)
|
|
89
86
|
scores = np.where(valid, scores, np.inf)
|
|
90
87
|
return w, scores, valid
|
|
91
88
|
|
|
92
89
|
def _min_samples_int(self, n_samples):
|
|
93
|
-
# Kembalikan jumlah sampel minimum dalam bentuk bilangan bulat
|
|
94
90
|
if isinstance(self.min_samples_rule, float) and self.min_samples_rule < 1.0:
|
|
95
91
|
return max(1, int(self.min_samples_rule * n_samples))
|
|
96
92
|
return int(self.min_samples_rule)
|
|
97
93
|
|
|
98
94
|
def find_best_rule(self, X_bin, gradients):
|
|
99
|
-
# 1. Jalankan beam search tervektorisasi untuk menemukan aturan terbaik
|
|
100
95
|
n_samples, n_features = X_bin.shape
|
|
101
96
|
min_s = self._min_samples_int(n_samples)
|
|
102
|
-
# Gunakan candidate features yang dikirim dari luar jika tersedia
|
|
103
97
|
if hasattr(self, 'forced_candidate_features') and self.forced_candidate_features is not None:
|
|
104
98
|
candidate_features = self.forced_candidate_features
|
|
105
99
|
else:
|
|
@@ -109,42 +103,48 @@ class BeamSearchRuleFinder:
|
|
|
109
103
|
return None
|
|
110
104
|
|
|
111
105
|
X_bin_bool = X_bin.astype(bool)
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
106
|
+
polaritas_list = [False, True] if self.allow_negation else [False]
|
|
107
|
+
|
|
108
|
+
# 1. Inisialisasi beam panjang satu — coba dua polaritas (asli & negasi) per fitur
|
|
109
|
+
H_list, meta_list = [], []
|
|
110
|
+
for cf in candidate_features:
|
|
111
|
+
kolom_pos = X_bin_bool[:, int(cf)]
|
|
112
|
+
for neg in polaritas_list:
|
|
113
|
+
kolom = ~kolom_pos if neg else kolom_pos
|
|
114
|
+
H_list.append(kolom.astype(np.float64))
|
|
115
|
+
meta_list.append((int(cf), neg))
|
|
116
|
+
|
|
117
|
+
H_init = np.array(H_list)
|
|
115
118
|
support_init = H_init.sum(axis=1)
|
|
116
119
|
valid_init = support_init >= min_s
|
|
117
|
-
|
|
118
120
|
if not valid_init.any():
|
|
119
121
|
return None
|
|
120
122
|
|
|
121
123
|
w_init, scores_init, w_valid = self._compute_scores_batch(H_init, gradients)
|
|
122
124
|
valid_mask = valid_init & w_valid
|
|
123
|
-
|
|
124
125
|
if not valid_mask.any():
|
|
125
126
|
return None
|
|
126
127
|
|
|
127
128
|
candidates = []
|
|
128
|
-
for i, cf in enumerate(
|
|
129
|
+
for i, (cf, neg) in enumerate(meta_list):
|
|
129
130
|
if valid_mask[i]:
|
|
130
131
|
pen = scores_init[i] * (1 + self.rule_complexity_penalty * 1)
|
|
131
|
-
candidates.append((pen, [
|
|
132
|
+
candidates.append((pen, [cf], [], [neg], H_init[i].astype(bool)))
|
|
132
133
|
|
|
133
134
|
if not candidates:
|
|
134
135
|
return None
|
|
135
136
|
|
|
136
137
|
candidates.sort(key=lambda x: x[0])
|
|
137
138
|
beam = candidates[:self.beam_width]
|
|
138
|
-
best_score, best_feats, best_ops, best_h = beam[0]
|
|
139
|
+
best_score, best_feats, best_ops, best_negs, best_h = beam[0]
|
|
139
140
|
|
|
140
|
-
#
|
|
141
|
+
# 2. Perluas aturan hingga panjang maksimum
|
|
141
142
|
for length in range(2, self.max_rule_length + 1):
|
|
142
143
|
new_candidates = []
|
|
143
144
|
|
|
144
|
-
for beam_score, beam_feats, beam_ops, beam_h in beam:
|
|
145
|
+
for beam_score, beam_feats, beam_ops, beam_negs, beam_h in beam:
|
|
145
146
|
beam_feat_set = set(beam_feats)
|
|
146
147
|
|
|
147
|
-
# a. Tentukan fitur yang dapat diperluas dengan mempertimbangkan constraint OHE
|
|
148
148
|
expandable = []
|
|
149
149
|
for cf in candidate_features:
|
|
150
150
|
cf_int = int(cf)
|
|
@@ -159,42 +159,42 @@ class BeamSearchRuleFinder:
|
|
|
159
159
|
if not expandable:
|
|
160
160
|
continue
|
|
161
161
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
valid_sup = support_new >= min_s
|
|
179
|
-
|
|
180
|
-
if not valid_sup.any():
|
|
181
|
-
continue
|
|
182
|
-
|
|
183
|
-
w_new, scores_new, w_valid_new = self._compute_scores_batch(
|
|
184
|
-
H_new, gradients
|
|
185
|
-
)
|
|
186
|
-
valid_combined = valid_sup & w_valid_new
|
|
162
|
+
cols_pos = X_bin_bool[:, expandable].T
|
|
163
|
+
kandidat_kolom = [(cols_pos, False)]
|
|
164
|
+
if self.allow_negation:
|
|
165
|
+
kandidat_kolom.append((~cols_pos, True))
|
|
166
|
+
|
|
167
|
+
for cols, neg_flag in kandidat_kolom:
|
|
168
|
+
for op in self.operators:
|
|
169
|
+
beam_h_2d = np.broadcast_to(beam_h, cols.shape)
|
|
170
|
+
if op == 'AND':
|
|
171
|
+
H_new = (beam_h_2d & cols).astype(np.float64)
|
|
172
|
+
elif op == 'OR':
|
|
173
|
+
H_new = (beam_h_2d | cols).astype(np.float64)
|
|
174
|
+
elif op == 'XOR':
|
|
175
|
+
H_new = (beam_h_2d ^ cols).astype(np.float64)
|
|
176
|
+
else:
|
|
177
|
+
continue
|
|
187
178
|
|
|
188
|
-
|
|
189
|
-
|
|
179
|
+
support_new = H_new.sum(axis=1)
|
|
180
|
+
valid_sup = support_new >= min_s
|
|
181
|
+
if not valid_sup.any():
|
|
190
182
|
continue
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
183
|
+
|
|
184
|
+
w_new, scores_new, w_valid_new = self._compute_scores_batch(H_new, gradients)
|
|
185
|
+
valid_combined = valid_sup & w_valid_new
|
|
186
|
+
|
|
187
|
+
for j, cf_int in enumerate(expandable):
|
|
188
|
+
if not valid_combined[j]:
|
|
189
|
+
continue
|
|
190
|
+
pen = scores_new[j] * (1 + self.rule_complexity_penalty * length)
|
|
191
|
+
new_candidates.append((
|
|
192
|
+
pen,
|
|
193
|
+
beam_feats + [cf_int],
|
|
194
|
+
beam_ops + [op],
|
|
195
|
+
beam_negs + [neg_flag],
|
|
196
|
+
H_new[j].astype(bool)
|
|
197
|
+
))
|
|
198
198
|
|
|
199
199
|
if not new_candidates:
|
|
200
200
|
break
|
|
@@ -203,16 +203,15 @@ class BeamSearchRuleFinder:
|
|
|
203
203
|
new_candidates = new_candidates[:self.beam_width]
|
|
204
204
|
|
|
205
205
|
if new_candidates[0][0] < best_score:
|
|
206
|
-
best_score, best_feats, best_ops, best_h = new_candidates[0]
|
|
206
|
+
best_score, best_feats, best_ops, best_negs, best_h = new_candidates[0]
|
|
207
207
|
beam = new_candidates
|
|
208
208
|
else:
|
|
209
209
|
break
|
|
210
210
|
|
|
211
|
-
# 4. Hitung ulang bobot optimal untuk aturan terbaik yang dipilih
|
|
212
211
|
h_final = best_h.astype(np.float64)
|
|
213
212
|
denom = np.dot(h_final, h_final)
|
|
214
213
|
if denom < 1e-10:
|
|
215
214
|
return None
|
|
216
215
|
w_final = np.dot(gradients, h_final) / denom
|
|
217
216
|
|
|
218
|
-
return Rule(best_feats, best_ops, w_final)
|
|
217
|
+
return Rule(best_feats, best_ops, w_final, negations=best_negs)
|
|
@@ -2,7 +2,7 @@ from setuptools import setup, find_packages
|
|
|
2
2
|
|
|
3
3
|
setup(
|
|
4
4
|
name="binboost",
|
|
5
|
-
version="0.2.
|
|
5
|
+
version="0.2.2",
|
|
6
6
|
author="Rangga Wahyu Pratama",
|
|
7
7
|
description="Gradient Boosting Berbasis Aturan Logika Adaptif untuk Fitur Biner",
|
|
8
8
|
long_description=open("README.md", encoding="utf-8").read(),
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|