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,7383Coefficients:
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.
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))
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_)))