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 mxnetsudo pip3 install mxnet-cu80sudo 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.jpg5092 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 mximport numpy as npdef 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 mxfrom symbols.vgg import get_vggdef train():num_classes = 120batch_size = 64 # shape not have to be it exactly aredata_shape = (3, 64, 64)num_epoch = 50prefix = '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 gpuctx=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 infobatch_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_importfrom __future__ import divisionfrom __future__ import print_functionfrom datetime import datetimeimport osimport randomimport sysimport threadingimport numpy as npimport tensorflow as tfclass TFRecordsGenerator(object): """this class is using for tf_records generations in image classification useFor usages:All images must contains in different folders, TFRecordsGenerator will traverseall 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 = nameself.classes_file_path = classes_file_pathself.images_dir = images_dirself.tf_records_saved_dir = tf_records_save_dirself.num_shards = num_shardsself.num_threads = num_threads def _int64_feature(value): if not isinstance(value, list):value = [value] return tf.train.Feature(int64_list=tf.train.Int64List(value=value)) 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 = 3image_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 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) == 3height = 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_threadsnum_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 + soutput_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 = 0files_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 += 1counter += 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 = 0print('%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 += 1shuffled_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 tfimport loggingimport numpy as npimport osimport timefrom datasets.tiny5.tiny5 import Tiny5from models.alexnet import AlexNetfrom models.vgg import VGGNetfrom models.fanet import FaNetlogging.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.FLAGSdef 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 = Truesaver = 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 = 0checkpoint = 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 = 0logging.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
與50位技術專家面對面20年技術見證,附贈技術全景圖總結
以上是生活随笔為你收集整理的PaddlePaddle, TensorFlow, MXNet, Caffe2 , PyTorch五大深度学习框架2017-10最新评测的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: paddlepaddle系列之三行代码从
- 下一篇: 万变不离其宗之海量数据下的算法问题处理思