目录
0 环境准备
由于BVLC Caffe不支持Cuda 8.0,以下实验均在Microsoft Caffe基础上完成。本节将完整列出编译安装深度学习框架Caffe必需的组件。
需要严格按照说明书安装,否则将导致安装失败。
- 安装VS2013
Visual Studio 2013是之后用于编译Caffe工程的IDE。
- 安装Anaconda2;或Miniconda3,提取码m02a
Caffe编译需要的是python2.7,所以一般做法是安装Anaconda2。注意安装过程中不要多余勾选它没勾的框。安好后手动添加环境变量%CONDA_HOME%(实际python2.7安装根目录)、%CONDA_HOME%\Scripts和%CONDA_HOME%\Library\bin。实际python2.7安装根目录是什么意思呢?就是实际的python2.7解释器——python.exe所在的目录喽。
注意,我编译时,是先用Anaconda3创建了python2.7虚拟环境“python27”,然后添加的第一个环境变量就要相应地变为%CONDA_HOME%\envs\python27,即%CONDA_HOME% := %CONDA_HOME%\envs\python27。创建好虚拟环境后记得要安装numpy。
或者有些人的虚拟环境默认是创建在其他地方的比如C盘的.conda文件夹等等,总之这一步的目的就是为了声明python2.7的位置,和配置PyCharm的环境变量的过程可以说是非常相似的。所以具体的环境变量根据你的python2.7的实际位置来定就好。
- 安装cuda8.0.61,lecj
步骤略。
- 安装,r1a1
步骤略。
1 Caffe编译(生成)
Caffe,全称Convolution Architecture For Feature Extraction,是一个清晰且快速的深度学习框架。下面介绍一下如何在Windows 10下配置Caffe框架。
- 下载微软caffe源码并解压到适当的路径。下面统一用%CAFFE_MASTER%指代解压后所在的目录。
- 下载NugetPackages并解压到任意的路径,提取码7uev。
这一步可以省略。这样的话在生成libcaffe这一步中,VS2013会自动帮我们下载NugetPachages,是不是很贴心呢(暖)?但是,为了避免某些校园网网络存在不稳定等的复杂情况而导致失败,我们先下载好了,这样就不用等着VS2013缓慢地下载啦。
- 复制%CAFFE_MASTER%
\windows\CommonSettings.props.example
为%CAFFE_MASTER%\windows\CommonSettings.props
-
用记事本打开CommonSetting.props,修改如下:将默认的<CudaVersion>7.5</CudaVersion>修改为
<CudaVersion>8.0</CudaVersion>;
默认<PythonSupport>
为false
,修改为<PythonSupport>true</PythonSupport>并
修改<PropertyGroup Condition="'$(PythonSupport)'=='true'">这一行下面的<PythonDir>
路径为当前python 2.7的安装路径<PythonDir>
%CONDA_HOME%\</PythonDir>;
默认<MatlabSupport>
为false,
修改为<MatlabSupport>true</MatlabSupport>并
修改<PropertyGroup Condition="'$(MatlabSupport)'=='true'">这一行下面的<MatlabDir>为当前Matlab安装路径<MatlabDir>%MATLAB_HOME%</MatlabDir>。
再提醒一下上面的%CONDA_HOME%和%MATLAB_HOME%取决于你的python2.7和Matlab的实际安装路径。
- 打开%CAFFE_MASTER%\windows下的Caffe.sln
,
调试器模式设为Release X64,
所有项目的属性中“将警告是为错误”设为“否”。 - 打开VS2013菜单栏的工具->NuGet程序包管理器->程序包管理器设置,选择程序包源,点右上方的加号,然后右下方名称任意(就叫默认的Package Source 1就好),点击旁边的“..."浏览并选择之前下载解压的NugetPackages文件夹,再点更新,再点右上方的上箭头将该源移到顶部,最后确定保存。
结合第2步,这一步就能确保编译过程中使用我们之前下载好的NugetPackages。
- 打开VS2013菜单栏的工具->选项,选择项目和解决方案->生成并运行,最大并行项目生成数设为3(或更小),确定保存。
- 生成libcaffe。
- 生成caffe。
- 依次生成剩余的14个项目。
如果在生成matcaffe时出现问题..\..\matlab\+caffe\private\caffe_.cpp(16): fatal error C1083: 无法打开包括文件: “gpu/mxGPUArray.h”: No such file or directory,则把%MATLAB_HOME%\toolbox\distcomp\gpu\extern\include下的gpu文件夹复制到%MATLAB_HOME%\extern\include下,然后继续从matcaffe开始生成。
如果在生成pycaffe时出现问题..\..\python\caffe\_caffe.cpp(10): fatal error C1083: 无法打开包括文件: “numpy/arrayobject.h”: No such file or directory,说明未安装numpy,给当前python2.7装上numpy即可。然后继续从pycaffe开始生成。
- 16个项目都生成成功后关闭vs2013。最后,在%CAFFE_MASTER%下出现Build文件夹,即%CAFFE_HOME% := %CAFFE_MASTER%\Build\x64\Release。可以选择将%CAFFE_HOME%添加到系统环境变量。
至此Windows 10下的caffe就安装成功了,是不是非常的简单呢?这里也提供一下我编译好的caffe作为参考(4uvj)。但是,这个版本并没有把matcaffe编译进去,而且如果以后想要做进一步更改,编译Caffe是必须步骤,所以建议完整地按照说明亲自安装。另外,%CAFFE_HOME%\pycaffe中的caffe文件夹是用于import caffe的caffe包。在
import caffe
前,需要将%CAFFE_HOME%\pycaffe加入python工程路径或将%CAFFE_HOME%\pycaffe下的caffe文件夹拷贝到相应python工程所在虚拟环境下的包引用目录中。caffe只支持python2.7的虚拟环境。
2 Caffe再编译
针对caffe添加新的层,网上有许多教程。但是,大部分教程关注的是Caffe for Linux,缺乏Caffe for Windows下的完整、快速和通用的实现方法。有一些贴出了Caffe for Windows下的实验流程,但是都集中于新层的hpp、cpp和cu代码的编写方面。虽然新层的hpp、cpp和cu代码的编写也很重要,但有些时候,往往我们得到了已编写好的新层代码,却不知如何将其再编译进Caffe for Windows中。因此,本节将以向Caffe for Windows中添加新损失函数(层)“ChangeLossLayer"为例,详细描述相应的完整实验步骤。(🐶)
实验原理
假设我想添加一个叫ChangeLossLayer的损失层,损失函数是L=l0+l1+l2,其中l1、l2和l3都具有Contrastive Loss的形式,且具有同一个阈值参数margin。除此之外,这个损失层还有额外3个参数alpha、beta和gamma用于调整l1、l2和l3的大小。类似于SmoothL1Loss具有的参数sigma可以在prototxt中定义,我们所添加的层具有的4个参数margin、alpha、beta和gamma也应该能够在prototxt中定义。所以这些参数都是需要后面在一个叫caffe.proto的文件里注册的。
怎么看出来一个新损失层需要哪些参数?只需要看它的cpp文件即可。以SmoothL1Loss为例,打开它的cpp文件,可以看到第5行表明需要用到的自定义参数就是sigma。同理,只需要看其他损失层的cpp相同位置,即可知道后面我们需要注册哪些参数。
template <typename Dtype>
void SmoothL1LossLayer<Dtype>::LayerSetUp(
const vector<Blob<Dtype>*>& bottom, const vector<Blob<Dtype>*>& top) {
SmoothL1LossParameter loss_param = this->layer_param_.smooth_l1_loss_param();
sigma2_ = loss_param.sigma() * loss_param.sigma(); //
has_weights_ = (bottom.size() >= 3);
if (has_weights_) {
CHECK_EQ(bottom.size(), 4) << "If weights are used, must specify both "
"inside and outside weights";
}
}
要添加ChangeLossLayer光有源代码是不够的,还需要在caffe.proto中定义新的结构化数据。关于caffe.proto的原理可以参考这里。caffe.proto的位置见下表。修改caffe.proto时务必注意格式和大小写。
caffe.proto中都是描述性语言,Caffe for Windows需要的是由caffe.proto编译而来的C代码(caffe.pb.h和caffe.pb.cc)。所以改完caffe.proto后会用到一个proto文件的编译器protoc。这个是需要额外下载的。
最后会用到原生工程自带的ProtoCompile.cmd。ProtoCompile.cmd在再编译Caffe工程的过程中由Caffe工程自动调用。ProtoCompile.cmd的位置见下表。ProtoCompile.cmd负责调用protoc编译caffe.proto并自动替换老caffe.pb.h和caffe.pb.cc。
目前全部的材料总结如下,下面将把新损失层集成到Caffe for Windows中。
实验过程
添加损失层
1、用记事本打开caffe.proto,做如下修改,具体的名字根据实际添加的层决定,但是修改时务必注意格式和大小写。
- 找到message LayerParameter块,根据上方显示的下一个可用ID(我的是151),在块内最下方添加
optional ChangeLossParameter change_loss_param = 151;
- 在块外任意空白位置添加
message ChangeLossParameter {
optional float margin = 1 [default = 1.0];
optional float alpha = 2 [default = 3.0];
optional float beta = 3 [default = 5.0];
optional float gamma = 4 [default = 50.0];
}
- 保存并关闭。不需要改动V1LayerParameter中的内容。
2、下载proto文件的编译器protoc-2.6.1-win32.zip,压缩包解压到%PROTOC_HOME%,将%PROTOC_HOME%添加到系统环境变量。之所以版本是2.6.1,是因为caffe不支持别的版本。
3、用记事本打开ProtoCompile.cmd,删掉第二行的set PROTO_DIR=%~2%;将第12行"%PROTO_DIR%protoc"改为"protoc",最后保存。
4、将新层的hpp文件拖到%CAFFE_MASTER%\include\caffe\layers下,将新层的cpp和cu文件拖到%CAFFE_MASTER%\src\caffe\layers下。
5、用VS2013打开%CAFFE_MASTER%\windows\Caffe.sln,
展开libcaffe\cu,右键layers->添加现有项->选择刚才拖动后的cu文件;展开libcaffe\src,右键layers->添加现有项->选择刚才拖动后的cpp文件;展开libcaffe\include,右键layers->添加现有项->选择刚才拖动后的hpp文件。
6、检查调试器模式是否设为Release X64,
所有项目的属性中“将警告是为错误”是否设为了“否”,是否启用了已下载好的NugetPackages,最大并行项目生成数是否设为了3(或更小)。matcaffe和pycaffe是否根据第一节已设置正确。
7、先右键libcaffe选“重新生成”,再右键caffe选“重新生成”,最后依次重新生成剩余的14个项目。
这样,Windows 10下的Caffe框架就重新编译成功了,我们就可以像使用其他层一样随意使用新添加的层了,是不是非常的简单方便呢😅?相比Caffe for Linux,不得不说安装和修改稍微复杂了一些,但是Microsoft Caffe已经尽可能地适配了Windows系统,而且安装成功后,后者的工作效率丝毫不比前者差,甚至能获得更完善的GPU支持。考虑到Windows 10的工作链比Linux完善得多,Caffe for Windows仍具有足够吸引人的性价比。
3 使用
以回归任务为例,样本是N×N×3的图片,当然可以直接读取。第二种方法是分别生成训练和测试的lmdb格式数据集,这种格式是Caffe数据层支持的,但是需要预先将图片和标签写入lmdb数据库
import os
import lmdb
from PIL import Image
import numpy as np
import sys
import caffe
file_input = open('./test.txt', 'r') # file info
img_list = []
label_list = []
num = 1
for line in file_input:
content = line.strip()
content = content.split(' ')
img_list.append(content[0]) # Name of the sample
label = []
for i in range(37): # Number of my tags of a label
label.append(float(content[i+1]))
label_list.append(label)
del content
print(num)
num = num + 1
file_input.close()
# Saving the samples to the database
in_db = lmdb.open('./img/', map_size=int(1e10))
with in_db.begin(write=True) as in_txn:
for in_idx, in_ in enumerate(img_list):
_in_ = in_.split('_')[0]
in__ = _in_ + '/' + in_ + '.bmp'
im_file = './' + in__
im = Image.open(im_file)
im = np.array(im)
im = im[:, :, ::-1]
im = im.transpose((2, 0, 1))
im_dat = caffe.io.array_to_datum(im)
in_txn.put('{:0>10d}'.format(in_idx), im_dat.SerializeToString())
print('image carried through: {} [{}/{}]'.format(in__, in_idx+1, len(img_list)))
del im_file, im, im_dat
in_db.close()
# Saving the corresponding labels to the database
in_db = lmdb.open('./label/', map_size=int(1e10))
with in_db.begin(write=True) as in_txn:
for in_idx, in_ in enumerate(img_list):
target_label = np.zeros((37, 1, 1))
for i in range(37):
target_label[i, 0, 0] = label_list[in_idx][i]
label_data = caffe.io.array_to_datum(target_label)
in_txn.put('{:0>10d}'.format(in_idx), label_data.SerializeToString())
print('labels carried through: {} [{}/{}]'.format(in_, in_idx+1, len(img_list)))
del target_label, label_data
in_db.close()
print('Preparation accomplished.')
网络就能直接从lmdb里读数据了。
使用caffe一般需要以下几个文件:
train_val.prototxt:设定训练的具体网络结构,即定义数据层、卷积层、全连接层、损失函数等等。Netscope能可视化train_val.prototxt中的网络。
solver.prototxt:设定超参数,如:
# The train/test net protocol buffer definition
net: "./train_val.prototxt"
test_iter:1313
test_interval: 10000
base_lr: 0.001
lr_policy: "step"
gamma:0.1
stepsize: 55000
display: 100
max_iter: 200000
momentum: 0.9
weight_decay: 0.00001
snapshot: 5000
snapshot_prefix: "./snapshot"
solver_mode: GPU
test_iter和test_interval一定要设,否则在训练时每迭代一次就进行一次测试,导致训练极慢。
deploy.prototxt:设定用模型进行预测时的网络结构,基本与train_val.prototxt相同。但它应该是不含loss layer的。
训练
启动训练
caffe.exe train --solver=./solver.prototxt --gpu=all
pause
一个epoch所含的iteration=样本数量÷batch_size。
预测
直接执行测试脚本
caffe test --weights=./snapshot_iter_200000.caffemodel --model=./deploy.prototxt --iterations=99402 --gpu=all
pause
也可以利用caffe的python接口进行预测
# -*- coding: utf-8 -*-
import caffe
net = caffe.Net('./deploy.prototxt', 1, weights='./snapshot_iter_200000.caffemodel')
with open('./test.txt') as image_list:
with open('./pd.txt', 'w') as r:
while 1:
list_name = image_list.readline()
if not list_name:
break
img_name = list_name.split(' ')[0]
img_y = list_name.split(' ')[1] # 真实值
out = net.forward()
score = (out['fc3'])[0, 0] # 获取预测值;fc3是我的最后一个全连接层名
r.write(img_name + " " + img_y + " " + repr(score) + '\n')