Neural Networks and Deep Learning阅读笔记(1)手写字符识别

Neural Networks and Deep Learning阅读笔记(1)手写字符识别

刚开始开始学习深度学习,找了一本比较简单的书来看看,顺便记个笔记。我是那种不记笔记估计看完一页忘一页的人╮(╯▽╰)╭嘻嘻嘻
emmm这篇笔记就是我自己的碎碎念啦,不严谨而且中英文随便换着用
第一章手写字符识别,通过这个简单的例子讲了神经网络的一些基本概念,包括神经元、感知器、随机梯度下降等。(好绝望,这些名词都不清楚,慢慢往下看吧)

首先,人辨别手写字符0-9看似容易,其实是一件非常复杂的事情,是我们大脑中的许多地方共同运作的结果,那我们如何使机器也能辨别出这些手写字符呢?有一种方法就是用大量的手写字符当做训练样本training examples

然后用一些神奇的方法让机器自己学会如何辨认这些字符,告诉它们一些辨认规则。【而且training examples越多,机器就对这些字符了解更多,相应的识别的肯定越来越好啦
这里介绍一种只用74行代码的方法,也不用神经网络的库,不过正确率只有96%+,不过下一章会有介绍99%+的更好的方法。

感知器perceptrons

感知器是一种神经元,现在更多的在使用sigmoid神经元而不使用感知器了,但是我们了解一下还是有必要滴~
perceptron接收several binary inputs, x1,x2,x3…然后产生一个single binary output
perceptrons
那具体在这个圈圈里是怎么计算出output的呢?我们给每一个输入乘一个权重weight,w1,w2,w3…weight都是实数,表明每个输入的重要程度,然后我们有一个阈值,如果输入的加权和大于这个阈值,output=1,小于则output=0
其实做决定的时候也不知不觉用了感知器呢,你先列出几个影响决定的指标,然后根据个人喜好决定这些指标的重要程度,根据加权和是否大于自己的心理预期去最终决定。
下面是多个感知器连接起来的网络

第一列的三个呢是first layer,而第二列四个又是在first layer的结果的基础上再一次进行计算,所以第二层比第一层更加的复杂和抽象,第三层就更是啦。所以这样多个感知器连接起来的网络可以进行复杂的decision-making!
每次都写加权和太麻烦啦!所以我们可以把加权和改写成向量点积的形式w·x,并且将threshold移到不等式右边,变成如果w·x+b<=0,output=0,w·x+b>=0,output=1。【懒得打公式╮(╯▽╰)╭
我们把b称作偏置bias。
感知器不仅可以用作decision making,还可以做一些逻辑运算AND, OR, NAND等,比如下图的NAND门运算,这个很简单啦。
NAND
将与非门换成感知器

推一推就很容易推出来了。
除去decision making & gate computation,感知器最重要的就是,我们可以设计一些学习算法learning algorithm去根据需要自动调整每一步的权重和偏置。感知器可以自己学习如何解决问题,而不是简单地通过输入得到输出。这种算法让感知器网络与门逻辑运算有了巨大的不同!

Sigmoid neurons

那么如何设计一个学习算法呢?我们上面知道通过改变weights&bias来给output一个非常小的改变

比如有一个手写数字分错了,我们可以尝试改变权重和偏置来使结果正确,这样一次次的改变参数使得我们的感知器表现越来越好。
但问题是,你不清楚如何才能让output有一个很小的改变,有时输出会变动很大。所以我们很难慢慢的调整weights和bias。
为了解决这个问题,我们设计了一个新的神经元——sigmoid neuron。样子嘛 与感知器差不多
sigmoid neuron
与感知器不同的是,output不再只取0和1,而是取0到1之间的任何实数。这个圈圈里不再是简单的计算加权和就直接比较,而是有了一个新的公式——sigmoid function:


所以一个sigmoid neuron的output为:
output
【幸好我之前接触过一点,不然看到这个式子我就懵了,其实就是把原本的离散输出变成连续的而已,从{0,1}映射到[0,1],这样子权重和偏置的一点点变化才不会导致output直接从0翻到1,而是在实数部分有一个小变化。
那为什么会选取 σ \sigma (z)呢?是因为这个函数对于w和b求偏导的结果非常简洁,可以清晰的看出w和b的变化是如何影响 σ \sigma 的变化的,不过之后还会有其他的函数。

The architecture of neural networks

介绍几个名词~
最左边一列是input layer,在这一层中的神经元是input neurons。最右边就是output layer以及output neurons。中间的层叫做hidden layer,上面的图只有一层hidden layer。
而使用这样简单的神经网络也比较直接,比如想知道一个手写数字是不是“9”,我们把每个像素当做一个输入神经元,灰度当做0到1之间的一个实数输入,通过一些方法加权,而输出神经元则输出这个数是“9”的概率。【当然,根据需要的不同,hidden layer的设计也可以变得十分复杂
如果上一层的输出是下一层的输入的话,我们把这样的网络叫做feedforward神经网络,如果有循环的话(也就是说一层的输出有可能是上层的输入),这样的神经网络被称为recurrent neural networks,这种循环中的神经元可以活跃持续一段时间,并且可以在一段时间之后激励下一层的神经元。

A simple network to classify handwritten digits

为了识别每个手写字符,我们将一串数字分开成单独的数字:


为了识别一个手写字符,我们使用如下三层神经网络:

我们的训练样本是28*28的图片,所以刚开始的input neuron就是784个。输入值是像素灰度值,0.0代表白色,1.0代表黑色。第二列是hidden layer,图中展示了15个神经元。第三层输出层有十个神经元,代表了0~9十个手写字符的概率,比如第一个神经元输出约为1的话,代表了我们认为这个字符是“0”。
不过为什么不仅仅用4个神经元的二进制来代表十个数字呢,这样的效率应该更高啊?为了解决这个问题,需要从原理上理解神经网络到底如何识别手写字符的。
考虑输出层第一个neuron,它决定了这个digit是否是“0”。而这个神经元的输入又是由hidden layer里的每个神经元的输出得到的,考虑hidden layer里面第一个神经元,假设它检测一个image是不是这样的(通过加重这一部分的像素权重而减轻图像其他部分的像素权重):

而第2、3、4个神经元分别检测一个image是不是这样的:

如果这四个神经元都是被激活的,那么我们可以认为,这个字符是“0”:

所以如果只用四个output neuron的话,第一个neuron就要努力检测四位二进制码哪一位是most significant bit,而最大的位跟上面的简单的图形无法简单的联系起来。

Learning with gradient descent

【嘻嘻嘻梯度下降我还是知道的~
我们手写字符的dataset呢就用MNIST data set啦~【默默吐槽美国人阿拉伯数字怎么写的这么不规整
我们将每一个输入的数字(784个像素值)都记为一个维数为784的向量x,将理想输出记为y=y(x)y就是一个十维的向量,也就是说,如果这个数字是“6”,那y(x)=(0,0,0,0,0,0,1,0,0,0)T
所以现在呢我们要设计一个算法让我们找到合适的weights和bias。如果我们得知了现有的输出和理想的输出之间的差距,找到weights和bias使这个差距变到最小,那我们就设计出了一个不错的算法。所以我们首先定义了一个损失函数cost function
cost function
其中w,b表示权重和偏置,n是training inputs的个数,y(x)a是理想输出和实际输出,a取决于w、b、x,我们取其差的模制的平方。所以损失函数也被称作均方误差函数(MSE)。
当实际输出等于理想输出时,损失函数为0。所以我们的目标就是使这个函数越小越好,方法就是使用梯度下降。C是一个平方函数,其图像如下图所示:

需要找到这个函数的全局最小,也就是它的顶点。
将损失函数的图像想象成一个峡谷,最终要到达峡谷的底部,我们先在山坡上随意选取一个点放一个小球,你要操纵这个小球让其滚到谷底。如果我们将球向v1、v2方向各移动一个微小的距离,那么损失函数将会变化:

若记:




若我们取

Δ \Delta C为负,符合我们的预期。其中η为learning rate,就是我们小球往下滚一步的步长。继续上述步骤,我们会进一步减小C,直到小球滚到谷底。其实散度算子就是为了计算小球朝哪个方向滚才能下降的最快。
梯度下降还有很多问题,但是这一章着重讲了一个问题,在计算梯度的时候,当inputs非常大时,计算非常复杂,所以介绍了一种随机梯度下降的方法(stochastic gradient descent)。即我们随机选取一小部分inputs计算梯度取平均值,可以很快得到全部样本梯度的一个很好的估计。
这批小样本我们称为mini-batch,含有m个样本,对于m我们的期望是它的数量足够达到使我们计算出来的梯度很好的估计总体梯度。

Implementing our network to classify digits

嘻嘻嘻终于到写代码的时候了,激动地搓手手
MNIST里面的测试集有60000张images,其中50000张是测试样本,10000张是验证集。这边用了测试集合中的50000张。
我们先把之后要用到的库import一下:

import random
import numpy as np

建一个network类:

class Network(object):
	def __init__(self,sizes):
		self.num_layers = len(sizes)
		self.sizes = sizes
		self.biases = [np.random.randn(y,1) for y in sizes[1:]]
		self.weights = [np.random.randn(y,x) for x,y in zip(sizes[:-1],sizes[1:])]

所以如果我们想创建一个三层的神经元网络,每层神经元个数分别是2,3,1,我们可以直接

net = Network([2, 3, 1])

而net.weight[1]则是代表了连接第二层到第三层神经元的权重,将这个矩阵记为w,则*wjk*则代表了第二层第k个神经元连接到第三层第j个神经元的权重。那第三层的激活值可以如下表示:

我们先定义激活函数sigmoid:

def sigmoid(z):
	return 1.0/(1.0+np.exp(-z))

之后是我们feedforward网络的主体,即给出input,得到输出:

def feedforward(self, a):
	for b,w in zip(self.biases, self,weights):
		a = sigmoid(np.dot(w,a)+b)
	return a

之后就是最重要的一个算法随机梯度下降:

def SGD(self, training_data, epochs, mini_batch_size, eta, test_data=None):
	if test_data: n_test = len(test_data):
		n = len(training_data)
	for j in xrange(epochs):
		random.shuffle(training_data)
		mini_batches = [training_data[k:k+mini_batch_size] for k in xrange(0, n, mini_batch_size)]
		for mini_batch in mini_batches:
			self.update_mini_batch(mini_batch, eta)
		if test_data:
			print("Epoch {0}: {1} / {2}".format(j, self.evaluate(test_data), n_test)
		else:
			print("Epoch {0} complete".format(j)

training data就是一列代表了输入和理想输出的(x,y)
epoch是想要训练的时期,一个时期代表前向和反向传递一次
mini_batch_size是用来求梯度的一小部分样本的个数
eta是学习率
evaluate函数返回正确的结果个数

在每一个epoch中,先random.shuffle训练样本,然后将其分成合适的mini_batch。然后对于每个mini_batch,我们用self.update_mini_batch这个函数更新每一步的权重和偏置。函数具体实现如下:

def updata_mini_batch(self, mini_batch,eta):
	nabla_b = [np.zeros(b.shape) for b in self.biases]
	nabla_w = [np.zeros(w.shape) for w in self.biases]
	for x, y in mini_batch:
		delta_nabla_b, delta_nabla_w = self.backprop(x,y)
		nabla_b = [nb+dnb for nb, dnb in zip(nabla_b,delta_nabla_b)]
		nabla_w = [nw+dnw for nw, dnw in zip(nabla_w,delta_nabla_w)]
		self.weights = [w-(eta/len(mini_batch))*nw for w, nw in zip(self.weights, nabla_w)]
		self.biases = [b-(eta/len(mini_batch))*nb for b, nb in zip(self.biases, nabla_b)]

【这一段感觉很简单啦,不做笔记了
最主要的就是self.backprop这个算法,这个反向传播算法之后会用到很多。backprop这一章没有讲,还是把代码一看

def backprop(self, x, y):
	nabla_b = [np.zeros(b.shape) for b in self.biases]
	nabla_w = [np.zeros(w.shape) for w in self.weights]
	#feedforword
	activation = x
	activations = [x]
	zs = []
	for b, w in zip(self.biases, self.weights):
		z = np.dot(w, activation)+b
		zs.append(z)
		activation = sigmoid(z)
		activations.append(activation)
	#backward pass
	delta = self.cost_derivative(activations[-1], y)*\sigmoid_prime(zs[-1])
	nabla_b[-1] = delta
	nabla_w[-1] = np.dot(delta, activations[-2].transpose())
	for l in xrange(2, self.num_layers):
		z = zs[-l]
		sp = sigmoid_prime(z)
		delta = np.dot(self.weights[-l+1].transpose(), delta)*sp
		nabla_b[-l] = delta
		nabla_w[-l] = np.dot(delta, activations[-l-1].transpose())
	return (nabla_b, nabla_w)

其实我对backpropagation还算有点印象,不过代码也是半懂不懂,没关系!之后会主要讲这个!不慌!
还有一些函数,sigmoid_prime计算sigmoid函数的微分,self.cost_derivative作者没讲,不过应该是在反向传播中计算偏微分滴~

def evaluate(self, test_data):
	test_results = [(np.argmax(self.feedforward(x)), y) for (x,y) in test_data]
	return sum(int(x==y) for (x,y) in test_results)

def cost_derivative(self, output_activations, y):
	return output_activations-y

def sigmoid_prime(z):
	return sigmoid(z)*(1-sigmoid(z))

所以我们在下载MNIST数据集之后,我们试着训练一下(30个时期,10个神经元的mini_batch,步长取3.0):

import network
net = network.Network([784,30,10])
net.SGD(training_data, 30, 10, 3.0, test_data=test_data)

也可以改变步长看看结果,当然选取合适的步长是hin重要滴~
插个书中给的GitHub链接:network

撒花✿✿ヽ(°▽°)ノ✿第一章终于看完了,其实内容不是很多也不难,有信心继续往下看了!

猜你喜欢

转载自blog.csdn.net/niyidan0527/article/details/85010412