Use C# And ML.NET Machine Learning To Predict Taxi Fares In New York
By: Mark Farragher
Tags: Machine Learning, Artificial Intelligence, Deep Learning, Csharp, Data Science

I’m using the awesome Rainbow CSV plugin for Visual Studio Code which is highlighting my CSV data file with these nice colors.

There are a lot of columns with interesting information in this data file, but I will only be focusing on the following:

Column 0: The data provider vendor ID

Column 3: Number of passengers

Column 4: Trip distance

Column 5: The rate code (standard, JFK, Newark, …)

Column 9: Payment type (credit card, cash, …)

Column 10: Fare amount

I’ll build a machine learning model in C# that will use columns 0, 3, 4, 5, and 9 as input, and use them to predict the taxi fare for every trip. Then I’ll compare the predicted fares with the actual taxi fares in column 10, and evaluate the accuracy of my model.

And I will use NET Core to build my app.

NET Core is really cool. It’s the multi-platform version of the NET framework and it runs flawlessly on Windows, OS/X, and Linux.

I’m using the 3.0 preview on my Mac right now and haven’t touched my Windows 10 virtual machine in days.

Here’s how to set up a new console project in NET Core:

$ dotnet new console -o PricePrediction

$ cd PricePrediction

Next, I need to install the ML.NET NuGet package:

$ dotnet add package Microsoft.ML

Now I’m ready to add some classes. I’ll need one to hold a taxi trip, and one to hold my model’s predictions.

I will modify the Program.cs file like this:

The TaxiTrip class holds one single taxi trip. Note how each field is adorned with a Column attribute that tell the CSV data loading code which column to import data from.

I’m also declaring a TaxiTripFarePrediction class which will hold a single fare prediction.

Now I’m going to load the training data in memory:

This code sets up a TextLoader to load the CSV data into memory. Note that all column data types are what you’d expect, except RateCode and PaymentType. These columns hold numeric values, but I’m loading then as string fields.

The reason I’m doing this is because RateCode is an enumeration with the following values:

1 = standard

2 = JFK

3 = Newark

4 = Nassau

5 = negotiated

6 = group

And PaymentType is defined as follows:

1 = Credit card

2 = Cash

3 = No charge

4 = Dispute

5 = Unknown

6 = Voided trip

These actual numbers don’t mean anything in this context. And I certainly don’t want the machine learning model to start believing that a trip to Newark is three times as important as a standard fare.

So converting these values to strings is a perfect trick to show the model that RateCode and PaymentType are just labels, and the underlying numbers don’t mean anything.

With the TextLoader all set up, a single call to Load() is sufficient to load the entire data file in memory.

I only have a single data file, so I am calling TrainTestSplit() to set up a training partition with 80% of the data and a test partition with the remaining 20% of the data.

You often see this 80/20 split in data science, it’s a very common approach to train and test a model.

Now I’m ready to start building the machine learning model:

Machine learning models in ML.NET are built with pipelines, which are sequences of data-loading, transformation, and learning components.

My pipeline has the following components:

CopyColumns which copies the FareAmount column to a new column called Label. This Label column holds the actual taxi fare that the model has to predict.

which copies the FareAmount column to a new column called Label. This Label column holds the actual taxi fare that the model has to predict. A group of three OneHotEncodings to perform one hot encoding on the three columns that contains enumerative data: VendorId, RateCode, and PaymentType. This is a required step because machine learning models cannot handle enumerative data directly.

to perform one hot encoding on the three columns that contains enumerative data: VendorId, RateCode, and PaymentType. This is a required step because machine learning models cannot handle enumerative data directly. Concatenate which combines all input data columns into a single column called Features. This is a required step because ML.NET can only train on a single input column.

which combines all input data columns into a single column called Features. This is a required step because ML.NET can only train on a single input column. AppendCacheCheckpoint which caches all data in memory to speed up the training process.

which caches all data in memory to speed up the training process. A final FastTree regression learner which will train the model to make accurate predictions.

The FastTreeRegressionTrainer is a very nice training algorithm that uses gradient boosting, a machine learning technique for regression problems.

A gradient boosting algorithm builds up a collection of weak regression models. It starts out with a weak model that tries to predict the taxi fare. Then it adds a second model that attempts to correct the error in the first model. And then it adds a third model, and so on.

The result is a fairly strong prediction model that is actually just an ensemble of weaker prediction models stacked on top of each other.

With the pipeline fully assembled, I can train the model on the training partition with a call to Fit().

I now have a fully- trained model. So now I need to load some validation data, predict the taxi fare for each trip, and calculate the accuracy of my model:

This code calls Transform(…) to set up predictions for every single taxi trip in the test partition. The Evaluate(…) method then compares these predictions to the actual taxi fares and automatically calculates three very handy metrics for me:

Rms : this is the root mean square error or RMSE value. It’s the go-to metric in the field of machine learning to evaluate models and rate their accuracy. RMSE represents the length of a vector in n-dimensional space, made up of the error in each individual prediction.

: this is the root mean square error or RMSE value. It’s the go-to metric in the field of machine learning to evaluate models and rate their accuracy. RMSE represents the length of a vector in n-dimensional space, made up of the error in each individual prediction. L1 : this is the mean absolute prediction error, expressed in dollars.

: this is the mean absolute prediction error, expressed in dollars. L2: this is the mean square prediction error, or MSE value. Note that RMSE and MSE are related: RMSE is just the square root of MSE.

To wrap up, let’s use the model to make a prediction.

I’m going to take a standard taxi trip for 19 minutes. I’ll be the only passenger and I’ll pay by credit card.

Here’s how to make the prediction:

I use the CreatePredictionEngine<…>(…) method to set up a prediction engine. The two type arguments are the input data class and the class to hold the prediction. And once my prediction engine is set up, I can simply call Predict(…) to make a single prediction.

I know that this trip is supposed to cost $15.50. How accurate will the model prediction be?

Here’s the code running in the Visual Studio Code debugger on my Mac: