source: orange/source/orange/liblinear_interface.cpp @ 11703:9b8d8ab7820c

Revision 11703:9b8d8ab7820c, 12.2 KB checked in by janezd <janez.demsar@…>, 7 months ago (diff)

Removed the GPL copyright notice from all files except orangeqt.

RevLine 
[8978]1#include <iostream>
2#include <sstream>
3
4#include <math.h>
5#include <stdio.h>
6#include <stdlib.h>
7#include <string.h>
8#include <stdarg.h>
[11610]9#include <assert.h>
10
[8978]11#include "liblinear_interface.ppp"
12
13#define Malloc(type,n) (type *) malloc((n)*sizeof(type))
14
15// Defined in linear.cpp. If a new solver is added this should be updated.
16
17static const char *solver_type_table[]=
18{
19    "L2R_LR", "L2R_L2LOSS_SVC_DUAL", "L2R_L2LOSS_SVC", "L2R_L1LOSS_SVC_DUAL", "MCSVM_CS",
[11670]20    "L1R_L2LOSS_SVC", "L1R_LR", "L2R_LR_DUAL",
21
22    #ifndef WITH_API_LIBLINEAR18
23        "", "", "",
24        "L2R_L2LOSS_SVR", "L2R_L2LOSS_SVR_DUAL", "L2R_L1LOSS_SVR_DUAL",
25    #endif
26
27    NULL
[8978]28};
29
30/*
31 *The folowing load save functions are used for orange pickling
32 */
33
34/*
35 * Save the model to an std::ostream. This is a modified `save_model` function
36 * from `linear.cpp` in LIBLINEAR package.
37 */
38int linear_save_model_alt(ostream &stream, struct model *model_)
39{
40    int i;
41    int nr_feature=model_->nr_feature;
42    int n;
43    const parameter& param = model_->param;
44
45    if(model_->bias>=0)
46        n=nr_feature+1;
47    else
48        n=nr_feature;
49
50    int nr_classifier;
51    if(model_->nr_class==2 && model_->param.solver_type != MCSVM_CS)
52        nr_classifier=1;
53    else
54        nr_classifier=model_->nr_class;
55
56    stream.precision(17);
57
58    stream << "solver_type " << solver_type_table[param.solver_type] << endl;
59    stream << "nr_class " << model_->nr_class << endl;
60    stream << "label";
61    for(i=0; i<model_->nr_class; i++)
62        stream << " " << model_->label[i];
63    stream << endl;
64
65    stream << "nr_feature " << nr_feature << endl;
66
67    stream << "bias " << model_->bias << endl;
68
69    stream << "w" << endl;
70    for(i=0; i<n; i++)
71    {
72        int j;
73        for(j=0; j<nr_classifier; j++)
74            stream << model_->w[i*nr_classifier+j] << " ";
75        stream << endl;
76    }
77
78    if (stream.good())
79        return 0;
80    else
81        return -1;
82}
83
84/*
85 * Save linear model into a std::string.
86 */
87int linear_save_model_alt(string &buffer, struct model *model_)
88{
89    std::ostringstream strstream;
90    int ret = linear_save_model_alt(strstream, model_);
91    buffer = strstream.rdbuf()->str();
92    return ret;
93}
94
95/*
96 * Load a linear model from std::istream. This is a modified `load_model`
97 * function from `linear.cpp` in LIBLINEAR package.
98 */
99struct model *linear_load_model_alt(istream &stream)
100{
101    int i;
102    int nr_feature;
103    int n;
104    int nr_class;
105    double bias;
106    model *model_ = Malloc(model,1);
107    parameter& param = model_->param;
108
109    model_->label = NULL;
110
111    char cmd[81];
112    stream.width(80);
113    while(stream.good())
114    {
115        stream >> cmd;
116        if(strcmp(cmd, "solver_type")==0)
117        {
118            stream >> cmd;
119            int i;
120            for(i=0;solver_type_table[i];i++)
121            {
122                if(strcmp(solver_type_table[i],cmd)==0)
123                {
124                    param.solver_type=i;
125                    break;
126                }
127            }
128            if(solver_type_table[i] == NULL)
129            {
130                fprintf(stderr,"unknown solver type.\n");
131                free(model_->label);
132                free(model_);
133                return NULL;
134            }
135        }
136        else if(strcmp(cmd,"nr_class")==0)
137        {
138            stream >> nr_class;
139            model_->nr_class=nr_class;
140        }
141        else if(strcmp(cmd,"nr_feature")==0)
142        {
143            stream >> nr_feature;
144            model_->nr_feature=nr_feature;
145        }
146        else if(strcmp(cmd,"bias")==0)
147        {
148            stream >> bias;
149            model_->bias=bias;
150        }
151        else if(strcmp(cmd,"w")==0)
152        {
153            break;
154        }
155        else if(strcmp(cmd,"label")==0)
156        {
157            int nr_class = model_->nr_class;
158            model_->label = Malloc(int, nr_class);
159            for(int i=0;i<nr_class;i++)
160                stream >> model_->label[i];
161        }
162        else
163        {
164            fprintf(stderr,"unknown text in model file: [%s]\n",cmd);
165            free(model_->label);
166            free(model_);
167            return NULL;
168        }
169    }
170
171    nr_feature=model_->nr_feature;
172    if(model_->bias>=0)
173        n=nr_feature+1;
174    else
175        n=nr_feature;
176
177    int nr_classifier;
178    if(nr_class==2 && param.solver_type != MCSVM_CS)
179        nr_classifier = 1;
180    else
181        nr_classifier = nr_class;
182
183    model_->w=Malloc(double, n*nr_classifier);
184    for(i=0; i<n; i++)
185    {
186        int j;
187        for(j=0; j<nr_classifier; j++)
188            stream >> model_->w[i*nr_classifier+j];
189    }
190    if (stream.fail())
191        return NULL;
192    else
193        return model_;
194}
195
196/*
197 * Load a linear model from a std:string.
198 */
199struct model *linear_load_model_alt(string &buffer)
200{
201    std::istringstream str_stream(buffer);
[10771]202    str_stream.exceptions(ios::failbit | ios::badbit);
[8978]203    return linear_load_model_alt(str_stream);
204}
205
206struct NodeSort{
207    bool operator () (const feature_node &lhs, const feature_node &rhs){
208        return lhs.index < rhs.index;
209    }
210};
211
212int countFeatures(const TExample &ex, bool includeMeta, bool includeRegular){
213    int count = 1;
214    if (includeRegular)
215        for (TExample::iterator i=ex.begin(); i!=ex.end(); i++)
216            if ((i->varType==TValue::INTVAR || i->varType==TValue::FLOATVAR) && i->isRegular() && i!=&ex.getClass())
217                count++;
218    if (includeMeta)
219        for (TMetaValues::const_iterator i=ex.meta.begin(); i!=ex.meta.end(); i++)
220            if ((i->second.varType==TValue::INTVAR || i->second.varType==TValue::FLOATVAR) && i->second.isRegular())
221                count++;
222    return count;
223}
224
[10771]225feature_node *feature_nodeFromExample(const TExample &ex, double bias){
226    int n_nodes = countFeatures(ex, false, true);
227
228    if (bias >= 0.0)
229        n_nodes++;
230
231    feature_node *nodes = new feature_node[n_nodes];
[8978]232    feature_node *ptr = nodes;
[10771]233
[8978]234    int index = 1;
[10771]235
236    for (TExample::iterator i=ex.begin(); i!=ex.end(); i++)
237        if (i!=&ex.getClass()){
238            if ((i->varType==TValue::INTVAR || (i->varType==TValue::FLOATVAR && (*i==*i))) && i->isRegular()){
239                if (i->varType==TValue::INTVAR)
240                    ptr->value = (int) *i;
241                else
242                    ptr->value = (float) *i;
243                ptr->index = index;
244                ptr++;
245            }
246            index++;
247        }
248
249    if (bias >= 0.0)
250    {
251        ptr->value = bias;
252        ptr->index = index;
253        ptr++;
[8978]254    }
[10771]255
[8978]256    ptr->index = -1;
257    return nodes;
258}
259
[10771]260problem *problemFromExamples(PExampleGenerator examples, double bias){
[8978]261    problem *prob = new problem;
262    prob->l = examples->numberOfExamples();
[10771]263    prob->n = examples->domain->attributes->size();
264
265    if (bias >= 0)
266        prob->n++;
267
[8978]268    prob->x = new feature_node* [prob->l];
[11670]269
270    #ifndef WITH_API_LIBLINEAR18
271        prob->y = new double [prob->l];
272    #else
273        prob->y = new int [prob->l];
274    #endif
275
[10771]276    prob->bias = bias;
[8978]277    feature_node **ptrX = prob->x;
[11670]278
279    #ifndef WITH_API_LIBLINEAR18
280        double *ptrY = prob->y;
281    #else
282        int *ptrY = prob->y;
283    #endif
284
[8978]285    PEITERATE(iter, examples){
[10771]286        *ptrX = feature_nodeFromExample(*iter, bias);
[11670]287
288        #ifndef WITH_API_LIBLINEAR18
289            *ptrY = (double) (*iter).getClass().intV;
290        #else
291            *ptrY = (int) (*iter).getClass();
292        #endif
293
[8978]294        ptrX++;
295        ptrY++;
296    }
297    return prob;
298}
299
300void destroy_problem(problem *prob){
[11610]301    for (int i = 0; i < prob->l; i++)
[8978]302        delete[] prob->x[i];
303    delete[] prob->x;
304    delete[] prob->y;
305}
306
307static void dont_print_string(const char *s){}
308
[11610]309
310/*
311 * Extract feature weights from a LIBLINEAR model.
312 * The number of class values must be provided.
313 */
314
315TFloatListList * extract_feature_weights(model * model, int nr_class_values) {
316    /* Number of liblinear classifiers.
317     *
318     * NOTE: If some class values do not have any data instances in
319     * the training set they are not present in the liblinear model
320     * so this number might be different than nr_class_values.
321     */
322    int nr_classifier = model->nr_class;
323    if (model->nr_class == 2 && model->param.solver_type != MCSVM_CS) {
324        // model contains a single weight vector
325        nr_classifier = 1;
326    }
327
328    // Number of weight vectors to return.
329    int nr_orange_weights = nr_class_values;
330    if (nr_class_values == 2 && model->param.solver_type != MCSVM_CS) {
331        nr_orange_weights = 1;
332    }
333
334    assert(nr_orange_weights >= nr_classifier);
335
336    int nr_feature = model->nr_feature;
337
338    if (model->bias >= 0.0){
339        nr_feature++;
340    }
341
342    int* labels = new int[model->nr_class];
343    get_labels(model, labels);
344
345    // Initialize the weight matrix (nr_orange_weights x nr_features).
346    TFloatListList * weights = mlnew TFloatListList(nr_orange_weights);
347    for (int i = 0; i < nr_orange_weights; i++){
348        weights->at(i) = mlnew TFloatList(nr_feature, 0.0f);
349    }
350
351    if (nr_classifier > 1) {
352        /*
353         * NOTE: If some class was missing from the training data set
354         * (had no instances) its weight vector will be left initialized
355         * to 0
356         */
357        for (int i = 0; i < nr_classifier; i++) {
358            for (int j = 0; j < nr_feature; j++) {
359                weights->at(labels[i])->at(j) = model->w[j * nr_classifier + i];
360            }
361        }
362    } else {
363        for (int j = 0; j < nr_feature; j++) {
364            if (nr_orange_weights > 1) {
365                /* There were more than 2 orange class values. This means
366                 * there were no instances for one or more classed in the
367                 * training data set. We cannot simply leave the 'negative'
368                 * class vector as zero because we would lose information
369                 * which class was used (i.e. we could not make a proper
370                 * negative classification using the weights).
371                 */
372                weights->at(labels[0])->at(j) = model->w[j];
373                weights->at(labels[1])->at(j) = - model->w[j];
374            } else {
375                weights->at(0)->at(j) = model->w[j];
376            }
377        }
378    }
379
380    delete[] labels;
381
382    return weights;
383}
384
385
[8978]386TLinearLearner::TLinearLearner(){
387    solver_type = L2R_LR;
388    eps = 0.01f;
[10771]389    C = 1;
390    bias = -1.0;
[8978]391    set_print_string_function(&dont_print_string);
392}
393
[11610]394
[8978]395PClassifier TLinearLearner::operator()(PExampleGenerator examples, const int &weight){
[11611]396    PDomain domain = examples->domain;
397
398    if (!domain->classVar) {
399        raiseError("classVar expected");
400    }
401
402    if (domain->classVar->varType != TValue::INTVAR) {
403        raiseError("Discrete class expected");
404    }
405
[8978]406    parameter *param = new parameter;
407    param->solver_type = solver_type;
408    param->eps = eps;
409    param->C = C;
410    param->nr_weight = 0;
411    param->weight_label = NULL;
412    param->weight = NULL;
[10771]413
[11670]414    #ifndef WITH_API_LIBLINEAR18
415        param->p = NULL;
416    #endif
417
[11610]418    // Shallow copy of examples.
419    PExampleTable train_data = mlnew TExampleTable(examples, /* owns= */ false);
420
421    /*
422     * Sort the training instances by class.
423     * This is necessary because LIBLINEAR's class/label/weight order
424     * is defined by the order of labels encountered in the training
425     * data. By sorting we make sure it matches classVar.values order.
426     */
427    vector<int> sort_column(domain->variables->size() - 1);
428    train_data->sort(sort_column);
429
430    problem *prob = problemFromExamples(train_data, bias);
[10771]431
[8978]432    const char * error_msg = check_parameter(prob, param);
433    if (error_msg){
434        delete param;
435        destroy_problem(prob);
[10948]436        raiseError("LIBLINEAR error: %s", error_msg);
[8978]437    }
[10683]438    /* The solvers in liblinear use rand() function.
[10948]439     * To make the results reproducible we set the seed from the data table's
[11610]440     * crc.
[10683]441     */
[11610]442    srand(train_data->checkSum(false));
[10683]443
[8978]444    model *model = train(prob, param);
445    destroy_problem(prob);
446
[11610]447    return PClassifier(mlnew TLinearClassifier(domain, model));
[8978]448}
449
[11610]450
451/*
452 * Construct a TLinearClassifer given a domain and a trained LIBLINEAR
453 * constructed model.
454 */
455
456TLinearClassifier::TLinearClassifier(PDomain domain, struct model * model) : TClassifierFD(domain) {
457    linmodel = model;
458    bias = model->bias;
459    dbias = model->bias;
[10771]460
[9409]461    computesProbabilities = check_probability_model(linmodel) != 0;
[10948]462
[11610]463    weights = extract_feature_weights(model, get_nr_values());
[8978]464}
465
[11610]466
467TLinearClassifier::~TLinearClassifier() {
[8978]468    if (linmodel)
469        free_and_destroy_model(&linmodel);
470}
471
[10948]472/* Return the number of discrete class values, or raise an error
473 * if the class_var is not discrete.
474 */
475int TLinearClassifier::get_nr_values()
476{
477    int nr_values = 0;
478    TEnumVariable * enum_var = NULL;
479    enum_var = dynamic_cast<TEnumVariable*>(classVar.getUnwrappedPtr());
[11610]480    if (enum_var) {
[10948]481        nr_values = enum_var->noOfValues();
482    }
[11610]483    else {
[10948]484        raiseError("Discrete class expected.");
485    }
[10951]486    return nr_values;
[10948]487}
488
[8978]489PDistribution TLinearClassifier::classDistribution(const TExample &example){
[10675]490    TExample new_example(domain, example);
[8978]491    int numClass = get_nr_class(linmodel);
[11610]492
[10771]493    feature_node *x = feature_nodeFromExample(new_example, bias);
[8978]494
495    int *labels = new int [numClass];
496    get_labels(linmodel, labels);
497
498    double *prob_est = new double [numClass];
499    predict_probability(linmodel, x, prob_est);
500
501    PDistribution dist = TDistribution::create(classVar);
502    for (int i=0; i<numClass; i++)
503        dist->setint(labels[i], prob_est[i]);
504
505    delete[] x;
506    delete[] labels;
507    delete[] prob_est;
508    return dist;
509}
510
511TValue TLinearClassifier::operator () (const TExample &example){
[10675]512    TExample new_example(domain, example);
[8978]513    int numClass = get_nr_class(linmodel);
[11610]514
[10771]515    feature_node *x = feature_nodeFromExample(new_example, bias);
[8978]516
[11670]517    #ifndef WITH_API_LIBLINEAR18
518        double predict_label = predict(linmodel, x);
519    #else
520        int predict_label = predict(linmodel, x);
521    #endif
522
[8978]523    delete[] x;
[11670]524
525    #ifndef WITH_API_LIBLINEAR18
526        return TValue((int) predict_label);
527    #else
528        return TValue(predict_label);
529    #endif
[8978]530}
Note: See TracBrowser for help on using the repository browser.