【问题标题】:A weighted version of SoftmaxOutput in mxnetmxnet 中 SoftmaxOutput 的加权版本
【发布时间】:2017-12-31 17:40:56
【问题描述】:

我正在 mxnet 中训练一个用于分类的神经网络,并且我想对某些类赋予更多的权重。所以基本上,我想使用mxnet.symbol.SoftmaxOutput 的加权版本。作为一个玩具示例,我使用了this mnist code 的简化版本。

这是定义网络的代码

import mxnet as mx
mnist = mx.test_utils.get_mnist()

batch_size = 100
train_iter = mx.io.NDArrayIter(mnist['train_data'], mnist['train_label'], batch_size, shuffle=True)
val_iter = mx.io.NDArrayIter(mnist['test_data'], mnist['test_label'], batch_size)

data = mx.sym.var('data')
data = mx.sym.flatten(data=data)


fc1  = mx.sym.FullyConnected(data=data, num_hidden=15)
act1 = mx.sym.Activation(data=fc1, act_type="relu")

# MNIST has 10 classes
fc2  = mx.sym.FullyConnected(data=act1, num_hidden=10)
# Softmax with cross entropy loss
mlp  = mx.sym.SoftmaxOutput(data=fc2, name='softmax')

这是训练网络的代码:

import logging
logging.getLogger().setLevel(logging.DEBUG)  # logging to stdout
# create a trainable module on CPU
mlp_model = mx.mod.Module(symbol=mlp, context=mx.cpu())
mlp_model.fit(train_iter,  # train data
              eval_data=val_iter,  # validation data
              optimizer='sgd',  # use SGD to train
              optimizer_params={'learning_rate':0.1},  # use fixed learning rate
              eval_metric='acc',  # report accuracy during training
              batch_end_callback = mx.callback.Speedometer(batch_size, 100), # output progress for each 100 data batches
              num_epoch=1)  # train for at most 10 dataset passes

现在假设我想给数字 0 赋予更多的权重。我在mlp 的定义正下方添加了以下几行:

weight=[[5,1,1,1,1,1,1,1,1,1]]
weight_sym  = mx.symbol.Variable(name="weight",
     init=mx.init.Constant(weight), attr={}, shape=(1,10))

mlp=mx.sym.broadcast_mul(mlp,weight_sym)

然后当我尝试训练时,我得到了错误:

TypeError: NDArray 不支持使用 [[5, 1, 1, 1, 1, 1, 1, 1, 1, 1]] 类型

这样做的正确方法是什么?

【问题讨论】:

标签: python machine-learning neural-network deep-learning mxnet


【解决方案1】:

在 MxNet 中没有开箱即用的加权 softmax 实现,但是为 MxNet 做出了很大贡献的同一个人开发了一个示例,该示例将加权 softmax 用于 2 个类(基本上,它是加权逻辑回归)。你可以在这里查看他们的实现 - https://github.com/apache/incubator-mxnet/blob/df558290930f9f3ed6941c306b5cc650505f7481/example/sparse/linear_classification/weighted_softmax_ce.py

该示例引入了一个新的自定义操作,它采用一个值 - 正类的权重。我已经根据您的用例对他们的代码进行了调整 - 传递一个权重数组而不是单个值。不幸的是,除了字符串之外,您不能将任何内容传递给自定义操作的构造函数。这就是为什么 class_weights_value 被定义为字符串并且在代码中转换为 nd.array 的原因。

这段代码不会像原来的 Softmax 那样快,它是用 C 语言以高度优化的方式编写的:

import mxnet as mx


class WeightedSoftmaxCrossEntropyLoss(mx.operator.CustomOp):
    """ softmax cross entropy weighted loss, where the loss is adjusted by \
(class_weight) / sum_of_all_weights)

    """

    def __init__(self, class_weights):
        # parse initial weights from a string to separate items
        self.class_weights = mx.nd.array([float(x) for x in class_weights.split(',')])
        # scale weights, so they would add up to 1
        self.class_scales = self.class_weights / len(self.class_weights)

    def forward(self, is_train, req, in_data, out_data, aux):
        """Implements forward computation.

        is_train : bool, whether forwarding for training or testing.
        req : list of {'null', 'write', 'inplace', 'add'}, how to assign to out_data. 'null' means skip assignment, etc.
        in_data : list of NDArray, input data.
        out_data : list of NDArray, pre-allocated output buffers.
        aux : list of NDArray, mutable auxiliary states. Usually not used.
        """
        data = in_data[0]
        label = in_data[1]
        pred = mx.nd.SoftmaxOutput(data, label)
        self.assign(out_data[0], req[0], pred)

    def backward(self, req, out_grad, in_data, out_data, in_grad, aux):
        """Implements backward computation

        req : list of {'null', 'write', 'inplace', 'add'}, how to assign to in_grad
        out_grad : list of NDArray, gradient w.r.t. output data.
        in_grad : list of NDArray, gradient w.r.t. input data. This is the output buffer.
        """
        label = in_data[1]
        pred = out_data[0]
        # move to GPU context if needed
        class_scales = self.class_scales.as_in_context(label.context)
        dx = pred - mx.nd.one_hot(label, len(class_scales))
        # find a weight based on a label of an example
        scale_factor = (class_scales[label]).reshape((pred.shape[0],1))
        #apply scaling
        rescaled_dx = scale_factor * dx
        self.assign(in_grad[0], req[0], rescaled_dx)


@mx.operator.register("weighted_softmax_ce_loss")
class WeightedSoftmaxCrossEntropyLossProp(mx.operator.CustomOpProp):
    def __init__(self, class_weights):
        super(WeightedSoftmaxCrossEntropyLossProp, self).__init__(True)
        self.class_weights = class_weights

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

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

    def infer_shape(self, in_shapes):
        """Calculate output shapes from input shapes. This can be
        omited if all your inputs and outputs have the same shape.

        in_shapes : list of shape. Shape is described by a tuple of int.
        """
        data_shape = in_shapes[0]
        output_shape = data_shape
        # return 3 lists representing inputs shapes, outputs shapes, and aux data shapes.
        return (in_shapes), (output_shape,), ()

    def create_operator(self, ctx, in_shapes, in_dtypes):
        #  create and return the CustomOp class.
        return WeightedSoftmaxCrossEntropyLoss(self.class_weights)

要使用它,您需要创建一个使用此自定义操作而不是常规 SoftmaxOutput 的模型。这是模型代码:

import logging
import mxnet as mx
# it is important to import this file, even it is not directly used in the code below
import WeightedSoftmaxCrossEntropyLoss

batch_size = 100
# define a string of weights - one weight for each class, starting from the '0' class
class_weights_values = '5,1,1,1,1,1,1,1,1,1'

mnist = mx.test_utils.get_mnist()
train_iter = mx.io.NDArrayIter(mnist['train_data'], mnist['train_label'], batch_size, shuffle=True)
val_iter = mx.io.NDArrayIter(mnist['test_data'], mnist['test_label'], batch_size)

data = mx.sym.var('data')
label_names = mx.symbol.Variable("softmax_label")

data = mx.sym.flatten(data=data)

fc1 = mx.sym.FullyConnected(data=data, num_hidden=15)
act1 = mx.sym.Activation(data=fc1, act_type="relu")
fc2 = mx.sym.FullyConnected(data=act1, num_hidden=10) # MNIST has 10 classes
# Weighted Softmax
weighted_softmax = mx.sym.Custom(fc2, label_names, op_type='weighted_softmax_ce_loss', class_weights=class_weights_values, name='out')
# Making sure it is used as a loss funciton
mlp = mx.sym.MakeLoss(weighted_softmax)

logging.getLogger().setLevel(logging.DEBUG)  # logging to stdout
# create a trainable module on CPU
mlp_model = mx.mod.Module(symbol=mlp, context=mx.cpu(), label_names=['softmax_label'])
mlp_model.fit(train_iter,  # train data
              eval_data=val_iter,  # validation data
              optimizer='sgd',  # use SGD to train
              optimizer_params={'learning_rate':0.1},  # use fixed learning rate
              eval_metric='acc',  # report accuracy during training
              batch_end_callback = mx.callback.Speedometer(batch_size, 100), # output progress for each 100 data batches
              num_epoch=1)  # train for at most 10 dataset passes

我希望这对你有用。我没有尝试使用培训的结果,也许它没有像你期望的那样工作。因此,请随时根据需要调整此代码。

【讨论】:

    【解决方案2】:

    可能与问题正交;但是如果你将 softmax 与交叉熵损失一起使用,那么添加样本权重就很简单了,请参阅:http://d2p0dj726sqh0a.cloudfront.net/api/gluon/_autogen/mxnet.gluon.loss.SoftmaxCrossEntropyLoss.html

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2022-01-19
      • 2020-07-07
      相关资源
      最近更新 更多