Caffe框架编译之后会生成动态链接库libcaffe.so,其本身并不能独立运行。如果需要运行Caffe,则需要编写一个main()函数,调用Caffe的API,这样才能构成一个完成的Caffe应用程序。

caffe有命令行、Python和Matlab三种接口,来实现日常实现、研究代码的交互以及实现快速原型。caffe以C++库为核心,其在开发中使用模块化接口,而不是每次都调用其定义的编译。

在tools/目录下就是一些调用libcaffe.so的使用工具源码。下面介绍这些工具的主要功能。

1、 caffe.bin工具(Command Line)

caffe.bin用法介绍

(Windows平台生成的是caffe.exe)

源码文件:caffe.cpp

$ . / buìld/tools/caffe.þin

caffe.bin: command line brew

usage: caffe

commands:

train 训练或微调一个模型

test 对一个模型打分

device_query 显示GPU诊断信息

time 评估模型执行时间

Flags from tools/caffe.cpp:

-gpu (可选参数,给定时运行在GPU模式,' -gpu all'则表示运行在所有可用GPU设备上,此时真正训练批量大小是N*B,N为指定的GPU设备数目)

-iterations (循环迭代次数,默认为50)

-model (指定模型定义文本文件名,*.prototxt)

-sighup_effect (可收到SIGHUP信号时要采取的动作,可选项:snapshot,stop或none,默认为snapshot,即打快照)

-sigint_effect (当收到SIGINT 信号时要采取的动作,可选项同上,默认stop>

-snapshot (恢复训练时需要指定上次中止的快照,*.solverstate)

-solver (指定求解器文本文件名,*.prototxt)

-weights (指定用于微调的预训练权值,*.caffemodel,不可与snapshot同时出现)

注意参数有两种表示形式:

单杠+接空格:-solver lenet.prototxt;

双杠+接等号:--solver=lenet.prototxt.

1.1 训练和预测

通过向caffe.bin传递不同参数(train/test)可以实现深度神经网络的训练、预测。

【模型训练】

caffe train命令可以从零开始学习模型,也可以从已保存的snapshots继续学习,或将已经训练好的模型应用在新的数据与人文上进行微调即fine-tuning学习:

所有的训练都需添加-sover solver.prototxt参数完成solver的配置;

继续训练需要添加-snapshot model_iter_1000.solverstate参数加载solver snapshot;

Fine-tuning需要添加-weights model.caffemodel参数完成模型初始化。

# 训练LeNet

caffe.bin train --solver=..\\..\\examples\\mnist\\lenet_solver.prototxt

# 在2号GPU上训练

caffe.bin train --solver=..\\..\\examples\\mnist\\lenet_solver.prototxt -gpu 2

# 从中断点的snapshot继续训练

caffe.bin train --solver=..\\..\\examples\\mnist\\lenet_solver.prototxt

-snapshot ..\\..\\examples\\mnist\\lenet_iter_5000.solverstate

# 微调caffeNet模型的权值以完成风格识别任务(style recongnition)

caffe train -solver examples/finetuning_on_flickr_style/slover.prototxt -weights models/bvlc_reference_caffenet/bvlc_reference_caffenet.caffemodel

【模型测试】

caffe test命令通过在test phase中运行模型得到分数,并且用这分数表示网络输出的最终结果。网络结构必须被适当定义,生成accuracy或loss作为其结果。测试过程中,终端会显示每个batch的得分,最后输出全部batch得分的平均值。

caffe.bin test -model ..\\..\\examples\\mnist\\lenet_train_test.prototxt -weights ..\\..\\examples\\mnist\\lenet_iter_10000.caffemodel -iterations 100

【导出log信息】

参见“8 绘制学习(Loss和Accuracy)曲线小节”

1.2 评估模型执行时间

编译好的Caffe可以通过运行caffe time命令,对当前平台上网络各层前向/后向计算计时:

# time命令默认执行50 iterations

./build/tools/caffe.bin time -model examples/mnist/lenet_train_test.prototxt

# 可以指定执行迭代次数

./build/tools/caffe.bin time -model examples/mnist/lenet_train_test.prototxt -iteration 100 -gpu 0

通过该工具可以统计前向传播的平均时间,反向传播的平均时间,一次前向-反向传播的平均时间,总共消耗时间以及每层的平均耗时。

(注意,只需传入模型的描述文件即可,但是必须指定正确的data路径,程序中测试若干次迭代,如50次迭代,计算平均耗时)

1.3 诊断

caffe device_query命令对于多GPU机器上,在指定的GPU运行,输出GPU细节信息用来参考与检测设备序号。

caffe device_query -gpu 0

1.4 并行模式

caffe 工具的-gpu标识,对于多GPU的模式下,允许使用逗号分开不同GPU的ID号。solver与net在每个GPU上都会实例化,因此batch size由于具有多个GPU而成倍增加,增加的倍数为使用的GPU块数。如果要重现单个GPU的训练,可以在网络定义中适当减少batch size。

# 在序号为0和1的GPU上训练(双倍的batch size)

caffe train -solver examples/mnist/lenet_solver.prototxt -gpu 0,1

# 在所有GPU上训练(将batch size乘上GPU数量)

caffe train -solver examples/mnist/lenet_solver.prototxt -gpu all

2、 Python接口调用

Python接口pycaffe是caffe的一个模块,其脚本保存在caffe/python。通过import caffe加载模块,实现forward与backward、IO、网络可视化以及求解模块等操作。所有模型数据,导数与参数都可以读取与写入。

caffe.Net是加载、配置和运行模型的中心接口;

caffe.Classifier与caffe.Detector为一般认为实现了便捷的接口;

caffe.SGDSolver表示求解器接口;

caffe.io通过预处理与protocol buffers,处理输入/输出;

cafffe.draw实现数据结构可视化;

caffe blobs通过numpy ndarrays实现即用性与有效性。

make pycaffe可编译pycaffe。通过exprot PYTHONPATH=/path/to/caffe/python:$PYTHONPATH将模块目录添加到自己的$PYTHONPATH目录,或者相似的指令来实现improt caffe。

具体可以参考:Caffe实战(四):pycaffe和matcaffe接口编译 Caffe实战(七):caffe可视化工具

3、 Matlab接口调用

MATLAB接口(matcaffe)是在caffe/matlab 路径中的 caffe 软件包。在matcaffe的基础上,可将Caffe整合进你的Matlab代码中。

在MatCaffe中,你可实现:

在Matlab中创建多个网络结构(nets)

进行网络的前向传播(forward)与反向传导(backward)计算

存取网络中的任意一层,或者网络层中的任意参数

在网络中,读取数据或误差,将数据或误差写入任意层的数据(blob),而不是局限

在输入blob或输出blob

保存网络参数进文件,从文件中加载

调整blob与network的形状

编辑网络参数,调整网络

在Matlab中,创建多个solvers进行训练

从solver快照(snapshots)恢复并继续训练

在solver中,访问训练网络(train nets)与测试网络(test nets)

迭代一定次数后将网络结构交回Matlab控制

将梯度方法融合进任意的Matlab代码中

caffe/matlab/demo/classification_demo.m 中有一个ILSVRC 图片的分类demo(需要在Model Zoo中下载 BVLC CaffeNet模型)。

用make all matcaffe命令编译MatCaffe。随后,需要用 make mattest命令进行测试。详细参考:Caffe实战(四):pycaffe和matcaffe接口编译

MatCaffe在使用上与PyCaffe相似。

【训练/测试前操作】

model = './models/bvlc_reference_caffenet/deploy.prototxt';

weights

='./models/bvlc_reference_caffenet/bvlc_reference_caffenet.caffemodel';

% 在创建net或solver之前,应该先设置运行模式以及GPU设备。

caffe.set_mode_cpu();

%或者使用GPU

caffe.set_mode_gpu();

caffe.set_device(gpu_id);

【创建/访问网络】

% 创建网络:

net = caffe.Net(model, weights, 'test'); % 创建网络并加载权值

% 或者

net = caffe.Net(model, 'test'); % 创建网络,但不加载权值

net.copy_from(weights); % 加载权值

% 存取网络中任意的blob;可以通过blob/layer name找到对应的数据

net.blobs('data').set_data(ones(net.blobs('data').shape));

% 访问每一层,对网络进行调整(surgery)

net.params('conv1', 1).set_data(net.params('conv1', 1).get_data() * 10); % 设置权值

net.params('conv1', 2).set_data(net.params('conv1', 2).get_data() * 10); % 设置偏置

% 或者

net.layers('conv1').params(1).set_data(net.layers('conv1').params(1).get_data() * 10);

net.layers('conv1').params(2).set_data(net.layers('conv1').params(2).get_data() * 10);

% 保存刚刚修改的网络:

net.save('my_net.caffemodel');

% 得到layer 的类型(返回一个string):

layer_type = net.layers('conv1').type

% 调整网络形状

net.blobs('data').reshape([227 227 3 1]); % 改造blob 'data'

net.reshape();

因为Matlab的标号从1开始,且以列(column)为主,则blob通常的4个维度在Matlab中用 [width, height, channels, num]表示,width是第一维。另外,图像通道形式为BGR。Caffe使用单精度float型数据。如果你的数据不是单精度的,set_data 会自动将数据转换为单精度。

【前向/反向传播】

Forward 可使用 net.forward 或者 net.forward_prefilled函数。net.forward 传入包含了输入数据的N-D arrays形式的单元阵列(cell array),返回包含输出数据的单元阵列。函数

net.forward_prefilled 使用了forward 过程中,input blobs(s)中已经存在的数据。如果没有输入就不会产生输出。在创建了类似data = rand(net.blobs('data').shape); prob_diff = rand(net.blobs('prob').shape);的输入数据后,可运行

res = net.forward({data}); % 注意这里输入的cell,可以多个数据cell同时输入

prob = res{1}; % 输出也是cell,因为输入可以是多张图片,则会输出多个结果,每个结果用cell存放

% 或者

net.blobs('data').set_data(data);

net.forward_prefilled();

prob = net.blobs('prob').get_data();

res = net.backward({prob_diff});

data_diff = res{1};

% 或者

net.blobs('prob').set_diff(prob_diff);

net.backward_prefilled();

data_diff = net.blobs('data').get_diff();

然而,上述的backward计算不能得到正确结果,因为Caffe认为它不需要backward计算。为了得到正确的backward结果,你需要在网络prototxt文件中设置'force_backward: true' 。

【训练网络】

solver = caffe.Solver('./models/bvlc_reference_caffenet/solver.prototxt');

solver.solve();

% 或者只训练1000 iterations

solver.step(1000);

% 得到迭代次数:

iter = solver.iter();

% 得到训练/测试网络:

train_net = solver.net;

test_net = solver.test_nets(1);

% 从“your_snapshot.solverstate”的snapshot继续训练:

solver.restore('your_snapshot.solverstate');

【输入/输出】

caffe.io 类提供了基础的输入函数 load_image 和read_mean。

mean_data = caffe.io.read_mean('./data/ilsvrc12/imagenet_mean.binaryproto');

通过caffe.io.load_image函数加载的图像已经是BGR,[width,height]形式,可以直接使用。如果训练/测试要求的输入图像为其它尺寸,只需要改变大小就行,不用调整width,height的维度顺序。

im_data = caffe.io.load_image('./examples/images/cat.jpg'); % 格式,维度顺序符合要求

im_data = imresize(im_data, [width, height]); % 只需要改变训练/测试要求的尺寸即可,resize using Matlab's imresize

记住width 是第一维度,通道为BGR,这与Matlab通常存储图片的方式不同。 如果你不想使用 caffe.io.load_image,而想使用Matlab自带接口加载图像,你可以这样做:

im_data = imread('./examples/images/cat.jpg'); % read image

im_data = im_data(:, :, [3, 2, 1]); % 从RGB转换为BGR

im_data = permute(im_data, [2, 1, 3]); % 改变width与height位置;其实相当于对im_data(:,:)转置,即把width按照列排,因为matalb是以列为主的。

im_data = single(im_data); % 转换为单精度

这里一定要注意加载图片的通道顺序和维度顺序:

matlab中imread()函数得到的通道顺序是RGB,而网络训练时使用的是BGR;因此需要对通道顺序改变。但是,在matcaffe中,caffe.io.load_image()函数加载的通道顺序是BGR,通道顺序与训练网络模型使用的通道顺序一致。(这是由于在matcaffe接口中,caffe.io.load_image()内部已经将输入的图像转换为网络模型需要的数据形式,具体可查看源码)

Matlab中用 [width, height, channels, num]表示,width是第一维,因此一定要改变图片宽高的顺序。(注意matlab矩阵中是按照列排的,因此第一维的数据width是按照列存的)

因此,通过caffe.io.load_image()自带的函数加载的图像是符合训练/测试要求的,只需要改变图像的尺寸即可;而利用matlab自带的imread()加载的图像数据,则需要改变通道顺序(RGB-->BGR),宽高维度顺序([height,width]-->[width,height])。

特别注意!特别注意!特别注意!

在matcaffe中,caffe.io.load_image()获得的数据形式是BGR,W x H x C(matlab中blob维度顺序是W x H x C),[0,255](single),满足网络模型需要的数据格式,因此直接使用,不需要进行改变(除了改变大小);

但在pycaffe中,caffe.io.load_image()获得的数据形式是RGB, H x W x C,[0, 1],需要进行通道顺序改变RGB-->BGR,数据维度顺序改变H x W x C-->C x H x W ,以及动态范围的改变[0,1]-->[0,255]

详细介绍可以参考: Caffe实战(八):pycaffe和matcaffe接口读取图像的区别(特别注意通道顺序和维度顺序差别)

【清除Nets和Solvers】

Call caffe.reset_all() 清除你创建的所有solvers与/或独立的nets。

更多关于matcaffe的操作可以参考:Caffe实战(七):caffe可视化工具

4、 特征提取

在一些在线应用中,Caffe常用作图像特征提取器。使用特征提取器可以有效降低图像数据维度,从而降低传输带宽。

Caffe提供的使用工具build/tools/extract_features.bin实现了特征提取功能,该程序需要一个训练好的网络和一个数据输入层,运行后可以得到相应数据通过网络某个中间层产生的特征图并保存到磁盘。

源文件:tools/extrac_features.cpp

用法:extract_features param1 ...

pretrained_net_param 预训练的网络权值参数,*.caffemodel

feature_extraction_proto_file 网络描述文件,*.prototxt

extract_feature_blob_name1[,name2,...] 需要提取的Blob名

save_feature_dataset_name1[,name,...] 保存特征名

num_mini_batches 做特征提取的数据批量数目

db_type 使用数据的格式,LMDB或LEVELDB

[CPU/GPU] 使用CPU还是GPU

[DEVICE_ID=0] 如果使用GPU,则选择设备编号

例如:

./build/tools/extract_features.bin models/bvlc_reference_caffenet/bvlc_reference_caffenet.caffemodel \

models/bvls_reference_caffenet/train_val.prototxt \

fc6,fc7,fc8 \

myfc6,myfc7,myfc8 \

10 \

lmdb \

GPU \

1

5、 图像分类

caffe中自带图像分类工具,对输入一张图片,输出分类结果。源码在caffe\examples\cpp_classification\classification.cpp

rem 分类工具

rem 参数(1)网络模型定义文件

rem 参数(2)权值文件

rem 参数(3)均值文件

rem 参数(4)同义词词集文件

rem 参数(5)输入图像

..\\..\\Build\\x64\\Release\\classification.exe ..\\..\\models\\bvlc_reference_caffenet\\deploy.prototxt ..\\..\\models\\bvlc_reference_caffenet\\bvlc_reference_caffenet.caffemodel ..\\..\\data\\ilsvrc12\\imagenet_mean.binaryproto ..\\..\\data\\ilsvrc12\\synset_words.txt ..\\..\\examples\\images\\cat.jpg

6、 转换图像格式

在Caffe数据输入层所有数据都以LEVELDB或LMDB形式提供。为此需要在预处理阶段将不同数据集转换为LEVELDB或LMDB格式。

Caffe 采用 LMDB、 LEVELDB而不是直接读取原始数据的原因:

数据类型多种多样(有二进制文件、文本文件、编码后的图像文件如 JPEG或PNG、网络爬取的数据等),不可能用一套代码实现所有类型的输入数据读取,转换为统一格式可以简化数据读取层的实现;

使用 LMDB 、 LEVELDB 可以提高磁盘的利用率。

tools/中提供三种数据集的转换工具:

CIFAR10数据集

图像转换格式源文件 tools/covert_fifar_data.cpp

转换脚本 examples\cifar10\create_cifar10.sh

ImageNet数据集(只要是图像格式都可以转换为lmdb)

图像转换格式源文件 tools\covert_imageset.cpp

转换脚本 examples\imagenet\create_imagenet.sh

参数:

root_path 数据集的根目录,即train.txt,val.txt中相对路径的根目录

train/val.txt 训练或验证集图像列表(图像路径 标签)

lmdb_dir 保存训练和验证集数据的目录,如train_lmdb和val_lmdb目录,在当前目录下创建

--resize_height=new_h 改变图像高

--resize_width=new_w 改变图像宽

--shuffle

例如:/path/caffe/convert_imageset ./ train.txt train_lmdb -resize_width 224 -resize_width 224 --shuffle

/path/caffe/convert_imageset ./ val.txt val_lmdb -resize_width 224 -resize_width 224 --shuffle

MNIST数据集

图像转换格式源文件 tools\convert_mnist_data.cpp

图像转换格式源文件 tools\convert_mnist_siamese_data.cpp

转换脚本 examples\mnist\create_mnist.sh

说明:具体的工具使用方法(如何转换图像格式)可以参考create_***.sh 脚本。

7、 计算图像均值

在数据读取层的Transform阶段,需要去均值操作。均值文件一般需要用原始数据计算得到。

源文件 tools/compute_image_mean.cpp

rem 计算均值

rem 参数(1)训练数据路径;--backend=lmdb 指的是数据格式

rem 参数(2)输出均值文件路径和名称

..\\..\\Build\\x64\\Release\\compute_image_mean.exe --backend=lmdb ..\\..\\examples\\cifar10\\cifar10_train_lmdb ..\\..\\examples\\cifar10\\mean.binaryproto

8、 绘制学习(Loss和Accuracy)曲线

在训练过程中画出accuracy和loss曲线能够更直观的观察网络训练的状态,以便更好的优化网络的训练。通过学习曲线,可以评估当前训练状态:

train loss不断下降,test loss不断下降,说明网络仍然在认真学习;

train loss不断下降,test loss趋于不变,说明网络过拟合;

train loss趋于不变,test loss趋于不变, 说明学习遇到瓶颈,需减小学习速率或批量数据尺寸;

train loss趋于不变,test loss不断下降,说明数据集100%有问题;

train loss不断上升,test loss不断上升(最终变为NaN),可能是网络结构设计不当、训练超参数设置不当、程序bug等某个问题引起的,需要进一步定位。

本文主要介绍在基于caffe框架训练网络时,利用caffe自带的工具包来绘制曲线。

caffe中自带小工具:

caffe-master/tools/extra/parse_log.sh (或parse_log.py)

caffe-master/tools/extra/extract_seconds.py(计算每次迭代的累计用时)

caffe-master/tools/extra/plot_training_log.py.example (在使用时去掉“.example”后缀)

【第一步】训练模型并保存日志文件

在训练过程中把终端输出的结果保存为一个日志文件,注意文件的后缀名必须是.log,这是因为在解析日志文件时有这个要求。

方法一:Linux数据重导向

TOOLS=./build/tools

$TOOLS/caffe train --solver=myself/road/prototxt_files/solver.prototxt 2>&1 | tee myself/road/Log/train_road_20180525.log

# 可用下面的命令可以提取log文件中的loss值:

cat train_road_20150525.log | grep "Train net output" | awk '{print $11}'

其中grep找到所有包含"Train net output"的行;

awk根据空格将该行分成若干个字段,并打印数值结果字段,也就是字段11

windous下可以利用cmd进行重导向(注意和linux下有些区别)

caffe train --solver=myself/road/prototxt_files/solver.prototxt > myself/road/Log/train_road_20180525.log 2>&1

方法二:Log命令参数

TOOLS=./build/tools

GLOG_logtostderr=0 GLOG_log_dir=myself/road/Log/ \ #保存log文件

$TOOLS/caffe train --solver=deepid/deepid2/deepid_solver.prototxt

在Linux下可以编写一个.sh脚本,按照时间命名输出的Log文件名,如下所示

#!/usr/bin/env sh

set -e

LOG_DIR=examples/mnist/log/

#echo $LOG_DIR

if [ ! -d $LOG_DIR ]; then

mkdir $LOG_DIR

fi

LOG_NAME=log-`date +%Y-%m-%d-%H-%M-%S`.log

#echo $LOG_NAME

LOG_PATH="$LOG_DIR$LOG_NAME"

echo "log path $LOG_PATH"

./build/tools/caffe train --solver=examples/mnist/lenet_solver.prototxt 2>&1 | tee $LOG_PATH $@

【第二步】解析训练日志

利用caffe中tools/extra文件夹下的parse_log.py来解析日志文件。 (提示:这里记得不要使用python3,因为有些地方不兼容,如果只有python3,那你可以根据提示修改相应的代码,下同。)

# 使用Python,注意不要忘记输出路径

# 这一步可不单独进行,因为在plot_training_log.py中已经导入该功能

python parse_log.py train_road_20180525.log ./ # log日志文件和parse_log.py(.sh)放在同一目录下面

# 使用脚本(linux)

sh parse_log.sh train_road_20180525.log

运行结束之后,会在当前文件夹下生成一个train_road_20180525.log.train文件和一个train_road_20180525.log.test文件。

【第三步】生成图片

python plot_training_log.py 0 save_train_road_0525.png train_road_0525.log

参数:

0 -- 图类型编号

.png -- 要保存的图像名

.log -- 输入的Log信息

图类型编号0-7代表的含义(create_field_index()中设置的key顺序有关,并不是唯一的 ):

Notes:

1. Supporting multiple logs.

2. Log file name must end with the lower-cased ".log".

Supported chart types:

0: Test accuracy vs. Iters (测试阶段准确率与迭代次数图)

1: Test accuracy vs. Seconds (测试阶段准确率与时间图)

2: Test learning rate vs. Iters (测试阶段学习率与迭代次数图)

3: Test learning rate vs. Seconds (测试阶段学习率与时间图)

4: Test loss vs. Iters (测试阶段损失与迭代次数图)

5: Test loss vs. Seconds (测试阶段损失与时间图)

6: Train learning rate vs. Iters (训练阶段学习率与迭代次数图)

7: Train learning rate vs. Seconds (训练阶段学习率与时间图)

8: Train loss vs. Iters (训练阶段损失与迭代次数图)

9: Train loss vs. Seconds (训练阶段损失与时间图)

【提取迭代时间】利用extract_seconds.py提取log日志中的迭代时间(seconds)

注意输出的是每次迭代相对初始时间的间隔。

在extract_seconds.py中,初始时间是从"Solving LeNet"行开始的,计算含有”Iteration"行的时间间隔(相对初始时间)。

提取log日志中的迭代时间(seconds)

参数(1)输入文件(.log);(2)输出文件

python2 extract_seconds.py lenet_train_test.log seconds.log

在windows平台上需要修改plot_training_log.py文件,具体可以参考:Caffe-Windows下画loss与accuracy曲线

【第一步】修改子函数plot_chart() 默认解析Log脚本

由于plot_training_log.py文件默认是调用parse_log.sh脚本文件的,而该文件在windows上无法调用,需要修改为调用parse_log.py文件;

注释掉 get_log_parsing_script()函数,并在plot_chart函数内部修改如下:

记得要在文件开头导入parse_log模块:import parse_log

【第二步】修改子函数creat_field_index()的key顺序

一定要和生成的**.log.test和**.log.train文件的第一行的顺序一致,注意这里只是修改顺序,添加(删减)项目,不要修改函数中的key名称;

如我的lenet_train_test.log.test文件开头如下所示:

第一行的NumIters对应的是函数的Iters,LearningRate对应函数的learing rate等等。一定要记住,只要保证顺序对应起来就可以,不必要求名称一致(文件的名称和函数中的key是相互独立的,这里不是通过名称查找对应的值,而是根据所在的索引位置取相应列的值)

【第三步】修改子函数load_data()

由于解析log文件生成的**.log.test和**.log.train文件的第一行是名称(字符),无法转换为float类型,因此从文件从第二行开始提取;这里需要注意两个地方:

f.readlines()函数读取所有行数据,而f.readline()读取一行数据,这里一定要留意(我就是一开始少了一个's',每次都读取第一行的名称数据,导致运行失败,找了好长时间才发现这个问题)

每行的分隔符可能是',',也可能是空格,需要根据生成的 **.log.test和**.log.train文件的格式去相应的修改;

保存的图片默认是png格式的,如果不是该格式系统会报错误;若要支持其他格式,可修改下面代码

9、 可视化工具

包括数据可视化,网络模型可视化,权值参数可视化以及特征图可视化

参考 Caffe实战(七):caffe可视化工具

参考

https://www.cnblogs.com/carle-09/p/9088347.html

实战干货:龙虎榜到底怎么看?别再盲目跟票了!
正确方法将电子书放到Kindle的documents文件夹,还是看不了书?