Deeplearning.ai課程中第4門卷積神經(jīng)網(wǎng)絡(luò)里, 第4周的作業(yè)是做一個(gè)人臉識(shí)別的小應(yīng)用.
這門課的作業(yè)設(shè)計(jì)都有些問(wèn)題. 因?yàn)樾枰臄?shù)據(jù)量很大, 需要的算力也很高, 所以不大可能讓學(xué)生從頭做一個(gè)深度神經(jīng)網(wǎng)絡(luò)然后訓(xùn)練出結(jié)果. 所以作業(yè)基本是兩頭的, 一頭是教如何建立這個(gè)深度神經(jīng)網(wǎng)絡(luò), 一頭是教如何應(yīng)用這個(gè)建好的網(wǎng)絡(luò), 中間的部分就一筆帶過(guò)說(shuō)我們已經(jīng)幫各位訓(xùn)練好了, 大家load就行了.
原有的人臉識(shí)別算法:
人臉識(shí)別的算法是所謂one-shot的方式, 不是直接去分類, 而是判斷兩個(gè)圖片是否是屬于統(tǒng)一類的, 或著說(shuō), 就是計(jì)算兩個(gè)圖片之間的距離.
人臉的照片(96*96像素)先經(jīng)過(guò)一個(gè)深度神經(jīng)網(wǎng)絡(luò)編碼器, 輸出一個(gè)128位的編碼.
FRmodel = faceRecoModel(input_shape=(3, 96, 96))
load_weights_from_FaceNet(FRmodel)
這個(gè)編碼器是已經(jīng)訓(xùn)練好的. 需要的話可以從這里下載到, (具體是那個(gè)文件我還沒(méi)弄清楚. )
有了編碼器, 就可以把任意人臉的照片進(jìn)行編碼, 例如:
database["danielle"] = img_to_encoding("images/danielle.png", FRmodel)
然后就是計(jì)算待測(cè)圖片和目標(biāo)圖片編碼的歐式距離. 一句話而已, 但屬于作業(yè)內(nèi)容, 我不能寫出來(lái).
算出距離以后, 跟一個(gè)閾值(! yu4 zhi2 !)比較一下, 如果距離小于閾值就認(rèn)為兩個(gè)圖片是一個(gè)人的, 于是就"識(shí)別"了.
Andrew Ng老師給出的應(yīng)用樣例是百度的門禁. 人走過(guò)去, 照相, 與ID記錄中保存的照片對(duì)比, 距離小于閾值, 放行.
原算法的問(wèn)題
交付的時(shí)候編碼器應(yīng)該是已經(jīng)完成的, 閾值也就是一個(gè)單一的數(shù), 估計(jì)也是寫死的. 所以這個(gè)人臉識(shí)別的應(yīng)用在交付給用戶應(yīng)用的時(shí)候是固定的. 當(dāng)然這也無(wú)可厚非, 畢竟大多數(shù)軟件是這樣的.
但神經(jīng)網(wǎng)絡(luò)畢竟得到的是個(gè)概率, 閾值也是人為設(shè)定的, 有可能出現(xiàn)一直把老板和掃地僧搞混的情況. 或者類似apple FaceID里的問(wèn)題, 也許親緣系數(shù)很近的親屬是可以互相解鎖的. (10歲男兒解鎖其母的FaceID, wired報(bào)道, 網(wǎng)易報(bào)道 )
(寫到這的時(shí)候我意識(shí)到自己不小心開(kāi)源了一個(gè)可以賺錢的算法, 算了, 開(kāi)了就開(kāi)了吧. 好像丟了一大筆錢 )
- 理論上, 可以把編碼器重新訓(xùn)練一遍, 比如把老板, 親屬, 掃地僧的照片加入到訓(xùn)練集里再重新修煉.
- 理論上, 用戶也可以調(diào)整閾值, 提高或者降低特異性敏感性.
但實(shí)際上, 顯然讓用戶重新訓(xùn)練這么大而深的網(wǎng)絡(luò)不現(xiàn)實(shí), 沒(méi)有數(shù)據(jù)集也沒(méi)有那么強(qiáng)的算力. 調(diào)整閾值也可能會(huì)造成其他的連帶問(wèn)題.
綜上, 賦予用戶后期調(diào)整的能力很重要.
并聯(lián)網(wǎng)絡(luò)
之前的神經(jīng)網(wǎng)絡(luò), 全連接也好, 卷積神經(jīng)網(wǎng)絡(luò)也好, 都是串聯(lián)的, 一層接著一層, 直到ResNet出現(xiàn), 在兩層網(wǎng)絡(luò)之間增加了短路的通道. 所謂短路, 就是直接把上一層的計(jì)算結(jié)果送到后面去. 被短路的幾層網(wǎng)絡(luò)只是負(fù)責(zé)訓(xùn)練"殘差".
類似的思想也可以用在這里. 分兩條路徑:
- 代數(shù)函數(shù)路徑
def dist_path(x1,x2,threshold):
d = tf.norm((x1-x2),axis=-1)
# output=1-Activation('sigmoid')(d-threshold)
output = 0.5 - (tf.sign(d-threshold))/2
return output
這條路徑上, 還是使用歐式距離來(lái)計(jì)算兩個(gè)輸入圖片x1,x2之間的距離, 然后跟閾值比較. 如果想二值輸出, 就用符號(hào)函數(shù)sign, 如果想得到連續(xù)輸出就用一下sigmoid.
這條路徑就是原來(lái)的計(jì)算方式, 參數(shù)都是寫死的.
- 修正神經(jīng)網(wǎng)絡(luò)
在這條路徑之外, 我又并聯(lián)了一條神經(jīng)網(wǎng)絡(luò)路徑, 所用的神經(jīng)網(wǎng)絡(luò)其實(shí)要求并不高, 一是大部分的feature已經(jīng)被編碼器模型提取好了, 二是代數(shù)路徑其實(shí)效果已經(jīng)不錯(cuò), 只需要微調(diào)一下. 所以并不一定需要多復(fù)雜的網(wǎng)絡(luò).
def tinker_path(x1,x2):
X=tf.concat([x1,x2],axis=-1)
X = Dense(128, activation='relu',
kernel_initializer =
RandomNormal(mean=0.0, stddev=0.05))(X)
X = Dense(128, activation='relu',
kernel_initializer =
RandomNormal(mean=0.0, stddev=0.05))(X)
X = Dense(1, activation='tanh',
kernel_initializer =
RandomNormal(mean=0.0, stddev=0.05))(X)
return X
這里僅使用了一個(gè)簡(jiǎn)單的全連接網(wǎng)絡(luò)示意, 實(shí)際中可能比這個(gè)層次稍微多一點(diǎn), 但我覺(jué)得沒(méi)必要太深. 卷積網(wǎng)絡(luò)可以考慮, 而且可以考慮把輸入的兩份編碼數(shù)據(jù)交叉排布成一個(gè)類似圖片的東西.
由于并聯(lián)上去的神經(jīng)網(wǎng)絡(luò)是為了修正誤差用的, 所以通常來(lái)說(shuō)它的輸出值應(yīng)該很小, 不影響主路徑的結(jié)果. 所以我在初始化的時(shí)候使用的隨機(jī)初始值是很集中于0附近的. 把標(biāo)準(zhǔn)差stddev改得很小.
- 合并網(wǎng)絡(luò)
將代數(shù)函數(shù)路徑和修正神經(jīng)網(wǎng)絡(luò)并聯(lián)在一起. 所謂并聯(lián)就是將兩個(gè)網(wǎng)絡(luò)進(jìn)行加權(quán)平均.
def face_tinker(x1,x2,threshold,alpha=[0.7, 0.3]):
paths=[dist_path(x1,x2,threshold), tinker_path(x1,x2)]
X=Add()([ a*path for (a,path) in zip(alpha,paths)])
return X
主要的是代數(shù)函數(shù)路徑, 給的權(quán)重應(yīng)當(dāng)高一點(diǎn), 次要的是修正神經(jīng)網(wǎng)絡(luò)路徑, 給的權(quán)重可以低一些, 但是修正神經(jīng)網(wǎng)絡(luò)要真的能夠改變輸出才行, 如果是[0.9, 0.1]這樣的加權(quán)平均就沒(méi)什么意義了.
完整的代碼在github上
后續(xù)
我對(duì)tensorflow還不是很熟練, 還應(yīng)當(dāng)補(bǔ)充上loss, 訓(xùn)練之類的.
實(shí)際使用中如果兩個(gè)人總是搞混, 可以把各自的照片多拍一些, 固定編碼器的參數(shù), 只去訓(xùn)練修正神經(jīng)網(wǎng)絡(luò), 哪怕過(guò)擬合了也沒(méi)什么關(guān)系, 畢竟是小范圍使用, 不必太過(guò)追求泛化.
唉, 丟了一大筆錢.
并聯(lián)網(wǎng)絡(luò)推廣
在已知代數(shù)函數(shù)上并聯(lián)一個(gè)神經(jīng)網(wǎng)絡(luò)進(jìn)行修正的模式很有意思.
現(xiàn)有的知識(shí)中其實(shí)已經(jīng)有大量的線性擬合或者簡(jiǎn)單的非線性擬合工具, 比如計(jì)算人工晶體度數(shù)的SRK公式, 但是這些公式面對(duì)特殊問(wèn)題的時(shí)候還是會(huì)出問(wèn)題, 比如遇到高度近視或者準(zhǔn)分子激光手術(shù)后的患者.
完全拋棄原有的公式, 使用深度神經(jīng)網(wǎng)絡(luò)重做一個(gè)算法當(dāng)然可以, 但這需要大量的數(shù)據(jù)堆砌, 好像人們以前的經(jīng)驗(yàn)也就浪費(fèi)了.
如果在已知代數(shù)函數(shù)的基礎(chǔ)上并聯(lián)一個(gè)神經(jīng)網(wǎng)絡(luò), 用神經(jīng)網(wǎng)絡(luò)來(lái)修正原有函數(shù), 就好像Taylor展開(kāi)那樣, 一點(diǎn)一點(diǎn)近似, 近似到夠用就可以了.
也許對(duì)數(shù)據(jù)集的大小, 對(duì)訓(xùn)練神經(jīng)網(wǎng)絡(luò)所需要的算力, 要求都會(huì)減低一些吧.
不過(guò), 我估計(jì)肯定有很多人嘗試過(guò)了, 而且失敗了不少. 通常覺(jué)得自己有個(gè)新想法的時(shí)候, 只是因?yàn)槲墨I(xiàn)閱讀得不夠多.
EOF( )