@@ -707,8 +707,8 @@ class Multinomial_LDL_Decomposition:
707
707
p : ndarray of shape (n_samples, n_classes)
708
708
Array of predicted probabilities per class (and sample).
709
709
710
- q : ndarray of shape (n_samples, n_classes)
711
- Helper array, q[:, j] = 1 - sum_{i=0}^j p[:, i].
710
+ q_inv : ndarray of shape (n_samples, n_classes)
711
+ Helper array, inverse of q[:, j] = 1 - sum_{i=0}^j p[:, i], i.e. 1/q .
712
712
713
713
sqrt_d : ndarray of shape (n_samples, n_classes)
714
714
Square root of the diagonal matrix D, D_ii = p_i * q_i / q_{i-1}
@@ -727,33 +727,35 @@ class Multinomial_LDL_Decomposition:
727
727
728
728
def __init__ (self , * , proba , proba_sum_to_1 = True ):
729
729
self .p = proba
730
- self . q = 1 - np .cumsum (self .p , axis = 1 ) # contiguity of p
730
+ q = 1 - np .cumsum (self .p , axis = 1 ) # contiguity of p
731
731
self .proba_sum_to_1 = proba_sum_to_1
732
732
if self .p .dtype == np .float32 :
733
733
eps = 2 * np .finfo (np .float32 ).resolution
734
734
else :
735
735
eps = 2 * np .finfo (np .float64 ).resolution
736
- if np .any (self . q [:, - 1 ] > eps ):
736
+ if np .any (q [:, - 1 ] > eps ):
737
737
warnings .warn (
738
738
"Probabilities proba are assumed to sum to 1, but they don't."
739
739
)
740
740
if self .proba_sum_to_1 :
8000
code>
741
741
# If np.sum(p, axis=1) = 1 then q[:, -1] = d[:, -1] = 0.
742
- self . q [:, - 1 ] = 0.0
742
+ q [:, - 1 ] = 0.0
743
743
# One might not need all classes for p to sum to 1, so we detect and
744
744
# correct all values close to zero.
745
- self . q [ self . q <= eps ] = 0.0
745
+ q [ q <= eps ] = 0.0
746
746
self .p [self .p <= eps ] = 0.0
747
- d = self .p * self . q
748
- # From now on, self. q is always used in the denominator. We handle q == 0 by
747
+ d = self .p * q
748
+ # From now on, q is always used in the denominator. We handle q == 0 by
749
749
# setting q to 1 whenever q == 0 such that a division of q is a no-op in this
750
750
# case.
751
- self .q [self .q == 0 ] = 1
751
+ q [q == 0 ] = 1
752
+ # And we use the inverse of q, 1/q.
753
+ self .q_inv = 1 / q
752
754
if self .proba_sum_to_1 :
753
755
# If q_{i - 1} = 0, then also q_i = 0.
754
- d [:, 1 :- 1 ] / = self .q [:, :- 2 ] # d[:, -1] = 0 anyway.
756
+ d [:, 1 :- 1 ] * = self .q_inv [:, :- 2 ] # d[:, -1] = 0 anyway.
755
757
else :
756
- d [:, 1 :] / = self .q [:, :- 1 ]
758
+ d [:, 1 :] * = self .q_inv [:, :- 1 ]
757
759
self .sqrt_d = np .sqrt (d )
758
760
759
761
def sqrt_D_Lt_matmul (self , x ):
@@ -787,8 +789,16 @@ def sqrt_D_Lt_matmul(self, x):
787
789
n_classes = self .p .shape [1 ]
788
790
for i in range (0 , n_classes - 1 ): # row i
789
791
# L_ij = -p_i / q_j, we need transpose L'
790
- for j in range (i + 1 , n_classes ): # column j
791
- x [:, i ] -= self .p [:, j ] / self .q [:, i ] * x [:, j ]
792
+ # for j in range(i + 1, n_classes): # column j
793
+ # x[:, i] -= self.p[:, j] / self.q[:, i] * x[:, j]
794
+ # The following is the same but faster.
795
+ px = np .einsum (
796
+ "ij,ij->i" ,
797
+ self .p [:, i + 1 : n_classes ],
798
+ x [:, i + 1 : n_classes ],
799
+ order = "A" ,
800
+ )
801
+ x [:, i ] -= px * self .q_inv [:, i ]
792
802
x *= self .sqrt_d
793
803
return x
794
804
@@ -823,8 +833,11 @@ def L_sqrt_D_matmul(self, x):
823
833
x *= self .sqrt_d
824
834
for i in range (n_classes - 1 , 0 , - 1 ): # row i
825
835
# L_ij = -p_i / q_j
826
- for j in range (0 , i ): # column j
827
- x [:, i ] -= self .p [:, i ] / self .q [:, j ] * x [:, j ]
836
+ # for j in range(0, i): # column j
837
+ # x[:, i] -= self.p[:, i] / self.q[:, j] * x[:, j]
838
+ # The following is the same but faster.
839
+ qx = np .einsum ("ij,ij->i" , self .q_inv [:, :i ], x [:, :i ], order = "A" )
840
+ x [:, i ] -= qx * self .p [:, i ]
828
841
return x
829
842
830
843
def inverse_L_sqrt_D_matmul (self , x ):
@@ -855,11 +868,13 @@ def inverse_L_sqrt_D_matmul(self, x):
855
868
n_classes = self .p .shape [1 ]
856
869
for i in range (n_classes - 1 , 0 , - 1 ): # row i
857
870
if i > 0 :
858
- fj = self .p [:, i ] / self .q [:, i - 1 ]
871
+ fj = self .p [:, i ] * self .q_inv [:, i - 1 ]
859
872
else :
860
873
fj = self .p [:, i ]
861
- for j in range (0 , i ): # column j
862
- x [:, i ] += fj * x [:, j ]
874
+ # for j in range(0, i): # column j
875
+ # x[:, i] += fj * x[:, j]
876
+ # The following is the same but faster.
877
+ x [:, i ] += fj * np .sum (x [:, :i ], axis = 1 )
863
878
if self .proba_sum_to_1 :
864
879
# x[:, :-1] /= self.sqrt_d[:, :-1]
865
880
mask = self .sqrt_d [:, :- 1 ] == 0
0 commit comments