Автор: Дмитро Рєзєнков Варіант: 12 Дата: 2026-05-06
Повноцінний відтворюваний ML-проект, що демонструє інтеграцію Git, DVC, MLflow та scikit-learn Pipeline для задачі багатокласової класифікації текстів.
- Датасет: 20 Newsgroups (
sklearn.datasets.fetch_20newsgroups), підмножина 4 категорій:alt.atheism,comp.graphics,sci.med,soc.religion.christian(3 669 документів) — стандартний sklearn-бенчмарк, дозволяє провести 7 експериментів за прийнятний час на CPU. Перехід на повні 20 класів робиться зміною спискуCATEGORIESуsrc/data/create_dataset.py. - Основна модель: Multinomial Naive Bayes
- Альтернативні моделі: Logistic Regression, Linear SVC
MLOps/
├── data/
│ ├── raw/ # Сирий датасет (під DVC)
│ ├── processed/ # Оброблені дані
│ └── external/ # Зовнішні джерела
├── models/ # Серіалізовані моделі (під DVC)
│ └── pipeline.pkl # Найкращий pipeline
├── notebooks/ # Дослідницькі ноутбуки
├── scripts/
│ ├── run_experiments.py # Запускає всі 7 експериментів
│ ├── train_best_pipeline.py # Тренує final pipeline на повному датасеті
│ ├── experiments_summary.json # Зведення метрик усіх runs
│ └── best_run.json # Параметри найкращого run
├── src/
│ ├── data/create_dataset.py # Завантаження 20 Newsgroups -> CSV
│ ├── models/
│ │ ├── pipeline.py # Фабрика Pipeline (TF-IDF + класифікатор)
│ │ └── train.py # Тренування одного run з повним логуванням MLflow
│ └── utils.py # load_model_from_file, load_model_from_mlflow, predict
├── tests/
├── .gitignore
├── requirements.txt
└── README.md
Розділення data/raw → processed → external:
raw/— сирі, незмінні дані (single source of truth)processed/— результат feature engineering, відтворюваний з rawexternal/— дані з третіх джерел (довідники, лексикони)
- Python 3.10+
- Git
- ~500 МБ вільного місця
git clone <url>
cd MLOps
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txtdvc pullЯкщо локальний remote (/tmp/dvc-storage) недоступний, перегенеруйте:
python -m src.data.create_datasetmlflow ui --port 5000 & # UI на http://localhost:5000
python scripts/run_experiments.py # 7 runs у експерименті lab1-variant12python scripts/train_best_pipeline.py
dvc add models/pipeline.pkl
git add models/pipeline.pkl.dvc && git commit -m "Update best pipeline"
dvc pushfrom src.utils import load_model_from_file, predict
model = load_model_from_file('models/pipeline.pkl')
predict(model, ['A new vaccine reduces side effects in clinical trials'])
# array([2]) # 2 = sci.medУсі експерименти використовують train_test_split(test_size=0.2, random_state=42, stratify=y) та 5-fold cross-validation на train. Pipeline: TfidfVectorizer (stop_words='english', min_df=2, sublinear_tf=True) → classifier.
| Run | Модель | Гіперпараметри | CV mean ± std | Test Acc | Test F1 |
|---|---|---|---|---|---|
svc_C0.5_bi |
LinearSVC | C=0.5, max_features=20000, ngram=(1,2) | 0.8848 ± 0.0082 | 0.8856 | 0.8842 |
logreg_C10_bi |
LogReg | C=10.0, max_features=20000, ngram=(1,2) | 0.8814 ± 0.0107 | 0.8801 | 0.8788 |
nb_alpha0.1_uni |
NB | α=0.1, max_features=10000, ngram=(1,1) | 0.8804 ± 0.0096 | 0.8787 | 0.8777 |
svc_C1_uni |
LinearSVC | C=1.0, max_features=10000, ngram=(1,1) | 0.8811 ± 0.0095 | 0.8787 | 0.8776 |
nb_alpha0.5_bi |
NB | α=0.5, max_features=10000, ngram=(1,2) | 0.8637 ± 0.0091 | 0.8692 | 0.8666 |
logreg_C1_uni |
LogReg | C=1.0, max_features=10000, ngram=(1,1) | 0.8790 ± 0.0096 | 0.8665 | 0.8646 |
nb_alpha1.0_uni |
NB | α=1.0, max_features=5000, ngram=(1,1) | 0.8504 ± 0.0047 | 0.8610 | 0.8578 |
- Pipeline:
TfidfVectorizer(max_features=20000, ngram_range=(1,2)) → LinearSVC(C=0.5, max_iter=2000, random_state=42) - test_f1_weighted: 0.8842
- test_accuracy: 0.8856
- cv_mean_accuracy: 0.8848 ± 0.0082
- MLflow run_id:
27d7522fab36444e825d27a242981cf4(експериментlab1-variant12) - Final retrained run_id:
7c209cefb4d34791a7143e909165384c(експериментlab1-variant12-final)
- α у NB: зменшення з 1.0 → 0.1 дало +2.0 п.п. F1 (менше згладжування — більше уваги до рідкісних термінів, що важливо для коротких документів).
- n-grams: додавання біграмів покращило LogReg (+1.4 п.п.) та LinearSVC (+0.7 п.п.) ціною x4 збільшення
max_features. Для NB ефект слабший — біграми вводять колінеарні ознаки, які NB обробляє гірше через припущення незалежності. - C у LogReg/SVC: збільшення дозволяє моделі підлаштуватись під складніші межі; для LogReg C=10 з біграмами дав +1.4 п.п. F1.
- max_features: збільшення з 5k до 20k стабільно покращувало результати, але virtual diminishing returns після ~10k для уніграмів.
Відтворення на новій машині:
# 1. Клонування проекту
git clone <repo-url>
cd MLOps
# 2. Залежності
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
# 3. Дані з DVC remote
dvc pull
# 4. Запуск експериментів
python scripts/run_experiments.py
# 5. Перегляд результатів
mlflow ui --port 5000Повернення до старішої версії датасету або моделі:
git log --oneline -- data/raw/dataset.csv.dvc # знайти потрібний commit
git checkout <commit-hash> data/raw/dataset.csv.dvc
dvc checkout data/raw/dataset.csv.dvc1. Що таке MLOps і які проблеми він вирішує? Назвіть п'ять основних компонентів MLOps інфраструктури.
MLOps — набір практик, що поєднує розробку ML-моделей з операційними процесами DevOps для автоматизації побудови, тестування, деплою та супроводу ML-систем.
Проблеми, які вирішує:
- Відтворюваність: фіксація даних, коду, гіперпараметрів і середовища.
- Версіонування великих артефактів: датасетів і моделей, для яких Git непридатний.
- Простежуваність експериментів: хто/коли/з якими параметрами тренував яку модель.
- Continuous training & deployment: автоматизація retraining та rollout у продакшн.
- Моніторинг моделей: виявлення data drift, concept drift, деградації якості в проді.
П'ять основних компонентів:
- Version Control: код (Git), дані та моделі (DVC, LakeFS, MLflow Registry).
- Experiment Tracking: MLflow, W&B, Neptune.
- Pipeline Orchestration: Airflow, Kubeflow, Prefect, Dagster.
- Model Serving / Deployment: MLflow Models, TorchServe, BentoML, KServe.
- Monitoring & Observability: Evidently, Prometheus, Grafana, дашборди для метрик якості та системних метрик.
2. Чому Git не підходить для версіонування великих датасетів? Які компоненти ML-проекту потребують версіонування?
Git зберігає повний blob кожної версії файлу в .git/objects через delta-compression, оптимізовану для текстових (рядкових) змін. Для бінарних або дуже великих файлів це призводить до:
- Експоненційного зростання
.git/: датасет 1 ГБ із 10 версіями = ~10 ГБ репо. - Повільних
clone/fetch/checkout(кожен користувач завантажує всю історію). - Неефективного diff: бінарні зміни не дельтуються — кожна версія зберігається повністю.
- GitHub обмежує файли > 100 МБ; LFS — 1 ГБ безкоштовно і платний понад.
Версіонувати в ML-проекті потрібно:
- Код:
*.py, конфіги (YAML, JSON), Dockerfile, скрипти. - Дані: raw, processed, external — через DVC.
- Моделі: serialized артефакти (
.pkl,.onnx,.h5) — через DVC або MLflow Registry. - Гіперпараметри і параметри тренування — у MLflow (params).
- Метрики експериментів — у MLflow (metrics).
- Залежності:
requirements.txt,pyproject.toml,Pipfile.lock. - Pipelines: послідовність етапів (DVC pipelines, MLflow Projects).
.dvc-файл — текстовий YAML-метафайл, що містить:
md5(контрольну суму вмісту реального файлу),size(розмір),path(відносний шлях),- (опціонально) залежності, outs, deps для DVC pipelines.
Приклад з цього проекту:
outs:
- md5: dea055c4e3f378e7d24cfebfb6e2e6c4
size: 5205498
hash: md5
path: dataset.csv.dvc-файл маленький (< 1 КБ) — його можна спокійно зберігати в Git. Сам датасет лежить у DVC remote (S3, GDrive, локальна папка) і витягується через dvc pull.
Різниця:
| Команда | Куди потрапляє файл |
|---|---|
git add file.csv |
Повний вміст потрапляє в .git/objects — погано для великих файлів. |
dvc add file.csv |
Створює file.csv.dvc (метадані) + переносить файл у .dvc/cache, додає до .gitignore. У Git іде лише метафайл. |
Workflow: dvc add → git add file.csv.dvc .gitignore → git commit → dvc push (фактичні дані → remote).
Чотири компоненти:
- MLflow Tracking — API + UI для логування параметрів, метрик, артефактів і коду під час тренування.
- MLflow Projects — стандартизований формат пакування ML-коду (MLproject + conda.yaml) для відтворюваного запуску.
- MLflow Models — універсальний формат серіалізації моделі з метаданими (signature, environment, flavor) для деплою на різні платформи.
- MLflow Model Registry — централізоване сховище версій моделей з life-cycle stages (None / Staging / Production / Archived).
Поняття:
- Experiment — логічна група пов'язаних runs, що відповідає одній задачі/гіпотезі (у нас:
lab1-variant12). - Run — одне виконання тренувального коду з конкретним набором параметрів. Ідентифікується унікальним
run_id. - Parameter — вхідне значення run (гіперпараметр моделі,
random_state,test_size). Логуються черезmlflow.log_param/log_params. - Metric — числовий вихід run (accuracy, f1, loss). Логуються через
mlflow.log_metric/log_metrics. Можуть мати timestamp/step для часових серій. - Artifact — будь-який файл, прив'язаний до run (модель, графік, classification_report, confusion_matrix). Логується через
mlflow.log_artifactабо типізованіmlflow.<flavor>.log_model.
Pipeline інкапсулює послідовність кроків ((name, transformer) для preprocessing і фінальний estimator) як єдиний об'єкт, що реалізує fit/transform/predict. У нашому проекті:
Pipeline([('tfidf', TfidfVectorizer(...)), ('clf', LinearSVC(...))])Запобігання data leakage:
Без Pipeline типова помилка — застосувати fit_transform на ВСЬОМУ датасеті ДО train_test_split:
X_scaled = scaler.fit_transform(X) # бачить test!
X_train, X_test = train_test_split(X_scaled)Тоді статистики (mean, std, IDF, словник) обчислюються з урахуванням тестових даних, що штучно завищує метрики.
Pipeline розв'язує це автоматично: при pipeline.fit(X_train, y_train) кожен transformer виконує fit ТІЛЬКИ на train; при pipeline.predict(X_test) ті ж самі transformers роблять лише transform (з параметрами, навченими на train). У cross-validation це особливо важливо — fit для кожного fold відбувається лише на train fold.
Додаткові переваги: один сериалізований об'єкт містить весь pipeline (preprocessing + model), що спрощує deployment і запобігає розсинхронізації preprocessing між тренуванням і інференсом.
6. Обґрунтуйте структуру директорій вашого проекту. Чому важливо розділяти raw/processed/external дані?
Структура слідує канонічному cookiecutter-data-science layout, доповненому MLOps-специфічними каталогами (models/, scripts/).
Розділення data/:
- raw/ — сирі, незмінні дані (single source of truth). Будь-яке відтворення має починатися звідси. У нас — оригінальний CSV від
fetch_20newsgroups. - processed/ — результат препроцесингу (feature engineering, очищення, encode). Має бути повністю відтворюваним з raw скриптами в
src/data/. Не зберігається довго — переробляється за потреби. - external/ — дані з третіх джерел (словники, embeddings, lookup-таблиці), які ми не контролюємо, але від яких залежать процеси.
Чому це важливо:
- Відтворюваність: raw зафіксований у DVC, processed виводиться з raw + коду — отже, експеримент завжди можна відновити.
- Розділення відповідальності: raw не зачіпає логіка препроцесингу, що знижує ризик випадкового пошкодження.
- Економія сховища: processed можна не пушити у DVC remote, бо він відтворюється.
- Аудит: видно, які саме external джерела використано і коли вони змінилися.
Каталоги src/data, src/features, src/models дзеркалюють етапи pipeline, а scripts/ містить orchestration-точки (entry points, які не імпортують одне одного).
git clone <repo-url> mlops-lab1
cd mlops-lab1
python3 -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
dvc pull # тягне data/raw/dataset.csv та models/pipeline.pkl з remote
ls data/raw/ models/ # перевірка
python -c "from src.utils import load_model_from_file; print('OK', load_model_from_file('models/pipeline.pkl').named_steps.keys())"Якщо remote недоступний (наприклад, локальний /tmp/dvc-storage був видалений):
python -m src.data.create_dataset # перегенерувати датасет
python scripts/train_best_pipeline.py # перетренувати модель8. Колега хоче відтворити ваш експеримент. Які інструкції дати? Потрібно повернутись до моделі тижневої давності. Як?
Інструкції відтворення:
git clone <url> && cd MLOpspython3 -m venv .venv && source .venv/bin/activatepip install -r requirements.txt— фіксовані версії гарантують ідентичні залежності.dvc pull— отримати датасет і модель з remote.python scripts/run_experiments.py— запустити всі 7 runs (random_state=42гарантує однакові train/test splits і CV-folds).mlflow ui --port 5000— порівняти отримані метрики зscripts/experiments_summary.json— мають збігатися bit-to-bit.
Повернення до моделі тижневої давності:
# 1. Знайти commit тижневої давності, що змінював модель
git log --since='1 week ago' --until='6 days ago' -- models/pipeline.pkl.dvc
# 2. Відновити .dvc-файл з того commit
git checkout <commit-hash> -- models/pipeline.pkl.dvc
# 3. Витягти відповідну версію моделі з DVC cache/remote
dvc checkout models/pipeline.pkl.dvc
# 4. (Опційно) повернути увесь стан репо на той момент
git checkout <commit-hash>
dvc checkoutДля звітів MLflow аналогічно — кожен run має run_id, через mlflow.sklearn.load_model(f"runs:/{run_id}/pipeline") модель відновлюється точно з того стану.
Pipeline vs. без Pipeline:
Ми не запускали окремий експеримент «без Pipeline», але data leakage чітко продемонстрований у літературі: при TfidfVectorizer.fit_transform на повних даних до split-у IDF обчислюється з урахуванням тесту, тестовий accuracy зазвичай завищений на 0.5–2 п.п. Cross-validation у такому варіанті ще оптимістичніший — fold-leak через спільний словник.
З Pipeline: TfidfVectorizer.fit викликається лише на train fold; для невідомих у train слів TF-IDF присвоює 0 — що чесно відображає реальну ситуацію продакшну.
Вплив гіперпараметрів на метрики (з нашої таблиці):
| Гіперпараметр | Зміна | Δ test_f1 |
|---|---|---|
MultinomialNB.alpha |
1.0 → 0.1 (з max_features 5k → 10k) | +0.0199 |
MultinomialNB.alpha |
0.1 → 0.5 + bigrams (10k features) | -0.0111 (bigrams шкодять NB через незалежність ознак) |
LogReg.C + n-grams |
C=1, uni → C=10, bi (10k → 20k) | +0.0142 |
LinearSVC.C + n-grams |
C=1, uni → C=0.5, bi (10k → 20k) | +0.0066 |
ngram_range |
(1,1) → (1,2) | +0.0066 для SVC, +0.0142 для LogReg, -0.0111 для NB |
max_features |
5k → 10k → 20k | стабільне зростання, але плато після ~10k для уніграмів |
Ключові висновки:
- Linear-моделі (LogReg, SVC) виграють від bigrams + більший словник, бо вони здатні навчитися ваг на корельованих ознаках.
- NB чутливий до згладжування (
alpha); великийalpha«розмиває» ймовірності рідкісних інформативних слів. - NB не виграє від bigrams, бо порушується припущення умовної незалежності.
- CV (5-fold) дає
std ≈ 0.008–0.010— отже, різниці < 0.005 між моделями статистично малозначущі.
| Проблема | Рішення |
|---|---|
ModuleNotFoundError: No module named 'pip' |
Системний Python без pip. Створіть venv: python3 -m venv .venv && source .venv/bin/activate, потім python -m ensurepip --upgrade. |
ModuleNotFoundError: pkg_resources (MLflow) |
pip install 'setuptools<81'. |
dvc init падає з cannot import _DIR_MARK from pathspec |
Несумісна версія pathspec: pip install 'pathspec<0.12'. |
dvc add → bad DVC file name ... is git-ignored |
Додайте у .gitignore рядок !*.dvc після /data/raw/*. |
dvc pull → failed to pull data from the cloud |
Локальний remote /tmp/dvc-storage зник після ребуту. Виконайте python -m src.data.create_dataset && dvc add data/raw/dataset.csv ще раз. |
mlflow ui → порт 5000 зайнятий |
mlflow ui --port 5001 або вбити процес: lsof -ti:5000 | xargs kill. |
LinearSVC warning dual will change |
Це майбутній breaking-change у sklearn 1.5; не впливає на результати в 1.4.x. Або явно вкажіть dual='auto'. |
| Великий час тренування | Зменшіть max_features, перейдіть на 2 категорії, або скористайтесь n_jobs=-1 у cross_val_score (вже включено). |
- DVC. https://dvc.org/doc
- MLflow. https://mlflow.org/docs/latest/index.html
- scikit-learn Pipeline. https://scikit-learn.org/stable/modules/compose.html
- Git. https://git-scm.com/doc