Ubuntu下Caffe實現物體分類


參考鏈接:

ubuntu下配置Caffe:https://blog.csdn.net/a_z666666/article/details/72853346

https://www.cnblogs.com/go-better/p/7161006.html

注意防坑點:Python和OpenCV版本都是采用的2.版本。(一開始在github上下載的,Python是3.6.4(由於安裝了anaconda)和OpenCV和4.0版本,Caffe還不支持4.0的OpenCV;后來OpenCV降到3.1.0版本,安裝還是各種坑。索性把Python和OpenCV都降到2.版本(先卸載了anaconda),我采用的Python是Ubuntu16.04自帶的2.7.12,OpenCV是2.4.11版本,一次即配置成功。)

下面開始介紹Caffe實現物體分類的具體流程:

前期准備參考上一篇博客鏈接:http://www.cnblogs.com/wmr95/p/9022415.html ,這里train和test的lmdb准備還是一樣(這里test.txt沒有標簽,可以先默認所有標簽都是1,然后生成lmdb,最后在acc層可以來預測test的lmdb來得到每個的預測結果,可以把預測結果寫入到一個txt中)。

(這里采用的是SENet,順便介紹下配置SENet的過程。)github鏈接:https://github.com/hujie-frank/SENet  (Momenta大佬寫的)

(采用其他網絡如resnet也是一樣的步驟,而且不需要添加新的層。直接配置prototxt即可。)

1. 首先配置好senet的prototxt(SENet-50_train.prototxt,SENet-50_deploy.prototxt,SENet-50_solver.prototxt)

大部分的配置都在example下:

a. SENet-50_solver.prototxt的配置:

net: "examples/signboard/senet/SENet-50_train.prototxt"   
base_lr: 0.0001  
lr_policy: "step"  
gamma: 0.1  
stepsize: 500  
display: 20  
max_iter: 2000  
momentum: 0.9  
weight_decay: 0.0005  
snapshot: 100  
snapshot_prefix: "examples/signboard/model/senet"  
solver_mode: CPU 

net:表示訓練網絡的路徑,solver_mode:表示CPU模式。

b. SENet-50_train.prototxt的配置改了下輸入和輸出:

輸入部分:

name: "SE-ResNet-50"
layer { 
  name: "data"
  type: "Data"
  top: "data"
  top: "label"
  include {
        phase: TRAIN
  }
  data_param {
      source: "examples/signboard/train_lmdb"
      batch_size: 16
      backend: LMDB
  }
}

輸出部分:

layer {
  name: "pool5/7x7_s1"
  type: "Pooling"
  bottom: "conv5_3"
  top: "pool5/7x7_s1"
  pooling_param {
    pool: AVE
    kernel_size: 3          #注意這里kersize改為3,這個可能是根據你輸入圖像的大小,我這里resize成224    
    stride: 1
  }
}
layer {
  name: "fc"         #這里原來name為classifier,報錯。問題鏈接:https://blog.csdn.net/raby_gyl/article/details/72357269
  type: "InnerProduct"
  bottom: "pool5/7x7_s1"
  top: "fc"          #這里同樣將classifier改成fc,下面bottom也要記得修改
  inner_product_param {
    num_output: 100    #這里改成你自己的分類數目
  }
}
layer {
  name: "accuracy"
  type: "Accuracy"
  bottom: "fc"
  bottom: "label"
  top: "accuracy"
  include {
    phase: TEST
  }
}
layer {
  name: "loss"
  type: "SoftmaxWithLoss"
  bottom: "fc"
  bottom: "label"
  top: "loss"
}#

c. SENet-50_deploy.prototxt配置:

輸入部分:

name: "SE-ResNet-50"
layer { 
  name: "data"
  type: "Data"
  top: "data"
  top: "label"
  include {
        phase: TEST
  }
  data_param {
      source: "examples/signboard/senet/test_lmdb"
      batch_size: 20
      backend: LMDB
  }
}

輸出部分:(這里解釋同train.prototxt)

layer {
  name: "pool5/7x7_s1"
  type: "Pooling"
  bottom: "conv5_3"
  top: "pool5/7x7_s1"
  pooling_param {
    pool: AVE
    kernel_size: 3
    stride: 1
  }
}
layer {
  name: "fc"
  type: "InnerProduct"
  bottom: "pool5/7x7_s1"
  top: "fc"
  inner_product_param {
    num_output: 100
  }
}
layer {
  name: "prob"
  type: "Softmax"
  bottom: "fc"
  top: "prob"
}
layer {
  name: "accuracy"
  type: "Accuracy"
  bottom: "fc"
  bottom: "label"
  top: "accuracy"
  include {
    phase: TEST
  }
}

2. 接下來在Caffe中添加並編譯Axpy層,在Caffe中主要添加三個文件:caffe/include/caffe/layers文件夾下:axpy_layer.hpp;caffe/src/caffe/layers文件夾下:axpy_layer.cpp和axpy_layer.cu文件。這三個文件在git上都有:https://github.com/hujie-frank/SENet

添加完之后,在caffe/src/caffe/proto中的caffe.proto中message LayerParameter中為新層添加參數:

這里注意AxpyParameter的ID不能和現有的重復,我這里取148。另外添加一個新的message,如果有參數就寫進去,沒有就空着。

3. 接下來在Caffe的路徑下進行make all就行了。執行命令:make all -j20

4. 接下來,編寫執行腳本進行訓練:

在caffe的路徑下新建一個train_senet.sh,輸入以下內容:

#!usr/bin/env sh

./build/tools/caffe train --solver=./examples/signboard/senet/SENet-50_solver.prototxt \
--weights=./examples/signboard/senet/SE-ResNet-50.caffemodel

執行train_senet.sh文件,便OK啦。

 

最后訓練模型完成之后,要進行test數據的預測,我這里想把每個圖片預測的分類寫到一個txt中。

1. 這里只修改caffe/src/caffe/layers/的accuracy_layer.cpp中的Forward_cpu部分:(因為我們在deploy.prototxt中寫了acc層,預測結果可以從這里獲取)

template <typename Dtype>
void AccuracyLayer<Dtype>::Forward_cpu(const vector<Blob<Dtype>*>& bottom,
    const vector<Blob<Dtype>*>& top) {
  ofstream fout;
  fout.open("/home/wanglaotou/Caffe/caffe-master/out.txt", std::ios::out | std::ios::app);
  Dtype accuracy = 0;//准確率
  const Dtype* bottom_data = bottom[0]->cpu_data();//100*10
  const Dtype* bottom_label = bottom[1]->cpu_data();//100*1,100個圖像對應的類
  const int dim = bottom[0]->count() / outer_num_;//dim=10
  const int num_labels = bottom[0]->shape(label_axis_);
  //全連接后的blob數據都是2維的,label_axis_=1,所以shape就應該是N*D中的D(這塊可以看我上一篇文章)
  //所以bottom[0]->shape(1)=10,也就是類別數
  vector<Dtype> maxval(top_k_+1);
  vector<int> max_id(top_k_+1);
  if (top.size() > 1) {
    caffe_set(nums_buffer_.count(), Dtype(0), nums_buffer_.mutable_cpu_data());
    caffe_set(top[1]->count(), Dtype(0), top[1]->mutable_cpu_data());
  }
  int count = 0;
  //LOG(INFO) << "outer_num_: "<<outer_num_<<"\n";
  //LOG(INFO) << "inner_num_: "<<inner_num_<<"\n";
  for (int i = 0; i < outer_num_; ++i) {//outer_num_=100
    for (int j = 0; j < inner_num_; ++j) {//inner_num_為每個圖像所對應的類別數,所以=1
      const int label_value =
          static_cast<int>(bottom_label[i * inner_num_ + j]);
          //將bottom_label的值賦給label_value,[i * inner_num_ + j]其實就是一個圖像一個類嘛
      if (has_ignore_label_ && label_value == ignore_label_) {
        continue;
      }
      if (top.size() > 1) ++nums_buffer_.mutable_cpu_data()[label_value];
      DCHECK_GE(label_value, 0);
      DCHECK_LT(label_value, num_labels);//label_value(0~9)肯定小於 num_labels(10)
      // Top-k accuracy  // top_k為取前k個最高評分(的預測標簽)
      std::vector<std::pair<Dtype, int> > bottom_data_vector;
      //這個主要就是用於對接下來兩步把測試評分與類別ID掛勾,並對評分排序(這里我其實是比較迷糊的,測試評分指的是bottom_data,難道說經過全連接后,得到的向量就是測試評分? 迷糊中~~~)
      for (int k = 0; k < num_labels; ++k) {
        bottom_data_vector.push_back(std::make_pair(
            bottom_data[i * dim + k * inner_num_ + j], k));
            //把測試評分與類別ID掛勾,
      }
      std::partial_sort(
          bottom_data_vector.begin(), bottom_data_vector.begin() + top_k_,
          bottom_data_vector.end(), std::greater<std::pair<Dtype, int> >());//排序
      // check if true label is in top k predictions
      //LOG(INFO) << "top_k_: "<<top_k_<<"\n";
      for (int k = 0; k < top_k_; k++) {
    
        //if (bottom_data_vector[k].second == label_value) {
        //  ++accuracy;
          // .second指的是類別,如果跟label_value相等,那就說明准確
        //  if (top.size() > 1) ++top[1]->mutable_cpu_data()[label_value];
        //  break;
        //}
    LOG(INFO) << "label_value: "<<bottom_data_vector[k].second + 1<<"\n";
    fout <<"label_value: "<<bottom_data_vector[k].second + 1<<"\n";
          fout.flush();
      }
      
      ++count;
    }
    
  }

  LOG(INFO) << "Accuracy: " << accuracy;
  top[0]->mutable_cpu_data()[0] = accuracy / count;
  if (top.size() > 1) {
    for (int i = 0; i < top[1]->count(); ++i) {
      top[1]->mutable_cpu_data()[i] =
          nums_buffer_.cpu_data()[i] == 0 ? 0
          : top[1]->cpu_data()[i] / nums_buffer_.cpu_data()[i];
    }
  }
  fout.close();
  // Accuracy layer should not be used as a loss function.
}

2. 編寫test.sh腳本文件進行測試:

#!usr/bin/env sh

./build/tools/caffe test --iterations=50 --model=./examples/signboard/senet/SENet-50_deploy.prototxt \
--weights=./examples/signboard/model/senet_iter_2000.caffemodel

3. 執行腳本完后會得到一個out.txt文件,內容如下:

到此,大功告成~


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM