前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >我的第一个caffe C++程序

我的第一个caffe C++程序

作者头像
云水木石
发布2019-07-01 14:45:22
9240
发布2019-07-01 14:45:22
举报

最近一段时间一直在考虑为浏览器添加AI过滤裸露图片的功能,但目前大多数AI相关的教程都是用python语言。如果是训练模型,使用python语言无疑是最合适的,但现在的需求是嵌入到产品中,必须要使用C++,为此特意比较了现在比较流行的深度学习框架,发现caffe比较契合需求。caffe本身使用C++语言开发,提供了丰富的C++ API,也提供了很多C++的示例。值得一提的是,雅虎提供了开源的色情图片检测模型open_nsfw,采用的正是caffe深度学习框架。因此我的目标是将open_nsfw集成到产品中。

研究了一番caffe示例和网上的一些教程,发现各有千秋,不同的模型,代码总有一些差异,真正尝试运行时,总有这样那样的问题。最后还是决定从最基本的模型入手,编写并运行一个caffe程序,并能够真正跑起来。

其实网上和书本上都有很多caffe C++的例子,但是真正自己编译运行是总会碰到这样或那样的问题。究其原因,主要是AI是一个新的领域,变化非常快,可能之前能够编译运行的代码,在新的版本上需要稍做修改才行。其次是环境的不同,比如我使用的是带GPU支持的caffe,结果编译遇到问题,网上就没有搜索到答案。所以这次记录的是我在我的环境下能够编译运行的代码,可能并不适用于你,仅供参考。

编译&运行环境
  • Host: ubuntu 16.04 64位操作系统
  • Docker: 选择的是bvlc/caffe:gpu这个镜像,这个虚拟机镜像里系统是ubunt 16.04,NVIDIA计算平台用的是CUDA 8.0版本

这篇文章主要还是说明caffe C++程序的编写,关于环境方面的问题可以搜索网上的资料。

Hello World

在很多编程教程中都会选择输出一个hello world作为第一个示例,作为我的第一个caffe程序,我也希望训练一个足够简单的AI模型,解决一个足够简单的问题。不过这个示例并不是输出hello world字符串,而是训练一个模型,能够计算布尔值的异或(XOR)值。

对于程序员来说,异或(XOR)运算并不陌生,简单说可以如下图表示:

嗯,就如同helloworld程序一样,这个程序并没有什么实际用途,但它足够简单,足以让我们对AI程序有个初步的印象。

使用的模型如下:

Caffe模型或Caffe神经网络通过连接一组blobs和层而形成。blob是一大块数据,层是应用于blob(数据)的操作。 层本身也可能有一个blob,即权重。因此,一个Caffe模型看起来像是一串交替的blobs和层,彼此相连。一个层需要blobs作为输入,并且它会生成新的blobs,成为下一层的输入。

使用模型文件表示如下(model.prototxt):

name: "XOR"
layer {
 name: "inputdata"
 type: "MemoryData"
 top: "fulldata"
 top: "fakelabel"
 include {
   phase: TRAIN
 }
 memory_data_param
 {
   batch_size: 64
   channels: 1
   height: 1
   width: 2
 }
}layer {
 name: "test_inputdata"
 type: "MemoryData"
 top: "fulldata"
 top: "fakelabel"
 include {
   phase: TEST
 }
 memory_data_param
 {
   batch_size: 4
   channels: 1
   height: 1
   width: 2
 }
}layer {
 name: "fc6"
 type: "InnerProduct"
 bottom: "fulldata"
 top: "fc6"
 inner_product_param {
   num_output: 2
   weight_filler {
     type: "xavier"
   }
   bias_filler {
     type: "constant"
   }
 }
}layer {
 name: "fc6sig"
 bottom: "fc6"
 top: "fc6"
 type: "Sigmoid"
}layer {
 name: "fc7"
 type: "InnerProduct"
 bottom: "fc6"
 top: "fc7"
 inner_product_param {
   num_output: 1
   weight_filler {
     type: "xavier"
   }
   bias_filler {
     type: "constant"
   }
 }
}layer {
 name: "output"
 bottom: "fc7"
 top: "output"
 type: "Sigmoid"
 include {
   phase: TEST
 }
}layer {
 name: "loss"
 type: "SigmoidCrossEntropyLoss"
 bottom: "fc7"
 bottom: "fakelabel"
 top: "loss"
}
模型解读

前两层是输入层,一个用于训练,另一个用于测试。这两层之间的唯一区别是batch_size。训练使用的批量大小为64,测试的批量大小为4,这是因为只需要测试这4种情况:0 xor 0,0 xor 1,1 xor 0,1 xor 1。

layer {
 name: "inputdata"
 type: "MemoryData"
 top: "fulldata"
 top: "fakelabel"
 include {
   phase: TRAIN
 }
 memory_data_param
 {
   batch_size: 64
   channels: 1
   height: 1
   width: 2
 }
}layer {
 name: "test_inputdata"
 type: "MemoryData"
 top: "fulldata"
 top: "fakelabel"
 include {
   phase: TEST
 }
 memory_data_param
 {
   batch_size: 4
   channels: 1
   height: 1
   width: 2
 }
}

下一层是第一个隐藏层,对应于上图中的黄色神经元。根据Caffe文档,过滤器可以对初始神经网络进行随机化,否则初始权重将为零。由于该模型是完全连接的网络,因此层类型为InnerProduct。

layer {
 name: "fc6"
 type: "InnerProduct"
 bottom: "fulldata"
 top: "fc6"
 inner_product_param {
   num_output: 2
   weight_filler {
     type: "xavier"
   }
   bias_filler {
     type: "constant"
   }
 }
}layer {
 name: "fc6sig"
 bottom: "fc6"
 top: "fc6"
 type: "Sigmoid"
}

再下来一层对应于图中的绿色输出神经元,它也是一个InnerProduct:

layer {
 name: "fc7"
 type: "InnerProduct"
 bottom: "fc6"
 top: "fc7"
 inner_product_param {
   num_output: 1
   weight_filler {
     type: "xavier"
   }
   bias_filler {
     type: "constant"
   }
 }
}

最后两个层用于激活,一个用于训练,另一个用于测试。

layer {
 name: "output"
 bottom: "fc7"
 top: "output"
 type: "Sigmoid"
 include {
   phase: TEST
 }
}layer {
 name: "loss"
 type: "SigmoidCrossEntropyLoss"
 bottom: "fc7"
 bottom: "fakelabel"
 top: "loss"
}
solver配置文件

solver配置文件(solver.prototxt)如下:

net: "model.prototxt"
base_lr: 0.02
lr_policy: "step"
gamma: 0.5
stepsize: 500000
display: 2000
max_iter: 5000000
snapshot: 1000000
snapshot_prefix: "XOR"
solver_mode: CPU

学习率从0.02开始,每500000步减少50%,整个迭代次数是5000000。

模型训练C++代码

首先,生成400组训练数据,每个培训数据的批量大小为64。

    float *data = new float[64 * 1 * 1 * 2 * 400];
   float *label = new float[64 * 1 * 1 * 1 * 400];   for (int i = 0; i < 64 * 1 * 1 * 1 * 400; i++){
       int a = rand() % 2;
       int b = rand() % 2;
       int c = a ^ b;
       data[i * 2 + 0] = a;
       data[i * 2 + 1] = b;
       label[i] = c;
   }

代码很简单,随机选择2个二进制数a和b并计算它们的xor值c。a和b保存在一起作为输入数据,c另存为一个单独的数组作为标签。

然后创建一个solver参数对象并加载solver.prototxt:

    SolverParameter solver_param;
   ReadSolverParamsFromTextFileOrDie("./solver.prototxt", &solver_param);

接下来,使用solver参数创建solver:

    shared_ptr<Solver<float> > // uses namespace std
   solver(SolverRegistry<float>::CreateSolver(solver_param));

然后,从solver的神经网络获取输入的MemoryData层,并输入训练数据:

    MemoryDataLayer<float> *dataLayer_trainnet =
   (MemoryDataLayer<float> *)
   (solver->net()->layer_by_name("inputdata").get());
   dataLayer_trainnet->Reset(data, label, 25600);

MemoryData的Reset函数允许您提供指向数据和标签内存的指针。每个标签的大小只能是1,而数据大小是在model.prototxt文件中指定的。25600是训练数据的计数,它必须是批量大小的64倍。

现在,调用下面的代码,开始训练网络:

solver->Solve();

模型训练完成后,生成XOR_iter_1000000.caffemodel、XOR_iter_1000000.solverstate ~ XOR_iter_5000000.caffemodel、XOR_iter_5000000.solverstate这样的模型文件,接下来我们将使用这些生成的模型文件进行测试。

模型测试C++代码

用相同的模型创建另一个网络,但传入TEST,并加载XOR_iter_5000000.caffemodel:

    shared_ptr<Net<float> >    testnet;   testnet.reset(new Net<float>("./model.prototxt", TEST));
   testnet->CopyTrainedLayersFrom("XOR_iter_5000000.caffemodel");

与训练阶段类似,需要获取输入的MemoryData层并将其输入传递给它进行测试:

    float testab[] = {0, 0, 0, 1, 1, 0, 1, 1};
   float testc[] = {0, 1, 1, 0};   MemoryDataLayer<float> *dataLayer_testnet = (MemoryDataLayer<float> *)(testnet->layer_by_name(argv[3]).get());   dataLayer_testnet->Reset(testab, testc, 4);

请注意,此输入层的名称是test_inputdata,而用于训练的输入层是inputdata。

然后计算神经网络输出:

    testnet->Forward();

完成之后,我们需要通过访问输出blob来获得结果:

    boost::shared_ptr<Blob<float> > output_layer = testnet->blob_by_name(argv[4]);   const float* begin = output_layer->cpu_data();
   const float* end = begin + 4;   vector<float> result(begin, end);

输出大小为4,并将输出保存到结果向量中。

最后打印结果:

    for (int i = 0; i < result.size(); i++) {
       cout<<"input: "<<(int)testab[i * 2 + 0]
       <<" xor "<<(int)testab[i * 2 + 1]
       <<", truth: "<<(int)testc[i]
       <<", result by NN: "<<result[i]<<endl;
   }

以下是这个简单的神经网络的测试结果:

input: 0 xor 0,  truth: 0.000000 result by nn: 0.000550
input: 0 xor 1,  truth: 1.000000 result by nn: 0.999368
input: 1 xor 0,  truth: 1.000000 result by nn: 0.999368
input: 1 xor 1,  truth: 0.000000 result by nn: 0.000626

你可能会疑惑了,怎么整这么复杂的代码,得出一个并不那么精确的结果。其实,随着机器学习的深入,你会发现概率论和统计学占据着主要的地位,一个模型能够有这么高的精度,已经非常不错了。

编译

代码编写出来,接下来肯定需要编译运行,但这部分网络上的资料相对比较少。因为caffe使用了cmake,所以这段代码我也使用cmake来构建。

这个任务其实相对比较简单,但却花费了很多时间。主要是我追求完美,希望能够让build系统更健壮,比如caffe库的头文件、库文件自动检测,相关依赖库的确定与链接等等。查了一些资料,在build caffe库的时候会生成一个FindCaffe.cmake的文件,但我费了老半天的劲也生成不出来。参照别人的FindCaffe.cmake,似乎也找不到,即使把caffe的头文件和库文件找到,还有相关的库,比如glog、openBLAS等等,还是需要手工编写。最后还是决定先手工编写,CMakeLists.txt文件如下:

cmake_minimum_required(VERSION 2.8.7)project(XORusingCAFFE C CXX)set(Caffe_INCLUDE_DIRS /opt/caffe/include /opt/caffe/build/include /usr/local/cuda/include)
set(Caffe_LIBRARIES caffe boost_system glog)include_directories(${Caffe_INCLUDE_DIRS})
link_directories(/opt/caffe/build/lib)add_executable(trainXOR  trainXOR.cpp)
target_link_libraries(trainXOR ${Caffe_LIBRARIES})add_executable(testXOR testXOR.cpp)
target_link_libraries(testXOR ${Caffe_LIBRARIES})

这是caffe docker环境下可用的编译配置文件,如果在别的环境下,可能需要稍微做一些修改。

至此,我的第一个caffe程序编写完毕,虽然大部分的内容来自网络,但总归是我亲手敲进去,并编译运行出来的,接下来我会研究如何将这个helloworld搬到Android手机上运行。

参考
  1. A Gentle Introduction to Artificial Neural Networks
  2. Caffe c++ helloworld example with MemoryData input
  3. C# Backpropagation Tutorial (XOR)
本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2018-04-02,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 云水木石 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 编译&运行环境
  • Hello World
  • 模型解读
  • solver配置文件
  • 模型训练C++代码
  • 模型测试C++代码
  • 编译
  • 参考
相关产品与服务
容器服务
腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档