2017年6月,Google公司開放了TensorFlow Object Detection API。這個項目使用TensorFlow實現了大多數深度學習目標檢測框架,其中就包括Faster R-CNN。
一、實現官方給的目標檢測的示例教程
1、下載TensorFlow Object Detection API
在github上該API存放在tensorflow/models項目下,下載地址為https://github.com/tensorflow/models。下載tensorflow/models后,應該得到一個models文件夾,models文件夾中還有一個research文件夾。下面的安裝命令都是以research文件夾為根目錄執行的。
1.1安裝protoc
在object_detection/protos中,可以看到一些proto文件,需要使用protoc程序將這些proto文件編譯為python文件。
windows系統中:
https://github.com/google/protobuf/releases(選擇最新版本的Windows64版本,不然可能會有錯),解壓后將bin文件夾中的protoc.exe放到C:\Windows下。(用於將protoc.exe所在的目錄配置到環境變量當中)。
linux系統中:
下載protoc-3.3.0-linux-x86_64.zip,下載后解壓,會得到一個protoc文件,將它復制到系統的可執行目錄即可,如在ubutu系統中,可移執行以下命令:
sudo cp bin/protoc /usr/bin/protoc
1.2編譯proto文件
在tensorflow-models\research\目錄下打開命令行窗口輸入以下代碼(我是在anaconda prompt中輸入)
protoc object_detection/protos/*.proto --python_out=.
如果此時不顯示任何信息,則表明運行成功。在research\object_detection\protos下,每一個proto文件都應該會有一個對應的.py文件。具體情況如下:
錯誤一:在這一步有時候會出錯,可以嘗試把/*.proto 這部分改成文件夾下具體的文件名,一個一個試,每運行一個,文件夾下應該出現對應的.py結尾的文件。不報錯即可,具體操作如下:
protoc object_detection/protos/anchor_generator.proto --python_out=.
把*號換成具體的文件名字,這樣就會生成一個.py文件。需要一個一個文件。雖然是笨方法,可是很有效。這樣一個一個替換很麻煩,為此,筆者將所有文件的名字附在下面,這樣復制粘貼就會很快。
1 protoc object_detection/protos/anchor_generator.proto --python_out=. 2 protoc object_detection/protos/argmax_matcher.proto --python_out=. 3 protoc object_detection/protos/bipartite_matcher.proto --python_out=. 4 protoc object_detection/protos/box_coder.proto --python_out=. 5 protoc object_detection/protos/box_predictor.proto --python_out=. 6 protoc object_detection/protos/eval.proto --python_out=. 7 protoc object_detection/protos/faster_rcnn.proto --python_out=. 8 protoc object_detection/protos/faster_rcnn_box_coder.proto --python_out=. 9 protoc object_detection/protos/graph_rewriter.proto --python_out=. 10 protoc object_detection/protos/grid_anchor_generator.proto --python_out=. 11 protoc object_detection/protos/hyperparams.proto --python_out=. 12 protoc object_detection/protos/image_resizer.proto --python_out=. 13 protoc object_detection/protos/input_reader.proto --python_out=. 14 protoc object_detection/protos/keypoint_box_coder.proto --python_out=. 15 protoc object_detection/protos/losses.proto --python_out=. 16 protoc object_detection/protos/matcher.proto --python_out=. 17 protoc object_detection/protos/mean_stddev_box_coder.proto --python_out=. 18 protoc object_detection/protos/model.proto --python_out=. 19 protoc object_detection/protos/multiscale_anchor_generator.proto --python_out=. 20 protoc object_detection/protos/optimizer.proto --python_out=. 21 protoc object_detection/protos/pipeline.proto --python_out=. 22 protoc object_detection/protos/post_processing.proto --python_out=. 23 protoc object_detection/protos/preprocessor.proto --python_out=. 24 protoc object_detection/protos/region_similarity_calculator.proto --python_out=. 25 protoc object_detection/protos/square_box_coder.proto --python_out=. 26 protoc object_detection/protos/ssd.proto --python_out=. 27 protoc object_detection/protos/ssd_anchor_generator.proto --python_out=. 28 protoc object_detection/protos/string_int_label_map.proto --python_out=. 29 protoc object_detection/protos/train.proto --python_out=.
1.3將Slim加入系統環境變量
在 ‘此電腦’-‘屬性’- ‘高級系統設置’ -‘環境變量’-‘系統變量’ 中,將models-master\research\ 和models-master\research\slim 添加進path中。
這個方法好像只適用於linux系統,如果是Windows系統會彈出ImportError: No module named nets錯誤,此時需要在命令行先跳轉到research/slim文件下,然后依次運行以下兩行代碼:
python setup.py build python setup.py install
如果運行后出現error: could not create 'build':(當文件已存在時,無法創建該文件)
原因是github下載下來的代碼庫中有個BUILD文件,而build和install指令需要新建build文件夾,名字沖突導致問題。暫時不清楚BUILD文件的作用。將該文件移動到其他目錄或刪除掉,再運行上述指令,即可成功安裝。
linux系統中:
將slim加入PYTHONPATH
Tensorflow Object Detection API是以slim為基礎實現的,需要將slim的目錄加入PYTHONPATH后才能正確運行。具體來說,還是在research文件夾下,執行以下命令:
export PYTHONPATH=$PYTHONPATH:'pwd':'pwd'/slim
1.4安裝完成測試
在檢測API是否正常時,進入目錄models-master\research下運行:
run object_detection/builders/model_builder_test.py
>>....................
----------------------------------------------------------------------
Ran 22 tests in 0.462s
OK (skipped=1)
如果出現以上運行結果,說明已安裝成功。
2、執行已經訓練好的模型
Objec Detection API默認提供了5個預訓練模型。它們都是使用COCO數據集訓練完成的,結構分別為SSD+MobileNet、SSD+Inception、R-FCN+ResNet101、Faster RCNN+Inception_ResNet。
如何使用這些預訓練模型呢?官方已經給了一個用jupyter notebook編寫好的例子。用jupyter-notebook打開object_detection_tutorial.ipynb運行示例文件。
#導入一些需要的包和設置環境
import numpy as np import tensorflow as tf import os import six.moves.urllib as urllib import sys import tarfile import zipfile from distutils.version import StrictVersion from collections import defaultdict from io import StringIO from matplotlib import pyplot as plt from PIL import Image # 這條命令讓在使用matplotlib繪圖時,不再使用窗口展示出來,而是直接在notebook中顯示 %matplotlib inline #將該模塊用到的的一些包導入 from object_detection.utils import ops as utils_ops from utils import label_map_util from utils import visualization_utils as vis_util if StrictVersion(tf.__version__) < StrictVersion('1.9.0'): raise ImportError('Please upgrade your TensorFlow installation to v1.9.* or later!') #模型准備,設置需要使用的模型的下載地址 MODEL_NAME = 'ssd_mobilenet_v1_coco_2017_11_17' MODEL_FILE = MODEL_NAME + '.tar.gz' DOWNLOAD_BASE = 'http://download.tensorflow.org/models/object_detection/' #frozen_inference_graph.pb文件就是后面需要導入的文件,它保存了網絡的結構和數據 PATH_TO_FROZEN_GRAPH = MODEL_NAME + '/frozen_inference_graph.pb' # mscoco_label_map.pbtxt文件中保存了index到類別名的映射,該文件就在object_dection/data文件夾下 PATH_TO_LABELS = os.path.join('data', 'mscoco_label_map.pbtxt') #下載預訓練模型 #下載該地址下的文件到本地(當前目錄中),可發現該目錄中多了一個ssd_mobilenet_v1_coco_2017_11_17.tar.gz文件 opener = urllib.request.URLopener() opener.retrieve(DOWNLOAD_BASE + MODEL_FILE, MODEL_FILE) #解壓該文件 tar_file = tarfile.open(MODEL_FILE) for file in tar_file.getmembers(): file_name = os.path.basename(file.name) if 'frozen_inference_graph.pb' in file_name: tar_file.extract(file, os.getcwd())#將pb文件提取到當前工作目錄下 #下載模型后,將它讀取到默認的計算圖中(實際讀取的是frozen_inference_graph.pb文件) #新建一個圖 detection_graph = tf.Graph()#定義一個圖 with detection_graph.as_default(): od_graph_def = tf.GraphDef()#重新定義一個圖 #tf.gfile.GFile(filename, mode)獲取文本操作句柄,類似於python提供的文本操作open()函數, #filename是要打開的文件名,mode是以何種方式去讀寫,將會返回一個文本操作句柄。 with tf.gfile.GFile(PATH_TO_FROZEN_GRAPH, 'rb') as fid: #將*.pb文件讀入serialized_graph serialized_graph = fid.read() #將serialized_graph的內容恢復到圖中 od_graph_def.ParseFromString(serialized_graph) #將od_graph_def導入當前默認圖中(加載模型) tf.import_graph_def(od_graph_def, name='') #載入coco數據集標簽文件 category_index = label_map_util.create_category_index_from_labelmap(PATH_TO_LABELS, use_display_name=True) print(category_index) #在進行檢測之前,定義一個幫助函數,該函數的功能是將圖片轉換為Numpy數組的形式 def load_image_into_numpy_array(image): (im_width, im_height) = image.size return np.array(image.getdata()).reshape((im_height, im_width, 3)).astype(np.uint8) #或者用PIL中的Image讀取可更方便轉為Numpy形式 # from PIL import Image # import numpy as np # image = Image.open("test_images/image1.jpg") # 用PIL中的Image.open打開圖像 # image_arr = np.array(image) # 轉化成numpy數組 #對輸入圖像進行目標檢測 PATH_TO_TEST_IMAGES_DIR = 'test_images' TEST_IMAGE_PATHS = [ os.path.join(PATH_TO_TEST_IMAGES_DIR, 'image{}.jpg'.format(i)) for i in range(1,3 ) ] # 輸出圖像的大小(單位是in) IMAGE_SIZE = (12, 8) with tf.Session(graph=detection_graph) as sess: for image_path in TEST_IMAGE_PATHS: image = Image.open(image_path) #將圖片轉換為numpy格式 image_np = load_image_into_numpy_array(image) #將圖片擴展一維,最后進入神經網絡的圖片格式應該是[1,?,?,3],括號內參數分別為一個batch傳入的數量,寬,高,通道數 image_np_expanded = np.expand_dims(image_np,axis = 0) #獲取模型中的tensor image_tensor = detection_graph.get_tensor_by_name('image_tensor:0') #boxes變量存放了所有檢測框 boxes = detection_graph.get_tensor_by_name('detection_boxes:0') #score表示每個檢測結果的confidence scores = detection_graph.get_tensor_by_name('detection_scores:0') #classes表示每個框對應的類別 classes = detection_graph.get_tensor_by_name('detection_classes:0') #num_detections表示檢測框的個數 num_detections = detection_graph.get_tensor_by_name('num_detections:0') #開始檢測 boxes,scores,classes,num_detections = sess.run([boxes,scores,classes,num_detections], feed_dict={image_tensor:image_np_expanded}) #可視化結果 #squeeze函數:從數組的形狀中刪除單維度條目,即把shape中為1的維度去掉 vis_util.visualize_boxes_and_labels_on_image_array( image_np, np.squeeze(boxes), np.squeeze(classes).astype(np.int32), np.squeeze(scores), category_index, use_normalized_coordinates=True, line_thickness=8) plt.figure(figsize=IMAGE_SIZE) plt.imshow(image_np)
檢測結果如下:
二、訓練自己的模型
以VOC 2012數據集為例,介紹如何使用Object Detection API訓練新的模型。VOC 2012是VOC2007數據集的升級版,一共有11530張圖片,每張圖片都有標注,標注的物體包括人、動物(如貓、狗、鳥等)、交通工具(如車、船飛機等)、家具(如椅子、桌子、沙發等)在內的20個類別。
1、下載數據集
首先下載數據集,並將其轉換為tfrecord格式。下載地址為:http://host.robots.ox.ac.uk/pascal/VOC/voc2012/VOCtrainval_11-May-2012.tar。
在research文件夾下,創建一個voc文件夾,把VOC2012解壓到這個文件夾下,解壓后,得到一個VOCdevkit文件夾:
JPEGImages文件中文件夾里存放了全部的訓練圖片和驗證圖片。
對於每一張圖像,都在Annotations文件夾中存放有對應的xml文件。保存着物體框的標注,包括圖片文件名,圖片大小,圖片邊界框等信息,以2007_000027.xml為例:
<annotation> #數據所在的文件夾名 <folder>VOC2012</folder> #圖片名稱 <filename>2007_000027.jpg</filename> <source> <database>The VOC2007 Database</database> <annotation>PASCAL VOC2007</annotation> <image>flickr</image> </source> #圖片的寬高和通道 <size> <width>486</width> <height>500</height> <depth>3</depth> </size> <segmented>0</segmented> <object> #類別名 <name>person</name> #物體的姿勢 <pose>Unspecified</pose> #物體是否被部分遮擋 <truncated>0</truncated> ##是否為難以辨識的物體,主要指要結合背景才能判斷出類別的物體。雖有標注, 但一般忽略這類物體 跳過難以識別的? <difficult>0</difficult> #邊界框 <bndbox> <xmin>174</xmin> <ymin>101</ymin> <xmax>349</xmax> <ymax>351</ymax> </bndbox> #下面的數據是人體各個部位邊界框 <part> <name>head</name> <bndbox> <xmin>169</xmin> <ymin>104</ymin> <xmax>209</xmax> <ymax>146</ymax> </bndbox> </part> <part> <name>hand</name> <bndbox> <xmin>278</xmin> <ymin>210</ymin> <xmax>297</xmax> <ymax>233</ymax> </bndbox> </part> <part> <name>foot</name> <bndbox> <xmin>273</xmin> <ymin>333</ymin> <xmax>297</xmax> <ymax>354</ymax> </bndbox> </part> <part> <name>foot</name> <bndbox> <xmin>319</xmin> <ymin>307</ymin> <xmax>340</xmax> <ymax>326</ymax> </bndbox> </part> </object> </annotation>
SegmentationClass(標注出每一個像素的類別)和SegmentationObject(標注出每個像素屬於哪一個物體)是分割相關的。
2、生成tfrecord文件
把data文件夾下pascal_label_map.pbtxt文件復制到voc文件夾下,這個文件存放着voc2012數據集物體的索引和對應的名字。
在object_detection文件夾中,執行以下命令可將VOC2012數據集轉換為tfrecord格式,轉換好的tfrecord保存在新建的voc文件夾下,分別為pascal_train.record和pascal_val.record
1 run create_pascal_tf_record.py --data_dir voc/VOCdevkit/ --year=VOC2012 \ 2 --set=train --output_path=voc/pascal_train.record 3 4 run create_pascal_tf_record.py --data_dir voc/VOCdevkit/ --year=VOC2012 \ 5 --set=val --output_path=voc/pascal_val.record
這里的代碼是為VOC2012數據集提前編寫好的,如果讀者希望使用自己的數據集,有兩種方法:
第一種是修改自己的數據集的標注格式,使和VOC2012一模一樣(主要是Annotations文件夾,ImageSets\Main文件夾,JPEGImages文件夾),然后即可以直接使用create_pascal_tf_record.py腳本轉換了。
另外一種方法就是修改create_pascal_tf_record.py,對讀取標簽的代碼進行修改。
3、下載模型
下載完VOC 2012數據集后,需要選擇合適的訓練模型。這里以Faster R-CNN + Inception-ResNet_v2模型為例進行介紹。首先下載在COCO數據集上預訓練的Faster R-CNN + Inception-ResNet_v2模型。在voc文件中新建一個pretrained文件夾,將下載的模型解壓到pretrained中,如圖:
里面包含四個文件分別為:
- model.meta:模型文件,該文件保存了metagraph信息,即計算圖的結構;
- model.ckpt.data:權重文件,該文件保存了graph中所有遍歷的數據;
- model.ckpt.index:該文件保存了如何將meta和data匹配起來的信息;
- pb文件:將模型文件和權重文件整合合並為一個文件,主要用途是便於發布,詳細內容可以參考博客https://blog.csdn.net/yjl9122/article/details/78341689;
- 一般情況下還會有個checkpoint文件,用於保存文件的絕對路徑,告訴TF最新的檢查點文件(也就是上圖中后三個文件)是哪個,保存在哪里,在使用tf.train.latest_checkpoint加載的時候要用到這個信息,但是如果改變或者刪除了文件中保存的路徑,那么加載的時候會出錯,找不到文件;
4、訓練模型
Object Detection API是依賴一種特殊的設置文件進行訓練的。在object_detection/samples/configs文件夾下,有一些設置文件的示例。可以參考faster_rcnn_inception_resnet_v2_atrous_coco.config文件創建的設置文件。先將faster_rcnn_inception_resnet_v2_atrous_coco.config復制一份到voc文件夾下,命名為faster_rcnn_inception_resnet_v2_atrous_voc.config。
faster_rcnn_inception_resnet_v2_atrous_voc.config文件有7處需要修改:
第一處為num_classes,需要將它修改為VOC2012 中物體類別數,即20.
第二處為eval_config中的num_examples,它表示在驗證階段需要執行的圖片數量,修改為VOC 2012驗證集的圖片數5823(可以在create_pascal_tf_record.py中,輸出對應的examples_list長度,就可以知道這個大小)。
還有5處為所有含PATH_TO_BE_CONFIGURED的地方。這些地方需要修改為自己的目錄,他們應該分別被修改為:
gradient_clipping_by_norm: 10.0 fine_tune_checkpoint: "voc/pretrained/model.ckpt"#第一處目錄修改 from_detection_checkpoint: true # Note: The below line limits the training process to 200K steps, which we # empirically found to be sufficient enough to train the pets dataset. This # effectively bypasses the learning rate schedule (the learning rate will # never decay). Remove the below line to train indefinitely. num_steps: 200000 data_augmentation_options { random_horizontal_flip { } } } train_input_reader: { tf_record_input_reader { input_path: "voc/pascal_train.record"#第二處目錄修改 } label_map_path: "voc/pascal_label_map.pbtxt"#第三處目錄修改 } eval_config: { num_examples: 5823#驗證集圖片數修改 # Note: The below line limits the evaluation process to 10 evaluations. # Remove the below line to evaluate indefinitely. max_evals: 10 } eval_input_reader: { tf_record_input_reader { input_path: "voc/pascal_val.record"#第四處目錄修改 } label_map_path: "voc/pascal_label_map.pbtxt"#第五處目錄修改 shuffle: false num_readers: 1 }
最后在voc文件夾中新建一個train_dir作為保存模型和日志的目錄,在object_detection文件夾中使用以下的命令就可以開始訓練了:(一個博客說最新的目標檢測API在利用model_main.py進行訓練時可能無法在GPU上順利執行,因此我們采取了另外一個策略,也即通過運行legacy文件夾下的train.py文件)
run legacy/train.py --train_dir voc/train_dir/ --pipeline_config_path voc/faster_rcnn_inception_resnet_v2_atrous_pets.config
可看到已經開始訓練了:
需要注意的是,如果發生內存和顯存不足報錯的情況,除了使用較小模型進行訓練外,還可以修改配置文件中的以下內容:
image_resizer { keep_aspect_ratio_resizer { min_dimension: 600 max_dimension: 1024 } }
這個部分表示將輸入圖像進行等比例縮放再進行訓練,縮放后的最大邊長為1024,最小邊長為600.可以將整兩個數值改小(我訓練的時候就分別改成512和300),使用的顯存就會變小。不過這樣做也可能導致模型的精度下降,因此我們需要根據自己的情況選擇適合的處理方法。
另外由於我們在設置文件中設置的訓練步數為200000步,因此整個訓練可能會消耗大量時間,這里我訓練到20000步就強行終止訓練了。
5、導出模型並預測單張圖片
如何將train_dir中的checkpoint文件導出並用於單張圖片的目標檢測?TensorFlow Object Detection API提供了一個export_inference_graph.py腳本用於導出訓練好的模型。具體方法是在object_detection目錄下執行:
run export_inference_graph.py \ --input_type image_tensor \ --pipeline_config_path voc/faster_rcnn_inception_resnet_v2_atrous_pets.config \ --trained_checkpoint_prefix voc/train_dir/model.ckpt-129 \ --output_directory voc/export/
其中model.ckpt-129表示使用第129步保存的模型。我們需要根據訓練文件夾下checkpoint的實際步數改成對應的值。導出的模型是voc/export/frozen_inference_graph.pb文件。
然后可以參考上面我們介紹的jupyter notebook代碼,自行編寫利用導出模型對單張圖片做目標檢測的腳本。然后將PATH_TO_FROZEN_GRAPH的值賦值為voc/export/frozen_inference_graph.pb,即導出模型文件。將PATH_TO_LABELS修改為voc/pascal_label_map.pbtxt,即各個類別的名稱。其它代碼都可以不改變,然后測試我們的圖片
1 import numpy as np 2 import os 3 import six.moves.urllib as urllib 4 import sys 5 import tarfile 6 import tensorflow as tf 7 import zipfile 8 from distutils.version import StrictVersion 9 from collections import defaultdict 10 from io import StringIO 11 from matplotlib import pyplot as plt 12 from PIL import Image 13 from object_detection.utils import ops as utils_ops 14 from utils import label_map_util 15 from utils import visualization_utils as vis_util 16 if StrictVersion(tf.__version__) < StrictVersion('1.9.0'): 17 raise ImportError('Please upgrade your TensorFlow installation to v1.9.* or later!') 18 19 %matplotlib inline 20 21 #frozen_inference_graph.pb文件就是后面需要導入的文件,它保存了網絡的結構和數據 22 PATH_TO_FROZEN_GRAPH = 'voc/export/frozen_inference_graph.pb' 23 # mscoco_label_map.pbtxt文件中保存了index到類別名的映射,該文件就在object_dection/data文件夾下 24 PATH_TO_LABELS = os.path.join('data', 'pascal_label_map.pbtxt') 25 26 #新建一個圖 27 detection_graph = tf.Graph() 28 with detection_graph.as_default(): 29 od_graph_def = tf.GraphDef() 30 with tf.gfile.GFile(PATH_TO_FROZEN_GRAPH, 'rb') as fid: 31 serialized_graph = fid.read() 32 od_graph_def.ParseFromString(serialized_graph) 33 tf.import_graph_def(od_graph_def, name='') 34 35 category_index = label_map_util.create_category_index_from_labelmap(PATH_TO_LABELS, use_display_name=True) 36 37 #這個函數也是一個方便使用的幫助函數,功能是將圖片轉換為Numpy數組的形式 38 def load_image_into_numpy_array(image): 39 (im_width, im_height) = image.size 40 return np.array(image.getdata()).reshape( 41 (im_height, im_width, 3)).astype(np.uint8) 42 43 #檢測 44 PATH_TO_TEST_IMAGES_DIR = 'test_images' 45 TEST_IMAGE_PATHS = [ os.path.join(PATH_TO_TEST_IMAGES_DIR, 'image{}.jpg'.format(i)) for i in range(1,3 ) ] 46 # 輸出圖像的大小(單位是in) 47 IMAGE_SIZE = (12, 8) 48 with tf.Session(graph=detection_graph) as sess: 49 for image_path in TEST_IMAGE_PATHS: 50 image = Image.open(image_path) 51 #將圖片轉換為numpy格式 52 image_np = load_image_into_numpy_array(image) 53 54 #將圖片擴展一維,最后進入神經網絡的圖片格式應該是[1,?,?,3],括號內參數分別為一個batch傳入的數量,寬,高,通道數 55 image_np_expanded = np.expand_dims(image_np,axis = 0) 56 57 #獲取模型中的tensor 58 image_tensor = detection_graph.get_tensor_by_name('image_tensor:0') 59 60 #boxes變量存放了所有檢測框 61 boxes = detection_graph.get_tensor_by_name('detection_boxes:0') 62 #score表示每個檢測結果的confidence 63 scores = detection_graph.get_tensor_by_name('detection_scores:0') 64 #classes表示每個框對應的類別 65 classes = detection_graph.get_tensor_by_name('detection_classes:0') 66 #num_detections表示檢測框的個數 67 num_detections = detection_graph.get_tensor_by_name('num_detections:0') 68 69 #開始檢測 70 boxes,scores,classes,num_detections = sess.run([boxes,scores,classes,num_detections], 71 feed_dict={image_tensor:image_np_expanded}) 72 73 #可視化結果 74 #squeeze函數:從數組的形狀中刪除單維度條目,即把shape中為1的維度去掉 75 vis_util.visualize_boxes_and_labels_on_image_array( 76 image_np, 77 np.squeeze(boxes), 78 np.squeeze(classes).astype(np.int32), 79 np.squeeze(scores), 80 category_index, 81 use_normalized_coordinates=True, 82 line_thickness=8) 83 plt.figure(figsize=IMAGE_SIZE) 84 plt.imshow(image_np)
電腦配置太低,訓練實在是太慢了,訓練到一百多步我就人為終止了,所以檢測效果並不好。
參考文獻:21個項目玩轉深度學習
http://www.cnblogs.com/zyly/p/9248394.html
https://blog.csdn.net/comway_Li/article/details/81434358