查看原文
其他

利用TensorFlow构建前馈神经网络

爬虫俱乐部 Stata and Python数据分析 2022-03-15

本文作者:陈   鼎,中南财经政法大学统计与数学学院

本文编辑:王子一

技术总编:张馨月

爬虫俱乐部云端课程

  爬虫俱乐部于2020年暑期在线上举办的Stata与Python编程技术训练营和Stata数据分析法律与制度专题训练营已经圆满结束啦~应广大学员需求,我们的课程现已在腾讯课堂全面上线,且继续提供答疑服务。现在关注公众号并在朋友圈转发推文《来腾讯课堂学Stata和Python啦》或《8月Stata数据分析法律与制度专场来啦!》,即可获得600元课程优惠券,集赞50个再领200元课程优惠券!(截图发至本公众号后台领取)原价2400元的课程,现在只要1600元!  
TensorFlow是由Google公司研发的基于数据流编程的符号数学系统,被广泛应用于机器学习、深度学习等领域,其前身是Google公司的神经网络算法库DistBelief。几年前强大的人工智能机器人AlphaGo便是基于TensorFlow框架实现的,其主要工作原理便是深度学习(Deep Learning)。在今天的推文里,我们将基于TensorFlow2.3,以MNIST数据集为例,利用一个简单的前馈神经网络解决多分类问题,并简述其原理与代码实现。
本文主要介绍的内容如下:
  • 为什么要引入深度学习

  • 深度学习算法原理

  • 基于TensorFlow的代码实现

(一) 为什么要引入深度学习

在推文《Logistic回归分析——以良/恶性乳腺癌肿数据集为例》中,小编简要阐述了Logistic回归的算法与原理。Logistic回归模型作为一个线性判别模型,在二分类问题上表现良好,但其局限性也是十分明显的:不适宜解决多分类问题,且在输入数据线性不可分的情况下发挥不佳。简而言之,单个Logistic回归模型一般太简单而无法解决许多现实问题,例如图像识别,自然语言处理等。
在传统机器学习表现乏力的情况下,深度学习(Deep Learning)应运而生。深度学习是机器学习领域中一个新的研究方向,它学习样本数据的内在规律,通过许多神经元与隐藏层挖掘并处理信息,最终让机器能像人一样具有分析学习的能力。一般的,我们用深度学习来指代我们训练一个神经网络模型的过程。
在大数据与人工智能时代,深度学习在处理大数据与训练模型的能力上远好于传统的机器学习,通过多层神经网络嵌套与大规模的迭代运算后得出的神经网络也表现出优异的模型判别能力。
图1 机器学习vs深度学习

(二) 从一个简单的例子说起

1.回归问题&分类问题

相信大家已经对线性回归模型(Liner regression model)有了很深的理解。它的作用是给定一系列特征值(x1,x2,x3,...,xn),输出一个预测值y。在将回归问题转换为分类问题的过程中,仅需要一个激活函数,将y值作为输入值输入,激活函数就能输出样本属于某一个类别的概率p,我们仅需要把样本判给概率值最大的类别即可:
其中f()为激活函数。
上述模型本质上就是一个最简单的神经网络。

2.MNIST数据集

MNIST数据集是美国国家标准与技术研究院收集整理的大型手写数字数据库,它包括了7万张0到9的手写数字图片,每个类别含有7000张风格各异的手写数字体,共有60,000个示例的训练集以及10,000个示例的测试集。下图展示了部分风格迥异的手写样本:

图2 手写数据集

数据集中的每个样本都是一张28*28像素的灰度手写数字图片,每一张手写数字图片都由一个行列均为28维的方阵表示,方阵中的每个元素代表该像素点的灰度值。在Python中,使用TensorFlow读取该数据集并通过神经网络模型进行训练,可以较好地解决计算机识别数字的问题。

图3 一个数据集的样本,数据格式为(28,28)
(像素点个数为28*28=784,且黑白图片只有一个颜色通道)
从数据集结构可以看出,我们的数据集格式是一个(28,28)的矩阵,一个矩阵代表了一张图片;在建立模型时,我们将矩阵展平成784维一列的向量(像素点个数为28*28=784),这使得线性模型能够很好地接收它们。我们输入一个矩阵(c,784),其中c为一次性输入的图片的个数,如果c=1,那么代表我们输入1张图片,这个图片含有784个特征(即784个像素点)。输入一张图片的数据后,最终输出的结果是一个10维的向量,向量中的每一个维度上的数值代表了属于某个类别的概率P,我们取其中最大的值对应的索引号(一个索引代表了一个类别),就可以得出算法预测出来的类别结果。

3.输入与输出

回到回归问题的讨论。对高维度的图像识别问题,用一个简单的线性模型往往无法解决。这时候可以构造一个类似"工厂车间"的模型:输入"原材料",通过种种工序,不断对"原材料"进行加工,虽然每一个"车间"的工序是十分简单的,但多个"车间"整合在一起,就可以完成一个非常复杂的任务。对应到多分类问题中,输入一个具有784个特征的图片,虽然在一个简单的线性分类器中无法很好地将各个类别分开,但是通过多个线性模型与激活函数的嵌套就能实现最终的分类。在神经网络模型中,通常采用relu函数作为非线性激活函数,其函数与图像如下:

图4 relu函数

假设有三个"车间"用来加工数据,利用数学语言可以如下描述:

以上述数学公式为例,我们就能画出一个很简单的图示来表示模型:

图5 神经网络模型

输入“原材料”,即每张图片对应的表示矩阵,并将其竖直堆叠起来作为一层,称为神经网络的输入层(input);在最尾层输出预测类别,称为神经网络的输出层(out)中间的车间我们称之为隐藏层(hidden)。如图所示,参数w1,b1,w2,b2,w3,b3将不同的车间很好地连接了起来。

在设定的三层神经网络模型中,我们可以人为地指定中间隐藏层的参数维数。这里指定c个样本在通过第一个车间之后的维度变为了[c,256]维,在通过第二个车间之后的维度变成了[c,128]维,在通过第三个车间之后变成了[c,10]维。用直观的矩阵运算来表示一下上述模型的计算过程:

4.优化参数

我们给上述过程(输入数据,输出概率,并最后进行判定类别)取了个名字,称为“前向传播”。在求解出分类结果之后,需要通过梯度下降法优化每个车间的参数,即优化w,b。在这个过程中,我们需要计算并输出评判分类好坏的指标,即损失函数。通过损失函数对每一个车间的w和b求导,得出梯度,并不断迭代更新参数,最终使得模型的判别精度最高,这个反向进行链式求导并不断优化模型的过程称为“反向传播”。幸运的是,TensorFlow2能够实现自动求导的过程,极大地方便了对参数的优化操作。

小编使用的损失函数为常用的MSE(均方误差),它代表着预测值与真实值的欧氏距离:

梯度下降法的迭代公式如下:

其中lr为学习率(Learning Rate)。本模型中需要优化的参数共有6个,分别为w1,b1,w2,b2,w3,b3。

其实到这里为止,小编都没有提到神经网络的概念,但是前馈神经网络模型的基本思想已经阐述完毕了。小编构造的这个像“车间”一样的模型就是一个简单的前馈神经网络(复杂的神经网络模型的隐藏层层数一般达到上百层)。
下面,我们继续以MNIST数据集为例展开实战操作。

(三)实战演练

1.读取数据

我们调用tensorflow.keras中的datasets方法即可快速加载所需数据。首次加载数据时电脑会自动从Google上下载数据,这可能会花费一定的时间。调用该函数后会返回训练集样本与测试集样本,我们用x_train,y_train,x_test,y_test分别接收训练集的特征,训练集特征对应的类别,测试集特征,测试集特征所对应的类别。
import tensorflow as tffrom tensorflow.keras import datasets(x_train,y_train),(x_test,y_test) = datasets.mnist.load_data()
在训练神经网络前,有如下几点需要注意:
首先,要将读入的数据集(numpy.ndarray格式)转换成张量(Tensor格式),这一方面便于实现自动求导,另一方面有利于TensorFlow内部进行数值计算。
其次,一般不是一次性读取所有训练集数据,而是将数据集分批次读取,在这里我们将一次性读取128个训练集数据(即c=128)。
此外,为了避免报错,一般将输入数据的格式改成tf.float32格式。
上述的要求可以通过Datasets.from_tensor_slices方法与map函数实现。下面是一个简单的数据预处理过程:
#加载数据集def prepocessing_data(x,y): ''' 数据预处理 :param x: 特征 :param y: 类型 :return: x,y ''' x = tf.cast(x,dtype=tf.float32)/255. #将特征数据转换成浮点型,并将输入数值等比例放缩到[0,1]区间上 y = tf.cast(y,dtype=tf.int32) #将分类结果转换成整型 return x,y
(x_train,y_train),(x_test,y_test) = datasets.mnist.load_data() #读取数据train_datasets = tf.data.Dataset.from_tensor_slices((x_train,y_train))train_datasets = train_datasets.map(prepocessing_data)train_datasets = train_datasets.shuffle(10000).batch(128) #将训练数据随机打乱,并每次读取128个数据,返回训练样本的迭代器test_datasets = tf.data.Dataset.from_tensor_slices((x_test, y_test))test_datasets = test_datasets.map(prepocessing_data)test_datasets = test_datasets.shuffle(10000).batch(128) #将测试集数据随机打乱,并每次读取128个数据,返回测试集样本的迭代器

2.参数随机初始化

我们在进行神经网络的前向传播时,需要事先指定w1,b1,w2,b2,w3,b3。这里我们利用截断的正态分布(防止梯度消失或梯度爆炸)随机生成w矩阵,并将b随机初始化成一个元素全为0的向量。为了避免梯度爆炸对模型的学习产生影响,我们将正态分布的标准差设置为0.1,下面是代码实现过程:
#随机初始化w1 = tf.Variable(tf.random.truncated_normal([784, 256], stddev=0.1))b1 = tf.Variable(tf.zeros([256]))w2 = tf.Variable(tf.random.truncated_normal([256, 128], stddev=0.1))b2 = tf.Variable(tf.zeros([128]))w3 = tf.Variable(tf.random.truncated_normal([128, 10], stddev=0.1))b3 = tf.Variable(tf.zeros([10]))lr = 1e-3 #指定学习率

3.模型训练

有了数据集与初始化的参数,我们就可以通过模型训练得到损失函数来评估模型的精度,并使用损失函数分别对所有的参数进行求导来计算梯度,进而完成对模型的优化。
for epoch in range(200): #实现200次迭代 for step,(x,y) in enumerate(train_datasets): #输入测试数据,进行模型训练 x = tf.reshape(x,[-1,28*28]) #将输入数据展平 with tf.GradientTape() as tape: #开始记录变量梯度 a1 = tf.nn.relu(x@w1+b1) #第一层 a2 = tf.nn.relu(a1@w2+b2) #第二层 out = a2@w3+b3 #我们要计算损失函数,因此不需要再套用relu函数,只需要得出预测值即可          #计算损失函数 y_one_hot = tf.one_hot(y, depth=10) #将真实结果转换成one_hot编码 loss = tf.square(out - y_one_hot) loss = tf.reduce_mean(loss) #计算MSE      #计算梯度 grads = tape.gradient(loss, [w1, b1, w2, b2, w3, b3]) w1.assign_sub(lr * grads[0]) b1.assign_sub(lr * grads[1]) w2.assign_sub(lr * grads[2]) b2.assign_sub(lr * grads[3]) w3.assign_sub(lr * grads[4]) b3.assign_sub(lr * grads[5]) if step%100 == 0: #每进行100次训练,返回当前模型的损失值大小 print(epoch,step,"loss:",float(loss))

4.模型预测

接下来, 利用测试集测试模型拟合的好坏。需要注意的是,我们在这个过程中需要用到前文最终训练完成的参数(w1,w2,w3,b1,b2,b3)。使用上述求解出来的这些参数,再进行一次前向传播操作,最后即可求解出预测结果的准确率。
#利用测试集预测准确率#注:如下代码应放入模型训练的第一层for循环中 total_correct_num,total_num = 0,0 for step,(x,y) in enumerate(test_datasets): x = tf.reshape(x,[-1,28*28]) a1 = tf.nn.relu(x@w1+b1) a2 = tf.nn.relu(a1@w2+b2) out = tf.nn.relu(a2@w3 + b3) # 获取最大的概率值          prob = tf.nn.softmax(out,axis=1)  #将输出结果转换成概率 pred = tf.argmax(prob,axis=1) #获取数值最大的元素所对应的索引号(即返回预测的类别) pred = tf.cast(pred,dtype=tf.float32) y = tf.cast(y,dtype=tf.float32) correct = tf.equal(pred,y) #判定预测值与真实值是否相等,返回一个Boolean类型的矩阵 correct = tf.cast(correct,dtype=tf.float32) #将Boolean类型的矩阵转换成元素为(0,1)的矩阵(True=1,False=0) correct_num = tf.reduce_sum(correct) #求和,得到预测正确的个数值 total_correct_num += int(correct_num) total_num += int(x.shape[0]) accuracy = total_correct_num/total_num #计算准确率 print("accuracy:",accuracy)

5.查看训练结果

从模型结果可以看到,在第一次训练中,我们的精度十分差(0.1706);但是在反复迭代学习200次后,我们预测的正确率达到了90%左右(0.88)。我们的损失函数在进行一次迭代后显著下降,最终在一个较小的值(0.03)上下波动,达到收敛状态。

图6 第一次训练模型返回的结果

图7 反复迭代200次后返回的结果

6.注意事项

神经网络有着出色的分类学习能力,但其带来的过拟合的风险也是巨大的。在多次训练学习过程中(尤其是全连接层)的神经网络往往会表现出过拟合的现象,这时候我们需要利用正则化或随机失活(drop out)方法来解决过拟合的问题。
文中完整的模型训练代码可以在后台回复“神经网络”来获取~




对我们的推文累计打赏超过1000元,我们即可给您开具发票,发票类别为“咨询费”。用心做事,不负您的支持!
往期推文推荐

推文合集(1)| Stata学习者必看的n篇推文!

Seminar | 诚信的价值

利用tushare获取股票数据及实现可视化

从Excel到Stata的“摆渡车”——import excel命令

光阴十载,见证了《经济研究》中的“高被引”

利用tushare获取股票数据

         在Python中实现Stata的stack功能

这些年,经管类C刊都在研究什么?

Seminar | 眼见为实吗?高管面部可信度、审计师任期与审计费用

Seminar | 恐怖袭击与CEO薪酬

代码补全,主题更换,Jupyter Notebook原来可以这样用?

【爬虫实战】“双十一”微博热搜实时跟进

Stata中的数值型变量分类神器--recode

fs命令——我们的小帮手

【邀请函】听说你还在为处理表格头大?

用stack取代excel的数据重整操作吧

  日期数据处理

         Seminar | 委托贷款:打开中国影子银行的黑匣子

         Seminar | 电话会议中的"non-answer"  

        Python 爬虫必杀技:XPath

         面对相对路径和绝对路径的分岔口,你选对了吗?

关于我们


微信公众号“Stata and Python数据分析”分享实用的stata、python等软件的数据处理知识,欢迎转载、打赏。我们是由李春涛教授领导下的研究生及本科生组成的大数据处理和分析团队。

此外,欢迎大家踊跃投稿,介绍一些关于stata和python的数据处理和分析技巧。
投稿邮箱:statatraining@163.com
投稿要求:
1)必须原创,禁止抄袭;
2)必须准确,详细,有例子,有截图;
注意事项:
1)所有投稿都会经过本公众号运营团队成员的审核,审核通过才可录用,一经录用,会在该推文里为作者署名,并有赏金分成。
2)邮件请注明投稿,邮件名称为“投稿+推文名称”。
3)应广大读者要求,现开通有偿问答服务,如果大家遇到有关数据处理、分析等问题,可以在公众号中提出,只需支付少量赏金,我们会在后期的推文里给予解答。

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存