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.
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.
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 Klassengewichtclass_weight [i]
wird tatsächlich mity [:, i]
multipliziert, das ist die entsprechende Log-Softmax-Ausgabe vonx
und hat die gleiche Form wiex
, 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.
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