ilovetools 0.2.29__tar.gz → 0.2.30__tar.gz

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.
Files changed (100) hide show
  1. {ilovetools-0.2.29/ilovetools.egg-info → ilovetools-0.2.30}/PKG-INFO +2 -2
  2. ilovetools-0.2.30/ilovetools/ml/pooling.py +679 -0
  3. {ilovetools-0.2.29 → ilovetools-0.2.30/ilovetools.egg-info}/PKG-INFO +2 -2
  4. {ilovetools-0.2.29 → ilovetools-0.2.30}/ilovetools.egg-info/SOURCES.txt +2 -0
  5. {ilovetools-0.2.29 → ilovetools-0.2.30}/pyproject.toml +2 -2
  6. {ilovetools-0.2.29 → ilovetools-0.2.30}/setup.py +2 -2
  7. ilovetools-0.2.30/tests/test_pooling.py +461 -0
  8. {ilovetools-0.2.29 → ilovetools-0.2.30}/LICENSE +0 -0
  9. {ilovetools-0.2.29 → ilovetools-0.2.30}/MANIFEST.in +0 -0
  10. {ilovetools-0.2.29 → ilovetools-0.2.30}/README.md +0 -0
  11. {ilovetools-0.2.29 → ilovetools-0.2.30}/ilovetools/__init__.py +0 -0
  12. {ilovetools-0.2.29 → ilovetools-0.2.30}/ilovetools/ai/__init__.py +0 -0
  13. {ilovetools-0.2.29 → ilovetools-0.2.30}/ilovetools/ai/embeddings.py +0 -0
  14. {ilovetools-0.2.29 → ilovetools-0.2.30}/ilovetools/ai/inference.py +0 -0
  15. {ilovetools-0.2.29 → ilovetools-0.2.30}/ilovetools/ai/llm_helpers.py +0 -0
  16. {ilovetools-0.2.29 → ilovetools-0.2.30}/ilovetools/audio/__init__.py +0 -0
  17. {ilovetools-0.2.29 → ilovetools-0.2.30}/ilovetools/automation/__init__.py +0 -0
  18. {ilovetools-0.2.29 → ilovetools-0.2.30}/ilovetools/automation/file_organizer.py +0 -0
  19. {ilovetools-0.2.29 → ilovetools-0.2.30}/ilovetools/conversion/__init__.py +0 -0
  20. {ilovetools-0.2.29 → ilovetools-0.2.30}/ilovetools/conversion/config_converter.py +0 -0
  21. {ilovetools-0.2.29 → ilovetools-0.2.30}/ilovetools/conversion/config_converter_fixed_header.py +0 -0
  22. {ilovetools-0.2.29 → ilovetools-0.2.30}/ilovetools/data/__init__.py +0 -0
  23. {ilovetools-0.2.29 → ilovetools-0.2.30}/ilovetools/data/feature_engineering.py +0 -0
  24. {ilovetools-0.2.29 → ilovetools-0.2.30}/ilovetools/data/preprocessing.py +0 -0
  25. {ilovetools-0.2.29 → ilovetools-0.2.30}/ilovetools/database/__init__.py +0 -0
  26. {ilovetools-0.2.29 → ilovetools-0.2.30}/ilovetools/datetime/__init__.py +0 -0
  27. {ilovetools-0.2.29 → ilovetools-0.2.30}/ilovetools/email/__init__.py +0 -0
  28. {ilovetools-0.2.29 → ilovetools-0.2.30}/ilovetools/email/template_engine.py +0 -0
  29. {ilovetools-0.2.29 → ilovetools-0.2.30}/ilovetools/files/__init__.py +0 -0
  30. {ilovetools-0.2.29 → ilovetools-0.2.30}/ilovetools/image/__init__.py +0 -0
  31. {ilovetools-0.2.29 → ilovetools-0.2.30}/ilovetools/ml/__init__.py +0 -0
  32. {ilovetools-0.2.29 → ilovetools-0.2.30}/ilovetools/ml/activations.py +0 -0
  33. {ilovetools-0.2.29 → ilovetools-0.2.30}/ilovetools/ml/anomaly_detection.py +0 -0
  34. {ilovetools-0.2.29 → ilovetools-0.2.30}/ilovetools/ml/attention.py +0 -0
  35. {ilovetools-0.2.29 → ilovetools-0.2.30}/ilovetools/ml/augmentation.py +0 -0
  36. {ilovetools-0.2.29 → ilovetools-0.2.30}/ilovetools/ml/clustering.py +0 -0
  37. {ilovetools-0.2.29 → ilovetools-0.2.30}/ilovetools/ml/cnn.py +0 -0
  38. {ilovetools-0.2.29 → ilovetools-0.2.30}/ilovetools/ml/cross_validation.py +0 -0
  39. {ilovetools-0.2.29 → ilovetools-0.2.30}/ilovetools/ml/dimensionality.py +0 -0
  40. {ilovetools-0.2.29 → ilovetools-0.2.30}/ilovetools/ml/dropout.py +0 -0
  41. {ilovetools-0.2.29 → ilovetools-0.2.30}/ilovetools/ml/ensemble.py +0 -0
  42. {ilovetools-0.2.29 → ilovetools-0.2.30}/ilovetools/ml/feature_selection.py +0 -0
  43. {ilovetools-0.2.29 → ilovetools-0.2.30}/ilovetools/ml/gradient_descent.py +0 -0
  44. {ilovetools-0.2.29 → ilovetools-0.2.30}/ilovetools/ml/imbalanced.py +0 -0
  45. {ilovetools-0.2.29 → ilovetools-0.2.30}/ilovetools/ml/interpretation.py +0 -0
  46. {ilovetools-0.2.29 → ilovetools-0.2.30}/ilovetools/ml/loss_functions.py +0 -0
  47. {ilovetools-0.2.29 → ilovetools-0.2.30}/ilovetools/ml/losses.py +0 -0
  48. {ilovetools-0.2.29 → ilovetools-0.2.30}/ilovetools/ml/lr_schedulers.py +0 -0
  49. {ilovetools-0.2.29 → ilovetools-0.2.30}/ilovetools/ml/metrics.py +0 -0
  50. {ilovetools-0.2.29 → ilovetools-0.2.30}/ilovetools/ml/neural_network.py +0 -0
  51. {ilovetools-0.2.29 → ilovetools-0.2.30}/ilovetools/ml/normalization.py +0 -0
  52. {ilovetools-0.2.29 → ilovetools-0.2.30}/ilovetools/ml/normalization_advanced.py +0 -0
  53. {ilovetools-0.2.29 → ilovetools-0.2.30}/ilovetools/ml/optimizers.py +0 -0
  54. {ilovetools-0.2.29 → ilovetools-0.2.30}/ilovetools/ml/pipeline.py +0 -0
  55. {ilovetools-0.2.29 → ilovetools-0.2.30}/ilovetools/ml/positional_encoding.py +0 -0
  56. {ilovetools-0.2.29 → ilovetools-0.2.30}/ilovetools/ml/regularization.py +0 -0
  57. {ilovetools-0.2.29 → ilovetools-0.2.30}/ilovetools/ml/rnn.py +0 -0
  58. {ilovetools-0.2.29 → ilovetools-0.2.30}/ilovetools/ml/schedulers.py +0 -0
  59. {ilovetools-0.2.29 → ilovetools-0.2.30}/ilovetools/ml/timeseries.py +0 -0
  60. {ilovetools-0.2.29 → ilovetools-0.2.30}/ilovetools/ml/tuning.py +0 -0
  61. {ilovetools-0.2.29 → ilovetools-0.2.30}/ilovetools/ml/weight_init.py +0 -0
  62. {ilovetools-0.2.29 → ilovetools-0.2.30}/ilovetools/security/__init__.py +0 -0
  63. {ilovetools-0.2.29 → ilovetools-0.2.30}/ilovetools/security/password_checker.py +0 -0
  64. {ilovetools-0.2.29 → ilovetools-0.2.30}/ilovetools/text/__init__.py +0 -0
  65. {ilovetools-0.2.29 → ilovetools-0.2.30}/ilovetools/utils/__init__.py +0 -0
  66. {ilovetools-0.2.29 → ilovetools-0.2.30}/ilovetools/utils/cache_system.py +0 -0
  67. {ilovetools-0.2.29 → ilovetools-0.2.30}/ilovetools/utils/logger.py +0 -0
  68. {ilovetools-0.2.29 → ilovetools-0.2.30}/ilovetools/utils/rate_limiter.py +0 -0
  69. {ilovetools-0.2.29 → ilovetools-0.2.30}/ilovetools/utils/retry.py +0 -0
  70. {ilovetools-0.2.29 → ilovetools-0.2.30}/ilovetools/validation/__init__.py +0 -0
  71. {ilovetools-0.2.29 → ilovetools-0.2.30}/ilovetools/validation/data_validator.py +0 -0
  72. {ilovetools-0.2.29 → ilovetools-0.2.30}/ilovetools/web/__init__.py +0 -0
  73. {ilovetools-0.2.29 → ilovetools-0.2.30}/ilovetools/web/scraper.py +0 -0
  74. {ilovetools-0.2.29 → ilovetools-0.2.30}/ilovetools/web/url_shortener.py +0 -0
  75. {ilovetools-0.2.29 → ilovetools-0.2.30}/ilovetools.egg-info/dependency_links.txt +0 -0
  76. {ilovetools-0.2.29 → ilovetools-0.2.30}/ilovetools.egg-info/requires.txt +0 -0
  77. {ilovetools-0.2.29 → ilovetools-0.2.30}/ilovetools.egg-info/top_level.txt +0 -0
  78. {ilovetools-0.2.29 → ilovetools-0.2.30}/requirements.txt +0 -0
  79. {ilovetools-0.2.29 → ilovetools-0.2.30}/setup.cfg +0 -0
  80. {ilovetools-0.2.29 → ilovetools-0.2.30}/tests/__init__.py +0 -0
  81. {ilovetools-0.2.29 → ilovetools-0.2.30}/tests/test_activations.py +0 -0
  82. {ilovetools-0.2.29 → ilovetools-0.2.30}/tests/test_attention.py +0 -0
  83. {ilovetools-0.2.29 → ilovetools-0.2.30}/tests/test_augmentation.py +0 -0
  84. {ilovetools-0.2.29 → ilovetools-0.2.30}/tests/test_cnn.py +0 -0
  85. {ilovetools-0.2.29 → ilovetools-0.2.30}/tests/test_dropout.py +0 -0
  86. {ilovetools-0.2.29 → ilovetools-0.2.30}/tests/test_gradient_descent.py +0 -0
  87. {ilovetools-0.2.29 → ilovetools-0.2.30}/tests/test_loss_functions.py +0 -0
  88. {ilovetools-0.2.29 → ilovetools-0.2.30}/tests/test_losses.py +0 -0
  89. {ilovetools-0.2.29 → ilovetools-0.2.30}/tests/test_lr_schedulers.py +0 -0
  90. {ilovetools-0.2.29 → ilovetools-0.2.30}/tests/test_neural_network.py +0 -0
  91. {ilovetools-0.2.29 → ilovetools-0.2.30}/tests/test_normalization.py +0 -0
  92. {ilovetools-0.2.29 → ilovetools-0.2.30}/tests/test_normalization_advanced.py +0 -0
  93. {ilovetools-0.2.29 → ilovetools-0.2.30}/tests/test_optimizers.py +0 -0
  94. {ilovetools-0.2.29 → ilovetools-0.2.30}/tests/test_positional_encoding.py +0 -0
  95. {ilovetools-0.2.29 → ilovetools-0.2.30}/tests/test_pypi_installation.py +0 -0
  96. {ilovetools-0.2.29 → ilovetools-0.2.30}/tests/test_regularization.py +0 -0
  97. {ilovetools-0.2.29 → ilovetools-0.2.30}/tests/test_rnn.py +0 -0
  98. {ilovetools-0.2.29 → ilovetools-0.2.30}/tests/test_schedulers.py +0 -0
  99. {ilovetools-0.2.29 → ilovetools-0.2.30}/tests/test_weight_init.py +0 -0
  100. {ilovetools-0.2.29 → ilovetools-0.2.30}/tests/verify_positional_encoding.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ilovetools
3
- Version: 0.2.29
3
+ Version: 0.2.30
4
4
  Summary: A comprehensive Python utility library with modular tools for AI/ML, data processing, and daily programming needs
5
5
  Home-page: https://github.com/AliMehdi512/ilovetools
6
6
  Author: Ali Mehdi
@@ -11,7 +11,7 @@ Project-URL: Repository, https://github.com/AliMehdi512/ilovetools
11
11
  Project-URL: Issues, https://github.com/AliMehdi512/ilovetools/issues
12
12
  Project-URL: Bug Reports, https://github.com/AliMehdi512/ilovetools/issues
13
13
  Project-URL: Source, https://github.com/AliMehdi512/ilovetools
14
- Keywords: utilities,tools,ai,ml,data-processing,automation,learning-rate,schedulers,step-lr,exponential-lr,cosine-annealing,one-cycle,cyclic-lr,sgdr,warm-restarts,reduce-on-plateau,polynomial-lr,warmup,multi-step-lr,optimization,training,deep-learning,neural-networks,pytorch,tensorflow,keras,convergence,super-convergence,adaptive-learning-rate
14
+ Keywords: utilities,tools,ai,ml,data-processing,automation,pooling,max-pooling,average-pooling,global-pooling,adaptive-pooling,maxpool2d,avgpool2d,global-max-pool,global-avg-pool,adaptive-max-pool,adaptive-avg-pool,cnn,convolutional-neural-networks,downsampling,feature-extraction,spatial-pooling,deep-learning,neural-networks,pytorch,tensorflow,keras,resnet,mobilenet,efficientnet,computer-vision,image-classification
15
15
  Classifier: Development Status :: 3 - Alpha
16
16
  Classifier: Intended Audience :: Developers
17
17
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
@@ -0,0 +1,679 @@
1
+ """
2
+ Pooling Layers Suite
3
+
4
+ This module implements various pooling operations for neural networks.
5
+ Pooling layers reduce spatial dimensions while retaining important features,
6
+ providing translation invariance and reducing computational complexity.
7
+
8
+ Implemented Pooling Types:
9
+ 1. MaxPool1D - 1D max pooling for sequences
10
+ 2. MaxPool2D - 2D max pooling for images
11
+ 3. AvgPool1D - 1D average pooling for sequences
12
+ 4. AvgPool2D - 2D average pooling for images
13
+ 5. GlobalMaxPool - Global max pooling (entire feature map)
14
+ 6. GlobalAvgPool - Global average pooling (entire feature map)
15
+ 7. AdaptiveMaxPool - Adaptive max pooling (fixed output size)
16
+ 8. AdaptiveAvgPool - Adaptive average pooling (fixed output size)
17
+
18
+ References:
19
+ - Max Pooling: Standard practice in CNNs
20
+ - Global Pooling: "Network In Network" (Lin et al., 2013)
21
+ - Adaptive Pooling: PyTorch adaptive pooling layers
22
+
23
+ Author: Ali Mehdi
24
+ Date: January 17, 2026
25
+ """
26
+
27
+ import numpy as np
28
+ from typing import Union, Tuple, Optional
29
+
30
+
31
+ class MaxPool1D:
32
+ """
33
+ 1D Max Pooling Layer.
34
+
35
+ Applies max pooling over 1D input (sequences, time series).
36
+ Selects maximum value within each pooling window.
37
+
38
+ Formula:
39
+ output[i] = max(input[i*stride : i*stride + pool_size])
40
+
41
+ Args:
42
+ pool_size: Size of pooling window
43
+ stride: Stride of pooling operation (default: pool_size)
44
+ padding: Padding to add (default: 0)
45
+
46
+ Example:
47
+ >>> pool = MaxPool1D(pool_size=2, stride=2)
48
+ >>> x = np.array([[[1, 3, 2, 4, 5, 1]]]) # (batch, channels, length)
49
+ >>> output = pool.forward(x)
50
+ >>> print(output) # [[[3, 4, 5]]]
51
+
52
+ Reference:
53
+ Standard practice in CNNs
54
+ """
55
+
56
+ def __init__(self, pool_size: int, stride: Optional[int] = None, padding: int = 0):
57
+ if pool_size <= 0:
58
+ raise ValueError(f"pool_size must be positive, got {pool_size}")
59
+ if padding < 0:
60
+ raise ValueError(f"padding must be non-negative, got {padding}")
61
+
62
+ self.pool_size = pool_size
63
+ self.stride = stride if stride is not None else pool_size
64
+ self.padding = padding
65
+ self.cache = None
66
+
67
+ def forward(self, x: np.ndarray) -> np.ndarray:
68
+ """
69
+ Forward pass.
70
+
71
+ Args:
72
+ x: Input tensor (batch, channels, length)
73
+
74
+ Returns:
75
+ Pooled output
76
+ """
77
+ batch_size, channels, length = x.shape
78
+
79
+ # Apply padding
80
+ if self.padding > 0:
81
+ x_padded = np.pad(x, ((0, 0), (0, 0), (self.padding, self.padding)),
82
+ mode='constant', constant_values=-np.inf)
83
+ else:
84
+ x_padded = x
85
+
86
+ # Calculate output length
87
+ out_length = (x_padded.shape[2] - self.pool_size) // self.stride + 1
88
+
89
+ # Initialize output
90
+ output = np.zeros((batch_size, channels, out_length))
91
+
92
+ # Store for backward pass
93
+ self.cache = (x.shape, x_padded)
94
+
95
+ # Perform max pooling
96
+ for i in range(out_length):
97
+ start = i * self.stride
98
+ end = start + self.pool_size
99
+ output[:, :, i] = np.max(x_padded[:, :, start:end], axis=2)
100
+
101
+ return output
102
+
103
+ def backward(self, grad_output: np.ndarray) -> np.ndarray:
104
+ """
105
+ Backward pass.
106
+
107
+ Args:
108
+ grad_output: Gradient from next layer
109
+
110
+ Returns:
111
+ Gradient w.r.t. input
112
+ """
113
+ x_shape, x_padded = self.cache
114
+ batch_size, channels, length = x_shape
115
+
116
+ # Initialize gradient
117
+ grad_input = np.zeros_like(x_padded)
118
+
119
+ out_length = grad_output.shape[2]
120
+
121
+ # Backpropagate through max pooling
122
+ for i in range(out_length):
123
+ start = i * self.stride
124
+ end = start + self.pool_size
125
+
126
+ # Find max positions
127
+ window = x_padded[:, :, start:end]
128
+ max_vals = np.max(window, axis=2, keepdims=True)
129
+ mask = (window == max_vals)
130
+
131
+ # Distribute gradient to max positions
132
+ grad_input[:, :, start:end] += mask * grad_output[:, :, i:i+1]
133
+
134
+ # Remove padding
135
+ if self.padding > 0:
136
+ grad_input = grad_input[:, :, self.padding:-self.padding]
137
+
138
+ return grad_input
139
+
140
+ def __call__(self, x: np.ndarray) -> np.ndarray:
141
+ return self.forward(x)
142
+
143
+
144
+ class MaxPool2D:
145
+ """
146
+ 2D Max Pooling Layer.
147
+
148
+ Applies max pooling over 2D input (images, feature maps).
149
+ Most common pooling operation in CNNs.
150
+
151
+ Formula:
152
+ output[i,j] = max(input[i*stride_h : i*stride_h + pool_h,
153
+ j*stride_w : j*stride_w + pool_w])
154
+
155
+ Args:
156
+ pool_size: Size of pooling window (int or tuple)
157
+ stride: Stride of pooling operation (default: pool_size)
158
+ padding: Padding to add (default: 0)
159
+
160
+ Example:
161
+ >>> pool = MaxPool2D(pool_size=2, stride=2)
162
+ >>> x = np.random.randn(32, 64, 28, 28) # (batch, channels, height, width)
163
+ >>> output = pool.forward(x)
164
+ >>> print(output.shape) # (32, 64, 14, 14)
165
+
166
+ Reference:
167
+ Standard practice in CNNs (AlexNet, VGG, ResNet)
168
+ """
169
+
170
+ def __init__(self, pool_size: Union[int, Tuple[int, int]],
171
+ stride: Optional[Union[int, Tuple[int, int]]] = None,
172
+ padding: Union[int, Tuple[int, int]] = 0):
173
+ # Handle pool_size
174
+ if isinstance(pool_size, int):
175
+ self.pool_h, self.pool_w = pool_size, pool_size
176
+ else:
177
+ self.pool_h, self.pool_w = pool_size
178
+
179
+ # Handle stride
180
+ if stride is None:
181
+ self.stride_h, self.stride_w = self.pool_h, self.pool_w
182
+ elif isinstance(stride, int):
183
+ self.stride_h, self.stride_w = stride, stride
184
+ else:
185
+ self.stride_h, self.stride_w = stride
186
+
187
+ # Handle padding
188
+ if isinstance(padding, int):
189
+ self.pad_h, self.pad_w = padding, padding
190
+ else:
191
+ self.pad_h, self.pad_w = padding
192
+
193
+ self.cache = None
194
+
195
+ def forward(self, x: np.ndarray) -> np.ndarray:
196
+ """
197
+ Forward pass.
198
+
199
+ Args:
200
+ x: Input tensor (batch, channels, height, width)
201
+
202
+ Returns:
203
+ Pooled output
204
+ """
205
+ batch_size, channels, height, width = x.shape
206
+
207
+ # Apply padding
208
+ if self.pad_h > 0 or self.pad_w > 0:
209
+ x_padded = np.pad(x, ((0, 0), (0, 0), (self.pad_h, self.pad_h),
210
+ (self.pad_w, self.pad_w)),
211
+ mode='constant', constant_values=-np.inf)
212
+ else:
213
+ x_padded = x
214
+
215
+ # Calculate output dimensions
216
+ out_h = (x_padded.shape[2] - self.pool_h) // self.stride_h + 1
217
+ out_w = (x_padded.shape[3] - self.pool_w) // self.stride_w + 1
218
+
219
+ # Initialize output
220
+ output = np.zeros((batch_size, channels, out_h, out_w))
221
+
222
+ # Store for backward pass
223
+ self.cache = (x.shape, x_padded)
224
+
225
+ # Perform max pooling
226
+ for i in range(out_h):
227
+ for j in range(out_w):
228
+ h_start = i * self.stride_h
229
+ h_end = h_start + self.pool_h
230
+ w_start = j * self.stride_w
231
+ w_end = w_start + self.pool_w
232
+
233
+ window = x_padded[:, :, h_start:h_end, w_start:w_end]
234
+ output[:, :, i, j] = np.max(window, axis=(2, 3))
235
+
236
+ return output
237
+
238
+ def backward(self, grad_output: np.ndarray) -> np.ndarray:
239
+ """Backward pass."""
240
+ x_shape, x_padded = self.cache
241
+ batch_size, channels, height, width = x_shape
242
+
243
+ grad_input = np.zeros_like(x_padded)
244
+ out_h, out_w = grad_output.shape[2], grad_output.shape[3]
245
+
246
+ for i in range(out_h):
247
+ for j in range(out_w):
248
+ h_start = i * self.stride_h
249
+ h_end = h_start + self.pool_h
250
+ w_start = j * self.stride_w
251
+ w_end = w_start + self.pool_w
252
+
253
+ window = x_padded[:, :, h_start:h_end, w_start:w_end]
254
+ max_vals = np.max(window, axis=(2, 3), keepdims=True)
255
+ mask = (window == max_vals)
256
+
257
+ grad_input[:, :, h_start:h_end, w_start:w_end] += \
258
+ mask * grad_output[:, :, i:i+1, j:j+1]
259
+
260
+ if self.pad_h > 0 or self.pad_w > 0:
261
+ grad_input = grad_input[:, :, self.pad_h:-self.pad_h, self.pad_w:-self.pad_w]
262
+
263
+ return grad_input
264
+
265
+ def __call__(self, x: np.ndarray) -> np.ndarray:
266
+ return self.forward(x)
267
+
268
+
269
+ class AvgPool1D:
270
+ """
271
+ 1D Average Pooling Layer.
272
+
273
+ Applies average pooling over 1D input.
274
+ Computes mean value within each pooling window.
275
+
276
+ Formula:
277
+ output[i] = mean(input[i*stride : i*stride + pool_size])
278
+
279
+ Args:
280
+ pool_size: Size of pooling window
281
+ stride: Stride of pooling operation (default: pool_size)
282
+ padding: Padding to add (default: 0)
283
+
284
+ Example:
285
+ >>> pool = AvgPool1D(pool_size=2, stride=2)
286
+ >>> x = np.array([[[1, 3, 2, 4, 5, 1]]])
287
+ >>> output = pool.forward(x)
288
+ >>> print(output) # [[[2.0, 3.0, 3.0]]]
289
+
290
+ Reference:
291
+ Standard practice in CNNs
292
+ """
293
+
294
+ def __init__(self, pool_size: int, stride: Optional[int] = None, padding: int = 0):
295
+ if pool_size <= 0:
296
+ raise ValueError(f"pool_size must be positive, got {pool_size}")
297
+
298
+ self.pool_size = pool_size
299
+ self.stride = stride if stride is not None else pool_size
300
+ self.padding = padding
301
+ self.cache = None
302
+
303
+ def forward(self, x: np.ndarray) -> np.ndarray:
304
+ """Forward pass."""
305
+ batch_size, channels, length = x.shape
306
+
307
+ if self.padding > 0:
308
+ x_padded = np.pad(x, ((0, 0), (0, 0), (self.padding, self.padding)),
309
+ mode='constant', constant_values=0)
310
+ else:
311
+ x_padded = x
312
+
313
+ out_length = (x_padded.shape[2] - self.pool_size) // self.stride + 1
314
+ output = np.zeros((batch_size, channels, out_length))
315
+
316
+ self.cache = x.shape
317
+
318
+ for i in range(out_length):
319
+ start = i * self.stride
320
+ end = start + self.pool_size
321
+ output[:, :, i] = np.mean(x_padded[:, :, start:end], axis=2)
322
+
323
+ return output
324
+
325
+ def backward(self, grad_output: np.ndarray) -> np.ndarray:
326
+ """Backward pass."""
327
+ x_shape = self.cache
328
+ batch_size, channels, length = x_shape
329
+
330
+ grad_input = np.zeros(x_shape)
331
+ out_length = grad_output.shape[2]
332
+
333
+ for i in range(out_length):
334
+ start = i * self.stride
335
+ end = start + self.pool_size
336
+
337
+ # Distribute gradient equally
338
+ grad_input[:, :, start:end] += grad_output[:, :, i:i+1] / self.pool_size
339
+
340
+ return grad_input
341
+
342
+ def __call__(self, x: np.ndarray) -> np.ndarray:
343
+ return self.forward(x)
344
+
345
+
346
+ class AvgPool2D:
347
+ """
348
+ 2D Average Pooling Layer.
349
+
350
+ Applies average pooling over 2D input.
351
+ Smoother than max pooling, preserves more spatial information.
352
+
353
+ Formula:
354
+ output[i,j] = mean(input[i*stride_h : i*stride_h + pool_h,
355
+ j*stride_w : j*stride_w + pool_w])
356
+
357
+ Args:
358
+ pool_size: Size of pooling window (int or tuple)
359
+ stride: Stride of pooling operation (default: pool_size)
360
+ padding: Padding to add (default: 0)
361
+
362
+ Example:
363
+ >>> pool = AvgPool2D(pool_size=2, stride=2)
364
+ >>> x = np.random.randn(32, 64, 28, 28)
365
+ >>> output = pool.forward(x)
366
+ >>> print(output.shape) # (32, 64, 14, 14)
367
+
368
+ Reference:
369
+ Used in LeNet, some modern architectures
370
+ """
371
+
372
+ def __init__(self, pool_size: Union[int, Tuple[int, int]],
373
+ stride: Optional[Union[int, Tuple[int, int]]] = None,
374
+ padding: Union[int, Tuple[int, int]] = 0):
375
+ if isinstance(pool_size, int):
376
+ self.pool_h, self.pool_w = pool_size, pool_size
377
+ else:
378
+ self.pool_h, self.pool_w = pool_size
379
+
380
+ if stride is None:
381
+ self.stride_h, self.stride_w = self.pool_h, self.pool_w
382
+ elif isinstance(stride, int):
383
+ self.stride_h, self.stride_w = stride, stride
384
+ else:
385
+ self.stride_h, self.stride_w = stride
386
+
387
+ if isinstance(padding, int):
388
+ self.pad_h, self.pad_w = padding, padding
389
+ else:
390
+ self.pad_h, self.pad_w = padding
391
+
392
+ self.cache = None
393
+
394
+ def forward(self, x: np.ndarray) -> np.ndarray:
395
+ """Forward pass."""
396
+ batch_size, channels, height, width = x.shape
397
+
398
+ if self.pad_h > 0 or self.pad_w > 0:
399
+ x_padded = np.pad(x, ((0, 0), (0, 0), (self.pad_h, self.pad_h),
400
+ (self.pad_w, self.pad_w)), mode='constant')
401
+ else:
402
+ x_padded = x
403
+
404
+ out_h = (x_padded.shape[2] - self.pool_h) // self.stride_h + 1
405
+ out_w = (x_padded.shape[3] - self.pool_w) // self.stride_w + 1
406
+
407
+ output = np.zeros((batch_size, channels, out_h, out_w))
408
+ self.cache = x.shape
409
+
410
+ for i in range(out_h):
411
+ for j in range(out_w):
412
+ h_start = i * self.stride_h
413
+ h_end = h_start + self.pool_h
414
+ w_start = j * self.stride_w
415
+ w_end = w_start + self.pool_w
416
+
417
+ window = x_padded[:, :, h_start:h_end, w_start:w_end]
418
+ output[:, :, i, j] = np.mean(window, axis=(2, 3))
419
+
420
+ return output
421
+
422
+ def backward(self, grad_output: np.ndarray) -> np.ndarray:
423
+ """Backward pass."""
424
+ x_shape = self.cache
425
+ batch_size, channels, height, width = x_shape
426
+
427
+ grad_input = np.zeros(x_shape)
428
+ out_h, out_w = grad_output.shape[2], grad_output.shape[3]
429
+
430
+ for i in range(out_h):
431
+ for j in range(out_w):
432
+ h_start = i * self.stride_h
433
+ h_end = h_start + self.pool_h
434
+ w_start = j * self.stride_w
435
+ w_end = w_start + self.pool_w
436
+
437
+ grad_input[:, :, h_start:h_end, w_start:w_end] += \
438
+ grad_output[:, :, i:i+1, j:j+1] / (self.pool_h * self.pool_w)
439
+
440
+ return grad_input
441
+
442
+ def __call__(self, x: np.ndarray) -> np.ndarray:
443
+ return self.forward(x)
444
+
445
+
446
+ class GlobalMaxPool:
447
+ """
448
+ Global Max Pooling Layer.
449
+
450
+ Reduces each feature map to a single value by taking maximum.
451
+ Commonly used before fully connected layers in classification.
452
+
453
+ Formula:
454
+ output[c] = max(input[:, c, :, :])
455
+
456
+ Example:
457
+ >>> pool = GlobalMaxPool()
458
+ >>> x = np.random.randn(32, 512, 7, 7) # (batch, channels, h, w)
459
+ >>> output = pool.forward(x)
460
+ >>> print(output.shape) # (32, 512)
461
+
462
+ Reference:
463
+ "Network In Network" (Lin et al., 2013)
464
+ """
465
+
466
+ def __init__(self):
467
+ self.cache = None
468
+
469
+ def forward(self, x: np.ndarray) -> np.ndarray:
470
+ """
471
+ Forward pass.
472
+
473
+ Args:
474
+ x: Input tensor (batch, channels, height, width) or (batch, channels, length)
475
+
476
+ Returns:
477
+ Pooled output (batch, channels)
478
+ """
479
+ # Handle both 3D and 4D inputs
480
+ if x.ndim == 3:
481
+ # (batch, channels, length)
482
+ output = np.max(x, axis=2)
483
+ elif x.ndim == 4:
484
+ # (batch, channels, height, width)
485
+ output = np.max(x, axis=(2, 3))
486
+ else:
487
+ raise ValueError(f"Expected 3D or 4D input, got {x.ndim}D")
488
+
489
+ self.cache = x
490
+ return output
491
+
492
+ def backward(self, grad_output: np.ndarray) -> np.ndarray:
493
+ """Backward pass."""
494
+ x = self.cache
495
+
496
+ if x.ndim == 3:
497
+ max_vals = np.max(x, axis=2, keepdims=True)
498
+ mask = (x == max_vals)
499
+ grad_input = mask * grad_output[:, :, np.newaxis]
500
+ else:
501
+ max_vals = np.max(x, axis=(2, 3), keepdims=True)
502
+ mask = (x == max_vals)
503
+ grad_input = mask * grad_output[:, :, np.newaxis, np.newaxis]
504
+
505
+ return grad_input
506
+
507
+ def __call__(self, x: np.ndarray) -> np.ndarray:
508
+ return self.forward(x)
509
+
510
+
511
+ class GlobalAvgPool:
512
+ """
513
+ Global Average Pooling Layer.
514
+
515
+ Reduces each feature map to a single value by averaging.
516
+ Reduces overfitting compared to fully connected layers.
517
+
518
+ Formula:
519
+ output[c] = mean(input[:, c, :, :])
520
+
521
+ Example:
522
+ >>> pool = GlobalAvgPool()
523
+ >>> x = np.random.randn(32, 512, 7, 7)
524
+ >>> output = pool.forward(x)
525
+ >>> print(output.shape) # (32, 512)
526
+
527
+ Reference:
528
+ "Network In Network" (Lin et al., 2013)
529
+ Used in ResNet, MobileNet, EfficientNet
530
+ """
531
+
532
+ def __init__(self):
533
+ self.cache = None
534
+
535
+ def forward(self, x: np.ndarray) -> np.ndarray:
536
+ """Forward pass."""
537
+ if x.ndim == 3:
538
+ output = np.mean(x, axis=2)
539
+ elif x.ndim == 4:
540
+ output = np.mean(x, axis=(2, 3))
541
+ else:
542
+ raise ValueError(f"Expected 3D or 4D input, got {x.ndim}D")
543
+
544
+ self.cache = x
545
+ return output
546
+
547
+ def backward(self, grad_output: np.ndarray) -> np.ndarray:
548
+ """Backward pass."""
549
+ x = self.cache
550
+
551
+ if x.ndim == 3:
552
+ spatial_size = x.shape[2]
553
+ grad_input = np.repeat(grad_output[:, :, np.newaxis], spatial_size, axis=2)
554
+ grad_input = grad_input / spatial_size
555
+ else:
556
+ spatial_size = x.shape[2] * x.shape[3]
557
+ grad_input = np.repeat(np.repeat(grad_output[:, :, np.newaxis, np.newaxis],
558
+ x.shape[2], axis=2), x.shape[3], axis=3)
559
+ grad_input = grad_input / spatial_size
560
+
561
+ return grad_input
562
+
563
+ def __call__(self, x: np.ndarray) -> np.ndarray:
564
+ return self.forward(x)
565
+
566
+
567
+ class AdaptiveMaxPool:
568
+ """
569
+ Adaptive Max Pooling Layer.
570
+
571
+ Pools to a fixed output size regardless of input size.
572
+ Automatically calculates pooling parameters.
573
+
574
+ Args:
575
+ output_size: Desired output size (int or tuple)
576
+
577
+ Example:
578
+ >>> pool = AdaptiveMaxPool(output_size=(7, 7))
579
+ >>> x1 = np.random.randn(32, 512, 14, 14)
580
+ >>> x2 = np.random.randn(32, 512, 28, 28)
581
+ >>> out1 = pool.forward(x1)
582
+ >>> out2 = pool.forward(x2)
583
+ >>> print(out1.shape, out2.shape) # Both (32, 512, 7, 7)
584
+
585
+ Reference:
586
+ PyTorch AdaptiveMaxPool2d
587
+ """
588
+
589
+ def __init__(self, output_size: Union[int, Tuple[int, int]]):
590
+ if isinstance(output_size, int):
591
+ self.out_h, self.out_w = output_size, output_size
592
+ else:
593
+ self.out_h, self.out_w = output_size
594
+
595
+ self.cache = None
596
+
597
+ def forward(self, x: np.ndarray) -> np.ndarray:
598
+ """Forward pass."""
599
+ batch_size, channels, in_h, in_w = x.shape
600
+
601
+ output = np.zeros((batch_size, channels, self.out_h, self.out_w))
602
+ self.cache = x
603
+
604
+ for i in range(self.out_h):
605
+ for j in range(self.out_w):
606
+ h_start = int(np.floor(i * in_h / self.out_h))
607
+ h_end = int(np.ceil((i + 1) * in_h / self.out_h))
608
+ w_start = int(np.floor(j * in_w / self.out_w))
609
+ w_end = int(np.ceil((j + 1) * in_w / self.out_w))
610
+
611
+ window = x[:, :, h_start:h_end, w_start:w_end]
612
+ output[:, :, i, j] = np.max(window, axis=(2, 3))
613
+
614
+ return output
615
+
616
+ def __call__(self, x: np.ndarray) -> np.ndarray:
617
+ return self.forward(x)
618
+
619
+
620
+ class AdaptiveAvgPool:
621
+ """
622
+ Adaptive Average Pooling Layer.
623
+
624
+ Pools to a fixed output size using averaging.
625
+
626
+ Args:
627
+ output_size: Desired output size (int or tuple)
628
+
629
+ Example:
630
+ >>> pool = AdaptiveAvgPool(output_size=(1, 1)) # Global avg pool
631
+ >>> x = np.random.randn(32, 512, 14, 14)
632
+ >>> output = pool.forward(x)
633
+ >>> print(output.shape) # (32, 512, 1, 1)
634
+
635
+ Reference:
636
+ PyTorch AdaptiveAvgPool2d
637
+ """
638
+
639
+ def __init__(self, output_size: Union[int, Tuple[int, int]]):
640
+ if isinstance(output_size, int):
641
+ self.out_h, self.out_w = output_size, output_size
642
+ else:
643
+ self.out_h, self.out_w = output_size
644
+
645
+ self.cache = None
646
+
647
+ def forward(self, x: np.ndarray) -> np.ndarray:
648
+ """Forward pass."""
649
+ batch_size, channels, in_h, in_w = x.shape
650
+
651
+ output = np.zeros((batch_size, channels, self.out_h, self.out_w))
652
+ self.cache = x
653
+
654
+ for i in range(self.out_h):
655
+ for j in range(self.out_w):
656
+ h_start = int(np.floor(i * in_h / self.out_h))
657
+ h_end = int(np.ceil((i + 1) * in_h / self.out_h))
658
+ w_start = int(np.floor(j * in_w / self.out_w))
659
+ w_end = int(np.ceil((j + 1) * in_w / self.out_w))
660
+
661
+ window = x[:, :, h_start:h_end, w_start:w_end]
662
+ output[:, :, i, j] = np.mean(window, axis=(2, 3))
663
+
664
+ return output
665
+
666
+ def __call__(self, x: np.ndarray) -> np.ndarray:
667
+ return self.forward(x)
668
+
669
+
670
+ __all__ = [
671
+ 'MaxPool1D',
672
+ 'MaxPool2D',
673
+ 'AvgPool1D',
674
+ 'AvgPool2D',
675
+ 'GlobalMaxPool',
676
+ 'GlobalAvgPool',
677
+ 'AdaptiveMaxPool',
678
+ 'AdaptiveAvgPool',
679
+ ]