Мозг и золотое сечение: второй заход

Я как-то написал статью, в которой описал простейшую математическую модель эволюции мозга и его отбора на умение складывать числа в системах счисления с основаниями 2 и золотым сечением, причём оказалось, что золотое сечение лучше работает. Так вот, мой первый опыт оказался совсем уж плохим, так как я не учёл ряд важных нюансов, связанных с тем, что ошибку стоило учитывать не на нейрон, а на бит информации, поэтому я решил улучшить свой эксперимент, и внести в него ещё несколько корректировок:
1. Я решил проверить 100 пар выборок по 15 (выборка обучения) и 1000 (тестовая выборка) векторов в системах счисления с равномерно распределёнными основаниями от 1,2 до 2 вместо двух заранее известных оснований.
2. Ещё я сделал линейную регрессию не только от расстояния от основания до золотого сечения, но и ещё от самого основания, количества координат в векторе и средней величины координаты в векторе ответа, чтобы учесть нелинейность зависимости ошибки от основания.
3. Также я проверил некоторые выборки на нормальность по критерию Колмогорова — Смирнова, ANOVе, но эти критерии показали, что выборки, скорее всего, отклоняются от гауссианы, поэтому я решил сделать взвешенную линейную регрессию вместо обычной. Однако ANOVA, хотя и показала F чуть-чуть меньшее, чем раньше (в районе 700-800 вместо 800-900), но всё равно результат остался более чем статистически значимым, а, значит, следовало провести ещё дополнительные тесты. В качестве этих тестов я взял гистограмму плотности распределения остатков регрессии и нормальный Q-Q — график функции распределения этих остатков.
Вот эти два графика:
image
image
Как видно, хотя отклонение от нормального распределения у распределения остатков статистически значимое (а слева даже небольшая вторая мода видна на гистограмме), на деле оно весьма близко к гауссиане, поэтому можно (с осторожностью и большими доверительными интервалами) на эту линейную регрессию опираться.
Теперь о том, как я генерировал выборки для испытания над ними нейросети. Вот код программы для генерации выборок:

#define _CRT_RAND_S //Определяем нужную переменную для использования rand_s()
#include "main.h" //Включаем заголовочный файл (речь о нём пойдёт далее)
int main(void)
{
	FILE *output,*test;
	int i;
	while (fopen("test.txt","w")==NULL)
		i=0;
	output=fopen("test.txt","w"); //Начинаем запись в файл с тестовой выборкой
	unsigned int p;
	p=0;//Создаём и инициализируем переменную для хранения значения rand_s();
	rand_s(&p);
	double a;
	a=0;
	a = 1.6+((double) ((double) ((double)p/UINT_MAX)-0.5)*0.8);//Генерируем основание системы счисления
	int n;
	n=0;//Инициализируем переменную для хранения размера вектора.
	bool *t;//Создаём массив с цифрами после запятой в данной системе счисления.
	while (malloc(sizeof(bool)*1000)==NULL)
		n=0;
	t = (bool *) malloc(sizeof(bool)*1000);
	rand_s(&p);
	double s;
	s=0;
	s = (double)p/UINT_MAX;//Генерируем случайное число от 0 до 1.
	calculus(a,s,t,1000);//Переводим s в a-ичную систему счисления.
	double mu;
	int q;
	mu=0;
	q=0;
	for (i=0;i<1000;i++)
	{
		if ((*(t+i))==true)
			mu =(double) mu+1;
	}//Вычисляем среднюю величину цифры после запятой, т. е. вероятность, что этой цифрой будет единица.
	mu=(double) mu/1000;
	printf("%10.9lfn",mu);
	n = (int) ((double) 14)/(log(mu)*mu/(log((double) 1/2))+log((double) 1-mu)*(1-mu)/log((double) 1/2)); //Вычисляем размерность вектора такого, чтобы он хранил примерно 14 бит информации в a-ичной системе счисления.
	printf("%in",n);
	free(t);
	while (malloc(sizeof(bool)*n)==NULL)
		i=0;
	t = (bool *) malloc(n*sizeof(bool));//Снова создаём массив для хранения чисел, но уже с другим числом цифр.
	double x,y,z;
	x=0;
	y=0;
	z=0;
	int j;
	j=0;
	int m;
	m=0;
	m=2*n;
	fprintf(output,"%i 1000n",m);//Выводим число координат в векторе и число пар векторов слагаемые-ответ.
	fprintf(output,"%lfn",a); //Выводим основание системы счисления
	for (i=0;i<1000;i++)
	{//Вычисляем слагаемые с помощью криптографического ГПСЧ, складываем их, записываеем их в первый вектор, а сумму - во второй.
		rand_s(&p);
		x = (double) p/UINT_MAX;
		rand_s(&p);
		y = (double) p/UINT_MAX;
		z=x+y;
		calculus(a,x,t,n);
		for (j=0;j

А вот и код заголовочного файла:

#include 
#include 
#include 
int main(void);
void calculus(double a, double x, bool *t, int n);//Определяем функцию для разложения числа x по основанию a в массив t из n элементов.
void calculus(double a, double x, bool *t, int n)
{
	int i,m,l;
	double b,y;
	b=0;
	m=0;
	l=0;
	b=1;
	int k;
	k=0;
	i=0;
	y=0;
	y=x;
        //Очищаем массив t от предыдущих данных.
	for (i=0;i1)
		{
			b=1;
			l=0;
			while ((b*ay)&&(m

Также я решил выложить полный код нейросети:

#include "main.h" //В заголовочном файле нет ничего, кроме включения других, стандратных заголовочных файлов и определения функции main(void).
int main(void)
{
	FILE *input, *output, *test;
	int i,j,k,k1,k2,l,q,n,m,r;
	double *x,*y,*z,*a,s,s1,h,h1,d,mu,buffer;
	d=0;
	mu=0;
	r=0;
	unsigned int p;
	n=0;
	while (fopen("input.txt","r")==NULL)
		i=0;
	while (fopen("output.txt","w")==NULL)
		i=0;
	input = fopen("input.txt","r");
	output = fopen("output.txt","w");
	fscanf(input,"%i %i",&n,&m);//Считываем количество координат и количество пар векторов.
	buffer=0;
	fscanf(input,"%lf",&buffer);//Считываем основание просто чтобы дальше можно было считывать вектора.
	while (malloc(sizeof(double)*n*m)==NULL)
		i=0;
	x = (double *) malloc(sizeof(double)*n*m);//Создаём массив для хранения слагаемых
	while (malloc(sizeof(double)*n*m)==NULL)
		i=0;
	z = (double *) malloc(sizeof(double)*n*m);//Создаём массив для хранения произведения матрицы на вектор.
	while (malloc(sizeof(double)*n*m)==NULL)
		i=0;
	y = (double *) malloc(sizeof(double)*n*m);//Создаём массив для хранения сумм.
	for (k=0;k0.01)||(q<10))//Цикл выполняется, пока разница между произведением на матрицу вектора слагаемых и его же, но отклонённого в случайную сторону, не станет колебаться у какого-то среднего значения.
	{
		s=0;
		for (k=0;k

Далее поговорим о том, как я проводил взвешенную линейную регрессию. Для этого я просто вычислил среднеквадратические отклонения результатов работы нейросети, а затем поделил на них единицу. Вот исходный код программы, с помощью которой я это сделал:

#include 
#include 
#include 
int main(void)
{
	int i;
	FILE *input,*output;
	while (fopen("input.txt","r")==NULL)
		i=0;
	input = fopen("input.txt","r");//У меня результаты для каждого основания были в отдельном файле.
	double mu,sigma,*x;
	mu=0;
	sigma=0;
	while (malloc(1000*sizeof(double))==NULL)
		i=0;
	x = (double *) malloc(sizeof(double)*1000);
	fscanf(input,"%lf",&mu);
	mu=0;
	for (i=0;i<1000;i++)
	{
		fscanf(input,"%lf",x+i);
	}
	for (i=0;i<1000;i++)
	{
		mu = mu+(*(x+i));
	}
	mu = mu/1000;
	while (fopen("WLS.txt","w") == NULL)
		i=0;
	output = fopen("WLS.txt","w");
	for (i=0;i<1000;i++)
	{
		sigma = sigma + (mu - (*(x+i)))*(mu - (*(x+i)));
	}
	sigma = sigma/1000;
	sigma = sqrt(sigma);
	sigma = 1/sigma;
	fprintf(output,"%10.9lfn",sigma);
	fclose(input);
	fclose(output);
	free(x);
	return 0;
};

Далее я добавил получившиеся веса в таблицу, куда свёл все данные, полученные в результате работы программы, а также значения переменных для вычисления регрессии, а затем вычислил её в JASP. Вот результаты:

Results

Linear Regression

Model Summary

Model R Adjusted R² RMSE
1 0.175 0.031 0.031 0.396
ANOVA

Model Sum of Squares df Mean Square F p
1 Regression 494.334 4 123.584 789.273 < .001
Residual 15657.122 99995 0.157
Total 16151.457 99999
Coefficients

Model Unstandardized Standard Error Standardized t p 0.5% 99.5%
1 (Intercept) -0.104 0.059 -1.751 0.080 -0.256 0.049
Расстояние между основанием и золотым сечением -0.113 0.010 -0.080 -11.731 < .001 -0.138 -0.088
Число измерений в векторе 0.008 2.328e -4 0.529 32.568 < .001 0.007 0.008
Средняя величина координаты вектора в ответе -0.951 0.181 -0.332 -5.255 < .001 -1.417 -0.485
Основание системы счисления 0.489 0.048 0.687 10.252 < .001 0.366 0.611

Далее у меня идёт гистограмма плотности распределения стандартизированных остатков регрессии:
image
А также нормальный квантиль-квантильный график стандартизированных остатков регрессии:
image
Затем я применил средние значения коэффициентов регрессии, получившиеся в её ходе, к переменным, и провёл свой статистический анализ по поиску наиболее вероятного минимума функции ошибки от основания системы счисления (насколько она связана с этими переменными), используя лемму Ферма, теорему Байеса и теорему Лагранжа следующим образом:
Дело в том, что распределение оснований системы счисления в выборке было заведомо равномерным, поэтому, если некое основание в промежутке (1,2;2) — минимум среднеквадратической ошибки, то так как по лемме Ферма она будет иметь нулевую производную, то плотность вероятности значений функции будет бесконечной. Тогда по теореме Байеса вычисляем бета-распределение функции распределения значений функции среднеквадратичных ошибок от основания, вычисляем её [функции распределения] доверительные интервалы в 99% в каждом значении функции среднеквадратических ошибок, а затем вычисляем доверительные интервалы уже в 98% (используем поправку Бонферрони) разницы между каждым значением функции распределения и значением в минимуме значения функции среднеквадратичных ошибок от основания системы счисления, затем делим крайние точки этого интервала на разницу между соответствующими аргументами функции распределения, и по теореме Лагранжа производная функции распределения в интервале между этими аргументами должна равняться хоть в одной точке получившемуся значению. Но эта производная и есть плотность вероятности, поэтому её максимум должен быть не меньше максимума из получившихся значений. Вот код программы, которой я этот анализ проводил, с пояснениями:

#include "main.h" //Включаем заголовочный файл.
int main(void)
{
	FILE *input,*output;
	int i,n,k,dFmax;
	double *x,*y,*F1,*F2,*F,*dF,*dF1,*dF2,t1,t2,xmin,xmax,ymin,ymax;
	t1=0;
	t2=0;
	while ((input=fopen("input.txt","r"))==NULL)
		i=0;
	while ((output=fopen("output.txt","w"))==NULL)
		i=0;
	n=0;
	while (fscanf(input,"%i",&n)==NULL)
		i=0;
	while ((x = (double *) malloc(sizeof(double)*n))==NULL) //Массив для оснований системы счисления.
		i=0;
	while ((y = (double *) malloc(sizeof(double)*n))==NULL) //Массив для ошибок, вычисленных по коэффициентам.
		i=0;
	while ((F = (double *) malloc(sizeof(double)*n))==NULL) //Массив для медиан бета-функции функции распределения ошибок.
		i=0;
	while ((F1 = (double *) malloc(sizeof(double)*n))==NULL) //Массив для левых концов доверительных интервалов бета-распределений.
		i=0;
	while ((F2 = (double *) malloc(sizeof(double)*n))==NULL)//Массив для правых концов доверительных интервалов бета-распределений.
		i=0;
	for (i=0;i(*(dF1+dFmax)))&&((*(dF2+i))>(*(dF2+dFmax))))
			dFmax=i;
	}
	xmin=0;
	xmax=0;
	ymin=0;
	ymax=0;
	xmin=(*x);
	xmax=(*x);
	ymin=(*y);
	ymax=(*y);
        //Вычисляем промежутки в, которых лежат минимальное значение функции распределения ошибок, и аргумент этой функции, от которого она имеет это значение:
	for (i=0;i<=dFmax;i++)
	{
		if ((*(x+i))>xmax)
			xmax=(*(x+i));
		if ((*(x+i))ymax)
			ymax=(*(y+i));
		if ((*(y+i))

А вот и код заголовочного файла:

#include 
#include 
#include 
int main(void);
double Bayesian(int n, int m, double x);//Вычисляем плотность вероятности бета-распределения  с n "успехами" и m "неудачами", в нашем случае это "значение случайной величины не больше" и "значение случайной величины больше" точки, в которой вычисляется функция распределения:
double Bayesian(int n, int m, double x)
{
	double c;
	c=(double) 1;
	int i;
	i=0;
	for (i=1;i<=m;i++)
	{
		c = c*((double) (n+i)/i);
	}
	for (i=0;i

Вот результат работы этой программы, когда я ей дал основания системы счисления и результаты регрессии:
x (- [1.501815; 1.663988] y (- [0.815782; 0.816937]
("(-" в данном случае просто запись знака «принадлежит» из теории множеств, а квадратные скобки обозначают интервал.)
Таким образом, у меня получилось, что наилучшее основание системы счисления в плане наименьшего количества ошибок при передаче информации лежит в интервале от 1.501815 до 1.663988, то есть золотое сечение в него попадает вполне. Правда я сделал одно допущение при вычислении минимума и ещё одно при вычислении количества информации в разных системах счисления: Во-первых, я допустил, что функция ошибок от основания непрерывно дифференциируема, во-вторых, что вероятность того, что равномерно распределённое число от 1,2 до 2 будет иметь цифрой единицу в какой-то конкретной цифре, будет примерно одинаковой после какой-то цифры после запятой.
Если что-то я сделал совсем не так, или просто неправльно, я открыт для критики и предложений. Надеюсь эта попытка была более удачной.

 

Источник

компьютерное моделирование, мозг, эволюция

Читайте также