机器学习入门:第十四章 卷积神经网络

本文深入介绍了卷积神经网络(CNN)的工作原理,包括卷积层、池化层以及全连接层。通过减少训练参数,CNN解决了传统神经网络在处理大量输入参数时的问题。卷积层利用卷积核提取特征,池化层则用于降低维度,全连接层将特征映射到输出类别。这种网络结构在图像识别任务中表现出色,尤其是手写数字识别。通过反向传播进行参数调整,CNN在保持高效的同时降低了过拟合风险。
摘要由CSDN通过智能技术生成
前言

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

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

在这里插入图片描述

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

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

在这里插入图片描述

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

最简单的一种想法就是,把图片的每个像素作为一个输入点,如果一张手写数字为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个可训练参数(每个滤波器55=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神经网络能够用在大输入的数据上。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

go2coding

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值