[PYTHON] [Application TF2.0] Un cas où l'augmentation de données à usage général a été parallélisée et réalisée à grande vitesse avec la fonction de jeu de données fort de l'exemple TF.

introduction

Cet article est l'article précédent "L'histoire selon laquelle la fonction de jeu de données qui peut être utilisée avec TensorFlow était forte" "[[application TF2.0] tf.data. C'est l'histoire d'une autre version améliorée de Data Augmentation qui a été un peu soulevée dans "Rendre l'augmentation des données plus rapide avec Dataset" (https://qiita.com/Suguru_Toyohara/items/49c2914b21615b554afa).

Lors de l'utilisation du système tf.data.Dataset pour l'amélioration de la vitesse et du système keras.preprocessing.image ** Réussite à réaliser du code qui peut être traité en parallèle. ** ** Je vais mettre le mécanisme réel et l'arrière-plan à ce point à côté du code.

Je mettrai le code ci-dessous

Arrangement environnemental

Tout d'abord, préparons l'environnement expérimental.

init


import tensorflow as tf
import tensorflow.keras as keras
import matplotlib.pyplot as plt
import sklearn
import numpy as np
from tqdm import tqdm
(tr_x,tr_y),(te_x,te_y)=keras.datasets.cifar10.load_data()
tr_x, te_x = tr_x/255.0, te_x/255.0
tr_y, te_y = tr_y.reshape(-1,1), te_y.reshape(-1,1)
model = keras.models.Sequential()
model.add(keras.layers.Convolution2D(32,3,padding="same",activation="relu",input_shape=(32,32,3)))
model.add(keras.layers.Convolution2D(32,3,padding="same",activation="relu"))
model.add(keras.layers.Convolution2D(32,3,padding="same",activation="relu"))
model.add(keras.layers.MaxPooling2D())
model.add(keras.layers.Convolution2D(128,3,padding="same",activation="relu"))
model.add(keras.layers.Convolution2D(128,3,padding="same",activation="relu"))
model.add(keras.layers.Convolution2D(128,3,padding="same",activation="relu"))
model.add(keras.layers.Convolution2D(128,3,padding="same",activation="relu"))
model.add(keras.layers.Convolution2D(128,3,padding="same",activation="relu"))
model.add(keras.layers.MaxPooling2D())
model.add(keras.layers.Convolution2D(256,3,padding="same",activation="relu"))
model.add(keras.layers.Convolution2D(256,3,padding="same",activation="relu"))
model.add(keras.layers.Convolution2D(256,3,padding="same",activation="relu"))
model.add(keras.layers.GlobalAveragePooling2D())
model.add(keras.layers.Dense(1000,activation="relu"))
model.add(keras.layers.Dense(128,activation="relu"))
model.add(keras.layers.Dense(10,activation="softmax"))
model.compile(loss="sparse_categorical_crossentropy",metrics=["accuracy"])

Un exemple d'augmentation des données

Confirmation des données

Tout d'abord, exprimons keras.preprocessing.image.random_rotate pour que cela puisse être fait avec .map.

random_rotate


from tensorflow.keras.preprocessing.image import random_rotation
from joblib import Parallel, delayed

def r_rotate(imgs, degree):
    pics=imgs.numpy()
    degree = degree.numpy()
    
    if tf.rank(imgs)==4:
        X=Parallel(n_jobs=-1)( [delayed(random_rotation)(pic, degree, 0, 1, 2) for pic in pics] )
        X=np.asarray(X)
    elif tf.rank(imgs)==3:
        X=random_rotation(pics, degree, 0, 1, 2)
    return X
@tf.function
def random_rotate(imgs, label):
    x = tf.py_function(r_rotate,[imgs,30],[tf.float32])
    X = x[0]
    X.set_shape(imgs.shape)
    return X, label

Maintenant, cela fonctionne réellement. Déplaçons-le et voyons les données.

Afficher les données


labels = np.array([
    'airplane',
    'automobile',
    'bird',
    'cat',
    'deer',
    'dog',
    'frog',
    'horse',
    'ship',
    'truck'])
tr_ds = tf.data.Dataset.from_tensor_slices((tr_x,tr_y)).shuffle(40000).batch(128).map(random_rotate)

plt.figure(figsize=(10,10),facecolor="white")
for b_img,b_label in tr_ds:
    for i, img,label in zip(range(25),b_img,b_label):
        plt.subplot(5,5,i+1)
        plt.xticks([])
        plt.yticks([])
        plt.grid(False)
        plt.imshow(img)
        plt.xlabel(labels[label])
    break
plt.show()

CIFAR10-random-rotate-sample.png

Test de vitesse

Vérifions à quelle vitesse ce sera réellement. Tout d'abord, la vitesse dans "[application TF2.0] tf.data.Dataset pour accélérer l'augmentation des données" était la suivante.

résultat


Train on 50000 samples
50000/50000 [==============================] - 9s 175us/sample - loss: 2.3420 - accuracy: 0.1197
Train on 50000 samples
50000/50000 [==============================] - 7s 131us/sample - loss: 2.0576 - accuracy: 0.2349
Train on 50000 samples
50000/50000 [==============================] - 7s 132us/sample - loss: 1.7687 - accuracy: 0.3435
Train on 50000 samples
50000/50000 [==============================] - 7s 132us/sample - loss: 1.5947 - accuracy: 0.4103
Train on 50000 samples
50000/50000 [==============================] - 7s 132us/sample - loss: 1.4540 - accuracy: 0.4705
CPU times: user 1min 33s, sys: 8.03 s, total: 1min 41s
Wall time: 1min 14s

Ensuite, je publierai le code et les résultats de l'implémentation précédente.

dataset


%%time
tr_ds = tf.data.Dataset.from_tensor_slices((tr_x,tr_y)).shuffle(40000)
tr_ds = tr_ds.batch(tr_x.shape[0]).map(random_rotate).repeat(5)
tr_ds = tr_ds.prefetch(tf.data.experimental.AUTOTUNE)

for img,label in tr_ds:
    model.fit(x=img,y=label,batch_size=128)

résultat


Train on 50000 samples
50000/50000 [==============================] - 9s 176us/sample - loss: 1.3960 - accuracy: 0.5021
Train on 50000 samples
50000/50000 [==============================] - 9s 173us/sample - loss: 1.2899 - accuracy: 0.5430
Train on 50000 samples
50000/50000 [==============================] - 9s 175us/sample - loss: 1.2082 - accuracy: 0.5750
Train on 50000 samples
50000/50000 [==============================] - 9s 171us/sample - loss: 1.1050 - accuracy: 0.6133
Train on 50000 samples
50000/50000 [==============================] - 7s 132us/sample - loss: 1.0326 - accuracy: 0.6405
CPU times: user 52 s, sys: 15.4 s, total: 1min 7s
Wall time: 48.7 s
random_rotate_cpu_and_GPU_processing_rate

Est-ce que vous avez le sentiment que la carte de prétraitement fonctionne sur le CPU alors que le GPU fonctionne à 90%? Comme il est de 48,7 secondes au total, il peut être raccourci d'environ 25 secondes. En outre, le temps sans carte était de 35,1 secondes, vous pouvez donc voir que l'augmentation des données peut être effectuée assez rapidement. Et si vous le faites de la même manière, vous pouvez ** tout le système keras.preprocessing.image. ** **

Augmentation des données de port possible avec Keras

Préparation

show_data


def show_data(tf_dataset):
    for b_img,b_label in tf_dataset:
        for i, img,label in zip(range(25),b_img,b_label):
            plt.subplot(5,5,i+1)
            plt.xticks([])
            plt.yticks([])
            plt.grid(False)
            plt.imshow(img)
            plt.xlabel(labels[label])
        break
    plt.show()

random_shift

Vous pouvez spécifier jusqu'à quel pourcentage du quart de travail au hasard.

random_shift


from tensorflow.keras.preprocessing.image import random_shift
from joblib import Parallel, delayed
def r_shift(imgs,wrg,hrg):
    pics=imgs.numpy()
    w = wrg.numpy()
    h = wrg.numpy()

    if tf.rank(imgs)==4:
        X=Parallel(n_jobs=-1)( [delayed(random_shift)(pic,w,h,0,1,2) for pic in pics] )
        X=np.asarray(X)
    elif tf.rank(imgs)==3:
        X=random_shift(pics, w,h, 0, 1, 2)
    return X
@tf.function
def tf_random_shift(imgs, label):
    x = tf.py_function(r_shift,[imgs,0.3,0.3],[tf.float32])
    X = x[0]
    X.set_shape(imgs.shape)
    return X, label

Visualisation de données


tr_ds = tf.data.Dataset.from_tensor_slices((tr_x,tr_y)).shuffle(40000).batch(128).map(tf_random_shift)

plt.figure(figsize=(10,10),facecolor="white")
show_data(tr_ds)

CIFAR10-random-shift.png

random_shear

Il peut être déformé. (Je ne connais pas les détails)

random_shear


from tensorflow.keras.preprocessing.image import random_shear

def r_shear(imgs,degree):
    pics=imgs.numpy()
    degree = degree.numpy()
    if tf.rank(imgs)==4:
        X=Parallel(n_jobs=-1)( [delayed(random_shear)(pic,degree,0,1,2) for pic in pics] )
        X=np.asarray(X)
    elif tf.rank(imgs)==3:
        X=random_shear(pics,degree,0,1,2)
    return X
@tf.function
def tf_random_shear(imgs, label):
    x = tf.py_function(r_shear,[imgs,30],[tf.float32])
    X = x[0]
    X.set_shape(imgs.shape)
    return X, label

Confirmation des données


tr_ds = tf.data.Dataset.from_tensor_slices((tr_x,tr_y)).shuffle(40000).batch(128).map(tf_random_shear)

plt.figure(figsize=(10,10),facecolor="white")
show_data(tr_ds)

CIFAR10-random-shear.png

random_zoom

Effectue un zoom aléatoire.

random_zoom


from tensorflow.keras.preprocessing.image import random_zoom

def r_zoom(imgs,range_w,range_h):
    pics=imgs.numpy()
    zoom_range = (range_w.numpy(),range_h.numpy())

    if tf.rank(imgs)==4:
        X=Parallel(n_jobs=-1)( [delayed(random_zoom)(pic,zoom_range,0,1,2) for pic in pics] )
        X=np.asarray(X)
    elif tf.rank(imgs)==3:
        X=random_zoom(pics,zoom_range,0,1,2)
    return X
@tf.function
def tf_random_zoom(imgs, label):
    x = tf.py_function(r_zoom,[imgs,0.5,0.5],[tf.float32])
    X = x[0]
    X.set_shape(imgs.shape)
    return X, label



Résultat de sortie


tr_ds = tf.data.Dataset.from_tensor_slices((tr_x,tr_y)).shuffle(40000).batch(128).map(tf_random_zoom)

plt.figure(figsize=(10,10),facecolor="white")
show_data(tr_ds)

CIFAR10-random-zoom.png

Il ressemble à la même taille ... Améliorons-le.

Amélioration

enhanced


from tensorflow.keras.preprocessing.image import random_zoom
import random
def zoom_range_gen(random_state):
    while True:
        x=random.uniform(random_state[0],random_state[1])
        yield (x,x)
def r_zoom(imgs):
    pics=imgs.numpy()
    random_state = [0.5,1.5]
    if tf.rank(imgs)==4:
        X=Parallel(n_jobs=-1)( [delayed(random_zoom)(pic,(x,y),0,1,2) for pic,(x,y) in zip(pics,zoom_range_gen(random_state))])
        X=np.asarray(X)
    elif tf.rank(imgs)==3:
        zoom_range=next(zoom_range_gen)
        X=random_zoom(pics,zoom_range,0,1,2)
    return X
@tf.function
def tf_random_zoom_enhanced(imgs, label):
    x = tf.py_function(r_zoom,[imgs],[tf.float32])
    X = x[0]
    X.set_shape(imgs.shape)
    return X, label

Vérifions les données

Confirmation des données


tr_ds = tf.data.Dataset.from_tensor_slices((tr_x,tr_y)).shuffle(40000).batch(128).map(tf_random_zoom_enhanced)

plt.figure(figsize=(10,10),facecolor="white")
show_data(tr_ds)

CIFAR10-random-zoom-enhanced.png

Ça fait du bien! !!

Mettez en œuvre une autre augmentation.

Ensuite, implémentez l'augmentation dans ce blog "Résumé de l'augmentation des données des images dans NumPy" L'augmentation dans Keras est basée sur Numpy, vous pouvez donc maintenant implémenter l'augmentation basée sur Numpy.

Je citerai l'image de Neko sur le blog "Résumé de l'augmentation des données des images dans NumPy". Je citerai également le contenu de cette implémentation. J'écrirai également la source dans le code.

random-flip

random_flip

Ici

Implémentons une inversion aléatoire gauche-droite. Cela a déjà été implémenté dans le système TF, nous allons donc l'utiliser.

random-flip


@tf.function
def flip_left_right(image,label):
    return tf.image.random_flip_left_right(image),label

@tf.function
def flip_up_down(image,label):
    return tf.image.random_flip_up_down(image),label


Confirmation des données


tr_ds = tf.data.Dataset.from_tensor_slices((tr_x,tr_y)).shuffle(40000).batch(128)
tr_ds = tr_ds.map(flip_left_right).map(flip_up_down)

plt.figure(figsize=(10,10),facecolor="white")
show_data(tr_ds)

CIFAR10-random-flip.png

random-clip

Ici, nous utiliserons l'augmentation d'échelle dans Blog. Scale Augmentation

Ici

Pour la mise en œuvre, je me suis référé au blog.

random-clip


from PIL import Image

def random_crop(pic, crop_size=(28, 28)):
    try:
        h, w, c = pic.shape
    except ValueError:
        raise ValueError("4Ds image can't decode")
    #Déterminez le point supérieur gauche de l'image dans la section spécifiée
    top = np.random.randint(0, h - crop_size[0])
    left = np.random.randint(0, w - crop_size[1])

    #Déterminez le point en bas à droite pour s'adapter à la taille
    bottom = top + crop_size[0]
    right = left + crop_size[1]

    #Découpez uniquement l'intersection du point supérieur gauche et du point inférieur droit
    pic = pic[top:bottom, left:right, :]
    return pic

def scale_augmentation(pic, scale_range=(38, 80), crop_size=32):
    scale_size = np.random.randint(*scale_range)
    Ppic = Image.fromarray(pic)
    Ppic = Ppic.resize((scale_size,scale_size),resample=1)
    pic = np.asarray(Ppic)

    return random_crop(pic, (crop_size, crop_size))

def r_crop(imgs):
    pics=imgs.numpy()
    pics=np.asarray(pics * 255.0,dtype=np.uint8)

    random_state = (38,60)
    crop_size=32
    if tf.rank(imgs)==4:
        X=Parallel(n_jobs=-1)([delayed(scale_augmentation)(pic,random_state,crop_size) for pic in pics ])
        X=np.asarray(X)
    elif tf.rank(imgs)==3:
        X=scale_augmentation(pics,random_state,crop_size)
    
    X=X/255.0
    return X
@tf.function
def tf_random_crop(imgs, label):
    x = tf.py_function(r_crop,[imgs],[tf.float32])
    X = x[0]
    X.set_shape(imgs.shape)
    return X, label

Confirmation des données


tr_ds = tf.data.Dataset.from_tensor_slices((tr_x,tr_y)).shuffle(40000).batch(128)
tr_ds = tr_ds.map(tf_random_crop)

plt.figure(figsize=(10,10),facecolor="white")
show_data(tr_ds)

CIFAR10-random-crop.png

random-erasing

random-erasing

Ici

Mettez cela en œuvre. Pour la mise en œuvre, j'ai fait référence à Blog.

random_erasing


def random_erasing(pic, p=0.5, s=(0.02, 0.4), r=(0.3, 3)):
    #Masquer ou non
    if np.random.rand() > p:
        return pic

    #Déterminez aléatoirement la valeur de pixel à masquer
    mask_value = np.random.random()

    try:
        h, w, c = pic.shape
    except ValueError:
        raise ValueError("4Ds image can't decode")
    #Taille du masque s de l'image originale(0.02~0.4)Décidez au hasard dans la double gamme
    mask_area = np.random.randint(h * w * s[0], h * w * s[1])

    #Ratio d'aspect du masque r(0.3~3)Décidé au hasard dans la gamme de
    mask_aspect_ratio = np.random.rand() * r[1] + r[0]

    #Déterminez la hauteur et la largeur du masque à partir de la taille et du rapport hauteur / largeur du masque
    #Hauteur et largeur calculées(Soit)Peut être plus grande que l'image d'origine, donc corrigez
    mask_height = int(np.sqrt(mask_area / mask_aspect_ratio))
    if mask_height > h - 1:
        mask_height = h - 1
    mask_width = int(mask_aspect_ratio * mask_height)
    if mask_width > w - 1:
        mask_width = w - 1

    top = np.random.randint(0, h - mask_height)
    left = np.random.randint(0, w - mask_width)
    bottom = top + mask_height
    right = left + mask_width
    pic[top:bottom, left:right, :].fill(mask_value)
    return pic

def r_erase(imgs):
    pics=imgs.numpy()

    if tf.rank(imgs)==4:
        X=Parallel(n_jobs=-1)([delayed(random_erasing)(pic) for pic in pics ])
        X=np.asarray(X)
    elif tf.rank(imgs)==3:
        X=random_erasing(pics)
    
    return X
@tf.function
def tf_random_erase(imgs, label):
    x = tf.py_function(r_erase,[imgs],[tf.float32])
    X = x[0]
    X.set_shape(imgs.shape)
    return X, label

Confirmation des données


tr_ds = tf.data.Dataset.from_tensor_slices((tr_x,tr_y)).shuffle(40000).batch(128)
tr_ds = tr_ds.map(tf_random_erase)

plt.figure(figsize=(10,10),facecolor="white")
show_data(tr_ds)

CIFAR10-random-erase.png

Avec CIFAR10, je suis un peu débordé, et il y a des choses que je ne comprends pas ...

Ce qui est important pour la mise en œuvre de l'augmentation à des fins générales

Voici quelques points à garder à l'esprit lors de l'écriture de code. C'est une chose basique, donc je suis sûr que certains d'entre vous peuvent penser que c'est quelque chose. Étonnamment, il n'y avait que des écueils, je vais donc les noter ici.

À propos de tf.data.Dataset.map

Il y a quelques pièges ici, mais le comportement lors du mappage est du type Tensor. ... ce que je veux dire, c'est que ** Eager Execution ne fonctionne pas sur Tensor géré par .map. ** ** En d'autres termes, la multiplication normale, etc. peut être parfaitement convertie en opération de type TF avec @ tf.function, Sinon, les opérations qui ne peuvent pas être effectuées sans nombres réels, telles que .numpy (), ne peuvent pas être utilisées **. ** ** Je pense que c'est facile à comprendre si vous pensez qu'il est simplement décrit comme une expression comme x + y = z.

Ce qu'il faut utiliser, c'est activer le mode Eager. Ce qu'il faut faire est d'utiliser ** tf.py_function. ** **

Que sont le mode Eager et le mode Graph en premier lieu?

Le mode graphique est comme une formule. C'est ce que j'ai fait avec Sess.run dans la série TF1.x. En concevant quelque chose comme la formule x + y = z, puis en attribuant des valeurs aux variables (c'est-à-dire en exécutant Session) 2 + 3 = 5 Z Tensor a une valeur de 5 pour la première fois. C'est là que le système TF1.x était difficile à comprendre.

Le mode Eager est comme la saisie d'une expression et la sortie de la valeur immédiatement exécutée. Sess.run est exécuté automatiquement et Graph est conservé, il semble donc facile à comprendre. (Je ne connais pas du tout ce domaine, j'espère donc que vous pourrez vous référer au guide officiel.) (S'il vous plaît dites-moi si vous faites une erreur)

À propos de tf.py_function

tf.py_function est une fonction qui peut être partiellement exécutée en mode Eager comme décrit dans Guide. Ici, en d'autres termes, il suffit de définir la fonction de boîte noire f (x) et de spécifier uniquement ce qui sort, et elle sera exécutée en mode graphique. Ce sera comme ça. En tant qu'expression, le côté TF veut une expression telle que x + f (a, b) = y, et ce qui est nécessaire ici, ce sont les types de données d'entrée et de sortie.

py_Pseudo code pour la fonction


def function(Entrée 1,Entrée 2):
    #Ici, il fonctionne en mode Eager
    #Traiter quelque chose
retour sortie 1,Sortie 2

[Sortie 1,Sortie 2] = tf.py_function(function,[Entrée 1,Entrée 2],[Sortie 1の型,Sortie 2の型])

Plus précisément, ce sera comme ça.

py_function


def function(data1,data2):
    return data1+data2,data1*data2
@tf.function
def process(tensor1,tensor2):
    [data1,data2]=tf.py_function(function,[tensor1,tensor2],[tf.float32,tf.float32])
    return data1, data2

En d'autres termes, la fonction de fonction ici est exécutée en mode Eager au moment de l'exécution, donc elle devient tf.Tensor avec une valeur dans Tensor. tf.Tensor et Tensor sont différents en mode Eager et en mode Graph, alors soyez prudent **

Comportement dans la fonction spécifiée par py_function

Ceci est fait en mode Eager et tf.Tensor est introduit, il est donc normal de faire le premier .numpy () et de renvoyer le résultat comme numpy. C'est là que surviennent de nombreux malentendus. tf.data.Dataset.map fonctionne uniquement en mode graphique au début. En plus de cela, certains doivent fonctionner en mode Eager.

Pour résumer le tout dans la figure

dataset-graph-mode

Cela semble être le comportement de tf.data.Dataset.from_tensor_slices. (Je suis désolé car ce ne sont pas des informations exactes) Et lorsque les données sont déchargées, ce sera comme suit.

dataset-eager-mode

Si vous codez dans cet esprit, vous pourrez coder en douceur sans être dérouté par de mystérieux bugs.

en conclusion

Avec cela, je pense avoir été en mesure de vous dire comment développer une augmentation de données à usage général. Je veux faire autre chose comme ça! J'espère que ceux qui disent cela réussiront de la même manière. Je suis vraiment soulagé de résoudre le mystère de «py_function». Veuillez utiliser tous les moyens.

Merci

Ce blog "Résumé de l'augmentation des données des images dans NumPy" a été très utile pour l'implémenter. Je profite de cette occasion pour vous remercier.

Recommended Posts

[Application TF2.0] Un cas où l'augmentation de données à usage général a été parallélisée et réalisée à grande vitesse avec la fonction de jeu de données fort de l'exemple TF.
[Big Query] Chargez une partie des données BQ dans les pandas à grande vitesse
Le résultat était meilleur lorsque les données d'apprentissage du mini-lot ont été faites un hybride de fixe et aléatoire avec un réseau de neurones.
J'ai construit l'environnement de développement d'AWS Chalice avec docker et j'ai essayé de déployer une application sans serveur à très haute vitesse