深度学习与计算机视觉 实践学习(2)——用caffe实现一个神经网络

用caffe实现一个神经网络

目的:对平面内线性不可分的数据进行二分类

环境:安装Caffe 

Caffe是个依赖非常多的框架,装完依赖包后,再装Caffe

先到要安装Caffe的目录下,执行 git clone htttps://github.com/BVLC/caffe.git

然后到Caffe目录下,找到Makefile.config.example文件复制一份,cp Makefile.config.example Makefile.config

与MXNet类似,Makefile.config是编译的配置文件,在这个文件里可以配置一些编译选项,一般来说主要配置CUDA,cuDNN,blas库。

默认情况下CPU_ONLY:=1是被注释掉的。除非是在没有NVIDIA的GPU机器上安装Caffe,那么把注释取消即可。

还有一个选项是WITH_PYTHON_LAYER:=1 意思是支持用Python定义神经网络中的层,和MXNet中定义层的方式有些类似。如果要使用一些python的层或是一些特定功能的Caffe版本,比如Ross Girshick的py-faster-rcnn,那么就需要把这一项前的注释取消。

配置好Makefile.config后就可以开始编译了,依次执行以下命令:

>> make pycaffe -j

>> make all -j

>> make test -j

因为Caffe的依赖过多,在安装过程中有可能找不到一些动态库,这时需要把相对路径加入到LD_LIBRARY_PATH即可,如果是头文件则可以把对应路径加入到CPLUS_INCLUDE_PATH中。比如找不到HDF5的库,可以执行:

>> export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/path/to/hdf5/serial

为了在python中直接调用Caffe,还需要把Caffe的Python路径加入到PYTHONPATH.

>> export PYTHONPATH=$PYTHONPATH:/path/to/caffe/python

Caffe的基本概念:从本质上来说,和MXNet中的Symbolic的使用方式差不多,都是先定义好一个计算关系,然后根据这个计算关系结合数据训练和使用模型。在caffe中,最基本的计算节点是层(layer),所有的计算关系都是基于层的,无论是Caffe预定义好的层,还是用户自定义的层。还有一种更常用的使用方式,即利用protobuf的格式而不是代码来定义网络的结构,然后再结合数据进行训练,也就是说用Caffe训练模型是不需要写代码的。这体现了Caffe设计哲学中利用表达式(expression)和模块化(modularity)的特点。

除了Caffe的接口文档,各种常用层的信息一览在Caffe官网也有列出,地址为:http://caffe.berkeleyvision.org/tutorial/layers.html

基于各种就可以构建成网络(Net),然后定义好数据,再定义一个梯度下降法做优化的Slover模块,就可以训练一个模型了。

在Caffe中,数据的形式是一种叫Blob的类,其实就是空间连续的多维数组,比如存储图像的时候是个四维的数组,四个维度分别是批大小、通道数、图像高度、图像宽度。

第一步:准备数据,在caffe中,非图像数据的支持不是很好,这里使用HDF5格式来准备产生的坐标数据和对应标签,具体代码如下,(注意:Caffe中HDF5Data层读取的并不是HDF5数据本身,而是一个HDF5文件的列表,所以我们除了生成data.h5外还生成一个data_h5.txt作为HDF5Data层的数据源。

import pickle
import numpy as np
import h5py
with open('data.pkl','rb') as f:
    samples, labels = pickle.load(f)
sample_size = len(labels)
samples = np.array(samples).reshape((sample_size, 2))
labels = np.array(labels).reshape((sample_size, 1))
h5_filename = 'data.h5'
with h5py.File(h5_filename, 'w') as h:
    h.create_dataset('data', data=samples)
    h.create_dataset('label', data=labels)
with open('data_h5.txt', 'w') as f:
    f.write(h5_filename)

第二步:定义网络和训练网络的train.prototxt。

name: "SimpleMLP"
layer {
  name: "data
  type: "HDF5Data"
  top: "data"
  top: "label"
  include {
    phase: TRAIN
  }
  hdf5_data_param {
    source: "data_h5.txt"
    batch_size: 41
  }
}

layer {
  name: "fc1"
  type: "InnerProduct"
  bottom: "data"
  top: "fc1"
  inner_product_param {
    num_output: 2
    weight_filler {
      type: "uniform"
    }
  }
}

layer{
  name: "sigmoid1"
  type: "Sigmoid"
  bottom: "fc1"
  top: "sigmoid1"
}

layer {
  name: "fc2"
  type: "InnerProduct"
  bottom: "sigmoid1"
  top: "fc2"
  inner_product_param {
    num_output: 2
    weight_filler {
      type: "uniform"
    }
  }
}

layer{
  name: "loss"
  type: "SoftmaxWithLoss"
  bottom: "fc2"
  bottom: "label"
  top: "loss"
}

程序代码并不复杂,每一层的意思也比较明确,name是层的意思,type指定层的类型,bottom是输入blob的名字,top是输出blob的名字。最后根据类型的不同,每一层会有特定的参数,比如InnerProduct,也就是全连接层中,num_output就是输出的数量,也就是隐藏单元的个数。整体来看,这个prototxt文件中首先定义了HDF5数据层,用于从HDF5文件中读取数据。读取的数据按照顺序排列分别是data和label。

因为在这个简单例子中,我们把所有的数据用于训练,所以只有一个phase为TRAIN的HDF5数据层,并且batch_size就是所有样本的数量。在这个简单例子中,只有全连接的InnerProduct层和激活函数的Sigmoid层,在这里需要提的是weight_filler这个参数。在mxnet中,默认是用均匀分布的随机数来初始化网络参数,所以没有专门指定。而在caffe中,默认的初始化参数居然是0,对于这个例子会很难收敛。所以我们专门把这个参数手动指定为uniform,这样会默认产生0~1之间的随机数用来初始化当前层,除了weight_filler之外还有bias_filler用来初始化偏置的值,不过因为默认值并不影响收敛,所以例子中略过了。

最后用SoftmaxWithLoss层来计算损失函数。

Caffer中自带专门用来可视化网络结构的脚本,路径是caffe/python/draw_net.py,执行:

>> python /path/to/caffee/python/draw_net.py train.prototxt mlp_train.png -randir BT

即可得到Caffe可视化后的网络结构并保存在mlp_train.png中。

第三步:定义好网络结构后还需要定义用于梯度下降的solver.prototxt:

net: "train.prototxt"
base_lr: 0.15
lr_policy: "fixed"
display: 100
max_iter: 2000
momentum: 0.95
snapshot_prefix: "simple_mlp"
solver_mode: CPU

第一个net参数指定了训练用train.prototxt。base_lr是学习率,这里设置为0.15。lr_policy是学习率随训练进行改变的策略,这里设置为fixed,意思是不做任何改变。display是指每迭代这么多次就显示一次训练进行的信息,比如当前loss的值。max_iter是最大训练次数,达到这个次数就停止,同时产生当前模型的参数和solver状态的备份。Momentum顾名思义是冲量的系数,snapshot_prefix是保存模型和solver文件的名字名字前缀。solver_mode指定是用cpu还是GPU进行训练,例子中先用CPU。

第四步:执行下面命令开始训练,

>> /path/to/caffe/build/tools/caffe train -solver solver.prototxt

训练过程中会输出当前的迭代次数和对应的loss值。

或者也可以用如下python脚本进行训练,训练的效果和前面的命令训练的效果一样。

import sys
import numpy as np
sys.path.append('/opt/caffe/python')
import caffe
solver = caffe.SGDSolver('solver.prototxt')
solver.solve()
net = solver.net
net.blobs['data'] = np.array([[0.5, 0.5]])
output = net.forward()
print(output)

第五步:

不过第四步训练完后不能立刻使用,第四步python脚本代码最后一行的print会输出如下结果:

{'loss': array(0.004321129061281681, dtype=float32)},这是一个字典,键是最后一层blob名字,值是loss的值。

是因为train.prototxt网络中,我们定义的是训练用的网络,到了做推断的阶段,还需要对这个结构做一些修改才可以,修改的主要部分是输入和输出部分,同时初始化网络权重的部分可有可无,完整内容如下:

name: "SimpleMLP"
input: "data"
input_shape {
  dim: 1
  dim: 2
}

layer {
  name: "fc1"
  type: "InnerProduct"
  bottom: "data"
  top: "fc1"
  inner_product_param {
    num_output: 2
  }
}

layer{
  name: "sigmoid1"
  type: "Sigmoid"
  bottom: "fc1"
  top: "sigmoid1"
}

layer {
  name: "fc2"
  type: "InnerProduct"
  bottom: "sigmoid1"
  top: "fc2"
  inner_product_param {
    num_output: 2
  }
}

layer{
  name: "softmax"
  type: "Softmax"
  bottom: "fc2"
  top: "prob"
}

HDF5Data层没有了,取代的是input用来指定输入blob的名字,还有input_shape中指定输入数据的形状。最后一层也变成了Softmax,最顶层输出取名为prob,表示属于某个类的概率。把这个用于推断的网络结构保存为test.prototxt,然后用如下python代码可以执行训练好的模型用于推断(Inference),最后生成一个可视化结果。

import sys
import pickle
import numpy as np
import matplotlib.pyplot as plt
  from mpl_toolkits.mplot3d import Axes3D
  sys.path.append('/opt/caffe/python')
import caffe
net = caffe.Net('test.prototxt', 'simple_mlp_iter_2000.caffemodel', caffe.TEST)
with open('data.pkl', 'rb') as f:
  samples, labels = pickle.load(f)
samples = np.array(samples)
labels = np.array(labels)
X = np.arange(0, 1.05, 0.05)
Y = np.arange(0, 1.05, 0.05)
X, Y = np.meshgrid(X, Y)
grids = np.array([[X[i][j], Y[i][j]] for i in range(X.shape[0])
                                     for j in range(X.shape[1])])
grid_probs = []
for grid in grids:
  net.blobs['data'].data[...] = grid.reshape((1,2)[...]
  output = net.forwad()
  grid_probs.append(output['prob'][0][1])
grid_probs = np.array(grid_probs).reshape(X.shape)
fig = plt.figure('Sample Surface')
ax = fig.gca(projection='3d')
ax.plot_surface(X, Y, grid_probs, alpha=0.15, color='k', rstride=2, cstride=2, lw=0.5)
samples0 = samples[labels==0]
samples0_probs = []
for sample in samples0:
  net.blobs['data'].data[...] = sample.reshape((1, 2))[...]
  output = net.forward()
  samples0_probs.append(output['probs'][0][1])
samples1 = samples[labels==1]
samples1_probs = []
for sample in samples1:
  net.blobs['data'].data[...] = sample.reshape((1, 2))[...]
  output = net.forward()
  samples1_probs.append(output['prob'][0][1])
ax.scatter(samples0[:,0], samples0[:,1], samples0_probs, c='b', marker='^', s=50)
ax.scatter(samples1[:,0], samples1[:,1], samples0_probs, c='r', marker='o', s=50)
plt.show()

猜你喜欢

转载自blog.csdn.net/Fan0920/article/details/107532419