9 votes

libsvm (C++) produit toujours la même prédiction

J'ai implémenté un wrapper OpenCV/C++ pour libsvm. Lorsque l'on fait une recherche de grille pour les paramètres SVM (noyau RBF), la prédiction toujours renvoie la même étiquette. J'ai créé des ensembles de données artificielles dont les données sont très facilement séparables (et j'ai essayé de prédire les données sur lesquelles je viens de m'entraîner), mais il renvoie toujours la même étiquette.

J'ai utilisé l'implémentation MATLAB de libsvm et obtenu une grande précision sur le même ensemble de données. Je dois faire quelque chose de mal en configurant le problème mais j'ai parcouru le README plusieurs fois et je n'arrive pas à trouver le problème.

Voici comment j'ai configuré le problème libsvm, où les données sont un Mat OpenCV :

    const int rowSize = data.rows;
    const int colSize = data.cols;

    this->_svmProblem = new svm_problem;
    std::memset(this->_svmProblem,0,sizeof(svm_problem));

    //dynamically allocate the X matrix...
    this->_svmProblem->x = new svm_node*[rowSize];
    for(int row = 0; row < rowSize; ++row)
        this->_svmProblem->x[row] = new svm_node[colSize + 1];

    //...and the y vector
    this->_svmProblem->y = new double[rowSize];
    this->_svmProblem->l = rowSize;

    for(int row = 0; row < rowSize; ++row)
    {
        for(int col = 0; col < colSize; ++col)
        {
            //set the index and the value. indexing starts at 1.
            this->_svmProblem->x[row][col].index = col + 1;
            double tempVal = (double)data.at<float>(row,col);
            this->_svmProblem->x[row][col].value = tempVal;
        }

        this->_svmProblem->x[row][colSize].index = -1;
        this->_svmProblem->x[row][colSize].value = 0;

        //add the label to the y array, and feature vector to X matrix
        double tempVal = (double)labels.at<float>(row);
        this->_svmProblem->y[row] = tempVal;
    }

}/*createProblem()*/

Voici comment je configure les paramètres, où svmParams est ma propre structure pour C/Gamma et autres :

    this->_svmParameter = new svm_parameter;
    std::memset(this->_svmParameter,0,sizeof(svm_parameter));
    this->_svmParameter->svm_type = svmParams.svmType;
    this->_svmParameter->kernel_type = svmParams.kernalType;
    this->_svmParameter->C = svmParams.C;
    this->_svmParameter->gamma = svmParams.gamma;
    this->_svmParameter->nr_weight = 0;
    this->_svmParameter->eps = 0.001;
    this->_svmParameter->degree = 1;
    this->_svmParameter->shrinking = 0;
    this->_svmParameter->probability = 1;
    this->_svmParameter->cache_size = 100;  

J'utilise la fonction de vérification des paramètres/problèmes fournie et aucune erreur n'est renvoyée.

Je m'entraîne alors comme tel :

this->_svmModel = svm_train(this->_svmProblem, this->_svmParameter);

Et puis prédire comme ça :

float pred = (float)svm_predict(this->_svmModel, x[i]);

Si quelqu'un pouvait m'indiquer où je me trompe, je l'apprécierais grandement. Merci.

EDIT :

En utilisant ce code j'ai imprimé le contenu du problème

for(int i = 0; i < rowSize; ++i)
    {
        std::cout << "[";
        for(int j = 0; j < colSize + 1; ++j)
        {
            std::cout << " (" << this->_svmProblem->x[i][j].index << ", " << this->_svmProblem->x[i][j].value << ")";
        }
        std::cout << "]" << " <" << this->_svmProblem->y[i] << ">" << std::endl;
    }

Voici le résultat :

[ (1, -1) (2, 0) (-1, 0)] <1>
[ (1, -0.92394) (2, 0) (-1, 0)] <1>
[ (1, -0.7532) (2, 0) (-1, 0)] <1>
[ (1, -0.75977) (2, 0) (-1, 0)] <1>
[ (1, -0.75337) (2, 0) (-1, 0)] <1>
[ (1, -0.76299) (2, 0) (-1, 0)] <1>
[ (1, -0.76527) (2, 0) (-1, 0)] <1>
[ (1, -0.74631) (2, 0) (-1, 0)] <1>
[ (1, -0.85153) (2, 0) (-1, 0)] <1>
[ (1, -0.72436) (2, 0) (-1, 0)] <1>
[ (1, -0.76485) (2, 0) (-1, 0)] <1>
[ (1, -0.72936) (2, 0) (-1, 0)] <1>
[ (1, -0.94004) (2, 0) (-1, 0)] <1>
[ (1, -0.92756) (2, 0) (-1, 0)] <1>
[ (1, -0.9688) (2, 0) (-1, 0)] <1>
[ (1, 0.05193) (2, 0) (-1, 0)] <3>
[ (1, -0.048488) (2, 0) (-1, 0)] <3>
[ (1, 0.070436) (2, 0) (-1, 0)] <3>
[ (1, 0.15191) (2, 0) (-1, 0)] <3>
[ (1, -0.07331) (2, 0) (-1, 0)] <3>
[ (1, 0.019786) (2, 0) (-1, 0)] <3>
[ (1, -0.072793) (2, 0) (-1, 0)] <3>
[ (1, 0.16157) (2, 0) (-1, 0)] <3>
[ (1, -0.057188) (2, 0) (-1, 0)] <3>
[ (1, -0.11187) (2, 0) (-1, 0)] <3>
[ (1, 0.15886) (2, 0) (-1, 0)] <3>
[ (1, -0.0701) (2, 0) (-1, 0)] <3>
[ (1, -0.17816) (2, 0) (-1, 0)] <3>
[ (1, 0.12305) (2, 0) (-1, 0)] <3>
[ (1, 0.058615) (2, 0) (-1, 0)] <3>
[ (1, 0.80203) (2, 0) (-1, 0)] <1>
[ (1, 0.734) (2, 0) (-1, 0)] <1>
[ (1, 0.9072) (2, 0) (-1, 0)] <1>
[ (1, 0.88061) (2, 0) (-1, 0)] <1>
[ (1, 0.83903) (2, 0) (-1, 0)] <1>
[ (1, 0.86604) (2, 0) (-1, 0)] <1>
[ (1, 1) (2, 0) (-1, 0)] <1>
[ (1, 0.77988) (2, 0) (-1, 0)] <1>
[ (1, 0.8578) (2, 0) (-1, 0)] <1>
[ (1, 0.79559) (2, 0) (-1, 0)] <1>
[ (1, 0.99545) (2, 0) (-1, 0)] <1>
[ (1, 0.78376) (2, 0) (-1, 0)] <1>
[ (1, 0.72177) (2, 0) (-1, 0)] <1>
[ (1, 0.72619) (2, 0) (-1, 0)] <1>
[ (1, 0.80149) (2, 0) (-1, 0)] <1>
[ (1, 0.092327) (2, -1) (-1, 0)] <2>
[ (1, 0.019054) (2, -1) (-1, 0)] <2>
[ (1, 0.15287) (2, -1) (-1, 0)] <2>
[ (1, -0.1471) (2, -1) (-1, 0)] <2>
[ (1, -0.068182) (2, -1) (-1, 0)] <2>
[ (1, -0.094567) (2, -1) (-1, 0)] <2>
[ (1, -0.17071) (2, -1) (-1, 0)] <2>
[ (1, -0.16646) (2, -1) (-1, 0)] <2>
[ (1, -0.030421) (2, -1) (-1, 0)] <2>
[ (1, 0.094346) (2, -1) (-1, 0)] <2>
[ (1, -0.14408) (2, -1) (-1, 0)] <2>
[ (1, 0.090025) (2, -1) (-1, 0)] <2>
[ (1, 0.043706) (2, -1) (-1, 0)] <2>
[ (1, 0.15065) (2, -1) (-1, 0)] <2>
[ (1, -0.11751) (2, -1) (-1, 0)] <2>
[ (1, -0.02324) (2, 1) (-1, 0)] <2>
[ (1, 0.0080356) (2, 1) (-1, 0)] <2>
[ (1, -0.17752) (2, 1) (-1, 0)] <2>
[ (1, 0.011135) (2, 1) (-1, 0)] <2>
[ (1, -0.029063) (2, 1) (-1, 0)] <2>
[ (1, 0.15398) (2, 1) (-1, 0)] <2>
[ (1, 0.097746) (2, 1) (-1, 0)] <2>
[ (1, 0.01018) (2, 1) (-1, 0)] <2>
[ (1, 0.015592) (2, 1) (-1, 0)] <2>
[ (1, -0.062793) (2, 1) (-1, 0)] <2>
[ (1, 0.014444) (2, 1) (-1, 0)] <2>
[ (1, -0.1205) (2, 1) (-1, 0)] <2>
[ (1, -0.18011) (2, 1) (-1, 0)] <2>
[ (1, 0.010521) (2, 1) (-1, 0)] <2>
[ (1, 0.036914) (2, 1) (-1, 0)] <2>

Ici, les données sont imprimées dans le format [ (index, valeur)...] étiquette.

L'ensemble de données artificielles que j'ai créé ne comporte que 3 classes, qui sont toutes facilement séparables avec une frontière de décision non linéaire. Chaque ligne est un vecteur de caractéristiques (observation), avec 2 caractéristiques (x coord, y coord). Libsvm demande de terminer chaque vecteur avec un label -1, donc je le fais.

EDIT2 :

Cette modification concerne mes valeurs C et Gamma utilisées pour la formation, ainsi que la mise à l'échelle des données. Je place normalement les données entre 0 et 1 (comme suggéré ici : http://www.csie.ntu.edu.tw/~cjlin/papers/guide/guide.pdf ). Je vais également mettre à l'échelle ce faux jeu de données et réessayer, bien que j'aie utilisé ce même jeu de données avec l'implémentation MATLAB de libsvm et qu'il ait pu séparer ces données non mises à l'échelle avec une précision de 100%.

Pour C et Gamma, j'utilise également les valeurs recommandées dans le guide. Je crée deux vecteurs et utilise une double boucle imbriquée pour essayer toutes les combinaisons :

std::vector<double> CList, GList;
double baseNum = 2.0;
for(double j = -5; j <= 15; j += 2) //-5 and 15
    CList.push_back(pow(baseNum,j));
for(double j = -15; j <= 3; j += 2) //-15 and 3
    GList.push_back(pow(baseNum,j));

Et la boucle ressemble à ça :

    for(auto CIt = CList.begin(); CIt != CList.end(); ++CIt) //for all C's
    {
        double C = *CIt;
        for(auto GIt = GList.begin(); GIt != GList.end(); ++GIt) //for all gamma's
        {
            double gamma = *GIt;
            svmParams.svmType = C_SVC;
            svmParams.kernalType = RBF;
            svmParams.C = C;
            svmParams.gamma = gamma;

        ......training code etc..........

EDIT3 :

Puisque je continue à faire référence à MATLAB, je vais montrer les différences de précision. Voici une carte thermique de la précision obtenue avec libsvm :

libsvm c++

Et voici la carte de précision que MATLAB produit en utilisant les mêmes paramètres et la même grille C/Gamma :

libsvm MATLAB

Voici le code utilisé pour générer les listes C/Gamma, et comment je m'entraîne :

CList = 2.^(-15:2:15);%(-5:2:15);
GList = 2.^(-15:2:15);%(-15:2:3);
cmd = ['-q -s 0 -t 2 -c ', num2str(C), ' -g ', num2str(gamma)];
model = ovrtrain(yTrain,xTrain,cmd);

EDIT4

En guise de vérification, j'ai reformaté mon faux jeu de données à l'échelle pour qu'il soit conforme au jeu de données utilisé par l'API de terminal Unix/Linux de libsvm. J'ai formé et prédit en utilisant un C/Gamma trouvé dans la carte de précision de MATLAB. La précision de la prédiction était de 100 %. Par conséquent, je dois absolument faire quelque chose de mal dans l'implémentation C++.

EDIT5

J'ai chargé le modèle formé depuis le terminal Linux dans ma classe wrapper C++. J'ai ensuite essayé de prédire le même ensemble de données que celui utilisé pour la formation. La précision en C++ était toujours aussi mauvaise ! Cependant, je suis très près de trouver la source du problème. Si MATLAB/Linux sont tous deux d'accord pour obtenir une précision de 100 % et que le modèle qu'ils produisent a déjà prouvé qu'il donnait une précision de 100 % sur le même ensemble de données que celui sur lequel il a été formé, et que maintenant ma classe wrapper C++ montre des performances médiocres avec le modèle vérifié... il y a trois situations possibles :

  1. La méthode que j'utilise pour transformer cv::Mats en svm_node* dont il a besoin pour la prédiction présente un problème.
  2. La méthode que j'utilise pour prédire les étiquettes a un problème.
  3. LES DEUX 2 et 3 !

Le code à inspecter vraiment maintenant est la façon dont je crée le svm_node. Le voici à nouveau :

svm_node** LibSVM::createNode(INPUT const cv::Mat& data)
{
    const int rowSize = data.rows;
    const int colSize = data.cols;

    //dynamically allocate the X matrix...
    svm_node** x = new svm_node*[rowSize];
    if(x == NULL)
        throw MLInterfaceException("Could not allocate SVM Node Array.");

    for(int row = 0; row < rowSize; ++row)
    {
        x[row] = new svm_node[colSize + 1]; //+1 here for the index-terminating -1
        if(x[row] == NULL)
            throw MLInterfaceException("Could not allocate SVM Node.");
    }

    for(int row = 0; row < rowSize; ++row)
    {
        for(int col = 0; col < colSize; ++col)
        {
            double tempVal = data.at<double>(row,col);
            x[row][col].value = tempVal;
        }

        x[row][colSize].index = -1;
        x[row][colSize].value = 0;
    }

    return x;
} /*createNode()*/

Et la prédiction :

cv::Mat LibSVM::predict(INPUT const cv::Mat& data)
{
    if(this->_svmModel == NULL)
        throw MLInterfaceException("Cannot predict; no model has been trained or loaded.");

    cv::Mat predMat;

    //create the libsvm representation of data
    svm_node** x = this->createNode(data);

    //perform prediction for each feature vector
    for(int i = 0; i < data.rows; ++i)
    {
        double pred = svm_predict(this->_svmModel, x[i]);
        predMat.push_back<double>(pred);
    }        

    //delete all rows and columns of x
    for(int i = 0; i < data.rows; ++i)
        delete[] x[i];
    delete[] x;

    return predMat;
}

EDIT6 :

Pour ceux d'entre vous qui regardent à la maison, j'ai formé un modèle (en utilisant le C/Gamma optimal trouvé dans MATLAB) en C++, je l'ai enregistré dans un fichier, puis j'ai essayé de prédire sur les données de formation via le terminal Linux. J'ai obtenu un score de 100%. Il y a un problème avec ma prédiction. o_0

EDIT7 :

J'ai enfin trouvé le problème. J'ai bénéficié d'une aide considérable pour le suivi des bogues pour le trouver. J'ai imprimé le contenu du tableau 2D de svm_node** utilisé pour la prédiction. C'était un sous-ensemble de la méthode createProblem(). Il y avait une partie de ce tableau que je n'ai pas réussi à copier et coller dans la nouvelle fonction. C'était l'index d'une caractéristique donnée ; il n'a jamais été écrit. Il aurait dû y avoir une ligne de plus :

x[row][col].index = col + 1; //indexing starts at 1

Et la prédiction fonctionne bien maintenant.

3voto

Pedrom Points 2079

Il serait utile de voir votre valeur gamma, puisque vos données ne sont pas normalisées, cela ferait une énorme différence.

Le gamma dans libsvm est inversement proportionnel au rayon de l'hypersphère, donc si ces sphères sont trop petites par rapport à la plage d'entrée, tout sera toujours activé et le modèle produira toujours la même valeur.

Donc, les deux recommandations sont les suivantes 1) Mettez vos valeurs d'entrée à l'échelle dans la plage [-1,1]. 2) Jouez avec les valeurs gamma.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X