От хакатона до production — путь Scopiq
Я — DoctorM&Ai, врач-заведующий клинико-диагностической лабораторией (КДЛ) с 15+ годами за плечами в микроскопе и соло AI-разработчик. Каждый день я тону в мазках крови, мочи и спермы: лаборанты считают лейкоциты вручную, тратят по 10–15 минут на слайд, ошибаются на 15–20% из-за усталости или артефактов. А пациентов — очередь на неделю. В 2023-м я сказал "хватит этой херне" и на хакатоне слепил MVP Scopiq — AI для автоматизированного анализа микроскопических изображений.
С хакатона до production прошло 9 месяцев, 500+ часов кода, 3 переписанных архитектуры и куча фейлов. Сейчас Scopiq обрабатывает 1500+ слайдов в день в моей КДЛ, с точностью 97,8% на лейкоцитах и скоростью 8 секунд на слайд. Экономия — 80% времени лаборантов. Но путь был адским: от overfitting'а на 500 фото до Kubernetes-кошмаров на соло. Расскажу всё по-честному, с кодом, стеком и цифрами. Структура классика: проблема → решение → реализация → результат → выводы. Поехали!
Проблема: Почему ручной анализ в КДЛ — это пиздец
В КДЛ 80% времени уходит на рутинный морфологический анализ: подсчёт эритроцитов, лейкоцитов, эпителия в мазках крови, мочи, сперматозоидов. Нормы простые: в моче <5 лейкоцитов в поле зрения (п/з), в крови лейкоцитарная формула (ЛФ) по 100 клеткам.
Факты из моей практики (2022–2023): - Объём: 500–800 слайдов/день в средней КДЛ. - Время на слайд: 10–20 мин (лаборант смотрит 10–20 п/з под микроскопом ×100–400). - Ошибки: 12–18% (по внутренним аудитам). Причины: артефакты (кристаллы, слизь), переутомление (смены 12ч), дефицит кадров (в моей КДЛ уволилось 3/7 лаборантов за год). - Стоимость: 1 анализ ~500 руб, но задержки = потеря 20–30% выручки. - Цифры рынка: В РФ 5000+ КДЛ, глобально — $10B на ручной микроскопии (Statista 2023). AI в гематологии (как Sysmex) есть, но дорого ($100k+), не для мазков.
Я пробовал автоматизаторы вроде CellaVision — круто, но $50k/штука + калибровка под каждый микроскоп. Нужен дешёвый софт для веб-камеры на окуляре (стоимость ~5k руб). Проблема: изображения noisy (разные освещения, микроскопы), классы несбалансированы (эритроцитов 90%, патологии 1%).
Решение: Scopiq — AI-скоп в кармане
Scopiq — end-to-end пайплайн для анализа мазков: 1. Детекция объектов: Клетки/артефакты на слайде. 2. Классификация: Лейкоциты (нейтрофилы, лимфоциты и т.д.), эритроциты, эпителий, сперма, патологии (бактерии, кристаллы). 3. Подсчёт и отчёт: ЛФ, нормы, флаг аномалий. 4. Интеграция: Фото с телефона/вебки → анализ → PDF-отчёт.
MVP с хакатона: 85% accuracy на тесте. Production: 97,8% (F1-score). Ключ — transfer learning + domain adaptation под noisy данные КДЛ.
Задачи ML: - Object detection: YOLOv8 (быстро, mAP 0.92). - Segmentation: SAM (Segment Anything) для точных масок. - Classification: fine-tune ResNet50 (top-1 98%).
Цель: <10 сек/слайд, accuracy >95%, цена — 50 руб/анализ (cloud).
Реализация: От Streamlit-MVP до микросервисов
Этап 1: Хакатон (48 часов, ноябрь 2023, "AI in Med" — 1-е место из 50 команд)
Стек: минималистичный, соло. - Frontend: Streamlit (webapp за 50 строк). - Backend: Python 3.11, OpenCV 4.8, Keras 2.15 (TensorFlow 2.15). - ML: Собственная CNN на базе MobileNetV2 (input 512x512, 8 классов: neutro, lympho, mono, eosino, baso, erythro, epi, artifact). - Данные: 1200 фото с моего микроскопа (вебка Olympus + iPhone), аугментация (Albumentations: rotate, blur, contrast ±30%).
Код MVP (detektion + classif):
import streamlit as st
import cv2
import numpy as np
from tensorflow.keras.models import load_model
from tensorflow.keras.applications import MobileNetV2
from tensorflow.keras.layers import Dense, GlobalAveragePooling2D
from tensorflow.keras.models import Model
import albumentations as A
# Загрузка модели (fine-tune MobileNet)
base_model = MobileNetV2(weights='imagenet', include_top=False, input_shape=(512,512,3))
x = base_model.output
x = GlobalAveragePooling2D()(x)
x = Dense(128, activation='relu')(x)
predictions = Dense(8, activation='softmax')(x) # 8 классов
model = Model(inputs=base_model.input, outputs=predictions)
model.load_weights('hakatron_model.h5') # accuracy 82% на val
@st.cache_resource
def load_model():
return model
def preprocess(img):
transform = A.Compose([
A.Resize(512,512),
A.Normalize(mean=0.485, std=0.229), # ImageNet stats
A.HorizontalFlip(p=0.5),
A.RandomBrightnessContrast(p=0.3)
])
return transform(image=img)['image']
st.title("Scopiq MVP")
uploaded = st.file_uploader("Загрузи слайд")
if uploaded:
img = cv2.imdecode(np.frombuffer(uploaded.read(), np.uint8), 1)
img_proc = preprocess(img)
pred = model.predict(np.expand_dims(img_proc,0))[0]
classes = ['нейтрофилы','лимфоциты','моноциты','эозинофилы','базофилы','эритроциты','эпителий','артефакт']
counts = {c: int(p*100) for c,p in zip(classes, pred)} # Dummy count
st.bar_chart(counts)
Провал #1: Grid search на CPU — 12 часов, overfitting (train acc 98%, val 72%). Решение: dropout 0.5 + early stopping. На хакатоне accuracy 82% на 200 val фото, но путала артефакты с лимфо (F1=0.65).
Демо: 50 слайдов за 2 мин, судьи в шоке — 1-е место, приз 100k руб.
Этап 2: Прототип (1 месяц, декабрь 2023)
Переход на PyTorch + YOLOv8 (Ultralytics). - Данные: +5000 фото (собрал с коллег, Kaggle hemocell + synthetic via CutMix). - Тренировка: RTX 3060 (локально), 10 epochs, batch 16, lr=0.001 (AdamW). - Стек: FastAPI + Uvicorn, Celery+Redis для async inference, PostgreSQL (SQLAlchemy).
Архитектура прототипа:
[Telegram Bot / Web] --> FastAPI (upload img)
|
v
[Preprocess: OpenCV + Albumentations]
|
v
[YOLOv8 detect] --> crop cells --> [SAM segment] --> [ResNet50 classif]
| |
v v
[Postprocess: count + stats] --> PDF (ReportLab) --> [DB store]
Код YOLO training:
# data.yaml
train: /data/train
val: /data/val
nc: 8 # classes
names: ['neutro', 'lympho', ...]
from ultralytics import YOLO
model = YOLO('yolov8n.pt') # nano для speed
model.train(data='data.yaml', epochs=50, imgsz=640, batch=32, device=0)
# Результат: mAP@0.5=0.85, inference 15ms/img на GPU
Провал #2: YOLO ловил клетки, но SAM (Meta) жрал 2GB VRAM и лагал на 1080p слайдах. Фикс: downscale to 1024x768 + ONNX export для CPU-fallback. Val mAP вырос с 0.78 до 0.91.
Deploy: Docker на VPS (Hetzner 4vCPU/16GB, 500руб/мес).
FROM python:3.11-slim
COPY . /app
RUN pip install -r requirements.txt # fastapi, ultralytics, torch, onnxruntime
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
Тестирование: 95 лаборантов, 2000 слайдов. Accuracy 89%, но false positives на slime (слизь) — 25%.
Этап 3: Production (январь–июль 2024, 5 итераций)
Полный стек: - Backend: FastAPI 0.104 (REST + WebSockets для live preview), Pydantic v2. - ML: PyTorch 2.1 + TorchServe (serving), MLflow (tracking: 150+ runs). - Queue: Celery 5.3 + Redis 7 + Flower dashboard. - DB: Postgres 15 + TimescaleDB (time-series для stats), MinIO (S3-like для imgs). - Frontend: HTMX + Alpine.js (no React, соло-friendly), Tailwind. - Deploy: Docker Compose → Kubernetes (k3s на 2-node cluster, DigitalOcean 20$/мес). CI/CD: GitHub Actions. - Мониторинг: Prometheus + Grafana, Sentry для ошибок.
Архитектура production:
Users (Web/Telegram/Mobile app)
|
Nginx (rate limit 100/min)
|
FastAPI Gateway (auth JWT)
/ | \
Preproc ML Queue DB
|
Celery Workers (YOLO+SAM+Classif)
|
TorchServe (GPU pod)
|
MinIO (store raw/preds)
Ключевой код: ML Pipeline (Celery task):
from celery import Celery
from ultralytics import YOLO
import onnxruntime as ort
import torch
from segment_anything import sam_model_registry
app = Celery('scopiq', broker='redis://localhost')
# Load models (singleton)
yolo_model = YOLO('scopiq_yolo.pt')
sam = sam_model_registry['vit_b']('sam_vitb.pt')
classif_sess = ort.InferenceSession('resnet50.onnx')
@app.task
def analyze_slide(image_path: str):
img = cv2.imread(image_path)
# Detect
results = yolo_model(img, conf=0.5, verbose=False)
boxes = results[0].boxes.xyxy.cpu().numpy()
counts = {c: 0 for c in CLASSES}
for box in boxes:
crop = crop_cell(img, box)
mask = sam_predict(sam, crop) # SAM mask
feat = extract_features(crop, mask) # ResNet feats
pred_class = classif_sess.run(None, {'input': feat})[0]
counts[pred_class] += 1
# Stats: LF = counts / sum(counts) * 100
return generate_report(counts)
Тренировка prod-модели: - Dataset: 15k изображений (собранные + public: 8k, synthetic 7k via Diffusers StableDiffusion). - Aug: RandAug + MixUp (alpha=0.2). - Hyperparams: CosineAnnealingLR, SWA (Stochastic Weight Averaging). - Hardware: Colab Pro (A100) + локальный 3060. Время: 48h train, MLflow logged: loss 0.12, mAP 0.94.
Провалы и фиксы (честно): 1. Overfitting v1: Train 99%, prod 81%. Фикс: 5-fold CV, +3k OOF данных. Время: 2 недели. 2. Latency spikes: Celery queue >5s. Фикс: Gunicorn workers=4, Redis AOF persistence. P95 -> 3s. 3. GPU OOM в K8s: TorchServe жрал 12GB. Фикс: FP16 + Torch.compile (PyTorch 2.1), pod limits 8GB. 4. Drift: Модель путала новые микроскопы (Canon vs Olympus). Фикс: Active Learning — фидбек лаборантов в DB, retrain ежемесячно (LoRA adapters, +2% acc). 5. Безопасность: SQLi в early FastAPI. Фикс: Pydantic strict + bandit scans. 0 vulns в prod. 6. Scale fail: 500 req/min — K8s crash. Фикс: HPA (Horizontal Pod Autoscaler), от 2 до 10 pods.
Бюджет: 150k руб (hardware/cloud), ROI: окупаемость за 2 мес (экономия 2 лаборанта × 80k/мес).
Результат: Цифры не врут
Метрики ML (prod, 10k слайдов, август 2024): | Метрика | Хакатон | Прототип | Production | Цель | |---------|---------|----------|------------|------| | mAP@0.5 (YOLO) | 0.72 | 0.85 | 0.94 | >0.90 | | F1-classif | 0.78 | 0.89 | 0.978 | >0.95 | | Inference time | 2.5s | 1.2s | 0.8s (CPU)/0.12s (GPU) | <1s | | False Pos/Neg | 22% | 11% | 2.2% | <5% |
Бизнес-результаты: - Объём: 1500 слайдов/день (×3 от manual). - Точность: 97.8% совпадение с экспертами (kappa 0.95, audit 500 слайдов). - Экономия: 75% времени (15мин → 20сек + 10сек review). - Пользователи: Моя КДЛ + 5 партнёров (200+ анализов/день). - Revenue: 50 руб/анализ × 45k/мес = 2.25M руб/мес (маржа 80%). - Uptime: 99.7% (Grafana дашборд).
График drift (MLflow):
Loss: train 0.08 → prod 0.15 (стабильно после LoRA).
Отзывы: "Сопик спасает жопу в пики" — старший лаборант.
Выводы: Уроки соло-доктора-AI
Scopiq — не идеал, но работает. Что пошло круто: YOLO+SAM stack (SOTA speed/acc), соло-deploy K8s (экономия 500k на команде), data flywheel (feedback loop).
Честные фейлы: - Время: 9 мес вместо 3 (data collection — 40% усилий). - Баги: 150+ Sentry issues, 20% — edge cases (грязные слайды). - Стоимость: Cloud 30k/мес, но ROI 10x. - Масштаб: Пока РФ, экспорт тормозит FDA-like cert (нужен ISO 13485).
Уроки: 1. Data first: 80% успеха — датасет. Используй synthetic + active learning. 2. MVP → Prod: Streamlit → FastAPI+Celery = win. K8s соло possible, но start с Compose. 3. Мониторь drift: Z-score на predictions, retrain auto. 4. Соло-limits: No deep learning ops (используй no-code как Roboflow). 5. Медицина: Cert > всё. Тестируй на реальных 10k+ cases.
Будущее: Мобильное app (iOS/Android), интеграция с 1C-Мед, multi-lang. Если внедряешь — пиши, поделю репой (github.com/doctormai/scopiq, MIT license). Вопросы? Комменты