Рассмотрим принцип работы нейронных сетей.

Мы начинаем очень интересную, но и объемную тему. Для начала мы попытаемся сами написать нейронную сеть (довольно упрощенную), чтобы хотя бы немного понять, что в ней происходит.

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

Нейрон (на рисунках ниже представлен в виде круга) - получает на входе информацию, производит вычисления и передает полученное значение дальше.
Имеют 3 основных типа:
  1. Входной - это первый слой в нейронной сети, который принимает входящие сигналы и передает их дальше. 
  2. Скрытый - производит различные преобразования для входных данных. Все нейроны в скрытом слое связаны с каждым нейроном в следующем слое. Скрытых слов может быть очень много. В примере ниже у нас будет 1 скрытый слой.
  3. Выходной - последний слой в сети, который получает данные от последнего скрытого слоя.

Синапс - связь между нейронами (на рисунках ниже представлен в виде соединительных линий между нейронами).

Вес - это самое важное, что есть в нейронной сети. Каждый синапс имеет вес. Входной сигнал для нейрона, проходя через веса, меняет свое значение, то усиливаясь, то ослабевая (сигнал умножается на вес, чем больше вес, тем сильнее выходной сигнал-ответ нейросети).

Мы начнем с примера из реальной жизни, потому что такие примеры "говорят" лучше всего ;)

Наш пример включает в себя:
1. Ребенка (главный герой),
2. Взрослых (папа, мама, бабушка).

Ребенок хочет получить печенье, но уже исчерпал свой "дневной лимит". Ребенок знает это, поэтому для достижения своей цели он пытается использовать суперсредства: плач или крик. Иногда это бывает эффективным, а иногда - неудачным.

Возможные кейсы:
1. Когда есть только папа, ребенок может плакать сколько угодно, но печенья не получит (так мы предполагаем ;)).
2. Если есть только мама или только бабушка и ребенок плачет, то печенье получит.
3. Если есть папа с мамой или папа с бабушкой, ребенок может плакать сколько угодно, но печенья там не будет.
4. Если все присутствуют и ребенок плачет, ребенок добьется своего и получит печенье.

Ребенок строит нейронную сеть (в своем мозгу) путем экспериментов.

Наша задача - попытаться построить упрощенную сеть, которая имитирует все эти состояния.

ВЕСА

Предположим, что веса в этой нейронной сети уже подобраны, нужно только проследить и понять, как работает сеть (и что на что умножается), чтобы получить конечный результат.

1. Идем слева направо
2. Значения на входе (то что в узлах/кружках самого левого края) - в данном случае это информация о присутствии мамы, папы и бабушки (если 1 то присутствуют, если 0, то отсутвуют).
3. Умножаем значения на входе на веса. Например для папы есть два вида весов: 0.8 и 0.
4. Для того чтобы посчитать значения следующего слоя (первый скрытый слой), нужно умножить веса на предыдущий слой и суммировать.
5. Получается все синапсы (линии соединения), которые входят в узел, влияют на значение этого узла.


Сумму, которую получаем в узле в конце дополнительно пропускаем через функцию активации. В нашем случае функция активации будет возвращать 0, если значение меньше 0,5, и 1 в противном случае, например, 0,8 => 1, 0,5 => 1, тогда как 0,2 => 0, -0,3 => 0 и т.д.
Кейс:
Только папа с ребенком.
Результат:
Печенья ребенок НЕ получает.
Папа
Первый слой: 0.8 * 1 = 0.8 => 1, а затем есть вес -1, поэтому результатом является отрицательное значение (т.е. меньше 0.5), и поэтому конечный результат равен 0 (нет печенья для ребенка).

Мама и бабушка
В данном случае они отсутствуют (неактивны), поэтому их значение равно 0.
  • Мама: 0*(-0.25) = 0
  • Бабушка: 0*(-0.25) = 0
Кейс:
Только мама с ребенком.
Результат:
Печенье ребенок получает.
Мама
Мама присутствует, поэтому она имеет 1 на входе, а затем имеет две связи (веса) с первым скрытым слоем: -0.25 и 0.5.
1 * -0.25 = -0.25, что после использования функции активации дает 0. Второе действие = 1 * 0.5 = 0.5, что не меньше 0.5, поэтому мы получаем 1. Затем рассчитываем: 0 * (-1 )+ 1 * 1 = 1, поэтому в конечном итоге будет 1 (будет печенье).

Папа и бабушка
Отсутствуют (т.е. в нейронах [на входе] есть значение 0).
Кейс:
Только бабушка с ребенком.
Результат:
Печенье ребенок получает.
Бабушка
Ситуация такая же, как и с мамой.

Мама и папа
Они отсутствуют (возможно, работают ради "счастья" ребенка, как это обычно бывает).
Кейс:
Папа и бабушка с ребенком.
Результат:
Печенье ребенок НЕ получает.
Папа и бабушка
Здесь уже весело :) Объединив знания из предыдущих примеров, мы видим, что в скрытом слое два нейрона принимают значение единицы. Далее появляется интересный факт: при переходе от первого скрытого слоя к выходному слою возникает конфликт. Папа не хочет давать печенье (поэтому вес равен -1), бабушка хочет (поэтому вес равен 1). В конце концов, отец выигрывает: -1 + 1 = 0, меньше 0,5, поэтому в конечном итоге 0 (печенья нет).

Мама
Она отсутствует.
Кейс:
Папа и мама с ребенком.
Результат:
Печенье ребенок НЕ получает.
Папа и мама

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

Папа
Отец отсутствует.
Кейс:
Папа, мама и бабушка с ребенком.
Результат:
Печенье ребенок получает.
Папа, мама и бабушка

Ситуация похожа на ту, что была с папой и мамой/бабушкой, но на этот раз папа ослаблен с обеих сторон.

Папа дает 0,8 от себя, но мама вычитает из этого 0,25, бабушка тоже вычитает 0,25, в итоге остается 0,3, что меньше 0,5. В итоге ребенок получает то, что так хотел - печенье :)
Код для запуска
import numpy as np
Код для запуска
DAD, MOM, GRAND_MOM = 1, 0, 0

def activation_func(x): return 0 if x < 0.5 else 1

#веса между входным слоем и первым скрытым слоем
def get_first_weights():
    return np.array([
        #папа  #мама  #бабушка
        [0.8,  -0.25,  -0.25],
        [0,    0.5,    0.5]
    ])

#веса между первым скрытым слоем и выходным слоем
def get_second_weights():
    return np.array([ -1, 1 ])

def predict(dad, mom, grand_mom, debug=False):
    inputs = np.array([dad, mom, grand_mom])
    
    first_weights = get_first_weights()
    
    second_weights = get_second_weights()
    
    hiden_input = np.dot(first_weights, inputs)
    if debug: print("hiden_input (before activation): {0}".format(hiden_input))
    
    hiden_output = np.array([activation_func(x) for x in hiden_input])
    if debug: print("hiden_output (after activation): {0}\n".format((hiden_output)))
    
    output = np.dot(second_weights, hiden_output)
    if debug: print("output(before activation): {0}".format(output))
    output = activation_func(output)
    if debug: print("output(after activation): {0}\n".format(output))
    
    return  output == 1
  
    
print("Should I cry: {0}?".format(predict(DAD, MOM, GRAND_MOM, debug=True)))

Результат: 


hiden_input (before activation): [0.8 0. ]

hiden_output (after activation): [1 0]


output(before activation): -1

output(after activation): 0


Should I cry: False?

Это пример урока из первого модуля нашего курса по нейронным сетям (к этому уроку еще полагается домашнее задание и полезные ссылки).