Обучението от начало до край
Накратко: Обучението на невронна мрежа е цикъл по епохи: разбъркване, партиди, проход напред, обратно разпространение и стъпка, докато грешката на валидацията спре да пада.
Проблем
Мрежата казва −0,23 дни
Страницата за плащане трябва да казва „пристига до 2 дни“. Времето на доставката зависи от разстоянието, от това дали поръчката е след 16:00 (тогава тръгва на другия ден) и малко от теглото. Права линия от линейната регресия не следва тази крива: в града е около ден, до 200 km расте бързо, после все по-бавно.
Мрежата от урока за backprop може да следва крива. Ето я с 4 скрити неврона и случайни тегла, преди обучение:
Rng rng = new Rng(42);Net net = new Net(4, 3);net.randomize(rng, 1.0);System.out.printf("грешка (MSE): %.4f%n", net.loss(trainX, trainY));for (int k : new int[] {0, 12, 23}) System.out.printf("%.0f km: мрежата казва %.2f, истина %.1f дни%n", TRAIN[k][0], net.predict(trainX[k]), trainY[k]);грешка (MSE): 7.18555 km: мрежата казва -0.23, истина 1.0 дни150 km: мрежата казва -0.17, истина 2.3 дни520 km: мрежата казва 0.18, истина 3.0 дниПоръчка на 5 km пристига след −0,23 дни. В урока за backprop видя една стъпка за един пример. Колко стъпки трябват, с кои примери, в какъв ред и кога да спреш?
Идеята
Цикълът на обучение
Обучението е един цикъл, който се върти, докато мрежата спре да се подобрява:
- Начални тегла: малки случайни числа от генератор със семе.
- Епоха: едно минаване през всички примери за обучение. Преди всяка епоха примерите се разбъркват, за да не учи мрежата реда им.
- Партида: няколко примера наведнъж. За всеки има проход напред и грешка, после назад. Наклоните се усредняват и мрежата прави една стъпка.
- Проверка: на всеки 100 епохи програмата печата грешката на обучението и на валидацията.
Това е цикълът на обучение (training loop). Числата, които печата, нарисувани като графика, са кривата на грешката. По нея разбираш дали мрежата учи, дали скоростта е добра и кога да спреш.
Данните са 32 поръчки от магазина: 24 за обучение и 8 за валидация. Мрежата никога не прави стъпка с поръчките за валидация. Те са мярката за нови поръчки.
Виж го
Данните и мрежата
Поръчките, записани в кода:
// поръчка: km, след 16:00 (1 или 0), kg, дни до доставкатаstatic final double[][] 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}};// валидация: поръчки, които мрежата не вижда при обучениетоstatic final double[][] 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, десетки kgstatic double[][] inputs(double[][] orders) { double[][] xs = new double[orders.length][]; for (int k = 0; k < orders.length; k++) xs[k] = new double[] {orders[k][0] / 100, orders[k][1], orders[k][2] / 10}; return xs;}
static double[] targets(double[][] orders) { double[] ys = new double[orders.length]; for (int k = 0; k < orders.length; k++) ys[k] = orders[k][3]; return ys;}Случайността идва от собствен генератор със семе. Едно семе дава едни и същи числа на всеки компютър и на четирите езика, затова урокът печата един и същ изход навсякъде. Числото 48271 и делението на 2³¹ − 1 са стар и проверен генератор (Park–Miller). Произведението е под 2⁵³, затова се смята точно дори в JavaScript:
class Rng { private long state;
Rng(long seed) { state = seed; }
// следващото число между 0 и 1; едно семе дава едни и същи числа double next() { state = state * 48271 % 2147483647; return (double) state / 2147483647; }}new Random(42) в Java също има семе. Тук генераторът е наш, за да е същият като в другите три езика.Мрежата е тази от урока за backprop, събрана в клас: 3 входа, 4 скрити неврона със сигмоида и един изход без активация, защото дните не са вероятност. step прави проход напред и назад за всяка поръчка от партидата, събира наклоните и мести всяко тегло веднъж, със средния наклон:
class Net { final double[][] w1; // вход → скрит слой final double[] b1; final double[] w2; // скрит слой → изход double b2; final double[] h; // изходите на скрития слой при последното predict
Net(int hidden, int inputs) { w1 = new double[hidden][inputs]; b1 = new double[hidden]; w2 = new double[hidden]; h = new double[hidden]; }
// случайни тегла от -scale до scale; отместванията остават 0 void randomize(Rng rng, double scale) { for (double[] row : w1) for (int i = 0; i < row.length; i++) row[i] = (rng.next() * 2 - 1) * scale; for (int j = 0; j < w2.length; j++) w2[j] = (rng.next() * 2 - 1) * scale; }
static double sigmoid(double z) { return 1 / (1 + Math.exp(-z)); }
double predict(double[] x) { double y = b2; for (int j = 0; j < w2.length; j++) { double z = b1[j]; for (int i = 0; i < x.length; i++) z += w1[j][i] * x[i]; h[j] = sigmoid(z); y += w2[j] * h[j]; } return y; }
double loss(double[][] xs, double[] ys) { double total = 0; for (int k = 0; k < xs.length; k++) { double e = predict(xs[k]) - ys[k]; total += e * e; } return total / xs.length; }
// една стъпка: средният наклон по примерите от партидата void step(double[][] xs, double[] ys, int[] batch, double rate) { int hidden = w2.length, inputs = xs[0].length; double[][] gw1 = new double[hidden][inputs]; double[] gb1 = new double[hidden], gw2 = new double[hidden]; double gb2 = 0; for (int k : batch) { double d = 2 * (predict(xs[k]) - ys[k]); gb2 += d; for (int j = 0; j < hidden; j++) { gw2[j] += d * h[j]; double dz = d * w2[j] * h[j] * (1 - h[j]); gb1[j] += dz; for (int i = 0; i < inputs; i++) gw1[j][i] += dz * xs[k][i]; } } int n = batch.length; b2 -= rate * gb2 / n; for (int j = 0; j < hidden; j++) { w2[j] -= rate * gw2[j] / n; b1[j] -= rate * gb1[j] / n; for (int i = 0; i < inputs; i++) w1[j][i] -= rate * gw1[j][i] / n; } }}Разбъркването е Fisher–Yates: отзад напред всяко място се разменя със случайно място преди него.
// Fisher–Yates: всяка подредба е еднакво вероятнаstatic void shuffle(int[] order, Rng rng) { for (int i = order.length - 1; i > 0; i--) { int k = (int) (rng.next() * (i + 1)); int tmp = order[i]; order[i] = order[k]; order[k] = tmp; }}Виж го
Цикълът и кривата на грешката
Rng rng = new Rng(42);Net net = new Net(4, 3);net.randomize(rng, 1.0);int size = 8;double rate = 0.05;int[] order = IntStream.range(0, trainX.length).toArray();System.out.println("епоха обучение валидация");for (int epoch = 1; epoch <= 1000; epoch++) { shuffle(order, rng); for (int start = 0; start < order.length; start += size) { int end = Math.min(start + size, order.length); int[] batch = Arrays.copyOfRange(order, start, end); net.step(trainX, trainY, batch, rate); } if (epoch % 100 == 0) { double t = net.loss(trainX, trainY); double v = net.loss(validX, validY); System.out.printf("%5d%10.4f%11.4f%n", epoch, t, v); }}Стъпка по стъпка, с думи
- Семе 42: всяко пускане започва от едни и същи случайни тегла. Преди обучението грешката е 7,19, далеч над графиката.
- Епоха е едно минаване през всичките 24 поръчки за обучение. Първо се разбъркват, със същия генератор.
- Партида от 8 поръчки: напред, грешка, назад, една стъпка. Три стъпки на епоха, 3000 за цялото обучение.
- Епоха 100: обучение 0,1365, валидация 0,0756. С поръчките за валидация не е направена нито една стъпка.
- Валидацията е най-ниска на епоха 200: 0,0705. После тръгва нагоре, а обучението продължава да пада.
- Епоха 1000: обучение 0,0381, валидация 0,2371. Мрежата наизустява три тежки пратки, закъснели по случайност.
- Най-добрата мрежа е около епоха 200, не на 1000. Спира се, когато валидацията спре да пада.
Целият цикъл: 1000 епохи, партиди по 8, скорост 0,05:
Rng rng = new Rng(42);Net net = new Net(4, 3);net.randomize(rng, 1.0);int size = 8;double rate = 0.05;int[] order = IntStream.range(0, trainX.length).toArray();System.out.println("епоха обучение валидация");for (int epoch = 1; epoch <= 1000; epoch++) { shuffle(order, rng); for (int start = 0; start < order.length; start += size) { int end = Math.min(start + size, order.length); int[] batch = Arrays.copyOfRange(order, start, end); net.step(trainX, trainY, batch, rate); } if (epoch % 100 == 0) { double t = net.loss(trainX, trainY); double v = net.loss(validX, validY); System.out.printf("%5d%10.4f%11.4f%n", epoch, t, v); }}епоха обучение валидация 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 епохи?
int examples = 24, size = 10, steps = 0;for (int epoch = 1; epoch <= 1000; epoch++) for (int start = 0; start < examples; start += size) steps++;System.out.println("стъпки: " + steps);24 примера по 10 са три партиди: 10, 10 и 4. Последната е по-малка, но е стъпка като другите, затова са 3 на епоха и 3000 общо. В step средният наклон е по броя примери в партидата, така че и малката партида прави стъпка с нормален размер.
Идеята
Партиди: всички, по един или по 8
Колко примера да влизат в една стъпка? Ето същата мрежа за 40 епохи с партида 24 (всички наведнъж), 8 и 1. След всяка стъпка програмата мери грешката на всички 24 поръчки и брои колко пъти стъпката я е вдигнала:
for (int size : new int[] {24, 8, 1}) { Rng rng = new Rng(42); Net net = new Net(4, 3); net.randomize(rng, 1.0); int[] order = IntStream.range(0, trainX.length).toArray(); int steps = 0, up = 0; double before = net.loss(trainX, trainY); for (int epoch = 1; epoch <= 40; epoch++) { shuffle(order, rng); for (int start = 0; start < order.length; start += size) { int end = Math.min(start + size, order.length); net.step(trainX, trainY, Arrays.copyOfRange(order, start, end), 0.05); double after = net.loss(trainX, trainY); steps++; if (after > before) up++; before = after; } } System.out.printf("партида %2d: %3d стъпки, %3d нагоре, грешка %.4f%n", size, steps, up, before);}партида 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 (партида): стъпка по средния наклон на няколко примера. Партидата е компромисът, който ползва почти всеки. На видеокарта примерите от една партида се смятат наведнъж, затова в истински проекти партидите са десетки или стотици примери.
Пробвай
Попълни
Наклоните на партидата се събират, а стъпката е по средния наклон. На какво се дели?
int n = ;
b2 -= rate * gb2 / n;На броя примери в партидата: batch.length. Без делението партида от 24 прави 24 пъти по-голяма стъпка от партида от 1, и смяната на партидата сменя и скоростта на учене.
Идеята
Лоша скорост, лошо начало
Кривата казва какво не е наред. Трите скорости от урока за градиентното спускане, но в мрежа. Грешката е на епоха 50, 100 и 200:
for (double rate : new double[] {0.005, 0.05, 2.0}) { Rng rng = new Rng(42); Net net = new Net(4, 3); net.randomize(rng, 1.0); int[] order = IntStream.range(0, trainX.length).toArray(); System.out.printf("скорост %.3f:", rate); for (int epoch = 1; epoch <= 200; epoch++) { trainEpoch(net, trainX, trainY, order, 8, rate, rng); double loss = net.loss(trainX, trainY); if (Double.isNaN(loss) || loss > 1e6) { System.out.print(" разходи се на епоха " + epoch); break; } if (epoch == 50 || epoch == 100 || epoch == 200) System.out.printf(" %.4f", loss); } System.out.println();}скорост 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. Затова програмата проверява и спира.
Началото също има значение:
Rng rng = new Rng(42);Net zeros = new Net(4, 3); // без randomize: всички тегла са 0int[] order = IntStream.range(0, trainX.length).toArray();for (int epoch = 1; epoch <= 200; epoch++) trainEpoch(zeros, trainX, trainY, order, 8, 0.05, rng);System.out.printf("нули: грешка %.4f%n", zeros.loss(trainX, trainY));for (int j = 0; j < zeros.w1.length; j++) System.out.printf(" неврон %d: %.2f %.2f %.2f%n", j + 1, zeros.w1[j][0], zeros.w1[j][1], zeros.w1[j][2]);
for (double scale : new double[] {8, 1}) { rng = new Rng(42); Net net = new Net(4, 3); net.randomize(rng, scale); double first = net.loss(trainX, trainY); order = IntStream.range(0, trainX.length).toArray(); for (int epoch = 1; epoch <= 200; epoch++) trainEpoch(net, trainX, trainY, order, 8, 0.05, rng); System.out.printf("тегла ×%.0f: в началото %.4f, след 200 епохи %.4f%n", scale, first, net.loss(trainX, trainY));}нули: грешка 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 и не пада, колкото и епохи да пусне. На кой ред е грешката?
Избери реда с грешката.
Двете разбърквания взимат различни случайни числа от генератора, затова след тях поръчките и отговорите вече не са в един ред. Мрежата учи например поръчка на 5 km с отговора на поръчка на 410 km. Най-доброто, което може, е да казва средното за всички, и грешката стои около разсейването на отговорите. Поправката е като в урока: разбърквай номерата (order) и взимай поръчката и отговора с един и същ номер.
Идеята
Кога да спреш
Какво точно наизустява мрежата? Сред поръчките за обучение има три тежки пратки (9,0 kg, 8,2 kg и 8,6 kg), които закъсняха с цял ден по случайност. Ето трите тежки пратки от валидацията, които не са закъснели:
Rng rng = new Rng(42);Net net = new Net(4, 3);net.randomize(rng, 1.0);int[] order = IntStream.range(0, trainX.length).toArray();for (int epoch = 1; epoch <= 1000; epoch++) { trainEpoch(net, trainX, trainY, order, 8, 0.05, rng); if (epoch == 200 || epoch == 1000) { System.out.println("епоха " + epoch + ":"); for (int k : new int[] {1, 3, 6}) // тежките пратки от валидацията System.out.printf(" %.0f km, %.1f kg: %.2f (истина %.1f)%n", VALID[k][0], VALID[k][2], net.predict(validX[k]), validY[k]); }}епоха 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 епохи няма подобрение, спира и връща снимката:
// копие на теглата: снимка на мрежата в този моментNet copy() { Net c = new Net(w2.length, w1[0].length); for (int j = 0; j < w2.length; j++) { c.w1[j] = w1[j].clone(); c.b1[j] = b1[j]; c.w2[j] = w2[j]; } c.b2 = b2; return c;}Rng rng = new Rng(42);Net net = new Net(4, 3);net.randomize(rng, 1.0);int[] order = IntStream.range(0, trainX.length).toArray();// проверка на всеки 10 епохи; спираме след 100 без подобрениеdouble best = Double.MAX_VALUE;int bestEpoch = 0, epoch = 0;Net bestNet = net.copy();while (epoch < 1000 && epoch - bestEpoch < 100) { epoch++; trainEpoch(net, trainX, trainY, order, 8, 0.05, rng); if (epoch % 10 == 0) { double valid = net.loss(validX, validY); if (valid < best) { best = valid; bestEpoch = epoch; bestNet = net.copy(); } }}System.out.printf("най-ниска валидация: %.4f на епоха %d%n", best, bestEpoch);System.out.println("спрях на епоха " + epoch);System.out.printf("пазя мрежата от епоха %d: %.4f%n", bestEpoch, bestNet.loss(validX, validY));най-ниска валидация: 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.
System.out.printf пише числото по Locale по подразбиране: на компютър с българска настройка ще видиш 0,1365. С Locale.ROOT като първи аргумент винаги е с точка.Упражнение
Ранно спиране при три скорости
Реши задачата в редактора си.
Вземи ранното спиране от урока и го пусни за скорости 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?
Тест
Провери се
Свързани уроци
Раздел „Свързани уроци“- Нужно отпреди: Преобучение: моделът, който наизустява · Неврон · Слоеве · Обратно разпространение
- Продължава в: Цифрите от касовата бележка
- Задачи по темата: Невронна мрежа отвътре