|
13 | 13 |
|
14 | 14 |
|
15 | 15 | def evaluate_lip_const_gen( |
16 | | - model: Model, |
17 | | - generator: Generator[Tuple[np.ndarray, np.ndarray], Any, None], |
18 | | - eps=1e-4, |
19 | | - seed=None, |
| 16 | + model: Model, generator: Generator[Tuple[np.ndarray, np.ndarray], Any, None] |
20 | 17 | ): |
21 | 18 | """ |
22 | | - Evaluate the Lipschitz constant of a model, with the naive method. |
23 | | - Please note that the estimation of the lipschitz constant is done locally around |
24 | | - input sample. This may not correctly estimate the behaviour in the whole domain. |
| 19 | + Evaluate the Lipschitz constant of a model, using the Jacobian of the model. |
| 20 | + Please note that the estimation of the Lipschitz constant is done locally around |
| 21 | + input samples. This may not correctly estimate the behaviour in the whole domain. |
25 | 22 | The computation might also be inaccurate in high dimensional space. |
26 | 23 |
|
27 | 24 | This is the generator version of evaluate_lip_const. |
28 | 25 |
|
29 | 26 | Args: |
30 | 27 | model: built keras model used to make predictions |
31 | 28 | generator: used to select datapoints where to compute the lipschitz constant |
32 | | - eps (float): magnitude of noise to add to input in order to compute the constant |
33 | | - seed (int): seed used when generating the noise ( can be set to None ) |
34 | 29 |
|
35 | 30 | Returns: |
36 | 31 | float: the empirically evaluated lipschitz constant. |
37 | 32 |
|
38 | 33 | """ |
39 | 34 | x, _ = generator.send(None) |
40 | | - return evaluate_lip_const(model, x, eps, seed=seed) |
| 35 | + return evaluate_lip_const(model, x) |
41 | 36 |
|
42 | 37 |
|
43 | | -def evaluate_lip_const(model: Model, x, eps=1e-4, seed=None): |
| 38 | +def evaluate_lip_const(model: Model, x): |
44 | 39 | """ |
45 | | - Evaluate the Lipschitz constant of a model, with the naive method. |
| 40 | + Evaluate the Lipschitz constant of a model, using the Jacobian of the model. |
46 | 41 | Please note that the estimation of the lipschitz constant is done locally around |
47 | | - input sample. This may not correctly estimate the behaviour in the whole domain. |
| 42 | + input samples. This may not correctly estimate the behaviour in the whole domain. |
48 | 43 |
|
49 | 44 | Args: |
50 | 45 | model: built keras model used to make predictions |
51 | 46 | x: inputs used to compute the lipschitz constant |
52 | | - eps (float): magnitude of noise to add to input in order to compute the constant |
53 | | - seed (int): seed used when generating the noise ( can be set to None ) |
54 | 47 |
|
55 | 48 | Returns: |
56 | | - float: the empirically evaluated lipschitz constant. The computation might also |
| 49 | + float: the empirically evaluated Lipschitz constant. The computation might also |
57 | 50 | be inaccurate in high dimensional space. |
58 | 51 |
|
59 | 52 | """ |
60 | | - y_pred = model.predict(x) |
61 | | - # x = np.repeat(x, 100, 0) |
62 | | - # y_pred = np.repeat(y_pred, 100, 0) |
63 | | - x_var = x + K.random_uniform( |
64 | | - shape=x.shape, minval=eps * 0.25, maxval=eps, seed=seed |
65 | | - ) |
66 | | - y_pred_var = model.predict(x_var) |
67 | | - dx = x - x_var |
68 | | - dfx = y_pred - y_pred_var |
69 | | - ndx = K.sqrt(K.sum(K.square(dx), axis=range(1, len(x.shape)))) |
70 | | - ndfx = K.sqrt(K.sum(K.square(dfx), axis=range(1, len(y_pred.shape)))) |
71 | | - lip_cst = K.max(ndfx / ndx) |
72 | | - print(f"lip cst: {lip_cst:.3f}") |
73 | | - return lip_cst |
| 53 | + batch_size = x.shape[0] |
| 54 | + x = tf.constant(x, dtype=model.input.dtype) |
| 55 | + |
| 56 | + # Get the jacobians of the model w.r.t. the inputs |
| 57 | + with tf.GradientTape() as tape: |
| 58 | + tape.watch(x) |
| 59 | + y_pred = model(x, training=False) |
| 60 | + batch_jacobian = tape.batch_jacobian(y_pred, x) |
| 61 | + |
| 62 | + # Reshape the jacobians (in case of multi-dimensional input/output like in conv) |
| 63 | + xdim = tf.reduce_prod(x.shape[1:]) |
| 64 | + ydim = tf.reduce_prod(y_pred.shape[1:]) |
| 65 | + batch_jacobian = tf.reshape(batch_jacobian, (batch_size, ydim, xdim)) |
| 66 | + |
| 67 | + # Compute the spectral norm of the jacobians and return the maximum |
| 68 | + b = tf.norm(batch_jacobian, ord=2, axis=[-2, -1]).numpy() |
| 69 | + return tf.reduce_max(b) |
74 | 70 |
|
75 | 71 |
|
76 | 72 | def _padding_circular(x, circular_paddings): |
|
0 commit comments