Ошибка и улучшение: градиент на пальцах
Модель не «понимает» с первого раза — она ошибается, измеряет, насколько сильно промахнулась, и чуть-чуть подкручивает свои настройки. И так миллионы раз.
Главная мысль урока: обучение модели — это не магия и не «озарение». Это спуск с горы вслепую: на каждом шаге смотришь, в какую сторону вниз, делаешь маленький шажок туда — и повторяешь, пока не окажешься у подножия. У подножия — правильный ответ.
Зачем тебе вообще эта «ошибка»
Представь: ты учишься кидать мяч в кольцо. Первый бросок — мимо, мяч улетел сильно вправо. Что ты делаешь? Не бросаешь снова точно так же. Ты чуть доворачиваешь руку влево и бросаешь опять. Снова мимо, но уже ближе. Ещё поправка — и вот мяч в кольце.
Заметь, что именно ты делал. Ты смотрел на промах — то есть на ошибку — и по её размеру и направлению решал, куда и насколько поправить движение. Большой промах вправо — большая поправка влево. Маленький промах — лёгкая поправка. Ровно так же учится любая модель, от той, что отличает кошку от собаки, до ChatGPT.
В прошлом уроке про признаки, метки и предсказания мы говорили, что модель берёт признаки объекта, умножает их на веса и выдаёт предсказание. Но мы тогда обошли стороной самый интересный вопрос: откуда берутся правильные веса? Сегодня разберёмся. К концу урока ты своими глазами увидишь в коде, как кривое предсказание шаг за шагом превращается в точное.
Ошибка предсказания — это насколько ответ модели отличается от правильного ответа (метки). Чем меньше ошибка, тем лучше модель угадала.
Метафора: спуск с горы в тумане
Вообрази, что ты стоишь где-то на склоне горы в густом тумане. Тебе надо спуститься в долину, но видно от силы на метр вокруг. Как быть?
Очень просто: ты щупаешь ногой землю вокруг себя и определяешь, в какую сторону склон идёт вниз круче всего. Делаешь туда один аккуратный шаг. Снова щупаешь. Снова шаг вниз. И так, шажок за шажком, ты доберёшься до самой низкой точки, даже ничего толком не видя.
Теперь переведём это на язык обучения:
| Гора в тумане | Обучение модели |
| Высота, на которой ты стоишь | Размер ошибки (хотим как можно меньше) |
| Долина внизу | Правильные ответы, минимум ошибки |
| Твоё положение на склоне | Текущие веса модели |
| Куда склон идёт вниз | Куда подкрутить веса, чтобы ошибка упала |
| Размер одного шага | Скорость обучения |
Вот это «в какую сторону склон идёт вниз и насколько круто» учёные называют красивым словом градиент. А весь процесс «щупаю склон — делаю шаг вниз — повторяю» — это градиентный спуск. Звучит грозно, но ты только что понял его на примере горы в тумане. Никакого матана.
Чем вообще измеряют ошибку
Прежде чем спускаться, нужно договориться, что именно считать «высотой». Самый понятный способ — взять разницу между предсказанием и правильным ответом. Предсказали 2, а надо было 3 — промах на единицу. Но у такого подхода есть подвох: если на одной картинке мы промахнулись на +2, а на другой на -2, плюс и минус взаимно сократятся, и в среднем получится «ноль ошибки», хотя модель ошиблась дважды.
Поэтому ошибки обычно возводят в квадрат — тогда и плюс, и минус становятся положительными и честно складываются. Заодно квадрат сильнее наказывает за крупные промахи: ошибиться на 3 в девять раз «дороже», чем ошибиться на 1. Именно поэтому во втором примере ниже ты увидишь строчку totalError += error * error — мы копим сумму квадратов ошибок по всем примерам. Эту сумму модель и стремится утащить вниз, к нулю.
Почему именно маленькие шаги
Может возникнуть мысль: зачем мелочиться, давай сразу прыгнем в долину одним гигантским прыжком? Проблема в том, что в тумане ты не видишь всю гору целиком. Ты знаешь только наклон прямо под ногами. Если прыгнуть слишком далеко, легко перелететь долину и оказаться на склоне напротив — ещё выше, чем был. Поэтому шаги делают маленькими и осторожными. Чуть позже мы увидим в коде, что бывает, когда шаг слишком большой.
Разбираем на коде: один вес ползёт к ответу
Начнём с самой простой ситуации, какую только можно придумать. У модели один-единственный вес w, и она умеет считать предсказание как w * x. Пусть на входе x = 2, а правильный ответ (метка) равен 3. Значит, идеальный вес — это 1.5, ведь 1.5 * 2 = 3. Но модель этого не знает! Она стартует с w = 0 и должна сама доползти до правильного значения, опираясь только на свои ошибки.
let w = 0; // стартовый вес — наугад
const x = 2; // вход
const target = 3; // правильный ответ (метка)
const lr = 0.1; // скорость обучения — размер шага
function predict(w) {
return w * x;
}
for (let step = 1; step <= 6; step++) {
const pred = predict(w);
const error = pred - target; // насколько промахнулись
const grad = error * x; // куда и насколько крутить вес
w = w - lr * grad; // делаем маленький шаг "вниз"
console.log(
"шаг " + step +
": предсказание=" + pred.toFixed(3) +
", ошибка=" + error.toFixed(3) +
", новый вес=" + w.toFixed(3)
);
}
console.log("итоговый вес \u2248 " + w.toFixed(3) + " (цель: 1.5)");Вывод:
шаг 1: предсказание=0.000, ошибка=-3.000, новый вес=0.600 шаг 2: предсказание=1.200, ошибка=-1.800, новый вес=0.960 шаг 3: предсказание=1.920, ошибка=-1.080, новый вес=1.176 шаг 4: предсказание=2.352, ошибка=-0.648, новый вес=1.306 шаг 5: предсказание=2.611, ошибка=-0.389, новый вес=1.383 шаг 6: предсказание=2.767, ошибка=-0.233, новый вес=1.430 итоговый вес ≈ 1.430 (цель: 1.5)
Смотри, что произошло. На первом шаге модель выдала 0 вместо 3 — ошибка целых -3. Она поправила вес, и предсказание подскочило до 1.2. Ошибка стала меньше. Ещё шаг — ещё ближе. К шестому шагу предсказание 2.767 почти дотянуло до 3, а вес 1.430 почти доехал до идеальных 1.5. Дай ей ещё десяток шагов — и она попадёт в точку.
Разберём ключевую строку w = w - lr * grad по словам:
grad = error * x— это и есть наш «наклон склона». Большая ошибка — большой градиент — большая поправка. Когда ошибка почти ноль, градиент почти ноль, и вес почти перестаёт двигаться. Удобно: чем ближе к цели, тем осторожнее шаги.- Минус перед
lr * gradозначает «иди в сторону, противоположную ошибке». Если предсказание было слишком маленьким (ошибка отрицательная), вес увеличивается. Если слишком большим — уменьшается. lr(скорость обучения) сдерживает аппетит: мы делаем не весь шаг целиком, а только его десятую часть. Те самые маленькие шажки из метафоры с горой.
Возвращаемся к кошкам и собакам
Один абстрактный вес — это разминка. Давай применим тот же приём к нашему сквозному примеру: модель, которая отличает кошку от собаки. Возьмём один простой признак — насколько уши круглые (у кошек уши обычно округлее). Закодируем: 1 — круглые уши, 0 — острые. Метка: 1 — кошка, 0 — собака.
У модели будет вес w при признаке и ещё один параметр b — сдвиг (его тоже подкручивают тем же способом). Модель стартует с нулей и учится на четырёх примерах. Следи за столбцом «ошибка» — он должен таять с каждой эпохой (одна эпоха — это один полный проход по всем обучающим примерам).
const data = [
{ roundEars: 1, isCat: 1 }, // круглые уши → кошка
{ roundEars: 0, isCat: 0 }, // острые уши → собака
{ roundEars: 1, isCat: 1 },
{ roundEars: 0, isCat: 0 },
];
let w = 0;
let b = 0;
const lr = 0.3;
for (let epoch = 1; epoch <= 5; epoch++) {
let totalError = 0;
for (const item of data) {
const pred = item.roundEars * w + b;
const error = pred - item.isCat;
totalError += error * error; // копим квадрат ошибки
w = w - lr * error * item.roundEars; // крутим вес признака
b = b - lr * error; // крутим сдвиг
}
console.log(
"эпоха " + epoch +
": ошибка=" + totalError.toFixed(3) +
", вес=" + w.toFixed(2) +
", сдвиг=" + b.toFixed(2)
);
}
console.log(
"круглые уши \u2192 " + (1 * w + b).toFixed(2) +
" (хотим ~1=кошка), острые уши \u2192 " + (0 * w + b).toFixed(2) +
" (хотим ~0=собака)"
);Вывод:
эпоха 1: ошибка=1.458, вес=0.45, сдвиг=0.25 эпоха 2: ошибка=0.351, вес=0.60, сдвиг=0.21 эпоха 3: ошибка=0.183, вес=0.71, сдвиг=0.16 эпоха 4: ошибка=0.102, вес=0.78, сдвиг=0.12 эпоха 5: ошибка=0.058, вес=0.83, сдвиг=0.09 круглые уши → 0.93 (хотим ~1=кошка), острые уши → 0.09 (хотим ~0=собака)
Видишь, как суммарная ошибка падает с 1.458 до 0.058? Это та же самая идея, что и с одним весом, просто теперь модель крутит сразу два числа и учится на нескольких примерах подряд. В конце она уверенно ставит кошке score 0.93 (близко к 1), а собаке — 0.09 (близко к 0). Никто не вписывал эти веса вручную — модель сама нашла их, спускаясь по склону ошибки.
А что насчёт нашего второго сквозного примера — фразы «Кошка пьёт ...»? Там работает ровно тот же механизм. Языковая модель предсказывает следующее слово, сравнивает с тем, какое слово реально стояло в тексте, считает ошибку и подкручивает миллиарды весов в ту сторону, где «молоко» становится вероятнее «кирпича». Масштаб другой, идея — одна и та же.
Давай задержимся на этой мысли, потому что она важная. В наших примерах было один-два веса, и ты мог проследить судьбу каждого глазами. У ChatGPT весов — сотни миллиардов. Никто не настраивает их по одному, и никто не понимает, за что отвечает каждый конкретный вес. Но правило обновления — то же самое, что в коде выше: посмотреть на ошибку, прикинуть, в какую сторону её уменьшить, сделать крошечный шаг. Просто этот шаг делается сразу для всех миллиардов весов одновременно, и повторяется он не пять раз, а триллионы раз на текстах со всего интернета. Когда тебе говорят, что обучение большой модели стоит миллионы долларов и месяцы работы тысяч видеокарт, речь именно об этом: гигантское число повторений того маленького цикла, что ты только что запускал у себя в браузере за доли секунды.
Откуда модель знает правильный ответ
Важная деталь: чтобы посчитать ошибку, нужен правильный ответ — метка. Поэтому всё, что мы сейчас делали, — это обучение с учителем из прошлых уроков. Кто-то заранее разметил картинки «кошка/собака», а в текстах «правильное следующее слово» — это просто то слово, которое реально стоит дальше в книге или статье. Интернет — гигантский сборник готовых правильных ответов.
Частые ошибки и подводные камни
Когда новички впервые встречают градиентный спуск, они спотыкаются примерно об одно и то же. Разберём, чтобы ты не наступал на эти грабли.
1. Слишком большой шаг — модель «скачет» и не сходится
Самая частая беда. Если скорость обучения большая, модель не аккуратно спускается, а перепрыгивает долину туда-сюда, с каждым разом всё сильнее. Посмотри, что бывает с тремя разными размерами шага на той же задаче из первого примера:
const x = 2, target = 3;
function run(lr) {
let w = 0;
const log = [];
for (let s = 1; s <= 5; s++) {
const pred = w * x;
const error = pred - target;
w = w - lr * error * x;
log.push(error.toFixed(2));
}
return log.join(", ");
}
console.log("маленький шаг 0.1: ошибки = " + run(0.1));
console.log("нормальный шаг 0.2: ошибки = " + run(0.2));
console.log("слишком большой 0.6: ошибки = " + run(0.6));Вывод:
маленький шаг 0.1: ошибки = -3.00, -1.80, -1.08, -0.65, -0.39 нормальный шаг 0.2: ошибки = -3.00, -0.60, -0.12, -0.02, -0.00 слишком большой 0.6: ошибки = -3.00, 4.20, -5.88, 8.23, -11.52
С шагом 0.1 ошибка спокойно уменьшается. С 0.2 — летит к нулю быстро и красиво. А с 0.6 — катастрофа: ошибка не падает, а растёт и меняет знак, модель «улетает в космос». Правильную скорость обучения подбирают экспериментально, и это одна из главных забот тех, кто обучает модели.
2. Слишком маленький шаг — учится целую вечность
Обратная крайность. Если шаг крошечный, модель ползёт к ответу так медленно, что обучение может занять в сотни раз больше времени. Как спускаться с горы, переставляя ноги на миллиметр. Дойдёшь, но к ночи. Нужна золотая середина — и подбирают её обычно так: пробуют несколько значений и смотрят, при каком ошибка падает быстро, но без скачков. Это нормальная часть работы, а не признак того, что что-то сломано.
3. Зазубрить вместо того, чтобы понять (переобучение)
Если гонять модель по одним и тем же примерам слишком долго, она может просто запомнить их наизусть вместе со всеми случайностями. Это переобучение: на знакомых картинках кошек ответы идеальны, а новую, незнакомую кошку модель путает с собакой. Снижать ошибку до нуля любой ценой — плохая цель. Важно, чтобы модель хорошо работала на новых данных, которых не видела.
4. Путать ошибку модели с «глупостью»
Большая ошибка в начале обучения — это абсолютно нормально и даже необходимо. Модель и должна стартовать криво, иначе ей не от чего отталкиваться. Ошибка здесь — не позор, а компас, который показывает, куда двигаться. Без ошибок не было бы и обучения.
5. Думать, что модель «понимает» смысл
Честный момент. Когда модель докрутила веса так, что кошка получает 0.93, она не «поняла», что такое кошка. Она просто нашла набор чисел, при которых ошибка минимальна на твоих примерах. Это мощно и полезно, но это не размышление и не сознание. Помни об этом, когда ChatGPT отвечает уверенно и складно: за этим стоит всё та же подкрутка весов, а не понимание в человеческом смысле.
Мини-практика: добавь второй признак сам
Теперь твоя очередь. Возьми за основу пример с кошками и собаками и прокачай его. Задание:
- Добавь каждому животному второй признак — например
loudBark(громко лает):1у собак,0у кошек. - Заведи для него отдельный вес
w2и включи его в предсказание:pred = roundEars * w1 + loudBark * w2 + b. - Не забудь обновлять
w2по тому же правилу, что иw1:w2 = w2 - lr * error * item.loudBark. - Запусти и посмотри, как ведут себя оба веса. Подсказка: вес при «громком лае» должен уйти в минус — ведь лай тянет ответ в сторону собаки (метка 0).
Поэкспериментируй: что будет, если поставить lr = 0.7? А lr = 0.01? Сверься с разделом про подводные камни — увидишь те самые эффекты своими глазами. Это и есть лучший способ закрепить идею: не прочитать про неё, а сломать модель и починить обратно.
Итоги
Сегодня ты разобрал, возможно, самую важную идею во всём машинном обучении — и без единой формулы.
- Ошибка предсказания — это разница между ответом модели и правильным ответом. Она измеримая, её можно посчитать числом.
- Обучение — это спуск с горы в тумане: модель смотрит на ошибку, определяет, куда подкрутить веса (это и есть градиент), и делает маленький шаг в нужную сторону. Повторяет много раз.
- Размер шага (скорость обучения) критичен: слишком большой — модель скачет и расходится, слишком маленький — учится вечно.
- Ровно так учатся и распознавалка кошек, и языковая модель, что предсказывает слово после «Кошка пьёт ...». Меняется лишь масштаб — число весов, — но не сама идея.
- Снижение ошибки до нуля — не цель. Цель — чтобы модель работала на новых данных, а не зазубрила старые.
Теперь у тебя есть полная картинка одного нейрона: признаки на входе, веса, предсказание и механизм, который эти веса настраивает на ошибках. В следующем уроке мы соберём из множества таких настраиваемых элементов нейросеть — и увидим, как наша задача «кошка или собака» поднимается на новый уровень: вместо одного признака модель начнёт сама находить сложные сочетания признаков. Идея спуска по ошибке останется ровно той же — просто склонов станет очень много.