ImageNet冠軍領隊帶你入門計算機視覺:監督學習與神經網路的簡單實現
作者 | 董健
編輯 | VincentAI前線出品| ID:ai-front
AI 前線導語:近幾年,人工智慧的浪潮席捲了整個科技圈。Google,Facebook,微軟,百度等全球最頂尖的科技公司都將目光轉向了人工智慧,並將之視為今後的戰略重心。 隨著人臉識別,輔助駕駛,AlphaGo 等應用的不斷湧現,基於學習的計算機視覺(learning based vision)正在越來越多的改變我們的生活 。本系列文章,將逐步介紹這些看似神奇的系統背後的視覺演算法原理。
本文是整個系列的第一篇文章,將會簡單介紹一下計算機視覺的發展,以及監督學習、神經網路的基本原理。最後的實踐部分,會用 TensorFlow 給出之前介紹演算法的一個簡單實現。
計算機視覺的發展
什麼是計算機視覺? 首先我們看一下維基百科的定義:
Computer vision is an interdisciplinary field that deals with how computers can be made for gaining high-level understanding from digital images or videos.
簡單來說,計算機視覺就是讓機器能自動的理解圖片或視頻。
計算機視覺的起源可以追溯到 1966 年,當時 MIT 著名的工智能專家 Marvin Minsky 給他的本科學生留了一個暑期作業 -「Link a camera to a computer and get the computer to describe what it saw」。 雖然人可以很容易的理解圖片,但事實證明,讓計算機理解圖片遠比我們一開始想像的複雜。
早期的計算機視覺研究,由於計算資源和數據的原因,主要集中在幾何和推理。 上世紀 90 年代,由於計算機硬體的不斷發展和數字照相機的逐漸普及,計算機視覺進入了快速發展期。 這期間的一大突破是各種人工設計特徵的湧現,例如 SIFT,HOG 等局部特徵。 這些特徵相對原始像素具有對尺度,旋轉等的魯棒性,因此得到了廣泛的應用,催生了如圖像拼接、圖像檢索、三位重建等視覺應用。 另一大突破是基於統計和機器學習的方法的流行。隨著數字照片的不斷普及,大規模的數據集也相伴而生,基於學習的計算機視覺(Learning based Vision),由於可以通過大量數據自動學習模型參數,漸漸的成為了主流。
隨著計算能力的不斷進步和海量互聯網數據的產生,傳統的基於人工特徵和 SVM/boosting 等簡單機器學習演算法的視覺技術遇到了瓶頸。因此,工業界和學術界都在探索如何避免繁瑣的人工特徵設計同時加強模型的擬合性能,從而進一步利用海量數據。深度學習很好的滿足了這一需求,因此在視覺領域得到了非常廣泛的應用。2010 年之後,計算機視覺逐漸進入了深度學習的時代。標誌性的事件是 ImageNet 2012 比賽。這次比賽中,基於深度學習的演算法大大超過了經過精心設計的傳統演算法,震驚了整個學術界,進而帶動了深度學習在其他領域中的應用。這次比賽也被看成是深度學習在整個人工智慧領域復興的標誌性事件。
目前,除了三維重建等 low-level vision 問題,基於深度學習的演算法,在大多數視覺問題上的性能已經遠遠超過了傳統演算法,因此本系列文章會重點介紹基於深度學習的計算機視覺演算法。
神經網路 (neural network)
神經網路(NN),簡單來說就是神經元組成的網路,是最早出現,也是最簡單的一種深度學習模型。其他很多更複雜的演算法比如卷積神經網路,深度增強學習中的許多概念都來源於神經網路。因此,我們在這篇文章中先介紹一下神經網路的原理。 要理解神經網路,我們需要先了解什麼是神經元。
神經元 & 感知器
神經元(neuron)是神經網路的最小單位。每個神經元將多個入映射到一個輸出。如圖所示,神經元的輸出是輸入的加權和加上偏置,再通過一個激活函數。具體可以表示成:
激活函數 φ 有各種不同的形式。如果使用 step 函數,那神經元等價於一個線性分類器:
這個分類器在歷史上被稱為感知器(Perceptron)。
多層神經網路
單層的感知器只能解決線性可分的問題。但實際中絕大多數問題都是非線性的,這時單層感知器就無能為力了。 為此,我們可以把單個的 neuron 組成網路,讓前一層 neuron 的輸出做為下一層 neuron 的輸入。組成如下圖所示的神經網路:
由於非線性激活函數的存在,多層神經網路就有了擬合非線性函數的能力。由於歷史的原因,多層神經網路也被稱為 multilayer perceptrons(MLP)。
神經網路具有擬合非線性函數的能力。但是為了擬合不同的非線性函數,我們是否需要設計不同的非線性激活函數和網路結構呢?
答案是不需要。
universal approximation theorem 已經證明,前向神經網路是一個通用的近似框架。 簡單來說,對常用的 sigmoid,relu 等激活函數,即使只有一層隱藏層的神經網路,只要有足夠多的神經元,就可以無限逼近任何連續函數。在實際中,淺層神經網路要逼近複雜非線性函數需要的神經元可能會過多,從而提升了學習的難度並影響泛化性能。因此,我們往往通過使用更深的模型,從而減少所需神經元的數量,提升網路的泛化能力。
機器學習的基本概念
深度神經網路是深度學習中的一類演算法,而深度學習是機器學習的一種特例。因此,這一節我們在機器學習的一般框架下,介紹模型訓練相關的基本概念,及其在 Tensorflow 中的實現。相關概念適用於包括 NN 在內的機器學習演算法。
機器學習的常見問題
常見的機器學習問題,可以抽象為 4 大類問題:
- 監督學習
- 非監督學習
- 半監督學習
- 增強學習
根據訓練數據是否有 label,可以將問題分為監督學習(所有數據都有 label),半監督學習(部分數據有 label)和非監督學習(所有數據都沒有 label)。 增強學習不同於前 3 種問題,增強學習也會對行為給出反饋(reward),但關注的是如何在環境中採取一系列行為,從而獲得最大的累積回報。監督學習是目前應用最廣泛,也是研究最充分的機器學習問題,本文接下來將重點介紹監督學習。
監督學習
在監督學習中,給定 N 個訓練樣本
我們的目標是得到一個從輸入到輸出的函數:
實際中,我們通常不會直接優化函數 f,而是根據問題的具體情況,選擇一組參數化的函數 fθ, 將優化函數 f 轉換成優化參數θ。
常見的分類問題和回歸問題,都是監督學習的一種特例。線性分類器,深度神經網路等模型,都是為了解決這些問題設計的參數化後的函數。為了簡單,我們以線性分類器
為例,需要優化的參數θ為 (w,b)。
損失函數
為了衡量函數的好壞,我們需要一個客觀的標準。 在監督學習中,這個評判標準通常是一個損失函數
對一個訓練樣本
模型的預測結果為 y^,那對應的損失為
損失越小,表明函數預測的結果越準確。實際中,需要根據問題的特點,選擇不同的損失函數。
二分類問題中,常用的 logistic regression 採用 sigmoid + cross entroy 作為 loss。對常見的 loss,tensorflow 都提供了相應的函數。
loss = tf.nn.sigmoid_cross_entropy_with_logits(labels=labels, logits=y)
對多分類問題,如上圖所示,我們可以將二分類的線性分類器進行擴展為 N 個線性方程:
然後通過
進行歸一化,歸一化後的結果作為每一類的概率。因此,多分類通常使用 softmax + cross entropy 作為損失函數。
loss = tf.nn.softmax_cross_entropy_with_logits(labels=labels, logits=logits)
可以證明,對於二分類問題,採用 sigmoid cross entroy 和 softmax cross entory 作為 loss,在理論上是完全等價的。此外,實際中通常採用直接計算 softmax cross entropy 而不是先計算 softmax,後計算 cross entory,這主要是從數值穩定性的角度考慮的。
損失最小化和正則項
在定義了損失函數之後,監督學習問題可以轉化為最小化實驗損失
實際中,為了保證模型的擬合能力,函數 $f$ 的複雜度有時會比較高。如最圖中最右邊情況所示,如果訓練樣本數較少或者 label 有錯誤時,直接最小化實驗損失而不對 $f$ 加限制的話,模型容易過擬合。
因此,在樣本數量較少或者標註質量不高的情況下,需要額外添加正則項(regularizer),保證模型的泛化能力。實際中根據不同需求,可以選擇不同的正則項。在神經網路當中,比較常見的是 l2 norm 正則項:
在 tensorflow 中,通常有兩種方法添加正則項。一種是根據需要,自己實現相應的 regularization loss,然後和其他 loss 相加進行優化。這種方法可以實現比較複雜的 regularizer。
weight_decay = tf.multiply(tf.nn.l2_loss(weights), wd, name=weight_loss)
對於常見的正則項,也可以使用 tensorflow 自帶的功能,對相應的變數進行正則化。然後將系統生成的所有 regularization loss 和其他 loss 相加進行優化。
tf.contrib.layers.apply_regularization(tf.contrib.layers.l2_regularizer(wd), weights)tf.losses.get_regularization_losses()
梯度下降和反向傳播
在定義了損失函數和正則項之後,最終正則化後的 loss 為:
有了 loss 函數,相應的參數可以通過標準的梯度下降演算法進行優化求解。例如對線性分類器中的 $w$,可以通過如下的公式進行迭代更新:
通過設定合適的 learning rate,參數會逐步收斂到局部 / 全局最優解。
反向傳播可以看成梯度下降在神經網路中的一個推廣,也是通過最小化 loss 函數,計算參數相對於 loss 的梯度,然後對參數進行迭代更新。具體的推導因為篇幅原因在這裡略過了。在 tensorflow 中,只需要指定 loss 函數和步長(learning rate),optimizer 可以自動幫我們完成梯度下降 / 反向傳播的過程:
tf.train.GradientDescentOptimizer(learning_rate).minimize(loss)
OCR 實戰
最後本文通過一個 OCR 的例子,展示如何用 Tensorflow 實現 Softmax 分類器和 MLP 分類器。實驗數據集採用著名的數字識別數據集 MNIST。該數據集包含了 60000 張訓練圖片和 10000 張測試圖片。數據集中的每一張圖片都代表了 0-9 中的一個數字,圖片的尺寸為 28×28。
Softmax 分類器
我們首先實現一個簡單的 Softmax 分類器。由於 Tensorflow 運行構建好的網路的過程比較複雜,為了提高開發效率和代碼的復用性,我們將代碼分成了 3 個主要模塊,分別是 Dataset 模塊,Net 模塊和 Solver 模塊。
模型結構
對每一個 Net 類里,我們需要實現三個函數:
inference
我們在 inference 函數中定義網路的主體結構。在 tensorflow 中,變數用 tf.Variable 表示。因為 Softmax 分類器是一個凸函數,任何初始化都可以保證達到全局最優解,因此我們可以簡單的將W
和b
初始化為 0。 Softmax 分類器可以簡單的通過一個矩陣乘法 y = tf.matmul(data, W) + b
後接一個tf.nn.softmax
函數實現。
loss
按照之前介紹的,為了保證數值穩定性,我們直接採用直接計算tf.nn.softmax_cross_entropy_with_logits
的方式。
metric
在訓練完模型後,我們需要在 validation 或 test 集合上驗證模型的性能。在測試集比較大時,我們無法一次得到模型在整個測試集上的結果,需要將測試集分成小的 batch,在每個 batch 上進行測試,之後將每個 batch 的結果匯總起來。 為此,tensorflow 提供了 tf.metrics 模塊,可以自動完成對每個 batch 進行評價,並將所有的評價匯總的功能。在這個例子里,我們是解決分類問題,因此可以使用tf.metrics.accuracy
計算分類的準確率。
Dataset
In versions of TensorFlow before 1.2, we recommended using multi-threaded, queue-based input pipelines for performance. Beginning with TensorFlow 1.2, however, we recommend using the tf.contrib.data module instead.
從 Tensorflow1.2 開始,Tensorflow 提供了基於 tf.contrib.data 的新 API。相比原來基於 QuequRunner 和 Coordinator 的 API,代碼結構簡潔了很多。所以我們在 Dataset 類中採用了新的 API,實現數據讀取。
我們首先讀取了 numpy.array 格式的 mnist 數據集images, labels
。然後通過tf.contrib.data.Dataset.from_tensor_slices
將之轉換成 tf.contrib.data.Dataset 格式。之後我們可以設置對 Dataset 的遍歷次數(None 代表無限次),batch size 以及是否對數據集進行 shuffle。 最後,我們採用最簡單的make_one_shot_iterator()
和get_next()
,得到網路的基本數據單元 batch。 按默認配置,每個 batch 含有 50 張圖和其對應的 label。
Solver
最後我們介紹 Sover 類。Solver 類主要包含五個函數:
- build_optimizer
因為網路比較簡單,這裡我們選用最基本的隨即梯度下降演算法tf.train.GradientDescentOptimizer
,並使用了固定的 learning rate。
- build_train_net、build_test_net
這兩個函數的作用類似,都是將 Dataset 中的數據和 Net 中的網路結構串聯起來。在最後我們調用tf.summary.scalar
將 loss 添加到 summary 中。 tensorflow 提供了強大的可視化模塊 tensorboard,可以很方便的對 summary 中的變數進行可視化。
- train_net
在 train_net 的開頭,我們完成了 Graph,Saver,summary 等模塊的初始化。 然後通過summary_writer.add_graph(tf.get_default_graph())
,將網路結構列印到 summary 中。
之後初始化tf.Session()
,並通過session.run
運行對應的操作。在 tensorflow 使用了符號式編程的模式,創建 Graph 的過程只是完成了構圖,並沒有對數據進行實際運算。在 Session 中運行對應的操作時,才真正對底層數據進行操作。
- test_net
和 train_net 類似,test_net 主要完成了各種模塊的初始化,之後讀取模型目錄文件下的 checkpoint 文件中記錄的最新的模型,並在測試集中進行測試。
下圖是 tensorboad 中可視化的網路結構,和 loss 的統計。可以看到,tensorboad 對我們進行分析提供很好的可視化支持。
最終程序的輸出結果如下:
可以看到,一個簡單的線性模型可以達到 92% 的準確率。我們猜測數字識別這個問題應該不是線性可分,因此使用更複雜的非線性分類器應該可以得到更好的結果。
MLP 分類器
由於我們採用了模塊化的設計,各個模塊直接基本是解耦合的。因此將 Softmax 分類器替換成 MLP 非常容易,我們只需要重新實現 Net 層就可以了。
為了證明 MLP 的效果,我們構造了一個含有 2 層 hidden layer 的神經網路。代碼結構和 Softmax 分類其大致一樣,就不做過多解釋了。 因為線性層在網路中多次出現,我們將他抽象為一個可以復用的函數。 另外,為了讓 graph 在 tensorboad 中的可視化效果更好,我們將相關的變數和操作,通過with tf.variable_scope(hidden1):
放置在同一個 variable_scope 下面。這樣所有相關變數和操作在 tensorboad 都會收縮成一個可以展開的節點,從而提供更好的可視化效果。
最終的網路結果和運行結果如下所示:
可以看到,使用了包含 2 層 hidden layer 的簡單神經網路,分類的準確率已經提升到了 97%,大大優於簡單的線性分類器。證明了模型選擇對最終性能的重要影響。
完整代碼下載:
https://github.com/Dong--Jian/Vision-Tutorial
作者簡介
董健,360 高級數據科學家,前 Amazon 研究科學家。 目前主要關注深度學習、強化學習、計算機視覺等方面的科學和技術創新,擁有豐富的大數據、計算機視覺經驗。曾經多次領隊參加 Pascal VOC、ImageNet 等世界著名人工智慧競賽並獲得冠軍。
博士期間在頂級國際學術會議和雜誌上發表過多篇學術論文。從 2015 年底加入 360 至今,董健作為主要技術人員參與並領導了多個計算機視覺和大數據項目。
-全文完-
關注人工智慧的落地實踐,與企業一起探尋 AI 的邊界,AICon 全球人工智慧技術大會火熱售票中,8 折倒計時一周搶票,詳情點擊:
https://aicon.geekbang.org/?utm_source=ai-front&utm_medium=zhihu
《深入淺出TensorFlow》迷你書現已發布,關注公眾號「AI前線」,ID:ai-front,回復關鍵字:TF,獲取下載鏈接!
推薦閱讀:
※生成式對抗網路(GAN)基礎
※計算機視覺涉及基礎總結與相關書目推薦
※看AI產品經理如何介紹「計算機視覺」(基於實戰經驗和案例)
※IROS2017筆記->Toward Unifying Model-based and Learning-based Robotics