Skip to content

Commit ae6b7ef

Browse files
nicolehaugenCESARDELATORRE
authored andcommitted
Time series (SSA) forecasting E2E & console app (dotnet#592)
* restructured to add ssa to forecasting trainer * In-progress sample showing model training for time series * further cleanup of the time series trainer sample * add some temp data generation for time series * supplemental data generator to get 24 months of data * added comments, minor fixes * improved intro comment * fixed build file * inital integration work adding timeseries into web UI * Updated ReadMe to include Time Series sample info * js lint * generate supplimental data to total 36 months * finished up web UI with static product (988) * Removed product 263 * change product id back to string * change productId back toto float and replace regression models * fix js bug resulting in 400 * get forecast based on current page * move data structures to a shared project * Dynamically train time series forecast model in web UI * readme edits * fixed spacing and typo * fix build issue * update to v1.3.1 * Update samples/csharp/end-to-end-apps/Forecasting-Sales/README.md Co-Authored-By: Brigit Murtaugh <[email protected]> * Update samples/csharp/end-to-end-apps/Forecasting-Sales/README.md Co-Authored-By: Brigit Murtaugh <[email protected]> * Update samples/csharp/end-to-end-apps/Forecasting-Sales/README.md Co-Authored-By: Brigit Murtaugh <[email protected]> * Update samples/csharp/end-to-end-apps/Forecasting-Sales/README.md Co-Authored-By: Brigit Murtaugh <[email protected]> * Update samples/csharp/end-to-end-apps/Forecasting-Sales/README.md Co-Authored-By: Brigit Murtaugh <[email protected]> * Update samples/csharp/end-to-end-apps/Forecasting-Sales/README.md Co-Authored-By: Brigit Murtaugh <[email protected]> * Update samples/csharp/end-to-end-apps/Forecasting-Sales/README.md Co-Authored-By: Brigit Murtaugh <[email protected]> * Update samples/csharp/end-to-end-apps/Forecasting-Sales/README.md Co-Authored-By: Brigit Murtaugh <[email protected]> * Update samples/csharp/end-to-end-apps/Forecasting-Sales/README.md Co-Authored-By: Brigit Murtaugh <[email protected]> * Update samples/csharp/end-to-end-apps/Forecasting-Sales/README.md Co-Authored-By: Brigit Murtaugh <[email protected]> * Update samples/csharp/end-to-end-apps/Forecasting-Sales/README.md Co-Authored-By: Brigit Murtaugh <[email protected]> * Update samples/csharp/end-to-end-apps/Forecasting-Sales/README.md Co-Authored-By: Brigit Murtaugh <[email protected]> * Update samples/csharp/end-to-end-apps/Forecasting-Sales/README.md Co-Authored-By: Brigit Murtaugh <[email protected]> * Update samples/csharp/end-to-end-apps/Forecasting-Sales/README.md Co-Authored-By: Brigit Murtaugh <[email protected]> * Update samples/csharp/end-to-end-apps/Forecasting-Sales/README.md Co-Authored-By: Brigit Murtaugh <[email protected]> * Update samples/csharp/end-to-end-apps/Forecasting-Sales/README.md Co-Authored-By: Brigit Murtaugh <[email protected]> * Update samples/csharp/end-to-end-apps/Forecasting-Sales/src/Shared/DataStructures/ProductData.cs Co-Authored-By: Brigit Murtaugh <[email protected]> * Update samples/csharp/end-to-end-apps/Forecasting-Sales/src/eShopDashboard/Pages/_Layout.cshtml Co-Authored-By: Brigit Murtaugh <[email protected]> * Update samples/csharp/end-to-end-apps/Forecasting-Sales/src/eShopDashboard/Pages/_Layout.cshtml Co-Authored-By: Brigit Murtaugh <[email protected]> * readme formatting and fixes. thanks @bamurtaugh
1 parent 0d74e5b commit ae6b7ef

File tree

190 files changed

+1083
-470
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

190 files changed

+1083
-470
lines changed

.vsts-dotnet-ci.yml

+1-1
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ phases:
110110
- task: DotNetCoreCLI@2
111111
displayName: Build eShopDashboardML (Regression)
112112
inputs:
113-
projects: '.\samples\csharp\end-to-end-apps\Regression-SalesForecast\eShopDashboardML.sln'
113+
projects: '.\samples\csharp\end-to-end-apps\Forecasting-Sales\eShopDashboardML.sln'
114114

115115
- phase: MovieRecommenderE2E
116116
queue: Hosted VS2017

samples/csharp/end-to-end-apps/Forecasting-Sales/README.md

+351
Large diffs are not rendered by default.

samples/csharp/end-to-end-apps/Regression-SalesForecast/eShopDashboardML.sln renamed to samples/csharp/end-to-end-apps/Forecasting-Sales/eShopDashboardML.sln

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11

22
Microsoft Visual Studio Solution File, Format Version 12.00
3-
# Visual Studio 15
4-
VisualStudioVersion = 15.0.27428.2037
3+
# Visual Studio Version 16
4+
VisualStudioVersion = 16.0.29123.88
55
MinimumVisualStudioVersion = 10.0.40219.1
66
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{F395612F-24C7-4666-90B2-62E417033B4B}"
77
EndProject
@@ -11,7 +11,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "eShopForecastModelsTrainer"
1111
EndProject
1212
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestObjectPoolingConsoleApp", "src\TestObjectPoolingConsoleApp\TestObjectPoolingConsoleApp.csproj", "{CF3DE8C7-81D6-4B2B-A2F0-82D15701F10A}"
1313
EndProject
14-
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CommonHelpers", "src\CommonHelper\CommonHelpers.csproj", "{BAE5D60B-7632-44BA-9785-9716DCA9D84A}"
14+
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Shared", "src\Shared\Shared.csproj", "{BAE5D60B-7632-44BA-9785-9716DCA9D84A}"
1515
EndProject
1616
Global
1717
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,9 @@
1-

2-
using Microsoft.ML.Data;
1+
using Microsoft.ML.Data;
32

4-
namespace eShopForecastModelsTrainer
3+
namespace eShopForecast
54
{
65
public class CountryData
76
{
8-
// next,country,year,month,max,min,std,count,sales,med,prev
97
[LoadColumn(0)]
108
public float next;
119

@@ -38,10 +36,10 @@ public class CountryData
3836

3937
[LoadColumn(10)]
4038
public float prev;
41-
}
4239

43-
public class CountrySalesPrediction
44-
{
45-
public float Score;
40+
public override string ToString()
41+
{
42+
return $"CountryData [next: {next}, country: {country}, year: {year}, month: {month}, max: {max}, min: {min}, std: {std}, count: {count}, sales: {sales}, med: {med}, prev: {prev}]";
43+
}
4644
}
4745
}
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
namespace eShopDashboard.Forecast
1+
namespace eShopForecast
22
{
33
/// <summary>
44
/// This is the output of the scored model, the prediction.
@@ -8,5 +8,4 @@ public class CountrySalesPrediction
88
// Below columns are produced by the model's predictor.
99
public float Score;
1010
}
11-
1211
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
using Microsoft.ML.Data;
2+
3+
namespace eShopForecast
4+
{
5+
public class ProductData
6+
{
7+
// The index of column in LoadColumn(int index) should be matched with the position of columns in the underlying data file.
8+
// The next column is used by the Regression algorithm as the Label (e.g. the value that is being predicted by the Regression model).
9+
[LoadColumn(0)]
10+
public float next;
11+
12+
[LoadColumn(1)]
13+
public float productId;
14+
15+
[LoadColumn(2)]
16+
public float year;
17+
18+
[LoadColumn(3)]
19+
public float month;
20+
21+
[LoadColumn(4)]
22+
public float units;
23+
24+
[LoadColumn(5)]
25+
public float avg;
26+
27+
[LoadColumn(6)]
28+
public float count;
29+
30+
[LoadColumn(7)]
31+
public float max;
32+
33+
[LoadColumn(8)]
34+
public float min;
35+
36+
[LoadColumn(9)]
37+
public float prev;
38+
39+
public override string ToString()
40+
{
41+
return $"ProductData [ productId: {productId}, year: {year}, month: {month:00}, next: {next:0000}, units: {units:0000}, avg: {avg:000}, count: {count:00}, max: {max:000}, min: {min}, prev: {prev:0000} ]";
42+
}
43+
}
44+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
namespace eShopForecast
2+
{
3+
/// <summary>
4+
/// This is the output of the scored regression model, the prediction.
5+
/// </summary>
6+
public class ProductUnitRegressionPrediction
7+
{
8+
// Below columns are produced by the model's predictor.
9+
public float Score;
10+
}
11+
12+
/// <summary>
13+
/// This is the output of the scored time series model, the prediction.
14+
/// </summary>
15+
public class ProductUnitTimeSeriesPrediction
16+
{
17+
public float[] ForecastedProductUnits { get; set; }
18+
19+
public float[] ConfidenceLowerBound { get; set; }
20+
21+
public float[] ConfidenceUpperBound { get; set; }
22+
}
23+
}
+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
using System.IO;
22
using Microsoft.ML;
33

4-
namespace CommonHelpers
4+
namespace Common
55
{
66
public class MLModelEngine<TData, TPrediction>
77
where TData : class

samples/csharp/end-to-end-apps/Regression-SalesForecast/src/CommonHelper/ObjectPool.cs renamed to samples/csharp/end-to-end-apps/Forecasting-Sales/src/Shared/ObjectPool.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
using System;
22
using System.Collections.Concurrent;
33

4-
namespace CommonHelpers
4+
namespace Common
55
{
66
public class ObjectPool<T>
77
{
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
using Microsoft.ML;
2+
using System;
3+
using System.Collections;
4+
using System.Collections.Generic;
5+
using System.Linq;
6+
7+
namespace eShopForecast
8+
{
9+
public class TimeSeriesDataGenerator
10+
{
11+
/// <summary>
12+
/// Supplements the data and returns the orignial list of months with addtional months
13+
/// prepended to total a full 36 months.
14+
public static IEnumerable<ProductData> SupplementData(MLContext mlContext, IDataView productDataSeries)
15+
{
16+
return SupplementData(mlContext, mlContext.Data.CreateEnumerable<ProductData>(productDataSeries, false));
17+
}
18+
19+
20+
/// <summary>
21+
/// Supplements the data and returns the orignial list of months with addtional months
22+
/// prepended to total a full 36 months.
23+
public static IEnumerable<ProductData> SupplementData(MLContext mlContext, IEnumerable<ProductData> singleProductSeries)
24+
{
25+
var supplementedProductSeries = new List<ProductData>(singleProductSeries);
26+
27+
// Get the first month in series
28+
var firstMonth = singleProductSeries.FirstOrDefault(p => p.year == 2017 && p.month == singleProductSeries.Select(pp => pp.month).Min());
29+
30+
var referenceMonth = firstMonth;
31+
32+
float randomCountDelta = 4;
33+
float randomMaxDelta = 10;
34+
35+
if (singleProductSeries.Count() < 12)
36+
{
37+
var yearDelta = 12 - singleProductSeries.Count();
38+
39+
for (int i = 1; i <= yearDelta; i++)
40+
{
41+
var month = firstMonth.month - i < 1 ? 12 - MathF.Abs(firstMonth.month - i) : firstMonth.month - 1;
42+
43+
var year = month > firstMonth.month ? firstMonth.year - 1 : firstMonth.year;
44+
45+
var calculatedCount = MathF.Round(singleProductSeries.Select(p => p.count).Average()) - randomCountDelta;
46+
var calculatedMax = MathF.Round(singleProductSeries.Select(p => p.max).Average()) - randomMaxDelta;
47+
var calculatedMin = new Random().Next(1, 5);
48+
49+
var productData = new ProductData
50+
{
51+
next = referenceMonth.units,
52+
productId = firstMonth.productId,
53+
year = year,
54+
month = month,
55+
units = referenceMonth.prev,
56+
avg = MathF.Round(referenceMonth.prev / calculatedCount),
57+
count = calculatedCount,
58+
max = calculatedMax,
59+
min = calculatedMin,
60+
prev = referenceMonth.prev - MathF.Round((referenceMonth.units - referenceMonth.prev) / 2) // subtract the delta from the previous month to this month
61+
};
62+
63+
supplementedProductSeries.Insert(0, productData);
64+
65+
referenceMonth = productData;
66+
}
67+
}
68+
69+
return SupplementDataWithYear(SupplementDataWithYear(supplementedProductSeries));
70+
}
71+
72+
/// <summary>
73+
/// If we have 12 months worth of data, this will suppliment the data with an additional 12
74+
/// PREVIOUS months based on the growth exponent provided
75+
/// </summary>
76+
/// <param name="singleProductSeries">The initial 12 months of product data.</param>
77+
/// <param name="growth">The amount the values should grow year over year.</param>
78+
/// <returns></returns>
79+
static IEnumerable<ProductData> SupplementDataWithYear(IEnumerable<ProductData> singleProductSeries, float growth = 0.1f)
80+
{
81+
if (singleProductSeries.Count() < 12)
82+
{
83+
throw new NotImplementedException("fix this, currently only handles if there's already a full 12 months or more of data.");
84+
}
85+
86+
var supplementedProductSeries = new List<ProductData>();
87+
88+
var growthMultiplier = 1 - growth;
89+
90+
var firstYear = singleProductSeries.Take(12);
91+
92+
foreach (var product in firstYear)
93+
{
94+
var newUnits = MathF.Floor(product.units * growthMultiplier);
95+
var newCount = new Random().Next((int)MathF.Floor(product.count * growthMultiplier), (int)product.count);
96+
var newMax = MathF.Floor(product.max * growthMultiplier);
97+
var newMin = new Random().Next(1, 4);
98+
99+
var newProduct = new ProductData
100+
{
101+
next = MathF.Floor(product.next * growthMultiplier),
102+
productId = product.productId,
103+
year = product.year - 1,
104+
month = product.month,
105+
units = newUnits,
106+
avg = MathF.Round(newUnits / newCount),
107+
count = newCount,
108+
max = newMax,
109+
min = newMin,
110+
prev = MathF.Floor(product.prev * growthMultiplier)
111+
};
112+
113+
supplementedProductSeries.Add(newProduct);
114+
}
115+
116+
supplementedProductSeries.AddRange(singleProductSeries);
117+
118+
return supplementedProductSeries;
119+
}
120+
}
121+
}
+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
using CommonHelpers;
1+
using Common;
22
using Microsoft.ML;
33
using System;
44
using System.Threading;
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
</ItemGroup>
1818

1919
<ItemGroup>
20-
<ProjectReference Include="..\CommonHelper\CommonHelpers.csproj" />
20+
<ProjectReference Include="..\Shared\Shared.csproj" />
2121
</ItemGroup>
2222

2323
<ItemGroup>
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
using CommonHelpers;
2-
using eShopDashboard.Forecast;
1+
using Common;
2+
using eShopForecast;
33
using eShopDashboard.Settings;
44
using Microsoft.AspNetCore.Mvc;
55
using Microsoft.Extensions.Logging;
@@ -38,7 +38,19 @@ public IActionResult GetCountrySalesForecast(string country,
3838
[FromQuery]float sales, [FromQuery]float std)
3939
{
4040
// Build country sample
41-
var countrySample = new CountryData(country, year, month, max, min, std, count, sales, med, prev);
41+
var countrySample = new CountryData()
42+
{
43+
country = country,
44+
year = year,
45+
month = month,
46+
max = max,
47+
min = min,
48+
std = std,
49+
count = count,
50+
sales = sales,
51+
med = med,
52+
prev = prev
53+
};
4254

4355
this.logger.LogInformation($"Start predicting");
4456
//Measure execution time
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
1-
using CommonHelpers;
2-
using eShopDashboard.Forecast;
1+
using Common;
2+
using eShopForecast;
33
using eShopDashboard.Settings;
44
using Microsoft.AspNetCore.Mvc;
55
using Microsoft.Extensions.ML;
@@ -12,10 +12,10 @@ namespace eShopDashboard.Controllers
1212
public class ProductDemandForecastController : Controller
1313
{
1414
private readonly AppSettings appSettings;
15-
private readonly PredictionEnginePool<ProductData, ProductUnitPrediction> productSalesModel;
15+
private readonly PredictionEnginePool<ProductData, ProductUnitRegressionPrediction> productSalesModel;
1616

1717
public ProductDemandForecastController(IOptionsSnapshot<AppSettings> appSettings,
18-
PredictionEnginePool<ProductData, ProductUnitPrediction> productSalesModel)
18+
PredictionEnginePool<ProductData, ProductUnitRegressionPrediction> productSalesModel)
1919
{
2020
this.appSettings = appSettings.Value;
2121

@@ -25,16 +25,27 @@ public ProductDemandForecastController(IOptionsSnapshot<AppSettings> appSettings
2525

2626
[HttpGet]
2727
[Route("product/{productId}/unitdemandestimation")]
28-
public IActionResult GetProductUnitDemandEstimation(string productId,
28+
public IActionResult GetProductUnitDemandEstimation(float productId,
2929
[FromQuery]int year, [FromQuery]int month,
3030
[FromQuery]float units, [FromQuery]float avg,
3131
[FromQuery]int count, [FromQuery]float max,
3232
[FromQuery]float min, [FromQuery]float prev)
3333
{
3434
// Build product sample
35-
var inputExample = new ProductData(productId, year, month, units, avg, count, max, min, prev);
35+
var inputExample = new ProductData()
36+
{
37+
productId = productId,
38+
year = year,
39+
month = month,
40+
units = units,
41+
avg = avg,
42+
count = count,
43+
max = max,
44+
min = min,
45+
prev = prev
46+
};
3647

37-
ProductUnitPrediction nextMonthUnitDemandEstimation = null;
48+
ProductUnitRegressionPrediction nextMonthUnitDemandEstimation = null;
3849

3950
//Predict
4051
nextMonthUnitDemandEstimation = this.productSalesModel.Predict(inputExample);

0 commit comments

Comments
 (0)