描述

dlib的神经网络算法虽然没法与tensorflow、pytorch这些框架相提并论的(dlib中可供选择的的损失函数、优化器十分有限,缺失自动微分等等),但不代表dlib不是一个强大的机器学习库。

dlib广泛应用于人脸识别、关键点检测、图像处理等领域,支持Linux/Windows/macOS多平台运行(跨平台编译很方便)。

这记录一下全连接网络下二分类、多分类、回归模型的设计与训练

二分类

处理二分类问题时,优先使用loss_binary_log(逻辑损失)、loss_binary_hinge(最大化分类间隔,SVM中最常用)损失函数,专门处理二分类问题的。这里需要注意:1表示正类,-1表示反类。

这里以breast_cancer.csv数据分类为例,设计一个30x60x60x1的神经网络(这里要注意:用loss_binary_xxx处理二分类问题,网络输出层只能有一个参数),当然数据归一化处理也是少不了的(为了提高模型的精度)。

void dnnBinaryClassifierTest(const string &pfile)
{
    vector<BreastCancer> vec = LoadCancer(pfile);
    vector<dlib::matrix<double>> train_datas;
    vector<float> train_labels; // 注意label的类型,与loss的选择有关
    for (int i = 0; i < vec.size(); i++)
    {
        BreastCancer canser = vec.at(i);
        train_datas.push_back({canser.ft[0], canser.ft[1], canser.ft[2], canser.ft[3], canser.ft[4], canser.ft[5], canser.ft[6],
                               canser.ft[7], canser.ft[8], canser.ft[9], canser.ft[10], canser.ft[11], canser.ft[12], canser.ft[13],
                               canser.ft[14], canser.ft[15], canser.ft[16], canser.ft[17], canser.ft[18], canser.ft[19], canser.ft[20],
                               canser.ft[21], canser.ft[22], canser.ft[23], canser.ft[24], canser.ft[25], canser.ft[26], canser.ft[27],
                               canser.ft[28], canser.ft[29]});
        train_labels.push_back(canser.tag > 0 ? 1 : -1);
    }

    dlib::vector_normalizer<dlib::matrix<double>> normalizer; // 数据归一化
    normalizer.train(train_datas);
    for (int i = 0; i < train_datas.size(); i++)
    {
        train_datas[i] = normalizer(train_datas.at(i));
    }
    dlib::randomize_samples(train_datas, train_labels); // 打乱数据

    vector<dlib::matrix<double>> test_datas;
    vector<float> test_labels;
    test_datas.assign(train_datas.begin(), train_datas.begin() + 150);
    test_labels.assign(train_labels.begin(), train_labels.begin() + 150);
    train_datas.assign(train_datas.begin() + 150, train_datas.end());
    train_labels.assign(train_labels.begin() + 150, train_labels.end());
    // 定义网络结构:输出层<-中间层隐层<-...<-输入层
    using net_type = 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>>>>>>>>>>;
    net_type net;
    dlib::dnn_trainer<net_type> trainer(net);
    trainer.set_max_num_epochs(50);   // 设置训练最大轮数
    trainer.set_learning_rate(0.0001);// 设置学习率
    trainer.set_mini_batch_size(4);   // 设置数据最批次
    trainer.be_verbose();			 // 输出训练过程日志
    trainer.train(train_datas, train_labels); // 训练模型
    net.clean(); // 很有必要执行。官方解释:模型在训练结束后会保留最后一批训练数据的相关状态,当然这些数据对模型预测结果不会产生任何影响,但会影响模型文件的大小

    int ok_count = 0;
    for (int i = 0; i < test_datas.size(); i++)
    {
        double ret_f = net(test_datas.at(i));
        cout << "predicted : " << ret_f << ",real val:" << test_labels.at(i) << endl;
        int pred_val = ret_f > 0 ? 1 : -1; // 大于0的是正类,小于0的是反类(会不会等于0,理论上有可能,但概率极低)
        if (test_labels.at(i) == pred_val)
            ok_count += 1;
    }
    cout << "accurary:" << (ok_count * 1.0) / test_datas.size() << endl;
    // 保存模型
    dlib::serialize("dnn_cancer_model.dat")<<net;
    dlib::serialize("dnn_cancer_normalizer.dat")<<normalizer;
}

多分类

多分类问题可以选择loss_multiclass_log损失函数。网络输出层参数与类别数保持一致,类别从0开始,网络输出结果为类别编号。

以鸢尾花分类为例,设计一个4x30x30x3的神经网络。

void dnnMultiClassifierTest(const string &pfile)
{
    vector<Iris> vec = LoadIris(pfile);
    vector<dlib::matrix<double>> train_datas;
    vector<unsigned long> train_labels; // 注意类型
    int type_no = 0;
    map<string, int> temp_map;
    for (int i = 0; i < vec.size(); i++)
    {
        Iris iris = vec.at(i);
        train_datas.push_back({iris.ft[0], iris.ft[1], iris.ft[2], iris.ft[3]});
        if (temp_map.find(iris.specie) == temp_map.end())
        {
            temp_map[iris.specie] = type_no;
            type_no += 1;
        }
        train_labels.push_back(temp_map[iris.specie]);
    }
    // iris的样本数据有三种分类,故输出层是3个参数;特征有4个,故输入层参数是4
    using net_type = 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>>>>>>>>>>;
    net_type net;
    dlib::dnn_trainer<net_type> trainer(net);
    trainer.set_max_num_epochs(20);
    trainer.set_learning_rate(0.001);
    trainer.set_mini_batch_size(4);
    trainer.be_verbose();
    trainer.train(train_datas,train_labels);
    net.clean();
    int ok_count=0;
    for (int i = 0; i < vec.size(); i++)
    {
        Iris iris = vec.at(i);
        uint32_t ret_d = net({iris.ft[0], iris.ft[1], iris.ft[2], iris.ft[3]});
        cout<<"predict:"<<ret_d<<",real:"<<temp_map[iris.specie]<<endl;
        if(ret_d == temp_map[iris.specie]) ok_count+=1;
    }
    cout << "accurary:" << (ok_count * 1.0) / vec.size() << endl;
    dlib::serialize("dnn_iris_model.dat")<<net;
}

回归

对于回归问题,dlib中目前好像只有一个选择:loss_mean_squared。

以boston_house_prices.csv为例,设计一个13x50x50x1的回归模型

void dnnRegressionTest(const string &pfile)
{
    vector<BostonHoursPrice> vec = LoadHoursPrice(pfile);
    vector<float> train_labels;
    vector<dlib::matrix<double>> train_datas;
    for (int i = 0; i < vec.size(); i++)
    {
        BostonHoursPrice price = vec.at(i);
        train_datas.push_back({price.ft[0], price.ft[1], price.ft[2], price.ft[3], price.ft[4], price.ft[5],
                               price.ft[6], price.ft[7], price.ft[8], price.ft[9], price.ft[10], price.ft[11],
                               price.ft[12]});
        train_labels.push_back(price.tag);
    }
    using net_type = 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>>>>>>>>>>;
    net_type net;
    dlib::dnn_trainer<net_type> trainer(net);
    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(train_datas, train_labels);
    net.clean();

    for (int i = 0; i < train_datas.size(); i++)
    {
        float ret_f = net(train_datas.at(i));
        cout << "predicted : " << ret_f << ",real val:" << train_labels.at(i) << endl;
    }
}

补充一个SVM回归的例子,svm的回归模型更简单些:

typedef dlib::matrix<double,13,1> price_type;
typedef dlib::radial_basis_kernel< price_type> rbf_kernel;

void RvmRegression(const string &pfile)
{
    vector<price_type> train_datas;
    vector<double> train_labels;
    LoadPriceData(pfile, train_datas, train_labels);
    const double gamma = 0.00001;
    dlib::rvm_regression_trainer<rbf_kernel> trainer; // rvm_regression_trainer 
    trainer.set_kernel(rbf_kernel(gamma));
    dlib::decision_function<rbf_kernel> learned_func = trainer.train(train_datas, train_labels);
    for (int i = 0; i < train_datas.size(); i++)
    {
        price_type pt = train_datas.at(i);
        double ret = learned_func(pt);
        cout << "predicted val:" << ret << ", real val:" << train_labels.at(i) << endl;
    }
    dlib::serialize("svm_reg_price.dat") << learned_func;
}
Logo

鲲鹏昇腾开发者社区是面向全社会开放的“联接全球计算开发者,聚合华为+生态”的社区,内容涵盖鲲鹏、昇腾资源,帮助开发者快速获取所需的知识、经验、软件、工具、算力,支撑开发者易学、好用、成功,成为核心开发者。

更多推荐