Вопрос или проблема
Я читаю книгу под названием “Глубокое обучение на Python”. Я нахожусь на этапе, где автор объясняет реализацию нейронной сети в “сыром” TensorFlow. То есть, используя минимальное количество Keras.
Я хотел добавить слой, чтобы посмотреть, что произойдет. Поскольку мы вводим больше весов, я ожидал значительного падения производительности в начале. Кроме того, я изменил функцию активации, чтобы ввести немного больше нелинейности.
Посмотрите на результаты:
Эпоха 0
потеря на пакете 0: 12.23
потеря на пакете 100: 14.48
потеря на пакете 200: 14.48
потеря на пакете 300: 13.10
потеря на пакете 400: 14.98
Эпоха 1
потеря на пакете 0: 13.60
потеря на пакете 100: 14.48
потеря на пакете 200: 14.48
потеря на пакете 300: 13.10
потеря на пакете 400: 14.98
Эпоха 2
потеря на пакете 0: 13.60
потеря на пакете 100: 14.48
потеря на пакете 200: 14.48
потеря на пакете 300: 13.10
потеря на пакете 400: 14.98
Эпоха 3
потеря на пакете 0: 13.60
потеря на пакете 100: 14.48
потеря на пакете 200: 14.48
потеря на пакете 300: 13.10
потеря на пакете 400: 14.98
Эпоха 4
потеря на пакете 0: 13.60
потеря на пакете 100: 14.48
потеря на пакете 200: 14.48
потеря на пакете 300: 13.10
потеря на пакете 400: 14.98
точность: 0.11
По какой-то причине потеря вообще не уменьшается. Мы попадаем в некий цикл. Найдено ли в коде локальное минимум или что-то еще? Вот код:
import tensorflow as tf
import math
import numpy as np
class NaiveDense:
def __init__(self, input_size, output_size, activation):
self.activation = activation
w_shape = (input_size, output_size)
w_intial_value = tf.random.uniform(w_shape, minval = 0, maxval=1e-1)
self.W = tf.Variable(w_intial_value)
b_shape = (output_size,)
b_initial_value = tf.zeros(b_shape)
self.b = tf.Variable(b_initial_value)
# прямой проход
def __call__(self, inputs):
return self.activation(tf.matmul(inputs, self.W) + self.b)
@property
def weights(self):
return [self.W, self.b]
class NaiveSequential:
def __init__(self, layers):
self.layers = layers
def __call__(self, inputs):
x = inputs
for layer in self.layers:
x = layer(x)
return x
@property
def weights(self):
weights = []
for layer in self.layers:
weights += layer.weights
return weights
model = NaiveSequential([
NaiveDense(input_size = 28 * 28, output_size=512, activation=tf.nn.relu),
NaiveDense(input_size = 512, output_size=256, activation=tf.nn.softplus),
NaiveDense(input_size = 256, output_size=10, activation=tf.nn.softmax)
])
class BatchGenerator:
def __init__(self, images, labels, batch_size = 128):
assert len(images) == len(labels)
self.index = 0
self.images = images
self.labels = labels
self.batch_size = batch_size
self.num_batches = math.ceil(len(images) / batch_size)
def next(self):
images = self.images[self.index : self.index + self.batch_size]
labels = self.labels[self.index : self.index + self.batch_size]
self.index += self.batch_size
return images, labels
def one_training_step(model, images_batch, labels_batch):
with tf.GradientTape() as tape:
predicitions = model(images_batch)
per_sample_losses = tf.keras.losses.sparse_categorical_crossentropy(labels_batch, predicitions)
average_loss = tf.reduce_mean(per_sample_losses)
gradients = tape.gradient(average_loss, model.weights)
update_weights(gradients, model.weights)
return average_loss
learning_rate = 1e-3
def update_weights(gradients, weights):
for g, w in zip(gradients, weights):
w.assign_sub(g * learning_rate)
def fit(model, images, labels, epochs, batch_size=128):
for epoch_counter in range(epochs):
print(f"Эпоха {epoch_counter}")
batch_generator = BatchGenerator(images, labels)
for batch_counter in range(batch_generator.num_batches):
images_batch, labels_batch = batch_generator.next()
loss = one_training_step(model, images_batch, labels_batch)
if batch_counter % 100 == 0:
print(f"потеря на пакете {batch_counter}: {loss:.2f}")
from tensorflow.keras.datasets import mnist
(train_images, train_labels), (test_images, test_labels) = mnist.load_data()
train_images = train_images.reshape((60000, 28 * 28))
train_images = train_images.astype("float32") / 255
test_images = test_images.reshape((10000, 28 * 28))
test_images = test_images.astype("float32") / 255
fit(model, train_images, train_labels, epochs=5, batch_size = 128)
predictions = model(test_images)
precictions = predictions.numpy()
predicted_labels = np.argmax(predictions, axis = 1)
matches = predicted_labels == test_labels
print(f"точность: {matches.mean():.2f}")
Я пытался добавить слой в Keras, и я получил желаемый результат. То есть точность увеличивается с 0.9793 до 0.9811.
from tensorflow import keras
from tensorflow.keras import layers
from tensorflow.keras.datasets import mnist
(train_images, train_labels), (test_images, test_labels) = mnist.load_data()
model = keras.Sequential([
layers.Dense(512, activation = "relu"),
layers.Dense(256, activation = "relu"),
layers.Dense(10, activation = "softmax")
])
model.compile(optimizer="rmsprop",
loss = "sparse_categorical_crossentropy",
metrics = ["accuracy"])
train_images = train_images.reshape((60000, 28 * 28))
train_images = train_images.astype("float32") / 255
test_images = test_images.reshape((10000, 28 * 28))
test_images = test_images.astype("float32") / 255
model.fit(train_images, train_labels, epochs=5, batch_size=128)
test_loss, test_acc = model.evaluate(test_images, test_labels)
print(f"Тестовая точность: {test_acc}")
Я, возможно, упустил что-то в расчете градиента в коде TensorFlow? Я знаю, что активации внутреннего слоя различаются в каждом случае, но я пробовал использовать одну и ту же активацию, и в результате получаю этот “цикл” в градиенте.
Ответ или решение
Ваша ситуация с обучением нейронной сети (NN) от начала является интересной и весьма распространенной проблемой, с которой сталкиваются многие разработчики, когда они начинают экспериментировать с архитектурами и гиперпараметрами. Рассмотрим детали ситуации и возможные причины, по которым ваша модель не показывает желаемого результата.
1. Проблема с градиентами
Когда вы добавляете слои в нейронную сеть, количество параметров увеличивается, что порой может привести к тому, что модель станет "сложнее" для обучения. Ваша модель может застрять в локальном минимуме или страдать от проблем, связанных с градиентами, таких как исчезновение или взрыв градиентов. В вашем случае, насколько видно, градиенты не обновляются должным образом, что может указывать на проблемы в вычислении или обновлении весов.
Рекомендация:
Проверьте, что активация слоев не приводит к проблемам с градиентами. Например, использование softplus
в промежуточном слое может привести к неэффективному обновлению весов. Рекомендуется попробовать заменить его на relu
для лучшей работы.
2. Инициализация весов
Неправильная инициализация весов также может привести к проблеме. Вы используете tf.random.uniform
для инициализации весов, что может привести к слишком маленьким или большим значениям, не позволяющим модели учиться.
Рекомендация:
Проверьте другие методы инициализации, такие как He
или Xavier
инициализация, которые могут помочь создать более приемлемые начальные условия для обучения.
w_initial_value = tf.random.normal(w_shape, stddev=np.sqrt(2. / input_size))
3. Доступность данных
Также важно проверить сам набор данных. Возможно, ваши данные не очень хорошо нормализованы или имеют какие-либо аномалии, которые мешают обучению. Вы нормализовали входные данные к диапазону [0, 1], что является хорошей практикой, тем не менее, убедитесь, что ваши метки соответствуют ожидаемым форматам.
4. Обучение и выбор оптимизатора
Вы используете простой метод обновления весов (стохастический градиентный спуск), у него есть свои ограничения в производительности. Попробуйте использовать более продвинутые оптимизаторы, такие как Adam или RMSprop, которые адаптивно подстраивают скорость обучения.
optimizer = tf.keras.optimizers.Adam(learning_rate)
5. Шаги улучшения
Проверка вывода
Может быть полезно производить вывод градиентов и значений за пределами функции потерь после каждой итерации для отладки:
print("Градиенты: ", gradients)
Оптимизация функции потерь
Попробуйте использовать регуляризацию в функции потерь, чтобы избежать переобучения:
average_loss = tf.reduce_mean(per_sample_losses) + regularization_term
Подведение итогов
Ваша задача состоит в том, чтобы тщательно проанализировать каждую секцию вашего кода и отдельно рассмотреть, как новые добавленные слои и их активации влияют на обучение. Важно иметь четкое понимание, как различные компоненты взаимодействуют и как они управляют очередностью градиентов и обновлением весов.
Попробуйте применять предложенные изменения и внимательно следите за движением функции потерь и градиентов во время обучения. С течением времени вы достигнете лучших результатов в вашей нейронной сети.