В этом посте я хочу рассказать о закономерностях, которые могут находить нейросети. Во многих руководствах для начинающих акцент делается на технике написания кода для нейронных сетей, при этом вопросы «логики» (что могут нейросети? какие архитектуры лучше подходят для каких задач и почему?) часто остаются в стороне. Я надеюсь, мой пост поможет начинающим лучше понять возможности нейронных сетей. Для этого мы попробуем посмотреть, как они справляются с некоторыми модельными задачами. Примеры кода будут приводиться на python с использованием библиотеки keras.

Задача 1. Начнём с простого. Построим нейронную сеть, аппроксимирующую синус.

import numpy as np
import matplotlib
import matplotlib.pyplot as plt
from keras.models import Sequential
from keras.layers import Dense


def get_X_y(n):
    X = np.random.uniform(0, np.pi, n)
    y = np.sin(X)
    return X, y


n = 40
X, y = get_X_y(n)
print("X shape:", X.shape)

model = Sequential()
model.add(Dense(6, input_dim=1, activation='relu'))
model.add(Dense(4, activation='relu'))
model.add(Dense(1, activation='sigmoid'))

model.compile(loss='mean_squared_error', optimizer='adam', metrics=['mean_squared_error'])

model.fit(X, y, epochs=1000, batch_size=4)

X_test = np.linspace(start=0, stop=np.pi, num=500)
print("X test shape:", X_test.shape)
y_test = model.predict(X_test)

font = {'weight': 'bold',
        'size': 25}

matplotlib.rc('font', **font)
axes = plt.gca()
axes.set_ylim(0, 1)
plt.plot(X_test, y_test, c='green', marker='o', markersize=5)
plt.title("Sinus approximated by neural network")
plt.yticks(np.arange(0, 1, 0.1))
plt.grid()
plt.show()

Получаем следующий график:



Как видим, нейронная сеть успешно справилась с задачей аппроксимации несложной функции.

Задача 2. Посмотрим, как нейросеть справится с более сложной задачей. Будем подавать на вход значения x, равномерно распределённые на отрезке [0, 1], а y будем задавать случайно: при x < 0.6 y будет случайной величиной, принимающей значение 0 с вероятностью 0.75 и 1 с вероятностью 0.25 (то есть биномиальной случайной величиной с p=0.25). При x > 0.6 y будет случайной величиной, принимающей значение 0 с вероятностью 0.3 и значение 1 с вероятностью 0.7. В качестве оптимизируемой функции возьмем среднеквадратическую ошибку.

import numpy as np
import matplotlib
import matplotlib.pyplot as plt
from keras.models import Sequential
from keras.layers import Dense


def get_X_y(n):
    X = np.random.uniform(0, 1, n)
    y0 = np.random.binomial(size=n, n=1, p=0.25)
    y1 = np.random.binomial(size=n, n=1, p=0.7)
    y = np.where(X < 0.6, y0, y1)
    return X, y


n_inputs = 1
n_hidden1 = 100
n_hidden2 = 50
n_outputs = 1

n = 2000

X, y = get_X_y(n)
print("X shape:", X.shape)

model = Sequential()
model.add(Dense(n_hidden1, input_dim=1, activation='relu'))
model.add(Dense(n_hidden2, activation='relu'))
model.add(Dense(1, activation='sigmoid'))

model.compile(loss='mean_squared_error', optimizer='adam', metrics=['accuracy'])

model.fit(X, y, epochs=200, batch_size=100)

X_test = np.linspace(start=0, stop=1, num=100)
print("X test shape:", X_test.shape)
y_test = model.predict(X_test)

font = {'weight': 'bold',
        'size': 25}

matplotlib.rc('font', **font)
axes = plt.gca()
axes.set_ylim(0, 1)
plt.plot(X_test, y_test, c='green', marker='o', markersize=5)
plt.title("Binomial distribution approximated by neural network")
plt.yticks(np.arange(0, 1, 0.1))
plt.grid()
plt.show()

Получаем следующий график аппроксимированной нейросетью функции:



Как видим, нейронная сеть аппроксимировала математическое ожидание нашей случайной величины y. Итак, нейросети могут (в принципе) приближать средние значения случайных величин, зависящих от параметров. Например, мы можем ожидать от них решения следующей задачи: люди с доходом до 1000 долларов в среднем несчастны, а люди с доходом выше 1000 долларов в среднем счастливы; надо научиться предсказывать «уровень счастья» в зависимости от дохода. Нейронная сеть сможет найти зависимость среднего уровня счастья от дохода, несмотря на то, что среди людей с любым уровнем дохода встречаются как счастливые, так и несчастные.

Задача 3. Теперь перейдём к предсказанию последовательностей. Будем рассматривать последовательности из 0 и 1, заданные следующим правилом: 10 членов — равновероятно 0 или 1, а одиннадцатый равен 1, если предыдущий член 0, и равновероятно 0 или 1, если предыдущий член 1. Будем генерировать такие последовательности длины 11 (10 входных членов последовательности и один, последний, мы предсказываем) и на них обучать нашу рекуррентную нейронную сеть. А после обучения проверим, как она справляется с предсказанием на новых последовательностях (также длины 11).

import numpy as np
from keras.models import Sequential
from keras.layers import LSTM, Dense


def get_X_y(m, n):
    X = np.random.binomial(size=(m,n), n=1, p=0.5)
    y0 = np.ones(m)
    y1 = np.random.binomial(size=m, n=1, p=0.5)
    y = np.where(X[:, n-1]==0, y0, y1)
    X = np.reshape(X, (X.shape[0], X.shape[1], 1))
    return X, y


model = Sequential()
model.add(LSTM(units=50))
model.add(Dense(units=1))
model.compile(optimizer = 'adam', loss = 'mean_squared_error')

X_train, y_train = get_X_y(1000, 10)
model.fit(X_train, y_train, epochs = 20, batch_size = 32)

m_test = 12
n_test = 10
X_test, y_test = get_X_y(m_test, n_test)
y_predicted = model.predict(X_test)

for i in range(m_test):
    print("x_last=",  X_test[i, n_test-1, 0], "y_predicted=", y_predicted[i, 0])

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

Номер последовательности Предпоследний член последовательности Предсказанное значение
1 0 0.96
2 0 0.95
3 0 0.97
4 0 0.96
5 0 0.96
6 1 0.45
7 0 0.94
8 1 0.50
9 0 0.96
10 1 0.42
11 1 0.44
12 0 0.92


Как видим, если предпоследний член последовательности 0, то нейронная сеть предсказывет близкое к 1 значение, а если он равен 1, то близкое к 0.5 значение. Это близко к оптимальному прогнозу. Аналогичный пример «из жизни» мог бы выглядеть так: «если я сегодня иду в кино, то завтра я обедаю в ресторане; если я сегодня иду в театр, то завтра обедаю где попало». Нейросеть, как мы убедились, может отлавливать закономерности такого типа и предсказать поход в ресторан по походу в кино (а по походу в театр предсказать «нечто среднее»).

Задача 4. Усложним задачу нейросети. Пусть всё будет как в предыдущем примере, только одиннадцатый член последовательности будет определяться не предыдущим, а вторым членом последовательности (по тому же правилу). Код здесь приводить не будем, поскольку он практически не отличается от предыдущего. Мой эксперимент показал, что нейросеть всё равно находит закономерность, но за больше время (пришлось использовать для обучения 100 эпох вместо 20).
Таким образом, нейронные сети могут (опять уточним — в принципе) отлавливать достаточно долгосрочные зависимости (в нашем «жизненном примере» — отлавливать закономерности типа «я иду в ресторан сегодня, если неделю назад я был в кино»).

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

0, 0, 1, 1
0, 1, 0, 1
0, 1, 1, 0


Таким образом, после начальной комбинации 0, 0 мы всегда встретим две единицы, после комбинации 0, 1 мы равновероятно встретим 0 или 1, зато последнее число мы будем знать наверняка. Нашу нейросеть мы теперь попросим возвращать последовательности, поставив return_sequences=True. В качестве прогнозируемых последовательностей возьмем наши же последовательности, сдвинутые на один шаг и дополненные справа нулём. Теперь мы уже можем предположить, что получится: на первом шаге нейросеть будет выдавать число, близкое к 2/3 (поскольку с вероятностью 2/3 второй член равен 1), а дальше для комбинации 0, 0 она будет выдавать два числа, близких к единице, а для 0, 1 сначала выдаст число, близкое к 0.5, а затем выдаст число, близкое к 0 или 1, в зависимости от того, получили ли мы последовательность 0, 1, 0 или 0, 1, 1. В конце нейросеть всегда будет выдавать число, близкое к 0. Проверка с помощью следующего кода показывает, что наши предположения верны.

import numpy as np
from keras.models import Sequential
from keras.layers import LSTM, Dense
import random


def get_X_y(n):
    X = np.zeros((n, 4))
    z = np.array([random.randint(0, 2) for i in range(n)])
    X[z == 0, :] = [0, 0, 1, 1]
    X[z == 1, :] = [0, 1, 0, 1]
    X[z == 2, :] = [0, 1, 1, 0]
    y = np.zeros((n, 4))
    y[:, :3] = X[:, 1:]
    X = np.reshape(X, (X.shape[0], X.shape[1], 1))
    y = np.reshape(y, (y.shape[0], y.shape[1], 1))
    return X, y


model = Sequential()
model.add(LSTM(units=20, return_sequences=True))
model.add(Dense(units=1))
model.compile(optimizer = 'adam', loss = 'mean_squared_error')

X_train, y_train = get_X_y(1000)
model.fit(X_train, y_train, epochs = 100, batch_size = 32)

X_test = np.zeros((3, 4))
X_test[0, :] = [0, 0, 1, 1]
X_test[1, :] = [0, 1, 0, 1]
X_test[2, :] = [0, 1, 1, 0]
X_test = np.reshape(X_test, (3, 4, 1))

y_predicted = model.predict(X_test)
print(y_predicted)


Из этого примера мы видим, что нейросеть может динамически менять прогноз в зависимости от поступившей информации. Так же поступали бы и мы, пробуя спрогнозировать некую последовательность: когда имеющаяся информация позволяет оценить вероятности исходов на следующем шаге, мы прогнозируем, исходя из этой информации; но когда мы узнаём дополнительную информацию на следующем шаге, то в зависимости от неё меняем прогноз.
Так, если мы видим, что кто-то идёт к нам из темноты, то говорим «это человек, детальнее мы не знаем»; когда мы в темноте начинаем различать длинные волосы, то говорим «это, наверное, женщина». Но если вслед за тем мы рассмотрим, что у человека есть усы, то скажем, что это, наверняка, мужчина (хоть и с длинными волосами). Подобно этому поступает, как мы видели, и нейросеть, используя всю полноту имеющейся на данный момент информации для прогноза.

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

Комментарии (7)


  1. lair
    18.09.2019 16:03

    В этом посте я хочу рассказать о «логике» нейросетей.

    И ничего об этом не рассказали. Просто привели простынки кода с задачами.


    1. Prince9403 Автор
      18.09.2019 18:24

      Спасибо за замечание. Попробовал немного улучшить пост, приведя иллюстрации «из жизни» и добавив пояснения.


      1. lair
        18.09.2019 18:25

        Вы, видимо, что-то свое понимаете под "логикой".


  1. vlreshet
    18.09.2019 16:05
    +2

    Эталонный пример как не надо рассказывать о работе нейросетей для начинающих


  1. CrazyElf
    18.09.2019 19:14

    Статья скорее не про логику нейросетей, а про аппроксимацию распределений. Видно, что писал человек, который силён статистике, простым людям читать тяжеловато. Тут могли бы помочь наглядные блок-схемы, чтобы можно было понять, что за «логику» мы пытаемся приблизить нейросетью.


  1. WhiteBlackGoose
    18.09.2019 22:57

    LSTM… новички? Как раз рнн это та область, про которую я бы не стал столько уверенно что-то утверждать. Предсказание результата абсолютно нетривиально, даже в отличии от тех же сверток, их очень тяжело понять интуитивно. А LSTM это еще и жирная сетка по своей логике. А насчет закономерностей… ну в принципе либо она аппроксимируется, либо нет. Новичку было бы интересно узнать про то, как не следует использовать нейросети. Привожу пример:
    У тебя есть данные по стоимости участков. Даны X={ширина; длина} участка, y={стоимость} участка. Если взять сетку или почти любую другую модель ML и попытаться обучить — ничего не выйдет.
    И тут мы плавно подходим к следующему. ML — это сначала о данных, лишь потом о всяких нейросетях. ИМХО, это было бы более полезно новичку. Отакота


  1. KirEv
    20.09.2019 03:41

    эти графики и «логики» напомнили университетский курс нейросетей… а еще больше — курс чисельных методов с элементами статистики )

    а с сетями, сложность построения в другом, суть: построить такую сеть для решения задачи, которую возможно «малой кровью» обучить, это как ребенка учить умножения, типо, 3х4, это трижды по четыре… или нейросеть на определение желтого, в период обучения входные данные содержат картинку с оттенками желтого с пометной «правильно», и картинки с другими оттенками с пометками «неправильно», в итоге нейросеть с большой вероятностью правильно определит оттенок цвета «самостоятельно», в зависимости от «качества обучения».