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

Неврон

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

Проблем

Мрежата спря плащането. Защо?

Магазинът сменя старото правило за измами с „невронна мрежа“ от външна фирма. На първия ден клиент плаща 230 € от нова сметка с чужда карта, а плащането спира за ръчна проверка. Клиентът пише в поддръжката: „Защо?“

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

Кутията се отваря. Вътре няма нищо, което не си виждал: умножение, събиране и една функция.

Идеята

Неврон: сума с тегла

Изкуственият неврон получава няколко числа (входовете). Всеки вход има свое тегло: колко силно влияе и в коя посока. Невронът умножава всеки вход по теглото му, събира и добавя още едно число, отместването (bias). Резултатът се казва z.

После z минава през функция, която решава какво излиза. Тя се казва функция на активация. Засега е сигмоидата: тя свива всяко z до число между 0 и 1.

Познато ли ти е? Това е логистичната регресия: претеглена сума, сигмоида, вероятност и праг. Един неврон със сигмоида е точно тя. Невронната мрежа е много такива неврони, свързани един след друг.

Виж го

Невронът в код

Трите функции: претеглената сума, сигмоидата и невронът, който ги свързва.

static double weightedSum(double[] x, double[] w, double b) {
double z = b; // отместването
for (int i = 0; i < x.length; i++) {
z += x[i] * w[i]; // вход × тегло
}
return z;
}
static double sigmoid(double z) {
return 1 / (1 + Math.exp(-z));
}
static double neuron(double[] x, double[] w, double b) {
return sigmoid(weightedSum(x, w, b));
}

Плащанията имат три признака: сумата в стотици евро, нова сметка (1 или 0) и чужда карта (1 или 0). Теглата са сложени на ръка, за да се смятат наум. Как мрежата ги намира сама, е урокът за обратното разпространение.

// сума (стотици €), нова сметка, чужда карта
double[] w = {0.9, 1.4, 1.8};
double b = -4.0;
double[][] payments = {
{0.40, 0, 0},
{1.20, 1, 0},
{8.90, 1, 1},
{0.60, 0, 1},
{6.40, 0, 0},
{2.30, 1, 1},
};
System.out.println(" сума € нова чужда z p");
for (double[] x : payments) {
double z = weightedSum(x, w, b);
double p = sigmoid(z);
String verdict = p >= 0.5 ? "проверка" : "ок";
System.out.printf("%8.0f %5.0f %7.0f %6.2f %6.3f %s%n",
x[0] * 100, x[1], x[2], z, p, verdict);
}
Изход
сума € нова чужда z p
40 0 0 -3.64 0.026 ок
120 1 0 -1.52 0.179 ок
890 1 1 7.21 0.999 проверка
60 0 1 -1.66 0.160 ок
640 0 0 1.76 0.853 проверка
230 1 1 1.27 0.781 проверка

Сега можеш да отговориш на клиента. Плащането за 230 € е последният ред: z = −4 + 2,3 × 0,9 + 1 × 1,4 + 1 × 1,8 = −4 + 2,07 + 1,4 + 1,8 = 1,27. Сигмоидата дава 0,781, над прага 0,5. Новата сметка и чуждата карта добавиха 3,2 към сумата. Без тях z щеше да е −1,93, а вероятността около 0,13.

Виж го

Сигналът минава през неврона

static double weightedSum(double[] x, double[] w, double b) {
double z = b; // отместването
for (int i = 0; i < x.length; i++) {
z += x[i] * w[i]; // вход × тегло
}
return z;
}
static double sigmoid(double z) {
return 1 / (1 + Math.exp(-z));
}
static double neuron(double[] x, double[] w, double b) {
return sigmoid(weightedSum(x, w, b));
}
Стъпка по стъпка, с думи
  1. Плащане за 230 € от нова сметка с чужда карта. Входовете са 2,30, 1 и 1.
  2. z започва от отместването: −4,00.
  3. Всеки вход по теглото си: 2,30 × 0,9 = 2,07, 1 × 1,4 = 1,40, 1 × 1,8 = 1,80.
  4. Сборът: z = −4 + 2,07 + 1,40 + 1,80 = 1,27.
  5. Сигмоидата свива z между 0 и 1: σ(1,27) = 0,781.
  6. 0,781 е над прага 0,5: плащането отива за проверка.
  7. 40 € от стара сметка с българска карта: z = −4 + 0,36 = −3,64, p = 0,026. Ок.
  8. Невронът: сума с тегла, отместване и сигмоида. Това е логистичната регресия.

Отместването е първо, после всеки вход добавя своя дял. Сигмоидата не променя реда: по-голямо z дава по-голямо p. Тя само го свива между 0 и 1, за да се чете като вероятност.

Пробвай

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

Колега пише претеглената сума сам. С отместване −4 всяко плащане излиза с много по-малко z, отколкото трябва, и нищо не стига до проверка. На кой ред е грешката?

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

Проблем

Три неврона, а колкото един

Колега иска мрежата да хваща по-сложни правила. Слага два неврона, които четат плащането, и трети, който чете техните изходи. Активация няма никъде: всеки неврон връща самата сума z.

Програмата пресмята трите неврона и, отделно, един неврон, чиито тегла са смятани от техните:

// два неврона четат плащането, без активация
double[] w1 = {0.5, 1.0, 1.5};
double b1 = -1.0;
double[] w2 = {1.0, -0.5, 0.5};
double b2 = 0.5;
// трети неврон чете техните изходи
double[] v = {1.5, -0.5};
double c = 0.5;
// същото като един неврон с тези тегла
double[] w3 = new double[3];
for (int i = 0; i < 3; i++) {
w3[i] = v[0] * w1[i] + v[1] * w2[i];
}
double b3 = v[0] * b1 + v[1] * b2 + c;
System.out.printf("w = [%.2f, %.2f, %.2f], b = %.2f%n", w3[0], w3[1], w3[2], b3);
double[][] payments = {{0.40, 0, 0}, {1.20, 1, 0}, {8.90, 1, 1}};
System.out.println(" сума € три неврона един неврон");
for (double[] x : payments) {
double a1 = weightedSum(x, w1, b1);
double a2 = weightedSum(x, w2, b2);
double out = weightedSum(new double[] {a1, a2}, v, c);
double one = weightedSum(x, w3, b3);
System.out.printf("%8.0f %13.4f %12.4f%n", x[0] * 100, out, one);
}
Изход
w = [0.25, 1.75, 2.00], b = -1.25
сума € три неврона един неврон
40 -1.1500 -1.1500
120 0.8000 0.8000
890 4.7250 4.7250

Колоните съвпадат до последната цифра. Трите неврона са колкото един. Причината е училищна алгебра: сума от суми е пак сума. Каквото и да наредиш едно след друго, без активация резултатът е една права (по-точно равнина) с други тегла.

Идеята

Активацията пречупва правата

Новото идва едва когато между невроните има функция, която не е права. Двете най-честите:

  • Сигмоида: свива z до между 0 и 1. Удобна е за изхода, когато искаш вероятност.
  • ReLU (rectified linear unit): max(0, z). Положителното минава, отрицателното става 0. Това е най-честата активация в скритите неврони на днешните мрежи.
static double relu(double z) {
return Math.max(0, z);
}
double[] zs = {-3, -1, 0, 1, 3};
System.out.println(" z sigmoid ReLU");
for (double z : zs) {
System.out.printf("%6.2f %8.4f %5.2f%n", z, sigmoid(z), relu(z));
}
Изход
z sigmoid ReLU
-3.00 0.0474 0.00
-1.00 0.2689 0.00
0.00 0.5000 0.00
1.00 0.7311 1.00
3.00 0.9526 3.00

И двете са огънати. Сигмоидата е „S“, а ReLU е права с пречупване при нулата. Сума от пречупени линии вече не е права, затова неврони с активация един след друг могат да опишат правило, което един неврон не може. Такова правило идва в следващия урок.

Пробвай

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

Неврон с ReLU оценява колко дни закъснение има доставката. Входовете са разстоянието в стотици километри и дали колетът е тежък. Закъснението не може да е отрицателно, затова ReLU. Какво ще отпечата?

double[] w = {0.5, 1.0}; // стотици км, тежък колет (1 или 0)
double b = -1.0;
double near = relu(weightedSum(new double[] {1.5, 0}, w, b));
double far = relu(weightedSum(new double[] {4.4, 0}, w, b));
double heavy = relu(weightedSum(new double[] {3.0, 1}, w, b));
System.out.printf("%.1f %.1f %.1f%n", near, far, heavy);
Отговор

Пробвай

Попълни

Довърши ReLU.

static double relu(double z) {
    return (0, z);
}

Идеята

Кога един неврон стига, а кога вреди

  • Правилото е почти право. Ако признаците сами по себе си казват отговора (по-голяма сума, повече риск), един неврон, тоест логистична регресия, прави колкото и голяма мрежа. Учи се по-бързо и иска по-малко данни.
  • Данните са малко. Мрежа със стотици тегла и 200 примера ги наизустява. Това е преобучението.
  • Трябва да обясниш решението. Теглата на един неврон се четат: „новата сметка добави 1,4“. В мрежа с много слоеве отговорът „защо“ е много по-труден, а при спряно плащане или отказан кредит той се иска.
  • Правилото е известно и твърдо. „Над 5 000 € винаги ръчна проверка“ е if, не модел.
  • Числата на входа са в различен мащаб. Сумата в евро (40, 890) до признаци 0 и 1 дава тегла, които трудно се учат. Затова сумата тук е в стотици евро. В истински модел входовете се нормират.
По-точно, за любопитните

Формулите. За неврон с входове x₁, x₂, x₃:

z = w₁·x₁ + w₂·x₂ + w₃·x₃ + b

p = σ(z) = 1 / (1 + e^(−z))

С думи: всеки вход по теглото си, плюс отместването, после сигмоидата. e е числото на Ойлер (около 2,718), а Math.exp(-z) е e на степен −z.

Защо три неврона без активация са един. Ако a₁ = w₁·x + b₁ и a₂ = w₂·x + b₂, то v₁·a₁ + v₂·a₂ + c = (v₁·w₁ + v₂·w₂)·x + (v₁·b₁ + v₂·b₂ + c). Това е един неврон с тегла v₁·w₁ + v₂·w₂. Точно това смята програмата с трите неврона.

Други активации: tanh (като сигмоида, но между −1 и 1), leaky ReLU (отрицателното не е 0, а малко, например 0,01·z), softmax (на изхода, когато класовете са повече от два: превръща няколко числа във вероятности със сбор 1). Има и много други, но ReLU и сигмоидата са достатъчни, за да разбереш всичко в модула.

С какво се правят мрежи в истинска работа. Тук всичко е цикли и масиви, за да виждаш всяко число. Истинските мрежи са с милиони тегла, а сметките са с матрици на видеокарта. Практиката на машинното обучение е предимно на Python: NumPy за масивите, scikit-learn за класическите модели, PyTorch за невронните мрежи. Има и други езици: ML.NET за C#, DJL и Tribuo за Java, TensorFlow.js и ONNX Runtime за JavaScript. Често моделът се учи на Python, а се ползва от програма на друг език (например през ONNX).

Името. „Неврон“ идва от биологията: клетка, която събира сигнали и „пали“, щом сборът мине прага. Приликата спира дотук. Изкуственият неврон е функция с числа.

Упражнение

Неврон за отзивите

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

Магазинът иска да подреди отзивите на добри и лоши. За всеки отзив вече са преброени хубавите думи („бърза“, „качествен“) и лошите („счупен“, „закъсня“). Напиши неврон със сигмоида с тегла 1,1 за хубавите думи и −1,7 за лошите и отместване 0,2. Пусни го върху петте отзива (хубави, лоши): (3, 0), (1, 2), (0, 0), (2, 1), (0, 3). Отзивът е добър, ако p е поне 0,5.

Изход
хубави думи 3, лоши думи 0: p = 0.971 → добър
хубави думи 1, лоши думи 2: p = 0.109 → лош
хубави думи 0, лоши думи 0: p = 0.550 → добър
хубави думи 2, лоши думи 1: p = 0.668 → добър
хубави думи 0, лоши думи 3: p = 0.007 → лош

Отзивът без нито една дума излиза добър с 0,550: това е само отместването. Тоест без други сведения моделът леко вярва, че отзивът е добър.

Още задачи

  • Трети признак. Добави удивителните знаци като признак с тегло −0,3 и виж кои отзиви сменят решението си.
  • Прагът. Сложи праг 0,7 вместо 0,5. Кои отзиви стават лоши?

Тест

Провери се

1. Какво смята един неврон?
2. Неврон със сигмоида и праг 0,5 върху признаците на плащането. Какво е това?
3. Колега слага три неврона един след друг, без активация, за да хване по-сложни правила. Какво получава?
4. Защо ReLU връща 0 за отрицателно z?
5. Кога един неврон (или логистична регресия) е по-добър избор от голяма мрежа?

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