zoukankan      html  css  js  c++  java
  • 感知机算法C++实现

      本节介绍感知机分类器的C++实现,其分类器实现框架会在后续的一些算法实现都会使用到。

      1、类的设计

      感知机类设计如下:

    View Code
    #pragma once
    #include <iostream>
    #include <fstream>
    #include <iomanip>
    #include <string>
    #include <vector>
    #include <set>
    #include <map>
    #include <algorithm>
    #include <cmath>
    #include <ctime>
    using namespace std;
    
    struct sparse_feat   #特征稀疏表示数据结构
    {
        vector<int> id_vec;
        vector<float> value_vec;
    };
    
    class Per
    {
    private:
        vector<sparse_feat> samp_feat_vec; #存储数据集特征
        vector<int> samp_class_vec;        #存储数据集的类别
        int feat_set_size;     #数据集的特征数
        int class_set_size;    #数据集的类别数
        vector< vector<float> > omega;  #模型参数
         
    public:
        Per();
        ~Per();
        void save_model(string model_file);
        void load_model(string model_file);
        void load_training_file(string training_file);
        void init_omega();
        
        int train_online(int criter, int max_loop, double loss_thrd, float learn_rate, float lambda, int avg);   #在线更新,主函数
        vector<float> calc_score(sparse_feat &samp_feat);
        vector<float> calc_neg_score(sparse_feat &samp_feat);
        vector<float> score_to_prb(vector<float> &score);
        int score_to_class(vector<float> &score);
        
        float classify_testing_file(string testing_file, string output_file, int output_format);
    
    private:
        void read_samp_file(string samp_file, vector<sparse_feat> &samp_feat_vec, vector<int> &samp_class_vec);
        void update_online_per(int samp_class, sparse_feat &samp_feat, float learn_rate);   #感知机更新侧率
       void calc_loss_per(double *loss, float *acc);                                       #感知机损失函数计算
        float calc_acc(vector<int> &test_class_vec, vector<int> &pred_class_vec);    
        sparse_feat product_pair_feature(const sparse_feat &samp_feat);
        
        vector<string> string_split(string terms_str, string spliting_tag);
    
    };

     
      本文采用稀疏表示的数据结构存储特征,用结构体sparse_feat表示特征,其中id_vec存储索引号的向量,value_vec存储特征值的向量,两者一一对应。目前主流的机器学习工具包均采用稀疏特征的存储方式。

      2.实现代码:

    View Code
      1 /********************************************************************
      2 * Linear Discriminant Function Classifier V0.10
      3 *********************************************************************/
      4 
      5 #include "Per.h"
      6 
      7 Per::Per()
      8 {
      9 }
     10 
     11 Per::~Per()
     12 {
     13 }
     14 
     15 void Per::save_model(string model_file)
     16 { 
     17     cout << "Saving model..." << endl;
     18     ofstream fout(model_file.c_str());
     19     for (int k = 0; k < feat_set_size; k++) {
     20         for (int j = 0; j < class_set_size; j++) {
     21             fout << omega[k][j] << " ";
     22         }
     23         fout << endl;
     24     }
     25     fout.close();
     26 }
     27 
     28 
     29 void Per::load_model(string model_file)
     30 {
     31     cout << "Loading model..." << endl;
     32     omega.clear();
     33     ifstream fin(model_file.c_str());
     34     if(!fin) {
     35         cerr << "Error opening file: " << model_file << endl;
     36         exit(0);
     37     }
     38     string line_str;
     39     while (getline(fin, line_str)) 
     40     {
     41         vector<string> line_vec = string_split(line_str, " ");
     42         vector<float>  line_omega;
     43         for (vector<string>::iterator it = line_vec.begin(); it != line_vec.end(); it++) 
     44         {
     45             float weight = (float)atof(it->c_str());
     46             line_omega.push_back(weight);
     47         }
     48         omega.push_back(line_omega);
     49     }
     50     fin.close();
     51     feat_set_size = (int)omega.size();
     52     class_set_size = (int)omega[0].size();
     53 }
     54 
     55 
     56 void Per::read_samp_file(string samp_file, vector<sparse_feat> &samp_feat_vec, vector<int> &samp_class_vec) {
     57     ifstream fin(samp_file.c_str());
     58     if(!fin) 
     59     {
     60         cerr << "Error opening file: " << samp_file << endl;
     61         exit(0);
     62     }
     63     string line_str;
     64     while (getline(fin, line_str)) 
     65     {
     66         size_t class_pos = line_str.find_first_of("\t");
     67         int class_id = atoi(line_str.substr(0, class_pos).c_str());
     68         samp_class_vec.push_back(class_id);
     69         string terms_str = line_str.substr(class_pos+1);
     70         sparse_feat samp_feat;
     71         samp_feat.id_vec.push_back(0); // bias
     72         samp_feat.value_vec.push_back(1); // bias
     73         if (terms_str != "") {
     74             vector<string> fv_vec = string_split(terms_str, " ");
     75             for (vector<string>::iterator it = fv_vec.begin(); it != fv_vec.end(); it++)
     76             {
     77                 size_t feat_pos = it->find_first_of(":");
     78                 int feat_id = atoi(it->substr(0, feat_pos).c_str());
     79                 float feat_value = (float)atof(it->substr(feat_pos+1).c_str());
     80                 samp_feat.id_vec.push_back(feat_id);
     81                 samp_feat.value_vec.push_back(feat_value);
     82             }
     83         }
     84         samp_feat_vec.push_back(samp_feat);
     85     }
     86     fin.close();
     87 }
     88 
     89 
     90 void Per::load_training_file(string training_file)
     91 {
     92     cout << "Loading training data..." << endl;
     93     read_samp_file(training_file, samp_feat_vec, samp_class_vec);
     94     feat_set_size = 0;
     95     class_set_size = 0;
     96     for (size_t i = 0; i < samp_class_vec.size(); i++) 
     97     {
     98         if (samp_class_vec[i] > class_set_size) 
     99         {
    100             class_set_size = samp_class_vec[i];
    101         }
    102         if (samp_feat_vec[i].id_vec.back() > feat_set_size) {
    103             feat_set_size = samp_feat_vec[i].id_vec.back();
    104         }    
    105     }
    106     class_set_size += 1;
    107     feat_set_size += 1;
    108 }
    109 
    110 void Per::init_omega()
    111 {
    112     //float int_value = 0.0;
    113     float int_value = (float)1/class_set_size;
    114     for (int i = 0; i < feat_set_size; i++)
    115     {
    116         vector<float> temp_vec(class_set_size, int_value);
    117         omega.push_back(temp_vec);
    118     }
    119 }
    120 
    121 // Stochastic Gradient Descent (SGD) optimization for the criteria of Perceptron (PER), Cross Entropy (CE), Leat Mean Square (LMS)
    122 int Per::train_online(int criter, int max_loop, double loss_thrd, float learn_rate, float lambda, int avg)
    123 {
    124     int id = 0;
    125     double loss = 0.0;
    126     double loss_pre = 0.0;
    127     vector< vector<float> > omega_sum(omega);
    128     while (id <= max_loop*(int)samp_class_vec.size()) 
    129     {
    130         // check loss value
    131         if (id%samp_class_vec.size() == 0) {
    132             int loop = id/(int)samp_class_vec.size();
    133             double loss = 0.0;
    134             float acc = 0.0;
    135         
    136             calc_loss_per(&loss, &acc);
    137         
    138             cout.setf(ios::left);
    139             cout << "Iter: " << setw(8) << loop << "Loss: " << setw(18) << loss << "Acc: " << setw(8) << acc << endl;
    140             if ((loss_pre - loss) < loss_thrd && loss_pre >= loss && id != 0)
    141             {
    142                 cout << "Reaching the minimal loss value decrease!" << endl;
    143                 break;
    144             }
    145             loss_pre = loss;
    146         }
    147         // update omega
    148         int r = (int)(rand()%samp_class_vec.size());
    149         //int r = (int)i%samp_class_vec.size();
    150         sparse_feat samp_feat = samp_feat_vec[r];
    151         int samp_class = samp_class_vec[r];
    152         update_online_per(samp_class, samp_feat, learn_rate);
    153         if (avg == 1 && id%samp_class_vec.size() == 0) 
    154         {
    155             for (int i = 0; i < feat_set_size; i++)
    156             {
    157                 for (int j = 0; j < class_set_size; j++)
    158                 {
    159                     omega_sum[i][j] += omega[i][j];
    160                 }
    161             }            
    162         }
    163         id++;
    164     }
    165     if (avg == 1) {
    166         for (int i = 0; i < feat_set_size; i++) 
    167         {
    168             for (int j = 0; j < class_set_size; j++)
    169             {
    170                 omega[i][j] = (float)omega_sum[i][j] / id;
    171             }
    172         }        
    173     }
    174     return 1;
    175 }
    176 
    177 void Per::update_online_per(int samp_class, sparse_feat &samp_feat, float learn_rate)
    178 {
    179     vector<float> score = calc_score(samp_feat);
    180     int pred_class = score_to_class(score);
    181     if (samp_class != pred_class) 
    182     {          
    183         for (size_t k = 0; k < samp_feat.id_vec.size(); k++) 
    184         {
    185             int feat_id = samp_feat.id_vec[k];
    186             float feat_value = samp_feat.value_vec[k];
    187             omega[feat_id][pred_class] -= learn_rate * feat_value;
    188             omega[feat_id][samp_class] += learn_rate * feat_value;
    189         }
    190     }
    191 }
    192 
    193 
    194 
    195 void Per::calc_loss_per(double *loss, float *acc)
    196 {
    197     double loss_value = 0.0;
    198     int err_num = 0;
    199     for (size_t i = 0; i < samp_class_vec.size(); i++)
    200     {
    201         int samp_class = samp_class_vec[i];
    202         sparse_feat samp_feat = samp_feat_vec[i];
    203         vector<float> score = calc_score(samp_feat);
    204         int pred_class = score_to_class(score);
    205         if (pred_class != samp_class) 
    206         {
    207             err_num++;
    208             loss_value += (score[pred_class] - score[samp_class]);
    209         }
    210     }
    211     *acc = 1 - (float)err_num / samp_class_vec.size();
    212     *loss = loss_value / samp_class_vec.size();
    213 }
    214 
    215 
    216 
    217 vector<float> Per::calc_score(sparse_feat &samp_feat)
    218 {
    219     vector<float> score(class_set_size, 0);
    220     for (int j = 0; j < class_set_size; j++)
    221     {
    222         for (size_t k = 0; k < samp_feat.id_vec.size(); k++)
    223         {
    224             int feat_id = samp_feat.id_vec[k];
    225             float feat_value = samp_feat.value_vec[k];
    226             score[j] += omega[feat_id][j] * feat_value;
    227         }
    228     }
    229     return score;
    230 }
    231 
    232 vector<float> Per::calc_neg_score(sparse_feat &samp_feat)
    233 {
    234     vector<float> score(class_set_size, 0);
    235     for (int j = 0; j < class_set_size; j++) 
    236     {
    237         for (size_t k = 0; k < samp_feat.id_vec.size(); k++)
    238         {
    239             int feat_id = samp_feat.id_vec[k];
    240             float feat_value = 1-samp_feat.value_vec[k];
    241             score[j] += omega[feat_id][j] * feat_value;
    242         }
    243     }
    244     return score;
    245 
    246 }
    247 vector<float> Per::score_to_prb(vector<float> &score)
    248 {   
    249     vector<float> prb(class_set_size, 0);
    250     for (int i = 0; i < class_set_size; i++)
    251     {
    252         float delta_prb_sum = 0.0;
    253         for (int j = 0; j < class_set_size; j++)
    254         {
    255             delta_prb_sum += exp(score[j] - score[i]);
    256         }
    257         prb[i] = 1 / delta_prb_sum;
    258     }
    259     return prb;
    260 }
    261 
    262 int Per::score_to_class(vector<float> &score)
    263 {
    264     int pred_class = 0;    
    265     float max_score = score[0];
    266     for (int j = 1; j < class_set_size; j++)
    267     {
    268         if (score[j] > max_score) {
    269             max_score = score[j];
    270             pred_class = j;
    271         }
    272     }
    273     return pred_class;
    274 }
    275 
    276 float Per::classify_testing_file(string testing_file, string output_file, int output_format)
    277 {
    278     cout << "Pair Classifying testing file..." << endl;
    279     vector<sparse_feat> test_feat_vec;
    280     vector<int> test_class_vec;
    281     vector<int> pair_test_class_vec;
    282     vector<int> pred_class_vec;
    283     vector<int> pair_pred_class_vec;
    284     read_samp_file(testing_file, test_feat_vec, test_class_vec);
    285     ofstream fout(output_file.c_str());
    286     for (size_t i = 0; i < test_class_vec.size(); i++) 
    287     {
    288         int samp_class = test_class_vec[i];
    289         pair_test_class_vec.push_back(1-test_class_vec[i]);
    290 
    291         sparse_feat samp_feat = test_feat_vec[i];
    292         sparse_feat pair_samp_feat=product_pair_feature(test_feat_vec[i]);
    293 
    294         vector<float> pred_score = calc_score(samp_feat);            
    295         int pred_class = score_to_class(pred_score);
    296 
    297         vector<float> pair_pred_score=calc_score(pair_samp_feat);
    298         int pair_pred_class=score_to_class(pair_pred_score);
    299 
    300         pred_class_vec.push_back(pred_class);
    301         
    302 
    303         if (pred_class==pair_pred_class)
    304         {
    305             vector<float> pred_prb = score_to_prb(pred_score);
    306             vector<float> pair_pred_prb=score_to_prb(pair_pred_score);
    307             float max_score=*(max_element(pred_prb.begin(),pred_prb.end()));
    308             float pair_max_score=*(max_element(pair_pred_prb.begin(),pair_pred_prb.end()));
    309             if (pair_max_score-max_score>0.2)
    310                 pair_pred_class=1-pair_pred_class;
    311         }
    312 
    313         pair_pred_class_vec.push_back(pair_pred_class);
    314 
    315         fout << pred_class << "\t";
    316         if (output_format == 1) {
    317             for (int j = 0; j < class_set_size; j++) {
    318                 fout << j << ":" << pred_score[j] << ' '; 
    319             }        
    320         }
    321         else if (output_format == 2)
    322         {
    323             vector<float> pred_prb = score_to_prb(pred_score);
    324             for (int j = 0; j < class_set_size; j++)
    325             {
    326                 fout << j << ":" << pred_prb[j] << ' '; 
    327             }
    328         }
    329         fout << endl;        
    330     }
    331     fout.close();
    332     float acc = calc_acc(test_class_vec, pred_class_vec);
    333     float pair_acc=calc_acc(pair_test_class_vec,pair_pred_class_vec);
    334     cout<<"Orgional acc is :"<<acc<<endl;
    335     cout<<"Pair acc is :"<<pair_acc<<endl;
    336     return acc;
    337 }
    338 
    339 float Per::calc_acc(vector<int> &test_class_vec, vector<int> &pred_class_vec)
    340 {
    341     size_t len = test_class_vec.size();
    342     if (len != pred_class_vec.size()) 
    343     {
    344         cerr << "Error: two vectors should have the same lenght." << endl;
    345         exit(0);
    346     }
    347     int err_num = 0;
    348     for (size_t id = 0; id != len; id++)
    349     {
    350         if (test_class_vec[id] != pred_class_vec[id])
    351         {
    352             err_num++;
    353         }
    354     }
    355     return 1 - ((float)err_num) / len;
    356 }
    357 
    358 
    359 vector<string> Per::string_split(string terms_str, string spliting_tag)
    360 {
    361     vector<string> feat_vec;
    362     size_t term_beg_pos = 0;
    363     size_t term_end_pos = 0;
    364     while ((term_end_pos = terms_str.find_first_of(spliting_tag, term_beg_pos)) != string::npos) 
    365     {
    366         if (term_end_pos > term_beg_pos) 
    367         {
    368             string term_str = terms_str.substr(term_beg_pos, term_end_pos - term_beg_pos);
    369             feat_vec.push_back(term_str);
    370         }
    371         term_beg_pos = term_end_pos + 1;
    372     }
    373     if (term_beg_pos < terms_str.size()) 
    374     {
    375         string end_str = terms_str.substr(term_beg_pos);
    376         feat_vec.push_back(end_str);
    377     }
    378     return feat_vec;
    379 }
    380 
    381 sparse_feat Per::product_pair_feature(const sparse_feat &samp_feat)
    382 {
    383     sparse_feat pair_sparse_feat;
    384     for (int i=0;i!=samp_feat.id_vec.size();i++)
    385     {
    386         int feat_id=samp_feat.id_vec[i];
    387         pair_sparse_feat.id_vec.push_back(feat_id);
    388         float feat_value=samp_feat.value_vec[i];
    389         pair_sparse_feat.value_vec.push_back(2-feat_value);
    390 
    391     }
    392 
    393     return pair_sparse_feat;
    394 
    395 }

      3.代码说明
      上述算法是后续代码的框架,比如load_model(), save_model(), update_online()等都是通用的,只是在不同的分类器,迭代侧率与损失函数不一样而已(算法关键),在感知机分类器我们迭代策略在使用update_online_per()函数,在计算损失函数使用calc_loss_per()函数。因此,你可以根据自己的需求,在上述算法框架上增加不同的准则策略。

      4.模型的训练与预测

      感知模型训练主函数Demo

      

    View Code
    View Code 
    
    #include <cstdlib>
    #include <iostream>
    #include <cstring>
    #include "Per.h"
    
    using namespace std;
    
    
    void print_help() 
    {
        cout << "\nPer training module, " << VERSION << ", " << VERSION_DATE << "\n\n"
            << "usage: per_train [options] training_file model_file [pre_model_file]\n\n"
            << "options: -h        -> help\n"
            << "         -n int    -> maximal iteration loops (default 200)\n"
            << "         -m double -> minimal loss value decrease (default 1e-03)\n"
            << "         -r double -> regularization parameter lambda of gaussian prior (default 0)\n"        
            << "         -l float  -> learning rate (default 1.0)\n"
            << "         -a        -> 0: final weight (default)\n"
            << "                   -> 1: average weights of all iteration loops\n"
            << "         -u [0,1]  -> 0: initial training model (default)\n"
            << "                   -> 1: updating model (pre_model_file is needed)\n" 
            << endl;
    }
    
    void read_parameters(int argc, char *argv[], char *training_file, char *model_file, 
                            int *criter, int *max_loop, double *loss_thrd, float *learn_rate, float *lambda,
                            int *avg, int *update, char *pre_model_file) {
        // set default options
        *criter = 0;
        *max_loop = 200;
        *loss_thrd = 1e-3;
        *learn_rate = 1.0;
        *lambda = 0.0;
        *avg = 0;
        *update = 0;
        int i;
        for (i = 1; (i<argc) && (argv[i])[0]=='-'; i++) {
            switch ((argv[i])[1]) {
                case 'h':
                    print_help();
                    exit(0);
                case 'c':
                    *criter = atoi(argv[++i]);
                    break;
                case 'n':
                    *max_loop = atoi(argv[++i]);
                    break;
                case 'm':
                    *loss_thrd = atof(argv[++i]);
                    break;
                case 'l':
                    *learn_rate = (float)atof(argv[++i]);
                    break;
                case 'r':
                    *lambda = (float)atof(argv[++i]);
                    break;
                case 'a':
                    *avg = atoi(argv[++i]);
                    break;
                case 'u':
                    *update = atoi(argv[++i]);
                    break;
                default:
                    cout << "Unrecognized option: " << argv[i] << "!" << endl;
                    print_help();
                    exit(0);
            }
        }
        
        if ((i+1)>=argc) {
            cout << "Not enough parameters!" << endl;
            print_help();
            exit(0);
        }
        strcpy (training_file, argv[i]);
        strcpy (model_file, argv[i+1]);
        if (*update) {
            if ((i+2)>=argc) {
                cout << "Previous model file is needed in update mode!" << endl;
                print_help();
                exit(0);
            }
            strcpy (pre_model_file, argv[i+2]);
        }
    }
    
    int per_train(int argc, char *argv[])
    {
        char training_file[200];
        char model_file[200];
        int criter;
        int max_loop;
        double loss_thrd;
        float learn_rate;
        float lambda;
        int avg;
        int update;
        char pre_model_file[200];
        read_parameters(argc, argv, training_file, model_file, &criter, &max_loop, &loss_thrd, &learn_rate, &lambda, &avg, &update, pre_model_file);
        
        Per per;
        per.load_training_file(training_file);
        if (update) {
            per.load_model(pre_model_file);
        }
        else {
            per.init_omega();    
        }
        per.train_online(criter, max_loop, loss_thrd, learn_rate, lambda, avg);
        per.save_model(model_file);
        return 0;
    }
    
    int main(int argc, char *argv[])
    {
        return per_train(argc, argv);
    }

      感知机模型预测主函数Demo

    View Code
    View Code 
    
    View Code 
    
    #include <cstdlib>
    #include <iostream>
    #include <cstring>
    #include "Per.h"
    
    using namespace std;
    
    
    void print_help()
     {
        cout << "\nOpenPR-LDF classification module, " << VERSION << ", " << VERSION_DATE << "\n\n"
            << "usage: ldf_classify [options] testing_file model_file output_file\n\n"
            << "options: -h        -> help\n"
            << "         -f [0..2] -> 0: only output class label (default)\n"
            << "                   -> 1: output class label with log-likelihood (weighted sum)\n"
            << "                   -> 2: output class label with soft probability\n"
            << endl;
    }
    
    void read_parameters(int argc, char *argv[], char *testing_file, char *model_file, 
                            char *output_file, int *output_format) 
        {
        // set default options
        *output_format = 0;
        int i;
        for (i = 1; (i<argc) && (argv[i])[0]=='-'; i++) 
        {
            switch ((argv[i])[1]) 
            {
                case 'h':
                    print_help();
                    exit(0);
                case 'f':
                    *output_format = atoi(argv[++i]);
                    break;
                default:
                    cout << "Unrecognized option: " << argv[i] << "!" << endl;
                    print_help();
                    exit(0);
            }
        }
        
        if ((i+2)>=argc)
         {
            cout << "Not enough parameters!" << endl;
            print_help();
            exit(0);
        }
        strcpy(testing_file, argv[i]);
        strcpy(model_file, argv[i+1]);
        strcpy(output_file, argv[i+2]);
    }
    
    int per_classify(int argc, char *argv[])
    {
        char testing_file[200];
        char model_file[200];
        char output_file[200];
        int output_format;
        read_parameters(argc, argv, testing_file, model_file, output_file, &output_format);
        Per per;
        per.load_model(model_file);
        float acc = per.classify_testing_file(testing_file, output_file, output_format);
        cout << "Accuracy: " << acc << endl;
        return 0;
    }
    
    int main(int argc, char *argv[])
    {
        return per_classify(argc, argv);
    }
  • 相关阅读:
    【iCore1S 双核心板_ARM】例程六:WWDG看门狗实验——复位ARM
    【emWin】例程三十二:窗口对象———Progbar
    【iCore4 双核心板】DEMO V1.0 测试程序发布
    【iCore1S 双核心板_FPGA】例程二:GPIO输入实验——识别按键输入
    【iCore1S 双核心板_ARM】例程五:IWDG看门狗实验——复位ARM
    【emWin】例程三十一:窗口对象——Multipage
    【iCore1S 双核心板_ARM】例程四:USART通信实验——通过命令控制LED
    【iCore1S 双核心板_FPGA】例程一:GPIO输出实验——点亮LED
    【iCore1S 双核心板_ARM】例程三:EXTI中断输入实验——读取ARM按键状态
    【emWin】例程三十:窗口对象——Multiedit
  • 原文地址:https://www.cnblogs.com/wangbogong/p/3062624.html
Copyright © 2011-2022 走看看