[PYTHON] Tipps zum Umgang mit Eingaben variabler Länge im Deep Learning Framework

Einführung

Bei der Verwendung von Matrizen variabler Länge gibt es verschiedene Muster, z. B. bei der Verarbeitung natürlicher Sprache. Ich habe das Gefühl, dass ich es jedes Mal neu implementiere, also werde ich es als Memorandum zusammenfassen.

In diesem Artikel werde ich die Implementierung von Chainer und Tensorflow vorstellen, die ich häufig verwende. (Hinweis: Ich habe den Produktionscode nicht kopiert und eingefügt, sondern für diesen Beitrag von Grund auf neu implementiert, sodass er nicht getestet wurde.)

Zu beachtende Punkte zu Chainer

Ich denke, Chainer empfiehlt, variable Längen als Liste von "Variablen" zu verwalten, anstatt sie mit "Variable" + "Länge" zu verwalten, wie unten beschrieben. Insbesondere L.NStepLSTM und [ F.pad_sequence](https: / /docs.chainer.org/en/stable/reference/generated/chainer.functions.pad_sequence.html) und so weiter.

Hinweis

Der folgende Code basiert auf der Annahme, dass die folgenden Importe durchgeführt wurden.

Chainer


import chainer
import chainer.functions as F
import numpy as np

Tensorflow


import tensorflow as tf
import numpy as np


sess = tf.InteractiveSession()

Text

Padding

Viele Deep-Learning-Frameworks unterstützen die Berechnung von Matrizen variabler Länge nicht direkt, um parallele GPU- und CPU-Berechnungen zu nutzen. Daher wird eine Polsterung durchgeführt, um das Teil außerhalb der Serienlänge mit einem geeigneten Wert entsprechend der maximalen Länge der Matrix zu füllen.

Darüber hinaus wird dieser Teil häufig in der Phase der Datenerstellung von Ihnen selbst ausgeführt, nicht im Rahmen des Deep Learning.

X = [np.array([1, 2]),
     np.array([11, 12, 13, 14]),
     np.array([21])]

#Mit int32 unter der Annahme, dass die Wort-ID behandelt wird
x = np.zeros([3, 4], dtype=np.int32)

for i, xi in enumerate(X):
    x[i, :len(xi)] = xi[:]

print x
# [[ 1  2  0  0]
#  [11 12 13 14]
#  [21  0  0  0]]

Bei Verwendung von Chainers L.EmbedId ist es besser, -1-Padding anstelle von 0-Padding zu verwenden und L.EmbedId (..., ignore_label = -1) zu verwenden.

Masking

Wenn Sie Summenpooling usw. durchführen, maskieren Sie den Teil außerhalb der durch Auffüllen erstellten Serienlänge mit 0 (jedoch [Maskierung nicht überbewerten](#Maskierung nicht überbewerten). Die Berechnung kann durch die Berechnung von "wo" realisiert werden.

mask.png (Wenn True, wird der l-Wert verwendet, und wenn False, wird der r-Wert als Maskierung verwendet.)

Wenn Sie diesen Prozess Schritt für Schritt schreiben:

Chainer


x = chainer.Variable(np.arange(1, 7).reshape(2, 3))
print x
# variable([[1 2 3]
#           [4 5 6]])

length = np.array([3, 2], dtype=np.int32)
print length
# [3 2]

xp = chainer.cuda.get_array_module(x.data)
mask = xp.tile(xp.arange(x.shape[-1]).reshape(1, -1), (x.shape[0], 1))
print mask
# [[0 1 2]
#  [0 1 2]]

mask = mask < length.reshape(-1, 1)
print mask
# [[ True  True  True]
#  [ True  True False]]

padding = xp.zeros(x.shape, dtype=x.dtype)
print padding
# [[0 0 0]
#  [0 0 0]]

z = F.where(mask, x, padding)
print z
# variable([[1 2 3]
#           [4 5 0]])

sequence_mask ist in Tensorflow praktisch.

Tensorflow


x = tf.constant(np.arange(1, 7).reshape(2, 3).astype(np.float32))
length = tf.constant(np.array([3, 2], dtype=np.int32))

mask = tf.sequence_mask(length, tf.shape(x)[-1])
padding = tf.fill(tf.shape(x), 0.0)
z = tf.where(mask, x, padding)
print z.eval()
# [[ 1.  2.  3.]
#  [ 4.  5.  0.]]

Chainer-Version (statt Numpy-Version) sequence_mask

Chainer


def sequence_mask(length, max_num=None):
    xp = chainer.cuda.get_array_module(length.data)
    if max_num is None:
        max_num = xp.max(length)
    # create permutation on (length.ndim + 1) dimension
    perms = xp.arange(max_num).reshape([1] * length.ndim + [-1])
    length = length.reshape([1] * (length.ndim - 1) + [-1] + [1])
    return perms < length

Reshape

Da sich Deep Learning häufig mit Matrizen des Ranges 2 mit der Größe "Mini-Batch-Größe x Merkmalsmenge" befasst, bieten viele Frameworks viele Funktionen, die solche Matrizen als Eingabe verwenden. Um die Vorteile dieser Funktionen nutzen zu können, wird eine Matrix aus "Mini-Batch x Sequenzlänge x Merkmalsmenge" in eine Rang-2-Matrix aus "(Mini-Batch-Größe * Sequenzlänge) x Merkmalsmenge" umgewandelt und verarbeitet.

reshape_1.png

Dies ist jedoch eine Verschwendung zusätzlicher Verarbeitung, wenn die Matrix relativ dünn ist. Sie können die Verarbeitung reduzieren, indem Sie bei der Indizierung Ihr Bestes geben. (Ich habe es nicht ausprobiert, aber wenn die Matrix nicht dünn ist, kann es einige Zeit dauern, den Speicher neu zuzuweisen. Seien Sie also vorsichtig.)

reshape_2.png

Im Fall von Tensorflow kann eine solche Verarbeitung durch die folgende Verarbeitung realisiert werden.

reshape_4.png

Chainer


# WARNING: I have not checked it in case of rank != 3

x = chainer.Variable(np.arange(18).astype(np.float32).reshape(3, 3, 2))
length = np.array([2, 3, 1], dtype=np.int32)
w = chainer.Variable(np.ones([2, 3], dtype=np.float32))

# sequence_Maske ist oben erwähnt
mask = sequence_mask(length, x.shape[length.ndim])
print mask
# [[ True  True False]
#  [ True  True  True]
#  [ True False False]]

x_reshaped = F.get_item(x, mask)
print x_reshaped
# [[  0.   1.]
#  [  2.   3.]
#  [  6.   7.]
#  [  8.   9.]
#  [ 10.  11.]
#  [ 12.  13.]]

y_reshaped = F.matmul(x_reshaped, w)
print y_reshaped
# [[  1.   1.   1.]
#  [  5.   5.   5.]
#  [ 13.  13.  13.]
#  [ 17.  17.  17.]
#  [ 21.  21.  21.]
#  [ 25.  25.  25.]]

pad_shape = [[0, 0] for _ in xrange(y_reshaped.ndim)]
pad_shape[length.ndim - 1][1] = 1
y_reshaped = F.pad(y_reshaped, pad_shape, 'constant', constant_values=0.)
print y_reshaped
# variable([[  1.,   1.,   1.],
#           [  5.,   5.,   5.],
#           [ 13.,  13.,  13.],
#           [ 17.,  17.,  17.],
#           [ 21.,  21.,  21.],
#           [ 25.,  25.,  25.],
#           [  0.,   0.,   0.]])


idx_size = np.prod(mask.shape)
inv_idx = np.ones([idx_size], dtype=np.int32) * -1
inv_idx[np.nonzero(mask.flat)[0]] = np.arange(x_reshaped.shape[0]).astype(np.int32)
print inv_idx
# [ 0  1 -1  2  3  4  5 -1 -1]

y = F.reshape(F.get_item(y_reshaped, inv_idx), list(x.shape[:length.ndim + 1]) + [-1])
print y
# [[[  1.   1.   1.]
#   [  5.   5.   5.]
#   [  0.   0.   0.]]
# 
#  [[ 13.  13.  13.]
#   [ 17.  17.  17.]
#   [ 21.  21.  21.]]
# 
#  [[ 25.  25.  25.]
#   [  0.   0.   0.]
#   [  0.   0.   0.]]]

Im Fall von Tensorflow kann eine solche Verarbeitung durch die folgende Verarbeitung realisiert werden.

reshape_3.png

Tensorflow


# WARNING: I have not checked it in case of rank != 3
x = tf.constant(np.arange(18).astype(np.float32).reshape(3, 3, 2))
length = tf.constant(np.array([2, 3, 1], dtype=np.int32))
w = tf.constant(np.ones([2, 3], dtype=np.float32))

mask = tf.sequence_mask(length, tf.shape(x)[tf.rank(length)])
print mask.eval()
# [[ True  True False]
#  [ True  True  True]
#  [ True False False]]

x_reshaped = tf.boolean_mask(x, mask)
print x_reshaped.eval()
# [[  0.   1.]
#  [  2.   3.]
#  [  6.   7.]
#  [  8.   9.]
#  [ 10.  11.]
#  [ 12.  13.]]

y_reshaped = tf.matmul(x_reshaped, w)
print y_reshaped.eval()
# [[  1.   1.   1.]
#  [  5.   5.   5.]
#  [ 13.  13.  13.]
#  [ 17.  17.  17.]
#  [ 21.  21.  21.]
#  [ 25.  25.  25.]]

idx = tf.to_int32(tf.where(mask))
print idx.eval()
# [[0 0]
#  [0 1]
#  [1 0]
#  [1 1]
#  [1 2]
#  [2 0]]

shape = tf.concat([tf.shape(x)[:-1], tf.shape(y_reshaped)[-1:]], 0)
print shape.eval()
# [3 3 3]

y = tf.scatter_nd(idx, y_reshaped, shape)
print y.eval()
# [[[  1.   1.   1.]
#   [  5.   5.   5.]
#   [  0.   0.   0.]]
# 
#  [[ 13.  13.  13.]
#   [ 17.  17.  17.]
#   [ 21.  21.  21.]]
# 
#  [[ 25.  25.  25.]
#   [  0.   0.   0.]
#   [  0.   0.   0.]]]

Implementierung von Softmax

Erwägen Sie einen Softmax für die äußerste Dimension einer bestimmten Matrix. Solche Situationen treten in ListNet Permutation Wahrscheinlichkeitsverteilung und bei der Berechnung der Aufmerksamkeit auf.

Softmax-Formel $ y_i = \frac{exp(x_i)}{\sum_jexp({x_j})} $

x = np.random.random([2, 3]).astype(np.float32)
# array([[ 0.44715771,  0.85983515,  0.08915455],
#        [ 0.02465274,  0.63411605,  0.01340247]], dtype=float32)

length = np.array([3, 2], dtype=np.int32)

Ich möchte Softmax nur mit dem blauen Bereich berechnen, wie in der folgenden Abbildung gezeigt.

masked_softmax.png

Tragen Sie vorher / nachher keine Maske.

Chainer


#Schlechtes Beispiel 1
x_ = np.copy(x)
x_[1, 2] = 0.
print F.softmax(x_)
# variable([[ 0.31153342,  0.47068265,  0.21778394],
#           [ 0.26211682,  0.48214924,  0.25573397]])

#Schlechtes Beispiel 2
y = F.softmax(x)
y[1, 2] = 0.
print y
# variable([[ 0.31153342,  0.47068265,  0.21778394],
#           [ 0.26121548,  0.48049128,  0.0       ]])
#Die Summe der zweiten Zeile ist 1.Offensichtlich nicht, weil es nicht 0 ist

Der Grund ist sehr einfach, Beispiel 1 ist für $ exp (0.258) \ neq 0 $. In Beispiel 2 beeinflusst "x [2,1]" die Berechnung des Nenners.

Bei der Softmax-Berechnung wird die Maskierung mit $ exp (-inf) = 0 $ durchgeführt.

Chainer


def masked_softmax(x, length):
    """
    Softmax operation on the ourter-most dimenstion of x.

    Args:
         x (chainer.Variable): Values to be passed to softmax
         length (numpy.ndarray or cupy.ndarray):
             Number of items in the outer-most dimension of x
    """
    assert x.ndim - 1 == length.ndim
    xp = chainer.cuda.get_array_module(x.data)
    x_shape = x.shape
    x = F.reshape(x, (-1, x_shape[-1]))
    # mask: (B, T)
    mask = xp.tile(xp.arange(x.shape[-1]).reshape(1, -1), (x.shape[0], 1))
    mask = mask < length.reshape(-1, 1)
    padding = xp.ones(x.shape, dtype=x.dtype) * -np.inf
    z = F.where(mask, x, padding)
    return F.reshape(F.softmax(z), x_shape)


print masked_softmax(chainer.Variable(x), length)
# variable([[ 0.31153342,  0.47068265,  0.21778394],
#           [ 0.35218161,  0.64781839,  0.        ]])

Tensorflow


def masked_softmax(x, length):
    """
    Softmax operation on the ourter-most dimenstion of x.

    Args:
         x (tf.Tensor): Values to be passed to softmax
         length (tf.Tensor): Number of items in the outer-most dimension of x
    """
    mask = tf.sequence_mask(length, tf.shape(x)[-1])
    padding = tf.fill(tf.shape(x), -np.inf)
    z = tf.where(mask, x, padding)
    return tf.nn.softmax(z, dim=-1)


print masked_softmax(
    tf.constant(x),
    tf.constant(length)).eval()
# [[ 0.31153342,  0.47068265,  0.21778394],
#  [ 0.35218161,  0.64781839,  0.        ]]

Appendix:

Vertraue nicht zu sehr in Mask

Im Deep-Learning-Framework gibt es bei einer Division auf 0 eine Spezifikation, bei der der Gradient zu "inf" wird, selbst wenn "where" verwendet wird. Daher funktioniert "Ich sollte maskieren, auch wenn ich eine instabile Berechnung mache" nicht.

Es gibt ein Netzwerk wie die folgende Formel.

e = f_0(x) \\
w = f_1(e)

Dies wird durch die Kettenregel wie folgt ausgedrückt. $ \frac{\partial w}{\partial x} = \frac{\partial w}{\partial e}\frac{\partial e}{\partial x} $

Dies wird nun (ungefähr) durch automatische Differenzierung wie folgt erreicht.

x.grad = e.grad * g(f_0, e, x)

Hier ist "g (f_0, e, x)" ein partielles Differential, ausgedrückt aus $ f_0 $ und seiner Eingabe / Ausgabe. Mit anderen Worten, unabhängig davon, welcher Differentialwert "z. B. rad" aus der oberen Formel stammt, wenn "der partielle Differentialwert der Formel $ f_0 $" inf "oder" nan "ist, ist" x.grad "auch" inf ". Es wird "oder" nan ". Wenn Sie dies mit Chainer und Tensorflow versuchen,

Tensorflow


sess = tf.InteractiveSession()

x = tf.constant(0.0)

t = x
e = 1. / x
w = tf.where(True, t, e)

print w.eval()  # 0.0
print tf.gradients(w, x)[0].eval()  # nan

Chainer


x = chainer.Variable(np.array([0.0], dtype=np.float32))
t = x
e = 1. / x
w = chainer.functions.where(np.array([True]), t, e)

w.grad = np.array([1.0], np.float32)
w.backward(retain_grad=True)

print w  # 0.
print x.grad  # nan

Recommended Posts

Tipps zum Umgang mit Eingaben variabler Länge im Deep Learning Framework
Tiefes Lernen mit Python Kapitel 2 (Material für runde Vorlesung)
Deep Learning für die Bildung von Verbindungen?
[AI] Deep Learning für das Entrauschen von Bildern
Deep Learning von Grund auf neu - Kapitel 4 Tipps für die in Python erlernte Theorie und Implementierung von Deep Learning
Windows → Linux Tipps zum Einbringen von Daten
Tipps zum Umgang mit Binärdateien in Python
Bilderkennungsmodell mit Deep Learning im Jahr 2016
Machen Sie Ihren eigenen PC für tiefes Lernen
Beispiel für den Umgang mit EML-Dateien in Python
Tipps zum Erstellen großer Anwendungen mit Flask
"Deep Learning von Grund auf neu" mit Haskell (unvollendet)
[Deep Learning] Nogisaka Gesichtserkennung ~ Für Anfänger ~
Tipps zum Erstellen kleiner Werkzeuge mit Python
Informationen zur Datenerweiterungsverarbeitung für tiefes Lernen
Einführung in Deep Learning (1) --Chainer wird Anfängern leicht verständlich erklärt.
[Für Anfänger] Was ist in Deep Learning von Grund auf neu geschrieben?