Отказ от ответственности: Анализ и прогнозирование, сделанные здесь, предназначены только для обучения и не должны использоваться для любой незаконной деятельности, такой как ставки.
Введение
Крикет, игра битой и мячом, является одной из самых популярных игр и проводится в различных форматах. Это игра чисел, каждый матч генерирует множество данных об игроках и матчах. Эти данные используются аналитиками и специалистами по исследованию данных для получения значимых выводов и прогнозов о матчах и результатах игроков. На этом занятии я проведу аналитику и прогнозирование данных по крикету с помощью Microsoft ML.Net framework и C#.
История
- Изобретен в 1550 году в Англии.
- Первый международный матч был сыгран между Канадой и США в Нью-Йорке
- Первый однодневный международный матч (ODI) был сыгран в 1971 году
- Первый Кубок мира по крикету был разыгран в 1975 году
- Первый матч T20 был сыгран в 2003 году
Форматы матчей
- Однодневный международный матч (ODI) : 50 Over
- Тестовый матч : 5 дней
- T20 : 20 оверов
- Индийская премьер-лига : 20 оверов
Статистика IPL (2019-2020)
- Страны-участницы: 104
- Стоимость : $6,7 млрд
- Зрители: 370 миллионов
Если мы посмотрим на вышеприведенную статистику, она ясно показывает популярность и деньги, связанные с крикетом.
Постановка задачи
Проанализировать набор данных по крикету и предсказать счет после 6 уинов в матче.
Обзор решения
Поскольку нам нужно предсказать непрерывное значение, это проблема регрессии в пространстве машинного обучения. Мы будем использовать .Net DataFrame для эксплораторного анализа данных (EDA) и ML.Net для прогнозирования счета по определенному мячу в течение 6 уиверсов.
Платформа/инструмент
Я использовал интерактивный инструмент .Net в Jupyter Notebook для создания файла *.ipynb с C# в качестве ядра.
Необходимые условия
- блокнот Jupyter Notebook
- dotnet interactive
Набор данных
Источник: CricSheet > Download T20 Dataset
Эксплораторный анализ данных — EDA
Резюме анализа данных
- Количество характеристик: 22
- Всего записей: 194K
- Характеристики имеют сочетание даты, числа и строки
- Содержит нулевые значения
Мы будем выполнять команду в интерактивном блокноте Jupyter на платформе .Net.
Давайте начнем
Откройте командную строку или powershell и выполните команду ‘Jupyter notebook’. В браузере по умолчанию откроется новое окно.
Нажмите кнопку Создать и выберите .Net(C#) в качестве ядра для создания нового блокнота.
Будет создан новый блокнот. Дайте ему имя, нажав на Untitled, и выполните следующую команду в ячейке, чтобы проверить установку .Net interactive.
1. Определите широкие элементы приложения
Установите пакеты Nuget
// ML.NET Nuget packages installation
#r "nuget:Microsoft.ML"
#r "nuget:Microsoft.ML.FastTree"
#r "nuget:Microsoft.Data.Analysis"
#r "nuget:Daany.DataFrame, 1.0.0"
#r "nuget:CsvHelper"
Импортировать пространства имен
using CsvHelper;
using CsvHelper.Configuration;
using Daany;
using Microsoft.ML;
using Microsoft.ML.Data;
using Microsoft.Data.Analysis;
using Microsoft.AspNetCore.Html;
using Microsoft.DotNet.Interactive.Formatting;
using static Microsoft.DotNet.Interactive.Formatting.PocketViewTags;
using System.Net.Http;
using System.IO;
using System.IO.Compression;
using System.Globalization;
Определить константы
// File
const string DATASET_DIRECTORY = "t20s_male_csv2";
const string DATASET_FILE = "t20s_male_csv2.zip";
const string DATASET_URL = "https://cricsheet.org/downloads/";
const string DATASET_MERGED_CSV = "t20_merged.csv";
const string DATASET_ALL_CSV = "t20_all.csv";
const string DATASET_CLEANED_CSV = "t20_cleaned.csv";
const string MODEL_FILE = "model.zip";
const string SEARCH_PATTERN = "*.csv";
// DataFrame/Table
const int TOP_COUNT = 5;
const int DEFAULT_ROW_COUNT = 10;
// Columns
const string CSV_COLUMN_BALL = "ball";
const string CSV_COLUMN_SCORE = "score";
const string CSV_COLUMN_RUNS_OFF_BAT = "runs_off_bat";
const string CSV_COLUMN_EXTRAS = "extras";
const string CSV_COLUMN_TOTAL_SCORE = "total_score";
const string CSV_COLUMN_VENUE = "venue";
const string CSV_COLUMN_INNINGS = "innings";
const string CSV_COLUMN_BATTING_TEAM = "batting_team";
const string CSV_COLUMN_BOWLING_TEAM = "bowling_team";
const string CSV_COLUMN_STRIKER = "striker";
const string CSV_COLUMN_NON_STRIKER = "non_striker";
const string CSV_COLUMN_BOWLER = "bowler";
// Misc
const float OVER_THRESHOLD = 6.0f;
2. Функции утилиты
Объединяет несколько CSV-файлов, находящихся в указанном каталоге
Набор данных, загруженный из CricSheet, представляет собой zip-файл с несколькими файлами, относящимися к матчу. Чтобы использовать его для анализа, необходимо объединить все файлы в один файл. Приведенный ниже метод поможет объединить эти файлы
/// <summary>
/// Merges files present in a directory into a single file
/// </summary>
/// <param name="sourceFolder">Directory containing files to be merged</param>
/// <param name="destinationFile">Output single file</param>
/// <param name="searchPattern">Pattern to be searched for files within a directory such as *.csv</param>
/// <param name="excludeFiles">A predicate to exclude files</param>
public void MergeCsv(string sourceFolder, string destinationFile, string searchPattern, Predicate<string> excludeFiles)
{
/*
https://chris.koester.io/index.php/2017/01/27/combine-csv-files/
C# script combines multiple csv files without duplicating headers.
Combining 8 files with a combined total of about 9.2 million rows
took about 3.5 minutes on a network share and 44 seconds on an SSD.
*/
// Specify wildcard search to match CSV files that will be combined
string[] filePaths = Array.FindAll(Directory.GetFiles(sourceFolder, searchPattern, SearchOption.AllDirectories), excludeFiles);
StreamWriter fileDest = new StreamWriter(destinationFile, true);
int i;
for (i = 0; i < filePaths.Length; i++)
{
string file = filePaths[i];
string[] lines = File.ReadAllLines(file);
if (i > 0)
{
lines = lines.Skip(1).ToArray(); // Skip header row for all but first file
}
foreach (string line in lines)
{
fileDest.WriteLine(line);
}
}
fileDest.Close();
}
Извлеченные файлы содержат файлы, заканчивающиеся на ‘info’, которые имеют другой набор колонок, и нам нужно игнорировать их при объединении. Для этого вводится вспомогательный метод.
/// <summary>
/// Exclude files based on a condition
/// </summary>
/// <param name="fileName">Name of the file</param>
/// <returns>True, if matched else false</returns>
public bool ExcludeFiles(string fileName)
{
if (fileName.EndsWith("info.csv"))
{
return false;
}
return true;
}
Форматировщики
По умолчанию вывод DataFrame не является корректным, и для того, чтобы отобразить его в виде таблицы, нам необходимо реализовать пользовательский форматер, как показано в следующей ячейке.
// Formats the table
Formatter.Register(typeof(Microsoft.Data.Analysis.DataFrame),(dataFrame, writer) =>
{
var df = dataFrame as Microsoft.Data.Analysis.DataFrame;
var headers = new List<IHtmlContent>();
headers.Add(th(i("index")));
headers.AddRange(df.Columns.Select(c => (IHtmlContent)th(c.Name)));
var rows = new List<List<IHtmlContent>>();
var take = 10;
for (var i = 0; i < Math.Min(take, df.Rows.Count); i++)
{
var cells = new List<IHtmlContent>();
cells.Add(td(i));
foreach (var obj in df.Rows[i])
{
cells.Add(td(obj));
}
rows.Add(cells);
}
var t = table(
thead(
headers),
tbody(
rows.Select(
r => tr(r))));
writer.Write(t);
}, "text/html");
3. Загрузить набор данных
Набор данных, присутствующий в CricSheet, находится в сжатом формате zip. Внутри он содержит файл csv, который мы будем использовать для анализа и прогнозирования.
3.a Очистить предыдущие данные
Приведенный ниже метод позволяет очистить все предыдущие данные в системе
/// <summary>
/// Clear previous data present in current working directory
/// </summary>
public void ClearPreviousData()
{
if (File.Exists(DATASET_FILE))
{
File.Delete(DATASET_FILE);
}
if (File.Exists(DATASET_MERGED_CSV))
{
File.Delete(DATASET_MERGED_CSV);
}
if (File.Exists(DATASET_CLEANED_CSV))
{
File.Delete(DATASET_CLEANED_CSV);
}
if (Directory.Exists(DATASET_DIRECTORY))
{
Directory.Delete(DATASET_DIRECTORY, true);
}
}
3.b Загрузка набора данных в DataFrame
/// <summary>
/// Loads the dataset from specified URL
/// </summary>
/// <param name="url">Remote URL</param>
/// <param name="fileName">Name of the file</param>
/// <returns>A DataFrame</returns>
public async Task<Microsoft.Data.Analysis.DataFrame> LoadDatasetAsync(string url, string fileName)
{
// Delete previous data
ClearPreviousData();
// Loads zip file from remote URL
var remoteFilePath = Path.Combine(url, fileName);
using (var httpClient = new HttpClient())
{
var contents = await httpClient.GetByteArrayAsync(remoteFilePath);
await File.WriteAllBytesAsync(fileName, contents);
}
// Unzip file -> Merge CSV -> Load to DataFrame
if (File.Exists(fileName))
{
var extractedDirectory = Path.Combine(Directory.GetCurrentDirectory(), DATASET_DIRECTORY);
try
{
ZipFile.ExtractToDirectory(fileName, extractedDirectory);
MergeCsv(extractedDirectory, DATASET_MERGED_CSV, SEARCH_PATTERN, ExcludeFiles);
return Microsoft.Data.Analysis.DataFrame.LoadCsv(DATASET_MERGED_CSV);
}
catch (Exception e)
{
display(e);
throw;
}
}
return new Microsoft.Data.Analysis.DataFrame();
}
Теперь мы загрузим DataFrame из файлов.
// Load Dataset
var cricketDataFrame = await LoadDatasetAsync(DATASET_URL, DATASET_FILE);
Предварительный просмотр этого датафрейма выглядит следующим образом
3.c Фильтрация
Поскольку мы рассматриваем данные за первые 6 оверов матча, нам нужно удалить данные за последующие оверы.
// Filter dataset to six overs
var filterColumn = cricketDataFrame.Columns[CSV_COLUMN_BALL].ElementwiseLessThanOrEqual(OVER_THRESHOLD);
var sixOverDataFrame = cricketDataFrame.Filter(filterColumn);
4. Анализ данных
Мы проанализируем отфильтрованный кадр данных, выполняя различные команды
4.a Верхние записи
sixOverDataFrame.Head(TOP_COUNT)
4.b Информация о наборе данных
// Information about the dataset
sixOverDataFrame.Info()
Мы видим, что в нашем наборе данных, состоящем из шести столбцов, имеется около 95264 записей. В первой строке представлен тип данных каждого столбца.
4.c Описание набора данных
// Description about the dataset
sixOverDataFrame.Description()
Минимальное значение 0,1 и максимальное значение 5,9 подтверждают, что набор данных состоит только из 6 оверов.
4.d Количество очков и общее количество очков
В наборе данных содержатся различные пробежки, набранные за каждый мяч, такие как runs_off_bat, extras, wides, legbyes и т.д. Нет столбца для количества пробежек, набранных за мяч. Мы можем получить его, сложив ‘runs_off_bat’ и ‘extras’.
Score = runs_off_bat + extras
Аналогично мы можем получить общее количество пробежек за мяч, имея кумулятивную сумму пробежек за мяч в иннинге.
Total Score = CumulativeSum(Score/inning)
Нам нужно добавить новый столбец (Score) в наш фрейм данных. Для добавления нового столбца используется класс PrimitiveDataFrameColumn.
// Add a column for Total Score
sixOverDataFrame.Columns.Add(new PrimitiveDataFrameColumn<int>(CSV_COLUMN_SCORE, sixOverDataFrame.Rows.Count));
sixOverDataFrame.Columns[CSV_COLUMN_SCORE] = sixOverDataFrame.Columns[CSV_COLUMN_RUNS_OFF_BAT] + sixOverDataFrame.Columns[CSV_COLUMN_EXTRAS];
// Information about the dataframe
sixOverDataFrame.Info()
Мы видим, что теперь добавлен новый столбец (счет).
Теперь мы добавим Total Score, который представляет собой общее количество пробежек, набранных до этого мяча.
/// <summary>
/// Adds a total score column to a DataFrame
/// </summary>
/// <param name="df">DataFrame to be updated</param>
public void AddTotalScorePerBall(Microsoft.Data.Analysis.DataFrame df)
{
// calculate total_score per ball
df.Columns.Add(new PrimitiveDataFrameColumn<Single>(CSV_COLUMN_TOTAL_SCORE, df.Rows.Count));
Single previousMatchId = -1;
Single previousInning = -1;
Single previousScore = -1;
foreach (var dfRow in df.Rows)
{
var matchId = (Single)dfRow[0];
var inning = (Single)dfRow[4];
var score = (Single)dfRow[df.Columns.Count - 2]; // Score
if (previousMatchId == -1) // First time
{
// Reset
previousMatchId = matchId;
previousInning = inning;
if (previousScore == -1)
{
previousScore = score;
}
}
if (matchId == previousMatchId && inning == previousInning)
{
Single newScore = previousScore + score;
// Total Score
dfRow[df.Columns.Count - 1] = (Single)newScore;
previousScore = newScore;
}
else
{
// Total Score
dfRow[df.Columns.Count - 1] = (Single)score;
// Reset
previousMatchId = -1;
previousInning = -1;
previousScore = score;
}
}
}
// Adds a total score column per pall
AddTotalScorePerBall(sixOverDataFrame);
// Display dataframe
display(sixOverDataFrame)
4.e Выбор признаков
Всего в наборе данных 24 столбца. Не все из них подходят для прогнозирования. Для выбора признаков существуют различные стратегии, такие как матрица смешения, график сиборна, которые могут быть применены для выбора подходящих признаков. Чтобы не усложнять ситуацию, я выбрал несколько параметров после проведения различных комбинаций. Выбранные признаки выглядят следующим образом.
Входные характеристики
- место проведения
- иннинги
- мяч
- битая_команда
- боулинг_команда
- нападающий
- не_страйкер
- боулер
Выходные данные
- total_runs
// Remove extra features
sixOverDataFrame.Columns.Remove("match_id");
sixOverDataFrame.Columns.Remove("season");
sixOverDataFrame.Columns.Remove("start_date");
sixOverDataFrame.Columns.Remove("runs_off_bat");
sixOverDataFrame.Columns.Remove("extras");
sixOverDataFrame.Columns.Remove("wides");
sixOverDataFrame.Columns.Remove("noballs");
sixOverDataFrame.Columns.Remove("byes");
sixOverDataFrame.Columns.Remove("legbyes");
sixOverDataFrame.Columns.Remove("penalty");
sixOverDataFrame.Columns.Remove("player_dismissed");
sixOverDataFrame.Columns.Remove("other_wicket_type");
sixOverDataFrame.Columns.Remove("other_player_dismissed");
sixOverDataFrame.Columns.Remove("score");
sixOverDataFrame.Columns.Remove("wicket_type");
Мы видим, что у нас есть 9 колонок, имеющих отношение только к нашей проблеме.
// Information about the dataset
sixOverDataFrame.Info()
Мы закончили анализ данных и получили набор данных, готовый для обучения модели и прогнозирования, которые будут описаны в следующем разделе.
Машинное обучение — обучение и прогнозирование
В этом разделе мы будем использовать подготовленный набор данных для составления прогнозов с помощью фреймворка ML.Net.
Примечание Для того чтобы делать предсказания, ML.Net требуется файл csv, который может быть загружен с помощью API ML.Net. Не существует прямого способа загрузки DataFrame для построения прогнозов, а DataFrame API не предоставляет никакого API для сохранения DataFrame в csv-файл.
Я обнаружил библиотеку ‘Danny’, которая позволяет создавать, манипулировать и сохранять DataFrame в CSV-файл.
Ссылка: https://github.com/bhrnjica/daany
Я буду использовать эту библиотеку для воссоздания DataFrame, выполнения модификаций, как описано выше, и сохранения его в CSV-файл для использования в прогнозировании. Я обнаружил, что она очень гибкая. Поскольку это в некотором роде повторяющаяся вещь, не стесняйтесь переходить к разделу «Обучение» для создания и обучения модели.
Манипулирование данными
Мы будем выполнять следующие манипуляции/преобразования с набором данных
- Удалить записи до 6 оверлей
- Удалите недостающие значения
- Добавьте столбец ‘score’, представляющий количество пробежек за мяч
- Добавьте столбец ‘total_score’, чтобы получить общий счет до текущего мяча.
Примечание
Файл набора данных csv имеет некоторые значения, которые содержат запятую ‘,’ в значении ячейки, например, место проведения матча «Simonds Stadium, South Geelong». В результате API ML.Net LoadFromTextFile(…) загружает их неправильно и считает их двумя отдельными значениями вместо одного. Чтобы решить эту проблему, я написал вспомогательную функцию CreateCsvAndReplaceSeparatorInCells(…) для замены запятой на указанный символ.
/// <summary>
/// Replace a character in a cell of csv with a defined separator
/// </summary>
/// <param name="inputFile">Name of input file</param>
/// <param name="outputFile">Name of output file</param>
/// <param name="separator">Separator such as comma</param>
/// <param name="separatorReplacement">Replacement character such as underscore</param>
private static void CreateCsvAndReplaceSeparatorInCells(string inputFile, string outputFile, char separator, char separatorReplacement)
{
var culture = CultureInfo.InvariantCulture;
using var reader = new StreamReader(inputFile);
using var csvIn = new CsvReader(reader, new CsvConfiguration(culture));
using var recordsIn = new CsvDataReader(csvIn);
using var writer = new StreamWriter(outputFile);
using var outCsv = new CsvWriter(writer, culture);
// Write Header
csvIn.ReadHeader();
var headers = csvIn.HeaderRecord;
foreach (var header in headers)
{
outCsv.WriteField(header.Replace(separator, separatorReplacement));
}
outCsv.NextRecord();
// Write rows
while (recordsIn.Read())
{
var columns = recordsIn.FieldCount;
for (var index = 0; index < columns; index++)
{
var cellValue = recordsIn.GetString(index);
outCsv.WriteField(cellValue.Replace(separator, separatorReplacement));
}
outCsv.NextRecord();
}
}
// Replace separator in cells
CreateCsvAndReplaceSeparatorInCells(DATASET_MERGED_CSV, DATASET_ALL_CSV, ',', '_');
// Load CSV
Daany.DataFrame cricketDaanyDataFrame = Daany.DataFrame.FromCsv(DATASET_ALL_CSV);
display(cricketDaanyDataFrame.RowCount());
Удалить недостающие значения
// Remove missing values
var missingValues = cricketDaanyDataFrame.MissingValues();
List<string> nonNullColumns = new List<string>();
foreach (var dfColumn in cricketDaanyDataFrame.Columns)
{
if (!missingValues.ContainsKey(dfColumn))
{
display(dfColumn);
nonNullColumns.Add(dfColumn);
}
}
cricketDaanyDataFrame = cricketDaanyDataFrame[nonNullColumns.ToArray()];
display(cricketDaanyDataFrame);
Фильтр набора данных для включения до 6 оверов и отбрасывания после этого
// Filter dataset to include till 6 overs and discard beyond that
var sixBallDaanyDataFrame = cricketDaanyDataFrame.Filter(CSV_COLUMN_BALL, OVER_THRESHOLD, FilterOperator.LessOrEqual);
Добавить колонку «счет
Вычисление общего количества очков
Нет прямого способа вычислить общий счет, поскольку он должен быть кумулятивной суммой и ограничен только 6 оверами в наборе данных. Я написал функцию для вычисления общего счета
/// <summary>
/// Adds a total_score column to the DataFrame by calculating the cumulative sum
/// </summary>
/// <param name="df">DataFrame to which new column is added</param>
public void AddTotalScorePerBallDaany(Daany.DataFrame df)
{
// https://github.com/bhrnjica/daany
// calculate total_score per ball
df.AddCalculatedColumn(CSV_COLUMN_TOTAL_SCORE, (r, i) =>
{
var response = Convert.ToSingle(r[CSV_COLUMN_SCORE]);
return 0;
});
int previousMatchId = -1;
int previousInning = -1;
float previousScore = -1;
int rowIndex = 0;
foreach (var dfRow in df.GetRowEnumerator())
{
var matchId = (int)dfRow[0];
var inning = (int)dfRow[4];
var score = Convert.ToSingle(dfRow[df.Columns.Count - 2]); // Score
if (previousMatchId == -1) // First time
{
// Reset
previousMatchId = matchId;
previousInning = inning;
if (previousScore == -1)
{
previousScore = score;
}
}
if (matchId == previousMatchId && inning == previousInning)
{
float newScore = previousScore + score;
// Total Score
dfRow[df.Columns.Count - 1] = newScore;
df[rowIndex, df.Columns.Count - 1] = newScore;
previousScore = newScore;
}
else
{
// Total Score
dfRow[df.Columns.Count - 1] = score;
df[rowIndex, df.Columns.Count - 1] = score;
// Reset
previousMatchId = -1;
previousInning = -1;
previousScore = score;
}
rowIndex++;
}
}
// Add Total score per ball to Daany Dataframe
AddTotalScorePerBallDaany(sixBallDaanyDataFrame);
display(sixBallDaanyDataFrame);
// Filter columns
sixBallDaanyDataFrame = sixBallDaanyDataFrame[CSV_COLUMN_VENUE, CSV_COLUMN_INNINGS, CSV_COLUMN_BALL, CSV_COLUMN_BATTING_TEAM, CSV_COLUMN_BOWLING_TEAM, CSV_COLUMN_STRIKER, CSV_COLUMN_NON_STRIKER, CSV_COLUMN_BOWLER, CSV_COLUMN_TOTAL_SCORE];
display(sixBallDaanyDataFrame);
Колонки окончательного набора данных
- Место проведения
- Иннинги
- Мяч
- Бьющий_команда
- Боулинг_команда
- Страйкер
- Не_страйкер
- Боулер
- Общий_результат
Сохранить кадр данных в CSV
// Save DataFrame to CSV
Daany.DataFrame.ToCsv(DATASET_CLEANED_CSV, sixBallDaanyDataFrame);
Обучение
Классы данных
Нам нужно создать несколько структур данных для сопоставления столбцов в нашем наборе данных.
Match
Match содержит входные признаки, участвующие в предсказании, и список отфильтрованных столбцов.
/// <summary>
/// Represents the input features involved in prediction
/// </summary>
public class Match
{
/// <summary>
/// Match venue
/// </summary>
[LoadColumn(0)]
public string Venue { get; set; }
/// <summary>
/// Innning within a Match
/// </summary>
[LoadColumn(1)]
public float Inning { get; set; }
/// <summary>
/// Current Ball being thrown
/// </summary>
[LoadColumn(2)]
public float Ball { get; set; }
/// <summary>
/// Name of the batting team
/// </summary>
[LoadColumn(3)]
public string BattingTeam { get; set; }
/// <summary>
/// Name of the bowling team
/// </summary>
[LoadColumn(4)]
public string BowlingTeam { get; set; }
/// <summary>
/// Batsman on strike
/// </summary>
[LoadColumn(5)]
public string Striker { get; set; }
/// <summary>
/// Non striker batsman
/// </summary>
[LoadColumn(6)]
public string NonStriker { get; set; }
/// <summary>
/// Current bowler
/// </summary>
[LoadColumn(7)]
public string Bowler { get; set; }
/// <summary>
/// Total score till the current ball
/// </summary>
[LoadColumn(8)]
public float TotalScore { get; set; }
public override string ToString()
{
var sb = new StringBuilder();
sb.Append($"Venue: {Venue}");
sb.Append($"nBatting Team: {BattingTeam}");
sb.Append($"nBowling Team: {BowlingTeam}");
sb.Append($"nInning: {Inning}");
sb.Append($"nBall: {Ball}");
sb.Append($"nStriker: {Striker}");
sb.Append($"nNon-Striker: {NonStriker}");
sb.Append($"nBowler: {Bowler}");
return sb.ToString();
}
}
MatchScorePrediction
/// <summary>
/// Manages the score prediction
/// </summary>
public class MatchScorePrediction
{
/// <summary>
/// Total runs scored by the team at the specified ball
/// </summary>
[ColumnName("Score")]
public float TotalScore { get; set; }
}
Загрузка набора данных
Теперь загрузим набор данных в MLContext фреймворка ML.Net для выполнения предсказания.
// Load the dataset
var mlContext = new MLContext(seed:1);
IDataView data = mlContext.Data.LoadFromTextFile<Match>(
path:DATASET_CLEANED_CSV,
hasHeader: true,
separatorChar: ',');
Разделение набора данных
Для получения лучшего результата всегда рекомендуется разделить набор данных на обучающий и тестовый.
// Split dataset
var trainTestData = mlContext.Data.TrainTestSplit(data, 0.2); // Training/Test : 80/20
Преобразование набора данных
Теперь мы создадим конвейер машинного обучения для обучения нашей модели. OneHotEncoding используется для нечисловых входных переменных. Она преобразует их в числовой формат, понятный алгоритму ML.
// Transform
var dataProcessPipeline = mlContext.Transforms.CopyColumns(outputColumnName: "Label", inputColumnName: nameof(Match.TotalScore))
.Append(mlContext.Transforms.Categorical.OneHotEncoding("VenueEncoded", nameof(Match.Venue)))
.Append(mlContext.Transforms.Categorical.OneHotEncoding("BattingTeamEncoded", nameof(Match.BattingTeam)))
.Append(mlContext.Transforms.Categorical.OneHotEncoding("BowlingTeamEncoded", nameof(Match.BowlingTeam)))
.Append(mlContext.Transforms.Categorical.OneHotEncoding("StrikerEncoded", nameof(Match.Striker)))
.Append(mlContext.Transforms.Categorical.OneHotEncoding("NonStrikerEncoded", nameof(Match.NonStriker)))
.Append(mlContext.Transforms.Categorical.OneHotEncoding("BowlerEncoded", nameof(Match.Bowler)))
.Append(mlContext.Transforms.Concatenate("Features",
"VenueEncoded",
nameof(Match.Inning),
nameof(Match.Ball),
"BattingTeamEncoded",
"BowlingTeamEncoded",
"StrikerEncoded",
"NonStrikerEncoded",
"BowlerEncoded"
));
Обучение
Метод Fit() позволяет обучить модель на заданном наборе данных. Здесь мы использовали алгоритм регрессии FastTree.
// Train
var trainingPipeline = dataProcessPipeline.Append(mlContext.Regression.Trainers.FastTree(labelColumnName: "Label", featureColumnName: "Features"));
var trainedModel = trainingPipeline.Fit(trainTestData.TrainSet);
Оценить
Оценим модель на тестовом наборе данных
var predictions = trainedModel.Transform(trainTestData.TestSet);
var metrics = mlContext.Regression.Evaluate(predictions, "Label", "Score");
display($"*************************************************");
display($"* Model quality metrics evaluation ");
display($"*------------------------------------------------");
display($"* RSquared Score: {metrics.RSquared:0.##}");
display($"* Root Mean Squared Error: {metrics.RootMeanSquaredError:#.##}");
Сохранить модель
// Save
var savedPath = Path.Combine(Directory.GetCurrentDirectory(), MODEL_FILE);
mlContext.Model.Save(trainedModel, trainTestData.TrainSet.Schema, savedPath);
display($"The model is saved to {savedPath}");
Предсказание
Наконец, мы сделаем предсказание на невидимых данных с помощью PredictionEngine из ML.Net и посмотрим, насколько велика разница между фактическим и предсказанным результатом.
// Predict
display("*********** Predict...");
var predictionEngine = mlContext.Model.CreatePredictionEngine<Match, MatchScorePrediction>(trainedModel);
var match = new Match
{
Ball = 3.4f,
BattingTeam = "India",
BowlingTeam = "New Zealand",
Bowler = "CJ Anderson",
Inning = 1,
Striker = "V Kohli",
NonStriker = "Yuvraj Singh",
Venue = "Vidarbha Cricket Association Stadium_ Jamtha"
};
// make the prediction
var prediction = predictionEngine.Predict(match);
// report the results
display($"Match Info:nn{match} ");
display($"n^^^^^^ Prediction: {prediction.TotalScore} ");
display($"**********************************************************************");
display($"Predicted score: {prediction.TotalScore:0.####}, actual score: 20");
display($"**********************************************************************");
Модель показала неплохие результаты, поскольку предсказанная оценка довольно близка к фактической. Нам нужно посмотреть, как она работает с другим набором данных. Мы использовали алгоритм FastTree, который кажется хорошим. Есть много вещей, которые мы можем попытаться улучшить здесь, выбирая различные алгоритмы, признаки, большой набор данных и т.д.
Заключение
Крикет — интересная игра с некоторыми нервными моментами во время матча, которые держат вас в напряжении. При таком волнении и энтузиазме генерируется большое количество данных о каждом мяче, который доставляется на поле. Эти данные разумно используются специалистами по исследованию данных, машинному обучению и искусственному интеллекту, чтобы сделать выводы и прогнозы о различных аспектах игры. В этом блокноте я только наметил возможности, которые могут быть использованы в игре в крикет. Мы взяли набор данных из Интернета, очистили, проанализировали и, наконец, смогли сделать предсказание о счете. Мы исследовали использование некоторых замечательных библиотек, таких как DataFrame, ML.Net и Daany.
Надеюсь, вам понравился этот блокнот.
Ресурсы
- Блокнот: https://github.com/praveenraghuvanshi/tech-sessions/blob/master/08022022-Developer-Week-2022/CricketAnalysis.ipynb
- Источник: https://github.com/praveenraghuvanshi/tech-sessions/tree/master/08022022-Developer-Week-2022/src/SportAnalytics
Контакт:
Linktree: https://linktr.ee/praveenraghuvanshi
Twitter: @praveenraghuvan
Github: praveenraghuvanshi
Блог: https://praveenraghuvanshi.github.io
Dev.To: https://dev.to/praveenraghuvanshi
Веб-сайт: https://www.praveenraghuvanshi.com
Электронная почта: contact@praveenraghuvanshi.com