Python >> Tutoriel Python >  >> Python

Régression logistique Scikit-learn vs Statsmodels

Quelle est la différence entre Statsmodels et Scikit-learn ? Les deux ont des moindres carrés ordinaires et une régression logistique, il semble donc que Python nous donne deux façons de faire la même chose. Statsmodels propose une modélisation du point de vue des statistiques . Scikit-learn propose certains des mêmes modèles du point de vue de l'apprentissage automatique .

Il faut donc comprendre la différence entre les statistiques et le machine learning ! Les statistiques font des inférences mathématiquement valables sur une population sur la base de données d'échantillon. Les statistiques répondent à la question :« Quelle est la preuve que X est lié à Y ? L'apprentissage automatique a pour objectif d'optimiser la précision prédictive plutôt que l'inférence. L'apprentissage automatique répond à la question :"Étant donné X, quelle prédiction devrions-nous faire pour Y ?"

Dans l'exemple ci-dessous, nous allons créer un faux ensemble de données avec des variables prédictives et une variable Y binaire. Ensuite, nous effectuerons une régression logistique avec scikit-learn et statsmodels. Nous verrons que scikit-learn nous permet de régler facilement le modèle pour optimiser la puissance prédictive. Statsmodels fournira un résumé des mesures statistiques qui seront très familières à ceux qui ont utilisé SAS ou R.

Si vous avez besoin d'une introduction à la régression logistique, consultez cet article Finxter.

Créer de fausses données pour le modèle de régression logistique

J'ai essayé d'utiliser des données accessibles au public pour cet exercice, mais je n'en ai pas trouvé une avec les caractéristiques que je voulais. J'ai donc décidé de créer de fausses données en utilisant NumPy ! Il y a un article ici qui explique les calculs et comment faire cela dans R.

import numpy as np
import pandas as pd

#The next line is setting the seed for the random number generator so that we get consistent results
rg = np.random.default_rng(seed=0)
#Create an array with 500 rows and 3 columns
X_for_creating_probabilities = rg.normal(size=(500,3))

Créez un tableau avec la première colonne supprimée. La colonne supprimée peut être considérée comme un bruit aléatoire ou comme une variable à laquelle nous n'avons pas accès lors de la création du modèle.

X1 = np.delete(X_for_creating_probabilities,0,axis=1)
X1[:5]
"""
array([[-0.13210486,  0.64042265],
       [-0.53566937,  0.36159505],
       [ 0.94708096, -0.70373524],
       [-0.62327446,  0.04132598],
       [-0.21879166, -1.24591095]])
"""

Nous allons maintenant créer deux autres colonnes corrélées avec X1. Les ensembles de données ont souvent des variables fortement corrélées. La corrélation augmente la probabilité de surajustement. Concaténer pour obtenir un seul tableau.

X2 = X1 + .1 * np.random.normal(size=(500,2))
X_predictors = np.concatenate((X1,X2),axis=1)

Nous voulons créer notre variable de résultat et la relier à X_predictors. Pour ce faire, nous utilisons nos données comme entrées du modèle de régression logistique pour obtenir des probabilités. Ensuite, nous définissons la variable de résultat, Y, sur Vrai lorsque la probabilité est supérieure à 0,5.

P = 1 / (1 + np.e**(-np.matmul(X_for_creating_probabilities,[1,1,1])))
Y = P > .5
#About half of cases are True
np.mean(Y)
#0.498


Divisez maintenant les données en données d'entraînement et de test. Nous allons exécuter une régression logistique sur les données d'entraînement, puis voir les performances du modèle sur les données d'entraînement.

#Set the first 50 rows to train the model
X_train = X_predictors[:50]
Y_train = Y[:50]

#Set the remaining rows to test the model
X_test = X_predictors[50:]
Y_test = Y[50:]

print(f"X_train: {len(X_train)} X_test: {len(X_test)}")
#X_train: 50 X_test: 450

Régression logistique avec Scikit-learn

Nous sommes prêts à former et tester des modèles.

Au fur et à mesure que nous formons les modèles, nous devons prendre des mesures pour éviter le surajustement. Un modèle d'apprentissage automatique peut avoir des résultats très précis avec les données utilisées pour entraîner le modèle. Mais cela ne signifie pas qu'il sera tout aussi précis lorsqu'il fera des prédictions avec des données qu'il n'a jamais vues auparavant. Lorsque le modèle ne parvient pas à se généraliser à de nouvelles données, nous disons qu'il a "sur-ajusté" les données d'apprentissage. Le surajustement est plus probable lorsqu'il y a peu d'observations sur lesquelles s'entraîner et lorsque le modèle utilise de nombreux prédicteurs corrélés.

Comment éviter le sur-ajustement ? Par défaut, la régression logistique de scikit-learn applique la régularisation. La régularisation équilibre le besoin de précision prédictive sur les données d'apprentissage avec une pénalité sur l'ampleur des coefficients du modèle. L'augmentation de la pénalité réduit les coefficients et donc réduit la probabilité de surajustement. Si la pénalité est trop importante, cela réduira le pouvoir prédictif des données d'entraînement et de test.

from sklearn.linear_model import LogisticRegression
scikit_default = LogisticRegression(random_state=0).fit(X_train, Y_train)
print(f"intecept: {scikit_default.intercept_} coeficients: {scikit_default.coef_}")
print(f"train accuracy: {scikit_default.score(X_train, Y_train)}")
print(f"test accuracy: {scikit_default.score(X_test, Y_test)}")
"""
Results will vary slightly, even when you set random_state.
intecept: [-0.44526823] coeficients: [[0.50031563 0.79636504 0.82047214 0.83635656]]
train accuracy: 0.8
test accuracy: 0.8088888888888889
"""

Nous pouvons désactiver la régularisation en définissant la pénalité sur aucune. L'application de la régularisation réduit l'amplitude des coefficients. Définir la pénalité sur aucune augmentera les coefficients. Notez que la précision des données de test diminue. Cela indique que notre modèle a surajusté les données d'entraînement.

from sklearn.linear_model import LogisticRegression
scikit_no_penalty = LogisticRegression(random_state=0,penalty='none').fit(X_train, Y_train)
print(f"intecept: {scikit_no_penalty.intercept_} coeficients: {scikit_no_penalty.coef_}")
print(f"train accuracy: {scikit_no_penalty.score(X_train, Y_train)}")
print(f"test accuracy: {scikit_no_penalty.score(X_test, Y_test)}")
"""
intecept: [-0.63388911] coeficients: [[-3.59878438  0.70813119  5.10660019  1.29684873]]
train accuracy: 0.82
test accuracy: 0.7888888888888889
"""


C est 1.0 par défaut. Des valeurs plus petites de C augmentent la régularisation, donc si nous définissons la valeur sur 0,1, nous réduisons l'amplitude des coefficients.

from sklearn.linear_model import LogisticRegression
scikit_bigger_penalty = LogisticRegression(random_state=0,C=.1).fit(X_train, Y_train)
print(f"intecept: {scikit_bigger_penalty.intercept_} \
    coeficients: {scikit_bigger_penalty.coef_}")
print(f"train accuracy: {scikit_bigger_penalty.score(X_train, Y_train)}")
print(f"test accuracy: {scikit_bigger_penalty.score(X_test, Y_test)}")
"""
intecept: [-0.13102803]     coeficients: [[0.3021235  0.3919277  0.34359251 0.40332636]]
train accuracy: 0.8
test accuracy: 0.8066666666666666
"""


C'est bien de pouvoir ajuster le coefficient de lissage, mais comment décide-t-on de la valeur optimale ? GridSearchCV de Scikit-learn fournit une méthode efficace mais facile à utiliser pour choisir une valeur optimale. La "Grid Search" dans GridSearch CV signifie que nous fournissons un dictionnaire avec les valeurs des paramètres que nous souhaitons tester. Le modèle est adapté à toutes les combinaisons de ces valeurs. Si nous avons 4 valeurs possibles pour C et 2 valeurs possibles pour le solveur, nous rechercherons dans toutes les combinaisons 4X2=8.

GridSearchCV recherche dans cette grille

C solveur
.01 newton-cg
.1 newton-cg
1 newton-cg
10 newton-cg
.01 lbfgs
.1 lbfgs
1 lbfgs
10 lbfgs

Le "CV" dans GridSearchCV signifie c ross-v alidation. La validation croisée est la méthode de segmentation des données d'apprentissage. Le modèle est formé sur tous les segments sauf un et le segment restant valide le modèle.

Itération Segment 1 Segment 2 Segment 3 Segment 4 Segment 5
1ère itération Validation Entraîner Entraîner Entraîner Entraîner
2e itération Entraîner Validation Entraîner Entraîner Entraîner
3e itération Entraîner Entraîner Validation Entraîner Entraîner
4e itération Entraîner Entraîner Entraîner Validation Entraîner
5e itération Entraîner Entraîner Entraîner Entraîner Validation

GridSearch et la validation croisée fonctionnent en combinaison. GridsearchCV parcourt les valeurs de C et du solveur pour différents segments de test et de formation. L'algorithme sélectionne la meilleure performance basée sur l'estimateur sur les segments de validation.

Cela nous permet de déterminer quelles valeurs de C et de solveur fonctionnent le mieux pour nos données d'apprentissage. C'est ainsi que scikit-learn nous aide à optimiser la précision prédictive.

Voyons-le en action.

from sklearn.model_selection import GridSearchCV
parameters = {'C':[.01, .1, 1, 10],'solver':['newton-cg','lbfgs']}
Logistic = LogisticRegression(random_state=0)
scikit_GridSearchCV = GridSearchCV(Logistic, parameters)
scikit_GridSearchCV.fit(X_train, Y_train)
print(f"best estimator: {scikit_GridSearchCV.best_estimator_}")
#best estimator: LogisticRegression(C=0.1, random_state=0, solver='newton-cg')

Utiliser la méthode des scores renvoie la précision moyenne sur les données de test et les étiquettes données. La précision est le pourcentage d'observations correctement prédites.

print(f"train accuracy: {scikit_GridSearchCV.score(X_train, Y_train)}")
print(f"test accuracy: {scikit_GridSearchCV.score(X_test, Y_test)}")
"""
train accuracy: 0.82
test accuracy: 0.8133333333333334
"""

Régression logistique avec Statsmodels

Essayons maintenant la même chose, mais avec des modèles de statistiques. Avec scikit-learn, pour désactiver la régularisation, nous définissons penalty='none' , mais avec statsmodels, la régularisation est désactivée par défaut. Une bizarrerie à surveiller est que Statsmodels n'inclut pas d'interception par défaut. Pour inclure une interception, nous utilisons la méthode sm.add_constant.

import statsmodels.api as sm

#adding constant to X
X_train_with_constant = sm.add_constant(X_train)
X_test_with_constant = sm.add_constant(X_test)

# building the model and fitting the data
sm_model_all_predictors = sm.Logit(Y_train, X_train_with_constant).fit()

# printing the summary table
print(sm_model_all_predictors.params)
"""
Optimization terminated successfully.
         Current function value: 0.446973
         Iterations 7
[-0.57361523 -2.00207425  1.28872367  3.53734636  0.77494424]
"""

Si vous avez l'habitude de faire de la régression logistique dans R ou SAS, la suite vous sera familière. Une fois que nous avons entraîné le modèle de régression logistique avec des modèles de statistiques, la méthode récapitulative produira facilement un tableau avec des mesures statistiques comprenant des valeurs de p et des intervalles de confiance.

sm_model_all_predictors.summary()
Dép. Variable : y Non. Observations : 50
Modèle : Logit Résidus Df : 45
Méthode : MLE Modèle Df : 4
Date : jeu, 04 février 2021 Pseudo R carré : 0,3846
Heure : 14:33:19 Log de vraisemblance : -21.228
convergé : Vrai LL-Null : -34.497
Type de covariance : non robuste Valeur p LLR : 2.464e-05
coef erreur type z P>|z| [0,025 0,975]
const -0.7084 0,478 -1.482 0,138 -1.645 0,228
x1 5.5486 4.483 1.238 0,216 -3.237 14.335
x2 10.2566 5.686 1.804 0,071 -0.887 21.400
x3 -3.9137 4.295 -0.911 0,362 -12.333 4.505
x4 -7.8510 5.364 -1.464 0,143 -18.364 2.662

Il y en a beaucoup ici, mais nous allons nous concentrer sur le deuxième tableau avec les coefficients.

La première colonne indique la valeur du coefficient. La quatrième colonne, avec l'en-tête P>|z|, montre les p-values. Une valeur de p est une mesure de probabilité, et les valeurs de p supérieures à 0,05 sont souvent considérées comme « non statistiquement significatives ». Aucun des prédicteurs n'est considéré comme statistiquement significatif ! Cela est dû au fait que nous avons un nombre relativement faible d'observations dans nos données d'entraînement et que les prédicteurs sont fortement corrélés. Certains packages statistiques comme R et SAS ont des méthodes intégrées pour sélectionner les fonctionnalités à inclure dans le modèle en fonction des prédicteurs qui ont des valeurs p faibles (significatives), mais malheureusement, cela n'est pas disponible dans les modèles de statistiques.

Si nous réessayons avec seulement x1 et x2, nous obtiendrons un résultat complètement différent, avec des valeurs de p très faibles pour x1 et x2, ce qui signifie que la preuve d'une relation avec la variable dépendante est statistiquement significative. Nous trichons, cependant - parce que nous avons créé les données, nous savons que nous n'avons besoin que de x1 et x2.

sm_model_x1_x2 = sm.Logit(Y_train, X_train_with_constant[:,:3]).fit()
sm_model_x1_x2.summary()

Nous voyons maintenant que x1 et x2 sont tous deux statistiquement significatifs.

Statsmodels n'a pas la même méthode de précision que nous avons dans scikit-learn. Nous utiliserons la méthode de prédiction pour prédire les probabilités. Ensuite, nous utiliserons la règle de décision selon laquelle les probabilités supérieures à 0,5 sont vraies et toutes les autres sont fausses. Il s'agit de la même règle utilisée lorsque scikit-learn calcule la précision.

all_predicted_train = sm_model_all_predictors.predict(X_train_with_constant)>.5
all_predicted_test = sm_model_all_predictors.predict(X_test_with_constant)>.5

x1_x2_predicted_train = sm_model_x1_x2.predict(X_train_with_constant[:,:3])>.5
x1_x2_predicted_test = sm_model_x1_x2.predict(X_test_with_constant[:,:3])>.5

#calculate the accuracy
print(f"train: {(Y_train==all_predicted_train).mean()} and test: {(Y_test==all_predicted_test).mean()}")
print(f"train: {(Y_train==x1_x2_predicted_train).mean()} and test: {(Y_test==x1_x2_predicted_test).mean()}")
"""
train: 0.8 and test: 0.8066666666666666
train: 0.8 and test: 0.8111111111111111
"""

Résumer les résultats

Créons un DataFrame avec les résultats. Les modèles ont une précision identique sur les données d'apprentissage, mais des résultats différents sur les données de test. Les modèles avec tous les prédicteurs et sans lissage ont la plus mauvaise précision de test, ce qui suggère qu'ils ont sur-ajusté les données d'apprentissage et ne se généralisent donc pas bien aux nouvelles données.

Même si nous utilisons les meilleures méthodes pour créer notre modèle, il y a toujours une chance dans la façon dont il se généralise aux données de test.

lst = [['scikit-learn','default', scikit_default.score(X_train, Y_train),scikit_default.score(X_test, Y_test)],
       ['scikit-learn','no penalty', scikit_no_penalty.score(X_train, Y_train),scikit_no_penalty.score(X_test, Y_test)],
       ['scikit-learn','bigger penalty', scikit_bigger_penalty.score(X_train, Y_train),scikit_bigger_penalty.score(X_test, Y_test)],
       ['scikit-learn','GridSearchCV', scikit_GridSearchCV.score(X_train, Y_train),scikit_GridSearchCV.score(X_test, Y_test)],
       ['statsmodels','include intercept and all predictors', (Y_train==all_predicted_train).mean(),(Y_test==all_predicted_test).mean()],
       ['statsmodels','include intercept and x1 and x2', (Y_train==x1_x2_predicted_train).mean(),(Y_test==x1_x2_predicted_test).mean()]
      ]
df = pd.DataFrame(lst, columns =['package', 'setting','train accuracy','test accuracy'])
df
forfait paramètre précision d'entraînement précision du test
0 scikit-learn par défaut 0.80 0.808889
1 scikit-learn pas de pénalité 0,78 0.764444
2 scikit-learn pénalité plus importante 0,82 0.813333
3 scikit-learn GridSearchCV 0.80 0.808889
4 modèles de statistiques inclure l'interception et tous les prédicteurs 0,78 0.764444
5 modèles de statistiques inclure l'interception et x1 et x2 0.80 0.811111

Scikit-learn vs Statsmodels

Le résultat est que vous devez utiliser Scikit-learn pour la régression logistique, sauf si vous avez besoin des résultats statistiques fournis par StatsModels.

Voici un tableau des similitudes et des différences les plus pertinentes :

Scikit-apprendre Modèles statistiques
Régularisation Utilise la régularisation L2 par défaut, mais la régularisation peut être désactivée à l'aide de penalty='none' N'utilise pas la régularisation par défaut
Réglage des hyperparamètres GridSearchCV permet un réglage facile du paramètre de régularisation L'utilisateur devra écrire des lignes de code pour régler le paramètre de régularisation
Intercepter Inclut l'interception par défaut Utilisez la méthode add_constant pour inclure une interception
Évaluation du modèle La méthode de score rapporte la précision de la prédiction La méthode récapitulative affiche les valeurs p, les intervalles de confiance et d'autres mesures statistiques
Quand devez-vous l'utiliser ? Pour des prédictions précises Pour l'inférence statistique.
Comparaison avec R et SAS Différent Similaire

C'est tout pour le moment! Veuillez consulter mes autres travaux sur learningtableau.com et mon nouveau site datasciencedrills.com.


Prochain article