ML.NET Series: Klasifikasi Gambar dengan Transfer Learning (Onnx dan Tensorflow)

Hi rekan-rekan Makers,

Bertemu lagi di seri ML.NET, jika rekan-rekan ingin melakukan klasifikasi gambar, deteksi objek, mengubah teks dari audio atau sebaliknya biasanya kita gunakan deep learning, yaitu subset dari Machine Learning yang secara khusus menggunakan algoritma neural network multi-layer untuk melakukan training dengan data yang sangat besar, jaringan ini dapat memapping antara input dan output dengan sangat baik. Nah untuk membuat klasifikasi gambar bisa menempuh dua cara yaitu: mentraining model semua dari awal dimana rekan-rekan membutuhkan dataset yang besar, compute power yang besar (gpu, ram, dsb), dan extra kesabaran untuk mengatur ribuan hyper-parameter untuk setiap layernya, yakin ingin menempuh jalan ini ? hehe.. nah alternatif kedua adalah menggunakan transfer learning, yaitu menggunakan pre-trained model (model yang sudah ditraining sebelumnya) lalu kita gunakan kembali untuk mengekstrasi feature dan training data baru. biasanya kita hanya mengubah beberapa layer saja, biasanya mengubah/menambah layer terakhir (penultimate) dimana layer ini spesifik untuk mengenali bentuk tertentu, sedangkan layer-layer awal akan mengekstrak feature yang lebih umum seperti garis tepi. Kelebihan dari transfer learning adalah waktu training yang lebih cepat, tidak membutuhkan data training yang begitu besar (banyak). 

Pada artikel ini kita akan gunakan 2 model yang menggunakan 2 format berbeda yaitu pb (model yang dibuat dengan tensorflow) dan onnx (format ML standard yang menjembatani beberapa framework ML agar modelnya dapat digunakan di berbagai platform), model ini ditraining dengan 1.2 juta gambar dari dataset ImageNet dan memiliki 1000 class (objek gambar) berbeda yang dapat dikenali. Dengan ML.Net kita akan tambah 1 layer di akhir untuk mengenali kategori gambar yang lebih umum seperti makanan, peralatan dan mainan menggunakan multi-class classification (LbfgsMaximumEntropy).

Untuk rekan-rekan yang belum mengikuti seri ini dari awal silakan baca dari artikel ini. Adapun beberapa hal yang sebelumnya dibahas antara lain regression, binary classification, multiclass classification, clustering, time-series, recommendation dengan matrix factorization, dan recommendation dengan field aware factorization.

Nah kita memiliki beberapa contoh gambar yang akan kita gunakan sebagai dataset yang akan kita gunakan memiliki attribut antara lain:

  1. file path, nama file gambar. Attribut ini kita gunakan sebagai features
  2. category, kategori gambar : mainan, makanan, dan peralatan. Attribut ini kita gunakan sebagai label

selanjutnya, rekan-rekan memastikan sudah menginstall .NET Core / .NET Framework versi terakhir, dalam artikel ini saya menggunakan .NET Core versi 2.2. Jika belum silakan download disini. Jika belum memiliki IDE silakan download visual studio and vs code.

Langkah pertama silakan buat project baru dengan tipe console application. Kalau dengan dengan .Net Core bisa menggunakan CLI, silakan buka terminal atau command line (cmd.exe). Lalu ketik:

mkdir TransferLearningImageClassification

Lalu masuk ke folder yang baru dibuat dengan mengetik:

cd TransferLearningImageClassification

Selanjutnya buat aplikasi console dengan mengetik:

dotnet new console

Kita perlu menambahkan nuget package ML.NET dan algoritma Recommendation dengan mengetik:

dotnet add package Microsoft.ML
dotnet add package Microsoft.ML.ImageAnalytics
dotnet add package Microsoft.ML.OnnxTransformer
dotnet add package Microsoft.ML.Scoring
dotnet add package Microsoft.ML.TensorFlow

Kemudian buatlah folder dengan nama “Data” dengan mengetik:

mkdir Data

Lalu download semua file dari link ini, dan masukan ke folder “Data” tersebut.

Kemudian buka dengan visual studio code folder “TransferLearningImageClassification”

pada Program.cs masukan namespace ML.NET dengan mengetik pada bagian atas:

using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using Microsoft.ML;
using static Microsoft.ML.Transforms.Image.ImagePixelExtractingEstimator;

Selanjutnya buatlah class dengan nama “ImageData.cs” isikan kode berikut:

using Microsoft.ML.Data;
using System;
using System.Collections.Generic;
using System.Text;

namespace TransferLearningImageClassification
{
public class ImageData
{
[LoadColumn(0)]public string ImagePath;

[LoadColumn(1)]public string Label;
}
}

Class diatas akan digunakan untuk memuat data training, mapping antara label dan gambar. Lalu buatlah class untuk prediksi dengan nama “ImagePrediction.cs”:

using System;
using System.Collections.Generic;
using System.Text;

namespace TransferLearningImageClassification
{
public class ImagePrediction : ImageData
{
public float[] Score;

public string PredictedLabelValue;
}
}

Lalu kembali ke “Program.cs” dan masukan kode berikut diatas void main:

static readonly string _assetsPath = GetAbsolutePath("../../../Data/");
static readonly string _trainTagsTsv = Path.Combine(_assetsPath, "inputs-train", "data", "tags.tsv");
static readonly string _predictImageListTsv = Path.Combine(_assetsPath, "inputs-predict", "data", "image_list.tsv");
static readonly string _trainImagesFolder = Path.Combine(_assetsPath, "inputs-train", "data");
static readonly string _predictImagesFolder = Path.Combine(_assetsPath, "inputs-predict", "data");
static readonly string _predictSingleImage = Path.Combine(_assetsPath, "inputs-predict-single", "data", "toaster3.jpg");
static readonly string _modelPath = Path.Combine(_assetsPath, "inputs-train", "inception", "mobilenetv2-1.0.onnx");
static readonly string _modelPathTF = Path.Combine(_assetsPath, "inputs-train", "inception", "tensorflow_inception_graph.pb");

static readonly string _outputImageClassifierZip = Path.Combine(_assetsPath, "outputs", "imageClassifier.zip");
private static string LabelTokey = nameof(LabelTokey);
private static string PredictedLabelValue = nameof(PredictedLabelValue);

Ini adalah deklarasi variabel-variabel yang akan kita gunakan untuk melakukan training dan prediksi. Perhatikan kita punya 2 path ke 2 jenis model berbeda yaitu _modelPathTF (tensorflow model) dan _modelPath (onnx model). Selanjutnya masukan kode berikut dalam void main:

MLContext mlContext = new MLContext(seed: 1);
//use onnx model
var model = ReuseAndTuneModelOnnx(mlContext,_trainTagsTsv,_trainImagesFolder,_modelPath,_outputImageClassifierZip);
//use tensorflow model
//var model = ReuseAndTuneModelTensorFlow(mlContext, _trainTagsTsv, _trainImagesFolder, _modelPathTF, _outputImageClassifierZip);
ClassifyImages(mlContext, _predictImageListTsv, _predictImagesFolder, _outputImageClassifierZip, model);
ClassifySingleImage(mlContext, _predictSingleImage, _outputImageClassifierZip, model);

Jika rekan-rekan ingin menggunakan onnx gunakan kode diatas, jika ingin menggunakan model tensorflow tinggal beri komentar pada baris dibawah “use onnx model” dan un-comment baris dibawah “use tensorflow model”. Kode ini membuat mlcontext yang akan digunakan sebagai pipeline dalam melakukan training atau prediksi. Selanjutnya kita perlu memasukan beberapa method. Berikut adalah method untuk melakukan training dan evaluasi dengan tensorflow model.

 public static ITransformer ReuseAndTuneModelTensorFlow(MLContext mlContext, string dataLocation, string imagesFolder, string inputModelLocation, string outputModelLocation)
{
var data = mlContext.Data.LoadFromTextFile<ImageData>(path: dataLocation, hasHeader: false);
var estimator = mlContext.Transforms.Conversion.MapValueToKey(outputColumnName: LabelTokey, inputColumnName: "Label").Append(mlContext.Transforms.LoadImages(outputColumnName: "input", imageFolder: _trainImagesFolder, inputColumnName: nameof(ImageData.ImagePath)))
.Append(mlContext.Transforms.ResizeImages(outputColumnName: "input", imageWidth: ModelSettings.ImageWidth, imageHeight: ModelSettings.ImageHeight, inputColumnName: "input"))
.Append(mlContext.Transforms.ExtractPixels(outputColumnName: "input", interleavePixelColors: ModelSettings.ChannelsLast, offsetImage: ModelSettings.Mean))
.Append(mlContext.Model.LoadTensorFlowModel(inputModelLocation)
.ScoreTensorFlowModel(outputColumnNames: new[] { "softmax2_pre_activation" }, inputColumnNames: new[] { "input" }, addBatchDimensionInput: true))
.Append(mlContext.MulticlassClassification.Trainers.LbfgsMaximumEntropy(labelColumnName: LabelTokey, featureColumnName: "softmax2_pre_activation"))
.Append(mlContext.Transforms.Conversion.MapKeyToValue(PredictedLabelValue, "PredictedLabel"))
.AppendCacheCheckpoint(mlContext);

ITransformer model = estimator.Fit(data);
var predictions = model.Transform(data);
var imageData = mlContext.Data.CreateEnumerable<ImageData>(data, false, true);
var imagePredictionData = mlContext.Data.CreateEnumerable<ImagePrediction>(predictions, false, true);
DisplayResults(imagePredictionData);
var multiclassContext = mlContext.MulticlassClassification;
var metrics = multiclassContext.Evaluate(predictions, labelColumnName: LabelTokey, predictedLabelColumnName: "PredictedLabel");
Console.WriteLine($"LogLoss is: {metrics.LogLoss}");
Console.WriteLine($"PerClassLogLoss is: {String.Join(" , ", metrics.PerClassLogLoss.Select(c => c.ToString()))}");
mlContext.Model.Save(model, data.Schema, outputModelLocation);
return model;

}

Dalam proses diatas kita meload data tsv yang digunakan untuk training, lalu kita buat estimator untuk melakukan transformasi data dan training. Perhatikan langkahnya: 

  1. Konversi kategori (label) menjadi key
  2. Load gambar training dari folder
  3. Resize gambar sesuai input yang diterima model yaitu 224 x 224 (width x height). 
  4. Lalu extract pixel yang ada dalam gambar menjadi array of float. Setup beberapa pengaturan lain seperti mean = 117, channel last = true, scale = 1.  
  5. Lalu load model tensorflow dari pathnya, lalu set kolom “input” sebagai masukan dan “softmax2_pre_activation” sebagai output. Nilai output kita masukan sebagai input pada layer terakhir dengan algoritma LbfgsMaximumEntropy. Untuk melihat struktu model rekan-rekan bisa gunakan netron, lihat disini.
  6. Setelah itu kembalikan label key menjadi kategori dengan method MapKeyToValue. Agar readable oleh user. 
  7. Panggil method Fit untuk memulai training.

Setelah proses training selesai kita dapat mengevaluasi akurasi model dengan cara method transform pada model. Lalu hasil prediksinya dikonversi ke enumerable untuk ditampilkan. Gunakan method  mlContext.MulticlassClassification.Evaluate untuk mengevaluasi akurasi model. Parameter Log Loss berisi perbedaan antara label pada data dan hasil prediksi, semakin mendekati 0 semakin baik, parameter Per Class Log Loss juga semakin mendekati 0 semakin baik. Nah bagaimana dengan method training dan evaluasi dengan Onnx model, berikut adalah kodenya:

public static ITransformer ReuseAndTuneModelOnnx(MLContext mlContext, string dataLocation, string imagesFolder, string inputModelLocation, string outputModelLocation)
{

var data = mlContext.Data.LoadFromTextFile<ImageData>(path: dataLocation, hasHeader: false);
var estimator = mlContext.Transforms.Conversion.MapValueToKey(outputColumnName: LabelTokey, inputColumnName: "Label")
.Append(mlContext.Transforms.LoadImages(outputColumnName: "input", imageFolder: _trainImagesFolder, inputColumnName: nameof(ImageData.ImagePath)))
.Append(mlContext.Transforms.ResizeImages(outputColumnName: "ImageResized", imageWidth: ModelSettings.ImageWidth, imageHeight: ModelSettings.ImageHeight, inputColumnName: "input"))
.Append(mlContext.Transforms.ExtractPixels(outputColumnName:"Red", inputColumnName: "ImageResized",
colorsToExtract: ColorBits.Red, offsetImage: 0.485f * 255, scaleImage: 1 / (0.229f * 255)))
.Append(mlContext.Transforms.ExtractPixels(outputColumnName:"Green", inputColumnName:"ImageResized",
colorsToExtract: ColorBits.Green, offsetImage: 0.456f * 255, scaleImage: 1 / (0.224f * 255)))
.Append(mlContext.Transforms.ExtractPixels(outputColumnName:"Blue",inputColumnName: "ImageResized",
colorsToExtract: ColorBits.Blue, offsetImage: 0.406f * 255, scaleImage: 1 / (0.225f * 255)))
.Append(mlContext.Transforms.Concatenate("data", "Red", "Green", "Blue"))
.Append(mlContext.Transforms.ApplyOnnxModel(modelFile: inputModelLocation,inputColumnName:"data",outputColumnName: "mobilenetv20_output_flatten0_reshape0"))
.Append(mlContext.MulticlassClassification.Trainers.LbfgsMaximumEntropy(labelColumnName: LabelTokey, featureColumnName: "mobilenetv20_output_flatten0_reshape0"))
.Append(mlContext.Transforms.Conversion.MapKeyToValue(PredictedLabelValue, "PredictedLabel"))
.AppendCacheCheckpoint(mlContext);
ITransformer model = estimator.Fit(data);
var predictions = model.Transform(data);
var imageData = mlContext.Data.CreateEnumerable<ImageData>(data, false, true);
var imagePredictionData = mlContext.Data.CreateEnumerable<ImagePrediction>(predictions, false, true);
DisplayResults(imagePredictionData);
var multiclassContext = mlContext.MulticlassClassification;
var metrics = multiclassContext.Evaluate(predictions, labelColumnName: LabelTokey, predictedLabelColumnName: "PredictedLabel");
Console.WriteLine($"LogLoss is: {metrics.LogLoss}");
Console.WriteLine($"PerClassLogLoss is: {String.Join(" , ", metrics.PerClassLogLoss.Select(c => c.ToString()))}");
mlContext.Model.Save(model,data.Schema,outputModelLocation);
return model;

}

Terlihat lebih panjang, ini dikarenakan model ini memiliki input dengan cara mengekstrak 3 channel (red, green, blue) gambar dengan parameter offset dan scale yang berbeda. Lalu hasilnya di gabung menjadi 1 vektor “data”. Lalu proses lainnya sama, hanya perhatikan kita gunakan method ApplyOnnxModel untuk melakukan scoring dengan onnx model, dan output dari model memiliki nama “mobilenetv20_output_flatten0_reshape0”. Output ini menjadi input untuk layer terakhir untuk multi-class classification. Selanjutnya masukan juga kode berikut untuk melakukan prediksi dengan gambar tunggal atau beberapa gambar sekaligus.

public static void ClassifySingleImage(MLContext mlContext, string imagePath, string outputModelLocation, ITransformer model)
{
var imageData = new ImageData()
{
ImagePath = imagePath
};
// Make prediction function (input = ImageData, output = ImagePrediction)
var predictor = mlContext.Model.CreatePredictionEngine<ImageData, ImagePrediction>(model);
var prediction = predictor.Predict(imageData);
Console.WriteLine($"Image: {Path.GetFileName(imageData.ImagePath)} predicted as: {prediction.PredictedLabelValue} with score: {prediction.Score.Max()} ");
}

public static void ClassifyImages(MLContext mlContext, string dataLocation, string imagesFolder, string outputModelLocation, ITransformer model)
{
var imageData = ReadFromTsv(dataLocation, imagesFolder);
var imageDataView = mlContext.Data.LoadFromEnumerable<ImageData>(imageData);
var predictions = model.Transform(imageDataView);
var imagePredictionData = mlContext.Data.CreateEnumerable<ImagePrediction>(predictions, false, true);
DisplayResults(imagePredictionData);
}

dan berikut adalah beberapa method bantuan yang kita gunakan untuk menentukan menampilkan hasil prediksi, mendapatkan path lengkap, load tsv file dan parameter extraksi gambar:

private static void DisplayResults(IEnumerable<ImagePrediction> imagePredictionData)
{
foreach (ImagePrediction prediction in imagePredictionData)
{
Console.WriteLine($"Image: {Path.GetFileName(prediction.ImagePath)} predicted as: {prediction.PredictedLabelValue} with score: {prediction.Score.Max()} ");
}
}
public static string GetAbsolutePath(string relativePath)
{
FileInfo _dataRoot = new FileInfo(typeof(Program).Assembly.Location);
string assemblyFolderPath = _dataRoot.Directory.FullName;
string fullPath = Path.Combine(assemblyFolderPath, relativePath);
return fullPath;
}
public static IEnumerable<ImageData> ReadFromTsv(string file, string folder)
{
return File.ReadAllLines(file)
.Select(line => line.Split('\t'))
.Select(line => new ImageData()
{
ImagePath = Path.Combine(folder, line[0])
});

}
private struct ModelSettings
{
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;
}

Nah sekarang rekan-rekan sudah berhasil mencoba transfer learning ML.NET dengan model tensorflow dan onnx. Source codenya bisa didapatkan dari sini

Beberapa referensi mengenai model yang digunakan:

  1. Inception Model TensorFlow : Link 
  2. MobileNet Model Onnx : Link

Selamat mencoba berkreasi dengan use case lain. 

Salam Makers ;D

 

Loading

You May Also Like