Comparaison des modèles R, statmodels, sklearn pour une tâche de classification avec régression logistique

j'ai fait quelques expériences avec la régression logistique dans R, python statmodels et sklearn. Alors que les résultats donnés par R et les modèles de statistiques concordent, il y a une certaine divergence avec ce qui est retourné par sklearn. J'aimerais comprendre pourquoi ces résultats sont différents. Je comprends que ce ne sont probablement pas les mêmes algorithmes d'optimisation qui sont utilisés sous le bois.

spécifiquement, j'utilise la norme Default ensemble de données (utilisé dans le Isl book). La suite Le code Python lit les données dans une base de données Default.

import pandas as pd
 # data is available here
Default = pd.read_csv('https://d1pqsl2386xqi9.cloudfront.net/notebooks/Default.csv', index_col=0)
 #
Default['default']=Default['default'].map({'No':0, 'Yes':1})
Default['student']=Default['student'].map({'No':0, 'Yes':1})
 #
I=Default['default']==0
print("Number of 'default' values :", Default[~I]['balance'].count())

nombre de valeurs par défaut : 333.

il y a un total de 10000 exemples, avec seulement 333 positifs

régression logistique en R

j'utilise la suite

library("ISLR")
data(Default,package='ISLR')
 #write.csv(Default,"default.csv")
glm.out=glm('default~balance+income+student', family=binomial, data=Default)
s=summary(glm.out)
print(s)
#
glm.probs=predict(glm.out,type="response") 
glm.probs[1:5]
glm.pred=ifelse(glm.probs>0.5,"Yes","No")
 #attach(Default)
t=table(glm.pred,Default$default)
print(t)
score=mean(glm.pred==Default$default)
print(paste("score",score))

Le résultat est comme suit

Call: glm(formula = "par défaut~solde+revenu+étudiant", de la famille = binomiale, données = par Défaut)

Déviance Résiduel: Min 1Q médiane 3Q Max

-2,4691 -0,1418 -0,0557 -0,0203 3,7383

Coefficients:

Estimate Std. Error z value Pr(>|z|)        
(Intercept) -1.087e+01  4.923e-01 -22.080  < 2e-16 
balance      5.737e-03    2.319e-04  24.738  < 2e-16  
income       3.033e-06  8.203e-06   0.370   0.71152     
studentYes  -6.468e-01  2.363e-01  -2.738  0.00619  

(paramètre de Dispersion pour binomiale de la famille 1)

Null deviance: 2920.6  on 9999  degrees of freedom Residual 

déviance: 1571.5 sur 9996 degrés de liberté AIC: 1579.5

nombre D'itérations de Fisher: 8

     glm.pred   No  Yes
 No  9627  228
 Yes   40  105 

1 " score 0,9732"

je suis trop paresseux pour couper et coller les résultats obtenus avec statmodels. Il suffit de dire qu'ils sont extrêmement similaires à ceux donnés par R.!--12-->

sklearn

Pour sklearn, j'ai couru le code suivant.

  • il existe un paramètre class_weight pour tenir compte des classes déséquilibrées. J'ai testé class_weight=None (no Weighting -- I think that is the default in R), et class_weight='auto' (weighing with the inverse fréquences remarqués dans les données)
  • j'ai aussi mis C=10000, l'inverse du paramètre de régularisation, de façon à minimiser l'effet de régularisation.

~~!--12-->

import sklearn
from sklearn.linear_model import LogisticRegression
from sklearn.metrics import confusion_matrix

features = Default[[ 'balance', 'income' ]]
target = Default['default']
# 
for weight in (None,  'auto'):
    print("*"*40+"nweight:",weight)

    classifier = LogisticRegression(C=10000, class_weight=weight, random_state=42) 
                #C=10000 ~ no regularization

    classifier.fit(features, target,)  #fit classifier on whole base
    print("Intercept", classifier.intercept_)
    print("Coefficients", classifier.coef_)

    y_true=target
    y_pred_cls=classifier.predict_proba(features)[:,1]>0.5
    C=confusion_matrix(y_true,y_pred_cls)

    score=(C[0,0]+C[1,1])/(C[0,0]+C[1,1]+C[0,1]+C[1,0])
    precision=(C[1,1])/(C[1,1]+C[0 ,1])
    recall=(C[1,1])/(C[1,1]+C[1,0])
    print("n Confusion matrix")
    print(C)
    print()
    print('{s:{c}<{n}}{num:2.4}'.format(s='Score',n=15,c='', num=score))
    print('{s:{c}<{n}}{num:2.4}'.format(s='Precision',n=15,c='', num=precision))
    print('{s:{c}<{n}}{num:2.4}'.format(s='Recall',n=15,c='', num=recall))

Les résultats sont donnés ci-dessous.

> **************************************** 
>weight: None 
>
>Intercept [ -1.94164126e-06] 
>
>Coefficients [[ 0.00040756 -0.00012588]]
> 
>  Confusion matrix 
>
>     [[9664    3]  
>     [ 333    0]]
> 
>     Score          0.9664 
>     Precision      0.0 
>     Recall         0.0
>
> **************************************** 
>weight: auto 
>
>Intercept [-8.15376429] 
>
>Coefficients 
>[[  5.67564834e-03   1.95253338e-05]]
> 
>  Confusion matrix 
>
>     [[8356 1311]  
>     [  34  299]]
> 
>     Score          0.8655 
>     Precision      0.1857 
>     Recall         0.8979

Ce que j'observe, c'est que pour class_weight=None, le Score est excellent mais aucun exemple positif est reconnu. Précision et rappel sont à zéro. Les coefficients trouvés sont très petit, en particulier l'ordonnée à l'origine. La modification de C ne change pas les choses. class_weight='auto' choses semble mieux, mais j'ai encore une précision qui est très faible (trop positif classés). Encore une fois, changer C n'aide pas. Si je modifie l'intercept à la main, je peux récupérer les résultats donnés par R. donc je soupçonne qu'il y a une divergence entre l'estimation des entrées dans les deux cas. Comme cela a une conséquence dans la spécification de la tri-seuil (analogue à un rééchantillonnage des règlements), cela peut expliquer les différences de performances.

cependant, j'apprécierais tout conseil pour le choix entre les deux solutions et aider à comprendre l'origine de ces différences. Grâce.

10
demandé sur jfb 2015-02-26 18:58:28

2 réponses

j'ai rencontré un problème similaire et a fini poster ce sujet sur /r/MachineLearning. Il s'avère que la différence peut être attribuée à la normalisation des données. Quelle que soit l'approche que scikit-learn utilise pour trouver les paramètres du modèle donnera de meilleurs résultats si les données sont normalisées. scikit-learn dispose d'une documentation sur le prétraitement des données (y compris la standardisation), qui peut être consultée sur le site Web de scikit-learn. ici.

Résultats

Number of 'default' values : 333
Intercept: [-6.12556565]
Coefficients: [[ 2.73145133  0.27750788]]

Confusion matrix
[[9629   38]
 [ 225  108]]

Score          0.9737
Precision      0.7397
Recall         0.3243

Code

# scikit-learn vs. R
# http://stackoverflow.com/questions/28747019/comparison-of-r-statmodels-sklearn-for-a-classification-task-with-logistic-reg

import pandas as pd
import sklearn

from sklearn.linear_model import LogisticRegression
from sklearn.metrics import confusion_matrix
from sklearn import preprocessing

# Data is available here.
Default = pd.read_csv('https://d1pqsl2386xqi9.cloudfront.net/notebooks/Default.csv', index_col = 0)

Default['default'] = Default['default'].map({'No':0, 'Yes':1})
Default['student'] = Default['student'].map({'No':0, 'Yes':1})

I = Default['default'] == 0
print("Number of 'default' values : {0}".format(Default[~I]['balance'].count()))

feats = ['balance', 'income']

Default[feats] = preprocessing.scale(Default[feats])

# C = 1e6 ~ no regularization.
classifier = LogisticRegression(C = 1e6, random_state = 42) 

classifier.fit(Default[feats], Default['default'])  #fit classifier on whole base
print("Intercept: {0}".format(classifier.intercept_))
print("Coefficients: {0}".format(classifier.coef_))

y_true = Default['default']
y_pred_cls = classifier.predict_proba(Default[feats])[:,1] > 0.5

confusion = confusion_matrix(y_true, y_pred_cls)
score = float((confusion[0, 0] + confusion[1, 1])) / float((confusion[0, 0] + confusion[1, 1] + confusion[0, 1] + confusion[1, 0]))
precision = float((confusion[1, 1])) / float((confusion[1, 1] + confusion[0, 1]))
recall = float((confusion[1, 1])) / float((confusion[1, 1] + confusion[1, 0]))
print("\nConfusion matrix")
print(confusion)
print('\n{s:{c}<{n}}{num:2.4}'.format(s = 'Score', n = 15, c = '', num = score))
print('{s:{c}<{n}}{num:2.4}'.format(s = 'Precision', n = 15, c = '', num = precision))
print('{s:{c}<{n}}{num:2.4}'.format(s = 'Recall', n = 15, c = '', num = recall))
1
répondu airalcorn2 2015-06-28 21:57:13

bien que ce post soit ancien, je voulais vous donner une solution. Dans votre post, vous comparez des pommes avec des oranges. Dans votre code R, vous estimez "balance, income, and student" sur "default". Dans votre code Python, vous n'estimez "balance and income" que sur "default". Bien sûr, vous ne pouvez pas obtenir les mêmes estimations. De plus, les différences ne peuvent pas être attribuées à la mise à l'échelle des caractéristiques, car la régression logistique n'en a généralement pas besoin par rapport aux moyennes.

Vous êtes en droit de mettez un C élevé, de sorte qu'il n'y ait pas de régularisation. Si vous voulez avoir la même sortie que dans R, Vous devez changer le solveur en "newton-cg". Différents solveurs peuvent donner des résultats différents, mais ils donnent toujours la même valeur objective. Tant que votre solveur converge, tout ira bien.

voici le code qui vous donne les mêmes estimations que dans les modèles R et stats:

import pandas as pd
from sklearn.linear_model import LogisticRegression
from patsy import dmatrices # 
import numpy as np

 # data is available here
Default = pd.read_csv('https://d1pqsl2386xqi9.cloudfront.net/notebooks/Default.csv', index_col=0)
 #
Default['default']=Default['default'].map({'No':0, 'Yes':1})
Default['student']=Default['student'].map({'No':0, 'Yes':1})

# use dmatrices to get data frame for logistic regression
y, X = dmatrices('default ~ balance+income+C(student)',
                  Default,return_type="dataframe")

y = np.ravel(y)

# fit logistic regression
model = LogisticRegression(C = 1e6, fit_intercept=False, solver = "newton-cg", max_iter=10000000)
model = model.fit(X, y)

# examine the coefficients
pd.DataFrame(zip(X.columns, np.transpose(model.coef_)))
5
répondu Dat Tran 2015-08-06 17:30:59