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

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

Если вам интересно, и в целом, ответ таков: однослойная сеть может представлять полуплоскости (то есть логику И или ИЛИ), двухслойная сеть может классифицировать точки внутри любого количества произвольных линий, а трехслойная сеть может классифицировать точки внутри любого количества произвольных линий. сеть может классифицировать любую произвольную форму по произвольным размерам. Следовательно, двухслойная модель может классифицировать любой выпуклый набор, а трехслойная модель может классифицировать любое количество непересекающихся выпуклых или вогнутых форм.

Но теперь позвольте мне подробнее остановиться на нескольких конкретных, но простых примерах. Начнем с загрузки Keras, numpy и matplotlib.

from keras import models
from keras import layers

import numpy as np
import matplotlib.pyplot as plt

np.random.seed(2018)

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

x=np.linspace(0,1,100)
y = 1 + x - x**2 + 10*x**3 - 10*x**4
fit = np.polyfit(x,y,1)
fit_fn = np.poly1d(fit) 
# fit_fn is now a function which takes in x and returns an estimate for y

plt.figure(figsize=(20,10))
plt.plot(x,y,'ob',linewidth=2)
plt.plot(x, fit_fn(x),'k',linewidth=2)
plt.title('Polynomial and linear fit',fontsize=20)
plt.xticks(fontsize=20)
plt.yticks(fontsize=20)
plt.grid('on')
plt.show()

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

Затем я спрошу, сколько нейронов и сколько слоев нужно нейронной сети для классификации простых наборов данных. Я начну с простых наборов данных, где мы хотим классифицировать данные И и ИЛИ, и продолжу исследовать наступивший сложный XOR и, наконец, проблемы классификации двух лун, используя Keras.

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

# constants
npts = 100 # points per blob
tot_npts = 4*npts # total number of points 
s = 0.005 # ~standard deviation
sigma = np.array([[s, 0], [0, s]]) #cov matrix

Проблема И

Проблема AND проста. Как вы можете видеть ниже, данные сгруппированы по четырем областям: [0,0], [0,1], [1,0] и [1,1]. Когда мы применяем логическую функцию И к каждой паре, следует, что [0,0] = 0, [0,1] = 0, [1,0] = 0, но [1,1] = 1. Мы помечаем пару данных как один (синий), когда обе точки равны единице. В противном случае мы помечаем данные как ноль (красный).

# Generate Data
data1 = np.random.multivariate_normal( [0,0], sigma, npts)
data2 = np.random.multivariate_normal( [0,1], sigma, npts)
data3 = np.random.multivariate_normal( [1,0], sigma, npts)
data4 = np.random.multivariate_normal( [1,1], sigma, npts)

and_data = np.concatenate((data1, data2, data3, data4)) # data
and_labels = np.concatenate((np.ones((3*npts)),np.zeros((npts)))) # labels
print(and_data.shape)
print(and_labels.shape)

plt.figure(figsize=(20,10))
plt.scatter(and_data[:,0][and_labels==0], and_data[:,1][and_labels==0],c='b')
plt.scatter(and_data[:,0][and_labels==1], and_data[:,1][and_labels==1],c='r')

plt.plot()
plt.title('AND problem',fontsize=20)
plt.xticks(fontsize=20)
plt.yticks(fontsize=20)
plt.grid('on')
plt.show()
(400, 2)
(400,)

Разделить данные AND просто. Прямая линия может разделить данные на синие и красные.

Это было просто.

Линейная линия представлена ​​как Wx + b (где W - наклон, а b - смещение), а в мире нейронных сетей - как np.dot (W, x) + b. Таким образом, одного слоя с одним нейроном (то есть одной линейной линии) будет достаточно для разделения данных AND.

Ниже вы можете увидеть мою реализацию Keras нейронной сети с одним слоем, одним нейроном и сигмовидной активацией. В качестве функции потерь я выбрал binary_crossentropy с оптимизатором Adam. Перебирая данные с batch_size, равным 16, модель сходится к правильному решению, измеренному по точности, примерно после 100 итераций по всем данным.

model = models.Sequential()
model.add(layers.Dense(1, activation='sigmoid', input_shape=(2,)))
model.summary()
model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])

history = model.fit(and_data, 
                    and_labels, 
                    epochs=200,
                    batch_size=16,
                    verbose=0)


history_dict = history.history
history_dict.keys()


plt.figure(figsize=(20,10))
plt.subplot(121)
loss_values = history_dict['loss']
#val_loss_values = history_dict['val_loss']
epochs = range(1, len(loss_values) + 1)
plt.plot(epochs, loss_values, 'bo', label='Training loss')
#plt.plot(epochs, val_loss_values, 'b', label='Validation loss')
plt.title('Training loss',fontsize=20)
plt.xlabel('Epochs',fontsize=20)
plt.ylabel('Loss',fontsize=20)
plt.legend(fontsize=20)
plt.xticks(fontsize=20)
plt.yticks(fontsize=20)


plt.subplot(122)
acc_values = history_dict['acc']
#val_acc_values = history_dict['val_acc']
epochs = range(1, len(loss_values) + 1)
plt.plot(epochs, acc_values, 'bo', label='Training acc')
#plt.plot(epochs, val_acc_values, 'b', label='Validation acc')
plt.title('Training accuracy',fontsize=20)
plt.xlabel('Epochs',fontsize=20)
plt.ylabel('Accuracy',fontsize=20)
plt.legend(fontsize=20)
plt.xticks(fontsize=20)
plt.yticks(fontsize=20)
plt.show()
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
dense_1 (Dense)              (None, 1)                 3         
=================================================================
Total params: 3
Trainable params: 3
Non-trainable params: 0
_________________________________________________________________

Проблема с операцией "ИЛИ"

Задача ИЛИ тоже проста. Опять же, данные сгруппированы по четырем областям: [0,0], [0,1], [1,0] и [1,1]. Как и раньше, к каждой паре применяется логическая функция ИЛИ. Отсюда следует, что [0,0] = 0, но [0,1] = 1, [1,0] = 1 и [1,1] = 1. Мы помечаем пару данных как ноль (красный), только когда обе точки равны нулю. В противном случае мы помечаем данные как один (синий).

# Generate Data
data1 = np.random.multivariate_normal( [0,0], sigma, npts)
data2 = np.random.multivariate_normal( [0,1], sigma, npts)
data3 = np.random.multivariate_normal( [1,0], sigma, npts)
data4 = np.random.multivariate_normal( [1,1], sigma, npts)

or_data = np.concatenate((data1, data2, data3, data4))
or_labels = np.concatenate((np.ones((npts)),np.zeros((3*npts))))

plt.figure(figsize=(20,10))
plt.scatter(or_data[:,0][or_labels==0], or_data[:,1][or_labels==0],c='b')
plt.scatter(or_data[:,0][or_labels==1], or_data[:,1][or_labels==1],c='r')
plt.title('OR problem',fontsize=20)
plt.xticks(fontsize=20)
plt.yticks(fontsize=20)
plt.grid('on')
plt.show()

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

model = models.Sequential()
model.add(layers.Dense(1, activation='sigmoid', input_shape=(2,)))
model.summary()
model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])

history = model.fit(or_data, 
                    or_labels, 
                    epochs=400,
                    batch_size=16,
                    verbose=0)


history_dict = history.history
history_dict.keys()


plt.figure(figsize=(20,10))
plt.subplot(121)
loss_values = history_dict['loss']
#val_loss_values = history_dict['val_loss']
epochs = range(1, len(loss_values) + 1)
plt.plot(epochs, loss_values, 'bo', label='Training loss')
#plt.plot(epochs, val_loss_values, 'b', label='Validation loss')
plt.title('Training loss',fontsize=20)
plt.xlabel('Epochs',fontsize=20)
plt.ylabel('Loss',fontsize=20)
plt.legend(fontsize=20)
plt.xticks(fontsize=20)
plt.yticks(fontsize=20)


plt.subplot(122)
acc_values = history_dict['acc']
#val_acc_values = history_dict['val_acc']
epochs = range(1, len(loss_values) + 1)
plt.plot(epochs, acc_values, 'bo', label='Training acc')
#plt.plot(epochs, val_acc_values, 'b', label='Validation acc')
plt.title('Training accuracy',fontsize=20)
plt.xlabel('Epochs',fontsize=20)
plt.ylabel('Accuracy',fontsize=20)
plt.legend(fontsize=20)
plt.xticks(fontsize=20)
plt.yticks(fontsize=20)
plt.show()
________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
dense_2 (Dense)              (None, 1)                 3         
=================================================================
Total params: 3
Trainable params: 3
Non-trainable params: 0
_________________________________________________________________

Проблема XOR

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

# Generate Data
data1 = np.random.multivariate_normal( [0,0], sigma, npts)
data2 = np.random.multivariate_normal( [0,1], sigma, npts)
data3 = np.random.multivariate_normal( [1,0], sigma, npts)
data4 = np.random.multivariate_normal( [1,1], sigma, npts)

xor_data = np.concatenate((data1, data4, data2, data3))
xor_labels = np.concatenate((np.ones((2*npts)),np.zeros((2*npts))))

plt.figure(figsize=(20,10))
plt.scatter(xor_data[:,0][xor_labels==0], xor_data[:,1][xor_labels==0],c='b')
plt.scatter(xor_data[:,0][xor_labels==1], xor_data[:,1][xor_labels==1],c='r')
plt.title('XOR problem',fontsize=20)
plt.xticks(fontsize=20)
plt.yticks(fontsize=20)
plt.grid('on')
plt.show()

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

model = models.Sequential()
model.add(layers.Dense(1, activation='sigmoid', input_shape=(2,)))
model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])

history = model.fit(xor_data, 
                    xor_labels, 
                    epochs=400,
                    batch_size=32,
                    verbose=0)
history_dict_10 = history.history

model = models.Sequential()
model.add(layers.Dense(1, activation='relu', input_shape=(2,)))
model.add(layers.Dense(1, activation='sigmoid'))
model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])

history = model.fit(xor_data, 
                    xor_labels, 
                    epochs=400,
                    batch_size=32,
                    verbose=0)
history_dict_11 = history.history

model = models.Sequential()
model.add(layers.Dense(2, activation='relu', input_shape=(2,)))
model.add(layers.Dense(1, activation='sigmoid'))
model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])

history = model.fit(xor_data, 
                    xor_labels, 
                    epochs=400,
                    batch_size=32,
                    verbose=0)
history_dict_21 = history.history


history = model.fit(xor_data, 
                    xor_labels, 
                    epochs=400,
                    batch_size=32,
                    verbose=0)
history_dict_41 = history.history

Чтобы проверить минимальный набор из двух слоев с двумя и одним нейронами (обозначен как 2_1), я также запустил еще две реализации Keras с меньшим количеством слоев и нейронов. Действительно, вы можете видеть, что модель 2_1 (два слоя с двумя и одним нейроном) сходится к правильному решению, в то время как модели 1_0 (один слой с одним нейроном) и 1_1 (два слоя с одним и одним нейроном) довольно хорошо аппроксимируют данные. но никогда не сходятся до 100% точности. Итак, хотя это не формальное доказательство, охватывающее все аспекты минимального набора, необходимого для классификации, оно должно дать вам достаточно практического понимания того, почему минимальный набор требует только двух слоев с двумя и одним нейроном.

plt.figure(figsize=(20,10))
plt.subplot(121)
loss_values = history_dict_10['loss']
epochs = range(1, len(loss_values) + 1)
plt.plot(epochs, history_dict_10['loss'], 'o', label='Training loss')
plt.plot(epochs, history_dict_11['loss'], 'o', label='Training loss')
plt.plot(epochs, history_dict_21['loss'], 'o', label='Training loss')
plt.title('Training loss',fontsize=20)
plt.xlabel('Epochs',fontsize=20)
plt.ylabel('Loss',fontsize=20)
plt.legend(['1_0','1_1','2_1','4_1'],fontsize=20)
plt.xticks(fontsize=20)
plt.yticks(fontsize=20)


plt.subplot(122)
acc_values = history_dict_10['loss']
epochs = range(1, len(loss_values) + 1)
plt.plot(epochs, history_dict_10['acc'], 'o', label='Training loss')
plt.plot(epochs, history_dict_11['acc'], 'o', label='Training loss')
plt.plot(epochs, history_dict_21['acc'], 'o', label='Training loss')
plt.title('Training accuracy',fontsize=20)
plt.xlabel('Epochs',fontsize=20)
plt.ylabel('Accuracy',fontsize=20)
plt.legend(['1_0','1_1','2_1','4_1'],fontsize=20)
plt.xticks(fontsize=20)
plt.yticks(fontsize=20)
plt.show()

Проблема двух лун

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

import sklearn.datasets as sk  
tm_data, tm_labels = sk.make_moons(n_samples=400, shuffle=True, noise=0.05, random_state=0)
print(tm_data.shape)
print(tm_labels.shape)
(400, 2)
(400,)
plt.figure(figsize=(20,10))
plt.scatter(tm_data[:,0][tm_labels==0],tm_data[:,1][tm_labels==0],c='b')
plt.scatter(tm_data[:,0][tm_labels==1],tm_data[:,1][tm_labels==1],c='r')
plt.title('Two-moon problem',fontsize=20)
plt.xticks(fontsize=20)
plt.yticks(fontsize=20)
plt.grid('on')
plt.show()

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

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

e_num=1000
bs_num=32
model = models.Sequential()
model.add(layers.Dense(1, activation='sigmoid', input_shape=(2,)))
model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
history = model.fit(tm_data, 
                    tm_labels, 
                    epochs=e_num,
                    batch_size=bs_num,
                    verbose=0)
history_dict = history.history
model = models.Sequential()
model.add(layers.Dense(2, activation='relu', input_shape=(2,)))
model.add(layers.Dense(1, activation='sigmoid'))
model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
history = model.fit(tm_data, 
                    tm_labels, 
                    epochs=e_num,
                    batch_size=bs_num,
                    verbose=0)
history_dict_21 = history.history
model = models.Sequential()
model.add(layers.Dense(4, activation='relu', input_shape=(2,)))
model.add(layers.Dense(1, activation='sigmoid'))
model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
history = model.fit(tm_data, 
                    tm_labels, 
                    epochs=e_num,
                    batch_size=bs_num,
                    verbose=0)
history_dict_41 = history.history
model = models.Sequential()
model.add(layers.Dense(2, activation='relu', input_shape=(2,)))
model.add(layers.Dense(2, activation='relu'))
model.add(layers.Dense(1, activation='sigmoid'))
model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
history = model.fit(tm_data, 
                    tm_labels, 
                    epochs=e_num,
                    batch_size=bs_num,
                    verbose=0)
history_dict_221 = history.history
model = models.Sequential()
model.add(layers.Dense(3, activation='relu', input_shape=(2,)))
model.add(layers.Dense(2, activation='relu'))
model.add(layers.Dense(1, activation='sigmoid'))
model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
history = model.fit(tm_data, 
                    tm_labels, 
                    epochs=e_num,
                    batch_size=bs_num,
                    verbose=0)
history_dict_321 = history.history
model = models.Sequential()
model.add(layers.Dense(4, activation='relu', input_shape=(2,)))
model.add(layers.Dense(2, activation='relu'))
model.add(layers.Dense(1, activation='sigmoid'))
model.compile(loss='binary_crossentropy', optimizer='adam', metrics=['accuracy'])
history = model.fit(tm_data, 
                    tm_labels, 
                    epochs=e_num,
                    batch_size=bs_num,
                    verbose=0)
history_dict_421 = history.history

Как и раньше, чтобы проверить минимальный набор из трех слоев с четырьмя, двумя и одним нейронами (обозначен как 4_________________________________________________________________ Layer (type) Output Shape Param # ================================================================= dense_1 (Dense) (None, 1) 3 ================================================================= Total params: 3 Trainable params: 3 Non-trainable params: 0 _________________________________________________________________1), я также запустил еще несколько реализаций Keras с меньшим количеством слоев и нейронов. Легко видеть, что только модель 4_________________________________________________________________ Layer (type) Output Shape Param # ================================================================= dense_1 (Dense) (None, 1) 3 ================================================================= Total params: 3 Trainable params: 3 Non-trainable params: 0 _________________________________________________________________1 достигает 100% точности, в то время как сети 2_1, 4_1, 2_________________________________________________________________ Layer (type) Output Shape Param # ================================================================= dense_1 (Dense) (None, 1) 3 ================================================================= Total params: 3 Trainable params: 3 Non-trainable params: 0 _________________________________________________________________1, 3_________________________________________________________________ Layer (type) Output Shape Param # ================================================================= dense_1 (Dense) (None, 1) 3 ================================================================= Total params: 3 Trainable params: 3 Non-trainable params: 0 _________________________________________________________________1 хорошо аппроксимируют данные, но никогда не сходятся до 100% точности.

plt.figure(figsize=(20,10))
plt.subplot(121)
loss_values = history_dict_21['loss']
epochs = range(1, len(loss_values) + 1)
plt.plot(epochs, history_dict_21['loss'], 'o', label='Training loss')
plt.plot(epochs, history_dict_41['loss'], 'o', label='Training loss')
plt.plot(epochs, history_dict_221['loss'], 'o', label='Training loss')
plt.plot(epochs, history_dict_321['loss'], 'o', label='Training loss')
plt.plot(epochs, history_dict_421['loss'], 'o', label='Training loss')
plt.title('Training loss',fontsize=20)
plt.xlabel('Epochs',fontsize=20)
plt.ylabel('Loss',fontsize=20)
plt.legend(['2_1','4_1','2_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
dense_1 (Dense)              (None, 1)                 3         
=================================================================
Total params: 3
Trainable params: 3
Non-trainable params: 0
_________________________________________________________________
1','3_________________________________________________________________ Layer (type) Output Shape Param # ================================================================= dense_1 (Dense) (None, 1) 3 ================================================================= Total params: 3 Trainable params: 3 Non-trainable params: 0 _________________________________________________________________1','4_________________________________________________________________ Layer (type) Output Shape Param # ================================================================= dense_1 (Dense) (None, 1) 3 ================================================================= Total params: 3 Trainable params: 3 Non-trainable params: 0 _________________________________________________________________1'],fontsize=20) plt.xticks(fontsize=20) plt.yticks(fontsize=20) plt.subplot(122) acc_values = history_dict_21['loss'] epochs = range(1, len(acc_values) + 1) plt.plot(epochs, history_dict_21['acc'], 'o', label='Training loss') plt.plot(epochs, history_dict_41['acc'], 'o', label='Training loss') plt.plot(epochs, history_dict_221['acc'], 'o', label='Training loss') plt.plot(epochs, history_dict_321['acc'], 'o', label='Training loss') plt.plot(epochs, history_dict_421['acc'], 'o', label='Training loss') plt.title('Training accuracy',fontsize=20) plt.xlabel('Epochs',fontsize=20) plt.ylabel('Accuracy',fontsize=20) plt.legend(['2_1','4_1','2_________________________________________________________________ Layer (type) Output Shape Param # ================================================================= dense_1 (Dense) (None, 1) 3 ================================================================= Total params: 3 Trainable params: 3 Non-trainable params: 0 _________________________________________________________________1','3_________________________________________________________________ Layer (type) Output Shape Param # ================================================================= dense_1 (Dense) (None, 1) 3 ================================================================= Total params: 3 Trainable params: 3 Non-trainable params: 0 _________________________________________________________________1','4_________________________________________________________________ Layer (type) Output Shape Param # ================================================================= dense_1 (Dense) (None, 1) 3 ================================================================= Total params: 3 Trainable params: 3 Non-trainable params: 0 _________________________________________________________________1'],fontsize=20) plt.xticks(fontsize=20) plt.yticks(fontsize=20) plt.show()

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

Здесь я выбрал несколько простых примеров и исследовал минимальное количество нейронов и слоев, необходимых для правильной классификации. Я показал, что однослойная система может представлять полуплоскости (то есть логику И или ИЛИ), что двухслойная система может классифицировать точки внутри любого количества произвольных линий (то есть логика XOR) и что 3- Система слоев может классифицировать невыпуклые формы (например, проблема двух лун).