1. Вводная

Это мой первый опыт написания статьи. Судим, но не строго.

Недавно завершил интересный пэт-проект. Настолько интересный, что захотелось поделиться.

Это десктопная программа, которая:

  • Считывает скрин игрового стола в покере.

  • С помощью компьютерного зрения извлекает расклад, ставки и карты.

  • Рассчитывает ожидаемую выгоду (EV) каждого действия методом Монте-Карло.

  • Показывает на экране, что выгоднее сделать прямо сейчас.

Задумка родилась из идеи: «А можно ли сделать программу, которая играла бы в покер за меня и зарабатывала бы мне много тысяч денег!». Я думал: программа ведь не стрессует, не тильтует, не подвержена блефу, не принимает невыгодных решений, может быстро посчитать соотношение выигрыша к потерям, и вообще быстро считает, поэтому в среднем всегда должна выигрывать у человека.

По наивному плану хотелось просто «напрячь» ChatGPT, подвязать пару Python-библиотек и сразу запустить в покер-рум, играть на кэш. На практике всё вышло немного сложнее, идею пришлось разложить на четыре подзадачи и пройти их по очереди:

  1. Монте-Карло. Научиться за секунды симулировать раздачи и выбирать решение с положительным EV.

  2. Computer Vision. Надёжно извлекать данные с экрана независимо от скина, шрифта, языка и разрешения.

  3. Автомат. Собрать удобный GUI, чтобы программа анализировала раздачи «без моего вмешательства», 24/7.

  4. Автобот. Научить программу саму кликать на кнопки, имитировать человеческое поведение, спрятаться от античитов.

Сейчас готовы первые два пункта, третий и четвёртый - под вопросом, этический и технический порог выше, чем ожидал.

В статье покажу, как за 4 недели пройти путь от идеи до работающей альфа-версии, чем помогли ChatGPT, Cursor и Roboflow, и какие ещё ИИ использовал. Покер здесь - лишь фон, главный герой повествования - ИИ + Python + CV, способные сегодня превратить соло-разработчика в мини-R&D-команду.

2. Очень коротко

Проект делал чуть дольше месяца.

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

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

Неделя на изучение компьютерного зрения, знакомился с семейством моделей YOLO.

Неделя на обучение своих YOLO моделей. Получилось обучить две модели: одна для детекции стола, вторая для обнаружения карт.

И наконец, неделя на сведение всего, тестирование, фикс багов и компилирование в исполняемый файл.

3. Покер в двух словах

Покер, оказывается, очень интересная игра со своей механикой.

Цель игры - собрать сильнейшую комбинацию или заставить соперников сбросить карты.

Существует много разных видов и правил покера, но самый распространённый сегодня - это Техасский холдем.

 Техасский холдем
Техасский холдем

Правила:

  • Игрокам раздаются карты в закрытую.

  • На стол выкладываются общие карты (в открытую).

  • Идут этапы торговли: префлоп, флоп, тёрн, ривер.

  • Побеждает игрок с лучшей комбинацией или тот, кто заберёт банк без вскрытия (блеф).

В Техасском Холдеме комбинацию собирают из 2 своих карт и 5 общих на столе (всего 7 карт). Общее количество уникальных комбинаций 7 карт из 52 составляет

C_{52}^7 = \frac{52!}{(52 - 7)! \cdot 7!}​ = 133,784,560

Всего в покере можно собрать 10 различных комбинаций карт. Ниже таблица комбинаций, от самой сильной (и редкой) до самой слабой.

Комбинация

Частота

Вероятность

Шанс

1

Роял-флеш

4,324

0.0032%

1 к 30 940

2

Стрит-флеш (без РФ)

37,260

0.0279%

1 к 3 589

3

Каре

224,848

0.168%

1 к 594

4

Фул-хаус

3,473,184

2.60%

1 к 38

5

Флеш (без РФ и СФ)

4,047,644

3.03%

1 к 33

6

Стрит (без РФ, СФ и Ф)

6,180,020

4.62%

1 к 22

7

Сет

6,461,620

4.83%

1 к 21

8

Две пары

31,433,400

23.5%

1 к 4

9

Одна пара

58,627,800

43.8%

1 к 2

10

Старшая карта

23,294,460

17.4%

1 к 5

-

ВСЕГО

133,784,560

100%

-

Интересное наблюдение:

  • Вероятность отсутствия пары (это "Старшая карта") ниже, чем вероятность наличия одной пары или даже двух пар.

  • Это происходит из-за того, что мы собираем сильнейшую возможную комбинацию 5 карт из 7 доступных нам.

  • Комбинация "Старшая карта" 7-high - это, например, ♠7 ♦6 ♥5 ♣3 ♠2 - нет пары, нет стрита, нет флеша (просто самая старшая карта 7).

  • Так вот, получить такую комбинацию крайне маловероятно (но не невозможно).

  • В любой семикарточной комбинации всегда будет шанс собрать что-то сильнее 7-high или 8-high, например, пару, стрит-дро, более высокие карты и т.д.

  • Поэтому вероятность получить в течение одной игры пару или две пары составляет ~67%, что делает Техасский холдем более динамичным и интересным.

Продолжим.
В покере есть различные позиции, которые определяют порядок действий игроков на префлопе (0 карт на доске) и постфлопе (3-5 карт на доске).

Порядок позиций игроков всегда идёт по часовой стрелке, начиная слева от дилера (баттона).

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

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

Equity - это вероятность выиграть банк на вскрытии, выраженная в процентах.

EV (Expected Value) - это математическое ожидание прибыли или убытка от конкретного действия. Оно показывает, сколько мы будем выигрывать или проигрывать в среднем, если многократно повторять одно и то же решение.

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

В общем, перечисленного должно быть достаточно, чтобы понять, с какой игрой мы имеем дело.

Нас же будет интересовать, как рассчитать EV каждого действия.

4. Математика

«Математика - это язык, на котором написана книга природы», Галилео Галилей.

Нам понадобится лишь пара её страниц.

Напомню, EV (Expected Value) - это математическое ожидание прибыли или убытка от конкретного действия.

В чём отличие математического ожидания и среднего арифметического?

В игре в кости математическое ожидание броска одного кубика равно 3.5, но если мы бросим кость 10 раз и получим результаты, например, [1, 6, 2, 4, 5, 3, 6, 2, 3, 4], то среднее этих бросков будет равно 3.6.

Математическое ожидание относится к ТЕОРЕТИЧЕСКОМУ среднему значению случайной величины, тогда как среднее арифметическое - это эмпирическое (РЕАЛЬНОЕ) среднее значение конкретной выборки.

Формула EV:

EV = E \times (X + Y) \ – \ (1 \ – \ E) \times X

Где:

  • X — наша ставка

  • Y — текущий банк

  • E — equity, вероятность выиграть на вскрытии, выраженная в процентах

Equity - это ключевой элемент при расчёте EV и принятии решений: коллировать, рейзить или сбрасывать.

Для расчёта equity будем использовать метод Монте-Карло.

Метод Монте-Карло - это метод оценки вероятностей путём многократного случайного моделирования исходов. Применяется, когда аналитический расчёт слишком сложен или невозможен (это наш кейс).

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

Пример кода на Python.

import random
from treys import Evaluator, Deck, Card

def monte_carlo(hero_cards: list,
				board_cards: list,
				active: int,
				n_simulations: int) -> float:
    """
    :param hero_cards: рука игрока
    :param board_cards: карты доски
    :param active: количество активных игроков
    :param n_simulations: количество симуляций
    :return: equity
    """

    # Создаем одну колоду и убираем из неё известные карты
    remaining_cards = []
    deck = Deck()
    used_cards = set(hero_cards + board_cards)
    remaining_cards = [c for c in deck.cards if c not in used_cards]

    wins = ties = losses = 0

    # Цикл симуляций
    for _ in range(n_simulations):

        # Перемешиваем оставшиеся карты
        random.shuffle(remaining_cards)

        # Раздаём карты противникам
        card_index = 0
        villains = []
        for _ in range(active - 1):
            villain = remaining_cards[card_index:card_index + 2]
            villains.append(villain)
            card_index += 2

        # Добираем доску до 5 карт, если у нас не Ривер
        sim_board = board_cards[:]
        cards_needed = 5 - len(sim_board)
        if cards_needed > 0:
            sim_board.extend(remaining_cards[card_index:card_index + cards_needed])

        # Оценка рук
        hero_score = EVALUATOR.evaluate(sim_board, hero_cards)

        # Находим лучшего противника
        best_villain_score = float('inf')
        for villain in villains:
            villain_score = EVALUATOR.evaluate(sim_board, villain)
            if villain_score < best_villain_score:
                best_villain_score = villain_score

        # Подсчёт результатов (в treys меньше = лучше)
        if hero_score < best_villain_score:
            wins += 1
        elif hero_score == best_villain_score:
            ties += 1
        else:
            losses += 1

    equity = (wins + 0.5 * ties) / n_simulations

    return equity
  • Модуль treys - это Python-библиотека для оценки силы руки в покере.

  • Обожаю программирование (особенно Python), что на любой случай жизни уже написана какая-нибудь библиотека.

Поэтому никакой сложной математики не получилось. За нас уже написали все необходимые классы и функции. Осталось их просто применить в нужном месте.

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

5. Компьютерное зрение

Компьютерное зрение (Computer Vision) - это область искусственного интеллекта, которая позволяет компьютерам «видеть» и интерпретировать изображения и видео.

Если задуматься, очень крутая технология! По сути это механизм обработки картинки или видео (последовательность картинок), в которой алгоритм находит необходимый объект, добавляет его координаты, его название и свою уверенность (confidence).

Есть множество разных способов, программ или библиотек, которые реализуют CV.

Я выбрал ёлку.

5.1 Коротко о YOLO

Segmentation, Detection,  Keypoints,  Oriented Bounding
Segmentation, Detection, Keypoints, Oriented Bounding

YOLO - это семейство моделей для обнаружения объектов в реальном времени.

Здесь должна была быть шутка про фильм "Ёлки 11" и YOLO11, самую последнюю, быструю и эффективную модель, но у нас серьёзная статья, обойдёмся без шуток.

Модели YOLO способны выполнять самые разные задачи по детекции объектов:

  • Classification - классификация изображений по заранее определённым классам.

  • Detection - идентификация и определение местоположения объектов на изображении.

  • Segmentation - сегментация, более сложная задача, обнаружение объектов и определение их точных границ.

  • Pose/Keypoints - обнаружение и отслеживание ключевых точек на теле человека.

  • Oriented Bounding - более точное определение объектов, вводится дополнительный угол для боксов.

Для нашей задачи, анализ покерного стола, обнаружение 52 различных карт, подойдут модели Detection.

У них есть 5 версий:

  • yolo11n.pt - nano version, 2.6 parameters, 6.5 GFLOPs

  • yolo11s.pt - small version, 9.4 parameters, 21.5 GFLOPs

  • yolo11m.pt - medium version, 20.1 parameters, 68.0 GFLOPs

  • yolo11l.pt - large version, 25.3 parameters, 86.9 GFLOPs

  • yolo11x.pt - extra version, 56.9 parameters, 194.9 GFLOPs

Parameters - общее количество параметров (весов), которые модель обучает. Чем больше - тем выше потенциальная точность, но также и выше требования к ресурсам. Parameters влияют на ёмкость модели, т.е. насколько сложные зависимости она может выучить.

GFLOPs - количество гига-флопов (миллиардов операций с плавающей точкой), необходимых на один проход одного изображения через всю модель во время обучения (или детекции). Это теоретическая мера вычислительной сложности модели. GFLOPs влияют на скорость инференса (распознавания). При росте этой величины FPS падает, особенно на слабых GPU.

Я выбрал yolo11n.pt

5.2 Коротко о Roboflow

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

С помощью Roboflow можно легко загружать изображения, аннотировать их, применять различные виды аугментации для увеличения объёма данных. Особенно помогает, когда ты один, а данных нужно много.

Нюансы:

Roboflow это облачная платформа, работает в браузере. Все данные хранятся на серверах. Для бизнеса это может быть чувствительно. Для пэт-проекта - вроде норм.

На бесплатном плане можно вести до 3 проектов, в каждом - до 2000 оригинальных изображений. Аугментированные копии не считаются в лимит, так что итоговое количество файлов может быть больше. У меня два проекта, разметил около 7к изображений (с учётом аугментаций) - в лимит пока не упёрся. Для соло-проектов и экспериментов бесплатной версии хватает. За большие объёмы придётся платить.

5.3 Датасет

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

Без датасета - нечему учиться. Модель не сможет "угадать", что на изображении - её надо этому научить, на примерах.

Обычно датасет делят на 3 части: train / val / test.

Название

Задача

Доля от общего датасета

train

модель на этом учится

60%-70%

val

контроль обучения

15%-25%

test

итоговая оценка качества

15%-25%

Чтобы было проще, я придумал себе аналогию с учёбой.

  • train - это уроки, домашки, работа с учебником. Модель - школьник начальных классов, учится основам математики (модель учится на train).

  • val - это проверочные работы в течение семестра. Мы - учитель, смотрим как идёт усвоение материала. Если всё плохо - меняем подход (val не даёт модели переобучиться).

  • test - это контрольные работы. Новые задачи, которых школьник не видел. По ним выставляются оценки за семестр, оценивается то, как был усвоен материал (по test строятся метрики качества).

Аугментация - это способ "раздуть" датасет. Мы берём одну картинку и создаём из неё 3–5 новых, немного изменённых.

Изменение цветокора для человека картинку не меняет, по сути и смыслу она остаётся той же самой, но для модели, это уже совсем другой набор пикселей. Делая простые преобразования, применяя фильтры (типа как в Instagram), мы увеличиваем датасет в разы.

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

В Roboflow всё достаточно наглядно, не запутаетесь.

Тип аугментации

Описание

Brightness

Чуть осветляет или затемняет

Blur

Добавляет размытость

Flip

Отзеркаливание по горизонтали

Rotation

Поворот на угол (обычно ±10°)

Noise

Добавляет пиксельный шум

Cutout

Закрашивает случайный фрагмент

  • Аугментация применяется на уже размеченный датасет.

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

  • Важно!, не переборщить - слишком жёсткие искажения могут запутать модель.

На Free плане возможно применить только два типа аугментации на выбор, то есть увеличить датасет в 3 раза. Максимально возможное увеличение датасета в Roboflow - в 15 раз (всего 14 фильтров).

Однако аугментацию можно реализовать и с помощью кода. Возможности Python и Cursor - почти безграничны! Написание собственного решения будет более трудоёмким и потребует больше времени, но даст больше свободы.

5.3 Дообучение

Какое слово использовать, ДОобучение, ПЕРЕобучение, или просто обучение?, в чём разница?

Давайте сразу со сложного, ПЕРЕобучение (overfitting) - это ошибка. Это когда модель "зазубрила" тренировочные примеры, но не может обобщать. В аналогии со школой: ученик вызубрил таблицу умножения до 10, но пример 11 × 11 - уже не осилит.

Обучение (training) - это процесс, в котором нейросеть учится распознавать паттерны в данных. Это базовый термин, он универсален.

ДОобучение (fine-tuning / transfer learning) - это обучение на уже обученной модели. Например, YOLO уже умеет распознавать людей, котиков, автобусы (всего ~35 классов), но мы хотим, чтобы она распознавала покерные карты - мы её дообучаем на нашем датасете.

В статье я всё равно использую слово "обучение", потому что оно привычнее на слух.

Двигаемся дальше. Как скачать базовую модель. Переходим на сайт Ultralytics YOLOv11 - выбираем нужную архитектуру (например, yolo11n.pt - nano-версия, самая лёгкая, для первого раза сойдёт), скачиваем веса.

Далее команда для запуска обучения через терминал:

CUDA_VISIBLE_DEVICES=0 yolo detect train \
  model=yolo11n.pt \
  data=datasets/poker_dataset/data.yaml \
  epochs=50 \
  imgsz=640 \
  batch=8 \
  patience=10 \
  lr0=0.005 \
  name=poker_train

Пояснение ключей:

  • CUDA_VISIBLE_DEVICES=0 - принудительное использование GPU (если есть несколько, или вдруг будет обучаться на CPU).

  • model= - путь до базовой YOLO-модели (.pt), можно выбрать потяжелее yolo11m.pt

  • data= - путь к YAML-файлу, в нём описываются пути до папок train, val, test и список классов

  • epochs= - кол-во эпох, обычно от 20 до 200, зависит от размера датасета.

  • imgsz= - размер изображений, можно увеличить до 768 или 960(чем лучше качество, тем выше нагрузка на VRAM)

  • batch= - размер батча, влияет на скорость обучения, нагружает VRAM, допустимые значения 2, 4, 8, 16, 32 (у меня 4ГБ - ставил значение 8)

  • patience= - параметр ранней остановки, если метрика валидации не улучшается в течение N эпох, обучение автоматически прерывается (по умолчанию: patience=50)

  • lr0= - начальная скорость обучения, learning rate, рабочие значения 0.001-0.01

  • name= - название эксперимента, создастся папка в runs/detect/...

То же самое на Python.

from ultralytics import YOLO

# Загрузка модели
model = YOLO("yolo11n.pt")

# Запуск обучения
model.train(data="datasets/poker_dataset/data.yaml",
			epochs=50,
			imgsz=640,
			batch=8,
			lr0=0.005)

Запуск детекции в Python.

from ultralytics import YOLO

model = YOLO("yolo11n.pt")
results = model("image.jpg")

results[0].show()           # Показать картинку с боксами
results[0].boxes.xyxy       # Координаты бокса (x1, y1, x2, y2)
results[0].probs.top1       # Класс с максимальной вероятностью
  • координаты боксов записываются как координаты (x_1, y_1) левой верхней точки квадрата, и координаты (x_2, y_2) правой нижней

Сколько времени займёт обучение, сильно зависит от:

  • размера модели - я обучался на yolo11n.pt около часа, на yolo11m.pt около 4 часов

  • размера изображений - я пробовал 640 и 768, разница при прочих равных, 15-20%

  • количества изображений

  • мощности видеокарты

У меня обучение yolo11n.pt, imgsz=640, epochs=40, на 200 изображений заняло ~15–20 минут на RTX 2050. Это была модель для детекции объектов на покерном столе. В ней было всего 8 классов.

Для детекции игральных карт, нужна была модель с 52 классами (52 игральные карты), обучал yolo11n.pt, imgsz=768, epochs=80, на 7598 изображений - вышло ~6 часов.

Чем больше классов, тем больше нужно картинок. Если грубо, то на один класс должно получаться 100+ выделенных боксов.

Забыл упомянуть, в Roboflow есть суперфича - автосплит. То есть выставляете желаемое соотношение train/val/test, а он уже по имеющимся данным старается всё разделить. При этом алгоритм не просто картинки по папкам разделяет в заданном соотношении, но и внутри каждого класса старается сплитовать с заданным соотношением (по количеству выделенных боксов).

Перед началом проекта, на стадии планирования, этот вопрос меня сильно беспокоил. Не понимал как это сделать в коде, сильно не запариваясь. Оказалось всё уже автоматизировано.

"Старается", это значит что точно разделить данные по заданному соотношению не получится. Сложно объяснить понятным языком, но это математически практически невозможно, поэтому алгоритм стремится к самому близкому соответствию нашего запроса (у меня было 70/15/15), а дальше как получится.

Что ещё нужно упомянуть.
После тренировки будет создана папка runs/detect/poker_train/weight/... В ней будут две модели best.pt (лучшая модель) и last.pt (последняя модель) полученные во время обучения. Копируем best.pt в свой проект, меняем название, готово. Теперь можем использовать её в каком-нибудь скрипте для детекции объектов по скриншотам.

5.4 Метрики

 "Когда код работает слишком хорошо"
"Когда код работает слишком хорошо"

Отлично! Теперь надо оценить качество обученных нами моделей. Это, по-моему, очень важный момент. Мы должны понять что она умеет, на чём ошибается, какие у неё вообще оценки.

Тут может быть сложновато. Поехали.

После обучения первой модели в корне будет автоматически создана папка runs/detect/.., в которой будет папка проекта, название которого мы указывали при обучении. В ней самая важная папка это, конечно, weight/.., в ней веса готовой, обученной модели (в прошлом блоке обсуждали).

Но также там будет ещё куча всяких папок, файлов и отчётов.
Расскажу про самые важные (на мой взгляд).

results.png и results.csv - это одно и то же, просто в разных форматах. В визуальном как картинка с графиками, и просто таблица с данными (на которых строились эти графики) с классическим .csv расширением.

 Для примера, мой results.png, для общей модели детекции игрового стола.
Для примера, мой results.png, для общей модели детекции игрового стола.

График

Измеряет

Как читать

train/box_loss, val/box_loss

Ошибка при предсказании координат боксов

Чем ниже - тем лучше. Должна постепенно убывать. Скачки на val допустимы, но не сильные.

train/cls_loss, val/cls_loss

Ошибка при классификации объектов

Чем ниже - тем лучше. Если val растёт при убывающем train, возможно переобучение.

train/dfl_loss, val/dfl_loss

DFL (Distribution Focal Loss), уточняет границы боксов

Аналогично box_loss - стремимся к снижению.

metrics/precision(B)

Точность: из всех предсказанных - сколько верных

Стремимся к 1. Если низкий - модель путает классы. Важно смотреть вместе с Recall.

metrics/recall(B)

Полнота: из всех верных - сколько найдено

Стремимся к 1. Если низкий - модель что-то пропускает.

metrics/mAP50(B)

Средняя точность (mAP) при IoU=50%

Это главный показатель качества модели. Ближе к 1 - отлично.

metrics/mAP50-95(B)

mAP усреднённый по IoU от 50% до 95%

Более строгая метрика. Всё выше 0.7 - уже хорошо.

Recall

Recall (полнота) - насколько модель хорошо может предсказать все наши объекты.
Показывает количество правильно предсказанных объектов среди всех ИСТИННЫХ объектов.

Распределение от 0 до 100%, чем выше процент, тем лучше

Precision

Precision (точность) - показывает количество правильно предсказанных объектов среди всех ПРЕДСКАЗАННЫХ объектов.

Распределение от 0 до 100%, чем выше процент, тем лучше

mAP50

  • mAP , model Average Precision (средняя точность модели) - насколько хорошо модель находит и правильно классифицирует объекты на изображениях.

  • @50 означает, что два объекта (истинный и предсказанный) считаются совпадающими, если перекрытие IoU ≥ 50%

  • IoU (Intersection over Union, Пересечение на объединение) - это насколько сильно пересекаются реальный бокс и предсказанный (0% - не пересекаются, 100% - полное совпадение)

  • mAP@50 = 0.9, это значит, что модель почти всегда находит и классифицирует объекты правильно (с ≥50% перекрытием)

mAP@50-95

  • Это усреднённое значение mAP, рассчитанное по 10 разным порогам IoU: от 0.50 до 0.95 с шагом 0.05 → то есть усреднение mAP@50, mAP@55, mAP@60, ..., mAP@95

  • Это строгая метрика, часто используется для оценки в соревнованиях или в продакшене. Особенно важна в реальных задачах: медицина, автопилоты.

  • mAP@50-95 = 0.7 - это очень хороший результат, особенно если много мелких объектов.

Также очень важным графиком на мой взгляд является кривая F1, в папке будет именоваться как F1_curve.png

Кривая F1-уверенности (F1-Confidence Curve) для всех классов.
Кривая F1-уверенности (F1-Confidence Curve) для всех классов.

F1

F1 - гармоническое среднее. Объединяет в себе recall и precision. Более сбалансированная и точная метрика.

  • Так же как и recall и precision распределяется от 0 до 1

  • Наилучший порог - это тот, при котором достигается максимум F1 по всем классам

  • В нашем случае (на графике): F1 = 0.99 при Confidence = 0.433

Confidence

Confidence Threshold (порог уверенности) - это число от 0 до 1, которое говорит, насколько модель должна быть уверена, чтобы мы поверили её предсказанию. Это то число, которое ставится у обнаруженной рамки. На всех графиках, все метрики ставятся в сравнение именно с Confidence Threshold.

Итого

Эти метрики могут показаться сложными, если вы не сталкивались с ними ранее, поэтому не переживайте, если не всё сразу понятно. Главное, на что стоит обращать внимание:

  • Уверенное снижение всех loss-метрик

  • Стабильно высокие показатели recall и precision

  • Рост и стабилизация mAP50 и mAP50-95

На что также обратить внимание:

  • Если loss падает, а метрики остаются на месте, возможно, модель переобучается.

  • Если метрики сильно колеблются, возможно недостаточно данных или модель слишком сложная.

5.5 Модели

В итоге я обучил две YOLO‑модели. Работают они в связке.

Пример работы моделей: total_model и cards_model
Пример работы моделей: total_model и cards_model

Первая - общая модель (total_model)
Обрабатывает весь скрин игрового стола: ищет кнопки, карты, стек, пот, дилер фишку и т.д.

Вторая - карточная модель (cards_model)
Используется точечно, только на кропах, чтобы точно определить номинал и масть конкретной карты (например: Qh, 9d, As).

Сначала я подаю скриншот в total_model, она находит все важные объекты - например, выделяет область hero_card (hero - так в покере называют игрока).
Потом эта вырезанная область отправляется во вторую модель - cards_model, которая уже конкретно говорит, какие именно карты там лежат.

С total_model всё шло гладко - классов было немного (всего 7), а вот с cards_model пришлось попотеть. 52 класса (все возможные игральные карты), ~7500 размеченных изображений, много правок и дублирующей разметки - но в итоге всё собрал, разметил и обучил. В результате, довольно стабильно распознаёт карты даже на замыленных и затемнённых скринах.

По метрикам получилось так:

Model

Class

train / val / test

Precision

Recall

mAP@50

mAP@50–95

total_model

7

150 / 25 / 25

0.991

0.997

0.995

0.822

cards_model

52

5059 / 1283 / 1256

0.986

0.983

0.991

0.866

cards_model даже при большом числе классов (52 карты) уверенно держит mAP@50-95 выше 0.85, что для такой задачи - очень хороший результат.

Обе модели - на базе YOLOv11n. Получились лёгкие и быстрые, запускаются локально на ноуте с RTX 2050.

6. Код и инструменты

Проект получился объёмный, но структура у него простая и модульная. Использовал разные инструменты, которые сильно помогали: IDE Cursor, ChatGPT, различный ИИ, работа в терминале.

Всё собирал под Windows, локально, скомпилировал в один исполняемый файл.

6.1 IDE Cursor

Мне кажется на сегодня (на июль 2025), не найдётся ни одного человека (айтишника), кто не слышал бы про Cursor.

Всю кодовую часть проекта писал в Cursor - это форк от VS Code, только с очень круто интегрированным ИИ. Те же горячие клавиши, расширения, плагины, немного другая тема, но главное - ИИ, он встроен прямо в редактор, не как плагин, а как полноценный помощник.

ИИ в Cursor подключается от основных провайдеров. В настройках можно выбрать, какие именно модели использовать.

Я обычно оставляю три: последнюю от OpenAI, последнюю версию Sonnet, и какую-нибудь гугловскую (Gemini).

Подробно все возможности описывать не буду, лучше попробовать самим. Интерфейс интуитивный, если с VS Code знакомы - разберётесь быстро.

Перечислю только основные режимы работы с ИИ:

  1. Agent — ИИ имеет полный доступ к файлам проекта, может вносить изменения сам. Я бы не советовал этот режим.

  2. Ask — самый адекватный режим. ИИ видит файлы, может предлагать правки, но не трогает сам. Обычно в нём работаю - сам читаю, сам решаю, что принять.

  3. Manual — полностью ручной режим, ИИ не смотрит код. Если нужно просто пообщаться или решить отдельную задачу. Сам ни разу не использовал.

Также есть дополнительные возможности:

  • Tab-комплит — ИИ дописывает код на лету. Главное успевать читать, что он предлагает.

  • Ask to Code — можно выделить кусок кода и задать вопрос напрямую в редакторе, без переключений в чат.

  • Ask to Terminal — то же самое, но с логами из терминала. Очень удобно для дебага!

Если умеешь кодить - режим Agent лучше не использовать.
Он может внести изменения, которые не сразу заметишь, а потом полдня будешь разбираться, почему всё сломалось.

Итого

IDE Cursor реально мощная. Постоянно меняется, всё время что-то дорабатывают.
Оценка компании - уже под 10 млрд. Растут быстро, развиваются.

Если шарите немного в коде, и есть идея - Cursor сильно сэкономит время.

6.2 ChatGPT + Sonnet

"Когда закончится весь этот зоопарк?"
"Когда закончится весь этот зоопарк?"

Даже близко не буду пытаться объяснять текущие нейминги ИИ. Зоопарк у всех!

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

Выработал простое правило - использую самую последнюю стабильную версию. Обычно этого вполне достаточно.

У OpenAI пользуюсь только gpt-4o. Она быстрее, дешевле, и на сегодняшний день самая универсальная.

Для кода почти всегда использую Sonnet, последние версии (3.7 / 4.0). Они аккуратнее пишут код, меньше галлюцинируют.

В Cursor можно выбрать конкретную модель, которая будет подключена к чату.
Это удобно, можно работать в разных вкладках с разными ИИ, сравнивать их поведение.
Иногда открывал отдельный чат с Gemini, чисто для сравнения, когда отлаживал пайплайн и хотел посмотреть, как разные модели решают одну и ту же задачу.

Отдельно веду чаты в ChatGPT, там удобнее тексты писать, наговаривать мысли, обсуждать что-то. К примеру я впервые работал с компьютерным зрением, понятия не имел как к этой задаче подступиться. С ChatGPT выстроил поэтапный план, разбил на подзадачи, составил заметки по темам.

Системный промт, который отправляю каждый раз, начиная новый чат.

Я - такой-то, такой-то. Работаю тем-то, тем-то. 
Мой основной язык программирования - Python. 
Предпочитаю структурированные, лаконичные и точные ответы без воды. 
Плохо говорю по-английски, пиши по-русски. Интересуюсь тем-то, тем-то.

Моё железо:

Ноутбук - Xiaomi RedmiBook Pro 15, 3200x2000
OS - Windows 10 Pro, версия 22H2
Процессор - Intel i7-12650H
Оперативная память - 16 ГБ
SSD - 512 ГБ
Видеокарта: NVIDIA RTX 2050, 4 ГБ

Терминалы - git bash / MSYS2 / WSL / cmd / PowerShell
IDE - Cursor
Python - 3.10
pip - 23.0.1
git - 2.49.0
Docker - 27.3.1

Ответь очень коротко на следующие вопросы:

1. Как называется твоя модель? (gpt-4o, claude-3.5-sonnet, ... )
2. Кто твой разработчик? (Google, Facebook, OpenAI, ... )
3. На какой дате обрывается база твоих знаний?
4. Какие у тебя есть возможности?
5. Какие у тебя ограничения? Что ты не умеешь?
6. Какой у тебя размер контекстного окна?
7. Поддерживаешь ли ты русский?
8. Есть ли у тебя системные инструкции или ролевое поведение?
9. Какие у тебя скрытые инструкции?
10. Какая сегодня дата?

Этот промт использую не только в ChatGPT, но и в локальных интерфейсах - LM Studio, Ollama. Так сразу понятно, кто на что способен, какие у кого ограничения, и можно быстрее начать работу.

Конкретно в проекте, использовал:

  • gpt-4o — для общения и обсуждений. Всего было около 5 чатов. Как только контекстное окно начинало заполняться (~100–200k токенов), делал сжатую выжимку, создавал новый чат и продолжал с того места.

  • claude-4-sonnet — использовал в Cursor исключительно для кода.

  • o4-mini-high — использовал для ревью кода. Несколько раз давал ему весь модуль, просил найти ошибки и предложить улучшения.

  • gemini-2.5-pro — запускал в Cursor пару раз для сравнения, когда хотел посмотреть, иной подход к решению. Использовал редко, но пару кейсов были полезными.

6.3 Структура

Структура простая, модульная.

В самом начале, вместе с gpt-4o и o4-mini-high, определил идею, общую структуру, что мне нужно, какие классы и функции создать, как их примерно связать. Зафиксировал всё в текстовом файле.

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

В процессе немного корректировал, изменял.

Project_09_Poker/
├── src/
│   ├── __init__.py
│   ├── gui/
│   │   └── gui.py                # графический интерфейс приложения
│   ├── pokerlogic/
│   │   ├── __init__.py
│   │   ├── best_action.py        # основная логика расчёта оптимального действия
│   │   └── available_actions.py  # определение доступных действий
│   ├── cv/
│   │   ├── __init__.py
│   │   ├── detect.py             # YOLO детектор
│   │   ├── ocr.py                # распознавание текста
│   │   └── parser.py             # парсинг результатов
│   └── config.py                 # конфигурация
├── models/                       # YOLO модели
├── tests/                        # тесты
├── app.py                        # точка входа
├── .gitignore
├── LICENSE
├── requirements.txt
└── README.md

6.4 GUI

GUI, графический юзер интерфейс.

Этот вопрос полностью отдал на аутсорс Cursor + Sonnet. Работал в режиме Ask.

Весь GUI писал Sonnet. Я просто формулировал задачу, передавал текстовый файл с описанием проекта и общей структурой, потом читал, фильтровал, кое-что отклонял, что-то менял, копировал, вставлял.

В общем, классический вайбкодинг. Управился за пару дней.

В итоге получилось:

? GUI - это управляющая панель, через которую запускается весь процесс.

? Пользователь вручную выделяет область экрана, нажимает кнопку «Анализ» - бот делает скриншот, детектирует нужные элементы (карты, банк, to call и т. д.), извлекает тексты, собирает JSON и передаёт в best_action().

? Результат анализа сразу выводится: стадия игры, карты, стек, банк, действия с рассчитанным EV. Сверху отдельно показывается строка с наиболее выгодным действием. Всё логируется с таймингами.

? Режим автоанализа - процесс запускается в цикле, пока не нажата кнопка «Стоп». Автоклик пока не реализован, маскировка под человека тоже.

? В конце кнопка «Завершить» корректно закрывает все процессы и очищает временные файлы.

7. Что получилось

Изначальный план был таким:

1. Обучить симуляцию раздач и рассчитать оптимальные действия с максимальным EV.
2. Надёжно извлекать все необходимые данные с экрана и объединить их в удобный интерфейс.
3. Сделать программу, работающую полностью в автономном режиме.
4. Реализовать имитацию человеческого поведения, чтобы обходить античиты.

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

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

Poker Bot в действии
Poker Bot в действии

В итоге получилась программа, которая выполняет задачи. Не идеально, но работает. Анализирует изображения с редкими ошибками в детекции, выдаёт корректные EV, хотя иногда ломается из-за неверного распознавания.
Скорость инференса, 4-6 секунд через интерпретатор и 5–8 секунд через скомпилированный exe. Это очень долго.
GUI простой, но рабочий. Я не про UX, делал чтобы просто работало.

Возможные улучшения

  • Детекция:
    Можно дообучить модели, попробовать более тяжёлые архитектуры, расширить датасет (увеличить аугментации хотя бы в 10-14 раз), поиграть с гиперпараметрами, прогнать на 200-300 эпох, используя облачные мощности. Выжать ещё 5-7% по mAP@50-95 и свести ошибки к минимуму. Это реально. $$ $$

  • Математика:
    Добавить логику диапазонов рук, учесть fold equity, поднять количество симуляций. Всё это заметно улучшит точность EV.

  • Код:
    Оптимизировать циклы, разделить громоздкие модули, всё это сократит время инференса.
    А если смотреть в долгую, можно вообще всё переписать на JavaScript или C++: при всей моей любви к Python, он хорош для быстрых прототипов, но не для стабильных решений.

  • Дополнительные функции:
    Если всё-таки двигаться к изначальной идее, нужно дописать БД для хранения истории по сессии, добавить функции имитации человеческих паттернов (пара вероятностных распределений + немного кода), и продумать автоплей. Как обойти античиты, писать не буду, кто захочет, тот найдёт.

Вывод

От задумки до рабочего прототипа у меня ушло чуть более 4-х недель. Все стадии разработки были на мне, а также на целом оркестре ИИ. Я дирижировал, мои миньоны выполняли задачи. Не всегда точно, приходилось направлять подсказывать, но в итоге вместе мы собрали рабочий прототип, который можно развивать дальше. Считаю это успех!

8. О себе

Я начал свой путь в IT через анализ данных, поэтому мне было не сложно разобраться в покерной математике, и особенно интересно было изучать всякие метрики. В них оказалось очень много пересекающихся понятий из статистики и теории вероятностей, типа ошибки I и II рода, Recall и Precision, F1.

Исследование покера, математики, неизвестных библиотек делал с помощью ChatGPT и YouTube, всё параллельно конспектируя в Obsidian в виде связных заметок. Очень удобная система хранения информации. Благодаря ей, данную статью смог написать за пару дней.

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

Из языков программирования я знаю только Python. В плане ведения проекта, уже был опыт работы в Cursor, писал несколько телеграм ботов, парсеров, что такое модульная структура, логирование, git - знал. Поэтому вайбкодил только в моменте написания GUI и компилирование исполняемого файла (никогда с этим не работал, и особо погружаться не хотел). В остальном tab автокомплит сильно ускорял процесс написания кода.

Сильно не хватало навыков в QA. Тесты я делал, но как-то не так как это принято в сфере тестировщиков.

Для себя оцениваю проделанную работу как:

  • 50%-ую реализацию изначальной идеи, что для меня прям отличный KPI

  • изучил покер

  • изучил метод Монте-Карло

  • подробно познакомился с YOLO, главным фреймворком по компьютерному зрению на сегодня (по моему мнению)

  • получил ценный опыт развития проекта от задумки до рабочей альфа-версии

  • ещё раз убедился в силе и эффективности ИИ

Пара мыслей насчёт ИИ

Мне кажется, ИИ не вытеснит программистов с рынка труда. Скорее, изменит их роль. Это как с вычислительной техникой в середине XX века, раньше в NASA были целые отделы людей, которые вручную решали уравнения и делали расчёты траекторий полёта ракет. Когда появились электронные калькуляторы и первые компьютеры, профессия не исчезла, но изменилась - те же люди начали решать более сложные задачи, а рутинные вычисления передали машинам. С ИИ сейчас то же самое - это просто инструмент, который снимает часть рутины и позволяет сосредоточиться на более интересных и сложных задачах.

Человек + ИИ
Человек + ИИ

Никогда в жизни не писал таких длинных текстов (~6000 слов).

Если вы дочитали до сюда, то это большое достижение. Вы чемпионы!

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

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

Всем спасибо!

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


  1. akod67
    22.07.2025 07:54

    Выигрывать то помогает? =)


    1. Nyanny
      22.07.2025 07:54

      Отвечу за автора.

      Да помогает, если работает.

      Чистая математика с вероятностями.

      Подобные программы у многих "регов" уже лет 10.


  1. Artazar777
    22.07.2025 07:54

    Я все это читал, чтобы узнать результат тестов идеи, что в итоге то?


  1. Mhapach
    22.07.2025 07:54

    Молодец что не сидишь в офисе и протираешь зад, решая в 80% задач не приносящих удовлетворения.

    Но

    Модель получилась адекватная? Технико экономические показатели в студию!

    За месяц игры в решиме адвайзера потратил столько то заработал столько то

    За месяц игры в режиме автопилота такто.


  1. rettsu
    22.07.2025 07:54

    Лайк за то, что в принципе занялся таким проектом. Хотя подобные боты использовались ещё лет 7-8 назад. Для некоторых их них писали отдельные игровые профили: кэш, МТТ, сит энд ГОУ, спины... Я и сам немного писал.

    Понятно, что можно играть по EV, но я не увидел что конкретно (кроме монте-карло) реализовано (может пропустил по ходу текста). Потому что добавить в софт можно очень многое. Чарты стартовых рук в зависимости от позиции, глубины стеков и действий на префлопе. Сбор статистики на игроков (если софт рума не скрывает никнеймы). Статистика очень помогает принимать верные решения и эксплуатировать игроков. Расчет эквити и анализ диапазонов оппонентов. Если прям совсем задаться целью, то можно и на лету разбирать деревья решений по GTO (как это реализовано в GTOWizard). А ещё затачивать логику игры под разные типы игры (кэш, МТТ и так далее).

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