小言_互联网的博客

【实战项目】基于BP神经网络的温度预测(附源码)

529人阅读  评论(0)

1. BP神经网络是什么

BP(Back-propagation,反向传播)神经网络是最传统的神经网络。也就是使用了Back-propagation算法的神经网络。请注意他不是时下流行的那一套深度学习。要训练深度学习level的网络你是不可以使用这种算法的。原因我们后面解释。而其实机器学习的bottleneck就是成功的突破了非常深的神经网络无法用BP算法来训练的问题。

那么反向传播的东西是什么呢?答案是:误差。就是在模拟过程中(这是一个循环,我们在训练神经网络的时候是要不断的去重复这个过程的)收集系统所产生的**误差,**并且返回这些误差到输出值,之后用这些误差来调整神经元的权重,这样生成一个可以模拟出原始问题的人工神经网络系统。
今天我们使用经典的BP神经网络进行回归训练,目的是为了预测出上海市多个维度因素影响下的地表气温预测

先上图:

2. 生物神经的运作原理

我在这里不讨论那些复杂的生物学和神经科学。其实很简单的一件事,我们人类之所以可以让飞机上天,是因为”学习“了从理论力学,信号系统再到导航控制的一系列知识。作家之所以可以写出伟大的著作,那是因为他学习了语言,比如英语汉语德语法语,也学习过李白杜甫,莎士比亚。其实狗狗之所以能知道要听见主人喊一个苹果加一个苹果要喊两声(之后可以得到一块炖牛肉),也是学习的结果。

那么学习的过程,核心问题是什么?答案是误差。比如你是一个备考高三数学的学生。你本身就会1+1=2,你把这个训练一万遍有用么?没用。你要做的就是找到自己不行的地方,找到自己的漏洞,有针对性的去突破和训练。这就是用误差来学习。

人类的过程也是一样的。学习,有误差然后有反馈,我们通过这些误差反馈来学习

这就是BP的目的

3. 神经网络的基础架构

凡是对这个事情有最基础了解和认识的朋友都知道神经网络其实就是几层神经元,每层神经元里有几个神经元点。不同layer之间的神经元相互连接。其实就是如此:

我不管生物或者神经学里在讲什么。在这,每一个神经元就是三件事:输入,判断和输出。输入层的神经元(就是那个圆形的圈,代表一个神经元或者一个神经细胞)是读入你输入的数据的。只要你有数据,这个玩意就能跑。这就好比你只要有汽油,汽车就能开是一个道理。中间则是”隐含层。“你可以控制这个隐含层的层数,以及每一层里有多少个神经元或者神经细胞。当然在实际操作里为了方便我们一般都直接认为你不管用几层,每层的神经元或者神经细胞数目都是一样的。因为这样的话写代码会比较方便。

每一层神经元内部都不互相连接。而相邻层的神经元点之间则互相连接。在我们这个问题里,两个相邻层,所有的神经元都是相互连接的。你说可不可以通过让这些神经元之间不互相连接来起到效果?的确,历史上的连接学派就是这样想的。但实际上你可以都给他联上。其实道理非常简单。如果我们真的要取消某两个点之间的连接的话,那么很显然只要设定这条连线上的数值为零即可。这好比一个网络电路,阻值本身就是无限大的。

除了神经元,你还需要关注一个东西,那便是神经线。在所有的神经线(两个神经元一连就是)上你可以赋予不同的权重。而这个则是训练的核心要务,说白了你就是那一套最接近完美答案的权重就可以了。

4. 运算过程:矩阵乘法

我们要把输入在这个数学结构上传递到输出,要怎么办呢?答案是,使用矩阵乘法。

其实这样的思路一开始是非常简单的。 我们以这个例子来说明。

在这幅图里,我们把上一层的第i个神经元和下一层的第j个神经元之间的权重(在3里讲的很明白)记为w(ij)。而把上一层传入的三个input,分别记为S(1),S(2)和S(3)。

因为数据肯定是在不同的层之间流动的。我们所做的就是通过一个矩阵乘法求解下一层的输出数值。

这个过程其实也非常简单。我们把输出记为O(1),O(2)和O(3)。

结合权重,这个其实不就是一个高中生都可以理解的思想吗?以O(1)举例。O(1)里的输出自然有来自S1,2,3的。那么分别按照权重去乘就可以了。权重自然就是一个大于等于零的实数嘛。

O(1)=S(1)*w(11)+S(2)*w(21)+S(3)*w(31)

类似的我们可以求解出O(2)和O(3)。我们把这一部分,留作练习题。希望你看到这里也自己写一下。

其实答案也很简单:

O(2)=S(1)*w(12)+S(2)*w(22)+S(3)*w(32)

O(3)=S(1)*w(13)+S(2)*w(23)+S(3)*w(33)

这就是一个矩阵的乘法!

5. 如何去调参数:微积分

其实这就是我们使用了人工神经网络来模拟学习的过程。理论上,这个算法可以处理世界上的任何问题。无论是股票交易还是生物信息,无论是飞机上天还是潜艇下水。当然了,工程就是另一回事了。

那么这些权重是如何被求解出来的呢?

答案便是:微积分。

更进一步:微积分里的链式求导法则。


上面我们了解了一下概念,现在我们来看看项目:

数据:

我们选取了一万多条数据来进行分析

平均气温,最高气温,最低气温,平均相对湿度,最小相对湿度,平均气压,最高气压,最低气压,最高地表气温,最低地表气温

使用多维度的分析方式来回归最后一列平均地表温度的预测

1:搭建BP神经网络回归模型

def model():
    # 模型定义
    x = tf.placeholder(tf.float32, [None, 10], name="X")  # 10个特征数据(10列)
    y = tf.placeholder(tf.float32, [None, 1], name="Y")  # 1个标签数据(1列)

    with tf.name_scope("Model"):
        w = tf.Variable(tf.random_normal([10, 1], stddev=0.01), name="W")

        # b 初始化值为1.0
        b = tf.Variable(1.0, name="b")
        def model(x, w, b):
            return tf.matmul(x, w) + b
        # 预测计算操作,前向计算节点
        pred = model(x, w, b)

    # 定义均方差损失函数
    # 定义损失函数
    with tf.name_scope("LossFunction"):
        loss_function = tf.reduce_mean(tf.pow(y - pred, 2))  # 均方误差

    # 创建优化器
    optimizer = tf.train.GradientDescentOptimizer(learning_rate).minimize(loss_function)
    return loss_function, optimizer, pred, x, y, b, w


def run_model(loss_function, optimizer, pred, x_data, y_data, x, y, b, w):
    # 声明会话
    sess = tf.Session()

    # 定义初始化变量的操作
    init = tf.global_variables_initializer()

    # 为TensorBoard可视化准备数据
    # 设置日志存储目录
    logdir = './test'
    # 创建一个操作,用于记录损失值loss,后面在TensorBoard中SCALARS栏可见
    sum_loss_op = tf.summary.scalar("loss", loss_function)

    # 把所有需要记录摘要日志文件的合并,方便一次性写入
    merged = tf.summary.merge_all()

    # 启动会话
    sess.run(init)

    # 创建摘要writer,将计算图写入摘要文件,后面在Tensorflow中GRAPHS栏可见
    writer = tf.summary.FileWriter(logdir, sess.graph)
    # 迭代训练
    loss_list = []  # 用于保存loss值的列表
    b0 = []
    Add_train_y = []
    Add_predict_y = []
    epochs = [i for i in range(train_epochs)]

    for epoch in range(train_epochs):
        loss_sum = 0.0
        for xs, ys in zip(x_data, y_data):
            xs = xs.reshape(1, 10)
            ys = ys.reshape(1, 1)

            _, summary_str, loss = sess.run([optimizer, sum_loss_op, loss_function], feed_dict={
   x: xs, y: ys})
            writer.add_summary(summary_str, epoch)

            loss_sum = loss_sum + loss

        x_data, y_data = shuffle(x_data, y_data)

        b0temp = b.eval(session=sess)  # 训练中当前变量b值
        w0temp = w.eval(session=sess)  # 训练中当前权重w值
        loss_average = loss_sum / len(y_data)  # 当前训练中的平均损失
        b0.append([abs(b0temp)])
        loss_list.append(loss_average)  # 每轮添加一次

        Add_train_y.append(np.sqrt(mean_squared_error(y_data, sess.run(pred, feed_dict={
   x: x_data}))))
        print('epoch:', epoch, 'loss:', loss_average)

    return loss_list, pred, Add_train_y, sess

详解一下:

定义一个模型:

 # 模型定义
    x = tf.placeholder(tf.float32, [None, 10], name="X")  # 10个特征数据(10列)
    y = tf.placeholder(tf.float32, [None, 1], name="Y")  # 1个标签数据(1列)

    with tf.name_scope("Model"):
        w = tf.Variable(tf.random_normal([10, 1], stddev=0.01), name="W")

        # b 初始化值为1.0
        b = tf.Variable(1.0, name="b")
        def model(x, w, b):
            return tf.matmul(x, w) + b
        # 预测计算操作,前向计算节点
        pred = model(x, w, b)

    # 定义均方差损失函数
    # 定义损失函数
    with tf.name_scope("LossFunction"):
        loss_function = tf.reduce_mean(tf.pow(y - pred, 2))  # 均方误差

    # 创建优化器
    optimizer = tf.train.GradientDescentOptimizer(learning_rate).minimize(loss_function)
    return loss_function, optimizer, pred, x, y, b, w

读取数据并使用最大最小值归一化:

def get_data():
    i = 0
    mydf = pd.read_csv('temp.csv', sep='\s+',encoding='utf-8')
    df = mydf.values
    print(df)
    i +=1
    # 把df转换成np的数组格式
    df = np.array(df)
    if i ==10000:
        return df
    return df

def normal(df):
    # 特征数据归一化
    # 对特征数据{0到11}列 做(0-1)归一化
    for i in range(10):
        df[:, i] = (df[:, i] - df[:, i].min()) / (df[:, i].max() - df[:, i].min())
    # x_data为归一化后的前10列特征数据
    x_data = df[:, :10]

    # y_data为最后1列标签数据
    y_data = df[:, 10]
    return x_data, y_data

接下来使用tensorflow的sess进入会话:

sess = tf.Session()

    # 定义初始化变量的操作
    init = tf.global_variables_initializer()

    # 为TensorBoard可视化准备数据
    # 设置日志存储目录
    logdir = './test'
    # 创建一个操作,用于记录损失值loss,后面在TensorBoard中SCALARS栏可见
    sum_loss_op = tf.summary.scalar("loss", loss_function)

    # 把所有需要记录摘要日志文件的合并,方便一次性写入
    merged = tf.summary.merge_all()

    # 启动会话
    sess.run(init)

    # 创建摘要writer,将计算图写入摘要文件,后面在Tensorflow中GRAPHS栏可见
    writer = tf.summary.FileWriter(logdir, sess.graph)
    # 迭代训练
    loss_list = []  # 用于保存loss值的列表
    b0 = []
    Add_train_y = []
    Add_predict_y = []
    epochs = [i for i in range(train_epochs)]

开始迭代:

    for epoch in range(train_epochs):
        loss_sum = 0.0
        for xs, ys in zip(x_data, y_data):
            xs = xs.reshape(1, 10)
            ys = ys.reshape(1, 1)

            _, summary_str, loss = sess.run([optimizer, sum_loss_op, loss_function], feed_dict={
   x: xs, y: ys})
            writer.add_summary(summary_str, epoch)

            loss_sum = loss_sum + loss

        x_data, y_data = shuffle(x_data, y_data)

        b0temp = b.eval(session=sess)  # 训练中当前变量b值
        w0temp = w.eval(session=sess)  # 训练中当前权重w值
        loss_average = loss_sum / len(y_data)  # 当前训练中的平均损失
        b0.append([abs(b0temp)])
        loss_list.append(loss_average)  # 每轮添加一次

        Add_train_y.append(np.sqrt(mean_squared_error(y_data, sess.run(pred, feed_dict={
   x: x_data}))))
        print('epoch:', epoch, 'loss:', loss_average)

接下来对每一步骤进行数据可视化:

def plot_all(loss_list, x_data, y_data, pred, Add_train_y, sess, x, y):
    plt.plot(loss_list)

    # 模型应用

    n = 10988

    sum = 0
    errors = 0
    for n in range(1, 10):
        print(n+2)
        sum +=1
        x_test = x_data[n]
        x_test = x_test.reshape(1, 10)
      
        predict = sess.run(pred, feed_dict={
   x: x_test})
        print("预测值:%f" % predict)

        target = y_data[n]
        print("标签值:%f\n" % target)
        errors += abs(predict - target)
    print('平均绝对误差:', float(errors)/sum)
    label = 'This is the Loss'  # 暂不支持中文
    loc = 'left'
    font_dict = {
   'fontsize': 14, \
                 'fontweight': 8.2, \
                 'verticalalignment': 'baseline', \
                 'horizontalalignment': loc}
    plt.title(label, fontdict=font_dict, loc=loc)
    plt.show()
    #######        曲线拟合效果,可以看出预测效果不错       #####
    test_predictions = sess.run(pred, feed_dict={
   x: x_data})

    plt.scatter(y_data, test_predictions)
    plt.xlabel('True Values ')
    plt.ylabel('Predictions ')
    plt.axis('equal')
    plt.xlim(plt.xlim())
    plt.ylim(plt.ylim())
    _ = plt.plot([-100, 100], [-100, 100])
    label = 'True Values  and Predictions'  # 暂不支持中文
    loc = 'left'
    font_dict = {
   'fontsize': 14, \
                 'fontweight': 8.2, \
                 'verticalalignment': 'baseline', \
                 'horizontalalignment': loc}
    plt.title(label, fontdict=font_dict, loc=loc)
    plt.show()
    plt.figure()
    plt.xlabel('Epoch')
    plt.ylabel('Mean Abs Error ')

    plt.plot(np.arange(train_epochs), Add_train_y,
           label = 'MAE')
    plt.legend()
    plt.ylim([0, 10])
    label = 'This is the MAE'  # 暂不支持中文
    loc = 'left'
    font_dict = {
   'fontsize': 14, \
                 'fontweight': 8.2, \
                 'verticalalignment': 'baseline', \
                 'horizontalalignment': loc}
    plt.title(label, fontdict=font_dict, loc=loc)
    plt.show()

最后启动程序:

df = get_data()  # getdata
x_data, y_data = normal(df)
loss_function, optimizer, pred, x, y, b, w = model()
loss_list, pred, Add_train_y, sess = run_model(loss_function, optimizer, pred, x_data, y_data, x, y, b, w)
plot_all(loss_list, x_data, y_data, pred, Add_train_y, sess, x, y)

使用matplotlib进行可视化:

开始迭代:一定情况下,损失随着epoch(迭代次数)增多而降低,也就是机器在 学习的过程中,学的次数越多,越熟悉。

损失可视化:

预测值和真实值比较,由图中可知几乎都处于一条斜率为一的直线上,所以预测值和真实值十分接近,也体现了神经网络的优势!

再来看看每一个Epoch的MAE:

平均绝对误差(MAE)是用于回归模型的另一个损失函数。MAE是目标和预测变量之间的绝对差异的总和。因此,它测量一组预测中的平均误差大小,而不考虑它们的方向。

平均绝对误差公式:

其中yip是预测结果,而yi是目标变量的真实值,二者的差值称之为残差。

可以看到MAE特别小,就,如果使用MSE(平方均值)损失函数,会更加发现BP神经网络的优势。

最后我们来预测一下某几条数据(当然也可以预测一堆):

然后我们来预测一下,神经网络没有见过的数据:

这样我们就根据多维度训练,预测了平均地温了!

接下来我们训练多维度来预测降水量:

我们还是使用同样的方法(过程见上)直接看结果:


接下来再说说一些重点概念:

激活函数:
神经网络中的每个神经元节点接受上一层神经元的输出值作为本神经元的输入值,并将输入值传递给下一层,输入层神经元节点会将输入属性值直接传递给下一层(隐层或输出层)。在多层神经网络中,上层节点的输出和下层节点的输入之间具有一个函数关系,这个函数称为激活函数(又称激励函数)。
激活函数的用途(为什么需要激活函数)?

如果不用激励函数(其实相当于激励函数是f(x) = x),在这种情况下你每一层节点的输入都是上层输出的线性函数,很容易验证,无论你神经网络有多少层,输出都是输入的线性组合,与没有隐藏层效果相当,这种情况就是最原始的感知机(Perceptron)了,那么网络的逼近能力就相当有限。正因为上面的原因,我们决定引入非线性函数作为激励函数,这样深层神经网络表达能力就更加强大(不再是输入的线性组合,而是几乎可以逼近任意函数)。
有哪些激活函数,都有什么性质和特点?

早期研究神经网络主要采用sigmoid函数或者tanh函数,输出有界,很容易充当下一层的输入。
近些年Relu函数及其改进型(如Leaky-ReLU、P-ReLU、R-ReLU等)在多层神经网络中应用比较多
ReLU函数其实就是一个取最大值函数,注意这并不是全区间可导的,但是我们可以取sub-gradient,如上图所示。ReLU虽然简单,但却是近几年的重要成果,有以下几大优点:
1) 解决了gradient vanishing问题 (在正区间)
2)计算速度非常快,只需要判断输入是否大于0
3)收敛速度远快于sigmoid和tanh

ReLU也有几个需要特别注意的问题:
1)ReLU的输出不是zero-centered
2)Dead ReLU Problem,指的是某些神经元可能永远不会被激活,导致相应的参数永远不能被更新。有两个主要原因可能导致这种情况产生: (1) 非常不幸的参数初始化,这种情况比较少见 (2) learning rate太高导致在训练过程中参数更新太大,不幸使网络进入这种状态。解决方法是可以采用Xavier初始化方法,以及避免将learning rate设置太大或使用adagrad等自动调节learning rate的算法。

尽管存在这两个问题,ReLU目前仍是最常用的activation function,因为快,甚至可以降维,在搭建人工神经网络的时候推荐优先尝试!

损失函数和代价函数

说实话,**损失函数(Loss Function)和代价函数(Cost Function)**并没有一个公认的区分标准,很多论文和教材似乎把二者当成了差不多的东西。

为了后面描述的方便,我们把二者稍微做一下区分(这里的区分仅仅对本文适用,对于其它的文章或教程需要根据上下文自行判断含义):

  损失函数主要指的是对于单个样本的损失或误差;代价函数表示多样本同时输入模型的时候总体的误差——每个样本误差的和然后取平均值。

举个例子,如果我们把单个样本的损失函数定义为:
L(a,y)=−[y⋅log(a)+(1−y)⋅log(1−a)]
L(a,y)=−[y⋅log(a)+(1−y)⋅log(1−a)]
  那么对于m个样本,代价函数则是:
C=−1m∑mi=0(y(i)⋅log(a(i))+(1−y(i))⋅log(1−a(i)))C=−m1i=0∑m(y(i)⋅log(a(i))+(1−y(i))⋅log(1−a(i)))

反向传播

反向传播的基本思想就是通过计算输出层与期望值之间的误差来调整网络参数,从而使得误差变小。

反向传播的思想很简单,然而人们认识到它的重要作用却经过了很长的时间。后向传播算法产生于1970年,但它的重要性一直到David Rumelhart,Geoffrey Hinton和Ronald Williams于1986年合著的论文发表才被重视。

事实上,人工神经网络的强大力量几乎就是建立在反向传播算法基础之上的。反向传播基于四个基础等式,数学是优美的,仅仅四个等式就可以概括神经网络的反向传播过程,然而理解这种优美可能需要付出一些脑力。

反向传播的过程就是利用梯度下降法原理,慢慢的找到代价函数的最小值,从而得到最终的模型参数。梯度下降法在反向传播中的具体应用见下一小节。
反向传播原理(四个基础等式)

反向传播能够知道如何更改网络中的权重w
w 和偏差bb 来改变代价函数值。最终这意味着它能够计算偏导数∂L(a[l],y)∂w[l]jk∂wjk[l]∂L(a[l],y) 和∂L(a[l],y)∂b[l]j∂bj[l]∂L(a[l],y)
  为了计算这些偏导数,我们首先引入一个中间变量δ[l]jδj[l],我们把它叫做网络中第lthlth层第jthjth个神经元的误差。后向传播能够计算出误差δ[l]jδj[l],然后再将其对应回∂L(a[l],y)∂w[l]jk∂wjk[l]∂L(a[l],y)和∂L(a[l],y)∂b[l]j

∂bj[l]∂L(a[l],y) 。

那么,如何定义每一层的误差呢?如果为第l
l 层第jj 个神经元添加一个扰动Δz[l]jΔzj[l],使得损失函数或者代价函数变小,那么这就是一个好的扰动。通过选择 Δz[l]jΔzj[l]与∂L(a[l],y)∂z[l]j

∂zj[l]∂L(a[l],y)符号相反(梯度下降法原理),就可以每次都添加一个好的扰动最终达到最优。


最后介绍结束,我们以后再见,打字不易,请留下一赞和关注~

欢迎大家关注我的公众号联系我 公众号名字在个人简介了~里面有我。
添加好友请备注一下哦,姓名方向学校、企业 例如 cv君-上海-二工大

上海第二工业大学 18智能A1 周小夏 (cv君)


转载:https://blog.csdn.net/qq_46098574/article/details/106144270
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场