Здравствуй, уважаемое сообщество GeekTimes! Не так давно здесь была опубликована серия статей посвященных работе над созданием модели нервной системы. И лучшим способом понять логику модели является возможность изучения программного кода её реализации. Я не только хочу донести более детально свои идеи, но и попросить помощи у сообщества. Мне известно, что среди читателей GT множество профессионалов в деле написания программного кода и Ваш опыт, знание может помочь развитию проекта. Иногда достаточно грамотного совета или рекомендации, чтобы решение такой не типичной задачи стало элегантным и лёгким.


Среда разработки — Unity3D, очень популярный игровой движок. Эта среда оказалась весьма удобной и доступной как в работе с редактором, так и наличием большого числа справок, пояснений и комментариев на русском языке. Поэтому моих скромных навыков программирования было достаточно, чтобы используя Unity3D реализовать свои идеи.

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

Хочу попросить прощения у тех, кто хочет обратиться к коду за его небрежность, возможное пренебрежение синтаксисом языка и возможные ошибки. Когда я начинал эту работу, в планах не было демонстрация кода. Я хотел проверить некоторые свои гипотезы.

Код отражает творческий поиск. В процессе работы я сталкивался с очередной проблемой, модель не желала действовать так, как я это себе представлял. И в течение дня ко мне могла придти идея того как это можно поправить. В эти моменты я мок быть вдохновлен неким озарением. Возвращаясь с работы, я с жадностью вносил поправки, в код игнорируя все нормы, на это не было времени. И как часто это бывает, эти озарения не приносили результата, или приносили не совсем желаемый результат. Так проходила работа над проектом, и спасибо за терпение моей супруги, что позволяла мне вести эту работу. Сложно найти время между семьей, работай, собственной прокрастинацией и ленью на рутинную работу стандартизации кода. Хотя это все равно придется когда-нибудь делать.

В своей работе я придержался несколько не типичной модели работы нейрона. Модель нейрона подобно биологическому нейрону могла действовать асинхронно от работы других нейронов. Сигналы от клавиш-рецепторов как, к примеру, от рецепторов кожи могут быть не синхронизированы. Поэтому в первых моделях под влиянием стереотипного мышления я выделял в работе нейрона некие фазы состояния длящиеся определённое количество шагов(циклов) всей системы, а шаги системы выполнялись синхронно во всех нейронах. Это не работало должным образом, да еще и было жутко неудобным. Но нужно было как-то синхронизировать входящие и исходящие сигналы, и правильно производить их оценку.

В определённый момент пришла идея, которую один пользователь интернета впоследствии окрестил «сливным бачком». Модель «сливного бачка» оказалась на удивления точной и применимой к биологическому нейрону, она объясняла очень наглядно механизмы суммации, и была удобней в реализации. Теперь эмулированные нейроны могли быть полностью независимы, всё как у реальных объектов.



Данная модель сумматора является самой точной моделью биологического нейрона из мне известных. Чтобы смоделировать детально клетку желудка потребуются невероятно большие вычислительные мощности, но всё что делает эта клетка, это вырабатывает соответствующий фермент или гормон при необходимости. Приписывание нейрону невероятных вычислительных свойств очень популярно среди кибернетиков современности.

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

Не буду утомлять. Ссылка на репозиторий GitHub.

Для примера, код скрипта в котором заключена главная логика работы нейроэлемента NeironScript.cs (извините за мой французский):

Много кода
using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class NeironScript : MonoBehaviour {

	public GameObject prefabNeiron;          //Префаб нейрона
	public GameObject prefabSinaps;          //Префаб синапса

	public int IndexNeiron = 0;              //Индекс нейрона

	private int _TypeIndexNeiron = 0;        //Индекс типа нейрона

	public int TypeIndexNeiron               //Индекс типа нейрона
	{
		get { return _TypeIndexNeiron; }
		set {
			if (value == 0) 
				{ _TypeIndexNeiron = value;
				  gameObject.GetComponent<SpriteRenderer>().color = new Color32(255, 255, 0, 255);//Жёлтый, сумматор
				}	
			if (value == 1) 
				{ _TypeIndexNeiron = value;
				gameObject.GetComponent<SpriteRenderer>().color = new Color32( 0, 255, 0, 255); //Зелёный, модулирующий //
				}	
			if (value == 2) 
				{ _TypeIndexNeiron = value;
				gameObject.GetComponent<SpriteRenderer>().color = new Color32(0, 255, 255, 255);//Голубой, асоциативный тип
				}
			if (value == 3) 
				{ _TypeIndexNeiron = value;
				gameObject.GetComponent<SpriteRenderer>().color = new Color32(255, 255, 255, 255);//Белый, тормозящие нейроны
				}
			}
	}
	// Настройки нейрона 0
	public float Adder = 0.0f; 										//Сумматор

	public float MaxAdder = 30f;									//Максимальное значение сумматора

	public float DampferAdder = 1.0f; 								//Регулятор сумматора
	public float thresholdTop = 1.0f; 								//Верхний баховый порог
	public float AnswerTime = 0.1f;      							//Время ответа
	public float TimeRepose = 0f;       							//Время отдыха 

	public bool IgnoreInput = false; 								//Игнорируется ли входы
	public List<GameObject> hitSinaps = new List<GameObject>();  	//Список синапсов

	public GameObject Area; 										//Область

	private bool _ActionN;											//Активен ли нейрон
	
	public bool ActionN												//Активен ли нейрон
	{
		get { return _ActionN; }
		set 
		{
			_ActionN = value;
			if (Area != null)
			{
				gameObject.GetComponent<LineRenderer>().enabled = value; //Показать вектор направления..
                bool existAction = Area.GetComponent<AreaScript>().NeironActionList.Contains(gameObject); //existAction = true - если нейрон в списке активных нейронов области
				if (_ActionN && (!existAction)) Area.GetComponent<AreaScript>().NeironActionList.Add(gameObject); //добавить в список активных области
				else Area.GetComponent<AreaScript>().NeironActionList.Remove(gameObject); //удалить из списка активных области
			}
		}
	}

	// Настройки нейрона 1 

	public float thresholdDown = -5.0f;			 		//Нижний порог 
	public float timeIgnore = 5.0f;   					//Время усиленной реполяризации при значении сумматора ниже нижнего порога
	public float bonusThreshold = 0f; 					//Надбавка на верхний порог
	public float DempferBonusThreshold = 1.0f; 			//Регулятор фактического значения порога
	public float TimeEvaluation = 5.0f;					//Время оценки
	public int LimitRecurrence = 5; 					//Лимит повторений
	public float thresholdTopUp = 1.0f;					//На сколько повысить порог

	public bool TimeEvaluationBool = false;				//Время оценки
	public int LimitEvaluationInt = 0;					//Счётчик  повторений в период оценки

	public float AdaptationTime = 0;                    //Время адаптации
	public float thresholdAdapt = 1f;                   //Минимум при адаптации

	// Настройка нейрона 2

	public float MaxForceSinaps = 100f;
	private Vector3 VectorPattern; 						//Вектор патерна
	public Vector3 VectorTrend; 						//Вектор пути

	public float Charge = 0.0f; 						//Заряд
	public float TimeCharge = 0.01f; 					//Время такта смены заряда

	private float changeAngle = 0f; 					//Изменение угла вектора

	public float FocusNeiron = 90f;						//Фокус нейрона
	public bool FocusDinamic = true;                    //Изменять фокус динамически
	public float StepFocus = 1f;						//Шаг изменения фокуса
	public float MaxFocus = 90f;						//Максимальное значение фокуса

	public float Plasticity = 1.0f; 					//Нейропластичность
	public bool PlasticityDinamic = true; 				//Изменяется ли пластичность 
	public float StepPlasticity = 0.01f;				//Шаг пластичности
	public float BasicPlasticity = 1.0f;				//Базовая пластичность (пластичность новых нейронов)
	public bool NewNeironDinamic = true;				//Создовать ли нейрон динамически

	

	private float angleMin = 0f;

	private bool CorunPlasticRun = false;

	// END VAR

    private Vector3 noveltyVector = Vector3.zero;
    private float noveltyFactor = 0.1f;

	IEnumerator StartSummator (){
		IgnoreInput = true;  //Включаем игнорирование внешних сигналов
		gameObject.GetComponent<SpriteRenderer>().color = new Color32(255, 0, 0, 255); //Подсветка красным
		ActionN = true; //Активное состояние для идикаторов выхода
		yield return new WaitForSeconds(AnswerTime); //Время ответа
		ActionN = false; 
		ExcitationTransfer (); //передача возбуждения
		yield return new WaitForSeconds(TimeRepose);//время отдыха
		IgnoreInput = false; // отключаем игнорирование внешних сигналов
		TypeIndexNeiron = _TypeIndexNeiron; //Возращаем цвет нейроэлементы 
	}

	IEnumerator repolarizationTime (){
		IgnoreInput = true; //включаем игнорирование внешних сигналов
		gameObject.GetComponent<SpriteRenderer>().color = new Color32(0, 0, 255, 255);//устанавливаем синий цвет
		yield return new WaitForSeconds(timeIgnore);//время игнора
		IgnoreInput = false;
		TypeIndexNeiron = _TypeIndexNeiron;//включаем свой цвет
	}

	IEnumerator StartModule (){
        IgnoreInput = true; //включаем игнорирование внешних сигналов
		ActionN = true; //Состояние активности, это значение считывают индикаторы выходов 
		gameObject.GetComponent<SpriteRenderer>().color = new Color32(255, 0, 0, 255);//крачный цвет
		yield return new WaitForSeconds(AnswerTime);//время ответа
		ExcitationTransfer ();//передача по всем исходящим синапсам
		ActionN = false;//выключаем активность 
		yield return new WaitForSeconds(TimeRepose);//время отдыха
		IgnoreInput = false;//перестаем игнорировать внешние сигналы
		TypeIndexNeiron = _TypeIndexNeiron;//возращаем цвет
		StartCoroutine ("EvaluationTime");//запуск времени оценки
		if ((AdaptationTime > 0) && (thresholdTop > thresholdAdapt)) StartCoroutine ("AdaptationVoid");//запуск адаптации, при значении настроект адаптации =0 адаптация не работает
        //и нет смысла запускать корунтину если порог на нижнем пределе
	}

	IEnumerator EvaluationTime(){ 
		TimeEvaluationBool = true;//Сейчас пойдет время оценки
		yield return new WaitForSeconds(TimeEvaluation);
		TimeEvaluationBool = false;//Время оценки кончилось
	}

	IEnumerator AdaptationVoid(){
		yield return new WaitForSeconds(AdaptationTime);//временной итервал 
        if (thresholdTop > thresholdAdapt) thresholdTop--;//снижаем порог, но не ниже базового
		if ((AdaptationTime > 0) && (thresholdTop > thresholdAdapt)) StartCoroutine ("AdaptationVoid");//снова запускаем адаптацию
	}

	IEnumerator NegativeRepolarization(){
		IgnoreInput = true; //влючаем игнорирование внешних сигналов
		ActionN = true; //Активность
        for (int i = 0; i < 16; i++)
        {  //перебираем занчения заряда
			Charge = Area.GetComponent<AreaScript>().Spike2[i];
			if (Charge > 0) gameObject.GetComponent<SpriteRenderer>().color = new Color32(255, 0, 0, 255); //красный
			else gameObject.GetComponent<SpriteRenderer>().color = new Color32(0, 0, 255, 255); //синий
			yield return new WaitForSeconds(TimeCharge); //с заданной частотой/скоростью
		}
		Charge = 0f;//заряд обнуляем
		TypeIndexNeiron = _TypeIndexNeiron;//свой цыет
		ActionN = false;//активность
		IgnoreInput = false;//больше не игнорируем внешние сигналы
	}

	IEnumerator StartAssociative(){
		IgnoreInput = true;//Игнорирование внешних сигналов
		ActionN = true;//Активность
        StartCoroutine("PositiveRepolarization"); //параллельно начинаем изменять заряд
		yield return new WaitForSeconds(AnswerTime); 		//Время ответа 
		Compass ();//отдельный блок кода
	}

	IEnumerator StartWhite() {
        IgnoreInput = true;//Игнорирование внешних сигналов
        ActionN = true;//Активность
        StartCoroutine("PositiveRepolarization");//параллельно начинаем изменять заряд
		yield return new WaitForSeconds(AnswerTime); 		//Время ответа
		ExcitationTransfer ();//ответ по всем исходящим синапсам
	}

	IEnumerator PositiveRepolarization(){
		for (int i = 0; i < 16; i++) {
            //перебор значений заряда
			Charge = Area.GetComponent<AreaScript>().Spike1[i];
			if (Charge > 0) gameObject.GetComponent<SpriteRenderer>().color = new Color32(255, 0, 0, 255); //красный
			else gameObject.GetComponent<SpriteRenderer>().color = new Color32(0, 0, 255, 255); //синий
			yield return new WaitForSeconds(TimeCharge); //с установленной частотой
		}
		Charge = 0f; //обнуляем заряд
		TypeIndexNeiron = _TypeIndexNeiron;//возращаем цвет
		ActionN = false;//активность отключаем
		yield return new WaitForSeconds(TimeRepose);//время отдыха
		IgnoreInput = false;//отключаем игнорирование
		StartCoroutine ("EvaluationTime");//Время оценки
		if ((AdaptationTime > 0) && (thresholdTop > thresholdAdapt)) StartCoroutine ("AdaptationVoid");//Адаптация
	}

	IEnumerator PlasticTimeCoruntine (Vector2 PT){//Временное изменение пластичности
		CorunPlasticRun = true;//Изменение временной пластичности началось
		float PlasticBuffer = Plasticity;//Сохраням текущюю пластичность
		Plasticity = PT.x;//Устанавливаем временную
		yield return new WaitForSeconds(PT.y);//Ждем необходимое время
		Plasticity = PlasticBuffer;//Возращаем прежнюю пластичность
		CorunPlasticRun = false;//Время изменения пластичности кончилось
	}

	public void ActiveNeiron (){ //В засисимости от типа нейрона запускаем соотвествующюю программу активации
		if (!IgnoreInput)
		{
			if (TypeIndexNeiron == 0) StartCoroutine ("StartSummator");//Синапс прямого действия
			if (TypeIndexNeiron == 1) StartCoroutine ("StartModule");//Модулирующий синапс
			if (TypeIndexNeiron == 2) StartCoroutine ("StartAssociative");//Изменяемый синапса, прямого действия ассоциативного нейроэлемента
			if (TypeIndexNeiron == 3) StartCoroutine ("StartWhite");//Синапс прямого действия
		}
	}

	private void Compass (){
		if (Area != null){ //Если нейрон не принадлежит области, то невозможно получить информацию о других обьектах системы
			VectorPattern = Vector3.zero; //Обнуляем точку паттерна
            //Подсчёт точки паттерна
			for (int i = 0; i < Area.GetComponent<AreaScript>().NeironActionList.Count; i++) { //Список всех активных нейронов
				if (gameObject == Area.GetComponent<AreaScript> ().NeironActionList [i]) continue; //Исключаем данный нейрон из расчётов
				Vector3 R = Area.GetComponent<AreaScript> ().NeironActionList [i].transform.position - transform.position;//получаем относительные кординаты, относительно данного нейрона
                //Формула определения заряд нейрона на единичный вектор 
				VectorPattern += (Area.GetComponent<AreaScript> ().NeironActionList [i].GetComponent<NeironScript> ().Charge * R.normalized);//R.sqrMagnitude; .normalized   //sqrMagnitude;!!!!!!!!!(Без квадрата лучше)
			}

			if (VectorPattern.sqrMagnitude < 3f) VectorPattern = VectorTrend; //незначительное влияние не учитываем, принимаем предыдущее значение вектора
			if (VectorPattern.sqrMagnitude == 0) VectorPattern = new Vector3(Random.Range(-1f, 1f), Random.Range(-1f, 1f), Random.Range(-1f, 1f)); 
            //Нет предыдущих значений (новый нейрон), нет других активных нейронов, то берём случайное значение, возбуждение должно передаться куда-нибудь 

			VectorPattern.Normalize(); //Получаем единичный вектор направления

            if (noveltyVector == Vector3.zero) noveltyVector = -VectorPattern; //новый нейрон (новизна ранее не определялась) устанавливаем максимальное значения - противоположное установленному вектору направления
			changeAngle = Vector3.Angle(VectorPattern, noveltyVector);// определяем угол между направлением и предыдущем значением вектора новизны

			if (Area != null) Area.SendMessage("MessageOriginality", changeAngle/180);//сообщаем области об уровне новизны для нейрона

			VectorTrend = VectorPattern; //промежуточное начение
            noveltyVector = Vector3.Slerp(noveltyVector, VectorPattern, noveltyFactor);//изменям вектор новизны
            //вектор новизны постепенно приближается к векторы направления
            //это имитация постепенного угасания получения удовольстия от нового опыта
            //если сразу присвоить noveltyVector = VectorPattern, то новизна будет оцениваться более реактивно (в ранних версиях так и было)
            //новое действие вызывает интерес даже при нескольких повторениях, а не угасает сразу после однократного повторения
			gameObject.GetComponent<LineRenderer>().SetPosition(0, transform.position);//визуализация вектора предпочитаемого направления
			gameObject.GetComponent<LineRenderer>().SetPosition(1, transform.position + VectorTrend * 6);

           
			if (PlasticityDinamic) {
				if (changeAngle < 10) Plasticity -= StepPlasticity; else Plasticity += StepPlasticity; //изменение пластичности
				if (Plasticity > 1) Plasticity = 1f;
				if (Plasticity < 0) Plasticity = 0f;
                //Идея с таким динамическим изменением пластичности сейчас считаю не перспективной
                //Пластичность характеризуется областью в мозге, а также имет большую роль играет в эмоциональных механизмах. 
                //Т.е. большее внимание стоит уделить внешним условиям изменения пластичности, чем изменять пластичность под влиянием внутренних состояний
			}

			if (FocusDinamic){
				if (changeAngle < 10) FocusNeiron -= StepFocus; else FocusNeiron = MaxFocus;
				if (FocusNeiron < 0) FocusNeiron = 0;
                //На данный моммент не получилось продемонстрировать эффективность динамически изменяемого фокуса.
                //Но перспектива для него есть в более маштабных моделях.
                //Динамическое изменение фопуса поможет имулировать иррадиацию и концентрацию.
                //Иррадиацию и концентрацию - наблюдаемые явления в нервной системе, модель нервной системы должна их имитировать
			}

            //динамическое создание нейронов
			if (NewNeironDinamic){
                if (!Physics.CheckSphere(transform.position + VectorTrend * 5, 3f))
                {   //Есть ли что-то в сферической области радиусом 3,
                    //центор которой расположен на растоянии 5 от данного нейрона
                    //в направлении вектора направления
					if (Area.GetComponent<AreaScript>().Global) NewNeiron(); //Глобальная область не имеет границ
					else 
					{
						if (Area.GetComponent<Collider>().bounds.Contains(transform.position + VectorTrend * 5)) NewNeiron(); //проверка выходит ли будующий нейрон за границы своей области  
					}
				}

				//Динамическое создание синапсов 
                Collider[] hitColliders = Physics.OverlapSphere(transform.position + VectorTrend * 5, 3f); //Список всех обьектов в сферической области по направлению   
				foreach (Collider value in hitColliders) //перебор всех из этого списка
				{
					if (value.tag == "Neiron") //нужны только нейроны
					{
						bool EnableSinaps = false; //синапса с этим обьектом нет
						foreach (GameObject sinapsValue in hitSinaps) //смотрим все свои синапсы 
						{
							if (sinapsValue.GetComponent<SinapsScript>().NeironTarget == value.gameObject) {
								EnableSinaps = true; //синапс такой есть 
								break; //дальше не перебираем
							} 	
						}
						
						if (!EnableSinaps) { //Если такого синапса нет
							GameObject cSinaps = Instantiate(prefabSinaps, transform.position, transform.rotation) as GameObject;//мы его создаём 
							cSinaps.transform.parent = transform;
							cSinaps.GetComponent<SinapsScript>().NeironTarget = value.gameObject;
							cSinaps.GetComponent<SinapsScript>().Force = 0f;
							hitSinaps.Add(cSinaps);

						}

					}
				}
			}

			//Нахождение минимального угла между векторами синапсов и вектором направления
			angleMin = 180f;
			if (hitSinaps.Count != 0) angleMin = Vector3.Angle(hitSinaps[0].GetComponent<SinapsScript>().NeironTarget.transform.position - transform.position, VectorTrend);
			foreach(GameObject ShershSinaps in hitSinaps)
			{
				float angleShersh = Vector3.Angle(ShershSinaps.GetComponent<SinapsScript>().NeironTarget.transform.position - transform.position, VectorTrend);
				if (angleShersh < angleMin) angleMin = angleShersh;
			}
           
			if (FocusNeiron < angleMin) FocusNeiron = angleMin;
            //Фокус не должен уменьшаться ниже того значения, которое приведёт к угасанию всех его синапсов.
            //В конус фокуса должен входить хотябы одни вектор синапса, 
            //иначе после укрепления рефлекса далее произойдет его необоснованное угнетение.

			//Подсчет весов 
			foreach(GameObject SinapsCoeff in hitSinaps){
					if (SinapsCoeff.GetComponent<SinapsScript>().TypeSinaps == 0){
					    float angleSinaps = Vector3.Angle(SinapsCoeff.GetComponent<SinapsScript>().NeironTarget.transform.position - transform.position, VectorTrend);
					    if (angleSinaps <= FocusNeiron) SinapsCoeff.GetComponent<SinapsScript>().Force += MaxForceSinaps * Plasticity;
					    else SinapsCoeff.GetComponent<SinapsScript>().Force -= MaxForceSinaps * Plasticity;
					    SinapsCoeff.GetComponent<SinapsScript>().Force = Mathf.Clamp(SinapsCoeff.GetComponent<SinapsScript>().Force, 0, MaxForceSinaps);
				    }
			}
		}

		ExcitationTransfer ();//передача по всем исходящим синапсам
	}

	private void NewNeiron (){
		GameObject clone = Instantiate(prefabNeiron, transform.position + VectorTrend * 6, transform.rotation) as GameObject;
        /* Интересная фича: если производить самокопирование из обьекта на сцене (как это происходит здесь),
         * то происходит копирование значений переменных родителя, а не беруться исходные значения настроенного префаба 
         * поэтому требуется это учитывать и устанавливать начальные настройки, значения переменных в новом экземпляре.
         * Не знание этого нюанса создало множество проблем....
         * */
		if (Area != null) Area.GetComponent<AreaScript>().amount++;//подсчёт нейронов в области принадлежащей нейрону

		clone.GetComponent<NeironScript>().Plasticity = BasicPlasticity;//стартовые настройки нейроэлемента
		clone.GetComponent<NeironScript>().ActionN = false;
		clone.GetComponent<NeironScript>().IgnoreInput = false;
		clone.GetComponent<NeironScript>().Adder = 0f;
		clone.GetComponent<NeironScript>().VectorTrend = Vector3.zero;
		clone.GetComponent<NeironScript>().Area = Area;
		clone.GetComponent<NeironScript>().TimeEvaluationBool = false;
		clone.GetComponent<NeironScript>().LimitEvaluationInt = 0;
		clone.GetComponent<NeironScript>().Charge = 0.0f; 
		clone.GetComponent<NeironScript>().FocusNeiron = MaxFocus;
		clone.GetComponent<NeironScript>().Plasticity =  BasicPlasticity;
		clone.GetComponent<NeironScript>().TypeIndexNeiron = 2;
        clone.GetComponent<NeironScript>().noveltyVector = Vector3.zero;
        clone.GetComponent<NeironScript>().VectorTrend = Vector3.zero;

		clone.GetComponent<LineRenderer>().SetPosition(0, clone.transform.position);
		clone.GetComponent<LineRenderer>().SetPosition(1, clone.transform.position);

		clone.SendMessage("StopNeiron"); //в новом обьекте даже корунтины находятся в той же фазе работы, что и родителе

		GameObject ManagerObj = GameObject.Find("Manager"); //...нужно исправить, Find перегружает  
		ManagerObj.GetComponent<ManagerScript>().EndIndexNeiron++;//подсчитываем нейроны
		clone.GetComponent<NeironScript>().IndexNeiron = ManagerObj.GetComponent<ManagerScript>().EndIndexNeiron;//определяем номер для нового нейрона
		clone.name = "Neiron" + clone.GetComponent<NeironScript>().IndexNeiron;//отмечаем идекс нейрона в имени обьекта

        foreach (GameObject sd in clone.GetComponent<NeironScript>().hitSinaps) Destroy(sd); //префаб размещенный в сцене из подобного себе обьекта, копирует и дочерниие обьеты родителя
		clone.GetComponent<NeironScript>().hitSinaps.Clear(); //приходится очищать новый обьект. Это странно..
	}

	void FixedUpdate(){ //Главная функция для нейрона срабатывает каждые 0.01с


		if (!IgnoreInput) //Если внешние сигналы не игнорируется
		{
			if (TypeIndexNeiron == 0)  // Это простой сумматор 
			{
				if (Adder > thresholdTop) //Пороговая функция
				{
					StartCoroutine ("StartSummator"); 
				}
			}

			if (TypeIndexNeiron == 1) //Это модулируемый нейроэлемент
			{
				if (Adder > thresholdTop + bonusThreshold) //Пороговая функция
				{
					
					if (TimeEvaluationBool) //Сейчас время оценки?
					{                       
						LimitEvaluationInt++; //Счётчик повторов
						StopCoroutine("EvaluationTime"); //Остановка корунтины отстивающей время оценки
						TimeEvaluationBool = false; //выключение фазы оценки
					}
					else LimitEvaluationInt = 0; //иначе сбрасываем счётчик повторов

					if ((LimitEvaluationInt > LimitRecurrence) && (bonusThreshold == 0)) thresholdTop += thresholdTopUp; //лимит повторов превышен и небыло модуляции значит повышаем порого - превыкание

					StopCoroutine ("AdaptationVoid");  //остановка адаптации, она выполняется только при протое нейрона
					StartCoroutine ("StartModule"); // Запуск модулируемого нейроэлемента 
					
				}

				if (Adder < thresholdDown) //пороговая функция на нижний порог
				{
					if (Area != null) StartCoroutine ("repolarizationTime"); //при достаточном тормозящем воздействии, биологический нейрон усиленно поляризуется
				}
			}

			if (TypeIndexNeiron == 2) //модулируемый нейроэлемент
			{
				if (Adder > thresholdTop + bonusThreshold) //порог сладывается из двух частей: обычной и модулируемой
				{
					if (TimeEvaluationBool) //если активация произошла во время оценки
					{
						LimitEvaluationInt++; //считаем лимит повторов
						StopCoroutine("EvaluationTime");//останавливаем подсчёт времени
						TimeEvaluationBool = false;
					}
					else LimitEvaluationInt = 0; //иначе время оценки прошло, сбрасываем счётчик

					if ((LimitEvaluationInt > LimitRecurrence) && (bonusThreshold == 0)) thresholdTop += thresholdTopUp; //лемит превышен и дело не в модуляции, то повышам порог 

					StopCoroutine ("AdaptationVoid");//активация призошла, приостанавливаем адаптацию (адаптация - снижеине порога при простое)
					StartCoroutine ("StartAssociative"); //Старт активации модулируемого нейроэлемента 
				}

				if (Adder < thresholdDown) //сумматор ниже нижнего порога
				{
					StartCoroutine ("NegativeRepolarization");  //усиленная реполяризация (торможение)
				}
			}

			if (TypeIndexNeiron == 3) //Ассоциативный нейроэлемент 
			{
				if (Adder > thresholdTop + bonusThreshold)//Порог из двух состовляющих
				{
					if (TimeEvaluationBool)//Время оценки...
					{
						LimitEvaluationInt++;
						StopCoroutine("EvaluationTime");
						TimeEvaluationBool = false;
					}
					else LimitEvaluationInt = 0;

					if ((LimitEvaluationInt > LimitRecurrence) && (bonusThreshold == 0)) thresholdTop += thresholdTopUp;

					StopCoroutine ("AdaptationVoid");
					StartCoroutine ("StartWhite");  
				}

				if (Adder < thresholdDown)
				{
					StartCoroutine ("NegativeRepolarization");  
				}
			}

		}

        if (Mathf.Abs(Adder) <= DampferAdder) Adder = 0f; //Демпфер сумматора
		if (Adder > DampferAdder) Adder -= DampferAdder;
		if (Adder < -DampferAdder) Adder += DampferAdder;

        if (Mathf.Abs(bonusThreshold) <= DempferBonusThreshold) bonusThreshold = 0f; //Демпфер дополнительного порога
		if (bonusThreshold > DempferBonusThreshold) bonusThreshold -= DempferBonusThreshold;
		if (bonusThreshold < -DempferBonusThreshold) bonusThreshold += DempferBonusThreshold;
	} 

	private void ExcitationTransfer () //Передача возбуждения
	{
		foreach (GameObject value in hitSinaps) // по всем синапсам
		{
			int T = value.GetComponent<SinapsScript>().TypeSinaps; //тип синапса
			float F = value.GetComponent<SinapsScript>().Force; //его сила
			GameObject NT = value.GetComponent<SinapsScript>().NeironTarget;//целевой нейрон
			if (T == 0) NT.SendMessage("AddSummator", F);//Передача сообщения
			if (T == 1) NT.SendMessage("AddTActual", F);
			if (T == 2) NT.SendMessage("ActiveNeiron");
            if (T == 3) NT.SendMessage("AddSummator", F);
			value.GetComponent<SinapsScript>().GoAction = true;//Запуск анимации синапса
		}
	}

	public void AddSummator (float Summ) //Для управления сумматором из других нейронов 
	{
		Adder += Summ;
		if (Adder > MaxAdder) Adder = MaxAdder;
        if (Adder < - MaxAdder) Adder = -MaxAdder;
	}

	public void AddTActual (float T)// управление порогом, модулирующее воздействие
	{
		bonusThreshold += T;
		if (bonusThreshold + thresholdTop < 0f) bonusThreshold = - thresholdTop + 0.0001f;
	}

	public void StopNeiron(){//остановка всех корунтин, возможно из других обьектов GameOject.SendMessage("StopNeiron") 
		StopAllCoroutines();
	}

	public void plasticSetTime (Vector2 plasticTime){
        //для управления пластичностью, SendMessage может передать только один параметр, Vectir2 - это два реальных числа
        //уровень пластичности и время изменения 
		if (!CorunPlasticRun) StartCoroutine("PlasticTimeCoruntine", plasticTime);
        if (TypeIndexNeiron == 2) thresholdTop = thresholdAdapt;
	}
}




Общение между нейронами осуществляется с помощью системы сообщений, в основе которых SendMessage, а все процессы, связанные с изменением состояний вынесены в корунтины.



На блок-схеме базовая основа нейроэлемента. SendMessage(“AddSummator”, F) – синапс прямого действия с силой F, увеличивает сумму сумматора на заданное число. Каждые 0,01с срабатывает функция FixedUpdate() в которой происходит уменьшение по модулю сумматора на установленный демпфер/число. А так же происходит проверка превышения порога на сумматоре, если порог превышен, то запускается корунтина. В период работы корунтины включается режим игнорирования внешних сигналов, но работа демпфера для сумматора продолжается как и возможность пополнить сумму. SendMessage(“ActiveNeiron”) – контактный синапс (эфапс), корунтина будет запущена если она в данный момент не выполняется иначе сигнал проигнорируется.

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

Многим был интересен исходный код проекта, но не только по данной причине я выкладываю исходник. Дело в том, что впереди еще масса работы, в планах серьёзно расширить возможности и инструментарий, создать некую среду позволяющей удобно работать с большим массивом элементов, структурировать и организовывать их. Я не обладаю большим опытом программирования, но я уверен, что большое количество людей из сообщества GeekTimes могут дать рекомендации о структуре, методах, оптимизации которые качественно улучшат проект. Я планирую не менять среду разработки, для меня важна эмпирика процесса разработки, а так же эстетика конечного результата и игровой движок Unity пока мне в этом очень хорошо помогал.
Поделиться с друзьями
-->

Комментарии (23)


  1. fivehouse
    01.11.2016 18:18
    +2

    Короткое резюме: без шансов. То есть: 1) существуют разные нейромедиаторы, по разному действующие на нейроны 2) поведение разных нейромедиаторов (и взаимодействие нейромедиаторов между собой и телом нейрона) на механической конкретной конструкции дентритов, шипиков и глии конкретного нейрона 3) изменение механической конкретной конструкции дентритов, шипиков и глии конкретного нейрона под воздействием сигналов и в разные промежутки времени 4) генетически распознаваемые сигналы и генетически обусловленная пластичность нерона и глии 5) взаимодействие малых ансамблей нейронов в соответствии с генетически обусловенной ролью нейронов в малом ансамбле 6) многочисленные параллельные места коннекта одих нейронов с другими со всеми эффектами Думаю, что для начала хватит…


    1. aigame
      01.11.2016 18:44

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


      1. xuexi
        01.11.2016 19:19
        +1

        Принцип У-Вэй, философское недеяние, очень хорошо прижился в наших северных широтах. Здесь вам не Долина.


        1. napa3um
          01.11.2016 19:30

          Принцип У-вэй — это не бездеятельность как таковая, а деятельность «в потоке», без рефлексии. Трактовка У-вэй в качестве оправдания бездеятельности и лени — типичное заблуждение.


          1. xuexi
            01.11.2016 19:45

            Я к тому, что у «нас» принято порицать сознательную деятельность, в том числе на самом старте.
            Аргументы могут быть разные, важен лишь одобряемый предполагаемый результат: философски обоснованное отсутствие действий. А тут уже трудно отличить равномерное движение или его отсутствие. В результате, в теории нейросетей, например, на сегодняшний день, пренебрежительно мало переведенных на русский язык англоязычных терминов. «Всё» там, а тут «у-вэй» в вульгарном понимании. Ю-вэйт, если угодно.


            1. napa3um
              02.11.2016 10:43

              И «у них», и «у нас» существуют люди, оправдывающие свою бездеятельность разными формами описаний своих рисков потери энергии и времени впустую или из желания сохранить статус-кво своих представлений о мире (это всё входит в список известных когнитивных искажений, никак не привязанных к национальности или вероисповеданию). А так же в психологии существует формулировка эффекта реактивного сопротивления, заключающегося в тенденции отстаивать свою точку зрения даже в отсутствии объективной пользы этой точки зрения. Люди с трудом учатся говорить «я могу быть не прав», если угодно.


      1. ooptimum
        02.11.2016 08:14
        +1

        С одной стороны, идея эмулировать работу мозга лежит на поверхности. А с другой — в коре головного мозга человека примерно 100.000.000.000 (сто миллиардов) нейронов. Сколько Вам необходимо для интеллектуального поведения Вашей программы, по Вашим прикидкам?

        Некое подобие интеллектуального поведения демонстрирует и персептрон, теоретические основы которого были разработаны уже более 50 лет назад. Поэтому руки чешутся посоветовать изучить классическую теорию нейронных сетей, но тогда Вы наверное перестанете заниматься тем, над чем Вы работаете сейчас. Поэтому не посоветую. Мне уже тоже любопытно, что у Вас получится из всего этого. А вдруг выстрелит? Да и вообще этот Ваш творческий процесс не только увлекателен, но и полезен как источник удовольствия и новых знаний. Так что я искренне желаю Вам удачи!


        1. aigame
          04.11.2016 11:10

          Спасибо. С классической теорией нейронных сетей я знаком и довольно-таки давно, благо на эту тему материалов очень много. Классические нейронные сети — это очень хорошая вещь, имеющая ряд преимуществ и у меня есть идеи неких «симбиозов», но об этом еще рано говорить.
          Сложно оценить размеры сети, время покажет. Во-первых, пока не ставиться цель получить некую личность, достаточно отдельных когнитивных функций, распознавание зрительных и слуховых образов, или просто распознавание команд на уровне домашних животных или детей. Во-вторых, нейроэлемент не совсем нейрон. Биологические клетки не выделяются точностью, увидеть направленную передачу в отдельной нервной клетке практически невозможно, это свойство можно выделить в некой группе. Можно сказать, что нейроны формируют нервную ткань мозга, подобно тому, как клетки печени формируют ткани печени определённым рисунком, паттерном. Нейроэлемент будет более продуктивен в плане вычислений. В-третьих, из 86 миллиардов (официальная цифра)) чуть больше половины это клетки-зёрна мозжечка. Эти клетки определяют, какой из каскадов тайминговой настройки будет запущен, механизм работы мозжечка можно упростить, сохранив его функционал.
          На мой взгляд, разум подобный человеческому возможно осилит хороший, производительный домашний ПК. Конечно, если модель будет оптимизирована. К примеру, приходилось видеть, как в компьютерной графике оперируют миллионами полигонов и вершин, причём для каждой вычисляется сложная формула работы материала и шейдера. В свой работе я пока не занимался оптимизацией, это один из сложных вопросов программирования, поэтому даже при относительно небольшом количестве элементов программа падает, ну какие тут могут быть искусственные мозги. Необходимо разделить графическую и расчетную часть, использовать потоки, а не корутины и т.д.
          Конечно, если строить систему, учитывая архитектуру каждого нейрона, топологию всех связей то потребуется бесконечное множество человеко-часов, мейнфрейм от IBM и немалая сумма денег. Главное это понимание логики и принципов работы чего-либо, зная их можно сделать лучше и проще.


    1. Volutar
      01.11.2016 22:12

      Есть мнение, что такой зоопарк нейромедиаторов был «придуман» природой как удобный механизм автоконфигурирования (autowiring) сети. Сугубо для того чтобы не перепутать что с чем соединять — и в результате по-другому нейроны в конкретной точке просто не соединятся. Ну и как механизм автобалансировки активности (как набор взаимновлияющих компонентов, с учётом существенной доли ингибиторной сети). А на уровне функциональном специализация нейрона на отдельных нейромедиаторах не меняет принципов и механизмов активности нейрона.

      Важнее именно те генетические механизмы, которые обеспечивают нейрону весь его активный репертуар, как перестройка создаваемая Na+/Ca+ спайками, как перестройки под воздействием NMDA спайков, как механизмы шипиковой динамики и перестройка чувствительности через метаботропные комплексы.


    1. napa3um
      02.11.2016 11:24

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


  1. Fedarod
    01.11.2016 18:49

    Зачем вам нейрон для сумматора? Смысл нейросети как раз в том, что вес конкретного нейрона может видоизменяться в зависимости от решаемой задачи. Т.е. нейрон может быть как фоном, так и детектором. Это не сумматор ни в каком виде. Его входы — это инструкции, как себя вести, а вовсе не ведро с водой.


    1. aigame
      01.11.2016 19:14

      В данной модели изменяются входы (изменение порога сумматора), более того изменяются выходы. Основанием для работы являются данные исследований биологического нейрона, прошу прощения, но «входов-инструкций» по всем признакам нет. В рисунке в статье довольно подробно изложено всё, что известно о том как нейрон обрабатывает (суммирует) входящие сигналы. Если честно, то традиционные нейросети я считаю выдающейся технологией, но её популяризация создало искажённое представление о нервной клетке.


      1. Fedarod
        01.11.2016 19:51
        -1

        Тут Редозубов уже показывал, как это может работать: распространяемыми волнами. Т.е. каждый нейрон может натренироваться понимать, в каком рабочем окружении он находится и что ему следует делать при конкретных входящих данных. Нейрон — это такой процессор, который сам себе инструкции формирует.

        Ну вот совершенно грубый пример: я хотел пописать, когда еще с работы уходил, но по пути домой я зашел в магазин и долго выбирал пиво, как раз завезли кучу новых, неиспробованных мной сортов. Когда я пришел домой, то еле разуться успел, так стало невмоготу. Разве мочевой пузырь не накапливал сумматор в ответственном нейроне? Накапливал. Там было переполнение буфера три раза. Только нейрон не реагировал, пока окружение не стало соответствовать.

        И это не возбуждение-торможение. Нейрон не был заторможен. Он был вне контекста. Ну, знаете, как когда там у алкоголиков-наркоманов бытие определяет сознание: потирая руки «Не испить ли нам винца? Гопца-дрипца, гоп-ца-ца!»(С) Именно в этом нахождении контекстов и состоит логическая работа мозга. Не суммирование, а именно нахождение решения, опрашивая нейросеть на предмет соответствия контексту: — ты в контексте? — проходи, нет — свободен. Кто крайний в контекст? И этот крайний отзывается. Это и нужно реализовать.

        И это уже доказано биологами, что «левые» нейроны игнорируются в текущем контексте. Как это происходит — по модели волн Редозубова или модели точки сборки Кастанеды, неизвестно, только именно над этим все и бьются сейчас.


        1. xuexi
          01.11.2016 19:54

          Редозубов изобрел доминанту Ухтомского?


          1. Fedarod
            01.11.2016 20:53

            Напомню принцип доминанты Ухтомского:
            Во все моменты жизнедеятельности создаются условия, при которых выполнение какой-либо функции становится более важным, чем выполнение других функций. Выполнение данной функции подавляет другие функции.

            Это в корне не верно. Я хочу пИсать, я что, обоссался? Если мне напряжение в мозг подать, как делал Ухтомский своим собакам, я, пожалуй, действительно обоссусь. Но мозг работает иначе. Мозг работает с контекстом. И если контекст говорит Пушкину стреляться с Дантесом, то Пушкин стреляется. Если мозг говорит собаке Ухтомского не какать, собака не какает без проводов в мозгу. Контекст находится снаружи, а не внутри. И контекст анализируется нейросетью, после чего уже мозг дает добро или игнорирует «доминанту», которая доминанта именно что в кавычках.


            1. xuexi
              01.11.2016 21:18

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


              1. Fedarod
                01.11.2016 22:29

                >Кажется, второй абзац вашего комментария подтверждает первый, а не опровергает его, а если точнее, понятие доминанты и выводы из этого понятия покрывают собой ситуацию из примера.

                Это только кажется. На самом деле существование такой доминанты — следствие контекста, а не его причина. Если всю жизнь жить в комнате депривации, то, возможно, будешь как овощ ходить под себя. Но новые контексты, как следствие новых сенсорных ощущений и их обработки, переопределяют доминанты вплоть до инстинкта самосохранения. Развитие и исследование контекста приводят к новым выводам и новым доминантам.

                Кастанеда занимался этим в своем антропологическом исследовании на тему алкоголизма у индейцев. И индейцы показали ему истинную причину: они не глупы, просто американцы нагнули Мексику, захватили месторождения и путем грабительского кредитования отобрали землю. И индейцы спились, хотя интеллектуально были не слабее европейцев. То же самое наблюдается в России. Люди пьют не потому, что они быдло, а потому что выхода нет: если ты не родственник судьи/прокурора/фсб то у твоего бизнеса нет шансов. Только рабский труд, бюджетный распил или разбой может стать основой жизни.

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

                Если бы это было не так, эволюции бы не было. Доминанты бы доминировали и все. Существо не использовало бы новые мутации.


                1. Ogoun
                  02.11.2016 15:40

                  По Маркову, кстати, интеллектуальный потенциал индейцев на первом или втором месте среди всех прочих народов Земли.


            1. akastargazer
              01.11.2016 21:55

              >Нейрон не был заторможен. Он был вне контекста.

              Его подавляла другая, более мощная доминанта — доминанта культуры, не позволяющая вам поссать прямо в магазине.


        1. napa3um
          02.11.2016 11:09

          Есть мнение, что Редозубов переоткрыл автоволны и «разряжённые» кортикальные колонки HTM, делая на этих озарениях слишком далеко идущие философские выводы. Посмотрим, когда публике будет представлена модель, решающая хоть какую-то практическую задачу с большей эффективностью, чем уже известные подходы. К работе Белкина примерно такая же «претензия», но у него есть хотя бы результат в виде исследовательского инструмента, позволяющего экспериментировать с различными устройствами нейроэлемента, проверять какие-то гипотезы. Т.е., он ещё не рассмотрел подкову блохи, но по пути к этому придумал конструкцию мелкоскопа (теперь главное не превратить мелкоскоп в калейдоскоп).


          1. fivehouse
            02.11.2016 13:39

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


            1. napa3um
              02.11.2016 13:41

              Ровно ВСЕ архитектуры искусственных нейросетей об этом (в терминах fuzzy logic), модель Редозубова — вовсе не исключение.


  1. IvanovAA
    02.11.2016 14:28
    +3

    На мой взгляд предложенная автором модель больше похожа на упрощенную версию проекта «BlueBrain», а не на классические нейросети, что делает ее интересной и перспективной. Надеюсь на продолжение публикаций.