第十三章 卷积神经网络

前言

用输入层,隐藏层,输出层组成了神经网络,把数据放入输入层,通过隐藏层,再到输出层,把训练的数据跟输出进行对比得出误差,把误差传回到隐藏层中训练各个层的参数。

这是典型的神经网络的结果图:

典型的神经网络用在了很多的场合中,比如分类上,也达到了很好的效果。

但是如果输入层的参数太多,会出现怎样的一种情况?如下是用神经网络来识别手写数字:

这是一个神经网络中常见的一个应用,如何用神经网络进行数字的识别?

最简单的一种想法就是,把图片的每个像素作为一个输入点,如果一张手写数字为32x32像素,输入节点为32x32=1024个,隐藏层的数目假设和输入层一样32x32=1024个,输出层为10个(代表从0到9的数字),这里总共有几个参数需要训练呢?1024x1024 + 1024x10=1058816,这样的参数是可怕的。

大脑对于图片的认知过程,是逐层的迭代和抽象的过程,从像素感知到抽取图片框架,大脑也是逐层感知。在进行这个过程时并不需要这么多的数据,对于机器来说,需要的是图片的重要信息,通过逐层的抽取,提出图片的特征,并用训练BP神经 网络。这种方法叫做卷积神经网络。

98年大名鼎鼎的Yann Lecun,提出了卷积神经网络算,并应用在手写数字的识别上。

卷积神经网络

卷积神经网络的大部分思想跟BP神经网络是一样的,也可以看做是对BP神经网络的一种扩展和改进,目的是降低训练参数的个数。在卷积神经网络中如何降低训练参数的呢?主要是在BP神经网络的基础上引进了两层特殊层,一个叫做卷积层,一个叫做池化层。

这个卷积的过程如下:

卷积的过程:卷积->池化(采样)->卷积->池化(采样)->全连接层。

卷积层

卷积层是卷积核在上一级输入层上通过逐一滑动窗口计算而得,卷积核中的每一个参数都相当于传统神经网络中的权值参数,与对应的局部像素相连接,将卷积核的各个参数与对应的局部像素值相乘之和,(通常还要再加上一个偏置参数),得到卷积层上的结果。

为简便起见,考虑一个大小为5×5的图像,和一个3×3的卷积核。这里的卷积核共有9个参数,就记为 Θ=[θij]3×3 吧。这种情况下,卷积核实际上有9个神经元,他们的输出又组成一个3×3的矩阵,称为特征图。第一个神经元连接到图像的第一个3×3的局部,第二个神经元则连接到第二个局部。具体如下图所示。

通过卷积定义,可以简单的认为是原始图片的数据和卷积核的乘机再求和,就是对图片的卷积操作。

图片通过卷积层后,加上一个偏置,还需要做激励运算。

卷积层的前向过程,总结出来如下:

def propagate(self):
    FMs = np.zeros([self.currLayer.get_n(), self.currLayer.shape()[0], self.currLayer.shape()[1]])
    inFMs = self.prevLayer.get_FM()

    k = 0 # kernel index, there is one foreach i, j combination
    for j in range(self.currLayer.get_n()): # foreach FM in the current layer
        for i in range(self.prevLayer.get_n()): # foreach FM in the previous layer
            if self.connections[i, j] == 1:

                # foreach neuron in the feature map
                for y_out in range(self.currLayer.shape()[0]):
                    for x_out in range(self.currLayer.shape()[1]):

                        # iterate inside the visual field for that neuron
                        for y_k in range(0, self.kernelHeight, self.stepY):
                            for x_k in range(0, self.kernelWidth, self.stepX):
                                FMs[j, y_out, x_out] += inFMs[i, y_out + y_k, x_out + x_k] * self.k[k, y_k, x_k]
                        # add bias
                        FMs[j, y_out, x_out] += 1 * self.biasWeights[j]
            # next kernel
            k += 1

        # compute sigmoid (of a matrix since it's faster than elementwise)
        FMs[j] = self.act.func(FMs[j])

    #print "out = ", FMs
    self.currLayer.set_FM(FMs)
    return FMs

回到原来的那张总图中,原始图像进来以后,先进入一个卷积层C1,由6个5x5的卷积核组成,卷积出28x28(28是这样定义的:32-5+1)的图像,C1有156个可训练参数(每个滤波器5<>5=25个unit参数和一个bias参数,一共6个滤波器,共(55+1)<>6=156个参数),注意这里每个卷积核只有55=25个参数,而不是没卷积一次的参数都要变。

池化层

池化(pool)即下采样(downsamples),目的是为了减少特征图。池化操作对每个深度切片独立,规模一般为 2*2,相对于卷积层进行卷积运算,池化层进行的运算一般有以下几种:

  • 最大池化(Max Pooling)。取4个点的最大值。这是最常用的池化方法。
  • 均值池化(Mean Pooling)。取4个点的均值。
  • 高斯池化。借鉴高斯模糊的方法。不常用。
  • 可训练池化。训练函数 ff ,接受4个点为输入,出入1个点。不常用。

最常见的池化层是规模为2*2, 步幅为2,对输入的每个深度切片进行下采样。每个MAX操作对四个数进行,如下图所示:

采用最大值的采用方式:

def propagate(self):
    [prevSizeY, prevSizeX] = self.prevLayer.shape()
    [currSizeY, currSizeX] = self.currLayer.shape()

    self.maximaLocationsX = np.zeros([self.currLayer.get_n(), self.currLayer.shape()[0], self.currLayer.shape()[1]])
    self.maximaLocationsY = np.zeros([self.currLayer.get_n(), self.currLayer.shape()[0], self.currLayer.shape()[1]])

    pooledFM = np.zeros([self.currLayer.get_n(), self.currLayer.shape()[0], self.currLayer.shape()[1]])

    yi = self.prevLayer.get_FM()

    for n in range(self.prevLayer.get_n()):
        for i in range(currSizeY):
            for j in range(currSizeX):
                reg = yi[n, i*self.poolingStepY:(i+1)*self.poolingStepY, j*self.poolingStepX:(j+1)*self.poolingStepX]
                loc = np.unravel_index(reg.argmax(), reg.shape) + np.array([i*self.poolingStepY, j*self.poolingStepY])
                self.maximaLocationsY[n, i, j] = loc[0]
                self.maximaLocationsX[n, i, j] = loc[1]
                pooledFM[n, i, j] = yi[n, loc[0], loc[1]]

    self.currLayer.set_FM(pooledFM)

通过池化层的图像数据进一步的减少了,维数进一步减少。

通过了卷积层,池化层,还得继续在通过卷积层,池化层,只是里面的参数不同而已,训练的方式是一样的。

全连接层

这一层更BP神经网络并没有区别,主要是把训练的参数变成结果。

这层基本上将一个输入量(无论输出是卷积或ReLU或池层)和输出一个N是程序选择类别的N维向量,这个全连接层的工作方式是,它着眼于前一层的输出(代表高阶特征的激活图),并确定哪些功能是最相关特定的类。例如如果该程序预测,一些图像是一只狗,它在激活图中会有高的值,代表高阶特征如一个爪子或4条腿等。类似地,如果该程序是预测一些图像是鸟的功能,它在激活图中会有高阶值,代表高阶特征如如翅膀或喙等。

计算输出结果:

def propagate(self):
        x = self.prevLayer.get_x()[np.newaxis]
        if self.currLayer.hasBias:
            x = np.append(x, [1])

        z = np.dot(self.w.T, x)

        # compute and store output
        y = self.act.func(z)
        self.currLayer.set_x(y)

        return y

整个过程可以用如下的方式进行组织:

inputLayer0  = layerFM(1, 32, 32, isInput = True) 
convLayer1   = layerFM(6, 28, 28)
poolLayer2   = layerFM(6, 14, 14)
convLayer3   = layerFM(16, 10, 10)
poolLayer4   = layerFM(16, 5, 5)
convLayer5   = layerFM(100, 1, 1)
hiddenLayer6 = layer1D(80)
outputLayer7 = layer1D(10, isOutput = True)

convolution01  = convolutionalConnection(inputLayer0, convLayer1, np.ones([1, 6]), 5, 5, 1, 1)  
pooling12      = poolingConnection(convLayer1, poolLayer2, 2, 2)
convolution23  = convolutionalConnection(poolLayer2, convLayer3, np.ones([6, 16]), 5, 5, 1, 1)  
pooling34      = poolingConnection(convLayer3, poolLayer4, 2, 2)
convolution45  = convolutionalConnection(poolLayer4, convLayer5, np.ones([16, 100]), 5, 5, 1, 1)
full56         = fullConnection(convLayer5, hiddenLayer6)
full67         = fullConnection(hiddenLayer6, outputLayer7)
训练过程

卷积神经网络训练参数的方法,也是采用反向传播,跟BP神经网络一样。通过误差向前传播的方式,不断的调整需要训练的参数。

全连层的误差计算和权重调整方式:

def bprop(self, ni, target = None, verbose = False):
    yj = self.currLayer.get_x()
    if verbose: 
        print "out = ", yj
        print "w = ", self.w

    # compute or retreive error of current layer
    if self.currLayer.isOutput:
        if target is None: raise Exception("bprop(): target values needed for output layer")
        currErr = -(target - yj) * self.act.deriv(yj)
        self.currLayer.set_error(currErr)
    else:
        currErr = self.currLayer.get_error()

    if verbose: print "currErr =  ", currErr

    yi = np.append(self.prevLayer.get_x(), [1])
    # compute error of previous layer
    if not self.prevLayer.isInput:
        prevErr = np.zeros(len(yi))
        for i in range(len(yi)):
            prevErr[i] = sum(currErr * self.w[i]) * self.act.deriv(yi[i])

        self.prevLayer.set_error(np.delete(prevErr,-1))

    # compute weight updates
    dw = np.dot(np.array(yi)[np.newaxis].T, np.array(currErr)[np.newaxis])
    self.w -= ni * dw

池化层的误差计算方法:

def bprop(self):
    currErr = self.currLayer.get_FM_error()
    prevErr = np.zeros([self.prevLayer.get_n(), self.prevLayer.shape()[0], self.prevLayer.shape()[1]])

    [currSizeY, currSizeX] = self.currLayer.shape()

    for n in range(self.prevLayer.get_n()):
        for i in range(currSizeY):
            for j in range(currSizeX):
                prevErr[n, self.maximaLocationsY[n, i, j], self.maximaLocationsX[n, i, j]] = currErr[n, i, j]

    self.prevLayer.set_FM_error(prevErr)

卷积层的误差和权重调整:

def bprop(self, ni, target = None, verbose = False):

    yi = self.prevLayer.get_FM() # get output of previous layer
    yj = self.currLayer.get_FM() # get output of current layer

    # TODO: A conv. layer cannot be an output, remove computing error part
    if not self.currLayer.isOutput:
        currErr = self.currLayer.get_FM_error()
    else:
        currErr = -(target - yj) * self.act.deriv(yj)
        self.currLayer.set_FM_error(currErr)
    #print "\ncurrent error = \n", currErr

    # compute error in previous layer
    prevErr = np.zeros([self.prevLayer.get_n(), self.prevLayer.shape()[0], self.prevLayer.shape()[1]])
    biasErr = np.zeros([self.currLayer.get_n()])

    k = 0 
    for j in range(self.currLayer.get_n()): # foreach FM in the current layer
        for i in range(self.prevLayer.get_n()): # foreach FM in the previous layer
            if self.connections[i, j] == 1:

                # foreach neuron in the feature map
                for y_out in range(self.currLayer.shape()[0]):
                    for x_out in range(self.currLayer.shape()[1]):

                        # iterate inside the visual field for that neuron
                        for y_k in range(0, self.kernelHeight, self.stepY):
                            for x_k in range(0, self.kernelWidth, self.stepX):
                                #FMs[j, y_out, x_out] += inFMs[i, y_out + y_k, x_out + x_k] * self.k[k, y_k, x_k]dd
                                prevErr[i, y_out + y_k, x_out + x_k] += self.k[k, y_k, x_k] * currErr[j, y_out, x_out]

                        # add bias
                        biasErr[j] += currErr[j, y_out, x_out] * self.k[k, y_k, x_k]
            # next kernel
            k += 1

    for i in range(self.prevLayer.get_n()):
        prevErr[i] = prevErr[i] * self.act.deriv(yi[i])

    for j in range(self.currLayer.get_n()):
        biasErr[j] = biasErr[j] * self.act.deriv(1)

    self.prevLayer.set_FM_error(prevErr)

    # compute weights update
    dw = np.zeros(self.k.shape)
    dwBias = np.zeros(self.currLayer.get_n())
    k = 0 
    for j in range(self.currLayer.get_n()): # foreach FM in the current layer
        for i in range(self.prevLayer.get_n()): # foreach FM in the previous layer
            if self.connections[i, j] == 1:

                # foreach neuron in the feature map
                for y_out in range(self.currLayer.shape()[0]):
                    for x_out in range(self.currLayer.shape()[1]):

                        # iterate inside the visual field for that neuron
                        for y_k in range(0, self.kernelHeight, self.stepY):
                            for x_k in range(0, self.kernelWidth, self.stepX):
                                #FMs[j, y_out, x_out] += inFMs[i, y_out + y_k, x_out + x_k] * self.k[k, y_k, x_k]dd
                                dw[k, y_k, x_k] +=  yi[i, y_out + y_k, x_out + x_k] * currErr[j, y_out, x_out]

                        # add bias
                        dwBias[j] += 1 * currErr[j, y_out, x_out]

            # next kernel
            k += 1

    # update weights
    self.k -= ni * dw
    self.biasWeights -= ni * dwBias

总结

卷积神经网络是用的较多的一种网络,通过卷积和池化减少了大量的训练参数,同时又能够快速缩减误差,能达到训练网络的目标。卷积神经网络大量的应用到图片的分类上,也取得了很好的效果,它是一种BP神经网络的改进和扩展,使得BP神经网络能够用在大输入的数据上。

PS: 如本文对您有帮助,不妨通过一下方式支持一下博主噢 ^_^

官方
微信
官方微信
Q Q
咨询
意见
反馈
返回
顶部