深度学习Caffe实战笔记(6)Windows caffe平台用Siamese网络跑自己的数据

终于到了介绍如何使用Siamese网络跑自己的数据了,在网上、论坛上、群里关于用Siamese网络的资料很多,但是实战的资料很少,难道是因为太容易了吗?反正博主查阅了各种地方,几乎没有找到Siamese网络实战的东东,即使有零星关于实战的东西,那也是基于Ubuntu系统,殊不知Ubuntu系统跑caffe可要比Windows简单的多了,所以,就本博主的调研情况来看,这篇博客绝对称的上是Windows平台使用Siamese网络跑自己的数据的第一篇详细资料!这一篇介绍如何利用Windows caffe Siamese网络跑自己的数据,下一篇打算介绍如何调整和搭建网络结构。。。。
开始train。(补充一句,这个博客是博主刚开始学习写的,有很多不足的地方,评论区有一高人指出了几个不足之处,望大家在训练的时候注意那几个地方)

1、准备数据
我们知道Siamese网络是要输入一个图像对,这个图像对是一对image pair,所以首先要把图像数据放到文件夹中,然后建立一个索引文件,索引文件的每一行是两个图像名,代表一个图像对。样式如下:

深度学习Caffe实战笔记(6)Windows caffe平台用Siamese网络跑自己的数据_第1张图片

2、转换数据
转换数据需要转换数据的可执行文件,这个可执行文件哪里得到呢?哎,说到这里都是眼泪了,搞了好长时间,终于找到了相应的代码,不过这个代码有一个大坑,不知道当时写这个代码的人是没有注意,还是故意挖坑呢:

/*
* convertImgToSiamese.cpp
*/

#include 
#include 
#include 
#include 
#include 
#include 
//#include 

#include "boost/scoped_ptr.hpp"
#include "gflags/gflags.h"
#include "glog/logging.h"
#include "leveldb/db.h"

#include "caffe/proto/caffe.pb.h"
#include "caffe/util/io.hpp"
#include "caffe/util/rng.hpp"
//#include "caffe/util/format.hpp"
#include "caffe/util/math_functions.hpp"

#include "opencv2/opencv.hpp"
#include "google/protobuf/text_format.h"
#include "stdint.h"
#include 
#include 
#include 

using namespace caffe;
using std::pair;
using boost::scoped_ptr;
using namespace cv;
using namespace std;

DEFINE_bool(gray, false, "when this option is on, treat images as grayscale ones");
DEFINE_bool(shuffle, false, "randomly shuffle the order of images and their labels");
DEFINE_string(backend, "leveldb", "the backend {lmdb, leveldb} for storing the result");
DEFINE_int32(resize_width, 0, "Width images are resized to");
DEFINE_int32(resize_height, 0, "Height images are resized to");
DEFINE_bool(check_size, false,
    "When this option is on, check that all the datum have the same size");
DEFINE_bool(encoded, false,
    "When this option is on, the encoded image will be save in datum");
DEFINE_string(encode_type, "",
    "Optional: What type should we encode the image as ('png','jpg',...).");
DEFINE_int32(channel, 3, "channel numbers of the image");     //1

//static bool ReadImageToMemory(const string &FileName, const int Height, const int Width, char *Pixels)   //2
static bool ReadImageToMemory(const string &FileName, const int Height, const int Width, char *Pixels)
{
    //read image
    //cv::Mat OriginImage = cv::imread(FileName, cv::IMREAD_GRAYSCALE);
    cv::Mat OriginImage = cv::imread(FileName);     //3. read color image
    CHECK(OriginImage.data) << "Failed to read the image.\n";

    //resize the image
    cv::Mat ResizeImage;
    cv::resize(OriginImage, ResizeImage, cv::Size(Width, Height));
    CHECK(ResizeImage.rows == Height) << "The heighs of Image is no equal to the input height.\n";
    CHECK(ResizeImage.cols == Width) << "The width of Image is no equal to the input width.\n";
    CHECK(ResizeImage.channels() == 3) << "The channel of Image is no equal to three.\n";    //4. should output the warning here

    // LOG(INFO) << "height " << ResizeImage.rows << " ";
    //LOG(INFO) << "weidth " << ResizeImage.cols << " ";
    //LOG(INFO) << "channels " << ResizeImage.channels() << "\n";

    // copy the image data to Pixels
    for (int HeightIndex = 0; HeightIndex < Height; ++HeightIndex)
    {
        const uchar* ptr = ResizeImage.ptr(HeightIndex);
        int img_index = 0;
        for (int WidthIndex = 0; WidthIndex < Width; ++WidthIndex)
        {
            for (int ChannelIndex = 0; ChannelIndex < ResizeImage.channels(); ++ChannelIndex)
            {
                int datum_index = (ChannelIndex * Height + HeightIndex) * Width + WidthIndex;
                *(Pixels + datum_index) = static_cast<char>(ptr[img_index++]);
            }
        }
    }
    return true;
}

int main(int argc, char** argv)
{
    //::google::InitGoogleLogging(argv[0]);
#ifndef GFLAGS_GFLAGS_H_
    namespace gflags = google;
#endif

    gflags::SetUsageMessage("Convert a set of color images to the leveldb\n"
        "format used as input for Caffe.\n"
        "Usage:\n"
        "    convert_imageset [FLAGS] ROOTFOLDER/ LISTFILE DB_NAME\n");
    caffe::GlobalInit(&argc, &argv);

    // 输入参数不足时报错
    if (argc < 4)
    {
        gflags::ShowUsageWithFlagsRestrict(argv[0], "tools/convert_imageset");
        return 1;
    }

    // 读取图像名字和标签
    std::ifstream infile(argv[2]);
    std::vector<std::pair<std::string, std::string> > lines;
    std::string filename;
    std::string pairname;
    int label;
    while (infile >> filename >> pairname)
    {
        lines.push_back(std::make_pair(filename, pairname));
    }

    // 打乱图片顺序
    if (FLAGS_shuffle)
    {
        // randomly shuffle data
        LOG(INFO) << "Shuffling data";
        shuffle(lines.begin(), lines.end());
    }


    LOG(INFO) << "A total of " << lines.size() << " images.";

    // 设置图像的高度和宽度
    int resize_height = std::max<int>(0, FLAGS_resize_height);
    int resize_width = std::max<int>(0, FLAGS_resize_width);
    int channel = std::max<int>(1, FLAGS_channel);     //5. add channel info

    // 打开数据库
    // Open leveldb
    leveldb::DB* db;
    leveldb::Options options;
    options.create_if_missing = true;
    options.error_if_exists = true;
    leveldb::Status status = leveldb::DB::Open(
        options, argv[3], &db);
    CHECK(status.ok()) << "Failed to open leveldb " << argv[3]
        << ". Is it already existing?";

    // 保存到leveldb
    // Storing to leveldb
    std::string root_folder(argv[1]);
    //char* Pixels = new char[2 * resize_height * resize_width];
    char* Pixels = new char[2 * resize_height * resize_width * channel];    //6. add channel
    const int kMaxKeyLength = 10;   //10
    char key[kMaxKeyLength];
    std::string value;

    caffe::Datum datum;
    //datum.set_channels(2);  // one channel for each image in the pair
    datum.set_channels(2 * channel);                //7. 3 channels for each image in the pair
    datum.set_height(resize_height);
    datum.set_width(resize_width);

    //
    // int line_size = (int)(lines.size()/2);
    // std::cout<<"number of lines: "<

    for (int LineIndex = 0; LineIndex < lines.size(); LineIndex++)
    {

        //int PairIndex = LineIndex + line_size;
        // cout<
        // int PairIndex = caffe::caffe_rng_rand() % lines.size();

        char* FirstImagePixel = Pixels;
        // cout<
        ReadImageToMemory(root_folder + lines[LineIndex].first, resize_height, resize_width, FirstImagePixel);  //8. add channel here

        //char *SecondImagePixel = Pixels + resize_width * resize_height;
        char *SecondImagePixel = Pixels + resize_width * resize_height * channel;       //10. add channel
        ReadImageToMemory(root_folder + lines[LineIndex].second, resize_height, resize_width, SecondImagePixel);  //9. add channel here

        // set image pair data
        // datum.set_data(Pixels, 2 * resize_height * resize_width);
        datum.set_data(Pixels, 2 * resize_height * resize_width * channel);     //11. correct

        // set label
        // for training, first 1000 pairs are true; for testing,first 1000 pairs are true
        // if (LineIndex<4000)   
        //train: 912,3000 true pairs, 81,1080 false pairs;
        //test: 35600 true pairs, 33500 false pairs
        if (LineIndex<9123000)
        {
            datum.set_label(1);
        }
        else
        {
            datum.set_label(0);
        }
        // printf("first index: %d, second index: %d, labels: %d \n", lines[LineIndex].second, lines[PairIndex].second, datum.label());

        // serialize datum to string
        datum.SerializeToString(&value);
        int key_value = (int)(LineIndex);
        _snprintf(key, kMaxKeyLength, "%08d", key_value);
        string keystr(key);
        cout << "label: " << datum.label() << ' ' << "key index: " << keystr << endl;
        //sprintf_s(key, kMaxKeyLength, "%08d", LineIndex);     

        db->Put(leveldb::WriteOptions(), std::string(key), value);

    }

    delete db;
    delete[] Pixels;

    return 0;
}

这个cpp文件里有一个大坑,下面再做介绍。在caffe-windows-master\build_cpu_only\同样的方法,把convert_imageset文件复制一下,然后把复制文件夹里的release删除,把剩下的三个文件重命名,然后把复制的这个工程用VS打开,把原来的cpp移除,把上面的代码作为cpp文件导进去,这个步骤的实现方法我就不截图了,前面的博客里都有详细的截图说明,不懂的去看前面的博客吧。

生成之后,在bin目录下会有convert_imagese_siamese.exe可执行文件,这个文件就是用来数据转换的文件,在data文件夹下新建一个文件夹用来存放数据和可执行文件,写一个数据转换脚本文件:
深度学习Caffe实战笔记(6)Windows caffe平台用Siamese网络跑自己的数据_第2张图片
分别转换训练集和验证集。
深度学习Caffe实战笔记(6)Windows caffe平台用Siamese网络跑自己的数据_第3张图片

深度学习Caffe实战笔记(6)Windows caffe平台用Siamese网络跑自己的数据_第4张图片

3、开始训练
有了数据集,就要训练了不是,写一个训练的脚本文件:
深度学习Caffe实战笔记(6)Windows caffe平台用Siamese网络跑自己的数据_第5张图片
Siamese网络还是用的caffe自带的那个Siamese网络协议和超参文件,这两个文件中需要修改的地方在这里我不做介绍了,不懂的同学去看之前的博客吧。
这个时候就出现大坑了。
深度学习Caffe实战笔记(6)Windows caffe平台用Siamese网络跑自己的数据_第6张图片
cv::resize错误,咋回事?起初我还以为是我的opencv有问题,所以在工程里我配置了一下opencv结果,还是不行,老方法,我就知道这个问题去问别人,别人也解答不了,也没人去解答,靠人不如靠自己啊!去一行行分析源码吧。。。。。。不看不知道,一看吓一跳啊,数据转换代码里有一个大坑。

// 设置图像的高度和宽度
    int resize_height = std::max<int>(0, FLAGS_resize_height);
    int resize_width = std::max<int>(0, FLAGS_resize_width);//要把数据转换成resize大小,转到FLAGS_resize_width和FLAGS_resize_height定义,发现是两个宏定义,这就是坑!
......
DEFINE_int32(resize_width, 0, "Width images are resized to");
DEFINE_int32(resize_height, 0, "Height images are resized to");//把要转化的大小设置成了0,要命了啊,怪不得会出现resize错误,都是0,当然会报错!

修改方法:把定义中的0改成你要设置的图像大小即可。
再编译一次,把数据重新转换,可以训练了。

4、一个疑问*(已经解决)*
开始训练以后,我的会caffe会报一个错误,第一个卷积层的卷积核参数共享提示维度不匹配,一个是20*1*5*5,共享层是20*5*5*5,没办法只能把这个参数共享关了,不知道具体原因是什么,参数共享的这两个卷积层明明是一样的 ,如果有知道原因的同学还想请教一下,在此先谢过了。

深度学习Caffe实战笔记(6)Windows caffe平台用Siamese网络跑自己的数据_第7张图片

把第一个参数共享关闭了之后,成功。

后来查看了Glog日志,发现问题在于Slice层数据分解的问题,至于Slice层的讲解在这个博客里介绍的比较详细
http://blog.csdn.net/u012235274/article/details/52438479
解决方法,就是在slice_point: 1去除,使得数据平均分配,就没有维度不匹配的问题了。

写在后面的话:
Siamese网络是两个lenet网络的合并,用ContrastiveLoss损失函数做图像对的训练,但是我觉得Siamese更多的是一种思想,所有挖掘双分支或者更多分支关系的网络都可以称为Siamese网络,而不仅仅局限于lenet。

你可能感兴趣的:(caffe-深度学习)