Генерация рандомных ветвлений на Питоне

    image

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

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

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

    1. Модуль рандомной генерации основного исполняемого кода
    2. Модуль, связанный с рандомным образованием опыта
    3. Модуль «компьютерного зрения» объектов ОС

    В этой статье пойдет речь о первом модуле, который пока что представляет из себя только генерацию рандомных ветвлений, т.е. конструкций типа «if-elif-else». Почему ветвления? Потому что по-большому счёту жизнь любого живого организма состоит из условных реакций: всё, что мы делаем, является ответом на воспринимаемую информацию. Клетки делятся, если наступают определенные условия, жертва пытается бежать, если видит более сильного хищника, а если он слабее, то может попытаться его атаковать, тараканы разбегаются, если включается свет, человек идет есть, если голоден и т.д. и т.п. — этот ряд бесконечен. Нет каких-либо независимых, отдельных действий, которые ничем не обусловлены. Следовательно, поведение, в частности, живых организмов описывается как реакция на условие: IF [что-то] THEN [что-то]. Это поведение мы и пытаемся генерировать.

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

    Прежде, чем мы перейдем к схемам и коду генератора, необходимо остановиться на функции принятия решений, которая используется как дирижер, давая исполняться то одной, то другой части кода. Ранее я уже писал о ней здесь. Мне тогда подсказали, что я описал идею Reinforcement Learning и игру Джона Конвеея под названием «Жизнь». Вполне может быть, я не имею ничего против того, чтобы использовать то, что уже наработано или открыто. В конце концов всё новое — это синтез уже известного, да я и сам признавал, что перенял идею приоритезации потоков, которая используется в винде. Здесь она очень подходит.

    В настоящее время упомянутая функция немного трансформировалась:

    def make_solution(p_random, p_deter):                       
        deter_flag = 0
        random_flag = 0
        if p_random >= random.random():
                p_random-=0.01                                  # баланс приоритета
                p_deter+=0.01
                random_flag = 1
        if p_deter >= random.random():
                p_deter-=0.01                                   # баланс приоритета
                p_random+=0.01
                deter_flag = 1
        if random_flag == 1 and deter_flag == 0:
            return(p_random, p_deter, 1)
        elif deter_flag == 1 and random_flag == 0:
            return(p_random, p_deter, -1)
        else:
            return (p_random, p_deter,0)

    На вход она принимает 2 вероятности (по умолчанию на старте они обе равны 0,5), после чего поочередно проверяет их срабатывание. Сработавшая вероятность уменьшает сама себя на 1% и одновременно увеличивает на 1% другую. Следовательно, с каждым разом, как вероятность срабатывает, она уменьшается, а другая — увеличивается. В результате ни одна вероятность не получает слишком большого преимущества перед другой, и они самобалансируются, образуя нормальное распределение с центром в 0,5 и с рабочей окрестностью не более +-10%, что отличает эту функцию от стандартной random, где вероятность в нашем случае всегда была бы равна 0,5 и не зависела бы от предыдущих вычислений.

    Образно говоря, это маятник вероятности с небольшой амплитудой. Если сработала первая вероятность и не сработала вторая — возвращается 1, в обратном случае возвращается -1, а если сработали или не сработали обе — 0. Таким образом, функция make_solution на 2 входящих вероятности возвращает одно из 3 возможных действий, давая, соответственно принимать сбалансированное решение на развилках с 3 возможными вариантами продолжения. В дальнейшем эта функция, скорее всего будет универсальной, и сможет принимать неопределенное количество вероятностей, т.к. вариативность на развилках может быть больше 3, но в случае с генератором if-elif-else трёх вариантов продолжения вполне достаточно.

    Здесь надо еще отметить, что в коде встречаются разные, так сказать, типовые развилки. Например, как будет видно ниже, в основной функции генератора находится развилка, в которой идет выбор схемы построения ветвления, коих всего 3, но в коде также присутствуют другие случаи: вставлять блок действия или запускать рекурсию, сколько строк действия генерировать, насколько сложной должна быть строка с условием, ставить or или and, elif или else.

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

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

    При написании генератора ветвлений нужно было сделать не только работоспособный код, который выдает безошибочные генерации, но и такой код, который может генерировать максимально возможные конструкции if-elif-else, а таких возможных вариантов не 2 и не 3. Рассмотрим, например, следующие возможные схемы.

    image

    Под значком [..] в схемах я подразумеваю набор выражений для условия либо блок рандомных действий. Самая элементарная схема — 1, где просто идет условие, а за ним блок действия. 2а и 2b — это вариации if с одним elif или одним else. В варианте 2с if идет уже в комбинации с несколькими elif без еlse. И, наконец, в варианте 2d представлена наиболее общая схема, где if содержит в себе несколько elif и 1 else.

    Всё было бы просто, если бы не необходимость построения неограниченных ветвлений. После каждого if, elif или else может вызываться рекурсия, которая в свою очередь также может рекурсировать дальше и плодить «вправо» новые блоки elif-else. Посмотрим на схему возможных вариантов.

    image

    В вариантах 2е и 2f показаны простые частные случаи такого рекурсивного ветвления, когда рекурсия вызывается либо после одиночного elif, либо после одиночного else. Вариант 2g описывает наиболее сложный и общий случай такой рекурсии, когда после каждого elif может идти блок действия+рекурсия (либо сразу рекурсия), и тоже самое может происходить после else.

    Есть еще вариации, когда рекурсия наступает сразу же после if или после if и блока действия.

    image

    Это видно в вариантах 3а и 3b. Вариант 3с показывает такую схему в наиболее общем виде.

    Нельзя сказать, что приведенные выше схемы охватывают все возможные варианты построения ветвлений, однако даже в таком виде конечный код легко рождает ветвления в 150 строк, уходя «вправо» на 10-15 шагов. В любом случае усложнить схемы при необходимости не составляет большого труда.

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

    image

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

    Перед тем, как перейти к непосредственному рассмотрению кода генератора, необходимо вспомнить, что сгенерированные блоки должны смещаться вправо по горизонтали, если это elif, else, рекурсия if или блоки действия, а также «возвращаться» назад влево, после того как ветка завершится. Причем, учитывая, что Питон весьма придирчив к горизонтальным отступам, желательно сделать шаг одинаковым (в нашем случае шаг равен 3).

    На следующей схеме проиллюстрировано, каким образом сдвигаются смещения.

    image

    Самое главное тут, что смещения при углублении ветвления сдвигаются всегда вправо. Однако, если у нас есть, например, блок elif-else, в котором есть несколько elif или одинарная пара elif-else, то существует необходимость «возврата» каретки, уплывшей вправо, для того, чтобы следующий elif (или else) начинался с того же смещения, что и предыдущий в блоке. Для этого необходимо сохранять изначальное смещение (wall_offset) и после окончания генерации ветки (например, полного ветвления одного elif), восстанавливать его. Таким образом достигается ровное нахождение «друг над другом» элементов elif, else в блоке. Причем у каждого нового блока это смещение своё. Тот же самый приём обеспечивает стройность общей конструкции if-elif-else (в том числе при рекурсиях).

    Теперь перейдем к коду. Код общим объемом около 200 строк состоит из 8 функций, одну из которых мы рассмотрели выше. Из-за рекурсивности и большого количества параметров, передаваемых в функции, он местами может быть плохо читаемым. Для начала приведу то самое «мясо», которое используется для генерации условных выражений и блоков действий.

    var_list = ['a','b']
    exp_list = ['a+b','b-a', 'b//a']
    sign = ['+','-','/','*','//']
    sign2 = ['>','<','==','>=','<=','!=']
    a = 3
    b = 2

    Как видно, используются две переменные: a и b (var_list), которые инициализированы, 3 арифметических выражения (exp_list), а также два листа со знаками (sign, sign2). Как и говорилось ранее, состав получаемых выражений сейчас значения не имеет и в данной статье не рассматривается — они нужны в основном для иллюстрации кода. Надо отметить еще такую особенность: в генерации блока elif-else нужно отслеживать появление else и прекращать генерацию, — в противном случае else может появиться перед elif, что естественно вызовет ошибку. Для этой цели используется флаг fin_else_flag.

    Начнем рассмотрение с основной функции генерации.

    def if_gen(exp_list, var_list, if_str, offset_koeff, fin_else_flag, prob_list):             
        choice_list = [exp_list, var_list]
        base_offset = ' '
        # основная структурная развилка
        prob_list[0],prob_list[1],sol = make_solution(prob_list[0],prob_list[1])       
        # if + блок действия (1 вариант в схеме)        
        if sol == 0: 
            # генерим блок действия со смещением+3                                                                   
            action_str = action_str_gen(choice_list, offset_koeff+3, prob_list)                 
            return(base_offset*offset_koeff+'if '+ if_sub(exp_list,var_list, sign, prob_list) +':\n' + action_str, offset_koeff, fin_else_flag, prob_list) 
        # if + elif/else (2 вариант в схеме)           
        elif sol == -1:                                                                         
            if_str= base_offset*offset_koeff+'if '+ if_sub(exp_list,var_list, sign, prob_list) +':\n' + action_str_gen(choice_list, offset_koeff+3, prob_list) # if [..]:
            # развилка elif/else
            prob_list[2],prob_list[3],sol2=make_solution(prob_list[2],prob_list[3])             
            if sol2!=0:
                ee_string='elif'
            else:
                 ee_string='else'
            # генерация блока elif/else
            if_str, offset_koeff, fin_else_flag, prob_list = elif_else_block(ee_string, offset_koeff, exp_list, var_list, sign, if_str, choice_list, fin_else_flag, prob_list)
            return(if_str, offset_koeff, fin_else_flag, prob_list)
        # if + if(рекурсия) (3 вариант в схеме)
        else:                                                                                   
                if_str= base_offset*offset_koeff+'if '+ if_sub(exp_list,var_list, sign, prob_list) +':\n' # if [..]:
                # развилка if/if+блок действия
                prob_list[4],prob_list[5],sol = make_solution(prob_list[4],prob_list[5])        
                if sol==0:
                    # генерим блок действия со смещением+3
                    if_str+=action_str_gen(choice_list, offset_koeff+3, prob_list)      
                # сохраняем смещение        
                wall_offset = offset_koeff                                                      
                if_rek, offset_koeff, fin_else_flag, prob_list = if_gen(exp_list, var_list, if_str, offset_koeff+3, fin_else_flag, prob_list) # рекурсия if+if
                # прицепляем сгенерированный рекурсивный кусок
                if_str+=if_rek   
                # развилка блок elif-else/блок действия                                                               
                prob_list[4],prob_list[5],sol2=make_solution(prob_list[4],prob_list[5])         
                if sol2!=0:
                    prob_list[2],prob_list[3],sol3=make_solution(prob_list[2],prob_list[3])
                    if sol3!=0:
                        ee_string='elif'
                    else:
                        ee_string='else'
                    if_str, offset_koeff, fin_else_flag, prob_list = elif_else_block(ee_string, wall_offset, exp_list, var_list, sign, if_str, choice_list, fin_else_flag, prob_list)  
                else:
                    # генерим блок действия со смещением+3
                    if_str+=action_str_gen(choice_list, offset_koeff+3, prob_list)              
                return(if_str, offset_koeff,fin_else_flag, prob_list)

    Функция принимает помимо списков с «мясом» для генерации (exp_list, var_list) также if_str — это строка, куда поочередно собирается генерируемый код. Принимается она здесь по причине того, что сама функция if_gen может рекурсивно вызываться, и кусок кода, сгенерированный ранее, желательно бы не терять.

    Параметр offset_koeff — это коэффициент смещения, который является множителем для строки с одним пробелом (base_offset) и, соответственно, именно он и отвечает за горизонтальные смещения блоков кода.

    Про fin_else_flag мы говорили выше, здесь он просто передается в функцию, которая отвечает за генерацию if + elif/else (см.ниже).

    Ну и есть еще параметр — prob_list, который представляет из себя лист с 10-ю вероятностями (5 парами вероятностей)
    prob_list = [0.5 for y in range(0,10)] 
    и используется функцией make_solution так, как мы обсуждали выше: в нее передается та или иная пара вероятностей из него, соответствующая типу развилки (например, основная структурная развилка использует первые 2 вероятности в листе: prob_list[0] и prob_list[1]). Результаты изменений вероятностей в этом листе, как пример, можно видеть на следующем рисунке.

    image

    Вероятности в этом списке меняются от генерации к генерации, если при очередной генерации соответствующий кусок кода попадал на исполнение.

    В самой функции в начале инициализирован вложенный список choice_list — он нужен для удобной рандомной генерации выражений из «мяса», и базовое смещение base_offset = ' ' в один пробел.

    После этого идет основная развилка, которая через функцию make_solution получает решение в переменную sol. Sol принимает одно из трёх значений (0,-1,1) и обуславливает, следовательно, по какой схеме будет строиться конструкция.

    В первом варианте реализуется самая простой вариант if+[..]. Ответ формируется в виде строки со текущим смещением (оно не обязательно равно 0!), строка «if», рандомное условие, которое генерируется функцией if_sub (будет рассмотрена дальше), перевод каретки и генерация блока действия с помощью функции action_str(см.ниже). В результате мы получаем нечто вроде:

    if ((a+b)==(b)):
       b=b
       a=b-a
       a=a

    Второй вариант отвечает за генерацию такого типа: if [..]+elif/else-блок (вариант 2 в схемах). Сначала там формируется аналогично строка if+[..], затем идет развилка elif/else, которая решает будет ли генерироваться блок elif-else, просто if-elif или if-else (функция elif_else_block — см. ниже). Результаты могут быть разными. Например:

    if ((a+b)==(a)):
       b=a+b
    elif ((b//a)==(a)):
       None
    elif ((a+b)<=(a)):
       a=b//a
    else:
       if ((b)<=(a)):
          a=b-a
          b=a

    if ((a)==(b-a)):
       b=b-a
       b=b
       a=b
       a=b-a
    elif ((b)>(b-a))and((a)<(b-a)):
       if ((b//a)<(a)):
          b=b-a
       elif ((a+b)<(b-a))and((b)<(a+b))or((a+b)==(a+b)):
          b=b
          a=b-a
       elif ((a)>(b-a)):
          None

    if ((b)<=(b-a))or((a+b)>=(b)):
       a=a
       b=b
    elif ((b)<=(b)):
       if ((a)>=(b)):
          a=a+b
          a=b
    elif ((b)>=(a)):
       a=b-a
       a=a
       if ((a)>=(b))and((b//a)==(a))and((b//a)!=(b)):
          b=b-a
    else:
       a=b//a
       if ((b//a)<(b-a)):
          a=b
          a=b-a
       else:
          if ((a)==(b)):
             a=a
             a=b//a
             b=b
             b=a+b
             b=a
          else:
             None

    Третий вариант реализует рекурсию с самого начала (вариант 3 в схемах), т.е. рождает ветвление вида:

    if ((a)==(a)):
       if ((a+b)<(b)):

    или
    if ((b-a)<=(a)):
       a=a
       if ((b-a)==(b)):
          a=a
          a=a

    Сначала идет формирование строки if (аналогично), потом появляется развилка, которая решает, вставлять ли дальше блок действия или нет, после чего сохраняется смещение и вызывается рекурсия. Смещение нужно сохранить, чтобы после того, как отработает рекурсия и вернет кусок кода, можно было добавить еще блок elif-else по тому же смещению, как и первоначальная строчка с if. Здесь видно, как elif и else в ветвлении стоят по одному смещению со своим «родным» if.

    if ((b-a)==(b)):
    
       if ((a)>(a+b)):
          if ((b)==(b-a)):
             b=b
             a=a
          elif ((b)>(b)):
             None
          else:
             None
             b=a
             b=b

    Дальше идет развилка elif-else-блок/блок действия, которая решает добавлять ли после рекурсии блок действия или elif-else блок. Если решено добавлять блок elif-else, то там аналогично вышеописанному случаю в схеме 2 выбирается elif или else.

    Здесь же надо обратить внимание на то, что рекурсия вызывается со смещением offset+3, чтобы сдвинуть вправо на шаг генерируемый код, а блок elif-else вызывается со смещением wall_offset, чтобы этот блок после рекурсии не поехал вправо, а остался с «родным» смещением изначального if.

    Результаты могут быть довольно разными: от простых до сложных, но появление рекурсии сразу же выдает наиболее витиеватые ветвления.

    if ((b-a)>(a+b))and((b)<(a+b)):
       if ((b-a)<=(a+b)):
          b=b//a
       elif ((b)!=(a)):
          a=b-a
    else:
       if ((a+b)!=(b-a)):
          a=a

    if ((b)<(b-a)):
       if ((a+b)==(b-a))and((b-a)<(a+b))and((b-a)==(a))and((a)>(b//a))or((a+b)>(b//a)):
          if ((b)>=(b-a)):
             a=b
             b=b
             if ((b)>(b)):
                a=a+b
                b=a+b
                a=a
                b=a+b
                b=b//a
                b=a
          else:
             b=a+b
             a=b
             a=b
       elif ((a)<(b-a)):
          a=b//a
          a=b-a

    if ((a)>=(b-a))or((a)>=(a))or((b)<=(b)):
       a=a
       a=a
    elif ((a)==(a))and((b)>(b-a)):
       a=b//a
       if ((a)<(b)):
          if ((a+b)==(b-a)):
             a=a
             if ((a)!=(b//a)):
                if ((b//a)!=(a))and((b-a)>=(b)):
                   a=b
                else:
                   None
                   a=b//a
          else:
             b=b
             b=a+b
             if ((b-a)<=(b//a)):
                a=b
                a=b
                a=a+b
    else:
       a=a+b
       if ((b-a)>=(a)):
          a=b
          if ((b-a)==(a))or((b)!=(b//a)):
             a=b-a
             a=a
             a=a
             a=b//a
             a=a+b
             b=a

    Теперь посмотрим на функцию elif_else_block, которая отвечает за формирование блока elif-else и вызывается из основной функции if_gen.

    def elif_else_block(ee_string, offset_koeff, exp_list, var_list, sign, if_str, choice_list,  fin_else_flag, prob_list):
        if ee_string=='elif':
            sol3 = 9
            # сохраняем смещение
            wall_offset = offset_koeff
            # генерация elif в цикле
            while sol3!=0 and fin_else_flag!=1:
                temp_str, offset_koeff, fin_else_flag, prob_list=elif_else('elif', wall_offset, exp_list, var_list, sign, if_str, choice_list, fin_else_flag, prob_list)
                if_str+=temp_str
                prob_list[6],prob_list[7],sol3 = make_solution(prob_list[6],prob_list[7])
            # развилка - добавлять ли else к группе elif?
            prob_list[2],prob_list[3],sol = make_solution(prob_list[2],prob_list[3])
            if sol!=0:
                # добавляем else, значит ставим флаг
                fin_else_flag=1
                temp_str,offset_koeff, fin_else_flag, prob_list=elif_else('else', wall_offset, exp_list, var_list, sign, if_str, choice_list, fin_else_flag, prob_list)
                if_str+=temp_str
            return(if_str,offset_koeff, fin_else_flag, prob_list)
        # генерация else
        else: 
              temp_str,offset_koeff, fin_else_flag, prob_list=elif_else('else', offset_koeff, exp_list, var_list, sign, if_str, choice_list, fin_else_flag, prob_list)
              if_str+=temp_str
              return(if_str, offset_koeff, fin_else_flag, prob_list)

    Данная функция решает, добавлять ли в код блок elif или elif/else. Ставить ли просто else она сама не решает, а зависит от входящего значения ee_string, которое она получает из основной функции if_gen. Сначала идет генерация блока elif в цикле while, где проверяются 2 условия: вероятностное — от него зависит количество самих elif в блоке и флаг fin_else_flag, который, если вдруг включается, то это означает, что до этого присоединялся else, и следовательно из цикла надо выходить.

    Решение присоединять ли к блоку elif еще и else решается развилкой с помощью всё той же функции make_solution, причем, если присоединяется else, то сразу же включается флаг fin_else_flag, который прекращает генерацию блока.

    Непосредственное же присоединение elif и else осуществляется функцией elif_else (см.ниже). Здесь надо обратить внимание, что при генерации блока elif (а также при присоединении к нему else) используется смещение wall_offset, чтобы ровно выстраивать блок в целом.

    Теперь рассмотрим функцию elif_else .

    <b>def elif_else(ee_string, offset_koeff, exp_list, var_list, sign, if_str, choice_list, fin_else_flag, prob_list):
        ee_str = ''
        # формирование строки else: или elif [..]:
        if ee_string=='else':
            ee_str += ' '*offset_koeff+ee_string + ':\n'
        elif ee_string=='elif':
            ee_str += ' '*offset_koeff+ee_string+' '+if_sub(exp_list, var_list, sign, prob_list) + ':\n'
        # развилка блок действия-None / блок действия+рекурсия
        prob_list[2],prob_list[3],sol = make_solution(prob_list[2],prob_list[3])
        if sol!=0:
            prob_list[6],prob_list[7],sol2 = make_solution(prob_list[6],prob_list[7])
            if sol2!=0:
                # блок действия
                ee_str+=action_str_gen(choice_list,offset_koeff+3, prob_list)
            else:
                # None
                ee_str+=' '*(offset_koeff+3)+'None\n'
            return(ee_str, offset_koeff, fin_else_flag, prob_list)
        else:
            # подразвилка блок действия
            prob_list[6],prob_list[7],sol2 = make_solution(prob_list[6],prob_list[7])
            if sol2==0:
                # блок действия
                ee_str+=action_str_gen(choice_list,offset_koeff+3, prob_list)
            # рекурсия if_gen
            if_str, offset_koeff,  fin_else_flag, prob_list = if_gen(exp_list, var_list, if_str, offset_koeff+3, fin_else_flag, prob_list)                 
            ee_str+=if_str
            return(ee_str, offset_koeff, fin_else_flag, prob_list)

    Функция отвечает за формирование самой строки elif или else, а также за последующую генерацию после этих строк блоков действий или рекурсии. Она также принимает переменную ee_string, которая содержит или elif, или else, и формирует соответствующую строку. Затем идет развилка, где определяется, что будет идти дальше: (блок действия или None), или (блок действия или блок действия+рекурсия). Внутри этой развилки есть разделение, соответственно на две подразвилки, и в каждом случае вызывается функция make_solution с соответствующими параметрами для принятия решения.

    Надо отметить, что когда в коде встречается if sol!=0, это значит, что мы намеренно даем преимущество одной части кода над другой, ибо если sol!=0, значит она равняется или -1, или 1, и следовательно другой кусок кода будет исполняться реже (только когда sol ==0). Это используется, в частности в функции elif_else_block, где нам выгоднее давать формироваться большему количеству elif в блоке, а не давать равную вероятность elif и else. Или, например, в функции elif_else мы даём преимущество варианту, когда формируется блок действия или None нежели, чем идет рекурсия — в противном случае ветвления могут разрастаться до совсем уже неприличных размеров.

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

    Функция, отвечающая за генерацию блока действия action_str.

    def action_str_gen(choice_list, offset_koeff, prob_list):
        sol = 9
        curr_offset = ' '*offset_koeff
        act_str = ''
        while sol!=0:
            act_str+= curr_offset+rand(rand(choice_list[1]))+'='+rand(rand(choice_list))+'\n'
            prob_list[6],prob_list[7],sol = make_solution(prob_list[6],prob_list[7])
        return(act_str)

    Здесь всё достаточно просто: из вложенного списка choise_list, который, как мы помним, состоит из var_list (список переменных) и exp_list (список выражений), эта функция составляет одну или несколько строк такого вида: a = a + b или b = b. Т.е. либо переменной присваивается выражение, либо другая переменная (включая себя саму). Функция rand осуществляют случайную выборку элемента из списка и нужна тут исключительно для того, чтобы не плодить монстроузные строки.

    def rand(t_list):
        return(t_list[random.randint(0,len(t_list)-1)])

    Функция генерации выражений if_sub для условий выглядит побольше.

    def if_sub(exp_list, var_list, sign, prob_list):
        sub_str = ''
        sol = 9
        choice_list = [exp_list, var_list]
        flag = 0
        while sol!=0:
            prob_list[6],prob_list[7],sol = make_solution(prob_list[6],prob_list[7])
            sub_str+='(('+rand(rand(choice_list))+')'+rand(sign2)+'('+rand(rand(choice_list))+'))'
            if flag == 1 and sol==1:
                sub_str+=')'
                flag=0
            or_and_exp = or_and(prob_list)
            if len(or_and_exp):
                sub_str+=or_and_exp
            else:
                break
            prob_list[6],prob_list[7],sol2 = make_solution(prob_list[6],prob_list[7])
            if sol2 == 1 and (sub_str[-1]=='D' or sub_str[-1]=='R') and flag == 0:
                sub_str+='('
                flag = 1
        
        if sub_str[-1] == '(':
            if sub_str[-2]=='d':
               sub_str=sub_str[0:-4]
            elif sub_str[-2]=='r':
                 sub_str=sub_str[0:-3]
            else:
                sub_str=sub_str[0:-1]
        elif sub_str[-1]=='d':
             sub_str=sub_str[0:-3]
        elif sub_str[-1]=='r':
             sub_str=sub_str[0:-2]
        else:
             None
        if flag == 1:
            sub_str+=')'
            return(sub_str)
        else:
            return(sub_str)
    

    Она генерирует выражения по типу: ((a)>=(b-a))or((a)>=(a))or((b)<=(b)). При этом как в левой, так и в правой части могут быть различные варианты и стоять как отдельные переменные, так и выражения или их группы. Здесь используются также логические операторы or и and, которые выбираются для удобства с помощью функции or_and_exp.

    def or_and(prob_list):
        prob_list[8],prob_list[9],sol = make_solution(prob_list[8],prob_list[9])
        if sol==-1:
            return('and')
        elif sol==1:
            return('or')
        else:
            return('')

    Остальная часть функции if_sub отрезает от выражений лишние хвосты и добавляет, когда нужно, закрывающие скобки, — рассматривать здесь эти танцы с бубнами, я думаю, нецелесообразно.

    Ну вот собственно и всё. Запустить генератор можно, например, так:

    var_list = ['a','b']
    exp_list = ['a+b','b-a', 'b//a']
    sign = ['+','-','/','*','//']
    sign2 = ['>','<','==','>=','<=','!=']
    a = 3
    b = 2       
    prob_list = [0.5 for y in range(0,10)]      
    while True:
         if_str = ''
         if_str, offset_koeff, fin_else_flag, prob_list = if_gen(exp_list, var_list, if_str, 0,0, prob_list)
         try:
             exec(compile(if_str,'gen','exec'))
             print(if_str)
             input()
             
         except ZeroDivisionError:
             None
         except:
             print('error')
             print(if_str)
             input()

    Сначала входные данные, включая лист с вероятностями prob_list, затем в бесконечном цикле вызов основной функции if_gen и запуск полученной сгенерированной строки на исполнение. Стоит обрабатывать отдельно ZeroDivisionError, т.к. деление на ноль при таком рандомном построении выражений встречается весьма часто. После запуска достаточно жать Enter для появления очередной генерации. Чаще всего они будут достаточно простыми, но нередко встречаются и разветвленные и даже очень разветвленные. Ну и import random в начало тоже было бы неплохо вставить;) Для тех, кто не хочет посмотреть собирать всё руками, можно скачать файл с Github (файл if_gen.py).

    В заключение я хочу сказать, что представленный код тестировался мною на сотнях тысячах генераций без ошибок, при этом он демонстрировал всю ту палитру схем if-elif-else, которую я и хотел в конце концов увидеть. Один раз я по ошибке дал в одной части кода слишком большую вероятность рекурсии и у меня вышла генерация в 52 000 (!) строк и при этом она была рабочей (хотя комп подвис секунд на 30). Это также свидетельствует о надежности алгоритма.

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

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

    В следующей статье мы будем рассматривать второй модуль, который будет отвечать за рандомное формирование опыта. Эта тема обещает быть куда интересней, чем генератор if, и я обязательно выложу результаты, как только они у меня будут.
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 34

      0
      если долго держать смерч над помойкой, то может собраться Боинг-747.
      Возможно из этого принципа сделали приложение «Социальный мониторинг»
        0
        а теперь из этих наработок сделают приложение для мигрантов
        –1
        Ну вот, еще один бредогенератор (
          +3

          А не «правильнее» ли было бы генерировать AST, а потом из него код?
          Ведь там-то дерево и всё уже готово.

            0
            возможно, что и да) но я не знаю этот модуль, гляну, спасибо
            0
            Сразу вспомнил «iPhuck 10» Пелевина. Окончание там трагическое. Но может это только у Пелевина.
              0

              "a = a" — это сильно.

                +2
                это не имеет значения в данном коде сейчас
                +1
                Думаю, вам будет интересно узнать про faculty.hampshire.edu/lspector/push3-description.html и github.com/erp12/PyshGP
                  0
                  Хм… весьма интересно. А сам язык получил какое-нибудь продолжение в гражданском обороте? Просто в инете кроме этой ссылки ничего не видно на первый взгляд.
                • НЛО прилетело и опубликовало эту надпись здесь
                    0
                    Смотря ЧТО зачем? Генератор нужен для того, чтобы у цифрового организма была возможность формировать рандомные реакции на входящие данные. А сам организм — это попытка не мучаться со сложными алгоритмами, которые так или иначе являются упрощением, а дать им вырасти самим так сказать в «естественной» среде уже без всякого упрощения. Потом их можно будет изучать и в ряде случаев, возможно, даже использовать для решения практических задач. Но о полном контроле над ними тут, конечно, надо забыть. сразу же.
                      0
                      Я тоже не сразу понял, но это похоже на попытку сделать что-то вроде модели для нейронной сети, но с задокументированной целевой функцией на Python.

                      Я не знаю что там автор хочет делать дальше, но если он хочет «цифровую жизнь в пробирке» — то можно задать этому «организму» набор доступных действий, скажем, над файлами — создать, записать, удалить, и тп. Затем нагенерировать десяток триллионов таких ЦФ и запустить в контейнерах. Возможно хоть один и сможет в итоге создать файл с «Hello» текстом внутри. Зато получим программу которая написана полностью алгоритмом.

                      Ну или можно запустить генерацию и запуск этих ЦФ лет на 10 непрерывно и, возможно, в итоге оно само выберется из контейнера и пришлет письмо автору. Или на Хабр напишет о себе.
                        0
                        Вы верно уловили про «набор доступных действий» — это будет в самом конце, когда можно будет этими наборами делать специфичные программы, заточенные под те или иные объекты. Но насчет десятилетий и триллионов генераций, я не уверен, что потребуется столько времени. Мы же не тупо генерируем исполняемую строку из 256 символов перебором, а пытаемся генерировать конструкции, что должно существенно уменьшать время. Плюс к этому следующим шагом будет разработка модели памяти, которая, как я думаю, должна мне через определенное время выдавать повторяющиеся действия, а не только хаотические — это тоже, вероятно, снизит время, особенно учитывая, что в память будет уходить не всё, а только то, что прошло по критериям.
                      +1
                      Идея интересная. Возможно вам поможет динамическая теория информации (Чернавский).
                      spkurdyumov.ru/economy/sinergetika-i-informaciya
                        0
                        Как-то переусложнено.
                        1) Есть лишь 1 вариант условия (немного смнемокоденно):
                        if cond :
                            for j in range(1,randint(0, max_expr))
                                expres
                        for i in range(0,randint(0, max_elif))
                            elif cond :
                                 for j in range(1,randint(0, max_expr))
                                     expres
                        for i in range(0,randint(0, 1))
                            else:
                                 for j in range(1,randint(0, max_expr))
                                     expres
                        

                        Где в виде выражениями может быть либо действие, либо ещё одно ветвление.

                        И не забывайте, в подобной структуре будет много «интронов» — мёртвого кода вроде if false или if true
                          0
                          Спасибо за вариант, но в этой схеме невозможно дальнейшее ветвление elif и else, т.к. и то и другое заканчивается просто выражениями expres. Т.е. на выходе тут в общем случае будет что-то такое:
                          if [..]:
                             [..]
                          elif [..]:
                              [..]
                              [..]
                          elif [..]:
                              [..]
                              [..]
                               ..
                          else:
                              [..]
                              [..] 


                          Но никогда не будет, например, такого:
                          if [..]:
                             [..]
                          elif [..]:
                              [..]
                              [..]
                              elif [..]:
                                  [..]
                                  [..]
                               ..
                          else:
                              if []:
                                  [..]
                                  [..]


                          А значит вся генерация упрется лишь в одну тривиальную схему, а мне нужна максимальная вариативность. Кроме того, max_elif и max_expr задают здесь конечное число elif и выражений, тогда как генерация должна быть теоретически не ограничена, и значит ее никак нельзя ограничивать с помощью range.
                            0
                            всё равно будут ограничения временем выполнения, ограничением памяти и переполнения стеков и т.д.
                            И сколь сложно не строй, всё равно пока получается лишь такое:
                          0
                          человек идет есть, если голоден

                          Человек ест и когда не голоден. И не ест, когда голоден. Процесс часто совсем не случайный. Голоден, а нет еды. Либо еда есть, но нужно поделиться с близкими.
                          Человеческое поведение сложней, чем модель стимул-реакция.
                          Вспоминая Докинза, основную идею можно выразить так: если долго держать смерч над помойкой, то может собраться Боинг-747.

                          Это сколько миллиардов лет нужно ждать? Каких-то 15? Ответ известен — никогда. Помойку раскидает в разные стороны, и в очаге не останется предметов для создания боинга, даже если они там были.
                          Прежде чем чего-то получится, должно быть нечто осознающее себя, свои желания и потребности. Желание летать было раньше Боинга — 747. Желание алгоритмизировать было раньше программы, выводящую «Hello World!!!», на экран монитора.
                          Вы какое свое желание хотите реализовать?
                            0
                            Спасибо за ответ, но, к сожалению, по первой части логику вы обрисовали неправильно.

                            «Либо еда есть, но нужно поделиться с близкими.» — здесь ЕСЛИ нужно поделиться с друзьями и ЕСЛИ еда есть, то — нужно поделиться. 2 условия и одна реакция. Стимул в виде 2 условий и реакция. Ведь «нужно поделиться» говорит о наличии у человека морального принципа: «ЕСЛИ у меня есть, ТО нужно поделиться» — 1 условие и 1 реакция.

                            «Человек ест и когда не голоден.». Значит он ест по другой причине, но эта причина всегда есть и она всегда является условием.

                            «И не ест, когда голоден.». Аналогично. Не ест по какой-то причине. Например, IF [нет еды]: не ем — условие и реакция.

                            «Процесс часто совсем не случайный.». Фишка в том, что он всегда не случайный. Всегда действия обусловлены чем-то. Поэтому не только человеческое поведение можно уложить в модель «стимул-реакция», но и вообще всё, что биологией считается живым.

                            Что же касается второй части, тут, вероятно, вам просто немного не хватает знаний. Вселенная сложилась примерно за 14млрд. лет из взаимодействия разрозненных частиц в полном хаосе. Неужели вы думаете, что генерация какого-то там кода или сборка какого-то там Боинга займет времени на целый миллиард лет больше? «Помойка» тогда 14 млрд. лет назад была гигантской, но она не просто сложила Боинг, но даже и человека, который его произвел, который сам по себе является куда более сложной конструкцией, чем самолет.

                            «Прежде чем чего-то получится, должно быть нечто осознающее себя, свои желания и потребности. ». А у кого было желание сложить человека? А Вселенную? И кстати, если бы вы интересовались таким разделом ИИ, как «Искусственная жизнь», то знали бы, как без всяких осознаний своих желаний и потребностей генерируются на искусственном поле «хищники» и «травоядные», которые не то что себя не осознают, но даже не знают, что они хищники и травоядные, что они могут умирать и даже, что им нужно есть для существования — и меж делом через несколько миллионов итераций (не лет!) те же «хищники» и «травоядные» каким-то чудодейственным образом охотятся друг за другом, убегают друг от друга, поджидают друг друга у мест кормления и используют обманные маневры — это поведение не закладывалось изначально, оно просто появилось банальной генерацией с отбором. И чтобы увидеть этот процесс никто не ждал не то что 15 млрд. лет, но даже одну неделю.

                              0
                              1. По поводу того, что во всем есть причина. Из известного результата всегда можно ретроспективно выстроить путь приведший к известному результату. Но имея условия, как-то плохо получается предсказать результат.
                              Если брать задачу с едой. Вот вы видите еду и человек не ест, вы можете по факту наблюдения вывести условия? Когда он вам объяснит почему он не ест, вы можете их составить. Но где гарантия, что он вас не обманул? Для этого вам нужно активно вмешаться в наблюдение. Вы можете после этого предсказать, что вмешательство в систему не повлекло за собой изменения структуры условий? Например, он не хочет есть при посторонних. А до вопроса он не ел, потому что думал о другом. То есть само действие может изменять не только вероятности, но и создавать и уничтожать условия и их цепочки.
                              Сама сетка условий динамична и скорее всего не устойчива и сильно зависит от граничных условий, начальных условий и контекста.

                              2. 14 или 15 млрд. лет — это не принципиальная разница по сравнению с жизнью человека. Во вторых вы оперируете гипотезой, о том что было в точке сингулярности. Была ли она никто не знает и доказать не может. Возвращаясь к условиям. Есть стандартная модель, которая построена на наличие симметрии. Есть большой вопрос про возникновение самой такой симметрии и какова ее вероятность. Но даже эта симметрия, если опереться на теорию суперструн. Может быть реализована 10 в 500 степени способов. Тут мы опять возвращаемся к начальным и граничным условиям. Вы можете спокойно посчитать, сколько времени нужно будет, чтоб просто перебрать все возможные начальные конфигурации.
                              3. По поводу искусственного интеллекта. Для того чтобы появились хищники и животные в поле, должна быть интерпретация что такое хищник, а что такое травоядное.
                              Начальные условия задает человек из своих желаний и представления, во вторых он же задает возможное поле объектов и операции над ними. Вы не можете сказать, что любое поле над любыми объектами дифференцируется на хищников и травоядных. А тем более доказать, что такие поля эквивалентны живой природе.
                              Есть такая теорема Больцано-Вейерштрассе, что из любой ограниченной последовательности можно выделить сходящуюся последовательность. Так и в хаусе, при своем желании можно увидеть любые объекты, любые процессы и их интерпретировать по своему желанию.
                              Это основная проблема, выделяя из поля структуры, человек наделяет их своими смыслами. Кто-то видит хищников и животных, кто-то мужчин и женщин. Но это всегда интерпретация наблюдателя, человека со своим жизненным опытом. Но вы никогда не поймете, почему одни объекты гоняются за другими. Потому что они хотят есть как хищник, или потому что просто считают, что другие не красивые и портят вид на поле, или хотят вам понравиться. Пока эти объекты не осознают себя и не смогут сообщить об этом, вы это никогда не узнаете.
                                0
                                1. Чтобы не уходить в оффтоп, давайте я объясню вам примерно, как я вижу будущую программу. Предположим на вход поступает информация об очередном объекте в виде цифрового массива такого состава: 1 — файл, 2-тип файла(исполняемый, текстовый и т.д.), 3-длинна, 4-количество строк и т.д. Т.е. это будет типа листа в Питоне [1, 3, 431, 67]. Вот этот входящий массив — УЖЕ УСЛОВИЕ. Это — внешний фактор. Потому что в памяти у программы будут зашиты предыдущие массивы со своими данными и действия, которая она предпринимала с ними раньше. С некоторой вероятностью, если входящий массив совпадает с ними, она будет принимать те же решения, которая принимала раньше. Таким образом, если поначалу эти действия будут абсолютно хаотичными, то с течением времени (очень небольшого, т.к. даже сейчас это 250 генераций в секунду) они станут частично повторяющимися, т.е. если код менял в текстовом файле 1-ю и 2-ю строку местами раньше, то он будет тоже самое делать и сейчас — таким образом, будет видна так сказать «осмысленность» действия, потому что оно повторяется. Но не всегда будет применяться память из-за вероятности в принятии решений — возможна и рандомная генерация на знакомый образ. И то, что такая «осмысленность» с нашей, человеческой точки зрения, не будет иметь смысла, будет иметь смысл с точки зрения кода. У него родится собственное поведение пусть даже нам не понятное и кажущееся бредом. Поэтому, когда вы спрашиваете меня: «Вот вы видите еду и человек не ест, вы можете по факту наблюдения вывести условия?», то поймите, что тут нет задачи вывести условия или что-то там предсказать, вмешиваться в какие-то наблюдения и т.д. Я априори знаю, что если он не ест, то на это есть причина, и если он ест, то на это есть причина, и если он поднял руку, на это есть причина, и любое его действие обусловлено причиной, которую мне знать совершенно не важно, мне главное, что модель «внешний фактор-реакция» работает тут на 100%.

                                2. «14 или 15 млрд. лет — это не принципиальная разница по сравнению с жизнью человека.» — т.е. вы по-прежнему считаете, что генерация кода займет примерно столько времени? Зачем гадать на кофейной гуще? — когда код будет завершен, это время само себя покажет довольно быстро и тут сразу не подсчитаешь все возможные варианты, да это и не нужно, потому что не нужен полный перебор тем более непоследовательный. Это не брутфорс и не тупая генерация в лоб строки перебором символов — посмотрите внимательно на код — там генерируются структуры, а не отдельные символы.

                                3. По поводу темы «Искусственной жизни» я вам рекомендую поизучать внимательнее тему. Прочтите, например, книгу «Программирование искусственного интеллекта в приложениях» (М. Тим Джонс) раздел 7, иначе нормальное обсуждение алгоритмов, которые лично у меня не вызывают сомнений, перетечет в абстрактные размышления об «интерпретации наблюдателя», совершенно здесь неуместные. И когда вы прочтете, каким образом «хищники» становятся вдруг хищниками, а «травоядные» травоядными, то у вас, поверьте, отпадут все вопросы по поводу почему они вдруг гоняются друг за другом и их поведение так напоминает поведение хищников и травоядных в биологической природе.
                                  0
                                  Простой вопрос. Как вы поймете, что код с генерированный вашей программы готов? Вы делаете случайное дерево операций с какой-то функцией вероятности с памятью. Возможно она где-то будет находить стационарные решения. Но это нужно доказывать, что будет устойчивое решение. Но главный вопрос
                                  Кроме того, этот код так или иначе всё равно выполняет какую-то нужную человеку задачу и очень из-за этого ограничен рамками.

                                  Кто определяет что эта задача нужна человеку или нет? Ну наверное только человек. Вы же не предлагаете человеку проверять нужность каждого стационарного кода?
                                  Так вы предлагаете создавать случайное синтаксическое дерево на основе лексем языка python. Можно это делать на лексемах абстрактной машине и потом написать транслятор этого дерева на любой язык программирования. С точки зрения проектирования — это более правильный путь, так как вы не будите мешать синтаксис с лексикой.
                                  Вы поинтересуйтесь профессией QA Engineer и почему она не исчезнет. Почему автоматического тестирования недостаточно, почему нужен code review. Под этим всем есть строгие математические доказательства. ИИ этих проблем не решит.
                                  Спасибо, книжку посмотрю. Но надо понимать, что ИИ не отменяет математики, теоремы Гёделя. Даже если о нем рассказывают талантливые популяризаторы.
                                    0
                                    1. Для питона есть функции и объекты работы с его грамматикой
                                    docs.python.org/3/library/tokenize.html
                                    docs.python.org/3/library/parser.html
                                    docs.python.org/3/library/ast.html#module-ast
                                    На нем можно спокойно работать на уровне синтетического дерева.
                                    2.
                                    И кстати, если бы вы интересовались таким разделом ИИ, как «Искусственная жизнь», то знали бы, как без всяких осознаний своих желаний и потребностей генерируются на искусственном поле «хищники» и «травоядные»

                                    Просмотрел я раздел 7 рекомендованной книги.
                                    Там хищники, травоядные и трава заданы аксиоматически, по ЖЕЛАНИЮ программиста. Там также заданы операции по ЖЕЛАНИЮ автора. Там даже польза задана руками: для того чтобы размножится, нужно сожрать n других объектов.
                                    Там есть осознание через программиста.
                                      0
                                      Про АСТ мне уже говорили выше в комментах, я понял, спасибо.

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

                                      И если вы это читали, то должны были задать себе свой собственный вопрос: а созданы ли были эти стратегии по ЖЕЛАНИЮ программиста? И ответ должен был бы также легко найтись: конечно нет. Следовательно, нечто похожее на стратегии биологических животных (например, стадность) проявилось без всякого изначального умысла или желания просто методом генерации и обычного отбора. Понимаете, что в таком случае совершенно неважно, что хищники, травоядные и трава заданы аксиоматически? Важно то, что их поведение само по себе складывается и никем не задается. Также как и в моём случае — генератор я пишу по своему желанию, но я понятия не имею о том, какой код будет сгенерирован, что он будет делать и какие стратегии у него сложатся. Причем если бы вы читали внимательно, то обратили бы также внимание на то, что «аксиоматика» у них задана очень плохенько: хищники и травоядные не знают, что они хищники и травоядные, что они должны есть и могут умирать. У меня же в коде вообще будет неизвестно, что получится: «хищник», «травоядный», «трава», «голосовой хелпер», «шут гороховый», «мучитель принтера» и т.д. — может получиться всё, что угодно. В этом и есть смысл свободного кода: не пихать в него свои хотелки, желания, представления о том, как надо, ML/DL модели и прочее — мы просто даём жизни развиваться и самой себе вырабатывать желания, направления и т.п.

                                      AutoML я глянул, но это другая область. ML — это чисто математические алгоритмы и сейчас они все подвернуты под datascience с конкретными бизнес-задачами (в основном предиктивная аналитика и рекомендательные системы, а также всякого рода распознавалки). Я их в данный момент изучаю, но они вряд ли применимы для целей проекта в данной статье.

                                        0
                                        Вы игнорируете главный вопрос, как вы поймете, что программа созданная программой полезна?
                                        Программа «Hello world» никому не нужна. Ее каждый школьник напишет за пару минут.
                                        Если вы займетесь моделированием на уровне молекул, то там тоже образуются устойчивые структуры. Но вот к живым системам это не имеет никакого отношения.
                                        Так и с моделью жизни про хищников и травоядных. То чего описываете — это формирование устойчивых структур взаимодействия, которые зависят от начальной аксиоматики. И проблема таких систем не в том, что увидеть их, а в том чтобы появилось принципиально новое на уровне аксиом.
                                        В этой модели трава никогда не станет ядовитой. В природе сплошь и рядом, травы становятся ядовитыми, травоядные охотятся за хищниками. Хищники жрут траву. Хищники убивают хищников, травоядные травоядных. Также наблюдается дружба между хищниками и травоядными. Такие стратегии с заданной аксиоматикой никогда не появятся… А это значит, что такая модель, хоть и содержит какие-то элементы, наблюдаемые в природе, но совершенно не годятся для практического моделирования жизни. Это все пока еще не живое.
                                        Как итог.
                                        Применить мат. методы к конечной аксиоматике и найти какие-то устойчивые структуры в нем. Это не проблема. Это еще греки делали.
                                        Проблема в том, что аксиоматик бесконечно много. Структуры не устойчивы, то есть небольшое изменение в аксиоматике приводит к разным устойчивым структурам, разницу между ними нельзя выразить в качестве непрерывной функции от изменения аксиоматики.
                                        Пробуйте, пока вы еще не понимаете, почему ваша задача не решена. Только туда куда вы идете уже ходили. Собственно на ней и появилась теория алгоритмов. Набьете шишек, пойдете читать учебники.
                                          0
                                          Как вы поймете, что код с генерированный вашей программы готов?
                                          — вы про этот вопрос? Потому что вопрос про полезность, я, к сожалению, не нашел. Ну ладно отвечу на оба. Готов он будет тогда, когда заработают все 3 модуля и код будет выдавать мне свои рандомные действия, которые я постараюсь зафиксировать. Полезность же можно оценить только в том случае, если была задана изначальная цель и получены конечные результаты. В моем случае, если код просто будет работать, и «жить» так сказать, я сразу же пойму, что он для меня полезен. Если же говорить про другую «полезность», более общую, то её, при желании, можно будет увидеть, изменяя третий модуль, который отвечает за специализацию кода. Полезность будет понятна от постановки конкретной задачи. На данном этапе кроме задачи работоспособности и рандомной генерации с использованием памяти, нет.

                                          Если вы займетесь моделированием на уровне молекул, то там тоже образуются устойчивые структуры. Но вот к живым системам это не имеет никакого отношения.

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

                                          И проблема таких систем не в том, что увидеть их, а в том чтобы появилось принципиально новое на уровне аксиом.

                                          Зачем? Мне как раз достаточно увидеть принципиально новое в структурах взаимодействия, а не «на уровне аксиом». Вы когда держите телефон в руке, то ожидаете услышать что-то новое по телефону, а не какие-то изменения в руке.
                                          В этой модели трава никогда не станет ядовитой.
                                          Если прописать такую возможность, то станет. Но для авторов не было такой задачи.

                                          Такие стратегии с заданной аксиоматикой никогда не появятся…

                                          Вот поэтому когда вы генерите РАНДОМНЫЙ код, то никакой изначальной аксиоматики и не закладывается. Вообще никакой. В такой системе может происходить ВСЁ, что угодно: и ядовитая трава и весь спектр поведения.

                                          Это все пока еще не живое.

                                          Здесь вы правы, и именно это я и пытаюсь изменить.

                                          Только туда куда вы идете уже ходили.
                                          . Я боюсь, вы не до конца понимаете, что именно я пытаюсь написать. Уж точно не то, что делали греки. У меня нет тут никаких аксиоматик, — все кирпичи строятся рандомно (случайно), и структуры на их основе тоже рандомны, где же тут аксиоматика и греки?

                                          Набьете шишек, пойдете читать учебники.

                                          Спасибо за пожелание. Учиться, учиться и еще раз учиться (с)

                                            0
                                            Наличие структур в живых организмах — это необходимое условие, но оно не является достаточным. Если вы возьмете любое неживое, там тоже будут устойчивые структуры.
                                            Поэтому само наличие структур не может быть основанием, для утверждения о том, что это живое или не живое. Их отличает что-то другое. Но это действительно очень сложный вопрос. Точно не на эту задачу.
                                      0
                                      Если вам тема интересна. То сейчас развивается такое направление как autoML
                                      ru.wikipedia.org/wiki/%D0%90%D0%B2%D1%82%D0%BE%D0%BC%D0%B0%D1%82%D0%B8%D1%87%D0%B5%D1%81%D0%BA%D0%BE%D0%B5_%D0%BC%D0%B0%D1%88%D0%B8%D0%BD%D0%BD%D0%BE%D0%B5_%D0%BE%D0%B1%D1%83%D1%87%D0%B5%D0%BD%D0%B8%D0%B5
                                      По желанию, вы можете найти реализации его у Google, Microsoft и т.д.
                                      Идея там простая. Вы скармливаете данные, указываете тип задачи. Шайтан машина прогоняет ваши данные через множество моделей, в том числе и современных, основанных на последних работах по ИИ. Выстраивает рейтинг алгоритмов, моделей. Вы выбираете какой хотите или указываете критерии и получаете код. В MS на C#
                                        0
                                        если входящий массив совпадает с ними, она будет принимать те же решения, которая принимала раньше. Таким образом, если поначалу эти действия будут абсолютно хаотичными, то с течением времени они станут частично повторяющимися, — таким образом, будет видна так сказать «осмысленность» действия, потому что оно повторяется.

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

                                        Вы игнорируете главный вопрос, как вы поймете, что программа созданная программой полезна?

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

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

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

                                          … ничто не делает оценку качества сгенерированного кода.

                                          В этом и есть сложность — не давать критериев оценки, потому что они, с большой долей вероятности будут либо ошибочными, либо неполными — и тут дело не столько субъективном взгляде на критерии, а в том, что они до конца неизвестны даже науке. В природе есть только один наиболее объективный критерий — приспособленность того или иного экземпляра к окружающей среде. Здесь нужно повторить то же самое. Ну совсем без них, наверное, не обойтись, потому что мы работаем в среде программирования и ОС, но я пока вижу только один возможный — выполнение кода без ошибок, тогда except будет утилизировать нерабочий код, а весь рабочий код будет исполняться, все остальное должно утилизироваться ОС, оставляя «жить» только приспособленные экземпляры.

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

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

                                          А что она делает?

                                          Не поверите, но это самый частый вопрос, который я слышу от программистов. К сожалению долгое прибывание в профессиональной среде обязывает задавать такие вопросы, потому что когда ты изо дня в день, из года в год получаешь задачи и пишешь программы, которые их решают, то складывается впечатление, что и любой следующий код должен ЧТО-ТО делать, направленное на решение задачи. Только вот когда задача стоит так: «Напиши мне аналог биологической жизни», то тут без биологической базы невозможно даже подступиться к ее решению: здесь не работают ни ML, ни DL, ни математика, ни какие-либо еще известные алгоритмы (во всяком случае целиком). При этом само решение — это как раз «булькать и что-то там крякать» — весьма непривычный для любого программиста метод. Подобное «бульканье и кряканье» в биологической природе приводит к тому, что, например, бактерии вырабатывают резистентность к антибиотикам, вирусы — к противовирусным, а опухоли сопротивляются химиотерапии — там не заседают штабы, которые планируют как им сопротивляться и что им делать, — они «булькают»(рекомбинируя) и «крякают» (мутируя), и за счет этого появляются поколения, которые живут дальше. Рандомно! И для того, чтобы это спокойно воспринимать как данность, достаточно прочесть хотя бы парочку научно-популярных книжек по эволюционной биологии, чего не делают не то, что подавляющее число программистов (им это просто не нужно для их задач), а вообще подавляющее число людей (им просто неинтересно). Но если вы хотите разбираться в подобных вещах и иметь взгляд, отличающийся от обывательского, я могу порекомендовать вам прочесть, например, «От атомов к древу» Ястребова или, что еще лучше, — «Рождение сложности» Маркова, а также ознакомиться в общих чертах с генетическими алгоритмами (в особенности с рекомбинацией и мутацией).

                                            0
                                            а также ознакомиться в общих чертах с генетическими алгоритмами (в особенности с рекомбинацией и мутацией)
                                            Мой вопрос про отсутствие у вас фитнес-функции должен был как бы намекнуть, что мне про ГА рассказывать не надо. )) Не буду настаивать. Начнёте экспериментировать, сами заметите, что без отбора один единственный рандом даст только шум. Главное проверять свои идеи на практике, так как мы все так или иначе до экспериментов плаваем в своих фантазиях.
                                              0
                                              Да, сорри, невнимательно прочёл. Если говорить про генератор в этой статье, то фитнес-функция здесь, очевидно, не нужна, потому что код еще не самодостаточен: этот генератор только часть организма. В самом же организме очевидно, без отбора не обойтись. Но я не хочу конкретизировать фитнес-функцию и устанавливать критерии сам, мне нужна ее генерация и возможно закрепление через определенное время (возможно через переключатели) — тут пока еще общая концепция в голове разрабатывается.
                                  0
                                  В начале было слово и это слово Бог. Бог вездесущ, то есть присутствует везде. Применительно к материальному миру это чего либо начало, центр, ось, ноль, рут, точка покоя и т.д. и т.п. В математике Марка Родина это цифра девять. В каждой молекуле, клетке, человеке Бог. Человека, клетку, организм, можно представить как точку куда стекается вся информация и в этом месте принимается решение (важная-неважная). Живые организмы начинаются с одной клетки и смысл её существования заключается в балансе трех вещей: еда(ресурсы), секс(размножение), доминирование (статус, положение среди себе подобных, в пространстве и т.д.). Организм обязан соблюдать баланс этих трех иначе погибнет(примет решение произвести апоптоз). Два типа взаимодействия: Симбиоз (совместное взаимовыгодное развитие друг друга), Паразитизм (захват и присвоение у другого). Организм из рецепторов сохраняет объекты окружающего мира путём его маркировки по трем этим параметрам(уровень еды, уровень избытка, уровень доминирования) и присваивает тройной уникальный код (еда, секс, статус), аналог кодонов в ДНК. Что, когда, как и кому делать хранится в ДНК (база данных). Тут скорее не ELSE IF, а уже CASE )))

                                  Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                  Самое читаемое