Бои UFC стали возможностью для ставок. Если бы вы могли предсказать победителя боя на основе статистики бойцов, вы могли бы делать подходящие ставки.

Создание бинарного классификатора

Этот туториал настроит вас на дополнительные прогнозы на бои UFC. Telepath SDK позволит вам построить модель в Node.js и позволит вам сосредоточиться на задаче создания интересных функций.

Предпосылки:

Приготовьте следующее:

  1. Учетная запись телепата
  2. Прочтите Руководство по началу работы или используйте Telepath Github для создания экземпляра проекта.

Получение данных

Данные, использованные для моделирования, размещены здесь.

✳️ Функции – это функции, которые будут объединены для создания прогностических функций, отмеченных зеленой звездочкой.

Fights

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

Fighters

Match_Stats

Использование Telepath для создания модели

Чтобы создать конвейер Telepath, вы должны объявить:

  1. Source : подключение к базе данных
  2. Pipeline : SQL-подобный запрос, который определяет инфраструктуру данных для прогнозов.
  3. ModelSpec : определение вашей модели, используемое для создания прогнозов.

Определить источник

import { ModelSpec, Pipeline, Source } from '@telepath/telepath';
const source = new Source("ufc", {
    sourceConnectionSlug: "ufc"
});

Чтобы узнать больше о создании Source, прочитайте это руководство.

Определить конвейер

Боевая статистика бойца за последний год:

Прогностические функции, показанные выше из таблицы Match_Stats, необходимо агрегировать и усреднить. Из некоторого опыта мы знаем, что последний год боев важнее, чем средний показатель за всю карьеру.

Поскольку в каждом бою участвуют два бойца; создайте файл utils.ts и создайте функцию для генерации SQL-запроса за последний год, в котором боец ​​сражался.

import { Source } from '@telepath/telepath';
import { Query, QuerySelect, QueryWhere } from '@telepath/telepath';
import { InputWrap } from '@telepath/runtime';
export function last_year_stats(num_fighter: string, source: Source): InputWrap<Query> {

    let select: QuerySelect = {};
    select["last_year"] = {
        max: "fights.year"
    }
    select["fighter_x_id".replace("x", num_fighter)] = "fighters.id"

    let query: InputWrap<Query> = {
        from: {
            sourceId: source.id,
            table: "fighters"
        },
        select: select,
        join: [
            {
                target: {
                    sourceId: source.id,
                    table: "fights"
                },
                on: ["fights.fighter_x_id".replace("x", num_fighter), "fighters.id"]
            }
        ],
        groupBy: ["fighters.id"]
    };

    return query
};

Обратите внимание на замену строки в функции на ключе оператора select. Используя этот метод, вы можете создавать столбцы с динамическими именами, на которые можно ссылаться позже в вашем запросе.

Эта функция сгенерирует запрос, похожий на следующий SQL:

select max(year) as max_year, fighter_one_id as fighter_id
from fighters
join fights on fights.fighter_one_id = fighters.id
group by fights.fighter_one_id

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

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

export function last_year_fought_stats(num_fighter: string, source: Source): InputWrap<Query> {

    let select: QuerySelect = {};
    select["kd_fighter_x_avg".replace("x", num_fighter)] = {
        avg: "ms.kd"
    };
    select["str_fighter_x_avg".replace("x", num_fighter)] = {
        avg: "ms.str"
    };
    select["td_fighter_x_avg".replace("x", num_fighter)] = {
        avg: "ms.td"
    };
    select["sub_fighter_x_avg".replace("x", num_fighter)] = {
        avg: "ms.sub"
    };
    select["fighter_x_id".replace("x", num_fighter)] = "ms.fighter_id";

    let where: QueryWhere = {};
    where["last_year_fighting_fighter_x.last_year".replace("x", num_fighter)] = {
        column: "fights.year",
    };

    let query: InputWrap<Query> = {
        from: {
            sourceId: source.id,
            table: "match_stats",
            as: "ms"
        },
        select: select,
        join: [
            {
                target: {
                    sourceId: source.id,
                    table: "fights"
                },
                on: ["ms.fighter_id", "fights.fighter_x_id".replace("x", num_fighter)]
            },
            {
                target: "last_year_fighting_fighter_x".replace("x", num_fighter),
                on: ["last_year_fighting_fighter_x.fighter_x_id".replace("x", num_fighter).replace("x", num_fighter),
                    "ms.fighter_id"]
            }
        ],
        where: where,
        groupBy: ["ms.fighter_id"]
    }

    return query
}

Обратите внимание на замену строки в ключе как в QuerySelect, так и в QueryWhere. Этот ключ (last_year_fighting_fighter_x) будет использоваться в Query и конвейере в файле index.ts, где он будет именем CTE, сгенерированным для максимального года истребителя.

Оператор SQL, который будет соответствовать вышеуказанной функции:

select avg(ms.kd) as kd_fighter_one, avg(ms.str) as str_fighter_one, avg(ms.td) as td_fighter_one, avg(ms.sub) as sub_fighter_one, ms.fighter_id as fighter_one_id
from match_stats as ms
join fights on fights.fighter_one_id = ms.fighter_id
join max_year_fighter_one on ms.fighter_id = max_year_fighter_one .fighter_id
where max_year_fighter_one .max_year = fights.year
group by ms.fighter_id

Боевая статистика и текущий пояс:

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

export function fighter_stats(num_fighter: string, source: Source): InputWrap<Query> {

    let select: QuerySelect = {};
    select["fighter_x_height".replace("x", num_fighter)] = "fighters.ht"
    select["fighter_x_weight".replace("x", num_fighter)] = "fighters.wt"
    select["fighter_x_reach".replace("x", num_fighter)] = "fighters.reach"
    select["fighter_x_stance".replace("x", num_fighter)] = "fighters.stance"
    select["fighter_x_w".replace("x", num_fighter)] = "fighters.w"
    select["fighter_x_l".replace("x", num_fighter)] = "fighters.l"
    select["fighter_x_d".replace("x", num_fighter)] = "fighters.d"
    select["fighter_x_belt".replace("x", num_fighter)] = "fighters.belt"
    select["fighter_x_id".replace("x", num_fighter)] = "fighters.id"

    let query: InputWrap<Query> = {
        from: {
            sourceId: source.id,
            table: "fighters"
        },
        select: select
    }

    return query
}

Объединение функций запроса CTE в конвейер

Чтобы избежать записи всего оператора select в базовом запросе, можно написать выражение CTE, на которое затем ссылаются в базовых запросах From. Обратите внимание, что отсутствие выбора равносильно select * from ... .

export const ufc_fights_pipeline = new Pipeline("ufc_fights_dataset", {
    input: {
        query: {
            from: "winner",
            with: {
                "last_year_fighting_fighter_one": last_year_stats("one", source),
                "last_year_fighting_fighter_two": last_year_stats("two", source),
                "last_year_fought_stats_fighter_one": last_year_fought_stats("one", source),
                "last_year_fought_stats_fighter_two": last_year_fought_stats("two", source),
                "fighter_stats_fighter_one": fighter_stats("one", source),
                "fighter_stats_fighter_two": fighter_stats("two", source),
                "winner": {
                    select: {
                        "winner": "fights.winner",
                        "fight_id": "fights.id",
                        "fighter_one_id": "fights.fighter_one_id",
                        "fighter_two_id": "fights.fighter_two_id"
                    },
                    from: {
                        sourceId: source.id,
                        table: "fights"
                    }
                }
            },
            join: [
                {
                    target: "last_year_fought_stats_fighter_one",
                    on: ["last_year_fought_stats_fighter_one.fighter_one_id_ms", "winner.fighter_one_id"]
                },
                {
                    target: "last_year_fought_stats_fighter_two",
                    on: ["last_year_fought_stats_fighter_two.fighter_two_id_ms", "winner.fighter_two_id"]
                },
                {
                    target: "fighter_stats_fighter_one",
                    on: ["fighter_stats_fighter_one.fighter_one_id_s", "winner.fighter_one_id"]
                },
                {
                    target: "fighter_stats_fighter_two",
                    on: ["fighter_stats_fighter_two.fighter_two_id_s", "winner.fighter_two_id"]
                }
            ]
        }
    }
});

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

Определить ModelSpec

import { ModelSpec } from "@telepath/telepath";
const model = new ModelSpec('ufc_model', {
    targetColumn: 'winner',
    pipelineId: ufc_fights_pipeline.id
})

Чтобы узнать больше о создании ModelSpec, прочитайте этот учебник.

Развернуть инфраструктуру

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

npx telepath deploy

Чтобы обучить модель, перейдите в пользовательский интерфейс Telepath:

  1. Выберите вкладку модели.
  2. Выберите модель, которая была обучена последней, с именем ufc_model.
  3. Нажмите кнопку «Начать обучение» и дождитесь создания API.

Предсказания

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

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

Всего при составлении прогноза будет два SQL-запроса.

with fighter_dynamic (
	select 
		avg(ms.kd) as fighter_kd, avg(ms.td) as fighter_td,
	  avg(ms.str) as fighter_str, avg(ms.sub) as fighter_sub
		ms.fighter_id
	from match_stats as ms
	group by ms.fighter_id
	where ms.fighter_id = "FIGHTER_ID_OF_PREDICTION_FIGHTER"
), fighter_static (
	select 
		fighters.reach, fighters.height, fighters.stance, fighters.weight,
	  fighters.belt, fighters.d, fighters.l, fighters.w, fighters.id
	from figthers
	where fighters.fighter_id = "FIGHTER_ID_OF_PREDICTION_FIGHTER"
)
select *
from fighter_static as fs
join fighter_dynamic as fd on fs.id = fd.fighter_id

Использование приведенных выше операторов SQL для сбора необходимой прогнозной информации.

{
  "data": {
    "fighter_one_d": "0",
    "fighter_one_l": "1",
    "fighter_one_w": "7",
    "fighter_one_reach": "79\\"",
    "fighter_one_height": "6' 4\\"",
    "fighter_one_stance": "Orthodox",
    "fighter_one_weight": "185 lb",
    "kd_fighter_one_avg": "0.214",
    "td_fighter_one_avg": "0.124",
    "str_fighter_one_avg": "2.125",
    "sub_fighter_one_avg": "1.25",
    "fighter_one_belt": "0",
    "fighter_two_d": "1",
    "fighter_two_l": "2",
    "fighter_two_w": "9",
    "fighter_two_reach": "79\\"",
    "fighter_two_height": "6' 1\\"",
    "fighter_two_stance": "Orthodox",
    "fighter_two_weight": "185 lb",
    "kd_fighter_two_avg": "0.214",
    "td_fighter_two_avg": "0.124",
    "str_fighter_two_avg": "2.125",
    "sub_fighter_two_avg": "1.25",
    "fighter_two_belt": "0"
  },
  "includeConfidence": true
}

После того, как данные собраны и помещены в test.json, полезная нагрузка может быть отправлена ​​с использованием приведенного ниже cURL, и ответом будет прогноз.

curl --location --request POST '<https://api.telepath.io/predict/ID-OF-MODEL>' \\
--header 'X-API-KEY: YOUR_X_API_KEY' \\
--header 'Content-Type: application/json' \\
-d "@test.json"

Ответ прогноза:

{
    "prediction": [
        [
            1,  // Fighter two will win
            0.8211654911996858  // 82% confidence in the prediction
        ]
    ]
}

Следующие шаги

С UFC

Обратите внимание, что это не все данные, которые можно собрать о бойце и бое UFC. Вы можете обновить эту модель с дополнительными функциями, которые вы создаете или добавляете в модель.

С SDK Telepath

Если вы заинтересованы в использовании Telepath, вы можете зарегистрироваться здесь.