使用 Caffe 内存层不会产生一致和确定性的结果

Posted

技术标签:

【中文标题】使用 Caffe 内存层不会产生一致和确定性的结果【英文标题】:Using Caffe memory layer does NOT produce consistent and deterministic results 【发布时间】:2016-02-18 12:42:35 【问题描述】:

我在 Windows 7 64 位机器上使用适用于 windows 的 Caffe 框架(从 here 下载)。我在 Visual Studio Community 2013 中使用 C++。我使用预训练的 GoogLeNet 模型来提取 loss1-fc 层输出以用作每个图像的特征向量。到目前为止一切顺利。

最近我尝试更改我的软件以用于视频帧。所以我将第一层从 ImageData 层更改为 Memory 层,这样我就可以向 Caffe 发送 OpenCV mats 的向量,而不是把每一帧写入磁盘并将文件列表发送到 caffe 的幼稚方法。

现在,我注意到对于相同的图像,我不会得到相同的结果!使用 ImageData 层时,没有这样的事情。

我使用 CPU(没有 Cudnn,没有 GPU)。

我用于特征提取的函数如下:

void feature_extraction_pipeline_memory(boost::shared_ptr<Net<Dtype>> feature_extraction_net, vector<cv::Mat> imgs, vector<int> labels, float** blobFeats, vector<string> blob_names)

    boost::dynamic_pointer_cast<caffe::MemoryDataLayer<float>>(feature_extraction_net->layers()[0])->AddMatVector(imgs, labels);

    size_t num_mini_batches = imgs.size();
    size_t num_features = blob_names.size();
    int dim_features;
    int batch_size;
    vector<Blob<float>*> input_vec;
    vector<int> image_indices(num_features, 0);

    for (size_t batch_index = 0; batch_index < num_mini_batches; ++batch_index) 
        feature_extraction_net->Forward(input_vec);
        for (size_t i = 0; i < num_features; ++i) 
            const boost::shared_ptr<Blob<Dtype>> feature_blob =     feature_extraction_net->blob_by_name(blob_names[i]);
            batch_size = feature_blob->num();
            dim_features = feature_blob->count() / batch_size;
            const Dtype* feature_blob_data;
            for (size_t n = 0; n < batch_size; ++n) 
                feature_blob_data = feature_blob->cpu_data() + feature_blob->offset(n);
                for (size_t d = 0; d < dim_features; ++d)
                    blobFeats[i][(image_indices[i] * dim_features) + d] = feature_blob_data[d];

                ++image_indices[i];
              // n < batch_size
          // i < num_features
      // batch_index < num_mini_batches

imgs 向量是 mat 的向量。 labels 是一个 int 向量,全部设置为 0。将所有图像添加到向量后,我再次将它们写入磁盘。我检查了,没有问题。所以加载图像时没有任何问题。顺便说一句,我使用 OpenCV 3.1。

GoogLeNet prototxt 文件中的内存层声明如下:

layer 
  name: "data"
  type: "MemoryData"
  top: "data"
  top: "label"
  memory_data_param 
   batch_size: 1
   channels: 3
   height: 227
   width: 227
  
  transform_param 
    crop_size: 227
    mirror: true
    mean_file: "model_googlenet_mem/imagenet_mean.binaryproto"
  
  include:  phase: TEST 

并且是第一层。

我打印每个图像的前 10 个值。请注意,图像 0、1、2、3 与复制的文件完全相同,对于 6、7 和 8 个图像也是如此。

1st run:
0.jpg ::  3.149, 0.000, 0.000, 0.000, 1.586, 0.000, 0.000, 0.755, 0.000, 4.749,
1.jpg ::  2.680, 0.000, 0.000, 0.560, 0.970, 0.000, 0.000, 1.083, 0.000, 4.420,
2.jpg ::  2.680, 0.000, 0.000, 0.560, 0.970, 0.000, 0.000, 1.083, 0.000, 4.420,
3.jpg ::  2.680, 0.000, 0.000, 0.560, 0.970, 0.000, 0.000, 1.083, 0.000, 4.420,
4.jpg ::  3.957, 0.000, 0.000, 0.000, 0.868, 0.000, 0.000, 0.000, 0.000, 6.396,
5.jpg ::  3.179, 0.000, 0.000, 0.000, 0.906, 0.000, 0.000, 0.000, 0.000, 5.508,
6.jpg ::  4.951, 0.000, 0.000, 0.000, 0.000, 0.343, 2.993, 0.000, 0.000, 0.000,
7.jpg ::  4.567, 0.000, 0.000, 0.000, 0.000, 1.251, 2.446, 0.000, 0.000, 0.000,
8.jpg ::  4.951, 0.000, 0.000, 0.000, 0.000, 0.343, 2.993, 0.000, 0.000, 0.000,
9.jpg ::  5.678, 0.000, 0.000, 2.010, 0.000, 1.064, 2.412, 0.000, 0.000, 0.000,

第二次运行:

0.jpg ::  2.680, 0.000, 0.000, 0.560, 0.970, 0.000, 0.000, 1.083, 0.000, 4.420,
1.jpg ::  2.680, 0.000, 0.000, 0.560, 0.970, 0.000, 0.000, 1.083, 0.000, 4.420,
2.jpg ::  3.149, 0.000, 0.000, 0.000, 1.586, 0.000, 0.000, 0.755, 0.000, 4.749,
3.jpg ::  2.680, 0.000, 0.000, 0.560, 0.970, 0.000, 0.000, 1.083, 0.000, 4.420,
4.jpg ::  3.957, 0.000, 0.000, 0.000, 0.868, 0.000, 0.000, 0.000, 0.000, 6.396,
5.jpg ::  2.928, 0.000, 0.000, 0.000, 0.769, 0.000, 0.000, 0.000, 0.000, 5.552,
6.jpg ::  4.567, 0.000, 0.000, 0.000, 0.000, 1.251, 2.446, 0.000, 0.000, 0.000,
7.jpg ::  4.567, 0.000, 0.000, 0.000, 0.000, 1.251, 2.446, 0.000, 0.000, 0.000,
8.jpg ::  4.951, 0.000, 0.000, 0.000, 0.000, 0.343, 2.993, 0.000, 0.000, 0.000,
9.jpg ::  5.678, 0.000, 0.000, 2.010, 0.000, 1.064, 2.412, 0.000, 0.000, 0.000,

相同图像的层输出不同,不同运行的层输出不同!当对 ImageData 层使用相同的过程时,没有这样的问题。此外,该问题也适用于其他层的输出,例如 loss3/classifier。所以,我怀疑 MemoryLayer 实现中可能存在错误。

有没有人注意到这种奇怪的行为?我读到 cudnn 可能会产生不确定的结果,但我在 CPU 上运行我的模型。欢迎对此提出任何想法。

【问题讨论】:

很奇怪。你跑过 caffe 自带的单元测试吗? test_memory_data_layer.cpp 应该非常全面地测试这一层。 opencv 是否有可能以非连续方式将图像存储在矩阵中?我知道 numpy 可以“玩”不同维度的“步幅”,以允许 O(1)“转置”和“重塑”操作,但代价是内存中元素的顺序不连续。您能否确保您输入图层的内存块对于所有运行的图像完全相同相同? @Shai 不知道这些测试。将尝试尽快返回结果。 @Shai 图像垫添加到矢量给 IsContinuous() = true。在将垫子添加到矢量之前,我尝试将垫子克隆到另一个临时垫子以确保连贯地复制。问题依然存在。 不知道这个...但是,OpenCV 3.1 有一个new module 可以与 dnn 以及 Caffe 一起使用。 可能可以提供帮助。 【参考方案1】:

我发现出了什么问题,我会在这里发布答案以帮助其他人。

事实证明,GoogLeNet 要求输入图像的大小为 224x224x3,并且您不能在 TEST 阶段减去平均值。所以通过将.prototxt文件中内存层的定义改成这样:

name: "GoogleNet"
layer 
  name: "data"
  type: "MemoryData"
  top: "data"
  top: "label"
  memory_data_param 
    batch_size: 1
    channels: 3
    height: 224
    width: 224
  

...

我得到了我预期的结果。非常感谢 @Miki 指出他们 dnn 模块上的 OpenCV 教程,这帮助我澄清了这一点。

【讨论】:

以上是关于使用 Caffe 内存层不会产生一致和确定性的结果的主要内容,如果未能解决你的问题,请参考以下文章

Caffe框架GPU与MLU计算结果不一致请问如何调试?

caffe中关于(ReLU层,Dropout层,BatchNorm层,Scale层)输入输出层一致的问题

何时在 Caffe 中使用就地层?

Caffe学习5-用C++自定义层以及可视化结果

Caffe学习5-用C++自定义层以及可视化结果

Cache一致性