Навчальний простір / Python
Основи штучного інтелекту

UCI Bike Sharing: часова валідація та ознаки без витоку

Часова валідація має відповідати моменту й горизонту прогнозу.

Зміст матеріалу

Мета та потрібні знання

Побудувати регресійну модель на відкритих часових даних, перевірити її на майбутньому періоді та дослідити інженерію ознак. Потрібно знати train/test, MAE, baseline, дерево рішень та часовий порядок. Демонстраційний набір — UCI Bike Sharing id275, day.csv. Результат — протокол часу, train-only порівняння моделей і фінальний звіт із графіком помилок.

Теоретичний мінімум і паспорт

Один рядок — календарний день прокату велосипедів; cnt є сумарною кількістю оренд. Casual і registered — дві частини цього самого підсумку. Їх включення у X майже безпосередньо розкриває ціль. Постановка: спрогнозувати попит на день до його початку. Для демонстрації використовуємо лише календарні поля, які відомі заздалегідь.

Фактична температура, вологість і погода цього дня до його завершення невідомі. Прогноз погоди міг би бути окремим джерелом, але таблиця містить спостережені характеристики, тому їх виключаємо. Instant є номером запису; він не замінює предметний аналіз часового тренду. MAE описує середню абсолютну помилку в орендах за день.

Паспорт: UCI id275, DOI 10.24432/C5W894, автор Hadi Fanaee-T, файл day.csv, ліцензія CC BY 4.0. Таблиця має 731 рядок і 16 колонок, охоплює 2011–2012 роки. Висновок обмежений цим містом і періодом; він не підтверджує роботу моделі в іншій транспортній системі.

Підготовка середовища: Kaggle або локальний Python

У Kaggle створіть Python Notebook у розділі Code, увімкніть Internet у налаштуваннях сесії для завантаження з UCI та запускайте код комірками згори вниз. Якщо доступ до Internet у вашому обліковому записі недоступний, завантажте CSV з офіційного UCI-каталогу вручну й підключіть його як вхідні дані notebook. У панелі Input знайдіть фактичний шлях файлу; використайте його в read_csv. У notebook можуть бути попередньо встановлені потрібні пакети; перевірте імпорт перед додатковим встановленням.

Локально створіть віртуальне середовище: python3 -m venv .venv, активуйте source .venv/bin/activate на macOS/Linux. У Windows використайте py -m venv .venv і .venv\Scripts\Activate.ps1. Встановіть python -m pip install numpy pandas matplotlib scikit-learn. Збережіть версії через python -m pip freeze > requirements-lock.txt. У Kaggle збережіть виконану версію notebook разом із результатами.

Каталог Kaggle містить набори різного походження та з різними ліцензіями. Відкритість перевіряємо за сторінкою конкретного dataset: джерело, дозвіл використання, версія, автор, опис полів. Для власного Kaggle-набору обов’язково додайте посилання на першоджерело. API token для наведеного прямого завантаження UCI не потрібен.

Повний демонстраційний код

python
from pathlib import Path
from urllib.request import urlopen
from zipfile import ZipFile
from io import BytesIO
from hashlib import sha256
import pandas as pd

cache = Path("data")
cache.mkdir(exist_ok=True)
archive = cache / "uci-275.zip"
if not archive.exists():
    url = "https://archive.ics.uci.edu/static/public/275/bike+sharing+dataset.zip"
    with urlopen(url, timeout=30) as response:
        archive.write_bytes(response.read())
print("Archive SHA256:", sha256(archive.read_bytes()).hexdigest())
with ZipFile(archive) as outer:
    with outer.open("day.csv") as source:
        df = pd.read_csv(source, sep=",")

import numpy as np
import matplotlib.pyplot as plt
from sklearn.model_selection import TimeSeriesSplit, cross_val_score
from sklearn.dummy import DummyRegressor
from sklearn.ensemble import RandomForestRegressor
from sklearn.metrics import mean_absolute_error

assert df.shape == (731, 16)
df["dteday"] = pd.to_datetime(df["dteday"], errors="raise")
df = df.sort_values("dteday").reset_index(drop=True)
assert df["dteday"].is_unique
assert df["dteday"].diff().dropna().eq(pd.Timedelta(days=1)).all()
features = ["season", "yr", "mnth", "holiday", "weekday", "workingday"]
X = df[features].copy()
y = df["cnt"].copy()
assert not X.isna().any().any()
assert (y >= 0).all()
cut = int(len(df) * 0.8)
X_train, X_test = X.iloc[:cut], X.iloc[cut:]
y_train, y_test = y.iloc[:cut], y.iloc[cut:]
cv = TimeSeriesSplit(n_splits=3, gap=1)
models = {
    "mean": DummyRegressor(strategy="mean"),
    "forest": RandomForestRegressor(n_estimators=100, max_depth=5,
                                     random_state=42, n_jobs=1)
}
for name, estimator in models.items():
    scores = -cross_val_score(estimator, X_train, y_train, cv=cv,
                              scoring="neg_mean_absolute_error")
    print(name, "fold MAE:", scores, "mean:", scores.mean())
# Фіксуємо демонстраційну модель до відкриття test.
model = models["forest"].fit(X_train, y_train)
predicted = model.predict(X_test)
print("Test MAE:", mean_absolute_error(y_test, predicted))
print("Test dates:", df["dteday"].iloc[cut], df["dteday"].iloc[-1])
output = Path("results")
output.mkdir(exist_ok=True)
result = pd.DataFrame({"date": df["dteday"].iloc[cut:].to_numpy(),
                       "actual": y_test.to_numpy(), "predicted": predicted})
result.to_csv(output / "bike-test.csv", index=False)
plt.plot(result["date"], result["actual"], label="Actual")
plt.plot(result["date"], result["predicted"], label="Predicted")
plt.ylabel("Rentals per day")
plt.legend()
plt.gcf().autofmt_xdate()
plt.tight_layout()
plt.savefig(output / "bike-test.png")
plt.close()

Пояснення коду та очікуваний результат

Порядок дат визначає split: перші 584 дні є development/train, останні 147 — test. TimeSeriesSplit розширює минулий training-відрізок і перевіряє наступний. Gap1 залишає один день між частинами; це демонстрація відступу, який у реальній задачі визначають за затримкою доступності даних. Для лагових ознак також потрібно перевірити спосіб їх побудови.

Scikit-learn повертає від’ємний MAE як scoring, тому змінюємо знак для читання помилки. DummyRegressor прогнозує середнє лише training-відрізка кожного fold. Forest має фіксовану обмежену глибину; фінальний test відкривається після визначення цього кандидата. Очікуються CV-оцінки, реальний test MAE, CSV і графік. Числа залежатимуть від запуску; впишіть отримані значення та версії.

Покрокове виконання

Заповніть паспорт і таблицю «поле — час доступності — рішення включення». Відтворіть код. Для кожного fold випишіть першу й останню дату train/validation за індексами cv.split. Переконайтеся, що train закінчується раніше за validation. Поясніть, чому випадкове перемішування не відповідає прогнозуванню майбутнього в цій постановці.

Порівняйте MAE на всіх folds, а не лише середнє. Зміна попиту між роками може створювати різну складність. На test-графіку опишіть періоди найбільших помилок та напрямок відхилення. Після перегляду test не добирайте глибину за цим графіком; новий цикл підбору вимагає іншої незалежної оцінки.

Самостійне завдання та інженерія ознак

На development-частині порівняйте календарні ознаки з циклічним представленням місяця: sin(2πm/12) і cos(2πm/12). Воно задає близькість грудня й січня у двовимірному просторі. Фіксоване календарне перетворення не використовує target. Побудуйте його самостійно, перевірте крайні місяці та порівняйте однакові часові folds. Не очікуйте гарантованого покращення для кожної моделі.

Альтернативний варіант — відкритий часовий dataset із Kaggle або UCI з документованою датою й числовою ціллю. Зафіксуйте горизонт прогнозу, джерело, ліцензію, частоту та пропуски часу. Якщо використовуєте lag, визначте, чи прогноз виконується щодня з новими спостереженнями або на весь період одразу. Справжні цілі попередніх test-днів не можна автоматично використовувати для багатокрокового прогнозу без відповідного протоколу.

Подайте дві заздалегідь визначені конфігурації, baseline, train-only оцінки, обґрунтування остаточного вибору та один фінальний test-звіт. Основне дослідження ознак або нового dataset виконайте самостійно. Результат може показати відсутність покращення; пояснення цього результату є частиною роботи.

Типові помилки та налагодження

Дуже мала помилка з casual і registered означає витік цілі. Unsorted dates порушують часовий split. Пропущені дні змінюють зміст індексного відступу; перевірка регулярності має відповідати вашому набору. Неправильний знак neg_mean_absolute_error створює хибну інтерпретацію.

Оберіть одну одиницю MAE та не порівнюйте її безпосередньо між наборами з різним масштабом. Якщо forest систематично недооцінює новий рівень попиту, поясніть обмеження дерева в екстраполяції та потребу нового спостереження, не вигадуйте причинність за графіком.

Результат і контрольні питання

Подайте notebook, паспорт, часові межі, таблицю доступності, метрики й аналіз помилок. Чому casual є витоком? Чим forecast weather відрізняється за доступністю від observed weather? Що означає MAE у цій роботі? Які ознаки можна отримати до початку дня?

Схема процесу

UCI Bike Sharing: часова валідація та ознаки без витокуОберіть елемент, щоб побачити пояснення

Визначаємо горизонт прогнозу.

Самоперевірка

Перевірте розуміння
Які ознаки прямо розкривають cnt?
Як обґрунтувати відповідь своїми словами?
Cnt складається з casual і registered. Ці фактичні підсумки дня недоступні до його початку.
Перед завершенням роботи

Підсумок

Часова валідація має відповідати моменту й горизонту прогнозу. Ознаки з майбутнього або складові цілі створюють витік. Інженерію ознак перевіряємо на development, а фінальний test використовуємо для незалежного звіту.

Джерела

Як оформити та здати роботу →

Опрацювали матеріал?

Збережіть цей крок у своєму прогресі.

← Повернутися до дисципліни
© 2026 Анастасія Іскандарова-МалаЕлектронний навчальний посібник · ДДТУ

Пошук у посібнику

Спробуйте «C++», «RAII» або «бази даних».