Вопрос или проблема
Я пытаюсь создать нейронную сеть без использования какой-либо библиотеки глубокого обучения, которая распознает числа из базы данных mnist. Ее структура: 784 входных нейрона (для 784 пикселей на изображениях чисел), 10 скрытых нейронов (только 1 скрытый слой) и 10 выходных нейронов. Есть 10 смещений для скрытого слоя.
Я думаю, что знаю, как обновить веса последнего слоя, но не первые, так как веса последнего слоя влияют на результат. Я не знаю, как обновить смещения. Если я допустил какие-то ошибки в обновлении последнего слоя, пожалуйста, дайте мне знать.
Вот код:
#прямое распространение
def forward(inp, w1, w2, biases):
hidsRes = []
outRes = []
for i in range(len(w1)):
n = np.dot(inp, w1[i])
n += biases[i]
n = relu(n)
hidsRes.append(n)
for i in range(len(w2)):
n = np.dot(hidsRes, w2[i])
outRes.append(n)
return softmax(outRes)
#обратное распространение
def back(avgResult, w1, w2, lr):
for i, w in enumerate(w2):
w2[i] += lr * avgResult[i] #Я обновляю только последний слой, основываясь на среднем ошибке каждого нейрона
def train(inps, hids, outs, randomWeightDiff, batchs, gens, lr):
w1, w2, b = initNn(inps, hids, outs, randomWeightDiff)
# загрузка набора данных mnist
x_train, x_test, y_train, y_test = getData()
for gen in range(gens):
errors = []
x_train, y_train = shuffle(x_train, y_train)
for batch in range(batchs):
prediction = forward(tolist(x_train[batch].tolist()), w1, w2, b)
y = y_train[batch]
target = [0 if i != y else 1 for i in range(10)]
errors.append([prediction[i] - target[i] for i in range(10)])
print(errors)
avg = [sum([errors[i][j] for j in range(len(errors))]) / 10 for i in range(10)]
back(avg, w1, w2, lr)
print("Поколение {gen} \n" + f"{avg}")
train(784, 10, 10, 2, 100, 1000, 0.01)
Я пробовал симулировать множество нейронных сетей и мутировать лучшие из них, но это было слишком медленно и не работало.
Кстати, я еще не изучалadvanced math.
Я думаю, что ваша самая большая ошибка – это обновление весов с использованием ошибки модели, а не производных ошибки. Это не обратное распространение. Чтобы обновить параметр сети, вы должны вычислить производную ошибки по отношению к параметру (с использованием правила цепи для более глубоких слоев) и применить ее к весу, используя метод градиентного спуска.
Минимизация ошибки немного похожа на спуск с холма. Градиенты сообщают вам, куда вы должны сделать следующий шаг, чтобы максимально быстро спуститься. Вам нужна информация о склоне пути вокруг вас. Использование средней ошибки можно сравнить с поиском пути вниз, зная только высоту, это не очень полезно.
Я держу кулачки за ваши дальнейшие шаги 🙂
Ответ или решение
Обновление весов первого слоя в нейронной сети представляет собой ключевую задачу при обучении модели. Давайте рассмотрим, как это сделать, основываясь на описании вашей сети и учете некоторых принципов обратного распространения ошибок.
Основные принципы
-
Обратное распространение ошибки: Для обновления весов нейронной сети необходимо не только знать среднюю ошибку на выходе модели, но и вычислить градиенты этих ошибок по отношению к каждому весу. Это достигается с помощью метода обратного распространения, который использует правило цепочки.
-
Градиентный спуск: Обновление весов осуществляется с использованием градиентного спуска, который предполагает перемещение весов в направлении, противоположном направлению градиента функции потерь, для минимизации ошибки.
Алгоритм обновления весов первого слоя
Давайте пошагово разберемся, как обновить веса первого слоя и смещения (bias) в вашей модели.
-
Вычисление градиента ошибки для выходного слоя:
- На выходном слое ваша ошибка, обозначаемая как
avg
, используется для вычисления градиента:
[
\delta^{(2)} = (y{\text{pred}} – y{\text{true}}) \cdot \text{softmax}'(z^{(2)})
]
где ( z^{(2)} ) – это выходные активации перед функцией softmax.
- На выходном слое ваша ошибка, обозначаемая как
-
Обновление весов второго слоя:
- Чтобы обновить веса второго слоя, вам нужно использовать градиент ошибки:
for i in range(len(w2)): w2[i] -= lr * np.outer(hidsRes, delta2) # где delta2 - это градиент для второго слоя
- Чтобы обновить веса второго слоя, вам нужно использовать градиент ошибки:
-
Вычисление градиента для скрытого слоя:
- Далее, вам необходимо вычислить δ(1) для скрытого слоя:
[
\delta^{(1)} = \delta^{(2)} \cdot w^{(2)} \cdot \text{relu}'(z^{(1)})
]
Здесь ( z^{(1)} ) – это активации скрытого слоя, аrelu'
– производная функции активации ReLU (которая равна 1 для положительных значений и 0 для отрицательных).
- Далее, вам необходимо вычислить δ(1) для скрытого слоя:
-
Обновление весов первого слоя:
- Обновление весов первого слоя производится аналогично:
for i in range(len(w1)): w1[i] -= lr * np.outer(X, delta1) # где delta1 - это градиент для скрытого слоя
- Обновление весов первого слоя производится аналогично:
-
Обновление смещений (bias):
- Смещения также следует обновлять с использованием вычисленных градиентов:
biases[i] -= lr * delta1 # Для каждого нейрона в скрытом слое
- Смещения также следует обновлять с использованием вычисленных градиентов:
Полный код
Обновление функции back
может выглядеть следующим образом:
def back(y_true, y_pred, w1, w2, hidsRes, inp, lr):
delta2 = y_pred - y_true # ошибка в выходном слое
for i in range(len(w2)):
w2[i] -= lr * np.outer(hidsRes, delta2) # обновление весов второго слоя
# Обратное распространение для скрытого слоя
delta1 = np.dot(delta2, w2) * relu_derivative(hidsRes) # вычисление градиента для скрытого слоя
for i in range(len(w1)):
w1[i] -= lr * np.outer(inp, delta1) # обновление весов первого слоя
# Обновление смещений
b -= lr * delta1 # аналогично для всех смещений
Заключение
Обновление весов первого слоя требует тщательного выполнения шагов обратного распространения. При этом важно правильно вычислять производные и использовать их для корректного обновления весов и смещений. Постепенно совершенствуя вашу реализацию, вы сможете достигать более высоких результатов в распознавании символов из данных MNIST. Успехов в обучении нейронных сетей!