Introduction to ragged tensors

Einfüh­rung in ausge­franste Tensoren

Beitrag teilen:

Einfüh­rung

Wir bespre­chen das Konzept von Tensor­flow

zerlumpten Tensoren

, das Ende 2018 einge­führt wurde. Wir erklären ihre grund­le­gende Struktur und warum sie nützlich sind.

Problem­stel­lung

Durch die Standar­di­sie­rung tradi­tio­neller Machine Learning-Probleme sind viele Modelle sehr einfach zu imple­men­tieren: Lesen Sie eine Tabelle mit Merkmalen aus einer Daten­bank, verwenden Sie pandas und numpy für die Vorver­ar­bei­tung, erstellen Sie ein Modell mit einer der bekannten Biblio­theken und geben Sie model.fit() ein. In vielen Fällen – das war’s! Erledigt!

Aber halt – was ist, wenn die Daten nicht in Tabel­len­form vorliegen und von Natur aus unregel­mäßig sind? Was ist, wenn es Instanzen mit unter­schied­li­chen Abmes­sungen gibt? Betrachten wir ein Szenario für die Klassi­fi­zie­rung von Zeitreihen und nehmen wir an, wir haben einen Daten­satz, der aus vier verschie­denen kurzen Zeitreihen besteht:

Introduction to ragged tensors

Wie Sie sehen können, unter­scheiden sich die Serien sowohl in der Anzahl als auch in der Zeit der Messungen. Da Modelle für maschi­nelles Lernen in der Regel eine feste Einga­be­größe benötigen, ist es etwas kompli­zierter, solche Daten in unsere Modelle einzu­passen.

Es gibt eine Reihe von Möglich­keiten, mit dieser Art von Eingaben umzugehen. Wir könnten zum Beispiel die Serien inter­po­lieren und virtu­elle Messungen zu den gleichen Zeitpunkten für jede Serie vornehmen:

Introduction to ragged tensors

Hier nehmen wir die Werte der Zeitstempel 0, 2, 4, 6, 8 und 10, so dass jede Serie aus 6 Werten besteht. In diesem Stadium müssen wir jedoch bereits Hyper­pa­ra­meter wie die Art der Inter­po­la­tion, die Anzahl der Werte usw. auswählen. Wir können uns jedoch nicht auf die Genau­ig­keit der Inter­po­la­tion verlassen, insbe­son­dere nicht bei extra­po­lierten Werten und bei Werten inner­halb großer Lücken zwischen aufein­an­der­fol­genden Messungen (siehe die orange­far­benen und grünen Reihen bei Zeit 10).

Wenn wir die Daten in ein Tensor­Flow-Keras-Modell einspeisen und keine Inter­po­la­ti­ons­tech­niken verwenden möchten, besteht eine gängige Praxis darin, die Reihen aufzu­füllen, z.B. mit Nullen am Ende. Dies ist notwendig, weil Tensor­Flow Daten­stapel zusam­men­fasst, die in jeder Dimen­sion die gleiche Form haben müssen. Eine Charge mit den oben genannten 4 Serien hätte die Form (4, 6), wobei 4 die Anzahl der Serien (=Chargen­größe) und 6 die Anzahl der Messungen pro Serie ist.

Die 6 ergibt sich jedoch aus künst­li­chen Daten, entweder inter­po­lierten Messungen oder Auffül­lungs­werten. Um die Ungewiss­heit und den Overhead dieser beiden Techniken zu überwinden, können wir die Origi­nal­daten mit Ragged-Tensoren bearbeiten.

Konzept der ausge­fransten Tensoren

Das Konzept der zerlumpten Tensoren ist überra­schend einfach, nachdem Sie die Absicht dahinter verstanden haben. Bleiben wir bei unserem obigen Beispiel mit 4 Zeitreihen. Wie Sie sehen können, ist die Mindest­an­zahl der Messungen pro Serie 3, während die Höchst­zahl 5 beträgt. Mit Padding müssten wir jede Reihe mit Nullen am Ende (oder manchmal am Anfang) auffüllen, um eine gemein­same Länge von 5 zu errei­chen.

Im Gegen­satz dazu besteht ein Ragged-Tensor aus der Verket­tung aller Werte aus allen Serien zusammen mit Metadaten, die angeben, wo die Verket­tung in die einzelnen Serien aufge­teilt werden soll. Definieren wir unseren Daten­rahmen df und dann unseren Ragged-Tensor rt:

Zeit Wert
0 3
3 1
6 8
8 0
10 9
0 15
5 11
8 7
0 12
2 7
4 8
9 2
0 9
4 0
6 13
10 4
row_splits = [0, 5, 8, 12, 16]
rt = tf.RaggedTensor.from_row_splits(values=df.values, row_splits=row_splits)
rt

<tf.RaggedTensor [[[0, 3], [3, 1], [6, 8], [8, 0], [10, 9]], [[0, 15], [5, 11], [8, 7]], [[0, 12], [2, 7], [4, 8], [9, 2]], [[0, 9], [4, 0], [6, 13], [10, 4]]]>

Wie wir sehen können, definiert das row_splits-Array die einzelnen Reihen durch die Angabe ihrer Start­reihe (einschließ­lich) und Endreihe (ausschließ­lich).

Das war’s. Dies ist die wirklich einfache Struktur der zerlumpten Tensoren. Alter­nativ zur Angabe von row_splits können wir denselben Ragged-Tensor auch mit einer der folgenden Methoden erstellen:

  • value_rowids: für jede Zeile in der verket­teten Reihe geben wir eine ID-Nummer an, die die einzelne Reihe indiziert:
value_rowids = [0, 0, 0, 0, 0, 1, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3]
rt_1 = tf.RaggedTensor.from_value_rowids(values=df.values, value_rowids=value_rowids)
rt_1

<tf.RaggedTensor [[[0, 3], [3, 1], [6, 8], [8, 0], [10, 9]], [[0, 15], [5, 11], [8, 7]], [[0, 12], [2, 7], [4, 8], [9, 2]], [[0, 9], [4, 0], [6, 13], [10, 4]]]>
  • row_lengths: wir geben die Länge jeder einzelnen Reihe an:
row_lengths = [5, 3, 4, 4]
rt_2 = tf.RaggedTensor.from_row_lengths(values=df.values, row_lengths=row_lengths)
rt_2

<tf.RaggedTensor [[[0, 3], [3, 1], [6, 8], [8, 0], [10, 9]], [[0, 15], [5, 11], [8, 7]], [[0, 12], [2, 7], [4, 8], [9, 2]], [[0, 9], [4, 0], [6, 13], [10, 4]]]>
  • Konstante: Wir können den Ragged-Tensor als „Konstante“ definieren, indem wir direkt eine Liste von Arrays angeben:
rt_3 = tf.ragged.constant([df.loc[0:4, :].values, df.loc[5:7, :].values, df.loc[8:11, :].values, df.loc[12:15, :].values])
rt_3

<tf.RaggedTensor [[[0, 3], [3, 1], [6, 8], [8, 0], [10, 9]], [[0, 15], [5, 11], [8, 7]], [[0, 12], [2, 7], [4, 8], [9, 2]], [[0, 9], [4, 0], [6, 13], [10, 4]]]>

Intern spielt es keine Rolle, welche Methode wir wählen, um einen zerlumpten Tensor zu erstellen, die Ergeb­nisse sind alle gleich­wertig. Als Nächstes sehen wir uns an, wie man mathe­ma­ti­sche Opera­tionen mit zerlumpten Tensoren durch­führt.

Arbeiten mit ausge­fransten Tensoren

Tensor­Flow bietet eine sehr prakti­sche Funktion, um Opera­tionen auf Ragged-Tensoren durch­zu­führen: tf.ragged.map_flat_values(op, *args, **kwargs). Sie tut das, was der Funkti­ons­name sagt – jeder zerlumpte Tensor in args wird durch seine verket­tete (=flache) Version ersetzt, wobei die Stapel­di­men­sion wegge­lassen wird. In unserem Beispiel ist dies dasselbe, als wenn wir direkt mit den df.values arbeiten würden. Der einzige Unter­schied besteht darin, dass die Ausgabe der Opera­tion wieder ein Ragged Tensor mit denselben Metada­ten­in­for­ma­tionen darüber ist, wo geteilt werden soll. Betrachten wir ein Beispiel, bei dem wir das Matrix­pro­dukt des Ragged-Tensors mit einer Matrix m der Form (2, 5) berechnen. Jede einzelne Serie in unserem Ragged-Tensor hat die Form (k, 2), wobei k der Anzahl der Messungen in der jewei­ligen Serie entspricht. Achten Sie darauf, dass Sie zuerst die Schwimmer anwerfen:

m = tf.random.uniform(shape=[2, 5])
print(m.shape)

(2, 5)

rt = tf.cast(rt, tf.float32)
result = tf.ragged.map_flat_values(tf.matmul, rt, m)
print(*(t.shape for t in result), sep='\n')

(5, 5)
(3, 5)
(4, 5)
(4, 5)

Perfekt! Der resul­tie­rende Ragged-Tensor hat die gleichen Zeilen­splits wie die Eingabe, aber die innere Dimen­sion hat sich durch die Matrix­mul­ti­pli­ka­tion von 2 auf 5 geändert. Wir könnten einige kompli­zier­tere Opera­tionen durch­führen, zum Beispiel wenn m keine 2-dimen­sio­nale Matrix, sondern ein 3-dimen­sio­naler Tensor ist:

m = tf.random.uniform(shape=[2, 5, 4])
print(m.shape)

(2, 5, 4)

rt = tf.cast(rt, tf.float32)
result = tf.ragged.map_flat_values(tf.einsum, "bi, ijk -> bjk", rt, m)
print(*(t.shape for t in result), sep='\n')

(5, 5, 4)
(3, 5, 4)
(4, 5, 4)
(4, 5, 4)

Wie erwartet, entspricht die Chargen-Dimen­sion b der Länge der einzelnen Serien, während die anderen Dimen­sionen von m stammen. Übrigens bezieht sich tf.einsum auf die Einstein-Summen­kon­ven­tion, die äußerst praktisch ist, wenn wir mit höher­di­men­sio­nalen Tensoren arbeiten. Lesen Sie mehr darüber

hier

.

Ein letzter Punkt: Es ist auch sehr einfach, Aggre­ga­tionen über ausge­franste Tensoren durch­zu­führen. Wenn wir zum Beispiel die spalten­weise Summe wissen wollen, können wir dafür Reduk­ti­ons­funk­tionen verwenden:

tf.reduce_sum(rt, axis=1)

<tf.Tensor: shape=(4, 2), dtype=float32, numpy=
array([[27., 21.],
       [13., 33.],
       [15., 29.],
       [20., 26.]], dtype=float32)>

Es gibt noch viele weitere Opera­tionen für ausge­franste Tensoren, die hier aufge­listet sind.

Fazit

Wir haben die Struktur von Tensor­Flow Ragged Tensoren kennen­ge­lernt und wie man grund­le­gende mathe­ma­ti­sche Opera­tionen mit ihnen durch­führt. Sie machen es unnötig, unnatür­liche Vorver­ar­bei­tungs­tech­niken wie Inter­po­la­tion oder Auffüllen anzuwenden. Dies ist beson­ders nützlich für unregel­mä­ßige Zeitrei­hen­da­ten­sätze, obwohl es viele andere Anwen­dungen gibt. Stellen Sie sich einen Daten­satz mit Bildern unter­schied­li­cher Größe vor – Ragged-Tensoren können sogar mit mehreren Ragged-Dimen­sionen umgehen, perfekt dafür.

In einem späteren Beitrag werde ich etwas tiefer in die Arbeit mit Ragged-Tensoren als Einga­be­typen für ein Keras-Modell eintau­chen, indem ich die einzelnen Zeitreihen als Sets behandle und die Aufmerk­sam­keit direkt auf die Ragged-Tensoren lenke. Bleiben Sie dran!

Picture of Torben Windler

Torben Windler

Projektanfrage

Vielen Dank für Ihr Interesse an den Leistungen von m²hycon. Wir freuen uns sehr, von Ihrem Projekt zu erfahren und legen großen Wert darauf, Sie ausführlich zu beraten.

Von Ihnen im Formular eingegebene Daten speichern und verwenden wir ausschließlich zur Bearbeitung Ihrer Anfrage. Ihre Daten werden verschlüsselt übermittelt. Wir verarbeiten Ihre personenbezogenen Daten im Einklang mit unserer Datenschutzerklärung.