tf-extra 1.0.0__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.
tf_extra/__init__.py ADDED
@@ -0,0 +1,19 @@
1
+ """
2
+ TF-Extra: A powerful extension library for TensorFlow/Keras.
3
+
4
+ Provides MultiOptimizer, custom layers, losses, and callbacks
5
+ that extend TensorFlow's functionality.
6
+
7
+ Usage:
8
+ import tf_extra
9
+ opt = tf_extra.optimizers.MultiOptimizer(optimizers_and_layers)
10
+ """
11
+
12
+ __version__ = "1.0.0"
13
+ __author__ = "Your Name"
14
+ __license__ = "MIT"
15
+
16
+ from tf_extra.tf_extra import optimizers
17
+ from tf_extra.tf_extra import layers
18
+ from tf_extra.tf_extra import losses
19
+ from tf_extra.tf_extra import callbacks
@@ -0,0 +1,13 @@
1
+ """TF-Extra Custom Callbacks Module."""
2
+
3
+ from tf_extra.callbacks.custom_callbacks import (
4
+ CosineAnnealingScheduler,
5
+ GradientAccumulationCallback,
6
+ TimeLimitCallback,
7
+ )
8
+
9
+ __all__ = [
10
+ "CosineAnnealingScheduler",
11
+ "GradientAccumulationCallback",
12
+ "TimeLimitCallback",
13
+ ]
@@ -0,0 +1,104 @@
1
+ """
2
+ Custom Keras Callbacks for TF-Extra.
3
+ """
4
+
5
+ import tensorflow as tf
6
+ import math
7
+ import time
8
+
9
+
10
+ class CosineAnnealingScheduler(tf.keras.callbacks.Callback):
11
+ """
12
+ Cosine Annealing Learning Rate Scheduler with Warm Restarts.
13
+
14
+ Implements SGDR: Stochastic Gradient Descent with Warm Restarts.
15
+ The learning rate follows a cosine curve from eta_max to eta_min,
16
+ resetting every T_0 epochs (multiplied by T_mult after each restart).
17
+
18
+ Args:
19
+ eta_min: Minimum learning rate. Default 1e-7.
20
+ eta_max: Maximum learning rate. Default 1e-3.
21
+ T_0: Number of epochs for the first restart. Default 10.
22
+ T_mult: Factor to increase T_0 after each restart. Default 2.
23
+ verbose: Whether to print learning rate changes. Default False.
24
+ """
25
+
26
+ def __init__(self, eta_min=1e-7, eta_max=1e-3, T_0=10, T_mult=2, verbose=False, **kwargs):
27
+ super().__init__(**kwargs)
28
+ self.eta_min = eta_min
29
+ self.eta_max = eta_max
30
+ self.T_0 = T_0
31
+ self.T_mult = T_mult
32
+ self.verbose = verbose
33
+ self.T_i = T_0
34
+ self.T_cur = 0
35
+
36
+ def on_epoch_begin(self, epoch, logs=None):
37
+ lr = self.eta_min + 0.5 * (self.eta_max - self.eta_min) * (
38
+ 1 + math.cos(math.pi * self.T_cur / self.T_i)
39
+ )
40
+ tf.keras.backend.set_value(self.model.optimizer.learning_rate, lr)
41
+
42
+ if self.verbose:
43
+ print(f"\nEpoch {epoch + 1}: CosineAnnealing LR = {lr:.8f}")
44
+
45
+ self.T_cur += 1
46
+ if self.T_cur >= self.T_i:
47
+ self.T_cur = 0
48
+ self.T_i = int(self.T_i * self.T_mult)
49
+
50
+
51
+ class GradientAccumulationCallback(tf.keras.callbacks.Callback):
52
+ """
53
+ Simulates larger batch sizes by accumulating gradients over multiple steps.
54
+
55
+ Note: This callback works best with a custom training loop. For
56
+ model.fit(), it adjusts the effective learning rate to simulate
57
+ accumulation.
58
+
59
+ Args:
60
+ accumulation_steps: Number of steps to accumulate before updating.
61
+ """
62
+
63
+ def __init__(self, accumulation_steps=4, **kwargs):
64
+ super().__init__(**kwargs)
65
+ self.accumulation_steps = accumulation_steps
66
+
67
+ def on_train_begin(self, logs=None):
68
+ # Scale learning rate by accumulation steps
69
+ current_lr = float(tf.keras.backend.get_value(self.model.optimizer.learning_rate))
70
+ self._original_lr = current_lr
71
+ scaled_lr = current_lr * self.accumulation_steps
72
+ tf.keras.backend.set_value(self.model.optimizer.learning_rate, scaled_lr)
73
+ print(f"GradientAccumulation: Scaled LR from {current_lr:.6f} to {scaled_lr:.6f}")
74
+
75
+ def on_train_end(self, logs=None):
76
+ tf.keras.backend.set_value(self.model.optimizer.learning_rate, self._original_lr)
77
+
78
+
79
+ class TimeLimitCallback(tf.keras.callbacks.Callback):
80
+ """
81
+ Stops training after a specified number of seconds.
82
+
83
+ Useful for Kaggle competitions or cloud instances with time limits.
84
+
85
+ Args:
86
+ max_seconds: Maximum training time in seconds.
87
+ verbose: Whether to print when stopping. Default True.
88
+ """
89
+
90
+ def __init__(self, max_seconds, verbose=True, **kwargs):
91
+ super().__init__(**kwargs)
92
+ self.max_seconds = max_seconds
93
+ self.verbose = verbose
94
+
95
+ def on_train_begin(self, logs=None):
96
+ self._start_time = time.time()
97
+
98
+ def on_epoch_end(self, epoch, logs=None):
99
+ elapsed = time.time() - self._start_time
100
+ if elapsed >= self.max_seconds:
101
+ if self.verbose:
102
+ print(f"\nTimeLimitCallback: Stopping training after {elapsed:.1f}s "
103
+ f"(limit: {self.max_seconds}s)")
104
+ self.model.stop_training = True
@@ -0,0 +1,27 @@
1
+ """TF-Extra Custom Layers Module."""
2
+
3
+ from tf_extra.layers.custom_layers import (
4
+ SpatialDropout1D,
5
+ ChannelAttention,
6
+ SpatialAttention,
7
+ CBAM,
8
+ SqueezeExcitation,
9
+ MultiHeadSelfAttention,
10
+ PositionalEncoding,
11
+ GeM,
12
+ AdaptiveConcatPool2D,
13
+ Mish,
14
+ )
15
+
16
+ __all__ = [
17
+ "SpatialDropout1D",
18
+ "ChannelAttention",
19
+ "SpatialAttention",
20
+ "CBAM",
21
+ "SqueezeExcitation",
22
+ "MultiHeadSelfAttention",
23
+ "PositionalEncoding",
24
+ "GeM",
25
+ "AdaptiveConcatPool2D",
26
+ "Mish",
27
+ ]
@@ -0,0 +1,361 @@
1
+ """
2
+ Custom Keras Layers for TF-Extra.
3
+
4
+ Provides production-ready layers that are commonly needed but not
5
+ included in the default tf.keras.layers module.
6
+ """
7
+
8
+ import tensorflow as tf
9
+ import numpy as np
10
+ import math
11
+
12
+
13
+ class SpatialDropout1D(tf.keras.layers.Layer):
14
+ """
15
+ Spatial 1D Dropout: drops entire 1D feature maps instead of individual elements.
16
+ Useful for NLP embeddings and 1D convolutions.
17
+
18
+ Args:
19
+ rate: Float between 0 and 1. Fraction of feature maps to drop.
20
+ """
21
+
22
+ def __init__(self, rate=0.5, **kwargs):
23
+ super().__init__(**kwargs)
24
+ self.rate = rate
25
+ self.dropout = tf.keras.layers.Dropout(rate)
26
+
27
+ def call(self, inputs, training=None):
28
+ # inputs shape: (batch, timesteps, channels)
29
+ # We want to drop entire channels, so we reshape
30
+ input_shape = tf.shape(inputs)
31
+ noise_shape = (input_shape[0], 1, input_shape[2])
32
+ return tf.nn.dropout(inputs, rate=self.rate, noise_shape=noise_shape) if training else inputs
33
+
34
+ def get_config(self):
35
+ config = super().get_config()
36
+ config["rate"] = self.rate
37
+ return config
38
+
39
+
40
+ class ChannelAttention(tf.keras.layers.Layer):
41
+ """
42
+ Channel Attention Module (from CBAM).
43
+
44
+ Applies both average-pooling and max-pooling, passes each through
45
+ a shared MLP, then combines them with a sigmoid gate.
46
+
47
+ Args:
48
+ reduction_ratio: Integer, reduction ratio for the bottleneck. Default 16.
49
+ """
50
+
51
+ def __init__(self, reduction_ratio=16, **kwargs):
52
+ super().__init__(**kwargs)
53
+ self.reduction_ratio = reduction_ratio
54
+
55
+ def build(self, input_shape):
56
+ channels = input_shape[-1]
57
+ reduced = max(1, channels // self.reduction_ratio)
58
+ self.fc1 = tf.keras.layers.Dense(reduced, activation="relu", use_bias=False)
59
+ self.fc2 = tf.keras.layers.Dense(channels, use_bias=False)
60
+ super().build(input_shape)
61
+
62
+ def call(self, inputs):
63
+ avg_pool = tf.reduce_mean(inputs, axis=[1, 2], keepdims=True)
64
+ max_pool = tf.reduce_max(inputs, axis=[1, 2], keepdims=True)
65
+
66
+ avg_out = self.fc2(self.fc1(avg_pool))
67
+ max_out = self.fc2(self.fc1(max_pool))
68
+
69
+ attention = tf.nn.sigmoid(avg_out + max_out)
70
+ return inputs * attention
71
+
72
+ def get_config(self):
73
+ config = super().get_config()
74
+ config["reduction_ratio"] = self.reduction_ratio
75
+ return config
76
+
77
+
78
+ class SpatialAttention(tf.keras.layers.Layer):
79
+ """
80
+ Spatial Attention Module (from CBAM).
81
+
82
+ Concatenates average-pooled and max-pooled features along the channel
83
+ axis, then applies a convolution with sigmoid activation.
84
+
85
+ Args:
86
+ kernel_size: Integer, kernel size for the convolution. Default 7.
87
+ """
88
+
89
+ def __init__(self, kernel_size=7, **kwargs):
90
+ super().__init__(**kwargs)
91
+ self.kernel_size = kernel_size
92
+
93
+ def build(self, input_shape):
94
+ self.conv = tf.keras.layers.Conv2D(
95
+ 1,
96
+ kernel_size=self.kernel_size,
97
+ padding="same",
98
+ activation="sigmoid",
99
+ use_bias=False,
100
+ )
101
+ super().build(input_shape)
102
+
103
+ def call(self, inputs):
104
+ avg_pool = tf.reduce_mean(inputs, axis=-1, keepdims=True)
105
+ max_pool = tf.reduce_max(inputs, axis=-1, keepdims=True)
106
+ concat = tf.concat([avg_pool, max_pool], axis=-1)
107
+ attention = self.conv(concat)
108
+ return inputs * attention
109
+
110
+ def get_config(self):
111
+ config = super().get_config()
112
+ config["kernel_size"] = self.kernel_size
113
+ return config
114
+
115
+
116
+ class CBAM(tf.keras.layers.Layer):
117
+ """
118
+ Convolutional Block Attention Module.
119
+
120
+ Sequentially applies Channel Attention then Spatial Attention.
121
+
122
+ Args:
123
+ reduction_ratio: Reduction ratio for channel attention. Default 16.
124
+ kernel_size: Kernel size for spatial attention conv. Default 7.
125
+ """
126
+
127
+ def __init__(self, reduction_ratio=16, kernel_size=7, **kwargs):
128
+ super().__init__(**kwargs)
129
+ self.channel_attention = ChannelAttention(reduction_ratio)
130
+ self.spatial_attention = SpatialAttention(kernel_size)
131
+ self.reduction_ratio = reduction_ratio
132
+ self.kernel_size = kernel_size
133
+
134
+ def call(self, inputs):
135
+ x = self.channel_attention(inputs)
136
+ x = self.spatial_attention(x)
137
+ return x
138
+
139
+ def get_config(self):
140
+ config = super().get_config()
141
+ config["reduction_ratio"] = self.reduction_ratio
142
+ config["kernel_size"] = self.kernel_size
143
+ return config
144
+
145
+
146
+ class SqueezeExcitation(tf.keras.layers.Layer):
147
+ """
148
+ Squeeze-and-Excitation block.
149
+
150
+ Recalibrates channel-wise feature responses by explicitly modelling
151
+ interdependencies between channels.
152
+
153
+ Args:
154
+ reduction_ratio: Reduction ratio for the bottleneck. Default 16.
155
+ """
156
+
157
+ def __init__(self, reduction_ratio=16, **kwargs):
158
+ super().__init__(**kwargs)
159
+ self.reduction_ratio = reduction_ratio
160
+
161
+ def build(self, input_shape):
162
+ channels = input_shape[-1]
163
+ reduced = max(1, channels // self.reduction_ratio)
164
+ self.fc1 = tf.keras.layers.Dense(reduced, activation="relu", use_bias=False)
165
+ self.fc2 = tf.keras.layers.Dense(channels, activation="sigmoid", use_bias=False)
166
+ super().build(input_shape)
167
+
168
+ def call(self, inputs):
169
+ se = tf.reduce_mean(inputs, axis=[1, 2], keepdims=True)
170
+ se = self.fc1(se)
171
+ se = self.fc2(se)
172
+ return inputs * se
173
+
174
+ def get_config(self):
175
+ config = super().get_config()
176
+ config["reduction_ratio"] = self.reduction_ratio
177
+ return config
178
+
179
+
180
+ class MultiHeadSelfAttention(tf.keras.layers.Layer):
181
+ """
182
+ Multi-Head Self-Attention layer.
183
+
184
+ A clean implementation of scaled dot-product multi-head attention
185
+ that can be used in custom transformer architectures.
186
+
187
+ Args:
188
+ embed_dim: Dimensionality of the embedding.
189
+ num_heads: Number of attention heads.
190
+ dropout_rate: Dropout rate for attention weights. Default 0.0.
191
+ """
192
+
193
+ def __init__(self, embed_dim, num_heads, dropout_rate=0.0, **kwargs):
194
+ super().__init__(**kwargs)
195
+ self.embed_dim = embed_dim
196
+ self.num_heads = num_heads
197
+ self.dropout_rate = dropout_rate
198
+
199
+ if embed_dim % num_heads != 0:
200
+ raise ValueError(
201
+ f"embed_dim ({embed_dim}) must be divisible by "
202
+ f"num_heads ({num_heads})."
203
+ )
204
+ self.head_dim = embed_dim // num_heads
205
+
206
+ def build(self, input_shape):
207
+ self.wq = tf.keras.layers.Dense(self.embed_dim)
208
+ self.wk = tf.keras.layers.Dense(self.embed_dim)
209
+ self.wv = tf.keras.layers.Dense(self.embed_dim)
210
+ self.dense = tf.keras.layers.Dense(self.embed_dim)
211
+ self.dropout = tf.keras.layers.Dropout(self.dropout_rate)
212
+ super().build(input_shape)
213
+
214
+ def _split_heads(self, x, batch_size):
215
+ x = tf.reshape(x, (batch_size, -1, self.num_heads, self.head_dim))
216
+ return tf.transpose(x, perm=[0, 2, 1, 3])
217
+
218
+ def call(self, inputs, mask=None, training=None):
219
+ batch_size = tf.shape(inputs)[0]
220
+
221
+ q = self._split_heads(self.wq(inputs), batch_size)
222
+ k = self._split_heads(self.wk(inputs), batch_size)
223
+ v = self._split_heads(self.wv(inputs), batch_size)
224
+
225
+ scale = tf.math.sqrt(tf.cast(self.head_dim, tf.float32))
226
+ scores = tf.matmul(q, k, transpose_b=True) / scale
227
+
228
+ if mask is not None:
229
+ scores += mask * -1e9
230
+
231
+ weights = tf.nn.softmax(scores, axis=-1)
232
+ weights = self.dropout(weights, training=training)
233
+
234
+ attention = tf.matmul(weights, v)
235
+ attention = tf.transpose(attention, perm=[0, 2, 1, 3])
236
+ attention = tf.reshape(attention, (batch_size, -1, self.embed_dim))
237
+
238
+ return self.dense(attention)
239
+
240
+ def get_config(self):
241
+ config = super().get_config()
242
+ config["embed_dim"] = self.embed_dim
243
+ config["num_heads"] = self.num_heads
244
+ config["dropout_rate"] = self.dropout_rate
245
+ return config
246
+
247
+
248
+ class PositionalEncoding(tf.keras.layers.Layer):
249
+ """
250
+ Sinusoidal Positional Encoding layer.
251
+
252
+ Adds positional information to embeddings using sine and cosine
253
+ functions of different frequencies.
254
+
255
+ Args:
256
+ max_len: Maximum sequence length. Default 5000.
257
+ embed_dim: Embedding dimensionality. If None, inferred from input.
258
+ """
259
+
260
+ def __init__(self, max_len=5000, embed_dim=None, **kwargs):
261
+ super().__init__(**kwargs)
262
+ self.max_len = max_len
263
+ self.embed_dim = embed_dim
264
+
265
+ def build(self, input_shape):
266
+ d_model = self.embed_dim or int(input_shape[-1])
267
+ pe = np.zeros((self.max_len, d_model), dtype=np.float32)
268
+ position = np.arange(0, self.max_len)[:, np.newaxis]
269
+ div_term = np.exp(np.arange(0, d_model, 2) * -(math.log(10000.0) / d_model))
270
+ pe[:, 0::2] = np.sin(position * div_term)
271
+ pe[:, 1::2] = np.cos(position * div_term[: d_model // 2 + d_model % 2][:pe[:, 1::2].shape[1]])
272
+ self.pe = tf.constant(pe[np.newaxis, :, :]) # (1, max_len, d_model)
273
+ super().build(input_shape)
274
+
275
+ def call(self, inputs):
276
+ seq_len = tf.shape(inputs)[1]
277
+ return inputs + self.pe[:, :seq_len, :]
278
+
279
+ def get_config(self):
280
+ config = super().get_config()
281
+ config["max_len"] = self.max_len
282
+ config["embed_dim"] = self.embed_dim
283
+ return config
284
+
285
+
286
+ class GeM(tf.keras.layers.Layer):
287
+ """
288
+ Generalized Mean Pooling (GeM).
289
+
290
+ Computes the generalized mean of each feature map. When p=1 it
291
+ becomes average pooling; as pโ†’โˆž it becomes max pooling.
292
+
293
+ Args:
294
+ p_init: Initial value of the learnable exponent p. Default 3.0.
295
+ eps: Small constant for numerical stability. Default 1e-6.
296
+ """
297
+
298
+ def __init__(self, p_init=3.0, eps=1e-6, **kwargs):
299
+ super().__init__(**kwargs)
300
+ self.p_init = p_init
301
+ self.eps = eps
302
+
303
+ def build(self, input_shape):
304
+ self.p = self.add_weight(
305
+ name="p", shape=(1,), initializer=tf.keras.initializers.Constant(self.p_init), trainable=True
306
+ )
307
+ super().build(input_shape)
308
+
309
+ def call(self, inputs):
310
+ x = tf.clip_by_value(inputs, clip_value_min=self.eps, clip_value_max=tf.float32.max)
311
+ x = tf.pow(x, self.p)
312
+ x = tf.reduce_mean(x, axis=[1, 2], keepdims=False)
313
+ x = tf.pow(x, 1.0 / self.p)
314
+ return x
315
+
316
+ def get_config(self):
317
+ config = super().get_config()
318
+ config["p_init"] = self.p_init
319
+ config["eps"] = self.eps
320
+ return config
321
+
322
+
323
+ class AdaptiveConcatPool2D(tf.keras.layers.Layer):
324
+ """
325
+ Adaptive Concatenated Pooling (from fastai).
326
+
327
+ Concatenates the results of adaptive average pooling and
328
+ adaptive max pooling, giving the model access to both statistics.
329
+
330
+ Args:
331
+ output_size: Target spatial size (H, W). Default (1, 1).
332
+ """
333
+
334
+ def __init__(self, output_size=(1, 1), **kwargs):
335
+ super().__init__(**kwargs)
336
+ self.output_size = output_size
337
+
338
+ def call(self, inputs):
339
+ avg = tf.reduce_mean(inputs, axis=[1, 2], keepdims=True)
340
+ mx = tf.reduce_max(inputs, axis=[1, 2], keepdims=True)
341
+ return tf.concat([avg, mx], axis=-1)
342
+
343
+ def get_config(self):
344
+ config = super().get_config()
345
+ config["output_size"] = self.output_size
346
+ return config
347
+
348
+
349
+ class Mish(tf.keras.layers.Layer):
350
+ """
351
+ Mish activation function: x * tanh(softplus(x)).
352
+
353
+ A self-regularized non-monotonic activation function that often
354
+ outperforms ReLU and Swish.
355
+ """
356
+
357
+ def call(self, inputs):
358
+ return inputs * tf.math.tanh(tf.math.softplus(inputs))
359
+
360
+ def get_config(self):
361
+ return super().get_config()
@@ -0,0 +1,19 @@
1
+ """TF-Extra Custom Losses Module."""
2
+
3
+ from tf_extra.losses.custom_losses import (
4
+ FocalLoss,
5
+ DiceLoss,
6
+ TverskyLoss,
7
+ LabelSmoothingCrossEntropy,
8
+ ContrastiveLoss,
9
+ TripletLoss,
10
+ )
11
+
12
+ __all__ = [
13
+ "FocalLoss",
14
+ "DiceLoss",
15
+ "TverskyLoss",
16
+ "LabelSmoothingCrossEntropy",
17
+ "ContrastiveLoss",
18
+ "TripletLoss",
19
+ ]
@@ -0,0 +1,196 @@
1
+ """
2
+ Custom Loss Functions for TF-Extra.
3
+
4
+ Provides commonly used loss functions not included in tf.keras.losses.
5
+ """
6
+
7
+ import tensorflow as tf
8
+
9
+
10
+ class FocalLoss(tf.keras.losses.Loss):
11
+ """
12
+ Focal Loss for addressing class imbalance.
13
+
14
+ Down-weights well-classified examples and focuses on hard negatives.
15
+ FL(p_t) = -alpha_t * (1 - p_t)^gamma * log(p_t)
16
+
17
+ Args:
18
+ alpha: Balancing factor. Default 0.25.
19
+ gamma: Focusing parameter. Default 2.0.
20
+ from_logits: Whether predictions are logits. Default False.
21
+ """
22
+
23
+ def __init__(self, alpha=0.25, gamma=2.0, from_logits=False, **kwargs):
24
+ super().__init__(**kwargs)
25
+ self.alpha = alpha
26
+ self.gamma = gamma
27
+ self.from_logits = from_logits
28
+
29
+ def call(self, y_true, y_pred):
30
+ y_true = tf.cast(y_true, tf.float32)
31
+ if self.from_logits:
32
+ y_pred = tf.nn.sigmoid(y_pred)
33
+
34
+ y_pred = tf.clip_by_value(y_pred, 1e-7, 1.0 - 1e-7)
35
+ p_t = y_true * y_pred + (1 - y_true) * (1 - y_pred)
36
+ alpha_t = y_true * self.alpha + (1 - y_true) * (1 - self.alpha)
37
+ focal_weight = alpha_t * tf.pow(1.0 - p_t, self.gamma)
38
+
39
+ bce = -(y_true * tf.math.log(y_pred) + (1 - y_true) * tf.math.log(1 - y_pred))
40
+ return tf.reduce_mean(focal_weight * bce)
41
+
42
+ def get_config(self):
43
+ config = super().get_config()
44
+ config.update({"alpha": self.alpha, "gamma": self.gamma, "from_logits": self.from_logits})
45
+ return config
46
+
47
+
48
+ class DiceLoss(tf.keras.losses.Loss):
49
+ """
50
+ Dice Loss for segmentation tasks.
51
+
52
+ Measures the overlap between predicted and ground truth masks.
53
+ DL = 1 - (2 * |X โˆฉ Y| + smooth) / (|X| + |Y| + smooth)
54
+
55
+ Args:
56
+ smooth: Smoothing constant to avoid division by zero. Default 1.0.
57
+ """
58
+
59
+ def __init__(self, smooth=1.0, **kwargs):
60
+ super().__init__(**kwargs)
61
+ self.smooth = smooth
62
+
63
+ def call(self, y_true, y_pred):
64
+ y_true = tf.cast(tf.reshape(y_true, [-1]), tf.float32)
65
+ y_pred = tf.cast(tf.reshape(y_pred, [-1]), tf.float32)
66
+ intersection = tf.reduce_sum(y_true * y_pred)
67
+ return 1.0 - (2.0 * intersection + self.smooth) / (
68
+ tf.reduce_sum(y_true) + tf.reduce_sum(y_pred) + self.smooth
69
+ )
70
+
71
+ def get_config(self):
72
+ config = super().get_config()
73
+ config["smooth"] = self.smooth
74
+ return config
75
+
76
+
77
+ class TverskyLoss(tf.keras.losses.Loss):
78
+ """
79
+ Tversky Loss โ€” generalization of Dice Loss.
80
+
81
+ Allows controlling the penalty for false positives vs false negatives
82
+ via alpha and beta.
83
+
84
+ Args:
85
+ alpha: Weight for false positives. Default 0.5.
86
+ beta: Weight for false negatives. Default 0.5.
87
+ smooth: Smoothing constant. Default 1.0.
88
+ """
89
+
90
+ def __init__(self, alpha=0.5, beta=0.5, smooth=1.0, **kwargs):
91
+ super().__init__(**kwargs)
92
+ self.alpha = alpha
93
+ self.beta = beta
94
+ self.smooth = smooth
95
+
96
+ def call(self, y_true, y_pred):
97
+ y_true = tf.cast(tf.reshape(y_true, [-1]), tf.float32)
98
+ y_pred = tf.cast(tf.reshape(y_pred, [-1]), tf.float32)
99
+ tp = tf.reduce_sum(y_true * y_pred)
100
+ fp = tf.reduce_sum((1 - y_true) * y_pred)
101
+ fn = tf.reduce_sum(y_true * (1 - y_pred))
102
+ tversky = (tp + self.smooth) / (tp + self.alpha * fp + self.beta * fn + self.smooth)
103
+ return 1.0 - tversky
104
+
105
+ def get_config(self):
106
+ config = super().get_config()
107
+ config.update({"alpha": self.alpha, "beta": self.beta, "smooth": self.smooth})
108
+ return config
109
+
110
+
111
+ class LabelSmoothingCrossEntropy(tf.keras.losses.Loss):
112
+ """
113
+ Cross Entropy with Label Smoothing.
114
+
115
+ Replaces hard 0/1 targets with smoothed targets to prevent
116
+ overconfidence and improve generalization.
117
+
118
+ Args:
119
+ smoothing: Label smoothing factor. Default 0.1.
120
+ """
121
+
122
+ def __init__(self, smoothing=0.1, **kwargs):
123
+ super().__init__(**kwargs)
124
+ self.smoothing = smoothing
125
+
126
+ def call(self, y_true, y_pred):
127
+ num_classes = tf.cast(tf.shape(y_pred)[-1], tf.float32)
128
+ y_true = tf.cast(y_true, tf.float32)
129
+ y_true = y_true * (1.0 - self.smoothing) + self.smoothing / num_classes
130
+ return tf.keras.losses.categorical_crossentropy(y_true, y_pred)
131
+
132
+ def get_config(self):
133
+ config = super().get_config()
134
+ config["smoothing"] = self.smoothing
135
+ return config
136
+
137
+
138
+ class ContrastiveLoss(tf.keras.losses.Loss):
139
+ """
140
+ Contrastive Loss for Siamese Networks.
141
+
142
+ L = y * d^2 + (1-y) * max(margin - d, 0)^2
143
+
144
+ Args:
145
+ margin: Margin for dissimilar pairs. Default 1.0.
146
+ """
147
+
148
+ def __init__(self, margin=1.0, **kwargs):
149
+ super().__init__(**kwargs)
150
+ self.margin = margin
151
+
152
+ def call(self, y_true, y_pred):
153
+ y_true = tf.cast(y_true, tf.float32)
154
+ square_pred = tf.square(y_pred)
155
+ margin_square = tf.square(tf.maximum(self.margin - y_pred, 0))
156
+ return tf.reduce_mean(y_true * square_pred + (1 - y_true) * margin_square)
157
+
158
+ def get_config(self):
159
+ config = super().get_config()
160
+ config["margin"] = self.margin
161
+ return config
162
+
163
+
164
+ class TripletLoss(tf.keras.losses.Loss):
165
+ """
166
+ Triplet Loss for metric learning.
167
+
168
+ L = max(d(anchor, positive) - d(anchor, negative) + margin, 0)
169
+
170
+ Expects y_pred to be a concatenation of [anchor, positive, negative]
171
+ embeddings along the last axis, each of size embed_dim.
172
+
173
+ Args:
174
+ margin: Margin for the triplet loss. Default 1.0.
175
+ """
176
+
177
+ def __init__(self, margin=1.0, **kwargs):
178
+ super().__init__(**kwargs)
179
+ self.margin = margin
180
+
181
+ def call(self, y_true, y_pred):
182
+ # y_pred: (batch, 3 * embed_dim)
183
+ embed_dim = tf.shape(y_pred)[-1] // 3
184
+ anchor = y_pred[:, :embed_dim]
185
+ positive = y_pred[:, embed_dim : 2 * embed_dim]
186
+ negative = y_pred[:, 2 * embed_dim :]
187
+
188
+ pos_dist = tf.reduce_sum(tf.square(anchor - positive), axis=-1)
189
+ neg_dist = tf.reduce_sum(tf.square(anchor - negative), axis=-1)
190
+ loss = tf.maximum(pos_dist - neg_dist + self.margin, 0.0)
191
+ return tf.reduce_mean(loss)
192
+
193
+ def get_config(self):
194
+ config = super().get_config()
195
+ config["margin"] = self.margin
196
+ return config
@@ -0,0 +1,5 @@
1
+ """TF-Extra Optimizers Module."""
2
+
3
+ from tf_extra.optimizers.multi_optimizer import MultiOptimizer
4
+
5
+ __all__ = ["MultiOptimizer"]
@@ -0,0 +1,234 @@
1
+ """
2
+ MultiOptimizer: Assign different optimizers to different layers of a Keras model.
3
+
4
+ This is a drop-in replacement for the deprecated tensorflow_addons MultiOptimizer,
5
+ fully compatible with TensorFlow 2.x (including TF 2.16+ / Keras 3).
6
+
7
+ Example:
8
+ import tensorflow as tf
9
+ import tf_extra
10
+
11
+ optimizers = [
12
+ tf.keras.optimizers.Adam(learning_rate=1e-6),
13
+ tf.keras.optimizers.Adam(learning_rate=1e-5),
14
+ tf.keras.optimizers.Adam(learning_rate=1e-4),
15
+ ]
16
+
17
+ optimizers_and_layers = [
18
+ (optimizers[0], model.layers[:100]),
19
+ (optimizers[1], model.layers[100:200]),
20
+ (optimizers[2], model.layers[-1]),
21
+ ]
22
+
23
+ opt = tf_extra.optimizers.MultiOptimizer(optimizers_and_layers)
24
+ model.compile(optimizer=opt, loss="binary_crossentropy")
25
+ """
26
+
27
+ import tensorflow as tf
28
+ import numpy as np
29
+ from typing import List, Tuple, Union, Any, Optional, Sequence
30
+
31
+
32
+ class MultiOptimizer(tf.keras.optimizers.Optimizer):
33
+ """
34
+ An optimizer that applies different optimizers to different sets of layers.
35
+
36
+ Each (optimizer, layers) pair assigns that optimizer to the trainable
37
+ variables of the given layer(s). Variables not covered by any pair
38
+ will use the fallback_optimizer (default: Adam with lr=1e-3).
39
+
40
+ Args:
41
+ optimizers_and_layers: A list of (optimizer, layers) tuples.
42
+ - optimizer: a tf.keras.optimizers.Optimizer instance.
43
+ - layers: a single tf.keras.layers.Layer, or a list of layers.
44
+ fallback_optimizer: Optional optimizer for any trainable variables
45
+ not covered by the pairs above. If None, uncovered variables
46
+ will raise a warning and use Adam(lr=1e-3).
47
+ name: Name for this optimizer. Default "MultiOptimizer".
48
+ **kwargs: Additional keyword arguments passed to the base Optimizer.
49
+
50
+ Raises:
51
+ ValueError: If optimizers_and_layers is empty.
52
+ TypeError: If an element is not a valid (optimizer, layers) tuple.
53
+ """
54
+
55
+ def __init__(
56
+ self,
57
+ optimizers_and_layers: List[
58
+ Tuple[tf.keras.optimizers.Optimizer, Union[tf.keras.layers.Layer, List[tf.keras.layers.Layer]]]
59
+ ],
60
+ fallback_optimizer: Optional[tf.keras.optimizers.Optimizer] = None,
61
+ name: str = "MultiOptimizer",
62
+ **kwargs,
63
+ ):
64
+ super().__init__(name=name, **kwargs)
65
+
66
+ if not optimizers_and_layers:
67
+ raise ValueError(
68
+ "`optimizers_and_layers` must be a non-empty list of "
69
+ "(optimizer, layers) tuples."
70
+ )
71
+
72
+ self._optimizers_and_layers = []
73
+ self._optimizer_list = []
74
+
75
+ for idx, pair in enumerate(optimizers_and_layers):
76
+ if not isinstance(pair, (list, tuple)) or len(pair) != 2:
77
+ raise TypeError(
78
+ f"Element {idx} of optimizers_and_layers must be a "
79
+ f"(optimizer, layers) tuple, got {type(pair)}."
80
+ )
81
+ optimizer, layer_or_layers = pair
82
+
83
+ if not isinstance(optimizer, tf.keras.optimizers.Optimizer):
84
+ raise TypeError(
85
+ f"Element {idx}: first item must be a "
86
+ f"tf.keras.optimizers.Optimizer, got {type(optimizer)}."
87
+ )
88
+
89
+ # Normalize layers to a list
90
+ if isinstance(layer_or_layers, tf.keras.layers.Layer):
91
+ layers_list = [layer_or_layers]
92
+ elif isinstance(layer_or_layers, (list, tuple)):
93
+ layers_list = list(layer_or_layers)
94
+ else:
95
+ raise TypeError(
96
+ f"Element {idx}: second item must be a Layer or list of "
97
+ f"Layers, got {type(layer_or_layers)}."
98
+ )
99
+
100
+ for layer in layers_list:
101
+ if not isinstance(layer, tf.keras.layers.Layer):
102
+ raise TypeError(
103
+ f"Expected tf.keras.layers.Layer, got {type(layer)}."
104
+ )
105
+
106
+ self._optimizers_and_layers.append((optimizer, layers_list))
107
+ if optimizer not in self._optimizer_list:
108
+ self._optimizer_list.append(optimizer)
109
+
110
+ # Fallback optimizer
111
+ if fallback_optimizer is not None:
112
+ self._fallback_optimizer = fallback_optimizer
113
+ else:
114
+ self._fallback_optimizer = tf.keras.optimizers.Adam(learning_rate=1e-3)
115
+
116
+ if self._fallback_optimizer not in self._optimizer_list:
117
+ self._optimizer_list.append(self._fallback_optimizer)
118
+
119
+ # Build variable-to-optimizer mapping (populated in _build_mapping)
120
+ self._var_optimizer_map = None
121
+
122
+ # ------------------------------------------------------------------
123
+ # Internal: build a dict variable.ref() -> optimizer
124
+ # ------------------------------------------------------------------
125
+ def _build_mapping(self, var_list):
126
+ """Build mapping from variable refs to their assigned optimizer."""
127
+ self._var_optimizer_map = {}
128
+ assigned_refs = set()
129
+
130
+ for optimizer, layers_list in self._optimizers_and_layers:
131
+ for layer in layers_list:
132
+ for var in layer.trainable_variables:
133
+ ref = var.ref()
134
+ if ref in assigned_refs:
135
+ import warnings
136
+ warnings.warn(
137
+ f"Variable '{var.name}' is assigned to multiple "
138
+ f"optimizers. The last assignment wins."
139
+ )
140
+ self._var_optimizer_map[ref] = optimizer
141
+ assigned_refs.add(ref)
142
+
143
+ # Assign fallback to any unassigned variables
144
+ for var in var_list:
145
+ ref = var.ref()
146
+ if ref not in self._var_optimizer_map:
147
+ self._var_optimizer_map[ref] = self._fallback_optimizer
148
+
149
+ # ------------------------------------------------------------------
150
+ # Public API expected by Keras
151
+ # ------------------------------------------------------------------
152
+ def apply_gradients(self, grads_and_vars, **kwargs):
153
+ """
154
+ Group gradients by their assigned optimizer and apply each group.
155
+ """
156
+ grads_and_vars = list(grads_and_vars)
157
+ all_vars = [v for _, v in grads_and_vars]
158
+
159
+ if self._var_optimizer_map is None:
160
+ self._build_mapping(all_vars)
161
+
162
+ # Bucket gradients by optimizer
163
+ optimizer_grads = {} # optimizer -> list of (grad, var)
164
+ for grad, var in grads_and_vars:
165
+ if grad is None:
166
+ continue
167
+ opt = self._var_optimizer_map.get(var.ref(), self._fallback_optimizer)
168
+ optimizer_grads.setdefault(id(opt), (opt, []))
169
+ optimizer_grads[id(opt)][1].append((grad, var))
170
+
171
+ # Apply each optimizer's gradients
172
+ results = []
173
+ for _, (opt, gv_list) in optimizer_grads.items():
174
+ results.append(opt.apply_gradients(gv_list, **kwargs))
175
+
176
+ return results
177
+
178
+ # ------------------------------------------------------------------
179
+ # Convenience: allow model.compile(optimizer=multi_opt) to work
180
+ # ------------------------------------------------------------------
181
+ def get_config(self):
182
+ """Return serializable config dict."""
183
+ config = super().get_config()
184
+ opt_layer_configs = []
185
+ for optimizer, layers_list in self._optimizers_and_layers:
186
+ opt_layer_configs.append(
187
+ {
188
+ "optimizer": tf.keras.optimizers.serialize(optimizer),
189
+ "layer_names": [l.name for l in layers_list],
190
+ }
191
+ )
192
+ config["optimizers_and_layers"] = opt_layer_configs
193
+ config["fallback_optimizer"] = tf.keras.optimizers.serialize(
194
+ self._fallback_optimizer
195
+ )
196
+ return config
197
+
198
+ @property
199
+ def learning_rate(self):
200
+ """Return the learning rate of the first optimizer (for logging)."""
201
+ return self._optimizers_and_layers[0][0].learning_rate
202
+
203
+ @learning_rate.setter
204
+ def learning_rate(self, value):
205
+ """Set learning rate on ALL sub-optimizers."""
206
+ for opt, _ in self._optimizers_and_layers:
207
+ opt.learning_rate = value
208
+ self._fallback_optimizer.learning_rate = value
209
+
210
+ @property
211
+ def sub_optimizers(self):
212
+ """Access list of (optimizer, layers) pairs."""
213
+ return list(self._optimizers_and_layers)
214
+
215
+ @property
216
+ def optimizers(self):
217
+ """Return flat list of unique optimizers."""
218
+ return list(self._optimizer_list)
219
+
220
+ # ------------------------------------------------------------------
221
+ # Required abstract methods (no-ops; real work delegated to sub-opts)
222
+ # ------------------------------------------------------------------
223
+ def _create_slots(self, var_list):
224
+ pass
225
+
226
+ def _resource_apply_dense(self, grad, handle, apply_state=None):
227
+ pass
228
+
229
+ def _resource_apply_sparse(self, grad, handle, indices, apply_state=None):
230
+ pass
231
+
232
+ def get_gradients(self, loss, params):
233
+ """Compute gradients โ€” delegates to tf.GradientTape compatible path."""
234
+ return super().get_gradients(loss, params)
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024 Your Name
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,213 @@
1
+ Metadata-Version: 2.1
2
+ Name: tf-extra
3
+ Version: 1.0.0
4
+ Summary: A powerful extension library for TensorFlow/Keras โ€” MultiOptimizer, custom layers, losses, and callbacks.
5
+ Home-page: https://github.com/yourusername/tf-extra
6
+ Author: Your Name
7
+ Author-email: Your Name <your.email@example.com>
8
+ License: MIT
9
+ Project-URL: Homepage, https://github.com/yourusername/tf-extra
10
+ Project-URL: Repository, https://github.com/yourusername/tf-extra
11
+ Project-URL: Issues, https://github.com/yourusername/tf-extra/issues
12
+ Keywords: tensorflow,keras,deep-learning,multi-optimizer,attention,focal-loss
13
+ Classifier: Development Status :: 5 - Production/Stable
14
+ Classifier: Intended Audience :: Developers
15
+ Classifier: Intended Audience :: Science/Research
16
+ Classifier: License :: OSI Approved :: MIT License
17
+ Classifier: Operating System :: OS Independent
18
+ Classifier: Programming Language :: Python :: 3
19
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
20
+ Requires-Python: >=3.8
21
+ Description-Content-Type: text/markdown
22
+ License-File: LICENSE
23
+ Requires-Dist: tensorflow>=2.10.0
24
+ Requires-Dist: numpy>=1.21.0
25
+ Provides-Extra: dev
26
+ Requires-Dist: pytest>=7.0; extra == "dev"
27
+ Requires-Dist: pytest-cov>=4.0; extra == "dev"
28
+ Requires-Dist: flake8>=5.0; extra == "dev"
29
+ Requires-Dist: black>=22.0; extra == "dev"
30
+ Requires-Dist: isort>=5.0; extra == "dev"
31
+ Requires-Dist: mypy>=0.990; extra == "dev"
32
+
33
+ # ๐Ÿš€ TF-Extra
34
+
35
+ **A powerful extension library for TensorFlow/Keras** โ€” the spiritual successor to `tensorflow_addons`.
36
+
37
+ [![PyPI version](https://badge.fury.io/py/tf-extra.svg)](https://pypi.org/project/tf-extra/)
38
+ [![Python 3.8+](https://img.shields.io/badge/python-3.8+-blue.svg)](https://www.python.org/downloads/)
39
+ [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
40
+ [![TensorFlow 2.10+](https://img.shields.io/badge/TensorFlow-2.10+-orange.svg)](https://www.tensorflow.org/)
41
+
42
+ ---
43
+
44
+ ## โœจ Features
45
+
46
+ | Module | Components |
47
+ |--------|-----------|
48
+ | **๐Ÿ”ง Optimizers** | `MultiOptimizer` โ€” assign different optimizers to different layers |
49
+ | **๐Ÿ“ Layers** | `CBAM`, `SqueezeExcitation`, `MultiHeadSelfAttention`, `GeM`, `Mish`, `PositionalEncoding`, `AdaptiveConcatPool2D` |
50
+ | **๐Ÿ“‰ Losses** | `FocalLoss`, `DiceLoss`, `TverskyLoss`, `LabelSmoothingCrossEntropy`, `ContrastiveLoss`, `TripletLoss` |
51
+ | **๐Ÿ“ž Callbacks** | `CosineAnnealingScheduler`, `GradientAccumulationCallback`, `TimeLimitCallback` |
52
+
53
+ ---
54
+
55
+ ## ๐Ÿ“ฆ Installation
56
+
57
+ ```bash
58
+ pip install tf-extra
59
+ ```
60
+
61
+ Or install from source:
62
+
63
+ ```bash
64
+ git clone https://github.com/yourusername/tf-extra.git
65
+ cd tf-extra
66
+ pip install -e .
67
+ ```
68
+
69
+ ---
70
+
71
+ ## ๐Ÿš€ Quick Start
72
+
73
+ ### MultiOptimizer (Drop-in replacement for `tfa.optimizers.MultiOptimizer`)
74
+
75
+ ```python
76
+ import tensorflow as tf
77
+ import tf_extra
78
+
79
+ # Load a pre-trained model
80
+ base_model = tf.keras.applications.ResNet50(weights='imagenet', include_top=False)
81
+ x = tf.keras.layers.GlobalAveragePooling2D()(base_model.output)
82
+ x = tf.keras.layers.Dense(1)(x)
83
+ model = tf.keras.Model(base_model.input, x)
84
+
85
+ # Define different learning rates for different parts of the model
86
+ optimizers = [
87
+ tf.keras.optimizers.Adam(learning_rate=1e-6), # Very slow for early layers
88
+ tf.keras.optimizers.Adam(learning_rate=1e-5), # Medium for middle layers
89
+ tf.keras.optimizers.Adam(learning_rate=1e-4), # Fast for the head
90
+ ]
91
+
92
+ optimizers_and_layers = [
93
+ (optimizers[0], base_model.layers[:100]),
94
+ (optimizers[1], base_model.layers[100:]),
95
+ (optimizers[2], model.layers[-1]),
96
+ ]
97
+
98
+ opt = tf_extra.optimizers.MultiOptimizer(optimizers_and_layers)
99
+ model.compile(
100
+ optimizer=opt,
101
+ loss=tf.keras.losses.BinaryCrossentropy(from_logits=True),
102
+ metrics=[tf.keras.metrics.BinaryAccuracy(threshold=0, name='accuracy')]
103
+ )
104
+ ```
105
+
106
+ ### Focal Loss (for imbalanced datasets)
107
+
108
+ ```python
109
+ import tf_extra
110
+
111
+ model.compile(
112
+ optimizer='adam',
113
+ loss=tf_extra.losses.FocalLoss(alpha=0.25, gamma=2.0),
114
+ metrics=['accuracy']
115
+ )
116
+ ```
117
+
118
+ ### CBAM Attention Layer
119
+
120
+ ```python
121
+ import tf_extra
122
+
123
+ x = tf.keras.layers.Conv2D(64, 3, padding='same')(inputs)
124
+ x = tf_extra.layers.CBAM(reduction_ratio=16)(x)
125
+ ```
126
+
127
+ ### Cosine Annealing with Warm Restarts
128
+
129
+ ```python
130
+ import tf_extra
131
+
132
+ scheduler = tf_extra.callbacks.CosineAnnealingScheduler(
133
+ eta_min=1e-7,
134
+ eta_max=1e-3,
135
+ T_0=10,
136
+ T_mult=2
137
+ )
138
+ model.fit(X, y, callbacks=[scheduler])
139
+ ```
140
+
141
+ ---
142
+
143
+ ## ๐Ÿ“– Full API Reference
144
+
145
+ ### `tf_extra.optimizers.MultiOptimizer`
146
+
147
+ ```python
148
+ MultiOptimizer(
149
+ optimizers_and_layers, # List of (optimizer, layers) tuples
150
+ fallback_optimizer=None, # Optional: optimizer for unassigned variables
151
+ name="MultiOptimizer"
152
+ )
153
+ ```
154
+
155
+ ### `tf_extra.layers`
156
+
157
+ | Layer | Description |
158
+ |-------|-------------|
159
+ | `CBAM(reduction_ratio=16, kernel_size=7)` | Convolutional Block Attention Module |
160
+ | `SqueezeExcitation(reduction_ratio=16)` | SE-Net attention block |
161
+ | `MultiHeadSelfAttention(embed_dim, num_heads)` | Transformer-style self-attention |
162
+ | `GeM(p_init=3.0)` | Generalized Mean Pooling |
163
+ | `Mish()` | Mish activation function |
164
+ | `PositionalEncoding(max_len=5000)` | Sinusoidal positional encoding |
165
+ | `AdaptiveConcatPool2D()` | Concatenated average + max pooling |
166
+ | `SpatialDropout1D(rate=0.5)` | Spatial dropout for 1D data |
167
+
168
+ ### `tf_extra.losses`
169
+
170
+ | Loss | Description |
171
+ |------|-------------|
172
+ | `FocalLoss(alpha=0.25, gamma=2.0)` | Focal loss for class imbalance |
173
+ | `DiceLoss(smooth=1.0)` | Dice loss for segmentation |
174
+ | `TverskyLoss(alpha=0.5, beta=0.5)` | Generalized Dice loss |
175
+ | `LabelSmoothingCrossEntropy(smoothing=0.1)` | Label smoothing |
176
+ | `ContrastiveLoss(margin=1.0)` | For Siamese networks |
177
+ | `TripletLoss(margin=1.0)` | For metric learning |
178
+
179
+ ### `tf_extra.callbacks`
180
+
181
+ | Callback | Description |
182
+ |----------|-------------|
183
+ | `CosineAnnealingScheduler(eta_min, eta_max, T_0, T_mult)` | SGDR scheduler |
184
+ | `GradientAccumulationCallback(accumulation_steps=4)` | Gradient accumulation |
185
+ | `TimeLimitCallback(max_seconds)` | Stop training after time limit |
186
+
187
+ ---
188
+
189
+ ## ๐Ÿงช Testing
190
+
191
+ ```bash
192
+ pip install -e ".[dev]"
193
+ pytest tests/ -v
194
+ ```
195
+
196
+ ---
197
+
198
+ ## ๐Ÿค Contributing
199
+
200
+ Contributions are welcome! Please read the contributing guidelines and submit a PR.
201
+
202
+ ---
203
+
204
+ ## ๐Ÿ“„ License
205
+
206
+ MIT License โ€” see [LICENSE](LICENSE) for details.
207
+
208
+ ---
209
+
210
+ ## ๐Ÿ™ Acknowledgements
211
+
212
+ Inspired by `tensorflow_addons` which is no longer maintained.
213
+ Built to provide the same functionality with modern TensorFlow compatibility.
@@ -0,0 +1,14 @@
1
+ tf_extra/__init__.py,sha256=tl7OZ4sEFDPOZ1-NL7lshGqMCi7dEBHMzDBcic_xLKY,492
2
+ tf_extra/callbacks/__init__.py,sha256=OxhzxY0CnjrUyI49IJBMzfnr1Vql3z8TtcoXAsWMXQc,288
3
+ tf_extra/callbacks/custom_callbacks.py,sha256=AsiGF7nopKC7bAsyc4pnZlSmdhPuMcjtinV2ur6xTbU,3658
4
+ tf_extra/layers/__init__.py,sha256=xSrCBxNg0GA7tso-lW8TngmBilWVR7_F01J8C1qZxnY,511
5
+ tf_extra/layers/custom_layers.py,sha256=PB8ktQT86AjgcXTAlHSMyIZXTI2jI2Hs7DPf8CWBMZk,11731
6
+ tf_extra/losses/__init__.py,sha256=pjMbfeqnqpceOLmEQD6HY2aVuahp3sMyc2zP1w5scrI,343
7
+ tf_extra/losses/custom_losses.py,sha256=fDDaxCwo_NcXK-51NH7mGLInmA7IHtwqvRNwbgpdgeQ,6250
8
+ tf_extra/optimizers/__init__.py,sha256=M1eLL7IFb6SQXZEHpTz2abXdjby8TFus2nDRfPoEe-k,128
9
+ tf_extra/optimizers/multi_optimizer.py,sha256=oI02Stbg067wyBWU0AdLyLtPlQGMfeALcNKOKNigupg,9199
10
+ tf_extra-1.0.0.dist-info/LICENSE,sha256=F-4b93u0OVrVwGXgMwBRq6MlGyUT9zmre1oh5Gft5Ts,1066
11
+ tf_extra-1.0.0.dist-info/METADATA,sha256=AgpScl2oJkMvnMp3AFfE4XozUaY2wmCge2zkfCLRL8c,6625
12
+ tf_extra-1.0.0.dist-info/WHEEL,sha256=tZoeGjtWxWRfdplE7E3d45VPlLNQnvbKiYnx7gwAy8A,92
13
+ tf_extra-1.0.0.dist-info/top_level.txt,sha256=o7AvNlkeqL9f3EDtsjgPLEBsCmm08OsrI-MMb2DBe4A,9
14
+ tf_extra-1.0.0.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: bdist_wheel (0.45.1)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1 @@
1
+ tf_extra