1
+ import numpy as np
2
+ import tensorflow as tf
3
+ from sklearn .base import check_array
4
+ from cvxopt import solvers , matrix
5
+
6
+ from adapt .base import BaseAdaptEstimator , make_insert_doc
7
+ from adapt .utils import set_random_seed
8
+
9
+
10
+ def pairwise_X (X , Y ):
11
+ batch_size = tf .shape (X )[0 ]
12
+ dim = tf .reduce_prod (tf .shape (X )[1 :])
13
+ X = tf .reshape (X , (batch_size , dim ))
14
+ Y = tf .reshape (Y , (batch_size , dim ))
15
+ X = tf .tile (tf .expand_dims (X , - 1 ), [1 , 1 , batch_size ])
16
+ Y = tf .tile (tf .expand_dims (Y , - 1 ), [1 , 1 , batch_size ])
17
+ return tf .reduce_sum (tf .square (X - tf .transpose (Y )), 1 )
18
+
19
+
20
+ def _get_optim_function (Xs , Xt , kernel = "linear" , gamma = 1. , degree = 2 , coef = 1. ):
21
+
22
+ n = len (Xs )
23
+ m = len (Xt )
24
+ p = Xs .shape [1 ]
25
+
26
+ Lxx = tf .ones ((n ,n ), dtype = tf .float64 ) * (1. / (n ** 2 ))
27
+ Lxy = tf .ones ((n ,m ), dtype = tf .float64 ) * (- 1. / (n * m ))
28
+ Lyy = tf .ones ((m ,m ), dtype = tf .float64 ) * (1. / (m ** 2 ))
29
+ Lyx = tf .ones ((m ,n ), dtype = tf .float64 ) * (- 1. / (n * m ))
30
+
31
+ L = tf .concat ((Lxx , Lxy ), axis = 1 )
32
+ L = tf .concat ((L , tf .concat ((Lyx , Lyy ), axis = 1 )), axis = 0 )
33
+
34
+ if kernel == "linear" :
35
+
36
+ @tf .function
37
+ def func (W ):
38
+ Kxx = tf .matmul (tf .matmul (Xs , tf .linalg .diag (W ** 1 )), tf .transpose (Xs ))
39
+ Kyy = tf .matmul (tf .matmul (Xt , tf .linalg .diag (W ** 1 )), tf .transpose (Xt ))
40
+ Kxy = tf .matmul (tf .matmul (Xs , tf .linalg .diag (W ** 1 )), tf .transpose (Xt ))
41
+
42
+ K = tf .concat ((Kxx , Kxy ), axis = 1 )
43
+ K = tf .concat ((K , tf .concat ((Kyy , tf .transpose (Kxy )), axis = 1 )), axis = 0 )
44
+
45
+ f = - tf .linalg .trace (tf .matmul (K , L ))
46
+ Df = tf .gradients (f , W )
47
+ H = tf .hessians (f , W )
48
+ return f , Df , H
49
+
50
+ elif kernel == "rbf" :
51
+
52
+ @tf .function
53
+ def func (W ):
54
+ Kxx = pairwise_X (tf .matmul (Xs , tf .linalg .diag (W ** 1 )), Xs )
55
+ Kyy = pairwise_X (tf .matmul (Xt , tf .linalg .diag (W ** 1 )), Xt )
56
+ Kxy = pairwise_X (tf .matmul (Xs , tf .linalg .diag (W ** 1 )), Xt )
57
+
58
+ K = tf .concat ((Kxx , Kxy ), axis = 1 )
59
+ K = tf .concat ((K , tf .concat ((Kyy , tf .transpose (Kxy )), axis = 1 )), axis = 0 )
60
+ K = tf .exp (- gamma * K )
61
+
62
+ f = - tf .linalg .trace (tf .matmul (K , L ))
63
+ Df = tf .gradients (f , W )
64
+ H = tf .hessians (f , W )
65
+ return f , Df , H
66
+
67
+ elif kernel == "poly" :
68
+
69
+ @tf .function
70
+ def func (W ):
71
+ Kxx = tf .matmul (tf .matmul (Xs , tf .linalg .diag (W ** 1 )), tf .transpose (Xs ))
72
+ Kyy = tf .matmul (tf .matmul (Xt , tf .linalg .diag (W ** 1 )), tf .transpose (Xt ))
73
+ Kxy = tf .matmul (tf .matmul (Xs , tf .linalg .diag (W ** 1 )), tf .transpose (Xt ))
74
+
75
+ K = tf .concat ((Kxx , Kxy ), axis = 1 )
76
+ K = tf .concat ((K , tf .concat ((Kyy , tf .transpose (Kxy )), axis = 1 )), axis = 0 )
77
+ K = (gamma * K + coef )** degree
78
+
79
+ f = - tf .linalg .trace (tf .matmul (K , L ))
80
+ Df = tf .gradients (f , W )
81
+ H = tf .hessians (f , W )
82
+ return f , Df , H
83
+
84
+ else :
85
+ raise ValueError ("kernel param should be in ['linear', 'rbf', 'poly']" )
86
+
87
+ return func
88
+
89
+
90
+ @make_insert_doc ()
91
+ class fMMD (BaseAdaptEstimator ):
92
+ """
93
+ fMMD : feature Selection with MMD
94
+
95
+ LDM selects input features inorder to minimize the
96
+ maximum mean discrepancy (MMD) between the source and
97
+ the target data.
98
+
99
+ Parameters
100
+ ----------
101
+ threshold : float or 'auto' (default='auto')
102
+ Threshold on ``features_scores_`` all
103
+ feature with score above threshold will be
104
+ removed.
105
+ If 'auto' the threshold is chosen to maximize
106
+ the difference between scores of selected features
107
+ and removed ones.
108
+
109
+ kernel : str (default='linear')
110
+ Choose the kernel between
111
+ ['linear', 'rbf', 'poly'].
112
+ The kernels are computed as follows:
113
+ ``rbf(X, Y) = exp(gamma * ||X-Y||^2)``
114
+ ``poly(X, Y) = (gamma * <X, Y> + coef)^degree``
115
+
116
+ gamma : float (default=1.)
117
+ Gamma multiplier for the 'rbf' and
118
+ 'poly' kernels.
119
+
120
+ degree : int (default=2)
121
+ Degree of the 'poly' kernel
122
+
123
+ coef : float (default=1.)
124
+ Coef of the 'poly' kernel
125
+
126
+ Attributes
127
+ ----------
128
+ estimator_ : object
129
+ Estimator.
130
+
131
+ selected_features_ : numpy array
132
+ The selected features
133
+
134
+ features_scores_ : numpy array
135
+ The score attributed to each feature
136
+
137
+ See also
138
+ --------
139
+ CORAL
140
+ FE
141
+ """
142
+
143
+ def __init__ (self ,
144
+ estimator = None ,
145
+ Xt = None ,
146
+ yt = None ,
147
+ threshold = "auto" ,
148
+ kernel = "linear" ,
149
+ gamma = 1. ,
150
+ degree = 2 ,
151
+ coef = 1. ,
152
+ copy = True ,
153
+ verbose = 1 ,
154
+ random_state = None ,
155
+ ** params ):
156
+
157
+ names = self ._get_param_names ()
158
+ kwargs = {k : v for k , v in locals ().items () if k in names }
159
+ kwargs .update (params )
160
+ super ().__init__ (** kwargs )
161
+
162
+
163
+ def fit_transform (self , Xs , Xt , ** fit_params ):
164
+ Xs = check_array (Xs )
165
+ Xt = check_array (Xt )
166
+ set_random_seed (self .random_state )
167
+
168
+ n = len (Xs )
169
+ m = len (Xt )
170
+ p = Xs .shape [1 ]
171
+
172
+ optim_func = _get_optim_function (tf .identity (Xs ),
173
+ tf .identity (Xt ),
174
+ self .kernel ,
175
+ self .gamma ,
176
+ self .degree ,
177
+ self .coef )
178
+
179
+ def F (x = None , z = None ):
180
+ if x is None : return 0 , matrix (1.0 , (p ,1 ))
181
+ x = tf .identity (np .array (x ).ravel ())
182
+ f , Df , H = optim_func (x )
183
+ f = f .numpy ()
184
+ Df = Df [0 ].numpy ().reshape (1 , - 1 )
185
+ H = H [0 ].numpy ()
186
+ if z is None : return matrix (f ), matrix (Df )
187
+ return matrix (f ), matrix (Df ), matrix (H )
188
+
189
+ linear_const_G = - np .eye (p )
190
+ squared_constraint_G = np .concatenate ((np .zeros ((1 , p )), - np .eye (p )), axis = 0 )
191
+
192
+ linear_const_h = np .zeros (p )
193
+ squared_constraint_h = np .concatenate ((np .ones (1 ), np .zeros (p )))
194
+
195
+ G = matrix (np .concatenate ((linear_const_G , squared_constraint_G )))
196
+ h = matrix (np .concatenate ((linear_const_h , squared_constraint_h )))
197
+ dims = {'l' : p , 'q' : [p + 1 ], 's' : []}
198
+ sol = solvers .cp (F , G , h , dims )
199
+
200
+ W = np .array (sol ["x" ]).ravel ()
201
+
202
+ self .selected_features_ = np .zeros (p , dtype = bool )
203
+
204
+ if self .threshold == "auto" :
205
+ args = np .argsort (W ).ravel ()
206
+ max_diff_arg = np .argmax (np .diff (W [args ]))
207
+ threshold = W [args [max_diff_arg ]]
208
+ self .selected_features_ [W <= threshold ] = 1
209
+ else :
210
+ self .selected_features_ [W <= self .threshold ] = 1
211
+
212
+ if np .sum (self .selected_features_ ) == 0 :
213
+ raise Exception ("No features selected" )
214
+
215
+ self .features_scores_ = W
216
+ return Xs [:, self .selected_features_ ]
217
+
218
+
219
+ def transform (self , X ):
220
+ X = check_array (X )
221
+ return X [:, self .selected_features_ ]
0 commit comments