使用CNN(convolutional neural nets)检测脸部关键点教程(三):卷积神经网络训练和数据扩充

第五部分 第二个模型:卷积神经网络

上图演示了卷积操作

LeNet-5式的卷积神经网络,是计算机视觉领域近期取得的巨大突破的核心。卷积层和之前的全连接层不同,采用了一些技巧来避免过多的参数个数,但保持了模型的描述能力。这些技巧是:

1, 局部联结:神经元仅仅联结前一层神经元的一小部分。

2, 权重共享:在卷积层,神经元子集之间的权重是共享的。(这些神经元的形式被称为特征图[feature map])

3, 池化:对输入进行静态的子采样。

局部性和权重共享的图示

卷积层的单元实际上连接了前一层神经元中的一个小的2维patch,先验知识让网络利用了输入中的2维结构。

当使用Lasagne中的卷积层时,我们必须进行一些输入准备。输入不再像刚刚一样是一个9216维的扁平向量,而是一个有着(c,0,1)形式的三维矩阵,其中C代表通道(颜色),0和1对应着图像的x和y维度。在我们的问题中,具体的三维矩阵为(1,96,96),因为我们仅仅使用了灰度一个颜色通道。

一个函数load2d对前述的load函数进行了包装,完成这个2维到三维的转变:


    def load2d(test=False, cols=None):
        X, y = load(test=test)
        X = X.reshape(-1, 1, 96, 96)
        return X, y

我们将要创建一个具有三个卷积层和两个全连接层的卷积神经网络。每个卷积层都跟着一个2*2的最大化池化层。初始卷积层有32个filter,之后每个卷积层我们把filter的数量翻番。形式为全连接的隐层将包含500个神经元。

这里还是一样没有任何形式(惩罚权重或者dropout)的正则化。事实证明当我们使用尺寸非常小的filter,如3*3或2*2,这些filter事实上已经起到了非常不错的正则化效果。

代码如下:


    net2 = NeuralNet(
        layers=[
            (‘input‘, layers.InputLayer),
            (‘conv1‘, layers.Conv2DLayer),
            (‘pool1‘, layers.MaxPool2DLayer),
            (‘conv2‘, layers.Conv2DLayer),
            (‘pool2‘, layers.MaxPool2DLayer),
            (‘conv3‘, layers.Conv2DLayer),
            (‘pool3‘, layers.MaxPool2DLayer),
            (‘hidden4‘, layers.DenseLayer),
            (‘hidden5‘, layers.DenseLayer),
            (‘output‘, layers.DenseLayer),
            ],
        input_shape=(None, 1, 96, 96),
        conv1_num_filters=32, conv1_filter_size=(3, 3), pool1_pool_size=(2, 2),
        conv2_num_filters=64, conv2_filter_size=(2, 2), pool2_pool_size=(2, 2),
        conv3_num_filters=128, conv3_filter_size=(2, 2), pool3_pool_size=(2, 2),
        hidden4_num_units=500, hidden5_num_units=500,
        output_num_units=30, output_nonlinearity=None,

        update_learning_rate=0.01,
        update_momentum=0.9,

        regression=True,
        max_epochs=1000,
        verbose=1,
        )

    X, y = load2d()  # load 2-d data
    net2.fit(X, y)

    # Training for 1000 epochs will take a while.  We‘ll pickle the
    # trained model so that we can load it back later:
    import cPickle as pickle
    with open(‘net2.pickle‘, ‘wb‘) as f:
        pickle.dump(net2, f, -1)

训练这个网络和第一个网络相比,将要耗费巨大的时空资源。每次迭代要慢15倍,整个1000次迭代下来要耗费20多分钟的时间,这还是在你有一个相当不错的GPU的基础上。

然而耐心总是得到回馈,我们的结果自然比刚刚好得多。让我们来看一看运行脚本时的输出,显示一系列网络层的形状,注意因为我们选择的窗口尺寸,第一个卷积层的32个filter输出了32张94*94 的特征图。

InputLayer            (None, 1, 96, 96)       produces    9216 outputs
Conv2DCCLayer         (None, 32, 94, 94)      produces  282752 outputs
MaxPool2DCCLayer      (None, 32, 47, 47)      produces   70688 outputs
Conv2DCCLayer         (None, 64, 46, 46)      produces  135424 outputs
MaxPool2DCCLayer      (None, 64, 23, 23)      produces   33856 outputs
Conv2DCCLayer         (None, 128, 22, 22)     produces   61952 outputs
MaxPool2DCCLayer      (None, 128, 11, 11)     produces   15488 outputs
DenseLayer            (None, 500)             produces     500 outputs
DenseLayer            (None, 500)             produces     500 outputs
DenseLayer            (None, 30)              produces      30 outputs

接下来我们看到,和第一个网络输出相同,是每一次迭代训练损失和验证损失以及他们之间的比率。

1000次迭代后的结果相对第一个网络,有了非常不错的提升


    >>> np.sqrt(0.001566) * 48
    1.8994904579913006

我们从测试集合里面取同一个样例画出两个网络的预测结果来进行对比:

    sample1 = load(test=True)[0][6:7]
    sample2 = load2d(test=True)[0][6:7]
    y_pred1 = net1.predict(sample1)[0]
    y_pred2 = net2.predict(sample2)[0]

    fig = pyplot.figure(figsize=(6, 3))
    ax = fig.add_subplot(1, 2, 1, xticks=[], yticks=[])
    plot_sample(sample1[0], y_pred1, ax)
    ax = fig.add_subplot(1, 2, 2, xticks=[], yticks=[])
    plot_sample(sample1[0], y_pred2, ax)
    pyplot.show()

net1(左边)和net2预测结果的对比

然后我们可以画出两个网络的学习曲线:

看起来非常不错,新的曲线非常平滑,但是应该注意的是,net2的验证错误曲线相对net1来说,比训练错误曲线更快的趋于水平。我相信如果使用更大的训练集合,结果还会进一步提升。如果我们把训练用例进行水平对称变换,会不会因为训练集的翻倍使得网络进一步改进呢?

第六部分 数据扩充

通常情况下,增加训练集的数量会让一个过拟合的网络取得更好的训练结果。(如果你的网络没有过拟合,那么你最好把它变大。(否则不足以描述数据))

数据扩成使我们人为通过一些手段(变形、添加噪声等等)增加训练用例的个数。显然,这要比手工收集更多的样本经济许多。所以,数据扩充是深度学习工具箱里必不可少的工具。

我们曾经在前面简短的提到batch learning。采集一个训练集合的矩阵,分成不同的批次(在我们的任务128个用例一批),是批处理迭代器的任务。 当把训练样本分成批次的时候,批处理迭代器可以顺便把输入变形这件事做的又快又好。所以当我们想要进行水平翻转的时候,我们不需要去翻倍数量巨大的训练集合。更好的方法是,我们在进行批处理迭代的时候,选取50%的几率进行水平翻转就可以了。这非常的方便,对某些问题来说这种手段可以让我们生产近乎无限的训练集,而不需要增加内存的使用。同时,输入图片的变形操作可以在GPU进行上一个批次运算的时候进行,所以可以说,这个操作几乎不增加任何额外的资源消耗。

水平翻转图片事实上仅仅是矩阵的切片操作:

X, y = load2d()
X_flipped = X[:, :, :, ::-1]  # simple slice to flip all images

# plot two images:
fig = pyplot.figure(figsize=(6, 3))
ax = fig.add_subplot(1, 2, 1, xticks=[], yticks=[])
plot_sample(X[1], y[1], ax)
ax = fig.add_subplot(1, 2, 2, xticks=[], yticks=[])
plot_sample(X_flipped[1], y[1], ax)
pyplot.show()

原始图片(左)和翻转图片

在右边的图片中,值得注意的是关键点的位置并不匹配。因为我们翻转了图片,所以我们也必须翻转目标位置的横坐标,同时,还要交换目标值的位置,因为left_eye_center_x 变换过之后的值,指示的实际上是right_eye_center_x。我们建立一个元组flip_indices来保存哪一列的目标向量需要交换位置。如果你还记得,最开始的时候我们读取的数据条数是这样的:

left_eye_center_x            7034
left_eye_center_y            7034
right_eye_center_x           7032
right_eye_center_y           7032
left_eye_inner_corner_x      2266
left_eye_inner_corner_y      2266
...

因为left_eye_center_x要和right_eye_center_x换位置,我们记录(0,2),同样left_eye_center_y要和right_eye_center_y换位置,我们记录元组(1,3),以此类推。最后,我们获得元组集合如下:

flip_indices = [
    (0, 2), (1, 3),
    (4, 8), (5, 9), (6, 10), (7, 11),
    (12, 16), (13, 17), (14, 18), (15, 19),
    (22, 24), (23, 25),
    ]

# Let‘s see if we got it right:
df = read_csv(os.path.expanduser(FTRAIN))
for i, j in flip_indices:
    print("# {} -> {}".format(df.columns[i], df.columns[j]))

# this prints out:
# left_eye_center_x -> right_eye_center_x
# left_eye_center_y -> right_eye_center_y
# left_eye_inner_corner_x -> right_eye_inner_corner_x
# left_eye_inner_corner_y -> right_eye_inner_corner_y
# left_eye_outer_corner_x -> right_eye_outer_corner_x
# left_eye_outer_corner_y -> right_eye_outer_corner_y
# left_eyebrow_inner_end_x -> right_eyebrow_inner_end_x
# left_eyebrow_inner_end_y -> right_eyebrow_inner_end_y
# left_eyebrow_outer_end_x -> right_eyebrow_outer_end_x
# left_eyebrow_outer_end_y -> right_eyebrow_outer_end_y
# mouth_left_corner_x -> mouth_right_corner_x
# mouth_left_corner_y -> mouth_right_corner_y

我们的批处理迭代器的实现将会从BachIterator类派生,重载transform()方法。把这些东西组合到一起,看看完整的代码:

class FlipBatchIterator(BatchIterator):
    flip_indices = [
        (0, 2), (1, 3),
        (4, 8), (5, 9), (6, 10), (7, 11),
        (12, 16), (13, 17), (14, 18), (15, 19),
        (22, 24), (23, 25),
        ]

    def transform(self, Xb, yb):
        Xb, yb = super(FlipBatchIterator, self).transform(Xb, yb)

        # Flip half of the images in this batch at random:
        bs = Xb.shape[0]
        indices = np.random.choice(bs, bs / 2, replace=False)
        Xb[indices] = Xb[indices, :, :, ::-1]

        if yb is not None:
            # Horizontal flip of all x coordinates:
            yb[indices, ::2] = yb[indices, ::2] * -1

            # Swap places, e.g. left_eye_center_x -> right_eye_center_x
            for a, b in self.flip_indices:
                yb[indices, a], yb[indices, b] = (
                    yb[indices, b], yb[indices, a])

        return Xb, yb

使用上述批处理迭代器进行训练,需要把它作为batch_iterator_train参数传递给NeuralNet。让我们来定义net3,一个和net2非常相似的网络。仅仅是在网络的最后添加了这些行:

net3 = NeuralNet(
    # ...
    regression=True,
    batch_iterator_train=FlipBatchIterator(batch_size=128),
    max_epochs=3000,
    verbose=1,
    )

现在我们已经采用了最新的翻转技巧,但同时我们将迭代次数增长了三倍。因为我们并没有真正改变训练集合的总数,所以每个epoch仍然使用和刚刚一样多的样本个数。事实证明,采用了新技巧后,每个训练epoch还是比刚刚多用了一些时间。这次我们的网络学到的东西更具一般性,理论上来讲学习更一般性的规律比学出过拟合总是要更难一些。

这次网络将花费一小时的训练时间,我们要确保在训练之后,把得到的模型保存起来。然后就可以去喝杯茶或者做做家务活,洗衣服也是不错的选择。

net3.fit(X, y)

import cPickle as pickle
with open(‘net3.pickle‘, ‘wb‘) as f:
    pickle.dump(net3, f, -1)
$ python kfkd.py
...
 Epoch  |  Train loss  |  Valid loss  |  Train / Val
--------|--------------|--------------|----------------
...
   500  |    0.002238  |    0.002303  |     0.971519
...
  1000  |    0.001365  |    0.001623  |     0.841110
  1500  |    0.001067  |    0.001457  |     0.732018
  2000  |    0.000895  |    0.001369  |     0.653721
  2500  |    0.000761  |    0.001320  |     0.576831
  3000  |    0.000678  |    0.001288  |     0.526410

让我们画出学习曲线和net2对比。应该看到3000次迭代后的效果,net3比net2的验证损失要小了5%。我们看到在2000次迭代之后,net2已经停止学习了,并且曲线变得不光滑;但是net3的效果却在一直改进,尽管进展缓慢。

看起来费了很大的劲却只取得了一点点加成,是这样吗?我们将在下一部分找到答案。

To be continue

时间: 2024-08-08 22:08:51

使用CNN(convolutional neural nets)检测脸部关键点教程(三):卷积神经网络训练和数据扩充的相关文章

使用CNN(convolutional neural nets)检测脸部关键点教程(二):浅层网络训练和测试

第三部分 第一个模型:一个隐层结构的传统神经网络 这一部分让我们从代码开始: # add to kfkd.py from lasagne import layers from lasagne.updates import nesterov_momentum from nolearn.lasagne import NeuralNet net1 = NeuralNet( layers=[ # three layers: one hidden layer ('input', layers.InputL

使用CNN(convolutional neural nets)检测脸部关键点教程(一)

本教程使用Lasagne(一个基于Theano可以快速构建神经网络的工具): 1,实现几种神经网络的搭建 2,讨论数据扩充(data augmentation)方法 3,讨论 学习"势" 的重要性 4,讨论 前训练(pre-training) 以上方法,将有利于改进我们的结果. 本教程基于你已经对神经网络有过一定的了解,因为在这里不再讨论神经网络的工作原理.但是这里有一些好的资料: 1, 一本deeplearning的在线书籍 2, Alec.Radford的视频教程"使用p

使用CNN(convolutional neural nets)检测脸部关键点教程(五):通过前训练(pre-train)训练专项网络

第九部分 训练专项网络 还记得在刚开始的时候我们丢掉的70%的训练数据吗?如果我们想要得到一个在Kaggle排行榜上有竞争力的成绩,那不是一个好主意.在70%的数据中,还有相当多的特征我们没有看到. 所以改变之前只训练一个模型的方式,我们训练几个专项网络,每一个都可以预测不同的目标集合.我们训练一个模型预测left_eye_center和right_eye_center,另一个模型预测nose_tip--:最终,我们有6个模型,使得我们可以完全利用训练数据,希望能够得到更好的预测效果. 这6个专

CNN(Convolutional Neural Network)

CNN(Convolutional Neural Network) 卷积神经网络(简称CNN)最早可以追溯到20世纪60年代,Hubel等人通过对猫视觉皮层细胞的研究表明,大脑对外界获取的信息由多层的感受野(Receptive Field)激发完成的.在感受野的基础上,1980年Fukushima提出了一个理论模型Neocognitron是感受野在人工神经网络领域的首次应用.1998年,Lecun等人提出的LeNet-5模型在手写字符识别上取得了成功,引起了学术界对卷积神经网络的关注.2012年

DeepTracker: Visualizing the Training Process of Convolutional Neural Networks(对卷积神经网络训练过程的可视化)

\ 里面主要的两个算法比较难以赘述,miniset主要就是求最小公共子集.(个人认为) 原文地址:https://www.cnblogs.com/TheKat/p/9799923.html

奉献pytorch 搭建 CNN 卷积神经网络训练图像识别的模型,配合numpy 和matplotlib 一起使用调用 cuda GPU进行加速训练

1.Torch构建简单的模型 # coding:utf-8 import torch class Net(torch.nn.Module): def __init__(self,img_rgb=3,img_size=32,img_class=13): super(Net, self).__init__() self.conv1 = torch.nn.Sequential( torch.nn.Conv2d(in_channels=img_rgb, out_channels=img_size, ke

卷积神经网络的并行化模型——One weird trick for parallelizing convolutional neural networks

前段时间一直在关注 CNN 的实现,查看了 caffe 的代码以及 convnet2 的代码.目前对单机多卡的内容比较感兴趣,因此特别关注 convnet2 关于 multi-GPU 的支持. 其中 cuda-convnet2 的项目地址发布在:Google Code:cuda-convnet2 关于 multi-GPU 的一篇比较重要的论文就是:One weird trick for parallelizing convolutional neural networks 本文也将针对这篇文章给

卷积神经网络(Convolutional Neural Network,CNN)

全连接神经网络(Fully connected neural network)处理图像最大的问题在于全连接层的参数太多.参数增多除了导致计算速度减慢,还很容易导致过拟合问题.所以需要一个更合理的神经网络结构来有效地减少神经网络中参数的数目.而卷积神经网络(Convolutional Neural Network,CNN)可以做到. 1. 卷积神经网络构成 图 1:卷积神经网络 输入层 整个网络的输入,一般代表了一张图片的像素矩阵.图 1中最左侧三维矩阵代表一张输入的图片,三维矩阵的长.宽代表了图

Convolutional neural network (CNN) - Pytorch版

import torch import torch.nn as nn import torchvision import torchvision.transforms as transforms # 配置GPU或CPU设置 device = torch.device('cuda:0' if torch.cuda.is_available() else 'cpu') # 超参数设置 num_epochs = 5 num_classes = 10 batch_size = 100 learning_