Initial commit

This commit is contained in:
2020-04-13 15:16:46 +01:00
commit 17a40cb95f
81 changed files with 10120 additions and 0 deletions
+24
View File
@@ -0,0 +1,24 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>netcoreapp3.1</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="BritishHorseRacingApi" Version="1.0.43" />
<PackageReference Include="Microsoft.EntityFrameworkCore" Version="3.1.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Relational" Version="3.1.2" />
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="3.1.2" />
<PackageReference Include="Microsoft.ML" Version="1.4.0" />
<PackageReference Include="Microsoft.ML.FastTree" Version="1.4.0" />
<PackageReference Include="Newtonsoft.Json" Version="12.0.3" />
<PackageReference Include="SuccessfulCodeTools" Version="1.0.8" />
<PackageReference Include="System.Data.SqlClient" Version="4.8.1" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\AutoBet.Db\AutoBet.Db.csproj" />
</ItemGroup>
</Project>
@@ -0,0 +1,16 @@
using Microsoft.ML;
using Microsoft.ML.Data;
using Microsoft.ML.Transforms;
namespace AutoBet.Learn.Extensions
{
public static class EstimatorExtensions
{
//Converts string to one hot encoding vectors
public static EstimatorChain<OneHotEncodingTransformer> EncodeColumn(this IEstimator<ITransformer> pipeline, MLContext mlContext, string column)
{
var catergoricalTransforms = mlContext.Transforms.Categorical;
return pipeline.Append(catergoricalTransforms.OneHotEncoding(outputColumnName: column + "Encoded", inputColumnName: column));
}
}
}
+10
View File
@@ -0,0 +1,10 @@
using Microsoft.ML.Data;
namespace AutoBet.Learn
{
public class HorseOddsResult
{
[ColumnName("Score")]
public float Odds;
}
}
+43
View File
@@ -0,0 +1,43 @@
using Microsoft.ML.Data;
using System;
namespace AutoBet.Learn
{
public class HorseOddsTrainingData
{
public string CourseName;
public int FixtureType;
public string RaceCriteriaRaceType;
public float DistanceValue;
public float RaceClass;
public float PrizeAmount;
public string HeadGearWearingCode;
public int HeadGearWearingSheepSkin;
public string CourseWinner;
public string DistanceWinner;
public string RacehorseName;
public string TrainerName;
public string OwnerName;
public string JockeyName;
public float WeightsJockeyClaiming;
public float DecimalOdds;
public float DivisionSequence;
public float AgeYears;
public float DrawnStall;
public float BhaRating;
public string WeightValue;
public float PenaltyValue;
public int SexType;
public float FinishTimeInMilliseconds;
public DateTime RaceDateTime;
public float RaceYear;
public float RaceMonth;
public float RaceDay;
public float WeightInGrams;
}
public class HorseOddsTrainingPrediction
{
[ColumnName("Score")]
public float FinishTimeInMilliseconds;
}
}
+236
View File
@@ -0,0 +1,236 @@
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;
}
}
}