237 lines
10 KiB
C#
237 lines
10 KiB
C#
using Microsoft.ML;
|
|||
|
|
using Microsoft.ML.Data;
|
||
|
|
using System;
|
||
|
|
using System.Data.SqlClient;
|
||
|
|
using System.Linq;
|
||
|
|
using AutoBet.Learn.Extensions;
|
||
|
|
using Microsoft.ML.Trainers;
|
||
|
|
using System.Collections.Generic;
|
||
|
|
|
||
|
|
namespace AutoBet.Learn
|
||
|
|
{
|
||
|
|
public class Program
|
||
|
|
{
|
||
|
|
static void Main(string[] args)
|
||
|
|
{
|
||
|
|
Learn();
|
||
|
|
Console.WriteLine("Hello World!");
|
||
|
|
}
|
||
|
|
public static DateTime DateToPredict = DateTime.Now.Date.AddDays(-3);
|
||
|
|
private static void Learn()
|
||
|
|
{
|
||
|
|
var daysAgo = DateToPredict.AddMonths(-3);
|
||
|
|
daysAgo = new DateTime(daysAgo.Year, daysAgo.Month, 1);
|
||
|
|
while (DateToPredict != daysAgo)
|
||
|
|
{
|
||
|
|
var mlContext = new MLContext();
|
||
|
|
var model = Train(mlContext);
|
||
|
|
Evaluate(mlContext, model);
|
||
|
|
UpdatePredictions(mlContext, model);
|
||
|
|
DateToPredict = DateToPredict.AddDays(-1);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private static void UpdatePredictions(MLContext mlContext, ITransformer model)
|
||
|
|
{
|
||
|
|
using (var db = new Db.HorseRacingDataContext())
|
||
|
|
{
|
||
|
|
var races = db.Races.Where(e => e.RaceDate == DateToPredict.Date).ToList();
|
||
|
|
foreach (var race in races)
|
||
|
|
{
|
||
|
|
var runners = db.Runners.Where(e => e.RaceId == race.RaceId && e.DecimalOdds != null).ToList();
|
||
|
|
foreach (var runner in runners)
|
||
|
|
{
|
||
|
|
var fixture = db.Fixtures.FirstOrDefault(e => e.FixtureId == race.FixtureId);
|
||
|
|
if (fixture == null)
|
||
|
|
continue;
|
||
|
|
var prediction = TestSinglePrediction(mlContext, model, new HorseOddsTrainingData()
|
||
|
|
{
|
||
|
|
AgeYears = runner.AgeYears,
|
||
|
|
CourseName = fixture.CourseName,
|
||
|
|
FixtureType = (int)fixture.FixtureType,
|
||
|
|
BhaRating = String.IsNullOrWhiteSpace(runner.BhaRating) ? 0 : int.Parse(runner.BhaRating),
|
||
|
|
CourseWinner = runner.CourseWinner,
|
||
|
|
DecimalOdds = (float)runner.DecimalOdds.Value,
|
||
|
|
DistanceValue = race.DistanceValue,
|
||
|
|
DistanceWinner = runner.DistanceWinner,
|
||
|
|
DivisionSequence = race.DivisionSequence,
|
||
|
|
DrawnStall = runner.DrawnStall ?? 0,
|
||
|
|
FinishTimeInMilliseconds = 0,
|
||
|
|
HeadGearWearingCode = runner.HeadGearWearingCode ?? "Unknown",
|
||
|
|
HeadGearWearingSheepSkin = runner.HeadGearWearingSheepSkin,
|
||
|
|
JockeyName = runner.JockeyName,
|
||
|
|
OwnerName = runner.OwnerName,
|
||
|
|
PenaltyValue = runner.PenaltyValue,
|
||
|
|
PrizeAmount = race.PrizeAmount,
|
||
|
|
RaceClass = race.RaceClass,
|
||
|
|
RaceCriteriaRaceType = race.RaceCriteriaRaceType,
|
||
|
|
RaceDateTime = race.RaceDate + race.RaceTime,
|
||
|
|
RacehorseName = runner.RaceHorseNameWithoutCountry,
|
||
|
|
SexType = (int)runner.SexType,
|
||
|
|
TrainerName = runner.TrainerName,
|
||
|
|
WeightInGrams = (float)runner.WeightInGrams,
|
||
|
|
WeightsJockeyClaiming = runner.WeightsJockeyClaiming,
|
||
|
|
WeightValue = runner.WeightValue,
|
||
|
|
RaceDay = (float)race.RaceDate.Day,
|
||
|
|
RaceMonth = (float)race.RaceDate.Month,
|
||
|
|
RaceYear = (float)race.RaceDate.Year
|
||
|
|
});
|
||
|
|
runner.Prediction = prediction;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
db.SaveChanges();
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
private static IDataView LoadData(MLContext mlContext, string filter = "")
|
||
|
|
{
|
||
|
|
var loader = mlContext.Data.CreateDatabaseLoader<HorseOddsTrainingData>();
|
||
|
|
|
||
|
|
var sqlCommand = @"SELECT
|
||
|
|
[CourseName]
|
||
|
|
,[FixtureType]
|
||
|
|
,[RaceCriteriaRaceType]
|
||
|
|
,[DistanceValue]
|
||
|
|
,[RaceClass]
|
||
|
|
,[PrizeAmount]
|
||
|
|
,[HeadGearWearingCode]
|
||
|
|
,[HeadGearWearingSheepSkin]
|
||
|
|
,[CourseWinner]
|
||
|
|
,[DistanceWinner]
|
||
|
|
,[RacehorseName]
|
||
|
|
,[TrainerName]
|
||
|
|
,[OwnerName]
|
||
|
|
,[JockeyName]
|
||
|
|
,[WeightsJockeyClaiming]
|
||
|
|
,[DecimalOdds]
|
||
|
|
,[DivisionSequence]
|
||
|
|
,[AgeYears]
|
||
|
|
,[DrawnStall]
|
||
|
|
,[BhaRating]
|
||
|
|
,[WeightValue]
|
||
|
|
,[PenaltyValue]
|
||
|
|
,[SexType]
|
||
|
|
,ISNULL([FinishTimeInMilliseconds],0) as FinishTimeInMilliseconds
|
||
|
|
,[RaceDateTime]
|
||
|
|
,[RaceMonth]
|
||
|
|
,[RaceYear]
|
||
|
|
,[RaceDay]
|
||
|
|
,[WeightInGrams]
|
||
|
|
FROM [dbo].[ResultsView] WHERE DecimalOdds IS NOT NULL " + filter;
|
||
|
|
|
||
|
|
var dbSource = new DatabaseSource(SqlClientFactory.Instance, Db.HorseRacingDataContext.ConnectionString, sqlCommand);
|
||
|
|
var data = loader.Load(dbSource);
|
||
|
|
return data;
|
||
|
|
}
|
||
|
|
|
||
|
|
public static ITransformer Train(MLContext mlContext)
|
||
|
|
{
|
||
|
|
var data = LoadData(mlContext, $"AND FinishTimeInMilliseconds IS NOT NULL AND RaceDate < '{DateToPredict.AddDays(-1).ToString("yyyy/MM/dd", null)}'");
|
||
|
|
//var trainers = new Dictionary<string, ITrainerEstimator<ISingleFeaturePredictionTransformer<object>, object>>()
|
||
|
|
//{
|
||
|
|
// { "FastForest", mlContext.Regression.Trainers.FastForest() },
|
||
|
|
// { "FastTree", mlContext.Regression.Trainers.FastTree() },
|
||
|
|
// { "FastTreeTweedie", mlContext.Regression.Trainers.FastTreeTweedie() },
|
||
|
|
// { "LbfgsPoissonRegression", mlContext.Regression.Trainers.LbfgsPoissonRegression() },
|
||
|
|
// { "Scda", mlContext.Regression.Trainers.Sdca() }
|
||
|
|
//};
|
||
|
|
|
||
|
|
//foreach (var trainer in trainers)
|
||
|
|
//{
|
||
|
|
// var modelPipeline = DataTransform(mlContext);
|
||
|
|
// var pipelineWithTrainer = modelPipeline.Append(trainer.Value);
|
||
|
|
|
||
|
|
// var model = pipelineWithTrainer.Fit(data);
|
||
|
|
// Console.WriteLine($">>>>>>> Evaluating algorithm: {trainer.Key} <<<<<<<");
|
||
|
|
// Evaluate(mlContext, model);
|
||
|
|
//}
|
||
|
|
|
||
|
|
|
||
|
|
Console.WriteLine($"Loading data");
|
||
|
|
var pipeline = DataTransform(mlContext);
|
||
|
|
|
||
|
|
//var pipelineWithTrainer = pipeline.Append(mlContext.Regression.Trainers.LbfgsPoissonRegression());
|
||
|
|
|
||
|
|
var selectedPipelineWithTrainer = pipeline.Append(pipeline.Append(mlContext.Regression.Trainers.LbfgsPoissonRegression()));
|
||
|
|
var finalModel = selectedPipelineWithTrainer.Fit(data);
|
||
|
|
|
||
|
|
return finalModel;
|
||
|
|
}
|
||
|
|
|
||
|
|
private static EstimatorChain<ColumnConcatenatingTransformer> DataTransform(MLContext mlContext)
|
||
|
|
{
|
||
|
|
var pipeline = mlContext.Transforms
|
||
|
|
.CopyColumns(outputColumnName: "Label", inputColumnName: "FinishTimeInMilliseconds")
|
||
|
|
.EncodeColumn(mlContext, "CourseName")
|
||
|
|
.EncodeColumn(mlContext, "FixtureType")
|
||
|
|
.EncodeColumn(mlContext, "RaceCriteriaRaceType")
|
||
|
|
.EncodeColumn(mlContext, "HeadGearWearingCode")
|
||
|
|
.EncodeColumn(mlContext, "RacehorseName")
|
||
|
|
.EncodeColumn(mlContext, "TrainerName")
|
||
|
|
.EncodeColumn(mlContext, "OwnerName")
|
||
|
|
.EncodeColumn(mlContext, "JockeyName")
|
||
|
|
.EncodeColumn(mlContext, "HeadGearWearingSheepSkin")
|
||
|
|
.EncodeColumn(mlContext, "CourseWinner")
|
||
|
|
.EncodeColumn(mlContext, "DistanceWinner")
|
||
|
|
.EncodeColumn(mlContext, "WeightValue")
|
||
|
|
.EncodeColumn(mlContext, "SexType")
|
||
|
|
.Append(mlContext.Transforms.Concatenate("Features",
|
||
|
|
"CourseNameEncoded",
|
||
|
|
"FixtureTypeEncoded",
|
||
|
|
"RaceCriteriaRaceTypeEncoded",
|
||
|
|
"DistanceValue",
|
||
|
|
"RaceClass",
|
||
|
|
"PrizeAmount",
|
||
|
|
"HeadGearWearingCodeEncoded",
|
||
|
|
"HeadGearWearingSheepSkinEncoded",
|
||
|
|
"CourseWinnerEncoded",
|
||
|
|
"DistanceWinnerEncoded",
|
||
|
|
"RacehorseNameEncoded",
|
||
|
|
"TrainerNameEncoded",
|
||
|
|
"OwnerNameEncoded",
|
||
|
|
"JockeyNameEncoded",
|
||
|
|
"WeightsJockeyClaiming",
|
||
|
|
"DecimalOdds",
|
||
|
|
"DivisionSequence",
|
||
|
|
"AgeYears",
|
||
|
|
"DrawnStall",
|
||
|
|
"WeightValueEncoded",
|
||
|
|
"PenaltyValue",
|
||
|
|
"SexTypeEncoded",
|
||
|
|
"RaceMonth",
|
||
|
|
"RaceYear",
|
||
|
|
"RaceDay"));
|
||
|
|
|
||
|
|
return pipeline;
|
||
|
|
}
|
||
|
|
|
||
|
|
private static RegressionMetrics Evaluate(MLContext mlContext, ITransformer model)
|
||
|
|
{
|
||
|
|
var dataView = LoadData(mlContext, $"AND FinishTimeInMilliseconds IS NOT NULL AND RaceDate = '{DateToPredict.AddDays(-1).ToString("yyyy/MM/dd", null)}'");
|
||
|
|
var predictions = model.Transform(dataView);
|
||
|
|
var metrics = mlContext.Regression.Evaluate(predictions, "Label", "Score");
|
||
|
|
Console.WriteLine();
|
||
|
|
|
||
|
|
|
||
|
|
var result = $"* Model quality metrics evaluation R2: {metrics.RSquared:0.####} Root Mean Squared Error: {metrics.RootMeanSquaredError:#.####} *";
|
||
|
|
var stars = String.Concat(Enumerable.Range(0, result.Length).Select(e => "*"));
|
||
|
|
|
||
|
|
Console.WriteLine(stars);
|
||
|
|
Console.WriteLine(result);
|
||
|
|
Console.WriteLine(stars);
|
||
|
|
|
||
|
|
Console.WriteLine();
|
||
|
|
return metrics;
|
||
|
|
}
|
||
|
|
|
||
|
|
private static float TestSinglePrediction(MLContext mlContext, ITransformer model, HorseOddsTrainingData values)
|
||
|
|
{
|
||
|
|
var predictionFunction = mlContext.Model.CreatePredictionEngine<HorseOddsTrainingData, HorseOddsTrainingPrediction>(model);
|
||
|
|
var prediction = predictionFunction.Predict(values);
|
||
|
|
Console.WriteLine($"Predicted time for {values.RacehorseName}: {prediction.FinishTimeInMilliseconds}");
|
||
|
|
return prediction.FinishTimeInMilliseconds;
|
||
|
|
}
|
||
|
|
|
||
|
|
}
|
||
|
|
}
|