Какво е машинно учене
Накратко: Машинното учене е начин програмата сама да извади правилото от примери (признаци и етикет), вместо програмистът да го пише на ръка с if.
Проблем
Колко дни ще пътува поръчката
Страницата на продукта трябва да казва „пристига след 2 дни“. Колега пише правилото по километрите: до 100 km е 1 ден, до 250 km са 2 дни, а нататък 3. После го пуска върху 12 минали поръчки, за които знаем колко са пътували:
// правилото на колегата: по километритеfunction rule(km) { if (km < 100) return 1; if (km < 250) return 2; return 3;}let right = 0;ORDERS.forEach((order, i) => { const guess = rule(order[0]); if (guess === DAYS[i]) right++; else console.log(`поръчка ${i + 1}: правилото казва ${guess}, пристигна за ${DAYS[i]}`);});console.log(`правилото познава ${right} от ${ORDERS.length}`);поръчка 2: правилото казва 1, пристигна за 2поръчка 5: правилото казва 2, пристигна за 3поръчка 6: правилото казва 2, пристигна за 3поръчка 9: правилото казва 3, пристигна за 4поръчка 10: правилото казва 3, пристигна за 4поръчка 12: правилото казва 3, пристигна за 5правилото познава 6 от 12Шест от дванадесет. Колегата добавя две правила: тежката пратка (над 10 kg) е с ден повече, а натовареният куриер (над 35 пратки в деня) също. Същият цикъл, с новото правило:
// още две правила: тежка пратка и натоварен куриерfunction rule(km, kg, parcels) { let days = km < 100 ? 1 : km < 250 ? 2 : 3; if (kg > 10) days++; if (parcels > 35) days++; return days;}поръчка 2: правилата казват 1, пристигна за 2поръчка 12: правилата казват 4, пристигна за 5правилата познават 10 от 12Десет от дванадесет, но с три нови числа на око: 10 kg, 35 пратки и „ден повече“. Защо не 8 kg? Защо +1, а не +0,5? А следващия месец магазинът сменя куриера и всички числа пак са грешни. При 50 000 поръчки и десет неща, които влияят, никой не може да ги нагласи на ръка.
Идеята
Правилото от примерите
Обърни посоката. Вместо да пишеш правилото и да го проверяваш на миналите поръчки, дай на програмата миналите поръчки и я остави сама да намери правилото. Това се казва машинно учене: програма, която извежда правилото от примери, вместо някой да го напише.
Всеки пример има две части:
- признаци (features): това, което знаеш за поръчката, преди да тръгне. Тук са три: разстоянието, теглото и колко пратки има куриерът в този ден;
- етикет (label): отговорът, който искаш да познаеш. Тук са дните, за които е пристигнала.
// минали поръчки: km, kg, пратки на куриера в деняconst ORDERS = [ [15, 1.2, 18], [40, 0.8, 35], [60, 3.5, 20], [120, 2.0, 22], [150, 12.0, 25], [180, 1.5, 40], [220, 0.5, 15], [250, 8.0, 30], [300, 2.5, 45], [320, 18.0, 20], [380, 1.0, 28], [400, 6.0, 42],];// за колко дни пристигна всякаconst DAYS = [1, 2, 1, 2, 3, 3, 2, 3, 4, 4, 3, 5];Това, което се получава от примерите и дава отговор за нова поръчка, се казва модел. Има много видове модели. Започваме с най-простия, в който се вижда всяко число.
Идеята
Най-близките съседи
Как би познал ти? Ще потърсиш минали поръчки, които приличат на новата: подобно разстояние, подобно тегло, подобно натоварване. Щом те са пътували по 3 дни, и новата вероятно ще пътува толкова.
Това е модел: k най-близки съседи (k-NN). Пази всички минали поръчки. За нова поръчка намира k-те най-близки и връща средното на дните им. Тук k = 3.
„Близо“ значи малко разстояние между две поръчки. Разликата по всеки признак се повдига на квадрат, квадратите се събират, и от сумата се взима корен, както разстоянието между две точки на картата.
Има капан. Километрите са стотици, килограмите са единици. Ако събереш разликите направо, 10 kg разлика тежи колкото 10 km, тоест нищо, и километрите решават сами. Затова всяка разлика се дели на обхвата на признака (най-голямото минус най-малкото в данните). Така всеки признак е в една мярка: 1 е „от единия край до другия“.
// колко са раздалечени стойностите на всеки признак: max − minfunction ranges(rows) { return rows[0].map((_, j) => { const column = rows.map((row) => row[j]); return Math.max(...column) - Math.min(...column); });}function distance(a, b) { let sum = 0; for (let j = 0; j < a.length; j++) { const d = (a[j] - b[j]) / RANGE[j]; sum += d * d; } return Math.sqrt(sum);}Виж го
Трите най-близки до новата поръчка
function nearest(order) { const dist = ORDERS.map((row) => distance(order, row)); return ORDERS.map((_, i) => i) .sort((a, b) => dist[a] - dist[b]);}
function predict(order, k) { const near = nearest(order); let sum = 0; for (const i of near.slice(0, k)) { sum += DAYS[i]; } return sum / k;}Стъпка по стъпка, с думи
- Нова поръчка: 140 km, 10 kg, 30 пратки за куриера. Отдолу са 12-те минали поръчки и за колко дни е пътувала всяка.
- Разстоянието до всяка минала поръчка. Всяка разлика е делена на обхвата на признака си: km не надвикват kg.
- После подрежда поръчките по разстояние: най-подобните са най-горе.
- Взима първите k = 3: поръчка 5, поръчка 8, поръчка 4. Останалите не броят.
- Прогнозата е средното на дните им: (3 + 3 + 2) / 3 = 2,7 дни.
- Никой не е писал правило. Отговорът идва от миналите поръчки, които най-много приличат на новата.
Моделът мери разстоянието до всяка минала поръчка, подрежда ги и взима първите k:
function nearest(order) { const dist = ORDERS.map((row) => distance(order, row)); return ORDERS.map((_, i) => i) .sort((a, b) => dist[a] - dist[b]);}
function predict(order, k) { const near = nearest(order); let sum = 0; for (const i of near.slice(0, k)) { sum += DAYS[i]; } return sum / k;}Новата поръчка е 140 km, 10 kg и 30 пратки за куриера:
const order = [140, 10.0, 30];console.log(`най-близките 3 до ${order[0]} km, ${order[1].toFixed(1)} kg, ${order[2]} пратки:`);for (const i of nearest(order).slice(0, 3)) { const o = ORDERS[i]; console.log(` поръчка ${i + 1}: ${o[0]} km, ${o[1].toFixed(1)} kg, ${o[2]} пратки → ${DAYS[i]} дни (разстояние ${distance(order, o).toFixed(2)})`);}console.log(`прогноза: ${predict(order, 3).toFixed(1)} дни`);най-близките 3 до 140 km, 10.0 kg, 30 пратки: поръчка 5: 150 km, 12.0 kg, 25 пратки → 3 дни (разстояние 0.20) поръчка 8: 250 km, 8.0 kg, 30 пратки → 3 дни (разстояние 0.31) поръчка 4: 120 km, 2.0 kg, 22 пратки → 2 дни (разстояние 0.53)прогноза: 2.7 дниНай-близо е тежката пратка от 150 km. Поръчка 8 е на 100 km повече, но е тежка и в същия натоварен ден, затова е втора. Леката поръчка от 120 km е трета. Средното на 3, 3 и 2 дни е 2,7.
Пробвай
Предвиди изхода
За поръчка от 230 km, 3 kg и 24 пратки трите най-близки са поръчка 4 (2 дни), поръчка 7 (2 дни) и поръчка 8 (3 дни), в този ред. Какво ще отпечата програмата?
const order = [230, 3.0, 24];console.log(`${predict(order, 1).toFixed(1)} ${predict(order, 3).toFixed(1)}`);С k = 1 прогнозата е дните на най-близката: поръчка 4, 2 дни. С k = 3 е средното на 2, 2 и 3: 7 / 3 = 2,33, отпечатано с един знак като 2.3.
Идеята
Прогноза за нови поръчки
Моделът е готов в момента, в който има данните. Нови поръчки:
const fresh = [[140, 10.0, 30], [90, 1.0, 20], [350, 4.0, 40]];for (const order of fresh) { console.log(`${order[0]} km, ${order[1].toFixed(1)} kg, ${order[2]} пратки: ${predict(order, 3).toFixed(1)} дни`);}140 km, 10.0 kg, 30 пратки: 2.7 дни90 km, 1.0 kg, 20 пратки: 1.3 дни350 km, 4.0 kg, 40 пратки: 4.0 дниКолко добре познава, не знаем от тези три числа. Честната проверка е върху поръчки, които моделът не е виждал, и е в урока за оценката.
Как се избира k? С k = 1 прогнозата е дните на една-единствена поръчка, а тя може да е изключение: празник, загубена пратка. С k равно на всички поръчки прогнозата е средното на всичко, и моделът спира да различава 15 km от 400 km. Обикновено се пробват няколко стойности и се взима тази, която греши най-малко на непознати поръчки.
Пробвай
Попълни
Прогнозата е средното на дните на k-те най-близки. На какво се дели сумата?
let sum = 0;
for (const i of near.slice(0, k)) {
sum += DAYS[i];
}
return sum / ;Сумата е от k поръчки, затова се дели на k. Ако я делиш на броя на всички поръчки, прогнозата става много по-малка от истинската.
Пробвай
Намери грешката
Колега пише разстоянието наново. Програмата върви, но съседите на поръчката от 140 km и 10 kg вече са поръчки 5, 4, 6: тежката поръчка 8 от същия натоварен ден изпадна, а на нейно място влезе лека пратка. За поръчката от 90 km прогнозата става 1,7 дни вместо 1,3. На кой ред е грешката?
Избери реда с грешката.
Разликата не се дели на обхвата. Километрите са стотици, килограмите и пратките са единици и десетици, затова разстоянието е почти само по километрите. Тежестта на пратката и натовареният куриер не тежат нищо. С / RANGE[j] (в C# / range[j]) всеки признак е в една и съща мярка.
Идеята
Кога машинното учене е грешният инструмент
Моделът е правило, което никой не е написал. Това е и силата му, и цената му.
- Правилото е известно и стабилно. Таксата за доставка е 4,90 € до 2 kg, 6,90 € над 2 kg, безплатна над 50 €. Това е
if, не модел. Модел ще го научи само приблизително и понякога ще сгреши с няколко цента. - Трябва точен отговор или обяснение. Данъкът, отстъпката по договор, правото на връщане. Там „обикновено е така“ не стига, а „моделът реши“ не е обяснение за клиента.
- Няма данни. Нов магазин без минали поръчки няма от какво да учи. Тогава правилото на око е честно начало, а данните идват после.
- Светът се е сменил. Моделът знае само миналото. Нов куриер, празници, стачка: прогнозите от старите поръчки лъжат, докато не се учи наново.
- Грешката струва скъпо. Модел, който греши в 1 от 20 случая, е добър за „пристига след 2 дни“ и лош за „пусни плащането“. Там до него стои правило или човек.
Машинното учене е за случаите, в които правилото има твърде много части, за да го напише човек, а примери има много.
По-точно, за любопитните
Колко струва една прогноза. k-NN не учи нищо предварително: при всяка нова поръчка сравнява с всички минали и ги подрежда. При n поръчки това е поне n разстояния на прогноза. За 12 поръчки е нищо, за 10 милиона е бавно. Затова истинските библиотеки ползват структури като k-d дърво, а при много признаци търсят приблизително. Какво значи „расте с n“, е в урока за Big-O.
Същото търсене, но за текст. Търсенето по смисъл в базите данни е k най-близки съседи върху вектори: урокът за векторите в курса по SQL.
Деленето на обхвата се казва min-max нормализиране. Друг начин е да извадиш средното и да делиш на стандартното отклонение. Защо мярката на признаците има значение и за други модели, ще видиш в урока за градиентното спускане.
Истинските инструменти. Тук всичко е цикли и масиви, за да видиш всяко число. Машинното учене на практика се прави главно на Python: NumPy за масивите, scikit-learn за класическите модели (там k-NN е KNeighborsRegressor), PyTorch за невронните мрежи. В C# има ML.NET, в Java DJL и Tribuo, в JavaScript TensorFlow.js и ONNX Runtime. Идеите са същите.
Обучението и пускането на модели в продукция (сървъри за обучение, deploy, следене дали моделът остарява) е инфраструктура и се учи в DevOps курса ZeroToOps.
Упражнение
Колко минути за опаковане
Реши задачата в редактора си.
Складът иска да знае колко минути ще отнеме опаковането на поръчка, за да разпредели хората. Признаците са броят артикули и общото тегло, а етикетът са минутите. Ето десет минали поръчки:
// минали поръчки: брой артикули, kgconst ORDERS = [ [1, 0.5], [2, 1.0], [3, 4.0], [5, 2.0], [6, 8.0], [8, 3.0], [10, 12.0], [12, 5.0], [15, 9.0], [20, 6.0],];// минути за опакованеconst MINUTES = [4, 6, 9, 10, 15, 14, 22, 18, 25, 28];Напиши k най-близки съседи с k = 3 и деление на обхвата и предскажи две нови поръчки: 4 артикула и 1,5 kg, и 11 артикула и 10 kg.
4 артикула, 1.5 kg: 6.7 мин11 артикула, 10.0 kg: 20.7 минОще задачи
- Без обхвата. Махни деленето на обхвата и виж кои съседи излизат. Кой признак решава сега?
- Тежест. Дай на съседите тежест: по-близкият брои повече (например 1 / разстояние). Сменя ли се прогнозата?
Тест
Провери се
Свързани уроци
Раздел „Свързани уроци“- Нужно отпреди: Параметри и return · static: общото за всички · Стрелкови функции · Стабилно сортиране и вграденият sort
- Продължава в: Линейна регресия · Градиентно спускане · Класификация: измама или не · Оценка: точността лъже
- Задачи по темата: Как учи модел