Unfortunately, the world of machine learning belongs to python.
It has long been entrenched as a working language for Data Silence, but Microsoft decided to argue and presented its own tool that can be easily integrated with the ecosystem that the whole world uses now. This is how ML.NET, a cross-platform and open source machine learning system for .NET developers, was born.
In this article, I want to show that using ml.net is no more difficult than the rest of the options that are, on a really working example, the link to which I will leave below. This is a channel in a telegram that automatically picks up data, classifies it (this is what we will consider) and posts. Who cares, welcome.
Formulation of the problem
As a teenager, I really wanted to have a cool bot where I can look at girls, which will not be packed with advertisements to the eyeballs, but just a photo and that's it. So, when I had free time, the stars and desire came together, I immediately began to solve this problem.
Data collection
To begin with, I bought an upload of Twitter data for the tag of interest to me, which the service gives in csv format (several different files that differ: the tweet itself, media, links). Having selected the file I need, we quickly write a class to parse the data, filter out duplicates. As a result, only references to images that will participate in training are left in memory. This is good, but all the same, images need to be labeled, that is, divided into categories. In my case, I chose: boys, girls, trash and other (at first I chose default, but when I went from strings to Enum, I had to change the name of the category). All these photos, I uploaded, meticulously divided into daddies that reflected the photo's tag, so it's time for the most interesting thing - the code.
Model training
Image classification algorithms are used to determine what is shown in the photo.
Image classification
Image classification is a computer vision problem. Image classification takes an image as input and classifies it into a prescribed class.
More specifically, I'll be using deep learning.
Deep learning
, , , , . TensorFlow Inception , ImageNet.
" ", ( 2000 , 2 , , +- ).
, , , , , . 4 500 .
. , model nuget :
using Microsoft.ML;
using Microsoft.ML.Data;
, :
private readonly string _inceptionTensorFlowModel; // Inception
private MLContext mlContext;
private ITransformer model;
private DataViewSchema schema;
private string modelName = "model.zip"; //
private string _setsPath = @"C:\datasets"; // ,
public Model(string inceptionTensorFlowModel)
{
mlContext = new MLContext();
_inceptionTensorFlowModel = inceptionTensorFlowModel;
}
MLContext - .NET. "" , , DbContext EntityFramework.
ITransformer - , , , .
DataViewSchema - .
, "", , .
public class ImageData
{
[LoadColumn(0)]
public string ImagePath;
[LoadColumn(1)]
public string Label;
//, ,
public static (IEnumerable<ImageData> train, IEnumerable<ImageData> test) ReadData(string pathToFolder)
{
List<ImageData> list = new List<ImageData>();
var directories = Directory.EnumerateDirectories(pathToFolder);
foreach (var dir in directories)
{
if (!dir.Contains("girls") && !dir.Contains("boys") && !dir.Contains("trash") && !dir.Contains("other"))
continue;
var label = dir.Split(@"\").Last();
foreach (var file in Directory.GetFiles(dir))
{
list.Add(new ImageData()
{
ImagePath = file,
Label = label
});
}
}
list = list.Shuffle().ToList();
return GetSets(list);
}
//
public static (IEnumerable<ImageData> train, IEnumerable<ImageData> test) GetSets(IEnumerable<ImageData> data)
{
var trainCount = data.Count() / 100 * 99;
var train = data.Take(trainCount);
var test = data.Skip(trainCount);
return (train, test);
}
}
public class ImagePrediction : ImageData
{
[ColumnName("Score")]
public float[] Score;
public string PredictedLabelValue;
}
IEnumerable :
,
public static IEnumerable<T> Shuffle<T>(this IEnumerable<T> source)
{
return source.Shuffle(new Random());
}
public static IEnumerable<T> Shuffle<T>(this IEnumerable<T> source, Random rng)
{
if (source == null) throw new ArgumentNullException("source");
if (rng == null) throw new ArgumentNullException("rng");
return source.ShuffleIterator(rng);
}
private static IEnumerable<T> ShuffleIterator<T>(
this IEnumerable<T> source, Random rng)
{
var buffer = source.ToList();
for (int i = 0; i < buffer.Count; i++)
{
int j = rng.Next(i, buffer.Count);
yield return buffer[j];
buffer[j] = buffer[i];
}
}
, :
private struct InceptionSettings
{
public const int ImageHeight = 224;
public const int ImageWidth = 224;
public const float Mean = 117;
public const float Scale = 1;
public const bool ChannelsLast = true;
}
, .
:
private double TrainModel()
{
IEstimator<ITransformer> pipeline = mlContext.Transforms.LoadImages(outputColumnName: "input", imageFolder: "", inputColumnName: nameof(ImageData.ImagePath))
.Append(mlContext.Transforms.ResizeImages(outputColumnName: "input", imageWidth: InceptionSettings.ImageWidth, imageHeight: InceptionSettings.ImageHeight, inputColumnName: "input"))
.Append(mlContext.Transforms.ExtractPixels(outputColumnName: "input", interleavePixelColors: InceptionSettings.ChannelsLast, offsetImage: InceptionSettings.Mean))
.Append(mlContext.Model.LoadTensorFlowModel(_inceptionTensorFlowModel).
ScoreTensorFlowModel(outputColumnNames: new[] { "softmax2_pre_activation" }, inputColumnNames: new[] { "input" }, addBatchDimensionInput: true))
.Append(mlContext.Transforms.Conversion.MapValueToKey(outputColumnName: "LabelKey", inputColumnName: "Label"))
.Append(mlContext.MulticlassClassification.Trainers.LbfgsMaximumEntropy(labelColumnName: "LabelKey", featureColumnName: "softmax2_pre_activation"))
.Append(mlContext.Transforms.Conversion.MapKeyToValue("PredictedLabelValue", "PredictedLabel"))
.AppendCacheCheckpoint(mlContext);
var loadImages = ImageData.ReadData(_setsPath);
IDataView trainingData = mlContext.Data.LoadFromEnumerable<ImageData>(loadImages.train);
ITransformer model = pipeline.Fit(trainingData);
IDataView testData = mlContext.Data.LoadFromEnumerable<ImageData>(loadImages.test);
IDataView predictions = model.Transform(testData);
List<ImagePrediction> imagePredictionData = mlContext.Data.CreateEnumerable<ImagePrediction>(predictions, true).ToList();
MulticlassClassificationMetrics metrics =
mlContext.MulticlassClassification.Evaluate(predictions,
labelColumnName: "LabelKey",
predictedLabelColumnName: "PredictedLabel");
schema = trainingData.Schema;
return metrics.LogLoss;
}
:
IEstimator<ITransformer> pipeline = mlContext.Transforms.LoadImages(outputColumnName: "input", imageFolder: "", inputColumnName: nameof(ImageData.ImagePath))
.Append(mlContext.Transforms.ResizeImages(outputColumnName: "input", imageWidth: InceptionSettings.ImageWidth, imageHeight: InceptionSettings.ImageHeight, inputColumnName: "input"))
.Append(mlContext.Transforms.ExtractPixels(outputColumnName: "input", interleavePixelColors: InceptionSettings.ChannelsLast, offsetImage: InceptionSettings.Mean))
. , :
.Append(mlContext.Model.LoadTensorFlowModel(_inceptionTensorFlowModel).
ScoreTensorFlowModel(outputColumnNames: new[] { "softmax2_pre_activation" }, inputColumnNames: new[] { "input" }, addBatchDimensionInput: true))
. , :
.Append(mlContext.Transforms.Conversion.MapValueToKey(outputColumnName: "LabelKey", inputColumnName: "Label"))
ml.net, , .
:
.Append(mlContext.MulticlassClassification.Trainers.LbfgsMaximumEntropy(labelColumnName: "LabelKey", featureColumnName: "softmax2_pre_activation"))
:
.Append(mlContext.Transforms.Conversion.MapKeyToValue("PredictedLabelValue", "PredictedLabel"))
.AppendCacheCheckpoint(mlContext);
:
var loadImages = ImageData.ReadData(_setsPath);
IDataView trainingData = mlContext.Data.LoadFromEnumerable<ImageData>(loadImages.train);
model = pipeline.Fit(trainingData);
, .
IDataView testData = mlContext.Data.LoadFromEnumerable<ImageData>(loadImages.test);
IDataView predictions = model.Transform(testData);
List<ImagePrediction> imagePredictionData = mlContext.Data.CreateEnumerable<ImagePrediction>(predictions, true).ToList();
MulticlassClassificationMetrics metrics =
mlContext.MulticlassClassification.Evaluate(predictions,
labelColumnName: "LabelKey",
predictedLabelColumnName: "PredictedLabel");
. . , "" .
schema = trainingData.Schema;
return metrics.LogLoss;
LogLoss( ).
, .
, , :
public void SaveModel() => mlContext.Model.Save(model, schema, Path.Combine(_setsPath, modelName));
, , :
public void FitModel()
{
var LogLoss = TrainModel();
Console.WriteLine($"LogLoss is {LogLoss}");
SaveModel();
}
, , , , , .
, , , .
:
private PredictionEngine<ImageData, ImagePrediction> predictor;
, (+ ):
public ImagePrediction ClassifySingleImage(string filePath)
{
if (model == null)
LoadModel();
if (predictor == null)
predictor = mlContext.Model.CreatePredictionEngine<ImageData, ImagePrediction>(model);
var imageData = new ImageData()
{
ImagePath = filePath
};
return predictor.Predict(imageData);
}
public void LoadModel() =>
model = mlContext.Model.Load(Path.Combine(_setsPath, modelName), out schema);
, .
, :
static void Main(string[] args)
{
Console.ForegroundColor = ConsoleColor.White;
Stopwatch s = new Stopwatch();
s.Start();
Model model = new Model(@"C:\tensorflow_inception_graph.pb");
model.FitModel();
Console.WriteLine($"##### Model train ended for {s.Elapsed.Minutes}:{s.Elapsed.Seconds} #####");
s.Restart();
var res1 = model.ClassifySingleImage(@"C:\EugRqKFXUAYMTWz.jpg");
Console.WriteLine($" > It's trash. Classification result is {res1.PredictedLabelValue} with score: {res1.Score.Max()}");
Console.WriteLine($"##### Ended for {s.Elapsed.Minutes}:{s.Elapsed.Seconds} #####");
s.Restart();
var res2 = model.ClassifySingleImage(@"C:\EvpmOjIXcAMgj5r.jpg");
Console.WriteLine($" > It's girl. Classification result is {res2.PredictedLabelValue} with score: {res1.Score.Max()}");
Console.WriteLine($"##### Ended for {s.Elapsed.Minutes}:{s.Elapsed.Seconds} #####");
}
:
Despite the rather weak metrics (I still used 20 images for tests): 0.55, but the model coped with its tasks perfectly. This is the model I use for my nsfw bot , which receives data from Twitter, and then classifies and posts it.
So it's not difficult enough to train the model and add to your project, the main desire is to figure it out. And you should never stop learning new things.