3.1 感知机与神经网络的
略。
3.2激活函数
感知机——“阶跃函数”:以阈值为界,一旦超过阈值,就切换输出。
感知机与神经网络的主要区别是激活函数其他方面基本一致。
3.2.1 sigmoid 函数
神经网络常用函数,
3.2.2 阶跃函数的实现
def step_function(x):
if x > 0:
return 1
else:
return 0
#改成能接收numpy数组的:
def step_function(x):
y = x > 0 #y等于一个bool数组
return y.astype(np.int)
#直观来说,以下x,y画图:
x = np.arange(-5.0, 5.0, 0.1) #生成[-0.5,0.5]以0.1为单位的数组
y = strp_function(x)
得:
如图,以0为界,输出0切换为1(反之),值呈阶梯式变化,故称为阶跃函数。
3.2.4 sigmoid 函数的实现
def sigmoid(x):
return 1 / (1 + np.exp(-x))
#x 为数组亦可(numpy的广播功能)
#直观来说,以下x,y画图:
x = np.arange(-5.0, 5.0, 0.1) #生成[-0.5,0.5]以0.1为单位的数组
y = sigmoid(x)
3.3.5 二者的比较
对比图
故可知:
- (不同点)神经网络中流动的是实数值信号,感知机是二元信号
- (相同点)宏观来说,输出形状相似。
- (相同点)无论信号多大/小,输出信号都在0~1。
3.2.6 非线性函数
阶跃函数和sigmoid函数都是非线性函数。
神经网络的激活函数必须使用非线性函数,否则无法发挥多层网络带来的优势。
3.2.7 ReLU 函数
现如今使用的函数。
输入大于0时,输出该值,输入小于等于0时,输出0.
def relu(x):
return np.maximum(0, x)
3.3 多维数组运算
3.3.1 多维数组
维数可以通过np.dim()
获得。
3.3.2 矩阵乘法
np.dot()
来进行数组乘积,eg.np.dot(A,B)
[tips:dot()
中,前列数 = 后行数]
3.3.3 神经网络的内积
下面通过numpy矩阵来实现神经网络,来实现下图神经网络(省略偏置和激活函数,只有权重)。
实现该神经网络时,要注意X、W、Y的形状,特别是X、W对应维度的元素个数是否一致。
X = np.array([1, 2])
x.shape ---> (2,)
W = np.array([[1, 3, 5],[2, 4, 6]])
print(W) --->[[1 3 5]
[2 4 6]]
W.shape --->(2, 3)
Y = np.dot(X, W)
print(Y) --->[5 11 17]
如图,可以一次性计算出Y的元素,即使Y中有成百上千个元素,避免了使用(例如使用for
)麻烦。
3.4 3层神经网络的实现
基于以上,我们可以代码实现下图神经网络。
3.4.1 符号确认
3.4.2 各层间的信号传递的实现
以下数学式来表示:
如果使用矩阵乘法:
接下来我们用代码来实现(3.9)
X = np.array([1.0, 0.5])
W1 = np.array([[0.1, 0.3, 0.5],[0.2, 0.4, 0.6]])
B1 = np.array([0.1, 0.2, 0.3])
print(W1.shape) --->(2, 3)
print(X.shape) --->(2,)
print(B1.shape) --->(3,)
A1 = np.dot(X, W1) + B1
接下来观察第一层中激活函数的计算过程。如下图,隐藏层的加权和(加权信号和偏置的总和)用a表示,被激活函数转换后的信号用z表示,h()表示激活函数,这里,我们使用sigmoid 函数来实现。
Z1 = sigmoid(A1)
print(A1) --->[0.3, 0.7, 1.1]
print(Z1) --->[0.57444252, 0.66818777, 0.75026011]
接下来实现第1层到第2层的信号传递(如下图)
W2 = np.array([[0.1, 0.4], [0.2, 0.5], [0.3, 0.6]])
B2 = np.array([0.1, 0.2])
'''
print(Z1.shape) --->(3,)
print(W1.shape) --->(3, 2)
print(B1.shape) --->(2,)
'''
A2 = np.dot(Z1, W2) + B2
Z2 = sigmoid(A2)
最后是第2层到输出层的信号传递。
def identity_function(x):
return x
W3 = np.array([[0.1, 0.3],[0.2, 0.4]])
B3 = np.array([0.1, 0.2])
A3 = np.dot(Z2, W3) + B3
Y = identity_function(A3) #或者 Y = A3
这里定义的identity_function()函数
(也称“恒等函数”),并将其作为输出层的激活函数。
3.4.3 代码实现小结
def init_network():# 权重、偏置初始化
network = {}# 字典保存每层的权重和偏置
network['W1'] = np.array([[0.1, 0.3, 0.5], [0.2, 0.4, 0.6]])
network['b1'] = np.array([0.1, 0.2, 0.3])
network['W2'] = np.array([[0.1, 0.4], [0.2, 0.5], [0.3, 0.6]])
network['b2'] = np.array([0.1, 0.2])
network['W3'] = np.array([[0.1, 0.3],[0.2, 0.4]])
network['b3'] = np.array([0.1, 0.2])
return network
def forward(network, x): # 封装了输入输出信号的处理过程
W1, W2, W3 = network['W1'],network['W2'],network['W3']
b1, b2, b3 = network['b1'],network['b2'],network['b3']
a1 = np.dot(x, W1) + b1
z1 = sigmoid(a1)
a2 = np.dot(z1, W2) + b2
z2 = sigmoid(a2)
a3 = np.dot(z2, W3) + b3
y = identity_function(a3)
return y
def identity_function(x):
return x
network = init_network()
x = np.array([1.0, 0.5])
y = forword(network, x)
print(y)
3.5 输出层的设计
神经网络可以用在 分类问题和回归问题,不过需要根据情况改变输出层的激活函数。
一般来说,回归问题用恒等函数,分类问题用softmax函数。
3.5.1 恒等函数和softmax函数
恒等函数会将输入按原样输出,不加任何改动地直接输出。
softmax函数可以使用下式表示:
(exp(X)是表示e的x次方的指数函数)
以下图可以看出,输出输出层的各个神经元都受到所有输入信号的影响。
现在来实现softmax函数:
a = np.array([0.3, 2.9, 4.0])
exp_a = np.exp(a)
sum_exp_a = np.sum(exp_a)
y = exp_a /sum_exp_a
#-----上式是按照公式来实现的,故不需要解释------
#-----接下来我们进行封装------
def softmax(a):
exp_a = np.exp(a)
sum_exp_a = np.sum(exp_a)
y = exp_a /sum_exp_a
return y
3.5.2 实现softmax函数的注意事项
上面的函数虽然正确描述了公式,但在计算机运算时存在溢出问题,即由于其中的指数函数运算。可能会返回一个代表无穷大的inf。
即可进行公式改进,改进后代码实现:
def softmax(a):
c = np.max(a)
exp_a = np.exp(a - c) #溢出对策
sum_exp_a = np.sum(exp_a)
y = exp_a / sum_exp_a
return y
3.5.3 softmax 函数 的特征
使用softmax()
函数,可以按如下方式计算神经网络的输出:
a = np.array([0.3, 2.9, 4.0])
y = softmax(a)
print(y) ---> [ 0.01821127 0.24519181 0.73659691]
np.sum(y) ---> 1.0
如上,softmax函数总是输出0.0~1.0之间的实数,并且输出值的总和是1。
输出值的总和是1,是softmax函数的重要性质。正因如此,我们才可以把softmax函数的输出解释为“概率”。
如上,可以看出y[2]的概率最大(这里需要注意数组是从“0”开始)。
一般来说,神经网络只把输出值最大的神经元对应的类别作为识别结果。并且,即使使用softmax函数,输出值的最大神经元的位置也不会变。因此,在进行分类时,输出层的softmax函数可以省略。在实际问题中由于指数函数的运算量,一般输出层的softmax函数会被省略。
3.5.4 输出层的神经元数量
根据解决的问题来决定。
3.6 手写数字识别
假设学习已经全部完成,我们使用学习到的参数,先实现神经网络的“推理处理”,也称为神经网络的前向传播(forward propagation)。
3.6.1 MNIST 数据集
这里使用MNIST手写数字图像集。该数据集是由0~9的数字图像构成的,训练图片有6万张,测试图片有1万张。
本书提供Python脚本mnist.py,使用其中的load_mnist()
函数,就可以按如下方式读入数据。
import sys, os
sys.path.append(os.pardir) #为了导入父目录中的文件而进行的设定
from dataset.mnist import load_mnist
(x_train, t_train) (x_test, t_test) = load_mnist(flatten=True,normalize=False)
print(x_train.shape) #(60000, 784)
print(t_train.shape) # (60000,)
print(x_test.shape) # (10000, 784)
print(t_test.shape) # (10000,)
load_mnist
以(训练图像,训练标签),(测试图像,测试标签)的形式返回读入的数据.
可设置参数如下:
normalize
设置是否将输入图像正规化为0.01.0的值,如果False,则输入图像的像素保持原来的0255flatten
设置是否展开输入图像(变成一维数组),如果False,输入图像为1×28×28的三维数组;如果True,则输入图像保存为784个元素构成的一维数组。one_hot_label
设置是否将标签保存为one—hot,此表示是仅正确解标签为1,其余皆为0的数组。当False,只是像7、2这样的简单保存正确解标签。为True,则保存为one-hot。
接下来我们尝试显示 MNIST图像,同时也确认一下数据。图像显示使用PIL模块。下述代码会让第一张训练图像显示出来:
import sys, os
sys.path.append(os.pardir)
import numpy as np
from dataset.mnist import load_mnist
from PIL import Image
def img_show(img):
pil_img = Image.fromarray(np.uint8(img))#把numpy数组的图像数据转换为PIL用的数据对象
pil_img.show()
(x_train, t_train),(x_test, t_test) = load_mnist(flatten=True,
normalize=False)
img = x_train[0]
label = t_train[0]
print(label) --->5
print(img.shape) ---> (784,)
img = img.reshape(28, 28) #把图像的形状变成原来的尺寸
print(img.shape) ---> (28, 28)
img_show(img)
3.6.2 神经网络的推理处理
下面对这个数据集实现神经网络的推理处理。
神经网络的输入层有784个(源于图像大小28*28=784)神经元,输出层有10个(源于0~9,10个数字类型)神经元。此外,这个神经网络有2个隐藏层,第1个隐藏层有50个神经元,第2个隐藏层有100个神经元,这个50和100可以设置为任何值。
先定义三个函数:
def get_data():
(x_train, t_train),(x_test, t_test) = load_mnist(normalize=True,flatten=True, one_hot_label=False)
return x_test, t_test
def init_network():# 会读入保存在pickle文件sample_weight.pkl中的学习到的权重参数
with open("sample_weight.pkl",'rb') as f:
network = pickle.load(f)
return network
def predict(network, x):
W1, W2, W3 = network['W1'], network['W2'], network['W3']
b1, b2, b3 = network['b1'], network['b2'], network['b3']
a1 = np.dot(x, W1) + b1
z1 = sigmoid(a1)
a2 = np.dot(z1, W2) + b2
z2 = sigmoid(a2)
a3 = np.dot(z2, W3) + b3
y = sigmoid(a3)
return y
然后评价它的识别精度,即能在多大程度上正确分类。
x, t = get_data() #获得数据集
network = init_network() #生成网络
accuracy_cnt = 0
for i in range(len(X)): #逐一取出保存在x中的图像数据
y = predict(network,x[i]) #分类
p = np.argmax(y) #获得概率最高的元素的索引 np.argmax(x)函数取出数组中最大值的索引,将获取被赋给参数x的数组中的最大值元素的索引。
if p == t[i]:# 比较神经网络所预测答案和正确解的标签
accuracy_cnt +=1
print("Accuracy:" + str(float(accuracy_cnt) / len(x)))# 回答正确的概率作为识别精度
执行完上述代码后,会显示“Accuracy:0.9352”。这表示有93.52%数据被正确分类了。
3.6.3 批处理
可以发现对应维度的元素个数是一致
如上输入100 × 784 输出100 × 10,这表示100张图像的结果被一次性输出了。比如x[0]和y[0]中保存了第0张图像及其推理结果。
这样打包式的输入数据称为批。
x, t = get_data()
network = init_network()
batch_size = 100 ##批数量
accuracy_cnt = 0
for i in range(0, len(X), batch_size):
x_batch = x[i:i+batch_size]#通过x[i:i+batch_size]从输入数据中抽取批数据
y_batch = predict(network,x_batch)
p = np.argmax(y_batch,axis=1)# axis=1 沿着一维方向
accuracy_cnt += np.sum(p == t[i:i+batch_size])
print("Accuracy:" + str(float(accuracy_cnt) / len(x)))# 回答正确的概率作为识别精度