Introduction à python pour le machine learning

Vous souhaitez vous initier au machine learning mais vous ne connaissez pas le python ? Vous êtes au bon endroit ! (1h de cours)

À l'heure actuelle, python domine les domaines de la science des données et de l'apprentissage automatique, après une longue période de suprémacie de R.

Vous devez donc absolument connaître ce langage pour pouvoir utiliser efficacement le machine learning.

Mais peut-être pensez-vous qu'apprendre un nouveau langage, ce n'est pas si simple.

Eh bien, python est spécial :

  • C'est vraiment facile de s'y mettre
  • Vous commencerez à être productif après seulement quelques jours de pratique
  • C'est un plaisir de l'utiliser

Pour ce tuto, je vais supposer que vous n'êtes pas un développeur professionnel, et je vous donnerai juste les bases nécessaires pour que vous puissiez vous lancer dans la data science et le machine learning.

J'espère que cela vous donnera un point de départ et la motivation pour en savoir plus sur ce superbe langage.

Vous ne le regretterez pas.

Ce tuto est conçu pour être interactif.

Pour l'exécuter, le plus simple est d'utiliser Google Colab en cliquant simplement sur ce lien . Vous pourrez exécuter le code vous-même et le modifier.

Comment obtenir de l'aide ?

Il est impossible de couvrir l'intégralité de python dans cette courte introduction, et beaucoup de sujets ne seront donc pas abordés.

Par exemple, nous vous expliquerons ce qu'est une liste, mais ne vous expliquerons pas comment la trier.

Et, au bout de quelques jours, vous aurez probablement oublié certains des concepts ou commandes spécifiques expliqués ici.

C'est tout à fait normal, et ce n'est pas un problème. Ne vous inquiétez pas pour ça !

Mais il est essentiel que vous sachiez comment trouver plus d'informations sur python lorsque vous en avez besoin.

La communauté python est extrêmement vaste, et la meilleure source d'information est donc Google. Si vous tapez simplement python doc sort list sur Google, vous obtiendrez votre réponse en quelques secondes.

De manière générale, la documentation python est très bonne, alors assurez-vous de l'utiliser.

Et si vous voulez aller plus loin après ce cours accéléré, vous pouvez commencer par le tutoriel python. Vous y passerez un jour ou deux, mais cela en vaut vraiment la peine.

Variables et objets

Vous avez probablement entendu parler de la programmation orientée objet, et cela peut vous sembler compliqué.

Ne vous inquiétez pas, nous n'allons pas en faire dans ce cours.

Mais nous passerons notre temps à utiliser des objets, car en python, tout est un objet. Un entier, une chaîne, une liste de valeurs et même une fonction : toutes ces choses sont des objets.

Commençons par regarder un entier. Avec la fonction intégrée id, nous pouvons obtenir l'identifiant unique associé à cet objet entier :

In [1]:
id(1)
Out[1]:
94719625988608

Et avec la fonction type, nous pouvons obtenir le type de l'objet :

In [2]:
type(1)
Out[2]:
int

Définissons maintenant une variable a, affectons-lui la valeur 1, et affichons-la :

In [3]:
a = 1
print(a)
1

Dans un notebook jupyter, comme nous l'avons fait ci-dessus, appeler print n'est pas toujours nécessaire pour imprimer la valeur d'un objet. A la fin d'une cellule, il suffit d'évaluer l'objet comme ceci :

In [4]:
b = 2
b
a
Out[4]:
1

Mais dans un script python, ou si vous n'êtes pas à la fin d'une cellule du notebook, vous devrez utiliser print.

Vous avez peut-être remarqué une grande différence par rapport aux autres langages, lorsque nous avons défini la variable « a » : nous n'avons pas eu à déclarer le type de la variable. En C++, par exemple, on taperait int a = 1;

En python, le type d'une variable se déduit du type de l'objet qui lui est affecté, ici un entier. Donc a est de type int :

In [5]:
type(a)
Out[5]:
int

Vérifions maintenant l'identifiant unique de a :

In [6]:
id(a)
Out[6]:
94719625988608

C'est l'identifiant de 1 ! Et c'est parce que a et 1 correspondent exactement au même objet en mémoire.


Exercice:

Considérez ce code :

b = 1
c = b

Pouvez-vous deviner quels seront les identifiants de « b » et « c » ? Vérifiez votre hypothèse dans la cellule ci-dessous :

In [ ]:
# write your code, and execute the cell with shift+enter

Important:

En python, les variables sont appelées "noms" ("names").

Et le processus d'attribution d'une valeur à une variable est appelé « liaison de nom » ("name binding"). En d'autres termes, vous pouvez voir une variable comme une étiquette attachée à un objet.

Lorsque nous faisons a=1, nous attachons l'étiquette a à l'entier 1. Si vous attachez une autre étiquette à 1, le même objet porte maintenant deux étiquettes. Lorsque nous procédons comme suit, nous déplaçons simplement l'étiquette a de 1 à 2 :

In [ ]:
a = 1 
a = 2

Même si ça semble évident, ne l'oubliez pas, car cela peut conduire à de nombreux malentendus, comme nous le verrons plus tard, dans la section sur les boucles.

Conditions

Très souvent, vous devrez prendre des décisions dans vos programmes, en fonction de la situation. Voici un exemple.

In [7]:
do_great_the_world = True
if do_great_the_world: 
    print('hello_world')
else: 
    print('get lost')
hello_world

Transformez maintenant do_great_the_world en False et réexécutez la cellule. Ça y est, vous savez faire de l'exécution conditionnelle.

À propos de l'indentation du code

En python, les indentations sont importantes, et ne sont pas seulement là pour rendre le code lisible et joli. Lorsque vous indentez le code (utilisez simplement la tabulation pour cela), vous spécifiez que vous créez un contexte imbriqué. Par exemple, dans le code ci-dessus, la ligne print('hello_world') est "sous" la condition if et ne sera exécutée que si do_great_the_world est vrai.

Dans la plupart des autres langages, cependant, les contextes sont entourés de séparateurs, comme '{}' en C++. Les personnes venant de ces autres langages n'aiment souvent pas être obligées de mettre en retrait.

Mais si vous vous lancez dans python, vous vous rendrez compte que :

  • ne mettre aucun séparateur permet une saisie plus rapide et plus facile
  • si les gens n'indentent pas, le code ne fonctionnera pas, vous n'aurez donc plus à perdre de temps à essayer de comprendre le code non indenté d'autres personnes.
  • Les éditeurs de texte et en particulier les IDE rendent très facile et naturel l'indentation : souvent, l'éditeur indentera automatiquement, et sinon il vous suffit d'appuyer sur la tabulation (ou sur shift+tab pour revenir en arrière) pour indenter le bloc de texte sélectionné.

Boucles

Les boucles permettent de répéter plusieurs fois une opération. C'est généralement à cela que servent les programmes, nous devons donc apprendre à le faire.

Tout d'abord, nous devons définir quelque chose sur lequel nous pouvons boucler, c'est à dire un itérable. Une liste est un itérable, et en voici une :

In [8]:
a = [1, 2, 3, 4]
print(a)
[1, 2, 3, 4]

Notez comme la fonction d'impression s'adapte à l'objet imprimé. Vous pouvez l'essayer sur n'importe quel type d'objet, et vous obtiendrez généralement un bon résultat.

Maintenant, écrivons une boucle pour calculer les carrés de toutes les valeurs de la liste, et pour sommer ces carrés :

In [9]:
squares = []
sumsq = 0
for x in a: 
    x2 = x**2
    squares.append(x2)
    sumsq += x2
print(squares)
print(sumsq)
[1, 4, 9, 16]
30

Ce que vous savez à ce stade sur les boucles est probablement suffisant dans la plupart des programmes, mais nous allons jeter un œil à quelques raccourcis syntaxiques.

Vous pouvez boucler de manière très compacte en utilisant les compréhensions de listes :

In [10]:
squares = [x**2 for x in a]
squares
Out[10]:
[1, 4, 9, 16]

Cela peut être pratique, et vous rencontrerez très souvent ce genre de constructions. Mais n'en abusez pas. Les débutants ont souvent tendance à utiliser plusieurs compréhensions de liste pour calculer différentes valeurs à partir du même itérable, par exemple :

In [ ]:
squares = [x**2 for x in a]
cubes = [x**3 for x in a]
print(squares)
print(cubes)
[1, 4, 9, 16]
[1, 8, 27, 64]

Mais ce n'est ni élégant ni efficace. Si vous devez calculer plusieurs choses à partir du même itérable, utilisez une construction de boucle standard pour économiser du temps CPU et faire plus pro.

Souvent, vous devez obtenir les valeurs de l'itérable, ainsi que l'index auquel la valeur est stockée. Pour cela, utilisez enumerate :

In [11]:
for i, x in enumerate(a): 
    print(i, x)
0 1
1 2
2 3
3 4

Exercice

Revenons à la liaison de noms dans le contexte des boucles. Dans le code ci-dessous, on boucle sur une liste, et on essaie d'ajouter 1 à tous les éléments de la liste, mais ça ne marche pas... comprenez-vous pourquoi ?

In [12]:
lst = list(range(5))
print(lst)
for x in lst:
    x += 1
print(lst)
[0, 1, 2, 3, 4]
[0, 1, 2, 3, 4]

Entrée et sortie de base

Pour la science des données, et surtout si les données commencent à devenir volumineuses, nous utiliserons des bibliothèques spécifiques pour lire les fichiers de données.

Vous verrez ça plus tard lorsque vous commencerez à utiliser des bibliothèques de science des données telles que numpy ou pandas.

Ici, vous apprendrez simplement à lire et à écrire des fichiers texte de base.

Écrivez dans un fichier texte en faisant :

In [13]:
a = [1, 2, 3, 4]
fname = 'myfile.txt'
# 'w' means that the file is opened in write mode
with open(fname, 'w') as out_file: 
    for i in a: 
        # we convert our integers to strings, 
        # and we write one integer per line
        # note that we need to add a newline 
        # character manually to the string,
        # which just contains the number
        out_file.write(str(i) + '\n')

Vous pouvez vérifier le contenu du fichier avec une fonction shell (le ! informe le notebook jupyter qu'il s'agit d'une fonction shell) :

In [14]:
! cat myfile.txt
1
2
3
4

Et maintenant vous pouvez relire le fichier :

In [15]:
with open(fname) as in_file: 
    for line in in_file: 
        # we remove the trailing '\n':
        line = line.strip()
        print(line)
1
2
3
4

Dans le cas de l'écriture et de la lecture, nous avons utilisé l'instruction with. Cette instruction garantit que le fichier n'est actif que dans le contexte de with. Lorsque le programme sort du contexte, le fichier se ferme automatiquement.

Il est important de s'assurer que les fichiers sont fermés lorsqu'ils ne sont plus nécessaires, donc :

Toujours utiliser l'instruction with pour ouvrir les fichiers !

Les fonctions

Voici une fonction basique, qui ne prend aucun argument (elle n'a pas de paramètres) :

In [16]:
def say_hello():
    print('hello world!')
    
say_hello()
hello world!

Pour ajouter des paramètres, il suffit de les spécifier dans la définition de la fonction :

In [17]:
def say_hello(somebody):
    print(f'hello {somebody}')
    
say_hello('world')
say_hello('colin')
hello world
hello colin

Vous pouvez ajouter n'importe quel paramètre dont vous avez besoin, en les séparant par des virgules :

In [19]:
def say_hello(a, b): 
    all_people = f'{a} and {b}'
    print(f'hello {all_people}')
    
say_hello('asterix', 'obelix')
hello asterix and obelix

Les fonctions peuvent également renvoyer des objets. Souvent un seul objet (peut-être une liste) ou un tuple d'objets. Pour ce faire, utilisez l'instruction return :

In [20]:
def square(x):
    return x**2

square(2)
Out[20]:
4
In [21]:
import random

def random_point():
    return random.random(), random.random()

random_point()
Out[21]:
(0.7366457555082949, 0.6557379764330106)

Vous pouvez définir des valeurs par défaut pour les arguments, comme ceci :

In [22]:
def say_hello(a='world'):
    print('hello {}'.format(a))

say_hello('colin')
say_hello()
hello colin
hello world

Enfin, les fonctions peuvent soit prendre des arguments positionnels soit des arguments par mots-clefs. Les arguments par mots-clefs doivent être fournis après tous les arguments positionnels. Par example:

In [23]:
def say_hello(greeting, person='laurel', another_person='hardy'):
    print('{greeting} {person} and {another}'.format(
        greeting=greeting,
        person=person,
        another=another_person
        )
    )
    
say_hello('hi', another_person='colin')
hi laurel and colin

Dans cet exemple, hi est un argument positionnel et person est un argument par mot-clef.

Bien entendu, les arguments positionnels sont à donner dans le bon ordre !

Les arguments par mots-clés sont intéressants car

  • ils permettent de spécifier uniquement certains des arguments qui ont une valeur par défaut.
  • ils sont plus explicites : de l'extérieur de la fonction, on comprend à quoi sert l'argument ;

Structures de données python

Listes

La structure de données la plus utilisée en python est certainement la liste.

Dans l'exemple suivant, nous illustrons quelques opérations de liste très courantes :

In [24]:
# create the list, using square brackets
data = ['a', 0, 1, 'b']
# append a single element at the end
data.append('c')
# extend the list with the contents of another list 
data.extend([2, 3])
# print the list length
print('data size', len(data))
# iterate on the list
for elem in data: 
    print(elem)
data size 7
a
0
1
b
c
2
3

Tuples

Les tuples sont très similaires aux listes. La différence entre les deux est que les listes sont mutables, tandis que les tuples sont immuables. Cela signifie que nous pouvons modifier une liste (comme nous l'avons fait lorsque nous avons ajouté des éléments à notre liste de données ci-dessus), mais nous ne pouvons pas modifier un tuple.

Dans l'exemple suivant, nous créons un tuple, l'imprimons et essayons d'y ajouter des éléments :

In [25]:
# create a tuple, using parentheses
tup = (0, 1)
print('length:', len(tup), 'elements:', tup)
tup.append(2)
length: 2 elements: (0, 1)
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
<ipython-input-25-c72b34954c76> in <module>()
      2 tup = (0, 1)
      3 print('length:', len(tup), 'elements:', tup)
----> 4 tup.append(2)

AttributeError: 'tuple' object has no attribute 'append'

Bien sûr, cela échoue car le tuple est immuable. Cela peut sembler être un gros inconvénient des tuples.

Mais ce n'est pas le cas ! Ce serait trop long d'expliquer pourquoi, et je me contenterai de vous donner une recommandation :

si vous devez définir une séquence qui ne doit pas ou ne sera pas modifiée, utilisez un tuple.

Cela facilitera le débogage de votre code et le rendra plus efficace en termes de mémoire.

Enfin, un mot sur l'affectation des tuples. Il est possible de créer un tuple en empaquetant des valeurs comme nous l'avons fait ci-dessus. Et en fait on peut omettre les parenthèses :

In [26]:
tup = 0, 1
tup
Out[26]:
(0, 1)

Et nous pouvons aussi déballer le tuple :

In [27]:
x, y = tup
print(x, y)
0 1

En faisant l'empaquetage et le déballage en même temps, nous pouvons initialiser plusieurs variables en une seule commande :

In [28]:
x, y = 0, 1
print(x, y)
0 1

Dictionnaires

Les dictionnaires sont très différents des listes et des tuples. Ce sont des structures de données mutables et mappables. Cela signifie que les dictionnaires contiennent des éléments constitués d'un mappage clé / valeur, et que nous pouvons modifier les dictionnaires existants.

En voici un :

In [29]:
data = {
    'x': 1,
    'y': 2
}

data
Out[29]:
{'x': 1, 'y': 2}

On peut ajouter des éléments (ou modifier la valeur des clés existantes !) comme ceci :

In [30]:
data['x'] = 0
data['z'] = 3
data
Out[30]:
{'x': 0, 'y': 2, 'z': 3}

Et nous pouvons accéder à la valeur d'une clé comme ça :

In [31]:
data['x']
Out[31]:
0

Les programmeurs Python utilisent souvent des dictionnaires (ou des listes de dictionnaires) pour garder une trace de leurs données. Par exemple, pour un annuaire téléphonique :

In [32]:
[
    {'first_name':'john',
     'last_name':'smith',
     'phone': 1234567 
    },
    {'first_name':'john',
     'last_name':'goodman',
     'phone': 7654321    
    },
    {'first_name':'will',
     'last_name':'smith',
     'phone': 1234851,     
    } 
]
Out[32]:
[{'first_name': 'john', 'last_name': 'smith', 'phone': 1234567},
 {'first_name': 'john', 'last_name': 'goodman', 'phone': 7654321},
 {'first_name': 'will', 'last_name': 'smith', 'phone': 1234851}]

Liste ou dictionnaire ?

Au début, décider quel conteneur utiliser peut ne pas être facile. Gardez donc à l'esprit les faits suivants :

Listes:

  • séquentiel, signifiant que les éléments sont disposés dans un ordre donné ;
  • peut être trié ;
  • fournir un accès très rapide à un élément lorsque son indice est connu, avec une complexité O(1)
  • vérifier si un élément est dans une liste est de complexité O(n), donc le temps nécessaire évolue linéairement avec le nombre d'éléments dans la liste

Dictionnaires :

  • fournir un accès très rapide à un élément lorsque sa clé est connue, avec une complexité O(1)
  • vérifier si une clé est dans le dictionnaire est de complexité O(1).
  • pour python < 3.6, les dictionnaires sont intrinsèquement non triés. Pour python 3.6, l'ordre d'insertion des éléments est conservé dans certaines implémentations de python. Pour python >= 3.7, l'ordre d'insertion est toujours conservé.

Exercice

Dans les cas suivants, utiliseriez-vous une liste, un dictionnaire, une liste de tuples ? Si vous utilisez une liste, quels éléments stockeriez-vous ? Si vous utilisez un dictionnaire, quelle clé et valeur stockeriez-vous ?

  • tracer des points de données (x, y). Dans quelle structure de données stockeriez-vous vos points avant de tracer ?
  • considérer l'annuaire téléphonique ci-dessus. Quelle serait la complexité pour trouver l'information pour une personne donnée ? Comment amélioreriez-vous le répertoire pour rendre la recherche très rapide ?

Le zen de python

Python est un langage extrêmement flexible.

Par exemple, vous pouvez opter pour une programmation orientée objet ou entièrement fonctionnelle, ou opter pour une approche hybride. Et si vous décidez de faire de la programmation orientée objet, par exemple, il y a plusieurs façons de le faire...

En fait, lorsque vous aurez besoin de faire quelque chose avec python, vous constaterez certainement que le plus difficile est de choisir comment le faire.

En cas de doute, suivez le Zen of Python (PEP20) :

  • Beau vaut mieux que laid.
  • Explicite vaut mieux qu'implicite.
  • Simple vaut mieux que complexe.
  • Complexe vaut mieux que compliqué.
  • Plat est mieux que imbriqué.
  • Clairsemé vaut mieux que dense.
  • La lisibilité compte.
  • Les cas spéciaux ne sont pas assez spéciaux pour enfreindre les règles.
  • Bien que la praticité bat la pureté.
  • Les erreurs ne doivent jamais passer en silence.
  • Sauf si explicitement réduit au silence.
  • Face à l'ambiguïté, refusez la tentation de deviner.
  • Il devrait y avoir une - et de préférence une seule - manière évidente de le faire.
  • Bien que cela ne soit pas évident au début, à moins que vous ne soyez néerlandais.
  • Mieux vaut maintenant que jamais.
  • Bien que jamais ne soit souvent mieux que en ce moment.
  • Si la mise en œuvre est difficile à expliquer, c'est une mauvaise idée.
  • Si la mise en œuvre est facile à expliquer, cela peut être une bonne idée.
  • Les espaces de noms sont une excellente idée - faisons-en plus !

Et pour ce qui est du style du code, suivez les recommendations de PEP8!.

Et maintenant?

Vous devriez maintenant pouvoir vous mettre à numpy, qui est essentiel pour le calcul scientifique en python, y compris le machine learning :

Introduction à numpy pour le machine learning


N'hésitez pas à me donner votre avis dans les commentaires ! Je répondrai à toutes les questions.

Et si vous avez aimé cet article, vous pouvez souscrire à ma newsletter pour être prévenu lorsque j'en sortirai un nouveau. Pas plus d'un mail par semaine, promis!

Retour


Encore plus de data science et de machine learning !

Rejoignez ma mailing list pour plus de posts et du contenu exclusif:

Je ne partagerai jamais vos infos.
Partagez si vous aimez cet article: