
這一次,我拒絕了Python,選擇了Go
如今,神經(jīng)網(wǎng)絡已經(jīng)非常流行,人們將它用于各種任務,特別是人臉識別應用。
最近,我用一個以 Go 語言為后端的軟件,實現(xiàn)了一個人臉識別項目。它能夠識別出上傳照片中的人像 (如流行歌手)是誰。這聽起來不錯,我決定試一下也給你們介紹一下項目的整個過程。
需要說明的是,我盡可能地將所需的系統(tǒng)配置控制在較低水平,以便更多用戶可以通過使用便宜的服務器來進行安裝,而這也是為什么實現(xiàn)過程不使用 CUDA
或 GPU 的原因——雖然你現(xiàn)在可以很容易地租用這樣的服務器,但它需要很高的成本,從而也會將很多潛在的使用者拒之門外。如果它只需要 CPU
而不需要外部依賴就能工作,情況會好很多。
▌選擇合適的語言
如果你詢問數(shù)據(jù)科學家或者那些有神經(jīng)網(wǎng)絡實踐經(jīng)驗的工作者,幾乎所有人都會建議你使用 Python
語言來解決機器學習任務??紤]到語言社區(qū),可用庫的數(shù)量,語言的簡單性等,Python 語言確實是一個明智的選擇。此外,在 Python
中,你還可以通過一些精彩的實例說明和文檔來找到一些受歡迎的人臉識別庫。
然而,這一次,我決定選用 Go 語言,主要有幾以下幾個原因:
我的論壇是用 Go 語言編寫的,我個人也真的很喜歡以 single-binary 為后端所帶來的便捷性。因此,在后端部署并整合人臉識別過程,而不需要 Python 實現(xiàn)的一些依賴和 IPC,這是很棒的。
Go 語言通常比 Python 更快,消耗的內(nèi)存更少。任何高性能 Python 庫的關鍵部分都是用 C / C++
語言編寫的,因此,無論如何你都會有 Python VM 的開銷。我偏愛于更快的語言,除非這種語言會嚴重影響開發(fā)時間。我不會用 C或C++ 作為
Web 應用程序編寫的主要語言,但 Go語言很好,它幾乎和 Python 一樣簡單。
我沒有在 Go 語言中找到人臉識別的有關庫,因此用 Go 語言實現(xiàn)這樣一個應用,對于整個社區(qū)而言,都是一件有趣又有幫助的事。
▌選擇合適的框架
如前所述,神經(jīng)網(wǎng)絡以及相應的實現(xiàn)框架如今正被廣泛地使用。僅在計算機視覺領域,可用的框架就有 Caffe,Torch,TensorFlow 等。
但是,有一個非??岬?a href='/map/jiqixuexi/' style='color:#000;font-size:inherit;'>機器學習庫 —— dlib 庫,一下就吸引了我的注意力。首先,它是用 C ++ 語言編寫的,因此你可以使用 cgo
輕松地創(chuàng)建 Go 語言綁定。其次,在 Wild benchmarks 基準的人臉識別任務上,據(jù)說它能實現(xiàn) 99.38%
的準確性,這聽起來是很不可思議的。再者,現(xiàn)在一些流行的人臉識別庫 face_recognition 和 openface 在底層都使用 dlib
庫,因此它在該任務上會是一個非常好的選擇。
▌安裝依賴項
一旦框架確定下來,那么我們要如何在機器上開發(fā)并部署這個項目呢?首先,C++ 依賴項的安裝將會有很大的困難,因為你無法通過簡便的“go
get”或“pip
install”命令來實現(xiàn)。要么只能希望你的操作系統(tǒng)存儲庫中提供這些依賴庫,要么你只能通過繁瑣的編譯過程來安裝,這樣的話,這個問題就更加令人討厭,因為有許多人都在
dlib 編譯過程碰到問題。
如果你不得不通過編譯過程來安裝,那么可以參考一下下面的教程,也許會有幫助
https://gist.github.com/ageitgey/629d75c1baac34dfa5ca2a1928a7aeaf
幸運的是,我們有更好的選擇:如果用戶的目標系統(tǒng)已知,我們可以構(gòu)建 dlib 庫的二進制安裝包來大大簡化整個過程。說到服務器軟件,Ubuntu 幾乎是系統(tǒng)標配,因此首先要保證你能支持這個系統(tǒng)。
Ubuntu的標準倉庫中自帶有 dlib庫,但其版本太舊了:人臉識別僅支持 dlib19.3 版本,所以我們需要構(gòu)建自己的包。我為 Ubuntu 16.04 和 18.04 創(chuàng)建了 PPA (自定義存儲庫),安裝過程非常簡單,如下:
sudo add-apt-repository ppa:kagamih/dlib
sudo apt-get update
sudo apt-get install libdlib-dev
以上命令將安裝最新的 dlib19.15 版本及 Intel 的數(shù)學核心庫,對于 Intel 處理器而言,這似乎是標準 BLAS 和 LAPACK 接口的最快實現(xiàn)。
對于 Debian sid 和 Ubuntu 18.10 (尚未發(fā)布) 而言,標準倉庫中同樣提供了 dlib 的安裝過程,你只需要如下命令:
sudo apt-get install libdlib-dev libopenblas-dev
這將使用 OpenBLAS 來代替 MKL,實現(xiàn)的速度同樣非??臁;蛘撸阋部梢酝ㄟ^ enable non-free package 并安裝 libmkl-dev 來實現(xiàn)。
我們還需要 libjpeg 來加載 JPEG 圖像:在 Ubuntu 上安裝 libjpeg-turbo8-dev 包,或在 Debian 上安裝 libjpeg62-turbo-dev。
到目前為止,我沒有給出其他系統(tǒng)的安裝說明,如果你在安裝 dlib 過程中碰到問題,可以訪問我的 github 希望能為你提供合理有效的安裝建議。
GitHub 地址:
https://github.com/Kagami/go-face
此外,我還考慮為 dlib 庫提供 Docker 鏡像
(其中有少部分內(nèi)容已存在),許多具有復雜依賴關系的項目都傾向于使用這種分布式方法。但在我看來,一個本機包能夠為用戶提供更好的體驗,你不需要在控制臺編寫長命令,也不需要處理
sandbox 環(huán)境中的內(nèi)容。
寫入依賴庫
當前人臉識別庫地工作原理通常是:通過為照片上的每張人臉返回一組數(shù)字 (矢量嵌入或描述符) 來比較區(qū)分它們,并通過比較這些數(shù)字來找到圖像中人的名字 (通常是通過計算歐幾里德距離向量,得到屬于同一個人的兩張人臉的最小距離)。這個概念這次就不在這里贅述了。
創(chuàng)建圖像中人臉的原始代碼并不是個重要的問題,這個過程幾乎是遵循官方的例子就可以了。你可以查看 facerec.cc 及其相應的頭文件 facerec.h,其中定義了 5 個函數(shù)和幾個在 Go 語言和 dlib 庫之間的交互結(jié)構(gòu)。
在這里,雖然 dlib
庫支持所有流行的圖像格式,但它只能從文件中加載它們。這將導致混亂,因為我們通常只會將圖像保存在內(nèi)存中并將其寫入臨時文件。因此,在這里我使用
libjpeg
來編寫自己的圖像加載器。由于大多數(shù)照片都以該格式存儲的,因此這種格式的加載器足以勝任大部分的需要,以后有需要我還會添加其他格式的圖像加載器。
我把 C++ 和 Go 語言的連接層放在 face.go 中。它提供了 Face 結(jié)構(gòu),用于保存圖像中人臉的坐標及其描述符,并通過 Recognizer 為所有操作提供接口,如初始化和實際識別。
一旦我們有了描述符,我們能做什么呢?在最簡單的情況下,你可以通過比較未知描述符與所有已知描述符之間的歐幾里德距離。但這并不完美,即使是當前最先進的人臉識別技術也會得到錯誤的答案。如果想稍微改善一下結(jié)果,我們需要使用每個人的許多圖像,并檢查這些圖像中是否有非常接近于所提供的人臉。
這也正是分類器 classify.cc 所做的工作。首先,計算距離,然后對這些距離進行排序,計算同一個人在前 10 個最小距離中的點擊數(shù)。)
諸如支持向量機,將會在這個任務上提供更好的算法性能。 dlib 甚至為訓練此類模型提供了便捷的 API。很少有文章會提到 SVM 在大型數(shù)據(jù)集上的性能,因此我打算先在大型集合上測試它。
使用
下面得到的結(jié)果你可以在 github 中查看:
import "github.com/Kagami/go-face"
?
GitHub 地址:
https://github.com/Kagami/go-face
相關的所有結(jié)構(gòu)和方法概述,請參閱 GoDoc 文檔,主要包括以下幾個內(nèi)容:
初始化識別器
識別所有的已知圖像并收集描述符
將具有相應類別的已知描述符傳遞給識別器
獲取未知圖像的描述符
對其類別進行分類
以下是一個工作示例,來說明了上述的所有步驟:
package main
import (
"fmt"
"log"
"path/filepath"
"github.com/Kagami/go-face"
)
// Path to directory with models and test images. Here it's
// assumed it points to the
// <https://github.com/Kagami/go-face-testdata> clone.
const dataDir = "testdata"
// This example shows the basic usage of the package: create an
// recognizer, recognize faces, classify them using few known
// ones.
func main() {
// Init the recognizer.
rec, err := face.NewRecognizer(dataDir)
if err != nil {
log.Fatalf("Can't init face recognizer: %v", err)
}
// Free the resources when you're finished.
defer rec.Close()
// Test image with 10 faces.
testImagePristin := filepath.Join(dataDir, "pristin.jpg")
// Recognize faces on that image.
faces, err := rec.RecognizeFile(testImagePristin)
if err != nil {
log.Fatalf("Can't recognize: %v", err)
}
if len(faces) != 10 {
log.Fatalf("Wrong number of faces")
}
// Fill known samples. In the real world you would use a lot of
// images for each person to get better classification results
// but in our example we just get them from one big image.
var samples []face.Descriptor
var cats []int32
for i, f := range faces {
samples = append(samples, f.Descriptor)
// Each face is unique on that image so goes to its own
// category.
cats = append(cats, int32(i))
}
// Name the categories, i.e. people on the image.
labels := []string{
"Sungyeon", "Yehana", "Roa", "Eunwoo", "Xiyeon",
"Kyulkyung", "Nayoung", "Rena", "Kyla", "Yuha",
}
// Pass samples to the recognizer.
rec.SetSamples(samples, cats)
// Now let's try to classify some not yet known image.
testImageNayoung := filepath.Join(dataDir, "nayoung.jpg")
nayoungFace, err := rec.RecognizeSingleFile(testImageNayoung)
if err != nil {
log.Fatalf("Can't recognize: %v", err)
}
if nayoungFace == nil {
log.Fatalf("Not a single face on the image")
}
catID := rec.Classify(nayoungFace.Descriptor)
if catID < 0 {
log.Fatalf("Can't classify")
}
// Finally print the classified label. It should be "Nayoung".
fmt.Println(labels[catID])
}
???
運行下面命令:
mkdir -p ~/go && cd ~/go # Or cd to your $GOPATH
mkdir -p src/go-face-example && cd src/go-face-example
git clone https://github.com/Kagami/go-face-testdata testdata
edit main.go # Paste example code
go get .
../../bin/go-face-example
由于在 dlib 的代碼中大量使用了 C++ 模板,因此需要一些時間來編譯 go-face (在我的 i7 上大約需要運行 1 分鐘)。 幸運的是,Go 語言能夠構(gòu)建輸出緩存,這樣可以在今后構(gòu)建的時候速度更快。
上面的示例輸出應打印“Nayoung”,表示能夠正確識別出未知圖像。
模型
go-face 需要 shape_predictor_5_face_landmarks.dat 和
dlib_face_recognition_resnet_model_v1.dat 模型才能開始工作。你可以從 dlib-models 倉庫中下載它們:
mkdir models && cd models
wget https://github.com/davisking/dlib-models/raw/master/shape_predictor_5_face_landmarks.dat.bz2
bunzip2 shape_predictor_5_face_landmarks.dat.bz2
wget https://github.com/davisking/dlib-models/raw/master/dlib_face_recognition_resnet_model_v1.dat.bz2
bunzip2 dlib_face_recognition_resnet_model_v1.dat.bz2
此外,當你要運行示例代碼時,還可以通過 go-face-testdata 倉庫來訪問這些模型。
未來的工作
我對結(jié)果非常滿意,通過簡單的 API,得到不錯的識別結(jié)果,還可以輕松嵌入到 Go 的應用程序中。當然,還有需要改進的地方:
為了追求簡單性和速度,在創(chuàng)建描述符時,go-face 無法對圖像進行一些預處理,如抖動。但是,增加圖像預處理操作是很有必要的,因為它可能會提高識別的性能。
Dlib 庫支持很多圖像格式 (如 JPEG,PNG,GIF,BMP,DNG),但是 go-face 目前只能實現(xiàn) JPEG 格式,未來的工作我們希望可以支持更多的格式。
正如 dlib 的作者 Davis 所建議的,相比于搜索最小距離,采用多類 SVM 可能會得到更好的分類結(jié)果,因此還需要進行額外的測試驗證。
在 go-face 中,除非真的需要,不然我盡量不復制值,但實際上它還測試過大樣本 (10,000+人臉數(shù)據(jù)集) 的測試性能,可能存在一些瓶頸,有待日后完善。
從人臉提取特征向量是一個強大的概念,因為你不需要收集自己的訓練數(shù)據(jù),這也是一項非常艱巨的任務 (Davis 曾提到創(chuàng)建 dlib 中
ResNet 模型所用到的 300 萬張人臉數(shù)據(jù)集),但為了獲得更高的識別性能這可能也是無法避免的,因此值得為自己模型的訓練提供相應的工具。
數(shù)據(jù)分析咨詢請掃描二維碼
若不方便掃碼,搜微信號:CDAshujufenxi
SQL Server 中 CONVERT 函數(shù)的日期轉(zhuǎn)換:從基礎用法到實戰(zhàn)優(yōu)化 在 SQL Server 的數(shù)據(jù)處理中,日期格式轉(zhuǎn)換是高頻需求 —— 無論 ...
2025-09-18MySQL 大表拆分與關聯(lián)查詢效率:打破 “拆分必慢” 的認知誤區(qū) 在 MySQL 數(shù)據(jù)庫管理中,“大表” 始終是性能優(yōu)化繞不開的話題。 ...
2025-09-18CDA 數(shù)據(jù)分析師:表結(jié)構(gòu)數(shù)據(jù) “獲取 - 加工 - 使用” 全流程的賦能者 表結(jié)構(gòu)數(shù)據(jù)(如數(shù)據(jù)庫表、Excel 表、CSV 文件)是企業(yè)數(shù)字 ...
2025-09-18DSGE 模型中的 Et:理性預期算子的內(nèi)涵、作用與應用解析 動態(tài)隨機一般均衡(Dynamic Stochastic General Equilibrium, DSGE)模 ...
2025-09-17Python 提取 TIF 中地名的完整指南 一、先明確:TIF 中的地名有哪兩種存在形式? 在開始提取前,需先判斷 TIF 文件的類型 —— ...
2025-09-17CDA 數(shù)據(jù)分析師:解鎖表結(jié)構(gòu)數(shù)據(jù)特征價值的專業(yè)核心 表結(jié)構(gòu)數(shù)據(jù)(以 “行 - 列” 規(guī)范存儲的結(jié)構(gòu)化數(shù)據(jù),如數(shù)據(jù)庫表、Excel 表、 ...
2025-09-17Excel 導入數(shù)據(jù)含缺失值?詳解 dropna 函數(shù)的功能與實戰(zhàn)應用 在用 Python(如 pandas 庫)處理 Excel 數(shù)據(jù)時,“缺失值” 是高頻 ...
2025-09-16深入解析卡方檢驗與 t 檢驗:差異、適用場景與實踐應用 在數(shù)據(jù)分析與統(tǒng)計學領域,假設檢驗是驗證研究假設、判斷數(shù)據(jù)差異是否 “ ...
2025-09-16CDA 數(shù)據(jù)分析師:掌控表格結(jié)構(gòu)數(shù)據(jù)全功能周期的專業(yè)操盤手 表格結(jié)構(gòu)數(shù)據(jù)(以 “行 - 列” 存儲的結(jié)構(gòu)化數(shù)據(jù),如 Excel 表、數(shù)據(jù) ...
2025-09-16MySQL 執(zhí)行計劃中 rows 數(shù)量的準確性解析:原理、影響因素與優(yōu)化 在 MySQL SQL 調(diào)優(yōu)中,EXPLAIN執(zhí)行計劃是核心工具,而其中的row ...
2025-09-15解析 Python 中 Response 對象的 text 與 content:區(qū)別、場景與實踐指南 在 Python 進行 HTTP 網(wǎng)絡請求開發(fā)時(如使用requests ...
2025-09-15CDA 數(shù)據(jù)分析師:激活表格結(jié)構(gòu)數(shù)據(jù)價值的核心操盤手 表格結(jié)構(gòu)數(shù)據(jù)(如 Excel 表格、數(shù)據(jù)庫表)是企業(yè)最基礎、最核心的數(shù)據(jù)形態(tài) ...
2025-09-15Python HTTP 請求工具對比:urllib.request 與 requests 的核心差異與選擇指南 在 Python 處理 HTTP 請求(如接口調(diào)用、數(shù)據(jù)爬取 ...
2025-09-12解決 pd.read_csv 讀取長浮點數(shù)據(jù)的科學計數(shù)法問題 為幫助 Python 數(shù)據(jù)從業(yè)者解決pd.read_csv讀取長浮點數(shù)據(jù)時的科學計數(shù)法問題 ...
2025-09-12CDA 數(shù)據(jù)分析師:業(yè)務數(shù)據(jù)分析步驟的落地者與價值優(yōu)化者 業(yè)務數(shù)據(jù)分析是企業(yè)解決日常運營問題、提升執(zhí)行效率的核心手段,其價值 ...
2025-09-12用 SQL 驗證業(yè)務邏輯:從規(guī)則拆解到數(shù)據(jù)把關的實戰(zhàn)指南 在業(yè)務系統(tǒng)落地過程中,“業(yè)務邏輯” 是連接 “需求設計” 與 “用戶體驗 ...
2025-09-11塔吉特百貨孕婦營銷案例:數(shù)據(jù)驅(qū)動下的精準零售革命與啟示 在零售行業(yè) “流量紅利見頂” 的當下,精準營銷成為企業(yè)突圍的核心方 ...
2025-09-11CDA 數(shù)據(jù)分析師與戰(zhàn)略 / 業(yè)務數(shù)據(jù)分析:概念辨析與協(xié)同價值 在數(shù)據(jù)驅(qū)動決策的體系中,“戰(zhàn)略數(shù)據(jù)分析”“業(yè)務數(shù)據(jù)分析” 是企業(yè) ...
2025-09-11Excel 數(shù)據(jù)聚類分析:從操作實踐到業(yè)務價值挖掘 在數(shù)據(jù)分析場景中,聚類分析作為 “無監(jiān)督分組” 的核心工具,能從雜亂數(shù)據(jù)中挖 ...
2025-09-10統(tǒng)計模型的核心目的:從數(shù)據(jù)解讀到?jīng)Q策支撐的價值導向 統(tǒng)計模型作為數(shù)據(jù)分析的核心工具,并非簡單的 “公式堆砌”,而是圍繞特定 ...
2025-09-10