您的位置:首页 > 理论基础 > 计算机网络

初识神经网络--识别手写数字

2017-07-18 15:37 513 查看
毕设要入深度学习的坑了,哭死



于是我就从神经网络开始看起了,看的是Michael Nielsen大神写的免费在线书,所有的源码也都放在GitHub上托管,越是厉害的人越愿意分享,向大神学习。

一、问题背景

这本书致力于解决识别手写数字,从这个问题入手,详细介绍了神经网络的原理。这本书虽然是从一个问题入手,但是重点放在神经网络的知识,介绍了在神经网络中究竟发生了什么。技术来来去去,原理总是不变。只要掌握了神经网络工作的原理,想让它完成某个任务就简单的多了。待识别的手写数字是美国标准数字研究院MINST的数据集,如下所示:



这些手写数字是28*28大小的灰度图像,我们人类看起来很简单能够识别,但是计算机识别起来却不是易事。神经⽹络以另⼀种⽅式看待这个问题。其主要思想是获取⼤量的⼿写数字,常称作训练样本。然后开发出.个可以从这些训练样本中进.学习的系统。换言之,神经网络使用样本来自动推断出识别手写数字的规则。另外,通过增加训练样本的数量,网络可以学到更多关于手写数字的知识,这样就能够提升自身的准确性。所以,上面例子中我们只是展出了100 个训练数字样本,而通过使用数千或者数百万或者数十亿的训练样本我们也许能够得到更好的手写数字识别器。

二、感知器

感知器的工作机制是输出是二进制输入的加权,输出有两种情况0和1,对输入样本进行判断。引入引⼊权重,w1;w2, ...,表⽰相应输⼊对于输出重要性的实数。一个简单的感知器如下图所示。






输出的代数形式为:






改成向量形式,并将阈值移到左边作为偏置b=-threshold,感知器的规则重新写为:



这是基本的数学模型,书中给了一个非常地道的解释:

假设这个周末就要来了,你听说你所在的城市有个奶酪节。你喜欢奶酪,正试着决定是否去参加。你也许会通过给三个因素设置权重来作出决定:

1. 天⽓好吗?

2. 你的男朋友或者⼥朋友会不会陪你去?

3. 这个节⽇举办的地点是否靠近交通站点?(你没有⻋)

你可以把这三个因素对应地⽤⼆进制变量x1; x2 和x3 来表⽰。例如,如果天⽓好,我们把x1 = 1,如果不好,x1 = 0。类似地,如果你的男朋友或⼥朋友同去,x2 = 1,否则x2 = 0。x3也类似地表⽰交通情况。现在,假设你是个嗜好奶酪的吃货,以⾄于即使你的男朋友或⼥朋友不感兴趣,也不管路有多难⾛都乐意去。但是也许你确实厌恶糟糕的天⽓,⽽且如果天⽓太糟你也没法出⻔。你可以

使⽤感知器来给这种决策建⽴数学模型。⼀种⽅式是给天⽓权重选择为w1 = 6 ,其它条件为w2 = 2 和w3 = 2。w1 被赋予更⼤的值,表⽰天⽓对你很重要,⽐你的男朋友或⼥朋友陪你,或者最近的交通站重要的多。最后,假设你将感知器的阈值设为5。这样,感知器实现了期望的决策模型,只要天⽓好就输出1,天⽓不好则为0。对于你的男朋友或⼥朋友
4000
是否想去,或者附近是否有公共交通站,其输出则没有差别。随着权重和阈值的变化,你可以得到不同的决策模型。例如,假设我们把阈值改为3 。那么感知器会按照天⽓好坏,或者结合交通情况和你男朋友或⼥朋友同⾏的意愿,来得出结果。换句话说,它变成了另⼀个不同的决策模型。降低阈值则表⽰你更愿意去。很明显,感知器不是⼈做出决策使⽤的全部模型。但是这个例⼦说明了⼀个感知器如何能权衡不同的依据来决策。这看上去也可以⼤致解释⼀个感知器⽹络能够做出微妙的决定:







第一列感知器是通过输入加权各自得出一个输出,第三层又根据第二层输出结果得到输出,以这种方式,多层感知器可以做出复杂巧妙的决策。我们可以设计学习算法,能够自动调整人工神经元的权重和偏置。这种调整可以响应外部的刺激,而不需要一个程序员的直接干预。这些学习算法是我们能够以某种根本区别于传统逻辑门的方式使用人工神经元。

三、S型神经元

因为感知器的输出不是0就是1,如果调整权值和偏置会使输出结果造成翻转,想要实现上面所说的学习过程非常艰难。我们可以引⼊⼀种称为S 型神经元的新的⼈⼯神经元来克服这个问题。S 型神经元和感知器类似,但是被修改为权重和偏置的微⼩改动只引起输出的微⼩变化。这对于让神经元⽹络学习起来是很关键的。让我来描述下S 型神经元。我们.描绘感知器的相同.式来描绘S 型神经元:



正如感知器,S型神经元有多个输入,且这些输入x1,x2,...,但是这些输入可以取0和1中的任意值,而不仅仅是0或1.例如,0.638...是一个s型神经元的有效输入。同样,s型神经元对每个输入有权重w1,w2,...和一个总的偏置,b。但是输入不是0或1.相反,它现在是σ(wx+b),这里σ被称为s型函数,定义为:



将其表现成权重和偏置的函数为:



σ函数又被称为逻辑函数,该函数之所以被称为s型函数,是因为其函数图形如下所示:



可以看出,这个函数具有平滑特性,这是个关键因素,σ平滑意味着权重和偏置的微小变化也会产生输出的一个微小变化,实际上,微积分告诉我们:


Δoutput 是⼀个反映权重和偏置变化—— 即Δwj 和Δb —— 的线性函数。这⼀线性使得选择权重和偏置的微⼩变化来达到输出的微⼩变化的运算变得容易。所以当S 型神经元有更多和感知器相同的本质的⾏为时,计算如何变化权重和偏置来使输出变化会更加容易。如果识别9这个数字,输出结果大于0.5,我们就认为是9,如果小于0.5,就认为结果不是9。

四、神经网络的架构

一个简单的神经网络



最左边一层称为输入层,中间一层为隐藏层,最右一层为输出层。对于手写数字识别问题,输入层为一幅图像的所有灰度,即28*28个输入单元,每个单元的取值为0~1之间(像素值0~255归一化而成),输出是10个神经元,如果待识别的数字是6,期望的输出是y(x) = (0; 0; 0; 0; 0; 0; 1; 0; 0; 0)’。我们希望有一个合适的算法,可以帮助我们找到一个最合适的参数权重和偏置,使得神经网络的实际输出和期望的输出差值最小,这就又回到了之前线性回归的地方,还是同样的二次代价函数,形式为:



其中a为w和b的函数,使用梯度下降法来解决这个问题是非常简单的。对于本文所使用的数据集而言,50000个训练样本和10000个测试样本,对梯度下降法进行适当的改进成随机梯度下降法,每次选取10个样本进行梯度方向的计算,可以大大加快运算速度。

这里贴上代码,一个简单的手写数字识别网络,准确率最高可以达到95.42%。

"""
network.py
~~~~~~~~~~

A module to implement the stochastic gradient descent learning
algorithm for a feedforward neural network. Gradients are calculated
using backpropagation. Note that I have focused on making the code
simple, easily readable, and easily modifiable. It is not optimized,
and omits many desirable features.
"""

#### Libraries
# Standard library
import random

# Third-party libraries
import numpy as np

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:])]

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)

def update_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.weights]
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)]

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]
# feedforward
activation = x
activations = [x] # list to store all the activations, layer by layer
zs = [] # list to store all the z vectors, layer by layer
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)

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(z):
return 1.0/(1.0+np.exp(-z))

def sigmoid_prime(z):
return sigmoid(z)*(1-sigmoid(z))完整源码和数据传送门https://github.com/mnielsen/neural-networks-and-deep-learning
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: