From 937f525e6e255f5bc6961b6a36c157e44a06955e Mon Sep 17 00:00:00 2001 From: jongari7 Date: Fri, 20 Sep 2024 12:48:01 +0200 Subject: [PATCH] Improved the importance() function, adding another method to compute it. Added an input variable: method = '1class' (for the previous method) or 'comb_classes' (for the new method). The default method is '1class'. Created a little function (subtract_from_index()) in the standard.py script to be used in the computation of the new method. --- .idea/misc.xml | 4 +++ .idea/pudu_jon.iml | 2 +- pudu/pudu.py | 75 +++++++++++++++++++++++++++++++++++----------- pudu/standards.py | 29 ++++++++++++++++++ 4 files changed, 92 insertions(+), 18 deletions(-) create mode 100644 .idea/misc.xml diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..a971a2c --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/pudu_jon.iml b/.idea/pudu_jon.iml index 8b8c395..b5ad51a 100644 --- a/.idea/pudu_jon.iml +++ b/.idea/pudu_jon.iml @@ -2,7 +2,7 @@ - + diff --git a/pudu/pudu.py b/pudu/pudu.py index 19070d5..00f23ce 100644 --- a/pudu/pudu.py +++ b/pudu/pudu.py @@ -68,10 +68,12 @@ def __init__(self, x, y, pf, model=None): # Some error handling error_handler.for_constructor(model, x, y) - - def importance(self, window=1, scope=None, evolution=None, padding='center', bias=0, - absolute=False, perturbation=ptn.Bidirectional(), mask=msk.All()): + + def importance(self, window=1, scope=None, evolution=None, + padding='center', bias=0, absolute=False, + perturbation=ptn.Bidirectional(), method='1class', + mask=msk.All()): """ Calculates the importance vector for the input feature. @@ -80,21 +82,33 @@ def importance(self, window=1, scope=None, evolution=None, padding='center', bia :type scope: tupple(int, int) :param scope: Starting and ending point of the analysis for each feature. - If `None`, the all the vector is analysed. + If `None`, the all the vector is analysed. :type evolution: int :param evolution: feature width to be changeg each time. - :type padding: string - :param padding: Type of padding. If the legnth of `x` is not divisible - by `window` then padding is applyed. If `center`, then equal padding - to each side is applyed. If `right`, then paading to the right is + :type padding: string + :param padding: Type of padding. If the legnth of `x` is not divisible + by `window` then padding is applyed. If `center`, then equal padding + to each side is applyed. If `right`, then paading to the right is added and `window`starts from `0`. If `left`, padding to the left is applyied and `window` ends at length `x`. If perfet `center` is not possible, then ipadding left is added `1`. - + :type absolute: bool - :param absolute: Weather or not the result is in absolute value or not. Default is `False`. + :param absolute: Weather or not the result is in absolute value or not. + Default is `False`. + + :type method: string + :param method: Method to compute the importance, there are 2 + posibilities: '1class': only is used the evolution class or the + currect class (if evolution=None) to compute the difference in the + probabilities. + And 'comb_classes': is used all the probabilities + classes to see how is the change between the different classes + (looking at probability ratio changes) to compute the importance, + and only taking into account the dominant term (If evolution=None, + the method always is the '1class'). Default is `1class`. """ error_handler.for_params(window, scope, padding, absolute, 0, None, None) @@ -105,6 +119,7 @@ def importance(self, window=1, scope=None, evolution=None, padding='center', bia scope, window, padd, evolution, total = standards.params_std(self.y, sh, scope, window, padding, evolution) + initial_class = int(self.y) p0 = self.pf(self.x) section = 1 row = padd[0][0] + scope[0][0] @@ -121,10 +136,33 @@ def importance(self, window=1, scope=None, evolution=None, padding='center', bia temp, temp2 = perturbation.apply(x_copy, row, col, window, bias) - if temp2 is None: - val = self.pf(temp) - p0 - else: - val = (self.pf(temp2) + self.pf(temp) - 2*p0) / 2 + if (method == '1class' or evolution == initial_class or + temp2 is not None): + if temp2 is None: + val = self.pf(temp) - p0 + else: + val = (self.pf(temp2) + self.pf(temp) - 2*p0) / 2 + + elif method == 'comb_classes': + # The importance is computed by the change in the + # difference between classes + p1 = self.pf(temp) + # Compute the difference between the class probs + dif_p0 = standards.subtract_from_index(p0, + initial_class) + dif_p1 = standards.subtract_from_index(p1, + initial_class) + + dif_probs = dif_p0 - dif_p1 + + # Only keeps the dominate value + # Find the index of the maximum absolute value + max_index = np.argmax(np.abs(dif_probs)) + # Create a new array filled with zeros + val = np.zeros_like(dif_probs) + # Set the value at max_index to the original value + # (not absolute) + val[max_index] = dif_probs[max_index] if absolute: val = abs(val) @@ -133,7 +171,7 @@ def importance(self, window=1, scope=None, evolution=None, padding='center', bia self.imp[0, row:row+window[0], col:col+window[1], 0] = val[evolution] else: self.imp[0, row:row+window[0], col:col+window[1], 0] = val - + del x_copy, temp, temp2 else: pass @@ -142,10 +180,13 @@ def importance(self, window=1, scope=None, evolution=None, padding='center', bia col += window[1] row += window[0] - + max_val, min_val = self.imp.max(), self.imp.min() - self.imp_rel = (self.imp - min_val) / (max_val - min_val) + if max_val == min_val: + self.imp_rel = np.zeros_like(self.imp) + else: + self.imp_rel = (self.imp - min_val) / (max_val - min_val) def speed(self, window=1, scope=None, evolution=None, padding='center', diff --git a/pudu/standards.py b/pudu/standards.py index 981088f..b0b2c15 100644 --- a/pudu/standards.py +++ b/pudu/standards.py @@ -86,3 +86,32 @@ def params_std(y, sh, scope, window, padding, evolution): total = sec_row*sec_col return scope, window, padding, evolution, total + + +def subtract_from_index(vector, index): + """ + This function substract the value of the index to all the vector values. + + :type vector: numpy array + :param vector: Vector (or array 1d) + + :type index: int + :param index: Index of the substracted value + + :rtype: numpy array + :returns: Vector (or array 1d) with the substracted result + """ + # Convert p0 to a numpy array of floats (in case it's not already) + vector = np.array(vector, dtype=float) + + # Check that the index is within the valid range + if index < 0 or index >= len(vector): + raise IndexError("The index is out of bounds") + + # Get the value at the selected index + value = vector[index] + + # Subtract the value at the selected index from all other elements + result = vector - value + + return result