rulelearn-ripper-interactive 0.2.3__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.
Files changed (73) hide show
  1. rulelearn_ri/__init__.py +6 -0
  2. rulelearn_ri/algorithms/__init__.py +0 -0
  3. rulelearn_ri/algorithms/r2n/__init__.py +0 -0
  4. rulelearn_ri/algorithms/r2n/base.py +28 -0
  5. rulelearn_ri/algorithms/r2n/functions.py +25 -0
  6. rulelearn_ri/algorithms/r2n/mask.py +58 -0
  7. rulelearn_ri/algorithms/r2n/preprocessing_layers.py +192 -0
  8. rulelearn_ri/algorithms/r2n/r2n_algo.py +287 -0
  9. rulelearn_ri/algorithms/r2n/simple_rulenet.py +75 -0
  10. rulelearn_ri/algorithms/r2n/training.py +34 -0
  11. rulelearn_ri/algorithms/r2n/utilities.py +117 -0
  12. rulelearn_ri/algorithms/rbm/__init__.py +0 -0
  13. rulelearn_ri/algorithms/rbm/boolean_rule_cg.py +65 -0
  14. rulelearn_ri/algorithms/rbm/rbm/BRCG.py +69 -0
  15. rulelearn_ri/algorithms/rbm/rbm/GLRM.py +98 -0
  16. rulelearn_ri/algorithms/rbm/rbm/__init__.py +6 -0
  17. rulelearn_ri/algorithms/rbm/rbm/beam_search.py +302 -0
  18. rulelearn_ri/algorithms/rbm/rbm/boolean_rule_cg.py +245 -0
  19. rulelearn_ri/algorithms/rbm/rbm/features.py +544 -0
  20. rulelearn_ri/algorithms/rbm/rbm/linear_regression.py +393 -0
  21. rulelearn_ri/algorithms/rbm/rbm/logistic_regression.py +447 -0
  22. rulelearn_ri/algorithms/rbm/utils.py +13 -0
  23. rulelearn_ri/algorithms/ripper/__init__.py +10 -0
  24. rulelearn_ri/algorithms/ripper/base.py +81 -0
  25. rulelearn_ri/algorithms/ripper/binding.py +541 -0
  26. rulelearn_ri/algorithms/ripper/mdl.py +97 -0
  27. rulelearn_ri/algorithms/ripper/pruning.py +212 -0
  28. rulelearn_ri/algorithms/ripper/ripper.py +600 -0
  29. rulelearn_ri/algorithms/ripper/ripper_ruleset_generator.py +26 -0
  30. rulelearn_ri/algorithms/ripper/single_rule.py +425 -0
  31. rulelearn_ri/dise.py +64 -0
  32. rulelearn_ri/trxf/__init__.py +0 -0
  33. rulelearn_ri/trxf/classifier/__init__.py +0 -0
  34. rulelearn_ri/trxf/classifier/ruleset_classifier.py +153 -0
  35. rulelearn_ri/trxf/core/__init__.py +5 -0
  36. rulelearn_ri/trxf/core/boolean_evaluator.py +15 -0
  37. rulelearn_ri/trxf/core/conjunction.py +50 -0
  38. rulelearn_ri/trxf/core/dnf_ruleset.py +64 -0
  39. rulelearn_ri/trxf/core/feature.py +155 -0
  40. rulelearn_ri/trxf/core/predicate.py +98 -0
  41. rulelearn_ri/trxf/core/ruleset_generator.py +25 -0
  42. rulelearn_ri/trxf/core/utils.py +40 -0
  43. rulelearn_ri/trxf/metrics.py +98 -0
  44. rulelearn_ri/trxf/pmml_export/__init__.py +4 -0
  45. rulelearn_ri/trxf/pmml_export/__version__.py +1 -0
  46. rulelearn_ri/trxf/pmml_export/models/__init__.py +24 -0
  47. rulelearn_ri/trxf/pmml_export/models/attribute.py +24 -0
  48. rulelearn_ri/trxf/pmml_export/models/characteristics.py +20 -0
  49. rulelearn_ri/trxf/pmml_export/models/data_dictionary.py +53 -0
  50. rulelearn_ri/trxf/pmml_export/models/mining_schema.py +25 -0
  51. rulelearn_ri/trxf/pmml_export/models/pmml_ruleset_model.py +16 -0
  52. rulelearn_ri/trxf/pmml_export/models/predicate.py +42 -0
  53. rulelearn_ri/trxf/pmml_export/models/rule.py +26 -0
  54. rulelearn_ri/trxf/pmml_export/models/ruleset.py +28 -0
  55. rulelearn_ri/trxf/pmml_export/models/ruleset_model.py +16 -0
  56. rulelearn_ri/trxf/pmml_export/models/scorecard.py +35 -0
  57. rulelearn_ri/trxf/pmml_export/pmml_exporter.py +18 -0
  58. rulelearn_ri/trxf/pmml_export/reader/__init__.py +2 -0
  59. rulelearn_ri/trxf/pmml_export/reader/abstract_reader.py +16 -0
  60. rulelearn_ri/trxf/pmml_export/reader/trxf_reader.py +113 -0
  61. rulelearn_ri/trxf/pmml_export/serializer/__init__.py +2 -0
  62. rulelearn_ri/trxf/pmml_export/serializer/abstract_serializer.py +11 -0
  63. rulelearn_ri/trxf/pmml_export/serializer/nyoka_serializer.py +162 -0
  64. rulelearn_ri/trxf/scorecard/__init__.py +3 -0
  65. rulelearn_ri/trxf/scorecard/bins.py +253 -0
  66. rulelearn_ri/trxf/scorecard/partition.py +71 -0
  67. rulelearn_ri/trxf/scorecard/scorecard.py +78 -0
  68. rulelearn_ripper_interactive-0.2.3.dist-info/METADATA +164 -0
  69. rulelearn_ripper_interactive-0.2.3.dist-info/RECORD +73 -0
  70. rulelearn_ripper_interactive-0.2.3.dist-info/WHEEL +5 -0
  71. rulelearn_ripper_interactive-0.2.3.dist-info/licenses/LICENSE +201 -0
  72. rulelearn_ripper_interactive-0.2.3.dist-info/licenses/NOTICE +11 -0
  73. rulelearn_ripper_interactive-0.2.3.dist-info/top_level.txt +1 -0
@@ -0,0 +1,6 @@
1
+ from importlib.metadata import PackageNotFoundError, version
2
+
3
+ try:
4
+ __version__ = version("rulelearn-ripper-interactive")
5
+ except PackageNotFoundError:
6
+ pass
File without changes
File without changes
@@ -0,0 +1,28 @@
1
+ from abc import ABC, abstractmethod
2
+
3
+ from torch import nn
4
+
5
+
6
+ class RuleNetwork(nn.Module, ABC):
7
+ """
8
+ Encapsulates a neural network that corresponds syntactically to some ruleset.
9
+ """
10
+
11
+ @abstractmethod
12
+ def extract_ruleset(self):
13
+ """ Extract a symbolic representation of the learned ruleset. The semantics of the ruleset must be provided
14
+ externally.
15
+
16
+ :return: A 2D list representing a ruleset in Disjunctive Normal Form. An inner list takes values from {-1, 0, 1}
17
+ representing a single rule, indicating the inclusion of the negated condition, exclusion,
18
+ and inclusion of the condition, respectively.
19
+ E.g. [[1, 0, 0, -1], [0, 1, 0, 0]] represents the ruleset
20
+ "(condition0 ^ not condition3) v condition1" for a rule network with 4 input conditions.
21
+ """
22
+ raise NotImplementedError
23
+
24
+
25
+ class PredicateLearningLayer(nn.Module, ABC):
26
+ @abstractmethod
27
+ def get_predicates(self, *args, **kwargs):
28
+ raise NotImplementedError
@@ -0,0 +1,25 @@
1
+ import torch
2
+
3
+
4
+ class DifferentiableHeaviside(torch.autograd.Function):
5
+ @staticmethod
6
+ def forward(ctx, input):
7
+ ctx.save_for_backward(input)
8
+ return torch.heaviside(input, torch.ones_like(input))
9
+
10
+ @staticmethod
11
+ def backward(ctx, grad_output):
12
+ input, = ctx.saved_tensors
13
+ return grad_output * torch.sigmoid(input) * (1 - torch.sigmoid(input))
14
+
15
+
16
+ class DifferentiableClamp(torch.autograd.Function):
17
+ @staticmethod
18
+ def forward(ctx, input):
19
+ ctx.save_for_backward(input)
20
+ return torch.clamp(input, max=1)
21
+
22
+ @staticmethod
23
+ def backward(ctx, grad_output):
24
+ input, = ctx.saved_tensors
25
+ return grad_output
@@ -0,0 +1,58 @@
1
+ from abc import ABC, abstractmethod
2
+
3
+ import torch
4
+ from torch import nn
5
+
6
+ from rulelearn_ri.algorithms.r2n.functions import DifferentiableHeaviside
7
+
8
+
9
+ class WeightMask(nn.Module, ABC):
10
+ """
11
+ Abstract class for the mask that is applied to the weight matrix of the NN.
12
+ """
13
+
14
+ def __init__(self, dim_out, dim_in):
15
+ super(WeightMask, self).__init__()
16
+ self.dim_out = dim_out
17
+ self.dim_in = dim_in
18
+ self.loc = torch.nn.Parameter(torch.randn(self.dim_out, self.dim_in), requires_grad=True)
19
+
20
+ @abstractmethod
21
+ def forward(self):
22
+ pass
23
+
24
+
25
+ class SimpleWeightMask(WeightMask):
26
+ """
27
+ Binary weight mask that is used in the AND/OR layer. Note that depening of the Boolean value cooling, we either implement a cooled sigmoid or stricly binary differentiable heavyside function.
28
+ """
29
+
30
+ def __init__(self, dim_out, dim_in, temp, cooling):
31
+ super(SimpleWeightMask, self).__init__(dim_out, dim_in)
32
+ self.cooling = cooling
33
+ self.temp = temp
34
+
35
+ def forward(self):
36
+ if self.cooling is True:
37
+ return torch.sigmoid((1 / self.temp) * self.loc)
38
+ else:
39
+ return DifferentiableHeaviside.apply(self.loc)
40
+
41
+ def set_temp(self, temp):
42
+ self.temp = temp
43
+
44
+ def get_temp(self):
45
+ return self.temp
46
+
47
+
48
+ class DeterministicWeightMask(WeightMask):
49
+ """
50
+ Simple deterministic weight mask.
51
+ """
52
+
53
+ def __init__(self, mask):
54
+ super(DeterministicWeightMask, self).__init__(mask.size()[0], mask.size()[1])
55
+ self.mask = mask
56
+
57
+ def forward(self):
58
+ return self.mask
@@ -0,0 +1,192 @@
1
+ import inspect
2
+ import itertools
3
+ import operator
4
+ from copy import copy
5
+ from functools import reduce
6
+
7
+ import numpy as np
8
+ import torch
9
+ from torch import nn
10
+
11
+ import rulelearn_ri.algorithms.r2n.mask as wm
12
+ from rulelearn_ri.algorithms.r2n.base import PredicateLearningLayer
13
+ from rulelearn_ri.trxf.core import Feature, Predicate, Relation
14
+
15
+
16
+ class NegationLayer(nn.Module):
17
+ """
18
+ Augment the input layer with its negation to explicitly provide negated conditions to the downstream rule net.
19
+ """
20
+
21
+ def __init__(self):
22
+ super(NegationLayer, self).__init__()
23
+
24
+ def forward(self, x):
25
+ return torch.cat((x, torch.ones_like(x) - x), 1)
26
+
27
+
28
+ def _normalize_weights(bias, weights):
29
+ max_coef = np.max(np.abs(weights), axis=1)
30
+ normbias = np.divide(bias, max_coef)
31
+ max_coef = np.tile(max_coef, (weights.shape[1], 1)).T
32
+ normweights = np.divide(weights, max_coef)
33
+ return normbias, normweights
34
+
35
+
36
+ class LinearFeatureLayer(PredicateLearningLayer):
37
+ r"""
38
+ This layer takes as input a set of input featues and creates a linear combinations thereof to constuct a predicate in the form \sum w x - b > 0
39
+ where w and b are the weights and biases of the neural network layer.
40
+ """
41
+
42
+ def __init__(self, dim_in, dim_out, init_temp=1):
43
+ super(LinearFeatureLayer, self).__init__()
44
+ torch.manual_seed(0)
45
+ self.layer = nn.Linear(dim_in, dim_out)
46
+ self.mask = wm.DeterministicWeightMask(torch.ones(dim_out, dim_in))
47
+ self.temp = init_temp
48
+ self.dim_out = dim_out
49
+
50
+ def forward(self, x):
51
+ z = self.mask()
52
+ regularized_weights = z * self.layer.weight
53
+ self.layer.weight.data = regularized_weights
54
+ return torch.sigmoid((1 / self.temp) * self.layer(x))
55
+
56
+ def get_penalty(self):
57
+ # return self.mask().mean()
58
+ return self.layer.weight.norm(1)
59
+
60
+ def update_temp(self, temp):
61
+ self.temp = temp
62
+
63
+ def get_temp(self):
64
+ return self.temp
65
+
66
+ def get_predicates(self, column_names, normalizer, thr, name_format='generic'):
67
+ weight = copy(self.layer.weight.detach().numpy())
68
+ bias = copy(self.layer.bias.detach().numpy())
69
+ assert len(column_names) == weight.shape[1], 'column_names: {} but weight dim_in: {}'.format(column_names,
70
+ weight.shape[1])
71
+ norm_bias, norm_weights = _normalize_weights(bias, weight)
72
+ weight[abs(norm_weights) < thr] = 0
73
+ bias[abs(norm_bias) < thr] = 0
74
+ if normalizer:
75
+ weight = normalizer.transform(weight)
76
+
77
+ features = []
78
+ for w, b in zip(weight, bias):
79
+ terms = [x for x in zip(w, column_names) if abs(x[0]) > 0]
80
+ feature = ' + '.join(list(map(lambda term: str(term[0]) + '*' + str(term[1]), terms)))
81
+ feature += ' + ' + str(b)
82
+ features.append(feature.replace('+ -', '- '))
83
+
84
+ predicates = list(map(lambda expression: Predicate(Feature(expression), Relation.GE, 0), features))
85
+ return predicates
86
+
87
+
88
+ class BasisExpansionLayer(nn.Module):
89
+ def __init__(self, basis_functions, application_list=None):
90
+ super().__init__()
91
+ self.functions = basis_functions + [lambda x: x]
92
+ self.application_list = application_list
93
+
94
+ def forward(self, x):
95
+ result = []
96
+ batch_size = x.size()[0]
97
+ for i in range(batch_size):
98
+ batch_result = []
99
+ for f in self.functions:
100
+ arity = len(inspect.getfullargspec(f).args)
101
+ args = [x[i]] * arity
102
+ cartesian = torch.cartesian_prod(*args).detach().numpy()
103
+ if arity == 1:
104
+ cartesian = np.expand_dims(cartesian, axis=1)
105
+ batch_result += [f(*args) for args in cartesian]
106
+ result.append(batch_result)
107
+
108
+ return torch.FloatTensor(result)
109
+
110
+
111
+ class ExpandedLinearFeatureLayer(PredicateLearningLayer):
112
+ def __init__(self, basis_functions, dim_in, dim_out):
113
+ super().__init__()
114
+ torch.manual_seed(0)
115
+
116
+ self.functions = basis_functions
117
+ self.dim_in = dim_in
118
+ self.dim_out = dim_out
119
+ self.expansion_layer = BasisExpansionLayer(self.functions)
120
+ arities = [len(inspect.getfullargspec(f).args) for f in self.functions] + [1]
121
+ dim_mid = sum([dim_in ** n for n in arities])
122
+ self.linear_feature_layer = LinearFeatureLayer(dim_mid, self.dim_out)
123
+
124
+ def forward(self, x):
125
+ return nn.Sequential(self.expansion_layer, self.linear_feature_layer)(x)
126
+
127
+ def get_predicates(self, column_names, normalizer, thr, name_format='generic'):
128
+ norm_obj = copy(normalizer)
129
+ if normalizer is not None:
130
+ scale = normalizer.scale_
131
+ expanded_scale = _expand_normalizer(scale, self.functions)
132
+ norm_obj.scale_ = expanded_scale
133
+ norm_obj.n_features_in_ = len(expanded_scale)
134
+ expanded_col_names = _expand_columns(column_names, self.functions, name_format)
135
+ return self.linear_feature_layer.get_predicates(expanded_col_names, norm_obj, thr, name_format)
136
+
137
+ def get_penalty(self):
138
+ return self.linear_feature_layer.get_penalty()
139
+
140
+ def update_temp(self, temp):
141
+ self.linear_feature_layer.update_temp(temp)
142
+
143
+
144
+ def _expand_columns(column_names, functions, name_format):
145
+ names = []
146
+ for i, f in enumerate(functions):
147
+ arity = len(inspect.getfullargspec(f).args)
148
+ repeat = [column_names] * arity
149
+ cols = itertools.product(*repeat)
150
+ names += ['f{}_'.format(i) + '_'.join(tup) for tup in cols] if name_format == 'generic' \
151
+ else ['*'.join(tup) for tup in cols]
152
+
153
+ return names + column_names
154
+
155
+
156
+ def _expand_normalizer(scale, functions):
157
+ expanded_scale = []
158
+ for i, f in enumerate(functions):
159
+ arity = len(inspect.getfullargspec(f).args)
160
+ repeat = [scale] * arity
161
+ scales = itertools.product(*repeat)
162
+ expanded_scale += [_prod(tup) for tup in scales]
163
+
164
+ return np.concatenate((np.array(expanded_scale), scale))
165
+
166
+
167
+ def _prod(iterable):
168
+ return reduce(operator.mul, iterable, 1)
169
+
170
+
171
+ class PredicateLayer(nn.Module):
172
+ """
173
+ This predicate layer combines categorical and numerical input features and, depending of the NegLayer flag, negates the categorical features.
174
+ """
175
+
176
+ def __init__(self, learning_layer, negation):
177
+ super(PredicateLayer, self).__init__()
178
+ self.LearningLayer = learning_layer
179
+ self.negation = negation
180
+
181
+ def forward(self, x):
182
+ x_cat_in = x[0]
183
+ x_cat_out = x_cat_in
184
+ x_num_in = x[1]
185
+ x_num_out = x_num_in
186
+ if x_num_in.nelement() != 0:
187
+ x_num_out = self.LearningLayer(x_num_in)
188
+ if x_cat_in.nelement() != 0 and self.negation:
189
+ neglayer = NegationLayer()
190
+ x_cat_out = neglayer(x_cat_out)
191
+ result = torch.cat((x_num_out, x_cat_out), -1)
192
+ return result
@@ -0,0 +1,287 @@
1
+ import math
2
+
3
+ import numpy as np
4
+ import pandas as pd
5
+ import torch
6
+ from sklearn.preprocessing import StandardScaler, OneHotEncoder
7
+ from torch import nn
8
+ from torch.utils.data import DataLoader, Dataset
9
+
10
+ import rulelearn_ri.algorithms.r2n.preprocessing_layers as pp
11
+ import rulelearn_ri.algorithms.r2n.simple_rulenet as rn
12
+ import rulelearn_ri.algorithms.r2n.utilities as util
13
+ from rulelearn_ri.algorithms.r2n.training import train as train_r2n
14
+ from rulelearn_ri.trxf.core.conjunction import Conjunction
15
+ from rulelearn_ri.trxf.core.dnf_ruleset import DnfRuleSet
16
+ from rulelearn_ri.trxf.core.feature import Feature
17
+ from rulelearn_ri.trxf.core.predicate import Predicate, Relation
18
+
19
+
20
+ class R2Nalgo:
21
+ """
22
+ Wrapper for the R2N algorithm which is described in the following paper: arxiv.org/abs/2201.06515.
23
+ """
24
+
25
+ def __init__(self,
26
+ cat_columns: list = None,
27
+ num_columns: list = None,
28
+ batch_size: int = 100,
29
+ learning_rate: float = 1e-3,
30
+ coef: float = 1e-3,
31
+ n_rules: int = 25,
32
+ n_conditions: int = 10,
33
+ n_seeds: int = 2,
34
+ init_temp: float = 1,
35
+ min_temp: float = 10 ** -4,
36
+ decay_rate: float = 0.999,
37
+ negation: bool = False,
38
+ normalize_num: bool = False,
39
+ basis_functions: list = None,
40
+ name_format='generic'
41
+ ):
42
+ """
43
+ batch_size : int
44
+ The batch size used during the training of the NN
45
+ learning rate : float
46
+ Learning rate of the neural network
47
+ coef: float
48
+ Sparsity promoting coefficient. Larger is sparser.
49
+ n_rules: int
50
+ Numbers of rules in the and layer
51
+ n_conditions: int
52
+ Numbers of output nodes of the multivariate predicate layer
53
+ n_seeds: int
54
+ Number of times the optimizer and network weights are reinitialized (number of runs)
55
+ max_epochs: int
56
+ Number of epochs ran per seed
57
+ init_temp: float
58
+ Initial temperature of the cooling schedule
59
+ min_temp: float
60
+ Minimal temperature of the cooling schedule
61
+ decay_rate: float
62
+ Decay rate of the cooling schedule
63
+ negation: bool
64
+ Whether negations of categorical features are considered or not
65
+ normalize_num: bool
66
+ Scale the input features before training. Only works for polynomial basis functions.
67
+ basis_functions: list of lambdas
68
+ List of basis expansion functions to be applied to the original input
69
+ name_format: bool
70
+ An option to specify a more informative feature names for some basis expansions (currently only quadratic).
71
+ E.g., x1*x0 instead of f1_x1_x0 as the feature name.
72
+ """
73
+ super(R2Nalgo, self).__init__()
74
+ self.batch_size = batch_size
75
+ self.learning_rate = learning_rate
76
+ self.coef = coef
77
+ self.n_rules = n_rules
78
+ self.n_conditions = n_conditions
79
+ self.n_seeds = n_seeds
80
+ self.decay_rate = decay_rate
81
+ self.init_temp = init_temp
82
+ self.min_temp = min_temp
83
+ self.max_epochs = math.ceil(
84
+ 1.2 * (math.log(self.min_temp) - math.log(self.init_temp)) / math.log(self.decay_rate))
85
+ self.negation = negation
86
+ self.normalize_num = normalize_num
87
+ self.basis_functions = basis_functions
88
+ self.name_format = name_format
89
+
90
+ self.encoder_object = None
91
+ self.norm_object = None
92
+ self.n_cat_features, self.n_num_features = None, None
93
+ self.num_column_names, self.cat_column_names = num_columns, cat_columns
94
+ self.model = None
95
+ self.opt_rule = None
96
+ self.predicate_learning_layer = None
97
+
98
+ def fit(self, train, train_labels):
99
+ """
100
+ The fit function for R2N algorithm. All float and int dtypes are considered numerical features.
101
+ All others are treated as categorical.
102
+ """
103
+
104
+ # Partition the input features through their dtype if cat and num features are not provided.
105
+ train.columns = train.columns.str.replace(' ', '')
106
+ if self.cat_column_names == None:
107
+ self.cat_column_names = list(train.select_dtypes(exclude=['float']).columns.str.replace(' ', ''))
108
+ if self.num_column_names == None:
109
+ self.num_column_names = list(train.select_dtypes(include=['float']).columns.str.replace(' ', ''))
110
+ X_cat, X_num, train_dataloader = self._preprocess(train, train_labels)
111
+
112
+ # Training loop
113
+ loss_fn = nn.MSELoss()
114
+ best_loss = math.inf
115
+ best_acc = -1
116
+ for _ in np.arange(0, self.n_seeds):
117
+
118
+ self.model, mlpnet, rulenet = self._construct_r2n()
119
+ optimizer = torch.optim.Adam(self.model.parameters(), lr=self.learning_rate, amsgrad=True)
120
+ temp = self.init_temp
121
+ for t in range(self.max_epochs):
122
+ temp, loss, success_rate_train = train_r2n(train_dataloader, self.model, loss_fn, optimizer, temp,
123
+ self.min_temp, self.decay_rate, self.coef, mv_reg=self.coef,
124
+ multivariate=(self.n_num_features != 0))
125
+ if t % 10 == 0:
126
+ print("Epoch:", t, "Success rate:", '%.1f' % success_rate_train, "Loss:", '%.5f' % loss,
127
+ "Temperature:", '%.1e' % temp)
128
+ if temp < self.min_temp and loss < best_loss:
129
+ if self.n_num_features > 0:
130
+ self.predicate_learning_layer = mlpnet
131
+ self.opt_rule = rulenet.extract_ruleset()
132
+ best_loss = loss
133
+ best_acc = success_rate_train
134
+ print('---------')
135
+
136
+ print("Max performance of the network:", best_acc)
137
+
138
+ # Post-processing
139
+ binarized_input = _binarize_input(X_num, X_cat, self.predicate_learning_layer, self.negation)
140
+ self.opt_rule = util.simplify_ruleset(util.filter_trivial_predicates(self.opt_rule, binarized_input))
141
+
142
+ def predict(self, features: pd.DataFrame) -> pd.DataFrame:
143
+ """
144
+ :param features: A pandas dataframe containing feature values
145
+ :return predictions: A pandas dataframe containing a single column called '__predictions__'
146
+ """
147
+ features.columns = features.columns.str.replace(' ', '')
148
+ _, X_cat_t, X_num_t = self._data_preprocessing(features, None, training=False)
149
+ pred_evaluation = _binarize_input(X_num_t, X_cat_t, self.predicate_learning_layer, self.negation)
150
+ eval_sol = util.batch_apply_ruleset(self.opt_rule, pred_evaluation.T)
151
+ predictions = pd.DataFrame(data=eval_sol, columns=['__predictions__'])
152
+
153
+ return predictions
154
+
155
+ def export_rules_to_trxf_dnf_ruleset(self, thr=0):
156
+ """
157
+ Args:
158
+ thr: Threshold to remove negligible coefficients. Any coefficient that has a smaller ratio to the dominating
159
+ coefficient than thr will be eliminated in the extracted ruleset.
160
+
161
+
162
+ Returns:
163
+ trxf.DnfRuleSet
164
+ """
165
+ learned_predicates = []
166
+ predetermined_predicates = []
167
+ if self.n_num_features > 0:
168
+ learned_predicates = self.predicate_learning_layer.get_predicates(
169
+ self.num_column_names, self.norm_object, thr, self.name_format)
170
+ if self.n_cat_features > 0:
171
+ a = list(zip(self.cat_column_names, self.encoder_object.categories_))
172
+ for item in a:
173
+ col_name = item[0]
174
+ categories = item[1]
175
+ predetermined_predicates += [Predicate(Feature(col_name), Relation.EQ, v) for v in categories]
176
+ predicates = learned_predicates + predetermined_predicates
177
+
178
+ return DnfRuleSet([Conjunction([pred for pred, w in zip(predicates, rule) if w > 0]) for rule in self.opt_rule],
179
+ then_part='Churn')
180
+
181
+ def _preprocess(self, train, train_labels):
182
+ y, X_cat, X_num = self._data_preprocessing(train, train_labels, training=True)
183
+ self.n_num_features = X_num.shape[1] if X_num is not None else 0
184
+ self.n_cat_features = X_cat.shape[1] if X_cat is not None else 0
185
+ dataset = _NumCatDataset(y, X_num, X_cat)
186
+ train_dataloader = DataLoader(dataset, self.batch_size)
187
+ return X_cat, X_num, train_dataloader
188
+
189
+ def _construct_r2n(self):
190
+ pred_learning = None
191
+ dim_num = 0
192
+ if self.n_num_features > 0:
193
+ if self.basis_functions is not None:
194
+ pred_learning = pp.ExpandedLinearFeatureLayer(self.basis_functions, self.n_num_features, self.n_conditions)
195
+ else:
196
+ pred_learning = pp.LinearFeatureLayer(self.n_num_features, self.n_conditions)
197
+ dim_num = pred_learning.dim_out
198
+ pred_layer = pp.PredicateLayer(pred_learning, self.negation)
199
+ dim_cat = 2 * self.n_cat_features if self.negation else self.n_cat_features
200
+ rulenet = rn.SimpleRuleNet(dim_num + dim_cat, self.n_rules)
201
+ model = nn.Sequential(pred_layer, rulenet)
202
+ return model, pred_learning, rulenet
203
+
204
+ def _data_preprocessing(self, train, train_labels, training):
205
+ """
206
+ Imports a pandas data frame and selects the numerical ('float') and categorical (NOT 'float') input features and
207
+ returns a pytorch dataframe and the number of categorical and numerical features.
208
+ """
209
+ X_cat, X_num = None, None
210
+ table_X_num, table_X_cat = train[self.num_column_names], train[self.cat_column_names]
211
+
212
+ if len(self.num_column_names)>0:
213
+ if self.normalize_num:
214
+ if training:
215
+ self.norm_object = StandardScaler(with_mean=False)
216
+ table_X_num = self.norm_object.fit_transform(np.array(table_X_num))
217
+ else:
218
+ assert self.norm_object is not None
219
+ table_X_num = self.norm_object.transform(np.array(table_X_num))
220
+ X_num = np.array(table_X_num, dtype=float)
221
+
222
+ if len(self.cat_column_names)>0:
223
+ if training:
224
+ print(table_X_num.shape[1], "numerical features and ", table_X_cat.shape[1], "categorical features")
225
+ print('---------')
226
+ self.encoder_object, X_cat = _categorical_encoding(table_X_cat.astype(str))
227
+ print(X_cat.shape[1], "categorical dummies")
228
+ print('---------')
229
+ else:
230
+ assert self.encoder_object is not None
231
+ X_cat = self.encoder_object.transform(table_X_cat.astype(str)).toarray()
232
+
233
+ if training:
234
+ y = np.array(train_labels, dtype=int)
235
+ else:
236
+ y = None
237
+
238
+ return y, X_cat, X_num
239
+
240
+
241
+
242
+ # Preprocessing function R2N ####
243
+
244
+ def _categorical_encoding(X_cat):
245
+ """
246
+ Encodes all categorical variables with OneHot encoding
247
+ """
248
+ ohe = OneHotEncoder(handle_unknown='ignore')
249
+ ohe.fit_transform(X_cat)
250
+ X = ohe.transform(X_cat).toarray()
251
+ return ohe, X
252
+
253
+
254
+ class _NumCatDataset(Dataset):
255
+ def __init__(self, y, X_num=None, X_cat=None):
256
+ self.X_num = X_num
257
+ self.X_cat = X_cat
258
+ self.num_tensor = torch.tensor(X_num, dtype=torch.float64) if X_num is not None else None
259
+ self.cat_tensor = torch.tensor(X_cat, dtype=torch.float64) if X_cat is not None else None
260
+ self.y_train = torch.tensor(y)
261
+
262
+ def __len__(self):
263
+ return len(self.y_train)
264
+
265
+ def __getitem__(self, idx):
266
+ xcat = self.cat_tensor[idx] if self.X_cat is not None else torch.empty(0)
267
+ xnum = self.num_tensor[idx] if self.X_num is not None else torch.empty(0)
268
+ return (xcat, xnum), self.y_train[idx]
269
+
270
+
271
+ def _binarize_input(X_num=None, X_cat=None, predicate_learning_layer=None, negation=False):
272
+ """
273
+ Evaluates the predicates and concatenates if necessary the numerical and categorical ones.
274
+ """
275
+ pred_evaluation = None
276
+ if X_num is not None:
277
+ pred_evaluation = (predicate_learning_layer(torch.tensor(X_num).float()) > 0.5).T.detach().numpy()
278
+ if X_cat is not None:
279
+ if X_num is not None:
280
+ pred_evaluation = np.concatenate((pred_evaluation, X_cat.T), axis=0)
281
+ else:
282
+ pred_evaluation = X_cat.T
283
+ if negation is not False:
284
+ pred_evaluation = np.concatenate((pred_evaluation, np.logical_not(X_cat.T)), axis=0)
285
+ if pred_evaluation is None:
286
+ raise ValueError('The input X_num and X_cat should not be both None')
287
+ return pred_evaluation
@@ -0,0 +1,75 @@
1
+ import torch
2
+ from torch import nn
3
+
4
+ import rulelearn_ri.algorithms.r2n.mask as wm
5
+ from rulelearn_ri.algorithms.r2n.base import RuleNetwork
6
+
7
+
8
+ class SimpleRuleNet(RuleNetwork):
9
+ """
10
+ Class that assembles the rule network part of r2n. It initializes and configures the conjunction and disjunction layer.
11
+ """
12
+ def __init__(self, n_conditions, n_rules, cooling=True):
13
+ super(SimpleRuleNet, self).__init__()
14
+ self.n_rules = n_rules
15
+ self.n_conditions = n_conditions
16
+ self.temp = 1
17
+ self.cooling = cooling
18
+ self.weightmask_1 = wm.SimpleWeightMask(self.n_rules, self.n_conditions, self.temp, self.cooling)
19
+ self.weightmask_2 = wm.SimpleWeightMask(1, self.n_rules, self.temp, self.cooling)
20
+ self.network = nn.Sequential(
21
+ ConjunctionLayer(self.weightmask_1),
22
+ DisjunctionLayer(self.weightmask_2),
23
+ )
24
+
25
+ def forward(self, x):
26
+ return self.network(x)
27
+
28
+ def extract_ruleset(self):
29
+ ruleset = []
30
+ w_conj = torch.heaviside(self.network[0].mask() - torch.tensor(0.5),
31
+ torch.ones_like(self.network[0].mask()))
32
+ w_disj = torch.heaviside(self.network[1].mask() - torch.tensor(0.5),
33
+ torch.ones_like(self.network[1].mask()))
34
+ for d_idx, d_weight in enumerate(torch.flatten(w_disj).tolist()):
35
+ if d_weight > 0:
36
+ rule = w_conj.tolist()[d_idx]
37
+ if sum(rule) > 0:
38
+ ruleset.append(rule)
39
+ return ruleset
40
+
41
+ def get_penalty(self):
42
+ and_masks = self.network[0].mask()
43
+ or_masks = self.network[1].mask()
44
+
45
+ return (or_masks.sum() + and_masks.sum()) / self.n_rules
46
+
47
+ def update_temp(self, temp):
48
+ self.weightmask_1.set_temp(temp)
49
+ self.weightmask_2.set_temp(temp)
50
+
51
+
52
+ class ConjunctionLayer(nn.Module):
53
+ """
54
+ Conjuction layer (And layer)
55
+ """
56
+ def __init__(self, mask):
57
+ super(ConjunctionLayer, self).__init__()
58
+ self.mask = mask
59
+
60
+ def forward(self, x):
61
+ neg_x = torch.ones_like(x) - x
62
+ prod = torch.matmul(neg_x, torch.transpose(self.mask().float(), 0, 1))
63
+ return torch.ones_like(prod) - torch.clamp(prod, max=1)
64
+
65
+
66
+ class DisjunctionLayer(nn.Module):
67
+ """
68
+ Disjunction layer (OR layer)
69
+ """
70
+ def __init__(self, mask):
71
+ super(DisjunctionLayer, self).__init__()
72
+ self.mask = mask
73
+
74
+ def forward(self, x):
75
+ return torch.clamp(torch.matmul(self.mask(), torch.transpose(x, 0, 1)), max=1)