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

Линейная разделимость

Линейная разделимость подразумевает существование гиперплоскости, разделяющей два класса. Например, рассмотрим набор данных с двумя функциями x1 и x2, в котором точки (−1, −1), (1, 1), (−3, −3), (4, 4) принадлежат одному классу и (− 1, 1), (1, −1), (−5, 2), (4, −8) принадлежат друг другу.

import matplotlib.pyplot as plt
c1x1 = [-1, 1, -3, 4]
c1x2 = [-1, 1, -3, 4]
plt.scatter(c1x1, c1x2, label='class1')
c2x1 = [-1, 1, -5, 4]
c2x2 = [1, -1, 2, -8]
plt.scatter(c2x1, c2x2, label='class2')
plt.xlabel('x1')
plt.ylabel('x2')
plt.legend() 
plt.show()

Этот набор данных не является линейно разделимым, поскольку не существует линейной функции, которая могла бы разделить class1 и class2.

Давайте рассмотрим одномерное представление z с точки зрения x1 и x2, так что набор данных линейно разделим с точки зрения одномерного представления, соответствующего z. Определив z = x1x2, данные можно представить одномерными и линейно разделимыми. Используя это отображение, точки класса 1 (−1, −1), (1, 1), (−3, −3), (4, 4) становятся (1), (1), (9), (6) и точки класса 2 (-1, 1), (1, -1), (-5, 2), (4, -8) становятся (-1), (-1), (-10), (-32 ).

z1 = [1, 1, 9, 6]
y1 = [0, 0, 0, 0]
plt.scatter(z1, y1, label='class1')
z2 = [-1, -1, -10, -32]
y2 = [0, 0, 0, 0]
plt.scatter(z2, y2, label='class2')
plt.scatter([0], [0], label='hyperplane')
plt.xlabel('z = x1*x2')
plt.legend()
plt.show()

Как вы можете видеть на графике выше, теперь z = 0 разделяет class1 и class2. Нелинейно разделимые данные могут быть представлены в линейно разделимой форме после применения нелинейного преобразования (в этом примере z = x1x2, что приводит к представлению данных в виде 1D).

Персептрон

Для линейно разделимых наборов данных линейный классификатор или SVM с линейным ядром может обеспечить 100% точность классификации данных. Линейные классификаторы классифицируют данные по меткам на основе линейной комбинации входных признаков. Однослойный персептрон является примером линейного классификатора. Он вычисляет линейную комбинацию входных признаков с параметрами (весами), передает их в качестве входных данных для знаковой функции, вычисляет потери и обновляет веса посредством градиентного спуска.

Чтобы лучше понять, как работает линейный классификатор, давайте реализуем алгоритм однослойного персептрона с нуля.

Рассмотрим двумерный набор данных, в котором все точки с x1 › x2 принадлежат положительному классу, а все точки с x1 ≤ x2 принадлежат отрицательному классу. Следовательно, истинным разделителем двух классов является линейная гиперплоскость (линия), определяемая x1 − x2 = 0. Теперь давайте создадим набор обучающих данных с 20 точками, случайно сгенерированными внутри единичного квадрата в положительном квадранте.

import matplotlib.pyplot as plt
import numpy as np
import random
x1_range = np.arange(0, 1, 0.01)
#create train data
random.seed(0)
x1_pos = []
x2_pos = []
x1_neg = []
x2_neg = []
train_data = []
for i in range(20):
    x1, x2 = random.random(), random.random()
    train_data.append(np.array([x1, x2]))
    
    if x1 > x2:
        x1_pos.append(x1)
        x2_pos.append(x2)
    else:
        x1_neg.append(x1)
        x2_neg.append(x2)
plt.scatter(x1_pos, x2_pos, label='positive')
plt.scatter(x1_neg, x2_neg, label='negative')
plt.plot(x1_range, x1_range, label='x1-x2=0')
plt.legend()
plt.show()

Давайте реализуем алгоритм персептрона и обучим его на 20 точках, указанных выше, и проверим его точность на 1000 случайно сгенерированных точках внутри единичного квадрата.

def perceptron(x_train, epoch, learning_rate, a):
    w = np.array([random.random(), random.random()])
    for n in range(epoch):
        for x in x_train:
            #linear combination
            dotprod = np.dot(x, w)
            #compute output
            pred = np.sign(dotprod)
            #prediction
            target = 1 if (x[0] > x[1]) else -1
           
            #calculate loss
            if pred*target == -1:
                gradient = - (target * x)
                w = w - learning_rate * gradient        
    return w
def predict(x_test, weights):
    n_correct = 0
    for x in x_test:
        dotprod = np.dot(x, weights)
        pred = np.sign(dotprod)
        
        if pred > 0:
            if x1 > x2:
                n_correct += 1
        else:
            if x1 <= x2:
                n_correct += 1
    return n_correct / 1000
#train
w_0 = perceptron(train_data, 20, 0.01, 0)
#create test data
test_data = []
for i in range(1000):
    x1, x2 = random.random(), random.random()
    test_data.append(np.array([x1, x2]))
print("Accuracy : ", predict(test_data, w_0))

Через 20 эпох точность персептрона достигает 100%.

Другим примером линейного классификатора является SVM (автомат опорных векторов). Цель SVM — найти плоскость с максимальным запасом, то есть максимальным расстоянием между точками данных обоих классов.

Чтобы оценить SVM в этом наборе данных, мы можем изменить критерий персептрона на потерю шарнира и повторить вычисление точности в тех же контрольных точках, что и выше. Потери шарнира используются для классификации «максимальной маржи» и равны следующей функции потерь, которую мы использовали для вышеуказанного критерия персептрона (a = 0), только заменив значение a на 1.

#train
w_1 = perceptron(train_data, 10, 0.01, 1) #change a to 1
print(w_1)
print("Accuracy : ", predict(test_data, w_1))

Как и в алгоритме персептрона, с помощью SVM мы также можем достичь 100% точности на этом линейно разделимом наборе данных.

Используя оба алгоритма персептрона с использованием линейного классификатора и SVM, мы увидели, что 100% точность может быть достигнута с линейно разделимым набором данных. Однако большая часть данных в реальном мире не является линейно разделимой. Тогда как мы можем классифицировать эти данные? Нелинейное преобразование является ключевым!

Нелинейное преобразование подразумевает ключевую идею для классификаторов многослойных нейронных сетей. Привнося нелинейные функции активации в скрытые слои, данные могут быть представлены в виде линейно разделимой формы в пространстве признаков. Таким образом, модель может изучать более сложные характеристики данных с помощью многослойных нейронных сетей.

Теперь у вас может быть интуитивное представление о том, как многослойные нейронные сети изучают скрытые функции и закономерности в данных. Линейные классификаторы просты и хорошо работают с линейно разделимыми данными, но имеют ограничения для изучения сложных особенностей реальных данных. Это отправная точка, чтобы понять множество вещей об архитектуре многоуровневых нейронных сетей для решения различных реальных проблем!

Ссылки





К. Аггарвал. Нейронные сети и глубокое обучение