原创

深度学习入门02---神经网络基础


对于感知机,面对复杂的函数,感知机也隐含着能够表示它的可能性。即便是计算机进行的复杂处理,感知机(理论上)也可以将其表示出来。但是,设定权重的工作,即确定合适的、能符合预期的输入与输出的权重,现在还是由人工进行的。神经网络的出现就是为了解决这个的坏消息。具体地讲,神经网络它可以自动地从数据中学习到合适的权重参数。

1.从感知机到神经网络

神经网络和感知机有很多共同点。用图来表示神经网络的话,我们把最左边的一列称为输入层,最右边的一列称为输出层,中间的一列称为中间层(隐藏层):

avatar

【说明】:上图中的神经网络一共由3层神经元构成,但实质上只有2 层神经元有权重。神经网络的形状类似感知机。实际上,就神经元的连接方式而言,与感知机并没有任何差异。

avatar

上面的数学表达式是之前我们分析的感知机数学关系式,b是偏置控制神经元被激活的容易程度;而w1和w2是表示各个信号的权重,用于控制各个信号的重要性。现在我们将式子分解变形,引入新函数h(x)

avatar

【说明】:首先,计算加权输入信号和偏置的总和;然后,用h(x)函数将a转换为输出y。

avatar

【说明】:表示神经元的○中明确显示了激活函数的计算过程,即信号的加权总和为节点a,然后节点a被激活函数h(x)转换成节点y。激活函数是连接感知机和神经网络的桥梁。

2.激活函数

之前例子中的感知机使用了阶跃函数作为激活函数。也就是说,在激活函数的众多候选函数中,感知机使用了阶跃函数。那么,如果感知机使用其他函数作为激活函数的话会怎么样呢?实际上,如果将激活函数从阶跃函数换成其他函数,就可以进入神经网络的世界了。下面我们就来介绍一下神经网络使用的激活函数:

2.1 sigmoid函数

sigmoid函数是非常常见的一种激活函数,其数学表达式如下:

avatar

import numpy as np
import matplotlib.pylab as plt

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

X = np.arange(-5.0, 5.0, 0.1)
Y = sigmoid(X)
plt.plot(X, Y)
plt.ylim(-0.1, 1.1)
plt.show()

avatar

2.2 阶跃函数
import numpy as np
import matplotlib.pylab as plt

def step_function(x):
    return np.array(x > 0, dtype=np.int)

X = np.arange(-5.0, 5.0, 0.1)
Y = step_function(X)
plt.plot(X, Y)
plt.ylim(-0.1, 1.1) 
plt.show()

avatar

【说明】:阶跃函数以0为界,输出从0切换为1(或者从1切换为 0)它的值呈阶梯式变化,所以称为阶跃函数。

2.3 sigmoid函数和阶跃函数的比较
import numpy as np
import matplotlib.pylab as plt

x = np.arange(-5.0, 5.0, 0.1)
y1 = sigmoid(x)
y2 = step_function(x)

plt.plot(x, y1)
plt.plot(x, y2, 'k--')
plt.ylim(-0.1, 1.1) #指定图中绘制的y轴的范围
plt.show()

avatar

【说明】:

(1)首先“平滑性”不同:sigmoid函数是一条平滑的曲线,输出随着输入发生连续性的变化。而阶跃函数以0为界,输出发生急剧性的变化。sigmoid函数的平滑性对神经网络的学习具有重要意义。 (2)另一个不同点是,相对于阶跃函数只能返回0或1,sigmoid函数可以返回在0-1之间无数的实数(这一点和刚才的平滑性有关)。也就是说,感知机中神经元之间流动的是0或1的二元信号,而神经网络中流动的是连续的实数值信号。

2.4 非线性函数

神经网络的激活函数必须使用非线性函数。换句话说,激活函数不能使用线性函数。因为使用线性函数的话,加深神经网络的层数就没有意义了,不管如何加深层数,总是存在与之等效的“无隐藏层的神经网络”。

【说明】:例如,我们把线性函数h(x)=cx作为激活函数,把 y(x) = h(h(h(X)))的运算对应3层神经网络。这个运算会进行y(x)=c×c×c× X 的乘法运算,但是同样的处理可以由y(x)=ax(a=c的三次方)一次乘法运算(即没有隐藏层的神经网络)来表示。如本例所示,使用线性函数时,无法发挥多层网络带来的优势。因此,为了发挥叠加层所带来的优势,激活函数必须使用非线性函数

2.4 ReLU函数

神经网络中除了常见的sigmoid函数,还有一种常用的激活函数:ReLU函数:

avatar

import numpy as np
import matplotlib.pylab as plt

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

x = np.arange(-5.0, 5.0, 0.1)
y = relu(x)
plt.plot(x, y)
plt.ylim(-1.0, 5.5)
plt.show()

avatar

3.实现一个3层神经网络

3层神经网络:输入层有2个神经元,第一个隐藏层有3个神经元,第二个隐藏层有2个神经元,输出层有2个神经元:

avatar

【计算推导】:

avatar

3.1 初始化
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])
3.2 输入层到第一个隐藏层

avatar

A1 = np.dot(X, W1) + B1
Z1 = sigmoid(A1)
3.3 第一个隐藏层到第二个隐藏层

avatar

W2 = np.array([[0.1, 0.4], [0.2, 0.5], [0.3, 0.6]])
B2 = np.array([0.1, 0.2])

A2 = np.dot(Z1, W2) + B2
Z2 = sigmoid(A2)
3.4 第二个隐藏层到输出层

avatar

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()函数(也称为“恒等函数”),并将其作为输出层的激活函数,恒等函数会将输入按原样输出。输出层所用的激活函数,要根据求解问题的性质决定。一般地,回归问题可以使用恒等函数,二元分类问题可以使用 sigmoid函数,多元分类问题可以使用softmax函数等等...

3.5 代码重构
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

network = init_network()
x = np.array([1.0, 0.5])
y = forward(network, x)
print(y) # [ 0.31682708 0.69627909]

【说明】:init_network()函数:进行权重和偏置的初始化,并将它们保存在字典变量network中。这个字典变量network中保存了每一层所需权重和偏置。forward()函数:封装了将输入信号转换为输出信号的处理过程。这里出现了 forward表示的是从输入到输出方向的传递处理。后面在进行神经网络的训练时,我们将使用后向(backward,从输出到输入方向)的处理方式。

至此,神经网络的前向处理的实现就完成了。通过巧妙地使用 NumPy 多维数组,我们高效地实现了神经网络。

5.输出层的设计

神经网络可以用在分类问题和回归问题上,不过需要根据情况改变输出层的激活函数。一般而言,回归问题用恒等函数,分类问题用softmax函数。

5.1 恒等函数

恒等函数会将输入按原样输出,对于输入的信息,不加以任何改动地直接输出。

avatar

5.2 softmax函数

softmax函数数学表达式如下:

avatar

def softmax(a):
    exp_a = np.exp(a)
    sum_exp_a = np.sum(exp_a)
    y = exp_a / sum_exp_a

    return y

【说明】:softmax函数在计算机的运算上有一定的缺陷:这个缺陷就是溢出问题。softmax函数的实现中要进行指数函数的运算,但是此时指数函数的值很容易变得非常大。如果在这些超大值之间进行除法运算,结果会出现“不确定”的情况。

avatar

>>> a = np.array([1010, 1000, 990])
>>> np.exp(a) / np.sum(np.exp(a)) # softmax函数的运算
array([ nan,  nan,  nan])         # 没有被正确计算
>>>
>>> c = np.max(a) # 1010
>>> a - c
array([  0, -10, -20])
>>>
>>> np.exp(a - c) / np.sum(np.exp(a - c))
array([  9.99954600e-01,   4.53978686e-05,   2.06106005e-09])

因此,为了避免溢出问题将代码改成下面这样:

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

【说明】:softmax函数的输出是0.0到1.0之间的实数。并且softmax函数的输出值的总和是1。输出总和为1是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 函数,输出值最大的神经元的位置也不会变。因此,神经网络在进行分类时,输出层的 softmax 函数可以省略。在实际的问题中,由于指数函数的运算需要一定的计算机运算量,因此输出层的 softmax 函数一般会被省略。

5.3 输出层的神经元数量

输出层的神经元数量需要根据待解决的问题来决定。对于分类问题,输出层的神经元数量一般设定为类别的数量,例如手写数字识别:

avatar

6.手写数字识别

这里使用的数据集是MNIST手写数字图像集。MNIST数据集是由0到9的数字图像构成的。训练图像有6万张,测试图像有1万张,这些图像可以用于学习和推理。MNIST数据集的一般使用方法是,先用训练图像进行学习,再用学习到的模型度量能在多大程度上对测试图像进行正确的分类。MNIST的图像数据是28像素 × 28像素的灰度图像(1通道),各个像素的取值在0-255之间,每个图像数据都相应地标有“7”“2”“1”等标签。

avatar

【下载数据集】:

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

【可以提前查看数据集相关情况】:

import numpy as np
from Deep_Learning_From_Scratch.dataset.mnist import load_mnist
from PIL import Image

# 载入数据: 参数flatten:是否转成一维数组;参数normalize:是否将特征转成0.0-1.0之间
(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,)

# 查看第一张手写数字
img = x_train[0]
label = t_train[0]
print("第一张手写数字为:", label)  # 5
print("第一张手写数字形状为:", img.shape)  # (784,)
img = img.reshape(28, 28)  # 把图像的形状变为原来的尺寸,以方便显示

# 显示手写数字照片
def img_show(img):
    pil_img = Image.fromarray(np.uint8(img))
    pil_img.show()

img_show(img)
训练集特征形状: (60000, 784)
训练集目标形状: (60000,)
测试集特征形状: (10000, 784)
测试集特征形状: (10000,)
第一张手写数字为: 5
第一张手写数字形状为: (784,)

【基础神经网络构建】:

# coding: utf-8
import numpy as np
import pickle
from Deep_Learning_From_Scratch.dataset.mnist import load_mnist
from Deep_Learning_From_Scratch.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


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_test, t_test = get_data()
network = init_network()

# 一次批处理100张
batch_size = 100
accuracy_cnt = 0

for i in range(0, len(x_test), batch_size):
    x_batch = x_test[i:i+batch_size]
    y_batch = predict(network, x_batch)  # 神经网络预测:y是包含10个概率的数组
    p = np.argmax(y_batch, axis=1)       # 获取概率最高的元素的索引
    accuracy_cnt += np.sum(p == t_test[i:i+batch_size])


print("正确率为:" + str(float(accuracy_cnt) / len(x_test)))

正确率为:0.9352
Python
深度学习
神经网络
  • 作者:李延松(联系作者)
  • 发表时间:2020-07-29 12:40
  • 版本声明:自由转载-非商用-非衍生-保持署名(创意共享3.0许可证)
  • 公众号转载:请在文末添加作者公众号二维码

评论

留言