TensorFlow est une plate-forme logicielle permettant de créer des modèles de machine learning (ML).

Si vous êtes débutant avec TensorFlow, commencez par lire Get Started with TensorFlow sur le site officiel de TensorFlow puis Train your first Neural Network

Ce tutoriel présente un logiciel de classification d’iris (assez classique dans le domaine du Deep Learning (DL)). C’est un peu le Hello World du DL (avec MNIST). Des bases de données d’exemples sont accessibles à tous sur Internet afin de s’entraîner et tester des modèles de classification (ou de régression). Ce tutoriel s’appuie uniquement sur Getting Started for ML Beginners.

Le logiciel source est disponible (cf. git clone https://github.com/tensorflow/models)

J’ai modifié ce logiciel (voir ici avec Colab) afin que toutes les fonctions soient accessibles dans le même fichier et j’ai ajouté quelques commentaires. Ma version du logiciel est aussi un peu différente car elle s’exécute dans l’environnement Jupyter. Au lieu de passer des arguments dans le main, j’utilise des constantes.

A part, ces quelques modifications mineures, le code est exactement le même.

J’ajoute ici quelques commentaires qui j’espère aideront à mieux comprendre ce premier tutoriel. En effet sauf si le DL n’a plus de secrets pour vous et que vous programmez chaque jour en Python avec NumPy, Pandas, … alors il risque d’y avoir quelques lignes difficilement compréhensibles.

Je me doute bien que si vous avez atterri sur cette page, ces mots-clés ne sont pas ésotériques pour vous mais de là à en faire votre pain quotidien !

On y va !

On a un jeu de données (iris) qui est séparé en deux. Une partie sert à l’apprentissage (train), l’autre partie sert au test. C’est la pratique habituelle (indispensable)  en DL.

Vous pouvez lire ici, en introduction le tutoriel rédigé pour la version 2 de TensorFlow.

Importation des données

Vous remarquez que les fichiers sont lus avec tf.keras.utils.get_file.

Keras est une sur-couche à TensorFlow. Son intérêt est d’être compatible avec différents logiciels de DL (TensorFlow, CNTK, Theano).

Le fichier lu sur le réseau est stocké en local (en cache)

train_path = tf.keras.utils.get_file(TRAIN_URL.split('/')[-1], TRAIN_URL)
def maybe_download():

# https://www.tensorflow.org/api_docs/python/tf/keras/utils/get_file
# Downloads a file from a URL if it not already in the cache.

# By default the file at the url origin is downloaded to the cache_dir ~/.keras, placed in
# the cache_subdir datasets, and given the filename fname.
# The final location of a file example.txt would therefore be ~/.keras/datasets/example.txt.
# get_file(
# fname,
# origin,
# ...
# )
# On fait d'abord un split sur /
# TEST_URL = "http://download.tensorflow.org/data/iris_test.csv"
# ['http:', '', 'download.tensorflow.org', 'data', 'iris_training.csv']
# Puis [-1] pour obtenir le dernier élément de la liste, soit iris_training.csv ici
# Le résultat obtenu pour train_path est sur mon Mac : /Users/xxx/.keras/datasets/iris_training.csv
train_path = tf.keras.utils.get_file(TRAIN_URL.split('/')[-1], TRAIN_URL)
test_path = tf.keras.utils.get_file(TEST_URL.split('/')[-1], TEST_URL)

return train_path, test_path

Comme dit précédemment, sans le domaine du DL, en Python, on utilise souvent les packages pandas, scikit-learn, numpy. On aurait pu lire les data avec sklearn, ça aurait été plus simple…

Les arguments de pd.read_csv :

filepath_or_buffer : str, pathlib.Path, py._path.local.LocalPath or any object with a read() method (such as a file handle or StringIO)
The string could be a URL. Valid URL schemes include http, ftp, s3, and file. For file URLs, a host is expected. For instance, a local file could be file ://localhost/path/to/table.csv

names : array-like, default None
List of column names to use. If file contains no header row, then you should explicitly pass header=None. Duplicates in this list will cause a UserWarning to be issued.

def load_data(y_name='Species'):
    """Returns the iris dataset as (train_x, train_y), (test_x, test_y)."""
    train_path, test_path = maybe_download()

    train = pd.read_csv(train_path, names=CSV_COLUMN_NAMES, header=0)
    # train :
    #
    #     SepalLength  SepalWidth  PetalLength  PetalWidth
    # 0            6.4         2.8          5.6         2.2
    # 1            5.0         2.3          3.3         1.0
    # 2            4.9         2.5          4.5         1.7
    # 3            4.9         3.1          1.5         0.1
    # ...
    # https://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.pop.html
    # Return item and drop from frame.
    train_x, train_y = train, train.pop(y_name)

    test = pd.read_csv(test_path, names=CSV_COLUMN_NAMES, header=0)

    # https://pandas.pydata.org/pandas-docs/stable/generated/pandas.DataFrame.pop.html
    # Return item and drop from frame.
    test_x, test_y = test, test.pop(y_name)

    return (train_x, train_y), (test_x, test_y)

Description des données

Ici, nous sommes dans un cas simple. Les données (features) sont simples à interpréter.

Le seul argument utilisé par tf.feature_column.numeric_column est key :
key : A unique string identifying the input feature. It is used as the column name and the dictionary key for feature parsing configs, feature Tensor objects, and feature columns.

# Feature columns describe how to use the input.
# A feature column is a data structure that tells your model how to interpret the data
# in each feature. In the Iris problem, we want the model to interpret the data in each feature
# as its literal floating-point value; that is, we want the model to interpret an input value
# like 5.4 as, well, 5.4. However, in other machine learning problems, it is often desirable to interpret data less literally.

my_feature_columns = []

# my_feature_columns
# 	[
# 		_NumericColumn(key='SepalLength', shape=(1,), default_value=None, dtype=tf.float32, normalizer_fn=None),
# 		_NumericColumn(key='SepalWidth', shape=(1,), default_value=None, dtype=tf.float32, normalizer_fn=None),
# 		_NumericColumn(key='PetalLength', shape=(1,), default_value=None, dtype=tf.float32, normalizer_fn=None),
# 		_NumericColumn(key='PetalWidth', shape=(1,), default_value=None, dtype=tf.float32, normalizer_fn=None)
# 	]
for key in train_x.keys():
    my_feature_columns.append(tf.feature_column.numeric_column(key=key))

Sélection du modèle

Il s’agit d’un réseau de neurones à trois couches (2 couches cachées). Chaque couche dispose de 10 neurones. Pourquoi 3 couches ? pourquoi 10 neurones ? ce n’est pas l’objet de ce tutoriel. Je reviendrai plus tard, dans un autre article, sur cette question.

# Build 2 hidden layer DNN with 10, 10 units respectively.
# To implement a neural network, the premade_estimators.py program uses a pre-made Estimator
# named tf.estimator.DNNClassifier. This Estimator builds a neural network that classifies examples.
# Nous avons un Réseau de Neurons avec 3 couches
# Chaque couche a 10 neurons
classifier = tf.estimator.DNNClassifier(
    feature_columns=my_feature_columns,
    # Two hidden layers of 10 nodes each.
    # Use the hidden_units parameter to define the number of neurons in each hidden layer of the neural network.
    # The length of the list assigned to hidden_units identifies the number of hidden layers (2, in this case).
    # Each value in the list represents the number of neurons in a particular hidden
    # layer (10 in the first hidden layer and 10 in the second hidden layer).
    # To change the number of hidden layers or neurons, simply assign a different list to the hidden_units parameter.

    hidden_units=[10, 10],
    # The model must choose between 3 classes.
    n_classes=3)

Apprentissage du modèle

définition de la fonction d’apprentissage

def train_input_fn(features, labels, batch_size):
    """An input function for training"""
    # Convert the inputs to a Dataset.
    # Creates a Dataset whose elements are slices of the given tensors.
    # https://www.tensorflow.org/versions/r1.2/api_docs/python/tf/contrib/data/Dataset
    dataset = tf.data.Dataset.from_tensor_slices((dict(features), labels))

    # Shuffle, repeat, and batch the examples.
    # Randomly shuffles the elements of this dataset.
    # https://www.tensorflow.org/api_docs/python/tf/data/Dataset
    # buffer_size: A tf.int64 scalar tf.Tensor, representing the number of elements from this dataset
    # from which the new dataset will sample.
    dataset = dataset.shuffle(CONST_BATCH_SIZE).repeat().batch(batch_size)

    # Build the Iterator, and return the read end of the pipeline.
    # https://www.tensorflow.org/programmers_guide/datasets#creating_an_iterator
    # A one-shot iterator is the simplest form of iterator, which only supports iterating
    # once through a dataset, with no need for explicit initialization. One-shot iterators handle
    # almost all of the cases that the existing queue-based input pipelines support,
    # but they do not support parameterization.

    # Creates an Iterator for enumerating the elements of this dataset.
    # https://www.tensorflow.org/versions/r1.2/api_docs/python/tf/contrib/data/Dataset
    # https://www.tensorflow.org/api_docs/python/tf/data/Dataset
    # Creates an Iterator for enumerating the elements of this dataset.
    return dataset.make_one_shot_iterator().get_next()

appel de la fonction d’apprentissage

# Train the Model.
# classifier.train(input_fn=lambda:iris_data.train_input_fn(train_x, train_y,100),steps=1000)
classifier.train(input_fn=lambda:train_input_fn(train_x, train_y, CONST_BATCH_SIZE), steps=CONST_TRAINING_STEPS)

Evaluation du modèle

définition de la fonction d’évaluation

def eval_input_fn(features, labels, batch_size):
    """An input function for evaluation or prediction"""
    features=dict(features)

    if labels is None:
        # No labels, use only features.
        inputs = features
    else:
        inputs = (features, labels)

    # Convert the inputs to a Dataset.
    # dataset = tf.data.Dataset.from_tensor_slices(inputs)
    # dataset = tf.data.Dataset.from_tensor_slices((dict(features), labels))
    dataset = tf.data.Dataset.from_tensor_slices(inputs)

    # Batch the examples
    assert batch_size is not None, "batch_size must not be None"
    dataset = dataset.batch(batch_size)
    return dataset.make_one_shot_iterator().get_next()

appel de la fonction d’évaluation

# Evaluate the model.
eval_result = classifier.evaluate(input_fn=lambda:eval_input_fn(test_x, test_y, CONST_BATCH_SIZE))
print('\nTest set accuracy: {accuracy:0.3f}\n'.format(**eval_result))
[/code]

Prédictions

# Generate predictions from the model
expected = ['Setosa', 'Versicolor', 'Virginica']

predict_x = {
    'SepalLength': [5.1, 5.9, 6.9],
    'SepalWidth': [3.3, 3.0, 3.1],
    'PetalLength': [1.7, 4.2, 5.4],
    'PetalWidth': [0.5, 1.5, 2.1],
}

# https://www.tensorflow.org/api_docs/python/tf/estimator/DNNClassifier
predictions = classifier.predict(input_fn=lambda:eval_input_fn(predict_x,labels=None,batch_size=CONST_BATCH_SIZE))

for pred_dict, expec in zip(predictions, expected):
    template = ('\nPrediction is "{}" ({:.1f}%), expected "{}"')
    class_id = pred_dict['class_ids'][0]
    probability = pred_dict['probabilities'][class_id]
    print(template.format(SPECIES[class_id], 100 * probability, expec))

Conclusion

Nous n’avons que deux hyperparamètres :

CONST_BATCH_SIZE = 1000
CONST_TRAINING_STEPS = 100

Lorsque ces valeurs sont celles par défaut, le Test set accuracy est 0.967.Prediction is « Setosa » (93.9%), expected « Setosa »Prediction is « Versicolor » (73.8%), expected « Versicolor »Prediction is « Virginica » (81.6%), expected « Virginica »

Lorsque ces valeurs sont celles par défaut, le Test set accuracy est 0.967.Prediction is "Setosa" (93.9%), expected "Setosa"Prediction is "Versicolor" (73.8%), expected "Versicolor"Prediction is "Virginica" (81.6%), expected "Virginica"

Quel est l’effet d’un changement de ces valeurs ? Note : avec les mêmes paramètres, à chaque fois le résultat est différent. Nous y reviendrons.

En augmentant le nombre de training steps :

CONST_BATCH_SIZE = 1000
CONST_TRAINING_STEPS = 200

Le Test set accuracy: 0.967 reste exactement le même. Par contre nous avons une amélioration sur nos prédictions.

Prediction is « Setosa » (99.1%), expected « Setosa »
Prediction is « Versicolor » (96.5%), expected « Versicolor »
Prediction is « Virginica » (83.9%), expected « Virginia »

En diminuant le nombre de training steps :

CONST_BATCH_SIZE = 1000
CONST_TRAINING_STEPS = 50

Le Test set accuracy: 0.967 reste exactement le même mais nous avons une détérioration sur nos prédictions.

Prediction is « Setosa » (92.4%), expected « Setosa »
Prediction is « Versicolor » (76.4%), expected « Versicolor »
Prediction is « Virginica » (65.9%), expected « Virginica »

En augmentant le batch size :

Le Test set accuracy: 0.967 reste exactement le même et nous avons une amélioration sur nos prédictions.

CONST_BATCH_SIZE = 2000
CONST_TRAINING_STEPS = 100

Prediction is « Setosa » (98.4%), expected « Setosa »
Prediction is « Versicolor » (87.7%), expected « Versicolor »
Prediction is « Virginica » (76.5%), expected « Virginia »

En diminuant le batch size :

CONST_BATCH_SIZE = 500
CONST_TRAINING_STEPS = 100

Le Test set accuracy: 0.967 reste exactement le même et nous avons une amélioration sur nos prédictions.

Prediction is « Setosa » (98.0%), expected « Setosa »
Prediction is « Versicolor » (87.1%), expected « Versicolor »
Prediction is « Virginica » (88.4%), expected « Virginia »

En augmentant le le nombre de training steps et le batch size :

CONST_BATCH_SIZE = 2000
CONST_TRAINING_STEPS = 200

Le Test set accuracy: 0.967 reste exactement le même et nous avons une amélioration encore supérieure sur nos prédictions.

Prediction is « Setosa » (98.9%), expected « Setosa »
Prediction is « Versicolor » (98.7%), expected « Versicolor »
Prediction is « Virginica » (94.0%), expected « Virginica »

Laisser un commentaire

Votre adresse de messagerie ne sera pas publiée. Les champs obligatoires sont indiqués avec *