application.py 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. # -*- coding: utf-8 -*-
  2. """
  3. Trainer-Anwendung fuer Trixy ML-Modelle.
  4. Trainiert Wakeword- und Sprechererkennungsmodelle.
  5. (Urspruenglich trixy_core/trainer.py, jetzt Teil des Trainer-Packages)
  6. """
  7. from enum import Enum, auto
  8. from pathlib import Path
  9. from trixy_core.application import IApplication
  10. from trixy_core.config.datasets.trainer import TrainerConfig
  11. from trixy_core.utils.debug import pinfo, pdebug, perror, pwarn
  12. class TrainingTarget(Enum):
  13. """Zu trainierende Modelltypen."""
  14. WAKEWORD = auto()
  15. VOICE_RECOGNITION = auto()
  16. INTENT = auto()
  17. ALL = auto()
  18. class TrainerApplication(IApplication):
  19. """
  20. Trainer-Anwendung fuer ML-Modelle.
  21. Unterstuetzt das Training von:
  22. - Wakeword-Modellen
  23. - Sprechererkennungsmodellen
  24. """
  25. def __init__(
  26. self,
  27. target: TrainingTarget = TrainingTarget.ALL,
  28. config_path: str | Path = "config/trainer_config.json",
  29. debug: bool = False
  30. ) -> None:
  31. super().__init__(debug)
  32. self._target = target
  33. self._config_path = Path(config_path)
  34. self._trainer_config: TrainerConfig | None = None
  35. @property
  36. def trainer_config(self) -> TrainerConfig:
  37. """Trainer-Konfiguration."""
  38. if self._trainer_config is None:
  39. raise RuntimeError("Trainer nicht initialisiert")
  40. return self._trainer_config
  41. @property
  42. def target(self) -> TrainingTarget:
  43. """Trainings-Ziel."""
  44. return self._target
  45. async def initialize(self) -> None:
  46. """Initialisiert den Trainer."""
  47. pinfo("Initialisiere Trainer...")
  48. self._trainer_config = self.config_manager.load(
  49. self._config_path,
  50. TrainerConfig,
  51. name="trainer"
  52. )
  53. data_dir = Path(self._trainer_config.data.raw_data_directory)
  54. processed_dir = Path(self._trainer_config.data.processed_data_directory)
  55. cache_dir = Path(self._trainer_config.data.cache_directory)
  56. output_dir = Path(self._trainer_config.output.models_directory)
  57. for directory in [data_dir, processed_dir, cache_dir, output_dir]:
  58. directory.mkdir(parents=True, exist_ok=True)
  59. pdebug(f"Trainer initialisiert fuer: {self._target.name}")
  60. async def start(self) -> None:
  61. """Startet das Training."""
  62. pinfo(f"Starte Training: {self._target.name}")
  63. if self._target in (TrainingTarget.WAKEWORD, TrainingTarget.ALL):
  64. await self._train_wakeword()
  65. if self._target in (TrainingTarget.VOICE_RECOGNITION, TrainingTarget.ALL):
  66. await self._train_voice_recognition()
  67. if self._target in (TrainingTarget.INTENT, TrainingTarget.ALL):
  68. await self._train_intent()
  69. pinfo("Training abgeschlossen")
  70. self.shutdown()
  71. async def stop(self) -> None:
  72. """Stoppt den Trainer."""
  73. pinfo("Trainer beendet")
  74. self.config_manager.stop_watching()
  75. async def _train_wakeword(self) -> None:
  76. """Trainiert Wakeword-Modelle."""
  77. pinfo("Trainiere Wakeword-Modell...")
  78. config = self._trainer_config.wakeword
  79. pdebug(f"Modell: {config.model_name}")
  80. pdebug(f"Epochen: {config.epochs}")
  81. pdebug(f"Batch-Groesse: {config.batch_size}")
  82. data_path = Path(self._trainer_config.data.raw_data_directory) / "wakeword"
  83. if not data_path.exists():
  84. pwarn(f"Keine Trainingsdaten gefunden: {data_path}")
  85. return
  86. samples = list(data_path.rglob("*.wav"))
  87. pinfo(f"Gefundene Samples: {len(samples)}")
  88. if len(samples) < self._trainer_config.data.min_samples_per_class:
  89. pwarn("Zu wenige Trainingsdaten")
  90. return
  91. pinfo("Wakeword-Training abgeschlossen")
  92. async def _train_voice_recognition(self) -> None:
  93. """Trainiert Sprechererkennungsmodelle."""
  94. pinfo("Trainiere Sprechererkennungs-Modell...")
  95. config = self._trainer_config.voice_recognition
  96. pdebug(f"Modell: {config.model_name}")
  97. pdebug(f"Epochen: {config.epochs}")
  98. pdebug(f"Embedding-Groesse: {config.embedding_size}")
  99. data_path = Path(self._trainer_config.data.raw_data_directory) / "voice_recognition"
  100. if not data_path.exists():
  101. pwarn(f"Keine Trainingsdaten gefunden: {data_path}")
  102. return
  103. speakers = [d for d in data_path.iterdir() if d.is_dir()]
  104. pinfo(f"Gefundene Sprecher: {len(speakers)}")
  105. for speaker_dir in speakers:
  106. samples = list(speaker_dir.glob("*.wav"))
  107. pdebug(f" {speaker_dir.name}: {len(samples)} Samples")
  108. pinfo("Sprechererkennungs-Training abgeschlossen")
  109. async def _train_intent(self) -> None:
  110. """Trainiert den Intent-Classifier."""
  111. pinfo("Trainiere Intent-Classifier...")
  112. try:
  113. from multiprocessing import Queue as MPQueue, Event as MPEvent
  114. from trixy_core.trainer.core.intent.trainer import IntentTrainer
  115. from trixy_core.trainer.dataclasses import TrainingProgress
  116. trainer = IntentTrainer()
  117. # Settings und Dataset ueber get_settings_schema Defaults
  118. # (werden normalerweise ueber die TUI konfiguriert)
  119. info = trainer.get_info()
  120. pdebug(f"IntentTrainer: {info.name} v{info.version}")
  121. # Queue fuer Fortschritts-Updates
  122. progress_queue: MPQueue = MPQueue()
  123. pause_flag = MPEvent()
  124. stop_flag = MPEvent()
  125. # Training in Thread ausfuehren (blockiert sonst den Event-Loop)
  126. import asyncio
  127. def run_training():
  128. try:
  129. trainer.train(progress_queue, pause_flag, stop_flag)
  130. except Exception as e:
  131. perror(f"Intent-Training Fehler: {e}")
  132. # Training synchron in Thread ausfuehren
  133. # (nicht daemon, damit der Prozess wartet bis fertig)
  134. pinfo(" Starte Training (kann einige Minuten dauern)...")
  135. result_holder: list[Exception | None] = [None]
  136. def run_training_sync():
  137. try:
  138. trainer.train(progress_queue, pause_flag, stop_flag)
  139. except Exception as e:
  140. result_holder[0] = e
  141. train_thread = __import__("threading").Thread(
  142. target=run_training_sync, daemon=False,
  143. )
  144. train_thread.start()
  145. # Fortschritt abfragen bis Training fertig
  146. while train_thread.is_alive():
  147. await asyncio.sleep(1.0)
  148. while not progress_queue.empty():
  149. try:
  150. progress = progress_queue.get_nowait()
  151. if progress.message:
  152. pinfo(f" {progress.message}")
  153. except Exception:
  154. break
  155. train_thread.join()
  156. # Restliche Nachrichten lesen
  157. while not progress_queue.empty():
  158. try:
  159. progress = progress_queue.get_nowait()
  160. if progress.message:
  161. pinfo(f" {progress.message}")
  162. except Exception:
  163. break
  164. if result_holder[0]:
  165. perror(f"Intent-Training Fehler: {result_holder[0]}")
  166. else:
  167. pinfo("Intent-Training abgeschlossen")
  168. except ImportError as e:
  169. perror(f"Intent-Trainer nicht verfuegbar: {e}")
  170. except Exception as e:
  171. perror(f"Intent-Training fehlgeschlagen: {e}")