diff --git a/.gitignore b/.gitignore index d1ad80c..eeb9501 100644 --- a/.gitignore +++ b/.gitignore @@ -8,6 +8,9 @@ EvenPerfectBitScanner.ResultsParser/bin/* EvenPerfectBitScanner.ResultsParser/obj/* EvenPerfectBitScanner.Tests/bin/* EvenPerfectBitScanner.Tests/obj/* +BuildDivisorTransitions/bin/* +BuildDivisorTransitions/obj/* PerfectNumbers.Core.Tests/bin/* PerfectNumbers.Core.Tests/obj/* .DS_Store +.vs/* \ No newline at end of file diff --git a/.vscode/launch.json b/.vscode/launch.json index 21e35ff..38a0589 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -8,7 +8,8 @@ "options": [ "EvenPerfectBitScanner", "EvenPerfectBitScanner.ResultsParser", - "EvenPerfectBitScanner.Benchmarks" + "EvenPerfectBitScanner.Benchmarks", + "BuildDivisorTransitions" ], "default": "EvenPerfectBitScanner" } @@ -68,6 +69,33 @@ "internalConsoleOptions": "neverOpen", "justMyCode": false }, + { + "name": "Run BuildDivisorTransitions", + "type": "coreclr", + "request": "launch", + "noDebug": true, + "preLaunchTask": "Build BuildDivisorTransitions Release", + "program": "${workspaceFolder}/BuildDivisorTransitions/bin/Release/net8.0/BuildDivisorTransitions.dll", + "args": [], + "cwd": "${workspaceFolder}", + "stopAtEntry": false, + "console": "integratedTerminal", + "internalConsoleOptions": "neverOpen", + "justMyCode": false + }, + { + "name": "Debug BuildDivisorTransitions", + "type": "coreclr", + "request": "launch", + "preLaunchTask": "Build BuildDivisorTransitions Debug", + "program": "${workspaceFolder}/BuildDivisorTransitions/bin/Debug/net8.0/BuildDivisorTransitions.dll", + "args": [], + "cwd": "${workspaceFolder}", + "stopAtEntry": false, + "console": "integratedTerminal", + "internalConsoleOptions": "neverOpen", + "justMyCode": false + }, { "name": "Run EvenPerfectBitScanner.ResultsParser", "type": "coreclr", @@ -136,4 +164,4 @@ "justMyCode": false } ] -} \ No newline at end of file +} diff --git a/.vscode/tasks.json b/.vscode/tasks.json index 90e6505..e42a24f 100644 --- a/.vscode/tasks.json +++ b/.vscode/tasks.json @@ -94,6 +94,36 @@ "group": "build", "problemMatcher": "$msCompile" }, + { + "label": "Build BuildDivisorTransitions Debug", + "command": "dotnet", + "type": "process", + "args": [ + "build", + "${workspaceFolder}/BuildDivisorTransitions/BuildDivisorTransitions.csproj", + "--configuration", + "Debug", + "--framework", + "net8.0" + ], + "group": "build", + "problemMatcher": "$msCompile" + }, + { + "label": "Build BuildDivisorTransitions Release", + "command": "dotnet", + "type": "process", + "args": [ + "build", + "${workspaceFolder}/BuildDivisorTransitions/BuildDivisorTransitions.csproj", + "--configuration", + "Release", + "--framework", + "net8.0" + ], + "group": "build", + "problemMatcher": "$msCompile" + }, { "label": "Run EvenPerfectBitScanner", "type": "process", @@ -124,6 +154,21 @@ ], "problemMatcher": [] }, + { + "label": "Run BuildDivisorTransitions", + "type": "process", + "command": "dotnet", + "args": [ + "run", + "--project", + "${workspaceFolder}/BuildDivisorTransitions/BuildDivisorTransitions.csproj", + "--configuration", + "Release", + "--framework", + "net8.0" + ], + "problemMatcher": [] + }, { "label": "Run Release (no debug)", "type": "process", @@ -183,4 +228,4 @@ "problemMatcher": [] } ] -} \ No newline at end of file +} diff --git a/BuildDivisorTransitions/BuildDivisorTransitions.csproj b/BuildDivisorTransitions/BuildDivisorTransitions.csproj new file mode 100644 index 0000000..48b2e9e --- /dev/null +++ b/BuildDivisorTransitions/BuildDivisorTransitions.csproj @@ -0,0 +1,8 @@ + + + Exe + net8.0 + enable + enable + + diff --git a/BuildDivisorTransitions/Program.cs b/BuildDivisorTransitions/Program.cs new file mode 100644 index 0000000..c4a474f --- /dev/null +++ b/BuildDivisorTransitions/Program.cs @@ -0,0 +1,211 @@ +using System.Globalization; +using System.Numerics; +using System.Text; + +namespace BuildDivisorTransitions; + +internal static class Program +{ + private const string OutputFileName = "NextKForecastTraning.csv"; + + private static int Main(string[] args) + { + if (args.Length != 2) + { + PrintUsage(); + return 1; + } + + string candidatesPath = args[0]; + string divisorsPath = args[1]; + + if (!File.Exists(candidatesPath)) + { + Console.Error.WriteLine($"Candidates file not found: {candidatesPath}"); + return 1; + } + + if (!File.Exists(divisorsPath)) + { + Console.Error.WriteLine($"Divisors file not found: {divisorsPath}"); + return 1; + } + + List candidatePs = LoadCandidates(candidatesPath); + Dictionary divisors = LoadDivisors(divisorsPath); + List transitions = BuildTransitions(candidatePs, divisors); + + WriteOutput(transitions); + + Console.WriteLine($"Saved {transitions.Count} transitions to {OutputFileName}."); + return 0; + } + + private static void PrintUsage() + { + Console.WriteLine("Usage: BuildDivisorTransitions "); + Console.WriteLine(" candidatesCsv: CSV with columns p,searchedMersenne,detailedCheck,passedAllTests"); + Console.WriteLine(" divisorsCsv: CSV with columns p,divisor"); + } + + private static List LoadCandidates(string path) + { + var result = new List(); + var seen = new HashSet(); + + using var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + using var reader = new StreamReader(stream, Encoding.UTF8, detectEncodingFromByteOrderMarks: true); + + while (!reader.EndOfStream) + { + string? line = reader.ReadLine(); + if (string.IsNullOrWhiteSpace(line)) + { + continue; + } + + string trimmedLine = line.Trim(); + if (trimmedLine.StartsWith("p", StringComparison.OrdinalIgnoreCase)) + { + continue; + } + + string[] parts = trimmedLine.Split(',', StringSplitOptions.TrimEntries); + if (parts.Length == 0) + { + continue; + } + + if (!long.TryParse(parts[0], NumberStyles.Integer, CultureInfo.InvariantCulture, out long p)) + { + throw new FormatException($"Invalid p value '{parts[0]}' in {path}."); + } + + if (seen.Add(p)) + { + result.Add(p); + } + } + + return result; + } + + private static Dictionary LoadDivisors(string path) + { + var result = new Dictionary(); + + using var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.ReadWrite); + using var reader = new StreamReader(stream, Encoding.UTF8, detectEncodingFromByteOrderMarks: true); + + while (!reader.EndOfStream) + { + string? line = reader.ReadLine(); + if (string.IsNullOrWhiteSpace(line)) + { + continue; + } + + string trimmedLine = line.Trim(); + if (trimmedLine.StartsWith("p", StringComparison.OrdinalIgnoreCase)) + { + continue; + } + + string[] parts = trimmedLine.Split(',', StringSplitOptions.TrimEntries); + if (parts.Length < 2) + { + throw new FormatException($"Line '{line}' in {path} does not contain both p and divisor columns."); + } + + if (!long.TryParse(parts[0], NumberStyles.Integer, CultureInfo.InvariantCulture, out long p)) + { + throw new FormatException($"Invalid p value '{parts[0]}' in {path}."); + } + + if (!BigInteger.TryParse(parts[1], NumberStyles.Integer, CultureInfo.InvariantCulture, out BigInteger divisor)) + { + throw new FormatException($"Invalid divisor value '{parts[1]}' for p={p} in {path}."); + } + + result[p] = divisor; + } + + return result; + } + + private static List BuildTransitions(IReadOnlyList candidatePs, IReadOnlyDictionary divisors) + { + var transitions = new List(); + + for (int i = 0; i < candidatePs.Count - 1; i++) + { + long currentP = candidatePs[i]; + long nextP = candidatePs[i + 1]; + + if (!divisors.TryGetValue(currentP, out BigInteger currentDivisor)) + { + continue; + } + + if (!divisors.TryGetValue(nextP, out BigInteger nextDivisor)) + { + continue; + } + + BigInteger currentK = CalculateK(currentP, currentDivisor); + BigInteger nextK = CalculateK(nextP, nextDivisor); + bool isNextKHigher = nextK > currentK; + transitions.Add(new Transition(currentP, currentDivisor, LastDigit(currentP), currentK, LastMersenneDigit(currentP), isNextKHigher)); + } + + return transitions; + } + + private static BigInteger CalculateK(long p, BigInteger divisor) + { + BigInteger numerator = divisor - BigInteger.One; + BigInteger denominator = new BigInteger(p) << 1; + + if (denominator <= BigInteger.Zero) + { + throw new InvalidOperationException($"Computed 2p is not positive for p={p}."); + } + + BigInteger remainder = numerator % denominator; + if (remainder != BigInteger.Zero) + { + throw new InvalidOperationException($"Divisor {divisor} is not congruent to 1 mod 2p for p={p}."); + } + + BigInteger k = numerator / denominator; + if (k < BigInteger.One) + { + throw new InvalidOperationException($"Calculated k={k} is less than 1 for p={p}, divisor={divisor}."); + } + + return k; + } + + private static void WriteOutput(IReadOnlyCollection transitions) + { + using var writer = new StreamWriter(OutputFileName, false, Encoding.UTF8); + writer.WriteLine("p,divisor,lastDigit,k,lastMersenneDigit,isNextKHigher"); + + foreach (Transition transition in transitions) + { + writer.WriteLine($"{transition.P.ToString(CultureInfo.InvariantCulture)},{FormatBigInteger(transition.Divisor)},{transition.LastDigit},{FormatBigInteger(transition.CurrentK)},{transition.LastMersenneDigit},{(transition.IsNextKHigher ? "1" : "0")}"); + } + } + + private static int LastDigit(long p) => (int)(Math.Abs(p) % 10); + + private static int LastMersenneDigit(long p) + { + long mod = p & 3; + return mod == 1 ? 1 : 7; // odd primes give p%4 == 1 or 3; 2^p mod 10 cycles -> 1 or 7 after subtracting 1 + } + + private static string FormatBigInteger(in BigInteger value) => value.ToString(CultureInfo.InvariantCulture); + + private sealed record Transition(long P, BigInteger Divisor, int LastDigit, BigInteger CurrentK, int LastMersenneDigit, bool IsNextKHigher); +} diff --git a/EvenPerfectBitScanner/EvenPerfectBitScanner.csproj b/EvenPerfectBitScanner/EvenPerfectBitScanner.csproj index c1a0f2a..fcebb40 100644 --- a/EvenPerfectBitScanner/EvenPerfectBitScanner.csproj +++ b/EvenPerfectBitScanner/EvenPerfectBitScanner.csproj @@ -13,7 +13,16 @@ + + + + - + + + PreserveNewest + + + \ No newline at end of file diff --git a/EvenPerfectBitScanner/MLModel1.consumption.cs b/EvenPerfectBitScanner/MLModel1.consumption.cs new file mode 100644 index 0000000..7fd3a37 --- /dev/null +++ b/EvenPerfectBitScanner/MLModel1.consumption.cs @@ -0,0 +1,103 @@ +// This file was auto-generated by ML.NET Model Builder. +using Microsoft.ML; +using Microsoft.ML.Data; +using System; +using System.Linq; +using System.IO; +using System.Collections.Generic; +namespace EvenPerfectBitScanner +{ + public partial class MLModel1 + { + /// + /// model input class for MLModel1. + /// + #region model input class + public class ModelInput + { + [LoadColumn(0)] + [ColumnName(@"p")] + public float P { get; set; } + + [LoadColumn(1)] + [ColumnName(@"divisor")] + public float Divisor { get; set; } + + [LoadColumn(2)] + [ColumnName(@"lastDigit")] + public float LastDigit { get; set; } + + [LoadColumn(3)] + [ColumnName(@"k")] + public float K { get; set; } + + [LoadColumn(4)] + [ColumnName(@"lastMersenneDigit")] + public float LastMersenneDigit { get; set; } + + [LoadColumn(5)] + [ColumnName(@"isNextKHigher")] + public float IsNextKHigher { get; set; } + + } + + #endregion + + /// + /// model output class for MLModel1. + /// + #region model output class + public class ModelOutput + { + [ColumnName(@"p")] + public float P { get; set; } + + [ColumnName(@"divisor")] + public float Divisor { get; set; } + + [ColumnName(@"lastDigit")] + public float LastDigit { get; set; } + + [ColumnName(@"k")] + public float K { get; set; } + + [ColumnName(@"lastMersenneDigit")] + public float LastMersenneDigit { get; set; } + + [ColumnName(@"isNextKHigher")] + public float IsNextKHigher { get; set; } + + [ColumnName(@"Features")] + public float[] Features { get; set; } + + [ColumnName(@"Score")] + public float Score { get; set; } + + } + + #endregion + + private static string MLNetModelPath = Path.GetFullPath("MLModel1.mlnet"); + + public static readonly Lazy> PredictEngine = new Lazy>(() => CreatePredictEngine(), true); + + + private static PredictionEngine CreatePredictEngine() + { + var mlContext = new MLContext(); + ITransformer mlModel = mlContext.Model.Load(MLNetModelPath, out var _); + return mlContext.Model.CreatePredictionEngine(mlModel); + } + + /// + /// Use this method to predict on . + /// + /// model input. + /// + public static ModelOutput Predict(ModelInput input) + { + var predEngine = PredictEngine.Value; + return predEngine.Predict(input); + } + } +} diff --git a/EvenPerfectBitScanner/MLModel1.evaluate.cs b/EvenPerfectBitScanner/MLModel1.evaluate.cs new file mode 100644 index 0000000..d46ae96 --- /dev/null +++ b/EvenPerfectBitScanner/MLModel1.evaluate.cs @@ -0,0 +1,125 @@ +// This file was auto-generated by ML.NET Model Builder. + +using Microsoft.ML.Data; +using Microsoft.ML; +using System; +using System.Collections.Generic; +using System.Linq; +using Plotly.NET; +using Plotly.NET.LayoutObjects; + +namespace EvenPerfectBitScanner +{ + public partial class MLModel1 + { + /// + /// Permutation feature importance (PFI) is a technique to determine the importance + /// of features in a trained machine learning model. PFI works by taking a labeled dataset, + /// choosing a feature, and permuting the values for that feature across all the examples, + /// so that each example now has a random value for the feature and the original values for all other features. + /// The evaluation metric (e.g. R-squared) is then calculated for this modified dataset, + /// and the change in the evaluation metric from the original dataset is computed. + /// The larger the change in the evaluation metric, the more important the feature is to the model. + /// + /// PFI typically takes a long time to compute, as the evaluation metric is calculated + /// many times to determine the importance of each feature. + /// + /// + /// The common context for all ML.NET operations. + /// IDataView used to evaluate the model. + /// Model to evaluate. + /// Label column being predicted. + /// A list of each feature and its importance. + public static List> CalculatePFI(MLContext mlContext, IDataView trainData, ITransformer model, string labelColumnName) + { + var preprocessedTrainData = model.Transform(trainData); + + var permutationFeatureImportance = + mlContext.Regression + .PermutationFeatureImportance( + model, + preprocessedTrainData, + labelColumnName: labelColumnName); + + var featureImportanceMetrics = + permutationFeatureImportance + .Select((kvp) => new { kvp.Key, kvp.Value.RSquared }) + .OrderByDescending(myFeatures => Math.Abs(myFeatures.RSquared.Mean)); + + var featurePFI = new List>(); + foreach (var feature in featureImportanceMetrics) + { + var pfiValue = Math.Abs(feature.RSquared.Mean); + featurePFI.Add(new Tuple(feature.Key, pfiValue)); + } + + return featurePFI; + } + + /// + /// R Squared is a measure of variation between the values predicted by the model and the true values. + /// In a "perfect" model, there would be no variation between predictions and true values. + /// + /// Here we will plot the predicted values vs the true values for the trained model. This RegressionChart.html + /// is then saved to the location specified by . + /// + /// See more information on R Squared at https://en.wikipedia.org/wiki/Coefficient_of_determination. + /// + /// IDataView used to train the model. + /// Model used for predictions. + /// Name of the predicted label column. + /// Folder path to save the RegressionChart.html file into. + public static void PlotRSquaredValues(IDataView trainData, ITransformer model, string labelColumnName, string folderPath) + { + // Number of rows to display in charts. + int numberOfRows = 1000; + // Use the model to make batch predictions on training data + var testResults = model.Transform(trainData); + + // Get the actual values from the dataset + var trueValues = testResults.GetColumn(labelColumnName).Take(numberOfRows); ; + + // Get the predicted values from the test results + var predictedValues = testResults.GetColumn("Score").Take(numberOfRows); + + // Setup what the graph looks like + var title = Title.init(Text: "R-Squared Plot"); + var layout = Layout.init(Title: title, PlotBGColor: Plotly.NET.Color.fromString("#e5ecf6")); + var xAxis = LinearAxis.init( + Title: Title.init("True Values"), + ZeroLineColor: Plotly.NET.Color.fromString("#ffff"), + GridColor: Plotly.NET.Color.fromString("#ffff"), + ZeroLineWidth: 2); + var yAxis = LinearAxis.init( + Title: Title.init("Predicted Values"), + ZeroLineColor: Plotly.NET.Color.fromString("#ffff"), + GridColor: Plotly.NET.Color.fromString("#ffff"), + ZeroLineWidth: 2); + + // We will plot the line that shows the perfect result. Setup that line here. + var maximumValue = Math.Max(trueValues.Max(), predictedValues.Max()); + var perfectX = new[] { 0, maximumValue }; + var perfectY = new[] { 0, maximumValue }; + + + + // Create the scatterplot that shows the true values vs the predicted values + var trueAndPredictedValues = Chart2D.Chart.Scatter(x: trueValues, y: predictedValues, mode: StyleParam.Mode.Markers) + .WithLayout(layout) + .WithXAxis(xAxis) + .WithYAxis(yAxis); + + // Setup the line that shows what a perfect prediction would look like + var perfectLineGraph = Chart2D.Chart.Line(x: perfectX, y: perfectY) + .WithLayout(layout) + .WithLine(Line.init(Width: 1.5)); + + var chartWithValuesAndIdealLine = Chart.Combine(new[] { trueAndPredictedValues, perfectLineGraph }); + var chartFilePath = folderPath + "\\RegressionChart.html"; + + chartWithValuesAndIdealLine.SaveHtml(chartFilePath); + } + } +} + + diff --git a/EvenPerfectBitScanner/MLModel1.mbconfig b/EvenPerfectBitScanner/MLModel1.mbconfig new file mode 100644 index 0000000..e3b174d --- /dev/null +++ b/EvenPerfectBitScanner/MLModel1.mbconfig @@ -0,0 +1,337 @@ +{ + "Scenario": "Regression", + "DataSource": { + "Version": 3, + "EscapeCharacter": "\"", + "ReadMultiLines": false, + "KeepDiacritics": false, + "KeepPunctuations": false, + "AllowQuoting": false, + "Type": "TabularFile", + "FilePath": "C:\\Users\\marek\\source\\repos\\even-perfect-numbers-scanner\\BuildDivisorTransitions\\bin\\Release\\net8.0\\NextKForecastTraning.csv", + "Delimiter": ",", + "DecimalMarker": ".", + "HasHeader": true, + "ColumnProperties": [ + { + "ColumnName": "p", + "ColumnPurpose": "Feature", + "ColumnDataFormat": "Single", + "IsCategorical": false, + "Type": "Column", + "Version": 5 + }, + { + "ColumnName": "divisor", + "ColumnPurpose": "Feature", + "ColumnDataFormat": "Single", + "IsCategorical": false, + "Type": "Column", + "Version": 5 + }, + { + "ColumnName": "lastDigit", + "ColumnPurpose": "Feature", + "ColumnDataFormat": "Single", + "IsCategorical": true, + "Type": "Column", + "Version": 5 + }, + { + "ColumnName": "k", + "ColumnPurpose": "Feature", + "ColumnDataFormat": "Single", + "IsCategorical": false, + "Type": "Column", + "Version": 5 + }, + { + "ColumnName": "lastMersenneDigit", + "ColumnPurpose": "Feature", + "ColumnDataFormat": "Single", + "IsCategorical": true, + "Type": "Column", + "Version": 5 + }, + { + "ColumnName": "isNextKHigher", + "ColumnPurpose": "Label", + "ColumnDataFormat": "Single", + "IsCategorical": true, + "Type": "Column", + "Version": 5 + } + ] + }, + "Environment": { + "Type": "LocalCPU", + "Version": 1 + }, + "RunHistory": { + "Version": 3, + "Type": "Result", + "Trials": [ + { + "Version": 1, + "Type": "Trial", + "TrainerName": "FastTreeRegression", + "Score": 0.30006118613190746, + "RuntimeInSeconds": 1.489, + "Parameter": { + "_SCHEMA_": "e0 * e1 * e2", + "e0": { + "OutputColumnNames": [ + "p", + "divisor", + "lastDigit", + "k", + "lastMersenneDigit" + ], + "InputColumnNames": [ + "p", + "divisor", + "lastDigit", + "k", + "lastMersenneDigit" + ] + }, + "e1": { + "InputColumnNames": [ + "p", + "divisor", + "lastDigit", + "k", + "lastMersenneDigit" + ], + "OutputColumnName": "Features" + }, + "e2": { + "NumberOfLeaves": 4, + "MinimumExampleCountPerLeaf": 9, + "NumberOfTrees": 8, + "MaximumBinCountPerFeature": 317, + "FeatureFraction": 0.98434331463123226, + "LearningRate": 0.3857390791447961, + "LabelColumnName": "isNextKHigher", + "FeatureColumnName": "Features", + "DiskTranspose": false + } + } + }, + { + "Version": 1, + "Type": "Trial", + "TrainerName": "FastTreeRegression", + "Score": 0.299969238277351, + "RuntimeInSeconds": 1.496, + "Parameter": { + "_SCHEMA_": "e0 * e1 * e2", + "e0": { + "OutputColumnNames": [ + "p", + "divisor", + "lastDigit", + "k", + "lastMersenneDigit" + ], + "InputColumnNames": [ + "p", + "divisor", + "lastDigit", + "k", + "lastMersenneDigit" + ] + }, + "e1": { + "InputColumnNames": [ + "p", + "divisor", + "lastDigit", + "k", + "lastMersenneDigit" + ], + "OutputColumnName": "Features" + }, + "e2": { + "NumberOfLeaves": 4, + "MinimumExampleCountPerLeaf": 9, + "NumberOfTrees": 8, + "MaximumBinCountPerFeature": 317, + "FeatureFraction": 0.98444606904931908, + "LearningRate": 0.38567641892007121, + "LabelColumnName": "isNextKHigher", + "FeatureColumnName": "Features", + "DiskTranspose": false + } + } + }, + { + "Version": 1, + "Type": "Trial", + "TrainerName": "FastTreeRegression", + "Score": 0.29992424440705012, + "RuntimeInSeconds": 0.993, + "Parameter": { + "_SCHEMA_": "e0 * e1 * e2", + "e0": { + "OutputColumnNames": [ + "p", + "divisor", + "lastDigit", + "k", + "lastMersenneDigit" + ], + "InputColumnNames": [ + "p", + "divisor", + "lastDigit", + "k", + "lastMersenneDigit" + ] + }, + "e1": { + "InputColumnNames": [ + "p", + "divisor", + "lastDigit", + "k", + "lastMersenneDigit" + ], + "OutputColumnName": "Features" + }, + "e2": { + "NumberOfLeaves": 4, + "MinimumExampleCountPerLeaf": 9, + "NumberOfTrees": 8, + "MaximumBinCountPerFeature": 317, + "FeatureFraction": 0.98445819026817927, + "LearningRate": 0.38613186537816252, + "LabelColumnName": "isNextKHigher", + "FeatureColumnName": "Features", + "DiskTranspose": false + } + } + }, + { + "Version": 1, + "Type": "Trial", + "TrainerName": "FastTreeRegression", + "Score": 0.29992046748552242, + "RuntimeInSeconds": 1.389, + "Parameter": { + "_SCHEMA_": "e0 * e1 * e2", + "e0": { + "OutputColumnNames": [ + "p", + "divisor", + "lastDigit", + "k", + "lastMersenneDigit" + ], + "InputColumnNames": [ + "p", + "divisor", + "lastDigit", + "k", + "lastMersenneDigit" + ] + }, + "e1": { + "InputColumnNames": [ + "p", + "divisor", + "lastDigit", + "k", + "lastMersenneDigit" + ], + "OutputColumnName": "Features" + }, + "e2": { + "NumberOfLeaves": 4, + "MinimumExampleCountPerLeaf": 9, + "NumberOfTrees": 8, + "MaximumBinCountPerFeature": 317, + "FeatureFraction": 0.984395476164661, + "LearningRate": 0.3858463322510558, + "LabelColumnName": "isNextKHigher", + "FeatureColumnName": "Features", + "DiskTranspose": false + } + } + }, + { + "Version": 1, + "Type": "Trial", + "TrainerName": "FastTreeRegression", + "Score": 0.29984566377505206, + "RuntimeInSeconds": 1.512, + "Parameter": { + "_SCHEMA_": "e0 * e1 * e2", + "e0": { + "OutputColumnNames": [ + "p", + "divisor", + "lastDigit", + "k", + "lastMersenneDigit" + ], + "InputColumnNames": [ + "p", + "divisor", + "lastDigit", + "k", + "lastMersenneDigit" + ] + }, + "e1": { + "InputColumnNames": [ + "p", + "divisor", + "lastDigit", + "k", + "lastMersenneDigit" + ], + "OutputColumnName": "Features" + }, + "e2": { + "NumberOfLeaves": 4, + "MinimumExampleCountPerLeaf": 8, + "NumberOfTrees": 8, + "MaximumBinCountPerFeature": 317, + "FeatureFraction": 0.98439134546037266, + "LearningRate": 0.38521733136295422, + "LabelColumnName": "isNextKHigher", + "FeatureColumnName": "Features", + "DiskTranspose": false + } + } + } + ], + "Estimators": { + "e0": "ReplaceMissingValues", + "e1": "Concatenate", + "e2": "FastTreeRegression", + "e3": "FastForestRegression", + "e4": "LightGbmRegression", + "e5": "LbfgsPoissonRegressionRegression", + "e6": "SdcaRegression" + }, + "Schema": "e0 * e1 * (e2 + e3 + e4 + e5 + e6)", + "MetricName": "RSquared", + "ModelFilePath": "C:\\Users\\marek\\source\\repos\\even-perfect-numbers-scanner\\EvenPerfectBitScanner\\MLModel1.mlnet" + }, + "Type": "TrainingConfig", + "Version": 5, + "TrainingOption": { + "Version": 2, + "Subsampling": false, + "Type": "RegressionTrainingOption", + "TrainingTime": 3600, + "LabelColumn": "isNextKHigher", + "ValidationOption": { + "Version": 0, + "Type": "CrossValidationValidationOption", + "NumberOfFolds": 10 + } + } +} \ No newline at end of file diff --git a/EvenPerfectBitScanner/MLModel1.mlnet b/EvenPerfectBitScanner/MLModel1.mlnet new file mode 100644 index 0000000..c913388 Binary files /dev/null and b/EvenPerfectBitScanner/MLModel1.mlnet differ diff --git a/EvenPerfectBitScanner/MLModel1.training.cs b/EvenPerfectBitScanner/MLModel1.training.cs new file mode 100644 index 0000000..2e2a801 --- /dev/null +++ b/EvenPerfectBitScanner/MLModel1.training.cs @@ -0,0 +1,100 @@ +// This file was auto-generated by ML.NET Model Builder. +using System; +using System.IO; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; +using Microsoft.ML; +using Microsoft.ML.Data; +using Microsoft.ML.Trainers; +using Microsoft.ML.Trainers.FastTree; + +namespace EvenPerfectBitScanner +{ + public partial class MLModel1 + { + public const string RetrainFilePath = @"C:\Users\marek\source\repos\even-perfect-numbers-scanner\BuildDivisorTransitions\bin\Release\net8.0\NextKForecastTraning.csv"; + public const char RetrainSeparatorChar = ','; + public const bool RetrainHasHeader = true; + public const bool RetrainAllowQuoting = false; + + /// + /// Train a new model with the provided dataset. + /// + /// File path for saving the model. Should be similar to "C:\YourPath\ModelName.mlnet" + /// Path to the data file for training. + /// Separator character for delimited training file. + /// Boolean if training file has a header. + public static void Train(string outputModelPath, string inputDataFilePath = RetrainFilePath, char separatorChar = RetrainSeparatorChar, bool hasHeader = RetrainHasHeader, bool allowQuoting = RetrainAllowQuoting) + { + var mlContext = new MLContext(); + + var data = LoadIDataViewFromFile(mlContext, inputDataFilePath, separatorChar, hasHeader, allowQuoting); + var model = RetrainModel(mlContext, data); + SaveModel(mlContext, model, data, outputModelPath); + } + + /// + /// Load an IDataView from a file path. + /// + /// The common context for all ML.NET operations. + /// Path to the data file for training. + /// Separator character for delimited training file. + /// Boolean if training file has a header. + /// IDataView with loaded training data. + public static IDataView LoadIDataViewFromFile(MLContext mlContext, string inputDataFilePath, char separatorChar, bool hasHeader, bool allowQuoting) + { + return mlContext.Data.LoadFromTextFile(inputDataFilePath, separatorChar, hasHeader, allowQuoting: allowQuoting); + } + + + /// + /// Save a model at the specified path. + /// + /// The common context for all ML.NET operations. + /// Model to save. + /// IDataView used to train the model. + /// File path for saving the model. Should be similar to "C:\YourPath\ModelName.mlnet. + public static void SaveModel(MLContext mlContext, ITransformer model, IDataView data, string modelSavePath) + { + // Pull the data schema from the IDataView used for training the model + DataViewSchema dataViewSchema = data.Schema; + + using (var fs = File.Create(modelSavePath)) + { + mlContext.Model.Save(model, dataViewSchema, fs); + } + } + + + /// + /// Retrain model using the pipeline generated as part of the training process. + /// + /// + /// + /// + public static ITransformer RetrainModel(MLContext mlContext, IDataView trainData) + { + var pipeline = BuildPipeline(mlContext); + var model = pipeline.Fit(trainData); + + return model; + } + + /// + /// build the pipeline that is used from model builder. Use this function to retrain model. + /// + /// + /// + public static IEstimator BuildPipeline(MLContext mlContext) + { + // Data process configuration with pipeline data transformations + var pipeline = mlContext.Transforms.ReplaceMissingValues(new []{new InputOutputColumnPair(@"p", @"p"),new InputOutputColumnPair(@"divisor", @"divisor"),new InputOutputColumnPair(@"lastDigit", @"lastDigit"),new InputOutputColumnPair(@"k", @"k"),new InputOutputColumnPair(@"lastMersenneDigit", @"lastMersenneDigit")}) + .Append(mlContext.Transforms.Concatenate(@"Features", new []{@"p",@"divisor",@"lastDigit",@"k",@"lastMersenneDigit"})) + .Append(mlContext.Regression.Trainers.FastTree(new FastTreeRegressionTrainer.Options(){NumberOfLeaves=4,MinimumExampleCountPerLeaf=9,NumberOfTrees=8,MaximumBinCountPerFeature=317,FeatureFraction=0.9843433146312323,LearningRate=0.3857390791447961,LabelColumnName=@"isNextKHigher",FeatureColumnName=@"Features",DiskTranspose=false})); + + return pipeline; + } + } + } diff --git a/EvenPerfectScanner.sln b/EvenPerfectScanner.sln index 6ec9da2..e727561 100644 --- a/EvenPerfectScanner.sln +++ b/EvenPerfectScanner.sln @@ -14,6 +14,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EvenPerfectBitScanner.Resul EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "EvenPerfectBitScanner.Benchmarks", "EvenPerfectBitScanner.Benchmarks\EvenPerfectBitScanner.Benchmarks.csproj", "{83551FCC-41FA-4162-B541-AFCB5666A29F}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BuildDivisorTransitions", "BuildDivisorTransitions\BuildDivisorTransitions.csproj", "{0A936837-5082-4C0B-AC3B-840AD2E11057}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -47,5 +49,9 @@ Global {83551FCC-41FA-4162-B541-AFCB5666A29F}.Debug|Any CPU.Build.0 = Debug|Any CPU {83551FCC-41FA-4162-B541-AFCB5666A29F}.Release|Any CPU.ActiveCfg = Release|Any CPU {83551FCC-41FA-4162-B541-AFCB5666A29F}.Release|Any CPU.Build.0 = Release|Any CPU + {0A936837-5082-4C0B-AC3B-840AD2E11057}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {0A936837-5082-4C0B-AC3B-840AD2E11057}.Debug|Any CPU.Build.0 = Debug|Any CPU + {0A936837-5082-4C0B-AC3B-840AD2E11057}.Release|Any CPU.ActiveCfg = Release|Any CPU + {0A936837-5082-4C0B-AC3B-840AD2E11057}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection EndGlobal