Я пытаюсь научить нейронную сеть изучать y = |x| функция. Как мы знаем, абсолютная функция имеет две разные линии, соединяющиеся друг с другом в нулевой точке. Итак, я пытаюсь использовать следующую последовательную модель:
Скрытый слой: 2 Плотный слой (релу активации) Выходной слой: 1 плотный слой
после обучения модели она соответствует только половине функции. Чаще всего это правая сторона, иногда левая. Как только я добавляю еще 1 слой в скрытый слой, чтобы вместо 2 у меня было 3, он идеально подходит для этой функции. Может ли кто-нибудь объяснить, почему нужен дополнительный слой, если абсолютная функция имеет только один разрез?
Вот код:
import numpy as np
X = np.linspace(-1000,1000,400)
np.random.shuffle(X)
Y = np.abs(X)
# Reshape data to fit the model input
X = X.reshape(-1, 1)
Y = Y.reshape(-1, 1)
import tensorflow as tf
import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
# Build the model
model = tf.keras.models.Sequential([
tf.keras.layers.Dense(2, activation='relu'),
tf.keras.layers.Dense(1)
])
# Compile the model
model.compile(optimizer='adam', loss='mse',metrics=['mae'])
model.fit(X, Y, epochs=1000)
# Predict using the model
Y_pred = model.predict(X)
# Plot the results
plt.scatter(X, Y, color='blue', label='Actual')
plt.scatter(X, Y_pred, color='red', label='Predicted')
plt.title('Actual vs Predicted')
plt.xlabel('X')
plt.ylabel('Y')
plt.legend()
plt.show()
График для 2 плотных слоев:
График для 3 плотных слоев:
То есть вы имеете в виду, что, например, relu выводит 0 для второго перцептрона, он никогда не будет его обновлять, потому что он 0, и будет фокусироваться только на первом?
Не совсем, но в основном в этой простой настройке, если оба скрытых модуля инициализируются на одной и той же «стороне», например. оба имеют положительный вес, они активируются только при положительных входных данных. Таким образом, они могут научиться правильной активации для положительных входов, но не для отрицательных (потому что там у них всегда есть отрицательная активация, которая скорректирована до 0 и нет градиента). По сути, у вас есть два скрытых блока, выполняющих одну и ту же задачу, поэтому это будет то же самое, что иметь один скрытый блок, который не может изучить функцию пресса.
Имея два плотных слоя, вы, по сути, имеете входной и выходной слой. Судя по вашему коду, у вас есть только два слоя, но нет скрытых слоев в вашей модели. Это позволяет аппроксимировать функции 1-й степени, например линии для классификации, регрессии и т. д. Градиент рассчитывается только дважды за итерацию, что дает вам по сути две опорные точки — отлично подходит для линий, но не очень хорошо для притч.
Если вы добавите еще один слой, у вас появится скрытый слой между входом и выходом. Это позволяет проводить более сложное моделирование, поскольку градиент теперь рассчитывается трижды за итерацию. Обычно он используется для аппроксимации функций второго порядка, например х^2 и тому подобное.
np.abs(x) похож на притчу и требует трехслойной модели. Если вам интересно, как и почему работают нейронные сети, на Coursera есть очень хороший курс от Эндрю Нг «Машинное обучение». Это бесплатно, если вам не нужен сертификат.
На самом деле это не так, потому что в приведенном выше коде первый слой Dense
является скрытым слоем (и входные данные по сути являются входным слоем, а последний слой Dense
— выходным слоем). Чтобы воспроизвести описанную вами ситуацию, нам нужна NN только с 1 слоем. Кажется, решение этого конкретного вопроса описано в первом комментарии под публикацией, мы можем просто проверить его, используя Dense
с разными значениями tf.keras.utils.set_random_seed(seed)
.
Нет, модель выше имеет только два определенных слоя. Первый плотный слой должен быть входным слоем. Никакого дополнительного входного слоя нигде не определено.
Спасибо за ответ @emely_pi, но первый слой - это скрытый слой, потому что входной слой настраивается автоматически и поэтому здесь не написано, поэтому у меня Входной слой => Плотный слой с 2 выходами => 1 выходной слой, поэтому с средний слой должен иметь возможность аппроксимировать функции x^2. Если бы это было не так, я бы получил ошибку, потому что форма первого слоя с двумя входами не соответствовала бы форме X, которая имеет один вход
Это зависит от инициализации веса.
Если оба веса инициализированы положительными числами, сеть может предсказывать только положительные числа. Для отрицательных чисел всегда выводится ноль. Это также не приведет к отсутствию градиентов — нет никакого небольшого шага к весам, который мог бы сделать выходные данные немного лучше.
Поэтому либо переключитесь на другую функцию активации, например, на дырявую Relu, которая также передает некоторый сигнал для отрицательных значений, либо измените init.
В приведенном ниже коде я демонстрирую это с различными пользовательскими инициализациями.
Good_init устанавливает один вес на положительное, другой на отрицательное значения -> Проблема решена. оба bad_inits устанавливают веса на один и тот же знак, и будет изучена только половина домена.
import tensorflow as tf
import tensorflow as tf
import numpy as np
import matplotlib.pyplot as plt
X = np.linspace(-1000,1000,400)
np.random.shuffle(X)
Y = np.abs(X)
# Reshape data to fit the model input
X = X.reshape(-1, 1)
Y = Y.reshape(-1, 1)
from keras import backend as K
def good_init(shape, dtype=None):
# one positive, one negative weight
val=np.linspace(-1,1,np.prod(shape)).reshape(shape)
return K.variable(value=val, dtype=dtype)
def bad_init_right(shape, dtype=None):
# both weights positive, only right side works
val=np.linspace(-1,1,np.prod(shape)).reshape(shape)
val=np.abs(val)
return K.variable(value=val, dtype=dtype)
def bad_init_left(shape, dtype=None):
# both weights negative, only right side works
val=np.linspace(-1,1,np.prod(shape)).reshape(shape)
val=-np.abs(val)
return K.variable(value=val, dtype=dtype)
# Build the model
model = tf.keras.models.Sequential([
tf.keras.layers.Dense(2, activation='relu', kernel_initializer=bad_init_left),
tf.keras.layers.Dense(1)
])
# Compile the model
model.compile(optimizer='adam', loss='mse',metrics=['mae'])
model.fit(X, Y, epochs=100)
# Predict using the model
Y_pred = model.predict(X)
# Plot the results
plt.scatter(X, Y, color='blue', label='Actual')
plt.scatter(X, Y_pred, color='red', label='Predicted')
plt.title('Actual vs Predicted')
plt.xlabel('X')
plt.ylabel('Y')
plt.legend()
plt.show()
у вас все получилось, спасибо! И последний вопрос: почему тогда добавление слоев помогает модели?
Если вы добавите третий нейрон, tf.keras.layers.Dense(2,activation='relu') -> tf.keras.layers.Dense(3,activation='relu'), вы увеличите вероятность наличия веса у обоих знаки от 50% до 75%.
Вероятно, просто проблема с инициализацией. Если оба модуля инициализированы на «одной и той же стороне» (т. е. оба имеют одинаковый знак для своих весов), они никогда не смогут переключиться, потому что для другой стороны градиент не получен (из-за жесткого выхода 0). На самом деле это должно сработать в 50% случаев или около того. С большим количеством слоев вероятность этого становится менее вероятной, поскольку у вас фактически больше юнитов, поэтому вероятность того, что все они «на одной стороне», уменьшается в геометрической прогрессии.