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;
}