Линейна регресия
Накратко: Линейната регресия предсказва число като сума от признаците с коефициенти; MAE казва колко греши средно, а RMSE наказва повече големите грешки, и двете в същите мерни единици.
Проблем
„Колко пари да чакам в петък?“
Счетоводството планира парите за следващите седмици. Пита колко оборот да чака в петък, 6 ноември, и в неделя, 8 ноември. Ето оборотът по дни от годината, без отказаните поръчки, защото те не носят пари:
Денят носи средно 304,62 €. Петъкът носи 350,79 €, а неделята 250,85 €. Декемврийските дни са средно с 513,42 €. Средното на всеки ден от седмицата е начало, но вътре се смесват и декември, и бавният ръст през годината. Трябва ти модел, който ги събира заедно.
Идеята
Сбор от признаци
Линейната регресия (linear regression) предсказва като сбор: едно начално число плюс по едно число за всеки признак, умножено по стойността му. Тези числа са коефициентите. Признаците тук са три вида:
- денят от седмицата, като колони с 0 и 1. С
drop_first=Trueколоната за понеделник отпада: понеделникът е базата, а всеки друг ден е „с колко повече от понеделник“; day, поредният номер на деня: 0 на 1.11.2025. Това е трендът;december: 1 за дните през декември.
Моделът учи от ноември до юни, 242 дни. Юли–октомври, 123 дни, са за теста. Защо не разбъркани, е в края на урока:
Началното число е 273,98 €: понеделник в ден 0, извън декември. Петъкът добавя 63,58 €, а неделята маха 45,44 €. Декември добавя 233,05 € на ден. Всеки ден добавя 0,06 €, тоест около 21,33 € за година. Така прогнозата за петък в декември е 273,98 + 63,58 + 233,05 плюс тренда.
По-точно, за любопитните
Коефициентите са оценки и носят шум, като средната в урока за извадките. Учен на всичките 365 дни, моделът дава на петъка +44,70 €, а не +63,58 €. Как регресията намира числата (най-малката сума на квадратите на грешките) има в урока Линейна регресия от курса по AI, без библиотека.
Пробвай
Кой е базата
Идеята
Колко греши: MAE и RMSE
Грешката се мери на теста. Има две обичайни мерки:
- MAE (mean absolute error): средната грешка без знака. В евро, и управителят я разбира: „грешим средно с толкова на ден“.
- RMSE (root mean squared error): грешките се повдигат на квадрат, взима се средното и после корен. Пак е в евро, но голямата грешка тежи повече.
sklearn.metrics има и двете:
Моделът греши средно с 59,88 € на ден (MAE), а RMSE е 75,91 €. Двете малки сметки показват разликата. Грешки 10 и 10 дават MAE 10 и RMSE 10. Грешки 0 и 20 дават същата MAE 10, но RMSE 14,14. Ако една голяма грешка ти струва повече от няколко малки, например празна каса в петък, гледаш RMSE. Иначе MAE е по-ясна за управителя.
Пробвай
А базовата линия?
Бие ли моделът „винаги средния ден“? Запиши в guess средния оборот на дните за обучение, а в mae_base и rmse_base грешките на тази прогноза на тестовите дни.
Решение
import numpy as np
import pandas as pd
from sklearn.linear_model import LinearRegression
orders = pd.read_csv('year_orders.csv')
items = pd.read_csv('year_items.csv')
orders = orders[orders['status'] != 'cancelled']
orders['ordered_at'] = pd.to_datetime(orders['ordered_at'])
items['revenue'] = items['quantity'] * items['unit_price']
lines = items.merge(orders, left_on='order_id', right_on='id')
daily = lines.set_index('ordered_at').resample('D')['revenue'].sum().to_frame()
daily['weekday'] = daily.index.dayofweek
daily['day'] = np.arange(len(daily))
daily['december'] = (daily.index.month == 12).astype(int)
X = pd.get_dummies(daily[['weekday', 'day', 'december']], columns=['weekday'], drop_first=True, dtype=int)
y = daily['revenue']
train = daily.index < '2026-07-01'
X_train, X_test = X[train], X[~train]
y_train, y_test = y[train], y[~train]
model = LinearRegression().fit(X_train, y_train)
guess = y_train.mean()
mae_base = np.abs(y_test - guess).mean()
rmse_base = np.sqrt(((y_test - guess) ** 2).mean())
print(round(guess, 2), round(mae_base, 2), round(rmse_base, 2))Базовата линия казва 314,34 € за всеки ден. Тя греши с MAE 66,24 € и RMSE 82,67 €. Моделът е с 59,88 € и 75,91 €: с 6–7 € на ден по-малко и по двете мерки. Не е много: оборотът на един ден е шумен, и повечето от грешката идва от това кои клиенти са дошли точно този ден. Без корена се получава 6 834,32: евро на квадрат, които не се сравняват с нищо. Със средното на теста (285,49 €) MAE е 61,02 € и базовата линия изглежда почти колкото модела, но ползва отговора.
Идеята
Остатъците
Остатък (residual) е фактът минус прогнозата. Плюс значи, че магазинът е продал повече, отколкото моделът е казал. При добър модел остатъците са разпръснати около 0, без шарка. Шарката е нещо, което моделът не знае:
Най-голямата грешка е на 30.07.2026: 239,28 € над прогнозата. Това е един необичайно силен четвъртък с 535,40 € оборот. По месеци юли, август и октомври са близо до 0 (3,99 €, −8,03 € и 20,42 €). Септември е −58,85 € на ден: цял месец магазинът е продавал по-малко, отколкото денят и трендът обясняват. Това не е шум, а нещо се е случило. Какво, ще разберем в проекта на курса.
Идеята
Какво моделът не може
Моделът знае само това, което признаците му казват. Без колоната december:
Трендът става −0,40 € на ден, −147,35 € за година. Декември е в началото на обучението и „вдига“ началото на правата, а моделът няма как да го обясни другояче. Грешката на теста става 69,43 €, по-лоша от базовата линия (66,24 €). Скокът от подаръчните карти в декември се вижда само ако признак го казва.
Още две граници:
- Без екстраполация. Правата продължава безкрай. От 8 месеца ръстът е 21,33 € за година, но дали ще продължи и догодина, данните не казват. Видели сме само един декември.
- Коефициентът не е причина. „+63,58 € в петък“ не значи, че акция, преместена в петък, ще донесе 63,58 €. Коефициентът описва данните. Колко носи едно действие, казва експеримент, както в урока Корелация и причина.
Пробвай
Къде моделът е сляп
Учи модела без колоната december на дните за обучение. Сметни остатъците му на същите дни и средния остатък по месеци в by_month (групирай по res.index.strftime('%Y-%m')). Начертай by_month със стълбове, със заглавие и надпис на вертикалната ос.
Решение
import numpy as np
import pandas as pd
from sklearn.linear_model import LinearRegression
orders = pd.read_csv('year_orders.csv')
items = pd.read_csv('year_items.csv')
orders = orders[orders['status'] != 'cancelled']
orders['ordered_at'] = pd.to_datetime(orders['ordered_at'])
items['revenue'] = items['quantity'] * items['unit_price']
lines = items.merge(orders, left_on='order_id', right_on='id')
daily = lines.set_index('ordered_at').resample('D')['revenue'].sum().to_frame()
daily['weekday'] = daily.index.dayofweek
daily['day'] = np.arange(len(daily))
daily['december'] = (daily.index.month == 12).astype(int)
X = pd.get_dummies(daily[['weekday', 'day', 'december']], columns=['weekday'], drop_first=True, dtype=int)
y = daily['revenue']
train = daily.index < '2026-07-01'
X_train, X_test = X[train], X[~train]
y_train, y_test = y[train], y[~train]
import matplotlib.pyplot as plt
no_dec = X.drop(columns='december')
blind = LinearRegression().fit(no_dec[train], y_train)
res = y_train - blind.predict(no_dec[train])
by_month = res.groupby(res.index.strftime('%Y-%m')).mean()
fig, ax = plt.subplots(figsize=(8, 3.5))
ax.bar(by_month.index, by_month.values)
ax.axhline(0, color='black', linewidth=1)
ax.set_title('Модел без декември: средният остатък по месеци')
ax.set_ylabel('€ на ден')Декември е 167,92 € на ден над прогнозата: моделът не вижда подаръчните карти и празниците. Януари е −100,63 €: правата, наклонена надолу заради декември, е твърде високо в началото на годината. С колоната december декемврийският стълб е почти 0. Остатъкът е факт минус прогноза. Обърнеш ли го, декември излиза под 0 и графиката казва обратното.
Идеята
Дели по време, не разбъркано
Моделът в този урок учи от ноември до юни и се проверява на юли–октомври. Защо не с train_test_split? Ето модел с деня и месеца, проверен по двата начина. Второто число на всеки ред е базовата линия:
Разбъркан, моделът греши с 59,81 €, а базовата линия с 81,48 €: 27% по-добре. Само че 87 от 92 тестови дни имат съседен ден, предишния или следващия, в обучението. Моделът е учил как са минали дните около тестовия, и по-късните.
Проверен по време, същият модел греши с 71,98 €, повече от базовата линия (66,24 €). Месеците юли–октомври ги няма в обучението. Колоните им са 0 във всички редове за обучение, и моделът им дава нивото на януари, най-слабия месец.
Истинската прогноза винаги е за дни след всички примери. Затова и тестът трябва да е след обучението. Разбъркването на дни пуска бъдещето в обучението. Това е изтичане на информация (leakage), и на него е посветен цял урок по-нататък.
Упражнение
Петък и неделя
Сега отговорът на счетоводството. Учи модела наново на всички 365 дни. Направи X_new за петък 6.11.2026 и неделя 8.11.2026 (day продължава броенето от 1.11.2025, december е 0) и запиши в forecast прогнозата за двата дни (Series с датите като индекс).
Решение
import numpy as np
import pandas as pd
from sklearn.linear_model import LinearRegression
orders = pd.read_csv('year_orders.csv')
items = pd.read_csv('year_items.csv')
orders = orders[orders['status'] != 'cancelled']
orders['ordered_at'] = pd.to_datetime(orders['ordered_at'])
items['revenue'] = items['quantity'] * items['unit_price']
lines = items.merge(orders, left_on='order_id', right_on='id')
daily = lines.set_index('ordered_at').resample('D')['revenue'].sum().to_frame()
daily['weekday'] = daily.index.dayofweek
daily['day'] = np.arange(len(daily))
daily['december'] = (daily.index.month == 12).astype(int)
X = pd.get_dummies(daily[['weekday', 'day', 'december']], columns=['weekday'], drop_first=True, dtype=int)
y = daily['revenue']
train = daily.index < '2026-07-01'
X_train, X_test = X[train], X[~train]
y_train, y_test = y[train], y[~train]
final = LinearRegression().fit(X, y)
dates = pd.to_datetime(['2026-11-06', '2026-11-08'])
new = pd.DataFrame({'weekday': dates.dayofweek, 'day': (dates - daily.index[0]).days, 'december': 0}, index=dates)
X_new = pd.get_dummies(new, columns=['weekday'], dtype=int).reindex(columns=X.columns, fill_value=0)
forecast = pd.Series(final.predict(X_new), index=dates).round(2)
print(forecast)Петъкът е 336,38 €, а неделята 236,58 €. При грешка около 60 € на ден честният отговор е „около 340 € в петък и около 240 € в неделя, плюс-минус 60 €“. Тестът беше проверката. За прогнозата учиш на всички дни, защото последните месеци са най-близо до ноември. Така петъкът е +44,70 € спрямо понеделника, а не +63,58 €. day е 370 и 372, броено точно както при ученето. С day 0 прогнозата е 329,70 € и 229,86 €, а със стария модел, учен до юни, 359,17 € и 250,27 €.
Тест
Провери се
Свързани уроци
Раздел „Свързани уроци“- Нужно отпреди: Първата графика · Разпределения · Корелация и причина · Модел: учене от примери
- Продължава в: Класификация · Изтичане на данни (data leakage) · Кръстосана проверка (cross-validation)
- Задачи по темата: Модели със scikit-learn