dimanche 1 octobre 2023

Les tranches en Python et Golang

 Les tranches ou slice sont présentes en Python sous deux formes:

La forme simple à la volée sous forme de fonction


Sous forme objet avec les slices nommés:

...

Avec  3 attributs et une  méthode spécifique

La méthode indices prend une longueur en paramètre et retourne les limites du découpage (utilité limitée)

Les tranches nommées sont utiles pour découper des lignes possédant des champs de taille fixe.
avec par exemple 
code_postal = [1,7)]
nom = [7,27]
etc ..


En golang.

Les tranches s'appuient sur des tableaux ou à défaut embarque un tableau interne.
Leur définition doit comporter: le type des données, la longueur et la capacité.
tranche_a := make([]int, 3, 6)


(ici longueur = 3 pour une capacité de 6)

Avec quelques pièges: 
tranche_b := tranche_a[1:3]

Si on modifie tranche_b ou tranche_a, les deux seront modifiés

tranche_b[0] = 42

Ce comportement peut évoluer si on ajoute des éléments par la fonction append. Si la longueur dépasse la capacité , automatique le tableau donnera lieu à un nouveau tableau d'un capacité augmentée. 

tranche_b = append(tranche_b, 19)
tranche_b = append(tranche_b, 20)
tranche_b = append(tranche_b, 21)
tranche_b = append(tranche_b, 22)
tranche_b = append(tranche_b, 23)
tranche_b[0] = 99


Dans ce dernier cas tranche_a et tranche_b pointeront vers deux espaces différents.

tranche_a [3 42 0] capacité: 6 taille: 3

tranche_b [99 0 18 19 20 21 22 23] capacité: 10 taille: 8


Pour éviter tout effet de bord:

  • Utiliser la fonction copy qui va dès le départ créer deux zones mémoires distinctes.
  • Utiliser la forme slice[debut:fin:max] pour éviter les effets de bord d'append.



mercredi 13 septembre 2023

De python à Golang: les tris

 Python propose trois méthodes principales pour trier des données:

  • L'instruction sorted
  • La méthode sort pour les conteneurs (list, tuple) .
  • La méthode __lt__ à implémenter

L'instruction  native 'sorted' qui permet de trier n'importe quelle collection à partir du moment où elle est:

  • Homogène: les éléments sont de même nature.
  • Itérable : elle peut délivrer les données  les unes après les autres. 
  • Il existe un dispositif connu de comparaison des données deux à deux

Démonstration:


Pour pallier aux différents cas de figure, l'instruction sorted introduit un paramètre supplémentaire: 'key' qui permet d appliquer une fonction appelée au moment du classement des données.
ici on peut ajouter: key=str   qui va transformer chaque item en string (sans impact pour un item déjà de cette classe) 

La solution la plus souple est d'utiliser une fonction lambda pour réaliser des personnalisations de tri complexes: exemple pour  faire  passer les lettres avant les chiffres.


(le symbole '|' a un code ascii inférieur à 'a')  Ici on ajoute '|' avant le resultat de la fonction 'str'.

Enfin, il est possible d'implémenter la méthode __lt__ d'un objet.

exemple ici où une classe implémente la méthode 'lt' basée sur l'identifiant des objets.



Avec comme résultat d'un tri:

Les fonctions de classement utilisent souvent les methodes: getattr pour un objet et operator.itemgetter  pour les autres conteneurs. Pour trier sur le 2 eme élément d'un tuple:



Et avec Golang ?


La prise en charge des tris ressemble au dernier exemple.
Le package 'sort' apporte des utilitaires pour trier la plupart des types de base.

Il s'utilise en l'état pour des tris basiques :

a := []int{3, 6, 4, 1}
    fmt.Println("avant", a)
    sort.Ints(a)
    fmt.Println("après", a)


avant [3 6 4 1]
après [1 3 4 6]

Si on reprend les exemples des autres posts:
avec un dé pipé: 
type DePipe struct {
    De
    Faceplus int
}

Après une série de tirage , on obtient 
face: 1 score: 16.00
face: 2 score: 14.20
face: 3 score: 15.50
face: 4 score: 13.60
face: 5 score: 13.60
face: 6 score: 27.10

Pour trier sur la fréquence, on utilisera  SliceStable du package sort.

De cette façon:

sort.SliceStable(tface, func(i, j int) bool { return tirages2[tface[i]] < tirages2[tface[j]] })

Si on souhaite quelle chose de plus souple, par exemple pour trier un tableau avec de nombres et des chaines. On peut définir un tableau acceptant tous les types et implémenter les fonctions de l'interface sort:(len , swap, less)
type Maliste []interface{}

func (a Maliste) Len() int      { return len(a) }
func (a Maliste) Swap(i, j int) { a[i], a[j] = a[j], a[i] }
func (a Maliste) Less(i, j int) bool {
    si := fmt.Sprintf("%v", a[i])
    sj := fmt.Sprintf("%v", a[j])
    if _, ok := a[i].(int); ok {
        si = fmt.Sprintf("|%v", a[i])
    }
    if _, ok := a[j].(int); ok {
        sj = fmt.Sprintf("|%v", a[j])
    }

    return si < sj
}

Utilisation:

g := Maliste{3, 6, "a", 4, 1}
    fmt.Printf("g avant tri: %T, %v\n", g, g)
    sort.Sort(g)
    fmt.Printf("g apres tri: %T, %v\n", g, g)


Ici dans la fonction less , comme dans l'exemple Python , j'ajoute un caractère dans un entier afin de faire passer les lettres avant les chiffres. Avec comme résultat:

g avant tri: main.Maliste, [3 6 a 4 1]
g apres tri: main.Maliste, [a 1 3 4 6]

Inverser l'ordre d'un tri.


En Python, l'option reverse permet de sélectionner l'ordre descendant:
 Exemple:

Pour le Go : c'est la fonction sort.Reverse qui inverse l'ordre.
sort.Sort(sort.Reverse(g))

g apres tri: main.Maliste, [a 1 3 4 6]
g apres inversion tri: main.Maliste, [6 4 3 1 a]


Voila pour les opérations basiques de tri.

lundi 28 août 2023

Régression linéaire, RIDGE et LASSO

 Le modèle de régression linéaire est un des piliers de l'apprentissage automatique. Simple et facile à mettre en œuvre, il n'est pourtant pas le seul de ce type.

Pour commencer savez vous à quoi sert la queue d'un cerf-volant ?

Son rôle premier n'est pas décoratif, la queue sert à lester l'arrière du cerf volant et ainsi le stabiliser. Sans elle le cerf-volant serait trop réactif et incontrôlable. 
Il en est de même pour les modèles d'apprentissage: ils ont besoin d'une régulation pour être moins sensibles aux variations des données. C'est ce que nous allons voir avec les modèles RIDGE et LASSO.

Un modèle de régression linéaire consiste à trouver l'équation d'une droite qui traverse au plus prés un nuage de point.

Construction d'un jeu de données. 

Je vais utiliser le générateur de données de SKLEARN pour construire une jeu de données se prêtant à une régression linéaire. Les principaux paramètres à choisir sont:
  • La taille du jeu de données
  • Le nombre de facteurs déterminants 
  • Le niveau de distorsion
 

from  sklearn.datasets import make_regression
 
X,y = make_regression(n_samples=100, 
                      n_features=3,
                      bias = 1,
                      n_informative=1, 
                      noise=30, 
                      random_state=1) 
Ici nous aurons 3 facteurs dont 2 sans trop d'information pour le modèle.

On peut le visualiser par l'affichage des corrélations

Seul de le dernier facteur (numeroté 2 ) affiche une corrélation avec le résultat (y) : en bas à droite.

J'isole des données de test avec la méthode split de SKLEARN:



Modélisation.

Je vais appliquer successivement une regression linéaire, Ridge et Lasso et comparer les pentes de la droite des modèles.

# Les imports
from sklearn.linear_model import LinearRegression
from sklearn.linear_model import Ridge
from sklearn import linear_model
 
# regression linéaire
reg = LinearRegression()
reg.fit(X_train,y_train)
 
# regression ridge
ridg = Ridge(alpha=10.0)
ridg.fit(X_train, y_train)
 
# regression Lasso
lasso = linear_model.Lasso(alpha=5)
lasso.fit(X_train, y_train)
On va tout d'abord vérifier que seul un facteur sur les trois est significatif:

X2 est bien le facteur dominant.

Dans le cadre de la régression linéaire , l'apprentissage va consister à déterminer la combinaison des coefficient des trois facteurs qui minimise les distances entre les points de mesure et le point calculé.

Si, on a trop de données ou à l'inverse très peu, le modèle va se comporter comme un cerf-volant sans queue. Il sera sensible aux variations des données à prédire. 

Aussi les modèles Ridge et Lasso visent à ajouter des contraintes sur la valeurs des coefficients. Mathématiquement le modèle Ridge va ajouter une régulation dite L2 et le modèle Lasso , une régulation L1.
L1 et L2 sont comme les handicaps pour les courses de chevaux:  une contrainte afin de réduire les inégalités.

La méthode générale pour calculer les coefficients restent les mêmes, on ajoutera en plus des contraintes ne concernant que les coefficients.

Le modèle RIDGE.

Ridge va tenter d'obtenir des coefficient plus petit afin d'adoucir la pente de la droite.
Cela se traduira pour une variation des X, une moindre variation du 'y'.
Illustration: 



Le modèle Lasso. 

Il va plus loin , il peut donner un coefficient égal à 0 pour un facteur et ainsi réduire le nombre de facteur d'un modèle.
Illustration pour le premier facteur:

 

Au niveau du score des modèles: ils sont proches, le Ridge est meilleur mais le Lasso sera peut être plus efficace dans le temps.

Cet article (lien )pour aller plus loin.

Conclusion:


Il ne faut pas hésiter à comparer plusieurs modèles, SKLEARN est votre ami, il simplifie grandement les choses.

Le notebook portant le code est sur github.






jeudi 24 août 2023

Python et golang : le passage de paramètre


 

En programmation le mode de passage de paramètre pour l'appel d'une fonction (ou d'une méthode) est un point central car il est souvent source de bug. Un paramètre passé à une fonction est avant tout une donnée. Généralement on peut distinguer deux modes de passage:

  • Passer la valeur de la donnée: le paramètre local de la fonction va être construit à partir de la recopie des valeurs de la donnée du bloc appelant.
  • Passer la référence de la donnée : La variable locale ne contiendra pas les valeurs de la donnée de départ mais la référence (adresse) de la donnée. C'est ce qu'on appelle un pointeur.

Une donnée d'un programme recoupe deux choses: une nom de donnée et un espace de stockage contenant la ou les valeurs associées à ce nom de donnée. 

Lorsque qu'on passe un paramètre par valeur, une nouvelle donnée est créée  et son espace associé est alimenté par une copie de la donnée émettrice. Dans le cas d'un passage par référence ou par adresse, le nom de donnée est créée et son espace associé est le même que celui de la donnée émettrice. Les deux données pointent vers le même espace mémoire.

Dans les langages sophistiqués, ce mécanisme est masqué au développeur. Il n'aura pas à se poser la question. Ainsi en Python, c'est l'adresse de la variable qui sera passée comme paramètre à l'appel de la fonction pour les données de type conteneur (list, tuple, set , obj) . C'est le contenu de la variable 'self' d'une méthode Python.

 Exemple pour la fonction suivante en Python:



Avec comme exécution: 

Et un effet de bord pas toujours voulu, illustré par Pythontutor : les deux variables pointent sur la même zone mémoire.

La même fonction appelée avec un tuple donnera un effet différent:




Explication: l'instruction '+='  n'a pas le même comportement en présence d'objet muable (list) qu' immuable (tuple). C'est pourquoi, il faut éviter de passer en paramètre des objets muables à une fonction Python.

Et en Golang ? .

Le Go permet de choisir son mode de passage de paramètre : par valeur (recopie) ou par pointeur (adresse).  Mais il va plus loin que ça. Pour une méthode, il va générer plusieurs signatures: une par valeur et une par pointeur. En fonction de ce que recevra la méthode, la bonne méthode sera appelée.

Exemple si on a codé ceci :

// GetFace: cette fonction/methode retourne le nombre de face du dé.
func (d De) GetFace() int {
	return d.Nbface
}
 
Bien que la signature préfigure une passage par valeur, il sera possible de fournir un pointeur.
Le compilateur aura généré les 2 formes de transmission: par valeur ou par pointeur
d1 := De{6}
    fmt.Printf("%v , %T\n", d1, d1)
    fmt.Printf("nb de face (adresse):%v\n", d1.GetFace())
    fmt.Printf("nb de face (pointeur):%v\n", (&d1).GetFace())
    d1p := &d1
    fmt.Printf("nb de face (pointeur V2):%v\n", d1p.GetFace())

Avec comme résultat:
{6} , libde.De
nb de face (adresse):6
nb de face (pointeur):6
nb de face (pointeur V2):6 
Cette simplification fonctionne dans aussi dans le cas inverse où une fonction doit utiliser un pointeur.

Cette facilité à une contrepartie : des difficultés à comprendre ou à reprendre un code.
Aussi, il est recommandé d'utiliser dans le codage et dans l'exécution le passage par adresse (pointeur)
Car seule l'utilisation des pointeurs permettra de modifier les attributs de la structures. 
Comme ci-dessous:
// methode setter
    d4 := DePipe{
        De{6},
        5,
    }
    fmt.Println("avant appel", d4)
    d4.SetFaceValeur(2)
    fmt.Println("apres appel", d4)
    tmp := &d4
    tmp.SetFacePointeur(3)
    fmt.Println("apres appel", d4)
    (&d4).SetFacePointeur(6)
    fmt.Println("apres appel", d4)

Avec comme exécution:
avant appel {{6} 5}
par valeur {{6} 2}
apres appel {{6} 5}
par pointeur {{6} 3}
apres appel {{6} 3}
par pointeur {{6} 6}
apres appel {{6} 6}

Conclusion: utilisez les pointeurs.

source https://github.com/germanlinux/lemongo/tree/main/lemonlabs/src/pointeurs

samedi 12 août 2023

De Python à Golang: premiers pas

 Passer d'un langage à un autre n'est pas toujours une démarche facile mais constitue un excellent exercice.

Avant de commencer à faire du Go (golang) , il faut retenir 3 choses:

  • La preparation du répertoire projet est importante: on ne peut pas faire un bout de code sur le coin de la table.
  • Il faut un bon IDE : Vscode ou VScodium sont tout à fait adaptés au Go: ils mettent en forme le code automatiquement.
  • La lecture de code Go est le meilleur moyen d'apprendre les bonnes pratiques du langage. Il ne faut pas chercher dans un premier temps à comprendre le code mais plutôt à observer comment le projet s'organise.


Commençons par un exemple simple: Je vais utiliser l'implémentation d'une librairie qui émule le lancer d'un dé en Python pour la transposer en Golang.




Cette classe propose 2 méthodes en plus du constructeur.

En Golang, il n'y pas de mécanisme d'encapsulation comme dans les autres langages, la description de l'objet est portée dans une structure, les fonctions viennent compléter l'ensemble.

type De struct{ Nbface int }
 
 
func (d De) GetFace() int {
	return d.Nbface
}
func (d De) Lance() int {
	n := rand.Intn(d.Nbface) + 1
	return n
}
 
On peut voir que les structures remplacent les classes. Que le 'self' envoyé en coulisse à une méthode Python, prend la forme d'un préfixe de la définition de la fonction en Go.
Comme Go est un langage compilé avec des types de variables statiques, une déclaration du type de la donnée est nécessaire. Mais il existe un sucre syntaxique bien utile l'opérateur ':='. Le type de la donnée d'affectation sera déduit à partir de la partie droite : signature de la fonction, type de la donnée reçue etc.

2 remarques: un élément déclaratif: type , nom de variable, conteneur, fonction etc.. sera utilisable en dehors de son fichier source (exportable) qu'a la condition que son nom commence par une lettre MAJUSCULE.

Utilisation de notre librairie


En python et en Go : par un import.

En python : from libde import De

Puis
En Go :



package main
 
import (
	"fmt"
	"lemonlabs/src/de/libde"
	"sort"
)
 
type De = libde.De
 
func main() {
 
	d1 := De{6}
	fmt.Printf("%v , %T\n", d1, d1)
	fmt.Printf("nb de face :%v\n", d1.GetFace())
	n1 := d1.Lance()
	fmt.Printf("face :%v", n1)
Exécution: go run .\main.go

{6} , libde.De
nb de face :6
face :3

Heritage vs composition.

Soit une classe Depipe qui permet de doubler les chances de sortie d'une face particulière.

En Python, on pourra utiliser les propriétés de l'héritage:

En Go, on utilisera la composition:


type De struct{ Nbface int }
 
type DePipe struct {
	De
	Faceplus int
}
 
A l'exécution de ce code:
d2 := DePipe{
        De{6},
        6,
    }
    fmt.Printf("%v , %T\n", d2, d2)
Avec le résultat suivant:

{{6} 6} , libde.DePipe

Par contre il faudra redevelopper la fonction getface pour depipe au moins d'utiliser une interface. 
(voir les prochains articles)

La suite des fonctions en Go



func (d DePipe) GetFace() int {
	return d.De.Nbface
}
func (d DePipe) Lance() int {
	n := rand.Intn(d.De.Nbface+1) + 1
	if n == d.De.Nbface+1 {
		n = d.Faceplus
	}
	return n
}

Remarque:  la structure qui sera passée aux fonctions Go (GetFace ou Lance) , sera une copie de la structure appelante  (par valeur) . 

Voici pour une première introduction, le source est disponible sur github .
Dans ces exemples, les champs ne sont pas mis à jour. Pour modifier un champ  d'une structure Go, il faudra utiliser un pointeur (dans les prochains articles) .

jeudi 3 août 2023

Collecte sur le web (3)

 A nouveau une liste de liens relatifs à Python, Golang , webSSO et divers sujets.

Python

Divers

Cloud - IAM

Outils de présentation

Langage GO - golang


jeudi 22 juin 2023

Traitement des données pour l'apprentissage automatique: suite

 Dans un article précédent, j'ai commencé à exposer des techniques pour réduire les données à traiter pour un dispositif d'apprentissage automatique. (voir article).

Continuons notre cheminement : comment éliminer les facteurs moins utiles ou encore comment supprimer des données non conformes (erreur de mesure etc.) 

Analyse des corrélations.

Il est possible d'obtenir la corrélation deux à deux des facteurs avec Pandas avec la méthode corr()


Une valeur proche de zéro est un signe de facteurs indépendants. 


On peut aussi avoir une confirmation visuelle par les lignes suivantes:

corrélation 2 à 2
A noter le ';' en fin d'instruction permettant de cacher les méta-informations de Matplotlib

Garder pour un apprentissage 2 facteurs qui sont fortement corrélés n'apportera pas d'informations nouvelles. Aussi, on peut supprimer un des deux facteurs sans risque d'appauvrir les informations.

Eliminations des valeurs aux extrémités.


Pour cela plusieurs solutions sont proposées

La méthode par l'écart type ou la déviation standard.

On ne conservera que les valeurs comprises dans une fourchette dont les extrémités sont calculées à partir de la moyenne et de l'écart type.  Il est possible de jouer sur l'amplitude de la fourchette.


Ici , on a pris 2 fois l'ecart type de chaque coté pour obtenir l'amplitude de la fourchette.
2 valeurs seulement sont éliminées à droite de la moyenne.

La méthode par la médiane et les quantiles.

La méthode quantile de Pandas permet de séparer les facteurs en jeux de données de même nombre (quantile(0.5)
Il faut donc calculer le quantile pour 0.25 puis 0.75
Exemple:

L'intervalle interquartile sera donné par quantile(0.75) - quantile(0.25)

On pourra déterminer une borne inférieure et supérieure: 
borne int = quant25 - irq * 1,2
et 
borne sup = quant75 + irq * 1.2
(1.2  à 1.5 sont des coefficients généralement utilisés)


Méthode avec LocalOutlierFactor   de sklearn


Utilisation :


Apres filtrage les valeurs retenues sont en rouge: