ML.NET Predictions on the Web in F# with the SAFE Stack

Building web sites in C# has be something that you could do for quitea while. But, did you know that you can do web sites in F#? Enter the SAFE Stack. An all-in-one framework that allows you to use F# on the server, but also allows you to use F# on the client side. That's right, no more JavaScript for the client side!

For a video version of this post, checkout the video below.

Introduction to the SAFE Stack

The SAFE Stack is built on top of these components:

  • Saturn
  • Azure
  • Fable
  • Elmish

Let's go into each of these in a bit more detail.

Saturn

Saturn is a backend framework built in F#. Saturn privides several parts to help us build web applications, such as the application lifecycle, a router, controllers, and views.

Azure

Azure is Microsoft's cloud platform. This is mostly used for hosting our website and any other cloud resources that we may need, such as Azure Blob Storage for files or Azure Event Hub for real time streaming data.

Fable

Fable is a JavaScript compiler. Similar to how TypeScript compiles into JavaScript, Fable does the same, except that you write F# and it compiles into JavaScript.

Elmish

The Elmish concept builds on top of Fable to provide a model-view-update pattern that is popularized in the Elm programming language.

Creating a Project

The best way to create a SAFE Stack project is to follow the steps in the documentation, but I'll highlight them here. By the way, their documentation is great!

There is a .NET template created to make creating a SAFE project much easier than manually putting it together.

To install the template, run the below command.

dotnet new -i SAFE.Template

1.png

Once the template is installed, make a new directory to keep the project files.

mkdir MLNET_SAFE

Then, you can use the .NET CLI to create a new project from the template with another command and specify the name of the project.

dotnet new SAFE -n MLNET_SAFE

Once that finishes, run the command to restore the tools used for the project. Specifically, the FAKE tool, which is used to build and run the project.

dotnet tool restore

2.png

With that done we can now run the app! To do that run the FAKE command with the run target.

dotnet fake build --target run

3.png

This is going to perform the following steps (which can be found in the build.fsx file):

  • Clean the solution
  • Run npm install to install client side dependencies
  • Compiles the projects
  • Run the projects in watch mode

When that completes, you can navigate to http://localhost:8080. We now have a running instance of the SAFE Stack!

4.png

The template is a todo app which helps show different aspects of the SAFE Stack. Feel free to explore the app and the code before continuing.

Adding ML.NET

The Model

For the ML.NET model, I'll be using the salary model that was created in the below video. It's a simple model with a small dataset to go over more of the F# and ML.NET nuances than working with the data itself.

In the Server project, add a new folder called "MLModel". In there, we can add the model file that was generated from the above video. We would also need to update the properties on the file to allow it to output during build.

Note that this can easily be in Azure Blob Storage instead and use the SDK to retrieve and download it from there.

5.png

Next, for the Server and Shared projects, add the Microsoft.ML NuGet packge. At this time, it's at version 1.5.4.

6.png

Updating the Shared File

Now we can update the file in the Shared project. We can put types and methods in this file that we know will be used in more than one other project. For our case, we can use the model input and output schemas.

type SalaryInput = {
    YearsExperience: float32
    Salary: float32
}

[<CLIMutable>]
type SalaryPrediction = {
    [<ColumnName("Score")>]
    PredictedSalary: float32
}

The SalaryInput class has two properties that are both of type float32. The SalaryPrediction class is special where we need to put the CLIMutable attribute on it. That has one property that's also of type float32. This property has the ColumnName attribute on it to map to the output column from the ML.NET model.

There's one other type we can add to our shared file. We can create an interface that has a method to get our predictions that can be called from the client to the server.

type ISalaryPrediction = { getSalaryPrediction: float32 -> Async<string> }

In this type, we create a method signature called getSalaryPrediction which takes in a paramter of type float32 and it returns a type of Async of string. So this method is asynchornous and will return a string result.

Updating the Server

Next, we can update our server file. This file contains the code to run the web server and any other methods that we may need to call from the client.

To run the web app you have the following code:

let webApp =
    Remoting.createApi()
    |> Remoting.withRouteBuilder Route.builder
    |> Remoting.fromValue predictionApi
    |> Remoting.buildHttpHandler

let app =
    application {
        url "http://0.0.0.0:8085"
        use_router webApp
        memory_cache
        use_static "public"
        use_gzip
    }

run app

The app variable creates an application instance and sets some properties of the web app, such as what the URL is, what router to use, and to use GZip compression. You can also add items such as using OAuth, set logging, or enable CORS.

The webApp variable creates the API and builds the routing. Both of these are based on the predictionApi variable which is based off the ISalaryPrediction type we defined in the shared file.

let predictionApi = { getSalaryPrediction =
    fun yearsOfExperience -> async {
        let prediction = prediction.PredictSalary yearsOfExperience
        match prediction with
        | p when p.PredictedSalary > 0.0f -> return p.PredictedSalary.ToString("C")
        | _ -> return "0"
    } }

The API has the one method we defined in the interface - getSalaryPrediction. This is where we implement that interface method. It takes in a variable, yearsOfExperience, and it runs an async method defined by the async keyword. In the brackets is what it should run.

All we are running in there is to use a prediction variable to call the PredictSalary method on it and pass in the years of experience variable to it. With the value from that we do a match expression and if the PredictedSalary property is greater than 0 we return that property formatted as a currency. If it is 0 or below, we just return the string "0".

But where did the prediction variable come from? Just above the API implementation, a new Prediction type is created.

type Prediction () =
    let context = MLContext()

    let (model, _) = context.Model.Load("./MLModel/salary-model.zip")

    let predictionEngine = context.Model.CreatePredictionEngine<SalaryInput, SalaryPrediction>(model)

    member __.PredictSalary yearsOfExperience =
        let predictedSalary = predictionEngine.Predict { YearsExperience = yearsOfExperience; Salary = 0.0f }

        predictedSalary

This creates the instance of the MLContext. It also loads in the model file, and creates a PredictionEngine instance from the model. Remember the SalaryInput and SalaryPrediction types are from the shared project. And notice that, when we load from the model, it returns a tuple. The first value returns the model whereas the second value returns the DataViewSchema. Since we don't need the DataViewSchema in our case, we can ignore it using an underscore (_) for that variable.

This type also creates a member method called PredictSalary. This is where we call the predictionEngine.Predict method and give it an instance of SalaryInput. Because F# is really good at inferring types, we can just give it the YearsExperience property and it knows that it is the SalaryInput type. We do need to supply the Salary property as well, but we can just set that to 0.0. Then, we return the predicted salary from this method. In F# we don't need to specify the return keyword. It automatically returns if it's the last item in the method.

Updating the Client

With the server updated to do what we need, we can now update the client to use the new information. Everything we need to update will be in the Index.fs file.

There are a few Todo items that it's trying to use here from the Shared project. We'll have to update these to use our new types.

First, we have the Model type. This is the state of our client side information. For the Todo application, it has two properties, Todos and Input. The Input property is the current input in the text box and the Todos property are the currently displayed Todos. So to update this we can change the Todos property to be PredictedSalary to indicate the currently predicted salary from the input of the years of experience. This property would need to be of type string.

type Model =
    { Input: string
      PredictedSalary: string }

The next part to update is the Msg type. This represents the different events that can update the state of your application. For todos, that can be adding a new todo or getting all of the todos. For our application we will keep the SetInput message to get the value of our input text box. We will remove the others and add two - PredictSalary and PredictedSalary. The PredictSalary message will initiate the call to the server to get the predicted salary from our model, and the PredictedSalary message will initiate when we got a new salary from the model so we can update our UI.

type Msg =
    | SetInput of string
    | PredictSalary
    | PredictedSalary of string

For the todosApi we simply rename it to predictionApi and change it to use the ISalaryPrediction instead of the ITodosApi.

let predictionApi =
    Remoting.createApi()
    |> Remoting.withRouteBuilder Route.builder
    |> Remoting.buildProxy<ISalaryPrediction>

The init method can be updated to use our updated model. So instead of having an array of Todos we just have a string of PredictedSalary.

let init(): Model * Cmd<Msg> =
    let model =
        { Input = ""
          PredictedSalary = "" }
    model, Cmd.none

Next, we update the update method. This takes in a message and will perform the work depending on what the message is. For the Todos app, if the message comes in as AddTodo it will then call the todosApi.addTodo method to add the todo to the in-memory storage. In our app, we will keep the SetInput message and add two more to match what we added in our Msg type from above. The PredictSalary message will convert the input from a string to a float32 and pass that into the predictionApi.getSalaryPrediction method. The PredictedSalary message will then update our current model with the new salary.

let update (msg: Msg) (model: Model): Model * Cmd<Msg> =
    match msg with
    | SetInput value ->
        { model with Input = value }, Cmd.none
    | PredictSalary ->
        let salary = float32 model.Input
        let cmd = Cmd.OfAsync.perform predictionApi.getSalaryPrediction salary PredictedSalary
        { model with Input = "" }, cmd
    | PredictedSalary newSalary ->
        { model with PredictedSalary = newSalary }, Cmd.none

The last thing to update here is in the containerBox method. This builds up the UI. You may have already noticed that there is no HTML in our solution anywhere. That's because Fable is using React behind the scenes and we are able to write the HTML in F#. We'll keep the majority of the UI so there's only a few items to update. The content is what's currently holding the list of todos in the current app. For our case, however, we want it to show the predicted salary so we'll remove the ordered list and replace it with the below div. This sets a label and, if the model.PredictedSalary is empty it doesn't display anything. But if it isn't empty it does a formatted string containg the predicted salary.

div [ ] [ label [ ] [ if not (System.String.IsNullOrWhiteSpace model.PredictedSalary) then sprintf "Predicted salary: %s" model.PredictedSalary |> str ]]

Next, we just need to update the placeholder in the text box to match what we would like the user to do.

Control.p [ Control.IsExpanded ] [
                Input.text [
                  Input.Value model.Input
                  Input.Placeholder "How many years of experience?"
                  Input.OnChange (fun x -> SetInput x.Value |> dispatch) ]
            ]

And with the button we just need to tell it to dispatch, or fire off a message, to the PredictSalary message.

Button.a [
   Button.Color IsPrimary
   Button.OnClick (fun _ -> dispatch PredictSalary)
]

With all of those updates we can now run the app again to see how it goes.


Being able to use F# for the client as well as the server is a great way for F# developers to not only build web applications without having to use JavaScript and any of their frameworks, but also so they can utilize their functional programming knowledge to reduce bugs in the code.

If I were building web apps for personal or freelance work, I'll definitely give the SAFE Stack a try. I believe my productivity and efficiency of building the web applications will be much better with it.

To learn more (there is a good bit to learn since we're not only using functional patterns in a web application, we are also using the model view update pattern for the UI) I highly recommend the Elmish documentation and Elmish book by Zaid Ajaj. I'll be referencing these a lot in the days to come.

Getting Up and Running with FsLab for Data Science

FSAdvent time is back this year! I'm going to use this post to start a new work in progress series on using F# for Dara science. This series will mainly consist of using FsLab to manipulate and visualize data.

Setting Up Your Environment

The easiest way to get setup in the IDE of you choosing is to just download the basic template of FsLab. This just references FsLab with Paket and gives a small script example. That's perfect for all that we need to do.

We're going to go through the script that it provides (with a bit of changes) in this post. Don't worry though, we'll go through a good bit of different aspects of the script in future posts. We're just getting up and running here as sometimes that can be the biggest hurdle to get started in something new.

Generating Data

FsLab comes with the FSharp.Data package which includes a few very useful type providers which help provide a safe and easy way to get data from external sources.

World Bank Type Provider

As previously mentioned, FSharp.Data comes with a few very useful type providers. One that we'll use here to help give us some real world data is the World Bank type provider. You could spend all day looking at what data it provides. For this post, though, we'll look at the US and EU percent added to their GDP from industry.

Setting Up Script

Before we can access the World Bank type provider, though, we need to set up our script to access it. If you used the basic template it will already have FsLab setup with Paket so you would just need to run paket.exe install or build the project.

Now we can reference FsLab so first thing to do is to load the script to access everything we need from it:

#load "packages/FsLab/FsLab.fsx"

Now that we have the FsLab script loaded we can access all sorts of libraries from it by using the 'open' keyword in F#.

open Deedle
open FSharp.Data
open XPlot.GoogleCharts
open XPlot.GoogleCharts.Deedle

We have a few external libraries we're using throughout our script:

  • Using Deedle to explore our data.
  • The FSharp.Data library for our World Bank type provider.
  • GoogleCharts and GoogleCharts.Deedle to visualize our data.

Getting Our Data

Next we instantiate the World Bank type provider and from there we get a reference to the Indicators object for the US and for the EU.

let wb = WorldBankData.GetDataContext()

let us = wb.Countries.``United States``.Indicators
let eu = wb.Countries.``European Union``.Indicators

From there we get the industry percent value added to the country's GDP and create a data series.

let usSeries = series us.``Industry, value added (% of GDP)``
let euSeries = series eu.``Industry, value added (% of GDP)``

Now here is where some work on the data happens.

abs (usSeries - euSeries)
|> Series.sort
|> Series.rev
|> Series.take 5

From here we get the absolute value of the difference of the two series, sort it, get the reverse of it, then take only the first five items. Looks like a bit, but we'll go into details of certain statistics for data science in future posts.

Charting Our Data

Now that we have the data we want, let's chart it. The charting library in FsLabs will allow us to customize the chart as we see fit for visualizing. For this there is an Options type that we can create. We'll just give a title and tell the legend to appear at the bottom.

let options = Options ( title = "% of GDP value added", legend = Legend(position="bottom") )

Now we just need to tell the charting library to chart our data.

[ usSeries.[2000 .. 2010]; euSeries.[2000 .. 2010] ]
|> Chart.Line
|> Chart.WithOptions options
|> Chart.WithLabels ["US"; "EU"]

We give it our data as an F# list, tell it to create a line chart, give it our options, and then tell what our labels are for our legend.

Executing

Just run it all in F# Interactive. :)

New Wintellect Blog Posts

For this Thanksgiving, I thought sharing a lot of my recent Wintellect blog posts. If I may say, there's a lot of good stuff for you to go through while digesting a Thanksgiving meal. Hope you all enjoy!

Spectron

I've been doing a bit of Spectron lately for a client and have been posting some insights I've gained from it.

Xamarin

I've been into Xamarin for quite a while now and have quite a few links that go through different aspects of it, mainly with F#. This includes my very first screencast and using FAKE with your Xamarin projects.

Wintellect F# Blog Post Collection

I know it seems I've been MIA lately, but the truth is I've actually been blogging quite a bit on F# at my spot in Wintellect for a little while. Below is a collection of what's currently out there.

There will definitely be more in the future so keep an eye out there for more posts on F# and there may even be a few on Xamarin with F#.

Introduction to Paket

Paket has been quite the talk lately in the .NET community, and for very good reason. It is essentially another way to manage dependencies in your .NET projects.

But why replace NuGet?

Paket is basically a NuGet replacement so I'm sure you're wondering, "why would we need to replace NuGet?" Paket essentially just takes the functionality of NuGet and adds some extra nice features.

For one thing, Paket makes it able for you to control exactly what's happening with your package dependencies. No more conflict between different packages if those packages reference different versions of the same dependent package.

Another really cool thing Paket does is that it can reference a single file from GitHub. How many times have you needed that and just wound up downloading what you needed and using it that way? If a new version of that file comes along, you'll have to repeat that process.

But I'm already using NuGet

No problem at all! Paket has a nifty convert-from-nuget command to get you up and going.

I'm hooked...but how do I get started?

First, you need to include a .paket folder in the root of your solution. This will include paket.exe that will be used to install and restore packages.

Once that folder and its contents are there, you'll need to create a paket.dependencies file in the root directory of your solution. This file will be similar to the following:

source http://nuget.org/api/v2
nuget FSharp.Data
nuget FAKE

This file tells Paket what the sources are (NuGet or GitHub) and the package/file names so it can be downloaded.

You can then use a build.cmd file or manually call paket.exe like below.

\.paket\paket.exe install

This will create a packages folder that will include all the libraries.

From here you can always manually reference the libraries that you want, but Paket makes this easy as well. In each of the folders where you have a project file, create a paket.references file that contain the names of each library you want to be referenced, like below.

FSharp.Data

Note that FAKE isn't in the file since it won't get referenced. The paket.references file will only add to the project if the library is in a lib folder. FAKE is in a tools folder. This isn't a problem since it can be referenced manually in the build.fsx file.

To get Paket to use the references file, simply rerun the install command with the --hard switch.

\.paket\paket.exe install --hard

This will look at the paket.references file and use that to automatically reference the project with the appropriate libraries.

After that, you're good to get started on your project.

Conclusion

Hopefully this walkthrough will help you get started with using Paket to make your package management easier than before. This is still a young and very active project so I wouldn't be surprised if there are tons of things that this can do for all of our .NET projects.

Why F# for the Enterprise

I have gotten bit by the F# bug lately. After helping to release the F# portion of Exercism I've started to enjoy more and more of what F# has to offer. Of course, F# is a sort of "hybrid" language where you can do object oriented and procedural programming if you make it that way, but out of the box it's trying to get you to go more functional.

Several folks have posted as to why learn F# in the first place, but I figured I'd give some reasons why I think F# would be great for scenarios inside enterprise applications. While a few what others have mentioned as good reasons to learn F# in the first place, I believe these would also be the top reasons for enterprise development.

What's wrong with the current enterprise solutions?

Well, nothing, really. Of course, just because one solution works doesn't mean it's the most productive or easiest to maintain. For example, CSS precompilers like SASS and LESS do this with plain CSS stylesheets.

Why not use something that makes our lives, as developers, easier and helps to bring back the fun in developing again?

No More NullReferenceExceptions

While the above always tends to make me laugh, I'm quite sure each and every developer has fixed a production defect that involved this error. 

In F# there are is no thought of "null". You'll even get a compiler error if you try to use "null" in a pure F# program. For example, take a peek at the below code:

We're just defining some record types - Person and Employee - and let binding the Employee record with some values. Now, what happens if I set Person to null?

The compiler won't even let us try that...

Trying to assign null to a record type.

So this shows that, in pure F# code, the compiler won't allow us to set records as null which greatly reduces, perhaps even eliminates, NullReferenceExceptions.

What if we still need null?

There are some exceptions where F# will allow you to use null. Both of which involve using the .NET libraries.

  • As a parameter when calling one of the .NET libraries.
  • Handling if a call to a .NET library returns null.

Even using null in these scenarios can greatly reduce the NullReferenceException occurring in your application. To me, that's just one less thing I need to worry about.

But I still need it in my F# code

No worries! The language has you covered on that aspect as well. Allow me to introduce the option type. This is used if a value might not exist.

Let's take a small example of a bank account.

If the account isn't open yet or has already been closed, we don't expect a value. Otherwise, we do expect some value.

None vs null

How is this different than assigning to null? Remember that None is an option type. Because of that, the compiler will help us out if we may have missed anything. For example, in F# you can do pattern matching. Let's see how we can use pattern matching with the option type.

Good to go there. But, what if we happened to miss a match on None? We'll get this warning:

Pattern match warning.

Units of Measure

Units of Measure allow you to explicitly type a certain unit. F# does have predefined unit names and unit symbols in the Microsoft.FSharp.Data.UnitSystems.SI.UnitNames and Microsoft.FSharp.Data.UnitSystems.SI.UnitSymbols namespaces.

For example, say we wanted to use meters. Instead of assuming our application is using meters throughout all the calculation, which has a probability of being converted to another unit but still being represented as meter, we would just need to do the following:

Microsoft has included a few units of measure for us, as mentioned earlier, but we can definitely create our own. Say we had an application that dealt with different types of world currencies. We can easily define them as units of measure.

Now, what does this do for us exactly? Let's try to convert how many euros are in a dollar amount.

The first method we defined works just fine since we're using the units of measure. However, the second method generates a compile error since it can't infer what type "0.74" should be.

Since a unit of measure is typed, it will generate compile errors and reduce the amount of defects your application can cause due to a mismatch of units. This type of error does happen in real world applications...even to NASA engineers.

Type Providers

Type providers in F# is essentially a way to access all sorts of data. There are quite a few of them all ready for you to use now.

Just the SQL type providers alone are worth it for enterprise applications. Even better, you can even use them to access Entity Framework and LINQ to SQL.

Another important one that may be of great use to enterprise applications is the JSON type provider

Type providers provide an easy way to consume data within F# and, if you'd like, use C# to handle that data.

 

The above are just a few reasons of why I believe F# to be of great use to developing enterprise applications. Using the above, the risks of error would be greatly reduced and productivity would be increased. I know I'll be doing more F# in my applications from now on and I can only see it getting even better in the future as this language evolves.