binboost 0.1.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.
binboost-0.1.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 BinBoost Authors
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,104 @@
1
+ Metadata-Version: 2.4
2
+ Name: binboost
3
+ Version: 0.1.0
4
+ Summary: Gradient Boosting Berbasis Aturan Logika Adaptif untuk Fitur Biner
5
+ Home-page: https://github.com/username/binboost
6
+ Author: BinBoost Authors
7
+ License: MIT
8
+ Project-URL: Homepage, https://github.com/username/binboost
9
+ Keywords: gradient boosting,logical rules,binary features,interpretable machine learning
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Operating System :: OS Independent
13
+ Classifier: Intended Audience :: Science/Research
14
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
15
+ Requires-Python: >=3.8
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENSE
18
+ Requires-Dist: numpy>=1.21.0
19
+ Requires-Dist: pandas>=1.3.0
20
+ Dynamic: author
21
+ Dynamic: home-page
22
+ Dynamic: license-file
23
+ Dynamic: requires-python
24
+
25
+ # BinBoost
26
+
27
+ **Gradient Boosting Berbasis Aturan Logika Adaptif untuk Fitur Biner**
28
+
29
+ BinBoost adalah algoritma klasifikasi gradient boosting yang membangun ensemble aturan logika murni (AND, OR, XOR) dengan binarisasi fitur numerik adaptif berbasis gradien pada setiap iterasi boosting. Setiap weak learner berupa aturan yang dapat dibaca langsung oleh manusia tanpa memerlukan alat bantu penjelasan pasca-pelatihan.
30
+
31
+ ## Kebaruan Utama
32
+
33
+ - **Binarisasi adaptif berbasis gradien**: nilai ambang batas fitur numerik dicari per iterasi untuk memaksimalkan korelasi dengan gradien saat ini
34
+ - **Ensemble aturan logika murni**: tidak ada pohon keputusan, setiap weak learner adalah aturan seperti `(A AND B)` atau `(C OR D)`
35
+
36
+ ## Instalasi
37
+
38
+ ```bash
39
+ pip install binboost
40
+ ```
41
+
42
+ ## Penggunaan Dasar
43
+
44
+ ```python
45
+ import numpy as np
46
+ from binboost import BinBoost
47
+
48
+ X = np.array([[1, 0, 1], [0, 1, 0], [1, 1, 0], [0, 0, 1]], dtype=float)
49
+ y = np.array([1, 0, 1, 0])
50
+
51
+ model = BinBoost(n_estimators=50, learning_rate=0.1, max_rule_length=2)
52
+ model.fit(X, y)
53
+
54
+ print(model.predict(X))
55
+ print(model.predict_proba(X))
56
+ print(model.rules_)
57
+
58
+ import pandas as pd
59
+ print(pd.DataFrame(model.rule_summary_))
60
+ ```
61
+
62
+ ## Hyperparameter Utama
63
+
64
+ | Parameter | Bawaan | Keterangan |
65
+ |---|---|---|
66
+ | `n_estimators` | 100 | Jumlah iterasi boosting |
67
+ | `learning_rate` | 0.1 | Faktor penyusutan tiap aturan |
68
+ | `loss` | `'logistic'` | Fungsi loss: `'logistic'`, `'focal'`, `'poly'` |
69
+ | `max_rule_length` | 2 | Jumlah maksimum fitur dalam satu aturan |
70
+ | `operators` | `['AND','OR']` | Operator logika yang digunakan |
71
+ | `beam_width` | 5 | Lebar beam search |
72
+ | `binarize_strategy` | `'gradient'` | Strategi binarisasi: `'gradient'`, `'quantile'`, `'uniform'`, `'kmeans'` |
73
+ | `subsample` | 0.8 | Fraksi data per iterasi |
74
+ | `feature_selection_threshold` | 0.01 | Ambang batas seleksi fitur berbasis gradien |
75
+
76
+ ## Fitur yang Didukung
77
+
78
+ - Fitur biner (0/1): langsung diproses
79
+ - Fitur numerik (int/float): dibinarisasi otomatis
80
+ - Fitur kategorikal 3+ kelas: wajib OneHotEncode terlebih dahulu
81
+
82
+ ```python
83
+ from sklearn.preprocessing import OneHotEncoder
84
+ enc = OneHotEncoder(sparse_output=False, drop='first')
85
+ X_encoded = enc.fit_transform(X[['kolom_kategorikal']])
86
+ ```
87
+
88
+ BinBoost secara otomatis mendeteksi kelompok fitur OneHotEncoding dan mencegah aturan yang tidak masuk akal seperti `(Warna_Merah AND Warna_Biru)`.
89
+
90
+ ## Atribut Setelah Pelatihan
91
+
92
+ ```python
93
+ model.rules_ # daftar teks aturan
94
+ model.rule_weights_ # bobot setiap aturan
95
+ model.feature_importances_ # skor kepentingan fitur
96
+ model.train_score_ # loss per iterasi
97
+ model.rule_summary_ # ringkasan lengkap atau konversi ke DataFrame
98
+ model.n_rules_ # jumlah aturan aktif
99
+ model.feature_usage_ # frekuensi penggunaan tiap fitur
100
+ ```
101
+
102
+ ## Lisensi
103
+
104
+ MIT
@@ -0,0 +1,80 @@
1
+ # BinBoost
2
+
3
+ **Gradient Boosting Berbasis Aturan Logika Adaptif untuk Fitur Biner**
4
+
5
+ BinBoost adalah algoritma klasifikasi gradient boosting yang membangun ensemble aturan logika murni (AND, OR, XOR) dengan binarisasi fitur numerik adaptif berbasis gradien pada setiap iterasi boosting. Setiap weak learner berupa aturan yang dapat dibaca langsung oleh manusia tanpa memerlukan alat bantu penjelasan pasca-pelatihan.
6
+
7
+ ## Kebaruan Utama
8
+
9
+ - **Binarisasi adaptif berbasis gradien**: nilai ambang batas fitur numerik dicari per iterasi untuk memaksimalkan korelasi dengan gradien saat ini
10
+ - **Ensemble aturan logika murni**: tidak ada pohon keputusan, setiap weak learner adalah aturan seperti `(A AND B)` atau `(C OR D)`
11
+
12
+ ## Instalasi
13
+
14
+ ```bash
15
+ pip install binboost
16
+ ```
17
+
18
+ ## Penggunaan Dasar
19
+
20
+ ```python
21
+ import numpy as np
22
+ from binboost import BinBoost
23
+
24
+ X = np.array([[1, 0, 1], [0, 1, 0], [1, 1, 0], [0, 0, 1]], dtype=float)
25
+ y = np.array([1, 0, 1, 0])
26
+
27
+ model = BinBoost(n_estimators=50, learning_rate=0.1, max_rule_length=2)
28
+ model.fit(X, y)
29
+
30
+ print(model.predict(X))
31
+ print(model.predict_proba(X))
32
+ print(model.rules_)
33
+
34
+ import pandas as pd
35
+ print(pd.DataFrame(model.rule_summary_))
36
+ ```
37
+
38
+ ## Hyperparameter Utama
39
+
40
+ | Parameter | Bawaan | Keterangan |
41
+ |---|---|---|
42
+ | `n_estimators` | 100 | Jumlah iterasi boosting |
43
+ | `learning_rate` | 0.1 | Faktor penyusutan tiap aturan |
44
+ | `loss` | `'logistic'` | Fungsi loss: `'logistic'`, `'focal'`, `'poly'` |
45
+ | `max_rule_length` | 2 | Jumlah maksimum fitur dalam satu aturan |
46
+ | `operators` | `['AND','OR']` | Operator logika yang digunakan |
47
+ | `beam_width` | 5 | Lebar beam search |
48
+ | `binarize_strategy` | `'gradient'` | Strategi binarisasi: `'gradient'`, `'quantile'`, `'uniform'`, `'kmeans'` |
49
+ | `subsample` | 0.8 | Fraksi data per iterasi |
50
+ | `feature_selection_threshold` | 0.01 | Ambang batas seleksi fitur berbasis gradien |
51
+
52
+ ## Fitur yang Didukung
53
+
54
+ - Fitur biner (0/1): langsung diproses
55
+ - Fitur numerik (int/float): dibinarisasi otomatis
56
+ - Fitur kategorikal 3+ kelas: wajib OneHotEncode terlebih dahulu
57
+
58
+ ```python
59
+ from sklearn.preprocessing import OneHotEncoder
60
+ enc = OneHotEncoder(sparse_output=False, drop='first')
61
+ X_encoded = enc.fit_transform(X[['kolom_kategorikal']])
62
+ ```
63
+
64
+ BinBoost secara otomatis mendeteksi kelompok fitur OneHotEncoding dan mencegah aturan yang tidak masuk akal seperti `(Warna_Merah AND Warna_Biru)`.
65
+
66
+ ## Atribut Setelah Pelatihan
67
+
68
+ ```python
69
+ model.rules_ # daftar teks aturan
70
+ model.rule_weights_ # bobot setiap aturan
71
+ model.feature_importances_ # skor kepentingan fitur
72
+ model.train_score_ # loss per iterasi
73
+ model.rule_summary_ # ringkasan lengkap atau konversi ke DataFrame
74
+ model.n_rules_ # jumlah aturan aktif
75
+ model.feature_usage_ # frekuensi penggunaan tiap fitur
76
+ ```
77
+
78
+ ## Lisensi
79
+
80
+ MIT
@@ -0,0 +1,5 @@
1
+ from .binboost import BinBoost
2
+
3
+ __version__ = "0.1.0"
4
+ __author__ = "BinBoost Authors"
5
+ __all__ = ["BinBoost"]
@@ -0,0 +1,95 @@
1
+ import numpy as np
2
+
3
+
4
+ class GradientBinarizer:
5
+ # Binarisasi fitur numerik berbasis korelasi dengan gradien saat ini
6
+
7
+ def __init__(self, strategy='gradient', n_thresholds='auto'):
8
+ self.strategy = strategy
9
+ self.n_thresholds = n_thresholds
10
+ # Ambang batas tetap per fitur disimpan setelah fit pertama untuk strategi non-gradien
11
+ self._fixed_thresholds = {}
12
+
13
+ def _get_candidates(self, values):
14
+ # Kembalikan daftar kandidat ambang batas untuk satu fitur
15
+ unique = np.unique(values)
16
+ if len(unique) <= 1:
17
+ return np.array([])
18
+ if self.n_thresholds == 'auto':
19
+ max_t = 256
20
+ else:
21
+ max_t = int(self.n_thresholds)
22
+ if len(unique) - 1 <= max_t:
23
+ return (unique[:-1] + unique[1:]) / 2
24
+ idx = np.linspace(0, len(unique) - 2, max_t, dtype=int)
25
+ return (unique[idx] + unique[idx + 1]) / 2
26
+
27
+ def _best_threshold_gradient(self, values, gradients):
28
+ # 1. Cari ambang batas t* yang memaksimalkan korelasi dengan gradien
29
+ candidates = self._get_candidates(values)
30
+ if len(candidates) == 0:
31
+ return 0.0, np.zeros(len(values), dtype=np.float64)
32
+ best_t = candidates[0]
33
+ best_score = -np.inf
34
+ for t in candidates:
35
+ binary = (values > t).astype(np.float64)
36
+ score = np.abs(np.dot(gradients, binary))
37
+ if score > best_score:
38
+ best_score = score
39
+ best_t = t
40
+ return best_t, (values > best_t).astype(np.float64)
41
+
42
+ def _best_threshold_quantile(self, col_idx, values):
43
+ # 1. Gunakan ambang batas tetap dari kuantil distribusi data
44
+ if col_idx not in self._fixed_thresholds:
45
+ candidates = self._get_candidates(values)
46
+ if len(candidates) == 0:
47
+ self._fixed_thresholds[col_idx] = 0.0
48
+ else:
49
+ self._fixed_thresholds[col_idx] = np.median(candidates)
50
+ t = self._fixed_thresholds[col_idx]
51
+ return t, (values > t).astype(np.float64)
52
+
53
+ def _best_threshold_uniform(self, col_idx, values):
54
+ # 1. Gunakan ambang batas tengah dari rentang nilai fitur
55
+ if col_idx not in self._fixed_thresholds:
56
+ self._fixed_thresholds[col_idx] = (values.min() + values.max()) / 2
57
+ t = self._fixed_thresholds[col_idx]
58
+ return t, (values > t).astype(np.float64)
59
+
60
+ def _best_threshold_kmeans(self, col_idx, values):
61
+ # 1. Gunakan titik tengah dua centroid K-Means satu dimensi sebagai ambang batas
62
+ if col_idx not in self._fixed_thresholds:
63
+ v = values.reshape(-1, 1)
64
+ c1 = np.percentile(values, 25)
65
+ c2 = np.percentile(values, 75)
66
+ for _ in range(100):
67
+ labels = (np.abs(values - c1) > np.abs(values - c2)).astype(int)
68
+ new_c1 = values[labels == 0].mean() if (labels == 0).any() else c1
69
+ new_c2 = values[labels == 1].mean() if (labels == 1).any() else c2
70
+ if np.abs(new_c1 - c1) < 1e-8 and np.abs(new_c2 - c2) < 1e-8:
71
+ break
72
+ c1, c2 = new_c1, new_c2
73
+ self._fixed_thresholds[col_idx] = (c1 + c2) / 2
74
+ t = self._fixed_thresholds[col_idx]
75
+ return t, (values > t).astype(np.float64)
76
+
77
+ def transform(self, X, gradients, numeric_cols):
78
+ # Binarisasi semua fitur numerik dan kembalikan matriks biner serta ambang batas iterasi ini
79
+ X_bin = X.copy().astype(np.float64)
80
+ thresholds_this_iter = {}
81
+ for col_idx in numeric_cols:
82
+ values = X[:, col_idx].astype(np.float64)
83
+ if self.strategy == 'gradient':
84
+ t, binary = self._best_threshold_gradient(values, gradients)
85
+ elif self.strategy == 'quantile':
86
+ t, binary = self._best_threshold_quantile(col_idx, values)
87
+ elif self.strategy == 'uniform':
88
+ t, binary = self._best_threshold_uniform(col_idx, values)
89
+ elif self.strategy == 'kmeans':
90
+ t, binary = self._best_threshold_kmeans(col_idx, values)
91
+ else:
92
+ raise ValueError(f"Strategi '{self.strategy}' tidak dikenali.")
93
+ X_bin[:, col_idx] = binary
94
+ thresholds_this_iter[col_idx] = t
95
+ return X_bin, thresholds_this_iter
@@ -0,0 +1,568 @@
1
+ import numpy as np
2
+
3
+ from .loss import get_loss, sigmoid
4
+ from .binarizer import GradientBinarizer
5
+ from .rule import Rule, BeamSearchRuleFinder
6
+
7
+
8
+ class BinBoost:
9
+ """
10
+ BinBoost: Gradient Boosting Berbasis Aturan Logika Adaptif.
11
+
12
+ Algoritma klasifikasi biner yang membangun ensemble aturan logika
13
+ murni (AND, OR, XOR) dengan binarisasi fitur numerik adaptif
14
+ berbasis gradien pada setiap iterasi boosting.
15
+
16
+ Parameter
17
+ ----------
18
+ n_estimators : int, default=100
19
+ Jumlah iterasi boosting.
20
+
21
+ learning_rate : float, default=0.1
22
+ Faktor penyusutan kontribusi setiap aturan.
23
+
24
+ loss : str, default='logistic'
25
+ Fungsi loss. Pilihan: 'logistic', 'focal', 'poly'.
26
+
27
+ poly_epsilon : float, default=1.0
28
+ Koefisien epsilon pada PolyLoss. Hanya berlaku saat loss='poly'.
29
+
30
+ focal_gamma : float, default=2.0
31
+ Parameter gamma pada Focal Loss. Hanya berlaku saat loss='focal'.
32
+
33
+ focal_alpha : float, default=0.25
34
+ Parameter alpha pada Focal Loss. Hanya berlaku saat loss='focal'.
35
+
36
+ max_rule_length : int, default=2
37
+ Jumlah maksimum fitur dalam satu aturan.
38
+
39
+ operators : list, default=['AND', 'OR']
40
+ Daftar operator logika yang digunakan.
41
+
42
+ use_xor : bool, default=False
43
+ Jika True maka XOR ditambahkan ke daftar operators.
44
+
45
+ rule_complexity_penalty : float, default=0.0
46
+ Penalti bobot proporsional dengan panjang aturan.
47
+
48
+ feature_selection_threshold : float, default=0.01
49
+ Ambang batas skor korelasi gradien minimum agar fitur masuk kandidat.
50
+
51
+ beam_width : int, default=5
52
+ Jumlah kandidat aturan terbaik yang dipertahankan per langkah beam search.
53
+
54
+ subsample : float, default=0.8
55
+ Fraksi data yang digunakan per iterasi boosting.
56
+
57
+ min_samples_rule : int or float, default=10
58
+ Jumlah minimum sampel yang harus memenuhi sebuah aturan.
59
+
60
+ binarize_strategy : str, default='gradient'
61
+ Strategi binarisasi fitur numerik. Pilihan: 'gradient', 'quantile',
62
+ 'uniform', 'kmeans'.
63
+
64
+ n_thresholds : int or str, default='auto'
65
+ Jumlah kandidat ambang batas per fitur numerik.
66
+
67
+ n_iter_no_change : int or None, default=None
68
+ Jumlah iterasi tanpa peningkatan sebelum pelatihan dihentikan lebih awal.
69
+
70
+ tol : float, default=1e-4
71
+ Ambang batas peningkatan minimum untuk early stopping.
72
+
73
+ random_state : int or None, default=None
74
+ Nilai acak untuk reprodusibilitas.
75
+
76
+ warm_start : bool, default=False
77
+ Jika True maka pelatihan dilanjutkan dari kondisi model sebelumnya.
78
+
79
+ Atribut yang tersedia Setelah fit
80
+ --------------------------------
81
+ estimators_ : list of Rule
82
+ Daftar aturan yang dipelajari.
83
+
84
+ rule_weights_ : ndarray of float
85
+ Bobot optimal setiap aturan.
86
+
87
+ rules_ : list of str
88
+ Representasi teks setiap aturan.
89
+
90
+ feature_importances_ : ndarray of float
91
+ Skor kepentingan setiap fitur.
92
+
93
+ thresholds_ : dict
94
+ Ambang batas optimal per fitur numerik per iterasi.
95
+
96
+ train_score_ : ndarray of float
97
+ Nilai loss per iterasi selama pelatihan.
98
+
99
+ n_features_in_ : int
100
+ Jumlah fitur saat fit dipanggil.
101
+
102
+ feature_names_in_ : ndarray of str or None
103
+ Nama fitur jika input berupa DataFrame.
104
+
105
+ n_estimators_ : int
106
+ Jumlah iterasi aktual setelah pelatihan.
107
+
108
+ ohe_groups_ : dict or None
109
+ Pemetaan kelompok fitur hasil OneHotEncoding.
110
+ """
111
+
112
+ def __init__(
113
+ self,
114
+ n_estimators=100,
115
+ learning_rate=0.1,
116
+ loss='logistic',
117
+ poly_epsilon=1.0,
118
+ focal_gamma=2.0,
119
+ focal_alpha=0.25,
120
+ max_rule_length=2,
121
+ operators=None,
122
+ use_xor=False,
123
+ rule_complexity_penalty=0.0,
124
+ feature_selection_threshold=0.01,
125
+ beam_width=5,
126
+ subsample=0.8,
127
+ min_samples_rule=10,
128
+ binarize_strategy='gradient',
129
+ n_thresholds='auto',
130
+ n_iter_no_change=None,
131
+ tol=1e-4,
132
+ random_state=None,
133
+ warm_start=False,
134
+ ):
135
+ self.n_estimators = n_estimators
136
+ self.learning_rate = learning_rate
137
+ self.loss = loss
138
+ self.poly_epsilon = poly_epsilon
139
+ self.focal_gamma = focal_gamma
140
+ self.focal_alpha = focal_alpha
141
+ self.max_rule_length = max_rule_length
142
+ self.operators = operators if operators is not None else ['AND', 'OR']
143
+ self.use_xor = use_xor
144
+ self.rule_complexity_penalty = rule_complexity_penalty
145
+ self.feature_selection_threshold = feature_selection_threshold
146
+ self.beam_width = beam_width
147
+ self.subsample = subsample
148
+ self.min_samples_rule = min_samples_rule
149
+ self.binarize_strategy = binarize_strategy
150
+ self.n_thresholds = n_thresholds
151
+ self.n_iter_no_change = n_iter_no_change
152
+ self.tol = tol
153
+ self.random_state = random_state
154
+ self.warm_start = warm_start
155
+
156
+ def _validate_input(self, X, y=None):
157
+ # Ubah input ke numpy array dan pastikan dimensinya benar
158
+ if hasattr(X, 'values'):
159
+ if hasattr(X, 'columns'):
160
+ self._input_feature_names = list(X.columns)
161
+ X = X.values
162
+ X = np.array(X, dtype=np.float64)
163
+ if X.ndim != 2:
164
+ raise ValueError("X harus berupa matriks dua dimensi.")
165
+ if y is not None:
166
+ y = np.array(y, dtype=np.float64)
167
+ unique_y = np.unique(y)
168
+ if not np.all(np.isin(unique_y, [0, 1])):
169
+ raise ValueError("Label y harus bernilai 0 atau 1.")
170
+ if len(y) != X.shape[0]:
171
+ raise ValueError("Jumlah baris X dan y harus sama.")
172
+ return X, y
173
+
174
+ def _detect_numeric_cols(self, X):
175
+ # Identifikasi kolom fitur yang bukan biner murni sebagai fitur numerik
176
+ numeric = []
177
+ for col in range(X.shape[1]):
178
+ unique_vals = np.unique(X[:, col])
179
+ if not np.all(np.isin(unique_vals, [0.0, 1.0])):
180
+ numeric.append(col)
181
+ return numeric
182
+
183
+ def _detect_ohe_groups(self, X):
184
+ # 1. Deteksi kelompok fitur OneHotEncoding berdasarkan sifat mutually exclusive
185
+ n_features = X.shape[1]
186
+ groups = {}
187
+ used = set()
188
+ group_id = 0
189
+ for i in range(n_features):
190
+ if i in used:
191
+ continue
192
+ group = [i]
193
+ for j in range(i + 1, n_features):
194
+ if j in used:
195
+ continue
196
+ # a. Dua fitur bersifat mutually exclusive jika tidak pernah bernilai 1 bersamaan
197
+ both_one = np.sum((X[:, i] == 1) & (X[:, j] == 1))
198
+ if both_one == 0:
199
+ # b. Pastikan keduanya memang fitur biner
200
+ if np.all(np.isin(np.unique(X[:, i]), [0.0, 1.0])) and \
201
+ np.all(np.isin(np.unique(X[:, j]), [0.0, 1.0])):
202
+ group.append(j)
203
+ used.add(j)
204
+ if len(group) > 1:
205
+ groups[group_id] = group
206
+ group_id += 1
207
+ used.add(i)
208
+ return groups if groups else None
209
+
210
+ def _subsample_indices(self, n_samples, rng):
211
+ # Kembalikan indeks subsample acak tanpa pengembalian
212
+ if self.subsample >= 1.0:
213
+ return np.arange(n_samples)
214
+ n_sub = max(1, int(n_samples * self.subsample))
215
+ return rng.choice(n_samples, size=n_sub, replace=False)
216
+
217
+ def _build_operators(self):
218
+ # Gabungkan daftar operator dan tambahkan XOR jika use_xor aktif
219
+ ops = list(self.operators)
220
+ if self.use_xor and 'XOR' not in ops:
221
+ ops.append('XOR')
222
+ return ops
223
+
224
+ def fit(self, X, y, sample_weight=None):
225
+ """
226
+ Latih BinBoost pada data X dan label y.
227
+
228
+ Parameter
229
+ ----------
230
+ X : array-like of shape (n_samples, n_features)
231
+ y : array-like of shape (n_samples,), nilai 0 atau 1
232
+ sample_weight : array-like of shape (n_samples,), opsional
233
+
234
+ Kembalian
235
+ ----------
236
+ self
237
+ """
238
+ self._input_feature_names = None
239
+ X, y = self._validate_input(X, y)
240
+ n_samples, n_features = X.shape
241
+
242
+ self.n_features_in_ = n_features
243
+ self.feature_names_in_ = (
244
+ np.array(self._input_feature_names)
245
+ if self._input_feature_names is not None else None
246
+ )
247
+
248
+ rng = np.random.RandomState(self.random_state)
249
+ ops = self._build_operators()
250
+
251
+ loss_fn = get_loss(
252
+ self.loss,
253
+ poly_epsilon=self.poly_epsilon,
254
+ focal_gamma=self.focal_gamma,
255
+ focal_alpha=self.focal_alpha,
256
+ )
257
+
258
+ numeric_cols = self._detect_numeric_cols(X)
259
+ self.ohe_groups_ = self._detect_ohe_groups(X)
260
+
261
+ binarizer = GradientBinarizer(
262
+ strategy=self.binarize_strategy,
263
+ n_thresholds=self.n_thresholds
264
+ )
265
+
266
+ rule_finder = BeamSearchRuleFinder(
267
+ operators=ops,
268
+ max_rule_length=self.max_rule_length,
269
+ beam_width=self.beam_width,
270
+ feature_selection_threshold=self.feature_selection_threshold,
271
+ min_samples_rule=self.min_samples_rule,
272
+ rule_complexity_penalty=self.rule_complexity_penalty,
273
+ ohe_groups=self.ohe_groups_,
274
+ )
275
+
276
+ if not self.warm_start or not hasattr(self, 'estimators_'):
277
+ self.estimators_ = []
278
+ self.rule_weights_ = []
279
+ self.thresholds_ = {col: [] for col in numeric_cols}
280
+ self.train_score_ = []
281
+ self._F = loss_fn.init_F(y)
282
+
283
+ no_improve_count = 0
284
+ best_score = np.inf
285
+
286
+ for m in range(self.n_estimators):
287
+ # 1. Hitung gradien dari fungsi loss yang dipilih
288
+ gradients = loss_fn.gradient(y, self._F)
289
+
290
+ # 2. Ambil subsample data untuk iterasi ini
291
+ idx = self._subsample_indices(n_samples, rng)
292
+ X_sub = X[idx]
293
+ g_sub = gradients[idx]
294
+
295
+ # 3. Binarisasi fitur numerik menggunakan strategi yang dipilih
296
+ X_bin, thresholds_iter = binarizer.transform(X_sub, g_sub, numeric_cols)
297
+
298
+ # 4. Cari aturan terbaik menggunakan beam search
299
+ rule = rule_finder.find_best_rule(X_bin, g_sub)
300
+ if rule is None:
301
+ break
302
+
303
+ rule.feature_names = (
304
+ list(self.feature_names_in_) if self.feature_names_in_ is not None else None
305
+ )
306
+
307
+ # 5. Perbarui prediksi model untuk seluruh data
308
+ X_bin_full, thresholds_full = binarizer.transform(X, gradients, numeric_cols)
309
+ h_full = rule.evaluate(X_bin_full)
310
+ self._F = self._F + self.learning_rate * rule.weight * h_full
311
+
312
+ # 6. Simpan aturan dan statistik iterasi ini
313
+ self.estimators_.append(rule)
314
+ self.rule_weights_.append(rule.weight)
315
+ for col, t in thresholds_full.items():
316
+ self.thresholds_[col].append(t)
317
+
318
+ current_score = loss_fn.loss(y, self._F)
319
+ self.train_score_.append(current_score)
320
+
321
+ # 7. Periksa kondisi early stopping jika diaktifkan
322
+ if self.n_iter_no_change is not None:
323
+ if current_score < best_score - self.tol:
324
+ best_score = current_score
325
+ no_improve_count = 0
326
+ else:
327
+ no_improve_count += 1
328
+ if no_improve_count >= self.n_iter_no_change:
329
+ break
330
+
331
+ self.n_estimators_ = len(self.estimators_)
332
+ self.train_score_ = np.array(self.train_score_)
333
+ self.rule_weights_ = np.array(self.rule_weights_)
334
+ self.rules_ = [r.to_string(self.feature_names_in_) for r in self.estimators_]
335
+ self._compute_feature_importances(n_features)
336
+ self.is_fitted_ = True
337
+ return self
338
+
339
+ def _compute_feature_importances(self, n_features):
340
+ # Hitung skor kepentingan fitur dari frekuensi kemunculan dikali rata-rata bobot absolut
341
+ importances = np.zeros(n_features)
342
+ for rule in self.estimators_:
343
+ gain = np.abs(rule.weight)
344
+ for f in rule.features:
345
+ importances[f] += gain
346
+ total = importances.sum()
347
+ self.feature_importances_ = importances / total if total > 0 else importances
348
+
349
+ def _check_is_fitted(self):
350
+ if not getattr(self, 'is_fitted_', False):
351
+ raise RuntimeError("Model belum dilatih. Panggil fit terlebih dahulu.")
352
+
353
+ def _decision_function(self, X):
354
+ # Hitung nilai F akhir untuk seluruh sampel
355
+ self._check_is_fitted()
356
+ X, _ = self._validate_input(X)
357
+ F = np.zeros(X.shape[0])
358
+ binarizer = GradientBinarizer(
359
+ strategy=self.binarize_strategy,
360
+ n_thresholds=self.n_thresholds
361
+ )
362
+ numeric_cols = self._detect_numeric_cols(X)
363
+ dummy_grad = np.ones(X.shape[0])
364
+ X_bin, _ = binarizer.transform(X, dummy_grad, numeric_cols)
365
+ for rule, w in zip(self.estimators_, self.rule_weights_):
366
+ h = rule.evaluate(X_bin)
367
+ F += self.learning_rate * w * h
368
+ return F
369
+
370
+ def predict_proba(self, X):
371
+ """
372
+ Kembalikan probabilitas kelas untuk setiap sampel.
373
+
374
+ Kembalian
375
+ ----------
376
+ ndarray of shape (n_samples, 2)
377
+ """
378
+ F = self._decision_function(X)
379
+ p = sigmoid(F)
380
+ return np.column_stack([1 - p, p])
381
+
382
+ def predict_log_proba(self, X):
383
+ """
384
+ Kembalikan logaritma probabilitas kelas untuk setiap sampel.
385
+
386
+ Kembalian
387
+ ----------
388
+ ndarray of shape (n_samples, 2)
389
+ """
390
+ return np.log(np.clip(self.predict_proba(X), 1e-15, None))
391
+
392
+ def predict(self, X):
393
+ """
394
+ Prediksi label kelas (0 atau 1) untuk setiap sampel.
395
+
396
+ Kembalian
397
+ ----------
398
+ ndarray of shape (n_samples,)
399
+ """
400
+ proba = self.predict_proba(X)
401
+ return (proba[:, 1] >= 0.5).astype(int)
402
+
403
+ def score(self, X, y):
404
+ """
405
+ Hitung akurasi klasifikasi pada data X dan label y.
406
+
407
+ Kembalian
408
+ ----------
409
+ float
410
+ """
411
+ _, y = self._validate_input(X, y)
412
+ preds = self.predict(X)
413
+ return np.mean(preds == y)
414
+
415
+ def staged_predict_proba(self, X):
416
+ """
417
+ Generator probabilitas per iterasi boosting.
418
+
419
+ Hasil
420
+ ----------
421
+ ndarray of shape (n_samples, 2) per iterasi
422
+ """
423
+ self._check_is_fitted()
424
+ X, _ = self._validate_input(X)
425
+ binarizer = GradientBinarizer(
426
+ strategy=self.binarize_strategy,
427
+ n_thresholds=self.n_thresholds
428
+ )
429
+ numeric_cols = self._detect_numeric_cols(X)
430
+ dummy_grad = np.ones(X.shape[0])
431
+ X_bin, _ = binarizer.transform(X, dummy_grad, numeric_cols)
432
+ F = np.zeros(X.shape[0])
433
+ for rule, w in zip(self.estimators_, self.rule_weights_):
434
+ h = rule.evaluate(X_bin)
435
+ F += self.learning_rate * w * h
436
+ p = sigmoid(F)
437
+ yield np.column_stack([1 - p, p])
438
+
439
+ def staged_predict(self, X):
440
+ """
441
+ Generator prediksi kelas per iterasi boosting.
442
+
443
+ Hasil
444
+ ----------
445
+ ndarray of shape (n_samples,) per iterasi
446
+ """
447
+ for proba in self.staged_predict_proba(X):
448
+ yield (proba[:, 1] >= 0.5).astype(int)
449
+
450
+ def apply(self, X):
451
+ """
452
+ Kembalikan nilai aktivasi setiap aturan untuk setiap sampel.
453
+
454
+ Kembalian
455
+ ----------
456
+ ndarray of shape (n_samples, n_estimators_)
457
+ """
458
+ self._check_is_fitted()
459
+ X, _ = self._validate_input(X)
460
+ binarizer = GradientBinarizer(
461
+ strategy=self.binarize_strategy,
462
+ n_thresholds=self.n_thresholds
463
+ )
464
+ numeric_cols = self._detect_numeric_cols(X)
465
+ dummy_grad = np.ones(X.shape[0])
466
+ X_bin, _ = binarizer.transform(X, dummy_grad, numeric_cols)
467
+ results = []
468
+ for rule in self.estimators_:
469
+ results.append(rule.evaluate(X_bin))
470
+ return np.column_stack(results)
471
+
472
+ def get_params(self, deep=True):
473
+ """
474
+ Kembalikan semua hyperparameter sebagai kamus.
475
+
476
+ Kembalian
477
+ ----------
478
+ dict
479
+ """
480
+ return {
481
+ 'n_estimators': self.n_estimators,
482
+ 'learning_rate': self.learning_rate,
483
+ 'loss': self.loss,
484
+ 'poly_epsilon': self.poly_epsilon,
485
+ 'focal_gamma': self.focal_gamma,
486
+ 'focal_alpha': self.focal_alpha,
487
+ 'max_rule_length': self.max_rule_length,
488
+ 'operators': self.operators,
489
+ 'use_xor': self.use_xor,
490
+ 'rule_complexity_penalty': self.rule_complexity_penalty,
491
+ 'feature_selection_threshold': self.feature_selection_threshold,
492
+ 'beam_width': self.beam_width,
493
+ 'subsample': self.subsample,
494
+ 'min_samples_rule': self.min_samples_rule,
495
+ 'binarize_strategy': self.binarize_strategy,
496
+ 'n_thresholds': self.n_thresholds,
497
+ 'n_iter_no_change': self.n_iter_no_change,
498
+ 'tol': self.tol,
499
+ 'random_state': self.random_state,
500
+ 'warm_start': self.warm_start,
501
+ }
502
+
503
+ def set_params(self, **params):
504
+ """
505
+ Tetapkan nilai hyperparameter.
506
+
507
+ Kembalian
508
+ ----------
509
+ self
510
+ """
511
+ for key, val in params.items():
512
+ if not hasattr(self, key):
513
+ raise ValueError(f"Parameter '{key}' tidak dikenali.")
514
+ setattr(self, key, val)
515
+ return self
516
+
517
+ @property
518
+ def rule_summary_(self):
519
+ """
520
+ Tabel ringkasan semua aturan beserta bobot, cakupan, dan dukungan.
521
+
522
+ Kembalian
523
+ ----------
524
+ list of dict yang dapat dikonversi ke DataFrame
525
+ """
526
+ self._check_is_fitted()
527
+ summary = []
528
+ for i, rule in enumerate(self.estimators_):
529
+ summary.append({
530
+ 'iterasi': i + 1,
531
+ 'aturan': self.rules_[i],
532
+ 'bobot': round(float(self.rule_weights_[i]), 6),
533
+ 'panjang_aturan': len(rule.features),
534
+ })
535
+ return summary
536
+
537
+ @property
538
+ def n_rules_(self):
539
+ """
540
+ Jumlah aturan aktif dalam model.
541
+
542
+ Kembalian
543
+ ----------
544
+ int
545
+ """
546
+ self._check_is_fitted()
547
+ return self.n_estimators_
548
+
549
+ @property
550
+ def feature_usage_(self):
551
+ """
552
+ Frekuensi kemunculan setiap fitur di dalam aturan.
553
+
554
+ Kembalian
555
+ ----------
556
+ dict
557
+ """
558
+ self._check_is_fitted()
559
+ usage = {}
560
+ for rule in self.estimators_:
561
+ for f in rule.features:
562
+ name = (
563
+ self.feature_names_in_[f]
564
+ if self.feature_names_in_ is not None
565
+ else f"X{f}"
566
+ )
567
+ usage[name] = usage.get(name, 0) + 1
568
+ return usage
@@ -0,0 +1,99 @@
1
+ import numpy as np
2
+
3
+
4
+ def sigmoid(x):
5
+ # Fungsi sigmoid dengan penjagaan numerik agar tidak overflow
6
+ return np.where(x >= 0, 1 / (1 + np.exp(-x)), np.exp(x) / (1 + np.exp(x)))
7
+
8
+
9
+ class LogisticLoss:
10
+ # Loss biner standar berbasis log-likelihood
11
+
12
+ def loss(self, y, F):
13
+ p = sigmoid(F)
14
+ p = np.clip(p, 1e-15, 1 - 1e-15)
15
+ return -np.mean(y * np.log(p) + (1 - y) * np.log(1 - p))
16
+
17
+ def gradient(self, y, F):
18
+ # 1. Gradien negatif sebagai arah penurunan loss
19
+ p = sigmoid(F)
20
+ return y - p
21
+
22
+ def init_F(self, y):
23
+ # Inisialisasi F0 dari proporsi kelas positif
24
+ p = np.clip(np.mean(y), 1e-15, 1 - 1e-15)
25
+ return np.full(len(y), np.log(p / (1 - p)))
26
+
27
+
28
+ class FocalLoss:
29
+ # Focal Loss untuk data tidak seimbang
30
+
31
+ def __init__(self, gamma=2.0, alpha=0.25):
32
+ self.gamma = gamma
33
+ self.alpha = alpha
34
+
35
+ def loss(self, y, F):
36
+ p = sigmoid(F)
37
+ p = np.clip(p, 1e-15, 1 - 1e-15)
38
+ pt = np.where(y == 1, p, 1 - p)
39
+ alpha_t = np.where(y == 1, self.alpha, 1 - self.alpha)
40
+ return -np.mean(alpha_t * ((1 - pt) ** self.gamma) * np.log(pt))
41
+
42
+ def gradient(self, y, F):
43
+ # 1. Gradien Focal Loss mengikuti turunan dari Lin et al. 2017
44
+ # a. Hitung probabilitas dan faktor modulasi
45
+ p = sigmoid(F)
46
+ p = np.clip(p, 1e-15, 1 - 1e-15)
47
+ pt = np.where(y == 1, p, 1 - p)
48
+ alpha_t = np.where(y == 1, self.alpha, 1 - self.alpha)
49
+ # b. Gradien lengkap dengan faktor fokus
50
+ weight = alpha_t * (1 - pt) ** self.gamma
51
+ grad = weight * (y - p) + self.gamma * weight * pt * np.log(pt) * np.where(y == 1, -(1 - p), p)
52
+ return grad
53
+
54
+ def init_F(self, y):
55
+ p = np.clip(np.mean(y), 1e-15, 1 - 1e-15)
56
+ return np.full(len(y), np.log(p / (1 - p)))
57
+
58
+
59
+ class PolyLoss:
60
+ # PolyLoss yang berbasis ekspansi polinomial
61
+ def __init__(self, epsilon=1.0):
62
+ self.epsilon = epsilon
63
+
64
+ def loss(self, y, F):
65
+ p = sigmoid(F)
66
+ p = np.clip(p, 1e-15, 1 - 1e-15)
67
+ pt = np.where(y == 1, p, 1 - p)
68
+ ce = -np.log(pt)
69
+ return np.mean(ce + self.epsilon * (1 - pt))
70
+
71
+ def gradient(self, y, F):
72
+ # 1. Gradien PolyLoss adalah gradien CE ditambah suku koreksi polinomial
73
+ p = sigmoid(F)
74
+ p = np.clip(p, 1e-15, 1 - 1e-15)
75
+ pt = np.where(y == 1, p, 1 - p)
76
+ # a. Gradien cross-entropy standar
77
+ grad_ce = y - p
78
+ # b. Suku koreksi dari ekspansi polinomial pertama
79
+ grad_poly = self.epsilon * np.where(y == 1, p * (1 - p), -p * (1 - p))
80
+ return grad_ce + grad_poly
81
+
82
+ def init_F(self, y):
83
+ p = np.clip(np.mean(y), 1e-15, 1 - 1e-15)
84
+ return np.full(len(y), np.log(p / (1 - p)))
85
+
86
+
87
+ def get_loss(name, **kwargs):
88
+ # Kembalikan objek loss sesuai nama yang dipilih pengguna
89
+ if name == 'logistic':
90
+ return LogisticLoss()
91
+ elif name == 'focal':
92
+ return FocalLoss(
93
+ gamma=kwargs.get('focal_gamma', 2.0),
94
+ alpha=kwargs.get('focal_alpha', 0.25)
95
+ )
96
+ elif name == 'poly':
97
+ return PolyLoss(epsilon=kwargs.get('poly_epsilon', 1.0))
98
+ else:
99
+ raise ValueError(f"Loss '{name}' tidak dikenali. Pilihan: 'logistic', 'focal', 'poly'.")
@@ -0,0 +1,168 @@
1
+ import numpy as np
2
+ from itertools import combinations
3
+
4
+
5
+ class Rule:
6
+ # Satu aturan logika dalam ensemble BinBoost
7
+
8
+ def __init__(self, features, operators, weight, feature_names=None):
9
+ # 1. Simpan komponen aturan
10
+ # a. Indeks fitur yang terlibat
11
+ self.features = features
12
+ # b. Daftar operator antar fitur
13
+ self.operators = operators
14
+ # c. Bobot optimal hasil perhitungan w_m*
15
+ self.weight = weight
16
+ self.feature_names = feature_names
17
+
18
+ def evaluate(self, X_bin):
19
+ # Hitung keluaran aturan untuk setiap sampel dan kembalikan larik biner
20
+ result = X_bin[:, self.features[0]].astype(bool)
21
+ for i, op in enumerate(self.operators):
22
+ next_col = X_bin[:, self.features[i + 1]].astype(bool)
23
+ if op == 'AND':
24
+ result = result & next_col
25
+ elif op == 'OR':
26
+ result = result | next_col
27
+ elif op == 'XOR':
28
+ result = result ^ next_col
29
+ return result.astype(np.float64)
30
+
31
+ def to_string(self, feature_names=None):
32
+ # Kembalikan representasi teks aturan yang dapat dibaca manusia
33
+ names = feature_names if feature_names is not None else self.feature_names
34
+ if names is None:
35
+ parts = [f"X{f}" for f in self.features]
36
+ else:
37
+ parts = [names[f] for f in self.features]
38
+ if len(parts) == 1:
39
+ return parts[0]
40
+ expr = parts[0]
41
+ for i, op in enumerate(self.operators):
42
+ expr = f"({expr} {op} {parts[i + 1]})"
43
+ return expr
44
+
45
+ def __repr__(self):
46
+ return f"Rule({self.to_string()}, weight={self.weight:.4f})"
47
+
48
+
49
+ class BeamSearchRuleFinder:
50
+ # Pencari aturan terbaik menggunakan beam search berbasis gradien
51
+
52
+ def __init__(self, operators, max_rule_length, beam_width,
53
+ feature_selection_threshold, min_samples_rule,
54
+ rule_complexity_penalty, ohe_groups=None):
55
+ self.operators = operators
56
+ self.max_rule_length = max_rule_length
57
+ self.beam_width = beam_width
58
+ self.feature_selection_threshold = feature_selection_threshold
59
+ self.min_samples_rule = min_samples_rule
60
+ self.rule_complexity_penalty = rule_complexity_penalty
61
+ self.ohe_groups = ohe_groups
62
+
63
+ def _select_features(self, X_bin, gradients):
64
+ # 1. Pilih fitur kandidat berdasarkan skor korelasi gradien
65
+ n_samples, n_features = X_bin.shape
66
+ scores = []
67
+ for f in range(n_features):
68
+ col = X_bin[:, f]
69
+ score = np.abs(np.dot(gradients, col)) / (np.sum(col) + 1e-10)
70
+ scores.append(score)
71
+ scores = np.array(scores)
72
+ selected = np.where(scores >= self.feature_selection_threshold)[0]
73
+ if len(selected) == 0:
74
+ selected = np.array([np.argmax(scores)])
75
+ return selected
76
+
77
+ def _same_ohe_group(self, f1, f2):
78
+ # Periksa apakah dua fitur berasal dari grup OneHotEncoding yang sama
79
+ if self.ohe_groups is None:
80
+ return False
81
+ for group in self.ohe_groups.values():
82
+ if f1 in group and f2 in group:
83
+ return True
84
+ return False
85
+
86
+ def _compute_score(self, h, gradients):
87
+ # 1. Hitung bobot optimal w_m* dan skor galat kuadrat terkecil
88
+ denom = np.dot(h, h)
89
+ if denom < 1e-10:
90
+ return None, np.inf
91
+ w = np.dot(gradients, h) / denom
92
+ penalty = self.rule_complexity_penalty
93
+ score = np.sum((gradients - w * h) ** 2)
94
+ return w, score
95
+
96
+ def _is_valid(self, h, n_samples):
97
+ # Periksa apakah jumlah sampel yang memenuhi aturan mencukupi min_samples_rule
98
+ support = int(np.sum(h))
99
+ if isinstance(self.min_samples_rule, float) and self.min_samples_rule < 1.0:
100
+ min_s = int(self.min_samples_rule * n_samples)
101
+ else:
102
+ min_s = int(self.min_samples_rule)
103
+ return support >= min_s
104
+
105
+ def find_best_rule(self, X_bin, gradients):
106
+ # 1. Jalankan beam search untuk menemukan aturan terbaik
107
+ n_samples = X_bin.shape[0]
108
+ candidate_features = self._select_features(X_bin, gradients)
109
+
110
+ # a. Inisialisasi beam dengan aturan panjang satu
111
+ beam = []
112
+ for f in candidate_features:
113
+ h = X_bin[:, f].astype(np.float64)
114
+ if not self._is_valid(h, n_samples):
115
+ continue
116
+ w, score = self._compute_score(h, gradients)
117
+ if w is not None:
118
+ beam.append(([f], [], w, score))
119
+
120
+ if not beam:
121
+ return None
122
+
123
+ beam.sort(key=lambda x: x[3])
124
+ beam = beam[:self.beam_width]
125
+ best_candidate = beam[0]
126
+
127
+ # b. Perluas aturan hingga panjang maksimum
128
+ for length in range(2, self.max_rule_length + 1):
129
+ new_beam = []
130
+ for (feats, ops, w_prev, score_prev) in beam:
131
+ for f in candidate_features:
132
+ if f in feats:
133
+ continue
134
+ # c. Terapkan constraint mutual exclusivity OHE
135
+ skip = False
136
+ for existing_f in feats:
137
+ if self._same_ohe_group(existing_f, f):
138
+ skip = True
139
+ break
140
+ if skip:
141
+ continue
142
+ for op in self.operators:
143
+ new_feats = feats + [f]
144
+ new_ops = ops + [op]
145
+ tmp_rule = Rule(new_feats, new_ops, 0.0)
146
+ h = tmp_rule.evaluate(X_bin)
147
+ if not self._is_valid(h, n_samples):
148
+ continue
149
+ w, score = self._compute_score(h, gradients)
150
+ # d. Terapkan penalti kompleksitas berdasarkan panjang aturan
151
+ penalized_score = score * (1 + self.rule_complexity_penalty * length)
152
+ if w is not None:
153
+ new_beam.append((new_feats, new_ops, w, penalized_score))
154
+
155
+ if not new_beam:
156
+ break
157
+
158
+ new_beam.sort(key=lambda x: x[3])
159
+ new_beam = new_beam[:self.beam_width]
160
+
161
+ if new_beam[0][3] < best_candidate[3]:
162
+ best_candidate = new_beam[0]
163
+ beam = new_beam
164
+ else:
165
+ break
166
+
167
+ feats, ops, w, score = best_candidate
168
+ return Rule(feats, ops, w)
@@ -0,0 +1,104 @@
1
+ Metadata-Version: 2.4
2
+ Name: binboost
3
+ Version: 0.1.0
4
+ Summary: Gradient Boosting Berbasis Aturan Logika Adaptif untuk Fitur Biner
5
+ Home-page: https://github.com/username/binboost
6
+ Author: BinBoost Authors
7
+ License: MIT
8
+ Project-URL: Homepage, https://github.com/username/binboost
9
+ Keywords: gradient boosting,logical rules,binary features,interpretable machine learning
10
+ Classifier: Programming Language :: Python :: 3
11
+ Classifier: License :: OSI Approved :: MIT License
12
+ Classifier: Operating System :: OS Independent
13
+ Classifier: Intended Audience :: Science/Research
14
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
15
+ Requires-Python: >=3.8
16
+ Description-Content-Type: text/markdown
17
+ License-File: LICENSE
18
+ Requires-Dist: numpy>=1.21.0
19
+ Requires-Dist: pandas>=1.3.0
20
+ Dynamic: author
21
+ Dynamic: home-page
22
+ Dynamic: license-file
23
+ Dynamic: requires-python
24
+
25
+ # BinBoost
26
+
27
+ **Gradient Boosting Berbasis Aturan Logika Adaptif untuk Fitur Biner**
28
+
29
+ BinBoost adalah algoritma klasifikasi gradient boosting yang membangun ensemble aturan logika murni (AND, OR, XOR) dengan binarisasi fitur numerik adaptif berbasis gradien pada setiap iterasi boosting. Setiap weak learner berupa aturan yang dapat dibaca langsung oleh manusia tanpa memerlukan alat bantu penjelasan pasca-pelatihan.
30
+
31
+ ## Kebaruan Utama
32
+
33
+ - **Binarisasi adaptif berbasis gradien**: nilai ambang batas fitur numerik dicari per iterasi untuk memaksimalkan korelasi dengan gradien saat ini
34
+ - **Ensemble aturan logika murni**: tidak ada pohon keputusan, setiap weak learner adalah aturan seperti `(A AND B)` atau `(C OR D)`
35
+
36
+ ## Instalasi
37
+
38
+ ```bash
39
+ pip install binboost
40
+ ```
41
+
42
+ ## Penggunaan Dasar
43
+
44
+ ```python
45
+ import numpy as np
46
+ from binboost import BinBoost
47
+
48
+ X = np.array([[1, 0, 1], [0, 1, 0], [1, 1, 0], [0, 0, 1]], dtype=float)
49
+ y = np.array([1, 0, 1, 0])
50
+
51
+ model = BinBoost(n_estimators=50, learning_rate=0.1, max_rule_length=2)
52
+ model.fit(X, y)
53
+
54
+ print(model.predict(X))
55
+ print(model.predict_proba(X))
56
+ print(model.rules_)
57
+
58
+ import pandas as pd
59
+ print(pd.DataFrame(model.rule_summary_))
60
+ ```
61
+
62
+ ## Hyperparameter Utama
63
+
64
+ | Parameter | Bawaan | Keterangan |
65
+ |---|---|---|
66
+ | `n_estimators` | 100 | Jumlah iterasi boosting |
67
+ | `learning_rate` | 0.1 | Faktor penyusutan tiap aturan |
68
+ | `loss` | `'logistic'` | Fungsi loss: `'logistic'`, `'focal'`, `'poly'` |
69
+ | `max_rule_length` | 2 | Jumlah maksimum fitur dalam satu aturan |
70
+ | `operators` | `['AND','OR']` | Operator logika yang digunakan |
71
+ | `beam_width` | 5 | Lebar beam search |
72
+ | `binarize_strategy` | `'gradient'` | Strategi binarisasi: `'gradient'`, `'quantile'`, `'uniform'`, `'kmeans'` |
73
+ | `subsample` | 0.8 | Fraksi data per iterasi |
74
+ | `feature_selection_threshold` | 0.01 | Ambang batas seleksi fitur berbasis gradien |
75
+
76
+ ## Fitur yang Didukung
77
+
78
+ - Fitur biner (0/1): langsung diproses
79
+ - Fitur numerik (int/float): dibinarisasi otomatis
80
+ - Fitur kategorikal 3+ kelas: wajib OneHotEncode terlebih dahulu
81
+
82
+ ```python
83
+ from sklearn.preprocessing import OneHotEncoder
84
+ enc = OneHotEncoder(sparse_output=False, drop='first')
85
+ X_encoded = enc.fit_transform(X[['kolom_kategorikal']])
86
+ ```
87
+
88
+ BinBoost secara otomatis mendeteksi kelompok fitur OneHotEncoding dan mencegah aturan yang tidak masuk akal seperti `(Warna_Merah AND Warna_Biru)`.
89
+
90
+ ## Atribut Setelah Pelatihan
91
+
92
+ ```python
93
+ model.rules_ # daftar teks aturan
94
+ model.rule_weights_ # bobot setiap aturan
95
+ model.feature_importances_ # skor kepentingan fitur
96
+ model.train_score_ # loss per iterasi
97
+ model.rule_summary_ # ringkasan lengkap atau konversi ke DataFrame
98
+ model.n_rules_ # jumlah aturan aktif
99
+ model.feature_usage_ # frekuensi penggunaan tiap fitur
100
+ ```
101
+
102
+ ## Lisensi
103
+
104
+ MIT
@@ -0,0 +1,14 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ setup.py
5
+ binboost/__init__.py
6
+ binboost/binarizer.py
7
+ binboost/binboost.py
8
+ binboost/loss.py
9
+ binboost/rule.py
10
+ binboost.egg-info/PKG-INFO
11
+ binboost.egg-info/SOURCES.txt
12
+ binboost.egg-info/dependency_links.txt
13
+ binboost.egg-info/requires.txt
14
+ binboost.egg-info/top_level.txt
@@ -0,0 +1,2 @@
1
+ numpy>=1.21.0
2
+ pandas>=1.3.0
@@ -0,0 +1 @@
1
+ binboost
@@ -0,0 +1,26 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0", "wheel"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "binboost"
7
+ version = "0.1.0"
8
+ description = "Gradient Boosting Berbasis Aturan Logika Adaptif untuk Fitur Biner"
9
+ readme = "README.md"
10
+ requires-python = ">=3.8"
11
+ license = { text = "MIT" }
12
+ dependencies = [
13
+ "numpy>=1.21.0",
14
+ "pandas>=1.3.0",
15
+ ]
16
+ keywords = ["gradient boosting", "logical rules", "binary features", "interpretable machine learning"]
17
+ classifiers = [
18
+ "Programming Language :: Python :: 3",
19
+ "License :: OSI Approved :: MIT License",
20
+ "Operating System :: OS Independent",
21
+ "Intended Audience :: Science/Research",
22
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
23
+ ]
24
+
25
+ [project.urls]
26
+ Homepage = "https://github.com/username/binboost"
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,25 @@
1
+ from setuptools import setup, find_packages
2
+
3
+ setup(
4
+ name="binboost",
5
+ version="0.1.0",
6
+ author="BinBoost Authors",
7
+ description="Gradient Boosting Berbasis Aturan Logika Adaptif untuk Fitur Biner",
8
+ long_description=open("README.md", encoding="utf-8").read(),
9
+ long_description_content_type="text/markdown",
10
+ url="https://github.com/username/binboost",
11
+ packages=find_packages(),
12
+ python_requires=">=3.8",
13
+ install_requires=[
14
+ "numpy>=1.21.0",
15
+ "pandas>=1.3.0",
16
+ ],
17
+ classifiers=[
18
+ "Programming Language :: Python :: 3",
19
+ "License :: OSI Approved :: MIT License",
20
+ "Operating System :: OS Independent",
21
+ "Intended Audience :: Science/Research",
22
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
23
+ ],
24
+ keywords="gradient boosting, logical rules, binary features, interpretable machine learning",
25
+ )