Thiết kế và Huấn luyện Mạng Neuron Toàn Kết Nối với Dlib

Mặc dù các thuật toán mạng neuron của Dlib không thể so sánh với các framework như TensorFlow hay PyTorch do hạn chế về hàm mất mát, bộ tối ưu hóa và thiếu khả năng tự động phân biệt, Dlib vẫn là một thư viện học máy mạnh mẽ.

Dlib được ứng dụng rộng rãi trong nhận dạng khuôn mặt, phát hiện điểm mấu chốt, xử lý ảnh và hỗ trợ đa nền tảng (Linux/Windows/macOS) với việc biên dịch chéo dễ dàng.

Bài viết này sẽ trình bày cách thiết kế và huấn luyện các mô hình phân loại nhị phân, phân loại đa lớp và hồi quy sử dụng mạng neuron toàn kết nối trong Dlib.

Phân loại nhị phân

Đối với bài toán phân loại nhị phân, nên ưu tiên sử dụng các hàm mất mát loss_binary_log (mất mát logarit) hoặc loss_binary_hinge (tối đa hóa khoảng cách phân loại, thường dùng trong SVM). Lưu ý rằng lớp 1 biểu thị lớp dương và -1 biểu thị lớp âm.

Ví dụ sau đây sử dụng tập dữ liệu breast_cancer.csv để thiết kế mạng neuron với cấu trúc 30x60x60x1. Cần lưu ý rằng khi sử dụng loss_binary_xxx cho bài toán phân loại nhị phân, lớp đầu ra của mạng chỉ có thể có một tham số. Việc chuẩn hóa dữ liệu là cần thiết để cải thiện độ chính xác của mô hình.


void dnnBinaryClassifierExample(const std::string &filePath)
{
    // Giả định có hàm LoadCancer để tải dữ liệu
    std::vector<BreastCancerData> dataset = LoadCancer(filePath);
    std::vector<dlib::matrix<double>> trainingFeatures;
    std::vector<float> trainingLabels; // Lưu ý kiểu dữ liệu của nhãn, liên quan đến lựa chọn hàm mất mát

    for (const auto& dataPoint : dataset)
    {
        // Trích xuất 30 đặc trưng và gán nhãn
        trainingFeatures.push_back({dataPoint.features[0], ..., dataPoint.features[29]});
        trainingLabels.push_back(dataPoint.label > 0 ? 1.0f : -1.0f);
    }

    // Chuẩn hóa dữ liệu
    dlib::vector_normalizer<dlib::matrix<double>> featureNormalizer;
    featureNormalizer.train(trainingFeatures);
    for (auto& features : trainingFeatures)
    {
        features = featureNormalizer(features);
    }

    // Xáo trộn dữ liệu và nhãn
    dlib::randomize_samples(trainingFeatures, trainingLabels);

    // Chia tập dữ liệu thành tập huấn luyện và tập kiểm tra
    std::vector<dlib::matrix<double>> testFeatures;
    std::vector<float> testLabels;
    size_t splitIndex = 150;
    testFeatures.assign(trainingFeatures.begin(), trainingFeatures.begin() + splitIndex);
    testLabels.assign(trainingLabels.begin(), trainingLabels.begin() + splitIndex);
    trainingFeatures.erase(trainingFeatures.begin(), trainingFeatures.begin() + splitIndex);
    trainingLabels.erase(trainingLabels.begin(), trainingLabels.begin() + splitIndex);

    // Định nghĩa kiến trúc mạng: Lớp đầu ra <- Lớp ẩn trung gian <- ... <- Lớp đầu vào
    // Kiến trúc: Input(30) -> FC(30) -> ReLU -> FC(60) -> ReLU -> FC(60) -> ReLU -> FC(1) -> LossBinaryLog
    using BinaryNetType = dlib::loss_binary_log<
        dlib::fc<1, dlib::relu<dlib::fc<60, dlib::relu<dlib::fc<60, dlib::relu<dlib::fc<30, dlib::input<dlib::matrix<double>>>>>>>>;
    BinaryNetType network;

    // Huấn luyện viên mạng
    dlib::dnn_trainer<BinaryNetType> trainer(network);
    trainer.set_max_num_epochs(50);   // Số epoch tối đa
    trainer.set_learning_rate(0.0001); // Tốc độ học
    trainer.set_mini_batch_size(4);   // Kích thước batch nhỏ
    trainer.be_verbose();             // Hiển thị thông tin huấn luyện

    trainer.train(trainingFeatures, trainingLabels); // Bắt đầu huấn luyện
    network.clean(); // Giải phóng bộ nhớ tạm không cần thiết sau huấn luyện

    // Đánh giá mô hình
    int correctPredictions = 0;
    for (size_t i = 0; i < testFeatures.size(); ++i)
    {
        double predictionScore = network(testFeatures.at(i));
        std::cout << "Predicted: " << predictionScore << ", Real: " << testLabels.at(i) << std::endl;
        int predictedClass = (predictionScore > 0) ? 1 : -1; // Phân loại dựa trên dấu của điểm số
        if (testLabels.at(i) == predictedClass)
            correctPredictions++;
    }
    std::cout << "Accuracy: " << (static_cast<double>(correctPredictions) / testFeatures.size()) << std::endl;

    // Lưu mô hình và bộ chuẩn hóa
    dlib::serialize("dnn_cancer_classifier_model.dat") << network;
    dlib::serialize("dnn_cancer_feature_normalizer.dat") << featureNormalizer;
}

Phân loại đa lớp

Đối với bài toán phân loại đa lớp, có thể chọn hàm mất mát loss_multiclass_log. Số lượng tham số ở lớp đầu ra phải bằng số lượng lớp, với các lớp được đánh số từ 0 trở lên. Đầu ra của mạng sẽ là chỉ số của lớp được dự đoán.

Ví dụ sau đây sử dụng tập dữ liệu hoa diên vĩ (Iris) để thiết kế mạng neuron với cấu trúc 4x30x30x3.


void dnnMultiClassifierExample(const std::string &filePath)
{
    // Giả định có hàm LoadIris để tải dữ liệu
    std::vector<IrisData> dataset = LoadIris(filePath);
    std::vector<dlib::matrix<double>> trainingFeatures;
    std::vector<unsigned long> trainingLabels; // Lưu ý kiểu dữ liệu
    int classIndexCounter = 0;
    std::map<std::string, int> speciesMap;

    for (const auto& dataPoint : dataset)
    {
        trainingFeatures.push_back({dataPoint.features[0], dataPoint.features[1], dataPoint.features[2], dataPoint.features[3]});
        
        // Gán chỉ số cho từng loại hoa
        if (speciesMap.find(dataPoint.species) == speciesMap.end())
        {
            speciesMap[dataPoint.species] = classIndexCounter++;
        }
        trainingLabels.push_back(speciesMap[dataPoint.species]);
    }

    // Kiến trúc mạng: Input(4) -> FC(4) -> ReLU -> FC(40) -> ReLU -> FC(40) -> ReLU -> FC(3) -> LossMulticlassLog
    // Số lớp là 3, số đặc trưng đầu vào là 4
    using MultiClassNetType = dlib::loss_multiclass_log<
        dlib::fc<3, dlib::relu<dlib::fc<40, dlib::relu<dlib::fc<40, dlib::relu<dlib::fc<4, dlib::input<dlib::matrix<double>>>>>>>>;
    MultiClassNetType network;

    // Huấn luyện viên mạng
    dlib::dnn_trainer<MultiClassNetType> trainer(network);
    trainer.set_max_num_epochs(20);
    trainer.set_learning_rate(0.001);
    trainer.set_mini_batch_size(4);
    trainer.be_verbose();

    trainer.train(trainingFeatures, trainingLabels);
    network.clean();

    // Đánh giá mô hình
    int correctPredictions = 0;
    for (size_t i = 0; i < dataset.size(); ++i)
    {
        const auto& dataPoint = dataset.at(i);
        dlib::matrix<double> inputFeatures = {dataPoint.features[0], dataPoint.features[1], dataPoint.features[2], dataPoint.features[3]};
        unsigned long predictedClassIndex = network(inputFeatures);
        
        std::cout << "Predicted Index: " << predictedClassIndex << ", Real Index: " << speciesMap[dataPoint.species] << std::endl;
        if (predictedClassIndex == speciesMap[dataPoint.species])
            correctPredictions++;
    }
    std::cout << "Accuracy: " << (static_cast<double>(correctPredictions) / dataset.size()) << std::endl;

    // Lưu mô hình
    dlib::serialize("dnn_iris_classifier_model.dat") << network;
}

Hồi quy

Đối với bài toán hồi quy, Dlib hiện tại dường như chỉ cung cấp một lựa chọn là loss_mean_squared (mất mát bình phương trung bình).

Ví dụ sau đây sử dụng tập dữ liệu boston_house_prices.csv để thiết kế mô hình hồi quy với cấu trúc 13x50x50x1.


void dnnRegressionExample(const std::string &filePath)
{
    // Giả định có hàm LoadHousePrices để tải dữ liệu
    std::vector<HousePriceData> dataset = LoadHousePrices(filePath);
    std::vector<float> trainingTargets;
    std::vector<dlib::matrix<double>> trainingFeatures;

    for (const auto& dataPoint : dataset)
    {
        // Trích xuất 13 đặc trưng
        trainingFeatures.push_back({dataPoint.features[0], ..., dataPoint.features[12]});
        trainingTargets.push_back(dataPoint.targetValue);
    }

    // Kiến trúc mạng: Input(13) -> FC(13) -> ReLU -> FC(50) -> ReLU -> FC(50) -> ReLU -> FC(1) -> LossMeanSquared
    using RegressionNetType = dlib::loss_mean_squared<
        dlib::fc<1, dlib::relu<dlib::fc<50, dlib::relu<dlib::fc<50, dlib::relu<dlib::fc<13, dlib::input<dlib::matrix<double>>>>>>>>;
    RegressionNetType network;

    // Huấn luyện viên mạng
    dlib::dnn_trainer<RegressionNetType> trainer(network);
    trainer.set_learning_rate(0.000001);
    trainer.set_min_learning_rate(0.00000001);
    trainer.set_mini_batch_size(8);
    trainer.set_max_num_epochs(200);
    trainer.be_verbose();

    trainer.train(trainingFeatures, trainingTargets);
    network.clean();

    // Hiển thị kết quả dự đoán so với giá trị thực
    for (size_t i = 0; i < trainingFeatures.size(); ++i)
    {
        float predictedValue = network(trainingFeatures.at(i));
        std::cout << "Predicted: " << predictedValue << ", Real: " << trainingTargets.at(i) << std::endl;
    }
}

Ví dụ hồi quy SVM

Bổ sung một ví dụ về hồi quy SVM, mô hình hồi quy SVM thường đơn giản hơn.


// Định nghĩa kiểu dữ liệu cho mẫu dữ liệu và kernel
typedef dlib::matrix<double, 13, 1> HousePriceSample;
typedef dlib::radial_basis_kernel<HousePriceSample> RBFKernel;

void SvmRegressionExample(const std::string &filePath)
{
    std::vector<HousePriceSample> trainingSamples;
    std::vector<double> trainingTargets;

    // Giả định có hàm LoadPriceData để tải dữ liệu vào trainingSamples và trainingTargets
    LoadPriceData(filePath, trainingSamples, trainingTargets);

    const double kernelGamma = 0.00001;
    dlib::rvm_regression_trainer<RBFKernel> trainer; // Sử dụng RVM cho hồi quy
    trainer.set_kernel(RBFKernel(kernelGamma));

    // Huấn luyện mô hình
    dlib::decision_function<RBFKernel> learnedFunction = trainer.train(trainingSamples, trainingTargets);

    // Hiển thị kết quả dự đoán so với giá trị thực
    for (size_t i = 0; i < trainingSamples.size(); ++i)
    {
        HousePriceSample sample = trainingSamples.at(i);
        double prediction = learnedFunction(sample);
        std::cout << "Predicted Value: " << prediction << ", Real Value: " << trainingTargets.at(i) << std::endl;
    }

    // Lưu mô hình SVM
    dlib::serialize("svm_regression_price_model.dat") << learnedFunction;
}

Thẻ: dlib C++ mạng neuron hồi quy phân loại nhị phân

Đăng vào ngày 9 tháng 7 lúc 21:47