Mars演算法實踐——人臉識別

繼盛發表於2019-01-08

源於MaxCompute,阿里首款自研科學計算引擎Mars1月16日開源釋出
https://promotion.aliyun.com/ntms/act/cloud/marsfbh.html

Mars 是一個基於矩陣的統一分散式計算框架,在之前的文章中已經介紹了 Mars 是什麼, 以及 Mars 分散式執行 ,而且 Mars 已經在 GitHub 中開源。當你看完 Mars 的介紹可能會問它能做什麼,這幾乎取決於你想做什麼,因為 Mars 作為底層運算庫,實現了 numpy 70% 的常用介面。這篇文章將會介紹如何使用 Mars 完成你想做的事情。

奇異值分解 (SVD)

在處理紛繁的資料時,作為資料處理者,首先想到的就是降維,SVD 就是其中一種比較常見的降維方法,在 numpy.linalg 模組中就有 svd 方法,當我們有20000個100維的資料需要處理,呼叫 SVD 介面:

In [1]: import numpy as np

In [2]: a = np.random.rand(20000, 100)

In [3]: %time U, s, V = np.linalg.svd(a)
CPU times: user 4min 3s, sys: 10.2 s, total: 4min 13s
Wall time: 1min 18s

可以看到即使 Numpy 使用了 mkl 加速,也需要1分多鐘的執行時間,當資料量更大時,單機的記憶體已經無法處理。

Mars 也實現了 SVD ,但是它比 Numpy 有更快的速度,因為利用矩陣分塊計算的演算法,能夠平行計算

In [1]: import mars.tensor as mt

In [2]: a = mt.random.rand(20000, 100, chunk_size=100)

In [3]: %time U, s, V = mt.linalg.svd(a).execute()
CPU times: user 5.42 s, sys: 1.49 s, total: 6.91 s
Wall time: 1.87 s

可以看到在相同資料量情況下,Mars 有幾十倍速度的提升,僅僅需要1秒多鍾就可以解決20000資料量的降維問題。想象一下淘寶使用者資料做矩陣分解時,分散式的矩陣運算就顯現出其價值。

主成分分析 (PCA)

提到降維,主成分分析也是一種重要的手段。PCA 會選取包含資訊量最多的方向對資料進行投影,其投影方向可以從最大化方差或者最小化投影誤差兩個角度理解。也就是通過低維表徵的向量和特徵向量矩陣,可以基本重構出所對應的原始高維向量。其最主要的公式如下所示:

$$

mathop {max }limits_{{mu _j}} frac{1}{n}{sumlimits_i^n {left( {{x_i}{mu _j} – overline x }
ight)} ^T}left( {{x_i}{mu _j} – overline x }
ight) = {mu _j}^TC{mu _j}

$$

$x_i$為每個樣本的資料,$mu _j$為新的投影方向,我們的目標就是使得投影方差最大化,從而找到主特徵。上面式子中的矩陣$C$在數學中可以用協方差矩陣表示,當然首先要對輸入的樣本做中心化調整。我們可以用隨機產生的陣列看一下 Numpy 是如何實現 PCA 降維操作:

import numpy as np

a = np.random.randint(0, 256, size=(10000, 100))
a_mean = a.mean(axis=1, keepdims=True)
a_new = a - a_mean
cov_a = (a_new.dot(a_new.T)) / (a.shape[1] - 1)

#利用SVD求協方差矩陣前20個特徵值
U, s, V = np.linalg.svd(cov_a)
V = V.T
vecs = V[:, :20]

#用低緯度的特徵向量表示原資料
a_transformed = a.dot(vecs)

由於隨機產生的資料本身就沒有太強的特徵,所以在100維資料中象徵性的取出前20維,一般可以用特徵值的比例取總和的前99%之類的數值。

再看一下 Mars 是如何實現的:

import mars.tensor as mt

a = mt.random.randint(0, 256, size=(10000, 100))
a_mean = a.mean(axis=1, keepdims=True)
a_new = a - a_mean
cov_a = (a_new.dot(a_new.T)) / (a.shape[1] - 1)

#利用SVD求協方差矩陣前20個特徵值
U, s, V = mt.linalg.svd(cov_a)
V = V.T
vecs = V[:, :20]

#用低緯度的特徵向量表示原資料
a_transformed = a.dot(vecs).execute()

可以看到除了 import 的不同,再者就是對最後需要資料的變數呼叫 execute 方法,甚至在未來我們做完 eager 模式後, execute 都可以省去,以前用 Numpy 寫的演算法可以幾乎無縫轉化成多程式以及分散式的程式,再也不用自己手動去寫MapReduce。

人臉識別

當 Mars 實現了基礎演算法時,便可以使用到實際的演算法場景中。PCA最著名的應用就是人臉特徵提取以及人臉識別,單個人臉圖片的維度很大,分類器很難處理,早起比較知名的人臉識別 Eigenface 演算法就是採用PCA演算法。本文以一個簡單的人臉識別程式作為例子,看看 Mars 是如何實現該演算法的。

本文的人臉資料庫用的是ORL face database,有40個不同的人共400張人臉圖片,每張圖片為 92*112 畫素的灰度圖片。這裡選取每組圖片的第一張人臉圖片作為測試圖片,其餘九張圖片作為訓練集。

首先利用 python 的 OpenCV 的庫將所有圖片讀取成一個大矩陣,也就是 360*10304大小的矩陣,每一行是每個人臉的灰度值,一共有360張訓練樣本。利用 PCA 訓練資料,data_mat 就是輸入的矩陣,k 是需要保留的維度。

import mars.tensor as mt
from mars.session import new_session

session = new_session()

def cov(x):
    x_new = x - x.mean(axis=1, keepdims=True)
    return x_new.dot(x_new.T) / (x_new.shape[1] - 1)

def pca_compress(data_mat, k):
    data_mean = mt.mean(data_mat, axis=0, keepdims=True)
    data_new = data_mat - data_mean
    
    cov_data = cov(data_new)

    U, s, V = mt.linalg.svd(cov_data)
    V = V.T
    vecs = V[:, :k]

    data_transformed = vecs.T.dot(data_new)
    return session.run(data_transformed, data_mean, vecs)

由於後續做預測識別,所以除了轉化成低維度的資料,還需要返回平均值以及低維度空間向量。可以看到中間過程平均臉的樣子,前幾年比較火的各地的平均臉就可以通過這種方式獲取,當然這裡的維度以及樣本比較少,大概只能看出個人臉的樣子。

mean_face.jpg

其實 data_transformed 中儲存的特徵臉按照畫素排列之後也能看出特徵臉的形狀。圖中有15個特徵臉,足以用來做一個人臉分類器。

feature_faces.jpg

另外在函式 PCA 中用了 session.run 這個函式,這是由於三個需要返回的結果並不是相互獨立的,目前的延遲執行模式下提交三次運算會增加運算量,同一次提交則不會,當然立即執行模式以及運算過的部分圖的剪枝工作我們也在進行中。

當訓練完成之後,就可以利用降維後的資料做人臉識別了。將之前非訓練樣本的圖片輸入,轉化成降維後的維度表示,在這裡我們就用簡單的歐式距離判斷與之前訓練樣本中每個人臉資料的差距,距離最小的就是識別出的人臉,當然也可以設定某個閾值,最小值超過閾值的判斷為識別失敗。最終在這個資料集下跑出來的準確率為 92.5%,意味著一個簡單的人臉識別演算法搭建完成。

# 計算歐氏距離
def compare(vec1, vec2):
    distance = mt.dot(vec1, vec2) / (mt.linalg.norm(vec1) * mt.linalg.norm(vec2))
    return distance.execute()

未來

上文展示瞭如何利用 Mars 一步一步地完成人臉識別小演算法的過程,可以看到 Mars 類 Numpy 的介面對演算法開發人員十分友好,演算法規模超出單機能力時,不再需要關注如果擴充套件到分散式環境,Mars 幫你處理背後所有的並行邏輯。

當然,Mars 還有很多可以改進的地方,比如在 PCA 中對協方差矩陣的分解,可以用特徵值、特徵向量計算,計算量會遠小於 SVD 方法,不過目前線性代數模組還沒有實現計算特徵向量的方法,這些特性我們會一步步完善,包括 SciPy 裡各種上層演算法介面的實現。大家有需求的可以在 GitHub 上提 issue 或者幫助我們共建 Mars。

Mars 作為一個剛剛開源的專案,十分歡迎提出其他任何想法與建議,我們需要大家的加入,讓 Mars 越來越好。


相關文章