Il est fortement recommandé d’avoir étudié le tutoriel #1 avant celui-ci car on y trouve de nombreux éléments communs qui ne sont pas re-expliqués ici.

Cet article commente le tutoriel Tensorflow #2 de Magnus Erik Hvass Pedersen  : Convolutional Neural Network. Le code est ici et la vidéo ici (en anglais).

Les convolutions ne sont pas expliquées dans notre article. Elles sont supposées connues.

Pour ceux qui ne savent pas ce que c’est, lisez cet excellent article Comment les Réseaux de neurones à convolution fonctionnent, traduction de How do Convolutional Neural Networks work?

Cette autre vidéo est aussi digne d’intérêt (pour les débutants).

Lorsque le code est similaire au tutoriel #1 alors il n’est pas commenté ici.

Flux

Le réseau implémenté est représenté ci-dessous :

Le logiciel proposé n’est pas notre préféré car cette version n’utilise pas Keras, or avec Keras tout est beaucoup plus facile – par contre il a l’intérêt d’entrer dans les détails qui permettent de comprendre ce qui se passe derrière.

Première convolution

Pour chaque image, on applique 16 filtres différents (5*5), puis un ReLU (Rectified Linear Unit La fonction a pour valeurs Max(0,x)), puis un max-pooling (2*2) ce qui réduit la résolution de l’image à 14*14 (au lieu de 28*28)

Imports

(voir  tutoriel #1)

Les imports sont ceux du tutoriel #1 auxquels on ajoute :

import time
from datetime import timedelta
import math

parce qu’on veut mesurer le temps d’exécution et qu’on a des racines carrées à calculer.

Configuration du réseau

# Convolutional Layer 1.
filter_size1 = 5          # Convolution filters are 5 x 5 pixels.
num_filters1 = 16         # There are 16 of these filters.

# Convolutional Layer 2.
filter_size2 = 5          # Convolution filters are 5 x 5 pixels.
num_filters2 = 36         # There are 36 of these filters.

# Fully-connected layer.
fc_size = 128             # Number of neurons in fully-connected layer.

Les paramètres du réseau sont ceux indiqués sur le schéma (plus haut)

 Lecture des données

(voir  tutoriel #1)

La seule différence est :

# Number of colour channels for the images: 1 channel for gray-scale.
num_channels = data.num_channels

parce qu’on pourrait avoir des images sur plusieurs channels (en couleurs)

Affichage des images

(voir  tutoriel #1)

Tensorflow

Variables

def new_weights(shape):
    return tf.Variable(tf.truncated_normal(shape, stddev=0.05))

def new_biases(length):
    return tf.Variable(tf.constant(0.05, shape=[length]))

Dans le tutoriel #1, les poids et les biais sont initialisés de la façon suivante (à vrai dire l’initialisation est faite après, il s’agit ici seulement d’une déclaration du graphe)

  • weights = tf.Variable(tf.zeros([img_size_flat, num_classes]))
  • biases = tf.Variable(tf.zeros([num_classes]))

tf.truncated_normal(shape, stddev=0.05) produira un tenseur 1D de la forme shape avec une déviation standard de 0.05 par rapport à la distribution normale, tandis que tf.constant(0.05, shape=[length]) créera comme le nom l’indique un tenseur de constantes.

Pourquoi choisir différentes façons d’initialiser les poids. Lire ici à ce sujet.

Ce qui est sûr, c’est qu’il ne faut pas initialiser les poids à zéro car ce n’est acceptable que pour un modèle linéaire (ce qui ne posait pas problème dans le tutoriel #1). Ce qui est sûr aussi c’est qu’on peut initialiser les biais à zéro, quelque soit le modèle.

Reste la question d’une initialisation avec une distribution normale. Il semblerait, d’après les experts, que ce ne soit pas la meilleure solution, notamment si on utilise ReLU comme fonction d’activation.

Il faudra revenir sur ce choix.

Création d’une couche de convolution

def new_conv_layer(input,              # The previous layer.
                   num_input_channels, # Num. channels in prev. layer.
                   filter_size,        # Width and height of each filter.
                   num_filters,        # Number of filters.
                   use_pooling=True):  # Use 2x2 max-pooling.

    # Shape of the filter-weights for the convolution.
    # This format is determined by the TensorFlow API.
    shape = [filter_size, filter_size, num_input_channels, num_filters]

    # Create new weights aka. filters with the given shape.
    weights = new_weights(shape=shape)

    # Create new biases, one for each filter.
    biases = new_biases(length=num_filters)

    # Create the TensorFlow operation for convolution.
    # Note the strides are set to 1 in all dimensions.
    # The first and last stride must always be 1,
    # because the first is for the image-number and
    # the last is for the input-channel.
    # But e.g. strides=[1, 2, 2, 1] would mean that the filter
    # is moved 2 pixels across the x- and y-axis of the image.
    # The padding is set to 'SAME' which means the input image
    # is padded with zeroes so the size of the output is the same.
    layer = tf.nn.conv2d(input=input,
                         filter=weights,
                         strides=[1, 1, 1, 1],
                         padding='SAME')

    # Add the biases to the results of the convolution.
    # A bias-value is added to each filter-channel.
    layer += biases

    # Use pooling to down-sample the image resolution?
    if use_pooling:
        # This is 2x2 max-pooling, which means that we
        # consider 2x2 windows and select the largest value
        # in each window. Then we move 2 pixels to the next window.
        layer = tf.nn.max_pool(value=layer,
                               ksize=[1, 2, 2, 1],
                               strides=[1, 2, 2, 1],
                               padding='SAME')

    # Rectified Linear Unit (ReLU).
    # It calculates max(x, 0) for each input pixel x.
    # This adds some non-linearity to the formula and allows us
    # to learn more complicated functions.
    layer = tf.nn.relu(layer)

    # Note that ReLU is normally executed before the pooling,
    # but since relu(max_pool(x)) == max_pool(relu(x)) we can
    # save 75% of the relu-operations by max-pooling first.

    # We return both the resulting layer and the filter-weights
    # because we will plot the weights later.
    return layer, weights

Le code parle de lui même si vous connaissez les convolutions.

stride

strides=[1, 1, 1, 1] c’est à dire décalage de 1 partout (le minimum).

Pour rappel :

The amount by which the filter shifts is the stride

Vous vous demandez pourquoi un vecteur de 4 éléments. L’explication est donnée dans les commentaires du code :

The first and last stride must always be 1,
# because the first is for the image-number and
# the last is for the input-channel.

padding

padding=’SAME’. Pour une explication du padding par Tf, voir ici.

pooling

On en a déjà parlé plus haut, voir ici.

reLu

Idem, voir ici.

Fully connected layer

Pour la création de cette couche entièrement connectée, on crée un outil qui redimensionne (2D) les tensors (4D) flatten_layer puis on applique layer = tf.matmul(input, weights) + biases avec ou sans reLu. Rien de très différent du tutoriel #1.

Accuracy

Le code qui suit est analogue à celui du tutoriel #1. Il n’est pas commenté.

Après 10000 itérations on obtient accuracy = 98,8% ! cet qui est nettement mieux que les 91,9% de la fois précédente.

Seulement, ce code est compliqué, bavard, laborieux. On peut faire plus simple avec Keras. C’est ce que nous montrons avec le tutoriel #3.

 

 

 

 

 

 

 

 

 

 

 

Laisser un commentaire

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