vendredi 21 avril 2023

exo-3 Python initiation

 La solution de l'exo-2 est bas de l'article.


Enoncé de l'exercice 3 python initiation.


Ecrire une fonction qui prend en paramètre une mot et qui retourne VRAI OU FAUX en fonction de la nature mot: est il un palindrome ou non.  Un palindrome est un mot qui se lit dans les deux sens.

exemple: 

print(palindrome('essai'))
print(palindrome('rotor'))    

retournera False puis True

Solution de l'exo-2 initiation.


with open('fichier_exo2.txt') as file:
    lignes= file.readlines()
 
cp = 0
lg = len(lignes)
bloc =''
for ligne in lignes:
    ligne = ligne[:-1]
    if ligne[-6:] == ' suite':
        bloc += ligne[:-5]
 
    else:
        if len(bloc) > 0 :
            cp += 1
            print(f"({cp}){bloc}{ligne}")
            bloc =''
        else:
            cp += 1
            print(f"({cp}){ligne}")
 

dimanche 16 avril 2023

A la recherche de la donnée perdue: avec Pandas et sklearn

 Les jeux de données pour alimenter un dispositif d'apprentissage automatique ne sont pas toujours homogènes ou cohérents. Il y a parfois des 'trous' dans les données. Ces manques peuvent avoir diverses origines: mauvais relevé, erreur de type ou d'encodage.

 La méthode info() d'un dataframe permet de connaitre l'état général des données : type et remplissage!;

 
dt.info()
<class 'pandas.core.frame.DataFrame'>
RangeIndex: 20 entries, 0 to 19
Data columns (total 2 columns):
 #   Column  Non-Null Count  Dtype  
---  ------  --------------  -----  
 0   X       16 non-null     float64
 1   y       20 non-null     float64
dtypes: float64(2)
memory usage: 448.0 bytes
ici , on traitera des données de type 'float' et 4 données de la première colonne sont manquantes:
None ou 'nan' :not a number

Le détail par ligne: 
dt[dt.isnull().values]
        X	    y
3	NaN	-52.517783
5	NaN	-23.697502
10	NaN	-13.255462
12	NaN	8.366929
Il n'est pas recommandé de conserver des données nulles dans son jeu de données. Les frameworks d'apprentissage
risquent de dysfonctionner. Face à ce problème, il peut y avoir plusieurs stratégies:
Supprimer la ligne entière ou essayer de 'combler' les trous.
Supprimer les lignes incomplètes est une option valable quand le nombre de lignes 'saines' est important.
Dans le cas présent, le jeu de données serait amputé de  4 lignes sur 20 => 1/5eme, la proportion est trop importante.
l'opération est plutôt simple, elle se fait par la méthode dropna()
Penser à ajouter l'option inplace=True pour mettre à jour le dataframe.
dt.dropna()
 
X	y
0	-1.913280	-87.286608
1	-1.724918	-79.815577
2	-1.412304	-58.442734
4	-0.908024	-27.520687
6	-0.469474	-19.956148
La ligne 3 a été supprimée.

Les solutions pour combler les manques.


On peut utiliser des  méthodes issues de Pandas ou d'autres plus sophistiquées portées par le framework scikit-learn (sklearn).

Les solutions Pandas

Elles sont couvertes par les méthodes:  replace() , fillna() , interpolate().
Replace est la plus généraliste: elle permet de remplacer une donnée par une autre.
Dans le cas présent il faut déterminer la valeur de remplacement. On peut comme ici mettre la valeur 0 en lieu et place des valeurs indéfinies.
La valeur  a remplacer est portée par le module numpy
dt.replace(np.nan , 0)
X	y
0	-1.913280	-87.286608
1	-1.724918	-79.815577
2	-1.412304	-58.442734
3	0.000000	-52.517783
4	-0.908024	-27.520687

La méthode fillna() est plus puissante , on peut choisir la valeur de remplacement suivant plusieurs méthodes:
 ‘backfill’, ‘bfill’, ‘ffill’, None   et  defaut None
Pandas utilisera la valeur, avant ou apres pour remplir les manques.
Exemple 
dt.fillna(method = 'bfill')  # ou dt.bfill()
	X	y
0	-1.913280	-87.286608
1	-1.724918	-79.815577
2	-1.412304	-58.442734
3	-0.908024	-52.517783
4	-0.908024	-27.520687

L'ordre des données est important, peut-être faut il trier les valeurs avant.
Ci dessous, un jeu de données est généré à partir de sklearn, assemblé dans un dataframe puis trié sur la colonne 'X'.

from sklearn.datasets import make_regression
X, y = make_regression(n_samples=20, n_features=1, noise=5, random_state=42)
print(X.shape)
print(y.shape)
dt = pd.DataFrame(zip(X.flat, y), columns = ['X', 'y'])
dt.sort_values('X', inplace =True)
 
 
(20, 1)
(20,)
           X          y
15 -1.913280 -87.286608
3  -1.724918 -79.815577
5  -1.412304 -58.442734
7  -1.012831 -52.517783
4  -0.908024 -27.520687
Pour terminer avec Pandas, la méthode  interpolate()  offre la possibilité de prédire les données manquantes en utilisant une regression linéaire.  Ici l'ordre de presentation importe peu.
Elle propose un bon nombre de résolution : 
  • 'linear’
  • ‘time’ pour les dates
  • ‘index’, ‘values’
  • ‘pad’ 
  • ‘nearest’
  • ,‘zero’
  • ‘slinear’
  • ‘quadratic’
  •  ‘cubic’
  • ‘from_derivatives’
 etc.

SKLearn , propose trois méthodes principales:

  • SimpleImputer : très basique, il remplace les valeurs par la moyenne , la médiane etc.
  • KNNImputer: utilise un algorithme basé sur le calcul de distance de voisinage de point
  • IterativeImputer: module experimental et très puissant.
Pour utiliser ces modèles, il est préférable de décharger le dataframe dans des structures numpy:


from sklearn.impute import KNNImputer,SimpleImputer
imp = IterativeImputer(random_state=0)
np1 = dt[['X', 'y']].to_numpy()
np2 = imp.fit_transform(np1)
simpl = SimpleImputer()
np3 = simpl.fit_transform(np1)
kn  = KNNImputer()
np4 = kn.fit_transform(np1)

Le graphisme ci-dessous visualise les résultats obtenus avec les différentes méthodes.
On commence par introduire des valeurs np.nan dans l'axe des X.

Par:
dt.iat[3, 0] = np.nan
dt.iat[10, 0] = np.nan
dt.iat[12, 0] = np.nan
dt.iat[5,0] = np.nan





Conclusion: ceux qui s'en sortent le mieux sont :
Pour Pandas : interpolate()
Pour SKLearn : KNNImputer (le meilleur) suivi de IterativeImputer mais il faudrait tester pour chacun les options disponibles.

from sklearn.metrics import mean_squared_error
print(mean_squared_error(dt_nan.X, np3[:,0] ))# SimpleImputer
print(mean_squared_error(dt_nan.X, np4[:,0] )) #KNNImputer
print(mean_squared_error(dt_nan.X, np2[:,0] )) # iterativeImputer
print(mean_squared_error(dt_nan.X, dt_inter.X )) #pandas interpolate
 
0.05726013724292493   # SimpleImputer
0.000988070710285649  #KNNImputer
0.002632595263623769  # iterativeImputer
0.003185180237325761  #pandas interpolate
Pour rappel, il n'est pas possible de connaitre à l'avance quel sera la meilleure solution.

vendredi 14 avril 2023

exo-2 python initiation

La solution de l'exercice 1 est donné à la fin de l'article.

 Exercice 2.


Soit un fichier contenant des lignes.

Une ligne de texte peut se continuer sur les lignes suivantes.

Le mot 'suite' est ajouté en fin de ligne pour indiquer que la suite est sur les lignes suivantes (1 ou n lignes)

exemple : 

mon texte ligne une

un autre texte suite

sur deux lignes   



Il faut afficher :

(1)mon texte ligne une

(2)un autre texte sur deux lignes

Un numéro de ligne est à ajouter en tout début de ligne.


Travail demandé.

1 ) Faire un programme qui va lire le fichier d'essai donné en exemple ici.

(telecharger le fichier et l'installer dans le répertoire du notebook)

Et qui produira le resultat suivant:

(1)Beautiful is better than ugly

(2)Explicit is better than implicit Simple is better than complex

(3)Complex is better than complicated

(4)Une histoire de poursuite

(5)Flat is better than nested Sparse is better than dense Readability counts

(6)Special cases aren't special enough to break the rules

(7)Although practicality beats purity

(8)Errors should never pass silently Unless explicitly silenced

(9)In the face of ambiguity, refuse the temptation to guess.

(10)There should be one-- and preferably only one --obvious way to do it.

(11)Although that way may not be obvious at first unless you're Dutch.

(12)Now is better than never


2 ) Même question mais en commençant par la derniere ligne pour terminer sur la première ligne.


Corrigé de l'exercice 1


#!/usr/bin/env python
# coding: utf-8
 
# Question 1 classe  Et logique
class EtLogique:
    '''simulateur de circuit logique ET'''
    def __init__(self):
        self.entreeX = 0
        self.entreeY = 0
        self.sortieET = 0
    def etat(self):
        self.sortieET = 0
        if self.entreeX == 1 and self.entreeY == 1:
            self.sortieET = 1
            return self.sortieET
        else:
            return 0
    def set_entreeX(self, valeur):
        if valeur:
            self.entreeX = 1
        else:
            self.entreeX = 0
 
    def set_entreeY(self, valeur):
        if valeur:
            self.entreeY = 1
        else:
            self.entreeY = 0
 
 
composant1 = EtLogique()
print(composant1.etat())
# 0
 
composant1.set_entreeX(1)
composant1.set_entreeY(True)
 
 
print(composant1.etat())
print(vars(composant1))
# 1
# {'entreeX': 1, 'entreeY': 1, 'sortieET': 1}
 
composant_test = EtLogique()
cas = [(0,0), (1,0),(0,1), (1,1)]
 
for item in cas:
    composant_test.set_entreeX(item[0])
    composant_test.set_entreeY(item[1])
    print(composant_test.etat())
# 0
# 0
# 0
# 1   
 
 
 
# Question 2 : circuit logique
composant1  = EtLogique()
composant2  = EtLogique()
comp_resultat = EtLogique()
composant1.set_entreeX(1)
composant1.set_entreeY(1)
 
composant2.set_entreeX(1)
composant2.set_entreeY(1)
 
comp_resultat.set_entreeX(composant1.etat())
comp_resultat.set_entreeY(composant2.etat())
print(comp_resultat.etat())
print(vars(comp_resultat))
# 1
# {'entreeX': 1, 'entreeY': 1, 'sortieET': 1}
 
 
# Question 3 non logique
class Non():
    def __init__(self, composant):
        self.composant = composant
    def etat(self):
        if self.composant.etat() == 1:
            return 0
        else:
            return 1
 
 
 
 
composantEt = EtLogique()
non1 = Non(composantEt)
print(non1.etat())
 
# 1
 
 
# Question 4 NonET
non_test = Non(composant1)
print(non_test.etat())
 
# 0
 
 
composant1.set_entreeY(0)
print(composant1.etat())
print(non_test.etat())
 
# 0
# 1
 
 
 

dimanche 9 avril 2023

Les deux dispositifs de gestion mémoire de CPython

 CPython est la la machine virtuelle de référence d'exécution des programmes Python. CPython compile et interprète les sources d'une programme python pour son exécution.

A ce titre, il prend en charge la gestion de la mémoire pour les variables ou les objets Python. 

Dans un but de rationnaliser l'empreinte mémoire, il va mettre en place deux mécanismes distincts pour gérer au mieux cette mémoire.

Un objet est composé de deux choses: un nom (1) de variable qui fait référence à une zone mémoire (2).

Quand un objet est détruit (commande del ?, fin de bloc ?) , le nom de l'objet (variable) est enlevé de l'espace de nommage mais la zone mémoire ne sera forcement rendue disponible. C'est à ce moment que les deux mécanismes évoqués vont entrer en jeu.

 1) La gestion par compteur de référence.

Ce dispositif est à la fois simple, robuste et efficace.

Chaque fois un objet est référencé par un autre, un compteur de référence est incrémenté de 1. Le mécanisme inverse s'applique chaque fois qu'un objet est déréférencé par un autre objet. Quand le compteur arrive à 0 , l'espace mémoire occupé par l'objet se récupéré.

Remarque : une opération comme celle-ci : a = b * 4 va incrémenter de 1 le compteur de référence de la variable b puis à la fin de l'operation le décrémenter. 


Exemple: utilisation de getrefcount pour afficher le nombre de référence.

import sys
>
a = 50000
b = ['etoile', a, 'neige']
c = (a, b)
d ='srer'
print('a', sys.getrefcount(a))
print('b', sys.getrefcount(b))
print('c', sys.getrefcount(c))
print('d', sys.getrefcount(d))
 
=>
a 4
b 3
c 2
d 2   

On supprime un objet qui en référençait un autre
del(c)
print('a', sys.getrefcount(a))
print('b', sys.getrefcount(b))
print('d', sys.getrefcount(d))
 
=> 
a 3
b 2
d 2
(mise en forme avec https://highlight.hohli.com/)

Mais parfois le dispositif peut être en pris en défaut par la présence de références cycliques:)

Soit deux listes
y = ['un' , 'deux' ]
z = [3, 5, 7]
print('y', sys.getrefcount(y))
print('z', sys.getrefcount(z))
 
=>
y 2
z 2
On ajoute à chaque liste , l'autre liste.
y.append(z)
z.append(y)
print('y', sys.getrefcount(y))
print('z', sys.getrefcount(z))
 
=>
y 3
z 3
On supprime la liste 'z'
del(z)
 
 
print('y', sys.getrefcount(y))
print(y)
 
=>
y 3
['un', 'deux', [3, 5, 7, [...]]]
Le compteur de référence sur y n'a pas bougé car même si la variable n'est plus accessible,  son contenu persiste en mémoire et il pointe toujours sur y.

image de https://pythontutor.com/
Avant la suppression de la liste Z



Apres suppression de Z , l nom de variable 'z' nest plus reconnu mais la zone mémoire est toujours utilisée.

On peut avoir le même phénomène avec un objet qui se référence lui même:


Exemple ici en faisant:
x =[]
x.append(x)

Pour pallier à cette carence, CPython utilise un deuxième dispositif: le garbage collector.

 2) Le garbage collector de CPython.


Il est doté de trois collections ou conteneurs  permettant de stocker des éléments du plus récent (collection 0) au plus ancien (collection 2): il est dit 'générationnel' .

Chaque fois qu'un objet devient  injoignable et que le dispositif de comptage n'a pas pu libérer la zone, il sera collecté et examiné finement type par type pour résoudre le dilemme.  

Le module gc permet de manipuler le garbage collector.
La méthode get_threshold() retourne les seuils pour les 3 générations: lorsque le nombre d'objet d'une génération  dépasse le seuil, le garbage collector se met en œuvre pour examiner chaque objet et résoudre les incohérences (références circulaires) . Un objet récent passera de la collection 0 , à la collection 1 puis enfin à la 2 qui contiendra de fait les objets les plus anciens. 

import gc
gc.get_threshold()
 
=>
(700, 10, 10)
 
gc.get_stats()
 
=>
[{'collections': 370, 'collected': 32693, 'uncollectable': 0},
 {'collections': 33, 'collected': 3952, 'uncollectable': 0},
 {'collections': 3, 'collected': 1561, 'uncollectable': 0}]
La méthode get_stats() retourne le nombre d'objet surveillé par génération.
Pour illustration:
On va créer une référence circulaire , puis supprimer l'objet et voir ainsi le travail du garbage collector
Le garbage collector est placé en mode debug.
# Préparation 
gc.set_debug( gc.DEBUG_COLLECTABLE| gc.DEBUG_SAVEALL )
n = gc.collect()
zorro = [1, 2,'autre chose']
maliste = ['ert', 4, 'divers']
 
# Création d'une référence circulaire
zorro.append(zorro)
# suppression de l'objet
del zorro
 
# Mesures
avant = [ str(x) for x in  gc.garbage]
print(len(avant))
avant= set(avant)
n = gc.collect()
print(n)
apres =  [ str(x) for x in  gc.garbage]
print(len(apres))
apres = set(apres)
dif = apres- avant
print("result ", dif)
 
=> 
1753
1
1754
result  {"[1, 2, 'autre chose', [...]]"}
gc: collectable <list 0x0000017E6AFDD480>
La zone mémoire sera libérée et sera disponible pour un autre stockage.
 MAIS ce n'est pas pour autant que CPython rendra de l'espace mémoire au système d'exploitation.

Conclusion.

Il est possible de modifier le seuil de déclenchement du garbage collector (méthode set_threshold) 
ou encore de le désactiver. Il n'y aucune raison a priori de modifier le comportement par défaut.

vendredi 7 avril 2023

Exercice 1 : python initiation

Niveau: initiation : solution la semaine prochaine

Bon weeek-end.

On désire émuler un opérateur logique ET. 

1) Créer une classe EtLogique qui aura pour attribut:

  • Une entreeX  (valeur 0 ou 1, booléen etc )
  • Une entreeY  (valeur 0 ou 1, booléen etc )
  • Une sortieET
  • Une méthode pour mettre à jour entreeX ou entreeY
  • Une méthode pour connaitre l'état de la sortieET




Tester le dispositif avec les 4 cas différents.

2) Faire un circuite logique avec 2 opérateurs logique connectés un troisième operateur logique.

Tester le dispositif.


3) Faire un émulateur de l'operateur logique NonLogique 

Il inverse la valeur d'entrée



4) Faire un circuit pour simuler l'inverse du Et logique (NAND)  

vendredi 24 mars 2023

Collecte sur le web (2eme article)

 Apres un premier article listant mes découvertes sur le web (du python mais pas seulement) ,voici la seconde édition plus structurée.



Sujets : pour le manager.

Simon Wardley  est un chercheur britannique et ancien PDG surtout connu pour la création de la cartographie permettant de visualiser dynamiquement la maturité d'une innovation ou d'une transformation.
L'objectif est de faire une presentation de 20 slides de 20 secondes chacun. Pour un total de 6min 40.
Ce mode permet d'aller à l'essentiel et de ne pas assommer l'auditoire.

Python:


Sujets datasciences.

Sujets pour les administrateurs systèmes.

Sujets architectures.


mardi 21 mars 2023

5 choses à savoir sur les dataclasses Python

La souplesse et les facilités qu'apportent les dataclasses, me font penser qu'un jour les dataclasses se retrouveront mises en  avant dans Python et soient dans le futur, le moyen natif de créer des classes.

(le notebook ci-dessous se retrouve sur mon github)

1) Comment savoir quel sera le comportement du constructeur de la classe ?

Le constructeur de la classe est généré par la dataclasse et il n'est pas visible en l'état.

Pour comprendre ce qui va se passer, on utilisera le module 'dis' qui permet de désassembler le code Python. Un source Python est traduit en bycode qui sera exécuté par la VM Python. Le module 'dis' montre la traduction qui sera faite de ce bytecode.

Exemple: Soit une classe dont le constructeur attend deux paramètres ident et nom.


L'utilisation du module dis donnera: 



Sur le listing, on  retrouve sans trop de difficulté les deux opérations d'initialisation du constructeur.

Il y  toujours la possibilité de modifier l'instanciation d'un nouvel objet en implémentant la méthode :__post_init__(). 
Cette méthode est appelée après le constructeur.

2) Dans le cadre de l'héritage, Dataclasse prend en charge pour vous l'appel du constructeur de la classe mère.

Dans le cadre de l'héritage, il est parfois nécessaire de faire appel à super().__init__(...)  pour initialiser les attributs de la classe mère.



Dans la classe fille:

Avec les dataclasses c'est plus simple:

En reprenant la dataclasse Exemple, on voit que le constructeur prend en charge les paramètres pour la classe mère et la classe fille


Attention: si la classe mère propose des valeurs par défaut,  les attributs de la classe fille doivent aussi en fournir. On retrouve ici la règle des paramètres  sans valeur par défaut en premier. C'est pour cela que l'attribut prénom est prévu avec une valeur par défaut.


3) Comment déclarer une variable de classe.

On va devoir utiliser le module  typing 



Avec l'initialisation de la variable de classe

4) Utilisation d'un attribut mutable (exemple: une liste ) dans le constructeur.



Dans cet exemple : l'attribut mail ne sera pas initialisé par le constructeur. Par contre ma_liste va poser un problème et provoquera l'erreur suivante:


Il faudra passer par une fabrique :


5) Il est possible d'adapter le comportement général de la classe en passant des paramètres au décorateur.


Exemple 
@dataclass(init=True, repr=True, eq=True, order=False, unsafe_hash=False, frozen=False,
           match_args=True, kw_only=False, slots=False, weakref_slot=False)



Génération du constructeur, de la méthode __repr__  etc.

Ne pas hésiter à consulter la documentation du module par la commande help('dataclasses')  ou directement dans une console pydoc dataclasses