神经网络简介

导语

书中已经表明感知机理论上可以表达任意的复杂函数(感知机简介),但问题是,对于一个已经做好框架的感知机,如何通过确定参数使得其能够实现函数想要的功能,在神经网络诞生之前,这些权重参数大多是人工设定的,神经网络的出现解决了自动从数据中学习权重参数的问题。

在学习神经网络如何自动学习之前,需要搞清楚神经网络的结构,以及神经网络是如何实现预测的,神经网络是如何在感知机的基础上优化的,书上的神经网络这一章详细的介绍了这些。

感知机与神经网络

神经网络一般分为三部分,输入层,隐藏层(该层可能有多层),输出层,如下图,其实和感知机没太大区别,具体的区别还是在内部的实现上。
在这里插入图片描述
感知机的概念和图见(感知机简介),这里不作赘述。
在感知机的式子当中,是存在偏置值b的,但是一般的图中没有加上它,如果我们把b也作为输入考虑进来的话,感知机的表示就会变成下图。

在这里插入图片描述

为了表示的更简单,书上将判断式子 b + w 1 x 1 + w 2 x 2 b+w_1x_1+w_2x_2 b+w1x1+w2x2视为了一个整体,引入新函数 h ( x ) h(x) h(x),得到式子 y = h ( b + w 1 x 1 + w 2 x 2 ) y=h(b+w_1x_1+w_2x_2) y=h(b+w1x1+w2x2),这里的 h ( x ) h(x) h(x)是一种判断函数,具体如下。

h ( x ) = { 0 , i f ( x ≤ 0 ) 1 , i f ( x > 0 ) h(x)= \begin{cases} 0,&if &(x\le0)\\ 1,&if &(x\gt0) \end{cases} h(x)={0,1,ifif(x0)(x>0)

这里的 h ( x ) h(x) h(x)也可以取别的函数。

激活函数

上文所提到的 h ( x ) h(x) h(x),作用是将输入信号总和转换为输出信号,即激活函数。在感知机的基础上,神经网络将信号总和与阈值判断分离开,设信号总和为 a a a,那么就可以得到 a = b + w 1 x 1 + w 2 x 2 , y = h ( a ) a=b+w_1x_1+w_2x_2,y=h(a) a=b+w1x1+w2x2,y=h(a),激活函数的作用方式具体如图。

在这里插入图片描述

有了激活函数,感知机就真正的迈向了神经网络,对于感知机来说,其激活函数是阶跃函数,如果把阶跃函数换成别的函数,就是神经网络了。由于不同的需求,因而诞生了许多不同的激活函数。

sigmoid

这里只简单给出函数式、图像和实现,其他的函数也一样。

函数式: h ( x ) = 1 1 + e − x h(x)=\frac{1}{1+e^{-x}} h(x)=1+ex1

图像:
在这里插入图片描述
函数实现:

def sigmoid(x):
	return 1/(1+np.exp(-x))

阶跃函数

函数式: h ( x ) = { 0 , i f ( x ≤ 0 ) 1 , i f ( x > 0 ) h(x)= \begin{cases} 0,&if &(x\le0)\\ 1,&if &(x\gt0) \end{cases} h(x)={0,1,ifif(x0)(x>0)

图像:
在这里插入图片描述函数实现:

def step_function(x):
	if x>0:
		return 1
	else :
		return 0

ReLU

函数式: h ( x ) = { 0 , i f ( x ≤ 0 ) x , i f ( x > 0 ) h(x)= \begin{cases} 0,&if &(x\le0)\\ x,&if &(x\gt0) \end{cases} h(x)={0,x,ifif(x0)(x>0)

图像:
在这里插入图片描述
函数实现:

def relu(x):
	return np.maximum(0,x)

线/非线性函数

根据书中的定义,线性函数指输出值是输入值常数倍的函数,图像上的表达是一条笔直的直线,非线性函数与之对应,指不像线性函数那样图像表达是一条直线的函数,上述提到的激活函数都是非线性函数。

神经网络的激活函数必须使用非线性函数,如果使用线性函数的话,神经网络的层数就变得没有意义了,可以这样理解,如果用线性函数来表达神经网络的话,假设每一层的参数是 a 1 , a 2 , . . . . . . , a n a_1,a_2,......,a_n a1,a2,......,an,函数表达式就会变成 y ( x ) = h ( . . . . . ( x ) . . . . . . ) y(x)=h(.....(x)......) y(x)=h(.....(x)......),其实可以直接转换成 y ( x ) = h ′ ( x ) , h ′ ( x ) = a 1 a 2 . . . . . . a n x y(x)=h'(x),h'(x)=a_1a_2......a_nx y(x)=h(x),h(x)=a1a2......anx,只需要一层就可以表达(书上说法:不管层数多少,总是存在与之等效的“无隐藏层的神经网络”),这样无法发挥多层神经网络的特点,因此必须使用非线性函数。

简单神经网络实现

书上给出了一些简单神经网络以及实用的例子,不过这只包含了推导的部分,不包含学习的部分,其中涉及到矩阵乘法的部分属于线性代数的基本常识,因此略过,没有记录。

3层神经网络实现

书上的神经网络采用numpy实现,激活函数选取sigmod,一共三层,粗略图和详细图分别如下。

在这里插入图片描述
在这里插入图片描述

用矩阵的乘法运算来表示: A i = X W i + B i , Z i = s i g m o i d ( A i ) A^{i}=XW^{i}+B^{i},Z^{i}=sigmoid(A^{i}) Ai=XWi+BiZi=sigmoid(Ai),其中, A A A为该层的总和, X X X为输入, W W W为权重, B B B为偏置, Z Z Z为输出(实现代码均来自原书,我只是更改了一点数据)。

第一层实现如下(加上了激活函数):

X=np.array([0.5,0.7])
W1=np.array([[0.1,0.2,0.3],[0.4,0.5,0.6]])
B1=np.array([0.1,0.2,0.4])
A1=np.dot(X,W1)+B1
Z1=sigmoid(A1)

一、二层间信号传递(加了激活函数):

W2=np.array([[0.2,0.5],[0.1,0.8],[0.4,0.7]])
B2=np.array([0.4,0.2])
A2=np.dot(Z1,W2)+B2
Z2=sigmoid(A2)

二层到输出层信号传递(加了激活函数)

W3=np.array([0.3,0.5],[0.1,0.6])
B3=np.array([0.9,0.3])
A3=np.dot(Z2,W3)+B3
Y=identity_function(A3)

合并成一个整体:

import numpy as np

def sigmoid(x):
	return 1/(1+np.exp(-x))

def identity_function(x):
    return x

def init_network():#对权重和偏置初始化
    network={}
    network['W1']=np.array([[0.1,0.2,0.3],[0.4,0.5,0.6]])
    network['b1']=np.array([0.1,0.2,0.4])
    network['W2']=np.array([[0.2,0.5],[0.1,0.8],[0.4,0.7]])
    network['b2']=np.array([0.4,0.2])
    network['W3']=np.array([[0.3,0.5],[0.1,0.6]])
    network['b3']=np.array([0.9,0.3])
    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

network =init_network()
x=np.array([0.5,0.7])
print(forward(network,x))

输出结果如图:
在这里插入图片描述

输出层

神经网络大多用于分类或者回归问题上,根据不同的情况采取不同的激活函数,一般分类用恒分函数,回归用softmax函数。

分类问题应用场景通常是给出多组图片,例如一堆猫狗,然后判断每张图片是猫是狗,回归问题应用场景通常是给出一些已经拿到的数据,例如01到22年房地产价格,要预测23年价格。

恒等函数

恒等函数是直通的,输入什么,输出就是什么,在输出层使用恒等函数时,输入信号会原封不动输出,具体如图。
在这里插入图片描述

softmax

分类问题中的softmax函数的表达式为 y k = e a k ∑ i = 1 n e a i y_k=\frac{e^{a_k}}{\sum_{i=1}^ne^{a_i}} yk=i=1neaieak,可以判断出,softmax函数的输出永远都位于0~1之间,因此可以将其输出视为概率,在神经网络中,一般认为输出值最大的神经元对应的类别为网络的识别结果,即使再使用一次softmax,相对的大小结果也是不会变的,因此输出层的softmax函数一般被省略了。

softmax的表示图如下:
在这里插入图片描述

python的实现如下:

import numpy as np
def softmax(a):
    exp_a=np.exp(a)
    return exp_a/np.sum(exp_a)

式子很简单,也易于理解,我们可以分别取 a k = 1 , 10 , 100 , 1000...... a_k=1,10,100,1000...... ak=1,10,100,1000......等数据带入算一下,当带入到100及以上的时候,可以发现 e a k e^{a_k} eak变成一个很大的数字,很难表达,而基于这样一个值的运算更是天方夜谭,softmax的使用到这里似乎陷入了停滞。

基于对数函数的性质,书上给出了这样一个计算的优化,过程如下:
y k = e x p ( a k ) ∑ i = 1 n e x p ( a i ) = C e x p ( a k ) C ∑ i = 1 n e x p ( a i ) = e x p ( a k + l n C ) ∑ i = 1 n e x p ( a i + l n C ) = e x p ( a k + C ′ ) ∑ i = 1 n e x p ( a i + C ′ ) \begin{aligned} y_k&=\frac{exp(a_k)}{\sum_{i=1}^nexp(a_i)}\\&=\frac{Cexp(a_k)}{C\sum_{i=1}^nexp(a_i)}\\ &=\frac{exp(a_k+lnC)}{\sum_{i=1}^nexp(a_i+lnC)}\\ &=\frac{exp(a_k+C')}{\sum_{i=1}^nexp(a_i+C')} \end{aligned} yk=i=1nexp(ai)exp(ak)=Ci=1nexp(ai)Cexp(ak)=i=1nexp(ai+lnC)exp(ak+lnC)=i=1nexp(ai+C)exp(ak+C)

也就是说同时对所有的 a k a_k ak加上或减去一个常数,不会影响最后的结果,为了方便,一般是减去所有输入信号的最大值。

于是python的表达就变成了:

import numpy as np
def softmax(a):
    exp_a=np.exp(a-np.max(a))
    return exp_a/np.sum(exp_a)

手写数字识别(仅前向传播)

书上在这章仅仅讲述了神经网络的推理,也就是在已经学习到参数的基础上,对输入的数据进行预测。

读入和显示

数据集采用MNIST手写数字图像集,源码和数据集请参考深度学习入门-基于Python的理论入门与实现源代码加mnist数据集下载推荐给出的链接,如果直接用源码来下载数据集的话八成下不下来。

这里并不剖析源码中mnist.py的部分,只简单解释下与读入和展示相关的代码。

读入数据使用mnist.py中的load_mnist()函数,函数代码如下:

def load_mnist(normalize=True, flatten=True, one_hot_label=False):
    """读入MNIST数据集
    
    Parameters
    ----------
    normalize : 将图像的像素值正规化为0.0~1.0
    one_hot_label : 
        one_hot_label为True的情况下,标签作为one-hot数组返回
        one-hot数组是指[0,0,1,0,0,0,0,0,0,0]这样的数组
    flatten : 是否将图像展开为一维数组
    
    Returns
    -------
    (训练图像, 训练标签), (测试图像, 测试标签)
    """
    if not os.path.exists(save_file):#如果不存在,直接重新运行
        init_mnist()
        
    with open(save_file, 'rb') as f:#加载对应路径的文件
        dataset = pickle.load(f)##构造数据集
    
    if normalize:#如果要规格化
        for key in ('train_img', 'test_img'):
            dataset[key] = dataset[key].astype(np.float32)
            dataset[key] /= 255.0
            
    if one_hot_label:#采用onehot模式
        dataset['train_label'] = _change_one_hot_label(dataset['train_label'])
        dataset['test_label'] = _change_one_hot_label(dataset['test_label'])
    
    if not flatten:#判断是否变为一维
         for key in ('train_img', 'test_img'):
            dataset[key] = dataset[key].reshape(-1, 1, 28, 28)

    return (dataset['train_img'], dataset['train_label']), (dataset['test_img'], dataset['test_label'])#返回每个数据集对应生成的信息 

函数以(训练图像,训练标签)(测试图像,测试标签)的键值对形式返回读入数据。

对于读入数据的展示代码如下:

# coding: utf-8
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))
    pil_img.show()

(x_train, t_train), (x_test, t_test) = load_mnist(flatten=True, normalize=False)#按照格式返回数据集

img = x_train[1]#拿第一个尝试展示
label = t_train[1]
print(label)  # 5

print(img.shape)  # (784,)
img = img.reshape(28, 28)  # 把图像的形状变为原来的尺寸
print(img.shape)  # (28, 28)

img_show(img)

执行结果:
在这里插入图片描述

在这里插入图片描述

推理处理

每个图像均为28×28的像素,考虑到要对每个像素输入,所以神经网络的输入层应有784个神经元,最后要对图像进行0~9的分类,所以输出层应该有10个神经元,书上给出的神经网络有2个隐藏层。

首先给出书上读入数据和预测的代码:

# coding: utf-8
import sys, os
sys.path.append(os.pardir)  # 为了导入父目录的文件而进行的设定
import numpy as np
import pickle
from dataset.mnist import load_mnist
from common.functions import sigmoid, softmax

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():#获得初始化的神经网络参数
    with open("sample_weight.pkl", 'rb') as f:
        network = pickle.load(f)
    return network

#pickle把运行中的对象保存成文件,加载则可恢复

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 = softmax(a3)#传播过程

    return y

x, t = get_data()
network = init_network()
accuracy_cnt = 0
for i in range(len(x)):
    y = predict(network, x[i])#对第i个图片进行预测
    p= np.argmax(y) # 获取概率最高的元素的索引,视为预测结果
    if p == t[i]:
        accuracy_cnt += 1

print("Accuracy:" + str(float(accuracy_cnt) / len(x)))

运行结果:

在这里插入图片描述

批处理

将许多组数据打包,作为单次输入的数据,这样的一组数据就是批,一般来说,批处理的会比分开逐步计算各个小型数组快,可以按照流水线的原理理解。

按照批处理的思想,将预测的代码可以改成如下:

batch_size = 100 # 批数量
accuracy_cnt = 0

for i in range(0, len(x), batch_size):
    x_batch = x[i:i+batch_size]#拿100个图片
    y_batch = predict(network, x_batch)#一次性预测一百张
    p = np.argmax(y_batch, axis=1)#生成一个最大值数组
    accuracy_cnt += np.sum(p == t[i:i+batch_size])#最大值数组与正确值一一比较

print("Accuracy:" + str(float(accuracy_cnt) / len(x)))

总结

神经网络的构造看起来很复杂,但是如果一步一步实现的话,其实也并不是什么难事,代码中体现的数学原理最多也就到矩阵相乘,还是挺容易理解的。

参考文献

  1. 《深度学习入门——基于Python的理论与实践》
  2. 深度学习入门-基于Python的理论入门与实现源代码加mnist数据集下载推荐
  • 20
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值