深度学习开源框架知识汇总

本文深入剖析了深度学习领域的两大主流框架——TensorFlow与Caffe,涵盖了从数据准备、模型定义到训练与测试的全过程。对比了它们在图像分类任务上的应用,提供了详实的代码示例与实践指导。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

这是一篇总结文,为了帮助自己清楚12大深度学习开源框架而做的小总结。

1 概述

1.1开源框架总览

先放一张各大开源框架的一个总览表:
在这里插入图片描述除此之外还有tiny-dnn,ConvNetJS,MarVin,Neon等等小众,以及CoreML等移动端框架。在选择开源框架时,要考虑很多原因,比如开源生态的完善性,比如自己项目的需求,比如自己熟悉的语言。 现在已经有很多开源框架之间进行互转的开源工具如MMDNN等,也降低了大家迁移框架的学习成本。
总的来说对于选择什么样的框架,有三可以给出一些建议:
(1) 不管怎么说,tensorflow/pytorch你都必须会,这是目前开发者最喜欢,开源项目最丰富的两个框架。

(2) 如果你要进行移动端算法的开发,那么Caffe是不能不会的。

(3) 如果你非常熟悉Matlab,matconvnet你不应该错过。

(4) 如果你追求高效轻量,那么darknet和mxnet你不能不熟悉。

(5) 如果你很懒,想写最少的代码完成任务,那么用keras吧。

(6) 如果你是java程序员,那么掌握deeplearning4j没错的。

1.2如何学习开源框架

要掌握好一个开源框架,通常需要做到以下几点:

(1) 掌握不同任务数据的准备和使用。

(2) 掌握模型的定义。

(3) 掌握训练过程和结果的可视化。

(4) 掌握训练方法和测试方法。

一个框架,官方都会开放有若干的案例,最常见的案例就是以 MNISI数据接口+预训练模型 的形式,供大家快速获得结果,但是这明显还不够,学习不应该停留在跑通官方的demo上,而是要解决实际的问题。

我们要学会从 自定义数据读取接口自定义网络的搭建模型的训练模型的可视化,模型的测试与部署等全方位进行掌握。
在所有框架的学习过程中,都要完成下面这个流程,只有这样,才能叫做真正的完成了一个训练任务。在这里插入图片描述
另外,所有的框架都使用同样的一个模型,这是一个 3层卷积+2层全连接的网络 ,由卷积+BN层+激活层组成,有的使用带步长的卷积,有的使用池化,差别不大。

输入图像,48483的RGB彩色图。

第一层卷积,通道数12,卷积核3*3。

第二层卷积,通道数24,卷积核3*3。

第三层卷积,通道数48,卷积核3*3。

第一层全连接,通道数128。

第二层全连接,通道数2,即类别数。

网络结构如下:
在这里插入图片描述
这是最简单的一种网络结构,优化的时候根据不同的框架,采用了略有不同的方案。因为此处的目标不是为了比较各个框架的性能,所以没有刻意保持完全一致。

2 开源框架

2.1 Caffe

github地址:https://github.com/BVLC/caffe

(1)caffe的使用通常是下面的流程:

在这里插入图片描述
以上的流程相互之间是解耦合的,所以caffe的使用非常优雅简单。

(2)caffe:图像分类从模型自定义到测试

这里展示一个分类任务的例子,数据存放地址https://github.com/longpeng2008/LongPeng_ML_Course。准备了 500 张微笑的图片、500 张非微笑的图片,放置在 data 目录下,图片预览如下,已经缩放到 60*60 的大小:
①准备训练数据
这是非微笑的图片:
在这里插入图片描述
这是微笑的图片:
在这里插入图片描述
Caffe 完成一个训练,必要准备以下资料:一个是 train.prototxt 作为网络配置文件,另一个是 solver.prototxt 作为优化参数配置文件,再一个是训练文件 list。

另外,在大多数情况下,需要一个预训练模型作为权重的初始化。
②准备网络配置文件
准备了一个 3*3 的卷积神经网络,它的 train.prototxt 文件是这样的:

name: "mouth"
layer {
 name: "data"
 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 有卷积核的学习率、卷积核的大小、输出通道数、初始化方法等,这些可以后续详细了解。
③准备训练 list
我们看上面的 data layer,可以到 看到image_data_param 里面有

source: "all_shuffle_train.txt"

它是什么呢,就是输入用于训练的 list,它的内容是这样的:

../../../../datas/mouth/1/182smile.jpg 1

../../../../datas/mouth/1/435smile.jpg 1

../../../../datas/mouth/0/40neutral.jpg 0

../../../../datas/mouth/1/206smile.jpg 1

../../../../datas/mouth/0/458neutral.jpg 0

../../../../datas/mouth/0/158neutral.jpg 0

../../../../datas/mouth/1/322smile.jpg 1

../../../../datas/mouth/1/83smile.jpg 1

../../../../datas/mouth/0/403neutral.jpg 0

../../../../datas/mouth/1/425smile.jpg 1

../../../../datas/mouth/1/180smile.jpg 1

../../../../datas/mouth/1/233smile.jpg 1

../../../../datas/mouth/1/213smile.jpg 1

../../../../datas/mouth/1/144smile.jpg 1

../../../../datas/mouth/0/327neutral.jpg 0

格式就是,图片的名字 + 空格 + label,这就是 Caffe 用于图片分类默认的输入格式。
④准备优化配置文件:

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 进行训练。
⑤训练与结果可视化
利用 C++ 的接口进行训练,命令如下:

SOLVER=./solver.prototxt 
WEIGHTS=./init.caffemodel 
../../../../libs/Caffe_Long/build/tools/caffe train -solver $SOLVER -weights $WEIGHTS -gpu 0 2>&1 | tee log.txt

其中,caffe train 就是指定训练。可以利用脚本可视化一下训练结果:
在这里插入图片描述在这里插入图片描述

(3)Caffe 测试

下面开始采用自己的图片进行测试。

①train.prototxt 与 test.prototxt 的区别
训练时的网络配置与测试时的网络配置是不同的,测试没有 acc 层,也没有 loss 层,取输出的 softmax 就是分类的结果。同时,输入层的格式也有出入,不需要再输入 label,也不需要指定图片 list,但是要指定输入尺度,我们看一下 test.prototxt 和可视化结果。

name: "mouth"
layer {
 name: "data"
 type: "Input"
 top: "data"
 input_param { shape: { dim: 1 dim: 3 dim: 48 dim: 48 } }
}

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"
   name: "loss"
   type: "Softmax"
   top: "prob"
}

在这里插入图片描述
②使用 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)

经过前面的介绍,我们已经学会了 Caffe 的基本使用,但是我们不能停留于此。Caffe 是一个非常优秀的开源框架,有必要去细读它的源代码。

至于怎么读 Caffe 的代码,建议阅读Caffe代码阅读系列内容。

2.2 Tensorflow

(1)特点

TensorFlow最大的特点是计算图,即先定义好图,然后进行运算,所以所有的TensorFlow代码,都包含两部分:

(1) 创建计算图,表示计算的数据流。它做了什么呢?实际上就是定义好了一些操作,你可以将它看做是Caffe中的prototxt的定义过程。

(2)运行会话,执行图中的运算,可以看作是Caffe中的训练过程。只是TensorFlow的会话比Caffe灵活很多,由于是Python 接口,取中间结果分析,Debug等方便很多。

(2)tensorflow:图像分类从模型自定义到测试

①什么是 TensorFlow
TensorFlow = Tensor + Flow
Tensor 就是张量,代表 N 维数组,与 Caffe 中的 blob 是类似的;Flow 即流,代表基于数据流图的计算。神经网络的运算过程,就是数据从一层流动到下一层,在 Caffe 的每一个中间 layer 参数中,都有 bottom 和 top,这就是一个分析和处理的过程。TensorFlow更直接强调了这个过程。
②数据准备
上一节我们说过 Caffe 中的数据准备,只需要准备一个 list 文件,其中每一行存储 image、labelid 就可以了,那是 Caffe 默认的分类网络的 imagedata 层的输入格式。如果想定义自己的输入格式,可以去新建自定义的 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 的数据。

到此,数据接口就定义完毕了,接下来在训练代码中看如何使用迭代器进行数据读取就可以了。

③模型定义
创建数据接口后,我们开始定义一个网络

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,就是因为参数丰富,适合从头训练一个模型。
④ 模型训练

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

⑤可视化
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

Loss 和 acc 的曲线图如下:
在这里插入图片描述

(3)TensorFlow 测试

上面已经训练好了模型,我们接下来的目标,就是要用它来做 inference 了。同样给出代码。

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 就可以得到最终的结果了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值