Tensorflow[實戰(zhàn)篇]——Face Recognition

前言

本文章的參考卷積神經(jīng)網(wǎng)絡(luò)應(yīng)用于人臉識別,通過Tensorflow改寫的代碼。也通過自己的想法改動了一些代碼。本文算是一個小小的demo吧,因為之前都是基礎(chǔ)篇。而這個算是基于之前的基礎(chǔ)學(xué)習(xí)做的demo。雖然本demo比較簡單,但我覺得可以給大家一些啟示。

源碼地址:https://github.com/Salon-sai/learning-tensorflow/tree/master/lesson3


數(shù)據(jù)材料

這是一個小型的人臉數(shù)據(jù)庫,一共有40個人,每個人有10張照片作為樣本數(shù)據(jù)。這些圖片都是黑白照片,意味著這些圖片都只有灰度0-255,沒有rgb三通道。于是我們需要對這張大圖片切分成一個個的小臉。整張圖片大小是1190 × 942,一共有20 × 20張照片。那么每張照片的大小就是(1190 / 20)× (942 / 20)= 57 × 47 (大約,以為每張圖片之間存在間距)。


olivettifaces數(shù)據(jù)庫

設(shè)計思路

那么我們得到這些樣本圖片之后,再將圖片進(jìn)行分類,每一個人一個類別,每個類別10張圖片樣本。我就取每一類的前八張作為訓(xùn)練樣本,每類第九張作為校驗樣本,每類第十張圖片作為測試樣本。每張圖片的label使用one-hot向量表示。

至于模型設(shè)計上面,選取了CNN卷積神經(jīng)網(wǎng)絡(luò)作為設(shè)計模型,一共有兩層卷積,一層全連接層,最后一層用softmax作為分類。然后用softmax的預(yù)測結(jié)果和真實的labels做Cross-Entropy,這樣就得到了Loss Function了。

代碼展示(部分)和說明

獲取dataset

def load_data(dataset_path):
    img = Image.open(dataset_path)
    # 定義一個20 × 20的訓(xùn)練樣本,一共有40個人,每個人都10張樣本照片
    img_ndarray = np.asarray(img, dtype='float64') / 64

    # 記錄臉數(shù)據(jù)矩陣,57 * 47為每張臉的像素矩陣
    faces = np.empty((400, 57 * 47))

    for row in range(20):
        for column in range(20):
            faces[20 * row + column] = np.ndarray.flatten(
                img_ndarray[row * 57: (row + 1) * 57, column * 47 : (column + 1) * 47]
            )

    label = np.zeros((400, 40))
    for i in range(40):
        label[i * 10: (i + 1) * 10, i] = 1

    # 將數(shù)據(jù)分成訓(xùn)練集,驗證集,測試集
    train_data = np.empty((320, 57 * 47))
    train_label = np.zeros((320, 40))
    vaild_data = np.empty((40, 57 * 47))
    vaild_label = np.zeros((40, 40))
    test_data = np.empty((40, 57 * 47))
    test_label = np.zeros((40, 40))

    for i in range(40):
        train_data[i * 8: i * 8 + 8] = faces[i * 10: i * 10 + 8]
        train_label[i * 8: i * 8 + 8] = label[i * 10: i * 10 + 8]

        vaild_data[i] = faces[i * 10 + 8]
        vaild_label[i] = label[i * 10 + 8]

        test_data[i] = faces[i * 10 + 9]
        test_label[i] = label[i * 10 + 9]

    return [
        (train_data, train_label),
        (vaild_data, vaild_label),
        (test_data, test_label)
    ]

跟之前說的思路一樣,讀取圖片然后按照每張照片的大小57 × 47去劃分照片,最后得到一個faces的數(shù)據(jù)矩陣,然后再給每張照片賦予相應(yīng)的label。最后劃分?jǐn)?shù)據(jù)成為訓(xùn)練數(shù)據(jù)集,校驗數(shù)據(jù)集,測試數(shù)據(jù)集。

模型網(wǎng)絡(luò)

1. 卷積層:

def convolutional_layer(data, kernel_size, bias_size, pooling_size):
    kernel = tf.get_variable("conv", kernel_size, initializer=tf.random_normal_initializer())
    bias = tf.get_variable('bias', bias_size, initializer=tf.random_normal_initializer())

    conv = tf.nn.conv2d(data, kernel, strides=[1, 1, 1, 1], padding='SAME')
    linear_output = tf.nn.relu(tf.add(conv, bias))
    pooling = tf.nn.max_pool(linear_output, ksize=pooling_size, strides=pooling_size, padding="SAME")

    return pooling

我定義了一個卷積層的函數(shù)用于,每在不同的scope下調(diào)用一次就生成相應(yīng)的卷積層及其參數(shù)。哈哈,是不是覺得這招很熟悉很好用呢?kernel,和bias分別是卷積核和偏移量(卷積就是對局部進(jìn)行Linear操作)。最后就是max_pool層(取得核中的最大值代碼這個核里面的元素,因為值越大顏色越深,那就越能代表他的特征。也可以理解成一個去噪的操作吧),做完pool層后,我們算是對圖片進(jìn)行一個(卷積+pooling)處理。

2. 全連接層和分類層的Linear部分:

def linear_layer(data, weights_size, biases_size):
    weights = tf.get_variable("weigths", weights_size, initializer=tf.random_normal_initializer())
    biases = tf.get_variable("biases", biases_size, initializer=tf.random_normal_initializer())

    return tf.add(tf.matmul(data, weights), biases)

相信大家都知道這是一個很有簡單的線性操作,我在這里就不多說了。

3. 整個網(wǎng)絡(luò):

def convolutional_neural_network(data):
    # 根據(jù)類別個數(shù)定義最后輸出層的神經(jīng)元
    n_ouput_layer = 40

    kernel_shape1=[5, 5, 1, 32]
    kernel_shape2=[5, 5, 32, 64]
    full_conn_w_shape = [15 * 12 * 64, 1024]
    out_w_shape = [1024, n_ouput_layer]

    bias_shape1=[32]
    bias_shape2=[64]
    full_conn_b_shape = [1024]
    out_b_shape = [n_ouput_layer]

    data = tf.reshape(data, [-1, 57, 47, 1])

    # 經(jīng)過第一層卷積神經(jīng)網(wǎng)絡(luò)后,得到的張量shape為:[batch, 29, 24, 32]
    with tf.variable_scope("conv_layer1") as layer1:
        layer1_output = convolutional_layer(
            data=data,
            kernel_size=kernel_shape1,
            bias_size=bias_shape1,
            pooling_size=[1, 2, 2, 1]
        )
    # 經(jīng)過第二層卷積神經(jīng)網(wǎng)絡(luò)后,得到的張量shape為:[batch, 15, 12, 64]
    with tf.variable_scope("conv_layer2") as layer2:
        layer2_output = convolutional_layer(
            data=layer1_output,
            kernel_size=kernel_shape2,
            bias_size=bias_shape2,
            pooling_size=[1, 2, 2, 1]
        )
    with tf.variable_scope("full_connection") as full_layer3:
        # 講卷積層張量數(shù)據(jù)拉成2-D張量只有有一列的列向量
        layer2_output_flatten = tf.contrib.layers.flatten(layer2_output)
        layer3_output = tf.nn.relu(
            linear_layer(
                data=layer2_output_flatten,
                weights_size=full_conn_w_shape,
                biases_size=full_conn_b_shape
            )
        )
        # layer3_output = tf.nn.dropout(layer3_output, 0.8)
    with tf.variable_scope("output") as output_layer4:
        output = linear_layer(
            data=layer3_output,
            weights_size=out_w_shape,
            biases_size=out_b_shape
        )

    return output;

一圖勝千言:


模型設(shè)計

訓(xùn)練過程:

在訓(xùn)練中我使用softmax_cross_entropy_with_logits作為Loss function,AdamOptimizer作為優(yōu)化算法(學(xué)習(xí)率為0.01,由于我的本本已經(jīng)是6年前的產(chǎn)物,而且沒有GPU,唯有調(diào)大點,讓他快點收斂算了[哭崩的臉.jpg])。

    predict = convolutional_neural_network(X)
    cost_func = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(logits=predict, labels=Y))
    optimizer = tf.train.AdamOptimizer(1e-2).minimize(cost_func)

    # 用于保存訓(xùn)練的最佳模型
    saver = tf.train.Saver()
    model_dir = './model'
    model_path = model_dir + '/best.ckpt'

    with tf.Session() as session:
        # 若不存在模型數(shù)據(jù),需要訓(xùn)練模型參數(shù)
        if not os.path.exists(model_path + ".index"):
            session.run(tf.global_variables_initializer())
            best_loss = float('Inf')
            for epoch in range(20):
                epoch_loss = 0
                for i in range(np.shape(train_set_x)[0] / batch_size):
                    x = train_set_x[i * batch_size: (i + 1) * batch_size]
                    y = train_set_y[i * batch_size: (i + 1) * batch_size]
                    _, cost = session.run([optimizer, cost_func], feed_dict={X: x, Y: y})
                    epoch_loss += cost

                print(epoch, ' : ', epoch_loss)
                if best_loss > epoch_loss:
                    best_loss = epoch_loss
                    if not os.path.exists(model_dir):
                        os.mkdir(model_dir)
                        print("create the directory: %s" % model_dir)
                    save_path = saver.save(session, model_path)
                    print("Model saved in file: %s" % save_path)

再次強調(diào),我的本本很舊了,而且鄙人又是學(xué)生。因此我把epoch設(shè)置為20。還好loss function應(yīng)該是一個convex function,所以也在20個epoch里到達(dá)min值0。(假如有錢就施舍一下給小弟弟吧,我又窮又想要GPU,難道我去拍AV當(dāng)男優(yōu)賺錢嗎?還是當(dāng)鴨好呢?)。最后就是有個Saver用于保存模型,畢竟他讓我本本元氣打傷,不可以讓訓(xùn)練的好的模型就此消失與內(nèi)存中。我都是保留最優(yōu)(loss function最?。┑哪P?。

校驗與測試

# 恢復(fù)數(shù)據(jù)并校驗和測試
saver.restore(session, model_path)
correct = tf.equal(tf.argmax(predict,1), tf.argmax(Y,1))
valid_accuracy = tf.reduce_mean(tf.cast(correct,'float'))
print('valid set accuracy: ', valid_accuracy.eval({X: vaild_set_x, Y: valid_set_y}))

test_pred = tf.argmax(predict, 1).eval({X: test_set_x})
test_true = np.argmax(test_set_y, 1)
test_correct = correct.eval({X: test_set_x, Y: test_set_y})
incorrect_index = [i for i in range(np.shape(test_correct)[0]) if not test_correct[i]]
for i in incorrect_index:
    print('picture person is %i, but mis-predicted as person %i'
        %(test_true[i], test_pred[i]))
plot(incorrect_index, "olivettifaces.gif")

最后就是恢復(fù)模型并且計算出校驗集的準(zhǔn)確率以及測試數(shù)據(jù)哪些出現(xiàn)錯誤,并標(biāo)注出來。

畫出在測試集中錯誤的數(shù)據(jù)

def plot(error_index, dataset_path):
    img = mpimg.imread(dataset_path)
    plt.imshow(img)
    currentAxis = plt.gca()
    for index in error_index:
        row = index // 2
        column = index % 2
        currentAxis.add_patch(
            patches.Rectangle(
                xy=(
                     47 * 9 if column == 0 else 47 * 19,
                     row * 57
                    ),
                width=47,
                height=57,
                linewidth=1,
                edgecolor='r',
                facecolor='none'
            )
    )
    plt.savefig("result.png")
    plt.show()
后臺打印的結(jié)果

我沒騙大家,我只是用cpu運算,我真的很窮(做鴨還是當(dāng)男優(yōu)呢?)。

最后是測試集合,當(dāng)中有五個標(biāo)記錯誤,在console也看到。這張圖片是標(biāo)注了那些被分類錯誤的。

分類錯誤的臉孔

總結(jié)

這是一個實戰(zhàn)篇,也算是給大家介紹一些關(guān)于Tensorflow卷積和pool的使用,但我覺得這個難度一般般吧。因為我覺得這些數(shù)據(jù)比較小,而且模型都比較簡單,大家應(yīng)該可以掌握。

不知道你有沒有這樣的感受,在剛剛?cè)腴T機器學(xué)習(xí)的時候,我們一般都是從MNIST、CIFAR-10這一類知名公開數(shù)據(jù)集開始快速上手,復(fù)現(xiàn)別人的結(jié)果,但總覺得過于簡單,給人的感覺太不真實。因為這些數(shù)據(jù)太“完美”了(干凈的輸入,均衡的類別,分布基本一致的測試集,還有大量現(xiàn)成的參考模型),要成為真正的數(shù)據(jù)科學(xué)家,光在這些數(shù)據(jù)集上跑模型卻是遠(yuǎn)遠(yuǎn)不夠的。而現(xiàn)實中你幾乎不可能遇到這樣的數(shù)據(jù)(現(xiàn)實數(shù)據(jù)往往有著殘缺的輸入,類別嚴(yán)重不均衡,分布不一致甚至隨時變動的測試集,幾乎沒有可以參考的論文),這往往讓剛進(jìn)入工作的同學(xué)手忙腳亂,無所適從。


引用來源與知乎分分鐘帶你殺入Kaggle Top 1%

正是如此,我們需要做不同實戰(zhàn),無論論文里面的idea還是比賽,我們都應(yīng)該盡力去實現(xiàn)它,不要說我懂得這個模型就啥都不做。希望大家保持一種謙卑的學(xué)習(xí)態(tài)度認(rèn)真努力吧。

好了,我這邊快下雨了。趕緊去飯?zhí)贸渣c飯先,不然就餓死于宿舍。就沒辦法做鴨做男優(yōu)拍AV賺錢換電腦,加GPU了。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容