Il existe deux façons principales de réduire le biais de classe dans un ensemble de données lors de l'apprentissage de la classification.
Cette fois, j'ai résumé ce qui m'intéressait lors de l'adoption de la deuxième méthode utilisant Chainer.
Plus précisément, la fonction softmax_cross_entropy a un argument appelé class_weight, mais en contrôlant cela, vous pouvez modifier la force d'entraînement pour chaque classe. Par exemple, dans le cas d'une classification à deux classes, cela signifie que l'apprentissage de la classe «1» peut être effectué deux fois plus fortement que l'apprentissage de la classe «0». Ensuite, quand vous lui donnez le double de poids, que signifie apprendre deux fois plus fort? Je me demandais, alors j'ai cherché.
Tout d'abord, lisez la [Documentation] de 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 ou ndarray) - Un tableau qui contient des poids constants qui seront multipliés par les valeurs de perte avec la deuxième dimension. La forme de ce tableau doit être
(x.shape [1],)
. Si c'est pasNone
, chaque poids de classeclass_weight [i] ʻest en fait multiplié par
y [:, i]qui est la sortie log-softmax correspondante de
xet a la même forme que
x` avant de calculer le valeur réelle de la perte.
En d'autres termes, il semble que $ log (Softmax (x)) $ calculé avant le calcul de la perte soit multiplié selon la forme de x. Je vois.
Voyons maintenant à quel stade le class_weight est réellement multiplié. Jetons d'abord un coup d'œil à la fonction forward dans softmax_cross_entropy.py.
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),
Il est à noter qu'à la ligne 11, class_weight est diffusé pour le résultat du calcul de log (Softmax (x)).
log_y *= _broadcast_to(self.class_weight.reshape(shape), x.shape)
Autrement dit, le calcul de l'erreur d'entropie croisée $ L = - \ sum t_ {k} \ log {(Softmax (y_ {k}))} $ avant l'ajout à $ \ log {(Softmax (y_ {k}))} $ Sera multiplié par.
A ce moment, $ k $ indique le nombre de classes.
En d'autres termes, la formule est $ L = - \ sum t_ {k} ClassWeight_ {k} \ log {(Softmax (y_ {k}))} $.
C'est comme documenté.
Pour voir cela, expérimentons de manière interactive.
import numpy as np import chainer x = np.array([[1, 0]]).astype(np.float32) t = np.array([1]).astype(np.int32) #classe'1'Être entraîné avec le double du poids 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)
Vous pouvez voir que la valeur de la perte est doublée.
Par conséquent, ce que nous avons appris jusqu'à présent, c'est que la pondération dans class_weight est susceptible d'être reflétée dans la valeur de perte en sortie telle quelle.
# Voyons l'effet sur la propagation arrière
Alors, quel est l'impact de l'apprentissage ou de la rétropropagation?
Ce que je veux vérifier ici, c'est quelle est la valeur de $ y-t $, qui est la valeur rétropropagée de softmax_cross_entropy.
Je suppose que la valeur de $ y-t $ est multipliée par le poids tel quel, mais vérifions l'implémentation de chainer pour le moment.
#### **`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
Ici, les lignes 9 à 17 calculent $ y-t $ cette fois, et vous pouvez voir que class_weight est diffusé à la valeur de rétropropagation comme prévu.
Vous pouvez également voir que la brillance est multipliée à la fin. Et ce qu'est gloss est comme grad_output, qui est membre de la classe Variable, grad. Vous pouvez vérifier le grad de la valeur initiale, voyons-le donc.
>> loss_nonweight.backward()
>> aloss_nonweight.backward()
>> loss_nonweight.grad
array(1.0, dtype=float32)
>> loss_withweight.grad
array(1.0, dtype=float32)
Bien sûr, j'avais des problèmes autrement, mais la première valeur de rétropropagation est $ \ frac {\ partial L} {\ partial L} = 1 $. Donc, ce résultat ne semble pas être faux.
De plus, pour le moment, il existe un paramètre _coeff qui est multiplié en plus de la brillance, mais ce n'est que l'inverse de batchsize (c'est-à-dire le membre pour la moyenne) pendant l'apprentissage par lots, et dans ce cas Est 1. À propos, lors du calcul de la perte, _coeff est également multiplié.
Il semble que le poids défini par class_weight soit directement lié à l'apprentissage attendu. Ensuite, c'est un peu forcé, mais c'est une expérience.
>> 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)
La valeur de la rétropropagation était array ([[0.7310586, 0.26894143]], dtype = float32) `` `en regardant` `` chainer.functions.softmax (x) .data
De, vous pouvez voir que c'est $ y --t $.
Et il a été confirmé que la valeur de la rétro-propagation a également été doublée correctement. Toutes nos félicitations.
En conclusion, nous avons constaté que le poids de class_weight est reflété proportionnellement à la valeur de la rétropropagation.
L'argument class_weigth dans softmax_cross_entropy implémenté dans Chainer est
J'ai découvert que.
Je ne sais pas qui l'obtiendra, mais si cela aide. J'apprécierais que vous me fassiez savoir si quelque chose ne va pas.
Recommended Posts