source: orange/source/orange/liblinear_interface.cpp @ 11016:e9a6850a6818

Revision 11016:e9a6850a6818, 11.8 KB checked in by Ales Erjavec <ales.erjavec@…>, 17 months ago (diff)

Fix the weight vector exposed to python if liblinear internally reorders the values of the binary class.

(references #1239)

Line 
1/*
2    This file is part of Orange.
3
4    Copyright 1996-2011 Faculty of Computer and Information Science, University of Ljubljana
5    Contact: janez.demsar@fri.uni-lj.si
6
7    Orange is free software: you can redistribute it and/or modify
8    it under the terms of the GNU General Public License as published by
9    the Free Software Foundation, either version 3 of the License, or
10    (at your option) any later version.
11
12    Orange is distributed in the hope that it will be useful,
13    but WITHOUT ANY WARRANTY; without even the implied warranty of
14    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15    GNU General Public License for more details.
16
17    You should have received a copy of the GNU General Public License
18    along with Orange.  If not, see <http://www.gnu.org/licenses/>.
19*/
20
21#include <iostream>
22#include <sstream>
23
24#include <math.h>
25#include <stdio.h>
26#include <stdlib.h>
27#include <string.h>
28#include <stdarg.h>
29#include "liblinear_interface.ppp"
30
31#define Malloc(type,n) (type *) malloc((n)*sizeof(type))
32
33// Defined in linear.cpp. If a new solver is added this should be updated.
34
35static const char *solver_type_table[]=
36{
37    "L2R_LR", "L2R_L2LOSS_SVC_DUAL", "L2R_L2LOSS_SVC", "L2R_L1LOSS_SVC_DUAL", "MCSVM_CS",
38    "L1R_L2LOSS_SVC", "L1R_LR", "L2R_LR_DUAL", NULL
39};
40
41/*
42 *The folowing load save functions are used for orange pickling
43 */
44
45/*
46 * Save the model to an std::ostream. This is a modified `save_model` function
47 * from `linear.cpp` in LIBLINEAR package.
48 */
49int linear_save_model_alt(ostream &stream, struct model *model_)
50{
51    int i;
52    int nr_feature=model_->nr_feature;
53    int n;
54    const parameter& param = model_->param;
55
56    if(model_->bias>=0)
57        n=nr_feature+1;
58    else
59        n=nr_feature;
60
61    int nr_classifier;
62    if(model_->nr_class==2 && model_->param.solver_type != MCSVM_CS)
63        nr_classifier=1;
64    else
65        nr_classifier=model_->nr_class;
66
67    stream.precision(17);
68
69    stream << "solver_type " << solver_type_table[param.solver_type] << endl;
70    stream << "nr_class " << model_->nr_class << endl;
71    stream << "label";
72    for(i=0; i<model_->nr_class; i++)
73        stream << " " << model_->label[i];
74    stream << endl;
75
76    stream << "nr_feature " << nr_feature << endl;
77
78    stream << "bias " << model_->bias << endl;
79
80    stream << "w" << endl;
81    for(i=0; i<n; i++)
82    {
83        int j;
84        for(j=0; j<nr_classifier; j++)
85            stream << model_->w[i*nr_classifier+j] << " ";
86        stream << endl;
87    }
88
89    if (stream.good())
90        return 0;
91    else
92        return -1;
93}
94
95/*
96 * Save linear model into a std::string.
97 */
98int linear_save_model_alt(string &buffer, struct model *model_)
99{
100    std::ostringstream strstream;
101    int ret = linear_save_model_alt(strstream, model_);
102    buffer = strstream.rdbuf()->str();
103    return ret;
104}
105
106/*
107 * Load a linear model from std::istream. This is a modified `load_model`
108 * function from `linear.cpp` in LIBLINEAR package.
109 */
110struct model *linear_load_model_alt(istream &stream)
111{
112    int i;
113    int nr_feature;
114    int n;
115    int nr_class;
116    double bias;
117    model *model_ = Malloc(model,1);
118    parameter& param = model_->param;
119
120    model_->label = NULL;
121
122    char cmd[81];
123    stream.width(80);
124    while(stream.good())
125    {
126        stream >> cmd;
127        if(strcmp(cmd, "solver_type")==0)
128        {
129            stream >> cmd;
130            int i;
131            for(i=0;solver_type_table[i];i++)
132            {
133                if(strcmp(solver_type_table[i],cmd)==0)
134                {
135                    param.solver_type=i;
136                    break;
137                }
138            }
139            if(solver_type_table[i] == NULL)
140            {
141                fprintf(stderr,"unknown solver type.\n");
142                free(model_->label);
143                free(model_);
144                return NULL;
145            }
146        }
147        else if(strcmp(cmd,"nr_class")==0)
148        {
149            stream >> nr_class;
150            model_->nr_class=nr_class;
151        }
152        else if(strcmp(cmd,"nr_feature")==0)
153        {
154            stream >> nr_feature;
155            model_->nr_feature=nr_feature;
156        }
157        else if(strcmp(cmd,"bias")==0)
158        {
159            stream >> bias;
160            model_->bias=bias;
161        }
162        else if(strcmp(cmd,"w")==0)
163        {
164            break;
165        }
166        else if(strcmp(cmd,"label")==0)
167        {
168            int nr_class = model_->nr_class;
169            model_->label = Malloc(int, nr_class);
170            for(int i=0;i<nr_class;i++)
171                stream >> model_->label[i];
172        }
173        else
174        {
175            fprintf(stderr,"unknown text in model file: [%s]\n",cmd);
176            free(model_->label);
177            free(model_);
178            return NULL;
179        }
180    }
181
182    nr_feature=model_->nr_feature;
183    if(model_->bias>=0)
184        n=nr_feature+1;
185    else
186        n=nr_feature;
187
188    int nr_classifier;
189    if(nr_class==2 && param.solver_type != MCSVM_CS)
190        nr_classifier = 1;
191    else
192        nr_classifier = nr_class;
193
194    model_->w=Malloc(double, n*nr_classifier);
195    for(i=0; i<n; i++)
196    {
197        int j;
198        for(j=0; j<nr_classifier; j++)
199            stream >> model_->w[i*nr_classifier+j];
200    }
201    if (stream.fail())
202        return NULL;
203    else
204        return model_;
205}
206
207/*
208 * Load a linear model from a std:string.
209 */
210struct model *linear_load_model_alt(string &buffer)
211{
212    std::istringstream str_stream(buffer);
213    str_stream.exceptions(ios::failbit | ios::badbit);
214    return linear_load_model_alt(str_stream);
215}
216
217struct NodeSort{
218    bool operator () (const feature_node &lhs, const feature_node &rhs){
219        return lhs.index < rhs.index;
220    }
221};
222
223int countFeatures(const TExample &ex, bool includeMeta, bool includeRegular){
224    int count = 1;
225    if (includeRegular)
226        for (TExample::iterator i=ex.begin(); i!=ex.end(); i++)
227            if ((i->varType==TValue::INTVAR || i->varType==TValue::FLOATVAR) && i->isRegular() && i!=&ex.getClass())
228                count++;
229    if (includeMeta)
230        for (TMetaValues::const_iterator i=ex.meta.begin(); i!=ex.meta.end(); i++)
231            if ((i->second.varType==TValue::INTVAR || i->second.varType==TValue::FLOATVAR) && i->second.isRegular())
232                count++;
233    return count;
234}
235
236feature_node *feature_nodeFromExample(const TExample &ex, double bias){
237    int n_nodes = countFeatures(ex, false, true);
238
239    if (bias >= 0.0)
240        n_nodes++;
241
242    feature_node *nodes = new feature_node[n_nodes];
243    feature_node *ptr = nodes;
244
245    int index = 1;
246
247    for (TExample::iterator i=ex.begin(); i!=ex.end(); i++)
248        if (i!=&ex.getClass()){
249            if ((i->varType==TValue::INTVAR || (i->varType==TValue::FLOATVAR && (*i==*i))) && i->isRegular()){
250                if (i->varType==TValue::INTVAR)
251                    ptr->value = (int) *i;
252                else
253                    ptr->value = (float) *i;
254                ptr->index = index;
255                ptr++;
256            }
257            index++;
258        }
259
260    if (bias >= 0.0)
261    {
262        ptr->value = bias;
263        ptr->index = index;
264        ptr++;
265    }
266
267    ptr->index = -1;
268    return nodes;
269}
270
271problem *problemFromExamples(PExampleGenerator examples, double bias){
272    problem *prob = new problem;
273    prob->l = examples->numberOfExamples();
274    prob->n = examples->domain->attributes->size();
275
276    if (bias >= 0)
277        prob->n++;
278
279    prob->x = new feature_node* [prob->l];
280    prob->y = new int [prob->l];
281    prob->bias = bias;
282    feature_node **ptrX = prob->x;
283    int *ptrY = prob->y;
284    PEITERATE(iter, examples){
285        *ptrX = feature_nodeFromExample(*iter, bias);
286        *ptrY = (int) (*iter).getClass();
287        ptrX++;
288        ptrY++;
289    }
290    return prob;
291}
292
293void destroy_problem(problem *prob){
294    for (int i=0; i<prob->l; i++)
295        delete[] prob->x[i];
296    delete[] prob->x;
297    delete[] prob->y;
298}
299
300static void dont_print_string(const char *s){}
301
302TLinearLearner::TLinearLearner(){
303    solver_type = L2R_LR;
304    eps = 0.01f;
305    C = 1;
306    bias = -1.0;
307    set_print_string_function(&dont_print_string);
308}
309
310PClassifier TLinearLearner::operator()(PExampleGenerator examples, const int &weight){
311    parameter *param = new parameter;
312    param->solver_type = solver_type;
313    param->eps = eps;
314    param->C = C;
315    param->nr_weight = 0;
316    param->weight_label = NULL;
317    param->weight = NULL;
318
319    PVariable classVar = examples->domain->classVar;
320    if (!classVar)
321        raiseError("classVar expected");
322    if (classVar->varType != TValue::INTVAR)
323        raiseError("Discrete class expected");
324
325    problem *prob = problemFromExamples(examples, bias);
326
327    const char * error_msg = check_parameter(prob, param);
328    if (error_msg){
329        delete param;
330        destroy_problem(prob);
331        raiseError("LIBLINEAR error: %s", error_msg);
332    }
333    /* The solvers in liblinear use rand() function.
334     * To make the results reproducible we set the seed from the data table's
335     * crc
336     */
337    PExampleTable extable(examples);
338    srand(extable->checkSum(false));
339
340    model *model = train(prob, param);
341    destroy_problem(prob);
342
343    return PClassifier(mlnew TLinearClassifier(examples->domain->classVar, examples, model));
344}
345
346TLinearClassifier::TLinearClassifier(const PVariable &var, PExampleTable _examples, struct model *_model){
347    classVar = var;
348    domain = _examples->domain;
349    examples = _examples;
350    linmodel = _model;
351    bias = _model->bias;
352    dbias = _model->bias;
353
354    computesProbabilities = check_probability_model(linmodel) != 0;
355    // Number of class values
356    int nr_values = this->get_nr_values();
357
358    /* Number of liblinear classifiers (if some class values are missing
359     * from the training set they are not present in the liblinear model).
360     */
361    int nr_classifier = linmodel->nr_class;
362    if (linmodel->nr_class == 2 && linmodel->param.solver_type != MCSVM_CS)
363    {
364        nr_classifier = 1;
365    }
366
367    // Number of weight vectors exposed in orange.
368    int nr_orange_weights = nr_values;
369    if (nr_values == 2 && linmodel->param.solver_type != MCSVM_CS)
370    {
371        nr_orange_weights = 1;
372    }
373
374    int nr_feature = linmodel->nr_feature;
375
376    if (linmodel->bias >= 0.0)
377    {
378        nr_feature++;
379    }
380
381    int* labels = new int[linmodel->nr_class];
382    get_labels(linmodel, labels);
383
384    // Initialize nr_orange_weights vectors
385    weights = mlnew TFloatListList(nr_orange_weights);
386    for (int i = 0; i < nr_orange_weights; i++)
387    {
388        weights->at(i) = mlnew TFloatList(nr_feature, 0.0f);
389    }
390
391    if (nr_classifier > 1)
392    {
393        for (int i = 0; i < nr_classifier; i++)
394        {
395            for (int j = 0; j < nr_feature; j++)
396            {
397                weights->at(labels[i])->at(j) = \
398                        linmodel->w[j*nr_classifier + i];
399            }
400        }
401}
402    else
403    {
404        /* If the order of the liblinear internaly stored classes
405         * is different from the order of orange's class values,
406         * we reverse the weight vector.
407         */
408        float factor = (labels[0] == 0)? 1.0f : -1.0f;
409
410        for (int j = 0; j < nr_feature; j++)
411        {
412            if (nr_orange_weights > 1)
413            {
414               /* There are more than 2 orange class values. This means
415                * there were no instances for one or more classed in the training
416                * data set.
417                */
418                weights->at(labels[0])->at(j) = linmodel->w[j];
419                weights->at(labels[1])->at(j) = - linmodel->w[j];
420            }
421            else
422            {
423                weights->at(0)->at(j) = factor * linmodel->w[j];
424            }
425        }
426    }
427    delete[] labels;
428}
429
430TLinearClassifier::~TLinearClassifier(){
431    if (linmodel)
432        free_and_destroy_model(&linmodel);
433}
434
435/* Return the number of discrete class values, or raise an error
436 * if the class_var is not discrete.
437 */
438int TLinearClassifier::get_nr_values()
439{
440    int nr_values = 0;
441    TEnumVariable * enum_var = NULL;
442    enum_var = dynamic_cast<TEnumVariable*>(classVar.getUnwrappedPtr());
443    if (enum_var)
444    {
445        nr_values = enum_var->noOfValues();
446    }
447    else
448    {
449        raiseError("Discrete class expected.");
450    }
451    return nr_values;
452}
453
454PDistribution TLinearClassifier::classDistribution(const TExample &example){
455    TExample new_example(domain, example);
456    int numClass = get_nr_class(linmodel);
457    map<int, int> indexMap;
458    feature_node *x = feature_nodeFromExample(new_example, bias);
459
460    int *labels = new int [numClass];
461    get_labels(linmodel, labels);
462
463    double *prob_est = new double [numClass];
464    predict_probability(linmodel, x, prob_est);
465
466    PDistribution dist = TDistribution::create(classVar);
467    for (int i=0; i<numClass; i++)
468        dist->setint(labels[i], prob_est[i]);
469
470    delete[] x;
471    delete[] labels;
472    delete[] prob_est;
473    return dist;
474}
475
476TValue TLinearClassifier::operator () (const TExample &example){
477    TExample new_example(domain, example);
478    int numClass = get_nr_class(linmodel);
479    map<int, int> indexMap;
480    feature_node *x = feature_nodeFromExample(new_example, bias);
481
482    int predict_label = predict(linmodel, x);
483    delete[] x;
484    return TValue(predict_label);
485}
486
Note: See TracBrowser for help on using the repository browser.