Heewon Oh commited on
Commit
912c002
·
1 Parent(s): 742e266

refactor: remove LGBM dependency and rollback to 3-tier verdict

Browse files

- Remove LGBM model loading, feature extraction, codec-aware classification
- Simplify classify() to pure 3-tier distribution-based verdict
- Delete core/codec_aware.py (no longer needed)
- Remove lightgbm from requirements.txt

app.py CHANGED
@@ -373,12 +373,12 @@ def analyze_audio(audio_path: str):
373
  # 2. E2E inference (ONNX — GPU if available, else CPU)
374
  chunk_probs, _ = _run_e2e(mono_tensor)
375
 
376
- # 3. Distribution-based verdict (LGBM 2nd-stage)
377
  seg_stats = compute_stats(chunk_probs)
378
  elapsed = time.time() - t0
379
 
380
  # 4. Generate visualizations
381
- verdict = classify(seg_stats, seg_probs=chunk_probs, audio_path=audio_path)
382
  verdict_html = VerdictCardBuilder.build(
383
  verdict, seg_stats, is_stereo,
384
  duration=info["duration"], elapsed=elapsed,
 
373
  # 2. E2E inference (ONNX — GPU if available, else CPU)
374
  chunk_probs, _ = _run_e2e(mono_tensor)
375
 
376
+ # 3. Distribution-based verdict (3-tier)
377
  seg_stats = compute_stats(chunk_probs)
378
  elapsed = time.time() - t0
379
 
380
  # 4. Generate visualizations
381
+ verdict = classify(seg_stats)
382
  verdict_html = VerdictCardBuilder.build(
383
  verdict, seg_stats, is_stereo,
384
  duration=info["duration"], elapsed=elapsed,
core/__pycache__/proprietary.cpython-312.pyc CHANGED
Binary files a/core/__pycache__/proprietary.cpython-312.pyc and b/core/__pycache__/proprietary.cpython-312.pyc differ
 
core/codec_aware.py DELETED
@@ -1,32 +0,0 @@
1
- """Codec-aware classification module."""
2
-
3
- from pathlib import Path
4
- import numpy as np
5
-
6
-
7
- def detect_codec(audio_path):
8
- """Detect if audio is lossless or lossy."""
9
- if audio_path is None:
10
- return 'unknown'
11
-
12
- if isinstance(audio_path, str):
13
- audio_path = Path(audio_path)
14
-
15
- ext = audio_path.suffix.lower()
16
-
17
- if ext in {'.wav', '.flac', '.aiff', '.aif'}:
18
- return 'lossless'
19
- elif ext in {'.mp3', '.aac', '.m4a', '.ogg', '.opus', '.wma'}:
20
- return 'lossy'
21
- else:
22
- return 'unknown'
23
-
24
-
25
- def get_codec_thresholds(codec_mode):
26
- """Get thresholds based on codec mode."""
27
- if codec_mode == 'lossless':
28
- return {'ai': 0.5, 'real': 0.5, 'name': 'Lossless (High Sensitivity)'}
29
- elif codec_mode == 'lossy':
30
- return {'ai': 0.8, 'real': 0.3, 'name': 'Lossy (Conservative)'}
31
- else:
32
- return {'ai': 0.7, 'real': 0.4, 'name': 'Unknown (Moderate)'}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
core/proprietary.py CHANGED
@@ -7,10 +7,7 @@
7
 
8
  import base64
9
  import json
10
- import os
11
- from pathlib import Path
12
  import numpy as np
13
- from scipy import stats as sp_stats
14
 
15
  # Encrypted parameters (XOR + Base64) - DO NOT MODIFY
16
  _ENC_P = 'AR3tX367a8ZODq4dcKFpkRJK8EYD8i6RWAW+GXKxZ9JYUcFLOvVpyFoNrhlkrWvQElDuD2ahfsNIE74PMt4mlxZMvBd8sHnKVh+8Tz31KJpYBb4VcKFpnxtHwUkp82nIWgyuHSE='
@@ -25,9 +22,6 @@ _K = _K1 + _K2 + _K3
25
  # Decryption cache (computed once)
26
  _cache = {}
27
 
28
- # LGBM model cache
29
- _lgbm_model = None
30
-
31
  # Obfuscated constants (decoys)
32
  _MAGIC_A = 0x1F3D5A7B
33
  _MAGIC_B = 0x9C8E2F41
@@ -133,135 +127,11 @@ def compute_stats(chunk_probs: list[float]) -> dict:
133
  }
134
 
135
 
136
- def _load_lgbm_model():
137
- """Load LGBM verdict model (lazy loading)."""
138
- global _lgbm_model
139
- if _lgbm_model is not None:
140
- return _lgbm_model
141
-
142
- import lightgbm as lgb
143
- from huggingface_hub import hf_hub_download
144
-
145
- # Try local path first
146
- local_model = Path(__file__).resolve().parent.parent / "models" / "lgbm_verdict.txt"
147
-
148
- if local_model.exists():
149
- model_path = str(local_model)
150
- else:
151
- # Download from HF Hub
152
- model_path = hf_hub_download("intrect/artifactnet-models", "lgbm_verdict.txt")
153
-
154
- _lgbm_model = lgb.Booster(model_file=model_path)
155
- return _lgbm_model
156
-
157
-
158
- def _extract_lgbm_features(seg_probs: list[float]) -> np.ndarray:
159
- """Extract LGBM features from segment probabilities (v8 2nd-stage)."""
160
- arr = np.array(seg_probs, dtype=np.float64)
161
- n = len(arr)
162
-
163
- if n == 0:
164
- return None
165
-
166
- # Distribution statistics
167
- features = [
168
- n, # n_segments
169
- arr.mean(), # mean
170
- arr.std(), # std
171
- np.median(arr), # median
172
- arr.min(), # min
173
- arr.max(), # max
174
- arr.max() - arr.min(), # range
175
- np.percentile(arr, 10), # p10
176
- np.percentile(arr, 25), # p25
177
- np.percentile(arr, 75), # p75
178
- np.percentile(arr, 90), # p90
179
- (arr >= 0.3).mean(), # r_03
180
- (arr >= 0.5).mean(), # r_05
181
- (arr >= 0.7).mean(), # r_07
182
- (arr >= 0.8).mean(), # r_08
183
- (arr >= 0.9).mean(), # r_09
184
- float(sp_stats.skew(arr)) if n >= 3 else 0.0, # skew
185
- float(sp_stats.kurtosis(arr)) if n >= 3 else 0.0, # kurtosis
186
- ]
187
-
188
- # Temporal features
189
- if n >= 2:
190
- diffs = np.diff(arr)
191
- features.append(diffs.std()) # temporal_std
192
- features.append(np.abs(diffs).max()) # temporal_max_jump
193
- else:
194
- features.extend([0.0, 0.0])
195
-
196
- return np.array(features, dtype=np.float32).reshape(1, -1)
197
-
198
-
199
- def classify(stats: dict, seg_probs: list[float] = None, audio_path: str = None) -> str:
200
- """LGBM 2nd-stage track-level verdict with codec-aware thresholds (v8.1).
201
-
202
- Codec-aware dual mode:
203
- - Lossless (WAV/FLAC): threshold=0.5 (high sensitivity)
204
- - Lossy (MP3/YouTube): threshold=0.8 (conservative, returns Uncertain for edge cases)
205
 
206
- Fallback to 3-Tier if LGBM fails.
207
  """
208
- # Detect codec mode
209
- from .codec_aware import detect_codec, get_codec_thresholds
210
- codec_mode = detect_codec(audio_path)
211
- thresholds = get_codec_thresholds(codec_mode)
212
-
213
- # 3-Tier quick check (strong signals, codec-independent)
214
- if seg_probs is not None:
215
- arr = np.array(seg_probs)
216
- high_ratio = (arr >= 0.8).mean()
217
- low_ratio = (arr < 0.5).mean()
218
-
219
- if high_ratio >= 0.75:
220
- return "AI Generated" # Strong AI signal
221
- elif low_ratio >= 0.85:
222
- return "Human-Made" # Strong Real signal
223
-
224
- # Try LGBM for uncertain zone
225
- if seg_probs is not None:
226
- try:
227
- model = _load_lgbm_model()
228
- features = _extract_lgbm_features(seg_probs)
229
-
230
- if features is not None:
231
- pred_proba = model.predict(features)[0]
232
-
233
- # Codec-aware thresholds
234
- if codec_mode == 'lossless':
235
- # High sensitivity (trained on WAV)
236
- if pred_proba >= thresholds['ai']:
237
- return "AI Generated"
238
- else:
239
- return "Human-Made"
240
-
241
- elif codec_mode == 'lossy':
242
- # Conservative (lossy artifacts can mimic AI)
243
- if pred_proba >= thresholds['ai']:
244
- return "AI Generated"
245
- elif pred_proba <= thresholds['real']:
246
- return "Human-Made"
247
- else:
248
- return "Uncertain" # 0.3~0.8 range
249
-
250
- else:
251
- # Unknown codec → moderate
252
- if pred_proba >= thresholds['ai']:
253
- return "AI Generated"
254
- elif pred_proba <= thresholds['real']:
255
- return "Human-Made"
256
- else:
257
- return "Uncertain"
258
-
259
- except Exception as e:
260
- # Fallback to 3-Tier on error
261
- print(f"LGBM error (fallback to 3-Tier): {e}")
262
- pass
263
-
264
- # Fallback: 3-Tier rule (legacy)
265
  t = _d(_ENC_T, _K)
266
  ph = stats["pct_high"]
267
  pa = stats["pct_above_50"]
 
7
 
8
  import base64
9
  import json
 
 
10
  import numpy as np
 
11
 
12
  # Encrypted parameters (XOR + Base64) - DO NOT MODIFY
13
  _ENC_P = 'AR3tX367a8ZODq4dcKFpkRJK8EYD8i6RWAW+GXKxZ9JYUcFLOvVpyFoNrhlkrWvQElDuD2ahfsNIE74PMt4mlxZMvBd8sHnKVh+8Tz31KJpYBb4VcKFpnxtHwUkp82nIWgyuHSE='
 
22
  # Decryption cache (computed once)
23
  _cache = {}
24
 
 
 
 
25
  # Obfuscated constants (decoys)
26
  _MAGIC_A = 0x1F3D5A7B
27
  _MAGIC_B = 0x9C8E2F41
 
127
  }
128
 
129
 
130
+ def classify(stats: dict) -> str:
131
+ """3-Tier distribution-based verdict (v8.0).
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
132
 
133
+ Encrypted threshold-based classification using segment distribution statistics.
134
  """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
135
  t = _d(_ENC_T, _K)
136
  ph = stats["pct_high"]
137
  pa = stats["pct_above_50"]
requirements.txt CHANGED
@@ -7,7 +7,7 @@ huggingface_hub>=0.20.0
7
  onnxruntime>=1.17.0
8
  torch>=2.0.0
9
  requests>=2.31.0
10
- lightgbm>=4.0.0
11
  gradio>=5.20.0
12
  fastapi>=0.104.0
13
  uvicorn>=0.24.0
 
7
  onnxruntime>=1.17.0
8
  torch>=2.0.0
9
  requests>=2.31.0
10
+
11
  gradio>=5.20.0
12
  fastapi>=0.104.0
13
  uvicorn>=0.24.0