[PYTHON] [TF2.0-Anwendung] Ein Fall, in dem die allgemeine Datenerweiterung mit der starken Datensatzfunktion des TF-Beispiels parallelisiert und mit hoher Geschwindigkeit realisiert wurde.

Einführung

Dieser Artikel ist der vorherige Artikel "Die Geschichte, dass die Datensatzfunktion, die mit TensorFlow verwendet werden kann, stark war" "[[TF2.0-Anwendung] tf.data. Es handelt sich um eine weiter verbesserte Version der Datenerweiterung, die in "Datenerweiterung mit Datensatz schneller machen" (https://qiita.com/Suguru_Toyohara/items/49c2914b21615b554afa) ein wenig angesprochen wurde.

Bei Verwendung des Systems "tf.data.Dataset" zur Geschwindigkeitsverbesserung und des Systems "keras.preprocessing.image" ** Es ist gelungen, Code zu realisieren, der parallel verarbeitet werden kann. ** ** ** Ich werde den eigentlichen Mechanismus und den Hintergrund an dieser Stelle neben den Code setzen.

Ich werde den Code unten setzen

Umweltvereinbarung

Lassen Sie uns zunächst die experimentelle Umgebung vorbereiten.

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"])

Ein Beispiel für Datenerweiterung

Bestätigung der Daten

Lassen Sie uns zuerst keras.preprocessing.image.random_rotate ausdrücken, damit dies mit .map durchgeführt werden kann.

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

Jetzt funktioniert es tatsächlich. Verschieben wir es und sehen uns die Daten an.

Daten anzeigen


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

Geschwindigkeitstest

Lassen Sie uns überprüfen, wie schnell es tatsächlich sein wird. Zunächst war die Geschwindigkeit in "[TF2.0-Anwendung] tf.data.Dataset zur Beschleunigung der Datenerweiterung" wie folgt.

Ergebnis


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

Als nächstes werde ich den Code und die Ergebnisse der vorherigen Implementierung veröffentlichen.

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)

Ergebnis


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

Ist es ein Gefühl, dass die Vorverarbeitungszuordnung auf der CPU funktioniert, während die GPU zu bis zu 90% läuft? Da es insgesamt 48,7 Sekunden sind, kann es um etwa 25 Sekunden verkürzt werden. Außerdem betrug die Zeit ohne Karte 35,1 Sekunden, sodass Sie sehen können, dass die Datenerweiterung relativ schnell durchgeführt werden kann. Und wenn Sie es auf die gleiche Weise tun, können Sie das gesamte System "keras.preprocessing.image" verwenden. ** ** **

Portdatenerweiterung mit Keras möglich

Vorbereitung

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

Sie können bis zu welchem Prozentsatz der Verschiebung zufällig angeben.

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

Datenvisualisierung


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

Es kann verzerrt sein. (Ich kenne die Details nicht)

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

Datenbestätigung


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

Zoomt zufällig.

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



Ausgabeergebnis


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

Es sieht aus wie die gleiche Größe ... Lassen Sie es uns verbessern.

Verbesserung

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

Lassen Sie uns die Daten überprüfen

Bestätigung der Daten


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

Es fühlt sich gut an! !!

Implementieren Sie eine weitere Erweiterung.

Implementieren Sie als Nächstes die Erweiterung in diesem Blog "Zusammenfassung der Datenerweiterung von Bildern in NumPy". Die Augmentation in Keras basiert auf Numpy, sodass Sie jetzt die Numpy-basierte Augmentation implementieren können.

Ich werde das Bild von Neko aus dem Blog "Zusammenfassung der Datenerweiterung von Bildern in NumPy" zitieren. Ich werde auch den Inhalt dieser Implementierung zitieren. Ich werde auch die Quelle in den Code schreiben.

random-flip

random_flip

Hier

Lassen Sie uns eine zufällige Umkehrung von links nach rechts implementieren. Dies wurde bereits im TF-System implementiert, daher werden wir es verwenden.

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


Bestätigung der Daten


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

Hier verwenden wir Scale Augmentation in Blog. Scale Augmentation

Hier

Für die Implementierung habe ich auf den [Blog] verwiesen (https://www.kumilog.net/entry/numpy-data-augmentation).

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")
    #Bestimmen Sie den oberen linken Punkt des Bildes im angegebenen Abschnitt
    top = np.random.randint(0, h - crop_size[0])
    left = np.random.randint(0, w - crop_size[1])

    #Bestimmen Sie den unteren rechten Punkt entsprechend der Größe
    bottom = top + crop_size[0]
    right = left + crop_size[1]

    #Schneiden Sie nur den Schnittpunkt des oberen linken Punkts und des unteren rechten Punkts aus
    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

Datenbestätigung


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

Hier

Implementieren Sie dies. Für die Implementierung habe ich auf [Blog] verwiesen (https://www.kumilog.net/entry/numpy-data-augmentation).

random_erasing


def random_erasing(pic, p=0.5, s=(0.02, 0.4), r=(0.3, 3)):
    #Ob maskiert oder nicht
    if np.random.rand() > p:
        return pic

    #Bestimmen Sie zufällig den zu maskierenden Pixelwert
    mask_value = np.random.random()

    try:
        h, w, c = pic.shape
    except ValueError:
        raise ValueError("4Ds image can't decode")
    #Maskengröße s des Originalbildes(0.02~0.4)Entscheide dich zufällig aus dem doppelten Bereich
    mask_area = np.random.randint(h * w * s[0], h * w * s[1])

    #Seitenverhältnis der Maske r(0.3~3)Zufällig aus dem Bereich von entschieden
    mask_aspect_ratio = np.random.rand() * r[1] + r[0]

    #Bestimmen Sie die Höhe und Breite der Maske anhand der Größe und des Seitenverhältnisses der Maske
    #Berechnete Höhe und Breite(Entweder)Kann größer als das Originalbild sein, korrigieren Sie es also
    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

Bestätigung der Daten


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

Mit CIFAR10 bin ich ein wenig überwältigt und es gibt einige Dinge, die ich nicht verstehe ...

Was ist wichtig für die Implementierung von Augmentation für allgemeine Zwecke?

Hier sind einige Dinge zu beachten, wenn Sie Code schreiben. Es ist eine grundlegende Sache, also bin ich sicher, dass einige von Ihnen denken, dass es etwas ist. Überraschenderweise gab es nur Fallstricke, deshalb werde ich sie hier aufschreiben.

Über tf.data.Dataset.map

Hier gibt es einige Fallstricke, aber das Verhalten während des Mappings ist vom Typ "Tensor". ... was ich sagen möchte ist, dass ** Eager Execution auf Tensor, der von .map verarbeitet wird, nicht funktioniert. ** ** ** Mit anderen Worten, normale Multiplikation usw. kann mit @ tf.function sauber in eine TF-Operation umgewandelt werden. Andernfalls können Operationen, die ohne reelle Zahlen nicht ausgeführt werden können, wie z. B. ".numpy ()", nicht verwendet werden **. ** ** ** Ich denke, es ist leicht zu verstehen, wenn Sie denken, dass es nur als Ausdruck wie x + y = z beschrieben wird.

Dort können Sie den Eager-Modus aktivieren. Was zu tun ist, ist ** tf.py_function zu verwenden. ** ** **

Was sind Eager-Modus und Grafik-Modus überhaupt?

Der Grafikmodus ist wie eine Formel. Das habe ich mit Sess.run in der TF1.x-Serie gemacht. Indem Sie so etwas wie die Formel x + y = z entwerfen und dann Variablen Werte zuweisen (dh Sitzung ausführen) 2 + 3 = 5 Z Tensor hat zum ersten Mal einen Wert von 5. Hier war das TF1.x-System schwer zu verstehen.

Der Eager-Modus ist wie die Eingabe eines Ausdrucks und die Ausgabe des sofort ausgeführten Werts. Sess.run wird automatisch ausgeführt und Graph wird beibehalten, sodass es leicht zu verstehen scheint. (Ich bin mit diesem Bereich überhaupt nicht vertraut, daher hoffe ich, dass Sie sich auf den offiziellen Leitfaden beziehen können.) (Bitte sagen Sie mir, wenn Sie einen Fehler machen)

Über tf.py_function

tf.py_function ist eine Funktion, die teilweise im Eager-Modus ausgeführt werden kann, wie in Guide beschrieben. Mit anderen Worten, stellen Sie hier einfach die Black-Box-Funktion f (x) ein und geben Sie nur an, was herauskommt. Sie wird dann im Grafikmodus ausgeführt. Es wird so sein. Als Ausdruck möchte die TF-Seite einen Ausdruck wie x + f (a, b) = y, und hier werden die Eingabe- und Ausgabedatentypen benötigt.

py_Pseudocode für die Funktion


def function(Eingabe 1,Eingabe 2):
    #Hier läuft es im Eager-Modus
    #Etwas verarbeiten
Rückgabe 1,Ausgabe 2

[Ausgabe 1,Ausgabe 2] = tf.py_function(function,[Eingabe 1,Eingabe 2],[Ausgabe 1の型,Ausgabe 2の型])

Insbesondere wird es so sein.

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

Mit anderen Worten, die Funktionsfunktion wird hier zur Laufzeit im Eager-Modus ausgeführt, sodass sie zu "tf.Tensor" mit einem Wert in "Tensor" wird. tf.Tensor und Tensor unterscheiden sich im Eager-Modus und im Graph-Modus. Seien Sie also vorsichtig **

Verhalten innerhalb der durch py_function angegebenen Funktion

Dies geschieht im Eager-Modus und "tf.Tensor" wird eingeblendet. Es ist also in Ordnung, das erste ".numpy ()" auszuführen und das Ergebnis als numpy zurückzugeben. Hier entstehen viele Missverständnisse. tf.data.Dataset.map funktioniert zunächst nur im Grafikmodus. Darüber hinaus müssen einige im Eager-Modus ausgeführt werden.

Um das Ganze in der Abbildung zusammenzufassen

dataset-graph-mode

Dies scheint das Verhalten von "tf.data.Dataset.from_tensor_slices" zu sein. (Es tut mir leid, weil es keine genauen Informationen sind) Und wenn Daten entladen werden, ist dies wie folgt.

dataset-eager-mode

Wenn Sie in diesem Sinne codieren, können Sie reibungslos codieren, ohne durch mysteriöse Fehler verwirrt zu werden.

abschließend

Ich glaube, ich konnte Ihnen damit sagen, wie Sie eine universelle Datenerweiterung entwickeln können. Ich möchte so etwas noch machen! Ich hoffe, dass diejenigen, die das sagen, es auf die gleiche Weise schaffen. Ich bin wirklich erleichtert, das Rätsel der "py_function" zu lösen. Bitte verwenden Sie alle Mittel.

Vielen Dank

Dieser Blog "Zusammenfassung der Datenerweiterung von Bildern in NumPy" war bei der Implementierung sehr hilfreich. Ich möchte diese Gelegenheit nutzen, um Ihnen zu danken.

Recommended Posts

[TF2.0-Anwendung] Ein Fall, in dem die allgemeine Datenerweiterung mit der starken Datensatzfunktion des TF-Beispiels parallelisiert und mit hoher Geschwindigkeit realisiert wurde.
[Große Abfrage] Laden Sie einen Teil der BQ-Daten mit hoher Geschwindigkeit in Pandas
Das Ergebnis war besser, als die Trainingsdaten des Mini-Batches als Hybrid aus fest und zufällig mit einem neuronalen Netzwerk erstellt wurden.
Ich habe die Entwicklungsumgebung von AWS Chalice mit Docker erstellt und versucht, eine serverlose Anwendung mit sehr hoher Geschwindigkeit bereitzustellen