libmultilabel 0.7.0__tar.gz → 0.7.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.
- {libmultilabel-0.7.0 → libmultilabel-0.7.2}/PKG-INFO +3 -3
- {libmultilabel-0.7.0 → libmultilabel-0.7.2}/libmultilabel/linear/linear.py +4 -4
- {libmultilabel-0.7.0 → libmultilabel-0.7.2}/libmultilabel/linear/metrics.py +4 -4
- {libmultilabel-0.7.0 → libmultilabel-0.7.2}/libmultilabel/linear/tree.py +48 -9
- {libmultilabel-0.7.0 → libmultilabel-0.7.2}/libmultilabel/linear/utils.py +6 -4
- {libmultilabel-0.7.0 → libmultilabel-0.7.2}/libmultilabel.egg-info/PKG-INFO +3 -3
- {libmultilabel-0.7.0 → libmultilabel-0.7.2}/libmultilabel.egg-info/requires.txt +2 -2
- {libmultilabel-0.7.0 → libmultilabel-0.7.2}/setup.cfg +3 -3
- {libmultilabel-0.7.0 → libmultilabel-0.7.2}/LICENSE +0 -0
- {libmultilabel-0.7.0 → libmultilabel-0.7.2}/README.md +0 -0
- {libmultilabel-0.7.0 → libmultilabel-0.7.2}/libmultilabel/__init__.py +0 -0
- {libmultilabel-0.7.0 → libmultilabel-0.7.2}/libmultilabel/common_utils.py +0 -0
- {libmultilabel-0.7.0 → libmultilabel-0.7.2}/libmultilabel/linear/__init__.py +0 -0
- {libmultilabel-0.7.0 → libmultilabel-0.7.2}/libmultilabel/linear/data_utils.py +0 -0
- {libmultilabel-0.7.0 → libmultilabel-0.7.2}/libmultilabel/linear/preprocessor.py +0 -0
- {libmultilabel-0.7.0 → libmultilabel-0.7.2}/libmultilabel/logging.py +0 -0
- {libmultilabel-0.7.0 → libmultilabel-0.7.2}/libmultilabel/nn/__init__.py +0 -0
- {libmultilabel-0.7.0 → libmultilabel-0.7.2}/libmultilabel/nn/attentionxml.py +0 -0
- {libmultilabel-0.7.0 → libmultilabel-0.7.2}/libmultilabel/nn/data_utils.py +0 -0
- {libmultilabel-0.7.0 → libmultilabel-0.7.2}/libmultilabel/nn/metrics.py +0 -0
- {libmultilabel-0.7.0 → libmultilabel-0.7.2}/libmultilabel/nn/model.py +0 -0
- {libmultilabel-0.7.0 → libmultilabel-0.7.2}/libmultilabel/nn/networks/__init__.py +0 -0
- {libmultilabel-0.7.0 → libmultilabel-0.7.2}/libmultilabel/nn/networks/bert.py +0 -0
- {libmultilabel-0.7.0 → libmultilabel-0.7.2}/libmultilabel/nn/networks/bert_attention.py +0 -0
- {libmultilabel-0.7.0 → libmultilabel-0.7.2}/libmultilabel/nn/networks/caml.py +0 -0
- {libmultilabel-0.7.0 → libmultilabel-0.7.2}/libmultilabel/nn/networks/kim_cnn.py +0 -0
- {libmultilabel-0.7.0 → libmultilabel-0.7.2}/libmultilabel/nn/networks/labelwise_attention_networks.py +0 -0
- {libmultilabel-0.7.0 → libmultilabel-0.7.2}/libmultilabel/nn/networks/modules.py +0 -0
- {libmultilabel-0.7.0 → libmultilabel-0.7.2}/libmultilabel/nn/networks/xml_cnn.py +0 -0
- {libmultilabel-0.7.0 → libmultilabel-0.7.2}/libmultilabel/nn/nn_utils.py +0 -0
- {libmultilabel-0.7.0 → libmultilabel-0.7.2}/libmultilabel.egg-info/SOURCES.txt +0 -0
- {libmultilabel-0.7.0 → libmultilabel-0.7.2}/libmultilabel.egg-info/dependency_links.txt +0 -0
- {libmultilabel-0.7.0 → libmultilabel-0.7.2}/libmultilabel.egg-info/top_level.txt +0 -0
- {libmultilabel-0.7.0 → libmultilabel-0.7.2}/pyproject.toml +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: libmultilabel
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.2
|
|
4
4
|
Summary: A library for multi-class and multi-label classification
|
|
5
5
|
Home-page: https://github.com/ASUS-AICS/LibMultiLabel
|
|
6
6
|
Author: LibMultiLabel Team
|
|
@@ -24,12 +24,12 @@ Requires-Dist: numba
|
|
|
24
24
|
Requires-Dist: pandas>1.3.0
|
|
25
25
|
Requires-Dist: PyYAML
|
|
26
26
|
Requires-Dist: scikit-learn
|
|
27
|
-
Requires-Dist: scipy
|
|
27
|
+
Requires-Dist: scipy<1.14.0
|
|
28
28
|
Requires-Dist: tqdm
|
|
29
29
|
Provides-Extra: nn
|
|
30
30
|
Requires-Dist: lightning==2.0.9; extra == "nn"
|
|
31
31
|
Requires-Dist: nltk; extra == "nn"
|
|
32
|
-
Requires-Dist: torch; extra == "nn"
|
|
32
|
+
Requires-Dist: torch<=2.3; extra == "nn"
|
|
33
33
|
Requires-Dist: torchmetrics==0.10.3; extra == "nn"
|
|
34
34
|
Requires-Dist: torchtext; extra == "nn"
|
|
35
35
|
Requires-Dist: transformers; extra == "nn"
|
|
@@ -79,7 +79,7 @@ def train_1vsrest(
|
|
|
79
79
|
options: str = "",
|
|
80
80
|
verbose: bool = True,
|
|
81
81
|
) -> FlatModel:
|
|
82
|
-
"""Trains a linear model for
|
|
82
|
+
"""Trains a linear model for multi-label data using a one-vs-rest strategy.
|
|
83
83
|
|
|
84
84
|
Args:
|
|
85
85
|
y (sparse.csr_matrix): A 0/1 matrix with dimensions number of instances * number of classes.
|
|
@@ -139,7 +139,7 @@ def _prepare_options(x: sparse.csr_matrix, options: str) -> tuple[sparse.csr_mat
|
|
|
139
139
|
raise ValueError("Invalid LIBLINEAR solver type. Only classification solvers are allowed.")
|
|
140
140
|
else:
|
|
141
141
|
# workaround for liblinear warning about unspecified solver
|
|
142
|
-
options_split.extend(["-s", "
|
|
142
|
+
options_split.extend(["-s", "1"])
|
|
143
143
|
|
|
144
144
|
bias = -1.0
|
|
145
145
|
if "-B" in options_split:
|
|
@@ -396,7 +396,7 @@ def train_cost_sensitive(
|
|
|
396
396
|
options: str = "",
|
|
397
397
|
verbose: bool = True,
|
|
398
398
|
) -> FlatModel:
|
|
399
|
-
"""Trains a linear model for
|
|
399
|
+
"""Trains a linear model for multi-label data using a one-vs-rest strategy
|
|
400
400
|
and cross-validation to pick an optimal asymmetric misclassification cost
|
|
401
401
|
for Macro-F1.
|
|
402
402
|
Outperforms train_1vsrest in most aspects at the cost of higher
|
|
@@ -500,7 +500,7 @@ def train_cost_sensitive_micro(
|
|
|
500
500
|
options: str = "",
|
|
501
501
|
verbose: bool = True,
|
|
502
502
|
) -> FlatModel:
|
|
503
|
-
"""Trains a linear model for
|
|
503
|
+
"""Trains a linear model for multi-label data using a one-vs-rest strategy
|
|
504
504
|
and cross-validation to pick an optimal asymmetric misclassification cost
|
|
505
505
|
for Micro-F1.
|
|
506
506
|
Outperforms train_1vsrest in most aspects at the cost of higher
|
|
@@ -300,13 +300,13 @@ def get_metrics(monitor_metrics: list[str], num_classes: int, multiclass: bool =
|
|
|
300
300
|
monitor_metrics = []
|
|
301
301
|
metrics = {}
|
|
302
302
|
for metric in monitor_metrics:
|
|
303
|
-
if re.match("P@\d+", metric):
|
|
303
|
+
if re.match(r"P@\d+", metric):
|
|
304
304
|
metrics[metric] = PrecisionAtK(top_k=int(metric[2:]))
|
|
305
|
-
elif re.match("R@\d+", metric):
|
|
305
|
+
elif re.match(r"R@\d+", metric):
|
|
306
306
|
metrics[metric] = RecallAtK(top_k=int(metric[2:]))
|
|
307
|
-
elif re.match("RP@\d+", metric):
|
|
307
|
+
elif re.match(r"RP@\d+", metric):
|
|
308
308
|
metrics[metric] = RPrecisionAtK(top_k=int(metric[3:]))
|
|
309
|
-
elif re.match("NDCG@\d+", metric):
|
|
309
|
+
elif re.match(r"NDCG@\d+", metric):
|
|
310
310
|
metrics[metric] = NDCGAtK(top_k=int(metric[5:]))
|
|
311
311
|
elif metric in {"Another-Macro-F1", "Macro-F1", "Micro-F1"}:
|
|
312
312
|
metrics[metric] = F1(num_classes, average=metric[:-3].lower(), multiclass=multiclass)
|
|
@@ -7,6 +7,7 @@ import scipy.sparse as sparse
|
|
|
7
7
|
import sklearn.cluster
|
|
8
8
|
import sklearn.preprocessing
|
|
9
9
|
from tqdm import tqdm
|
|
10
|
+
import psutil
|
|
10
11
|
|
|
11
12
|
from . import linear
|
|
12
13
|
|
|
@@ -26,6 +27,7 @@ class Node:
|
|
|
26
27
|
"""
|
|
27
28
|
self.label_map = label_map
|
|
28
29
|
self.children = children
|
|
30
|
+
self.is_root = False
|
|
29
31
|
|
|
30
32
|
def isLeaf(self) -> bool:
|
|
31
33
|
return len(self.children) == 0
|
|
@@ -57,7 +59,7 @@ class TreeModel:
|
|
|
57
59
|
x: sparse.csr_matrix,
|
|
58
60
|
beam_width: int = 10,
|
|
59
61
|
) -> np.ndarray:
|
|
60
|
-
"""Calculates the
|
|
62
|
+
"""Calculates the probability estimates associated with x.
|
|
61
63
|
|
|
62
64
|
Args:
|
|
63
65
|
x (sparse.csr_matrix): A matrix with dimension number of instances * number of features.
|
|
@@ -71,10 +73,10 @@ class TreeModel:
|
|
|
71
73
|
return np.vstack([self._beam_search(all_preds[i], beam_width) for i in range(all_preds.shape[0])])
|
|
72
74
|
|
|
73
75
|
def _beam_search(self, instance_preds: np.ndarray, beam_width: int) -> np.ndarray:
|
|
74
|
-
"""Predict with beam search using cached
|
|
76
|
+
"""Predict with beam search using cached probability estimates for a single instance.
|
|
75
77
|
|
|
76
78
|
Args:
|
|
77
|
-
instance_preds (np.ndarray): A vector of cached
|
|
79
|
+
instance_preds (np.ndarray): A vector of cached probability estimates of each node, has dimension number of labels + total number of metalabels.
|
|
78
80
|
beam_width (int): Number of candidates considered.
|
|
79
81
|
|
|
80
82
|
Returns:
|
|
@@ -93,18 +95,18 @@ class TreeModel:
|
|
|
93
95
|
continue
|
|
94
96
|
slice = np.s_[self.weight_map[node.index] : self.weight_map[node.index + 1]]
|
|
95
97
|
pred = instance_preds[slice]
|
|
96
|
-
children_score = score - np.maximum(0, 1 - pred)
|
|
98
|
+
children_score = score - np.square(np.maximum(0, 1 - pred))
|
|
97
99
|
next_level.extend(zip(node.children, children_score.tolist()))
|
|
98
100
|
|
|
99
101
|
cur_level = sorted(next_level, key=lambda pair: -pair[1])[:beam_width]
|
|
100
102
|
next_level = []
|
|
101
103
|
|
|
102
104
|
num_labels = len(self.root.label_map)
|
|
103
|
-
scores = np.full(num_labels,
|
|
105
|
+
scores = np.full(num_labels, 0.0)
|
|
104
106
|
for node, score in cur_level:
|
|
105
107
|
slice = np.s_[self.weight_map[node.index] : self.weight_map[node.index + 1]]
|
|
106
108
|
pred = instance_preds[slice]
|
|
107
|
-
scores[node.label_map] = np.exp(score - np.maximum(0, 1 - pred)
|
|
109
|
+
scores[node.label_map] = np.exp(score - np.square(np.maximum(0, 1 - pred)))
|
|
108
110
|
return scores
|
|
109
111
|
|
|
110
112
|
|
|
@@ -116,7 +118,7 @@ def train_tree(
|
|
|
116
118
|
dmax=10,
|
|
117
119
|
verbose: bool = True,
|
|
118
120
|
) -> TreeModel:
|
|
119
|
-
"""Trains a linear model for
|
|
121
|
+
"""Trains a linear model for multi-label data using a divide-and-conquer strategy.
|
|
120
122
|
The algorithm used is based on https://github.com/xmc-aalto/bonsai.
|
|
121
123
|
|
|
122
124
|
Args:
|
|
@@ -133,20 +135,39 @@ def train_tree(
|
|
|
133
135
|
label_representation = (y.T * x).tocsr()
|
|
134
136
|
label_representation = sklearn.preprocessing.normalize(label_representation, norm="l2", axis=1)
|
|
135
137
|
root = _build_tree(label_representation, np.arange(y.shape[1]), 0, K, dmax)
|
|
138
|
+
root.is_root = True
|
|
136
139
|
|
|
137
140
|
num_nodes = 0
|
|
141
|
+
# Both type(x) and type(y) are sparse.csr_matrix
|
|
142
|
+
# However, type((x != 0).T) becomes sparse.csc_matrix
|
|
143
|
+
# So type((x != 0).T * y) results in sparse.csc_matrix
|
|
144
|
+
features_used_perlabel = (x != 0).T * y
|
|
138
145
|
|
|
139
146
|
def count(node):
|
|
140
147
|
nonlocal num_nodes
|
|
141
148
|
num_nodes += 1
|
|
149
|
+
node.num_features_used = np.count_nonzero(features_used_perlabel[:, node.label_map].sum(axis=1))
|
|
142
150
|
|
|
143
151
|
root.dfs(count)
|
|
144
152
|
|
|
153
|
+
model_size = get_estimated_model_size(root)
|
|
154
|
+
print(f"The estimated tree model size is: {model_size / (1024**3):.3f} GB")
|
|
155
|
+
|
|
156
|
+
# Calculate the total memory (excluding swap) on the local machine
|
|
157
|
+
total_memory = psutil.virtual_memory().total
|
|
158
|
+
print(f"Your system memory is: {total_memory / (1024**3):.3f} GB")
|
|
159
|
+
|
|
160
|
+
if total_memory <= model_size:
|
|
161
|
+
raise MemoryError(f"Not enough memory to train the model.")
|
|
162
|
+
|
|
145
163
|
pbar = tqdm(total=num_nodes, disable=not verbose)
|
|
146
164
|
|
|
147
165
|
def visit(node):
|
|
148
|
-
|
|
149
|
-
|
|
166
|
+
if node.is_root:
|
|
167
|
+
_train_node(y, x, options, node)
|
|
168
|
+
else:
|
|
169
|
+
relevant_instances = y[:, node.label_map].getnnz(axis=1) > 0
|
|
170
|
+
_train_node(y[relevant_instances], x[relevant_instances], options, node)
|
|
150
171
|
pbar.update()
|
|
151
172
|
|
|
152
173
|
root.dfs(visit)
|
|
@@ -195,6 +216,24 @@ def _build_tree(label_representation: sparse.csr_matrix, label_map: np.ndarray,
|
|
|
195
216
|
return Node(label_map=label_map, children=children)
|
|
196
217
|
|
|
197
218
|
|
|
219
|
+
def get_estimated_model_size(root):
|
|
220
|
+
total_num_weights = 0
|
|
221
|
+
|
|
222
|
+
def collect_stat(node: Node):
|
|
223
|
+
nonlocal total_num_weights
|
|
224
|
+
|
|
225
|
+
if node.isLeaf():
|
|
226
|
+
total_num_weights += len(node.label_map) * node.num_features_used
|
|
227
|
+
else:
|
|
228
|
+
total_num_weights += len(node.children) * node.num_features_used
|
|
229
|
+
|
|
230
|
+
root.dfs(collect_stat)
|
|
231
|
+
|
|
232
|
+
# 16 is because when storing sparse matrices, indices (int64) require 8 bytes and floats require 8 bytes
|
|
233
|
+
# Our study showed that among the used features of every binary classification problem, on average no more than 2/3 of weights obtained by the dual coordinate descent method are non-zeros.
|
|
234
|
+
return total_num_weights * 16 * 2 / 3
|
|
235
|
+
|
|
236
|
+
|
|
198
237
|
def _train_node(y: sparse.csr_matrix, x: sparse.csr_matrix, options: str, node: Node):
|
|
199
238
|
"""If node is internal, computes the metalabels representing each child and trains
|
|
200
239
|
on the metalabels. Otherwise, train on y.
|
|
@@ -76,17 +76,18 @@ class MultiLabelEstimator(sklearn.base.BaseEstimator):
|
|
|
76
76
|
scoring_metric (str, optional): The scoring metric. Defaults to 'P@1'.
|
|
77
77
|
"""
|
|
78
78
|
|
|
79
|
-
def __init__(self, options: str = "", linear_technique: str = "1vsrest", scoring_metric: str = "P@1"):
|
|
79
|
+
def __init__(self, options: str = "", linear_technique: str = "1vsrest", scoring_metric: str = "P@1", multiclass: bool = False):
|
|
80
80
|
super().__init__()
|
|
81
81
|
self.options = options
|
|
82
82
|
self.linear_technique = linear_technique
|
|
83
83
|
self.scoring_metric = scoring_metric
|
|
84
84
|
self._is_fitted = False
|
|
85
|
+
self.multiclass = multiclass
|
|
85
86
|
|
|
86
87
|
def fit(self, X: sparse.csr_matrix, y: sparse.csr_matrix):
|
|
87
88
|
X, y = sklearn.utils.validation.check_X_y(X, y, accept_sparse=True, multi_output=True)
|
|
88
89
|
self._is_fitted = True
|
|
89
|
-
self.model = LINEAR_TECHNIQUES[self.linear_technique](y, X, self.options)
|
|
90
|
+
self.model = LINEAR_TECHNIQUES[self.linear_technique](y, X, options=self.options)
|
|
90
91
|
return self
|
|
91
92
|
|
|
92
93
|
def predict(self, X: sparse.csr_matrix) -> np.ndarray:
|
|
@@ -96,8 +97,9 @@ class MultiLabelEstimator(sklearn.base.BaseEstimator):
|
|
|
96
97
|
|
|
97
98
|
def score(self, X: sparse.csr_matrix, y: sparse.csr_matrix) -> float:
|
|
98
99
|
metrics = linear.get_metrics(
|
|
99
|
-
[self.scoring_metric],
|
|
100
|
-
y.shape[1],
|
|
100
|
+
monitor_metrics=[self.scoring_metric],
|
|
101
|
+
num_classes=y.shape[1],
|
|
102
|
+
multiclass=self.multiclass
|
|
101
103
|
)
|
|
102
104
|
preds = self.predict(X)
|
|
103
105
|
metrics.update(preds, y.toarray())
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: libmultilabel
|
|
3
|
-
Version: 0.7.
|
|
3
|
+
Version: 0.7.2
|
|
4
4
|
Summary: A library for multi-class and multi-label classification
|
|
5
5
|
Home-page: https://github.com/ASUS-AICS/LibMultiLabel
|
|
6
6
|
Author: LibMultiLabel Team
|
|
@@ -24,12 +24,12 @@ Requires-Dist: numba
|
|
|
24
24
|
Requires-Dist: pandas>1.3.0
|
|
25
25
|
Requires-Dist: PyYAML
|
|
26
26
|
Requires-Dist: scikit-learn
|
|
27
|
-
Requires-Dist: scipy
|
|
27
|
+
Requires-Dist: scipy<1.14.0
|
|
28
28
|
Requires-Dist: tqdm
|
|
29
29
|
Provides-Extra: nn
|
|
30
30
|
Requires-Dist: lightning==2.0.9; extra == "nn"
|
|
31
31
|
Requires-Dist: nltk; extra == "nn"
|
|
32
|
-
Requires-Dist: torch; extra == "nn"
|
|
32
|
+
Requires-Dist: torch<=2.3; extra == "nn"
|
|
33
33
|
Requires-Dist: torchmetrics==0.10.3; extra == "nn"
|
|
34
34
|
Requires-Dist: torchtext; extra == "nn"
|
|
35
35
|
Requires-Dist: transformers; extra == "nn"
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[metadata]
|
|
2
2
|
name = libmultilabel
|
|
3
|
-
version = 0.7.
|
|
3
|
+
version = 0.7.2
|
|
4
4
|
author = LibMultiLabel Team
|
|
5
5
|
license = MIT License
|
|
6
6
|
license_file = LICENSE
|
|
@@ -30,7 +30,7 @@ install_requires =
|
|
|
30
30
|
pandas>1.3.0
|
|
31
31
|
PyYAML
|
|
32
32
|
scikit-learn
|
|
33
|
-
scipy
|
|
33
|
+
scipy<1.14.0
|
|
34
34
|
tqdm
|
|
35
35
|
python_requires = >=3.8
|
|
36
36
|
|
|
@@ -38,7 +38,7 @@ python_requires = >=3.8
|
|
|
38
38
|
nn =
|
|
39
39
|
lightning==2.0.9
|
|
40
40
|
nltk
|
|
41
|
-
torch
|
|
41
|
+
torch<=2.3
|
|
42
42
|
torchmetrics==0.10.3
|
|
43
43
|
torchtext
|
|
44
44
|
transformers
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|