人工神经网络实践

 

人工神经网络(Artificial Neural Network,ANN) 是一种受人脑生物神经网络信息处理方式启发而诞生的一种计算模型,得益于语音识别、计算机视觉和文本处理方面的许多突破性成果,人工神经网络在机器学习研究和工业领域引起了强烈反响。本篇博客将对人工神经网络算法进行介绍。

 

1 神经元节点

神经元是神经网络的最基本单元,它接收一个外部输入数据或其他神经元节点的输出作为输入,经过计算后将产生一个输出。每个神经元节点都有一个权重和偏置参数,权重象征着对应的输入相对于其他输入的重要程度,而偏置则是用于对权重与输入的计算结果进行校正,如图1所示:

 

图1 神经元节点
 

图1中的神经元节点接受$x_i$作为输入,然后与相关联的权重参数$w_i$相乘后求和,加上偏置$b$后使用函数$f$进行变换输出获得$y$作为这一神经元节点的输出值。对于函数$f$,通常我们称之为激活函数,其作用是添加一个非线性变换,这是至关重要的一步操作,因为现实世界中的绝大多数数据都不会是呈线性分布,如果没有为网络中的神经元节点添加激活函数,整个网络只不过是多个线性分类器的组合,依然只能完成线任务。所以,神经元节点中的数学运算我们可以更一般化得表达为: $$y = Activation(\sum\limits_i^N {{w_i} \cdot {x_i} + b} )$$

 

激活函数接受一个数值作为输入,对该数值进行非线性运算之后输出一个数值作为结果。下面说说常见的几个激活函数。
(1)sigmod函数 $$f(x) = \frac{1}{{1 + {e^{ - x}}}}$$ sigmoid函数可以将整个实数范围的的任意值映射到[0,1]范围内,当输入值较大时,sigmoid将返回一个接近于1的值,而当输入值较小时,返回值将接近于0。sigmod函数图像如图2所示。

 

image

图2 sigmod函数图像
 

(2)Relu函数 $$f(x) = \max (0,x)$$ relu(Rectified Linear Units修正线性单元),是目前被使用最为频繁得激活函数,relu函数在x<0时,输出始终为0。由于x>0时,relu函数的导数为1,即保持输出为x,所以relu函数能够在x>0时保持梯度不断衰减,从而缓解梯度消失的问题,还能加快收敛速度,还能是神经网络具有稀疏性表达能力,这也是relu激活函数能够被使用在深层神经网络中的原因。由于当x<0时,relu函数的导数为0,导致对应的权重无法更新,这样的神经元被称为"神经元死亡"。relu函数图像如图3所示。 image

图3 relu函数图像
 

(3)softmax函数 $$f({x_i}) = \frac{{{e^{{x_i}}}}}{{\sum\limits_i {{e^{{x_i}}}} }}$$ softmax函数是sigmoid函数的进化,在处理分类问题是很方便,它可以将所有输出映射到成概率的形式,即值在[0,1]范围且总和为1。例如输出变量为[1.5,4.4,2.0],经过softmax函数激活后,输出为[0.04802413, 0.87279755, 0.0791784 ],分别对应属于1、2、3类的概率。

 

2 前馈神经网络

前馈神经网络是的最原始也是最简单的一种人工神经网络。前馈神经网络由多个层组成,每一层包含多个神经元节点,相邻层直接神经元节点相互连接,每一条连接都有一个权重参数$w$与之关联,如图4所示。

 

图4 前馈神经网络结构
 

前馈神经网络中层节点可以分为3类。
(1)输入层节点。输入层节点是前馈神经网络的第一层节点,作为外部数据到神经网络的入口而存在,输入层节点不负责进行任何技术,只是将外部输入的数据传递到隐藏层节点。
(2)隐藏层节点。隐藏层节点是前馈神经网络的中间部分,它不与外界进行直接接触(这也是为什么成为隐藏层的原因)。隐藏层节点负责对输出层节点传递过来的数据进行一定运算,然后传递给输出层。前馈神经网络可以零个或多个隐藏层。
(3)输出层节点。输出层节点是整个前馈神经网络结构上最末尾上一层的节点,负责将隐藏层中传递过来的信息进行整合并输出到外界。
在前馈网络中,信息仅在一个方向上向前移动,即从输入节点到隐藏节点(如果有)再到输出节点不存在循环或回路的情况。我们可以粗略地将前馈神经网络分为两类:
(1)单层感知机模型。单层感知机模型是最简单的一种神经网络模型,不包含隐藏层,也只能处理线性任务。关于单层感知机模型,请参考我的另一篇博客
(2)多层感知机。多层干自己是单程感知机的进化版,其组成结构中至少包含一个隐藏层,且在层与层之间添加了激活函数进行非线性变换,使其可以适用于非线性任务中。下文中,我们将围绕多层感知机对神经网络展开介绍。

 

3 多层感知机

如图5所示是一个包含一个隐藏层的多层感知机,从图中可以看出,节点直接的连接都有一个权值,当然,图中只显示了红色节点的三个参数。
(1)输入层。输入层具有三个节点,偏置节点的值为$1$(有时候偏置并不会作为一个节点,而是作为下一层节点中的一个参数而存在),其它两个节点将$X1$和$X2$作为外部输入(其数值取决于输入数据集)。如上所述,在输入层中不执行任何计算,因此输入层中节点的输出分别为$1$、$X1$和$X2$,它们被馈送到隐藏层。
(2)隐藏层。图5所示网络结构隐藏层中同样具有三个节点,其偏置节点的输出为1,隐藏层中其他两个节点的输出取决于输入层$(1,X1,X2)$的输出以及与之相关的权值,权值与$(1,X1,X2)$运算后使用激活函数$f$进行非线性变换,最终的结果作为输出传递到下一层。
(3)输出层。输出层具有两个节点,这两个节点从隐藏层获取输入,并执行与红色隐藏层节点所示的类似计算,计算结果(Y1和Y2)用作为多层感知器的输出。

 

图5 多层感知机
 

给定一组特征$X =(x1,x2,…,xn)$和目标值$Y$,多层感知器可以学习特征与目标值之间的关系,以进行分类或回归。举例来说,假设有如下一个描述学生成绩的数据集。两个输入列显示了学生学习的小时数和学生获得的期中成绩,最终结果列可以有两个值1或0,表示学生是否通过了最后一个学期。我们可以看到,如果学生学习了35个小时,并且在期中获得了67分,那么他/她最终将通过期末考试。

 

 

现在,假设我们要预测一个学习25小时并在期中获得70分的学生能否通过期末考试。这是一个二进制分类问题,多层感知器可以从给定的示例中学习(训练数据),并在给定新数据点的情况下做出明智的预测。接下来,我们继续说说多层感知机模型是如何进行学习的。

 

4 反向传播算法

所谓神经网络的学习或者说训练,就是指通过不断地调整参数优化解决指定问题所需的参数,使得神经网络的输出值与真实取值之间的差异最小化,这里的参数包括各层神经元之间的连接权重以及偏置等。这个学习的过程一般通过误差反向传播算法来完成。
误差反向传播算法是怎么工作的呢?

随机初始化网络中权重参数后,就可以开始进入训练阶段,依次将数据集中的数据输入到网络中,对应的,网络模型会对每一个输出进行相应产生一个输出。将该输出与我们已经知实际值进行比较,并将将误差“传播”回上一层,并相应地调整权重。重复该过程,直到输出误差低于预定阈值为止——这就是误差反向传播算法的大致过程。我们继续用上文中学生学习时长、期中考成绩与期末是否通过的例子说说误差具体是怎么反向传播的。

第一步:前向传播
如图5所示,假设从输入到该节点的连接权重为$w1$,$w2$和$w3$。先将上文例子中第一条数据[35, 67, 1]输入到网络中,数据[35, 67, 1]可以拆分为两个部分:
(1)特征向量[35, 67]
(2)目标向量[1, 0]
用$f$表示激活函数,$V$表示网络输出,那么,图5的过程可以表示为:
$$V = f(1 \times w1 + 35 \times w2 + 67 \times w3)$$ 隐藏层中,另外两个节点的输出计算也是类似的,只不过在图中并没有表示出来。当三个隐藏层节点完成计算后,获得的结果将继续往下一层也就是输出层传输,输出层将输出两个概率值。假设输出层中两个节点的输出概率分别为0.4和0.6(由于权重是随机分配的,因此输出也将是随机的)。我们可以看到,计算出的概率(0.4和0.6)与期望的概率(分别为1和0)相距甚远,误差较大,不过到此第一次的前向传播就完成了。

 

图5 前向传播
 

第二步:反向传播与参数更新
我们计算输出节点处的总误差,将这些误差传播回网络,然后我们使用梯度下降法法来更新网络中的所有参数,以减少输出层的误差。如下图6所示(现在请姑且先忽略图中公式,公式将在下文中用到)。假设上图节点v的权重$w1$,$w2$,$w3$更新后权重为$w4$,$w5$,$w6$,更新过程如下所示:

 

图6 发向传播更新参数
 

如果我们现在再次向网络输入相同的样本数据,则网络的表现将会比之前更好,因为现在已经调整了权重使得预测误差更小。如图7所示,与之前的[0.6,-0.4]相比,现在输出节点上的误差减少到[0.2,-0.2]。

 

 

上述过程就完成了一次反向传播过程,在大量样本情况下,通过反向传播更新参数不断更新,网络模型效果将得到不断提升,从而解决实际问题。接下来,我们用TensorFlow来搭建一个人工神经网络模型,实现服饰图片分类。

 

5 Tensorflow实现人工神经网络

 

我们使用Fashion Mnist数据集来训练、测试一个神经网络模型。Fashion Mnist数据集由70,000张黑白图片构成,每张图片大小为 28x28,由十类服饰图片构成。我们使用60,000张图片作为训练集,10,000张图片作为测试集。这个数据集可以从 TensorFlow 中直接获取,返回值为numpy数组。

In [9]:
import tensorflow as tf
import matplotlib.pyplot as plt
from tensorflow import keras
from tensorflow.keras import datasets, layers, optimizers, Sequential ,metrics
 

加载数据集

In [18]:
(x, y), (x_test, y_test) = datasets.fashion_mnist.load_data()
 

来看看数据集具体是什么样的:

In [27]:
index = 1
fig, axes = plt.subplots(4, 3, figsize=(8, 4), tight_layout=True)
for row in range(4):
    for col in range(3):
        axes[row, col].imshow(x[index])
        axes[row, col].axis('off')
        axes[row, col].set_title(y[index])
        index += 1
plt.show()
 
 

图片上方数字为图像对应的类别标签,其具体含义如下图表格所示:

 

 

定义一个函数对图中像素进行处理:

In [28]:
def preprocess(x, y):
    x = tf.cast(x, dtype=tf.float32) / 255.
    y = tf.cast(y, dtype=tf.int32)
    return x, y
In [29]:
print(x.shape, y.shape)
print(x_test.shape, y_test.shape)
 
(60000, 28, 28) (60000,)
(10000, 28, 28) (10000,)
In [30]:
batchs = 128  # 每个簇的大小
 

打乱训练数据集:

In [31]:
db = tf.data.Dataset.from_tensor_slices((x, y))
db = db.map(preprocess).shuffle(10000).batch(batchs)
 

对测试集,虽然不需要打乱,但是也要进行与数据集同样的预处理和分割出簇:

In [32]:
db_test = tf.data.Dataset.from_tensor_slices((x_test, y_test))
db_test = db_test.map(preprocess).batch(batchs)
 

现在,数据已经准备就绪,可以开始搭建模型了。因为图像大小为28*28,展开就是784大小,所以输入层大小一定是784,我们可以从下一层,也就是第一个隐藏层开始定义,我们设置为256个节点,接下来各层分别是128,64,32个节点,最后一层为输出层,因为有10个类别,所以我们在输出层定义10个节点。

In [34]:
model = Sequential([
    layers.Dense(256, activation=tf.nn.relu),  #  [b, 784]  --> [b, 256]
    layers.Dense(128, activation=tf.nn.relu),  #  [b, 256]  --> [b, 128]
    layers.Dense(64, activation=tf.nn.relu),  #  [b, 128]  --> [b, 64]
    layers.Dense(32, activation=tf.nn.relu),  #  [b, 64]  --> [b, 32]
    layers.Dense(10)  #  [b, 32]  --> [b, 10]
    ]
)
model.build(input_shape=[None,28*28])
model.summary()
optimizer = optimizers.Adam(lr=1e-3)#1e-3
 
Model: "sequential"
_________________________________________________________________
Layer (type)                 Output Shape              Param #   
=================================================================
dense (Dense)                multiple                  200960    
_________________________________________________________________
dense_1 (Dense)              multiple                  32896     
_________________________________________________________________
dense_2 (Dense)              multiple                  8256      
_________________________________________________________________
dense_3 (Dense)              multiple                  2080      
_________________________________________________________________
dense_4 (Dense)              multiple                  330       
=================================================================
Total params: 244,522
Trainable params: 244,522
Non-trainable params: 0
_________________________________________________________________
 

从上面的输出可以看出每一层参数量,例如第一层有参数200960个,对于一个简单的图像分类任务来说,这个参数量是十分巨大的,这也是为什么会有我们下一篇博客中将要介绍的卷积神经网络诞生的原因之一。

 

定义训练过程,这个过程通过模型的fit方法配置可以轻松完成,不过,为了展示神经网络的具体工作流程,下面我们重新撸一遍代码:

In [40]:
def main():
    for epoch in range(10):  # 迭代训练30次后停止
        train_loss = 0
        train_num = 0
        for step, (x, y) in enumerate(db):
            # db分成batch后,大概有469个batch
            # x的shape(batchs,28,28),因为数据集已经分成了一个个簇,所以每一个x都是是一个簇,包含batchs张图片
            # 为方便运算,重新将x reshape成二维数据,x的shape就变成了(batchs, 784)
            x = tf.reshape(x, [-1, 28*28])
            
            with tf.GradientTape() as tape:  # 梯度
                
                logits = model(x)  # 使用模型对x进行计算,输出为预测值,也就是整个模型的输出。输出的logits的shape为(batchs, 10)
                y_onehot = tf.one_hot(y,depth=10)  # 对y进行热独编码
                
                loss_mse = tf.reduce_mean(tf.losses.MSE(y_onehot, logits))
                loss_ce = tf.losses.categorical_crossentropy(y_onehot, logits, from_logits=True)
                loss_ce = tf.reduce_mean(loss_ce)  # 计算整个簇的平均loss
            grads = tape.gradient(loss_ce, model.trainable_variables)  # 计算梯度
            optimizer.apply_gradients(zip(grads, model.trainable_variables)) # 更新梯度
            
            if step % 300 == 0: # 没训练完300个簇输出一次loss
                print((epoch), step, 'loss:', float(loss_ce), float(loss_mse))
            train_loss += float(loss_ce)
            train_num += x.shape[0]
        loss = train_loss / train_num
        
        
        total_correct = 0
        total_num = 0
        for x,y in db_test:# 每一次迭代都是用测试集验证一下准确率
            x = tf.reshape(x, [-1, 28*28])  # 跟上面训练的时候一样,x的shape也是(batchs,28,28)所以reshape成二维的shape为(128,784)
            logits = model(x)  # 进行预测,logits是(batchs, 10)的tensor
            prob = tf.nn.softmax(logits, axis=1)  # 将logits里面每一个(每一个预测值)转换为概率的形式,保证概率和为1
            pred = tf.argmax(prob, axis=1)  #每一行最大概率所在索引
            pred = tf.cast(pred, dtype=tf.int32)
            correct = tf.equal(pred, y)  # 判断预测值是否与真实值相等
            correct = tf.reduce_sum(tf.cast(correct, dtype=tf.int32))  # 统计相等的个数

            total_correct += int(correct)
            total_num += x.shape[0]
        acc = total_correct / total_num
        print(epoch, 'avg-loss:',loss,'test acc:', acc)
 

调用main()方法,开始训练模型:

In [41]:
main()
 
0 0 loss: 0.08061596751213074 296.3019714355469
0 300 loss: 0.176801860332489 353.3863220214844
0 avg-loss: 0.0007949150484676162 test acc: 0.8895
1 0 loss: 0.11498871445655823 387.3428955078125
1 300 loss: 0.05147100239992142 411.7041015625
1 avg-loss: 0.0007653930709076425 test acc: 0.8895
2 0 loss: 0.10222060978412628 440.11517333984375
2 300 loss: 0.0964309498667717 398.7025451660156
2 avg-loss: 0.0007472941488958896 test acc: 0.8909
3 0 loss: 0.10470890998840332 319.424560546875
3 300 loss: 0.09943300485610962 411.2906494140625
3 avg-loss: 0.0007366230035511156 test acc: 0.8919
4 0 loss: 0.09135544300079346 442.5450744628906
4 300 loss: 0.11110477149486542 483.79437255859375
4 avg-loss: 0.0007339626496657729 test acc: 0.8945
5 0 loss: 0.059614263474941254 422.6461486816406
5 300 loss: 0.10703736543655396 485.8603210449219
5 avg-loss: 0.0007124265033130844 test acc: 0.8916
6 0 loss: 0.12480953335762024 364.9261169433594
6 300 loss: 0.08893868327140808 568.0016479492188
6 avg-loss: 0.0006790677736513317 test acc: 0.8908
7 0 loss: 0.1477620005607605 367.45355224609375
7 300 loss: 0.08248952776193619 461.7186279296875
7 avg-loss: 0.0006600742932719489 test acc: 0.8915
8 0 loss: 0.07141244411468506 549.00341796875
8 300 loss: 0.08829254657030106 616.8319702148438
8 avg-loss: 0.0006306016177870333 test acc: 0.8842
9 0 loss: 0.12584742903709412 347.49615478515625
9 300 loss: 0.08275362849235535 383.50506591796875
9 avg-loss: 0.0006549825438919167 test acc: 0.8911
 

可以看到,经过30次迭代训练之后,模型的准确率基本稳定在左右89%。

posted @ 2020-05-03 10:17  奥辰  阅读(...)  评论(...编辑  收藏