Introduction à la régression logistique avec Python

Publié: 29-10-2019
Le code de ce post est écrit avec Python version 3.7.3 à l'aide du logiciel Spyder.

Dans le précédent article <Un premier regard sur la classification linéaire et la régression logistique>, j’avais introduit la régression logistique et j'ai montré la différence entre une régression logistique et une régression linéaire.

Dans cet article, je vais vous montrer un exemple très simple d'utilisation de la régression logistique pour la classification.

Considérez les points de données dans la figure ci-dessous. Vous avez 20 points de données allant de 0 à 1 (axe des x). Chaque point de données appartient à une catégorie, par exemple les catégories 0 et 1. Vous pouvez également remarquer sur le graphique que les points de données inférieurs à 0.6 appartiennent à la catégorie 0 et les points de données supérieurs à 0.6 appartiennent à la catégorie 1. Disons que nous avons un nouveau point de données (le point noir), notre objectif est de prédire la catégorie de ce nouveau point de données à l’aide de la régression logistique.


La régression logistique modélise la probabilité de la classe par défaut (par exemple, la première classe). Par exemple, si nous modélisons le problème ci-dessus en tant que catégorie 0 ou 1 à partir de points de données, la première classe pourrait être la catégorie 1 et le modèle de régression logistique pourrait être écrit comme la probabilité de la catégorie 1 pour un point de données x: $$ P(category = 1 | x) $$

En d'autres termes, nous modélisons la probabilité qu'une entrée (X) appartienne à la classe par défaut (Y = 1), nous pouvons l'écrire formellement de la manière suivante: $$ P(X) = P(Y = 1|X) $$

Le graphique de calcul de la régression logistique peut être visualisé comme suit:


w et x sont des vecteurs dont la taille dépend du nombre d'entités en entrée, et b est le biais scalaire. Dans l'exemple de ce post, nous n'avons qu'une seule entité (feature).

Le modèle de régression logistique prend des entrées à valeur réelle et permet de prédire la probabilité que l'entrée appartienne à la classe par défaut (classe 1). Si la probabilité est supérieure à 0.5, nous pouvons considérer le résultat comme une prédiction pour la classe par défaut (classe 1), sinon la prédiction concerne l'autre classe (classe 0). Pour cet ensemble de données, la régression logistique a deux coefficients, tout comme la régression linéaire, par exemple: $$ z \; (sortie) = x \times w + b $$

L’algorithme d’apprentissage consistera à découvrir les meilleures valeurs pour les coefficients ( w et b) en se basant sur les données d’apprentissage. Contrairement à la régression linéaire, le résultat est transformé en probabilité en utilisant la fonction logistique: $$ a = P(classe = 1) = \sigma (z) $$ $$ a = \frac{1}{1+e^{-z}} $$

Régression logistique par descente de gradient stochastique

La descente de gradient est un algorithme d'optimisation utilisé pour trouver les valeurs de paramètres (coefficients) d'une fonction (f) qui minimise une fonction de coût (cost). La descente de gradient est mieux utilisée lorsque les paramètres ne peuvent pas être calculés analytiquement (par exemple en utilisant l'algèbre linéaire) et doivent être recherchés par un algorithme d'optimisation. Dans cet exemple, les coefficients sont les paramètres w et b. La fonction f est notre modèle de régression logistique utilisé pour classifier les points de données. Dans cet article, je ne couvrirai pas la fonction de coût de la régression logistique, je la conserverai pour un autre article.

Nous allons estimer les valeurs de nos coefficients en utilisant la descente de gradient stochastique. Nous pouvons appliquer la descente de gradient stochastique au problème de la recherche des coefficients pour le modèle de régression logistique.

L'entrée de notre problème est un vecteur X avec des dimensions (n, m). m est le nombre d'observations/d'échantillons (ici 20) et n le nombre d'entités (ici 1). $$ X_{[1,20]} = [[x_1, x_2, ... , x_{20}]] $$

Les coefficients sont initialisés à zéro: $$ w_{[1,1]} = [[0]] \\ b = 0 $$

Pour chaque échantillon de notre ensemble de données X, nous allons calculer la prédiction comme suit: $$ prédiction = \frac{1}{1 + e^{-(x_i \; \times \; w \; + \; b)}} $$

Nous pouvons calculer les nouvelles valeurs de coefficient en utilisant deux équations de mise à jour simples: $$ w = w - \alpha \times (x_i \times (prédiction-y_i)) \\ b = b - \alpha \times (1 \times (prédiction-y_i)) $$ Tels que alpha est le taux d'apprentissage et y_i est la catégorie/étiquette réelle de l'échantillon i. Alpha est un paramètre que vous devez spécifier au début du parcours d’entraînement. Il s’agit du taux d’apprentissage et de la mesure dans laquelle les coefficients (et donc le modèle) changent ou apprennent à chaque mise à jour. Nous choisirons 0.03 dans notre exemple, n'hésitez pas à le changer et à tester le résultat.

Dans cet article, je n'expliquerai pas comment nous en sommes arrivés à ces équations de mise à jour ci-dessus. Dans un autre article, j'entrerai dans les détails de la descente de gradient et la raison pour laquelle les coefficients sont mis à jour de cette manière.

Une autre chose à définir est le nombre d'époques. Une seule itération dans l'ensemble de données d'apprentissage s'appelle une époque. Il est courant de répéter la procédure de descente de gradient stochastique pour un nombre déterminé d'époques. À la fin de l'époque, vous pouvez calculer le score de classification et les valeurs d'erreur du modèle. Étant donné qu’il s’agit d’un problème de classification, il serait intéressant de savoir à quel point le modèle est précis à chaque époque. Dans cet exemple, nous allons calculer le score de classification après chaque époque.

C'est le pseudo-code de notre algorithme d'apprentissage:

            
                w = [[0]]
                b=0
                epoch = une valeur numérique
                pour j allant de 0 à epoch faire:
                    pour chaque échantillon x,y dans X,Y:
                        z = x * w + b
                        a = sigmoid(z)
                        w = w - alpha * (x(a-y))
                        b = b - alpha * (a-y)
            
        

Implémentation en Python de la régression logistique

Importez les bibliothèques nécessaires:

            
                import numpy as np
                import matplotlib.pyplot as plt
            
        

Définissez le vecteur d’entrée X et le vecteur de sortie Y. Nous allons changer la forme de X en [1,20] et Y en [20,1].

            
                x=np.array([0.01,0.012,0.2,0.3,0.55,0.04,0.35,0.23,0.12,0.49,
                            0.65,0.7,0.73,0.8,0.96,0.89,0.75,0.92,0.68,0.67])
                #[1,20]
                x=x.reshape(1,len(x))
                y=np.array([0,0,0,0,0,0,0,0,0,0,1,1,1,1,1,1,1,1,1,1])
                #[20,1]
                y=y.reshape(len(y),1)
            
        

Vous pouvez visualiser les données comme suit:

            
                color1=(0.69411766529083252, 0.3490196168422699, 0.15686275064945221, 1.0)
                color2=(0.65098041296005249, 0.80784314870834351, 0.89019608497619629, 1.0)
                colormap = np.array([color1,color2])
                plt.figure()
                plt.scatter(x,y, s=500, c=colormap[y.reshape(-1)])
                plt.yticks([0,1],[0,1])
                plt.grid()
                plt.show()
            
        

Définissez le nombre d'échantillons et la dimension des données d'entrée.

            
                #nombre d'échantillons
                N=x.shape[1]
                #dimension
                D=x.shape[0]
            
        

Définir la fonction sigmoïde qui calcule les prédictions:

            
                def sigmoid(z):
                    return 1 / (1 + np.exp(-z))
            
        

Définissez les coefficients w et b à zéro:

            
                w=np.zeros((D))
                w=w.reshape(len(w),1)

                b=0
            
        

Nous voulons maintenant optimiser les coefficients en utilisant la descente de gradient stochastique. Nous fixerons le nombre d'époques à 150 et le taux d'apprentissage à 0.03. Après chaque itération, nous ajouterons la prédiction à un tableau y_hat.
Après avoir itéré sur tous les échantillons de X, nous arrondirons les valeurs des prédictions de manière à ce que les valeurs inférieures à 0.5 deviennent 0 et les autres deviennent 1.
Ensuite, nous calculerons le nombre de prédictions correctes en comparant nos prédictions avec le vecteur Y.
Après chaque époque, nous ajoutons le score de classification à un tableau score_list afin de visualiser le processus d'apprentissage.

            
                epoch=150
                score_list=[]
                for j in range(epoch):
                    y_hat=[]
                    for i in range(N):
                        z=x[0,i]*w[0] + b
                        #calculer la prédiction
                        a=sigmoid(z)

                        #mettre à jour les coefficients
                        #alpha est défini à 0.03
                        w[0] = w[0] - 0.03 * (x[0,i]*(a-y[i,0]))
                        b=b - 0.03 * (a-y[i,0])

                        y_hat.append(a)

                    y_hat=np.array(y_hat)
                    y_hat=y_hat.reshape(len(y_hat),1)
                    #nombre de prédictions correctes
                    score = np.mean(np.round(y_hat) == y)
                    print("classification score : "  + str(score))
                    score_list.append(score)
            
        

Visualisez le processus d'apprentissage:

            
                plt.figure()
                plt.plot(np.arange(0,epoch),score_list)
                plt.xlabel('Epochs',fontsize=22)
                plt.ylabel('Accuracy',fontsize=22)
                plt.grid()
                plt.show()
            
        

Vous pouvez remarquer comment l'algorithme apprend les coefficients optimisés après chaque époque en utilisant la descente de gradient. Cela commence avec une précision de 50% et se termine avec une précision de 95%.

Nous voulons maintenant tester notre modèle sur de nouveaux points de données pour vérifier s'il est capable de les classer correctement. Définissons d'abord la fonction de prédiction:

            
                def predict(x,w,b):
                    return np.round(sigmoid([x_test]*w+b))
            
        

Cette fonction prend en entrée le nouveau point de données et les coefficients optimisés (w et b).

Essayons maintenant de prédire un nouveau point de données ayant une valeur de 0.35 et de visualiser le résultat:

        
            x_test=0.35
            output=predict(x_test,w,b)

            plt.figure()
            plt.scatter(x,y, s=500, c=colormap[y.T.reshape(-1)])
            plt.scatter(x_test,output, s=500,color='g')
            plt.yticks([0,1],[0,1])
            plt.grid()
            plt.show()
        
    

Notre modèle a classé le point de données dans la catégorie 0, qui est la catégorie attendue. Essayons avec un autre point de données avec une valeur de 0.7:

        
            x_test=0.7
            output=predict(x_test,w,b)
        
    

Nous avons également obtenu la catégorie attendue.

La régression logistique est un modèle linéaire

Pourquoi la régression logistique est considérée comme un modèle linéaire si elle utilise une fonction non linéaire (sigmoïde) pour calculer les prédictions?

La réponse courte est que la régression logistique est considérée comme un modèle linéaire généralisé, car le résultat dépend toujours de la somme des entrées et des paramètres. Ou en d'autres termes, la sortie ne peut pas dépendre du produit (ou quotient, etc.) de ses paramètres.

Quand on parle de la somme des paramètres, on parle de Z (le produit scalaire de nos entités en entrée et des coefficients de modèle respectifs w et b): $$ Z= X \times W + B \\ z = x_0 \times w_0 + x_1 \times w_1 + ... + 1 \times b $$

La régression logistique est une méthode linéaire, mais les prédictions sont transformées à l'aide de la fonction logistique. L'impact de ceci est que nous ne pouvons plus comprendre les prédictions comme une combinaison linéaire des entrées, contrairement à la régression linéaire. Par exemple, le modèle peut être défini comme suit: $$ P(X) = \frac{1}{1 + e^{-Z}} $$

La clé est que notre modèle est additif notre résultat z dépend de l'additivité du paramètre de poids (W).
Il n’existe aucune interaction entre les valeurs du paramètre de poids qui rendrait notre modèle non linéaire, rien de tel que: $$ x_0 \times w_0 \times x_1 \times w_1 $$

Sans trop plonger dans les calculs, nous pouvons contourner l'équation ci-dessus comme suit (rappelez-vous que nous pouvons supprimer le "e" d'un côté en ajoutant un "ln()" à l'autre): $$ \log (\frac{P(X)}{1-P(X)}) = X \times W + B $$

Ceci est utile car nous pouvons voir que le calcul de la sortie à droite est à nouveau linéaire (tout comme la régression linéaire) et que l'entrée à gauche est un logarithme naturel de la probabilité de la classe par défaut.

Conclusion

Cet article a montré une implémentation très simple de la régression logistique. Cet article ne couvre pas tous les sujets de régression logistique que je publierai dans les prochains articles. Il est important de se rappeler que la régression logistique convient aux problèmes de classification binaire. D'autres sujets que vous pouvez lire et qui vous aident à mieux comprendre la régression logistique:

  • Fonction d'erreur d'entropie croisée (cross-entropy)
  • Maximum de vraisemblance
  • Dérivée de la fonction de perte