【问题标题】:How to do weighted softmax output custom op in mxnet?如何在 mxnet 中进行加权 softmax 输出自定义操作?
【发布时间】:2017-11-22 05:37:13
【问题描述】:

我想用加权版本替换 mx.symbol.SoftmaxOutput(根据标签在整个数据集中的频率分配不同的权重)

原始函数运行良好,如下所示:

cls_prob = mx.symbol.SoftmaxOutput(data=data,
                                   label=label,
                                   multi_output=True,
                                   normalization='valid',
                                   use_ignore=True, 
                                   ignore_label=-1,
                                   name='cls_prob')

我写的当前代码如下。代码可以正常运行,但是损失很快爆发到 nan。我正在处理检测问题,当我将我的代码用作 CustomOp 时,RCNNL1 丢失很快就变成了 nan。 另一件事是我必须忽略标签 -1 并且我不知道如何正确地做到这一点。任何帮助将不胜感激。

import mxnet as mx
import numpy as np

class WeightedSoftmaxCrossEntropyLoss(mx.operator.CustomOp):
    def __init__(self, num_class):
        self.num_class = int(num_class)

    def forward(self, is_train, req, in_data, out_data, aux):

        data = in_data[0]
        label = in_data[1]
        pred = mx.nd.SoftmaxOutput(data, label, multi_output=True,
                               normalization='valid', use_ignore=True, ignore_label=-1,
                               name='rcnn_cls_prob')

        self.assign(out_data[0], req[0], pred)

    def backward(self, req, out_grad, in_data, out_data, in_grad, aux):
        cls_weight = np.array([
            0.002852781814876101, 
            0.30715984513157385, 
            1.0932468996115976, 
            1.1598757152765971, 
            0.20739109264009636, 
            1.1984256112776808, 
            0.18746186040248036, 
            2.9009928470737023, 
            0.92140970338602113, 
            1.200317380251021
        ])
        label = in_data[1]
        pred = out_data[0]
        label = label.asnumpy().astype('int32').reshape((-1))
        pred = pred.asnumpy().reshape((pred.shape[0], pred.shape[1], -1)).transpose((0, 2, 1))
        pred = pred.reshape((label.shape[0], -1))

        # Need to ignore label (how)
        out_inds = np.where(label == -1)[0]
        #label = label[keep_inds]
        one_hot = np.zeros((label.shape[0], self.num_class))
        one_hot[np.arange(label.shape[0]), label] = 1
        # gradient
        dx = pred - one_hot
        #dx[out_inds] = 0.0
        weighted_dx = cls_weight * dx / 4
        self.assign(in_grad[0], req[0], weighted_dx)

@mx.operator.register("weighted_softmax_ce_loss")
class WeightedSoftmaxCrossEntropyLossProp(mx.operator.CustomOpProp):
    def __init__(self, num_class):
        super(WeightedSoftmaxCrossEntropyLossProp, self).__init__(need_top_grad=False)
        self.num_class = num_class

    def list_arguments(self):
        return ['data', 'label']

    def list_outputs(self):
        return ['output']

    def infer_shape(self, in_shapes):
        data_shape = in_shapes[0]
        label_shape = (in_shapes[0][0],)
        output_shape = in_shapes[0]
        return [data_shape, label_shape], [output_shape], []

    def create_operator(self, ctx, in_shapes, in_dtypes):
        #  create and return the CustomOp class.
        `enter code here`return WeightedSoftmaxCrossEntropyLoss(self.num_class)

【问题讨论】:

    标签: python mxnet


    【解决方案1】:

    我不确定在这里使用 customop 是否是最好的,因为它可能会很慢。 因为 SoftmaxOuput 在后向传播中计算梯度,所以不方便按照您的意愿乘以损失。 但是,使用符号 API 并不太复杂。我附上了一个玩具示例,希望对您有所帮助。

    import mxnet as mx
    import numpy as np
    import logging
    
    # learn floor function from random numbers in [-1, -1 + num_classes]
    n = 10000
    batch_size = 128
    num_classes = 10
    x = (np.random.random((n,)) * num_classes) - 1
    y = np.floor(x)
    print(x[:2])
    print(y[:2])
    
    # define graph
    data = mx.symbol.Variable('data')
    label = mx.symbol.Variable('label')
    class_weights = mx.symbol.Variable('class_weights')
    fc = mx.sym.FullyConnected(data=data, num_hidden=num_classes)
    fc = mx.sym.Activation(data=fc, act_type='relu')
    proba = mx.sym.FullyConnected(data=fc, num_hidden=num_classes)
    proba = mx.sym.softmax(proba)
    
    # multipy cross entropy loss by weight
    cross_entropy = -mx.sym.pick(proba, label) * mx.sym.pick(class_weights, label)
    
    # mask the loss to zero when label is -1
    mask = mx.sym.broadcast_not_equal(label, mx.sym.ones_like(label) * -1)
    cross_entropy = cross_entropy * mask
    
    # fit module
    class_weights = np.array([np.arange(1, 1 + num_classes)]*n) 
    data_iter = mx.io.NDArrayIter(data={'data': x, 'class_weights': class_weights}, label={'label': y}, batch_size=batch_size)
    mod = mx.mod.Module(
        mx.sym.Group([mx.sym.MakeLoss(cross_entropy, name='ce_loss'), mx.sym.BlockGrad(proba)]),
        data_names=[v.name for v in data_iter.provide_data],
        label_names=[v.name for v in data_iter.provide_label]
    )
    logger = logging.getLogger()
    logger.setLevel(logging.DEBUG)
    mod.bind(data_shapes=data_iter.provide_data, label_shapes=data_iter.provide_label)
    mod.init_params()
    mod.fit(
        data_iter, 
        num_epoch=200, 
        optimizer=mx.optimizer.Adam(learning_rate=0.01, rescale_grad=1.0/batch_size),
        batch_end_callback=mx.callback.Speedometer(batch_size, 200), 
        eval_metric=mx.metric.Loss(name="loss", output_names=["ce_loss_output"]))
    
    # show result, -1 are not predicted correctly as we did not compute their loss
    probas = mod.predict(data_iter)[1].asnumpy()
    print(zip(x, np.argmax(probas, axis=1)))
    

    【讨论】:

    • 谢谢,它对我帮助很大。我会试试看它是否有效。关于您说自定义操作可能很慢的另一个问题?是因为使用 asnumpy() 将内存交换到 cpu 吗?我已经修改了 caffe 中的加权 softmax 熵损失,是否可以将其移植到 mxnet 中?谢谢!
    • 是的。对于 Cafe,我没有尝试移植模型也许你可以尝试 onnx aws.amazon.com/blogs/ai/announcing-the-availability-of-onnx-1-0
    • @geoalgo 感谢您的出色回答!您能否解释一下将 mx.sym.Group([mx.sym.MakeLoss(cross_entropy, name='ce_loss'), mx.sym.BlockGrad(proba)]) 传递给模块如何使其理解第一个元素是损失第二个元素是网络的输出?另外,为什么要屏蔽第二个参数的梯度呢?
    • 分组允许告诉模块在每个前向传递中需要计算几个输出。这允许您稍后在调用 'mod.predict(data_iter)[1]' 时提取概率,它执行前向传递并提取组的第二个元素。 BlockGrad 是必需的,因为不应从概率计算梯度。你需要告诉这个输出不需要在后面做任何事情,否则如果我没记错的话,mxnet 只会最小化你的概率:-)
    • 现在您可能想知道与不需要此分组的 SoftmaxOutput 的区别。这有点令人困惑,但 SoftmaxOutput 计算前向传递中的输出和后向传递中的损失:它使您无需执行此分组技巧,但使获取损失变得更加棘手。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-05-17
    • 2012-08-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多