JsonDONSON commited on
Commit
c33c303
·
verified ·
1 Parent(s): e083cd6

Upload 5 files

Browse files
Files changed (5) hide show
  1. app.py +71 -0
  2. best_model.pth +3 -0
  3. config.py +204 -0
  4. model.py +128 -0
  5. requirements.txt +33 -0
app.py ADDED
@@ -0,0 +1,71 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import torch
3
+ import librosa
4
+ import numpy as np
5
+ import noisereduce as nr # <-- CRITICAL ADDITION
6
+ from model import MachineSoundCNN # Make sure this matches your model class name!
7
+
8
+ # 1. Define your exact classes
9
+ CLASSES = [
10
+ "Machine 1 Normal", "Machine 1 Abnormal",
11
+ "Machine 2 Normal", "Machine 2 Abnormal",
12
+ "Machine 3 Normal", "Machine 3 Abnormal"
13
+ ]
14
+
15
+ # 2. Load the Model (Forcing CPU for the Hugging Face Free Tier)
16
+ device = torch.device('cpu')
17
+ model = MachineSoundCNN() # Initialize EL sir's architecture
18
+ model.load_state_dict(torch.load('best_model.pth', map_location=device))
19
+ model.eval() # Set to evaluation mode (freezes BatchNorm/Dropout)
20
+
21
+ # 3. Preprocessing & Inference Function
22
+ def predict_machine_sound(audio_path):
23
+ if audio_path is None:
24
+ return "Please upload an audio file."
25
+
26
+ # Load audio at exactly 16kHz
27
+ y, sr = librosa.load(audio_path, sr=16000)
28
+
29
+ # --- CRITICAL FIX: NOISE REDUCTION ---
30
+ # Replicating your specific 0.5-second noise profile logic
31
+ profile_length = int(0.5 * sr)
32
+ if len(y) > profile_length:
33
+ noise_profile = y[:profile_length]
34
+ y_clean = nr.reduce_noise(y=y, sr=sr, y_noise=noise_profile, prop_decrease=0.8)
35
+ else:
36
+ y_clean = nr.reduce_noise(y=y, sr=sr, y_noise=y, prop_decrease=0.5)
37
+
38
+ # Clip signal to [-1.0, 1.0] just like your training pipeline
39
+ y_clean = np.clip(y_clean, -1.0, 1.0)
40
+ # -------------------------------------
41
+
42
+ # Generate Mel Spectrogram (128 bins) using the CLEAN audio
43
+ mel_spec = librosa.feature.melspectrogram(y=y_clean, sr=sr, n_mels=128)
44
+ mel_spec_db = librosa.power_to_db(mel_spec, ref=np.max)
45
+
46
+ # Reshape for the CNN: (Batch, Channel, Height, Time) -> (1, 1, 128, time_steps)
47
+ input_tensor = torch.tensor(mel_spec_db, dtype=torch.float32).unsqueeze(0).unsqueeze(0)
48
+
49
+ # Forward Pass through the CNN
50
+ with torch.no_grad():
51
+ outputs = model(input_tensor)
52
+ # Convert raw output numbers into percentages (0.0 to 1.0)
53
+ probabilities = torch.nn.functional.softmax(outputs[0], dim=0)
54
+
55
+ # Format output for the Gradio UI: a dictionary of {Class Name: Probability}
56
+ result = {CLASSES[i]: float(probabilities[i]) for i in range(len(CLASSES))}
57
+ return result
58
+
59
+ # 4. Build the Web Interface
60
+ interface = gr.Interface(
61
+ fn=predict_machine_sound,
62
+ inputs=gr.Audio(type="filepath", label="Upload Machine Audio (.wav)"),
63
+ outputs=gr.Label(num_top_classes=6, label="CNN Prediction Confidence"),
64
+ title="Industrial Machine Sound Anomaly Detector",
65
+ description="Upload an audio clip of an industrial machine. The Custom CNN will analyze the Mel Spectrogram and predict if it is operating normally or failing.",
66
+ allow_flagging="never"
67
+ )
68
+
69
+ # 5. Launch the App
70
+ if __name__ == "__main__":
71
+ interface.launch()
best_model.pth ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:c1a1f9eb14f2604bd2493cd2130b198a8d6ee4d032154bc9a9922eb94950f930
3
+ size 9970018
config.py ADDED
@@ -0,0 +1,204 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ ===============================================================================
3
+ config.py — Central Configuration for the Machine Sound Classification Pipeline
4
+ ===============================================================================
5
+
6
+ PURPOSE:
7
+ This file is the SINGLE SOURCE OF TRUTH for every hyperparameter,
8
+ file path, and constant used across the project. Every team member
9
+ should import values from here instead of hard-coding numbers.
10
+
11
+ If you need to change a value (e.g. sample rate, n_mels, batch size),
12
+ change it HERE and it propagates everywhere automatically.
13
+
14
+ OWNER: Shared (everyone imports from here)
15
+ ===============================================================================
16
+ """
17
+
18
+ import os
19
+
20
+ # =============================================================================
21
+ # 1. PATH CONFIGURATION
22
+ # =============================================================================
23
+ # Root directory of the project (where this file lives)
24
+ PROJECT_ROOT = os.path.dirname(os.path.abspath(__file__))
25
+
26
+ # Directory where the examiner places test .wav files
27
+ # CRITICAL: infer.py reads from this exact folder
28
+ DATA_DIR = os.path.join(PROJECT_ROOT, "data")
29
+
30
+ # Directory for the training dataset (organized by class)
31
+ TRAIN_DATA_DIR = os.path.join(PROJECT_ROOT, "train_data")
32
+
33
+ PROCESSED_DATA_DIR = os.path.join(PROJECT_ROOT, "processed_features")
34
+
35
+ # Directory where trained model checkpoints are saved
36
+ CHECKPOINT_DIR = os.path.join(PROJECT_ROOT, "checkpoints")
37
+
38
+ # Output files required by the submission
39
+ RESULTS_FILE = os.path.join(PROJECT_ROOT, "results.txt")
40
+ TIME_FILE = os.path.join(PROJECT_ROOT, "time.txt")
41
+
42
+ # Directory for split metadata (train/val/test file lists)
43
+ SPLITS_DIR = os.path.join(PROJECT_ROOT, "splits")
44
+
45
+
46
+ # =============================================================================
47
+ # 2. AUDIO PREPROCESSING CONSTANTS
48
+ # =============================================================================
49
+ # Target sampling rate — all audio is resampled to this before processing.
50
+ # 16 kHz is standard for machine sound analysis; Nyquist limit = 8 kHz.
51
+ # Owner: JSON (resampling.py)
52
+ TARGET_SR = 16000
53
+
54
+ # Silence removal threshold in dB.
55
+ # Frames quieter than this (relative to peak) are considered silence.
56
+ # 20 dB is a safe starting point for factory recordings.
57
+ # Owner: EL sir (silence_removal.py)
58
+ SILENCE_TOP_DB = 40
59
+
60
+ # Noise reduction — duration (in seconds) of the noise profile sample.
61
+ # We estimate the noise floor from the first N seconds of each clip.
62
+ # Owner: EL sir (noise_reduction.py)
63
+ NOISE_PROFILE_DURATION = 0.5 # seconds
64
+
65
+
66
+ # =============================================================================
67
+ # 3. MEL SPECTROGRAM PARAMETERS
68
+ # =============================================================================
69
+ # These MUST be agreed upon by sala7 (feature extraction) and EL sir (CNN input).
70
+ # Changing n_mels here changes the CNN input height automatically.
71
+ # Owner: sala7 (mel_spectrogram.py) — coordinated with EL sir
72
+
73
+ # Number of mel filter banks (height of the spectrogram "image")
74
+ N_MELS = 128
75
+
76
+ # FFT window size — 1024 samples @ 16 kHz = 64 ms analysis window.
77
+ # For machine fault detection, 64 ms captures one full rotation of many motors.
78
+ N_FFT = 1024
79
+
80
+ # Hop length — 512 samples = 50% overlap between consecutive frames.
81
+ # Good time resolution without quadrupling computation.
82
+ HOP_LENGTH = 512
83
+
84
+ # Maximum frequency for the mel filterbank.
85
+ # At 16 kHz sampling rate, Nyquist = 8 kHz, so fmax = 8000.
86
+ FMAX = 8000
87
+
88
+ # Power for the mel spectrogram (2.0 = power spectrogram)
89
+ MEL_POWER = 2.0
90
+
91
+
92
+ # =============================================================================
93
+ # 4. FIXED-SIZE TENSOR PARAMETERS
94
+ # =============================================================================
95
+ # After silence removal, audio clips vary in length. We must pad/trim
96
+ # spectrograms to a uniform time dimension for batching.
97
+ #
98
+ # CALCULATION:
99
+ # Original audio ≈ 11 seconds → 11 * 16000 = 176,000 samples
100
+ # Time frames = ceil(176000 / 512) = 344 frames (full 11s)
101
+ # After silence trimming, clips are shorter → 256 frames ≈ 8.2 seconds
102
+ # is a reasonable target that captures the machine sound while
103
+ # discarding silence.
104
+ #
105
+
106
+ # Final input shape to the CNN: (batch, channels, n_mels, time_frames)
107
+ # channels = 1 (grayscale spectrogram)
108
+ CNN_INPUT_CHANNELS = 1
109
+
110
+
111
+ # =============================================================================
112
+ # 5. DATA SPLIT RATIOS
113
+ # =============================================================================
114
+ # Stratified split ratios — every class appears in every split at the same proportion.
115
+ # Owner: JSON (splits.py)
116
+ TRAIN_RATIO = 0.70
117
+ VAL_RATIO = 0.15
118
+ TEST_RATIO = 0.15
119
+
120
+ # Random seed for reproducibility across all random operations
121
+ RANDOM_SEED = 42
122
+
123
+
124
+ # =============================================================================
125
+ # 6. MODEL ARCHITECTURE PARAMETERS
126
+ # =============================================================================
127
+ # Number of output classes:
128
+ # 0 = Machine 1 Normal, 1 = Machine 1 Abnormal,
129
+ # 2 = Machine 2 Normal, 3 = Machine 2 Abnormal,
130
+ # 4 = Machine 3 Normal, 5 = Machine 3 Abnormal
131
+ # Owner: EL sir (cnn.py)
132
+ NUM_CLASSES = 6
133
+
134
+ # Convolutional layer filter counts (depth progression)
135
+ # Each successive layer doubles the filters to capture more complex patterns.
136
+ CNN_FILTERS = [32, 64, 128, 256]
137
+
138
+ # Kernel size for all Conv2D layers
139
+ CNN_KERNEL_SIZE = 3
140
+
141
+ # Padding for Conv2D layers (1 = 'same' padding with kernel_size=3)
142
+ CNN_PADDING = 1
143
+
144
+ # Pool size for MaxPool2d layers
145
+ CNN_POOL_SIZE = 2
146
+
147
+ # Output size of AdaptiveAvgPool2d before the classifier head
148
+ # This makes the model accept any time-length input gracefully.
149
+ ADAPTIVE_POOL_OUTPUT = (4, 4)
150
+
151
+
152
+ # =============================================================================
153
+ # 7. TRAINING HYPERPARAMETERS
154
+ # =============================================================================
155
+ # Owner: Osama (trainer.py)
156
+
157
+ # Optimizer: AdamW (corrects weight decay application vs vanilla Adam)
158
+ LEARNING_RATE = 1e-3
159
+ WEIGHT_DECAY = 1e-4
160
+
161
+ # Batch size for training DataLoader
162
+ BATCH_SIZE = 64
163
+
164
+ # Maximum number of training epochs
165
+ MAX_EPOCHS = 100
166
+
167
+ # Early stopping — stop if val loss doesn't improve for this many epochs
168
+ EARLY_STOPPING_PATIENCE = 10
169
+
170
+ # Learning rate scheduler — cosine annealing
171
+ LR_SCHEDULER_T_MAX = MAX_EPOCHS # period of the cosine cycle
172
+
173
+ # Number of DataLoader workers for parallel data loading
174
+ NUM_WORKERS = 8
175
+
176
+
177
+ # =============================================================================
178
+ # 8. AUGMENTATION PARAMETERS
179
+ # =============================================================================
180
+ # Owner: sala7 (augmentation.py)
181
+ # These are applied ONLY during training (not val/test).
182
+
183
+ # SpecAugment: number of frequency bands to mask
184
+ FREQ_MASK_PARAM = 20
185
+
186
+ # SpecAugment: number of time steps to mask
187
+ TIME_MASK_PARAM = 30
188
+
189
+ # Gaussian noise injection — standard deviation
190
+ NOISE_STD = 0.005
191
+
192
+ # Probability of applying each augmentation
193
+ AUGMENT_PROB = 0.5
194
+
195
+
196
+ # =============================================================================
197
+ # 9. INFERENCE PARAMETERS
198
+ # =============================================================================
199
+ # Path to the best saved model checkpoint (used by infer.py)
200
+ BEST_MODEL_PATH = os.path.join(CHECKPOINT_DIR, "best_model.pth")
201
+
202
+ # Device selection for inference (auto-detect GPU)
203
+ import torch
204
+ DEVICE = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model.py ADDED
@@ -0,0 +1,128 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ ===============================================================================
3
+ model/cnn.py — CNN Architecture for Machine Sound Classification
4
+ ===============================================================================
5
+
6
+ OWNER: EL sir
7
+ """
8
+
9
+ import torch
10
+ import torch.nn as nn
11
+ from config import (
12
+ CNN_INPUT_CHANNELS,
13
+ NUM_CLASSES,
14
+ CNN_FILTERS,
15
+ CNN_KERNEL_SIZE,
16
+ CNN_PADDING,
17
+ CNN_POOL_SIZE,
18
+ ADAPTIVE_POOL_OUTPUT,
19
+ )
20
+
21
+ class ConvBlock(nn.Module):
22
+ """
23
+ A single convolutional block: Conv2d → BatchNorm → ReLU → MaxPool2d.
24
+ """
25
+ def __init__(self, in_channels, out_channels):
26
+ super(ConvBlock, self).__init__()
27
+ self.conv = nn.Conv2d(
28
+ in_channels, out_channels,
29
+ kernel_size=CNN_KERNEL_SIZE,
30
+ padding=CNN_PADDING,
31
+ bias=False # Bias is redundant when followed by BatchNorm
32
+ )
33
+ self.bn = nn.BatchNorm2d(out_channels)
34
+ self.relu = nn.ReLU(inplace=True)
35
+ self.pool = nn.MaxPool2d(CNN_POOL_SIZE)
36
+
37
+ def forward(self, x):
38
+ x = self.conv(x)
39
+ x = self.bn(x)
40
+ x = self.relu(x)
41
+ x = self.pool(x)
42
+ return x
43
+
44
+
45
+ class MachineSoundCNN(nn.Module):
46
+ """
47
+ Custom CNN for 6-class machine sound classification from mel spectrograms.
48
+ """
49
+ def __init__(self, num_classes=NUM_CLASSES):
50
+ super(MachineSoundCNN, self).__init__()
51
+
52
+ # ---------------------------------------------------------------------
53
+ # 1. Feature Extractor (Dynamic Conv Blocks)
54
+ # ---------------------------------------------------------------------
55
+ layers = []
56
+ in_channels = CNN_INPUT_CHANNELS
57
+
58
+ # Dynamically build blocks based on config.py (e.g., 32 -> 64 -> 128 -> 256)
59
+ for out_channels in CNN_FILTERS:
60
+ layers.append(ConvBlock(in_channels, out_channels))
61
+ in_channels = out_channels # The output of this layer is the input to the next
62
+
63
+ self.features = nn.Sequential(*layers)
64
+
65
+ # ---------------------------------------------------------------------
66
+ # 2. Adaptive Pooling
67
+ # ---------------------------------------------------------------------
68
+ # This squashes whatever time dimension is left into a fixed (4, 4) grid
69
+ self.adaptive_pool = nn.AdaptiveAvgPool2d(ADAPTIVE_POOL_OUTPUT)
70
+
71
+ # ---------------------------------------------------------------------
72
+ # 3. Classifier Head (Fully Connected)
73
+ # ---------------------------------------------------------------------
74
+ # Calculate flattened size: Last filter size (256) * height (4) * width (4) = 4096
75
+ flattened_size = CNN_FILTERS[-1] * ADAPTIVE_POOL_OUTPUT[0] * ADAPTIVE_POOL_OUTPUT[1]
76
+
77
+ self.classifier = nn.Sequential(
78
+ nn.Flatten(),
79
+ nn.Dropout(p=0.5), # Regularization: Prevent overfitting
80
+ nn.Linear(flattened_size, 512), # Hidden layer to compress features
81
+ nn.ReLU(inplace=True),
82
+ nn.Dropout(p=0.5), # Regularization
83
+ nn.Linear(512, num_classes) # Output raw logits (6 classes)
84
+ )
85
+
86
+ def forward(self, x):
87
+ # Extract visual features from the spectrogram
88
+ x = self.features(x)
89
+ # Pool them to a fixed mathematical size
90
+ x = self.adaptive_pool(x)
91
+ # Make the final classification
92
+ x = self.classifier(x)
93
+ return x
94
+
95
+
96
+ # =============================================================================
97
+ # SHAPE SANITY CHECK
98
+ # =============================================================================
99
+ if __name__ == "__main__":
100
+ print("=" * 60)
101
+ print("CNN Architecture Shape Sanity Check")
102
+ print("=" * 60)
103
+
104
+ model = MachineSoundCNN()
105
+
106
+ # Create a dummy batch based on JSON's exact tensor output (batch=4, ch=1, mels=128, time=281)
107
+ dummy_input = torch.randn(4, 1, 128, 281)
108
+ print(f"\nInput shape: {dummy_input.shape}")
109
+
110
+ # Trace through the architecture
111
+ x = dummy_input
112
+ for i, block in enumerate(model.features):
113
+ x = block(x)
114
+ print(f"After Conv Block {i+1} ({CNN_FILTERS[i]} filters): {x.shape}")
115
+
116
+ x = model.adaptive_pool(x)
117
+ print(f"After AdaptivePool: {x.shape}")
118
+
119
+ x = model.classifier(x)
120
+ print(f"After Classifier: {x.shape}")
121
+
122
+ print(f"\n✓ Output shape is correct: {x.shape} (batch=4, classes={NUM_CLASSES})")
123
+
124
+ # Count parameters
125
+ total_params = sum(p.numel() for p in model.parameters())
126
+ trainable_params = sum(p.numel() for p in model.parameters() if p.requires_grad)
127
+ print(f"\nTotal parameters: {total_params:,}")
128
+ print("=" * 60)
requirements.txt ADDED
@@ -0,0 +1,33 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # =============================================================================
2
+ # requirements.txt
3
+ # =============================================================================
4
+
5
+ gradio==3.36.0
6
+
7
+ # Core deep learning
8
+ torch==2.9.1
9
+ torchaudio==2.9.1
10
+ torchvision==0.24.1
11
+
12
+ # Audio processing
13
+ librosa==0.10.1
14
+ soundfile==0.12.1
15
+ resampy==0.4.3 #added this for kaiser window in resampling
16
+
17
+ # Noise reduction
18
+ noisereduce==3.0.2
19
+
20
+ # Numerical / scientific
21
+ numpy==1.26.4
22
+ scipy==1.12.0
23
+
24
+ # Data handling
25
+ scikit-learn==1.4.1.post1
26
+ pandas==2.2.1
27
+
28
+ # Visualization
29
+ matplotlib==3.8.3
30
+ seaborn==0.13.2
31
+
32
+ # Progress bars
33
+ tqdm==4.66.2