Код нейроэлемента

8a3d4434e1734accbc47e621f066b340.jpg

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

Содержание цикла статей


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

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

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

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

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

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

fae77b3309014f2daceb0627eb9ce2f5.jpg

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

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

Не буду утомлять. Ссылка на репозиторий GitHub: https://github.com/BelkinAndrey/SoNS

Для примера, код скрипта в котором заключена главная логика работы нейроэлемента 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().color = new Color32(255, 255, 0, 255);//Жёлтый, сумматор
                                }       
                        if (value == 1) 
                                { _TypeIndexNeiron = value;
                                gameObject.GetComponent().color = new Color32( 0, 255, 0, 255); //Зелёный, модулирующий //
                                }       
                        if (value == 2) 
                                { _TypeIndexNeiron = value;
                                gameObject.GetComponent().color = new Color32(0, 255, 255, 255);//Голубой, асоциативный тип
                                }
                        if (value == 3) 
                                { _TypeIndexNeiron = value;
                                gameObject.GetComponent().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 hitSinaps = new List();         //Список синапсов

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

        private bool _ActionN;                                                                                  //Активен ли нейрон
        
        public bool ActionN                                                                                             //Активен ли нейрон
        {
                get { return _ActionN; }
                set 
                {
                        _ActionN = value;
                        if (Area != null)
                        {
                                gameObject.GetComponent().enabled = value; //Показать вектор направления..
                bool existAction = Area.GetComponent().NeironActionList.Contains(gameObject); //existAction = true - если нейрон в списке активных нейронов области
                                if (_ActionN && (!existAction)) Area.GetComponent().NeironActionList.Add(gameObject); //добавить в список активных области
                                else Area.GetComponent().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().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().color = new Color32(0, 0, 255, 255);//устанавливаем синий цвет
                yield return new WaitForSeconds(timeIgnore);//время игнора
                IgnoreInput = false;
                TypeIndexNeiron = _TypeIndexNeiron;//включаем свой цвет
        }

        IEnumerator StartModule (){
        IgnoreInput = true; //включаем игнорирование внешних сигналов
                ActionN = true; //Состояние активности, это значение считывают индикаторы выходов 
                gameObject.GetComponent().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().Spike2[i];
                        if (Charge > 0) gameObject.GetComponent().color = new Color32(255, 0, 0, 255); //красный
                        else gameObject.GetComponent().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().Spike1[i];
                        if (Charge > 0) gameObject.GetComponent().color = new Color32(255, 0, 0, 255); //красный
                        else gameObject.GetComponent().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().NeironActionList.Count; i++) { //Список всех активных нейронов
                                if (gameObject == Area.GetComponent ().NeironActionList [i]) continue; //Исключаем данный нейрон из расчётов
                                Vector3 R = Area.GetComponent ().NeironActionList [i].transform.position - transform.position;//получаем относительные кординаты, относительно данного нейрона
                //Формула определения заряд нейрона на единичный вектор 
                                VectorPattern += (Area.GetComponent ().NeironActionList [i].GetComponent ().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().SetPosition(0, transform.position);//визуализация вектора предпочитаемого направления
                        gameObject.GetComponent().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().Global) NewNeiron(); //Глобальная область не имеет границ
                                        else 
                                        {
                                                if (Area.GetComponent().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().NeironTarget == value.gameObject) {
                                                                EnableSinaps = true; //синапс такой есть 
                                                                break; //дальше не перебираем
                                                        }       
                                                }
                                                
                                                if (!EnableSinaps) { //Если такого синапса нет
                                                        GameObject cSinaps = Instantiate(prefabSinaps, transform.position, transform.rotation) as GameObject;//мы его создаём 
                                                        cSinaps.transform.parent = transform;
                                                        cSinaps.GetComponent().NeironTarget = value.gameObject;
                                                        cSinaps.GetComponent().Force = 0f;
                                                        hitSinaps.Add(cSinaps);

                                                }

                                        }
                                }
                        }

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

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

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

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

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

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

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

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

        foreach (GameObject sd in clone.GetComponent().hitSinaps) Destroy(sd); //префаб размещенный в сцене из подобного себе обьекта, копирует и дочерниие обьеты родителя
                clone.GetComponent().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().TypeSinaps; //тип синапса
                        float F = value.GetComponent().Force; //его сила
                        GameObject NT = value.GetComponent().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().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, а все процессы, связанные с изменением состояний вынесены в корунтины.

67697dcef56e4cd08ebfc9edcffc7118.jpg

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

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

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

© Geektimes