深度学习框架对比
作者:互联网
一.caffe
1.1定义
Caffe 是以 C++/CUDA 代码为主,最早的深度学习框架之一,比 TensorFlow、Mxnet、Pytorch 等都更早,支持命令行、Python 和 Matlab 接口,单机多卡、多机多卡等都可以很方便的使用,CPU 和 GPU 之间无缝切换。对于入门级别的任务,如图像分类,Caffe 上手的成本最低,几乎不需要写一行代码就可以开始训练,所以我推荐 Caffe 作为入门学习的框架。Caffe 相对于 TensorFlow 等使用 pip 一键安装的方式来说,编译安装稍微麻烦一些,但其实依旧很简单,我们以 Ubuntu 16.04 为例子,官网的安装脚本足够用了,它有一些依赖库。
Caffe 完成一个训练,必要准备以下资料:一个是 train.prototxt 作为网络配置文件,另一个是 solver.prototxt 作为优化参数配置文件,再一个是训练文件 list。
另外,在大多数情况下,需要一个预训练模型作为权重的初始化。
1.2.训练
(1)准备网络配置文件
我们准备了一个 3*3 的卷积神经网络,它的 train.prototxt 文件是这样的:
type: "ImageData"
top: "data"
top: "clc-label"
image_data_param {
source: "all_shuffle_train.txt"
batch_size: 96
shuffle: true
}
transform_param {
mean_value: 104.008
mean_value: 116.669
mean_value: 122.675
crop_size: 48
mirror: true
}
include: { phase: TRAIN}
}
layer {
name: "data"
type: "ImageData"
top: "data"
top: "clc-label"
image_data_param {
source: "all_shuffle_val.txt"
batch_size: 30
shuffle: false
}
transform_param {
mean_value: 104.008
mean_value: 116.669
mean_value: 122.675
crop_size: 48
mirror: false
}
include: { phase: TEST}
}
layer {
name: "conv1"
type: "Convolution"
bottom: "data"
top: "conv1"
param {
lr_mult: 1
decay_mult: 1
}
param {
lr_mult: 2
decay_mult: 0
}
convolution_param {
num_output: 12
pad: 1
kernel_size: 3
stride: 2
weight_filler {
type: "xavier"
std: 0.01
}
bias_filler {
type: "constant"
value: 0.2
}
}
}
layer {
name: "relu1"
type: "ReLU"
bottom: "conv1"
top: "conv1"
}
layer {
name: "conv2"
type: "Convolution"
bottom: "conv1"
top: "conv2"
param {
lr_mult: 1
decay_mult: 1
}
param {
lr_mult: 2
decay_mult: 0
}
convolution_param {
num_output: 20
kernel_size: 3
stride: 2
pad: 1
weight_filler {
type: "xavier"
std: 0.1
}
bias_filler {
type: "constant"
value: 0.2
}
}
}
layer {
name: "relu2"
type: "ReLU"
bottom: "conv2"
top: "conv2"
}
layer {
name: "conv3"
type: "Convolution"
bottom: "conv2"
top: "conv3"
param {
lr_mult: 1
decay_mult: 1
}
param {
lr_mult: 2
decay_mult: 0
}
convolution_param {
num_output: 40
kernel_size: 3
stride: 2
pad: 1
weight_filler {
type: "xavier"
std: 0.1
}
bias_filler {
type: "constant"
value: 0.2
}
}
}
layer {
name: "relu3"
type: "ReLU"
bottom: "conv3"
top: "conv3"
}
layer {
name: "ip1-mouth"
type: "InnerProduct"
bottom: "conv3"
top: "pool-mouth"
param {
lr_mult: 1
decay_mult: 1
}
param {
lr_mult: 2
decay_mult: 0
}
inner_product_param {
num_output: 128
weight_filler {
type: "xavier"
}
bias_filler {
type: "constant"
value: 0
}
}
}
layer {
bottom: "pool-mouth"
top: "fc-mouth"
name: "fc-mouth"
type: "InnerProduct"
param {
lr_mult: 1
decay_mult: 1
}
param {
lr_mult: 2
decay_mult: 1
}
inner_product_param {
num_output: 2
weight_filler {
type: "xavier"
}
bias_filler {
type: "constant"
value: 0
}
}
}
layer {
bottom: "fc-mouth"
bottom: "clc-label"
name: "loss"
type: "SoftmaxWithLoss"
top: "loss"
}
layer {
bottom: "fc-mouth"
bottom: "clc-label"
top: "acc"
name: "acc"
type: "Accuracy"
include {
phase: TRAIN
}
include {
phase: TEST
}
}
可以看出,Caffe 的这个网络配置文件,每一个卷积层,都是以 layer{} 的形式定义,layer 的bottom、top 就是它的输入输出,type 就是它的类型,有的是数据层、有的是卷积层、有的是 loss 层。
我们采用 netscope 来可视化一下这个模型。
从上面看很直观的看到,网络的输入层是 data 层,后面接了3个卷积层,其中每一个卷积层都后接了一个 relu 层,最后 ip1-mouth、fc-mouth 是全连接层。Loss 和 acc 分别是计算 loss 和 acc 的层。
各层的配置有一些参数,比如 conv1 有卷积核的学习率、卷积核的大小、输出通道数、初始化方法等,这些可以后续详细了解。
(3)准备优化配置文件:
net: "./train.prototxt"
test_iter: 100
test_interval: 10
base_lr: 0.00001
momentum: 0.9
type: "Adam"
lr_policy: "fixed"
display: 100
max_iter: 10000
snapshot: 2000
snapshot_prefix: "./snaps/conv3_finetune"
solver_mode: GPU
介绍一下上面的参数。
net 是网络的配置路径。test_interval是指训练迭代多少次之后,进行一次测试。test_iter是测试多少个batch,如果它等于 1,就说明只取一个 batchsize 的数据来做测试,如果 batchsize 太小,那么对于分类任务来说统计出来的指标也不可信,所以最好一次测试,用到所有测试数据。因为,常令test_iter*test_batchsize=测试集合的大小。
base_lr、momentum、type、lr_policy是和学习率有关的参数,base_lr和lr_policy决定了学习率大小如何变化。type 是优化的方法,以后再谈。max_iter是最大的迭代次数,snapshot 是每迭代多少次之后存储迭代结果,snapshot_prefix为存储结果的目录,caffe 存储的模型后缀是 .caffemodel。solver_mode可以指定用 GPU 或者 CPU 进行训练。
1.3.测试
train.prototxt 与 test.prototxt 的区别
训练时的网络配置与测试时的网络配置是不同的,测试没有 acc 层,也没有 loss 层,取输出的 softmax 就是分类的结果。同时,输入层的格式也有出入,不需要再输入 label,也不需要指定图片 list,但是要指定输入尺度,我们看一下 test.prototxt 和可视化结果。
使用 Python 进行测试
由于 Python 目前广泛使用,下面使用 Python 来进行测试,它要做的就是导入模型、导入图片、输出结果。
下面是所有的代码,我们详细解释下:
---代码段1,这一段,我导入一些基本库,同时导入caffe的路径---
#_*_ coding:utf8
import sys
sys.path.insert(0, '../../../../../libs/Caffe_Long/python/')
import caffe
import os,shutil
import numpy as np
from PIL import Image as PILImage
from PIL import ImageMath
import matplotlib.pyplot as plt
import time
import cv2
---代码段2,这一段,我们添加一个参数解释器,方便参数管理---
debug=True
import argparse
def parse_args():
parser = argparse.ArgumentParser(description='test resnet model for portrait segmentation')
parser.add_argument('--model', dest='model_proto', help='the model', default='test.prototxt', type=str)
parser.add_argument('--weights', dest='model_weight', help='the weights', default='./test.caffemodel', type=str)
parser.add_argument('--testsize', dest='testsize', help='inference size', default=60,type=int)
parser.add_argument('--src', dest='img_folder', help='the src image folder', type=str, default='./')
parser.add_argument('--gt', dest='gt', help='the gt', type=int, default=0)
args = parser.parse_args()
return args
def start_test(model_proto,model_weight,img_folder,testsize):
---代码段3,这一段,我们就完成了网络的初始化---
caffe.set_device(0)
#caffe.set_mode_cpu()
net = caffe.Net(model_proto, model_weight, caffe.TEST)
imgs = os.listdir(img_folder)
pos = 0
neg = 0
for imgname in imgs:
---代码段4,这一段,是读取图片并进行预处理,还记得我们之前的训练,是采用 BGR 的输入格式,减去了图像均值吧,同时,输入网络的图像,也需要 resize 到相应尺度。预处理是通过 caffe 的类,transformer 来完成,set_mean 完成均值,set_transpose 完成维度的替换,因为 caffe blob 的格式是 batch、channel、height、width,而 numpy 图像的维度是 height、width、channel 的顺序---
imgtype = imgname.split('.')[-1]
imgid = imgname.split('.')[0]
if imgtype != 'png' and imgtype != 'jpg' and imgtype != 'JPG' and imgtype != 'jpeg' and imgtype != 'tif' and imgtype != 'bmp':
print imgtype,"error"
continue
imgpath = os.path.join(img_folder,imgname)
img = cv2.imread(imgpath)
if img is None:
print "---------img is empty---------",imgpath
continue
img = cv2.resize(img,(testsize,testsize))
transformer = caffe.io.Transformer({'data': net.blobs['data'].data.shape})
transformer.set_mean('data', np.array([104.008,116.669,122.675]))
transformer.set_transpose('data', (2,0,1))
---代码段5,这一段,就得到了输出结果了,并做一些可视化显示---
out = net.forward_all(data=np.asarray([transformer.preprocess('data', img)]))
result = out['prob'][0]
print "---------result prob---------",result,"-------result size--------",result.shape
probneutral = result[0]
print "prob neutral",probneutral
probsmile = result[1]
print "prob smile",probsmile
problabel = -1
probstr = 'none'
if probneutral > probsmile:
probstr = "neutral:"+str(probneutral)
pos = pos + 1
else:
probstr = "smile:"+str(probsmile)
neg = neg + 1
if debug:
showimg = cv2.resize(img,(256,256))
cv2.putText(showimg,probstr,(30,50),cv2.FONT_HERSHEY_SIMPLEX,1,(0,0,255),1)
cv2.imshow("test",showimg)
k = cv2.waitKey(0)
if k == ord('q'):
break
print "pos=",pos
print "neg=",neg
if __name__ == '__main__':
args = parse_args()
start_test(args.model_proto,args.model_weight,args.img_folder,args.testsize)
二.Tensorflow
2.1.定义
TensorFlow 是 Google brain 推出的开源机器学习库,与 Caffe 一样,主要用作深度学习相关的任务。
与 Caffe 相比 TensorFlow 的安装简单很多,一条 pip 命令就可以解决,新手也不会误入各种坑。
TensorFlow = Tensor + Flow
Tensor 就是张量,代表 N 维数组,与 Caffe 中的 blob 是类似的;Flow 即流,代表基于数据流图的计算。
神经网络的运算过程,就是数据从一层流动到下一层,在 Caffe 的每一个中间 layer 参数中,都有 bottom 和 top,这就是一个分析和处理的过程。TensorFlow更直接强调了这个过程。
TensorFlow 最大的特点是计算图,即先定义好图,然后进行运算,所以所有的TensorFlow 代码,都包含两部分:
(1)创建计算图,表示计算的数据流。它做了什么呢?实际上就是定义好了一些操作,你可以将它看做是 Caffe 中的 prototxt 的定义过程。
(2)运行会话,执行图中的运算,可以看作是 Caffe 中的训练过程。只是TensorFlow的会话比 Caffe 灵活很多,由于是 Python 接口,取中间结果分析,Debug 等方便很多。
2.2训练
有了 TensorFlow 这个工具后,我们接下来的任务就是开始训练模型。训练模型,包括数据准备、模型定义、结果保存与分析。
2.2.1 数据准备
如果想定义自己的输入格式,可以去新建自定义的 Data Layer,而 Caffe 官方的 data layer 和 imagedata layer 都非常稳定,几乎没有变过,这是我更欣赏 Caffe 的一个原因。因为输入数据,简单即可。相比之下,TensorFlow 中的数据输入接口就要复杂很多,更新也非常快,我知乎有一篇文章,说过从《从 Caffe 到 TensorFlow 1,IO 操作》,有兴趣的读者可以了解一下。
这里我们不再说 TensorFlow 中有多少种数据 IO 方法,先确定好我们的数据格式,那就是跟 Caffe一样,准备好一个list,它的格式一样是 image、labelid,然后再看如何将数据读入 TensorFlow 进行训练。我们定义一个类,叫 imagedata,模仿 Caffe 中的使用方式。代码如下,
import tensorflow as tf
from tensorflow.contrib.data import Dataset
from tensorflow.python.framework import dtypes
from tensorflow.python.framework.ops import convert_to_tensor
import numpy as np
class ImageData:
def read_txt_file(self):
self.img_paths = []
self.labels = []
for line in open(self.txt_file, 'r'):
items = line.split(' ')
self.img_paths.append(items[0])
self.labels.append(int(items[1]))
def __init__(self, txt_file, batch_size, num_classes,
image_size,buffer_scale=100):
self.image_size = image_size
self.batch_size = batch_size
self.txt_file = txt_file ##txt list file,stored as: imagename id
self.num_classes = num_classes
buffer_size = batch_size * buffer_scale
# 读取图片
self.read_txt_file()
self.dataset_size = len(self.labels)
print "num of train datas=",self.dataset_size
# 转换成Tensor
self.img_paths = convert_to_tensor(self.img_paths, dtype=dtypes.string)
self.labels = convert_to_tensor(self.labels, dtype=dtypes.int32)
# 创建数据集
data = Dataset.from_tensor_slices((self.img_paths, self.labels))
print "data type=",type(data)
data = data.map(self.parse_function)
data = data.repeat(1000)
data = data.shuffle(buffer_size=buffer_size)
# 设置self data Batch
self.data = data.batch(batch_size)
print "self.data type=",type(self.data)
def augment_dataset(self,image,size):
distorted_image = tf.image.random_brightness(image,
max_delta=63)
distorted_image = tf.image.random_contrast(distorted_image,
lower=0.2, upper=1.8)
# Subtract off the mean and divide by the variance of the pixels.
float_image = tf.image.per_image_standardization(distorted_image)
return float_image
def parse_function(self, filename, label):
label_ = tf.one_hot(label, self.num_classes)
img = tf.read_file(filename)
img = tf.image.decode_jpeg(img, channels=3)
img = tf.image.convert_image_dtype(img, dtype = tf.float32)
img = tf.random_crop(img,[self.image_size[0],self.image_size[1],3])
img = tf.image.random_flip_left_right(img)
img = self.augment_dataset(img,self.image_size)
return img, label_
下面来分析上面的代码,类是 ImageData,它包含几个函数,__init__构造函数,read_txt_file数据读取函数,parse_function数据预处理函数,augment_dataset数据增强函数。
构造函数分为以下几个步骤:
(1)读取变量,文本 list 文件txt_file,批处理大小batch_size,类别数num_classes,要处理成的图片大小image_size,一个内存变量buffer_scale=100。
(2)在获取完这些值之后,就到了read_txt_file函数。代码很简单,就是利用self.img_paths和 self.labels存储输入 txt 中的文件列表和对应的 label,这一点和 Caffe 很像了。
(3)然后,就是分别将img_paths和 labels 转换为 Tensor,函数是convert_to_tensor,这是 Tensor 内部的数据结构。
(4)创建 dataset,Dataset.from_tensor_slices,这一步,是为了将 img 和 label 合并到一个数据格式,此后我们将利用它的接口,来循环读取数据做训练。当然,创建好 dataset 之后,我们需要给它赋值才能真正的有数据。data.map 就是数据的预处理,包括读取图片、转换格式、随机旋转等操作,可以在这里做。
data = data.repeat(1000) 是将数据复制 1000 份,这可以满足我们训练 1000 个 epochs。data = data.shuffle(buffer_size=buffer_size)就是数据 shuffle 了,buffer_size就是在做 shuffle 操作时的控制变量,内存越大,就可以用越大的值。
(5)给 selft.data 赋值,我们每次训练的时候,是取一个 batchsize 的数据,所以 self.data = data.batch(batch_size),就是从上面创建的 dataset 中,一次取一个 batch 的数据。
到此,数据接口就定义完毕了,接下来在训练代码中看如何使用迭代器进行数据读取就可以了。
2.2 .2模型定义
创建数据接口后,我们开始定义一个网络。
def simpleconv3net(x):
x_shape = tf.shape(x)
with tf.variable_scope("conv3_net"):
conv1 = tf.layers.conv2d(x, name="conv1", filters=12,kernel_size=[3,3], strides=(2,2), activation=tf.nn.relu,kernel_initializer=tf.contrib.layers.xavier_initializer(),bias_initializer=tf.contrib.layers.xavier_initializer())
bn1 = tf.layers.batch_normalization(conv1, training=True, name='bn1')
conv2 = tf.layers.conv2d(bn1, name="conv2", filters=24,kernel_size=[3,3], strides=(2,2), activation=tf.nn.relu,kernel_initializer=tf.contrib.layers.xavier_initializer(),bias_initializer=tf.contrib.layers.xavier_initializer())
bn2 = tf.layers.batch_normalization(conv2, training=True, name='bn2')
conv3 = tf.layers.conv2d(bn2, name="conv3", filters=48,kernel_size=[3,3], strides=(2,2), activation=tf.nn.relu,kernel_initializer=tf.contrib.layers.xavier_initializer(),bias_initializer=tf.contrib.layers.xavier_initializer())
bn3 = tf.layers.batch_normalization(conv3, training=True, name='bn3')
conv3_flat = tf.reshape(bn3, [-1, 5 * 5 * 48])
dense = tf.layers.dense(inputs=conv3_flat, units=128, activation=tf.nn.relu,name="dense",kernel_initializer=tf.contrib.layers.xavier_initializer())
logits= tf.layers.dense(inputs=dense, units=2, activation=tf.nn.relu,name="logits",kernel_initializer=tf.contrib.layers.xavier_initializer())
if debug:
print "x size=",x.shape
print "relu_conv1 size=",conv1.shape
print "relu_conv2 size=",conv2.shape
print "relu_conv3 size=",conv3.shape
print "dense size=",dense.shape
print "logits size=",logits.shape
return logits
上面就是我们定义的网络,是一个简单的3层卷积。在 tf.layers 下,有各种网络层,这里就用到了 tf.layers.conv2d,tf.layers.batch_normalization和 tf.layers.dense,分别是卷积层,BN 层和全连接层。我们以一个卷积层为例:
conv1 = tf.layers.conv2d(x, name="conv1", filters=12,kernel_size=[3,3], strides=(2,2), activation=tf.nn.relu,kernel_initializer=tf.contrib.layers.xavier_initializer(),bias_initializer=tf.contrib.layers.xavier_initializer())
x 即输入,name 是网络名字,filters 是卷积核数量,kernel_size即卷积核大小,strides 是卷积 stride,activation 即激活函数,kernel_initializer和bias_initializer分别是初始化方法。可见已经将激活函数整合进了卷积层,更全面的参数,请自查 API。其实网络的定义,还有其他接口,tf.nn、tf.layers、tf.contrib,各自重复,在我看来有些混乱。这里之所以用 tf.layers,就是因为参数丰富,适合从头训练一个模型。
2.3 模型训练
from dataset import *
from net import simpleconv3net
import sys
import os
import cv2
////-------1 定义一些全局变量-------////
txtfile = sys.argv[1]
batch_size = 64
num_classes = 2
image_size = (48,48)
learning_rate = 0.0001
debug=False
if __name__=="__main__":
////-------2 载入网络结构,定义损失函数,创建计算图-------////
dataset = ImageData(txtfile,batch_size,num_classes,image_size)
iterator = dataset.data.make_one_shot_iterator()
dataset_size = dataset.dataset_size
batch_images,batch_labels = iterator.get_next()
Ylogits = simpleconv3net(batch_images)
print "Ylogits size=",Ylogits.shape
Y = tf.nn.softmax(Ylogits)
cross_entropy = tf.nn.softmax_cross_entropy_with_logits(logits=Ylogits, labels=batch_labels)
cross_entropy = tf.reduce_mean(cross_entropy)
correct_prediction = tf.equal(tf.argmax(Y, 1), tf.argmax(batch_labels, 1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))
update_ops = tf.get_collection(tf.GraphKeys.UPDATE_OPS)
with tf.control_dependencies(update_ops):
train_step = tf.train.AdamOptimizer(learning_rate).minimize(cross_entropy)
saver = tf.train.Saver()
in_steps = 100
checkpoint_dir = 'checkpoints/'
if not os.path.exists(checkpoint_dir):
os.mkdir(checkpoint_dir)
log_dir = 'logs/'
if not os.path.exists(log_dir):
os.mkdir(log_dir)
summary = tf.summary.FileWriter(logdir=log_dir)
loss_summary = tf.summary.scalar("loss", cross_entropy)
acc_summary = tf.summary.scalar("acc", accuracy)
image_summary = tf.summary.image("image", batch_images)
////-------3 执行会话,保存相关变量,还可以添加一些debug函数来查看中间结果-------////
with tf.Session() as sess:
init = tf.global_variables_initializer()
sess.run(init)
steps = 10000
for i in range(steps):
_,cross_entropy_,accuracy_,batch_images_,batch_labels_,loss_summary_,acc_summary_,image_summary_ = sess.run([train_step,cross_entropy,accuracy,batch_images,batch_labels,loss_summary,acc_summary,image_summary])
if i % in_steps == 0 :
print i,"iterations,loss=",cross_entropy_,"acc=",accuracy_
saver.save(sess, checkpoint_dir + 'model.ckpt', global_step=i)
summary.add_summary(loss_summary_, i)
summary.add_summary(acc_summary_, i)
summary.add_summary(image_summary_, i)
#print "predict=",Ylogits," labels=",batch_labels
if debug:
imagedebug = batch_images_[0].copy()
imagedebug = np.squeeze(imagedebug)
print imagedebug,imagedebug.shape
print np.max(imagedebug)
imagelabel = batch_labels_[0].copy()
print np.squeeze(imagelabel)
imagedebug = cv2.cvtColor((imagedebug*255).astype(np.uint8),cv2.COLOR_RGB2BGR)
cv2.namedWindow("debug image",0)
cv2.imshow("debug image",imagedebug)
k = cv2.waitKey(0)
if k == ord('q'):
break
2.4 可视化
TensorFlow 很方便的一点,就是 Tensorboard 可视化。Tensorboard 的具体原理就不细说了,很简单,就是三步。
第一步,创建日志目录。
log_dir = 'logs/'
if not os.path.exists(log_dir): os.mkdir(log_dir)
第二步,创建 summary 操作并分配标签,如我们要记录 loss、acc 和迭代中的图片,则创建了下面的变量:
loss_summary = tf.summary.scalar("loss", cross_entropy)acc_summary = tf.summary.scalar("acc", accuracy)image_summary = tf.summary.image("image", batch_images)
第三步,session 中记录结果,如下面代码:
_,cross_entropy_,accuracy_,batch_images_,batch_labels_,loss_summary_,acc_summary_,image_summary_ = sess.run([train_step,cross_entropy,accuracy,batch_images,batch_labels,loss_summary,acc_summary,image_summary])
查看训练过程和最终结果时使用:
tensorboard --logdir=logs
2.5TensorFlow 测试
import tensorflow as tf
from net import simpleconv3net
import sys
import numpy as np
import cv2
import os
testsize = 48
x = tf.placeholder(tf.float32, [1,testsize,testsize,3])
y = simpleconv3net(x)
y = tf.nn.softmax(y)
lines = open(sys.argv[2]).readlines()
count = 0
acc = 0
posacc = 0
negacc = 0
poscount = 0
negcount = 0
with tf.Session() as sess:
init = tf.global_variables_initializer()
sess.run(init)
saver = tf.train.Saver()
saver.restore(sess,sys.argv[1])
#test one by one, you can change it into batch inputs
for line in lines:
imagename,label = line.strip().split(' ')
img = tf.read_file(imagename)
img = tf.image.decode_jpeg(img,channels = 3)
img = tf.image.convert_image_dtype(img,dtype = tf.float32)
img = tf.image.resize_images(img,(testsize,testsize),method=tf.image.ResizeMethod.NEAREST_NEIGHBOR)
img = tf.image.per_image_standardization(img)
imgnumpy = img.eval()
imgs = np.zeros([1,testsize,testsize,3],dtype=np.float32)
imgs[0:1,] = imgnumpy
result = sess.run(y, feed_dict={x:imgs})
result = np.squeeze(result)
if result[0] > result[1]:
predict = 0
else:
predict = 1
count = count + 1
if str(predict) == '0':
negcount = negcount + 1
if str(label) == str(predict):
negacc = negacc + 1
acc = acc + 1
else:
poscount = poscount + 1
if str(label) == str(predict):
posacc = posacc + 1
acc = acc + 1
print result
print "acc = ",float(acc) / float(count)
print "poscount=",poscount
print "posacc = ",float(posacc) / float(poscount)
print "negcount=",negcount
print "negacc = ",float(negacc) / float(negcount)
从上面的代码可知,与 Train 时同样,需要定义模型,这个跟 Caffe 在测试时使用的 Deploy 是一样的。
然后,用 restore 函数从 saver 中载入参数,读取图像并准备好网络的格式,sess.run 就可以得到最终的结果了。
三、pytorch
3.1定义
一句话总结 Pytorch = Python + Torch。
Torch 是纽约大学的一个机器学习开源框架,几年前在学术界非常流行,包括 Lecun等大佬都在使用。但是由于使用的是一种绝大部分人绝对没有听过的 Lua 语言,导致很多人都被吓退。后来随着 Python 的生态越来越完善,Facebook 人工智能研究院推出了Pytorch并开源。Pytorch不是简单的封装 Torch并提供Python接口,而是对Tensor以上的所有代码进行了重构,同TensorFlow一样,增加了自动求导。
后来Caffe2全部并入Pytorch,如今已经成为了非常流行的框架。很多最新的研究如风格化、GAN 等大多数采用Pytorch源码。
3.1.1 特点
(1)动态图计算。TensorFlow从静态图发展到了动态图机制Eager Execution,pytorch则一开始就是动态图机制。动态图机制的好处就是随时随地修改,随处debug,没有类似编译的过程。
(2)简单。相比TensorFlow中Tensor、Variable、Session等概念充斥,数据读取接口频繁更新,tf.nn、tf.layers、tf.contrib各自重复,Pytorch则是从Tensor到Variable再到nn.Module,最新的Pytorch已经将Tensor和Variable合并,这分别就是从数据张量到网络的抽象层次的递进。有人调侃TensorFlow的设计是“make it complicated”,那么 Pytorch的设计就是“keep it simple”。
3.1.2 重要概念
(1)Tensor/Variable
每一个框架都有基本的数据结构,Caffe是blob,TensorFlow和Pytorch都是Tensor,都是高维数组。Pytorch中的Tensor使用与Numpy的数组非常相似,两者可以互转且共享内存。
tensor包括cpu和gpu两种类型,如torch.FloatTensortorch.cuda.FloatTensorvirable,就分别表示cpu和gpu下的32位浮点数。
tensor包含一些属性。data,即Tensor内容;Grad,是与data对应的梯度;requires_grad,是否容许进行反向传播的学习,更多的可以去查看API。
(2)nn.module
抽象好的网络数据结构,可以表示为网络的一层,也可以表示为一个网络结构,这是一个基类。在实际使用过程中,经常会定义自己的网络,并继承nn.Module。具体的使用,我们看下面的网络定义吧。
(3)torchvision包,包含了目前流行的数据集,模型结构和常用的图片转换工具
3.2 训练介绍
训练模型包括数据准备、模型定义、结果保存与分析。
3.2.1 数据读取
前面已经介绍了Caffe和TensorFlow的数据读取,两者的输入都是图片list,但是读取操作过程差异非常大,Pytorch与这两个又有很大的差异。这一次,直接利用文件夹作为输入,这是 Pytorch更加方便的做法。数据读取的完整代码如下:
data_dir = '../../../../datas/head/'
data_transforms = {
'train': transforms.Compose([
transforms.RandomSizedCrop(48),
transforms.RandomHorizontalFlip(),
transforms.ToTensor(),
transforms.Normalize([0.5,0.5,0.5], [0.5,0.5,0.5])
]),
'val': transforms.Compose([
transforms.Scale(64),
transforms.CenterCrop(48),
transforms.ToTensor(),
transforms.Normalize([0.5,0.5,0.5], [0.5,0.5,0.5])
]),
}
image_datasets = {x: datasets.ImageFolder(os.path.join(data_dir, x),
data_transforms[x]) for x in ['train', 'val']}
dataloders = {x: torch.utils.data.DataLoader(image_datasets[x],
batch_size=16,
shuffle=True,
num_workers=4) for x in ['train', 'val']}
(1)datasets.ImageFolder
Pytorch的torchvision模块中提供了一个dataset 包,它包含了一些基本的数据集如mnist、coco、imagenet和一个通用的数据加载器ImageFolder。
magefolder有3个成员变量。
self.classes:用一个list保存类名,就是文件夹的名字。
self.class_to_idx:类名对应的索引,可以理解为 0、1、2、3 等。
self.imgs:保存(imgpath,class),是图片和类别的数组。
不同文件夹下的图,会被当作不同的类,天生就用于图像分类任务。
(2)Transforms
这一点跟Caffe非常类似,就是定义了一系列数据集的预处理和增强操作。到此,数据接口就定义完毕了,接下来在训练代码中看如何使用迭代器进行数据读取就可以了,包括 scale、减均值等。
(3)torch.utils.data.DataLoader
这就是创建了一个 batch,生成真正网络的输入。
3.2.2 模型定义
import torch
import torch.nn as nn
import torch.nn.functional as F
import numpy as np
class simpleconv3(nn.Module):`
def __init__(self):
super(simpleconv3,self).__init__()
self.conv1 = nn.Conv2d(3, 12, 3, 2)
self.bn1 = nn.BatchNorm2d(12)
self.conv2 = nn.Conv2d(12, 24, 3, 2)
self.bn2 = nn.BatchNorm2d(24)
self.conv3 = nn.Conv2d(24, 48, 3, 2)
self.bn3 = nn.BatchNorm2d(48)
self.fc1 = nn.Linear(48 * 5 * 5 , 1200)
self.fc2 = nn.Linear(1200 , 128)
self.fc3 = nn.Linear(128 , 2)
def forward(self , x):
x = F.relu(self.bn1(self.conv1(x)))
x = F.relu(self.bn3(self.conv3(x)))
x = x.view(-1 , 48 * 5 * 5)
x = F.relu(self.fc1(x))
x = F.relu(self.fc2(x))
x = self.fc3(x)
return x
我们的例子都是采用一个简单的3层卷积 + 2层全连接层的网络结构。根据上面的网络结构的定义,需要做以下事情。
(1)simpleconv3(nn.Module)
继承nn.Module,前面已经说过,Pytorch的网络层是包含在nn.Module 里,所以所有的网络定义,都需要继承该网络层,并实现super方法,如下:
super(simpleconv3,self).__init__()
这个就当作一个标准执行就可以了。
(2)网络结构的定义都在nn包里,举例说明:
torch.nn.Conv2d(in_channels, out_channels, kernel_size, stride=1, padding=0, dilation=1, groups=1, bias=True)
完整的接口如上,定义的第一个卷积层如下:
nn.Conv2d(3, 12, 3, 2)
即输入通道为3,输出通道为12,卷积核大小为3,stride=2,其他的层就不一一介绍了,大家可以自己去看nn的API。
(3)forward
backward方法不需要自己实现,但是forward函数是必须要自己实现的,从上面可以看出,forward 函数也是非常简单,串接各个网络层就可以了。
对比Caffe和TensorFlow可以看出,Pytorch的网络定义更加简单,初始化方法都没有显示出现,因为 Pytorch已经提供了默认初始化。
如果我们想实现自己的初始化,可以这么做:
init.xavier_uniform(self.conv1.weight)init.constant(self.conv1.bias, 0.1)
它会对conv1的权重和偏置进行初始化。如果要对所有conv层使用 xavier 初始化呢?可以定义一个函数:
def weights_init(m):
if isinstance(m, nn.Conv2d):
xavier(m.weight.data)
xavier(m.bias.data)
net = Net()
net.apply(weights_init)
3.3 模型训练
网络定义和数据加载都定义好之后,就可以进行训练了。
def train_model(model, criterion, optimizer, scheduler, num_epochs=25):
for epoch in range(num_epochs):
print('Epoch {}/{}'.format(epoch, num_epochs - 1))
for phase in ['train', 'val']:
if phase == 'train':
scheduler.step()
model.train(True)
else:
model.train(False)
running_loss = 0.0 running_corrects = 0.0
for data in dataloders[phase]:
inputs, labels = data
if use_gpu:
inputs = Variable(inputs.cuda())
labels = Variable(labels.cuda())
else:
inputs, labels = Variable(inputs), Variable(labels)
optimizer.zero_grad()
outputs = model(inputs)
_, preds = torch.max(outputs.data, 1)
loss = criterion(outputs, labels)
if phase == 'train':
loss.backward()
optimizer.step()
running_loss += loss.data.item()
running_corrects += torch.sum(preds == labels).item()
epoch_loss = running_loss / dataset_sizes[phase]
epoch_acc = running_corrects / dataset_sizes[phase]
if phase == 'train':
writer.add_scalar('data/trainloss', epoch_loss, epoch)
writer.add_scalar('data/trainacc', epoch_acc, epoch)
else:
writer.add_scalar('data/valloss', epoch_loss, epoch)
writer.add_scalar('data/valacc', epoch_acc, epoch)
print('{} Loss: {:.4f} Acc: {:.4f}'.format(
phase, epoch_loss, epoch_acc))
writer.export_scalars_to_json("./all_scalars.json")
writer.close()
return model
分析一下上面的代码,外层循环是epoches,然后利用 for data in dataloders[phase] 循环取一个epoch 的数据,并塞入variable,送入model。需要注意的是,每一次forward要将梯度清零,即optimizer.zero_grad(),因为梯度会记录前一次的状态,然后计算loss进行反向传播。
loss.backward() optimizer.step()
下面可以分别得到预测结果和loss,每一次epoch 完成计算。
epoch_loss = running_loss / dataset_sizes[phase]
epoch_acc = running_corrects / dataset_sizes[phase]
_, preds = torch.max(outputs.data, 1)
loss = criterion(outputs, labels)
定义网络并使用torch.load和load_state_dict载入模型。
3.4 测试
import torch
import torch.nn as nn
import torch.optim as optim
from torch.optim import lr_scheduler
from torch.autograd import Variable
import torchvision
from torchvision import datasets, models, transforms
import time
import os
from PIL import Image
import sys
import torch.nn.functional as F
from net import simpleconv3
data_transforms = transforms.Compose([
transforms.Resize(48),
transforms.ToTensor(),
transforms.Normalize([0.5,0.5,0.5], [0.5,0.5,0.5])])
net = simpleconv3()
modelpath = sys.argv[1]
net.load_state_dict(torch.load(modelpath,map_location=lambda storage,loc: storage))
magepath = sys.argv[2]
image = Image.open(imagepath)
imgblob = data_transforms(image).unsqueeze(0)
imgblob = Variable(imgblob)
net.eval()
predict = F.softmax(net(imgblob))
print(predict)
用PIL的Image包读取图片,这里没有用OpenCV,因为Pytorch默认的图片读取工具就是PIL的Image,它会将图片按照RGB的格式,归一化到 0~1 之间。读取图片之后,必须转化为Tensor变量。
evaluation的时候,必须设置torch.no_grad(),然后就可以调用 softmax 函数得到结果了。
四、DL4J
4.1 定义
不同于深度学习广泛应用的语言Python,DL4J是为java和jvm编写的开源深度学习库,支持各种深度学习模型。DL4J最重要的特点是支持分布式,可以在Spark和Hadoop上运行,支持分布式CPU和GPU运行。DL4J是为商业环境,而非研究所设计的,因此更加贴近某些生产环境。
4.2 DL4J准备训练
4.2.1DL4J安装
系统要求:
- Java:开发者版7或更新版本(仅支持64位版本)
- Apache Maven:Maven是针对Java的项目管理工具,兼容IntelliJ等IDE,可以让我们轻松安装DL4J项目库
- IntelliJ IDEA (建议)或 Eclipse
- Git
4.2.2 数据准备
DL4J有自己的特殊的数据结构DataVec,所有的输入数据在进入神经网络之前要先经过向量化。向量化后的结果就是一个行数不限的单列矩阵。
熟悉Hadoop/MapReduce的朋友肯定知道它的输入用InputFormat来确定具体的InputSplit和RecordReader。DataVec也有自己FileSplit和RecordReader,并且对于不同的数据类型(文本、CSV、音频、图像、视频等),有不同的RecordReader,下面是一个图像的例子。
int height = 48; // 输入图像高度
int width = 48; // 输入图像宽度
int channels = 3; // 输入图像通道数
int outputNum = 2; // 2分类
int batchSize = 64;
int nEpochs = 100;
int seed = 1234;
Random randNumGen = new Random(seed);
// 训练数据的向量化
File trainData = new File(inputDataDir + "/train");
FileSplit trainSplit = new FileSplit(trainData, NativeImageLoader.ALLOWED_FORMATS, randNumGen);
ParentPathLabelGenerator labelMaker = new ParentPathLabelGenerator(); // parent path as the image label
ImageRecordReader trainRR = new ImageRecordReader(height, width, channels, labelMaker);
trainRR.initialize(trainSplit);
DataSetIterator trainIter = new RecordReaderDataSetIterator(trainRR, batchSize, 1, outputNum);
// 将像素从0-255缩放到0-1 (用min-max的方式进行缩放)
DataNormalization scaler = new ImagePreProcessingScaler(0, 1);
scaler.fit(trainIter);
trainIter.setPreProcessor(scaler);
// 测试数据的向量化
File testData = new File(inputDataDir + "/test");
FileSplit testSplit = new FileSplit(testData, NativeImageLoader.ALLOWED_FORMATS, randNumGen);
ImageRecordReader testRR = new ImageRecordReader(height, width, channels, labelMaker);
testRR.initialize(testSplit);
DataSetIterator testIter = new RecordReaderDataSetIterator(testRR, batchSize, 1, outputNum);
testIter.setPreProcessor(scaler); // same normalization for better results
数据准备的过程分成以下几个步骤:
1)通过FileSplit处理输入文件,FileSplit决定了文件的分布式的分发和处理。
2)ParentPathLabelGenerator通过父目录来直接生成标签,这个生成标签的接口非常方便,比如说如果是二分类,我们先将两个父目录设定为0和1,然后再分别在里面放置对应的图像就行。
3)通过ImageRecordReader读入输入图像。RecordReader是DataVec中的一个类,ImageRecordReader是RecordReader中的一个子类,这样就可以将输入图像转成向量化的带有索引的数据。
4)生成DataSetIterator,实现了对输入数据集的迭代。
4.2.3 网络定义
在Deeplearning4j中,添加一个层的方式是通过NeuralNetConfiguration.Builder()调用layer,指定其在所有层中的输入及输出节点数nIn和nOut,激活方式activation,层的类型如ConvolutionLayer等。
// 设置网络层及超参数
MultiLayerConfiguration conf = new NeuralNetConfiguration.Builder()
.seed(seed)
.l2(0.0005)
.updater(new Adam(0.0001))
.weightInit(WeightInit.XAVIER)
.list()
.layer(0, new ConvolutionLayer.Builder(3, 3)
.nIn(channels)
.stride(2, 2)
.nOut(12)
.activation(Activation.RELU)
.weightInit(WeightInit.XAVIER)
.build())
.layer(1, new BatchNormalization.Builder()
.nIn(12)
.nOut(12)
.build())
.layer(2, new ConvolutionLayer.Builder(3, 3)
.nIn(12)
.stride(2, 2)
.nOut(24)
.activation(Activation.RELU)
.weightInit(WeightInit.XAVIER)
.build())
.layer(3, new BatchNormalization.Builder()
.nIn(24)
.nOut(24)
.build())
.layer(4, new ConvolutionLayer.Builder(3, 3)
.nIn(24)
.stride(2, 2)
.nOut(48)
.activation(Activation.RELU)
.weightInit(WeightInit.XAVIER)
.build())
.layer(5, new BatchNormalization.Builder()
.nIn(48)
.nOut(48)
.build())
.layer(6, new DenseLayer.Builder().activation(Activation.RELU)
.nOut(128).build())
.layer(7, new OutputLayer.Builder(LossFunctions.LossFunction.NEGATIVELOGLIKELIHOOD)
.nOut(outputNum)
.activation(Activation.SOFTMAX)
.build())
.setInputType(InputType.convolutionalFlat(48, 48, 3)) // InputType.convolutional for normal image
.backprop(true).pretrain(false).build();
这里的网络结构和之前的caffe、tensorflow、pytorch等框架采用的网络结构是一样的,都是一个3层的神经网络。
4.3 模型训练
数据准备好了,网络也建好了,接下来就可以训练了。
// 新建一个多层网络模型
MultiLayerNetwork net = new MultiLayerNetwork(conf);
net.init();
// 训练的过程中同时进行评估
for (int i = 0; i < nEpochs; i++) {
net.fit(trainIter);
log.info("Completed epoch " + i);
Evaluation trainEval = net.evaluate(trainIter);
Evaluation eval = net.evaluate(testIter);
log.info("train: " + trainEval.precision());
log.info("val: " + eval.precision());
trainIter.reset();
testIter.reset();
}
//保存模型
ModelSerializer.writeModel(net, new File(modelDir + "/mouth-model.zip"), true);
训练的过程非常简单直观,直接通过net.fit()加载trainIter就可以,其中trainIter在数据准备中已经定义好了。
通过net.evaluate(trainIter)和net.evaluate(testIter)的方式来评估训练和测试的表现,这里我们将每个epoch的准确率打印出来。
4.4 可视化
DL4J提供的用户界面可以在浏览器中看到实时的训练过程。
第一步:
将用户界面依赖项添加到pom文件中:
<dependency>
<groupId>org.deeplearning4j</groupId>
<artifactId>deeplearning4j-ui_2.10</artifactId>
<version>${dl4j.version}</version>
</dependency>
第二步:
在项目中启动用户界面
//初始化用户界面后端,获取一个UI实例
UIServer uiServer = UIServer.getInstance();
//设置网络信息(随时间变化的梯度、分值等)的存储位置。这里将其存储于内存。
StatsStorage statsStorage = new InMemoryStatsStorage();
//将StatsStorage实例连接至用户界面,让StatsStorage的内容能够被可视化
uiServer.attach(statsStorage);
//添加StatsListener来在网络定型时收集这些信息
net.setListeners(new StatsListener(statsStorage));
首先我们初始化一个用户界面后端,设置网络信息的存储位置。
这里将其存储于内存,也可以放入文件中,通过new FileStatsStorage(File)的方式实现。
再将StatsStorage实例连接至用户界面,让StatsStorage的内容能够被可视化。
最后添加StatsListener监听,在网络定型时收集这些信息。
默认的浏览器地址是:http://localhost:9000/train/overview
下面可视化一下损失函数值随迭代次数的变化曲线
标签:框架,self,image,深度,tf,import,data,对比,size 来源: https://www.cnblogs.com/guitaryang/p/16353038.html