基于PaddlePaddle框架的BP神经网络的鲍鱼年龄的预测

# 经典的线性回归模型主要用来预测一些存在着线性关系的数据集。回归模型可以理解为:存在一个点集,用一条曲线去拟合它分布的过程。如果拟合曲线是一条直线,则称为线性回归。如果是一条二次曲线,则被称为二次回归。线性回归是回归模型中最简单的一种。 本教程使用PaddlePaddle建立起一个鲍鱼年龄预测模型。
# 
# 在线性回归中:
# 
# (1)假设函数是指,用数学的方法描述自变量和因变量之间的关系,它们之间可以是一个线性函数或非线性函数。 在本次线性回顾模型中,我们的假设函数为 Y’= wX+b ,其中,Y’表示模型的预测结果(预测的鲍鱼年龄),用来和真实的Y区分。模型要学习的参数即:w,b。
# 
# (2)损失函数是指,用数学的方法衡量假设函数预测结果与真实值之间的误差。这个差距越小预测越准确,而算法的任务就是使这个差距越来越小。 建立模型后,我们需要给模型一个优化目标,使得学到的参数能够让预测值Y’尽可能地接近真实值Y。这个实值通常用来反映模型误差的大小。不同问题场景下采用不同的损失函数。 对于线性模型来讲,最常用的损失函数就是均方误差(Mean Squared Error, MSE)。
# 
# (3)优化算法:神经网络的训练就是调整权重(参数)使得损失函数值尽可能得小,在训练过程中,将损失函数值逐渐收敛,得到一组使得神经网络拟合真实模型的权重(参数)。所以,优化算法的最终目标是找到损失函数的最小值。而这个寻找过程就是不断地微调变量w和b的值,一步一步地试出这个最小值。 常见的优化算法有随机梯度下降法(SGD)、Adam算法等等
# 

# # **Step1.数据准备**
# 
# **认识数据:**
# 
# 
# 数据集共4177行,每行9列
# 
# 前8列用来描述鲍鱼的各种信息,分别是性别、长度、直径、高度、总重量、皮重、内脏重量、克重,最后一列为该鲍鱼的年龄
# 
# 
# **数据准备:**
# 
# 1.从文件中加载数据
# 
# 2.对数据进行归一化
# 
# 3.构造数据集提供器




# 读取文件
import numpy as np
import os
import matplotlib.pyplot as plt

data_X = []
data_Y = []
# 将性别(M:雄性,F:雌性,I:未成年)映射成数字
sex_map = { 'I': 0, 'M': 1, 'F': 2 }
with open ('/home/aistudio/data/data15920/baoyu.txt') as f:
    for line in f.readlines():
        line = line.split(',')
        line[0] = sex_map[line[0]]
        data_X.append(line[:-1])
        data_Y.append(line[-1:])
# 转换为nparray
data_X = np.array(data_X, dtype='float32')
data_Y = np.array(data_Y, dtype='float32')
# 检查大小
print('data shape', data_X.shape, data_Y.shape)
print('data_x shape[1]', data_X.shape[1])
# 归一化
for i in range(data_X.shape[1]):
    _min = np.min(data_X[:,i])                            #每一列的最小值
    _max = np.max(data_X[:,i])                            #每一列的最大值
    data_X[:, i] = (data_X[:, i] - _min) / (_max - _min)  #归一化到0-1之间
    
import paddle
import paddle.fluid as fluid
from sklearn.model_selection import train_test_split
# 分割训练集、测试集
X_train, X_test, y_train, y_test = train_test_split(data_X,          #被划分的样本特征集
                                                    data_Y,          #被划分的样本标签
                                                    test_size=0.2,   #测试集占比
                                                    random_state=0)  #随机数种子,在需要重复试验的时候,保证得到一组一样的随机数
# 自定义reader,每次返回一个样本数据
def reader_creator(_X, _Y):
    def reader():
        for _x, _y in zip(_X, _Y):
            yield [_x, _y]          #返回Iterable对象
    return reader
    
# 一个minibatch中有16个数据
BATCH_SIZE = 20

#定义了用于训练与验证的数据提供器。提供器每次读入一个大小为BATCH_SIZE的数据批次。
#BATCH_SIZE个数据项组成一个mini batch。
train_reader = paddle.batch(reader_creator(X_train, y_train),
                            batch_size=BATCH_SIZE)
test_reader = paddle.batch(reader_creator(X_test, y_test), 
                           batch_size=BATCH_SIZE)


# # **Step2.网络配置**
# 
# **(1)网络搭建**:对于线性回归来讲,它就是一个从输入到输出的简单的全连接层。
# 
# 对于鲍鱼年龄预测数据集,假设鲍鱼属性和年龄之间的关系可以被属性间的线性组合描述。

#定义输入的形状和数据类型,张量变量x,表示8维的特征值
x = fluid.layers.data(name='x', shape=[8], dtype='float32')

#定义输出的形状和数据类型,张量y,表示目标值
y = fluid.layers.data(name='y', shape=[1], dtype='float32')


#定义一个简单的线性网络,连接输入和输出的全连接层
#input:输入;
#size:该层输出单元的数目
#act:激活函数
fc2 = fluid.layers.fc(input=x, size=200, bias_attr=True)
drop = fluid.layers.dropout(x=fc2, dropout_prob=0.5)
fc3 = fluid.layers.fc(input=drop, size=800, bias_attr=True, act='relu')
drop = fluid.layers.dropout(x=fc3, dropout_prob=0.5)
fc4 = fluid.layers.fc(input=drop, size=800, bias_attr=True, act='relu')
drop = fluid.layers.dropout(x=fc4, dropout_prob=0.5)
fc5 = fluid.layers.fc(input=drop, size=100, bias_attr=True, act='relu')
y_predict=fluid.layers.fc(input=fc5, size=1, bias_attr=True, act=None)

# **(2)定义损失函数**
# 
# 此处使用均方差损失函数。
# 
# square_error_cost(input,lable):接受输入预测值和目标值,并返回方差估计,即为(y-y_predict)的平方
cost = fluid.layers.square_error_cost(input=y_predict, label=y) #求方差
avg_cost = fluid.layers.mean(cost)                              #对方差求平均值,得到平均损失
#克隆main_program得到test_program,使用参数for_test来区分该程序是用来训练还是用来测试,该api请在optimization之前使用.
test_program = fluid.default_main_program().clone(for_test=True)
# **(3)定义优化函数**

# 此处使用的是随机梯度下降。
optimizer = fluid.optimizer.AdamOptimizer(learning_rate=0.0001)
opts = optimizer.minimize(avg_cost)

# 完成上述定义,也就是完成了 fluid.default_main_program 的构建过程,fluid.default_main_program 中承载着神经网络模型,前向反向计算,以及优化算法对网络中可学习参数的更新。

# **fluid的设计思想:**
# 用户编写一段python程序,通过调用 Fluid 提供的算子,向一段 Program 中添加**变量**以及**对变量的操作**(Operators )。
# 
# 用户**只需要描述核心的前向计算**,不需要关心反向计算、分布式下以及异构设备下如何计算。
# 
# Fluid 的 Program 的形式上类似一段 C++ 或 Java 程序。
# 
# 包含:
# 
# 1.本地变量的定义
# 
# 2.一系列的operator
# 
# 
# **用户完成网络定义后,一段 Fluid 程序中通常存在 2 段 Program:**
# 
# 1.**fluid.default_startup_program**:定义了创建模型参数,输入输出,以及模型中可学习参数的初始化等各种操作
# 
# default_startup_program 可以由框架自动生成,使用时无需显示地创建
# 
# 
# 2.**fluid.default_main_program** :定义了神经网络模型,前向反向计算,以及优化算法对网络中可学习参数的更新
# 
# 使用Fluid的核心就是构建起 default_main_program

# # **Step3.网络训练 & Step4.网络评估**

# **(1)创建Executor**
# 
# 首先定义运算场所 fluid.CPUPlace()和 fluid.CUDAPlace(0)分别表示运算场所为CPU和GPU
# 
# Executor:接收传入的program,通过run()方法运行program。

#use_cuda为False,表示运算场所为CPU;use_cuda为True,表示运算场所为GPU 
use_cuda = False  
place = fluid.CUDAPlace(0) if use_cuda else fluid.CPUPlace()

#创建一个Executor实例exe
exe = fluid.Executor(place)  

#正式进行网络训练前,需先执行参数初始化
exe.run(fluid.default_startup_program())

# **(2)定义输入数据维度**
# 
# DataFeeder负责将数据提供器(train_reader,test_reader)返回的数据转成一种特殊的数据结构,使其可以输入到Executor中。
# 
# feed_list设置向模型输入的向变量表或者变量表名

# In[ ]:


# 告知网络传入的数据分为两部分,第一部分是x值,第二部分是y值
feeder = fluid.DataFeeder(place=place, feed_list=[x, y])


# **(3)定义绘制训练过程的损失值变化趋势的方法draw_train_process**

# In[ ]:



iter=0;
iters=[]
train_costs=[]

def draw_train_process(iters,train_costs):
    title="training cost"
    plt.title(title, fontsize=24)
    plt.xlabel("iter", fontsize=14)
    plt.ylabel("cost", fontsize=14)
    plt.plot(iters, train_costs,color='red',label='training cost') 
    plt.grid()
    plt.show()


# **(4)训练并保存模型**
# 
# Executor接收传入的program,并根据feed map(输入映射表)和fetch_list(结果获取表) 向program中添加feed operators(数据输入算子)和fetch operators(结果获取算子)。 feed map为该program提供输入数据。fetch_list提供program训练结束后用户预期的变量。
# 
# 使用feed方式送入训练数据,先将reader数据转换为PaddlePaddle可识别的Tensor数据,传入执行器进行训练。
# 
# 注:enumerate() 函数用于将一个可遍历的数据对象(如列表、元组或字符串)组合为一个索引序列,同时列出数据和数据下标

# In[ ]:


#训练轮数:所有训练数据的一个前向传递和一个后向传递为一轮
EPOCH_NUM=100
#模型保存路径
model_save_dir = "/home/aistudio/work/fit_a_line.inference.model"

for pass_id in range(EPOCH_NUM):                                  #训练EPOCH_NUM轮
    # 开始训练
    for batch_id, data in enumerate(train_reader()):              #遍历train_reader迭代器
        train_cost = exe.run(program=fluid.default_main_program(),#运行主程序
                             feed=feeder.feed(data),              #喂入一个batch的训练数据
                             fetch_list=[avg_cost])               #fetch 误差、准确率,fetch_list中设置要获取的值
        if batch_id % 100 == 0:
            print("Pass:%d, Cost:%0.5f" % (pass_id, train_cost[0])) #每训练100次,打印一次平均损失值
        
        iter=iter+1                 
        iters.append(iter)                  
        train_costs.append(train_cost[0])
       
   
    # 开始验证,并输出验证集经过一轮的平均损失
    test_costs = []
    for batch_id, data in enumerate(test_reader()):               #遍历test_reader迭代器
        test_cost= exe.run(program=test_program,                  #运行测试program
                            feed=feeder.feed(data),               #喂入一个batch的测试数据
                            fetch_list=[avg_cost])                #fetch均方误差,fetch_list中设置要获取的值
                            
        test_costs.append(test_cost[0])  
    test_cost = (sum(test_costs) / len(test_costs))           #每轮的平均误差
    print('Test:%d, Cost:%0.5f' % (pass_id, test_cost))       #打印平均损失
    
    #保存模型
    # 如果保存路径不存在就创建
if not os.path.exists(model_save_dir):
    os.makedirs(model_save_dir)
print ('save models to %s' % (model_save_dir))

#保存训练参数到指定路径中,构建一个专门用预测的program
fluid.io.save_inference_model(model_save_dir,       #保存预测model的路径
                                  ['x'],            #预测需要 feed 的数据
                                  [y_predict],      #保存预测结果的变量
                                  exe)              #exe 保存 inference model
#绘制训练过程,损失随迭代次数的变化                                
draw_train_process(iters,train_costs)


# # **Step5.模型预测**
# **(1)创建预测用的Executor**

# In[ ]:


infer_exe = fluid.Executor(place)    #创建推测用的executor
inference_scope = fluid.core.Scope() #Scope指定作用域

# **(2)可视化真实值与预测值方法定义**
def draw_infer_result(groud_truths,infer_results):
    title='abalone'
    plt.title(title, fontsize=24)
    x = np.arange(1,20) 
    y = x
    plt.plot(x, y)
    plt.xlabel('ground truth', fontsize=14)
    plt.ylabel('infer result', fontsize=14)
    plt.scatter(groud_truths, infer_results,color='green',label='training cost') 
    plt.grid()
    plt.show()

# **(3)开始预测**
# 
# 通过fluid.io.load_inference_model,预测器会从params_dirname中读取已经训练好的模型,来对从未遇见过的数据进行预测。

with fluid.scope_guard(inference_scope):#修改全局/默认作用域(scope), 运行时中的所有变量都将分配给新的scope。
    #从指定目录中加载预测模型
    [inference_program,                             #预测用的program
     feed_target_names,                             #一个str列表,它包含需要在预测 Program 中提供数据的变量的名称。
     fetch_targets] = fluid.io.load_inference_model(#fetch_targets: 从中可以得到预测结果。
                                    model_save_dir, #model_save_dir:模型保存路径 
                                    infer_exe)      #infer_exe: 预测用executor
                                    
    #获取预测数据
    INFER_BATCH_SIZE=10
    infer_reader = paddle.batch(reader_creator(X_test, y_test), batch_size=INFER_BATCH_SIZE)
    #从infer_reader中分割x
    infer_data = next(infer_reader())
    infer_x = np.array([data[0] for data in infer_data]).astype("float32")
    infer_y= np.array([data[1] for data in infer_data]).astype("float32")
    
    
    results = infer_exe.run(inference_program,                              #预测模型
                            feed={feed_target_names[0]: np.array(infer_x)},  #喂入要预测的x值
                            fetch_list=fetch_targets)                       #得到推测结果
    infer_results=[]    #预测值
    groud_truths=[]     #真实值
    sum_cost=0
    for i in range(INFER_BATCH_SIZE):
        infer_result=results[0][i]       #经过预测后的值
        ground_truth=infer_y[i]          #真实值
        infer_results.append(infer_result)
        groud_truths.append(ground_truth)
        print("No.%d: infer result is %.2f,ground truth is %.2f" % (i, infer_result,ground_truth))
        cost=np.power(infer_result-ground_truth,2)
        sum_cost+=cost
    print("平均误差为:",sum_cost/INFER_BATCH_SIZE)
       
    draw_infer_result(groud_truths,infer_results)

项目源代码获取请点击这里


版权声明:本文为qq_44479403原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。