Архитектуры нейронных сетей неуклонно развиваются по размеру и сложности, например. PALM от Google или известные языковые модели GPT-2 и GPT-3.

В этой статье я хочу сделать шаг назад, к основным понятиям нейронных сетей. Мы реализуем нейронную сеть в нескольких строках кода Python без каких-либо зависимостей, но Numpy для предсказания знаменитого набора данных Iris.
Вам не нужны какие-либо базовые знания, кроме некоторых основ исчисления и матричных вычислений. .

Выше вы можете увидеть визуализацию структуры сети. У нас есть входной слой i размера 4 для входных данных Iris, скрытый слой j с сигмовидной активацией с размером 10 и выходным слоем k с размером 3, чтобы предсказать 3 класса цветов. Всего получается 70 обучаемых параметров (4*10 + 3*10 = 70; без параметров смещения).

Итак, давайте начнем!

Сначала мы инициализируем веса сети.
Итак, подведем итог: нам нужны две матрицы, которые содержат обучаемые веса (параметры) сети. Wij с формой (4,10) и Wjk с формой (10,3). Мы инициализируем эти весовые матрицы случайными числами от -1 до 1:

self.w1 = np.random.uniform(-1, 1, size=(4, 10))        
self.w2 = np.random.uniform(-1, 1, size=(10, 3))

Здесь у вас есть иллюстрация нашей функции активации, сигмовидной функции. Он отображает произвольный ввод x в значение между [0,1].

Поместите в функции python сигмовидную функцию и производную от сигмовидной функции, которые определяются как:

def sigm(x):
    return 1 / (1 + np.exp(-x))
def d_sigm(x):
    return sigm(x) *(1-sigm(x))

Переход вперед:

Прямой проход состоит из 3 шагов:

  1. Матричное умножение входных параметров Iris x и весовой матрицы Wij
  2. Расчет сигмовидной активации исхода 1.
  3. Умножение матриц с результатом 2. и Wjk
def forward(self, x):
        
        s_j = np.dot(x, self.w1)
        a_j = sigm(s_j)
        s_k = np.dot(a_j, self.w2)
        y_hat = s_k
        return y_hat, s_j, a_j

Обратите внимание, что мы также сохраняем предварительные активации s_j и активации a_j, потому что они понадобятся нам позже при обратном проходе.

Обратный проход (обратное распространение):

Здесь вы можете ознакомиться с обозначениями и важными формулами, которые мы используем для выполнения обратного прохода:

Важную роль в обратном пасе играют дельты. Вы можете думать о дельтах как об ошибке предсказания сети, которая передается обратно через сеть слой за слоем для расчета адаптации весов, градиентов.
Градиенты говорят нам, в каком направлении и насколько веса должны быть адаптированы, чтобы свести эмпирическую ошибку сети к минимуму.

Теперь давайте выведем конкретный обратный проход для нашей Iris Net.

Сначала мы вычисляем дельты для выходного слоя k и скрытого слоя j.
Затем мы вычисляем градиенты матриц W_kj и W_ij.

Обратите внимание, что у нас есть функции активации только в скрытом слое, у нас нет активаций в выходном слое, что означает, что термин f’(s_k) = 1, и его можно исключить из уравнения в строке 3.

Чтобы поместить это в код:

def backward(self,x):
        
        y_hat, s_j, a_j   = self.forward(x)
        loss = y - y_hat
        d_k = loss                    
        d_j = self.w2.dot(d_k.T).T * d_sigm(s_j) 
        grads_w2 = np.dot(a_j.T, d_k)
        grads_w1 = np.dot(x.T,d_j)
        self.w1 += grads_w1 * self.lr
        self.w2 += grads_w2 * self.lr
        return y_hat, np.mean(loss)

После вычисления градиентов мы обновляем веса нашей сети, умножая градиенты на нашу скорость обучения lr и добавляя результат к нашим матрицам:

self.w1 += grads_w1 * self.lr
self.w2 += grads_w2 * self.lr

Для процесса обучения мы запускаем обратную функцию до тех пор, пока не достигнем сходимости, в нашем случае мы запускаем ее до тех пор, пока не достигнем точности более 97% в наборе данных Iris, правильно классифицировав более 146 из 150 образцов.

nn = NN()
for i in range(10000):
    y_hat, loss = nn.backward(x)
        
    if np.count_nonzero((np.argmax(y_hat,axis=1)-iris.target)) < 4:
        print("Iteration", i)
        print("Accuracy > 97%")
        print("Label differences:")
        print(np.argmax(y_hat,axis=1) - iris.target)
        break

Чтобы собрать все это в 60 строк кода: