Как стать автором
Обновить

Почему нам везде хочется видеть золотое сечение? Попытка (неудачная) эволюционного анализа при помощи нейросетей на C++

Время на прочтение5 мин
Количество просмотров3.1K
Недавно я задался вопросом: связано ли как-то наше желание везде видеть золотое сечение с какими-то сугубо культурными вещами, или же в этом скрыта какая-то более глубокая закономерность, связанная с устройством нашего мозга? Чтобы разобраться в этом вопросе, я решил сделать несколько вещей:

  1. Сформулировать конкретную гипотезу относительно данной закономерности. Я решил, что лучше всего подойдёт предположение, что наш мозг использует систему счисления, основанную на разложении чисел на степени золотого сечения, так как некоторые её особенности очень близки работе примитивных нейросетей: дело в том, что степени золотого сечения более высокого порядка можно разложить бесконечным числом способов в суммы степеней менее высокого порядка и даже отрицательных степеней. Таким образом, более высокая степень как бы «возбуждается» от нескольких низших степеней, тем самым проявляя то самое сходство с нейросетью.
  2. Описать конкретный способ её проверки: я выбрал мат. моделирование эволюции мозга посредством случайных изменений в простейшей возможной нейросети — матрице линейного оператора.
  3. Составить критерии подтверждения гипотезы. Моим критерием было то, что система счисления, основанная на золотом сечении, реализуется на нейросетевом движке при тех же объёмах информации с меньшим числом ошибок, чем двоичная.

Так как речь идёт о программировании, опишу поподробнее второй и третий пункты.

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

Что же касается кодировки данных в самом векторе, то я использовал 28-мерный вектор для двух 14-значных бинарных чисел и их суммы (после первых 14 знаков в сумме идёт просто 14 нулей для заполнения) и 40-мерный вектор для двух чисел в системе с золотым сечением.

Входной файл же имеет следующий формат.

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

Все последующие строки: первая строка — вход нейросети, вторая — правильный результат обработки.

Вот фрагмент кода нейросети, отвечающий за её обучение на выборке из входных данных и соответствующих им правильным результатам:

	while (((d-mu)*(d-mu)>0.01)||(q<10)) //Нейросеть обучается, пока отклонение результата от правильного не станет "топтаться" на ровном месте
	{
		s=0; //Инициализирую переменную для хранения суммы квадратов разностей результата нейросети с правильным
		for (k=0;k<m;k++)
		{
			for (i=0;i<n;i++)
			{
					(*(z+k*n+i))=0;
			}
			for (i=0;i<n;i++)
			{
				for (j=0;j<n;j++)
				{
					(*(z+k*n+i))=(*(z+k*n+i))+(*(a+i*n+j))*(*(x+k*n+j));
				}
			} // Вычисляем результат умножения матрицы нейросети на вектор

			for (i=0;i<n;i++)
			{
				s=s+((*(z+k*n+i))-(*(y+k*n+i)))*((*(z+k*n+i))-(*(y+k*n+i)));
			} //Вычисляем сумму квадратов разностей между результатом нейросети и правильным
			s1=s+1;
		}
		while (s<s1) //Нейросеть пробует изменять матрицу в разные стороны, пока сумма квадратов не будет меньше, чем была раньше
		{
			s1=0; //Инициализируем переменную для новой суммы квадратов
			for (k=0;k<m;k++)
			{
				for (i=0;i<n;i++)
				{
					(*(z+k*n+i))=0;
				}
			}//Инициализируем массив (вектор) для хранения результата умножения матрицы
			rand_s(&p);
			k1 = (int) (p/((int) (UINT_MAX/n)));
			rand_s(&p);
			k2 = (int) (p/((int) (UINT_MAX/n)));
//Генерируем координаты изменяемой "связи" между нейронами, то есть элемента матрицы
			rand_s(&p);
			h=((double) p/UINT_MAX)-0.5; //Генерируем шаг
			h1=1;
			rand_s(&p);
			l=((int) ((double) p/UINT_MAX)*20);
			for (i=0;i<l;i++)
			{
				h1=h1/10;
			}
			h=h*h1;
//Делаем, чтобы шаг более равномерно пробегал различные порядки
			for (k=0;k<m;k++)
			{
				for (i=0;i<n;i++)
				{
					for (j=0;j<n;j++)
					{
						if ((i==k1)&&(j==k2))
							(*(z+k*n+i))=(*(z+k*n+i))+(*(a+i*n+j))*(*(x+k*n+j))+h*(*(x+k*n+j));
						else
							(*(z+k*n+i))=(*(z+k*n+i))+(*(a+i*n+j))*(*(x+k*n+j));
					}
				}
//Вычисляем результат умножения изменённой матрицы на вектор
				for (i=0;i<n;i++)
				{
					s1=s1+((*(z+k*n+i))-(*(y+k*n+i)))*((*(z+k*n+i))-(*(y+k*n+i)));
				}//Вычисляем сумму квадратов разностей между результатом изменённой нейросети и правильным результатом
			}
		}
		(*(a+k1*n+k2))=(*(a+k1*n+k2))+h;
//Изменяем нейросеть после нахождения успешного варианта с меньшей ошибкой в результате
		s1=0;
		d=0;
		for (k1=0;k1<n;k1++)
		{
			for (k2=0;k2<n;k2++)
			{
				for (k=0;k<m;k++)
				{
					for (i=0;i<n;i++)
					{
						(*(z+k*n+i))=0;
					}
				}
				for (k=0;k<m;k++)
				{
					for (i=0;i<n;i++)
					{
						for (j=0;j<n;j++)
						{
							if ((i==k1)&&(j==k2))
								(*(z+k*n+i))=(*(z+k*n+i))+((*(a+i*n+j))+0.1)*(*(x+k*n+j));
							else
								(*(z+k*n+i))=(*(z+k*n+i))+(*(a+i*n+j))*(*(x+k*n+j));
						}
					}
				}
				s1=0;
				for (k=0;k<m;k++)
				{
					for (i=0;i<n;i++)
					{
						s1=s1+((*(z+k*n+i))-(*(y+k*n+i)))*((*(z+k*n+i))-(*(y+k*n+i)));
					}
				}
				d=d+(s1-s)*(s1-s)/(n*m);// Вычисляем средний квадрат изменения результата при изменении элемента матрицы
			}
		}
		mu=mu*((double) q/(q+1))+((double) d/(q+1));//Вычисляем среднее значение квадрата изменения за несколько прошедших циклов
		q=q+1;
		printf("%lf \n",mu);//Выводим на экран это самое среднее для отладки кода
	}

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

Я сравнил ошибки в разных системах счисления парными t-тестами и вот, что у меня получилось.

Сравнение: Золотое сечение — двоичная система
Гипотеза: Ошибка при золотом сечении в среднем меньше.
Результаты:
t = -22.033
df = 999
p<0.001
Cohen's d = -0.697 (При золотом сечении ошибка меньше)
99% доверительный интервал для Cohen's d:
от -inf до -0.615
Тест на нормальность распределения Шапиро — Уилка:
W = 0.998 p=0.382 (распределения примерно соответствуют нормальному)
Дескриптивная статистика:
Золотое сечение:
Среднее арифметическое: 0.365
Стандартное отклонение: 0.044
Двоичная система:
Среднее арифметическое: 0.414
Стандартное отклонение: 0.055

Все данные, использованные в данном небольшом кустарном исследовании, я решил оставить пока у себя как доказательства того, что я не нарисовал цифры с потолка. Кто попросит, тому могу отправить.

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

UPD. С момента публикации автор провёл новое исследование, в котором учёл поправку на количество измерений и влияние основания системы счисления отдельно от его расстояния до золотого сечения при помощи линейной регрессии. Результат оказался неутешительным: близость основания к золотому сечению скорее увеличивает ошибку, чем уменьшает её, так что сенсация, как всегда, сорвалась.
Теги:
Хабы:
Всего голосов 14: ↑10 и ↓4+6
Комментарии2

Публикации

Истории

Работа

QT разработчик
8 вакансий
Программист C++
133 вакансии

Ближайшие события