using System; using System.Collections.Generic; using System.IO; using System.IO.Compression; using System.Linq; using System.Net; using System.Net.Http; using System.Threading; using System.Threading.Tasks; using Microsoft.ML; using Microsoft.ML.Data; using Microsoft.ML.Trainers; using Microsoft.ML.Vision; namespace Samples.Dynamic { public class LearningRateSchedulingCifarResnetTransferLearning { public static void Example() { // Set the path for input images. string assetsRelativePath = @"../../../assets"; string assetsPath = GetAbsolutePath(assetsRelativePath); string imagesDownloadFolderPath = Path.Combine(assetsPath, "inputs", "images"); // Download Cifar Dataset and set train and test dataset // paths. string finalImagesFolderName = DownloadImageSet( imagesDownloadFolderPath); string finalImagesFolderNameTrain = "cifar\\train"; string fullImagesetFolderPathTrain = Path.Combine( imagesDownloadFolderPath, finalImagesFolderNameTrain); string finalImagesFolderNameTest = "cifar\\test"; string fullImagesetFolderPathTest = Path.Combine( imagesDownloadFolderPath, finalImagesFolderNameTest); MLContext mlContext = new MLContext(seed: 1); //Load all the original train images info. IEnumerable<ImageData> train_images = LoadImagesFromDirectory( folder: fullImagesetFolderPathTrain, useFolderNameAsLabel: true); IDataView trainDataset = mlContext.Data. LoadFromEnumerable(train_images); // Apply transforms to the input dataset: // MapValueToKey : map 'string' type labels to keys // LoadImages : load raw images to "Image" column trainDataset = mlContext.Transforms.Conversion .MapValueToKey("Label", keyOrdinality: Microsoft.ML.Transforms .ValueToKeyMappingEstimator.KeyOrdinality.ByValue) .Append(mlContext.Transforms.LoadRawImageBytes("Image", fullImagesetFolderPathTrain, "ImagePath")) .Fit(trainDataset) .Transform(trainDataset); // Load all the original test images info and apply // the same transforms as above. IEnumerable<ImageData> test_images = LoadImagesFromDirectory( folder: fullImagesetFolderPathTest, useFolderNameAsLabel: true); IDataView testDataset = mlContext.Data. LoadFromEnumerable(test_images); testDataset = mlContext.Transforms.Conversion .MapValueToKey("Label", keyOrdinality: Microsoft.ML.Transforms .ValueToKeyMappingEstimator.KeyOrdinality.ByValue) .Append(mlContext.Transforms.LoadRawImageBytes("Image", fullImagesetFolderPathTest, "ImagePath")) .Fit(testDataset) .Transform(testDataset); // Set the options for ImageClassification. var options = new ImageClassificationTrainer.Options() { FeatureColumnName = "Image", LabelColumnName = "Label", // Just by changing/selecting InceptionV3/MobilenetV2 // here instead of // ResnetV2101 you can try a different architecture/ // pre-trained model. Arch = ImageClassificationTrainer.Architecture.ResnetV2101, Epoch = 182, BatchSize = 128, LearningRate = 0.01f, MetricsCallback = (metrics) => Console.WriteLine(metrics), ValidationSet = testDataset, ReuseValidationSetBottleneckCachedValues = false, ReuseTrainSetBottleneckCachedValues = false, // Use linear scaling rule and Learning rate decay as an option // This is known to do well for Cifar dataset and Resnet models // You can also try other types of Learning rate scheduling // methods available in LearningRateScheduler.cs LearningRateScheduler = new LsrDecay() }; // Create the ImageClassification pipeline. var pipeline = mlContext.MulticlassClassification.Trainers. ImageClassification(options) .Append(mlContext.Transforms.Conversion.MapKeyToValue( outputColumnName: "PredictedLabel", inputColumnName: "PredictedLabel")); Console.WriteLine("*** Training the image classification model " + "with DNN Transfer Learning on top of the selected " + "pre-trained model/architecture ***"); // Train the model. // This involves calculating the bottleneck values, and then // training the final layer. Sample output is: // Phase: Bottleneck Computation, Dataset used: Train, Image Index: 1 // Phase: Bottleneck Computation, Dataset used: Train, Image Index: 2 // ... // Phase: Training, Dataset used: Train, Batch Processed Count: 18, Learning Rate: 0.01 Epoch: 0, Accuracy: 0.9166667,Cross-Entropy: 0.4866541 // ... var trainedModel = pipeline.Fit(trainDataset); Console.WriteLine("Training with transfer learning finished."); // Save the trained model. mlContext.Model.Save(trainedModel, testDataset.Schema, "model.zip"); // Load the trained and saved model for prediction. ITransformer loadedModel; DataViewSchema schema; using (var file = File.OpenRead("model.zip")) loadedModel = mlContext.Model.Load(file, out schema); // Evaluate the model on the test dataset. // Sample output: // Making bulk predictions and evaluating model's quality... // Micro - accuracy: ...,macro - accuracy = ... EvaluateModel(mlContext, testDataset, loadedModel); // Predict image class using a single in-memory image. TrySinglePrediction(fullImagesetFolderPathTest, mlContext, loadedModel); Console.WriteLine("Prediction on a single image finished."); Console.WriteLine("Press any key to finish"); Console.ReadKey(); } // Predict on a single image. private static void TrySinglePrediction(string imagesForPredictions, MLContext mlContext, ITransformer trainedModel) { // Create prediction function to try one prediction. var predictionEngine = mlContext.Model .CreatePredictionEngine<InMemoryImageData, ImagePrediction>(trainedModel); // Load test images. IEnumerable<InMemoryImageData> testImages = LoadInMemoryImagesFromDirectory(imagesForPredictions, false); // Create an in-memory image object from the first image in the test data. InMemoryImageData imageToPredict = new InMemoryImageData { Image = testImages.First().Image }; // Predict on the single image. var prediction = predictionEngine.Predict(imageToPredict); Console.WriteLine($"Scores : [{string.Join(",", prediction.Score)}], " + $"Predicted Label : {prediction.PredictedLabel}"); } // Evaluate the trained model on the passed test dataset. private static void EvaluateModel(MLContext mlContext, IDataView testDataset, ITransformer trainedModel) { Console.WriteLine("Making bulk predictions and evaluating model's " + "quality..."); // Evaluate the model on the test data and get the evaluation metrics. IDataView predictions = trainedModel.Transform(testDataset); var metrics = mlContext.MulticlassClassification.Evaluate(predictions); Console.WriteLine($"Micro-accuracy: {metrics.MicroAccuracy}," + $"macro-accuracy = {metrics.MacroAccuracy}"); Console.WriteLine("Predicting and Evaluation complete."); } //Load the Image Data from input directory. public static IEnumerable<ImageData> LoadImagesFromDirectory(string folder, bool useFolderNameAsLabel = true) { var files = Directory.GetFiles(folder, "*", searchOption: SearchOption.AllDirectories); foreach (var file in files) { if (Path.GetExtension(file) != ".jpg" && Path.GetExtension(file) != ".JPEG" && Path.GetExtension(file) != ".png") continue; var label = Path.GetFileName(file); if (useFolderNameAsLabel) label = Directory.GetParent(file).Name; else { for (int index = 0; index < label.Length; index++) { if (!char.IsLetter(label[index])) { label = label.Substring(0, index); break; } } } yield return new ImageData() { ImagePath = file, Label = label }; } } // Load In memory raw images from directory. public static IEnumerable<InMemoryImageData> LoadInMemoryImagesFromDirectory(string folder, bool useFolderNameAsLabel = true) { var files = Directory.GetFiles(folder, "*", searchOption: SearchOption.AllDirectories); foreach (var file in files) { if (Path.GetExtension(file) != ".jpg" && Path.GetExtension(file) != ".JPEG" && Path.GetExtension(file) != ".png") continue; var label = Path.GetFileName(file); if (useFolderNameAsLabel) label = Directory.GetParent(file).Name; else { for (int index = 0; index < label.Length; index++) { if (!char.IsLetter(label[index])) { label = label.Substring(0, index); break; } } } yield return new InMemoryImageData() { Image = File.ReadAllBytes(file), Label = label }; } } // Download and unzip the image dataset. public static string DownloadImageSet(string imagesDownloadFolder) { // get a set of images to teach the network about the new classes // CIFAR dataset ( 50000 train images and 10000 test images ) string fileName = "cifar10.zip"; // https://github.com/YoongiKim/CIFAR-10-images string url = $"https://github.com/YoongiKim/CIFAR-10-images/archive/refs/heads/master.zip"; Download(url, imagesDownloadFolder, fileName).Wait(); UnZip(Path.Combine(imagesDownloadFolder, fileName), imagesDownloadFolder); return Path.GetFileNameWithoutExtension(fileName); } // Download file to destination directory from input URL. public static async Task<bool> Download(string url, string destDir, string destFileName) { if (destFileName == null) destFileName = url.Split(Path.DirectorySeparatorChar).Last(); Directory.CreateDirectory(destDir); string relativeFilePath = Path.Combine(destDir, destFileName); if (File.Exists(relativeFilePath)) { Console.WriteLine($"{relativeFilePath} already exists."); return false; } Console.WriteLine($"Downloading {relativeFilePath}"); using (HttpClient client = new HttpClient()) { var response = await client.GetStreamAsync(new Uri($"{url}")).ConfigureAwait(false); using (var fs = new FileStream(relativeFilePath, FileMode.CreateNew)) { await response.CopyToAsync(fs); } } Console.WriteLine(""); Console.WriteLine($"Downloaded {relativeFilePath}"); return true; } // Unzip the file to destination folder. public static void UnZip(String gzArchiveName, String destFolder) { var flag = gzArchiveName.Split(Path.DirectorySeparatorChar) .Last() .Split('.') .First() + ".bin"; if (File.Exists(Path.Combine(destFolder, flag))) return; Console.WriteLine($"Extracting."); var task = Task.Run(() => { ZipFile.ExtractToDirectory(gzArchiveName, destFolder); }); while (!task.IsCompleted) { Thread.Sleep(200); Console.Write("."); } File.Create(Path.Combine(destFolder, flag)); Console.WriteLine(""); Console.WriteLine("Extracting is completed."); } // Get absolute path from relative path. public static string GetAbsolutePath(string relativePath) { FileInfo _dataRoot = new FileInfo(typeof( LearningRateSchedulingCifarResnetTransferLearning).Assembly.Location); string assemblyFolderPath = _dataRoot.Directory.FullName; string fullPath = Path.Combine(assemblyFolderPath, relativePath); return fullPath; } // InMemoryImageData class holding the raw image byte array and label. public class InMemoryImageData { [LoadColumn(0)] public byte[] Image; [LoadColumn(1)] public string Label; } // ImageData class holding the imagepath and label. public class ImageData { [LoadColumn(0)] public string ImagePath; [LoadColumn(1)] public string Label; } // ImagePrediction class holding the score and predicted label metrics. public class ImagePrediction { [ColumnName("Score")] public float[] Score; [ColumnName("PredictedLabel")] public string PredictedLabel; } } } |