Introduction
已经有一段时间了,Softmax的问题没有解决。比如分类的时候,看大家似乎都用的SoftmaxOutput作为Loss Op,传入了两个参数(i.e.: data,label),照理说应该输出loss的值;也就是说作为Loss应该输出的是一个标量(bachsize=1的时候),然后我常会去想这样做后期进行predict要花些功夫把原本的预测值找出来。但发现最后做Metric 的时候却把output直接拿来和label对比,比如下面这段:
# python/mxnet/module/module.py update_metric()
# --->
# python/build/lib.linux-x86_64-2.7/mxnet/module/executor_group.py
def update_metric(self, eval_metric, labels):
for texec, islice in zip(self.execs, self.slices):
labels_slice = []
for label, axis in zip(labels, self.label_layouts):
if axis == 0:
# slicing NDArray along axis 0 can avoid copying
labels_slice.append(label[islice])
elif axis > 0:
# pylint: disable=no-member
label_my_slice = nd.slice_axis(label, axis=axis, begin=islice.start,
end=islice.stop).as_in_context(label.context)
# pylint: enable=no-member
labels_slice.append(label_my_slice)
else:
labels_slice.append(label)
eval_metric.update(labels_slice, texec.outputs)
# eval_metric 与 python/mxnet/metric.py 有关
class CustomMetric(EvalMetric): # 随机选一个参考
...
def update(self, labels, preds):
if not self._allow_extra_outputs:
check_label_shapes(labels, preds)
for pred, label in zip(preds, labels):
label = label.asnumpy()
pred = pred.asnumpy()
if pred.shape[1] == 2:
pred = pred[:, 1]
reval = self._feval(label, pred)
if isinstance(reval, tuple):
(sum_metric, num_inst) = reval
self.sum_metric += sum_metric
self.num_inst += num_inst
else:
self.sum_metric += reval
self.num_inst += 1
最近又遇到了也走这条道的LogisticRegressionOutput,终于要打算解决了。
现在解决这个问题的一个优势是,之前从MakeLoss中参到了一些,后面又发现了BlockGrad的功用。
Assumption
从这两个Op可以大致猜到两个Output吧
- data的值做处理,结果作为output;
- 然后又对data与label的distancee进行了计算,目的是得到grad,这就是说真正的标量loss被掩盖了,要的只是由此产生的gradient。
Experiments
做个验证:
import mxnet as mx
import numpy as np
m=mx.nd.ones((3,4))
n=mx.nd.zeros((3,4))
vm=mx.sym.Variable('m')
vn=mx.sym.Variable('n')
out=mx.nd.LogisticRegressionOutput(m,n) # 顺手做下 NDArray 类型
out.asnumpy()
#array([[ 0.7310586, 0.7310586, 0.7310586, 0.7310586],
# [ 0.7310586, 0.7310586, 0.7310586, 0.7310586],
# [ 0.7310586, 0.7310586, 0.7310586, 0.7310586]], dtype=float32) exp(1) / ( 1+exp(1) )
out=mx.sym.LogisticRegressionOutput(data=vm,label=vn)
exec_ = out.bind(mx.cpu(),{'m':m,'n':n})
exec_.forward()
exec_.outputs[0].asnumpy()
#array([[ 0.7310586, 0.7310586, 0.7310586, 0.7310586],
# [ 0.7310586, 0.7310586, 0.7310586, 0.7310586],
# [ 0.7310586, 0.7310586, 0.7310586, 0.7310586]], dtype=float32)
m=mx.nd.ones((3,4))+.3
n=mx.nd.zeros((3,4))+0.2
out=mx.sym.LogisticRegressionOutput(data=vm,label=vn)
exec_ = out.bind(mx.cpu(),{'m':m,'n':n})
exec_.forward()
exec_.outputs[0].asnumpy()
#array([[ 0.78583497, 0.78583497, 0.78583497, 0.78583497],
# [ 0.78583497, 0.78583497, 0.78583497, 0.78583497],
# [ 0.78583497, 0.78583497, 0.78583497, 0.78583497]], dtype=float32) exp(1.3) / (1+exp(1.3))
##########################
# 看看 SoftmaxOutput 这编辑器 orz...
##########################
m=mx.nd.ones((3,4))
n=mx.nd.zeros((3,4))+0.7
out=mx.sym.SoftmaxOutput(data=vm,label=vn)
exec_ = out.bind(mx.cpu(),{'m':m,'n':n})
exec_.forward()
exec_.outputs[0].asnumpy()
#array([[ 0.25, 0.25, 0.25, 0.25],
# [ 0.25, 0.25, 0.25, 0.25],
# [ 0.25, 0.25, 0.25, 0.25]], dtype=float32)
m=mx.nd.ones((3,4))
n=mx.nd.zeros((3,4))+0.2 # 改变 label 的值
out=mx.sym.SoftmaxOutput(data=vm,label=vn)
exec_ = out.bind(mx.cpu(),{'m':m,'n':n})
exec_.forward()
exec_.outputs[0].asnumpy()
#array([[ 0.25, 0.25, 0.25, 0.25],
# [ 0.25, 0.25, 0.25, 0.25],
# [ 0.25, 0.25, 0.25, 0.25]], dtype=float32) 输出没变
m=mx.nd.uniform(0,1,(3,4)) # 随机数
n=mx.nd.zeros((3,4))+0.2
out=mx.sym.SoftmaxOutput(data=vm,label=vn)
exec_ = out.bind(mx.cpu(),{'m':m,'n':n})
exec_.forward()
exec_.outputs[0].asnumpy()
#array([[ 0.24227935, 0.21061647, 0.38156345, 0.16554071],
# [ 0.37370193, 0.1872514 , 0.20918882, 0.22985782],
# [ 0.28395435, 0.28981918, 0.21832471, 0.20790176]], dtype=float32)
就是这样。