PaddlePaddle, TensorFlow, MXNet, Caffe2 , PyTorch五大深度學習框架2017-10評測

前言

本文將是2017下半年以來,最新也是最全的一個深度學習框架評測。這裏的評測並不是簡單的使用評測,我們將用這五個框架共同完成一個深度學習任務,從框架使用的易用性、訓練的速度、數據預處理的繁瑣程度,以及顯存佔用大小等幾個方面來進行全方位的測評,除此之外,我們還將給出一個非常客觀,非常全面的使用建議。最後提醒大家本篇文章不僅僅是一個評測,你甚至可以作爲五大框架的入門教程

0. 五大框架概覽

在評測之前,讓我們先對這五大框架進行一個全方位的概覽,以及他們目前所處的發展地位。首先在這五大框架中,很多人肯定會問,爲什麼沒有Keras?爲什麼沒有CNTK?在這裏我說明一點,本篇文章偏向於工業化級別的應用評測,主要評測主流框架,當然不是說Keras和CNTK就不主流了,文章沒有任何利益相關的東西,只不過是Keras本身就擁有多種框架作爲後端,因此與它的後端框架對比也就沒有任何意義,Keras毫無疑問是速度最慢的。而CNTK由於筆者對Windows無感因此也就沒有在評測範圍之內(CNTK也是一個優秀的框架,當然也跨平臺,感興趣者可以去踩踩坑)。

TensorFlow可以說是目前發展來說最活躍的,TensorFlow目前已經有72.3k個star,MXNet是11.5k,Caffe2是5.9K, 當然caffe2要推出的稍晚一些,MXNet的官方GitHub repo也是後來又轉到Apache的孵化項目中。但是從GitHub受關注度來看,無疑TensorFlow和MXNet是更被看好的。

即使我不做這篇測評,很多人也知道這些框架目前爲止有一些這樣的評價:

  • TensorFlow API比較繁雜,使用上手困難,亂七八糟的東西很多,但是生態豐富,很多深度學習模型多有TF的實現,有Google大佬加持;
  • MXNet 佔用內存小,速度快,非常小巧玲瓏,有着天生的開源基因,完全靠社區推動的框架;
  • Caffe2 是面向工業級應用的框架,但是推出較晚,而且主打Python2(execuse me? 2017年了還主打Python2?), 我不由自主的黑一下,從安裝部署角度來說用戶體驗不是非常友好;
  • PyTorch 是Facebook面向學術界推出的一個框架,使用非常簡單,搭建神經網絡就像Keras和matlab一樣,但是我又不得不黑一下,每次還得判斷一下是GPU還是CPU?(execuse me? 真的應了那句話,我踩過了tf的坑才知道tf的好);
  • PaddlePadddle 百度開源的一個框架,國內也有很多人用,我的感受是,非常符合中國人的使用習慣,但是在API的實用性上還有待進一步加強,我曾經寫過一篇博客入門PaddlePaddle,不得不說,PaddlePaddle的中文文檔寫的非常清楚,上手比較簡單PaddlePaddle三行代碼從入門到精通;

以上評價是以前的評價,夾雜着一絲個人使用感受,最後說一下他們各自目前的好的動向:

  • TensorFlow models這個模型庫更新非常快,以前的一些圖片分類,目標檢測,圖片生成文字,生成對抗網絡都有現成的深度學習應用的例子,包括現在更新的基於知識圖譜的問答項目,神經網絡編程機器人等項目,這些官方生態對於一個框架來說非常有用,這無疑是tf的一個長處
  • MXNet早在幾個月前就推出了Gluon這個接口,說白了就是一個Keras,包裝了一個更加方便使用的API,但是目前來說還只能實現一些簡單的網絡的構建,複雜的還是得用原生的API,這裏有一個教程鏈接Gluon資料, 除此之外,MXNet也有一個實例倉庫,其中有一些有意思的項目比如語音識別,但是感覺實現的非常不友好,代碼幾乎凌亂不堪;
  • Caffe2 Caffe2相對於前面兩者來說可以說非常弱了,沒有絲毫亮點,說好的一個C++高速工業級框架的呢?除了吹牛逼忽悠大衆能搞些有用的官方使用文檔或者教程出來嗎?不好多說什麼。
  • PyTorch就一筆帶過了,偏向於學術快速實現,要工業級應用,比如做個模型跑到服務器上或者安卓手機上或者嵌入式上應該搞不來;
  • PaddlePaddle 現在做的還不錯,我強調一句,Paddle是唯一一個不配置任何第三方庫,克隆直接make就能成功的框架, 被caffe編譯虐過的人應該對此深有感觸。

說了這麼多,相信大家對目前的框架有了一個大致的瞭解,那麼接下來我們就用其中幾個框架來完成分類圖片這麼一個任務吧,這裏面將包含圖片如何導入模型如何寫網絡, 整個訓練的Pipeline等內容。

我們此次評測的任務是圖片分類,大家嘗試任何一個框架只需要新建一個文件夾,比如mxnet_classifier, 把數據扔到 data 裏即可,我們側重評測數據預處理的複雜程度,和網絡編寫的複雜程度。

圖片下載地址images.tar , annotations.tar. 解壓之後得到:

paddle_test
└── data
├── annotation.tar
└── images.tar

解壓之後Images下面每一個文件夾是一個類別的狗, 其實分類任務我們只要這個就可以了。

1. MXNet

首先上場的,用MXNet吧。建議大家看一下上面我貼出的Gluon李沐大神寫的PPT,包含了Gluon和其他框架的區別,以及MXNet在多GPU上訓練的優勢。

沒有安裝的安裝一下:

sudo pip3 install mxnet
sudo pip3 install mxnet-cu80
sudo pip3 install mxnet-cu80mkl

分別是CPU乞丐版,GPU土豪版,GPU加CPU加速至尊豪華版。安裝完了你應該clone一下mxnet的源代碼,從tools裏面找到im2rec.py這個工具,我們做圖片,不管是檢測還是分割還是分類,都按照mxnet的邏輯把圖片轉成二進制的rec格式吧。

我們現在有了Images文件夾,用im2rec.py處理參數這樣寫:

python3 im2rec.py standford_dogs Images/ --list true --recursive true --train-ratio 0.8 --test-ratio 0.2

這一步會生成兩個文件:

  • standford_dogs_train.lst
  • standford_dogs_test.lst

standford_dogs 是前綴, —list true表示生成列表,recursive用戶這種每一個文件夾代表一類的情況,最後在standford_dogs_train.lst 裏面的一行是這樣的:

5008 27.000000 n02092339-Weimaraner/n02092339_2885.jpg
5092 27.000000 n02092339-Weimaraner/n02092339_6548.jpg

第一個數字是圖片的總數目的index,第二個應該是類別的index但是這個.0000有點不可思議。好了,有了這個lst文件我們繼續用im2rec來生成rec二進制數據吧, 這一步非常簡單了,直接load上面的prefix和Images這個圖片根目錄即可:

python3 im2rec.py standford_dogs Images/

mxnet會依次生成train和test的rec文件:

OK, mxnet做數據集也不是非常的麻煩,這個過程如果滿分五分的話我給4分,pytorch如果不考慮性能的話應該是最直接的,直接從文件夾導入,但是rec格式更快。生成之後總共有了2.8G的文件。

好了,數據準備了,直接寫一個網絡開始訓練羅?我要寫一個vgg怎麼辦?我要看論文嗎?我要從第一層開始看網絡結構嗎?我要換ResNet怎麼辦?要換Inception怎麼辦?沒有關係!mxnet 官方example包含了大多數這些網絡結構!!

├── alexnet.py
├── googlenet.py
├── inception-bn.py
├── inception-resnet-v2.py
├── inception-v3.py
├── inception-v4.py
├── lenet.py
├── mlp.py
├── mobilenet.py
├── resnet-v1.py
├── resnet.py
├── resnext.py
└── vgg.py

更重要的是,我們看看alexnet的代碼:

import mxnet as mx
import numpy as np
def get_symbol(num_classes, dtype='float32', **kwargs):
input_data = mx.sym.Variable(name="data")
if dtype == 'float16':
input_data = mx.sym.Cast(data=input_data, dtype=np.float16)
# stage 1
conv1 = mx.sym.Convolution(name='conv1',
data=input_data, kernel=(11, 11), stride=(4, 4), num_filter=96)
relu1 = mx.sym.Activation(data=conv1, act_type="relu")
lrn1 = mx.sym.LRN(data=relu1, alpha=0.0001, beta=0.75, knorm=2, nsize=5)
pool1 = mx.sym.Pooling(
data=lrn1, pool_type="max", kernel=(3, 3), stride=(2,2))
# stage 2
conv2 = mx.sym.Convolution(name='conv2',
data=pool1, kernel=(5, 5), pad=(2, 2), num_filter=256)
relu2 = mx.sym.Activation(data=conv2, act_type="relu")
lrn2 = mx.sym.LRN(data=relu2, alpha=0.0001, beta=0.75, knorm=2, nsize=5)
pool2 = mx.sym.Pooling(data=lrn2, kernel=(3, 3), stride=(2, 2), pool_type="max")
# stage 3
conv3 = mx.sym.Convolution(name='conv3',
data=pool2, kernel=(3, 3), pad=(1, 1), num_filter=384)
relu3 = mx.sym.Activation(data=conv3, act_type="relu")
conv4 = mx.sym.Convolution(name='conv4',
data=relu3, kernel=(3, 3), pad=(1, 1), num_filter=384)
relu4 = mx.sym.Activation(data=conv4, act_type="relu")
conv5 = mx.sym.Convolution(name='conv5',
data=relu4, kernel=(3, 3), pad=(1, 1), num_filter=256)
relu5 = mx.sym.Activation(data=conv5, act_type="relu")
pool3 = mx.sym.Pooling(data=relu5, kernel=(3, 3), stride=(2, 2), pool_type="max")
# stage 4
flatten = mx.sym.Flatten(data=pool3)
fc1 = mx.sym.FullyConnected(name='fc1', data=flatten, num_hidden=4096)
relu6 = mx.sym.Activation(data=fc1, act_type="relu")
dropout1 = mx.sym.Dropout(data=relu6, p=0.5)
# stage 5
fc2 = mx.sym.FullyConnected(name='fc2', data=dropout1, num_hidden=4096)
relu7 = mx.sym.Activation(data=fc2, act_type="relu")
dropout2 = mx.sym.Dropout(data=relu7, p=0.5)
# stage 6
fc3 = mx.sym.FullyConnected(name='fc3', data=dropout2, num_hidden=num_classes)
if dtype == 'float16':
fc3 = mx.sym.Cast(data=fc3, dtype=np.float32)
softmax = mx.sym.SoftmaxOutput(data=fc3, name='softmax')
return softmax

非常非常非常簡潔!!!!,只是一個函數,唯一不同的就是類別的數目不同,最後函數根據類別不同返回一個softmax的loss。

最後我們看看怎麼把數據導入,然後訓練的!!!

"""
train pipe line in mxnet
"""
import mxnet as mx
from symbols.vgg import get_vgg
def train():
num_classes = 120
batch_size = 64
# shape not have to be it exactly are
data_shape = (3, 64, 64)
num_epoch = 50
prefix = 'standford_dogs_model'
train_iter = mx.io.ImageRecordIter(
path_imgrec="data/standford_dogs_train.rec",
data_shape=data_shape,
batch_size=batch_size,
)
val_iter = mx.io.ImageRecordIter(
path_imgrec="data/standford_dogs_test.rec",
data_shape=data_shape,
batch_size=batch_size,
)
model = mx.model.FeedForward(
# set mx.gpu(0, 1) for multiple gpu
ctx=mx.cpu(),
symbol=get_vgg(num_classes=num_classes),
num_epoch=num_epoch,
learning_rate=0.01,
)
model.fit(
X=train_iter,
eval_data=val_iter,
# every 10 iteration log info
batch_end_callback=mx.callback.Speedometer(batch_size, 10),
epoch_end_callback=mx.callback.do_checkpoint(prefix=prefix)
)
if __name__ == '__main__':
train()

尼瑪,簡直簡單到想哭。大家注意這裏get_vgg就是直接從官方的example/image-classification裏面拿的,我們訓練一個vgg看看。運行之後發現網絡已經跑起來了:

溫馨提示一下,MXNet貌似已經摒棄了上面的寫法,上面的寫法和PyTorch一樣,是一種生成式的寫法,Model和Module的區別就是,後者更加Tensor化,也就是圖化,運行之前先把GPU佔領一下再說。

OK, MXNet的坑已經踩完了。我來總結一下MXNet不爲人知的幾點:

  • 這是一個良心框架。可以看出它的開發者再用心的追求速度和易用性,否則也不會推出Gluon這個接口了,這個接口就是讓普通開發者更加易用,同時追求速度;
  • MXNet是唯一一個比較中立的框架,你要知道,Google推出TensorFlow可是有小九九的,其內部至少有幾套速度更快的純C寫的版本,否則TensorFlow怎麼那麼慢?不拉開差距怎麼來的KPI?怎麼讓全球開發者爲Google服務?(不是Google員工也是不是Google敵對員工,逃…)
  • MXNet的未來潛力很大,我最近在研究MXNet構建複雜的網絡,比如Cycle-GAN,比如Seq2Seq的實現,但是不得不承認,這方面TensorFlow更加強大…

2. PaddlePaddle

爲什麼第二個評測用PaddlePaddle?第一,它最近表現很好,但是知道人很少,秉着爲開發者引路的原則,增加以下曝光度,其實說實話,很多人不知道PaddlePaddle已經升級到了v2的Python API,而且內部還引入很多Go語言的代碼,我沒有仔細看這些代碼是用來幹啥的,但是很顯然,PaddlePaddle在追求速度。

對Paddle的評測我這裏列舉以下Paddle的幾個亮點的地方:

  • 相對來說更易用的API,所謂相對是因爲,它還是有一些冗雜的地方;
  • 佔用內存小,速度快,Paddle在百度內部應該也服務了相當多的項目,因此工業應用不成問題;
  • 中文支持,不想國外的框架,PaddlePaddle還是有着相當多的中文文檔的;
  • PaddlePaddle在自然語言處理上有很多現成的歷程,比如情感分類,甚至是語音識別都有Demo;
  • PaddlePaddle支持多機多卡訓練,也算是集大成者。

關於PaddlePaddle使用的Pipeline異步到我之前寫的一個文章傳送門

3. TensorFlow

關於tf,還真的是愛恨交加,從剛入手到現在,他的API的繁雜性以及訓練的繁瑣幾乎讓人望而卻步,不過好在它有一個非常強大的生態。我們來看看TensorFlow做分類任務應該怎麼做。

首先,毫無疑問,最好的方法是把圖片放到tfrecord這個文件類型中去。但是如何生成tfrecord是個蛋疼的問題,在這裏我申明一點,tfrecord和MXNet的rec文件不同:

tfrecod是將文件以鍵值對的形式存放起來了,每個記錄就是一個example,而MXNet存儲需要先建立一個lst,然後從lst轉成二進制文件。好吧其實也差不多,不過你應該能理解我說的意思。

我們看一下一個用來將圖片轉爲tfrecord的代碼:

from __future__ import absolute_import
from __future__ import division
from __future__ import print_function
from datetime import datetime
import os
import random
import sys
import threading
import numpy as np
import tensorflow as tf
class TFRecordsGenerator(object):
"""
this class is using for tf_records generations in image classification use
For usages:
All images must contains in different folders, TFRecordsGenerator will traverse
all folders and find different classes.
"""
def __init__(self,
name,
images_dir,
classes_file_path,
tf_records_save_dir,
num_shards=4,
num_threads=4):
self.name = name
self.classes_file_path = classes_file_path
self.images_dir = images_dir
self.tf_records_saved_dir = tf_records_save_dir
self.num_shards = num_shards
self.num_threads = num_threads
@staticmethod
def _int64_feature(value):
if not isinstance(value, list):
value = [value]
return tf.train.Feature(int64_list=tf.train.Int64List(value=value))
@staticmethod
def _bytes_feature(value):
return tf.train.Feature(bytes_list=tf.train.BytesList(value=[value]))
def _convert_to_example(self, filename, image_buffer, label, text, height, width):
"""
Example for image classification
:param filename:
:param image_buffer:
:param label:
:param text:
:param height:
:param width:
:return:
"""
color_space = 'RGB'
channels = 3
image_format = 'JPEG'
example = tf.train.Example(features=tf.train.Features(feature={
'image/height': self._int64_feature(height),
'image/width': self._int64_feature(width),
'image/color_space': self._bytes_feature(tf.compat.as_bytes(color_space)),
'image/channels': self._int64_feature(channels),
'image/class/label': self._int64_feature(label),
'image/class/text': self._bytes_feature(tf.compat.as_bytes(text)),
'image/format': self._bytes_feature(tf.compat.as_bytes(image_format)),
'image/filename': self._bytes_feature(tf.compat.as_bytes(os.path.basename(filename))),
'image/encoded': self._bytes_feature(tf.compat.as_bytes(image_buffer))}))
return example
class ImageCoder(object):
def __init__(self):
self._sess = tf.Session()
self._png_data = tf.placeholder(dtype=tf.string)
image = tf.image.decode_png(self._png_data, channels=3)
self._png_to_jpeg = tf.image.encode_jpeg(image, format='rgb', quality=100)
self._decode_jpeg_data = tf.placeholder(dtype=tf.string)
self._decode_jpeg = tf.image.decode_jpeg(self._decode_jpeg_data, channels=3)
def png_to_jpeg(self, image_data):
return self._sess.run(self._png_to_jpeg,
feed_dict={self._png_data: image_data})
def decode_jpeg(self, image_data):
image = self._sess.run(self._decode_jpeg,
feed_dict={self._decode_jpeg_data: image_data})
assert len(image.shape) == 3
assert image.shape[2] == 3
return image
@staticmethod
def _is_png(filename):
return '.png' in filename
def _process_image(self, filename, coder):
with tf.gfile.FastGFile(filename, 'r') as f:
image_data = f.read()
if self._is_png(filename):
print('Converting PNG to JPEG for %s' % filename)
image_data = coder.png_to_jpeg(image_data)
image = coder.decode_jpeg(image_data)
assert len(image.shape) == 3
height = image.shape[0]
width = image.shape[1]
assert image.shape[2] == 3
return image_data, height, width
def _process_image_files_batch(self, coder, thread_index, ranges, name, file_names,
texts, labels, num_shards):
num_threads = len(ranges)
assert not num_shards % num_threads
num_shards_per_batch = int(num_shards / num_threads)
shard_ranges = np.linspace(ranges[thread_index][0],
ranges[thread_index][1],
num_shards_per_batch + 1).astype(int)
num_files_in_thread = ranges[thread_index][1] - ranges[thread_index][0]
counter = 0
for s in range(num_shards_per_batch):
shard = thread_index * num_shards_per_batch + s
output_filename = '%s-%.5d-of-%.5d.tfrecord' % (name, shard, num_shards)
output_file = os.path.join(self.tf_records_saved_dir, output_filename)
writer = tf.python_io.TFRecordWriter(output_file)
shard_counter = 0
files_in_shard = np.arange(shard_ranges[s], shard_ranges[s + 1], dtype=int)
for i in files_in_shard:
filename = file_names[i]
label = labels[i]
text = texts[i]
image_buffer, height, width = self._process_image(filename, coder)
example = self._convert_to_example(filename, image_buffer, label,
text, height, width)
writer.write(example.SerializeToString())
shard_counter += 1
counter += 1
if not counter % 1000:
print('%s [thread %d]: Processed %d of %d images in thread batch.' %
(datetime.now(), thread_index, counter, num_files_in_thread))
sys.stdout.flush()
writer.close()
print('%s [thread %d]: Wrote %d images to %s' %
(datetime.now(), thread_index, shard_counter, output_file))
sys.stdout.flush()
shard_counter = 0
print('%s [thread %d]: Wrote %d images to %d shards.' %
(datetime.now(), thread_index, counter, num_files_in_thread))
sys.stdout.flush()
def _process_image_files(self, file_names, texts, labels):
assert len(file_names) == len(texts)
assert len(file_names) == len(labels)
spacing = np.linspace(0, len(file_names), self.num_threads + 1).astype(np.int)
ranges = []
for i in range(len(spacing) - 1):
ranges.append([spacing[i], spacing[i + 1]])
print('Launching %d threads for spacings: %s' % (self.num_threads, ranges))
sys.stdout.flush()
coord = tf.train.Coordinator()
coder = self.ImageCoder()
threads = []
for thread_index in range(len(ranges)):
args = (coder, thread_index, ranges, self.name, file_names,
texts, labels, self.num_shards)
t = threading.Thread(target=self._process_image_files_batch, args=args)
t.start()
threads.append(t)
coord.join(threads)
print('%s: Finished writing all %d images in data set.' %
(datetime.now(), len(file_names)))
sys.stdout.flush()
def _find_image_files(self):
print('Determining list of input files and labels from %s.' % self.images_dir)
unique_labels = [l.strip() for l in tf.gfile.FastGFile(
self.classes_file_path, 'r').readlines()]
labels = []
file_names = []
texts = []
label_index = 1
for text in unique_labels:
jpeg_file_path = '%s/%s/*' % (self.images_dir, text)
matching_files = tf.gfile.Glob(jpeg_file_path)
labels.extend([label_index] * len(matching_files))
texts.extend([text] * len(matching_files))
file_names.extend(matching_files)
if not label_index % 100:
print('Finished finding files in %d of %d classes.' % (
label_index, len(labels)))
label_index += 1
shuffled_index = list(range(len(file_names)))
random.seed(12345)
random.shuffle(shuffled_index)
file_names = [file_names[i] for i in shuffled_index]
texts = [texts[i] for i in shuffled_index]
labels = [labels[i] for i in shuffled_index]
print('Found %d JPEG files across %d labels inside %s.' %
(len(file_names), len(unique_labels), self.images_dir))
print('[INFO] Attempting logging out file_names list: {}'.format('\n'.join(file_names)))
return file_names, texts, labels
def generate(self):
assert not self.num_shards % self.num_threads, (
'Please make the FLAGS.num_threads commensurate with FLAGS.train_shards')
print('Saving results to %s' % self.tf_records_saved_dir)
file_names, texts, labels = self._find_image_files()
self._process_image_files(file_names, texts, labels)
print('All Done! Solved {} images. tf_records file saved into {}.'.format(len(file_names), os.path.abspath(
self.tf_records_saved_dir)))

這是我包裝的一個類,只要傳入路徑調用generate就可以生成tfrecord文件。看到這裏估計你已經哭了,尼瑪這麼複雜?!!!!????

好吧,暫且不管這個具體咋麼實現的,再來看看數據怎麼load進模型的吧:

import tensorflow as tf
import logging
import numpy as np
import os
import time
from datasets.tiny5.tiny5 import Tiny5
from models.alexnet import AlexNet
from models.vgg import VGGNet
from models.fanet import FaNet
logging.basicConfig(level=logging.DEBUG,
format='%(asctime)s %(filename)s line:%(lineno)d %(levelname)s %(message)s',
datefmt='%a, %d %b %Y %H:%M:%S')
tf.app.flags.DEFINE_string('checkpoints_dir', './checkpoints/tiny5/', 'checkpoints save path.')
tf.app.flags.DEFINE_string('model_prefix', 'tiny5-alex-net', 'model save prefix.')
tf.app.flags.DEFINE_boolean('is_restore', False, 'to restore from previous or not.')
tf.app.flags.DEFINE_integer('target_width', 256, 'target width for resize.')
tf.app.flags.DEFINE_integer('target_height', 256, 'target height for resize.')
tf.app.flags.DEFINE_integer('batch_size', 24, 'batch size for train.')
FLAGS = tf.app.flags.FLAGS
def running(is_train=True):
if not os.path.exists(FLAGS.checkpoints_dir):
os.makedirs(FLAGS.checkpoints_dir)
tiny5 = Tiny5(
images_dir='./datasets/tiny5/images',
classes_file_path='./datasets/tiny5/tiny5_classes.txt',
target_height=FLAGS.target_height,
target_width=FLAGS.target_width,
batch_size=FLAGS.batch_size
)
images, labels = tiny5.batch_inputs()
print(images)
# model = AlexNet(num_classes=5)
# model = VGGNet(num_classes=5)
model = FaNet(num_classes=5)
config = tf.ConfigProto()
config.gpu_options.allow_growth = True
saver = tf.train.Saver(max_to_keep=2)
init_op = tf.group(tf.global_variables_initializer(), tf.local_variables_initializer())
with tf.Session() as sess:
coord = tf.train.Coordinator()
threads = tf.train.start_queue_runners(sess=sess, coord=coord)
sess.run(init_op)
start_epoch = 0
checkpoint = tf.train.latest_checkpoint(FLAGS.checkpoints_dir)
if FLAGS.is_restore:
if checkpoint:
saver.restore(sess, checkpoint)
logging.info("restore from the checkpoint {0}".format(checkpoint))
start_epoch += int(checkpoint.split('-')[-1])
if is_train:
step = 0
logging.info('training start...')
try:
while not coord.should_stop():
feed_dict = model.make_train_inputs(images, labels)
_, loss, step = sess.run(
[model.train_op, model.loss, model.global_step], feed_dict=feed_dict
)
logging.info('epoch {}, loss {}'.format(step, loss))
except tf.errors.OutOfRangeError:
logging.info('optimization done! enjoy color net.')
saver.save(sess, os.path.join(FLAGS.checkpoints_dir, FLAGS.checkpoints_prefix), global_step=step)
except KeyboardInterrupt:
logging.info('interrupt manually, try saving checkpoint for now...')
saver.save(sess, os.path.join(FLAGS.checkpoints_dir, FLAGS.model_prefix), global_step=step)
logging.info('last epoch were saved, next time will start from epoch {}.'.format(step))
finally:
coord.request_stop()
coord.join(threads)
else:
logging.info('start inference...')
inference_image_path = './images/1.png'
input_image = tiny5.single_image_input(inference_image_path)
feed_dict = model.make_inference_inputs(input_image)
outputs = sess.run([model.inference_outputs(n_top=2)], feed_dict=feed_dict)
print(outputs)
def main(args):
running(args)
if __name__ == '__main__':
tf.app.run()

這個訓練的代碼,大概的訓練步驟分爲:

  • 使用tf.ConfigProto()來生成一個config,設置gpu自動生長,同時設置一個saver,這個saver就是最大保存的數目;
  • 設置初始化的變量op,設置一個tf.Train.Coordinator()來作爲訓練協調者,初始化圖;
  • for循環所有的epoch,在每次循環裏面catch一下tf.errors.OutOfRangeError表示一個batch訓練完了,catch一下KeyBoardInterrupt;
  • 最後是保存模型

大家可以感受一下TensorFlow一整套流程下來的複雜程度。這裏面還沒有寫我的網絡,沒有寫我的數據DataLoader,整個代碼在我的GitHub倉庫可以找到原始代碼,傳送門, 如果你覺得那個項目過於陳舊可以跟進我的一些最新的項目,我近期在TensorFlow上做的工作有:

  • 用Google最新nmt模型訓練聊天機器人;
  • 使用GAN做Cylce-GAN生成;
  • 使用KnowledgeDatabase和知識圖譜做問答系統;
  • 目標檢測和分割等常規性工作

4. PyTorch

PyTorch如果做圖片預測我就不詳細講了,很多人說PyTorch很簡單,但是我並沒有覺得簡單到哪裏去,我總結一下PyTorch目前來說一些優點吧。

  • 立即式編程,也就是運行立馬出結果,不同於TensorFlow的圖式,你必須把所有程序寫完之後才知道結果什麼;
  • 安裝也比較方便,但是跨平臺部署就比較麻煩了,這也和PyTorch的定位有關,當然PyTorch剛推出來的時候有幾篇官方教程寫的不錯,主要是RNN文本生成,Seq2Seq翻譯的實現,有興趣的同學可以看一下,但是都是非常簡單的實現,跟TensorFlow的官方例子差距蠻大;
  • 只是構建網絡比較簡單,但是具體訓練的PipeLine還是有點麻煩,尤其是我每次變量還得指定是CPU還是GPU,每次load模型的時候還得load是CPU還是GPU,個人感覺略麻煩;

PyTorch推出來的時候很火,現在貌似熄火了….

5. Caffe2

caffe2 不得不提一下,caffe的進化版本????caffe用着還好,c++調接口還蠻方便,例子也很多,caffe2爲毛主打python,還python2???不過這也跟caffe2定位於工業使用有關,但是總體來說有這麼幾點:

  • 感覺沒有多少社區,雖然caffe非常多公司用,但是那畢竟是第一代版本,一般公司用用還行,容易與時代脫節;
  • caffe2也沒有多少亮點,官方的教程我是沒有看到什麼實質性的東西,後期也沒有更多的example;
  • 好像C++接口也不是非常友好,至少在例子上很少….一個框架推出來,不教人去用那推出來有啥意思?

總結

我寫文章喜歡一目瞭然,文章結構大致對比了5種框架的優缺點,那麼我直接給使用者一些建議,防止大家採坑:

  • 如果你是深度學習老鳥,你應該選擇TensorFlow,但是我不得不告訴你TensorFlow在1.2版本推出來的API,在1.4版本很有可能就大改了…..
  • 如果你是深度學習菜鳥,你應該選擇MXNet或者PaddlePaddle,很多人會說,我曹,爲什麼不用Keras??好吧,Keras當然也可以用,但是不建議一直用,還是得熟悉一下稍微底層一些的框架;
  • 如果你是….如果你是小學生?高中生或者初中生,你可以用一下PaddlePaddle,因爲你英文可能不太好。

如果你想跟進我的更多TensorFlow項目歡迎在Github尋找我的聯繫方式,加入QQ羣交流。

This article was original written by Jin Tian, welcome re-post, first come with https://jinfagang.github.io . but please keep this copyright info, thanks, any question could be asked via wechat: jintianiloveu

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章