config.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480
  1. """
  2. Configuration management for Voice Recognition system.
  3. This module provides comprehensive configuration classes for the voice recognition
  4. system, including model configuration, audio processing settings, recognition
  5. parameters, and performance optimization options.
  6. """
  7. import os
  8. import json
  9. import logging
  10. from typing import Dict, List, Optional, Union, Any, Tuple
  11. from dataclasses import dataclass, field, asdict
  12. from pathlib import Path
  13. from enum import Enum
  14. import torch
  15. # Configure logging
  16. logger = logging.getLogger(__name__)
  17. class ModelArchitectureType(Enum):
  18. """Supported model architecture types."""
  19. ECAPA_TDNN = "ecapa_tdnn"
  20. TITANET_S = "titanet_s"
  21. SPEAKERNET_M = "speakernet_m"
  22. class LossType(Enum):
  23. """Supported loss function types."""
  24. ANGULAR_MARGIN = "angular_margin" # ArcFace
  25. GE2E = "ge2e"
  26. TRIPLET = "triplet"
  27. CONTRASTIVE = "contrastive"
  28. class FeatureType(Enum):
  29. """Supported audio feature types."""
  30. LOG_MEL = "log_mel"
  31. MFCC = "mfcc"
  32. class VADType(Enum):
  33. """Supported Voice Activity Detection types."""
  34. WEBRTC = "webrtc"
  35. ENERGY_BASED = "energy_based"
  36. COMBINED = "combined"
  37. @dataclass
  38. class ModelConfig:
  39. """Configuration for voice recognition model."""
  40. model_path: str
  41. model_password: Optional[str] = None
  42. architecture_type: ModelArchitectureType = ModelArchitectureType.ECAPA_TDNN
  43. embedding_dim: int = 192
  44. num_speakers: int = 4
  45. speaker_mapping: Dict[str, str] = field(default_factory=dict)
  46. # Architecture-specific parameters
  47. ecapa_channels: int = 512
  48. use_attention_pooling: bool = True
  49. titanet_channels: List[int] = field(default_factory=lambda: [128, 256, 512, 1024])
  50. speakernet_hidden_dim: int = 512
  51. speakernet_num_layers: int = 4
  52. dropout_rate: float = 0.1
  53. use_batch_norm: bool = True
  54. def __post_init__(self):
  55. """Validate configuration after initialization."""
  56. if isinstance(self.architecture_type, str):
  57. self.architecture_type = ModelArchitectureType(self.architecture_type)
  58. if self.num_speakers < 2:
  59. raise ValueError("Number of speakers must be at least 2")
  60. if self.num_speakers > 200:
  61. logger.warning(f"Large number of speakers ({self.num_speakers}) may impact performance")
  62. if self.embedding_dim < 64:
  63. raise ValueError("Embedding dimension must be at least 64")
  64. @dataclass
  65. class AudioConfig:
  66. """Configuration for audio processing."""
  67. sample_rate: int = 16000
  68. audio_length: float = 3.0 # seconds
  69. feature_type: FeatureType = FeatureType.LOG_MEL
  70. # Log-Mel Spectrogram parameters
  71. n_mels: int = 40
  72. n_fft: int = 512
  73. hop_length: int = 160 # 10ms at 16kHz
  74. win_length: int = 400 # 25ms at 16kHz
  75. f_min: float = 80.0
  76. f_max: Optional[float] = None
  77. # MFCC parameters
  78. n_mfcc: int = 40
  79. dct_type: int = 2
  80. norm: str = "ortho"
  81. log_mels: bool = False
  82. # Voice Activity Detection
  83. use_vad: bool = True
  84. vad_type: VADType = VADType.WEBRTC
  85. vad_aggressiveness: int = 2 # 0-3 for WebRTC VAD
  86. vad_frame_duration: int = 30 # ms
  87. energy_threshold: float = 0.01
  88. # Data augmentation
  89. use_augmentation: bool = True
  90. noise_factor: float = 0.05
  91. speed_factor: float = 0.05
  92. pitch_factor: float = 0.02
  93. volume_factor: float = 0.1
  94. reverb_factor: float = 0.03
  95. def __post_init__(self):
  96. """Validate configuration after initialization."""
  97. if isinstance(self.feature_type, str):
  98. self.feature_type = FeatureType(self.feature_type)
  99. if isinstance(self.vad_type, str):
  100. self.vad_type = VADType(self.vad_type)
  101. if self.sample_rate not in [8000, 16000, 22050, 44100, 48000]:
  102. logger.warning(f"Unusual sample rate: {self.sample_rate}Hz")
  103. if self.audio_length < 0.5:
  104. raise ValueError("Audio length must be at least 0.5 seconds")
  105. if self.audio_length > 10.0:
  106. logger.warning(f"Long audio length ({self.audio_length}s) may impact performance")
  107. @dataclass
  108. class RecognitionConfig:
  109. """Configuration for voice recognition engine."""
  110. confidence_threshold: float = 0.7
  111. verification_threshold: float = 0.6
  112. identification_threshold: float = 0.5
  113. # Loss function configuration
  114. loss_type: LossType = LossType.ANGULAR_MARGIN
  115. angular_margin: float = 0.5
  116. angular_scale: float = 64.0
  117. ge2e_init_w: float = 10.0
  118. ge2e_init_b: float = -5.0
  119. contrastive_margin: float = 2.0
  120. triplet_margin: float = 0.3
  121. # Similarity calculation
  122. similarity_metric: str = "cosine" # "cosine", "euclidean", "dot_product"
  123. use_score_normalization: bool = True
  124. # Temporal filtering
  125. use_temporal_smoothing: bool = True
  126. smoothing_window_size: int = 5
  127. confidence_decay_factor: float = 0.9
  128. # Anti-spoofing
  129. use_anti_spoofing: bool = False
  130. spoofing_threshold: float = 0.5
  131. def __post_init__(self):
  132. """Validate configuration after initialization."""
  133. if isinstance(self.loss_type, str):
  134. self.loss_type = LossType(self.loss_type)
  135. if not 0.0 <= self.confidence_threshold <= 1.0:
  136. raise ValueError("Confidence threshold must be between 0.0 and 1.0")
  137. if not 0.0 <= self.verification_threshold <= 1.0:
  138. raise ValueError("Verification threshold must be between 0.0 and 1.0")
  139. if not 0.0 <= self.identification_threshold <= 1.0:
  140. raise ValueError("Identification threshold must be between 0.0 and 1.0")
  141. @dataclass
  142. class PerformanceConfig:
  143. """Configuration for performance optimization."""
  144. device: str = "auto" # "auto", "cpu", "cuda", "mps"
  145. batch_size: int = 1
  146. use_half_precision: bool = False
  147. use_torch_compile: bool = False
  148. # Memory management
  149. max_memory_usage_mb: int = 512
  150. clear_cache_interval: int = 100
  151. # Latency optimization
  152. target_latency_ms: float = 100.0
  153. use_streaming_inference: bool = True
  154. chunk_size_ms: float = 30.0
  155. # Threading
  156. num_workers: int = 2
  157. pin_memory: bool = True
  158. # Model optimization
  159. use_quantization: bool = False
  160. quantization_backend: str = "fbgemm" # "fbgemm", "qnnpack"
  161. use_tensorrt: bool = False
  162. def __post_init__(self):
  163. """Validate and set device configuration."""
  164. if self.device == "auto":
  165. if torch.cuda.is_available():
  166. self.device = "cuda"
  167. elif hasattr(torch.backends, 'mps') and torch.backends.mps.is_available():
  168. self.device = "mps"
  169. else:
  170. self.device = "cpu"
  171. if self.batch_size < 1:
  172. raise ValueError("Batch size must be at least 1")
  173. if self.batch_size > 32:
  174. logger.warning(f"Large batch size ({self.batch_size}) may cause memory issues")
  175. @dataclass
  176. class VoiceRecognitionConfig:
  177. """Main configuration class for voice recognition system."""
  178. model_config: ModelConfig
  179. audio_config: AudioConfig = field(default_factory=AudioConfig)
  180. recognition_config: RecognitionConfig = field(default_factory=RecognitionConfig)
  181. performance_config: PerformanceConfig = field(default_factory=PerformanceConfig)
  182. # System configuration
  183. debug_mode: bool = False
  184. log_level: str = "INFO"
  185. save_debug_audio: bool = False
  186. debug_audio_dir: str = "./debug/voice_recognition"
  187. def __post_init__(self):
  188. """Validate complete configuration."""
  189. # Ensure audio length matches expected input for model
  190. expected_samples = int(self.audio_config.audio_length * self.audio_config.sample_rate)
  191. if expected_samples < 8000: # Minimum for reliable recognition
  192. logger.warning(f"Short audio length may impact recognition accuracy")
  193. # Validate feature extraction parameters
  194. if self.audio_config.feature_type == FeatureType.LOG_MEL:
  195. if self.audio_config.n_mels != 40:
  196. logger.warning("Standard n_mels is 40 for voice recognition")
  197. elif self.audio_config.feature_type == FeatureType.MFCC:
  198. if self.audio_config.n_mfcc != 40:
  199. logger.warning("Standard n_mfcc is 40 for voice recognition")
  200. def to_dict(self) -> Dict[str, Any]:
  201. """Convert configuration to dictionary."""
  202. return asdict(self)
  203. @classmethod
  204. def from_dict(cls, config_dict: Dict[str, Any]) -> 'VoiceRecognitionConfig':
  205. """Create configuration from dictionary."""
  206. model_config = ModelConfig(**config_dict.get('model_config', {}))
  207. audio_config = AudioConfig(**config_dict.get('audio_config', {}))
  208. recognition_config = RecognitionConfig(**config_dict.get('recognition_config', {}))
  209. performance_config = PerformanceConfig(**config_dict.get('performance_config', {}))
  210. # Remove nested configs from main dict
  211. main_config = {k: v for k, v in config_dict.items()
  212. if k not in ['model_config', 'audio_config', 'recognition_config', 'performance_config']}
  213. return cls(
  214. model_config=model_config,
  215. audio_config=audio_config,
  216. recognition_config=recognition_config,
  217. performance_config=performance_config,
  218. **main_config
  219. )
  220. def save(self, file_path: str) -> None:
  221. """Save configuration to JSON file."""
  222. config_dict = self.to_dict()
  223. # Convert enums to strings for JSON serialization
  224. def convert_enums(obj):
  225. if isinstance(obj, dict):
  226. return {k: convert_enums(v) for k, v in obj.items()}
  227. elif isinstance(obj, list):
  228. return [convert_enums(item) for item in obj]
  229. elif isinstance(obj, Enum):
  230. return obj.value
  231. return obj
  232. config_dict = convert_enums(config_dict)
  233. Path(file_path).parent.mkdir(parents=True, exist_ok=True)
  234. with open(file_path, 'w') as f:
  235. json.dump(config_dict, f, indent=2)
  236. logger.info(f"Configuration saved to {file_path}")
  237. @classmethod
  238. def load(cls, file_path: str) -> 'VoiceRecognitionConfig':
  239. """Load configuration from JSON file."""
  240. if not os.path.exists(file_path):
  241. raise FileNotFoundError(f"Configuration file not found: {file_path}")
  242. with open(file_path, 'r') as f:
  243. config_dict = json.load(f)
  244. logger.info(f"Configuration loaded from {file_path}")
  245. return cls.from_dict(config_dict)
  246. def create_default_config(model_path: str, model_password: Optional[str] = None) -> VoiceRecognitionConfig:
  247. """Create default configuration for voice recognition."""
  248. model_config = ModelConfig(
  249. model_path=model_path,
  250. model_password=model_password,
  251. architecture_type=ModelArchitectureType.ECAPA_TDNN,
  252. embedding_dim=192
  253. )
  254. return VoiceRecognitionConfig(model_config=model_config)
  255. def create_titanet_config(model_path: str, model_password: Optional[str] = None) -> VoiceRecognitionConfig:
  256. """Create TitaNet-S optimized configuration."""
  257. model_config = ModelConfig(
  258. model_path=model_path,
  259. model_password=model_password,
  260. architecture_type=ModelArchitectureType.TITANET_S,
  261. embedding_dim=192,
  262. titanet_channels=[128, 256, 512, 1024],
  263. dropout_rate=0.05
  264. )
  265. audio_config = AudioConfig(
  266. audio_length=2.5,
  267. use_augmentation=True,
  268. noise_factor=0.03
  269. )
  270. recognition_config = RecognitionConfig(
  271. loss_type=LossType.GE2E,
  272. confidence_threshold=0.6,
  273. verification_threshold=0.6
  274. )
  275. performance_config = PerformanceConfig(
  276. batch_size=64,
  277. use_half_precision=True
  278. )
  279. return VoiceRecognitionConfig(
  280. model_config=model_config,
  281. audio_config=audio_config,
  282. recognition_config=recognition_config,
  283. performance_config=performance_config
  284. )
  285. def create_speakernet_config(model_path: str, model_password: Optional[str] = None) -> VoiceRecognitionConfig:
  286. """Create SpeakerNet-M optimized configuration."""
  287. model_config = ModelConfig(
  288. model_path=model_path,
  289. model_password=model_password,
  290. architecture_type=ModelArchitectureType.SPEAKERNET_M,
  291. embedding_dim=256,
  292. speakernet_hidden_dim=512,
  293. speakernet_num_layers=4,
  294. dropout_rate=0.1
  295. )
  296. audio_config = AudioConfig(
  297. feature_type=FeatureType.MFCC,
  298. n_mfcc=40,
  299. audio_length=3.0
  300. )
  301. recognition_config = RecognitionConfig(
  302. loss_type=LossType.TRIPLET,
  303. triplet_margin=0.3,
  304. confidence_threshold=0.65
  305. )
  306. return VoiceRecognitionConfig(
  307. model_config=model_config,
  308. audio_config=audio_config,
  309. recognition_config=recognition_config
  310. )
  311. def auto_tune_config(config: VoiceRecognitionConfig,
  312. target_latency_ms: float = 100.0,
  313. target_accuracy: float = 0.9) -> VoiceRecognitionConfig:
  314. """Automatically tune configuration for performance and accuracy targets."""
  315. # Adjust batch size based on target latency
  316. if target_latency_ms < 50:
  317. config.performance_config.batch_size = 1
  318. config.performance_config.use_half_precision = True
  319. config.audio_config.audio_length = min(config.audio_config.audio_length, 2.0)
  320. elif target_latency_ms < 100:
  321. config.performance_config.batch_size = min(config.performance_config.batch_size, 4)
  322. config.performance_config.use_half_precision = True
  323. # Adjust thresholds based on target accuracy
  324. if target_accuracy >= 0.95:
  325. config.recognition_config.confidence_threshold = max(0.8, config.recognition_config.confidence_threshold)
  326. config.recognition_config.verification_threshold = max(0.75, config.recognition_config.verification_threshold)
  327. config.audio_config.use_augmentation = True
  328. elif target_accuracy <= 0.8:
  329. config.recognition_config.confidence_threshold = min(0.6, config.recognition_config.confidence_threshold)
  330. config.recognition_config.verification_threshold = min(0.55, config.recognition_config.verification_threshold)
  331. config.performance_config.target_latency_ms = target_latency_ms
  332. logger.info(f"Configuration auto-tuned for {target_latency_ms}ms latency and {target_accuracy} accuracy")
  333. return config
  334. def validate_config_compatibility(config: VoiceRecognitionConfig) -> List[str]:
  335. """Validate configuration compatibility and return list of warnings/errors."""
  336. warnings = []
  337. # Check device compatibility
  338. if config.performance_config.device == "cuda" and not torch.cuda.is_available():
  339. warnings.append("CUDA device specified but not available, falling back to CPU")
  340. config.performance_config.device = "cpu"
  341. # Check memory requirements
  342. estimated_memory = estimate_memory_usage(config)
  343. if estimated_memory > config.performance_config.max_memory_usage_mb:
  344. warnings.append(f"Estimated memory usage ({estimated_memory}MB) exceeds limit ({config.performance_config.max_memory_usage_mb}MB)")
  345. # Check audio processing compatibility
  346. if config.audio_config.use_vad and config.audio_config.vad_type == VADType.WEBRTC:
  347. try:
  348. import webrtcvad
  349. except ImportError:
  350. warnings.append("WebRTC VAD specified but webrtcvad package not available")
  351. # Check feature extraction compatibility
  352. hop_length_ms = config.audio_config.hop_length / config.audio_config.sample_rate * 1000
  353. if hop_length_ms > 15:
  354. warnings.append(f"Large hop length ({hop_length_ms:.1f}ms) may reduce temporal resolution")
  355. return warnings
  356. def estimate_memory_usage(config: VoiceRecognitionConfig) -> int:
  357. """Estimate memory usage in MB for the given configuration."""
  358. # Audio buffer memory
  359. audio_samples = int(config.audio_config.audio_length * config.audio_config.sample_rate)
  360. audio_memory = audio_samples * 4 * config.performance_config.batch_size # float32
  361. # Feature memory
  362. if config.audio_config.feature_type == FeatureType.LOG_MEL:
  363. feature_frames = audio_samples // config.audio_config.hop_length
  364. feature_memory = feature_frames * config.audio_config.n_mels * 4 * config.performance_config.batch_size
  365. else: # MFCC
  366. feature_frames = audio_samples // config.audio_config.hop_length
  367. feature_memory = feature_frames * config.audio_config.n_mfcc * 4 * config.performance_config.batch_size
  368. # Model memory (rough estimate)
  369. model_memory = config.model_config.embedding_dim * config.model_config.num_speakers * 4
  370. if config.model_config.architecture_type == ModelArchitectureType.ECAPA_TDNN:
  371. model_memory += config.model_config.ecapa_channels * 1000 # Rough estimate
  372. elif config.model_config.architecture_type == ModelArchitectureType.TITANET_S:
  373. model_memory += sum(config.model_config.titanet_channels) * 500
  374. total_memory_bytes = audio_memory + feature_memory + model_memory
  375. return int(total_memory_bytes / (1024 * 1024)) # Convert to MB
  376. # Example configurations for testing
  377. def get_example_configs() -> Dict[str, VoiceRecognitionConfig]:
  378. """Get example configurations for different use cases."""
  379. return {
  380. "default": create_default_config("/path/to/model.pth"),
  381. "titanet": create_titanet_config("/path/to/titanet_model.pth"),
  382. "speakernet": create_speakernet_config("/path/to/speakernet_model.pth"),
  383. "lightweight": auto_tune_config(create_default_config("/path/to/model.pth"), target_latency_ms=50),
  384. "high_accuracy": auto_tune_config(create_default_config("/path/to/model.pth"), target_accuracy=0.95)
  385. }