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

Обучението от начало до край

Накратко: Обучението на невронна мрежа е цикъл по епохи: разбъркване, партиди, проход напред, обратно разпространение и стъпка, докато грешката на валидацията спре да пада.

Проблем

Мрежата казва −0,23 дни

Страницата за плащане трябва да казва „пристига до 2 дни“. Времето на доставката зависи от разстоянието, от това дали поръчката е след 16:00 (тогава тръгва на другия ден) и малко от теглото. Права линия от линейната регресия не следва тази крива: в града е около ден, до 200 km расте бързо, после все по-бавно.

Мрежата от урока за backprop може да следва крива. Ето я с 4 скрити неврона и случайни тегла, преди обучение:

const rng = new Rng(42);
const net = new Net(4, 3);
net.randomize(rng, 1.0);
console.log(`грешка (MSE): ${net.loss(trainX, trainY).toFixed(4)}`);
for (const k of [0, 12, 23])
console.log(`${TRAIN[k][0]} km: мрежата казва ${net.predict(trainX[k]).toFixed(2)}, ` +
`истина ${trainY[k].toFixed(1)} дни`);
Изход
грешка (MSE): 7.1855
5 km: мрежата казва -0.23, истина 1.0 дни
150 km: мрежата казва -0.17, истина 2.3 дни
520 km: мрежата казва 0.18, истина 3.0 дни

Поръчка на 5 km пристига след −0,23 дни. В урока за backprop видя една стъпка за един пример. Колко стъпки трябват, с кои примери, в какъв ред и кога да спреш?

Идеята

Цикълът на обучение

Обучението е един цикъл, който се върти, докато мрежата спре да се подобрява:

  1. Начални тегла: малки случайни числа от генератор със семе.
  2. Епоха: едно минаване през всички примери за обучение. Преди всяка епоха примерите се разбъркват, за да не учи мрежата реда им.
  3. Партида: няколко примера наведнъж. За всеки има проход напред и грешка, после назад. Наклоните се усредняват и мрежата прави една стъпка.
  4. Проверка: на всеки 100 епохи програмата печата грешката на обучението и на валидацията.

Това е цикълът на обучение (training loop). Числата, които печата, нарисувани като графика, са кривата на грешката. По нея разбираш дали мрежата учи, дали скоростта е добра и кога да спреш.

Данните са 32 поръчки от магазина: 24 за обучение и 8 за валидация. Мрежата никога не прави стъпка с поръчките за валидация. Те са мярката за нови поръчки.

Виж го

Данните и мрежата

Поръчките, записани в кода:

// поръчка: km, след 16:00 (1 или 0), kg, дни до доставката
const TRAIN = [
[5, 0, 1.2, 1.0], [12, 1, 3.5, 1.6], [18, 0, 0.8, 1.0],
[25, 0, 6.0, 1.2], [28, 1, 2.1, 1.8], [35, 0, 4.4, 1.3],
[48, 1, 0.5, 2.2], [60, 0, 9.0, 2.5], [75, 0, 1.7, 1.6],
[90, 1, 2.9, 2.6], [110, 0, 5.5, 1.9], [130, 1, 0.9, 2.7],
[150, 0, 3.1, 2.3], [170, 0, 8.2, 3.3], [185, 1, 1.4, 3.1],
[195, 0, 2.6, 2.3], [210, 0, 4.8, 2.6], [240, 1, 0.6, 3.3],
[280, 0, 8.6, 3.6], [320, 0, 1.9, 2.7], [360, 1, 3.3, 3.6],
[410, 0, 5.0, 2.8], [460, 1, 2.2, 3.6], [520, 0, 0.7, 3.0],
];
// валидация: поръчки, които мрежата не вижда при обучението
const VALID = [
[8, 1, 2.4, 1.6], [22, 0, 8.8, 1.0], [40, 0, 1.1, 1.4],
[95, 1, 7.9, 2.5], [160, 0, 2.0, 2.3], [205, 1, 3.9, 3.1],
[300, 0, 9.4, 2.6], [480, 1, 4.6, 3.6],
];

Признаците се свеждат до близки мащаби: километрите в стотици, теглото в десетки килограми. Иначе 520 km щяха да заглушат всичко останало:

// признаците в близки мащаби: стотици km, 0 или 1, десетки kg
const inputs = (orders) => orders.map(([km, late, kg]) => [km / 100, late, kg / 10]);
const targets = (orders) => orders.map((order) => order[3]);

Случайността идва от собствен генератор със семе. Едно семе дава едни и същи числа на всеки компютър и на четирите езика, затова урокът печата един и същ изход навсякъде. Числото 48271 и делението на 2³¹ − 1 са стар и проверен генератор (Park–Miller). Произведението е под 2⁵³, затова се смята точно дори в JavaScript:

class Rng {
constructor(seed) {
this.state = seed;
}
// следващото число между 0 и 1; едно семе дава едни и същи числа
next() {
this.state = (this.state * 48271) % 2147483647;
return this.state / 2147483647;
}
}
Math.random() в JavaScript няма семе: всяко пускане дава други числа. Затова генераторът е наш.

Мрежата е тази от урока за backprop, събрана в клас: 3 входа, 4 скрити неврона със сигмоида и един изход без активация, защото дните не са вероятност. step прави проход напред и назад за всяка поръчка от партидата, събира наклоните и мести всяко тегло веднъж, със средния наклон:

const sigmoid = (z) => 1 / (1 + Math.exp(-z));
class Net {
constructor(hidden, inputs) {
this.w1 = Array.from({ length: hidden }, () => new Array(inputs).fill(0)); // вход → скрит слой
this.b1 = new Array(hidden).fill(0);
this.w2 = new Array(hidden).fill(0); // скрит слой → изход
this.b2 = 0;
this.h = new Array(hidden).fill(0); // изходите на скрития слой при последното predict
}
// случайни тегла от -scale до scale; отместванията остават 0
randomize(rng, scale) {
for (const row of this.w1)
for (let i = 0; i < row.length; i++) row[i] = (rng.next() * 2 - 1) * scale;
for (let j = 0; j < this.w2.length; j++) this.w2[j] = (rng.next() * 2 - 1) * scale;
}
predict(x) {
let y = this.b2;
for (let j = 0; j < this.w1.length; j++) {
let z = this.b1[j];
for (let i = 0; i < x.length; i++) z += this.w1[j][i] * x[i];
this.h[j] = sigmoid(z);
y += this.w2[j] * this.h[j];
}
return y;
}
loss(xs, ys) {
let total = 0;
for (let k = 0; k < xs.length; k++) {
const e = this.predict(xs[k]) - ys[k];
total += e * e;
}
return total / xs.length;
}
// една стъпка: средният наклон по примерите от партидата
step(xs, ys, batch, rate) {
const hidden = this.w2.length, inputs = xs[0].length;
const gw1 = Array.from({ length: hidden }, () => new Array(inputs).fill(0));
const gb1 = new Array(hidden).fill(0), gw2 = new Array(hidden).fill(0);
let gb2 = 0;
for (const k of batch) {
const d = 2 * (this.predict(xs[k]) - ys[k]);
gb2 += d;
for (let j = 0; j < hidden; j++) {
gw2[j] += d * this.h[j];
const dz = d * this.w2[j] * this.h[j] * (1 - this.h[j]);
gb1[j] += dz;
for (let i = 0; i < inputs; i++) gw1[j][i] += dz * xs[k][i];
}
}
const n = batch.length;
this.b2 -= (rate * gb2) / n;
for (let j = 0; j < hidden; j++) {
this.w2[j] -= (rate * gw2[j]) / n;
this.b1[j] -= (rate * gb1[j]) / n;
for (let i = 0; i < inputs; i++) this.w1[j][i] -= (rate * gw1[j][i]) / n;
}
}
}

Разбъркването е Fisher–Yates: отзад напред всяко място се разменя със случайно място преди него.

// Fisher–Yates: всяка подредба е еднакво вероятна
function shuffle(order, rng) {
for (let i = order.length - 1; i > 0; i--) {
const k = Math.floor(rng.next() * (i + 1));
[order[i], order[k]] = [order[k], order[i]];
}
}

Виж го

Цикълът и кривата на грешката

const rng = new Rng(42);
const net = new Net(4, 3);
net.randomize(rng, 1.0);
const size = 8;
const rate = 0.05;
const order = [...trainX.keys()];
console.log("епоха обучение валидация");
for (let epoch = 1; epoch <= 1000; epoch++) {
shuffle(order, rng);
for (let start = 0; start < order.length; start += size) {
const batch = order.slice(start, start + size);
net.step(trainX, trainY, batch, rate);
}
if (epoch % 100 === 0) {
const t = net.loss(trainX, trainY).toFixed(4);
const v = net.loss(validX, validY).toFixed(4);
const e = String(epoch).padStart(5);
console.log(e + t.padStart(10) + v.padStart(11));
}
}
Стъпка по стъпка, с думи
  1. Семе 42: всяко пускане започва от едни и същи случайни тегла. Преди обучението грешката е 7,19, далеч над графиката.
  2. Епоха е едно минаване през всичките 24 поръчки за обучение. Първо се разбъркват, със същия генератор.
  3. Партида от 8 поръчки: напред, грешка, назад, една стъпка. Три стъпки на епоха, 3000 за цялото обучение.
  4. Епоха 100: обучение 0,1365, валидация 0,0756. С поръчките за валидация не е направена нито една стъпка.
  5. Валидацията е най-ниска на епоха 200: 0,0705. После тръгва нагоре, а обучението продължава да пада.
  6. Епоха 1000: обучение 0,0381, валидация 0,2371. Мрежата наизустява три тежки пратки, закъснели по случайност.
  7. Най-добрата мрежа е около епоха 200, не на 1000. Спира се, когато валидацията спре да пада.

Целият цикъл: 1000 епохи, партиди по 8, скорост 0,05:

const rng = new Rng(42);
const net = new Net(4, 3);
net.randomize(rng, 1.0);
const size = 8;
const rate = 0.05;
const order = [...trainX.keys()];
console.log("епоха обучение валидация");
for (let epoch = 1; epoch <= 1000; epoch++) {
shuffle(order, rng);
for (let start = 0; start < order.length; start += size) {
const batch = order.slice(start, start + size);
net.step(trainX, trainY, batch, rate);
}
if (epoch % 100 === 0) {
const t = net.loss(trainX, trainY).toFixed(4);
const v = net.loss(validX, validY).toFixed(4);
const e = String(epoch).padStart(5);
console.log(e + t.padStart(10) + v.padStart(11));
}
}
Изход
епоха обучение валидация
100 0.1365 0.0756
200 0.1041 0.0705
300 0.0826 0.0776
400 0.0667 0.1048
500 0.0556 0.1350
600 0.0486 0.1718
700 0.0443 0.1967
800 0.0417 0.2202
900 0.0396 0.2243
1000 0.0381 0.2371

Обучението пада от 7,19 до 0,0381. Валидацията пада до епоха 200, а после се качва: на епоха 1000 е 0,2371, повече от три пъти по-лоша от най-добрата. Мрежата продължава да се нагажда към поръчките за обучение, включително към това, което в тях е случайност. Към това ще се върнем в края на урока.

Пробвай

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

Ако партидата е 10, а примерите са 24, колко стъпки прави обучението за 1000 епохи?

const examples = 24, size = 10;
let steps = 0;
for (let epoch = 1; epoch <= 1000; epoch++)
for (let start = 0; start < examples; start += size) steps++;
console.log(`стъпки: ${steps}`);
Отговор

Идеята

Партиди: всички, по един или по 8

Колко примера да влизат в една стъпка? Ето същата мрежа за 40 епохи с партида 24 (всички наведнъж), 8 и 1. След всяка стъпка програмата мери грешката на всички 24 поръчки и брои колко пъти стъпката я е вдигнала:

for (const size of [24, 8, 1]) {
const rng = new Rng(42);
const net = new Net(4, 3);
net.randomize(rng, 1.0);
const order = [...trainX.keys()];
let steps = 0, up = 0;
let before = net.loss(trainX, trainY);
for (let epoch = 1; epoch <= 40; epoch++) {
shuffle(order, rng);
for (let start = 0; start < order.length; start += size) {
net.step(trainX, trainY, order.slice(start, start + size), 0.05);
const after = net.loss(trainX, trainY);
steps++;
if (after > before) up++;
before = after;
}
}
const s = String(size).padStart(2), n = String(steps).padStart(3), u = String(up).padStart(3);
console.log(`партида ${s}: ${n} стъпки, ${u} нагоре, грешка ${before.toFixed(4)}`);
}
Изход
партида 24: 40 стъпки, 0 нагоре, грешка 0.2913
партида 8: 120 стъпки, 21 нагоре, грешка 0.1840
партида 1: 960 стъпки, 495 нагоре, грешка 0.1238
  • Всички наведнъж: наклонът е точният наклон на цялата грешка, затова нито една стъпка не я вдига. Но стъпката е една на епоха, и след 40 епохи мрежата е най-далеч.
  • По един пример: 24 стъпки на епоха, затова за 40 епохи стига най-ниско. Но наклонът на един пример е груба догадка за наклона на всички: 495 от 960 стъпки вдигат грешката.
  • Партида от 8: три стъпки на епоха и само 21 от 120 нагоре.

Това се казва mini-batch (партида): стъпка по средния наклон на няколко примера. Партидата е компромисът, който ползва почти всеки. На видеокарта примерите от една партида се смятат наведнъж, затова в истински проекти партидите са десетки или стотици примери.

Пробвай

Попълни

Наклоните на партидата се събират, а стъпката е по средния наклон. На какво се дели?

const n = ;
this.b2 -= (rate * gb2) / n;

Идеята

Лоша скорост, лошо начало

Кривата казва какво не е наред. Трите скорости от урока за градиентното спускане, но в мрежа. Грешката е на епоха 50, 100 и 200:

for (const rate of [0.005, 0.05, 2.0]) {
const rng = new Rng(42);
const net = new Net(4, 3);
net.randomize(rng, 1.0);
const order = [...trainX.keys()];
let line = `скорост ${rate.toFixed(3)}:`;
for (let epoch = 1; epoch <= 200; epoch++) {
trainEpoch(net, trainX, trainY, order, 8, rate, rng);
const loss = net.loss(trainX, trainY);
if (Number.isNaN(loss) || loss > 1e6) {
line += ` разходи се на епоха ${epoch}`;
break;
}
if ([50, 100, 200].includes(epoch)) line += ` ${loss.toFixed(4)}`;
}
console.log(line);
}
Изход
скорост 0.005: 0.3892 0.3201 0.2472
скорост 0.050: 0.1698 0.1365 0.1041
скорост 2.000: разходи се на епоха 2

С 0,005 мрежата учи, но бавно: на епоха 200 (0,2472) е по-зле, отколкото 0,05 на епоха 50 (0,1698). Кривата пада полегато и не спира да пада, а това значи, че можеш да вдигнеш скоростта. С 2 всяка стъпка прескача дъното и следващият наклон е още по-стръмен. Грешката е над милион още на втората епоха, а след нея идват безкрайност и NaN. Затова програмата проверява и спира.

Началото също има значение:

let rng = new Rng(42);
const zeros = new Net(4, 3); // без randomize: всички тегла са 0
let order = [...trainX.keys()];
for (let epoch = 1; epoch <= 200; epoch++)
trainEpoch(zeros, trainX, trainY, order, 8, 0.05, rng);
console.log(`нули: грешка ${zeros.loss(trainX, trainY).toFixed(4)}`);
zeros.w1.forEach((row, j) =>
console.log(` неврон ${j + 1}: ` + row.map((x) => x.toFixed(2)).join(" ")));
for (const scale of [8, 1]) {
rng = new Rng(42);
const net = new Net(4, 3);
net.randomize(rng, scale);
const first = net.loss(trainX, trainY);
order = [...trainX.keys()];
for (let epoch = 1; epoch <= 200; epoch++)
trainEpoch(net, trainX, trainY, order, 8, 0.05, rng);
const last = net.loss(trainX, trainY);
console.log(`тегла ×${scale}: в началото ${first.toFixed(4)}, след 200 епохи ${last.toFixed(4)}`);
}
Изход
нули: грешка 0.1127
неврон 1: 0.99 0.47 0.19
неврон 2: 0.99 0.47 0.19
неврон 3: 0.99 0.47 0.19
неврон 4: 0.99 0.47 0.19
тегла ×8: в началото 33.9817, след 200 епохи 0.2971
тегла ×1: в началото 7.1855, след 200 епохи 0.1041
  • Нули. Четирите скрити неврона започват еднакви, получават еднакви наклони и правят еднакви стъпки. След 200 епохи теглата им са до последната цифра едни и същи: мрежата е с един скрит неврон, повторен четири пъти. Случайните начални тегла са там, за да се различат невроните.
  • Тегла ×8. Сумите в сигмоидата са големи, затова при повечето поръчки тя е почти 0 или почти 1, там, където е плоска. Плоска крива значи почти нулев наклон и малки стъпки: след 200 епохи грешката е почти три пъти по-голяма, отколкото с теглата ×1.

Пробвай

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

Колега пише цикъла по свой начин и вместо номерата разбърква самите масиви. Грешката стои над 0,6 и не пада, колкото и епохи да пусне. На кой ред е грешката?

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

Идеята

Кога да спреш

Какво точно наизустява мрежата? Сред поръчките за обучение има три тежки пратки (9,0 kg, 8,2 kg и 8,6 kg), които закъсняха с цял ден по случайност. Ето трите тежки пратки от валидацията, които не са закъснели:

const rng = new Rng(42);
const net = new Net(4, 3);
net.randomize(rng, 1.0);
const order = [...trainX.keys()];
for (let epoch = 1; epoch <= 1000; epoch++) {
trainEpoch(net, trainX, trainY, order, 8, 0.05, rng);
if (epoch === 200 || epoch === 1000) {
console.log(`епоха ${epoch}:`);
for (const k of [1, 3, 6]) { // тежките пратки от валидацията
const [km, , kg, days] = VALID[k];
const y = net.predict(validX[k]).toFixed(2);
console.log(` ${km} km, ${kg.toFixed(1)} kg: ${y} (истина ${days.toFixed(1)})`);
}
}
}
Изход
епоха 200:
22 km, 8.8 kg: 1.31 (истина 1.0)
95 km, 7.9 kg: 2.53 (истина 2.5)
300 km, 9.4 kg: 3.13 (истина 2.6)
епоха 1000:
22 km, 8.8 kg: 1.84 (истина 1.0)
95 km, 7.9 kg: 3.09 (истина 2.5)
300 km, 9.4 kg: 3.38 (истина 2.6)

На епоха 200 предсказанията са близо до истината. На епоха 1000 мрежата е научила „тежкото пътува по-бавно“: пратката на 22 km получава 1,84 дни вместо 1,0. Правило, което в данните е само случайност, е преобучение: обучението се подобрява, а новите поръчки се влошават.

Затова се спира, когато валидацията спре да пада. Това е ранно спиране (early stopping). Програмата проверява валидацията на всеки 10 епохи и пази снимка (копие) на най-добрата мрежа. Ако 100 епохи няма подобрение, спира и връща снимката:

// копие на теглата: снимка на мрежата в този момент
copy() {
const c = new Net(this.w2.length, this.w1[0].length);
c.w1 = this.w1.map((row) => [...row]);
c.b1 = [...this.b1];
c.w2 = [...this.w2];
c.b2 = this.b2;
return c;
}
const rng = new Rng(42);
const net = new Net(4, 3);
net.randomize(rng, 1.0);
const order = [...trainX.keys()];
// проверка на всеки 10 епохи; спираме след 100 без подобрение
let best = Infinity, bestEpoch = 0, epoch = 0;
let bestNet = net.copy();
while (epoch < 1000 && epoch - bestEpoch < 100) {
epoch++;
trainEpoch(net, trainX, trainY, order, 8, 0.05, rng);
if (epoch % 10 === 0) {
const valid = net.loss(validX, validY);
if (valid < best) {
best = valid;
bestEpoch = epoch;
bestNet = net.copy();
}
}
}
console.log(`най-ниска валидация: ${best.toFixed(4)} на епоха ${bestEpoch}`);
console.log(`спрях на епоха ${epoch}`);
console.log(`пазя мрежата от епоха ${bestEpoch}: ${bestNet.loss(validX, validY).toFixed(4)}`);
Изход
най-ниска валидация: 0.0667 на епоха 230
спрях на епоха 330
пазя мрежата от епоха 230: 0.0667

Най-добрата валидация е 0,0667 на епоха 230, по-добре от всяка точка на кривата горе, защото проверката е по-честа. Обучението спира на епоха 330 вместо 1000, и мрежата, която се пази, е от епоха 230.

Валидацията реши кога да спреш, затова вече не е честна мярка за новите поръчки. Окончателната оценка се прави на трета част от данните, теста, който не е участвал в нито едно решение.

Идеята

Кога вреди

  • Мрежа за всичко. 32 поръчки и три признака са малко данни: права с няколко признака или по-проста формула често стигат, обясняват се по-лесно и не наизустяват. Мрежата си струва, когато правилата са твърде много, за да ги напишеш, и има достатъчно примери.
  • Едно пускане. Друго семе дава други начални тегла и друга крива. Едно пускане може да е късмет. Сравнявай промени с няколко семена и гледай средното.
  • Малка валидация. Осем поръчки са шумна мярка: една странна поръчка мести кривата. Колкото по-малко са данните, толкова по-внимателно четеш една точка.
  • Ранното спиране не поправя данните. Трите случайни закъснения още са там. По-добре е да ги намериш и да ги махнеш или поправиш, а ако теглото наистина не влияе, да не го даваш като признак.
  • Цената. Тук обучението е под секунда. Истинските мрежи се учат часове или седмици на много видеокарти. Клъстерите за обучение и пускането на модел в работа са инфраструктура и се учат в курса по DevOps ZeroToOps.
По-точно, за любопитните

Стъпката с формула. Партидата е B примера, а η е скоростта. За всяко тегло w:

w ← w − η · (1/B) · Σ ∂L/∂w

С думи: събери наклона на грешката по w за всеки пример от партидата, раздели на B, умножи по скоростта и извади. ∂L/∂w е наклонът, който backprop смята за едно тегло. При B, равно на всички примери, това е обикновеното градиентно спускане. При B = 1 се казва стохастично (SGD), а с партиди е mini-batch SGD.

Защо 2 · (y − t). Грешката на един пример е (y − t)². Наклонът ѝ по y е 2 · (y − t): ако изходът е с 0,5 над истината, наклонът е 1, а ако е с 0,5 под нея, е −1. Оттам започва backprop в step.

Истинските инструменти. Практиката на машинното обучение е главно на Python. В PyTorch същият цикъл е: DataLoader(..., batch_size=8, shuffle=True) дава партидите, optimizer.zero_grad() нулира наклоните (нашите нови масиви в началото на стъпката), loss.backward() е backprop, а optimizer.step() мести теглата. Семето е torch.manual_seed(42). В C# същото прави ML.NET или TorchSharp, в Java DJL или Tribuo, а в JavaScript TensorFlow.js, ONNX Runtime пуска вече научен модел. Цикълът е същият, само че матриците се смятат на видеокарта.

По-умни стъпки. Почти никой не ползва голата стъпка от урока. Momentum помни посоката на последните стъпки и минава по-гладко през шума на партидите. Adam пази отделна скорост за всяко тегло. Скоростта често се сваля с времето (learning rate schedule): големи стъпки в началото, малки накрая.

Началните тегла. Тук случайните тегла са от −1 до 1. В по-големи мрежи интервалът се смята по броя входове на неврона, например ±1/√n (Xavier, He), за да не са сумите твърде големи още в първия слой.

Еднакви числа на четирите езика. Събиранията и умноженията тук вървят в един и същ ред, затова резултатът е един и същ бит по бит. exp не е обещано, че е еднакъв до последния бит на всеки език, затова програмите печатат по 4 знака. Изходът е проверен и е един и същ на Java, C#, Python и JavaScript.

toFixed(4) винаги пише точка, независимо от езика на системата.

Упражнение

Ранно спиране при три скорости

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

Вземи ранното спиране от урока и го пусни за скорости 0,02, 0,05 и 0,2, всеки път с нова мрежа от семе 42. За всяка скорост отпечатай най-ниската валидация, епохата ѝ и епохата, на която обучението спира. Не е нужно да пазиш копие на мрежата.

Изход
скорост 0.02: най-ниска валидация 0.0683 на епоха 470, спрях на епоха 570
скорост 0.05: най-ниска валидация 0.0667 на епоха 230, спрях на епоха 330
скорост 0.20: най-ниска валидация 0.0628 на епоха 50, спрях на епоха 150

С 0,2 мрежата стига най-ниската си валидация за 50 епохи и спира на 150. С 0,02 ѝ трябват 470. По-голямата скорост стига дъното по-бързо, но и тръгва нагоре по-рано. Затова валидацията се проверява често, а не само в края.

Още задачи

  • Шест неврона. Пусни основния цикъл с 6 скрити неврона. По-рано ли тръгва нагоре валидацията?
  • Без теглото. Махни теглото от признаците (2 входа вместо 3) и пусни основния цикъл. Какво става с валидацията на епоха 1000?

Тест

Провери се

1. Какво е една епоха?
2. Грешката на обучението пада през цялото време, а валидацията е най-ниска на епоха 200 и после расте. Коя мрежа пазиш?
3. След втората епоха грешката е над милион, а после става NaN. Кое е най-вероятно?
4. Всички тегла в началото са 0. Какво става с четирите скрити неврона?
5. Защо обикновено се учи с партиди, а не с всички примери наведнъж или по един?

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