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

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

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

Проблем

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

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

// минали поръчки: разстояние в km и часове до доставката
double[] km = [30, 50, 80, 100, 130, 160, 200, 240, 270, 300];
double[] 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
static double Predict(double w, double b, double km) =>
w * km + b;

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

Идеята

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

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

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

double w = 0.12, b = 6;
for (var i = 0; i < km.Length; i++)
{
var guess = Predict(w, b, km[i]);
Console.WriteLine($"{km[i]} km: истина {hours[i]} ч, правата {guess:F2} ч, грешка {guess - hours[i]:F2}");
}
Console.WriteLine($"MSE: {Mse(w, b):F2}");
Изход
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)
double Mse(double w, double b)
{
double sum = 0;
for (var i = 0; i < km.Length; i++)
{
var error = Predict(w, b, km[i]) - hours[i];
sum += error * error;
}
return sum / km.Length;
}

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

// средната грешка без квадрат
double MeanError(double w, double b)
{
double sum = 0;
for (var i = 0; i < km.Length; i++)
{
sum += Predict(w, b, km[i]) - hours[i];
}
return sum / km.Length;
}
string[] names = ["A", "B", "C"];
double[] ws = [0.08, 0.12, 0];
double[] bs = [12, 6, 25];
for (var i = 0; i < names.Length; i++)
{
Console.WriteLine($"права {names[i]}: MSE {Mse(ws[i], bs[i]):F2}, средна грешка {MeanError(ws[i], bs[i]):F2}");
}
Изход
права 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. Правата, която греши малко навсякъде, е по-добра от тази, която е точна на половината поръчки и греши много на другата половина.

Виж го

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

static double Predict(double w, double b, double km) =>
w * km + b;
double Mse(double w, double b)
{
double sum = 0;
for (var i = 0; i < km.Length; i++)
{
var error = Predict(w, b, km[i]) - hours[i];
sum += error * error;
}
return sum / km.Length;
}
Стъпка по стъпка, с думи
  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 €. Какво ще отпечата програмата?

double[] kg = [1, 2, 3];
double[] price = [3, 5, 7];
double sum = 0;
for (var i = 0; i < 3; i++)
{
var error = 2 * kg[i] - price[i];
sum += error * error;
}
Console.WriteLine($"{sum / 3:F2}");
Отговор

Пробвай

Попълни

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

var error = Predict(w, b, km[i]) - hours[i];
sum += ;

Пробвай

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

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

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

Идеята

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

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

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

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

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

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

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

var n = km.Length;
double meanKm = 0, meanHours = 0;
for (var i = 0; i < n; i++)
{
meanKm += km[i];
meanHours += hours[i];
}
meanKm /= n;
meanHours /= n;
double top = 0, bottom = 0;
for (var i = 0; i < n; i++)
{
top += (km[i] - meanKm) * (hours[i] - meanHours);
bottom += (km[i] - meanKm) * (km[i] - meanKm);
}
var w = top / bottom;
var b = meanHours - w * meanKm;
Console.WriteLine($"най-добрата права: {w:F4} ч/km, {b:F2} ч");
Console.WriteLine($"MSE: {Mse(w, b):F2}");
Изход
най-добрата права: 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 и цена на куриера в €
double[] kg = [0.5, 1.0, 1.5, 2.0, 3.0, 4.0, 5.0, 6.0];
double[] 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 за достъпно обучение по програмиране ·