欢迎你们来到此次实验,在此次实验中咱们将使用PaddlePaddle来实现一个多层神经网络,这个多层神经网络包含2个隐藏层,而且在隐藏层中使用到了Relu激活函数,在最后的输出层使用了Softmax激活函数。多层神经网络具备比逻辑回归更强的学习能力,而且更适合解决多分类问题,如今让咱们进入实验来看看多层神经网络与逻辑回归之间的差别性吧! html
你将学会python
实现一个具备两个隐藏层的神经网络,用于解决多分类问题算法
使用batch_norm作数据归一化数组
在隐藏层中使用Relu激活函数网络
在输出层使用Softmax激活函数app
使用classification_cost框架
使用Adam做为优化器less
如今让咱们进入实验吧!ide
首先,载入几个须要用到的库,它们分别是:函数
In[2]
import matplotlib import numpy as np import matplotlib.pyplot as plt import os import csv import paddle import paddle.fluid as fluid from __future__ import print_function try: from paddle.fluid.contrib.trainer import * from paddle.fluid.contrib.inferencer import * except ImportError: print( "In the fluid 1.0, the trainer and inferencer are moving to paddle.fluid.contrib", file=sys.stderr) from paddle.fluid.trainer import * from paddle.fluid.inferencer import * %matplotlib inline
问题描述:
红酒的品种多样,质量也有高低之分,质量的好坏决定了红酒的价格定位,假设你被聘为一家红酒供应商的红酒质量鉴定专家,红酒供应商给你提供了一些红酒的指标值和评分数据,但愿你能从这些数据中学习到红酒的质量鉴定方法。
你的目标:
构建一个多层神经网络来对红酒质量评分
数据集分析:
红酒数据集是采集于葡萄牙北部“Vinho Verde”葡萄酒的数据,它是研究Classification/Regression模型训练的经典数据集。这个数据集包含了红白两种葡萄酒的数据,在本实验中采用了红酒数据做为实验数据,红酒的数据包含 11 个特征值(指标)和一个 0-10 的评分值(因为隐私等问题,在特征值中不包含价格、品牌等因素,只涵盖了红酒的物理化学性质因素):
输入值:
输出值:
12 - quality (0-10的评分) 质量
文件路径
红酒数据被存储在当前文件夹下的 data 目录中,data 目录中共有两个数据文件,分别是:
咱们暂时先使用数据量较少的红酒数据来训练模型,固然你能够在完成实验后,使用白(葡萄)酒数据来从新训练或者验证你的模型。
In[3]
# 得到当前文件夹 cur_dir = os.path.dirname(os.path.realpath("__file__")) # 得到文件路径 filename = cur_dir + "/winequality-red.csv"
载入数据
首先,咱们使用csv.reader()来读取红酒数据,并存入data数组中,输出数据的属性和一组值。
In[4]
with open(filename) as f: reader = csv.reader(f) data = [] for row in reader: data.append([i for i in row[0].split(';')]) print( data[0],"\n" ) print( data[1] )
['fixed acidity', '"volatile acidity"', '"citric acid"', '"residual sugar"', '"chlorides"', '"free sulfur dioxide"', '"total sulfur dioxide"', '"density"', '"pH"', '"sulphates"', '"alcohol"', '"quality"'] ['7.4', '0.7', '0', '1.9', '0.076', '11', '34', '0.9978', '3.51', '0.56', '9.4', '5']
能够看到,数据中存储了关于红酒的11类特征值和1个标签值(分数)。
预处理
如今让咱们对数据进行一些预处理操做。观察上面输出的数据样例,咱们发现数据是以字符串的形式存储的,而且第一行数据(data[0])存储的是属性,而不是具体的数据,因此咱们须要去除第一行数据,而且将剩余的数据类型转换为np.float32的numpy数组。这一操做十分简单,只须要使用 np.array(array).astype(type) 便可完成。 例如,咱们有一个 list 为 arr = ['1', '2', '3']
,咱们使用 np.array(arr).astype(np.float32)
既能够将其转化为 numpy 类型的数据。
练习:
取出除第一行外的数据,并将数据类型转换为 np.float32 的 numpy 数组。
特别须要注意除了第一行外,其余的每一行都要转化,因此,能够考虑使用切片技术,将除了第一行意外的数据都放入
np.array()
切片技术:data[5:] 表示从下标为 5 的位置开始向后取得全部的行
In[49]
##练习 data=np.array(data[1:]).astype(np.float32) print( data[0] )
[ 7.4 0.7 0. 1.9 0.076 11. 34. 0.9978 3.51 0.56 9.4 5. ]
** 指望输出: **
[ 7.4000001 0.69999999 0. 1.89999998 0.076 11. 34. 0.99779999 3.50999999 0.56 9.39999962 5. ] |
想要训练一个模型,咱们首先须要将原始数据集切分为训练数据集(train set)和训练数据集(test set),定义一个ratioratioratio变量,它是一个介于[0,1][0,1][0,1]区间的标量,表明着训练数据占总数据的比重,例如设置ratio=0.8ratio=0.8ratio=0.8,它表示训练数据占总数据量的八成,若是data_num表明数据总数,那么ratio * data_num等于训练集数量。
在数据量不大的状况下,一般的切分方式 8:2 或者 7:3
** 练习: **
将数据划分为训练数据集和测试数据集(由于数据量较少,建议将ratio设置为0.8左右较为合理)
In[50]
# 练习 ratio = 0.8 data_num = len(data) slice = int(ratio * data_num) train_set = data[:slice] test_set = data[slice:] print( "train set shape:", train_set.shape ) print( "test set shape:", test_set.shape )
train set shape: (1273, 12) test set shape: (319, 12)
若是将ratio设置为0.8则
** 指望输出: **
** train set shape ** | (1278, 12) |
** test set shape ** | (320, 12) |
在逻辑回归的实验中咱们介绍了reader()的构造方法以及生成器的概念,在这里咱们一样构造一个read_data()函数来读取训练数据集train_set或者测试数据集test_set。它的具体实现是在read_data()函数内部构造一个reader(),使用yield关键字来让reader()成为一个Generator(生成器)。
注意 因为红酒的品质鉴定属于多分类问题(将结果划分为0-10的离散整数),因此咱们将标签值(评分)的数据类型转化为integer类型。
In[27]
def read_data(data): def reader(): for d in data: yield d[:-1], int(d[-1]) return reader test_arr = [ [0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 1], [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] ] reader = read_data(test_arr) for d in reader(): print( d )
([0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1], 1) ([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 0)
指望输出:
([0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1, 0.1], 1) ([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], 0) |
完成了数据的预处理工做并构造了 read_data() 来读取数据,接下来将进入模型的训练过程,使用 PaddlePaddle 来构造可训练的 Logistic 回归模型,关键步骤以下:
设置训练场所
配置网络结构和优化方法
训练准备
模型训练
模型检验
预测
绘制学习曲线
首先进行设置训练使用的设备。在复杂量较低的时候使用 CPU 就能够完成任务,可是对于大规模计算就须要使用 GPU 训练。目前 GPU 训练都是基于 CUDA 工具之上的。
In[28]
# 设置训练场所 use_cuda = False place = fluid.CUDAPlace(1) if use_cuda else fluid.CPUPlace()
这一阶段,咱们关注的是网络拓扑结构的配置和优化方法的配置
** 网络结构: **
了解一下咱们即将要配置的网络结构,如图所示,它的基本结构是输入层、两个隐藏层和输出层,两个隐藏层使用ReLU激活函数,输出层使用Softmax激活函数(多分类),除此以外,在输入层以后添加一层Batch Normalization对数据进行归一化处理。
背景知识
下面咱们简单介绍一下网络结构中使用到的 ReLU 以及 Softmax 激活函数:
ReLU(Rectified linear unit)
ReLU激活函数一般比sigmoid和tanh激活函数的表现更好,其中一个缘由是 sigmoid 和 tanh 在两端的饱和阶段梯度接近 0,容易形成梯度消失问题(Vanishing Gradient Problem),尤为是在深度网络中更加明显,而 ReLU 在非负区间的梯度为常数,所以不存在梯度消失问题,使得模型的收敛速度维持在一个稳定状态。咱们在两个隐藏层上使用 ReLU 做为激活函数。
Softmax
Softmax 是神经网络中另外一种激活函数,计算输出层的值。主要用于神经网络最后一层,做为输出层进行多分类,是逻辑回归二分类的推广。
Sigmoid 将结果值映射到 [0,1][0,1][0,1] 区间,用来作二分类。 而 Softmax 函数形式以下,把一个 k 维的向量 (y_1,y_2,y_3,y_4 … y_k.) 映射成 (a_1,a_2,a_3 … a_k.),其中 aia_iai 介于区间[0,1][0,1][0,1],根据 aia_iai 的大小来进行多分类的任务,如取权重最大的一维。
配置网络结构
如今咱们已经了解了网络结构,开始着手配置吧!
输入层:
咱们能够定义 x = fluid.layers.data(name='x', shape=[11], dtype='float32') 来表示生成一个数据输入层,名称为“x”,数据类型为11维向量;
隐藏层:
咱们定义两个隐藏层h1和h2,以h1为例,定义h1 = fluid.layers.fc(input=x, size=32, act='relu'),表示生成一个全链接层类型的隐藏层,输入数据为norm1,神经元个数为32,激活函数为Relu();
输出层:
咱们能够定义predict = fluid.layers.fc(input=h2, size=10, act='softmax')表示生成一个全链接层,输入数据为h2,输出结果共有10个,分别表示十个不一样的分类,激活函数为Softmax();
标签层
咱们能够定义label = fluid.layers.data(name='label', shape=[1], dtype='int64')表示生成一个数据层,名称为“label”,数据类型为包含0-9的整型。
定义损失函数
在配置网络结构以后,咱们须要定义一个损失函数来计算梯度并优化参数。fluid 提供不少的损失函数, 在这里咱们可使用 fluid 提供的用于多分类的损失函数。
fluid 在 layers 里面提供了 cross_entropy 函数用来作分类问题的损失函数。这个函数有两个参数分别是 input 和 lable,分别对应预测值和标签值。其形式以下: cost = paddle.layer.cross_entropy(input=y_predict, label=y_label)。
当输入一个batch的数据时,损失算子的输出有多个值,每一个值对应一条样本的损失,因此一般会在损失算子后面使用mean等算子,来对损失作归约。 损失规约形式以下:avg_cost = fluid.layers.mean(cost)。 注意,虽然在神经网络知识中没有这个步骤,可是在 fluid 代码中老是建议加入这个步骤。
练习
特别的
因为本例使用 trainer 的写法,因此须要将整个网络拓扑结构包装到函数中方便后面使用。
In[61]
# 封装 train_func def train_func(): # 输入层 x = fluid.layers.data(name='x', shape=[11], dtype='float32') #隐藏层 ### START CODE HERE ### (≈ 2 lines of code) h1 = fluid.layers.fc(input=x, size=32, act='relu') h2 = fluid.layers.fc(input=h1, size=16, act='relu') ### END CODE HERE ### #预测层 ### START CODE HERE ### (≈ 2 lines of code) y_predict = fluid.layers.fc(input=h2, size=10, act='softmax') ### END CODE HERE ### #标签 y_label = fluid.layers.data(name='label', shape=[1], dtype='int64') # 损失函数 ### START CODE HERE ### (≈ 2 lines of code) cost = fluid.layers.cross_entropy(input=y_predict, label=y_label) avg_cost = fluid.layers.mean(cost) #acc = fluid.layers.accuracy(input=y_predict, label=y_label) ### END CODE HERE ### return avg_cost
** optimizer **
参数建立完成后,咱们须要定义一个优化器optimizer,在这里咱们尝试使用Adam来做为优化器,它的计算公式以下:
\begin{split}m(w, t) & = \beta_1 m(w, t-1) + (1 - \beta_1) \nabla Q_i(w) \\ v(w, t) & = \beta_2 v(w, t-1) + (1 - \beta_2)(\nabla Q_i(w)) ^2 \\ w & = w - \frac{\eta m(w, t)}{\sqrt{v(w,t) + \epsilon}}\end{split}
Adam是一种经常使用的、效果良好的自适应学习率调整优化算法,一般只须要将参数设置为beta1=0.9,beta2=0.999,epsilon=1e-08,不须要做修改便可让模型产生好的收敛效果。在 fluid 中可使用接口 fluid.optimizer.Adam() 来建立 Adam 优化器。这个函数接受 4个参数分别是:learning_rate, beta1, beta2 和 epsilon。
建立优化器仅仅是向 fluid 后台添加了优化器并无显示的使用。若是想让优化器真正的发挥做用,须要使用优化器,使用的方法十分简单,调用优化器的 minimize 函数便可,这个函数接受的参数就是将要被优化的损失函数。其形式以下:avg_cost
** 练习: **
** **特别的** **
因为本例使用 trainer 的写法,因此须要将优化器包装到函数中方便后面使用。
In[52]
#封装 优化器 def optimizer_func(): #建立optimizer ### START CODE HERE ### (≈ 1 lines of code) optimizer=fluid.optimizer.Adam( learning_rate=0.2, beta1=0.9, beta2=0.999, epsilon=1e-08 ) #opts = optimizer.minimize(avg_cost) ### END CODE HERE ### return optimizer
这个阶段咱们关注的是小的相关内容的配置。
** 定义映射 **
输入网络的数据要与网络自己应该接受的数据相匹配。在 fluid 中使用 feed_order 的概念来保证输入的数据与网络接受的数据的顺序是一致的。本示例中使用 feed_order = ['x', 'label'] 来告知网络,输入的数据是分为两部分,第一部分是 x 值,第二部分是 label 值。
In[53]
feed_order = ['x', 'label']
** 定义文件路径 **
在 fluid 中,默认模型的相关数据是须要保存在硬盘上的。也就是说在训练阶段会将训练好的模型保存在硬盘上,在将预测阶段能够直接 load 磁盘上的模型数据,进而作出预测。
In[54]
params_dirname = "./DNN_model"
** 定义事件处理函数 **
在 fluid 中,若是是用 trainer 的方式来训练的话,那么,在训练的时候容许开发者本身定义事件回调函数。目前接受的事件有 BeginEpochEvent、EndEpochEvent、BeginStepEvent、EndStepEvent。
In[55]
# Plot data from paddle.v2.plot import Ploter train_title = "Train cost" test_title = "Test cost" plot_cost = Ploter(train_title, test_title) step = 0 # 事件处理 def event_handler_plot(event): global step if isinstance(event, EndStepEvent): if event.step % 2 == 0: # 若干个batch,记录cost if event.metrics[0] < 10: plot_cost.append(train_title, step, event.metrics[0]) plot_cost.plot() if event.step % 20 == 0: # 若干个batch,记录cost test_metrics = trainer.test( reader=test_reader, feed_order=feed_order) if test_metrics[0] < 10: plot_cost.append(test_title, step, test_metrics[0]) plot_cost.plot() # if test_metrics[0] < 1.0: # # 若是准确率达到阈值,则中止训练 # print('loss is less than 10.0, stop') # trainer.stop() # 将参数存储,用于预测使用 if params_dirname is not None: trainer.save_params(params_dirname) step += 1
** 定义执行器 **
为了可以运行开发者定义的网络拓扑结构和优化器,须要定义执行器。由执行器来真正的执行参数的初始化和网络的训练过程。
In[56]
# 建立执行器,palce在程序初始化时设定 exe = fluid.Executor(place) # 初始化执行器 exe.run( fluid.default_startup_program() )
[]
** 定义reader **
网络接受的数据其实是一个又一个的 mini-batch 。 paddle 框架为开发者准备好了 paddle.batch 函数来提供一个又一个 mini-batch。在实际输入数据的时候,咱们但愿的是数据顺序不要影响网络是训练,paddle 框架也准备了 paddle.reader.shuffle 函数来打乱输入的顺序。
** 练习: ** 设置 BATCH_SIZE 为 10
BATCH_SIZE 的大小决定了 每一个 mini-batch 中灌入的数据的数量
In[57]
# 设置 BATCH_SIZE 的大小 ### START CODE HERE ### (≈ 1 lines of code) BATCH_SIZE = 10 ### END CODE HERE ### # 设置训练reader train_reader = paddle.batch( paddle.reader.shuffle( read_data(train_set), buf_size=500), batch_size=BATCH_SIZE) #设置测试 reader test_reader = paddle.batch( paddle.reader.shuffle( read_data(test_set), buf_size=500), batch_size=BATCH_SIZE)
** 定义trainer **
trainer 负责收集训练须要的相关信息。定义 trainer 时须要提供 3个重要信息:
In[62]
#建立训练器 from paddle.fluid.contrib.trainer import * from paddle.fluid.contrib.inferencer import * trainer = Trainer( train_func= train_func, place= place, optimizer_func= optimizer_func)
** 开始训练 **
在作好了全部的准备工做以后,就开始开始训练了。因为本例使用的是 trainer 的方法,因此能够直接调用 trainer 的 train 方法来执行训练。train 方法主要须要设置3个参数: reader、num_epochs 和 feeder_order。 其中,reader 表示可以持续提供 mini-batch 的数据源。num_epochs 表示全部的数据将要训练多少轮次(就是一个数字)。 feeder_order 表示数据的顺序。
咱们注意到,reader 和 feeder_order 在前面的准备过程当中已经准备好了。 除了这三个参数外,train 还接受一个 event_handler 参数。这个参数容许开发者本身定义回调函数,用以在训练过程当中打印训练相关的信息,甚至在合适的时候中止训练。 函数的形式以下:
trainer.train( reader= , num_epochs= , event_handler= , feed_order= )
** 练习: **
In[63]
from paddle.fluid.contrib.trainer import * from paddle.fluid.contrib.inferencer import * ### START CODE HERE ### (≈ 1 lines of code) trainer.train( reader=train_reader, num_epochs=10, event_handler=event_handler_plot, feed_order=feed_order) ### END CODE HERE ###
<Figure size 432x288 with 0 Axes>
模型训练完成后,接下来使用训练好的模型在测试数据集上看看效果。 本阶段主要有两个部分:预测和预测效果评估
** 定义预测网络拓扑结构 **
fluid 设计者认为训练的网络和预测的网络并不必定是彻底相同的。因此在预测阶段,开发者须要本身定义测试的网络,可是这个网络拓扑结构和训练网络的拓扑结构必须是兼容的,不然从硬盘中 load 回来的数据是没法应用到预测网络中。
In[64]
# 定义数据的 feeder 为预测使用 feeder = None #定义预测网络拓扑结构 def inference_func(): global feeder x = fluid.layers.data(name='x', shape=[11], dtype='float32') h1 = fluid.layers.fc(input=x, size=32, act='relu') h2 = fluid.layers.fc(input=h1, size=16, act='relu') predict = fluid.layers.fc(input=h2, size=10, act='softmax') label = fluid.layers.data(name='label', shape=[1], dtype='int64') feeder = fluid.DataFeeder(place=place, feed_list=['x', 'label']) return predict
** 定义运算设备 **
通常是 CPU 或者 GPU 设备
In[65]
# 设置训练场所 use_cuda = False place = fluid.CUDAPlace(1) if use_cuda else fluid.CPUPlace()
** 定义预测器 **
预测器的定义须要3个重要信息:
具体代码形式以下:
inferencer = Inferencer( infer_func= , param_path= , place= )
** 练习: ** 本身定义 预测器
In[79]
# 定义预测器 ### START CODE HERE ### (≈ 3 lines of code) inferencer = Inferencer( infer_func =inference_func, param_path = params_dirname, place=place) ### END CODE HERE ###
** 执行预测 **
有了 预测器以后 仅仅是有了预测的能力尚未真的去预测结果。真正的预测须要使用 inferencer.infer() 函数。这个函数的参数就将要被预测的数据。 这个函数接受的参数有两种写法:
In[80]
# 从新定义 test reader BATCH_SIZE = 4 #设置测试 reader test_reader = paddle.batch( paddle.reader.shuffle( read_data(test_set), buf_size=200), batch_size=BATCH_SIZE)
In[81]
for mini_batch in test_reader(): #真的执行预测 mini_batch_data = feeder.feed(mini_batch) mini_batch_result = inferencer.infer(mini_batch_data) # 打印预测结果 mini_batch_result = np.argsort(mini_batch_result) #找出可能性最大的列标,升序排列 mini_batch_result = mini_batch_result[0][:, -1] #把这些列标拿出来 print('预测结果:%s'%mini_batch_result) # 打印真实结果 label = np.array(mini_batch_data['label']) # 转化为 label label = label.flatten() #转化为一个 array print('真实结果:%s'%label) break
预测结果:[6 5 5 6] 真实结果:[5 5 6 5]
下面定义评估效果的函数
In[82]
def right_ratio(right_counter, total): ratio = float(right_counter)/total return ratio
In[83]
# 评估函数 data_set 是一个reader def evl(data_set): total = 0 #操做的元素的总数 right_counter = 0 #正确的元素 pass_num = 0 for mini_batch in data_set(): pass_num += 1 #预测 mini_batch_data = feeder.feed(mini_batch) mini_batch_result = inferencer.infer(mini_batch_data) #预测的结果 mini_batch_result = np.argsort(mini_batch_result) #找出可能性最大的列标,升序排列 mini_batch_result = mini_batch_result[0][:, -1] #把这些列标拿出来 # print('预测结果:%s'%mini_batch_result) label = np.array(mini_batch_data['label']) # 转化为 label label = label.flatten() #转化为一个 array # print('真实结果:%s'%label) #计数 label_len = len(label) total += label_len for i in xrange(label_len): if mini_batch_result[i] == label[i]: right_counter += 1 ratio = right_ratio(right_counter, total) return ratio
In[84]
ratio = evl(train_reader) print('训练数据的正确率 %0.2f%%'%(ratio*100)) ratio = evl(test_reader) print('预测数据的正确率 %0.2f%%'%(ratio*100))
训练数据的正确率 44.23% 预测数据的正确率 41.69%
经过这个练习咱们应该记住:
ReLU激活函数比tanh和sigmoid更适合深层神经网络,由于它不存在梯度消失问题
使用Batch Normalization可以加速模型训练
利用Softmax能够解决多分类问题
Adam是一种经常使用的、效果良好的自适应学习率调整优化算法,一般使用它可以获得不错的学习效果。
至此,咱们完成了比逻辑回归模型稍复杂的多层神经网络模型的配置和训练,不难发现,在PaddlePaddle中,只须要经过简单地叠加或删除数据层、链接层等,就能够轻易地改变模型结构,自由度很高,你们能够尝试使用更多层数的神经网络或者改变每一层的神经元个数来修改模型,在调试中加深对深度学习的理解和PaddlePaddle框架的熟悉度。
>> 访问 PaddlePaddle 官网,了解更多相关内容。