7.1 单隐藏层神经网络构建与应用

> 单个神经元模型

可以把它简单分为几部分:首先是输入数据,输入数据是由输入样本的特征来决定的,在手写字符识别的例子中,输入是28×28像素点的灰度图,把每一个像素点的值当做是其中的一个特征,那么总共有28×28也就是784个特征点,从x1到x784,然后还需要一个偏置项b。

神经元所做的第一件事是求和,用到了上图黑框中的公式,对xi×wi求和,wi为这条边的权重,最后加上偏置项,得到神经元的计算值,然后再做激活处理,这里用f()函数来表示激活函数,最终通过激活函数运算得到的结果,就是这个神经元的输出结果。

如果把求和和激活函数看作一个整体,这个灰色的圆圈就可以表示神经元模型,接下来所要讲的多层神经网络,也就是基于多个这样的模型做扩展。

> 常见激活函数

由于上一讲中是个分类应用,所以用Softmax函数把结果进行分类的概率运算,但常见激活函数还包括Sigmoid、ReLU、tanh。

> Sigmoid

Sigmoid也称为S型函数,在上一讲中有介绍过,它的公式和图像如下所示:

它把所有的x的值映射到0-1之间,形成一条S型的曲线。

> tanh

它是一个双曲正切函数,长得跟Sigmoid很类似,但它们的区别在于tanh的取值范围从-1到1。

> ReLU

ReLU又叫修正线性单元函数,这个函数比较简单,若x≥0,它的值为x本身,若x<0,它的值为0。

> 单神经元模型效果

在上一讲中,通过单神经元建模,训练了150轮次,设置单批样本数为50,学习率为0.01等等,最终可以达到90%左右的正确率。

如果对这个准确率还不是很满意的话,没关系,我们可以做进一步的改进处理——加一些神经元。

> 全连接神经网络

这是一个全连接的单隐藏层的神经网络,可以分为三层:第一层是输入层,刚刚有提到过,是根据训练样本的特征决定有多少个节点,最后加上一个偏置项;第二层是隐藏层,隐藏层中的每一个节点就是一个神经元,最后也需要加上一个偏置项;第三层是输出层,所有隐藏层的输出都会作为下一层(也就是输出层)中每一个节点的输入。

什么是全连接呢?以上图中的a1为例,它上一层中的所有节点,从x1到b都有线跟它相连,即作为它的输入,同时,它作为输出层y1-yj的上层节点之一,它的输出会反馈给输出层上所有的节点,这样就形成了一个全连接的网络。而且在这个全连接的网络中,同一层之间没有线相连,跨层之间也没有线相连,因此这个全连接指的是相邻两层的所有节点之间相连。根据箭头的方向,前面一层所有的输出作为后面一层每一个节点的输入,形成一个全连接的网络。

在这个网络中,我们通常会说这个神经网络有多少层,这个层数是由隐藏层的数量决定的,不用考虑输入层和输出层,上图的结构中隐藏层只有一层,因此是一个单隐藏层的全连接神经网络。

更复杂一点的神经网络,比如有两个隐藏层,我们称之为两层全连接层神经网络。

> 全连接单隐藏层网络建模实现

> 载入数据

> 构建输入层

在输入层中,x1-xn都是样本的特征值,是28×28的灰度图,它总共有784个特征。这里定义了一个占位符x,它的形为784,前面的参数为None,表示行数不限定,之后根据训练时确定的一次输入多少个样本再进行赋值就可以了。占位符y表示样本的标签值,因为是one hot的,所以有10列。

> 构建隐藏层

这里我们设置了256个神经元,大家也可以根据自己的喜好去调整。

这是一个全连接的模型,跟a1相连的有784个特征以及一个偏置项b。所以这里定义了W1,也就是权值,它的形为784,表示784行(784个特征),它的列为H1_NN,也就是隐藏层的节点数,所以这里的权值总共有784×256=200704个,它的初值用了tf.randon_normal()函数,产生一个符合正态分布的随机数。这里还定义了b1,也就是偏置项,它也跟256个节点相连,因此也是H1_NN,它的初值用了tf.zeros()函数,赋值为0。

隐藏层中,神经网络的计算规则是输入的特征值乘以对应边上的权值,这里用了tf.matmul()函数,实现矩阵叉乘,然后再加上偏置项b。并对运算结果用了ReLU函数,它在TensorFlow中集成了,只要直接调用即可。最后,把得到结果赋给Y1。

> 构建输出层

输出层的每一个y的输入是前一层所有节点的输出,因此这里的权值W2的形就是H1_NN,也就是前一层的256个节点,由于输出的是one hot的十分类值,因此后面一个参数为10。偏置项b2跟10个节点相连,因此,它的形为10。

通过tf.matmul()函数实现Y1和 W2的叉乘,再加上偏置项b2,得到前向计算(forward)的值。这个前向计算并没有加上激活函数。

最后的预测值(pred)通过softmax形成一个多分类的结果。

> 定义损失函数

> 设置训练参数

训练轮数定义为40轮;一次训练的样本数为50;在一轮中batch的总数为训练集的大小除以批次大小,也就是1100次;显示粒度为1,学习率为0.01。

> 选择优化器

> 定义准确率

准确率的定义跟上一讲一样,通过argmax函数得到正确的数值(十个元素中最大值的下标)。对于标签值y来说,只有一个为1,其他全部为0,返回的就是值为1的下标。对于预测值pred来说,通过softmax后得到的是十个0-1的概率值,返回的就是概率最大的值的下标。然后通过equal函数,判断两者的值是否相等,返回true或false。再通过类型转换(tf.cast)把它变成浮点数(0或1)。最后计算均值,得到准确率。

> 模型训练

开头记录了开始时间,然后进行了变量初始化。接下来是for循环进行训练,每次通过TensorFlow中封装好函数读取下一批数据,而且在读取之前,已经做了洗牌工作。然后把得到的样本值作为输入,进行优化训练。优化器训练完一轮后,通过验证的数据集去验证数据的损失和准确率并显示出来。在整个训练完成后,显示运行的总时间。

> 训练结果

运行之后可能会出现下面的结果:

loss值出现了nan的情况,而准确率一直停留在0.0958。我们知道,一个十分类的问题,就算随机猜一个数,它的概率也是10%,所以这个数据有问题的。那么为什么会产生这样的问题呢?

主要是计算得到的loss值不对,超过了认可的范围。

前面定义损失函数的时候,用了交叉熵损失函数,它的公式没有问题,但是我们对前面获得的pred求了log,如果pred的值为0或者接近于0,会引起数据的不稳定,得到nan的结果。

如何破解这个难题呢?

在TensorFlow中,提供了一种新的损失函数定义的方法——结合Softmax的交叉熵损失函数定义方法:

它的第一个参数是前向计算的值,第二个参数是标签值y。

注意:这里所用的参数forward是没有经过softmax的前向计算的值,而之前定义的交叉熵损失函数,是经过softmax后的值,两者是不同的。

所以损失函数的定义要改成这种新的形式。

修改后再训练的结果如下:

可以看到,只是用了单隐藏层的神经网络,第一轮训练后的准确率就已经达到了93.26%,比上一讲中用一个神经元训练了150轮后的准确率还要高。

40轮结束后,得到的训练集的准确率为97.56%,总耗时为87.11秒。

> 评估模型

用测试集对训练好的模型进行评估,最后的结果为97.18%。虽然略低于训练集,但是差别不大,还是很不错的。

> 应用模型

当这个模型基本上达到要求,就可以用它进行预测了。

> 进行预测

这里的预测还是选用了测试集中的样本。

把需要测试的图像的数据集作为输入填充进去,产生pred,通过argmax取出最大值的下标,即它所预测的数字,最后赋给prediction_result,它是一个列表。

查看前十项的结果:

这里给出的是10个数字,在上一讲中也提到过,这样的显示并不是那么直观。我们所关心的是:它哪里预测出现了错误。

> 找出预测错误

prediction_result是预测得到的结果,mnist.test.labbels是正确的标签。通过对两者的比较(用两个等号),判断它们的值是否相等并把结果赋给compare_list,compare_list中包含了所有标签的正确与否。

接下来,把所有有误的预测值的下标找出来:

err_list中保存了所有预测有误的值的下标,这里用到了列表递推式,做10000次(compare_list的长度)循环,如果列表项所对应的值为False,就把它的下标加入到err_list。最后输出err_list列表中的所有内容以及列表的长度(代表有多少项没有预测正确)。由于之前测试集的准确率为97.18%,也就意味着有282条数据没有预测正确,这跟上图中的结果一致。

> 定义输出错误分类的函数

由于在err_list中,只能知道预测错误的下标,而不能知道错误的值是多少、标签值是多少,因此再定义一个函数来解决这个问题:

它有两个参数:一个是标签值的列表;另一个是预测值的列表。这里还是用到了compare_list和err_list,下一步只需要在err_list中,针对每一个错误的值显示它的下标、标签值以及预测值就可以了。全部输出完毕后,会有一个总计,统计一共有多少个预测错误。

第一个预测错误的下标为119,标签为2,预测值为8;第二个预测错误的下标为247,标签为4,预测值为2;……。这样,就以文本的形式告诉我们它到底把什么误判成了什么。我们也可以用到上一讲的知识,做一个图像可视化。

> 定义可视化函数

这里的定义跟上一讲一样,就不再赘述。构建好函数之后,就可以可视化查看预测错误的样本:

比如下标为610的值是错的,就可以看到它把4当成了2。

示例代码

载入数据.py
import tensorflow as tf

# 导入Tensorflow提供的读取MNIST的模块
import tensorflow.examples.tutorials.mnist.input_data as input_data 

# 读取MNIST数据
mnist = input_data.read_data_sets("MNIST_data/", one_hot=True) 
建立模型.py
# 构建输入层
x = tf.placeholder(tf.float32, [None, 784], name="X")
y = tf.placeholder(tf.float32, [None, 10],name="Y")

# 构建隐藏层
# 隐藏层神经元数量
H1_NN = 256

W1 = tf.Variable(tf.random_normal([784, H1_NN]))
b1 = tf.Variable(tf.zeros([H1_NN]))

Y1 = tf.nn.relu(tf.matmul(x, W1) + b1)

# 构建输出层
W2 = tf.Variable(tf.random_normal([H1_NN,10]))
b2 = tf.Variable(tf.zeros([10]))

forward = tf.matmul(Y1, W2) + b2
pred = tf.nn.softmax(forward)
训练模型.py
# 定义损失函数
loss_function = tf.reduce_mean(
    tf.nn.softmax_cross_entropy_with_logits(logits=forward, labels=y))
    
# 设置训练参数
train_epochs = 40
batch_size = 50
total_batch = int(mnist.train.num_examples/batch_size)
display_step = 1
learning_rate = 0.01

# 选择优化器
optimizer = tf.train.AdamOptimizer(learning_rate).minimize(loss_function)  

# 定义准确率
correct_prediction = tf.equal(tf.argmax(y, 1), tf.argmax(pred, 1))
accuracy = tf.reduce_mean(tf.cast(correct_prediction, tf.float32))

# 训练
# 记录训练开始时间
from time import time
startTime=time()

sess = tf.Session()
sess.run(tf.global_variables_initializer())

for epoch in range(train_epochs):
    for batch in range(total_batch):
        xs, ys = mnist.train.next_batch(batch_size)# 读取批次数据
        sess.run(optimizer,feed_dict={x: xs,y: ys}) # 执行批次训练
           
    #total_batch个批次训练完成后,使用验证数据计算误差与准确率   
    loss,acc = sess.run([loss_function,accuracy],
                        feed_dict={x: mnist.validation.images, 
                                   y: mnist.validation.labels})
    
    if (epoch+1) % display_step == 0:
        print("Train Epoch:", '%02d' % (epoch+1), 
              "Loss=", "{:.9f}".format(loss)," Accuracy=","{:.4f}".format(acc))

# 显示运行总时间    
duration =time()-startTime
print("Train Finished takes:","{:.2f}".format(duration))
评估模型.py
accu_test =  sess.run(accuracy,
                      feed_dict={x: mnist.test.images, y: mnist.test.labels})

print("Test Accuracy:",accu_test)
进行预测.py
# 由于pred预测结果是one-hot编码格式,所以需要转换为0~9数字

prediction_result=sess.run(tf.argmax(pred,1), 
                           feed_dict={x: mnist.test.images })
                           
#查看预测结果中的前10项
prediction_result[0:10] 
可视化.py
import matplotlib.pyplot as plt
import numpy as np
def plot_images_labels_prediction(images,      # 图像列表
                                  labels,      # 标签列表
                                  prediction,  # 预测值列表
                                  index,       # 从第index个开始显示
                                  num=10 ):    # 缺省一次显示 10 幅
    fig = plt.gcf() # 获取当前图表,Get Current Figure
    fig.set_size_inches(10, 12)  # 1英寸等于 2.54 cm
    if num > 25: 
        num = 25            # 最多显示25个子图
    for i in range(0, num):
        ax = plt.subplot(5,5, i+1) # 获取当前要处理的子图
        
        ax.imshow(np.reshape(images[index],(28, 28)),  # 显示第index个图像
                  cmap='binary')
            
        title = "label=" + str(np.argmax(labels[index]))  # 构建该图上要显示的title信息
        if len(prediction)>0:
            title += ",predict=" + str(prediction[index]) 
            
        ax.set_title(title,fontsize=10)   # 显示图上的title信息
        ax.set_xticks([]);  # 不显示坐标轴
        ax.set_yticks([])        
        index += 1 
    plt.show()


plot_images_labels_prediction(mnist.test.images,
                              mnist.test.labels,
                              prediction_result,0,10)
找出预测错误.py
compare_lists = prediction_result==np.argmax(mnist.test.labels,1)
print(compare_lists)

err_lists = [i for i in range(len(compare_lists)) if compare_lists[i]==False]
print(err_lists, len(err_lists))

# 定义一个输出错误分类的函数
import numpy as np
def print_predict_errs(labels,       # 标签列表
                       prediction):  # 预测值列表 
    count = 0
    compare_lists = (prediction==np.argmax(labels,1))
    err_lists = [i for i in range(len(compare_lists)) if compare_lists[i]==False]
    for x in err_lists:
        print("index="+str(x)+
              " 标签值=",np.argmax(labels[x]),
              "预测值=",prediction[x])
        count = count + 1
    print("总计:"+str(count))
    
print_predict_errs(labels=mnist.test.labels,
            prediction=prediction_result)
            
plot_images_labels_prediction(mnist.test.images,
                              mnist.test.labels,
                              prediction_result,460,10)

Last updated