Góc nhìn kiến trúc về ML.NET

Phần lớn nha phat trien .NET cho rằng hoc may luôn cần Python, Jupyter Notebook, pandas, scikit-learn và một ngăn xếp cong nghe hoàn toàn xa la được gắn thêm vào ung dung C#. Giả định này kéo theo nhiều chi phí: phải duy trì hai bo ma nguon, các lớp ket noi roi ren và quy trình trien khai dễ phát sinh lỗi khó nhận biết.

ML.NET là framework hoc may ma nguon mo, đa nen tang của Microsoft, được xây dung dành riêng cho .NET. Framework này cho phép bạn huan luyen, danh gia và trien khai mo hinh hoc may bằng các mau thiet ke C# quen thuoc, không cần runtime Python, thu vien xa la hay phải chuyển đổi qua lại giữa các hệ sinh thái.

Bài viết này trình bày kiến trúc của ML.NET, cách ket hop pipeline hoc may, các tac vu hoc may được hỗ trợ sẵn và cách xây dung một pipeline hoạt động bên trong ung dung .NET.

Tại sao nên dùng ML.NET?

Python chiếm ưu thế trong hoc may chủ yếu vì đây là ngôn ngu mà các nhà khoa hoc và nhà nghien cuu lựa chọn khi lĩnh vực này bắt đầu phát triển, không hẳn vì Python vượt trội về mặt ky thuat cho các ung dung san xuat. Hệ sinh thái scikit-learn, pandas và TensorFlow sau đó phát triển theo, củng cố thói quen này. Với thử nghiem và notebook, lựa chọn đó hoàn toàn hợp lý. Tuy nhiên, trong ung dung doanh nghiep .NET, nó tạo ra nhiều ma sat đáng kể.

Đưa một mo hinh được huan luyen bằng Python vào ung dung .NET thường dẫn đến một trong ba hướng: bọc mo hinh bằng dich vu REST hoặc gRPC, tức bổ sung một lượt gọi qua mạng và một tien trinh cần duy trì; sử dụng binding ngôn ngu có thể không hỗ trợ nen tang mục tiêu; hoặc chuyen mo hinh sang dinh dang ONNX rồi nap thông qua thu vien trung gian. Mỗi hướng đều làm tăng độ phức tạp mà không liên quan trực tiếp đến bài toan hoc may của bạn.

Thực tế vận hành cũng cho thấy vấn đề này. Nhiều khảo sát trong ngành chỉ ra rằng một phần lớn doanh nghiep thử nghiem hoc may nhưng không đưa được hệ thống vào san xuat thành công. Phức tạp trong khâu ket noi là một nguyên nhân quan trọng. ML.NET thay đổi bài toan đó: ma nguon huan luyen, cac lop schema và logic suy luan đều nằm trong cùng một solution .NET, được build, trien khai và giam sat giống như các phần còn lại của ung dung.

Ngoai sự thuận tiện khi trien khai, thiet ke của ML.NET cũng rất tự nhiên với lap trinh vien .NET. MLContext có vai trò tương tự DbContext của Entity Framework. Pipeline được ket hop giống cách LINQ nối các thao tac trên tap du lieu. Kieu input và output của mo hinh chỉ là các lop C# thông thường có gan thuoc tinh, không có gì quá đặc biệt. Sự quen thuộc này giúp một nhom đã thành thạo C# bắt đầu xây dung và lap lai pipeline hoc may mà không phải thay đổi cách tu duy.

Cách triển khai

Cấu trúc solution gồm ba project

Một solution ML.NET được tổ chức tốt thường gồm ba project riêng biệt, mỗi project đảm nhận một trách nhiệm rõ ràng:

MySolution/
├── MyApp.Trainer/          # Ung dung console — dieu phoi pipeline hoc may,
│                           # nap du lieu, huan luyen, danh gia va luu mo hinh
├── MyApp.MLSchema/         # Class library — kieu du lieu input/output dung chung
│                           # giua trainer va ung dung client
└── MyApp.Web/              # Ung dung client — nap mo hinh va chay du doan

Project trainer không phải một script chạy một lần. Đây là ma nguon san xuat sẽ được chạy lại mỗi khi du lieu thay đổi hoặc mo hinh bị suy giảm chất lượng. Hãy thiết kế project này có thể cấu hình và chạy lại. Thu vien schema chứa các kieu C# mô tả du lieu, giúp trainer và client thống nhất cấu trúc mà không phải lap lai dinh nghia. Project client là ung dung thực tế của bạn, chẳng hạn Web API, ung dung Blazor, cong cu console hoặc bất kỳ thành phần nào cần kết quả du doan.

MLContext: điểm bắt đầu của pipeline

Mọi pipeline ML.NET đều bắt đầu bằng một instance duy nhất của MLContext. Hãy xem nó giống như DbContext: đối tượng này cung cấp quyền truy cập đến các thao tac hoc may, quản lý trang thai pipeline và nên được tạo một lần rồi dùng chung trong phần ma nguon huan luyen.

using Microsoft.ML;

// Tao mot MLContext cho moi pipeline
var mlContext = new MLContext(seed: 42);

// seed giup cac thao tac ngau nhien co the lap lai giua cac lan chay

Tham số seed kiểm soát việc tao so ngau nhien nội bộ. Khi cố định tham số này, hai lần huan luyen trên cùng một tap du lieu sẽ tạo ra kết quả có thể lap lai. Điều này rất quan trọng khi bạn so sánh các cấu hình thuat toan.

Định nghĩa schema du lieu bằng lop C#

ML.NET nap du lieu vào các đối tượng C# có kieu mạnh. Bạn khai báo một lop có các thuoc tinh ánh xạ đến các cot trong nguồn du lieu bằng LoadColumnAttribute. Lop output mô tả kết quả mà mo hinh sẽ du doan.

using Microsoft.ML.Data;

// Input: mot dong trong file CSV huan luyen
public class SentimentInput
{
    [LoadColumn(0)]
    public string ReviewText { get; set; } = string.Empty;

    [LoadColumn(1), ColumnName("Label")]
    public bool Sentiment { get; set; }  // true = tich cuc, false = tieu cuc
}

// Output: ket qua mo hinh tra ve
public class SentimentPrediction
{
    [ColumnName("PredictedLabel")]
    public bool Prediction { get; set; }

    public float Probability { get; set; }

    public float Score { get; set; }
}

LoadColumn(index) ánh xạ thuoc tinh đến vị trí cot bắt đầu từ 0 trong file nguồn. ColumnName ghi đè tên mà ML.NET dùng nội bộ cho cot. Label là tên quy uoc cho cot mục tiêu mà trainer cần học.

Nap du lieu bằng IDataView

ML.NET biểu diễn du lieu dạng bảng thông qua interface IDataView, một abstraction lazy dựa trên cursor, tương tự tap ket qua của co so du lieu. Bạn có thể nap du lieu từ file CSV, tap trong bo nho, co so du lieu hoặc file nhi phan. View không được nap toàn bộ vào bo nho cho đến khi pipeline thực thi, nhờ đó mức sử dụng bo nho vẫn được kiểm soát với tap du lieu lớn.

// Nap tu file CSV — du lieu van lazy cho den khi pipeline chay
IDataView trainingData = mlContext.Data.LoadFromTextFile<SentimentInput>(
    path: "data/reviews-train.csv",
    hasHeader: true,
    separatorChar: ',');

// Hoac nap tu danh sach trong bo nho — huu ich khi kiem thu
var reviews = new List<SentimentInput>
{
    new() { ReviewText = "Great product!", Sentiment = true },
    new() { ReviewText = "Broken on arrival.", Sentiment = false }
};
IDataView inMemoryData = mlContext.Data.LoadFromEnumerable(reviews);

Xây dung pipeline chuyen doi và huan luyen

Sức mạnh của ML.NET nằm ở khả năng ket hop các phep chuyen doi du lieu và trainer thành một pipeline duy nhất, có thể tái sử dụng, tương tự cách LINQ nối các thao tac. Mỗi lời gọi Append thêm một bước vào pipeline. Chưa có gì thực thi cho đến khi bạn gọi Fit.

// Buoc 1: chuyen van ban thô thành vector dac trung so
var featuriseText = mlContext.Transforms.Text
    .FeaturizeText(
        outputColumnName: "Features",
        inputColumnName: nameof(SentimentInput.ReviewText));

// Buoc 2: chon trainer cho bai toan phan loai nhi phan
var trainer = mlContext.BinaryClassification.Trainers
    .SdcaLogisticRegression(
        labelColumnName: "Label",
        featureColumnName: "Features");

// Ket hop phep chuyen doi va trainer thanh mot pipeline
var trainingPipeline = featuriseText.Append(trainer);

// Fit thuc thi pipeline: chuyen doi du lieu va huan luyen mo hinh
ITransformer trainedModel = trainingPipeline.Fit(trainingData);

FeaturizeText chuyen chuoi van ban thô thành vector dac trung kiểu số thực bằng phân tích n-gram và chuan hoa. Toàn bộ phần tien xu ly mà khi dùng Python có thể cần nhiều thao tac NumPy được thực hiện trong một lời gọi. SdcaLogisticRegression là thuat toan Stochastic Dual Coordinate Ascent, phù hợp cho các bài toan nhi phan với tốc độ và độ chính xác tốt.

Các tac vu hoc may và trainer khả dụng

ML.NET tổ chức thuat toan theo loại bài toan hoc may cần giải, gọi là tac vu. Mỗi tac vu cung cấp thuộc tính Trainers chứa các thuat toan được hỗ trợ. Xác định tac vu trước sẽ giúp thu hẹp lựa chọn trainer phù hợp.

  • BinaryClassification: quyết định giữa hai nhóm, chẳng hạn spam và không spam, gian lan và hợp lệ.
  • MulticlassClassification: phân loại thành từ ba nhóm trở lên, chẳng hạn gan nhan chu de hoặc phat hien y dinh.
  • Regression: du doan một giá trị liên tục như gia, thoi luong hoặc so luong.
  • Clustering: gom nhóm du lieu chưa có nhan dua trên mức độ tương đồng, chẳng hạn phan khuc khach hang.
  • AnomalyDetection: phat hien gia tri bat thuong và su kien khác lạ, chẳng hạn bat thuong tren may chu.
  • Ranking: sắp xếp đối tượng theo mức độ liên quan, chẳng hạn ket qua tim kiem hoặc goi y.
  • Forecasting: du doan gia tri tương lai trong chuoi thoi gian, chẳng hạn doanh so hoặc nhu cau.
// Phan loai nhi phan
var binaryTrainer = mlContext.BinaryClassification.Trainers
    .SdcaLogisticRegression();

// Hoi quy
var regressionTrainer = mlContext.Regression.Trainers
    .OnlineGradientDescent();

// Phan loai da lop
var multiclassTrainer = mlContext.MulticlassClassification.Trainers
    .SdcaMaximumEntropy();

// Phat hien bat thuong
var anomalyDetector = mlContext.AnomalyDetection.Trainers
    .RandomizedPca(featureColumnName: "Features");

Đánh giá mo hinh

Sau khi huan luyen, bạn cần đánh giá mo hinh trên tap kiem tra được giữ riêng để đo khả năng tong quat hoa. ML.NET cung cấp cac chi so danh gia riêng cho từng tac vu, giúp bạn so sánh những con số có ý nghĩa với bài toan đang giải.

// Giữ lai 20% du lieu de danh gia
var dataSplit = mlContext.Data.TrainTestSplit(trainingData, testFraction: 0.2);

// Huan luyen lai tren tap train
ITransformer model = trainingPipeline.Fit(dataSplit.TrainSet);

// Chuyen tap test qua mo hinh
IDataView predictions = model.Transform(dataSplit.TestSet);

// Danh gia cac chi so phan loai nhi phan
var metrics = mlContext.BinaryClassification
    .Evaluate(predictions, labelColumnName: "Label");

Console.WriteLine($"Accuracy:  {metrics.Accuracy:P2}");
Console.WriteLine($"AUC:       {metrics.AreaUnderRocCurve:P2}");
Console.WriteLine($"F1 Score:  {metrics.F1Score:P2}");

// Ket qua vi du:
// Accuracy:  92.40%
// AUC:       96.10%
// F1 Score:  91.80%

Lưu và nap mo hinh đã huan luyen

Mo hinh đã huan luyen là một do thi tinh toan được serialize. Bạn lưu mo hinh vào file .zip, sau đó nap file này trong ung dung client để thực hiện du doan. Cùng một file nhi phan có thể chạy trong mọi tien trinh .NET, dù đó là Web API, dich vu nen hay ung dung Blazor WASM chạy phía server.

// Trong project trainer — luu sau khi danh gia thanh cong
mlContext.Model.Save(model, trainingData.Schema, "sentiment-model.zip");
Console.WriteLine("Model saved to sentiment-model.zip");

// Trong project client — nap mot lan khi khoi dong
var mlContextClient = new MLContext();
ITransformer loadedModel = mlContextClient.Model.Load(
    "sentiment-model.zip",
    out DataViewSchema schema);

// PredictionEngine khong an toan khi dung dong thoi
var predictionEngine = mlContextClient.Model
    .CreatePredictionEngine<SentimentInput, SentimentPrediction>(loadedModel);

var sample = new SentimentInput { ReviewText = "Absolutely worth every penny!" };
SentimentPrediction result = predictionEngine.Predict(sample);

Console.WriteLine($"Positive: {result.Prediction}, Probability: {result.Probability:P1}");

Trong ASP.NET Core, hãy dùng PredictionEnginePool<TIn, TOut> từ goi NuGet Microsoft.Extensions.ML thay cho CreatePredictionEngine. Pool quản lý an toàn các instance PredictionEngine khi có nhiều request đồng thời và hỗ trợ nap lai mo hinh khi file thay đổi mà không cần khoi dong lai tien trinh.

// Program.cs trong project ASP.NET Core
builder.Services.AddPredictionEnginePool<SentimentInput, SentimentPrediction>()
    .FromFile(
        modelName: "SentimentModel",
        filePath: "sentiment-model.zip",
        watchForChanges: true);

// Trong controller hoac endpoint minimal API
app.MapPost("/predict", (
    PredictionEnginePool<SentimentInput, SentimentPrediction> pool,
    SentimentInput input) =>
{
    var prediction = pool.Predict("SentimentModel", input);
    return Results.Ok(new { prediction.Prediction, prediction.Probability });
});

Kiến trúc kết hợp như thế nào?

  • Project trainer: tạo MLContext, nap IDataView, xây dung pipeline, huan luyen ITransformer, danh gia và luu mo hinh.
  • Thu vien schema: chứa các lop SentimentInput và SentimentPrediction được dùng chung giữa trainer và client.
  • Ung dung client: nap file mo hinh, tạo pool du doan và xử lý các request cần kết quả.
MLContext
   |
   v
IDataView --> Pipeline --> ITransformer
              |               |
       Transform + Trainer  Evaluate + Save .zip
              |
       Schema classes dung chung
              |
       Model.Load(.zip) --> PredictionEnginePool

Tóm tắt

ML.NET cung cấp cho lap trinh vien .NET một hướng tiếp cận hoc may đầy đủ mà không phải rời khỏi hệ sinh thái quen thuộc. Những điểm quan trọng cần ghi nhớ:

  • MLContext là điểm truy cập duy nhất cho mọi thao tac. Hãy xem nó như DbContext và duy trì một instance cho mỗi pipeline.
  • IDataView cung cấp cơ chế truy cập du lieu lazy dựa trên cursor, giúp mức sử dụng bo nho ổn định ngay cả với tap huan luyen lớn.
  • Pipeline được ket hop bằng Append. Hãy nối các phep chuyen doi với trainer rồi gọi Fit một lần để thực thi toàn bộ.
  • ML.NET tổ chức thuat toan theo tac vu: BinaryClassification, Regression, MulticlassClassification, AnomalyDetection, Clustering, Ranking và Forecasting.
  • Trong ASP.NET Core, hãy dùng PredictionEnginePool từ Microsoft.Extensions.ML để xử lý du doan đồng thời an toàn và hỗ trợ nap lai mo hinh.
  • Hãy chia solution thành ba project: trainer, thu vien schema và client. Cách tổ chức này giúp tach biet trách nhiệm và cho phép từng phần phát triển độc lập.

Sau khi nắm được kiến trúc, bước tiếp theo là áp dụng ML.NET vào một tình huống thực tế. Bạn có thể tìm hiểu cách AutoML tự động lựa chọn thuat toan trong bài viết Phat hien ngôn ngu trong .NET bằng ML.NET và AutoML, hoặc xem ví dụ hoc sau với mo hinh được huan luyen trước trong bài viết Nhan dien hinh anh trong .NET bằng ML.NET và ONNX Runtime.