Вопрос или проблема
Я пытаюсь реализовать сеть Хопфилда на Python, используя библиотеку NumPy. В сети 2500 узлов (50 высота x 50 ширина). Сеть обучается 10 шаблонам из изображений размером 50×50, хранящимся в папке “patterns”. Изображения представляют собой цифры от 0 до 9. Изображения преобразуются в двумерный массив, уплощаются до одномерного (2500×1) и обучаются.
После изучения шаблонов сети дается номер изображения (из которого я удалил несколько пикселей), чтобы сопоставить его с сохраненными шаблонами. Но проблема, с которой я сталкиваюсь, заключается в том, что он не сходится к одному из сохраненных шаблонов, а выводит что-то вроде случайного:
Даже если я попытаюсь ввести точный шаблон из тех, которые он уже выучил, он все равно не сходится к этому шаблону.
Вот код моей попытки реализовать сеть Хопфилда.
import numpy as np
import matplotlib.pyplot as plt
from matplotlib import image
from os import listdir
import random
from os.path import isfile, join
# Параметры сети Хопфилда
input_width = 50
input_height = 50
number_of_nodes = input_width * input_height
learning_rate = 1
# Инициализация массива входов в -1 и массива весов в 0
input = np.zeros((number_of_nodes))
input[True] = -1
weights = np.zeros((number_of_nodes,number_of_nodes))
#*******************************
# Основные функции сети Хопфилда
#********************************
# Случайно активируем узлы, пока общий выход не изменится
# сопоставляем шаблон, сохраненный в сети Хопфилда.
def calculate_output(input, weights):
changed = True
while changed:
indices = list(range(len(input)))
random.shuffle(indices)
new_input = np.zeros((number_of_nodes))
clamped_input = input.clip(min=0) # исключаем узлы с отрицательным значением, не работает в любом случае
for i in indices:
sum = np.dot(weights[i], clamped_input)
new_input[i] = 1 if sum >= 0 else -1
changed = not np.allclose(input[i], new_input[i], atol=1e-3)
input = np.array(new_input)
return np.array(input)
# активация(W x I) = вывод
# сопоставляем шаблон, сохраненный в сети Хопфилда.
def calculate_output_2(input, weights):
output = np.dot(weights,input)
# применяем пороговое значение
output[output >= 0] = 1 # зеленый на изображении
output[output < 0] = -1 # пурпурный на изображении
return output
# Сохраняем шаблоны в сети Хопфилда
def learn(input, weights):
I = np.identity(number_of_nodes) # диагональ всегда будет равна 1, если вход только 1/-1
updates = learning_rate * np.outer(input,input) - I
updates = updates/number_of_nodes
weights[:] = weights + updates
#*******************************
# Прочие функции
#*******************************
# отображаем массив и показываем на экране
def show_array(arr):
data = arr.reshape((-1, input_width))
plt.imshow(data) # построение графика по столбцам
plt.show()
# обучаем шаблонам (изображениям), размещенным в папке "patterns" (изображения чисел 0-9)
def learn_numbers():
for f in listdir("patterns/"):
file = join("patterns/", f)
if isfile(file):
print(file)
im = image.imread(file)
grey = im[:,:,0] # преобразование в двумерный массив из трехканального RGB изображения
grey = np.where(grey==1,-1,1) # преобразование белого пикселя в -1, а черного в 1
learn(grey.flatten(), weights) # преобразование двумерного изображения в одномерный массив (2500) и сохранение в весах
# читаем тестовое изображение и сопоставляем ближайший шаблон
def calculate_img_output(weights, image_address):
# отображаем тестируемое изображение
im = image.imread(image_address)
grey = im[:,:,0] # преобразование в двумерный массив из трехканального RGB изображения
grey = np.where(grey==1,-1,1) # преобразование белого пикселя в -1, а черного в 1
plt.imshow(grey) # построение графика по столбцам
plt.show()
# получаем шаблон с использованием случайной активации
output = calculate_output(grey.flatten(), weights)
# отображаем шаблон
show_array(output)
# получаем шаблон
output = calculate_output_2(grey.flatten(), weights)
# отображаем шаблон
show_array(output)
#****************************
# Тестирование кода
#*****************************
# обучаем шаблоны изображения числами от 0-9
learn_numbers()
# пытаемся сопоставить частичный шаблон
calculate_img_output(weights, "partial/p.png")
Любая помощь в объяснении, почему сеть ведет себя таким образом и что можно сделать для исправления, будет очень полезной.
Проблема в том, что скорость обучения установлена слишком высокой. Минимальная допустимая скорость обучения для вашего кода составляет 0.00057 (по крайней мере, на моем устройстве). Любая скорость обучения выше этого значения не позволит сети сойтись. Я не вносил никаких других изменений в ваш код. Установив скорость обучения 0.00057 или ниже, сеть начинает сходиться.
Ответ или решение
Конечно, давайте рассмотрим вашу проблему с сетью Хопфилда более детально. Ваша сеть Хопфилда, реализованная на Python и использующая библиотеку NumPy, не сходится к одной из выученных образов, что может быть вызвано несколькими проблемами. Ниже мы подробно разберем возможные причины и способы их устранения.
Проблема с высокой скоростью обучения
Как было отмечено в вашем описании, скорость обучения (learning rate) в вашей сети установлена слишком высокая. В модели Хопфилда скорость обучения существенно влияет на устойчивость и сходимость сети. В вашем коде она установлена как 1, что может привести к превышению требуемого уровня чувствительности сети и возникновению случайных вывода. Оптимальное значение, как вы уже выяснили, составляет 0.00057 или ниже.
Полная реализация и анализ
1. Инициализация весов и входных данных
Проверьте правильность инициализации весов. Они должны быть равны нулю на диагонали и использоваться без модификаций в других элементах, чтобы не нарушать симметрию сети.
weights = np.zeros((number_of_nodes, number_of_nodes))
2. Обучение сети
Во время обучения корректируйте веса, используя оригинальную формулу обучения Хопфилда:
updates = learning_rate * np.outer(input, input) - I
где I – это единичная матрица, что предотвращает самосвязанность узлов.
3. Обработка входных данных
Ваши изображения преобразуются в формате, где белые пиксели соответствуют -1, а черные – 1. Это соответствует логике большинства сетей Хопфилда и должно сохраняться в процессе обработки.
grey = np.where(grey == 1, -1, 1)
4. Механизм обновления
Функция calculate_output
должна правильно обновлять узлы, обеспечивая перемешивание индексов и итеративное обновление до скрытия изменений:
changed = True
while changed:
indices = list(range(len(input)))
random.shuffle(indices)
new_input = np.zeros((number_of_nodes))
clamped_input = input.clip(min=0) # Clamping может быть убран для стандартной активации
for i in indices:
sum = np.dot(weights[i], input) # Используется полный вход
new_input[i] = 1 if sum >= 0 else -1
changed = not np.array_equal(input, new_input)
input = np.copy(new_input)
Дополнительные рекомендации
- Визуализация и проверка: Проверяйте каждый шаг с помощью визуализации, чтобы убедиться, что вход и выход являются ожидаемыми.
- Диагностика ошибок: Используйте промежуточные принты для выявления узлов, которые неправильно обновляются.
- Тестирование с меньшим количеством образов: Начните с меньшего количества образов для более простого анализа.
Заключение
Сеть Хопфилда может быть чувствительной к параметрам и архитектуре. Правильно устанавливая скорость обучения и анализируя процесс обновления узлов, можно достичь правильной сходимости модели. Убедитесь, что все шаги правильно настроены в соответствии с тем, что мы обсудили, и внесите необходимые коррективы. Если вы будете следовать этим рекомендациям, вы должны увидеть улучшение в поведении сети.