What is Blazor WebAssembly?
In case this is your first time learning about Blazor, let me introduce you to what Blazor WebAssembly is all about.
Blazor is an open source and cross-platform web UI framework for building single-page apps using .NET and C# instead of JavaScript. Blazor is based on a powerful and flexible component model for building rich interactive web UI. You implement Blazor UI components using a combination of .NET code and Razor syntax: an elegant melding of HTML and C#. Blazor components can seamlessly handle UI events, bind to user input, and efficiently render UI updates.
What is ML.NET?
ML.NET is a free, cross-platform, open-source machine learning framework provided by Microsoft. It is made specifically for the .NET community. With the help of ML.Net framework, you can easily implement and integrate machine learning features into your existing or new .Net applications.
To start working on ML.Net, you don’t have to be a machine learning expert. You can just start building simple ML.Net applications while teaching yourself.
In this article, I am going to give a demo of a simple yet exciting ML.Net based project. We will develop an application that will be used to forecast bike rental service demand using univariate time series analysis.
The code for this sample can be found on the CloudBloq/BlazorWithML.NET repository on GitHub.
What You’ll Need to Get Started
- Visual Studio 2019 with the ".NET Core cross-platform development" workload installed.
Needed Projects
We will be making use of three projects:
- An ASP.NET Blazor WebAssembly project which serves as the UI to display the forecast.
- An ASP.NET Web Api project which feeds the UI with data (the bike rental service demand forecast) from the service.
- A service [C# Class Library (.NET Standard)] which will contain the logics used to forecast bike rental service demand.
If you clone the CloudBloq/BlazorWithML.NET repository, the database already resides in the Data folder in project BlazorWithML.NET.Api. When you run the application for the first time, the database will be automatically created and filled with the needed data. The database already contains the data that will be used to train the model.
Setting Up the Projects
- ASP.NET Blazor WebAssembly project
Launch Visual Studio, click the Create a new project link. Next, type Blazor in the search box and click the first option that comes up (Blazor App):
Give the project and solution a name, e.g. BlazorWithML.NETPOC then click the Create button.
Next, select the Blazor WebAssembly App option and click the Create button. This will create the project and solution for you. This link will help you understand a Blazor WebAssembly project structure
- ASP.NET Web Api project
Right click the Solution in the Solution Explorer and select Add -> New Project, search for Web Application and select the ASP.NET Core Web Application option:
click Next, give the project a name BlazorWithML.NET.Api, then click Create, select the API option:
and finally click Create.
- C# Class Library (.NET Standard)
Right click the Solution in the Solution Explorer and select Add -> New Project, search for Class Library (.NET Standard) and select the C# version. Click Next, give the project a name Forecasting_BikeSharingDemandLib, then click Create.
Adding Projects Dependencies
We need to add the ML.NET and SqlClient package to the service: Right-click the Forecasting_BikeSharingDemandLib project, select Manage NuGet Packages for Solution. Under the Browse section, search for Microsoft.ML and click on it then click the Install button. After that, search for Microsoft.ML.Timeseries then click the Install button. Also search for System.Data.SqlClient and Install it.
We need to give the Api access to the Service: This can be done by adding a reference of the service to the Api. Right-click the Dependencies of project BlazorWithML.NET.Api and click Add project Reference, select the Forecasting_BikeSharingDemandLib checkbox and click ok.
Writing the Service
The service is the backend of the application. It contains the logics used to forecast bike rental service demand.
We will make use of univariate time series analysis algorithm known as Singular Spectrum Analysis to forecast the demand for bike rentals.
Understand the problem
In order to run an efficient operation, inventory management plays a key role. Having too much of a product in stock means not generating any revenue. Having too little product leads to lost sales. Therefore, the constant question is, what is the optimal amount of inventory to keep on hand? Time-series analysis helps provide an answer to these questions by looking at historical data, identifying patterns, and using this information to forecast values some time in the future.
The algorithm used in this article for analyzing data is univariate time-series analysis.
Univariate time-series analysis takes a look at a single numerical observation over a period of time at specific intervals, for example monthly sales. It works by decomposing a time-series into a set of principal components. These components can be interpreted as the parts of a signal that correspond to trends, noise, seasonality, and many other factors. Then, these components are reconstructed and used to forecast values some time in the future.
Create the needed files and folders
Right-click the Forecasting_BikeSharingDemandLib in the Solution Explorer then select Add -> New Folder and give it the name Model. This folder will contain the objects used to model the output.
The output will be in two forms:
- The Evaluation, which will contain the Mean Absolute Error and the Root Mean Squared Error and
- The Forecast, which will contain Lower Estimate, UpperEstimate, Forecast...
Create the Forecast output object by right clicking the Model folder and select Add -> class then name it ForecastOutput.cs. The code for it is:
public class ForecastOutput
{
public string Date { get; set; }
public float ActualRentals { get; set; }
public float LowerEstimate { get; set; }
public float Forecast { get; set; }
public float UpperEstimate { get; set; }
}
Next, right-click the Model folder and select Add -> class then name it EvaluateOutput.cs. The code for it is:
public class EvaluateOutput
{
public float MeanAbsoluteError { get; set; }
public double RootMeanSquaredError { get; set; }
}
We also need to add the class that will contain the logic. Right-click the project Forecasting_BikeSharingDemandLib and select Add -> class then name it BikeForcast.cs.
The Logic
The entire code for the file BikeForcast.cs is (I will break it down bit by bit):
GetConnectionString(), lines 15 to 22, is used to get the connection string where the data is stored and also the Model path. MLModel.zip can be found in the root folder of project BlazorWithML.NET.Api.
Lines 144 to 151 define the ModelInput class. This class is used to hold the database objects and it contains the following columns:
- RentalDate: The date of the observation.
- Year: The encoded year of the observation (0=2011, 1=2012).
- TotalRentals: The total number of bike rentals for that day.
Lines 153 to 161 define the ModelOutput class. This class contains the following columns:
- ForecastedRentals: The predicted values for the forecasted period.
- LowerBoundRentals: The predicted minimum values for the forecasted period.
- UpperBoundRentals: The predicted maximum values for the forecasted period.
Line 24 defines the GetBikeForcast() method that takes in a parameter numberOfDaysToPredict : int. The parameter will be gotten from the UI. The user will have to input the amount of days (between 1 and 500) he/she wants to predict for. This method holds the logic to forecast the demands with the help of two other methods for Evaluate and Forecast.
Line 26 defines the MLContext. The MLContext class is a starting point for all ML.NET operations, and initializing mlContext creates a new ML.NET environment that can be shared across the model creation workflow objects. It's similar, conceptually, to DBContext in Entity Framework.
Next we need to load the data from the database.
Line 29 creates a DatabaseLoader that loads records of type ModelInput. Line 32 defines the query to load the data from the database.
ML.NET algorithms expect data to be of type Single. Therefore, numerical values coming from the database that are not of type Real, a single-precision floating-point value, have to be converted to Real.
The Year and TotalRental columns are both integer types in the database. Using the CAST built-in function, they are both cast to Real.
We will create a DatabaseSource to connect to the database and execute the query on line 35, after which we then load the data into an IDataView on line 40.
The dataset contains two years' worth of data. Only data from the first year is used for training; the second year's data is held out to compare the actual values against the forecast produced by the model. Filter the data (line 43 and 44) using the FilterRowsByColumn transform.
For the first year, only the values in the Year column less than 1 are selected by setting the upperBound parameter to 1. Conversely, for the second year, values greater than or equal to 1 are selected by setting the lowerBound parameter to 1.
Next, we will define a pipeline that uses the SsaForecastingEstimator to forecast values in a time-series dataset, as shown on lines 47 to 56.
The forecastingPipeline takes 365 data points for the first year and samples or splits the time-series dataset into 30-day (monthly) intervals as specified by the seriesLength parameter. Each of these samples is analyzed through a weekly or 7-day window. When determining what the forecasted value for the next period(s) is(are), the values from the previous seven days are used to make a prediction.
The model is set to forecast a period of the number of day(s) the User input into the future as defined by the horizon parameter. Because a forecast is an informed guess, it's not always 100% accurate. Therefore, it's good to know the range of values in the best and worst-case scenarios as defined by the upper and lower bounds. In this case, the level of confidence for the lower and upper bounds is set to 95%. The confidence level can be increased or decreased accordingly. The higher the value, the wider the range is between the upper and lower bounds to achieve the desired level of confidence.
On line 59, we use the Fit method to train the model and fit the data to the previously defined forecastingPipeline.
Next, we will evaluate the model, and lines 74 to 104 defines an helper method that will be used to evaluate the model.
To evaluate performance, the following metrics are used:
- Mean Absolute Error: Measures how close predictions are to the actual value. This value ranges between 0 and infinity. The closer to 0, the better the quality of the model.
- Root Mean Squared Error: Summarizes the error in the model. This value ranges between 0 and infinity. The closer to 0, the better the quality of the model.
We evaluate how well the model performs by forecasting next year's data and comparing it against the actual values.
Inside the Evaluate method, forecast the second year's data by using the Transform method with the trained model on line 77.
We get the actual values from the data by using the CreateEnumerable method on line 80, and the forecasted values by using the CreateEnumerable method on line 85.
We then calculate the difference between the actual and forecasted values, commonly referred to as the error, on line 90.
We measure performance by computing the Mean Absolute Error and Root Mean Squared Error values on lines 93 and 94.
Next, we need to save the evaluated model. The model is saved in a file called MLModel.zip as specified by the previously defined modelPath variable from the GetConnectionString() method. Use the Checkpoint method to save the model on line 66.
Finally, we will use the model to forecast demand. Lines 106 to 140 define the Forecast helper method.
We create a List to hold the result of the forecast on line 108, then we use the Predict method on line 111 to forecast rentals for the number of day(s) entered by the user.
Then we align the actual and forecasted values on lines 113 to 131.
With this, we are done with the logic.
The Api
The Blazor WebAssembly app will need an Api to get the forecast from the service. First, we need to enable CORS on the Api. Go to the Startup.cs file of project BlazorWithML.NET.Api. Create a new field:
readonly string AllowedOrigin = "allowedOrigin";
Next, add the CORS service to your app by adding these lines of code to the ConfigureServices(IServiceCollection services) method:
services.AddCors(option => {
option.AddPolicy("allowedOrigin",
builder => builder.AllowAnyOrigin().AllowAnyMethod().AllowAnyHeader()
);
});
Finally, add the CORS middleware to the Configure(IApplicationBuilder app, IWebHostEnvironment env) method:
app.UseCors(AllowedOrigin);
Ensure that this middleware is added as the first middleware in the pipeline that is right after the:
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage()
}
The Controller
Create an empty Api controller in the Controllers folder, and name it BikeDemandForcastController.cs. The code for this controller is:
The first endpoint, lines 11 to 17, (/GetEvaluateOutput/{numberOfDaysToPredict}) is used to get the EvaluateOutput.
The second endpoint, lines 20 to 26, (/GetForecastOutput/{numberOfDaysToPredict}) is used to get the ForecastOutput.
The UI
First, we are going to set the base Uri of the Api endpoints. This can be done in the Program.cs of project BlazorWithML.NET by replacing:
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) });
with
builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri("https://localhost:5001/api/") });
Model Objects
The Api returns data in JSON format so we need to map the response data to an object which would be displayed to the user. Create a new folder called Model and create a class called ForecastOutput.cs in the Model folder. The code for this class is:
Create another class called EvaluateOutput.cs:
We will also need an object to store the input from the user. In the Model folder create a class called BikeForcastInput.cs:
Blazor Page
Blazor pages end with .razor and they are called Razor component.
In the Pages folder, create a Razor Component and name it BikeForcast.razor This component will be used to get the input(number of day(s) from the user) and display the Evaluate and Forecast output. The code is:
Line 1 defines the Uri the BikeForcast.razor will be mapped to. Lines 11 to 70 define the components that will be displayed to the user. The GetBikeDemandForcast() (lines 78 to 82) is used to call the endpoints to get the Evaluate and Forecast.
- Navigation
Replace the code in the NavMenu.razor component in the Shared folder with:
With this, we are done with coding. Now it's time to test our application.
Testing the application
First, we need to set the UI (BlazorWithML.NET) and Api (BlazorWithML.NET.Api) to start up at the same time when we run the application.
Right-click the solution then click Set Startup Projects. Select Multiple startup projects and set the Action of BlazorWithML.NET and BlazorWithML.NET.Api to Start. You should get the image below:
Click Apply then Ok. Right-click the solution and click Build Solution. After Build is successful, click CTRL + f5 to run the application. If you are getting an IIS error restart your VS Build the project again, and click CTRL + f5. Once the application is running you, should get the image below:
Click the Bike Demand Forcast link, enter a number between 1 and 500, and you should get the result of the Forecast in a table as shown below:
Do More Blazor or ML.NET!!
If you enjoyed this article and feel like learning more about Blazor WebAssembly or ML.NET, the links below might be just what you need:
Awesome! Please share it with anyone you think could use this information. Thanks for reading. As always if you have any questions, comments, or concerns about this post feel free to leave a comment below.
Top comments (6)
So, I have put to practice all that you have shared here and I have a bug since yesterday 13th February 2021, I will appreciate if you be willing to assist. Apparently I sent you a connection request on LinkedIn but I think you are yet to confirm it. How can I reach you so I could share my code base with you.
Regards
Hi, thanks for reading the article. I have accepted your LinkedIn request, thanks
Wow, I am blown away by the details of your article. I am working on s mini ML. Net project and the knowledge shared here will be valuable to my learning curve.
Hello Mr. Ojo, I have gone through everything you explained here and also applied same. I have a bug will you be willing to assist me please?
Yes I am willing to assist, thanks.
Hi! After running the app, and editing the number of days, I get the error: dev-to-uploads.s3.amazonaws.com/up...