Reading from Database
Now that we have data in our database let's read from it. This code will be similar than what we used to write to the database by using the SqlConnection
class again. In fact, the only differences is the query we send to it and how we read in the data.
We do need a variable to add each row to, though, so we can create a new List
of WineData
objects.
var data = new List<WineData>();
Within the SqlConnection
we can create a select statement that will return all of the columns and execute it with the ExecuteReader
function. This returns a SqlDataReader
object and we can use that to extract out the data.
In a while
loop, which checks that the reader can read the next row, use the List
variable created earlier to add a new instance of the WineData
object to it and we can map from the reader to the object using the reader.GetValue
method. The GetValue
parameter will be the column position and then we'll do a ToString
on it. Note that we need the Parse
method from above again here to parse the strings into a float
.
using (var conn = new SqlConnection(_sqlConectionString))
{
conn.Open();
var selectCmd = "SELECT * FROM dbo.WineData";
var sqlCommand = new SqlCommand(selectCmd, conn);
var reader = sqlCommand.ExecuteReader();
while (reader.Read())
{
data.Add(new WineData
{
Type = reader.GetValue(0).ToString(),
FixedAcidity = Parse(reader.GetValue(1).ToString()),
VolatileAcidity = Parse(reader.GetValue(2).ToString()),
CitricAcid = Parse(reader.GetValue(3).ToString()),
ResidualSugar = Parse(reader.GetValue(4).ToString()),
Chlorides = Parse(reader.GetValue(5).ToString()),
FreeSulfurDioxide = Parse(reader.GetValue(6).ToString()),
TotalSulfurDioxide = Parse(reader.GetValue(7).ToString()),
Density = Parse(reader.GetValue(8).ToString()),
Ph = Parse(reader.GetValue(9).ToString()),
Sulphates = Parse(reader.GetValue(10).ToString()),
Alcohol = Parse(reader.GetValue(11).ToString()),
Quality = Parse(reader.GetValue(12).ToString())
});
}
}
Creating the Model
Now that we have our data from the database, let's use it to create an ML.NET model.
First thing, though, let's create an instance of the MLContext
.
var context = new MLContext();
We can use the LoadFromEnumerable
helper method to load the IEnumerable
data that we have into the IDataView
that ML.NET uses. In previous versions of ML.NET this used to be called ReadFromEnumerable
.
var mlData = context.Data.LoadFromEnumerable(data);
Now that we have the IDataView
we can use that to split the data into a training set and test set. In previous versions of ML.NET this returned a tuple and it could be deconstructed into two variables (var (trainSet, testSet) = ...
), but now it returns an object.
var testTrainSplit = context.Regression.TrainTestSplit(mlData);
With the data set up, we can create the pipeline. The two main things to do here is to set up the Type
feature, which denotes if the wine is red or white, as one hot encoded. Then we concatenate each of the other features into a feature array. We'll use the FastTree
trainer and since our label column isn't named "Label", we set the labelColumnName
parameter to the name of the label we want to predict, which is "Quality".
var pipeline = context.Transforms.Categorical.OneHotEncoding("TypeOneHot", "Type")
.Append(context.Transforms.Concatenate("Features", "FixedAcidity", "VolatileAcidity", "CitricAcid",
"ResidualSugar", "Chlorides", "FreeSulfurDioxide", "TotalSulfurDioxide", "Density", "Ph", "Sulphates", "Alcohol"))
.Append(context.Regression.Trainers.FastTree(labelColumnName: "Quality"));
With the pipeline created, we can now call the Fit
method on it with our training data.
var model = pipeline.Fit(testTrainSplit.TrainSet);
Save Model
With our new model, let's save it to Azure Blob Storage so we can retrieve it to build an API around the model.
To start, we'll use the connection string that we put in the config earlier. We then pass that into the Parse
method of the CloudStorageAccount
class.
var storageAccount = CloudStorageAccount.Parse(configuration["blobConnectionString"]);
With a reference to the storage account, we can now use that to create a client and use the client to create a reference to the container that we will call "models". This container will need to be created in the storage account, as well.
var client = storageAccount.CreateCloudBlobClient();
var container = client.GetContainerReference("models");
With the container reference, we can create a blob reference to a file, which we created earlier as a field.
var blob = container.GetBlockBlobReference(fileName);
To save the model to a file, we can create a file stream using File.Create
and inside the stream we can call the context.Model.Save
method.
using (var stream = File.Create(fileName))
{
context.Model.Save(model, stream);
}
And to upload the file to blob storage, just call the UploadFromFileAsync
method. Note that this method is async, so we need to mark the containing method as async
and add await
in front of this method.
await blob.UploadFromFileAsync(fileName);
After running this, there should now be a file added to blob storage.