ilovetools 0.2.28__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.
- {ilovetools-0.2.28/ilovetools.egg-info → ilovetools-0.2.30}/PKG-INFO +2 -2
- ilovetools-0.2.30/ilovetools/ml/pooling.py +679 -0
- ilovetools-0.2.30/ilovetools/ml/schedulers.py +588 -0
- {ilovetools-0.2.28 → ilovetools-0.2.30/ilovetools.egg-info}/PKG-INFO +2 -2
- {ilovetools-0.2.28 → ilovetools-0.2.30}/ilovetools.egg-info/SOURCES.txt +4 -0
- {ilovetools-0.2.28 → ilovetools-0.2.30}/pyproject.toml +2 -2
- {ilovetools-0.2.28 → ilovetools-0.2.30}/setup.py +2 -2
- ilovetools-0.2.30/tests/test_pooling.py +461 -0
- ilovetools-0.2.30/tests/test_schedulers.py +397 -0
- {ilovetools-0.2.28 → ilovetools-0.2.30}/LICENSE +0 -0
- {ilovetools-0.2.28 → ilovetools-0.2.30}/MANIFEST.in +0 -0
- {ilovetools-0.2.28 → ilovetools-0.2.30}/README.md +0 -0
- {ilovetools-0.2.28 → ilovetools-0.2.30}/ilovetools/__init__.py +0 -0
- {ilovetools-0.2.28 → ilovetools-0.2.30}/ilovetools/ai/__init__.py +0 -0
- {ilovetools-0.2.28 → ilovetools-0.2.30}/ilovetools/ai/embeddings.py +0 -0
- {ilovetools-0.2.28 → ilovetools-0.2.30}/ilovetools/ai/inference.py +0 -0
- {ilovetools-0.2.28 → ilovetools-0.2.30}/ilovetools/ai/llm_helpers.py +0 -0
- {ilovetools-0.2.28 → ilovetools-0.2.30}/ilovetools/audio/__init__.py +0 -0
- {ilovetools-0.2.28 → ilovetools-0.2.30}/ilovetools/automation/__init__.py +0 -0
- {ilovetools-0.2.28 → ilovetools-0.2.30}/ilovetools/automation/file_organizer.py +0 -0
- {ilovetools-0.2.28 → ilovetools-0.2.30}/ilovetools/conversion/__init__.py +0 -0
- {ilovetools-0.2.28 → ilovetools-0.2.30}/ilovetools/conversion/config_converter.py +0 -0
- {ilovetools-0.2.28 → ilovetools-0.2.30}/ilovetools/conversion/config_converter_fixed_header.py +0 -0
- {ilovetools-0.2.28 → ilovetools-0.2.30}/ilovetools/data/__init__.py +0 -0
- {ilovetools-0.2.28 → ilovetools-0.2.30}/ilovetools/data/feature_engineering.py +0 -0
- {ilovetools-0.2.28 → ilovetools-0.2.30}/ilovetools/data/preprocessing.py +0 -0
- {ilovetools-0.2.28 → ilovetools-0.2.30}/ilovetools/database/__init__.py +0 -0
- {ilovetools-0.2.28 → ilovetools-0.2.30}/ilovetools/datetime/__init__.py +0 -0
- {ilovetools-0.2.28 → ilovetools-0.2.30}/ilovetools/email/__init__.py +0 -0
- {ilovetools-0.2.28 → ilovetools-0.2.30}/ilovetools/email/template_engine.py +0 -0
- {ilovetools-0.2.28 → ilovetools-0.2.30}/ilovetools/files/__init__.py +0 -0
- {ilovetools-0.2.28 → ilovetools-0.2.30}/ilovetools/image/__init__.py +0 -0
- {ilovetools-0.2.28 → ilovetools-0.2.30}/ilovetools/ml/__init__.py +0 -0
- {ilovetools-0.2.28 → ilovetools-0.2.30}/ilovetools/ml/activations.py +0 -0
- {ilovetools-0.2.28 → ilovetools-0.2.30}/ilovetools/ml/anomaly_detection.py +0 -0
- {ilovetools-0.2.28 → ilovetools-0.2.30}/ilovetools/ml/attention.py +0 -0
- {ilovetools-0.2.28 → ilovetools-0.2.30}/ilovetools/ml/augmentation.py +0 -0
- {ilovetools-0.2.28 → ilovetools-0.2.30}/ilovetools/ml/clustering.py +0 -0
- {ilovetools-0.2.28 → ilovetools-0.2.30}/ilovetools/ml/cnn.py +0 -0
- {ilovetools-0.2.28 → ilovetools-0.2.30}/ilovetools/ml/cross_validation.py +0 -0
- {ilovetools-0.2.28 → ilovetools-0.2.30}/ilovetools/ml/dimensionality.py +0 -0
- {ilovetools-0.2.28 → ilovetools-0.2.30}/ilovetools/ml/dropout.py +0 -0
- {ilovetools-0.2.28 → ilovetools-0.2.30}/ilovetools/ml/ensemble.py +0 -0
- {ilovetools-0.2.28 → ilovetools-0.2.30}/ilovetools/ml/feature_selection.py +0 -0
- {ilovetools-0.2.28 → ilovetools-0.2.30}/ilovetools/ml/gradient_descent.py +0 -0
- {ilovetools-0.2.28 → ilovetools-0.2.30}/ilovetools/ml/imbalanced.py +0 -0
- {ilovetools-0.2.28 → ilovetools-0.2.30}/ilovetools/ml/interpretation.py +0 -0
- {ilovetools-0.2.28 → ilovetools-0.2.30}/ilovetools/ml/loss_functions.py +0 -0
- {ilovetools-0.2.28 → ilovetools-0.2.30}/ilovetools/ml/losses.py +0 -0
- {ilovetools-0.2.28 → ilovetools-0.2.30}/ilovetools/ml/lr_schedulers.py +0 -0
- {ilovetools-0.2.28 → ilovetools-0.2.30}/ilovetools/ml/metrics.py +0 -0
- {ilovetools-0.2.28 → ilovetools-0.2.30}/ilovetools/ml/neural_network.py +0 -0
- {ilovetools-0.2.28 → ilovetools-0.2.30}/ilovetools/ml/normalization.py +0 -0
- {ilovetools-0.2.28 → ilovetools-0.2.30}/ilovetools/ml/normalization_advanced.py +0 -0
- {ilovetools-0.2.28 → ilovetools-0.2.30}/ilovetools/ml/optimizers.py +0 -0
- {ilovetools-0.2.28 → ilovetools-0.2.30}/ilovetools/ml/pipeline.py +0 -0
- {ilovetools-0.2.28 → ilovetools-0.2.30}/ilovetools/ml/positional_encoding.py +0 -0
- {ilovetools-0.2.28 → ilovetools-0.2.30}/ilovetools/ml/regularization.py +0 -0
- {ilovetools-0.2.28 → ilovetools-0.2.30}/ilovetools/ml/rnn.py +0 -0
- {ilovetools-0.2.28 → ilovetools-0.2.30}/ilovetools/ml/timeseries.py +0 -0
- {ilovetools-0.2.28 → ilovetools-0.2.30}/ilovetools/ml/tuning.py +0 -0
- {ilovetools-0.2.28 → ilovetools-0.2.30}/ilovetools/ml/weight_init.py +0 -0
- {ilovetools-0.2.28 → ilovetools-0.2.30}/ilovetools/security/__init__.py +0 -0
- {ilovetools-0.2.28 → ilovetools-0.2.30}/ilovetools/security/password_checker.py +0 -0
- {ilovetools-0.2.28 → ilovetools-0.2.30}/ilovetools/text/__init__.py +0 -0
- {ilovetools-0.2.28 → ilovetools-0.2.30}/ilovetools/utils/__init__.py +0 -0
- {ilovetools-0.2.28 → ilovetools-0.2.30}/ilovetools/utils/cache_system.py +0 -0
- {ilovetools-0.2.28 → ilovetools-0.2.30}/ilovetools/utils/logger.py +0 -0
- {ilovetools-0.2.28 → ilovetools-0.2.30}/ilovetools/utils/rate_limiter.py +0 -0
- {ilovetools-0.2.28 → ilovetools-0.2.30}/ilovetools/utils/retry.py +0 -0
- {ilovetools-0.2.28 → ilovetools-0.2.30}/ilovetools/validation/__init__.py +0 -0
- {ilovetools-0.2.28 → ilovetools-0.2.30}/ilovetools/validation/data_validator.py +0 -0
- {ilovetools-0.2.28 → ilovetools-0.2.30}/ilovetools/web/__init__.py +0 -0
- {ilovetools-0.2.28 → ilovetools-0.2.30}/ilovetools/web/scraper.py +0 -0
- {ilovetools-0.2.28 → ilovetools-0.2.30}/ilovetools/web/url_shortener.py +0 -0
- {ilovetools-0.2.28 → ilovetools-0.2.30}/ilovetools.egg-info/dependency_links.txt +0 -0
- {ilovetools-0.2.28 → ilovetools-0.2.30}/ilovetools.egg-info/requires.txt +0 -0
- {ilovetools-0.2.28 → ilovetools-0.2.30}/ilovetools.egg-info/top_level.txt +0 -0
- {ilovetools-0.2.28 → ilovetools-0.2.30}/requirements.txt +0 -0
- {ilovetools-0.2.28 → ilovetools-0.2.30}/setup.cfg +0 -0
- {ilovetools-0.2.28 → ilovetools-0.2.30}/tests/__init__.py +0 -0
- {ilovetools-0.2.28 → ilovetools-0.2.30}/tests/test_activations.py +0 -0
- {ilovetools-0.2.28 → ilovetools-0.2.30}/tests/test_attention.py +0 -0
- {ilovetools-0.2.28 → ilovetools-0.2.30}/tests/test_augmentation.py +0 -0
- {ilovetools-0.2.28 → ilovetools-0.2.30}/tests/test_cnn.py +0 -0
- {ilovetools-0.2.28 → ilovetools-0.2.30}/tests/test_dropout.py +0 -0
- {ilovetools-0.2.28 → ilovetools-0.2.30}/tests/test_gradient_descent.py +0 -0
- {ilovetools-0.2.28 → ilovetools-0.2.30}/tests/test_loss_functions.py +0 -0
- {ilovetools-0.2.28 → ilovetools-0.2.30}/tests/test_losses.py +0 -0
- {ilovetools-0.2.28 → ilovetools-0.2.30}/tests/test_lr_schedulers.py +0 -0
- {ilovetools-0.2.28 → ilovetools-0.2.30}/tests/test_neural_network.py +0 -0
- {ilovetools-0.2.28 → ilovetools-0.2.30}/tests/test_normalization.py +0 -0
- {ilovetools-0.2.28 → ilovetools-0.2.30}/tests/test_normalization_advanced.py +0 -0
- {ilovetools-0.2.28 → ilovetools-0.2.30}/tests/test_optimizers.py +0 -0
- {ilovetools-0.2.28 → ilovetools-0.2.30}/tests/test_positional_encoding.py +0 -0
- {ilovetools-0.2.28 → ilovetools-0.2.30}/tests/test_pypi_installation.py +0 -0
- {ilovetools-0.2.28 → ilovetools-0.2.30}/tests/test_regularization.py +0 -0
- {ilovetools-0.2.28 → ilovetools-0.2.30}/tests/test_rnn.py +0 -0
- {ilovetools-0.2.28 → ilovetools-0.2.30}/tests/test_weight_init.py +0 -0
- {ilovetools-0.2.28 → 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.
|
|
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,
|
|
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
|
+
]
|