[PYTHON] Ich habe das Argument class_weight von Chainers Funktion softmax_cross_entropy untersucht.

Einführung

Es gibt zwei Möglichkeiten, um die Klassenverzerrung innerhalb eines Datensatzes bei der Trainingsklassifizierung zu verringern.

Dieses Mal habe ich zusammengefasst, woran ich interessiert war, als ich die zweite Methode mit Chainer angewendet habe.

Motivation

Insbesondere hat die Funktion softmax_cross_entropy ein Argument namens class_weight. Indem Sie dies steuern, können Sie die Trainingsstärke für jede Klasse ändern. Im Fall einer Zwei-Klassen-Klassifizierung bedeutet dies beispielsweise, dass das Lernen der Klasse '1' doppelt so stark durchgeführt werden kann wie das Lernen der Klasse '0'. Was bedeutet es dann, wenn Sie doppelt so viel Gewicht geben, doppelt so stark zu lernen? Ich habe mich gefragt, also habe ich nachgeschlagen.

Ausführungsumgebung

Lassen Sie uns die Auswirkungen auf den Verlust sehen

Lesen Sie zuerst die [Dokumentation] von Chainer (! Https://docs.chainer.org/en/stable/reference/generated/chainer.functions.softmax_cross_entropy.html#chainer.functions.softmax_cross_entropy).

chainer.functions.softmax_cross_entropy(x, t, normalize=True, cache_score=True, class_weight=None, ignore_label=-1, reduce='mean') ... · Class_weight (ndarray oder ndarray) - Ein Array, das konstante Gewichte enthält, die mit den Verlustwerten zusammen mit der zweiten Dimension multipliziert werden. Die Form dieses Arrays sollte "(x.shape [1],)" sein nicht None, jedes Klassengewicht class_weight [i] wird tatsächlich mity [:, i]multipliziert, das ist die entsprechende Log-Softmax-Ausgabe von x und hat die gleiche Form wie x, bevor das berechnet wird tatsächlicher Verlustwert.

Mit anderen Worten, es scheint, dass $ log (Softmax (x)) $, das vor der Berechnung des Verlusts berechnet wurde, mit der Form von x multipliziert wird. Das war's.

Nun wollen wir sehen, in welchem Stadium das class_weight tatsächlich multipliziert wird. Schauen wir uns zunächst die Vorwärtsfunktion in softmax_cross_entropy.py an.

chainer/functions/loss/softmax_cross_entropy.py


    def forward_cpu(self, inputs):
        x, t = inputs
        if chainer.is_debug():
            self._check_input_values(x, t)

        log_y = log_softmax._log_softmax(x, self.use_cudnn)
        if self.cache_score:
            self.y = numpy.exp(log_y)
        if self.class_weight is not None:
            shape = [1 if d != 1 else -1 for d in six.moves.range(x.ndim)]
            log_y *= _broadcast_to(self.class_weight.reshape(shape), x.shape)
        log_yd = numpy.rollaxis(log_y, 1)
        log_yd = log_yd.reshape(len(log_yd), -1)
        log_p = log_yd[numpy.maximum(t.ravel(), 0), numpy.arange(t.size)]

        log_p *= (t.ravel() != self.ignore_label)
        if self.reduce == 'mean':
            # deal with the case where the SoftmaxCrossEntropy is
            # unpickled from the old version
            if self.normalize:
                count = (t != self.ignore_label).sum()
            else:
                count = len(x)
            self._coeff = 1.0 / max(count, 1)

            y = log_p.sum(keepdims=True) * (-self._coeff)
            return y.reshape(()),
        else:
            return -log_p.reshape(t.shape),

Es ist zu beachten, dass in Zeile 11 class_weight für das Berechnungsergebnis von log (Softmax (x)) gesendet wird.

log_y *= _broadcast_to(self.class_weight.reshape(shape), x.shape)


 Das heißt, die Berechnung des Kreuzentropiefehlers $ L = - \ sum t_ {k} \ log {(Softmax (y_ {k}))} $ vor dem Hinzufügen zu $ \ log {(Softmax (y_ {k}))} $ Wird mit multipliziert.
 Zu diesem Zeitpunkt gibt $ k $ die Anzahl der Klassen an.
 Mit anderen Worten lautet die Formel $ L = - \ sum t_ {k} ClassWeight_ {k} \ log {(Softmax (y_ {k}))} $.
 Es ist wie dokumentiert.

 Um dies zu sehen, experimentieren wir interaktiv.

import numpy as np import chainer x = np.array([[1, 0]]).astype(np.float32) t = np.array([1]).astype(np.int32) #Klasse'1'Mit doppeltem Gewicht trainieren cw = np.array([1, 2]).astype(np.float32) sce_nonweight = chainer.functions.loss.softmax_cross_entropy.SoftmaxCrossEntropy() sce_withweight = chainer.functions.loss.softmax_cross_entropy.SoftmaxCrossEntropy(class_weight=cw) loss_nonweight = sce_nonweight(x, t) loss_withweight = sce_withweight(x, t) loss_nonweight.data array(1.31326162815094, dtype=float32)

loss_withweight.data array(2.62652325630188, dtype=float32)


 Sie können sehen, dass der Verlustwert verdoppelt wird.

 Daher haben wir bisher gelernt, dass sich die Gewichtung in class_weight so wie sie ist im Ausgabeverlustwert widerspiegelt.

# Lassen Sie uns die Auswirkungen auf die Rückausbreitung sehen

 Welche Auswirkungen hat Lernen oder Backpropagation?
 Was ich hier überprüfen möchte, ist der Wert von $ y-t $, der von softmax_cross_entropy zurückpropagiert wird.
 Ich vermute, dass der Wert von $ y-t $ so wie er ist mit dem Gewicht multipliziert wird, aber lassen Sie uns vorerst die Implementierung von Chainer überprüfen.


#### **`chainer/functions/loss/softmax_cross_entropy.py`**
```python

    def backward_cpu(self, inputs, grad_outputs):
        x, t = inputs
        gloss = grad_outputs[0]
        if hasattr(self, 'y'):
            y = self.y.copy()
        else:
            y = log_softmax._log_softmax(x, self.use_cudnn)
            numpy.exp(y, out=y)
        if y.ndim == 2:
            gx = y
            gx[numpy.arange(len(t)), numpy.maximum(t, 0)] -= 1
            if self.class_weight is not None:
                shape = [1 if d != 1 else -1 for d in six.moves.range(x.ndim)]
                c = _broadcast_to(self.class_weight.reshape(shape), x.shape)
                c = c[numpy.arange(len(t)), numpy.maximum(t, 0)]
                gx *= _broadcast_to(numpy.expand_dims(c, 1), gx.shape)
            gx *= (t != self.ignore_label).reshape((len(t), 1))
        else:
            # in the case where y.ndim is higher than 2,
            # we think that a current implementation is inefficient
            # because it yields two provisional arrays for indexing.
            n_unit = t.size // len(t)
            gx = y.reshape(y.shape[0], y.shape[1], -1)
            fst_index = numpy.arange(t.size) // n_unit
            trd_index = numpy.arange(t.size) % n_unit
            gx[fst_index, numpy.maximum(t.ravel(), 0), trd_index] -= 1
            if self.class_weight is not None:
                shape = [1 if d != 1 else -1 for d in six.moves.range(x.ndim)]
                c = _broadcast_to(self.class_weight.reshape(shape), x.shape)
                c = c.reshape(gx.shape)
                c = c[fst_index, numpy.maximum(t.ravel(), 0), trd_index]
                c = c.reshape(y.shape[0], 1, -1)
                gx *= _broadcast_to(c, gx.shape)
            gx *= (t != self.ignore_label).reshape((len(t), 1, -1))
            gx = gx.reshape(y.shape)
        if self.reduce == 'mean':
            gx *= gloss * self._coeff
        else:
            gx *= gloss[:, None]
        return gx, None

Hier berechnen die Zeilen 9 bis 17 dieses Mal $ y-t $, und Sie können sehen, dass class_weight wie erwartet an den Backpropagation-Wert gesendet wird.

Sie können auch sehen, dass der Glanz am Ende multipliziert wird. Und was Glanz ist, ist wie grad_output, das ein Mitglied der Variablenklasse grad ist. Sie können den Grad des Anfangswertes überprüfen, also lassen Sie es uns sehen.

>> loss_nonweight.backward()
>> aloss_nonweight.backward()
>> loss_nonweight.grad
array(1.0, dtype=float32)
>> loss_withweight.grad
array(1.0, dtype=float32)

Natürlich hatte ich sonst Probleme, aber der erste Backpropagation-Wert ist $ \ frac {\ partielles L} {\ partielles L} = 1 $. Dieses Ergebnis ist also nicht falsch.

Außerdem gibt es vorerst einen Parameter _coeff, der zusätzlich zum Glanz multipliziert wird. In diesem Fall ist dies jedoch nur die Umkehrung der Stapelgröße beim Lernen des Stapels (dh des Elements für die Mittelwertbildung) Ist 1. Übrigens wird bei der Berechnung des Verlusts auch _coeff multipliziert.

Es scheint, dass das durch class_weight definierte Gewicht wie erwartet in direktem Zusammenhang mit dem Lernen steht. Dann ist es ein wenig gezwungen, aber es ist ein Experiment.

>> sce_nonweight.backward_cpu((x,t),[loss_nonweight.grad])
(array([[ 0.7310586, -0.7310586]], dtype=float32), None)
>> sce_withweight.backward_cpu((x,t),[loss_withweight.grad])
(array([[ 1.4621172, -1.4621172]], dtype=float32), None)

Der Wert der Backpropagation war "Array ([[0.7310586, 0.26894143]], dtype = float32)", wenn "chainer.functions.softmax (x) .data" betrachtet wird An können Sie sehen, dass es $ y - t $ ist. Und es wurde bestätigt, dass der Wert der Rückausbreitung auch richtig verdoppelt wurde. Herzliche Glückwünsche.

Zusammenfassend stellt sich heraus, dass sich das Gewicht von class_weight proportional zum Wert von backpropagation widerspiegelt.

Fazit

Das in classMainer implementierte Argument class_weigth in softmax_cross_entropy lautet

Ich habe herausgefunden, dass.

Ich weiß nicht, wer es bekommen wird, aber ob es hilft. Ich würde es begrüßen, wenn Sie mich wissen lassen könnten, wenn etwas daran falsch ist.

Recommended Posts

Ich habe das Argument class_weight von Chainers Funktion softmax_cross_entropy untersucht.
Ich habe die Pivot-Table-Funktion von Pandas ausprobiert
Korrigieren Sie die Argumente der in map verwendeten Funktion
Ich habe den Gerätebaum untersucht
Ich habe ein wenig versucht, das Verhalten der Zip-Funktion
Ich habe versucht, das lokale Minimum der Goldstein-Preis-Funktion zu bekämpfen
Ich möchte den Namen der ausgeführten Funktion / Methode erhalten
Wenn Sie eine Liste mit dem Standardargument der Funktion angeben ...
Python: Ich möchte die Verarbeitungszeit einer Funktion genau messen
Ich habe eine Funktion erstellt, um die Bewegung eines zweidimensionalen Arrays (Python) zu sehen.
Ich habe den Inhalt des Docker-Volumes überprüft
Ich habe den asynchronen Server von Django 3.0 ausprobiert
Ich habe die Optionen von copyMakeBorder von OpenCV überprüft
Die Ordnerstruktur von Flask ist zusammengefasst
[Python3] Schreiben Sie das Codeobjekt der Funktion neu
Ich kannte die Grundlagen von Python nicht
Über die Argumente der Setup-Funktion von PyCaret
Die Python-Projektvorlage, an die ich denke.
Machen Sie den Standardwert des Arguments unveränderlich
[Python] Ich habe versucht, den Funktionsnamen durch den Funktionsnamen zu ersetzen
Ich habe die Implementierung von range gelesen (Objects / rangeobject.c)
Besiege die Wahrscheinlichkeitsdichtefunktion der Normalverteilung
Holen Sie sich den Aufrufer einer Funktion in Python
Ich habe die Liste der Tastenkombinationen von Jupyter überprüft
Ich habe versucht, die Trapezform des Bildes zu korrigieren
Probieren Sie Progate Free Edition [Python I]
Ich habe die Sitzungsaufbewahrungsdauer von Django überprüft
Ich habe die Verarbeitungsgeschwindigkeit der numpy eindimensionalisierung überprüft
Ich habe einige der neuen Funktionen von Python 3.8 touched angesprochen
Ich habe versucht, die inverse Gammafunktion in Python zu implementieren
Ich habe die Varianten von UKR gelesen und implementiert
Ich möchte das Erscheinungsbild von zabbix anpassen
Ich habe versucht, den Bildfilter von OpenCV zu verwenden
Ich möchte die Aktivierungsfunktion Mish verwenden
Ich habe versucht, die Texte von Hinatazaka 46 zu vektorisieren!
Hinweis: Bedeutung der Angabe von nur * (Sternchen) als Argument in der Funktionsdefinition von Python