ilovetools 0.2.26__tar.gz → 0.2.28__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.26/ilovetools.egg-info → ilovetools-0.2.28}/PKG-INFO +3 -2
- ilovetools-0.2.28/ilovetools/ml/augmentation.py +624 -0
- ilovetools-0.2.28/ilovetools/ml/losses.py +758 -0
- {ilovetools-0.2.26 → ilovetools-0.2.28/ilovetools.egg-info}/PKG-INFO +3 -2
- {ilovetools-0.2.26 → ilovetools-0.2.28}/ilovetools.egg-info/SOURCES.txt +4 -0
- {ilovetools-0.2.26 → ilovetools-0.2.28}/ilovetools.egg-info/requires.txt +1 -0
- {ilovetools-0.2.26 → ilovetools-0.2.28}/pyproject.toml +3 -2
- {ilovetools-0.2.26 → ilovetools-0.2.28}/setup.py +3 -2
- ilovetools-0.2.28/tests/test_augmentation.py +485 -0
- ilovetools-0.2.28/tests/test_losses.py +540 -0
- {ilovetools-0.2.26 → ilovetools-0.2.28}/LICENSE +0 -0
- {ilovetools-0.2.26 → ilovetools-0.2.28}/MANIFEST.in +0 -0
- {ilovetools-0.2.26 → ilovetools-0.2.28}/README.md +0 -0
- {ilovetools-0.2.26 → ilovetools-0.2.28}/ilovetools/__init__.py +0 -0
- {ilovetools-0.2.26 → ilovetools-0.2.28}/ilovetools/ai/__init__.py +0 -0
- {ilovetools-0.2.26 → ilovetools-0.2.28}/ilovetools/ai/embeddings.py +0 -0
- {ilovetools-0.2.26 → ilovetools-0.2.28}/ilovetools/ai/inference.py +0 -0
- {ilovetools-0.2.26 → ilovetools-0.2.28}/ilovetools/ai/llm_helpers.py +0 -0
- {ilovetools-0.2.26 → ilovetools-0.2.28}/ilovetools/audio/__init__.py +0 -0
- {ilovetools-0.2.26 → ilovetools-0.2.28}/ilovetools/automation/__init__.py +0 -0
- {ilovetools-0.2.26 → ilovetools-0.2.28}/ilovetools/automation/file_organizer.py +0 -0
- {ilovetools-0.2.26 → ilovetools-0.2.28}/ilovetools/conversion/__init__.py +0 -0
- {ilovetools-0.2.26 → ilovetools-0.2.28}/ilovetools/conversion/config_converter.py +0 -0
- {ilovetools-0.2.26 → ilovetools-0.2.28}/ilovetools/conversion/config_converter_fixed_header.py +0 -0
- {ilovetools-0.2.26 → ilovetools-0.2.28}/ilovetools/data/__init__.py +0 -0
- {ilovetools-0.2.26 → ilovetools-0.2.28}/ilovetools/data/feature_engineering.py +0 -0
- {ilovetools-0.2.26 → ilovetools-0.2.28}/ilovetools/data/preprocessing.py +0 -0
- {ilovetools-0.2.26 → ilovetools-0.2.28}/ilovetools/database/__init__.py +0 -0
- {ilovetools-0.2.26 → ilovetools-0.2.28}/ilovetools/datetime/__init__.py +0 -0
- {ilovetools-0.2.26 → ilovetools-0.2.28}/ilovetools/email/__init__.py +0 -0
- {ilovetools-0.2.26 → ilovetools-0.2.28}/ilovetools/email/template_engine.py +0 -0
- {ilovetools-0.2.26 → ilovetools-0.2.28}/ilovetools/files/__init__.py +0 -0
- {ilovetools-0.2.26 → ilovetools-0.2.28}/ilovetools/image/__init__.py +0 -0
- {ilovetools-0.2.26 → ilovetools-0.2.28}/ilovetools/ml/__init__.py +0 -0
- {ilovetools-0.2.26 → ilovetools-0.2.28}/ilovetools/ml/activations.py +0 -0
- {ilovetools-0.2.26 → ilovetools-0.2.28}/ilovetools/ml/anomaly_detection.py +0 -0
- {ilovetools-0.2.26 → ilovetools-0.2.28}/ilovetools/ml/attention.py +0 -0
- {ilovetools-0.2.26 → ilovetools-0.2.28}/ilovetools/ml/clustering.py +0 -0
- {ilovetools-0.2.26 → ilovetools-0.2.28}/ilovetools/ml/cnn.py +0 -0
- {ilovetools-0.2.26 → ilovetools-0.2.28}/ilovetools/ml/cross_validation.py +0 -0
- {ilovetools-0.2.26 → ilovetools-0.2.28}/ilovetools/ml/dimensionality.py +0 -0
- {ilovetools-0.2.26 → ilovetools-0.2.28}/ilovetools/ml/dropout.py +0 -0
- {ilovetools-0.2.26 → ilovetools-0.2.28}/ilovetools/ml/ensemble.py +0 -0
- {ilovetools-0.2.26 → ilovetools-0.2.28}/ilovetools/ml/feature_selection.py +0 -0
- {ilovetools-0.2.26 → ilovetools-0.2.28}/ilovetools/ml/gradient_descent.py +0 -0
- {ilovetools-0.2.26 → ilovetools-0.2.28}/ilovetools/ml/imbalanced.py +0 -0
- {ilovetools-0.2.26 → ilovetools-0.2.28}/ilovetools/ml/interpretation.py +0 -0
- {ilovetools-0.2.26 → ilovetools-0.2.28}/ilovetools/ml/loss_functions.py +0 -0
- {ilovetools-0.2.26 → ilovetools-0.2.28}/ilovetools/ml/lr_schedulers.py +0 -0
- {ilovetools-0.2.26 → ilovetools-0.2.28}/ilovetools/ml/metrics.py +0 -0
- {ilovetools-0.2.26 → ilovetools-0.2.28}/ilovetools/ml/neural_network.py +0 -0
- {ilovetools-0.2.26 → ilovetools-0.2.28}/ilovetools/ml/normalization.py +0 -0
- {ilovetools-0.2.26 → ilovetools-0.2.28}/ilovetools/ml/normalization_advanced.py +0 -0
- {ilovetools-0.2.26 → ilovetools-0.2.28}/ilovetools/ml/optimizers.py +0 -0
- {ilovetools-0.2.26 → ilovetools-0.2.28}/ilovetools/ml/pipeline.py +0 -0
- {ilovetools-0.2.26 → ilovetools-0.2.28}/ilovetools/ml/positional_encoding.py +0 -0
- {ilovetools-0.2.26 → ilovetools-0.2.28}/ilovetools/ml/regularization.py +0 -0
- {ilovetools-0.2.26 → ilovetools-0.2.28}/ilovetools/ml/rnn.py +0 -0
- {ilovetools-0.2.26 → ilovetools-0.2.28}/ilovetools/ml/timeseries.py +0 -0
- {ilovetools-0.2.26 → ilovetools-0.2.28}/ilovetools/ml/tuning.py +0 -0
- {ilovetools-0.2.26 → ilovetools-0.2.28}/ilovetools/ml/weight_init.py +0 -0
- {ilovetools-0.2.26 → ilovetools-0.2.28}/ilovetools/security/__init__.py +0 -0
- {ilovetools-0.2.26 → ilovetools-0.2.28}/ilovetools/security/password_checker.py +0 -0
- {ilovetools-0.2.26 → ilovetools-0.2.28}/ilovetools/text/__init__.py +0 -0
- {ilovetools-0.2.26 → ilovetools-0.2.28}/ilovetools/utils/__init__.py +0 -0
- {ilovetools-0.2.26 → ilovetools-0.2.28}/ilovetools/utils/cache_system.py +0 -0
- {ilovetools-0.2.26 → ilovetools-0.2.28}/ilovetools/utils/logger.py +0 -0
- {ilovetools-0.2.26 → ilovetools-0.2.28}/ilovetools/utils/rate_limiter.py +0 -0
- {ilovetools-0.2.26 → ilovetools-0.2.28}/ilovetools/utils/retry.py +0 -0
- {ilovetools-0.2.26 → ilovetools-0.2.28}/ilovetools/validation/__init__.py +0 -0
- {ilovetools-0.2.26 → ilovetools-0.2.28}/ilovetools/validation/data_validator.py +0 -0
- {ilovetools-0.2.26 → ilovetools-0.2.28}/ilovetools/web/__init__.py +0 -0
- {ilovetools-0.2.26 → ilovetools-0.2.28}/ilovetools/web/scraper.py +0 -0
- {ilovetools-0.2.26 → ilovetools-0.2.28}/ilovetools/web/url_shortener.py +0 -0
- {ilovetools-0.2.26 → ilovetools-0.2.28}/ilovetools.egg-info/dependency_links.txt +0 -0
- {ilovetools-0.2.26 → ilovetools-0.2.28}/ilovetools.egg-info/top_level.txt +0 -0
- {ilovetools-0.2.26 → ilovetools-0.2.28}/requirements.txt +0 -0
- {ilovetools-0.2.26 → ilovetools-0.2.28}/setup.cfg +0 -0
- {ilovetools-0.2.26 → ilovetools-0.2.28}/tests/__init__.py +0 -0
- {ilovetools-0.2.26 → ilovetools-0.2.28}/tests/test_activations.py +0 -0
- {ilovetools-0.2.26 → ilovetools-0.2.28}/tests/test_attention.py +0 -0
- {ilovetools-0.2.26 → ilovetools-0.2.28}/tests/test_cnn.py +0 -0
- {ilovetools-0.2.26 → ilovetools-0.2.28}/tests/test_dropout.py +0 -0
- {ilovetools-0.2.26 → ilovetools-0.2.28}/tests/test_gradient_descent.py +0 -0
- {ilovetools-0.2.26 → ilovetools-0.2.28}/tests/test_loss_functions.py +0 -0
- {ilovetools-0.2.26 → ilovetools-0.2.28}/tests/test_lr_schedulers.py +0 -0
- {ilovetools-0.2.26 → ilovetools-0.2.28}/tests/test_neural_network.py +0 -0
- {ilovetools-0.2.26 → ilovetools-0.2.28}/tests/test_normalization.py +0 -0
- {ilovetools-0.2.26 → ilovetools-0.2.28}/tests/test_normalization_advanced.py +0 -0
- {ilovetools-0.2.26 → ilovetools-0.2.28}/tests/test_optimizers.py +0 -0
- {ilovetools-0.2.26 → ilovetools-0.2.28}/tests/test_positional_encoding.py +0 -0
- {ilovetools-0.2.26 → ilovetools-0.2.28}/tests/test_pypi_installation.py +0 -0
- {ilovetools-0.2.26 → ilovetools-0.2.28}/tests/test_regularization.py +0 -0
- {ilovetools-0.2.26 → ilovetools-0.2.28}/tests/test_rnn.py +0 -0
- {ilovetools-0.2.26 → ilovetools-0.2.28}/tests/test_weight_init.py +0 -0
- {ilovetools-0.2.26 → ilovetools-0.2.28}/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.28
|
|
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,data-augmentation,image-augmentation,text-augmentation,mixup,cutout,random-erasing,autoaugment,randaugment,albumentations,computer-vision,nlp,deep-learning,neural-networks,training,generalization,overfitting
|
|
15
15
|
Classifier: Development Status :: 3 - Alpha
|
|
16
16
|
Classifier: Intended Audience :: Developers
|
|
17
17
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
@@ -27,6 +27,7 @@ License-File: LICENSE
|
|
|
27
27
|
Requires-Dist: requests>=2.31.0
|
|
28
28
|
Requires-Dist: numpy>=1.24.0
|
|
29
29
|
Requires-Dist: pandas>=2.0.0
|
|
30
|
+
Requires-Dist: scipy>=1.10.0
|
|
30
31
|
Provides-Extra: ai
|
|
31
32
|
Requires-Dist: openai>=1.0.0; extra == "ai"
|
|
32
33
|
Requires-Dist: transformers>=4.30.0; extra == "ai"
|
|
@@ -0,0 +1,624 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Data Augmentation Suite
|
|
3
|
+
|
|
4
|
+
This module implements various data augmentation techniques for training neural networks.
|
|
5
|
+
Augmentation artificially increases dataset size and diversity, improving generalization.
|
|
6
|
+
|
|
7
|
+
Implemented Techniques:
|
|
8
|
+
|
|
9
|
+
Image Augmentation:
|
|
10
|
+
1. Rotation - Rotate images by random angles
|
|
11
|
+
2. Horizontal Flip - Mirror images horizontally
|
|
12
|
+
3. Vertical Flip - Mirror images vertically
|
|
13
|
+
4. Random Crop - Extract random patches
|
|
14
|
+
5. Center Crop - Extract center region
|
|
15
|
+
6. Color Jitter - Adjust brightness, contrast, saturation
|
|
16
|
+
7. Gaussian Noise - Add random noise
|
|
17
|
+
8. Gaussian Blur - Apply blur filter
|
|
18
|
+
9. Random Erasing - Erase random rectangular regions
|
|
19
|
+
10. Cutout - Mask out square regions
|
|
20
|
+
11. Mixup - Blend two images
|
|
21
|
+
12. Normalize - Standardize pixel values
|
|
22
|
+
|
|
23
|
+
Text Augmentation:
|
|
24
|
+
1. Synonym Replacement - Replace words with synonyms
|
|
25
|
+
2. Random Insertion - Insert random words
|
|
26
|
+
3. Random Swap - Swap word positions
|
|
27
|
+
4. Random Deletion - Delete random words
|
|
28
|
+
|
|
29
|
+
References:
|
|
30
|
+
- Rotation/Flip/Crop: Classic augmentation techniques
|
|
31
|
+
- Mixup: "mixup: Beyond Empirical Risk Minimization" (Zhang et al., 2017)
|
|
32
|
+
- Cutout: "Improved Regularization of CNNs with Cutout" (DeVries & Taylor, 2017)
|
|
33
|
+
- Random Erasing: "Random Erasing Data Augmentation" (Zhong et al., 2017)
|
|
34
|
+
- AutoAugment: "AutoAugment: Learning Augmentation Policies" (Cubuk et al., 2018)
|
|
35
|
+
|
|
36
|
+
Author: Ali Mehdi
|
|
37
|
+
Date: January 15, 2026
|
|
38
|
+
"""
|
|
39
|
+
|
|
40
|
+
import numpy as np
|
|
41
|
+
from typing import Tuple, Optional, List
|
|
42
|
+
import random
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class ImageAugmenter:
|
|
46
|
+
"""
|
|
47
|
+
Image Augmentation Pipeline.
|
|
48
|
+
|
|
49
|
+
Provides various augmentation techniques for image data.
|
|
50
|
+
All methods work with NumPy arrays in (H, W, C) format.
|
|
51
|
+
|
|
52
|
+
Example:
|
|
53
|
+
>>> aug = ImageAugmenter()
|
|
54
|
+
>>> image = np.random.rand(224, 224, 3)
|
|
55
|
+
>>> rotated = aug.rotate(image, angle=30)
|
|
56
|
+
>>> flipped = aug.horizontal_flip(image)
|
|
57
|
+
>>> noisy = aug.gaussian_noise(image, std=0.1)
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
def __init__(self, seed: Optional[int] = None):
|
|
61
|
+
"""
|
|
62
|
+
Initialize augmenter.
|
|
63
|
+
|
|
64
|
+
Args:
|
|
65
|
+
seed: Random seed for reproducibility
|
|
66
|
+
"""
|
|
67
|
+
if seed is not None:
|
|
68
|
+
np.random.seed(seed)
|
|
69
|
+
random.seed(seed)
|
|
70
|
+
|
|
71
|
+
def rotate(self, image: np.ndarray, angle: float) -> np.ndarray:
|
|
72
|
+
"""
|
|
73
|
+
Rotate image by specified angle.
|
|
74
|
+
|
|
75
|
+
Args:
|
|
76
|
+
image: Input image (H, W, C)
|
|
77
|
+
angle: Rotation angle in degrees (positive = counter-clockwise)
|
|
78
|
+
|
|
79
|
+
Returns:
|
|
80
|
+
Rotated image
|
|
81
|
+
|
|
82
|
+
Example:
|
|
83
|
+
>>> aug = ImageAugmenter()
|
|
84
|
+
>>> image = np.random.rand(100, 100, 3)
|
|
85
|
+
>>> rotated = aug.rotate(image, angle=45)
|
|
86
|
+
"""
|
|
87
|
+
from scipy.ndimage import rotate as scipy_rotate
|
|
88
|
+
return scipy_rotate(image, angle, reshape=False, mode='nearest')
|
|
89
|
+
|
|
90
|
+
def random_rotation(self, image: np.ndarray, max_angle: float = 30) -> np.ndarray:
|
|
91
|
+
"""
|
|
92
|
+
Rotate image by random angle.
|
|
93
|
+
|
|
94
|
+
Args:
|
|
95
|
+
image: Input image (H, W, C)
|
|
96
|
+
max_angle: Maximum rotation angle in degrees
|
|
97
|
+
|
|
98
|
+
Returns:
|
|
99
|
+
Randomly rotated image
|
|
100
|
+
"""
|
|
101
|
+
angle = np.random.uniform(-max_angle, max_angle)
|
|
102
|
+
return self.rotate(image, angle)
|
|
103
|
+
|
|
104
|
+
def horizontal_flip(self, image: np.ndarray) -> np.ndarray:
|
|
105
|
+
"""
|
|
106
|
+
Flip image horizontally (left-right).
|
|
107
|
+
|
|
108
|
+
Args:
|
|
109
|
+
image: Input image (H, W, C)
|
|
110
|
+
|
|
111
|
+
Returns:
|
|
112
|
+
Horizontally flipped image
|
|
113
|
+
|
|
114
|
+
Example:
|
|
115
|
+
>>> aug = ImageAugmenter()
|
|
116
|
+
>>> image = np.random.rand(100, 100, 3)
|
|
117
|
+
>>> flipped = aug.horizontal_flip(image)
|
|
118
|
+
"""
|
|
119
|
+
return np.fliplr(image)
|
|
120
|
+
|
|
121
|
+
def vertical_flip(self, image: np.ndarray) -> np.ndarray:
|
|
122
|
+
"""
|
|
123
|
+
Flip image vertically (up-down).
|
|
124
|
+
|
|
125
|
+
Args:
|
|
126
|
+
image: Input image (H, W, C)
|
|
127
|
+
|
|
128
|
+
Returns:
|
|
129
|
+
Vertically flipped image
|
|
130
|
+
|
|
131
|
+
Example:
|
|
132
|
+
>>> aug = ImageAugmenter()
|
|
133
|
+
>>> image = np.random.rand(100, 100, 3)
|
|
134
|
+
>>> flipped = aug.vertical_flip(image)
|
|
135
|
+
"""
|
|
136
|
+
return np.flipud(image)
|
|
137
|
+
|
|
138
|
+
def random_crop(self, image: np.ndarray, crop_size: Tuple[int, int]) -> np.ndarray:
|
|
139
|
+
"""
|
|
140
|
+
Extract random crop from image.
|
|
141
|
+
|
|
142
|
+
Args:
|
|
143
|
+
image: Input image (H, W, C)
|
|
144
|
+
crop_size: (height, width) of crop
|
|
145
|
+
|
|
146
|
+
Returns:
|
|
147
|
+
Randomly cropped image
|
|
148
|
+
|
|
149
|
+
Example:
|
|
150
|
+
>>> aug = ImageAugmenter()
|
|
151
|
+
>>> image = np.random.rand(224, 224, 3)
|
|
152
|
+
>>> cropped = aug.random_crop(image, crop_size=(128, 128))
|
|
153
|
+
"""
|
|
154
|
+
h, w = image.shape[:2]
|
|
155
|
+
crop_h, crop_w = crop_size
|
|
156
|
+
|
|
157
|
+
if crop_h > h or crop_w > w:
|
|
158
|
+
raise ValueError(f"Crop size {crop_size} larger than image {(h, w)}")
|
|
159
|
+
|
|
160
|
+
top = np.random.randint(0, h - crop_h + 1)
|
|
161
|
+
left = np.random.randint(0, w - crop_w + 1)
|
|
162
|
+
|
|
163
|
+
return image[top:top + crop_h, left:left + crop_w]
|
|
164
|
+
|
|
165
|
+
def center_crop(self, image: np.ndarray, crop_size: Tuple[int, int]) -> np.ndarray:
|
|
166
|
+
"""
|
|
167
|
+
Extract center crop from image.
|
|
168
|
+
|
|
169
|
+
Args:
|
|
170
|
+
image: Input image (H, W, C)
|
|
171
|
+
crop_size: (height, width) of crop
|
|
172
|
+
|
|
173
|
+
Returns:
|
|
174
|
+
Center cropped image
|
|
175
|
+
|
|
176
|
+
Example:
|
|
177
|
+
>>> aug = ImageAugmenter()
|
|
178
|
+
>>> image = np.random.rand(224, 224, 3)
|
|
179
|
+
>>> cropped = aug.center_crop(image, crop_size=(128, 128))
|
|
180
|
+
"""
|
|
181
|
+
h, w = image.shape[:2]
|
|
182
|
+
crop_h, crop_w = crop_size
|
|
183
|
+
|
|
184
|
+
if crop_h > h or crop_w > w:
|
|
185
|
+
raise ValueError(f"Crop size {crop_size} larger than image {(h, w)}")
|
|
186
|
+
|
|
187
|
+
top = (h - crop_h) // 2
|
|
188
|
+
left = (w - crop_w) // 2
|
|
189
|
+
|
|
190
|
+
return image[top:top + crop_h, left:left + crop_w]
|
|
191
|
+
|
|
192
|
+
def color_jitter(self, image: np.ndarray, brightness: float = 0.2,
|
|
193
|
+
contrast: float = 0.2, saturation: float = 0.2) -> np.ndarray:
|
|
194
|
+
"""
|
|
195
|
+
Randomly adjust brightness, contrast, and saturation.
|
|
196
|
+
|
|
197
|
+
Args:
|
|
198
|
+
image: Input image (H, W, C) in range [0, 1]
|
|
199
|
+
brightness: Max brightness adjustment factor
|
|
200
|
+
contrast: Max contrast adjustment factor
|
|
201
|
+
saturation: Max saturation adjustment factor
|
|
202
|
+
|
|
203
|
+
Returns:
|
|
204
|
+
Color-jittered image
|
|
205
|
+
|
|
206
|
+
Example:
|
|
207
|
+
>>> aug = ImageAugmenter()
|
|
208
|
+
>>> image = np.random.rand(100, 100, 3)
|
|
209
|
+
>>> jittered = aug.color_jitter(image, brightness=0.3)
|
|
210
|
+
"""
|
|
211
|
+
# Brightness
|
|
212
|
+
brightness_factor = 1 + np.random.uniform(-brightness, brightness)
|
|
213
|
+
image = np.clip(image * brightness_factor, 0, 1)
|
|
214
|
+
|
|
215
|
+
# Contrast
|
|
216
|
+
contrast_factor = 1 + np.random.uniform(-contrast, contrast)
|
|
217
|
+
mean = image.mean(axis=(0, 1), keepdims=True)
|
|
218
|
+
image = np.clip((image - mean) * contrast_factor + mean, 0, 1)
|
|
219
|
+
|
|
220
|
+
# Saturation (convert to HSV, adjust S channel)
|
|
221
|
+
if saturation > 0 and image.shape[-1] == 3:
|
|
222
|
+
saturation_factor = 1 + np.random.uniform(-saturation, saturation)
|
|
223
|
+
# Simple saturation adjustment in RGB space
|
|
224
|
+
gray = image.mean(axis=-1, keepdims=True)
|
|
225
|
+
image = np.clip((image - gray) * saturation_factor + gray, 0, 1)
|
|
226
|
+
|
|
227
|
+
return image
|
|
228
|
+
|
|
229
|
+
def gaussian_noise(self, image: np.ndarray, mean: float = 0.0,
|
|
230
|
+
std: float = 0.1) -> np.ndarray:
|
|
231
|
+
"""
|
|
232
|
+
Add Gaussian noise to image.
|
|
233
|
+
|
|
234
|
+
Args:
|
|
235
|
+
image: Input image (H, W, C)
|
|
236
|
+
mean: Mean of Gaussian noise
|
|
237
|
+
std: Standard deviation of Gaussian noise
|
|
238
|
+
|
|
239
|
+
Returns:
|
|
240
|
+
Noisy image
|
|
241
|
+
|
|
242
|
+
Example:
|
|
243
|
+
>>> aug = ImageAugmenter()
|
|
244
|
+
>>> image = np.random.rand(100, 100, 3)
|
|
245
|
+
>>> noisy = aug.gaussian_noise(image, std=0.05)
|
|
246
|
+
"""
|
|
247
|
+
noise = np.random.normal(mean, std, image.shape)
|
|
248
|
+
noisy_image = image + noise
|
|
249
|
+
return np.clip(noisy_image, 0, 1)
|
|
250
|
+
|
|
251
|
+
def gaussian_blur(self, image: np.ndarray, sigma: float = 1.0) -> np.ndarray:
|
|
252
|
+
"""
|
|
253
|
+
Apply Gaussian blur to image.
|
|
254
|
+
|
|
255
|
+
Args:
|
|
256
|
+
image: Input image (H, W, C)
|
|
257
|
+
sigma: Standard deviation of Gaussian kernel
|
|
258
|
+
|
|
259
|
+
Returns:
|
|
260
|
+
Blurred image
|
|
261
|
+
|
|
262
|
+
Example:
|
|
263
|
+
>>> aug = ImageAugmenter()
|
|
264
|
+
>>> image = np.random.rand(100, 100, 3)
|
|
265
|
+
>>> blurred = aug.gaussian_blur(image, sigma=2.0)
|
|
266
|
+
"""
|
|
267
|
+
from scipy.ndimage import gaussian_filter
|
|
268
|
+
return gaussian_filter(image, sigma=(sigma, sigma, 0))
|
|
269
|
+
|
|
270
|
+
def random_erasing(self, image: np.ndarray, probability: float = 0.5,
|
|
271
|
+
area_ratio_range: Tuple[float, float] = (0.02, 0.4),
|
|
272
|
+
aspect_ratio_range: Tuple[float, float] = (0.3, 3.3)) -> np.ndarray:
|
|
273
|
+
"""
|
|
274
|
+
Randomly erase rectangular regions (Random Erasing augmentation).
|
|
275
|
+
|
|
276
|
+
Args:
|
|
277
|
+
image: Input image (H, W, C)
|
|
278
|
+
probability: Probability of applying erasing
|
|
279
|
+
area_ratio_range: Range of erased area ratio
|
|
280
|
+
aspect_ratio_range: Range of aspect ratio
|
|
281
|
+
|
|
282
|
+
Returns:
|
|
283
|
+
Image with random erasing applied
|
|
284
|
+
|
|
285
|
+
Example:
|
|
286
|
+
>>> aug = ImageAugmenter()
|
|
287
|
+
>>> image = np.random.rand(100, 100, 3)
|
|
288
|
+
>>> erased = aug.random_erasing(image, probability=0.5)
|
|
289
|
+
|
|
290
|
+
Reference:
|
|
291
|
+
Zhong et al., "Random Erasing Data Augmentation", 2017
|
|
292
|
+
"""
|
|
293
|
+
if np.random.random() > probability:
|
|
294
|
+
return image
|
|
295
|
+
|
|
296
|
+
h, w = image.shape[:2]
|
|
297
|
+
area = h * w
|
|
298
|
+
|
|
299
|
+
for _ in range(100): # Try up to 100 times
|
|
300
|
+
target_area = np.random.uniform(*area_ratio_range) * area
|
|
301
|
+
aspect_ratio = np.random.uniform(*aspect_ratio_range)
|
|
302
|
+
|
|
303
|
+
erase_h = int(np.sqrt(target_area * aspect_ratio))
|
|
304
|
+
erase_w = int(np.sqrt(target_area / aspect_ratio))
|
|
305
|
+
|
|
306
|
+
if erase_h < h and erase_w < w:
|
|
307
|
+
top = np.random.randint(0, h - erase_h)
|
|
308
|
+
left = np.random.randint(0, w - erase_w)
|
|
309
|
+
|
|
310
|
+
# Erase with random values
|
|
311
|
+
image = image.copy()
|
|
312
|
+
image[top:top + erase_h, left:left + erase_w] = np.random.random(
|
|
313
|
+
(erase_h, erase_w, image.shape[2])
|
|
314
|
+
)
|
|
315
|
+
break
|
|
316
|
+
|
|
317
|
+
return image
|
|
318
|
+
|
|
319
|
+
def cutout(self, image: np.ndarray, n_holes: int = 1,
|
|
320
|
+
length: int = 16) -> np.ndarray:
|
|
321
|
+
"""
|
|
322
|
+
Apply Cutout augmentation (mask out square regions).
|
|
323
|
+
|
|
324
|
+
Args:
|
|
325
|
+
image: Input image (H, W, C)
|
|
326
|
+
n_holes: Number of holes to cut out
|
|
327
|
+
length: Side length of square holes
|
|
328
|
+
|
|
329
|
+
Returns:
|
|
330
|
+
Image with cutout applied
|
|
331
|
+
|
|
332
|
+
Example:
|
|
333
|
+
>>> aug = ImageAugmenter()
|
|
334
|
+
>>> image = np.random.rand(100, 100, 3)
|
|
335
|
+
>>> cutout_img = aug.cutout(image, n_holes=1, length=20)
|
|
336
|
+
|
|
337
|
+
Reference:
|
|
338
|
+
DeVries & Taylor, "Improved Regularization of CNNs with Cutout", 2017
|
|
339
|
+
"""
|
|
340
|
+
h, w = image.shape[:2]
|
|
341
|
+
image = image.copy()
|
|
342
|
+
|
|
343
|
+
for _ in range(n_holes):
|
|
344
|
+
y = np.random.randint(h)
|
|
345
|
+
x = np.random.randint(w)
|
|
346
|
+
|
|
347
|
+
y1 = np.clip(y - length // 2, 0, h)
|
|
348
|
+
y2 = np.clip(y + length // 2, 0, h)
|
|
349
|
+
x1 = np.clip(x - length // 2, 0, w)
|
|
350
|
+
x2 = np.clip(x + length // 2, 0, w)
|
|
351
|
+
|
|
352
|
+
image[y1:y2, x1:x2] = 0
|
|
353
|
+
|
|
354
|
+
return image
|
|
355
|
+
|
|
356
|
+
def mixup(self, image1: np.ndarray, image2: np.ndarray,
|
|
357
|
+
alpha: float = 0.2) -> Tuple[np.ndarray, float]:
|
|
358
|
+
"""
|
|
359
|
+
Apply Mixup augmentation (blend two images).
|
|
360
|
+
|
|
361
|
+
Args:
|
|
362
|
+
image1: First input image (H, W, C)
|
|
363
|
+
image2: Second input image (H, W, C)
|
|
364
|
+
alpha: Beta distribution parameter
|
|
365
|
+
|
|
366
|
+
Returns:
|
|
367
|
+
Tuple of (mixed image, mixing coefficient lambda)
|
|
368
|
+
|
|
369
|
+
Example:
|
|
370
|
+
>>> aug = ImageAugmenter()
|
|
371
|
+
>>> img1 = np.random.rand(100, 100, 3)
|
|
372
|
+
>>> img2 = np.random.rand(100, 100, 3)
|
|
373
|
+
>>> mixed, lam = aug.mixup(img1, img2, alpha=0.2)
|
|
374
|
+
|
|
375
|
+
Reference:
|
|
376
|
+
Zhang et al., "mixup: Beyond Empirical Risk Minimization", 2017
|
|
377
|
+
"""
|
|
378
|
+
if image1.shape != image2.shape:
|
|
379
|
+
raise ValueError(f"Image shapes must match: {image1.shape} vs {image2.shape}")
|
|
380
|
+
|
|
381
|
+
lam = np.random.beta(alpha, alpha) if alpha > 0 else 1
|
|
382
|
+
mixed_image = lam * image1 + (1 - lam) * image2
|
|
383
|
+
|
|
384
|
+
return mixed_image, lam
|
|
385
|
+
|
|
386
|
+
def normalize(self, image: np.ndarray, mean: Tuple[float, ...],
|
|
387
|
+
std: Tuple[float, ...]) -> np.ndarray:
|
|
388
|
+
"""
|
|
389
|
+
Normalize image with mean and standard deviation.
|
|
390
|
+
|
|
391
|
+
Args:
|
|
392
|
+
image: Input image (H, W, C)
|
|
393
|
+
mean: Mean for each channel
|
|
394
|
+
std: Standard deviation for each channel
|
|
395
|
+
|
|
396
|
+
Returns:
|
|
397
|
+
Normalized image
|
|
398
|
+
|
|
399
|
+
Example:
|
|
400
|
+
>>> aug = ImageAugmenter()
|
|
401
|
+
>>> image = np.random.rand(100, 100, 3)
|
|
402
|
+
>>> normalized = aug.normalize(image, mean=(0.5, 0.5, 0.5), std=(0.5, 0.5, 0.5))
|
|
403
|
+
"""
|
|
404
|
+
mean = np.array(mean).reshape(1, 1, -1)
|
|
405
|
+
std = np.array(std).reshape(1, 1, -1)
|
|
406
|
+
return (image - mean) / std
|
|
407
|
+
|
|
408
|
+
|
|
409
|
+
class TextAugmenter:
|
|
410
|
+
"""
|
|
411
|
+
Text Augmentation Pipeline.
|
|
412
|
+
|
|
413
|
+
Provides various augmentation techniques for text data.
|
|
414
|
+
Useful for NLP tasks with limited training data.
|
|
415
|
+
|
|
416
|
+
Example:
|
|
417
|
+
>>> aug = TextAugmenter()
|
|
418
|
+
>>> text = "The quick brown fox jumps over the lazy dog"
|
|
419
|
+
>>> augmented = aug.random_deletion(text, p=0.1)
|
|
420
|
+
"""
|
|
421
|
+
|
|
422
|
+
def __init__(self, seed: Optional[int] = None):
|
|
423
|
+
"""
|
|
424
|
+
Initialize text augmenter.
|
|
425
|
+
|
|
426
|
+
Args:
|
|
427
|
+
seed: Random seed for reproducibility
|
|
428
|
+
"""
|
|
429
|
+
if seed is not None:
|
|
430
|
+
random.seed(seed)
|
|
431
|
+
|
|
432
|
+
def synonym_replacement(self, text: str, n: int = 1) -> str:
|
|
433
|
+
"""
|
|
434
|
+
Replace n words with synonyms.
|
|
435
|
+
|
|
436
|
+
Note: This is a simplified version. For production, use libraries
|
|
437
|
+
like NLTK with WordNet for actual synonym replacement.
|
|
438
|
+
|
|
439
|
+
Args:
|
|
440
|
+
text: Input text
|
|
441
|
+
n: Number of words to replace
|
|
442
|
+
|
|
443
|
+
Returns:
|
|
444
|
+
Text with synonyms replaced
|
|
445
|
+
|
|
446
|
+
Example:
|
|
447
|
+
>>> aug = TextAugmenter()
|
|
448
|
+
>>> text = "The quick brown fox"
|
|
449
|
+
>>> augmented = aug.synonym_replacement(text, n=1)
|
|
450
|
+
"""
|
|
451
|
+
words = text.split()
|
|
452
|
+
|
|
453
|
+
# Simple synonym dictionary (expand for production use)
|
|
454
|
+
synonyms = {
|
|
455
|
+
'quick': ['fast', 'rapid', 'swift'],
|
|
456
|
+
'brown': ['tan', 'beige', 'chocolate'],
|
|
457
|
+
'lazy': ['idle', 'sluggish', 'inactive'],
|
|
458
|
+
'big': ['large', 'huge', 'enormous'],
|
|
459
|
+
'small': ['tiny', 'little', 'mini'],
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
for _ in range(n):
|
|
463
|
+
replaceable = [i for i, w in enumerate(words) if w.lower() in synonyms]
|
|
464
|
+
if not replaceable:
|
|
465
|
+
break
|
|
466
|
+
|
|
467
|
+
idx = random.choice(replaceable)
|
|
468
|
+
word = words[idx].lower()
|
|
469
|
+
words[idx] = random.choice(synonyms[word])
|
|
470
|
+
|
|
471
|
+
return ' '.join(words)
|
|
472
|
+
|
|
473
|
+
def random_insertion(self, text: str, n: int = 1) -> str:
|
|
474
|
+
"""
|
|
475
|
+
Randomly insert n words into the text.
|
|
476
|
+
|
|
477
|
+
Args:
|
|
478
|
+
text: Input text
|
|
479
|
+
n: Number of words to insert
|
|
480
|
+
|
|
481
|
+
Returns:
|
|
482
|
+
Text with random insertions
|
|
483
|
+
|
|
484
|
+
Example:
|
|
485
|
+
>>> aug = TextAugmenter()
|
|
486
|
+
>>> text = "The quick fox"
|
|
487
|
+
>>> augmented = aug.random_insertion(text, n=1)
|
|
488
|
+
"""
|
|
489
|
+
words = text.split()
|
|
490
|
+
|
|
491
|
+
for _ in range(n):
|
|
492
|
+
if not words:
|
|
493
|
+
break
|
|
494
|
+
|
|
495
|
+
# Insert a random word from the text
|
|
496
|
+
random_word = random.choice(words)
|
|
497
|
+
random_idx = random.randint(0, len(words))
|
|
498
|
+
words.insert(random_idx, random_word)
|
|
499
|
+
|
|
500
|
+
return ' '.join(words)
|
|
501
|
+
|
|
502
|
+
def random_swap(self, text: str, n: int = 1) -> str:
|
|
503
|
+
"""
|
|
504
|
+
Randomly swap n pairs of words.
|
|
505
|
+
|
|
506
|
+
Args:
|
|
507
|
+
text: Input text
|
|
508
|
+
n: Number of swaps to perform
|
|
509
|
+
|
|
510
|
+
Returns:
|
|
511
|
+
Text with random swaps
|
|
512
|
+
|
|
513
|
+
Example:
|
|
514
|
+
>>> aug = TextAugmenter()
|
|
515
|
+
>>> text = "The quick brown fox"
|
|
516
|
+
>>> augmented = aug.random_swap(text, n=1)
|
|
517
|
+
"""
|
|
518
|
+
words = text.split()
|
|
519
|
+
|
|
520
|
+
for _ in range(n):
|
|
521
|
+
if len(words) < 2:
|
|
522
|
+
break
|
|
523
|
+
|
|
524
|
+
idx1, idx2 = random.sample(range(len(words)), 2)
|
|
525
|
+
words[idx1], words[idx2] = words[idx2], words[idx1]
|
|
526
|
+
|
|
527
|
+
return ' '.join(words)
|
|
528
|
+
|
|
529
|
+
def random_deletion(self, text: str, p: float = 0.1) -> str:
|
|
530
|
+
"""
|
|
531
|
+
Randomly delete words with probability p.
|
|
532
|
+
|
|
533
|
+
Args:
|
|
534
|
+
text: Input text
|
|
535
|
+
p: Probability of deleting each word
|
|
536
|
+
|
|
537
|
+
Returns:
|
|
538
|
+
Text with random deletions
|
|
539
|
+
|
|
540
|
+
Example:
|
|
541
|
+
>>> aug = TextAugmenter()
|
|
542
|
+
>>> text = "The quick brown fox jumps"
|
|
543
|
+
>>> augmented = aug.random_deletion(text, p=0.2)
|
|
544
|
+
"""
|
|
545
|
+
words = text.split()
|
|
546
|
+
|
|
547
|
+
if len(words) == 1:
|
|
548
|
+
return text
|
|
549
|
+
|
|
550
|
+
new_words = [w for w in words if random.random() > p]
|
|
551
|
+
|
|
552
|
+
# If all words deleted, return random word
|
|
553
|
+
if len(new_words) == 0:
|
|
554
|
+
return random.choice(words)
|
|
555
|
+
|
|
556
|
+
return ' '.join(new_words)
|
|
557
|
+
|
|
558
|
+
|
|
559
|
+
class AugmentationPipeline:
|
|
560
|
+
"""
|
|
561
|
+
Composable augmentation pipeline.
|
|
562
|
+
|
|
563
|
+
Chain multiple augmentation operations together.
|
|
564
|
+
|
|
565
|
+
Example:
|
|
566
|
+
>>> from ilovetools.ml.augmentation import ImageAugmenter, AugmentationPipeline
|
|
567
|
+
>>> aug = ImageAugmenter()
|
|
568
|
+
>>> pipeline = AugmentationPipeline([
|
|
569
|
+
... lambda x: aug.random_rotation(x, max_angle=15),
|
|
570
|
+
... lambda x: aug.horizontal_flip(x) if np.random.random() > 0.5 else x,
|
|
571
|
+
... lambda x: aug.gaussian_noise(x, std=0.05)
|
|
572
|
+
... ])
|
|
573
|
+
>>> image = np.random.rand(100, 100, 3)
|
|
574
|
+
>>> augmented = pipeline(image)
|
|
575
|
+
"""
|
|
576
|
+
|
|
577
|
+
def __init__(self, transforms: List):
|
|
578
|
+
"""
|
|
579
|
+
Initialize pipeline.
|
|
580
|
+
|
|
581
|
+
Args:
|
|
582
|
+
transforms: List of augmentation functions
|
|
583
|
+
"""
|
|
584
|
+
self.transforms = transforms
|
|
585
|
+
|
|
586
|
+
def __call__(self, data):
|
|
587
|
+
"""Apply all transforms in sequence."""
|
|
588
|
+
for transform in self.transforms:
|
|
589
|
+
data = transform(data)
|
|
590
|
+
return data
|
|
591
|
+
|
|
592
|
+
def add(self, transform):
|
|
593
|
+
"""Add transform to pipeline."""
|
|
594
|
+
self.transforms.append(transform)
|
|
595
|
+
|
|
596
|
+
def remove(self, index: int):
|
|
597
|
+
"""Remove transform at index."""
|
|
598
|
+
del self.transforms[index]
|
|
599
|
+
|
|
600
|
+
|
|
601
|
+
# Convenience functions
|
|
602
|
+
def rotate_image(image: np.ndarray, angle: float) -> np.ndarray:
|
|
603
|
+
"""Rotate image by angle."""
|
|
604
|
+
return ImageAugmenter().rotate(image, angle)
|
|
605
|
+
|
|
606
|
+
|
|
607
|
+
def flip_horizontal(image: np.ndarray) -> np.ndarray:
|
|
608
|
+
"""Flip image horizontally."""
|
|
609
|
+
return ImageAugmenter().horizontal_flip(image)
|
|
610
|
+
|
|
611
|
+
|
|
612
|
+
def add_noise(image: np.ndarray, std: float = 0.1) -> np.ndarray:
|
|
613
|
+
"""Add Gaussian noise to image."""
|
|
614
|
+
return ImageAugmenter().gaussian_noise(image, std=std)
|
|
615
|
+
|
|
616
|
+
|
|
617
|
+
__all__ = [
|
|
618
|
+
'ImageAugmenter',
|
|
619
|
+
'TextAugmenter',
|
|
620
|
+
'AugmentationPipeline',
|
|
621
|
+
'rotate_image',
|
|
622
|
+
'flip_horizontal',
|
|
623
|
+
'add_noise',
|
|
624
|
+
]
|