5

I am new in caffe framework and I would like to use caffe to implement the training with multi-label. I use two LMDB to save data and labels, respectively. The data LMDB is of dimension Nx1xHxW while the label LMDB is of dimension Nx1x1x3. Labels are float data.

The text file is as follow:

5911 3
train/train_data/4224.bmp        13         0        12
train/train_data/3625.bmp        11         3         7
...                              ...

I use C++ to create LMDB. My main.cpp:

#include <algorithm>
#include <fstream>  // NOLINT(readability/streams)
#include <string>
#include <utility>
#include <vector>
#include <QImage>

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

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

#include <boost/filesystem.hpp>
#include <iomanip>
#include <iostream>  // NOLINT(readability/streams)
#include <string>

#include "google/protobuf/message.h"

#include "caffe/common.hpp"
#include "caffe/proto/caffe.pb.h"
#include "caffe/util/format.hpp"

#ifndef CAFFE_TMP_DIR_RETRIES
#define CAFFE_TMP_DIR_RETRIES 100
#endif

using namespace caffe;  // NOLINT(build/namespaces)
using std::pair;
using boost::scoped_ptr;

const char *dat_lab="/home/mul/caffe-master/examples/2D_3D/new/info/train.data";
string data_db="/home/mul/caffe-master/examples/2D_3D/new/2D_3D_data_leveldb";
string label_db="/home/mul/caffe-master/examples/2D_3D/new/2D_3D_label_leveldb";
string root="/home/mul/caffe-master/examples/2D_3D/new/";
string path;

int main()
{

    //Create data DB
    scoped_ptr<db::DB> dat_db(db::GetDB("leveldb"));
    dat_db->Open(data_db, db::NEW);
    scoped_ptr<db::Transaction> dat_txn(dat_db->NewTransaction());

    //Create label DB
    scoped_ptr<db::DB> lab_db(db::GetDB("leveldb"));
    lab_db->Open(label_db, db::NEW);
    scoped_ptr<db::Transaction> lab_txn(lab_db->NewTransaction());

    //Storing to db
    Datum dat_datum,lab_datum;
    int count=0;

    std::ifstream infile(dat_lab);
    std::string filename;
    const char *dataname;
    int dataNum;
    int labelcount;
    QImage img;
    infile>>dataNum>>labelcount;
    LOG(INFO) << "A total of " << dataNum<< " images.";

    for (int line_id = 0; line_id < dataNum; ++line_id)
    {
        infile>>filename;
        path=root+filename;
        dataname=path.c_str();
        img.load(dataname);

        dat_datum.set_channels(1);
        dat_datum.set_height(img.height());
        dat_datum.set_width(img.width());
        dat_datum.clear_data();
        dat_datum.clear_float_data();

        int datum_channels = dat_datum.channels();
        int datum_height = dat_datum.height();
        int datum_width = dat_datum.width();
        int datum_size = datum_channels * datum_height * datum_width;
        std::string buffer(datum_size, ' ');
        const uchar* ptr = img.bits();
        int img_index = 0;
        for (int h = 0; h < datum_height; ++h)
        {

            for (int w = 0; w < datum_width; ++w)
            {
                for (int c = 0; c < datum_channels; ++c)
                {
                    int datum_index = (c * datum_height + h) * datum_width + w;
                    buffer[datum_index] = static_cast<char>(ptr[img_index++]);
                }
            }
        }
        dat_datum.set_data(buffer);


        lab_datum.set_channels(labelcount);
        lab_datum.set_height(1);
        lab_datum.set_width(1);
        lab_datum.clear_data();
        lab_datum.clear_float_data();
        for(int i=0;i<labelcount;++i)
        {
            float mid;
            infile>>mid;
            lab_datum.add_float_data(mid);
        }

        // sequential
        string key_str = caffe::format_int(line_id, 8);

        // Put in db
        string out;
        CHECK(dat_datum.SerializeToString(&out));
        dat_txn->Put(key_str, out);
        CHECK(lab_datum.SerializeToString(&out));
        lab_txn->Put(key_str, out);

        if (++count % 1000 == 0)
        {
            // Commit db
            dat_txn->Commit();
            dat_txn.reset(dat_db->NewTransaction());
            lab_txn->Commit();
            lab_txn.reset(lab_db->NewTransaction());
            LOG(INFO) << "Processed " << count << " files.";
        }
    }
    // write the last batch
    if (count % 1000 != 0)
    {
        dat_txn->Commit();
        lab_txn->Commit();
        LOG(INFO) << "Processed " << count << " files.";
    }
    return 0;
}

Two LMDB can be created successfully. But when I use caffe to implement the training with two LMDB, the result is always wrong. The loss layer is EUCLIDEAN_LOSS and the loss can not descent. I don't know whether the code which can create two LMDB is wrong. Who can help me ? Thanks whatever.

  • why do you use `dat_datum.set_data(buffer)` to set the image data while using `lab_datum.add_float_data(mid)` for the labels? I do not have experience with `Datum` c++ interface, but is it possible that setting `lab_datum.set_channels(labelcount)` and then using `add_float_data` method change the size of the data and eventually sets the label values at locations `3, 4, 5` instead of `0, 1, 2`? – Shai May 17 '16 at 06:24
  • instead of using levelfb for the labels, why don't you consider using hdf5 input? see [this answer](http://stackoverflow.com/a/31808324/1714410) for an example. – Shai May 17 '16 at 06:28
  • This method has big enough training data so that the memory of the computer can not load all the data. That is why I do not accept hdf5. I have only just solved this problem and thanks for your answer. – Frankie.Chen May 18 '16 at 07:34
  • Can you share your solution? BTW, you do not need to put all the data in a *single* hdf5 file: caffe supports data stored in several hdf5 files. – Shai May 18 '16 at 07:36
  • Firstly, my method is right. But when I use my network which has been trained to do the test , I get the wrong answer because I use different way to open the picture data .Moreover, if convenient, can you tell me how to use several hdf5 files in caffe? Thanks – Frankie.Chen May 20 '16 at 02:33
  • caffe `"HDF5Data"` layer get as `"source"` a **text** file listing filenames of hdf5 files: you can have as many hdf5 files storing your data, listing them in the `"source"` file will make caffe read them one after the other during training. – Shai May 20 '16 at 05:14
  • Do you mean I should list all the hdf5 file names in a text file like "examples/classify/train1.h5 examples/classify/train2.h5 examples/classify/train3.h5" and then carry out the program that the caffe can train them all ? What should I write in model.prototxt? – Frankie.Chen May 20 '16 at 09:15

1 Answers1

2

As a whole, your code above is Ok but you should notice that:

  1. Your .cpp is to create LEVELDB instead of LMDB, of course this is not the reason that caused your problem, either type is ok.
  2. The "label LMDB" generated by your code is of dimension Nx3x1x1 instead of Nx1x1x3(NumberxChannelxWidthxHeight).
  3. In a learning task using minibatch SGD, as far as I know, it is very useful to shuffle your data for training to get more optimized model. I'm not sure whether you noticed this. But at least your cpp didn't shuffle your "train.data".
  4. Most important of all, the reason that caused your problem here most possibly lied in the data layer in your network which read your data and label lmdb/leveldb files, because you assigned the labels to the float data of datum and the DataLayer in caffe in fact does not read the float data(Only if you used your self-defined data layer). So please also upload your prototxt file which defines your network. Thus we can find out what the problem really was.

At last, I added a "MultiTaskData" layer MultiTaskDataLayer to read multi-labels from datum for multi-task training and you can make simple modifications to add it to your caffe and use like this:

    name: "AgeNet"
    layer {
        name: "Age"
        type: "MultiTaskData"
        top: "data"
        top: "age_label"
        top: "gender_label"
        data_param { 
            source: "age_gender_classification_0_60p_train_leveldb"   
            batch_size: 60 
            task_num: 2
            label_dimension: 1
            label_dimension: 1
        }
        transform_param {
            scale: 0.00390625
            crop_size: 60
            mirror: true
        }
        include:{ phase: TRAIN }
    }
    layer { 
        name: "cls_age" 
        type: "InnerProduct"
        bottom: "data"  
        top: "cls_age" 
        param {
            lr_mult: 1
            decay_mult: 1
        }
        param {
            lr_mult: 2
            decay_mult: 0
        }
        inner_product_param {
            num_output: 7
            weight_filler {
            type: "xavier"
            }    
        bias_filler {      
            type: "constant"
            }  
        }
    }
    layer {  
        name: "age_loss"  
        type: "SoftmaxWithLoss"  
        bottom: "cls_age" 
        bottom: "age_label"
        top: "age_loss"
        include:{ phase: TRAIN }
    }
    layer { 
        name: "cls_gender" 
        type: "InnerProduct"
        bottom: "data"  
        top: "cls_gender" 
        param {
            lr_mult: 1
            decay_mult: 1
        }
        param {
            lr_mult: 2
            decay_mult: 0
        }
        inner_product_param {
            num_output: 2
            weight_filler {
                type: "xavier"
            }    
            bias_filler {      
                type: "constant"
            }  
        }
    }
    layer {  
        name: "gender_loss"  
        type: "SoftmaxWithLoss"  
        bottom: "cls_gender" 
        bottom: "gender_label"
        top: "gender_loss"
        include:{ phase: TRAIN }
    }
Dale
  • 1,608
  • 1
  • 9
  • 26