страница 1
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
Похожие работы
|
Введение в архитектуру нейронных сетей и алгоритм обратного распространения - страница №1/1
Глава 5. Введение в архитектуру нейронных сетей и алгоритм обратного распространения В данной главе вводится понятие многослойных нейронных сетей, обучение в которых осуществляется с помощью алгоритма обратного распространения. Это, пожалуй, самый важный алгоритм обучения в нейронных сетях, внесший значительный вклад в развитие методов расчета, которые имеют естественное происхождение. После детального изучения нейронных сетей и алгоритма обратного распространения рассматривается использование нейронных сетей при разработке ИИ для игр. Существует множество вариантов нейронных сетей и обучающих алгоритмов, однако в данной главе основное внимание уделяется многослойным сетям, в которых используется алгоритм обратного распространения. Сначала описываются компоненты нейронных сетей, обсуждается алгоритм обучения и ряд проблем, которые могут возникнуть при его применении. Приводится пример простой сети и последовательно разбирается работа алгоритма обратного распространения. Наконец, нейронные сети рассматриваются как средство создания «живых» персонажей в компьютерных играх. Искусственные нейронные сети имитируют работу мозга. Информация передается между нейронами, а структура и вес нервных окончаний определяют поведение сети. Однослойные перцептроны Однослойный перцептпрон (Single layer perceptron - SLP) представляет собой концептуальную модель, которая состоит из одного процессора. Каждое соединение от входа к ядру включает коэффициент, который показывает фактор веса, и обозначается с помощью веса который определяет влияние ячейки ui на другую ячейку. Положительные веса показывают усиление, а отрицательные - запрещение. Совместно с входами в ячейку они определяют поведение сети. Схема однослойного перцептрона представлена на рис. 5.2. Ячейка на рис. 5.2 включает три входа (и(, и2 и и3). Кроме этого, есть вход смещения (wQ), о котором будет рассказано позже. Каждое входное соединение имеет вес (wj, w2 и w3). Наконец, существует единый выход, О. Состояние нейрона обозначено как у и определяется уравнением 5.1. (5.1) Рис. 5.2. Однослойный перцептрон Выражение, показанное в уравнении 5.1, является функцией, которая сумми-рует сигналы на всех входах с учетом веса, а затем добавляет смещение. Затем ре-зультат передается в активационную функцию, которая может быть определена так,как показано в уравнении 5.2 (в данном случае функция является пороговой). (5.2) Если значение состояния больше нуля, то выходное значение будет равно 1. Иначе оно составит -1. Моделирование булевых выражений с помощью SLP Хотя однослойный перцептрон является очень простой моделью, ее возможности весьма велики. Например, можно легко сконструировать базовые логические функции, как показано на рис. 5.3. Ключ И Ключ ИЛИ Ключ НЕ Рис. 5.3. Логические функции, построенные с помощью однослойных перцептронов Вспомните, что функция И имеет значение 1, если оба входа равны 1, в противном случае функция возвращает значение 0. Поэтому если заданы оба входа I (вектор и = (1,1)), то, используя активационную функцию из уравнения 5.2 в качестве порога, получим следующий результат: Как показывают оба примера, модель простого перцептрона правильно реализует логическую функцию И (а также функции ИЛИ и НЕ). Однако однослойный перцептрон не может смоделировать такую логическую функцию, как исключающее ИЛИ (XOR). Эта неспособность к моделированию функции XOR известна как проблема отделимости. Из-за нее Марвин Мински и Сеймур Паперт в 1960-е гг. уничтожили результаты своих разработок в области связей и стали заниматься стандартными подходами к изучению ИИ. Проблема отделимости была легко решена путем добавления одного или нескольких слоев между входами и выходами нейронной сети (см. рис. 5.4). Это привело к созданию модели, известной как многослойные перцептроны (Multiple layer perceptron - MLP). Многослойные сети Многослойные сети позволяют создавать более сложные, нелинейные связи между входными данными и результатами на выходе. На рис. 5.4 многослойная сеть состоит из входного, промежуточного (или скрытого) и выходного слоев. Входной слой представляет входы в сеть и не состоит из ячеек (нейронов) в традиционном смысле слова. В этом примере для каждой ячейки задан идентификатор un. Две входные ячейки называются (u1, u2), две скрытые ячейки (u3, u4), а выходная ячейка - (u5). Обозначение соединений в сети стандартизовано в форме w13 и отображает связь с учетом веса между и3 и иг Рис. 5.4. Многослойные перцептроны В то время как входные ячейки (u, и u2) просто задают входное значение для сети, скрытые и выходные ячейки представляют собой функцию (уравнение 5.1). Результат суммирования дополнительно обрабатывается функцией сжатия (обычно сигмоид), результат которой выдается на выходе из ячейки. Функция сигмоида показана на рис. 5.5. Теперь изучим полную картину ячейки в нейронной сети. На рис. 5.6 изображена выходная ячейка для сети, показанной на рис. 5.4. Выходная ячейка u5 получает результат от двух скрытых ячеек (u3и u4) через веса w53 и w54 соответственно. Здесь важно отметить, что функция сигмоида должна быть применена и к скрытым узлам сети на рис. 5.6, но в данном случае они опускаются, чтобы Функция сжатия (сигмоид) проиллюстрировать только обработку выходного слоя. Уравнение на рис. 5.6 показывает сумму результатов входов скрытого слоя с весами соединений. Функция f(x) представляет сигмоид, примененный к результату. В сети со скрытым и выходным слоями сначала выполняется расчет скрытого слоя, а затем его результаты используются для расчета выходного слоя. Выполнение алгоритма начинается с создания произвольно сгенерированных весов для многослойной сети. Затем процесс, описанный ниже, повторяется до тех пор, пока средняя ошибка на входе не будет признана достаточно малой: 1. Берется пример входного сигнала Е с соответствующим правильным значением выхода С. 2. Рассчитывается прямое распространение Е через сеть (определяются весовые суммы S( и активаторы и. для каждой ячейки). 3. Начиная с выходов, выполняется обратное движение через ячейки выходного и промежуточного слоя, при этом программа рассчитывает значения ошибок (уравнения 5.3 и 5.4): (5.3) (5.4) (Обратите внимание, что m обозначает все ячейки, связанные со скрытым узлом, w - заданный вектор веса, а и - активация). 4. Наконец, веса в сети обновляются следующим образом (уравнение 5.5 и 5.6): (5.5) (5.6) Здесь р представляет коэффициент обучения (или размер шага). Это небольшое значение ограничивает изменение, которое может произойти при каждом шаге. Совет Параметр р можно определить таким образом, чтобы он указывал скорость продвижения алгоритма обратного распространения к решению. Лучше начать тестирование с небольшого значения (0,1) и затем постепенно его повышать. Продвижение вперед по сети рассчитывает активации ячеек и выход, профдвижение назад - градиент (по отношению к данному примеру). Затем веса I обновляются таким образом, чтобы минимизировать ошибку для данного I входного сигнала. Коэффициент обучения минимизирует процент изменения, (котороеможет произойти с весами. Хотя при небольшом коэффициенте процесс может занять больше времени, мы минимизируем возможность пропуска правильной комбинации весов. Если коэффициент обучения слишком веник, сеть может никогда не сойтись, то есть не будут найдены правильные [веса связей. Рассмотрим пример функционирования сети в процессе обучения. Пример алгоритма обратного распространения Изучим работу алгоритма обратного распространения, взяв в качестве примера сеть, показанную на рис. 5.7. Рис. 5.7. Пример алгоритма обратного распространения Развитие вперед Сначала выполняется расчет движения входного сигнала по сети. Рассмотрим значения для скрытого слоя: Вспомните, что f(x) является активационной функцией, то есть функцией сиг-моида (уравнение 5.7): (5.7) Теперь сигнал дошел до скрытого слоя. Конечный шаг заключается в том, чтобы переместить сигнал из скрытого слоя в выходной слой и рассчитать значения на выходе из сети: Правильной реакцией нейронной сети на тестовый входной сигнал является 1,0; значение, рассчитанное сетью, составляет 0,78139. Это не так уж и плохо, однако можно уменьшить значение ошибки, применив для сети алгоритм обратного распространения. Для коррекции весовых коэффициентов в сети обычно используется среднеквадратичная ошибка. Для одного узла ошибка определяется в уравнении 5.8: Поэтому ошибка составляет: Алгоритм обратного распространения для ошибки Теперь применим обратное распространение, начиная с определения ошибки в выходном и скрытых узлах. Используя уравнение 5.1, рассчитаем ошибку в выходном узле: Теперь следует рассчитать ошибку для двух скрытых узлов. Для этого используется производная функция сигмоида (уравнение 5.5), которая показана в виде уравнения 5.9: (5.9) Используя уравнение 5.2, рассчитаем ошибки для скрытых узлов: Изменения весов соединений Теперь, когда рассчитаны значения ошибок для выходного и скрытого слоев, можно с помощью уравнений 5.3 и 5.4 изменить веса. Используем коэффициент обучения (р), равный 0,5. Сначала следует обновить веса для соединений между выходным и скрытым слоями: Теперь нужно обновить смещение для выходной ячейки: Для w5.4 вес уменьшен, а для w5.3 - увеличен. Смещение было обновлено для повышения возбуждения. Теперь нужно показать изменение весов для скрытого слоя (для входа к скрытым ячейкам): Последний шаг - это обновление смещений для ячеек: Обновление весов для выбранного обучающего сигнала завершается. Проверим, что алгоритм действительно уменьшает ошибку на выходе, введя тот же обучающий сигнал еще раз: Вспомните, что начальная ошибка была равна 0,023895. Текущая ошибка составляет 0,022, а это значит, что одна итерация алгоритма обратного распространения позволила уменьшить среднюю ошибку на 0,001895. Расчет поведения ИИ для компьютерных игр Алгоритм обратного распространения применяется при создании нейроконтрол-леров для персонажей компьютерных игр. Нейроконтроллерами (Neurocontroller) обычно называются нейронные сети, которые используются при управлении. В этом приложении мы задействуем нейронную сеть, чтобы выбрать действие из доступного списка на основании того, в какой окружающей среде находится персонаж. Термины «персонаж» и «агент» далее употребляются как синонимы. Причина использования нейронных сетей в качестве нейроконтроллеров заключается в том, что невозможно задать для ИИ персонажа игры поведение, которое охватывало бы все возможные в окружающей среде ситуации. Поэтому необходимо обучить нейронную сеть на ограниченном количестве примеров (то есть образцов поведения в зависимости от обстановки), а затем позволить ей самостоятельно генерировать поведение во всех прочих ситуациях. Способность генерировать правильную реакцию на различные ситуации, не входящие в набор обучающих, является ключевым фактором при создании нейроконтроллера. Другим преимуществом нейроконтроллера является то, что он не является строго заданной функцией, которая обеспечивает взаимодействие между окружающей средой и реакцией на нее. Незначительные изменения в окружающей среде могут вызвать различную реакцию у нейроконтроллера, отчего поведение персонажа выглядит более естественным. Фиксированные деревья поведения или конечные автоматы вызывают предсказуемую реакцию, что довольно плохо отражается на игре. Как показано на рис. 5.8, окружающая среда предоставляет для персонажа определенную информацию, которая затем передается агенту. Процесс «восприятия» окружающей среды называется предчувствием. Нейроконтроллер обеспечивает возможность выбора действия, посредством которого персонаж взаимодействует с окружающей средой. Окружающая среда при этом изменяется, персонаж вновь обращается к восприятию, и цикл возобновляется. В предыдущем примере описывалась нейронная сеть с одним выходом. В компьютерных играх используется несколько иная архитектура - сеть, построенная по принципу «победитель получает все». Такие архитектуры полезны в том случае, если выходы должны быть разделены на несколько классов (рис. 5.9). Группа ячеек по принципу «победитель получает все» Входной слой Скрытый слой Выходной слой Рис. 5.9. Группа «победитель получает все» В сети, созданной по принципу «победитель получает все», выходная ячейка с большей суммой весов является «победителем» группы и допускается к действию. В рассматриваемом приложении каждая ячейка представляет определенное поведение, которое доступно для персонажа в игре. В качестве примеров поведения можно назвать такие действия, как выстрелить из оружия, убежать, уклониться и др. Срабатывание ячейки в группе по принципу «победитель получает все» приводит к тому, что агент выполняет определенное действие. Когда агенту вновь позволяется оценить окружающую среду, процесс повторяется. На рис. 5.10 представлена сеть, которая использовалась для тестирования архитектуры и метода выбора действия. Четыре входа обозначают «здоровье персонажа» (0 - плохое, 2 - хорошее), «имеет нож» (1, если персонаж имеет нож, 0 -в противном случае), «имеет пистолет» (1, если персонаж имеет пистолет, 0 -в противном случае) и «присутствует враг» (количество врагов в поле зрения). Выходы определяют поведение, которое выберет персонаж. Действие «атаковать» приводит к тому, что персонаж атакует врагов в поле зрения, «бежать» Группа ячеек по принципу «победитель получает все» Входной слой Скрытый слой Выходной слой Рис. 5.10. Архитектура нейроконтроллера для компьютерных игр вынуждает персонаж убегать, «уворачиваться» приводит к произвольному движению персонажа, а «прятаться» вынуждает персонаж искать укрытие. Это высокоуровневые образы поведения, и предполагается, что подсистема поведения будет выбирать действие и следовать ему. Примечание Выбранная здесь архитектура (три скрытые ячейки) была определена способом проб и ошибок. Три скрытые ячейки могут быть обучены для всех представленных примеров со 100% точностью. Уменьшение количества ячеек до двух или одной приводит к созданию сети, которая не может правильно классифицировать все примеры. Обучение нейроконтроллера Нейроконтроллер в игровой среде представляет собой постоянный элемент персонажа. Дальше мы обсудим обучение нейроконтроллера в режиме реального времени. Обучение нейроконтроллера состоит в предоставлении обучающих примеров из небольшой группы желательных действий. Затем следует выполнение алгоритма обратного распространения с учетом желаемого результата и действительного результата. Например, если персонаж имеет пистолет, здоров и видит одного врага, желаемое действие - атаковать. Однако если персонаж здоров, имеет нож, но видит двух врагов, то правильное действие - спрятаться. Данные для тестирования представляют собой несколько сценариев с набором действий. Поскольку требуется, чтобы нейроконтроллер вел себя так же, как настоящий человек, мы не будем обучать его для каждого случая. Сеть должна рассчитывать реакцию на входы и выполнять действие, которое будет похожим на обучающие сценарии. Примеры, которые использовались для обучения сети, представлены в табл. 5.1. Таблица 5.1. Примеры, которые используются для обучения нейроконтроллера
Данные, приведенные в табл. 5.1, были переданы сети в произвольном порядке во время обучения с помощью алгоритма обратного распространения. График снижения средней ошибки показан на рис. 5.11. В большинстве случаев сеть успешно проходит обучение на всех представленных примерах. При некоторых запусках один или два примера приводят Чтобы протестировать нейроконтроллер, сети были представлены новые примеры. Это позволило определить, как сеть будет реагировать на сценарии, о которых ей ничего не известно. Данные тесты дают ответ на вопрос, насколько хорошо нейроконтроллер может генерировать и выполнять нужные действия, реагируя на непредвиденную ситуацию. Если предложить нейроконтроллеру сценарий, в котором персонаж полностью здоров, владеет оружием двух видов и видит двух врагов (то есть 2:1:1:1), нейроконтроллер выберет действие «атаковать». Это разумная реакция на данную ситуацию. Теперь рассмотрим сценарий, в котором персонаж полностью здоров, владеет ножом и видит трех врагов (то есть 2:1:0:3). Нейроконтроллер выбирает действие «спрятаться», вполне разумный выбор в данной ситуации. Другие примеры показаны в табл. 5.2. Таблица 5.2. Примеры, иллюстрирующие правильную генерацию действий
Таким образом, нейроконтроллер правильно генерирует действие из заданного набора в ответ на новую обстановку (табл. 5.2). Хотя его не обучали конкретно для этих примеров, он способен правильно на них реагировать. Совет Исходный код алгоритма обратного распространения вы можете найти в архиве с примерами на сайте издательства «ДМК Пресс» www.dmk.ru. Обсуждение исходного кода Рассмотрим исходный код реализации алгоритма обратного распространения для конфигурируемой сети, а также код, который выполняет обучение и тестирование нейроконтроллера. Глобальные константы и переменные показаны в листинге 5.1. Листинг 5.1. Гповальные константы и переменные для нейронной сети и алгоритма обратного распространения #define INPUT_NEURONS 4 #define HIDDEN_NEURONS 3 #de-fine OUTPUT_NEURONS 4 I* Веса */ /* Вход скрытых ячеек (со смещением) */ double wih[INPUT_NEURONS+l][HIDDEN_NEURONS]; /* Вход выходных ячеек (со смещением) */ double who[HIDDEN_NEURONS+l][OUTPuT_NEURONS]; /* Активаторы */ double inputs[INPUT_NEURONS]; double hidden[HIDDEN_NEURONS]; double target[OUTPUT_NEURONS]; double actual[OUTPUT_NEURONS]; /* Ошибки */ double erro[OUTPUT_NEURONS]; double errh[HIDDEN_NEURONS]; Веса определяются как веса соединений между входным и скрытым (wih), а также между скрытым и выходным слоями (who). Вес соединения между и5 и и, (рис. 5.10) является весом входа в скрытый слой, представленный wih[0] [0] (так как и, - это первая входная ячейка, а и5 - первая скрытая ячейка, начиная с нуля). Данный вес обозначается как w5.1. Вес w11.7 (соединение между ячейкой и„ выходного слоя и и7 скрытого слоя) равен who [ 2 ] [ 3 ]. Веса смещения занимают последнюю строку в каждой таблице и идентифицируются с помощью значения +1 в массивах wih и who. Значения сигналов хранятся в четырех массивах. Массив inputs определяет значение входных ячеек, массив hidden содержит выход для скрытых ячеек, массив target предоставляет желаемое значение сети для заданных входов, а массив actual отображает реальный результат работы сети. Ошибки сети предоставляются в двух массивах. Массив егго хранит ошибку для каждой входной ячейки. Массив errh содержит ошибки скрытых ячеек. Чтобы найти произвольные веса для инициализации сети, создается группа макросов, показанная в листинге 5.2. Листинг 5.2. Макросы и символьные константы для алгоритма обратного распространения #define LEARN_RATE 0.2 /* Коэффициент обучения */ #define RAND_WEIGHT ( ((float)rand() / (float)RAND_MAX) - 0.5) #define getSRandO ((float)rand() / (float)RAND_MAX) #define getRand(x) (int)((x) * getSRandO) #define sqr(x) ((x)*(x)) Веса произвольно выбираются в диапазоне (от -0,5 до 0,5). Коэффициент обучения задается как 0,2. Диапазон весов и коэффициент обучения могут быть изменены в зависимости от проблемы и требуемой точности решения. Существуют три вспомогательные функции, которые используются, чтобы задавать произвольные веса для сети, а также для работы алгоритма. Они приведены в листинге 5.3. Листинг 5.3. Вспомогательные функции для алгоритма обратного распространения void assignRandomWeights( void ) { int hid, inp, out; for (inp = 0 ; inp } for (hid = 0 ; hid OUt++) { who[hid][out] = RAND_WEIGHT; } } - double sigmoid( double val ) { return (1.0 / (1.0 + exp(-val))); double sigmoidDerivative( double val ) { return ( val * (1.0 - val) ); Функция assignRandomWeights произвольно задает вес для всех соединений сети (включая все смещения). Функция sigmoid определяет значение функции сжатия (сигмоида), которая используется при прямом вычислении (уравнение 5.5). Функция sigmoidDerivative устанавливает значение производной функции sigmoid и используется при обратном распространении ошибки. Следующая функция реализует фазу прямого вычисления алгоритма (листинг 5.4). void feedForward( ) { int inp, hid, out; double sum; /* Вычислить вход в скрытый слой */ for (hid = 0 ; hid sum = 0.0; for (inp = 0 ; inp
/* Добавить смещение */ sum += wih[INPUT_NEURONS][hid]; hidden[hid] = sigmoid( sum ); } /* Вычислить вход в выходной слой */ for (out = 0 ; out sum =0.0; for (hid = 0 ; hid
/* Добавить смещение */ sum += who№IDDEN_NEURONS] [out] ; actual[out] = sigmoid( sum ); } } Как показано в листинге 5.4, алгоритм прямого распространения начинает свою работу с расчета активации для скрытых слоев с учетом входов из входного слоя. Смещение добавляется в ячейку до вычисления функции сигмоида. Затем аналогичным образом рассчитывается выходной слой. Обратите внимание, что сеть может иметь одну или несколько выходных ячеек. Поэтому обработка выходных ячеек помещена в цикл, чтобы рассчитать все необходимые активации на выходе. Алгоритм обратного распространения показан в листинге 5.5. Листинг 5.5. Алгоритм обратного распространения void backPropagate( void ) { int inp, hid, out; /* Вычислить ошибку выходного слоя (шаг 3 для выходных ячеек) */ for (out = 0 ; out OUt++) { errofout] = (target[out]-actual[out])* sigmoidDerivative( actual[out]); /* Вычислить ошибку скрытого слоя (шаг 3 для скрытого слоя) */ for (hid = 0 ; hid errh[hid] = 0.0; for (out = 0 ; out
errhfhid] *= sigmoidDerivative( hidden[hid] ); } /* Обновить веса для выходного слоя (шаг 4 для выходных ячеек) */ for (out = 0 ; out who[hid][out] += (LEARN_RATE * erro[out] * hidden[hid]); /* Обновить смещение */ who[HIDDEN_NEURONS][out] += (LEARN_RATE * erro[out]); /* Обновить веса для скрытого слоя (шаг 4 для скрытого слоя) */ for (hid = 0 ; hid wih[inp][hid] += (LEARN_RATE * errh[hid] * inputs[inp]); } /* Обновить смещение */ wih[INPUT_NEURONS][hid] += (LEARN_RATE * errh[hid]); } Данная функция следует алгоритму, описанному в разделе «Пример алгоритма обратного распространения». Сначала рассчитывается ошибка выходной ячейки (или ячеек) с использованием действительного и желаемого результатов. Далее определяются ошибки для скрытых ячеек. Наконец, обновляются веса для всех соединений в зависимости от того, находятся они во входном или выходном скрытом слое. Здесь важно отметить, что в алгоритме не рассчитывается вес для смещения, а просто применяется ошибка к самому смещению. В алгоритме прямого распространения смещение добавляется без использования для него веса. Данный алгоритм позволяет рассчитать выход многослойной нейронной сети и ее изменения с помощью алгоритма обратного распространения. Рассмотрим исходный код примера, в котором этот алгоритм используется для реализации нейроконтроллера. В листинге 5.6 приведена структура, задачей которой является отображение примеров для обучения. Структура задает входы (здоровье, нож, пистолет, враг), атакже значение желаемого результата (массив out). Листинг 5.6 также включает инициализированные данные для обучения сети. Листинг 5.6. Отображение данных для обучения нейроконтроллера typedef struct { double health; double knife; double gun; double enemy;' double out[OUTPUT_NEURONS]; } ELEMENT; #define MAX_SAMPLES 18 /* Н К G Е A R W Н */ ELEMENT samples[MAX_SAMPLES] = { { 2.0, 0.0, 0.0, 0.0, {0.0, 0.0, 1.0, 0.0} }, { 2.0, 0.0, 0.0, 1.0, {0.0, 0.0, 1.0, 0.0} }, { 2.0, 0.0, 1.0, 1.0, {1.0, 0.0, 0.0, 0.0} }, { 2.0, 0.0, 1.0, 2.0, {1.0, 0.0, 0.0, 0.0} }, { 2.0, 1.0, 0.0, 2.0, {0.0, 0.0, 0.0, 1.0} }, { 2.0, 1.0, 0.0, 1.0, {1.0, 0.0, 0.0, 0.0} }, { 1.0, 0.0, 0.0, 0.0, {0.0, 0.0, 1.0, 0.0} }, { 1.0, 0.0, 0.0, 1.0, {0.0, 0.0, 0.0, 1.0} }, { 1.0, 0.0, 1.0, 1.0, {1.0, 0.0, 0.0, 0.0} }, { 1.0, 0.0, 1.0, 2.0, {0.0, 0.0, 0.0, 1.0} }, { 1.0, 1.0, 0.0, 2.0, {0.0, 0.0, 0.0, 1.0} }, { 1.0, 1.0, 0.0, 1.0, {0.0, 0.0, 0.0, 1.0} }, { 0.0, 0.0, 0.0, 0.0, {0.0, 0.0, 1.0, 0.0} }, { 0.0, 0.0, 0.0, 1.0, {0.0, 0.0, 0.0, 1.0} }, { 0.0, 0.0, 1.0, 1.0, {0.0, 0.0, 0.0, 1.0} }, { 0.0, 0.0, 1.0, 2.0, {0.0, 1.0, 0.0, 0.0} }, { 0.0, 1.0, 0.0, 2.0, {0.0, 1.0, 0.0, 0.0} }, { 0.0, 1.0, 0.0, 1.0, {0.0, 0.0, 0.0, 1.0} } Вспомните, что вход «здоровье» может иметь три значения (2 - здоров, 1 - не полностью здоров и 0 - нездоров). Входы «нож» и «пистолет» являются булевыми (1 - если предмет есть, 0 - если его нет), а вход «враг» показывает количество врагов в пределах видимости. Действия также являются булевыми, где ненулевое значение показывает выбранное действие. Поскольку сеть построена по принципу «победитель получает все», следует закодировать простую функцию, которая будет определять выходную ячейку с самой большой суммой весов. Она выполняет поиск по вектору максимального значения и возвращает строку, которая отображает нужное действие (листинг 5.7). Возвращенное значение затем может использоваться в качестве индекса в массиве строк strings и позволяет вывести текст, показывающий реакцию. char *strings[4]={"Attack", "Run", "Wander", "Hide"}; int action( double *vector ) { int index, sel; double max; sel = 0; max = vector[sel]; ; for (index = 1 ; index max) { max = vector[index]; sel = index; } } return( sel ) ; } Наконец, в листинге 5.8 показана функция main, которая выполняет обучение и тестирование нейроконтроллера. Листинг 5.8. Пример функции main, которая используется для обучения и тестирования нейроконтроллера int main() { double err; int i, sample=0, iterations=0; int sum = 0; out = fopen("stats.txt", "w"); /* Инициализировать генератор случайных чисел */ srand( time(NULL) ) ; assignRandomWeights(); /* Обучить сеть */ while (1) { if (++sample == MAX_SAMPLES) sample = 0; inputs[0] = samples[sample].health; inputs[l] = samples[sample].knife; inputs[2] = samples[sample].gun; inputs[3] = samples[sample].enemy; target[0] = samples[sample].out[0] target[1] = samples[sample].out[1] target[2] = samples[sample].out[2] target[3] = samples[sample].out[3] feedForward(); err = 0.0; for (i = 0 ; i err += sqr( (samples[sample].out[i] - actual[i]) ); } err = 0.5 * err; fprintf(out, "%g\n", err); printf("mse = %g\n", err); if (iterations++ > 100000) break; backPropagate(); } /* Проверить сеть */ for i=0 ; i inputs[0] = samples[i].health; inputs[l] = samples[i].knife; inputs[2] = samples[i].gun; inputs[3] = samples[i].enemy; target[0] = samples[i].out[0]; target[1] = samples[i].out[1]; target[2] = samples[i].out[2]; target[3] = samples[i].out[3]; feedForward(); if (action(actual)!= action(target)) { } ((float)sum / (float)MAX_SAMPLES) * 100.0); /* Выполнение тестов */ /* Здоровье Нож Пистолет Враг*/ inputs[0] = 2.0; inputs[1] = 1.0; dnputs[2] = 1.0; inputs[3] = 1.0; feedForward(); printf("2111 Action %s\n", strings[action(actual)]); inputs[0] = 1.0; inputs[l] = 1.0; inputs[2] = 1.0; inputs[3] = 2.0; feedForward(); printf("1112 Action %s\n", strings[action(actual)]); inputs[0] = 0.0; inputs[l] = 0.0; inputs[2] = 0.0; inputs[3] = 0.0; feedForward(); printf("0000 Action %s\n", strings[action(actual)]) ; inputs[0] = 0.0; inputs[l] = 1.0; inputs[2] = 1.0; inputs[3] = 1.0; feedForward(); printf("0111 Action %s\n", strings[action(actual)]) ; inputs[0] = 2.0; inputs[l] = 0.0; inputs[2] = 1.0; inputs[3] = 3.0; feedForward(); printf("2013 Action %s\n", strings[action(actual)]); inputs[0] = 2.0; inputs[1] = 1.0; inputs[2] = 0.0; inputs[3] = 3.0; feedForward(); printf("2103 Action %s\n", strings[action(actual)]); inputstO] = 0.0; inputs[1] = 1.0; inputs[2] = 0.0; inputs[3] = 3.0; feedForward(); printf("0103 Action %s\n", strings[action(actual)]); fclose(out); return 0; После инициализации генератора случайных чисел с помощью функции srand будут произвольно сгенерированы веса соединений сети. Затем для обучения сети выполняется цикл while. Примеры рассматриваются последовательно, а не в произвольном порядке. Выполняется расчет реакции сети на входной вектор, а затем - проверка средней ошибки. Наконец, запускается алгоритм обратного распространения, чтобы изменить веса соединений сети. После выполнения ряда итераций программа выходит из цикла, и сеть тестируется на точность на основе значений, заданных для обучения. После стандартного тестирования выполняется тестирование сети с примерами, которые не входили в начальную группу при обучении. Если нейронная сеть будет применяться в компьютерной игре, веса могут быть выведены в файл для последующего использования. На этом этапе сеть можно оптимизировать, чтобы уменьшить количество ресурсов, необходимых для расчетов. Когда нейроконтроллер встраивается в игру, его обучение считается завершенным и не может продолжаться дальше. Чтобы дать персонажу возможность обучаться, вы можете добавить в игру элементы алгоритма обратного распространения. Это позволит изменять веса в зависимости от ситуаций игрового процесса. Очень простой механизм может изменять веса нейроконтроллера на основании последнего действия, выполненного персонажем. Если действие привело к отрицательным последствиям, например, смерти персонажа, веса для действия в данной обстановке могут быть запрещены. Это снизит вероятность повторения указанного действия в будущем. Далее все ИИ-персонажи игры могут получать одни и те же знания, пользуясь тем, что называется «эволюцией Ламарка», при которой дети учатся на ошибках своих родителей. Пройдя несколько игр, персонажи постепенно будут стремиться избегать отрицательных результатов. Память нейроконтроллера В нейронной сети может быть сформирована функция памяти. Для всех входов создаются линии задержки (при этом входной вектор расширяется в одном или двух измерениях). Первый вход от окружающей среды не исчезает, а становится частью другого входа в сеть. Данная функция может быть выполнена для нескольких элементов, что позволяет запрограммировать для персонажа некоторую память, а также многие другие возможности, свойственные разумным существам. Этот метод также включает обратную связь. Последние действия могут направляться в сеть, что позволяет нейроконтроллеру помнить о них. Дополнительная обратная связь может включать информацию о здоровье персонажа, а также его эмоциональном состоянии. Данные механизмы способствуют генерации сложных действий, похожих на поступки человека. Алгоритм обратного распространения может использоваться для обучения нейронных сетей, которые имеют различное назначение. Ниже приведен краткий список возможных областей применения-алгоритма: □ общее распознавание моделей; □ диагностика ошибок; □ мониторинг состояния пациентов врача; □ распознавание персонажей; □ фильтрация данных; □ анализ запахов и ароматов; □ распознавание фальшивых банкнот и документов. В этой главе рассматривались нейронные сети и алгоритм обратного распространения. Для иллюстрации алгоритма было выбрано обучение нейроконтролле-ров в компьютерных играх. Рассказывалось, как алгоритм позволяет нейроконтроллеру правильно реагировать на непредвиденные ситуации. Из-за высоких системных требований алгоритм обратного распространения может не подходить для всех игровых архитектур, однако он позволяет добиться наибольшей реалистичности в поведении персонажа, добавляя нелинейные связи между окружающей средой и выбором действия. Литература и ресурсы 1. Ваггонер Б., Спир Б. Жан-Баптист Ламарк (1744-1829) (Waggoner В., Speer В. Jean-Baptiste Lamarck (1744-1829)). Доступно по адресу http:// www.ucmp.berkeley.edu/history/lamarck.html). 2. Галлант С. Обучение в нейронных сетях и экспертные системы (Gallant S. L., Neutral Network Learning and Expert Systems. - Cambridge, Mass.: MIT Press, 1994). 3. Мински M., Паперт С. Перцептроны: введение в компьютерную геометрию (Minsky М., Papert S. Perceptrons: An Introduction to Computational Geometry. - Cambridge, Mass.: MIT Press, 1969). |
|