Classification d’iris – #1

Il y a 2 ans, une éternité, un tutoriel de classification d’iris, à l’aide de TensorFlow 1.x avait déjà été présenté sur un de nos autres sites.

Depuis cette date, TensorFlow a fondamentalement changé. La version 2.x est très différente de la 1.x. Raison pour laquelle, l’article précédent est obsolète.

Ce nouveau tutoriel de classification d’iris, disponible sur le site de TensorFlow, est repris ici et commenté. Le cours est divisé en quatre parties : le dataset, le modèle, l’apprentissage, les prédictions.

Le problèmeRéférencesCodeGist

Le problème

Classer des iris, en 3 catégories (Iris setosa, Iris virginica et Iris versicolor), à partir des dimensions (largeur et longueur) des sépales et pétales est un problème classique du Machine Learning. Puisqu’on peut le traiter facilement avec les outils de la statistique, pourquoi ne pas essayer de le faire, de façon plus compliquée avec un réseau de neurones ! Tout cela, bien sûr, dans un but pédagogique.

Références

Code

Le notebook Jupyter, en Python, dans l’environnement GCP est disponible ici.

Gist

Imports

import os
import matplotlib.pyplot as plt
import tensorflow as tf
print("TensorFlow version: {}".format(tf.__version__))
print("Eager execution: {}".format(tf.executing_eagerly()))
TensorFlow version: 2.2.0-rc2
Eager execution: True

Le test pour savoir si on est en mode eager est inutile car depuis la version 2.x de Tf on est obligatoirement en mode eager.

Dataset

Le fichier d’origine est à cette adresse : https://archive.ics.uci.edu/ml/datasets/iris

C’est un fichier csv, organisé de la façon suivante : tout d’abord une ligne de description (nombre d’enregistrements, de catégories et le nom des catégories).

Puis pour chaque enregistrement la longueur du sépale, sa largeur, la longueur du pétale, sa largeur, sa classe (0, 1, 2) pour Iris Setosa, Iris Versicolour; Iris Virginica

Download the dataset

train_dataset_url = "https://storage.googleapis.com/download.tensorflow.org/data/iris_training.csv"
train_dataset_fp = tf.keras.utils.get_file(fname=os.path.basename(train_dataset_url),
                                           origin=train_dataset_url)
print("Local copy of the dataset file: {}".format(train_dataset_fp))
Downloading data from https://storage.googleapis.com/download.tensorflow.org/data/iris_training.csv
8192/2194 [================================================================================================================] - 0s 0us/step
Local copy of the dataset file: /root/.keras/datasets/iris_training.csv

Inspection des données

!head -n5 {train_dataset_fp}
120,4,setosa,versicolor,virginica
6.4,2.8,5.6,2.2,2
5.0,2.3,3.3,1.0,1
4.9,2.5,4.5,1.7,2
4.9,3.1,1.5,0.1,0

! permet d’exécuter une commande système

Création d’un tf.data.Dataset

On choisit de créer un dataset en fournissant le nom des colonnes. Ce n’est pas obligatoire. Seuls les file_pattern, batch_size doivent être précisés.

tf.data.experimental.make_csv_dataset

# column order in CSV file
column_names = ['sepal_length', 'sepal_width', 'petal_length', 'petal_width', 'species']

feature_names = column_names[:-1]
label_name = column_names[-1]

print("Features: {}".format(feature_names))
print("Label: {}".format(label_name))
Features: ['sepal_length', 'sepal_width', 'petal_length', 'petal_width']
Label: species
batch_size = 32

train_dataset = tf.data.experimental.make_csv_dataset(
    train_dataset_fp,
    batch_size,
    column_names=column_names,
    label_name=label_name,
    num_epochs=1)

On crée aussi une variable class_names qui nous servira pour plus tard, uniquement pour de l’affichage.

class_names = ['Iris setosa', 'Iris versicolor', 'Iris virginica']

Le dataset est itérable. Deux façons de voir le contenu :

next(itertake
features, labels = next(iter(train_dataset))
print(features)
OrderedDict([('sepal_length', <tf.Tensor: shape=(32,), dtype=float32, numpy=
array([7.7, 4.8, 6.1, 6.4, 6.7, 5.7, 6.7, 5.7, 5.8, 6. , 7.3, 6.2, 6.1,
       4.6, 4.8, 5.1, 5.6, 6.5, 7.2, 5.4, 5.8, 6.1, 6.4, 5.9, 5.5, 5.3,
       5.1, 6.3, 4.4, 5.7, 4.9, 6.3], dtype=float32)>), ('sepal_width', <tf.Tensor: shape=(32,), dtype=float32, numpy=
array([2.6, 3.4, 2.6, 2.8, 3.1, 2.8, 3. , 4.4, 2.8, 2.2, 2.9, 2.8, 2.8,
       3.6, 3. , 3.8, 2.7, 3. , 3.6, 3.9, 2.7, 3. , 3.2, 3. , 3.5, 3.7,
       3.7, 2.5, 3. , 2.9, 3.1, 2.7], dtype=float32)>), ('petal_length', <tf.Tensor: shape=(32,), dtype=float32, numpy=
array([6.9, 1.6, 5.6, 5.6, 5.6, 4.1, 5. , 1.5, 5.1, 5. , 6.3, 4.8, 4.7,
       1. , 1.4, 1.6, 4.2, 5.8, 6.1, 1.3, 4.1, 4.9, 5.3, 5.1, 1.3, 1.5,
       1.5, 5. , 1.3, 4.2, 1.5, 4.9], dtype=float32)>), ('petal_width', <tf.Tensor: shape=(32,), dtype=float32, numpy=
array([2.3, 0.2, 1.4, 2.2, 2.4, 1.3, 1.7, 0.4, 2.4, 1.5, 1.8, 1.8, 1.2,
       0.2, 0.1, 0.2, 1.3, 2.2, 2.5, 0.4, 1. , 1.8, 2.3, 1.8, 0.2, 0.2,
       0.4, 1.9, 0.2, 1.3, 0.1, 1.8], dtype=float32)>)])
print(labels)
tf.Tensor([2 0 2 2 2 1 1 0 2 2 2 2 1 0 0 0 1 2 2 0 1 2 2 2 0 0 0 2 0 1 0 2], shape=(32,), dtype=int32)
takeone = train_dataset.take(1)

for data in takeone:
  print(data)
(OrderedDict([('sepal_length', <tf.Tensor: shape=(32,), dtype=float32, numpy=
array([6.9, 6.6, 6.2, 6.4, 4.6, 6.4, 6.5, 4.9, 4.4, 6.7, 7.3, 7.9, 5.6,
       4.6, 6.8, 6.3, 6.1, 4.4, 6.3, 5. , 5.8, 4.9, 7.7, 4.8, 4.6, 6.8,
       5.5, 6.4, 5.8, 6.4, 5.4, 5. ], dtype=float32)>), ('sepal_width', <tf.Tensor: shape=(32,), dtype=float32, numpy=
array([3.1, 3. , 2.2, 2.7, 3.4, 3.1, 3.2, 3. , 3. , 3.1, 2.9, 3.8, 2.7,
       3.1, 3.2, 3.3, 3. , 3.2, 2.7, 3. , 2.8, 3.1, 2.6, 3.4, 3.6, 3. ,
       2.6, 3.2, 2.7, 2.8, 3. , 3.4], dtype=float32)>), ('petal_length', <tf.Tensor: shape=(32,), dtype=float32, numpy=
array([4.9, 4.4, 4.5, 5.3, 1.4, 5.5, 5.1, 1.4, 1.3, 5.6, 6.3, 6.4, 4.2,
       1.5, 5.9, 4.7, 4.9, 1.3, 4.9, 1.6, 5.1, 1.5, 6.9, 1.6, 1. , 5.5,
       4.4, 4.5, 5.1, 5.6, 4.5, 1.5], dtype=float32)>), ('petal_width', <tf.Tensor: shape=(32,), dtype=float32, numpy=
array([1.5, 1.4, 1.5, 1.9, 0.3, 1.8, 2. , 0.2, 0.2, 2.4, 1.8, 2. , 1.3,
       0.2, 2.3, 1.6, 1.8, 0.2, 1.8, 0.2, 2.4, 0.1, 2.3, 0.2, 0.2, 2.1,
       1.2, 1.5, 1.9, 2.2, 1.5, 0.2], dtype=float32)>)]), <tf.Tensor: shape=(32,), dtype=int32, numpy=
array([1, 1, 1, 2, 0, 2, 2, 0, 0, 2, 2, 2, 1, 0, 2, 1, 2, 0, 2, 0, 2, 0,
       2, 0, 0, 2, 1, 1, 2, 2, 1, 0], dtype=int32)>)
data[0]['sepal_length']
<tf.Tensor: shape=(32,), dtype=float32, numpy=
array([6.9, 6.6, 6.2, 6.4, 4.6, 6.4, 6.5, 4.9, 4.4, 6.7, 7.3, 7.9, 5.6,
       4.6, 6.8, 6.3, 6.1, 4.4, 6.3, 5. , 5.8, 4.9, 7.7, 4.8, 4.6, 6.8,
       5.5, 6.4, 5.8, 6.4, 5.4, 5. ], dtype=float32)>

Généralement les données ne sont pas organisées de cette façon dans le dataset. On a habituellement les valeurs de chaque enregistrement et un vecteur pour les variables à expliquer. C’est ce qu’on va faire.

tf.stack

def pack_features_vector(features, labels):
  """Pack the features into a single array."""
  features = tf.stack(list(features.values()), axis=1)
  return features, labels
train_dataset = train_dataset.map(pack_features_vector)
features, labels = next(iter(train_dataset))
print(features[:5])
tf.Tensor(
[[7.2 3.2 6.  1.8]
 [4.9 3.1 1.5 0.1]
 [5.4 3.7 1.5 0.2]
 [5.  3.5 1.3 0.3]
 [5.5 2.4 3.8 1.1]], shape=(5, 4), dtype=float32)

Au sujet de tf.stack

tf.stack stacks a list of rank-R tensors into one rank-(R+1) tensor.

x = tf.constant([1, 4]) 
y = tf.constant([2, 5]) 
z = tf.constant([3, 6]) 
tf.stack([x, y, z]) 
<tf.Tensor: shape=(3, 2), dtype=int32, numpy=
array([[1, 4],
       [2, 5],
       [3, 6]], dtype=int32)>

Given a list of length N of tensors of shape (A, B, C); if axis == 0 then the output tensor will have the shape (N, A, B, C). if axis == 1 then the output tensor will have the shape (A, N, B, C)

tf.stack([x, y, z], axis=1)
<tf.Tensor: shape=(2, 3), dtype=int32, numpy=
array([[1, 2, 3],
       [4, 5, 6]], dtype=int32)>

Suite du tutoriel ici.