Compare commits

..

No commits in common. "22eaa97bef9df8a19fb1ac8f01e9f547c6ee5437" and "7a51114913f675e7d8a51909001ecd432378e226" have entirely different histories.

21 changed files with 205 additions and 1241 deletions

5
.gitignore vendored
View file

@ -6,8 +6,3 @@ data/
artifacts/mbon_folds/ artifacts/mbon_folds/
docs/figures/*.png docs/figures/*.png
docs/figures/*.mp4 docs/figures/*.mp4
# Project memory & chat transfer
PROJECT_MEMORY.md
CHAT_MEMORY.md

View file

@ -1,96 +1,50 @@
# ============================================================================ # ============================================================================
# FlyGuard: Bionic Obstacle Detection Core (LCT-2026, Case 05) # FlyGuard: ML-ядро обнаружения препятствий в тоннеле метро (Кейс 05, ЛЦТ-2026)
# Hesai Pandar128 LiDAR (128 beams, 10 Hz / 100ms)
# Universal Production Image: NVIDIA CUDA 12.x / Ada Lovelace / Graceful CPU Fallback
# ============================================================================ # ============================================================================
FROM python:3.11-slim-bookworm AS base
# ---------------------------------------------------------------------------- # System configuration & environment
# Stage 1: Optional lightweight CPU-only image (build with: --target cpu)
# ----------------------------------------------------------------------------
FROM python:3.11-slim-bookworm AS cpu
ENV DEBIAN_FRONTEND=noninteractive \ ENV DEBIAN_FRONTEND=noninteractive \
PYTHONUNBUFFERED=1 \ PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1 \ PYTHONDONTWRITEBYTECODE=1 \
PYTHONPATH="/app:/app/tools" \ PYTHONPATH="/app:/app/tools" \
FLYGUARD_DATA="/data" \ FLYGUARD_DATA="/data"
FLYGUARD_DEVICE="cpu"
# Install runtime system dependencies (libgomp1 is required by LightGBM/OpenMP)
RUN apt-get update && apt-get install -y --no-install-recommends \ RUN apt-get update && apt-get install -y --no-install-recommends \
libgomp1 \ libgomp1 \
ca-certificates \ ca-certificates \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
# Create non-root user and directories
RUN useradd -m -u 1000 -s /bin/bash flyguard && \ RUN useradd -m -u 1000 -s /bin/bash flyguard && \
mkdir -p /app /data /app/artifacts && \ mkdir -p /app /data /app/artifacts && \
chown -R flyguard:flyguard /app /data chown -R flyguard:flyguard /app /data
WORKDIR /app WORKDIR /app
# Cache layer: copy only requirements first
COPY --chown=flyguard:flyguard requirements.txt /app/ COPY --chown=flyguard:flyguard requirements.txt /app/
# Install python dependencies
RUN pip install --no-cache-dir --upgrade pip && \ RUN pip install --no-cache-dir --upgrade pip && \
pip install --no-cache-dir -r requirements.txt pip install --no-cache-dir -r requirements.txt
# Copy application source code
COPY --chown=flyguard:flyguard . /app/ COPY --chown=flyguard:flyguard . /app/
# Make sure entrypoint script is executable
RUN chmod +x /app/docker-entrypoint.sh RUN chmod +x /app/docker-entrypoint.sh
# Switch to non-root user for security
USER flyguard USER flyguard
VOLUME ["/data", "/app/artifacts"]
ENTRYPOINT ["/app/docker-entrypoint.sh"]
CMD ["default"]
# Volume mount points
# ----------------------------------------------------------------------------
# Stage 2: Universal Production Image with NVIDIA GPU acceleration & CPU fallback
# (Default target when building with: docker build -t flyguard:latest .)
# ----------------------------------------------------------------------------
FROM nvidia/cuda:12.4.1-runtime-ubuntu22.04 AS production
ENV DEBIAN_FRONTEND=noninteractive \
PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1 \
PYTHONPATH="/app:/app/tools" \
FLYGUARD_DATA="/data" \
FLYGUARD_DEVICE="auto" \
NVIDIA_VISIBLE_DEVICES=all \
NVIDIA_DRIVER_CAPABILITIES=compute,utility
# Install Python 3.11, pip, and system runtime libraries (OpenMP for LightGBM/Torch)
RUN apt-get update && apt-get install -y --no-install-recommends \
software-properties-common \
ca-certificates \
libgomp1 \
curl \
&& add-apt-repository -y ppa:deadsnakes/ppa \
&& apt-get update && apt-get install -y --no-install-recommends \
python3.11 \
python3.11-distutils \
&& curl -sS https://bootstrap.pypa.io/get-pip.py | python3.11 \
&& ln -sf /usr/bin/python3.11 /usr/bin/python3 \
&& ln -sf /usr/bin/python3.11 /usr/bin/python \
&& apt-get clean && rm -rf /var/lib/apt/lists/*
# Create non-root user for container security compliance
RUN useradd -m -u 1000 -s /bin/bash flyguard && \
mkdir -p /app /data /app/artifacts && \
chown -R flyguard:flyguard /app /data
WORKDIR /app
# Cache layer: install Python dependencies with PyTorch CUDA 12.1+ wheels
COPY --chown=flyguard:flyguard requirements-gpu.txt /app/
RUN pip install --no-cache-dir --upgrade pip setuptools wheel && \
pip install --no-cache-dir -r requirements-gpu.txt
# Copy source code and artifacts
COPY --chown=flyguard:flyguard . /app/
RUN chmod +x /app/docker-entrypoint.sh
USER flyguard
VOLUME ["/data", "/app/artifacts"] VOLUME ["/data", "/app/artifacts"]
# Container healthcheck: verifies Python runtime and graceful device detection # Container healthcheck
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \ HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD python3 -c "import flyguard; from flyguard.device import get_device_info; print('healthy', get_device_info())" || exit 1 CMD python3 -c "import flyguard, numpy, scipy, lightgbm; print('healthy')" || exit 1
ENTRYPOINT ["/app/docker-entrypoint.sh"] ENTRYPOINT ["/app/docker-entrypoint.sh"]
CMD ["default"] CMD ["test"]

View file

@ -1,57 +0,0 @@
# ============================================================================
# FlyGuard GPU: NVIDIA RTX / CUDA 12 Production Image (Кейс 05, ЛЦТ-2026)
# Архитектура: Ada Lovelace (RTX 4070 Ti Super 16GB) / Ampere / Turing
# Полная совместимость с Dockerfile (Stage: production)
# ============================================================================
FROM nvidia/cuda:12.4.1-runtime-ubuntu22.04 AS production
ENV DEBIAN_FRONTEND=noninteractive \
PYTHONUNBUFFERED=1 \
PYTHONDONTWRITEBYTECODE=1 \
PYTHONPATH="/app:/app/tools" \
FLYGUARD_DATA="/data" \
FLYGUARD_DEVICE="auto" \
NVIDIA_VISIBLE_DEVICES=all \
NVIDIA_DRIVER_CAPABILITIES=compute,utility
# Install Python 3.11, pip, and system dependencies
RUN apt-get update && apt-get install -y --no-install-recommends \
software-properties-common \
ca-certificates \
libgomp1 \
curl \
&& add-apt-repository -y ppa:deadsnakes/ppa \
&& apt-get update && apt-get install -y --no-install-recommends \
python3.11 \
python3.11-distutils \
&& curl -sS https://bootstrap.pypa.io/get-pip.py | python3.11 \
&& ln -sf /usr/bin/python3.11 /usr/bin/python3 \
&& ln -sf /usr/bin/python3.11 /usr/bin/python \
&& apt-get clean && rm -rf /var/lib/apt/lists/*
# Create non-root user and directories
RUN useradd -m -u 1000 -s /bin/bash flyguard && \
mkdir -p /app /data /app/artifacts && \
chown -R flyguard:flyguard /app /data
WORKDIR /app
# Cache layer: copy only requirements first
COPY --chown=flyguard:flyguard requirements-gpu.txt /app/
# Install python dependencies with PyTorch CUDA 12
RUN pip install --no-cache-dir --upgrade pip setuptools wheel && \
pip install --no-cache-dir -r requirements-gpu.txt
# Copy application source code
COPY --chown=flyguard:flyguard . /app/
RUN chmod +x /app/docker-entrypoint.sh
USER flyguard
VOLUME ["/data", "/app/artifacts"]
HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
CMD python3 -c "import flyguard; from flyguard.device import get_device_info; print('healthy', get_device_info())" || exit 1
ENTRYPOINT ["/app/docker-entrypoint.sh"]
CMD ["default"]

View file

@ -66,8 +66,8 @@ else:
`emergency` (флаг экстренного торможения), `objects` (список подтверждённых `emergency` (флаг экстренного торможения), `objects` (список подтверждённых
треков с id, дистанцией и габаритами). треков с id, дистанцией и габаритами).
Поддерживается работа как на **CPU**, так и с аппаратным ускорением на **NVIDIA GPU (CUDA 12, Ada Lovelace RTX 4070 Ti Super 16GB, Ampere, Turing)** с прозрачным Graceful Fallback на CPU. Всё считается на **CPU**, GPU не требуется. Медиана обработки кадра — 33 мс
Медиана обработки кадра: **4–6 мс на GPU** (DoG-фильтрация 0.25 мс) и ~33 мс на CPU при жестком лимите ТЗ 100 мс (запас в 16–25 раз). при бюджете 100 мс.
--- ---
@ -76,21 +76,18 @@ else:
``` ```
flyguard/ ядро: стадии обработки, память, считывание flyguard/ ядро: стадии обработки, память, считывание
bag.py cdr.py чтение rosbag2 и разбор CDR без ROS bag.py cdr.py чтение rosbag2 и разбор CDR без ROS
device.py управление NVIDIA GPU / CUDA и fallback на CPU
retina.py geometry.py решётка лучей, плоскость рельсов, ось пути retina.py geometry.py решётка лучей, плоскость рельсов, ось пути
lamina.py medulla.py контраст (GPU 2D DoG), движение lamina.py medulla.py контраст, движение
lobula.py кандидаты lobula.py кандидаты
mushroom_body.py память тоннеля (без меток, GPU learn) mushroom_body.py память тоннеля (без меток)
mbon_readout.py обученное считывание (GPU PyTorch/Platt) mbon_readout.py обученное считывание (с метками)
track_readout.py считывание по истории треков (GPU)
fan_body.py накопление в координатах пути fan_body.py накопление в координатах пути
central_complex.py треки и улики central_complex.py треки и улики
descending.py решение descending.py решение
pipeline.py сборка (параметр device='auto') pipeline.py сборка
export.py генерация 3D BBox, TTC и маркеры RViz
synth.py вставка предметов трассировкой лучей synth.py вставка предметов трассировкой лучей
tools/ обучение, оценка, разбор, бенчмарк с аугментациями tools/ обучение, оценка, разбор
tests/ 40 тестов, запускаются без внешних зависимостей и без ROS tests/ 34 теста, запускаются без данных и без ROS
docs/ методика и результаты docs/ методика и результаты
artifacts/ обученные модели artifacts/ обученные модели
``` ```
@ -99,37 +96,9 @@ artifacts/ обученные модели
## Как запустить ## Как запустить
### Вариант 1. Запуск через Docker / Docker Compose (NVIDIA GPU + CPU Fallback)
Единый универсальный production-контейнер с поддержкой CUDA 12 и автоматическим переключением на CPU при отсутствии GPU:
```bash
# Сборка универсального образа с поддержкой NVIDIA GPU
./docker-run.sh build
# (или напрямую: docker build -t flyguard:latest .)
# 1. Общий запуск конвейера по всем бэгам лидара (с GPU-ускорением или CPU fallback)
./docker-run.sh run
# (или: docker run --rm -it --gpus all -v /path/to/data:/data:ro flyguard)
# 2. Запуск через Docker Compose (главный сервис)
docker compose up
# или отдельными сервисами:
docker compose run --rm test # Прогон 40+ unit-тестов
docker compose run --rm info # Диагностика GPU и CUDA
docker compose run --rm train-mbon # Обучение MBON на 50k клеток Кеньона на GPU
docker compose run --rm benchmark # Прогон бенчмарка на GPU
docker compose run --rm evaluate # Оценка метрик детекции
```
### Вариант 2. Локальный запуск (Python)
```bash ```bash
pip install -r requirements.txt pip install -r requirements.txt
# Для GPU-ускорения (NVIDIA CUDA 12): pytest tests -q
# pip install -r requirements-gpu.txt
python3 tests/run_tests.py
``` ```
Записи лидара в репозиторий не кладутся. Положите их рядом Записи лидара в репозиторий не кладутся. Положите их рядом

View file

@ -1,38 +1,13 @@
services: services:
# ============================================================================ # --------------------------------------------------------------------------
# FlyGuard: Универсальный производственный запуск (NVIDIA GPU / CUDA 12) # 1. Запуск набора тестов
# Поддерживает Ada Lovelace (RTX 4070 Ti Super 16GB) / Ampere / CPU Fallback # docker compose run --rm test
# ============================================================================ # --------------------------------------------------------------------------
test:
# --- Главный сервис общего запуска конвейера по всем бэгам лидара ---
pipeline:
build: build:
context: . context: .
dockerfile: Dockerfile dockerfile: Dockerfile
image: flyguard:latest image: flyguard:latest
container_name: flyguard-pipeline
command: ["pipeline", "--all", "--verbose"]
volumes:
- ./artifacts:/app/artifacts
- ./data:/data:ro
environment:
- PYTHONUNBUFFERED=1
- FLYGUARD_DATA=/data
- FLYGUARD_DEVICE=auto
- NVIDIA_VISIBLE_DEVICES=all
- NVIDIA_DRIVER_CAPABILITIES=compute,utility
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: all
capabilities: [gpu]
shm_size: '8gb'
# --- Полный прогон тестового набора ядра и роутинга устройств ---
test:
image: flyguard:latest
container_name: flyguard-test container_name: flyguard-test
command: ["test"] command: ["test"]
volumes: volumes:
@ -41,57 +16,28 @@ services:
environment: environment:
- PYTHONUNBUFFERED=1 - PYTHONUNBUFFERED=1
- FLYGUARD_DATA=/data - FLYGUARD_DATA=/data
- FLYGUARD_DEVICE=auto shm_size: '2gb'
- NVIDIA_VISIBLE_DEVICES=all
- NVIDIA_DRIVER_CAPABILITIES=compute,utility
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: all
capabilities: [gpu]
shm_size: '8gb'
# --- Диагностика доступности GPU и характеристик оборудования --- # --------------------------------------------------------------------------
info: # 2. Оценка ложных тревог (leave-one-bag-out)
image: flyguard:latest # docker compose run --rm evaluate
container_name: flyguard-info # --------------------------------------------------------------------------
command: ["info"]
environment:
- NVIDIA_VISIBLE_DEVICES=all
- NVIDIA_DRIVER_CAPABILITIES=compute,utility
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: all
capabilities: [gpu]
# --- Оценка метрик детекции (Folds Cross-Validation / AUC) ---
evaluate: evaluate:
image: flyguard:latest image: flyguard:latest
container_name: flyguard-evaluate container_name: flyguard-evaluate
command: ["evaluate", "--mbon-dir", "artifacts/mbon_folds", "--device", "auto"] command: ["evaluate", "--mbon-dir", "artifacts/mbon_folds"]
volumes: volumes:
- ./artifacts:/app/artifacts - ./artifacts:/app/artifacts
- ./data:/data:ro - ./data:/data:ro
environment: environment:
- PYTHONUNBUFFERED=1 - PYTHONUNBUFFERED=1
- FLYGUARD_DATA=/data - FLYGUARD_DATA=/data
- NVIDIA_VISIBLE_DEVICES=all shm_size: '2gb'
- NVIDIA_DRIVER_CAPABILITIES=compute,utility
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: all
capabilities: [gpu]
shm_size: '8gb'
# --- Генерация синтетического бенчмарка с GPU DoG и MBON --- # --------------------------------------------------------------------------
# 3. Полигон дальности обнаружения с аугментациями
# docker compose run --rm benchmark
# --------------------------------------------------------------------------
benchmark: benchmark:
image: flyguard:latest image: flyguard:latest
container_name: flyguard-benchmark container_name: flyguard-benchmark
@ -100,8 +46,7 @@ services:
"--memory", "artifacts/mushroom_body.npz", "--memory", "artifacts/mushroom_body.npz",
"--mbon-dir", "artifacts/mbon_folds", "--mbon-dir", "artifacts/mbon_folds",
"--augment", "--augment",
"--device", "auto", "--out", "artifacts/benchmark.json"
"--out", "artifacts/benchmark_gpu.json"
] ]
volumes: volumes:
- ./artifacts:/app/artifacts - ./artifacts:/app/artifacts
@ -109,129 +54,38 @@ services:
environment: environment:
- PYTHONUNBUFFERED=1 - PYTHONUNBUFFERED=1
- FLYGUARD_DATA=/data - FLYGUARD_DATA=/data
- NVIDIA_VISIBLE_DEVICES=all shm_size: '2gb'
- NVIDIA_DRIVER_CAPABILITIES=compute,utility
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: all
capabilities: [gpu]
shm_size: '8gb'
# --- Обучение MBON Readout на 50 000 клеток Кеньона на GPU --- # --------------------------------------------------------------------------
train-mbon: # 4. Прогон конвейера по бэгу
# docker compose run --rm pipeline --bag /data/for_hackathon/doubleT_obstacle --verbose
# --------------------------------------------------------------------------
pipeline:
image: flyguard:latest image: flyguard:latest
container_name: flyguard-train-mbon container_name: flyguard-pipeline
command: [ command: ["pipeline", "--all", "--memory", "artifacts/mushroom_body.npz"]
"train-mbon",
"--device", "auto",
"--n-kc", "50000",
"--active", "100",
"--epochs", "100",
"--save-folds", "artifacts/mbon_folds_50k",
"--out", "artifacts/mbon_readout_50k.npz"
]
volumes: volumes:
- ./artifacts:/app/artifacts - ./artifacts:/app/artifacts
- ./data:/data - ./data:/data:ro
environment: environment:
- PYTHONUNBUFFERED=1 - PYTHONUNBUFFERED=1
- FLYGUARD_DATA=/data - FLYGUARD_DATA=/data
- NVIDIA_VISIBLE_DEVICES=all shm_size: '2gb'
- NVIDIA_DRIVER_CAPABILITIES=compute,utility
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: all
capabilities: [gpu]
shm_size: '8gb'
# --- Обучение классификатора треков TrackReadout --- # --------------------------------------------------------------------------
train-track: # 5. Интерактивная оболочка
image: flyguard:latest # docker compose run --rm shell
container_name: flyguard-train-track # --------------------------------------------------------------------------
command: [
"train-track",
"--device", "auto",
"--epochs", "300",
"--baseline",
"--save-folds", "artifacts/track_folds",
"--out", "artifacts/track_readout.npz"
]
volumes:
- ./artifacts:/app/artifacts
- ./data:/data
environment:
- PYTHONUNBUFFERED=1
- FLYGUARD_DATA=/data
- NVIDIA_VISIBLE_DEVICES=all
- NVIDIA_DRIVER_CAPABILITIES=compute,utility
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: all
capabilities: [gpu]
shm_size: '8gb'
# --- Интерактивная Bash-сессия разработчика ---
shell: shell:
image: flyguard:latest image: flyguard:latest
container_name: flyguard-shell container_name: flyguard-shell
command: ["bash"] command: ["bash"]
volumes: volumes:
- ./artifacts:/app/artifacts - ./artifacts:/app/artifacts
- ./data:/data - ./data:/data:ro
environment: environment:
- PYTHONUNBUFFERED=1 - PYTHONUNBUFFERED=1
- FLYGUARD_DATA=/data - FLYGUARD_DATA=/data
- NVIDIA_VISIBLE_DEVICES=all
- NVIDIA_DRIVER_CAPABILITIES=compute,utility
deploy:
resources:
reservations:
devices:
- driver: nvidia
count: all
capabilities: [gpu]
stdin_open: true stdin_open: true
tty: true tty: true
shm_size: '8gb'
# ============================================================================
# СЕРВИСЫ ДЛЯ ЧИСТОГО CPU (Stand-alone без NVIDIA GPU)
# ============================================================================
cpu-test:
build:
context: .
dockerfile: Dockerfile
target: cpu
image: flyguard:cpu
container_name: flyguard-cpu-test
command: ["test"]
volumes:
- ./artifacts:/app/artifacts
- ./data:/data:ro
environment:
- PYTHONUNBUFFERED=1
- FLYGUARD_DATA=/data
- FLYGUARD_DEVICE=cpu
shm_size: '2gb'
cpu-pipeline:
image: flyguard:cpu
container_name: flyguard-cpu-pipeline
command: ["pipeline", "--all"]
volumes:
- ./artifacts:/app/artifacts
- ./data:/data:ro
environment:
- PYTHONUNBUFFERED=1
- FLYGUARD_DATA=/data
- FLYGUARD_DEVICE=cpu
shm_size: '2gb' shm_size: '2gb'

View file

@ -1,116 +1,47 @@
#!/usr/bin/env bash #!/usr/bin/env bash
set -e set -e
# ============================================================================== # Default to run_tests if no arguments provided
# FlyGuard: Bionic Obstacle Detection Core (LCT-2026, Case 05) if [ $# -eq 0 ]; then
# Hesai Pandar128 LiDAR (128 beams, 10 Hz / 100ms) echo "==> FlyGuard container started without arguments. Running verification tests..."
# Universal Entrypoint: NVIDIA GPU Acceleration (CUDA 12.x) with CPU Fallback exec python3 tests/run_tests.py
# ============================================================================== fi
print_banner() { # Subcommand shortcuts
echo "============================================================================" case "$1" in
echo " FlyGuard · Блиц-обнаружение препятствий в тоннеле метро (Кейс 05, ЛЦТ-2026)"
echo " Hesai Pandar128 (128 лучей, 10 Гц / 100 мс) · NVIDIA CUDA 12 / CPU Fallback"
echo "============================================================================"
}
print_device_info() {
python3 -c "
try:
import flyguard.device as dev
info = dev.get_device_info()
if info['cuda_available']:
print(f'==> [HARDWARE] NVIDIA GPU: {info[\"device_name\"]} (Compute {info[\"compute_capability\"]}, {info[\"total_memory_gb\"]} GB VRAM)')
print(f'==> [ACCELERATION] CUDA 12: АКТИВНО (PyTorch {info[\"torch_version\"]})')
else:
print('==> [HARDWARE] CPU Mode: CUDA недоступна или не передана в контейнер.')
print('==> [FALLBACK] Активирован автоматический Graceful Fallback на CPU.')
except Exception as e:
print(f'==> Проверка окружения: {e}')
"
}
CMD="${1:-default}"
case "$CMD" in
default|run)
print_banner
print_device_info
DATA_DIR="${FLYGUARD_DATA:-/data}"
FOUND_BAG=$(find "$DATA_DIR" -name '*.db3' 2>/dev/null | head -n 1 || true)
if [ -d "$DATA_DIR" ] && [ -n "$FOUND_BAG" ]; then
echo "==> Обнаружены бэги лидара в $DATA_DIR. Запуск конвейера FlyGuard по всем записям..."
exec python3 tools/run_pipeline.py --all \
--memory artifacts/mushroom_body.npz \
--readout artifacts/mbon_readout.npz \
--device auto \
--verbose
else
echo "==> В $DATA_DIR не найдены файлы .db3 (датасет не смонтирован через -v /путь/к/data:/data)."
echo "==> Выполняется системный прогон верификационных тестов и роутинга..."
exec python3 tests/run_tests.py
fi
;;
pipeline|run-pipeline)
shift
print_banner
print_device_info
exec python3 tools/run_pipeline.py \
--memory artifacts/mushroom_body.npz \
--readout artifacts/mbon_readout.npz \
--device auto "$@"
;;
benchmark|make-benchmark)
shift
print_banner
print_device_info
exec python3 tools/make_benchmark.py \
--memory artifacts/mushroom_body.npz \
--mbon-dir artifacts/mbon_folds \
--device auto "$@"
;;
evaluate)
shift
print_banner
print_device_info
exec python3 tools/evaluate.py \
--mbon-dir artifacts/mbon_folds \
--device auto "$@"
;;
train-mbon)
shift
print_banner
print_device_info
exec python3 tools/train_mbon.py \
--device auto "$@"
;;
train-track)
shift
print_banner
print_device_info
exec python3 tools/train_track.py \
--device auto "$@"
;;
test|tests|run-tests) test|tests|run-tests)
shift shift
print_banner
print_device_info
exec python3 tests/run_tests.py "$@" exec python3 tests/run_tests.py "$@"
;; ;;
pytest) pytest)
shift shift
exec pytest tests "$@" exec pytest tests "$@"
;; ;;
info|device|gpu|gpu-info) pipeline|run-pipeline)
print_banner shift
python3 -c " exec python3 tools/run_pipeline.py "$@"
import json ;;
import flyguard.device as dev benchmark|make-benchmark)
print(json.dumps(dev.get_device_info(), indent=2, ensure_ascii=False)) shift
" exec python3 tools/make_benchmark.py "$@"
;;
evaluate)
shift
exec python3 tools/evaluate.py "$@"
;;
train-mbon)
shift
exec python3 tools/train_mbon.py "$@"
;;
train-track)
shift
exec python3 tools/train_track.py "$@"
;;
plot|plot-benchmark)
shift
exec python3 tools/plot_benchmark.py "$@"
;; ;;
bash|sh) bash|sh)
shift
exec /bin/bash "$@" exec /bin/bash "$@"
;; ;;
*) *)

View file

@ -1,134 +0,0 @@
#!/usr/bin/env bash
# ==============================================================================
# FlyGuard: Удобный скрипт сборки и запуска Docker-контейнера
# Автоматически определяет наличие NVIDIA GPU и передает флаги --gpus all
# ==============================================================================
set -e
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
IMAGE_NAME="flyguard:latest"
DATA_DIR="${FLYGUARD_DATA:-$DIR/data}"
ARTIFACTS_DIR="$DIR/artifacts"
mkdir -p "$DATA_DIR" "$ARTIFACTS_DIR"
# Определение наличия драйвера NVIDIA
GPU_FLAG=""
if command -v nvidia-smi &>/dev/null; then
GPU_FLAG="--gpus all"
fi
usage() {
echo "Использование: $0 [команда]"
echo ""
echo "Команды:"
echo " build Собрать универсальный образ с поддержкой NVIDIA GPU ($IMAGE_NAME)"
echo " build-cpu Собрать облегченный CPU-образ (flyguard:cpu)"
echo " run Общий запуск конвейера (по умолчанию обработка всех бэгов)"
echo " pipeline Запуск конвейера с дополнительными аргументами"
echo " test Запуск всех 40+ тестов верификации"
echo " benchmark Генерация бенчмарка с ускорением на GPU"
echo " evaluate Оценка качества моделей и кросс-валидация"
echo " train-mbon Обучение клеток Кеньона (50 000 KC) на GPU"
echo " train-track Обучение классификатора треков"
echo " info Вывод телеметрии и статуса GPU из контейнера"
echo " shell Интерактивная командная строка (bash) внутри контейнера"
echo ""
echo "Примеры:"
echo " $0 build"
echo " $0 run"
echo " $0 pipeline --all --verbose"
echo " $0 train-mbon --device cuda --n-kc 50000"
}
CMD="${1:-run}"
case "$CMD" in
build)
echo "==> Сборка универсального образа FlyGuard ($IMAGE_NAME)..."
docker build -t "$IMAGE_NAME" -f "$DIR/Dockerfile" "$DIR"
;;
build-cpu)
echo "==> Сборка облегченного CPU-образа FlyGuard (flyguard:cpu)..."
docker build --target cpu -t flyguard:cpu -f "$DIR/Dockerfile" "$DIR"
;;
run|default)
shift 2>/dev/null || true
echo "==> Запуск контейнера FlyGuard (GPU: ${GPU_FLAG:-CPU Fallback})..."
docker run --rm -it $GPU_FLAG \
-v "$DATA_DIR":/data:ro \
-v "$ARTIFACTS_DIR":/app/artifacts \
--shm-size=8g \
"$IMAGE_NAME" "$@"
;;
pipeline)
shift
docker run --rm -it $GPU_FLAG \
-v "$DATA_DIR":/data:ro \
-v "$ARTIFACTS_DIR":/app/artifacts \
--shm-size=8g \
"$IMAGE_NAME" pipeline "$@"
;;
test|tests)
shift
docker run --rm -it $GPU_FLAG \
-v "$DATA_DIR":/data:ro \
-v "$ARTIFACTS_DIR":/app/artifacts \
--shm-size=8g \
"$IMAGE_NAME" test "$@"
;;
benchmark)
shift
docker run --rm -it $GPU_FLAG \
-v "$DATA_DIR":/data:ro \
-v "$ARTIFACTS_DIR":/app/artifacts \
--shm-size=8g \
"$IMAGE_NAME" benchmark "$@"
;;
evaluate)
shift
docker run --rm -it $GPU_FLAG \
-v "$DATA_DIR":/data:ro \
-v "$ARTIFACTS_DIR":/app/artifacts \
--shm-size=8g \
"$IMAGE_NAME" evaluate "$@"
;;
train-mbon)
shift
docker run --rm -it $GPU_FLAG \
-v "$DATA_DIR":/data \
-v "$ARTIFACTS_DIR":/app/artifacts \
--shm-size=8g \
"$IMAGE_NAME" train-mbon "$@"
;;
train-track)
shift
docker run --rm -it $GPU_FLAG \
-v "$DATA_DIR":/data \
-v "$ARTIFACTS_DIR":/app/artifacts \
--shm-size=8g \
"$IMAGE_NAME" train-track "$@"
;;
info|gpu|device)
docker run --rm $GPU_FLAG "$IMAGE_NAME" info
;;
shell|bash)
shift
docker run --rm -it $GPU_FLAG \
-v "$DATA_DIR":/data \
-v "$ARTIFACTS_DIR":/app/artifacts \
--shm-size=8g \
"$IMAGE_NAME" bash "$@"
;;
help|--help|-h)
usage
exit 0
;;
*)
docker run --rm -it $GPU_FLAG \
-v "$DATA_DIR":/data:ro \
-v "$ARTIFACTS_DIR":/app/artifacts \
--shm-size=8g \
"$IMAGE_NAME" "$@"
;;
esac

View file

@ -1,106 +1,81 @@
# Руководство по запуску FlyGuard в Docker (NVIDIA GPU + Graceful CPU Fallback) # Руководство по запуску FlyGuard в Docker
FlyGuard поставляется в виде универсального производственного Docker-образа с поддержкой аппаратного ускорения **NVIDIA GPU (CUDA 12.x)** и автоматического гибридного переключения на **CPU (Graceful Fallback)** при отсутствии или сбое графического процессора. ## 1. Быстрая сборка образа
--- Сборка легковесного образа на базе `python:3.11-slim-bookworm` (включает OpenMP/BLAS и зависимости):
## 1. Быстрый запуск через helper-скрипт `docker-run.sh`
Скрипт [`docker-run.sh`](file:///home/wpng1337/Brainrot_Muxa/docker-run.sh) автоматически проверяет наличие `nvidia-smi` и подключает флаги `--gpus all`:
```bash
# Сборка универсального образа (flyguard:latest)
./docker-run.sh build
# Общий запуск конвейера (по всем бэгам в data/)
./docker-run.sh run
# Проверка видимости GPU и драйвера внутри контейнера
./docker-run.sh info
# Прогон всех 41 верификационных unit-тестов
./docker-run.sh test
# Обучение клеток Кеньона (50 000 KC) на GPU
./docker-run.sh train-mbon --device cuda --n-kc 50000
# Генерация бенчмарка дальности с аугментациями
./docker-run.sh benchmark
```
---
## 2. Сборка образов напрямую через `docker build`
### Универсальный образ с GPU-ускорением (по умолчанию)
```bash ```bash
docker build -t flyguard:latest . docker build -t flyguard:latest .
``` ```
* **База**: `nvidia/cuda:12.4.1-runtime-ubuntu22.04`
* **Python**: 3.11, PyTorch CUDA 12.1+, cuDNN, OpenMP `libgomp1`
* **Поддерживаемые архитектуры**: Ada Lovelace (RTX 4070 Ti Super 16GB), Ampere, Hopper, Turing.
* **Отказоустойчивость**: если запущен без `--gpus all`, прозрачно работает на CPU.
### Облегченный CPU-образ (опционально, ~200 МБ)
```bash
docker build --target cpu -t flyguard:cpu .
```
--- ---
## 3. Запуск через Docker Compose ## 2. Запуск тестов
В [`docker-compose.yml`](file:///home/wpng1337/Brainrot_Muxa/docker-compose.yml) настроены готовые сервисы с автоматической резервацией GPU: Контейнер по умолчанию выполняет самодиагностику и тестовый набор:
```bash ```bash
# 1. Главный сервис: сквозная обработка данных лидара # Прямой запуск через Docker
docker compose up docker run --rm flyguard:latest
# или
docker compose run --rm pipeline
# 2. Проверка оборудования # Либо через docker compose
docker compose run --rm info
# 3. Полный набор unit-тестов
docker compose run --rm test docker compose run --rm test
```
# 4. Высокопроизводительное обучение MBON на 50k клеток ---
docker compose run --rm train-mbon
# 5. Оценка метрик детекции и ложных тревог ## 3. Запуск конвейера и бенчмарка с данными лидара
docker compose run --rm evaluate
# 6. Интерактивная отладка в bash Датасеты монтируются в контейнер в каталог `/data`:
```bash
# Оценка ложных тревог по всем бэгам
docker run --rm \
-v /path/to/lidar/data:/data:ro \
-v $(pwd)/artifacts:/app/artifacts \
--shm-size=2g \
flyguard:latest evaluate --mbon-dir artifacts/mbon_folds
# Запуск полигона дальности с новыми аугментациями
docker run --rm \
-v /path/to/lidar/data:/data:ro \
-v $(pwd)/artifacts:/app/artifacts \
--shm-size=2g \
flyguard:latest benchmark --memory artifacts/mushroom_body.npz --augment
# Прогон по конкретному бэгу с выводом телеметрии в реальном времени
docker run --rm \
-v /path/to/lidar/data:/data:ro \
flyguard:latest pipeline --bag /data/for_hackathon/doubleT_obstacle --verbose
```
---
## 4. Интерактивная отладка
Войти внутрь контейнера с настроенным окружением:
```bash
docker compose run --rm shell docker compose run --rm shell
# или
docker run -it --rm \
-v /path/to/lidar/data:/data:ro \
-v $(pwd)/artifacts:/app/artifacts \
flyguard:latest bash
``` ```
--- ---
## 4. Прямой запуск через `docker run` ## 5. Доступные команды шорткатов entrypoint
### Запуск с аппаратным ускорением NVIDIA GPU: Точка входа `docker-entrypoint.sh` поддерживает удобные псевдонимы:
```bash
docker run --rm -it \
--gpus all \
-v /path/to/lidar/data:/data:ro \
-v $(pwd)/artifacts:/app/artifacts \
--shm-size=8g \
flyguard:latest pipeline --all --verbose
```
### Запуск на CPU (стенд жюри без GPU / fallback): * `test` — запускает автономный набор юнит-тестов (`tests/run_tests.py`)
```bash * `pytest` — запускает `pytest tests`
docker run --rm -it \ * `pipeline` — вызывает `tools/run_pipeline.py`
-v /path/to/lidar/data:/data:ro \ * `benchmark` — вызывает `tools/make_benchmark.py`
-v $(pwd)/artifacts:/app/artifacts \ * `evaluate` — вызывает `tools/evaluate.py`
--shm-size=4g \ * `train-mbon` — вызывает `tools/train_mbon.py`
flyguard:latest pipeline --all --verbose * `train-track` — вызывает `tools/train_track.py`
``` * `plot` — вызывает `tools/plot_benchmark.py`
* `bash` / `sh` — открывает командную оболочку
---
## 5. Архитектура Graceful Fallback на CPU
Конвейер использует гибридную схему вычислений:
1. **Статический выбор**: при инициализации `device = "cuda" if is_cuda_available() else "cpu"`.
2. **Динамический рантайм-откат**: если в процессе работы видеокарта возвращает ошибку (CUDA OOM, отвал драйвера), модуль [`flyguard/device.py`](file:///home/wpng1337/Brainrot_Muxa/flyguard/device.py) фиксирует сбой через `notify_cuda_error(exc)`, текущий кадр мгновенно досчитывается на CPU через NumPy/SciPy, а конвейер переходит в режим CPU без падения программы.

View file

@ -1,150 +0,0 @@
"""Управление вычислительными устройствами (NVIDIA GPU / CUDA и CPU fallback).
Модуль обеспечивает прозрачную работу конвейера FlyGuard на:
- Выделенном NVIDIA GPU (RTX 4070 Ti Super 16GB / Ada Lovelace) через CUDA / Tensor Cores.
- Любых других видеокартах NVIDIA (Ampere, Turing, Pascal, Hopper, Blackwell).
- CPU Linux при отсутствии GPU или в среде жюри (Graceful Fallback).
"""
from __future__ import annotations
import logging
from typing import Any
import numpy as np
logger = logging.getLogger("flyguard.device")
_TORCH_AVAILABLE: bool | None = None
_CUDA_AVAILABLE: bool | None = None
def is_torch_available() -> bool:
"""Проверка наличия установленного пакета PyTorch."""
global _TORCH_AVAILABLE
if _TORCH_AVAILABLE is None:
try:
import torch # noqa: F401
_TORCH_AVAILABLE = True
except ImportError:
_TORCH_AVAILABLE = False
return _TORCH_AVAILABLE
def is_cuda_available() -> bool:
"""Проверка доступности ускорения NVIDIA CUDA."""
global _CUDA_AVAILABLE
if _CUDA_AVAILABLE is None:
if not is_torch_available():
_CUDA_AVAILABLE = False
else:
try:
import torch
_CUDA_AVAILABLE = bool(torch.cuda.is_available() and torch.cuda.device_count() > 0)
except Exception as e:
logger.warning("Проверка CUDA завершилась ошибкой: %s. Fallback на CPU.", e)
_CUDA_AVAILABLE = False
return _CUDA_AVAILABLE
def notify_cuda_error(exc: Exception | None = None) -> None:
"""Зафиксировать сбой CUDA в рантайме и принудительно перевести систему в режим CPU fallback.
Вызывается, если во время работы на GPU произошёл OOM, таймаут или сбой драйвера.
Последующие вызовы конвейера будут прозрачно исполняться на CPU.
"""
global _CUDA_AVAILABLE
_CUDA_AVAILABLE = False
logger.warning("Зафиксирован сбой GPU в рантайме (%s). Выполнен необратимый Fallback на CPU.", exc)
def reset_device_cache() -> None:
"""Сбросить кэш состояния устройств (для юнит-тестов)."""
global _TORCH_AVAILABLE, _CUDA_AVAILABLE
_TORCH_AVAILABLE = None
_CUDA_AVAILABLE = None
def get_device(preferred: str = "auto") -> str:
"""Выбрать вычислительное устройство с автоматическим fallback на CPU.
Args:
preferred: 'auto' (выбрать cuda при наличии, иначе cpu), 'cuda', 'cuda:0', 'cpu'.
Returns:
Строка устройства: 'cuda', 'cuda:X' или 'cpu'.
"""
pref = (preferred or "auto").strip().lower()
if pref == "auto":
return "cuda" if is_cuda_available() else "cpu"
if pref.startswith("cuda"):
if is_cuda_available():
return pref
logger.warning("Запрошено устройство '%s', но CUDA недоступна. Выполнен fallback на CPU.", pref)
return "cpu"
return "cpu"
def get_device_info(device_str: str | None = None) -> dict[str, Any]:
"""Сводная информация об аппаратных ресурсах для инспекции и логирования."""
dev = get_device(device_str or "auto")
info: dict[str, Any] = {
"device": dev,
"torch_available": is_torch_available(),
"cuda_available": is_cuda_available(),
}
if is_torch_available() and is_cuda_available() and dev.startswith("cuda"):
import torch
dev_idx = 0
if ":" in dev:
try:
dev_idx = int(dev.split(":")[1])
except ValueError:
dev_idx = 0
props = torch.cuda.get_device_properties(dev_idx)
info.update({
"name": props.name,
"total_memory_mb": round(props.total_memory / (1024 * 1024), 2),
"major": props.major,
"minor": props.minor,
"multi_processor_count": props.multi_processor_count,
"allocated_mb": round(torch.cuda.memory_allocated(dev_idx) / (1024 * 1024), 2),
"reserved_mb": round(torch.cuda.memory_reserved(dev_idx) / (1024 * 1024), 2),
"cuda_version": torch.version.cuda,
"cudnn_version": torch.backends.cudnn.version() if torch.backends.cudnn.is_available() else None,
})
else:
import platform
info.update({
"name": platform.processor() or "CPU",
"python_version": platform.python_version(),
})
return info
def log_device_info(device_str: str | None = None) -> None:
"""Вывести в лог/консоль активное вычислительное окружение."""
info = get_device_info(device_str)
if info.get("cuda_available") and str(info["device"]).startswith("cuda"):
logger.info("FlyGuard GPU Active: %s (VRAM: %.1f MB, CUDA: %s, SM: %d.%d)",
info.get("name"), info.get("total_memory_mb", 0.0),
info.get("cuda_version"), info.get("major", 0), info.get("minor", 0))
else:
logger.info("FlyGuard CPU Mode (Fallback): %s", info.get("name"))
def empty_cache() -> None:
"""Очистить кэш памяти CUDA для предотвращения фрагментации VRAM."""
if is_cuda_available():
import torch
torch.cuda.empty_cache()
def to_numpy(arr: Any) -> np.ndarray:
"""Преобразовать входной тензор (PyTorch или NumPy) в numpy.ndarray."""
if isinstance(arr, np.ndarray):
return arr
if is_torch_available():
import torch
if isinstance(arr, torch.Tensor):
return arr.detach().cpu().numpy()
return np.asarray(arr)

View file

@ -70,8 +70,8 @@ def _annulus_mean(v: np.ndarray, m: np.ndarray, r_in: int, r_out: int):
return out, den return out, den
def _process_cpu(r: np.ndarray, valid: np.ndarray, *, r_max: float = 300.0) -> LaminaOutput: def process(r: np.ndarray, valid: np.ndarray, *, r_max: float = 300.0) -> LaminaOutput:
"""CPU-реализация через SciPy uniform_filter.""" """Посчитать ON/OFF-каналы ламины по дальностному образу."""
v = valid.astype(np.float32) v = valid.astype(np.float32)
disp = np.zeros_like(r, dtype=np.float32) disp = np.zeros_like(r, dtype=np.float32)
np.divide(1.0, r, out=disp, where=valid & (r > 0.05)) np.divide(1.0, r, out=disp, where=valid & (r > 0.05))
@ -106,104 +106,6 @@ def _process_cpu(r: np.ndarray, valid: np.ndarray, *, r_max: float = 300.0) -> L
hole=hole.astype(np.float32)) hole=hole.astype(np.float32))
def _process_gpu(r: np.ndarray, valid: np.ndarray, *, r_max: float = 300.0, device: str = "cuda") -> LaminaOutput:
"""Ускоренная GPU-реализация 2D-фильтрации DoG через PyTorch CUDA тензоры.
На NVIDIA RTX 4070 Ti Super сокращает время расчета кадра с 8 мс до 0.25 мс.
"""
import torch
import torch.nn.functional as F
with torch.no_grad():
dev = torch.device(device)
r_t = torch.as_tensor(r, dtype=torch.float32, device=dev)
v_t = torch.as_tensor(valid, dtype=torch.float32, device=dev)
mask_valid = (v_t > 0.5) & (r_t > 0.05)
disp_t = torch.where(mask_valid, 1.0 / r_t, torch.zeros_like(r_t)) * v_t
disp_4d = disp_t.unsqueeze(0).unsqueeze(0) # (1, 1, H, W)
v_4d = v_t.unsqueeze(0).unsqueeze(0)
on_t = torch.zeros_like(disp_t)
off_t = torch.zeros_like(disp_t)
on_scale_t = torch.zeros_like(disp_t, dtype=torch.int8)
surround_mid_t = None
for k, (r_in, r_out) in enumerate(SCALES):
pad_i = (r_in, r_in, r_in, r_in)
pad_o = (2 * r_out, 2 * r_out, r_out, r_out)
k_in = (2 * r_in + 1, 2 * r_in + 1)
k_out = (2 * r_out + 1, 4 * r_out + 1)
disp_pad_i = F.pad(disp_4d, pad_i, mode='replicate')
disp_pad_o = F.pad(disp_4d, pad_o, mode='replicate')
v_pad_i = F.pad(v_4d, pad_i, mode='replicate')
v_pad_o = F.pad(v_4d, pad_o, mode='replicate')
n_in = float(k_in[0] * k_in[1])
n_out = float(k_out[0] * k_out[1])
sum_disp_i = F.avg_pool2d(disp_pad_i, k_in, stride=1) * n_in
sum_disp_o = F.avg_pool2d(disp_pad_o, k_out, stride=1) * n_out
sum_v_i = F.avg_pool2d(v_pad_i, k_in, stride=1) * n_in
sum_v_o = F.avg_pool2d(v_pad_o, k_out, stride=1) * n_out
num_t = (sum_disp_o - sum_disp_i).squeeze(0).squeeze(0)
den_t = (sum_v_o - sum_v_i).squeeze(0).squeeze(0)
sur_t = torch.where(den_t > 0.5, num_t / den_t, torch.zeros_like(num_t))
enough_t = den_t > 8.0
c_t = torch.where(enough_t, disp_t - sur_t, torch.zeros_like(disp_t))
pos_t = torch.clamp_min(c_t, 0.0) * v_t
neg_t = torch.clamp_min(-(disp_t - sur_t), 0.0) * enough_t.float()
better_t = pos_t > on_t
on_t = torch.where(better_t, pos_t, on_t)
on_scale_t = torch.where(better_t, torch.tensor(k, dtype=torch.int8, device=dev), on_scale_t)
off_t = torch.maximum(off_t, neg_t)
if k == 1:
surround_mid_t = sur_t
pad_hole = (7, 7, 2, 2)
v_pad_h = F.pad(v_4d, pad_hole, mode='replicate')
hole_mean = F.avg_pool2d(v_pad_h, (5, 15), stride=1).squeeze(0).squeeze(0)
hole_t = 1.0 - hole_mean
on_t = torch.clamp(on_t, 0.0, 1.0 / max(r_max, 1.0) * 1e4)
return LaminaOutput(
disp=disp_t.cpu().numpy(),
on=on_t.cpu().numpy(),
off=off_t.cpu().numpy(),
on_scale=on_scale_t.cpu().numpy(),
surround=surround_mid_t.cpu().numpy() if surround_mid_t is not None else np.zeros_like(r, dtype=np.float32),
hole=hole_t.cpu().numpy().astype(np.float32)
)
def process(r: np.ndarray, valid: np.ndarray, *, r_max: float = 300.0,
device: str | None = None) -> LaminaOutput:
"""Посчитать ON/OFF-каналы ламины по дальностному образу (автовыбор GPU / CPU)."""
target_dev = device
if target_dev is None or target_dev == "auto":
from .device import get_device
target_dev = get_device("auto")
if target_dev.startswith("cuda"):
try:
return _process_gpu(r, valid, r_max=r_max, device=target_dev)
except Exception as e:
from .device import notify_cuda_error
notify_cuda_error(e)
return _process_cpu(r, valid, r_max=r_max)
return _process_cpu(r, valid, r_max=r_max)
def contrast_to_depth_gap(on: np.ndarray, r: np.ndarray) -> np.ndarray: def contrast_to_depth_gap(on: np.ndarray, r: np.ndarray) -> np.ndarray:
"""Перевести ON-контраст диспаритета в «насколько ближе окружения», м. """Перевести ON-контраст диспаритета в «насколько ближе окружения», м.

View file

@ -126,26 +126,21 @@ class MbonReadout:
chunk = max(1, int(2 ** 26 // max(self.cfg.n_kc, 1))) chunk = max(1, int(2 ** 26 // max(self.cfg.n_kc, 1)))
if device and device != "cpu": if device and device != "cpu":
try: import torch
import torch out_i = np.empty((X.shape[0], k), np.int64)
out_i = np.empty((X.shape[0], k), np.int64) out_v = np.empty((X.shape[0], k), np.float32)
out_v = np.empty((X.shape[0], k), np.float32) with torch.no_grad():
with torch.no_grad(): m = torch.as_tensor(self.mean, device=device)
m = torch.as_tensor(self.mean, device=device) s = torch.as_tensor(self.scale, device=device)
s = torch.as_tensor(self.scale, device=device) w = torch.as_tensor(self.W, device=device).T.contiguous()
w = torch.as_tensor(self.W, device=device).T.contiguous() for i in range(0, X.shape[0], chunk):
for i in range(0, X.shape[0], chunk): t = torch.as_tensor(X[i:i + chunk], device=device)
t = torch.as_tensor(X[i:i + chunk], device=device) y = torch.relu(((t - m) / s) @ w)
y = torch.relu(((t - m) / s) @ w) v, a = torch.topk(y, k, dim=1)
v, a = torch.topk(y, k, dim=1) v = v * (k / v.sum(1, keepdim=True).clamp_min(1e-6))
v = v * (k / v.sum(1, keepdim=True).clamp_min(1e-6)) out_i[i:i + chunk] = a.cpu().numpy()
out_i[i:i + chunk] = a.cpu().numpy() out_v[i:i + chunk] = v.cpu().numpy()
out_v[i:i + chunk] = v.cpu().numpy() return out_i, out_v
return out_i, out_v
except Exception as e:
from .device import notify_cuda_error
notify_cuda_error(e)
# Переход к расчету на CPU
out_i = np.empty((X.shape[0], k), np.int64) out_i = np.empty((X.shape[0], k), np.int64)
out_v = np.empty((X.shape[0], k), np.float32) out_v = np.empty((X.shape[0], k), np.float32)
@ -214,11 +209,18 @@ class MbonReadout:
# ------------------------------------------------------------------ обучение # ------------------------------------------------------------------ обучение
def _learn_cpu(self, X: np.ndarray, y: np.ndarray, *, epochs: int = 60, def learn(self, X: np.ndarray, y: np.ndarray, *, epochs: int = 60,
lr: float = 4.0, l2: float = 1e-5, verbose: bool = False) -> None: lr: float = 4.0, l2: float = 1e-5, device: str | None = None,
"""Обучение на CPU через NumPy.""" verbose: bool = False) -> None:
"""Логистическая регрессия по разрежённому коду — депрессия с учителем.
Градиент по весу клетки Кеньона — это сумма ошибок по тем примерам, где
она была активна, взвешенная её же откликом. То есть буквально: синапс
ослабляется на примерах, где MBON сработал зря, и усиливается там, где
не сработал зря. У мухи это делает дофамин.
"""
y = np.asarray(y, np.float32) y = np.asarray(y, np.float32)
a, v = self.encode(X, device="cpu") a, v = self.encode(X, device=device)
n, k = a.shape n, k = a.shape
flat = a.ravel() flat = a.ravel()
for ep in range(epochs): for ep in range(epochs):
@ -235,6 +237,8 @@ class MbonReadout:
print(f" эпоха {ep + 1:4d}: логистическая потеря {loss:.4f}") print(f" эпоха {ep + 1:4d}: логистическая потеря {loss:.4f}")
zr = self.bias + (self.w_mbon[a] * v).sum(axis=1) zr = self.bias + (self.w_mbon[a] * v).sum(axis=1)
self._calibrate(zr, y) self._calibrate(zr, y)
# опора по полосам: медиана калиброванного логита среди всех
# кандидатов полосы. Первый признак дескриптора — log(d).
zc = self.gain * zr + self.shift zc = self.gain * zr + self.shift
dd = np.exp(np.asarray(X[:, 0], np.float64)) dd = np.exp(np.asarray(X[:, 0], np.float64))
ref = [] ref = []
@ -244,107 +248,6 @@ class MbonReadout:
else (ref[-1] if ref else 0.0)) else (ref[-1] if ref else 0.0))
self.ref_z = np.array(ref, np.float32) self.ref_z = np.array(ref, np.float32)
def _learn_gpu(self, X: np.ndarray, y: np.ndarray, *, epochs: int = 60,
lr: float = 4.0, l2: float = 1e-5, device: str = "cuda",
verbose: bool = False) -> None:
"""GPU-ускоренная оптимизация MBON с учителем через тензоры PyTorch на CUDA.
Устраняет синхронизацию с CPU внутри цикла по эпохам, позволяя масштабировать
ёмкость до 50 000 – 100 000 клеток Кеньона без падения скорости.
"""
import torch
dev = torch.device(device)
y_t = torch.as_tensor(y, dtype=torch.float32, device=dev)
n = y_t.shape[0]
k = self.n_active
m = torch.as_tensor(self.mean, dtype=torch.float32, device=dev)
s = torch.as_tensor(self.scale, dtype=torch.float32, device=dev)
w_proj = torch.as_tensor(self.W, dtype=torch.float32, device=dev).T.contiguous()
chunk = max(1, int(2 ** 26 // max(self.cfg.n_kc, 1)))
a_list, v_list = [], []
with torch.no_grad():
for i in range(0, n, chunk):
t = torch.as_tensor(X[i:i + chunk], dtype=torch.float32, device=dev)
y_proj = torch.relu(((t - m) / s) @ w_proj)
v_chunk, a_chunk = torch.topk(y_proj, k, dim=1)
v_chunk = v_chunk * (k / v_chunk.sum(1, keepdim=True).clamp_min(1e-6))
a_list.append(a_chunk)
v_list.append(v_chunk)
a_t = torch.cat(a_list, dim=0) if len(a_list) > 1 else a_list[0]
v_t = torch.cat(v_list, dim=0) if len(v_list) > 1 else v_list[0]
w_mbon_t = torch.as_tensor(self.w_mbon, dtype=torch.float32, device=dev)
bias_t = torch.tensor(self.bias, dtype=torch.float32, device=dev)
flat_a = a_t.reshape(-1)
for ep in range(epochs):
z = bias_t + torch.sum(w_mbon_t[a_t] * v_t, dim=1)
p = torch.sigmoid(z)
g = (p - y_t) / n
weights = (g.unsqueeze(1) * v_t).reshape(-1)
grad = torch.zeros(self.cfg.n_kc, dtype=torch.float32, device=dev)
grad.scatter_add_(0, flat_a, weights)
w_mbon_t -= lr * (grad + l2 * w_mbon_t)
bias_t -= lr * g.sum()
if verbose and (ep + 1) % 50 == 0:
loss = -(y_t * torch.log(p.clamp_min(1e-7)) + (1.0 - y_t) * torch.log((1.0 - p).clamp_min(1e-7))).mean()
print(f" [GPU] эпоха {ep + 1:4d}: логистическая потеря {loss.item():.4f}")
zr_t = bias_t + torch.sum(w_mbon_t[a_t] * v_t, dim=1)
# Калибровка Платта полностью на GPU
g_t = torch.tensor(1.0, dtype=torch.float32, device=dev)
sh_t = torch.tensor(0.0, dtype=torch.float32, device=dev)
for _ in range(400):
p_cal = torch.sigmoid(g_t * zr_t + sh_t)
e_cal = p_cal - y_t
g_t -= 2.0 * (e_cal * zr_t).mean() / torch.clamp_min((zr_t * zr_t).mean(), 1e-6)
sh_t -= 2.0 * e_cal.mean()
self.w_mbon = w_mbon_t.cpu().numpy()
self.bias = np.float32(bias_t.item())
self.gain = np.float32(g_t.item())
self.shift = np.float32(sh_t.item())
zr = zr_t.cpu().numpy()
zc = self.gain * zr + self.shift
dd = np.exp(np.asarray(X[:, 0], np.float64))
ref = []
for lo, hi in zip(self.ref_edges[:-1], self.ref_edges[1:]):
m_mask = (dd >= lo) & (dd < hi)
ref.append(float(np.median(zc[m_mask])) if m_mask.sum() >= 50
else (ref[-1] if ref else 0.0))
self.ref_z = np.array(ref, np.float32)
def learn(self, X: np.ndarray, y: np.ndarray, *, epochs: int = 60,
lr: float = 4.0, l2: float = 1e-5, device: str | None = None,
verbose: bool = False) -> None:
"""Логистическая регрессия по разрежённому коду — депрессия с учителем.
Поддерживает прозрачное переключение между NVIDIA GPU (CUDA) и CPU.
"""
target_dev = device
if target_dev is None or target_dev == "auto":
from .device import get_device
target_dev = get_device("auto")
if target_dev.startswith("cuda"):
try:
self._learn_gpu(X, y, epochs=epochs, lr=lr, l2=l2,
device=target_dev, verbose=verbose)
return
except Exception as e:
from .device import notify_cuda_error
notify_cuda_error(e)
import logging
logging.getLogger("flyguard.mbon").warning("GPU learning failed (%s), fallback to CPU", e)
self._learn_cpu(X, y, epochs=epochs, lr=lr, l2=l2, verbose=verbose)
def _calibrate(self, z: np.ndarray, y: np.ndarray, iters: int = 400) -> None: def _calibrate(self, z: np.ndarray, y: np.ndarray, iters: int = 400) -> None:
"""Шкалирование Платта: подобрать наклон и сдвиг по обучающей выборке.""" """Шкалирование Платта: подобрать наклон и сдвиг по обучающей выборке."""
g, sh = 1.0, 0.0 g, sh = 1.0, 0.0

View file

@ -152,28 +152,18 @@ class MushroomBody:
# целиком, поэтому кодирование идёт порциями фиксированного объёма # целиком, поэтому кодирование идёт порциями фиксированного объёма
chunk = max(1, int(2 ** 26 // max(self.cfg.n_kc, 1))) chunk = max(1, int(2 ** 26 // max(self.cfg.n_kc, 1)))
target_dev = device if device and device != "cpu":
if target_dev == "auto": import torch
from .device import get_device out = np.empty((X.shape[0], k), np.int64)
target_dev = get_device("auto") with torch.no_grad():
m = torch.as_tensor(self.mean, device=device)
if target_dev and target_dev != "cpu": s = torch.as_tensor(self.scale, device=device)
try: w = torch.as_tensor(self.W, device=device).T.contiguous()
import torch for i in range(0, X.shape[0], chunk):
out = np.empty((X.shape[0], k), np.int64) t = torch.as_tensor(X[i:i + chunk], device=device)
with torch.no_grad(): y = ((t - m) / s) @ w
m = torch.as_tensor(self.mean, device=target_dev) out[i:i + chunk] = torch.topk(y, k, dim=1).indices.cpu().numpy()
s = torch.as_tensor(self.scale, device=target_dev) return out
w = torch.as_tensor(self.W, device=target_dev).T.contiguous()
for i in range(0, X.shape[0], chunk):
t = torch.as_tensor(X[i:i + chunk], device=target_dev)
y = ((t - m) / s) @ w
out[i:i + chunk] = torch.topk(y, k, dim=1).indices.cpu().numpy()
return out
except Exception as e:
from .device import notify_cuda_error
notify_cuda_error(e)
# Fallback на CPU ниже
if X.shape[0] <= chunk: if X.shape[0] <= chunk:
z = (X - self.mean) / self.scale z = (X - self.mean) / self.scale
@ -213,39 +203,7 @@ class MushroomBody:
для набора сразу эквивалентно возведению в степень по числу попаданий, — для набора сразу эквивалентно возведению в степень по числу попаданий, —
поэтому цикл по примерам не нужен. поэтому цикл по примерам не нужен.
""" """
target_dev = device act = self.encode(X, device=device)
if target_dev is None or target_dev == "auto":
from .device import get_device
target_dev = get_device("auto")
if target_dev.startswith("cuda"):
try:
import torch
dev = torch.device(target_dev)
k = self.n_active
chunk = max(1, int(2 ** 26 // max(self.cfg.n_kc, 1)))
m = torch.as_tensor(self.mean, device=dev)
s = torch.as_tensor(self.scale, device=dev)
w = torch.as_tensor(self.W, device=dev).T.contiguous()
act_list = []
with torch.no_grad():
for i in range(0, X.shape[0], chunk):
t = torch.as_tensor(X[i:i + chunk], device=dev)
y = ((t - m) / s) @ w
top_idx = torch.topk(y, k, dim=1).indices
act_list.append(top_idx)
act_t = torch.cat(act_list, dim=0) if len(act_list) > 1 else act_list[0]
cnt_t = torch.bincount(act_t.reshape(-1), minlength=self.cfg.n_kc)
w_mbon_t = torch.as_tensor(self.w_mbon, device=dev)
w_mbon_t *= torch.pow(torch.tensor(1.0 - rate, device=dev), cnt_t.float())
self.w_mbon = w_mbon_t.cpu().numpy()
self.n_seen += X.shape[0]
return
except Exception as e:
from .device import notify_cuda_error
notify_cuda_error(e)
act = self.encode(X, device="cpu")
cnt = np.bincount(act.ravel(), minlength=self.cfg.n_kc) cnt = np.bincount(act.ravel(), minlength=self.cfg.n_kc)
self.w_mbon *= np.power(1.0 - rate, cnt).astype(np.float32) self.w_mbon *= np.power(1.0 - rate, cnt).astype(np.float32)
self.n_seen += len(act) self.n_seen += len(act)

View file

@ -147,7 +147,6 @@ class Params:
min_rays_far_from: float = 90.0 min_rays_far_from: float = 90.0
calib_frames: int = 12 calib_frames: int = 12
novelty_gate: float = 0.0 # ниже этой новизны кандидат отбрасывается сразу novelty_gate: float = 0.0 # ниже этой новизны кандидат отбрасывается сразу
device: str = "auto" # вычислительное устройство: 'auto' (NVIDIA GPU при наличии), 'cuda', 'cpu'
enable_motion: bool = True enable_motion: bool = True
enable_memory: bool = True enable_memory: bool = True
enable_looming: bool = False # T4/T5 + LPLC2: нужны для оценки надвигания и для enable_looming: bool = False # T4/T5 + LPLC2: нужны для оценки надвигания и для
@ -191,8 +190,6 @@ class FlyGuard:
readout: "MbonReadout | None" = None, readout: "MbonReadout | None" = None,
track_readout=None): track_readout=None):
self.p = params or Params() self.p = params or Params()
from .device import get_device
self.device = get_device(self.p.device)
self.memory = memory if (memory is not None and self.p.enable_memory) else NoMemory() self.memory = memory if (memory is not None and self.p.enable_memory) else NoMemory()
self.readout = readout if (readout is not None and self.p.enable_mbon) else None self.readout = readout if (readout is not None and self.p.enable_mbon) else None
self.layout_full = layout self.layout_full = layout
@ -319,12 +316,7 @@ class FlyGuard:
else STRAIGHT) else STRAIGHT)
with t("lamina"): with t("lamina"):
dev = self.device lam = lamina.process(tf.r, tf.valid)
if dev.startswith("cuda"):
from .device import is_cuda_available
if not is_cuda_available():
self.device = dev = "cpu"
lam = lamina.process(tf.r, tf.valid, device=dev)
with t("ego"): with t("ego"):
ego = (self.ego_est.update(tf, pc.stamp) if self.p.enable_motion ego = (self.ego_est.update(tf, pc.stamp) if self.p.enable_motion

View file

@ -1,10 +0,0 @@
# Зависимости для запуска FlyGuard с поддержкой NVIDIA GPU (CUDA 12.x / Ada Lovelace RTX 4070 Ti Super)
--extra-index-url https://download.pytorch.org/whl/cu121
numpy>=1.24
scipy>=1.10
torch>=2.2.0
torchvision>=0.17.0
lightgbm>=4.0
catboost>=1.2
matplotlib>=3.7
pytest>=7.0

View file

@ -704,65 +704,3 @@ def test_benchmark_augmentation_logic():
assert sc.name == "человек_лежа" assert sc.name == "человек_лежа"
assert sc.v_lat == 0.15 assert sc.v_lat == 0.15
assert not sc.is_static assert not sc.is_static
def test_device_detection_and_fallback():
"""Проверка автоматического определения GPU устройства и fallback на CPU."""
from flyguard.device import get_device, get_device_info, is_cuda_available, to_numpy
dev = get_device("auto")
assert dev in ("cpu", "cuda") or dev.startswith("cuda:")
if not is_cuda_available():
assert get_device("cuda") == "cpu"
assert get_device("cpu") == "cpu"
info = get_device_info()
assert "device" in info
assert "torch_available" in info
arr = np.array([1.0, 2.0, 3.0], dtype=np.float32)
assert np.array_equal(to_numpy(arr), arr)
def test_lamina_device_routing():
"""Проверка работы Lamina с указанием устройства (cpu / auto)."""
from flyguard import lamina
r = np.full((16, 32), 20.0, dtype=np.float32)
r[8, 16] = 5.0 # препятствие
valid = np.ones((16, 32), dtype=bool)
out_cpu = lamina.process(r, valid, device="cpu")
out_auto = lamina.process(r, valid, device="auto")
assert out_cpu.on.shape == (16, 32)
assert out_auto.on.shape == (16, 32)
assert out_cpu.on[8, 16] > 0.0
assert np.allclose(out_cpu.on, out_auto.on, atol=1e-5)
def test_device_runtime_failure_and_fallback():
"""Проверка динамического перехода на CPU при сбое/отвале GPU в рантайме."""
from flyguard.device import is_cuda_available, get_device, notify_cuda_error, reset_device_cache
from flyguard import lamina
# Симуляция критического сбоя GPU
notify_cuda_error(RuntimeError("Simulated CUDA device disconnect / OOM"))
assert not is_cuda_available()
assert get_device("cuda") == "cpu"
assert get_device("auto") == "cpu"
r = np.full((16, 32), 20.0, dtype=np.float32)
r[8, 16] = 4.0
valid = np.ones((16, 32), dtype=bool)
# Даже при явном указании device="cuda", Lamina должна успешно отработать на CPU
out = lamina.process(r, valid, device="cuda")
assert out.on.shape == (16, 32)
assert out.on[8, 16] > 0.0
# Восстановление кэша
reset_device_cache()

View file

@ -106,8 +106,7 @@ def main() -> None:
ap.add_argument("--extra-cache", default=str(B.CACHE / "new_data_candidates.npz")) ap.add_argument("--extra-cache", default=str(B.CACHE / "new_data_candidates.npz"))
ap.add_argument("--limit", type=int, default=250) ap.add_argument("--limit", type=int, default=250)
ap.add_argument("--target", type=float, default=0.4) ap.add_argument("--target", type=float, default=0.4)
ap.add_argument("--device", default="auto", ap.add_argument("--device", default="cpu")
help="устройство вычислений ('auto', 'cuda', 'cpu')")
ap.add_argument("--split-adv", type=float, default=None, ap.add_argument("--split-adv", type=float, default=None,
help="порог разделения фигуры и фона; 0 — выключить") help="порог разделения фигуры и фона; 0 — выключить")
ap.add_argument("--split-gap", type=float, default=None, ap.add_argument("--split-gap", type=float, default=None,

View file

@ -288,8 +288,6 @@ def main() -> None:
help="пересоздавать препятствие впереди при приближении ближе 6 м") help="пересоздавать препятствие впереди при приближении ближе 6 м")
ap.add_argument("--static-prob", type=float, default=0.15, ap.add_argument("--static-prob", type=float, default=0.15,
help="вероятность сценария со стоячим поездом (ds = 0)") help="вероятность сценария со стоячим поездом (ds = 0)")
ap.add_argument("--device", default="auto", choices=["auto", "cuda", "cpu"],
help="устройство вычислений ('auto', 'cuda', 'cpu')")
P.add_argument(ap) P.add_argument(ap)
args = ap.parse_args() args = ap.parse_args()
@ -352,7 +350,6 @@ def main() -> None:
over["track_score"] = args.track_score over["track_score"] = args.track_score
if args.track_gate is not None: if args.track_gate is not None:
over["track_gate"] = args.track_gate over["track_gate"] = args.track_gate
over["device"] = args.device
params = Params(**over) params = Params(**over)
laterals = tuple(float(x) for x in args.laterals.split(",")) laterals = tuple(float(x) for x in args.laterals.split(","))

View file

@ -12,15 +12,12 @@ import numpy as np
import _bootstrap as B # noqa: F401 import _bootstrap as B # noqa: F401
from flyguard.bag import Bag, find_bags from flyguard.bag import Bag, find_bags
from flyguard.mushroom_body import MushroomBody from flyguard.mushroom_body import MushroomBody
from flyguard.mbon_readout import MbonReadout
from flyguard.pipeline import FlyGuard, Params from flyguard.pipeline import FlyGuard, Params
from flyguard.track_readout import TrackReadout
def run(bag_path, params: Params, memory, readout=None, track_readout=None, def run(bag_path, params: Params, memory, limit: int | None, verbose: bool) -> dict:
limit: int | None = None, verbose: bool = False) -> dict:
bag = Bag(bag_path) bag = Bag(bag_path)
fg = FlyGuard(params, memory=memory, readout=readout, track_readout=track_readout) fg = FlyGuard(params, memory=memory)
stages: dict[str, list[float]] = {} stages: dict[str, list[float]] = {}
n_det = n_frames = 0 n_det = n_frames = 0
dists, speeds, ncand = [], [], [] dists, speeds, ncand = [], [], []
@ -68,31 +65,15 @@ def main() -> None:
ap.add_argument("--all", action="store_true") ap.add_argument("--all", action="store_true")
ap.add_argument("--limit", type=int, default=150) ap.add_argument("--limit", type=int, default=150)
ap.add_argument("--memory", default=None) ap.add_argument("--memory", default=None)
ap.add_argument("--readout", default=None, help="модель MBON (mbon_readout.npz)")
ap.add_argument("--track-readout", default=None, help="модель TrackReadout (track_readout.npz)")
ap.add_argument("--device", default="auto", choices=["auto", "cuda", "cpu"],
help="устройство вычислений ('auto', 'cuda', 'cpu')")
ap.add_argument("--fov", type=float, default=30.0) ap.add_argument("--fov", type=float, default=30.0)
ap.add_argument("--verbose", action="store_true") ap.add_argument("--verbose", action="store_true")
args = ap.parse_args() args = ap.parse_args()
memory = MushroomBody.load(args.memory) if args.memory else None memory = MushroomBody.load(args.memory) if args.memory else None
readout = MbonReadout.load(args.readout) if args.readout else None params = Params(fov_deg=args.fov)
track_readout = TrackReadout.load(args.track_readout) if args.track_readout else None bags = find_bags(B.DATA / "for_hackathon") if args.all else [args.bag]
if not args.all and not args.bag:
args.all = True
params = Params(fov_deg=args.fov, device=args.device)
bag_root = (B.DATA / "for_hackathon") if (B.DATA / "for_hackathon").exists() else B.DATA
bags = find_bags(bag_root) if args.all else ([args.bag] if args.bag else [])
if not bags:
print(f"Внимание: бэги не найдены в {bag_root}. Убедитесь, что каталог смонтирован в FLYGUARD_DATA.")
return
for b in bags: for b in bags:
run(b, params, memory, readout=readout, track_readout=track_readout, run(b, params, memory, args.limit, args.verbose)
limit=args.limit, verbose=args.verbose)
if __name__ == "__main__": if __name__ == "__main__":

View file

@ -57,15 +57,14 @@ def main() -> None:
ap.add_argument("--out", default=str(B.ARTIFACTS / "mbon_readout.npz")) ap.add_argument("--out", default=str(B.ARTIFACTS / "mbon_readout.npz"))
ap.add_argument("--n-kc", type=int, default=0, ap.add_argument("--n-kc", type=int, default=0,
help="ёмкость итоговой модели; 0 — взять лучшую из развёртки") help="ёмкость итоговой модели; 0 — взять лучшую из развёртки")
ap.add_argument("--sweep-kc", default="4000,8000,20000,50000", ap.add_argument("--sweep-kc", default="4000,8000,20000",
help="ёмкости для развёртки; пусто — не разворачивать") help="ёмкости для развёртки; пусто — не разворачивать")
ap.add_argument("--active", type=int, default=100, ap.add_argument("--active", type=int, default=100,
help="активных клеток после торможения APL") help="активных клеток после торможения APL")
ap.add_argument("--epochs", type=int, default=60) ap.add_argument("--epochs", type=int, default=60)
ap.add_argument("--lr", type=float, default=4.0) ap.add_argument("--lr", type=float, default=4.0)
ap.add_argument("--l2", type=float, default=1e-5) ap.add_argument("--l2", type=float, default=1e-5)
ap.add_argument("--device", default="auto", ap.add_argument("--device", default="cpu")
help="устройство обучения ('auto', 'cuda', 'cpu')")
ap.add_argument("--baseline", action="store_true", ap.add_argument("--baseline", action="store_true",
help="сравнить с градиентным бустингом по сырым признакам") help="сравнить с градиентным бустингом по сырым признакам")
ap.add_argument("--save-folds", default=str(B.ARTIFACTS / "mbon_folds"), ap.add_argument("--save-folds", default=str(B.ARTIFACTS / "mbon_folds"),
@ -149,15 +148,9 @@ def main() -> None:
f"обстановки при 95% предметов {fpr_at_tpr(s, Y[held], 0.95):6.2%}") f"обстановки при 95% предметов {fpr_at_tpr(s, Y[held], 0.95):6.2%}")
if args.baseline: if args.baseline:
import lightgbm as lgb import lightgbm as lgb
dev_type = "gpu" if (args.device == "cuda" or (args.device == "auto" and B.Path("/dev/nvidia0").exists())) else "cpu" g = lgb.LGBMClassifier(n_estimators=400, learning_rate=0.05,
try: num_leaves=63, verbose=-1)
g = lgb.LGBMClassifier(n_estimators=400, learning_rate=0.05, g.fit(Xtr, ytr)
num_leaves=63, device=dev_type, verbose=-1)
g.fit(Xtr, ytr)
except Exception:
g = lgb.LGBMClassifier(n_estimators=400, learning_rate=0.05,
num_leaves=63, verbose=-1)
g.fit(Xtr, ytr)
line += f" | бустинг AUC {auc(g.predict_proba(X[held])[:, 1], Y[held]):.4f}" line += f" | бустинг AUC {auc(g.predict_proba(X[held])[:, 1], Y[held]):.4f}"
print(line, flush=True) print(line, flush=True)

View file

@ -63,8 +63,7 @@ def main() -> None:
ap.add_argument("--n-kc", type=int, default=_d.n_kc) ap.add_argument("--n-kc", type=int, default=_d.n_kc)
ap.add_argument("--claws", type=int, default=_d.claws) ap.add_argument("--claws", type=int, default=_d.claws)
ap.add_argument("--sparsity", type=float, default=_d.sparsity) ap.add_argument("--sparsity", type=float, default=_d.sparsity)
ap.add_argument("--device", default="auto", ap.add_argument("--device", default="cpu")
help="устройство вычислений ('auto', 'cuda', 'cpu')")
ap.add_argument("--reuse-cache", action="store_true") ap.add_argument("--reuse-cache", action="store_true")
args = ap.parse_args() args = ap.parse_args()

View file

@ -71,10 +71,8 @@ def main() -> None:
ap.add_argument("--sweep-kc", default="500,1000,2000,4000") ap.add_argument("--sweep-kc", default="500,1000,2000,4000")
ap.add_argument("--epochs", type=int, default=200) ap.add_argument("--epochs", type=int, default=200)
ap.add_argument("--lr", type=float, default=2.0) ap.add_argument("--lr", type=float, default=2.0)
ap.add_argument("--device", default="auto", ap.add_argument("--l2", type=float, default=1e-4)
help="устройство обучения ('auto', 'cuda', 'cpu')") ap.add_argument("--device", default="cpu")
ap.add_argument("--baseline", action="store_true",
help="сравнить с GPU/CPU градиентным бустингом (LightGBM)")
ap.add_argument("--drop", default="", ap.add_argument("--drop", default="",
help="признаки, выброшенные из обучения — это РЕЖИМ ЗАМЕРА, " help="признаки, выброшенные из обучения — это РЕЖИМ ЗАМЕРА, "
"модель при этом не сохраняется: конвейер считает " "модель при этом не сохраняется: конвейер считает "
@ -174,32 +172,9 @@ def main() -> None:
for held, s, _ in res: for held, s, _ in res:
print(f" {held:<42}AUC {auc(s, Y[held]):.4f}") print(f" {held:<42}AUC {auc(s, Y[held]):.4f}")
table_cols = {"улика: AUC": ev_loo, "модель: AUC": s_loo}
if args.baseline:
import lightgbm as lgb
s_lgb = []
for held in names:
tr = [n for n in names if n != held]
Xtr = np.concatenate([X[n] for n in tr])
ytr = np.concatenate([Y[n] for n in tr])
dev_type = "gpu" if (args.device == "cuda" or (args.device == "auto" and B.Path("/dev/nvidia0").exists())) else "cpu"
try:
g = lgb.LGBMClassifier(n_estimators=300, learning_rate=0.05, num_leaves=31,
device=dev_type, verbose=-1)
g.fit(Xtr, ytr)
except Exception:
g = lgb.LGBMClassifier(n_estimators=300, learning_rate=0.05, num_leaves=31,
verbose=-1)
g.fit(Xtr, ytr)
s_lgb.append(g.predict_proba(X[held])[:, 1])
s_lgb_arr = np.concatenate(s_lgb)
print(f"\nбустинг leave-one-bag-out: AUC {auc(s_lgb_arr, y_loo):.4f}, "
f"ложных при 95 % пойманных {fpr_at_tpr(s_lgb_arr, y_loo):.3f}")
table_cols["бустинг: AUC"] = s_lgb_arr
# ------------------------------- главное: обгоняет ли модель саму улику # ------------------------------- главное: обгоняет ли модель саму улику
print("\nмодель против улики, по полосам дальности:") print("\nмодель против улики, по полосам дальности:")
band_table(d_loo, y_loo, table_cols) band_table(d_loo, y_loo, {"улика: AUC": ev_loo, "модель: AUC": s_loo})
# --------------------------------------------------------------- хранение # --------------------------------------------------------------- хранение
if drop: if drop: