Към съдържанието
wannacode

Линейна регресия

Накратко: Линейната регресия предсказва число с права y = w · x + b, а средният квадрат на грешките (MSE) казва с едно число колко добре правата пасва на данните.

Проблем

Двама колеги, две прави

Складът иска да обещава часове, не дни. Ето 10 минали поръчки: разстоянието и за колко часа пристигнаха.

# минали поръчки: разстояние в km и часове до доставката
KM = [30, 50, 80, 100, 130, 160, 200, 240, 270, 300]
HOURS = [10, 11, 17, 17, 23, 24, 31, 33, 40, 44]

Двама колеги гледат точките и предлагат правило:

  • права A: всеки километър добавя 0,08 часа, плюс 12 часа в склада;
  • права B: всеки километър добавя 0,12 часа, плюс 6 часа в склада.

На графиката и двете изглеждат прилично. Колегите спорят цял следобед. Спорът няма да свърши, докато нямат едно число, което казва колко е добра правата.

Идеята

Правата като модел

Двете предложения са от един вид: часове = w · km + b.

  • w е наклонът: колко часа добавя всеки километър;
  • b е отместването: часовете при 0 km, тоест обработката в склада.

Модел, който предсказва число с права, се казва линейна регресия. Да научиш модела значи да избереш w и b.

# правата: часове = w · km + b
def predict(w, b, km):
return w * km + b

За разлика от k най-близки съседи, правата не пази поръчките. Тя е две числа, а прогнозата е едно умножение и едно събиране. И продължава наклона си: за 500 km казва 0,12 · 500 + 6 = 66 часа, докато съседите на 500 km са поръчките от 300 km.

Идеята

Грешката на всяка поръчка

За всяка поръчка правата казва някакво число. Грешката е разликата: колко казва правата минус колко е било наистина. Положителна е, когато правата обещава повече, отрицателна, когато обещава по-малко.

Права B, поръчка по поръчка:

w, b = 0.12, 6
for km, hours in zip(KM, HOURS):
guess = predict(w, b, km)
print(f"{km} km: истина {hours} ч, правата {guess:.2f} ч, грешка {guess - hours:.2f}")
print(f"MSE: {mse(w, b):.2f}")
Изход
30 km: истина 10 ч, правата 9.60 ч, грешка -0.40
50 km: истина 11 ч, правата 12.00 ч, грешка 1.00
80 km: истина 17 ч, правата 15.60 ч, грешка -1.40
100 km: истина 17 ч, правата 18.00 ч, грешка 1.00
130 km: истина 23 ч, правата 21.60 ч, грешка -1.40
160 km: истина 24 ч, правата 25.20 ч, грешка 1.20
200 km: истина 31 ч, правата 30.00 ч, грешка -1.00
240 km: истина 33 ч, правата 34.80 ч, грешка 1.80
270 km: истина 40 ч, правата 38.40 ч, грешка -1.60
300 km: истина 44 ч, правата 42.00 ч, грешка -2.00
MSE: 1.83

Десет грешки, част с плюс, част с минус. Трябва ни едно число за цялата права.

Идеята

Едно число: средният квадрат

Всяка грешка се повдига на квадрат, квадратите се събират и се делят на броя на поръчките. Това е средният квадрат на грешките, MSE (mean squared error). Число, което казва колко е лош моделът, се казва функция на грешката (loss). По-малко е по-добре, а 0 значи, че правата минава през всяка точка.

# средният квадрат на грешките (MSE)
def mse(w, b):
total = 0
for km, hours in zip(KM, HOURS):
error = predict(w, b, km) - hours
total += error * error
return total / len(KM)
zip(KM, HOURS) върви по двата списъка едновременно: на всяка стъпка дава една двойка km и часове.

Защо квадрат? Сравни трите прави. C е хоризонталната права през средното, 25 часа за всяка поръчка:

# средната грешка без квадрат
def mean_error(w, b):
total = 0
for km, hours in zip(KM, HOURS):
total += predict(w, b, km) - hours
return total / len(KM)
lines = {"A": (0.08, 12), "B": (0.12, 6), "C": (0, 25)}
for name, (w, b) in lines.items():
print(f"права {name}: MSE {mse(w, b):.2f}, средна грешка {mean_error(w, b):.2f}")
Изход
права A: MSE 17.35, средна грешка -0.52
права B: MSE 1.83, средна грешка -0.28
права C: MSE 124.00, средна грешка 0.00

Без квадрата права C има средна грешка 0,00: колкото е над точките, толкова е под тях, и плюсовете унищожават минусите. Най-лошата права „печели“. С квадрата нищо не се унищожава: C е 124, A е 17,35, а B е 1,83. Колегата с права B печели спора.

Квадратът прави и още нещо: голямата грешка тежи повече. Грешка от 4 часа дава 16, а две грешки по 2 часа дават общо 8. Правата, която греши малко навсякъде, е по-добра от тази, която е точна на половината поръчки и греши много на другата половина.

Виж го

Грешките като квадрати

def predict(w, b, km):
return w * km + b
def mse(w, b):
total = 0
for km, hours in zip(KM, HOURS):
error = predict(w, b, km) - hours
total += error * error
return total / len(KM)
Стъпка по стъпка, с думи
  1. 10 минали поръчки: колко km и за колко часа пристигнаха.
  2. Права A: 0,08 часа на km плюс 12 часа.
  3. Грешката на всяка поръчка: колко казва правата минус колко беше. Едни точки са над правата, други под нея.
  4. Всяка грешка на квадрат е квадрат със страна грешката. Средната им площ е MSE: 17,35.
  5. Права B: 0,12 часа на km плюс 6 часа. Правата се завърта, а квадратите се свиват.
  6. MSE на B е 1,83 срещу 17,35 на A.
  7. Правата с по-малка MSE е по-близо до точките. Едно число вместо спор.

Всеки квадрат е една грешка на квадрат: страната му е разстоянието от точката до правата. MSE е средната им площ. Права B е по-добра, защото квадратите ѝ са по-малки, не защото някой така е преценил.

Пробвай

Предвиди изхода

Куриерът взима 2 € на килограм. Три пратки от 1, 2 и 3 kg са стрували 3, 5 и 7 €. Какво ще отпечата програмата?

kg = [1, 2, 3]
price = [3, 5, 7]
total = 0
for x, y in zip(kg, price):
error = 2 * x - y
total += error * error
print(f"{total / 3:.2f}")
Отговор

Пробвай

Попълни

Какво се добавя към сумата за всяка поръчка?

error = predict(w, b, km) - hours
total += 

Пробвай

Намери грешката

Колега пише функцията на грешката и сравнява правите. Неговата програма казва, че най-добрата е хоризонталната права C. На кой ред е грешката?

Избери реда с грешката.

Идеята

Кога правата лъже

Правата е най-простият модел и често е достатъчна. Но:

  • Светът не е прав. Ако след 300 km поръчката минава през друг склад и чака още ден, точките правят стъпало. Правата минава между двете части и греши и на двете.
  • Една странна точка. Поръчка, изгубена за седмица, е грешка от стотици часове, а квадратът ѝ тежи колкото всички останали заедно. Правата се накланя към нея и започва да лъже за нормалните поръчки. Такива точки се проверяват и махат или се мери със средната абсолютна грешка.
  • Далеч извън данните. Поръчките са до 300 km. За 2 000 km правата казва 246 часа, но никой не е проверил дали изобщо има такава доставка.
  • Връзка не значи причина. Правата казва, че по-далечните поръчки пътуват по-дълго. Тя не казва защо. Ако по-далечните градове просто се обслужват от по-бавен куриер, по-бърз куриер там ще счупи правата.

Правата, която колегите избраха на око, е само едно от безбройните w и b. Коя е най-добрата и как да я намериш без да гадаеш, е в урока за градиентното спускане.

По-точно, за любопитните

Формулата за най-добрата права. За една права има точна формула (метод на най-малките квадрати). w е колко се движат заедно km и часовете, делено на колко се движат самите km. b е такова, че правата минава през средната точка:

w = Σ (km − средно km) · (часове − средно часове) / Σ (km − средно km)², b = средно часове − w · средно km.

n = len(KM)
mean_km = 0
mean_hours = 0
for km, hours in zip(KM, HOURS):
mean_km += km
mean_hours += hours
mean_km /= n
mean_hours /= n
top = 0
bottom = 0
for km, hours in zip(KM, HOURS):
top += (km - mean_km) * (hours - mean_hours)
bottom += (km - mean_km) * (km - mean_km)
w = top / bottom
b = mean_hours - w * mean_km
print(f"най-добрата права: {w:.4f} ч/km, {b:.2f} ч")
print(f"MSE: {mse(w, b):.2f}")
Изход
най-добрата права: 0.1241 ч/km, 5.64 ч
MSE: 1.62

Най-добрата права е малко по-добра от B (1,62 срещу 1,83). Колегата е преценил добре.

Защо не винаги формулата? Тя е за правата с квадратна грешка. При много признаци, при друга функция на грешката и при невронните мрежи точна формула няма или е твърде бавна. Затова се ползва градиентното спускане.

Повече признаци. Правата може да има по едно w за всеки признак: часове = w₁ · km + w₂ · kg + w₃ · пратки + b. Идеята и MSE са същите.

Средната абсолютна грешка (MAE) събира |грешка| вместо квадрата. Тя не наказва толкова голямата грешка и е по-спокойна към странни точки. MSE е по-гладка и затова по-удобна за градиентното спускане.

В библиотеките: LinearRegression в scikit-learn смята точно тази формула. В ML.NET е Sdca или Ols регресия.

Упражнение

Цената на куриера

Реши задачата в редактора си.

Куриерът не публикува ценоразписа си, но магазинът пази колко е платил за осем пратки:

# пратки: тегло в kg и цена на куриера в €
KG = [0.5, 1.0, 1.5, 2.0, 3.0, 4.0, 5.0, 6.0]
EUR = [4.10, 4.60, 5.00, 5.60, 6.40, 7.50, 8.30, 9.40]

Колегите предлагат три прави за цената: A е 0,90 € на kg плюс 3,80 €, B е 1,10 € на kg плюс 3,20 €, C е 0,70 € на kg плюс 4,60 €. Напиши функцията на грешката (MSE), отпечатай я за трите и кажи коя е най-добра.

Изход
права A (0.90 €/kg + 3.80 €): MSE 0.0144
права B (1.10 €/kg + 3.20 €): MSE 0.0769
права C (0.70 €/kg + 4.60 €): MSE 0.2869
най-малка MSE: права A

Още задачи

  • Средната абсолютна грешка. Смени квадрата с абсолютна стойност. Сменя ли се победителят?
  • Формулата. Сметни най-добрата права с формулата от „По-точно“. Колко е по-добра от A?

Тест

Провери се

1. Правата е часове = 0,12 · km + 6. Какво значи 6?
2. Защо грешките се повдигат на квадрат, преди да се съберат?
3. Права A има MSE 17,35, а права B има MSE 1,83. Коя е по-добра за тези поръчки?
4. Една поръчка се изгубва и пристига след 200 часа вместо след 25. Какво става с правата с най-малка MSE?
5. Защо правата може да каже нещо за поръчка на 500 km, а k най-близки съседи не може?

Създаден от Иво Гергов като част от кампанията на Evo Tech за достъпно обучение по програмиране ·