Tensorflow[实战篇]——Face Recognition

前言

本文章的参考卷积神经网络应用于人脸识别,通过Tensorflow改写的代码。也通过自己的想法改动了一些代码。本文算是一个小小的demo吧,因为之前都是基础篇。而这个算是基于之前的基础学习做的demo。虽然本demo比较简单,但我觉得可以给大家一些启示。

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


数据材料

这是一个小型的人脸数据库,一共有40个人,每个人有10张照片作为样本数据。这些图片都是黑白照片,意味着这些图片都只有灰度0-255,没有rgb三通道。于是我们需要对这张大图片切分成一个个的小脸。整张图片大小是1190 × 942,一共有20 × 20张照片。那么每张照片的大小就是(1190 / 20)× (942 / 20)= 57 × 47 (大约,以为每张图片之间存在间距)。


olivettifaces数据库

设计思路

那么我们得到这些样本图片之后,再将图片进行分类,每一个人一个类别,每个类别10张图片样本。我就取每一类的前八张作为训练样本,每类第九张作为校验样本,每类第十张图片作为测试样本。每张图片的label使用one-hot向量表示。

至于模型设计上面,选取了CNN卷积神经网络作为设计模型,一共有两层卷积,一层全连接层,最后一层用softmax作为分类。然后用softmax的预测结果和真实的labels做Cross-Entropy,这样就得到了Loss Function了。

代码展示(部分)和说明

获取dataset

def load_data(dataset_path):
    img = Image.open(dataset_path)
    # 定义一个20 × 20的训练样本,一共有40个人,每个人都10张样本照片
    img_ndarray = np.asarray(img, dtype='float64') / 64

    # 记录脸数据矩阵,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

    # 将数据分成训练集,验证集,测试集
    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的数据矩阵,然后再给每张照片赋予相应的label。最后划分数据成为训练数据集,校验数据集,测试数据集。

模型网络

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

我定义了一个卷积层的函数用于,每在不同的scope下调用一次就生成相应的卷积层及其参数。哈哈,是不是觉得这招很熟悉很好用呢?kernel,和bias分别是卷积核和偏移量(卷积就是对局部进行Linear操作)。最后就是max_pool层(取得核中的最大值代码这个核里面的元素,因为值越大颜色越深,那就越能代表他的特征。也可以理解成一个去噪的操作吧),做完pool层后,我们算是对图片进行一个(卷积+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. 整个网络:

def convolutional_neural_network(data):
    # 根据类别个数定义最后输出层的神经元
    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])

    # 经过第一层卷积神经网络后,得到的张量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]
        )
    # 经过第二层卷积神经网络后,得到的张量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:
        # 讲卷积层张量数据拉成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;

一图胜千言:


模型设计

训练过程:

在训练中我使用softmax_cross_entropy_with_logits作为Loss function,AdamOptimizer作为优化算法(学习率为0.01,由于我的本本已经是6年前的产物,而且没有GPU,唯有调大点,让他快点收敛算了[哭崩的脸.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)

    # 用于保存训练的最佳模型
    saver = tf.train.Saver()
    model_dir = './model'
    model_path = model_dir + '/best.ckpt'

    with tf.Session() as session:
        # 若不存在模型数据,需要训练模型参数
        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)

再次强调,我的本本很旧了,而且鄙人又是学生。因此我把epoch设置为20。还好loss function应该是一个convex function,所以也在20个epoch里到达min值0。(假如有钱就施舍一下给小弟弟吧,我又穷又想要GPU,难道我去拍AV当男优赚钱吗?还是当鸭好呢?)。最后就是有个Saver用于保存模型,毕竟他让我本本元气打伤,不可以让训练的好的模型就此消失与内存中。我都是保留最优(loss function最小)的模型。

校验与测试

# 恢复数据并校验和测试
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")

最后就是恢复模型并且计算出校验集的准确率以及测试数据哪些出现错误,并标注出来。

画出在测试集中错误的数据

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()
后台打印的结果

我没骗大家,我只是用cpu运算,我真的很穷(做鸭还是当男优呢?)。

最后是测试集合,当中有五个标记错误,在console也看到。这张图片是标注了那些被分类错误的。

分类错误的脸孔

总结

这是一个实战篇,也算是给大家介绍一些关于Tensorflow卷积和pool的使用,但我觉得这个难度一般般吧。因为我觉得这些数据比较小,而且模型都比较简单,大家应该可以掌握。

不知道你有没有这样的感受,在刚刚入门机器学习的时候,我们一般都是从MNIST、CIFAR-10这一类知名公开数据集开始快速上手,复现别人的结果,但总觉得过于简单,给人的感觉太不真实。因为这些数据太“完美”了(干净的输入,均衡的类别,分布基本一致的测试集,还有大量现成的参考模型),要成为真正的数据科学家,光在这些数据集上跑模型却是远远不够的。而现实中你几乎不可能遇到这样的数据(现实数据往往有着残缺的输入,类别严重不均衡,分布不一致甚至随时变动的测试集,几乎没有可以参考的论文),这往往让刚进入工作的同学手忙脚乱,无所适从。


引用来源与知乎分分钟带你杀入Kaggle Top 1%

正是如此,我们需要做不同实战,无论论文里面的idea还是比赛,我们都应该尽力去实现它,不要说我懂得这个模型就啥都不做。希望大家保持一种谦卑的学习态度认真努力吧。

好了,我这边快下雨了。赶紧去饭堂吃点饭先,不然就饿死于宿舍。就没办法做鸭做男优拍AV赚钱换电脑,加GPU了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,968评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,601评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,220评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,416评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,425评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,144评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,432评论 3 401
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,088评论 0 261
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,586评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,028评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,137评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,783评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,343评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,333评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,559评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,595评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,901评论 2 345

推荐阅读更多精彩内容