Mac M1 optimizations, fix train pipeline, add Hey Cosmo wake word model

- Fix install_mac.sh: use venv + Python 3.12 (3.14 incompatible with ML libs)
- Fix run_mac.sh: activate venv, add CPU thread optimization env vars
- Fix agent.py: remove f-string from SYSTEM_PROMPT template (NameError on import)
- Add missing deps: sounddevice, pydub, imageio-ffmpeg, omegaconf
- Optimize for M1: torch.inference_mode, set_num_threads, OMP/MKL tuning
- Switch to qwen2.5:3b for faster LLM responses on Mac
- Switch Whisper to medium model with auto compute (small+int8 had poor Russian)
- Add initial_prompt for better Russian transcription
- Add open_app tool for native macOS app launching
- Fix TTS: sanitize Latin text to Cyrillic for Silero compatibility
- Fix wake word echo: add cooldown after TTS, reset model state, raise threshold
- Make "Слушаю" TTS synchronous to avoid mic interference
- Fix train Dockerfile: remove tensorflow/onnx2tf (only ONNX needed), fix deps
- Fix train.sh: use wget for dataset download, add --shm-size=2g
- Add trained hey_cosmo.onnx wake word model
- Add TODO section to CLAUDE.md (ChatterBox TTS, Ollama Modelfile ideas)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-11 11:19:53 +03:00
parent 6010816f1d
commit 110d9cde29
15 changed files with 183 additions and 94 deletions

View File

@@ -190,6 +190,11 @@ def my_tool(param: str) -> str:
Добавь в список `ALL_TOOLS` в конце файла — агент автоматически получит доступ. Добавь в список `ALL_TOOLS` в конце файла — агент автоматически получит доступ.
## TODO / Идеи
- [ ] **ChatterBox TTS вместо Silero** — более живой голос с эмоциями и клонированием голоса (10-30 сек сэмпла). Источник: [vndee/local-talking-llm](https://github.com/vndee/local-talking-llm). Параметр `exaggeration` (0.01.0) управляет экспрессивностью. Заменить `cosmo/tts.py`, интерфейс `say()`/`say_async()` не меняется. Проверить латентность на M1 CPU.
- [ ] **Ollama Modelfile** — запечь системный промпт и параметры (короткие ответы, русский язык) прямо в модель через Modelfile, вместо передачи в каждом запросе.
## Разработка ## Разработка
Логи пишутся в `logs/cosmo.log`. Уровень логирования меняется в конфиге (`logging.level: DEBUG`). Логи пишутся в `logs/cosmo.log`. Уровень логирования меняется в конфиге (`logging.level: DEBUG`).

View File

@@ -7,18 +7,22 @@ audio:
silence_duration: 1.0 silence_duration: 1.0
whisper: whisper:
model_size: "small" # На Mac без GPU small быстрее чем distil-large model_size: "medium" # medium — лучшее качество русского на CPU (small слишком много ошибок)
device: "cpu" # Mac Intel/Apple Silicon — CPU (MPS пока не стабилен в faster-whisper) device: "cpu" # Mac Intel/Apple Silicon — CPU (MPS пока не стабилен в faster-whisper)
compute_type: "int8" # int8 быстрее на CPU compute_type: "auto" # auto вместо int8 — int8 слишком сильно режет качество русского
language: "ru" language: "ru"
initial_prompt: "Cosmo, открой браузер, найди программу, запусти приложение." # подсказка для русского контекста
ollama: ollama:
base_url: "http://localhost:11434" base_url: "http://localhost:11434"
model: "qwen2.5:7b" model: "qwen2.5:3b" # 3b быстрее на M1 (~2x), достаточно для голосовых команд
temperature: 0.2 temperature: 0.2
max_tokens: 1024 max_tokens: 1024
max_agent_steps: 10 max_agent_steps: 10
performance:
num_threads: 4 # CPU потоки для torch (TTS/Whisper)
tts: tts:
enabled: true enabled: true
silero_speaker: "eugene" # xenia (женский) baya aidar eugene kseniya silero_speaker: "eugene" # xenia (женский) baya aidar eugene kseniya

View File

@@ -19,7 +19,7 @@ else:
from cosmo.tools import ALL_TOOLS, set_memory from cosmo.tools import ALL_TOOLS, set_memory
_PLATFORM_NOTE = "Windows. Используй Git Bash, 'start' для запуска приложений." _PLATFORM_NOTE = "Windows. Используй Git Bash, 'start' для запуска приложений."
SYSTEM_PROMPT = f"""Ты — Cosmo, умный голосовой ассистент. Платформа: {_PLATFORM_NOTE} SYSTEM_PROMPT = """Ты — Cosmo, умный голосовой ассистент. Платформа: """ + _PLATFORM_NOTE + """
Правила: Правила:
1. Используй инструменты для выполнения задач — не выдумывай результаты 1. Используй инструменты для выполнения задач — не выдумывай результаты

View File

@@ -71,7 +71,8 @@ class Cosmo:
self._command_event.set() self._command_event.set()
def _process_command(self): def _process_command(self):
self.tts.say_async("Слушаю") # Синхронно — сначала говорим, потом слушаем (иначе TTS мешает записи)
self.tts.say("Слушаю")
# Partial results — печатаем в лог что слышим в реальном времени # Partial results — печатаем в лог что слышим в реальном времени
def on_partial(text): def on_partial(text):

View File

@@ -61,9 +61,10 @@ def find_program(name: str) -> str:
""" """
Найти программу или приложение на macOS по имени. Найти программу или приложение на macOS по имени.
Ищет в PATH, /Applications, ~/Applications и через Spotlight (mdfind). Ищет в PATH, /Applications, ~/Applications и через Spotlight (mdfind).
Возвращает путь. Чтобы запустить — используй open_app.
Args: Args:
name: имя программы, например 'webstorm', 'chrome', 'cursor' name: имя программы, например 'webstorm', 'chrome', 'cursor', 'safari'
""" """
stem = name.strip() stem = name.strip()
logger.info(f"[find_program] {stem}") logger.info(f"[find_program] {stem}")
@@ -92,6 +93,27 @@ def find_program(name: str) -> str:
return f"Программа '{name}' не найдена." return f"Программа '{name}' не найдена."
@tool
def open_app(name: str) -> str:
"""
Запустить приложение на macOS по имени или полному пути.
Примеры: open_app("Safari"), open_app("/Applications/Safari.app"), open_app("Telegram").
Args:
name: имя приложения или полный путь к .app
"""
logger.info(f"[open_app] {name}")
# Если передан полный путь к .app
if name.endswith(".app") and os.path.exists(name):
result = run_shell(f'open "{name}"')
else:
# Пробуем по имени через open -a
result = run_shell(f'open -a "{name}"')
if result.startswith("[ошибка") or "returncode=1" in result:
return f"Не удалось открыть '{name}'. Попробуй find_program чтобы найти точное имя."
return f"Приложение '{name}' запущено."
@tool @tool
def open_browser(url: str, search: bool = False) -> str: def open_browser(url: str, search: bool = False) -> str:
""" """
@@ -197,6 +219,7 @@ def memory_list(prefix: str = "") -> str:
ALL_TOOLS = [ ALL_TOOLS = [
run_shell, run_shell,
find_program, find_program,
open_app,
open_browser, open_browser,
read_file, read_file,
write_file, write_file,

View File

@@ -20,11 +20,13 @@ class Transcriber:
"language": whisper_cfg["language"], "language": whisper_cfg["language"],
"device": whisper_cfg["device"], "device": whisper_cfg["device"],
"compute_type": whisper_cfg["compute_type"], "compute_type": whisper_cfg["compute_type"],
# Подсказка для Whisper — улучшает распознавание русского
"initial_prompt": whisper_cfg.get("initial_prompt", ""),
# Silero VAD параметры # Silero VAD параметры
"silero_sensitivity": 0.4, "silero_sensitivity": 0.4,
"webrtc_sensitivity": 3, "webrtc_sensitivity": 3,
"post_speech_silence_duration": audio_cfg["silence_duration"], "post_speech_silence_duration": audio_cfg["silence_duration"],
"min_length_of_recording": 0.3, "min_length_of_recording": 0.5,
"min_gap_between_recordings": 0.01, "min_gap_between_recordings": 0.01,
# Отключаем wake word в RealtimeSTT — используем свой # Отключаем wake word в RealtimeSTT — используем свой
"wakeword_backend": "none", "wakeword_backend": "none",

View File

@@ -32,6 +32,10 @@ class TTS:
self.enabled = False self.enabled = False
return return
# Оптимизация CPU-инференса на Apple Silicon
num_threads = config.get("performance", {}).get("num_threads", 4)
torch.set_num_threads(num_threads)
self._load_model() self._load_model()
def _load_model(self): def _load_model(self):
@@ -52,16 +56,33 @@ class TTS:
logger.warning("TTS отключён") logger.warning("TTS отключён")
self.enabled = False self.enabled = False
@staticmethod
def _sanitize_text(text: str) -> str:
"""Заменяет латиницу на читаемый русский для TTS."""
import re
# Транслитерация частых англ. слов которые Silero не прочитает
text = re.sub(r'[Ss]afari', 'Сафари', text)
text = re.sub(r'[Cc]hrome', 'Хром', text)
text = re.sub(r'[Tt]elegram', 'Телеграм', text)
text = re.sub(r'[Ww]eb[Ss]torm', 'ВебШторм', text)
text = re.sub(r'[Vv][Ss]\s?[Cc]ode', 'ВиЭс Код', text)
# Оставшиеся латинские слова — убираем, чтобы Silero не зависал
text = re.sub(r'[A-Za-z]+', '', text)
# Убираем лишние пробелы
text = re.sub(r'\s+', ' ', text).strip()
return text if text else "Готово"
def say(self, text: str): def say(self, text: str):
"""Произнести текст синхронно.""" """Произнести текст синхронно."""
if not self.enabled or self._model is None: if not self.enabled or self._model is None:
logger.info(f"[TTS]: {text}") logger.info(f"[TTS]: {text}")
return return
text = self._sanitize_text(text)
logger.debug(f"TTS: '{text}'") logger.debug(f"TTS: '{text}'")
with self._lock: with self._lock:
try: try:
with torch.no_grad(): with torch.inference_mode():
audio = self._model.apply_tts( audio = self._model.apply_tts(
text=text, text=text,
speaker=self.speaker, speaker=self.speaker,

View File

@@ -5,6 +5,7 @@ Wake word detector для Cosmo.
import os import os
import glob import glob
import time
import threading import threading
import queue import queue
import numpy as np import numpy as np
@@ -31,7 +32,8 @@ class WakeWordDetector:
self._thread = None self._thread = None
# Порог уверенности для срабатывания (0.0 1.0) # Порог уверенности для срабатывания (0.0 1.0)
self.threshold = 0.5 # 0.7 — баланс между надёжностью и защитой от ложных срабатываний/эха TTS
self.threshold = 0.7
logger.info("Загружаю wake word модель openwakeword...") logger.info("Загружаю wake word модель openwakeword...")
@@ -88,14 +90,18 @@ class WakeWordDetector:
"""Приостановить детект (пока идёт запись команды).""" """Приостановить детект (пока идёт запись команды)."""
self._paused = True self._paused = True
def resume(self): def resume(self, cooldown: float = 1.5):
"""Возобновить детект после записи команды.""" """Возобновить детект после записи команды с защитой от эха."""
# Очищаем очередь, чтобы не срабатывать на эхо # Ждём пока эхо от TTS затухнет
time.sleep(cooldown)
# Очищаем очередь — там буферизованный звук TTS
while not self._audio_queue.empty(): while not self._audio_queue.empty():
try: try:
self._audio_queue.get_nowait() self._audio_queue.get_nowait()
except queue.Empty: except queue.Empty:
break break
# Сбрасываем внутреннее состояние модели (накопленные скоры)
self.model.reset()
self._paused = False self._paused = False
logger.debug("Wake word детектор возобновлён") logger.debug("Wake word детектор возобновлён")

View File

@@ -6,14 +6,23 @@ echo " Установка Cosmo на macOS"
echo "============================================" echo "============================================"
# --- Python --- # --- Python ---
if ! command -v python3 &>/dev/null; then # Предпочитаем Python 3.12 (лучшая совместимость с ML-пакетами)
PYTHON_BIN=""
for py in python3.12 python3.11 python3.13 python3; do
if command -v "$py" &>/dev/null; then
PYTHON_BIN="$py"
break
fi
done
if [ -z "$PYTHON_BIN" ]; then
echo "ОШИБКА: Python3 не найден." echo "ОШИБКА: Python3 не найден."
echo "Установи через Homebrew: brew install python@3.11" echo "Установи через Homebrew: brew install python@3.12"
exit 1 exit 1
fi fi
PYTHON_VERSION=$(python3 -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')") PYTHON_VERSION=$($PYTHON_BIN -c "import sys; print(f'{sys.version_info.major}.{sys.version_info.minor}')")
echo "Python: $PYTHON_VERSION" echo "Python: $PYTHON_VERSION ($PYTHON_BIN)"
# --- Homebrew зависимости --- # --- Homebrew зависимости ---
if command -v brew &>/dev/null; then if command -v brew &>/dev/null; then
@@ -24,19 +33,22 @@ else
echo "Если будут ошибки с аудио — установи: /bin/bash -c \"\$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)\"" echo "Если будут ошибки с аудио — установи: /bin/bash -c \"\$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)\""
fi fi
echo "[2/5] Обновляю pip..." echo "[2/5] Создаю виртуальное окружение..."
python3 -m pip install --upgrade pip rm -rf venv
$PYTHON_BIN -m venv venv
source venv/bin/activate
echo "[3/5] Устанавливаю зависимости..." echo "[3/5] Обновляю pip и устанавливаю зависимости..."
python3 -m pip install -r requirements.txt pip install --upgrade pip
pip install -r requirements.txt
echo "[4/5] Устанавливаю faster-whisper..." echo "[4/5] Устанавливаю faster-whisper..."
# На Mac (Apple Silicon) используем CPU compute type # На Mac (Apple Silicon) используем CPU compute type
python3 -m pip install faster-whisper pip install faster-whisper
echo "[5/5] Устанавливаю openwakeword..." echo "[5/5] Устанавливаю openwakeword..."
python3 -m pip install openwakeword pip install openwakeword
python3 -c "import openwakeword; openwakeword.utils.download_models()" 2>/dev/null || true python -c "import openwakeword; openwakeword.utils.download_models()" 2>/dev/null || true
echo "" echo ""
echo "============================================" echo "============================================"

BIN
models/hey_cosmo.onnx Normal file

Binary file not shown.

View File

@@ -16,6 +16,11 @@ ollama==0.4.4 # официальный Python клиент Ollama
pyyaml==6.0.2 pyyaml==6.0.2
loguru==0.7.2 loguru==0.7.2
# Аудио
sounddevice>=0.5.0
pydub>=0.25.1
imageio-ffmpeg>=0.6.0
# Инструменты агента # Инструменты агента
psutil==6.0.0 psutil==6.0.0
pyautogui==0.9.54 pyautogui==0.9.54

View File

@@ -10,5 +10,18 @@ if ! curl -s http://localhost:11434 &>/dev/null; then
sleep 2 sleep 2
fi fi
# Оптимизация CPU-инференса на Apple Silicon
export OMP_NUM_THREADS=4
export MKL_NUM_THREADS=4
export PYTORCH_MPS_HIGH_WATERMARK_RATIO=0.0
# Активируем venv
if [ -d "venv" ]; then
source venv/bin/activate
else
echo "venv не найден. Сначала запусти: bash install_mac.sh"
exit 1
fi
# Запускаем с Mac-конфигом # Запускаем с Mac-конфигом
COSMO_PLATFORM=mac python3 cosmo/main.py --config config/config_mac.yaml "$@" COSMO_PLATFORM=mac python cosmo/main.py --config config/config_mac.yaml "$@"

View File

@@ -1,88 +1,86 @@
# Dockerfile для обучения wake word модели openWakeWord # Dockerfile для обуч<EFBFBD><EFBFBD>ния wake word модели openWakeWord
# Python 3.11 + torch 2.5 (последний совместимый с py3.11) + рабочие зависимости 2026 # Python 3.11 + torch (CPU) — без tensorflow (нам нужен только ONNX, не TFLite)
FROM python:3.11-slim FROM python:3.11-slim
WORKDIR /app WORKDIR /app
# Системные зависимости (включая build-essential для webrtcvad) # Системные зависимости
RUN apt-get update && apt-get install -y \ RUN apt-get update && apt-get install -y --no-install-recommends \
git wget curl ffmpeg libsndfile1 \ git wget curl ffmpeg libsndfile1 \
build-essential python3-dev \ build-essential python3-dev cmake \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
# Клонируем openWakeWord и piper-sample-generator # --- Слой 1: PyTorch (самый тяжёлый, кэшируется) ---
RUN git clone https://github.com/dscripka/openWakeWord /openWakeWord
RUN git clone https://github.com/rhasspy/piper-sample-generator /piper-sample-generator
# Torch 2.5.0 — последний для Python 3.11, CPU версия (обучение не требует GPU)
RUN pip install --no-cache-dir \ RUN pip install --no-cache-dir \
torch==2.5.0 \ torch==2.5.0 \
torchaudio==2.5.0 \ torchaudio==2.5.0 \
--index-url https://download.pytorch.org/whl/cpu --index-url https://download.pytorch.org/whl/cpu
# Зависимости обучения с совместимыми версиями # --- Слой 2: ML-зависимости (без tensorflow!) ---
RUN pip install --no-cache-dir \ RUN pip install --no-cache-dir \
mutagen==1.47.0 \ mutagen==1.47.0 \
torchinfo==1.8.0 \ torchinfo==1.8.0 \
torchmetrics==1.2.0 \ torchmetrics==1.2.0 \
speechbrain==1.0.3 \ speechbrain==1.0.3 \
audiomentations==0.43.1 \
torch-audiomentations==0.12.0 \
pronouncing==0.2.0 \ pronouncing==0.2.0 \
"datasets==2.20.0" \
"pyarrow==14.0.2" \
"fsspec==2023.12.2" \
acoustics==0.2.6 \ acoustics==0.2.6 \
pyyaml "scipy<1.15" scikit-learn tqdm
# --- Слой 3: Аудио-аугментация ---
RUN pip install --no-cache-dir \
audiomentations==0.43.1 \
torch-audiomentations==0.12.0
# --- Слой 4: Датасеты и ONNX ---
RUN pip install --no-cache-dir \
"datasets>=2.20.0" \
"pyarrow>=15.0.0" \
webrtcvad \ webrtcvad \
onnx \ onnx \
onnxruntime \ onnxruntime
onnx2tf \
pyyaml scipy scikit-learn tqdm
# TFLite конвертация через onnx2tf (замена мёртвого onnx_tf)
# Патчим train.py чтобы использовал onnx2tf вместо onnx_tf
RUN pip install --no-cache-dir \
tensorflow-cpu==2.21.0 \
tensorflow_probability==0.24.0
# --- Сл<D0A1><D0BB>й 5: openWakeWord ---
RUN git clone https://github.com/dscripka/openWakeWord /openWakeWord
RUN pip install --no-cache-dir -e /openWakeWord RUN pip install --no-cache-dir -e /openWakeWord
# Патч: заменяем onnx_tf на onnx2tf в train.py # Ресурсные модели для feature extraction (melspectrogram + embedding)
RUN python - <<'EOF' RUN mkdir -p /openWakeWord/openwakeword/resources/models && \
import re, pathlib wget -q -O /openWakeWord/openwakeword/resources/models/melspectrogram.onnx \
"https://github.com/dscripka/openWakeWord/releases/download/v0.5.1/melspectrogram.onnx" && \
wget -q -O /openWakeWord/openwakeword/resources/models/embedding_model.onnx \
"https://github.com/dscripka/openWakeWord/releases/download/v0.5.1/embedding_model.onnx"
# Патч train.py: убираем зависимость от onnx_tf/tensorflow (нам нужен только ONNX)
RUN python - <<'PATCH'
import pathlib
train_py = pathlib.Path("/openWakeWord/openwakeword/train.py") train_py = pathlib.Path("/openWakeWord/openwakeword/train.py")
text = train_py.read_text() text = train_py.read_text()
# Заменяем импорт onnx_tf
text = text.replace(
"import onnx_tf",
"import onnx2tf as onnx_tf_compat"
)
text = text.replace(
"from onnx_tf.backend import prepare",
"# onnx_tf replaced by onnx2tf"
)
# Заменяем вызов convert_onnx_to_tflite если он есть
text = re.sub(
r"onnx_tf\.backend\.prepare\(.*?\)",
"None # onnx2tf handles tflite conversion differently",
text, flags=re.DOTALL
)
train_py.write_text(text)
print("train.py patched OK")
EOF
# Устанавливаем piper-sample-generator # Заменяем всю функцию convert_onnx_to_tflite на заглушку
old_func = text[text.find("def convert_onnx_to_tflite("):]
old_func = old_func[:old_func.find("\nif __name__")]
new_func = '''def convert_onnx_to_tflite(onnx_model_path, output_path):
"""Skipped — ONNX-only mode, TFLite not needed."""
return None
'''
text = text.replace(old_func, new_func)
train_py.write_text(text)
print("train.py patched: convert_onnx_to_tflite replaced with stub")
PATCH
# --- Слой 6: piper-sample-generator v2.0.0 (совместим с openWakeWord train.py) ---
RUN git clone --branch v2.0.0 https://github.com/rhasspy/piper-sample-generator /piper-sample-generator
RUN pip install --no-cache-dir piper-phonemize || true
RUN pip install --no-cache-dir -e /piper-sample-generator 2>/dev/null || \ RUN pip install --no-cache-dir -e /piper-sample-generator 2>/dev/null || \
pip install --no-cache-dir piper-tts pip install --no-cache-dir piper-tts
# Скачиваем TTS модель LibriTTS-R medium (~66 MB) для генерации примеров # TTS модель (.pt checkpoint) для генерации примеров
RUN mkdir -p /piper-sample-generator/models && \ RUN mkdir -p /piper-sample-generator/models && \
wget -q --show-progress \ wget -q --show-progress \
-O /piper-sample-generator/models/en_US-libritts_r-medium.onnx \ -O /piper-sample-generator/models/en_US-libritts_r-medium.pt \
"https://huggingface.co/rhasspy/piper-voices/resolve/main/en/en_US/libritts_r/medium/en_US-libritts_r-medium.onnx" && \ "https://github.com/rhasspy/piper-sample-generator/releases/download/v2.0.0/en_US-libritts_r-medium.pt"
wget -q \
-O /piper-sample-generator/models/en_US-libritts_r-medium.onnx.json \
"https://huggingface.co/rhasspy/piper-voices/resolve/main/en/en_US/libritts_r/medium/en_US-libritts_r-medium.onnx.json"
RUN mkdir -p /data /output /samples RUN mkdir -p /data /output /samples

View File

@@ -40,6 +40,11 @@ batch_n_per_class:
"adversarial_negative": 50 "adversarial_negative": 50
"positive": 50 "positive": 50
# Пути для аугментации (пустые — аугментация без RIR и фонового шума)
rir_paths: []
background_paths: []
background_paths_duplication_rate: []
# Архитектура модели # Архитектура модели
model_type: "dnn" model_type: "dnn"
layer_size: 32 layer_size: 32

View File

@@ -51,24 +51,17 @@ NEGATIVE_FEATURES="$DATA_DIR/openwakeword_features_ACAV100M_2000_hrs_16bit.npy"
VALIDATION_FEATURES="$DATA_DIR/validation_set_features.npy" VALIDATION_FEATURES="$DATA_DIR/validation_set_features.npy"
if [ ! -f "$NEGATIVE_FEATURES" ]; then if [ ! -f "$NEGATIVE_FEATURES" ]; then
echo "[2/4] Скачиваю негативный датасет (~20 GB, один раз)..." echo "[2/4] Скачиваю негативный датасет (~17 GB + ~500 MB, один раз)..."
echo " Это займёт время в зависимости от скорости интернета." echo " Это займёт время в зависимости от скорости интернета."
docker run --rm \ echo ""
-v "$DATA_DIR:/data" \ echo " Скачиваю ACAV100M features (~17 GB)..."
cosmo-wakeword-trainer \ wget -q --show-progress \
python -c " -O "$NEGATIVE_FEATURES" \
from datasets import load_dataset "https://huggingface.co/datasets/davidscripka/openwakeword_features/resolve/main/openwakeword_features_ACAV100M_2000_hrs_16bit.npy"
import numpy as np, os echo " Скачиваю validation features (~500 MB)..."
print('Скачиваю ACAV100M features...') wget -q --show-progress \
ds = load_dataset('davidscripka/openwakeword_features', 'ACAV100M_2000_hrs_16bit', split='train') -O "$VALIDATION_FEATURES" \
arr = np.array(ds['features']) "https://huggingface.co/datasets/davidscripka/openwakeword_features/resolve/main/validation_set_features.npy"
np.save('/data/openwakeword_features_ACAV100M_2000_hrs_16bit.npy', arr)
print('Скачиваю validation features...')
ds_val = load_dataset('davidscripka/openwakeword_features', 'validation_set', split='train')
arr_val = np.array(ds_val['features'])
np.save('/data/validation_set_features.npy', arr_val)
print('Датасет скачан.')
"
echo " Датасет готов." echo " Датасет готов."
else else
echo "[2/4] Негативный датасет уже скачан. Пропускаю." echo "[2/4] Негативный датасет уже скачан. Пропускаю."
@@ -86,6 +79,7 @@ if [ -d "$POSITIVE_DIR" ] && [ -n "$(ls "$POSITIVE_DIR"/*.wav 2>/dev/null)" ]; t
fi fi
docker run --rm \ docker run --rm \
--shm-size=2g \
-v "$SCRIPT_DIR/cosmo_config.yaml:/app/cosmo_config.yaml" \ -v "$SCRIPT_DIR/cosmo_config.yaml:/app/cosmo_config.yaml" \
-v "$DATA_DIR:/data" \ -v "$DATA_DIR:/data" \
-v "$MODELS_DIR:/output" \ -v "$MODELS_DIR:/output" \