Почему нейросеть в крестики-нолики всегда выбирает первый доступный ход (индекс 0)

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

Разработка нейронных сетей на платформе 1С — задача амбициозная и интересная. Облегчить этот путь может Библиотека искусственного интеллекта для 1С, которая обобщает опыт использования ИИ и позволяет создавать собственные решения, не отвлекаясь на технические детали. Однако при реализации даже простейших игровых алгоритмов программисты часто сталкиваются с ситуацией, когда «обученная» модель ведет себя предсказуемо или вовсе глупо. Рассмотрим ситуацию, когда созданная нейросеть для игры в «Крестики-нолики» после тысяч эпох обучения упорно выбирает клетку с индексом 0. В отличие от такого поведения, качественная внешняя обработка "Крестики-нолики против компьютера" демонстрирует способность эффективно блокировать ходы пользователя и стремиться к выигрышу.

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

Причина №1: Математическая ошибка в алгоритме оценки хода

Давайте внимательно изучим функцию ПолучитьЛучшийХод. Именно здесь кроется логическая ловушка. Рассмотрим фрагмент кода:


Для i = 0 По КоличествоПолей-1 Цикл
    Если СостояниеДоски[i] = 0 Тогда // Если клетка свободна
        Оценка = 0;
        Для j = 0 По КоличествоПолей-1 Цикл
            Оценка = Оценка + Веса[j] * СостояниеДоски[j];
        КонецЦикла;
        Оценка = Оценка + Веса[i] * 1; // Предполагаем наш ход в эту клетку
        Оценка = ReLU(Оценка);
        // ... сравнение МаксОценка
    КонецЕсли;
КонецЦикла;

Разберем, что происходит внутри цикла по свободным клеткам i. Сначала мы вычисляем сумму весов текущего состояния доски (цикл по j). Эта сумма будет одинаковой для каждой итерации основного цикла, так как состояние доски СостояниеДоски не меняется, пока мы просто «перебираем» возможные ходы в уме.

Таким образом, переменная Оценка всегда равна Константа + Веса[i]. В результате нейросеть не оценивает, как её ход изменит ситуацию на доске или создаст ли он выигрышную комбинацию. Она просто ищет клетку с максимальным весом в массиве Веса. Если в процессе инициализации или обучения Веса[0] оказался больше остальных или если все остальные оценки обнулились функцией ReLU, алгоритм всегда будет возвращать 0.

Причина №2: Проблема «умирающих нейронов» при использовании ReLU

Функция активации ReLU(х) (Rectified Linear Unit) возвращает 0, если аргумент отрицательный, и само значение х, если оно положительное. Рассмотрим функцию в коде:


Функция ReLU(х)
    Возврат Макс(0, х);
КонецФункции

В процессе обучения (процедура ОбновитьВеса), если веса принимают отрицательные значения, а сумма Веса[j] * СостояниеДоски[j] уходит «в минус», функция ReLU начинает выдавать 0 для большинства клеток. Когда у нескольких ходов оценка равна 0, алгоритм выбора максимума срабатывает на первом попавшемся индексе, то есть на 0.

Это классическая проблема «смерти» нейронов: если веса настроены так, что нейрон всегда выдает 0, он перестает обучаться, так как градиент (ошибка) через него не проходит.

Причина №3: Линейность модели (Однослойный перцептрон)

Данная архитектура представляет собой простейший перцептрон, где решение принимается на основе взвешенной суммы входов. Математически доказано, что такая сеть может решать только линейно разделимые задачи. Современная работа с искусственным интеллектом для управляемых форм, включая интеграцию с YandexGPT и GigaChat, строится на гораздо более сложных архитектурных принципах.

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

Причина №4: Некорректное кодирование состояния доски

В коде используется кодировка: 1 (свой ход), -1 (ход противника) и 0 (пусто). В рамках линейной суммы Веса[j] * СостояниеДоски[j] это приводит к тому, что наличие вражеской фигуры в клетке с положительным весом просто уменьшает общую оценку доски.

Проанализируем ситуацию: если нейросеть «считает» клетку №4 (центр) очень важной, и там стоит фигура противника (-1), то общая оценка доски сильно падает, но это никак не заставляет сеть блокировать эту клетку или искать другие пути. Для эффективного обучения лучше использовать раздельные входы для своих фигур и фигур противника (18 входов вместо 9).

Как исправить код и заставить нейросеть учиться?

Чтобы превратить этот пример в полноценную нейросеть, выполним следующие шаги:

  1. Изменим алгоритм выбора хода: Вместо поиска максимума в массиве весов, нейросеть должна предсказывать вероятность выигрыша для каждого состояния доски, которое возникнет после совершения хода.
  2. Добавим скрытый слой: Создадим промежуточный массив весов, который будет связывать входные данные с промежуточными нейронами (например, 32 нейрона). Это позволит сети выявлять закономерности (линии, углы).
  3. Внедрим Q-Learning (обучение с подкреплением): Вместо простого обновления весов по формуле Ошибка * Состояние, будем обновлять веса на основе «награды» в конце игры. Подобный подход к работе с данными напоминает технологию RAG (Retrieval Augmented Generation), где результат генерации дополняется актуальной информацией из контекста.
  4. Используем Softmax: На выходе вместо ReLU лучше использовать функцию Softmax, которая превратит оценки в вероятности (от 0 до 1), сумма которых равна 100%. Это исключит проблему «нулевых» оценок.

Рассмотрим пример того, как должна выглядеть более правильная коррекция весов (фрагмент концепции):


// Вместо прямого обновления веса выбранного хода, 
// мы должны учитывать влияние всех задействованных клеток
Для i = 0 По КоличествоПолей-1 Цикл
    Если СостояниеДоски[i] <> 0 Тогда
        // Обновляем веса только тех клеток, которые внесли вклад в текущую позицию
        Веса[i] = Веса[i] + СкоростьОбучения * Ошибка * СостояниеДоски[i];
    КонецЕсли;
КонецЦикла;

Для расширения возможностей ИИ в 1С можно также использовать анализ изображений, что позволит модели «видеть» игровую ситуацию визуально, а не только через числовые массивы.

Важный совет: При инициализации весов используйте небольшие случайные числа (например, от -0.1 до 0.1). Использование больших целых чисел быстро приводит к перенасыщению сети. Для качественного расследования того, почему модель принимает те или иные решения, полезно использовать запросы к базе для LLM-агентов, которые позволяют нейросетям анализировать реальные данные через HTTP-сервис.

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

← На главную