L1 Loss 函数

1. Pytorch nn.L1Loss

torch.nn.L1Loss()

torch.nn.L1Loss() 源码

class torch.nn.L1Loss(size_average=None, reduce=None, reduction='elementwise_mean')

L1 Loss 主要用来计算 input x 和 target y 的逐元素间差值的平均绝对值.

L1 Loss 可描述为:

$l(x, y) = L = \lbrace l_1, ..., l_N \rbrace ^T$

其中,$l_n = |x_n - y_n|$,$N$ 为 batch size.

如果 reduce=Truesize_average=True,则 $l(x, y) = mean(L)$;

如果 reduce=Truesize_average=False,则 $l(x, y) = sum(L)$;

input x 和 target y 都可以是包含 n 个元素的任意 shape.

Shape:

  • Input x - (N, * )
  • Target y - (N, * )
  • Output - 标量值, 如果reduction=False,则与 Input x 一致,(N, *)

示例:

>>> loss = nn.L1Loss()
>>> input = torch.randn(3, 5, requires_grad=True)
>>> target = torch.randn(3, 5)
>>> output = loss(input, target)
>>> output.backward()

2. Caffe 添加 L1 Loss 层(转)

原文:在caffe中添加新层 L1 Loss layer - isMarvellous

caffe - l1_loss_layer - Github

2.1 L1 Loss 的计算推导

与欧式距离(L2 Loss)相似,L1 Loss也是两个输入向量直接距离的一种度量.

但L2 Loss的梯度在接近零点的时候梯度值也会接近于0,使学习进程变慢,而L1 Loss的梯度是一个常数,不存在这个问题. L1 Loss 和 L2 Loss 还有一些不同的特点,各有使用的场合. 这里主要关注如何在caffe中实现 L1 Loss.

2.1.1 Forward 计算

L1 Loss 前向计算的是两个输入向量 $\mathbf{x}_1$ 和 $\mathbf{x}_2$ 间的距离,即:

$L = \frac{1}{N} \sum_i^N ||\mathbf{x}_1^{(i)} - \mathbf{x}_2^{(i)}||$

$N$ 为输入样本的数量.

2.1.2 Backward 计算

L1 Loss 自身没有参数,只需计算对输入数据的导数即可:

如果 $x_1^{(i)} > x_2^{(i)}$,则 $\frac{\partial L}{\partial \mathbf{x}_1^{(i)}} = \frac{1}{N}$,$\frac{\partial L}{\partial \mathbf{x}_2^{(i)}} = - \frac{1}{N}$

如果 $x_1^{(i)} < x_2^{(i)}$,则 $\frac{\partial L}{\partial \mathbf{x}_1^{(i)}} = - \frac{1}{N}$,$\frac{\partial L}{\partial \mathbf{x}_2^{(i)}} = \frac{1}{N}$

2.2. Caffe 实现

在caffe中添加层一般需要以下几个步骤:

  • [1] - 在include/caffe/layers/l1_loss_layer.hpp中添加声明
  • [2] - 在 src/caffe/layers/l1_loss_layer.cpp 中进行实现
  • [3] - 如果需要GPU版本,在 src/caffe/layers/l1_loss_layer.cu 中进行实现.
  • [4] - 在 cpp 文件中用 layer_factory.hpp 提供的宏实例化并注册新的层. 假如新的层叫做 L1LossLayer: (好像不需要这一步...)

    INSTANTIATE_CLASS(L1LossLayer);
    REGISTER_LAYER_CLASS(L1Loss);
  • [5] - 在 src/caffe/test/test_l1_loss_layer.cpp 中写测试。
  • [6] - 编译

    make -j
    make test -j
    make runtest GTEST_FILTER='L1LossLayerTest/*'

2.2.1 Forward 计算

前向计算主要是实现Forward_cpu和Forward_gpu两个函数.

CPU 版本:

// src/caffe/layers/l1_loss_layer.cpp 
template <typename Dtype> 
void L1LossLayer<Dtype>::Forward_cpu(const vector<Blob<Dtype>*>& bottom, const vector<Blob<Dtype>*>& top) { 
    int count = bottom[0]->count(); 
    caffe_sub(count, 
              bottom[0]->cpu_data(), 
              bottom[1]->cpu_data(), 
              diff_.mutable_cpu_data()); 
    Dtype loss = caffe_cpu_asum(count, diff_.cpu_data()) / bottom[0]->num(); 
    top[0]->mutable_cpu_data()[0] = loss; 
}

GPU 版本:

template <typename Dtype> 
void L1LossLayer<Dtype>::Forward_gpu(const vector<Blob<Dtype>*>& bottom, const vector<Blob<Dtype>*>& top) { 
    int count = bottom[0]->count(); 
    caffe_gpu_sub(count, 
                  bottom[0]->gpu_data(), 
                  bottom[1]->gpu_data(), 
                  diff_.mutable_gpu_data()); 
    Dtype asum; 
    caffe_gpu_asum(count, diff_.gpu_data(), &asum); // gpu函数,使用gpu_data() 
    Dtype loss = asum / bottom[0]->num(); 
    top[0]->mutable_cpu_data()[0] = loss; // 这里没有使用gpu函数,是普通的cpu运算,所以使用cpu_data() 
}

2.2.2 Backward 计算

反向计算主要是实现Backward_cpu和Backward_gpu两个函数.

CPU 版本:

template <typename Dtype> 
void L1LossLayer<Dtype>::Backward_cpu(const vector<Blob<Dtype>*>& top, const vector<bool>& propagate_down, const vector<Blob<Dtype>*>& bottom) { 
    for (int i = 0; i < 2; ++i) { 
        if (propagate_down[i]) { 
            const Dtype sign = (i == 0) ? 1 : -1; // 对两个输入的反向计算的差异仅是正负号,所以根据输入blob的序号确定一个符号即可 
            const Dtype alpha = sign * top[0]->cpu_diff()[0] / bottom[i]->num(); // alpha = 1/N. top[0]->cpu_diff()[0]是weight_loss 
            // 使用diff_的符号来判断两个输入blob哪个大 
            caffe_cpu_sign(bottom[i]->count(), 
                           diff_.cpu_data(), 
                           bottom[i]->mutable_cpu_diff()); 
            // caffe_cpu_scale(n, alpha, x, y): y = alpha * x 
            caffe_cpu_scale(bottom[i]->count(), 
                            alpha, 
                            bottom[i]->cpu_diff(), 
                            bottom[i]->mutable_cpu_diff()); 
        } 
    } 
}

解释一下上面函数中的 top[0]->cpu_diff()[0].

我们知道,每一层回传的梯度是由上一层传回来的梯度乘以本层的梯度得到的. 但我们现在本来就是loss层了,后面没有层了,那这个top[0]->cpu_diff()[0]是什么呢?注意,这里只是取了top[0]->cpu_diff()的第一个元素,其实它就是我们在prototxt中定义的 loss_weight.

GPU 版本:

template <typename Dtype> 
void L1LossLayer<Dtype>::Backward_gpu(const vector<Blob<Dtype>*>& top, const vector<bool>& propagate_down, const vector<Blob<Dtype>*>& bottom) { 
    for (int i = 0; i < 2; ++i) { 
        if (propagate_down[i]) { 
            const Dtype sign = (i == 0) ? 1 : -1; 
            const Dtype alpha = sign * top[0]->cpu_diff()[0] / bottom[i]->num(); // 这里是cpu运算,使用cpu_diff() 
            caffe_gpu_sign(bottom[i]->count(), 
                           diff_.gpu_data(), 
                           bottom[i]->mutable_gpu_diff()); 
            caffe_gpu_scale(bottom[i]->count(), 
                           alpha, 
                           bottom[i]->gpu_diff(), 
                           bottom[i]->mutable_gpu_diff()); 
        } 
    } 
}

2.3 测试文件

测试文件是用来检查我们编写的层的前向和后向计算是否正确的. 主要分为以下几个部分.

通过 L1LossLayerTest 类中的 TestForward() 成员来检查 loss 是否可以被 loss weight 正确放缩.

void TestForward() { 
    // 不指定loss weight,得到一个loss值loss_weight_1,相当于loss weight为1。
    LayerParameter layer_param; 
    L1LossLayer<Dtype> layer_weight_1(layer_param); 
    layer_weight_1.SetUp(this->blob_bottom_vec_, this->blob_top_vec_); 
    const Dtype loss_weight_1 = layer_weight_1.Forward(this->blob_bottom_vec_, this->blob_top_vec_); 
    // 指定一个特定的loss weight,再得到一个loss值loss_weight_2, 
    // 然后检查loss_weight_2是否被正确地放缩.
    const Dtype kLossWeight = 3.7; 
    layer_param.add_loss_weight(kLossWeight); 
    L1LossLayer<Dtype> layer_weight_2(layer_param); 
    layer_weight_2.SetUp(this->blob_bottom_vec_, this->blob_top_vec_); 
    const Dtype loss_weight_2 = layer_weight_2.Forward(this->blob_bottom_vec_, this->blob_top_vec_); 
    const Dtype kErrorMargin = 1e-5; 
    EXPECT_NEAR(loss_weight_1 * kLossWeight, loss_weight_2, kErrorMargin); 
    // 确保loss不会过小 
    const Dtype kNonTrivialAbsThresh = 1e-1; 
    EXPECT_GE(fabs(loss_weight_1), kNonTrivialAbsThresh); 
}

利用数值方法计算梯度,然后和本层的梯度计算进行比较,检查是否正确. 这是通过调用caffe提供的GradientChecker实现的.

// 通过和数值计算的梯度值对比,检查本层梯度计算是否正确
TYPED_TEST(L1LossLayerTest, TestGradient) { 
    typedef typename TypeParam::Dtype Dtype; 
    LayerParameter layer_param; 
    const Dtype kLossWeight = 3.7; 
    layer_param.add_loss_weight(kLossWeight); 
    L1LossLayer<Dtype> layer(layer_param); 
    layer.SetUp(this->blob_bottom_vec_, this->blob_top_vec_); 
    GradientChecker<Dtype> checker(1e-4, 1e-2, 1701); // 1e-4为梯度数值计算的步长,1e-2为比较的阈值 checker.
    CheckGradientExhaustive(&layer, this->blob_bottom_vec_, this->blob_top_vec_); 
}

3. 其它实现

keras 里的定义:keras/losses.py

from . import backend as K

def mean_absolute_error(y_true, y_pred):
    return K.mean(K.abs(y_pred - y_true), axis=-1)

tensorflow 里的定义 : tf.losses.absolute_difference - Adds an Absolute Difference loss to the training procedure.

tf.losses.absolute_difference(
    labels,
    predictions,
    weights=1.0,
    scope=None,
    loss_collection=tf.GraphKeys.LOSSES,
    reduction=Reduction.SUM_BY_NONZERO_WEIGHTS
)
Last modification:October 13th, 2018 at 03:48 pm

4 comments

  1. 北念

    请问在caffe里面添加l1_loss,需要在caffe.proto增加对应的LayerParameter message吗?

    1. AIHGF
      @北念

      是的,参数需要添加到 caffe.proto 中

      1. 北念
        @AIHGF

        您好,请问您发给我一份修改过的caffe.proto吗?我改了几次,编译不通过,十分感谢!邮箱2456628333@qq.com

        1. AIHGF
          @北念

          出错的问题是什么?

Leave a Comment