Рубрики
Без рубрики

Создание нейронной сети с нуля в Python

Автор оригинала: Usman Malik.

Создание нейронной сети с нуля в Python

Это первая статья из серии статей на тему “Создание нейронной сети с нуля в Python”.

  • Создание нейронной сети с нуля в Python
  • Создание нейронной сети с нуля в Python: Добавление скрытых слоев
  • Создание нейронной сети с нуля в Python: Многоклассовая классификация

Вступление

Вы когда-нибудь задумывались, как чат-боты, такие как Siri, Alexa и Cortona, могут отвечать на запросы пользователей? Или как автономные автомобили могут управлять собой без помощи человека? Все эти модные продукты имеют одну общую черту: искусственный интеллект (ИИ). Именно искусственный интеллект позволяет им выполнять такие задачи без надзора или контроля со стороны человека. Но остается вопрос: “Что такое ИИ?” Простой ответ на этот вопрос: “ИИ-это комбинация сложных алгоритмов из различных математических областей, таких как алгебра, математическое исчисление, Вероятность и статистика.”

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

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

Проблема

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

Человек 1 1 0 1 0
Человек 2 0 0 0 1
Человек 3 0 1 0 0
Человек 4 1 1 1 0
Человек 5 1 1 1 1

В приведенной выше таблице мы имеем пять столбцов: Человек, Курение, Ожирение, Физические упражнения и Диабет. Здесь 1 означает истину, а 0-ложь. Например, первый человек имеет значения 0, 1, 0, что означает, что он не курит, страдает ожирением и не занимается спортом. Этот человек тоже диабетик.

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

Примечание : Это всего лишь вымышленный набор данных, в реальной жизни тучные люди не обязательно всегда страдают диабетом.

Решение

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

Теория нейронных сетей

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

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

Нейронная сеть, которую мы собираемся создать, имеет следующее визуальное представление.

нейронная сеть

Нейронная сеть работает в два этапа: Прямое и обратное распространение. Мы подробно обсудим оба этих шага.

Подача Вперед

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

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

Ниже приведены шаги, которые выполняются во время фазы прямой связи нейронной сети:

Шаг 1: (Вычислите точечное произведение между входными данными и весами)

Узлы входного слоя связаны с выходным слоем тремя весовыми параметрами. В выходном слое значения во входных узлах умножаются на соответствующие им веса и суммируются. Наконец, к сумме добавляется член смещения. b на приведенном выше рисунке относится к термину смещения.

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

Математически на шаге 1 мы выполняем следующее вычисление:

$$ + x2w2 + x3w3 + b $$

Шаг 2: (Передайте результат с шага 1 через функцию активации)

Результатом шага 1 может быть набор любых значений. Однако в нашем выводе мы имеем значения в виде 1 и 0. Мы хотим, чтобы наши выходные данные были в том же формате. Для этого нам нужна функция активации , которая сжимает входные значения между 1 и 0. Одной из таких функций активации является функция sigmoid .

Сигмоидная функция возвращает 0,5, когда вход равен 0. Он возвращает значение, близкое к 1, если входное значение является большим положительным числом. В случае отрицательного входа сигмоидная функция выводит значение, близкое к нулю.

Математически сигмоидную функцию можно представить в виде:

$$ \theta_{X. W} = \frac{\mathrm{1} }{\mathrm{1} + e^{-X. W} } $$

Попробуем построить сигмовидную функцию:

input = np.linspace(-10, 10, 100)

def sigmoid(x):
    return 1/(1+np.exp(-x))

from matplotlib import pyplot as plt
plt.plot(input, sigmoid(input), c="r")

В приведенном выше сценарии мы сначала случайным образом генерируем 100 линейных точек между -10 и 10. Для этого мы используем метод linspace из библиотеки NumPy . Далее определим функцию sigmoid . Наконец, мы используем библиотеку matplotlib для построения входных значений по значениям, возвращаемым функцией sigmoid . Выход выглядит примерно так:

сигмовидная функция

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

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

Обратное распространение

В самом начале, прежде чем вы начнете тренироваться, нейронная сеть делает случайные предсказания, которые далеки от правильных.

Принцип работы нейронной сети прост. Мы начинаем с того, что позволяем сети делать случайные предсказания о выходе. Затем мы сравниваем прогнозируемый выход нейронной сети с фактическим выходом. Затем мы точно настраиваем наши веса и смещение таким образом, чтобы наш прогнозируемый выход стал ближе к фактическому выходу, который в основном известен как “обучение нейронной сети”.

В разделе обратного распространения мы тренируем наш алгоритм. Давайте рассмотрим шаги, связанные с разделом обратного распространения.

Шаг 1: (Расчет стоимости)

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

Есть несколько других способов найти стоимость, но мы будем использовать функцию mean squared error cost. Функция затрат – это просто функция, которая находит стоимость данных прогнозов.

Среднеквадратичная функция стоимости ошибки может быть математически представлена в виде:

$$ MSE = \frac{\mathrm{1} }{\mathrm{n}}} ^{n} (предсказано – наблюдаемо)^{2} $$

Здесь n – число наблюдений.

Шаг 2: (Минимизация затрат)

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

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

Это задача оптимизации , где мы должны найти минимумы функций .

Чтобы найти минимумы функции, мы можем использовать алгоритм gradient decent . Алгоритм градиентного спуска может быть математически представлен следующим образом:

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

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

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

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

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

И это почти все. Теперь настало время реализовать то, что мы изучили до сих пор. Мы создадим простую нейронную сеть с одним входным и одним выходным слоем в Python.

Реализация нейронной сети в Python

Давайте сначала создадим наш набор функций и соответствующие метки. Выполните следующий сценарий:

import numpy as np
feature_set = np.array([[0,1,0],[0,0,1],[1,0,0],[1,1,0],[1,1,1]])
labels = np.array([[1,0,0,1,1]])
labels = labels.reshape(5,1)

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

Следующий шаг-определение гиперпараметров для нашей нейронной сети. Для этого выполните следующий сценарий:

np.random.seed(42)
weights = np.random.rand(3,1)
bias = np.random.rand(1)
lr = 0.05

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

На следующем шаге мы инициализируем наши веса нормально распределенными случайными числами. Поскольку у нас есть три объекта на входе, у нас есть вектор с тремя весами. Затем мы инициализируем значение смещения другим случайным числом. Наконец, мы установили скорость обучения на 0,05.

Далее нам нужно определить нашу функцию активации и ее производную (я сейчас объясню, почему нам нужно найти производную активации). Наша функция активации-сигмовидная функция, о которой мы говорили ранее.

Следующий скрипт Python создает эту функцию:

def sigmoid(x):
    return 1/(1+np.exp(-x))

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

def sigmoid_der(x):
    return sigmoid(x)*(1-sigmoid(x))

Производной сигмоидной функции является просто сигмоид(x) * сигмоид(1-x) .

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

Посмотрите на следующий сценарий:

for epoch in range(20000):
    inputs = feature_set

    # feedforward step1
    XW = np.dot(feature_set, weights) + bias

    #feedforward step2
    z = sigmoid(XW)


    # backpropagation step 1
    error = z - labels

    print(error.sum())

    # backpropagation step 2
    dcost_dpred = error
    dpred_dz = sigmoid_der(z)

    z_delta = dcost_dpred * dpred_dz

    inputs = feature_set.T
    weights -= lr * np.dot(inputs, z_delta)

    for num in z_delta:
        bias -= lr * num

Не пугайтесь этого кода. Я объясню это строчка за строчкой.

На первом этапе мы определяем количество эпох. Эпоха-это в основном количество раз, когда мы хотим обучить алгоритм на наших данных. Мы будем тренировать алгоритм на наших данных 20 000 раз. Я проверил это число и обнаружил, что ошибка в значительной степени минимизируется после 20 000 итераций. Вы можете попробовать с другим номером. Конечная цель – минимизировать ошибку.

Далее мы сохраняем значения из feature_set в переменную input . Затем мы выполняем следующую строку:

XW = np.dot(feature_set, weights) + bias

Здесь мы находим точечное произведение входного сигнала и вектора веса и добавляем к нему смещение. Это шаг 1 раздела прямой связи.

В этой строке:

z = sigmoid(XW)

Мы пропускаем точечный продукт через сигмовидную функцию активации, как описано в шаге 2 раздела прямой связи. Это завершает передовую часть нашего алгоритма.

Сейчас самое время начать обратное распространение. Переменная z содержит прогнозируемые выходные данные. Первый шаг обратного распространения-найти ошибку. Мы делаем это в следующей строке:

error = z - labels

Затем мы печатаем ошибку на экране.

Теперь пришло время выполнить шаг 2 обратного распространения, который является сутью этого кода.

Мы знаем, что наша функция затрат:

$$ MSE = \frac{\mathrm{1} }{\mathrm{n}}}^{n} (предсказано – наблюдаемо)^{2} $$

Нам нужно дифференцировать эту функцию по отношению к каждому весу. Для этой цели мы будем использовать цепное правило дифференциации . Предположим, что “d_cost” является производной нашей функции стоимости по отношению к весу “w”, мы можем использовать цепное правило, чтобы найти эту производную, как показано ниже:

Здесь,

может быть рассчитан как:

Здесь 2 является константой и поэтому может быть проигнорировано. Это в основном ошибка, которую мы уже вычислили. В коде вы можете увидеть строку:

dcost_dpred = error # ........ (2)

Далее мы должны найти:

Здесь “d_pred” – это просто сигмоидная функция, и мы дифференцировали ее по отношению к входному точечному произведению “z”. В сценарии это определяется как:

dpred_dz = sigmoid_der(z) # ......... (3)

Наконец, мы должны найти:

Мы это знаем:

Следовательно, производная по отношению к любому весу-это просто соответствующий вход. Следовательно, наша конечная производная функции затрат по отношению к любому весу равна:

slope = input x dcost_dpred x dpred_dz

Взгляните на следующие три строки:

z_delta = dcost_dpred * dpred_dz
inputs = feature_set.T
weights -= lr * np.dot(inputs, z_delta)

Здесь мы имеем переменную z_delta , которая содержит произведение cost_speed и speed_dz . Вместо того чтобы перебирать каждую запись и умножать входные данные на соответствующие z_delta , мы берем транспонирование входной матрицы признаков и умножаем ее на z_delta . Наконец, мы умножаем переменную скорости обучения lr на производную, чтобы увеличить скорость сходимости.

Затем мы перебираем каждое производное значение и обновляем наши значения смещения, а также показанные в этом скрипте:

Как только цикл начнется, вы увидите, что общая ошибка начинает уменьшаться, как показано ниже:

0.001700995120272485
0.001700910187124885
0.0017008252625468727
0.0017007403465365955
0.00170065543909367
0.0017005705402162556
0.0017004856499031988
0.0017004007681529695
0.0017003158949647542
0.0017002310303364868
0.0017001461742678046
0.0017000613267565308
0.0016999764878018585
0.0016998916574025129
0.00169980683555691
0.0016997220222637836
0.0016996372175222992
0.0016995524213307602
0.0016994676336875778
0.0016993828545920908
0.0016992980840424554
0.0016992133220379794
0.0016991285685766487
0.0016990438236577712
0.0016989590872797753
0.0016988743594415108
0.0016987896401412066
0.0016987049293782815

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

Теперь вы можете попытаться предсказать значение одного экземпляра. Предположим, у нас есть запись о пациенте, который курит, не страдает ожирением и не занимается физическими упражнениями. Давайте выясним, может ли он быть диабетиком или нет. Входная функция будет выглядеть следующим образом: [1,0,0].

Выполните следующий сценарий:

single_point = np.array([1,0,0])
result = sigmoid(np.dot(single_point, weights) + bias)
print(result)

В выводе вы увидите:

[0.00707584]

Вы можете видеть, что человек, скорее всего, не диабетик, так как значение гораздо ближе к 0, чем к 1.

Теперь давайте проверим другого человека, который не курит, страдает ожирением и не занимается спортом. Входной вектор признаков будет равен [0,1,0]. Выполните этот сценарий:

single_point = np.array([0,1,0])
result = sigmoid(np.dot(single_point, weights) + bias)
print(result)

В выводе вы увидите следующее значение:

[0.99837029]

Вы можете видеть, что это значение очень близко к 1, что, вероятно, связано с ожирением человека.

Ресурсы

Хотите узнать больше о создании нейронных сетей для решения сложных задач? Если это так, попробуйте проверить некоторые другие ресурсы, например этот онлайн-курс:

Глубокое Обучение A-Z: Практические Искусственные Нейронные Сети

Она охватывает нейронные сети гораздо более подробно, включая сверточные нейронные сети, рекуррентные нейронные сети и многое другое.

Вывод

В этой статье мы создали очень простую нейронную сеть с одним входным и одним выходным слоем с нуля в Python. Такая нейронная сеть называется просто персептроном . Персептрон способен классифицировать линейно разделяемые данные. Линейно разделимые данные-это тип данных, которые могут быть разделены гиперплоскостью в n-мерном пространстве.

Искусственные нейронные сети реального слова гораздо сложнее, мощнее и состоят из нескольких скрытых слоев и нескольких узлов в скрытом слое. Такие нейронные сети способны определять нелинейные реальные границы принятия решений. В следующей статье я объясню, как создать многослойную нейронную сеть с нуля на Python.