作者:解琛
时间:2021 年 2 月 3 日

二、使用 BP 神经网络拟合多输入多输出曲线

列表中的数据是某地区20年公路运量数据,其中属性 人口数量机动车数量公路面积 作为输入,属性 公路客运量公路货运量 作为输出。

请用神经网络拟合此多输入多输出曲线。

年份人口数量/万人机动车数量/万辆公路面积/万平米公路客运量/万人公路货运量/万吨
199020.550.60.0951261237
199122.440.750.1162171379
199225.370.850.1177301385
199327.130.90.1491451399
199429.451.050.2104601663
199530.11.350.23113871714
199630.961.450.23123531834
199734.061.60.32157504322
199836.421.70.32183048132
199938.091.850.34198368936
200039.132.150.362102411099
200139.992.20.361949011203
200241.932.250.382043310524
200344.592.350.492259811115
200447.32.50.562510713320
200552.892.60.593344216762
200655.732.70.593683618673
200756.762.850.674054820724
200859.172.950.694292720803
200960.633.10.794346221804

预测结果如下。

output

实现的代码如下。

#!/usr/bin/env python
# coding=utf-8
# 作者:解琛;
# 功能:神经网络进行多输入多输出的拟合;
# 时间:2020 年 2 月 3 日;
# 备注:无。

import numpy as np
import matplotlib.pyplot as plt

def getSrcData():
    '''获取原始数据;'''
    # 初始化的输入数据;

    # 人口数量;
    population = [
        20.55, 22.44, 25.37, 27.13, 29.45, 30.10, 30.96, 34.06, 36.42, 38.09,
        39.13, 39.99, 41.93, 44.59, 47.30, 52.89, 55.73, 56.76, 59.17, 60.63
    ]

    # 机动车数量;
    vehicle = [
        0.6, 0.75, 0.85, 0.9, 1.05, 1.35, 1.45, 1.6, 1.7, 1.85, 2.15, 2.2, 2.25,
        2.35, 2.5, 2.6, 2.7, 2.85, 2.95, 3.1
    ]

    # 公路面积;
    roadarea = [
        0.09, 0.11, 0.11, 0.14, 0.20, 0.23, 0.23, 0.32, 0.32, 0.34, 0.36, 0.36,
        0.38, 0.49, 0.56, 0.59, 0.59, 0.67, 0.69, 0.79
    ]

    # 初始化输出数据;

    # 公路客运量;
    passengertraffic = [
        5126, 6217, 7730, 9145, 10460, 11387, 12353, 15750, 18304, 19836, 21024,
        19490, 20433, 22598, 25107, 33442, 36836, 40548, 42927, 43462
    ]

    # 公路货运量;
    freighttraffic = [
        1237, 1379, 1385, 1399, 1663, 1714, 1834, 4322, 8132, 8936, 11099, 11203,
        10524, 11115, 13320, 16762, 18673, 20724, 20803, 21804
    ]

    # 返回初始数据;
    return population, vehicle, roadarea, passengertraffic, freighttraffic

def normalizeData(population, vehicle, roadarea, passengertraffic, freighttraffic):
    '''将数据进行标准化处理'''
    global sampleout, sampleinminmax, sampleoutminmax

    # 将输入数据改为 3 × 20 的矩阵;
    samplein = np.mat([population, vehicle, roadarea]) 
    # print("samplein: ", samplein)
    # [
    #  [20.55 22.44 25.37 27.13 29.45 30.1  30.96 34.06 36.42 38.09 39.13 39.99
    #   41.93 44.59 47.3  52.89 55.73 56.76 59.17 60.63]
    #  [ 0.6   0.75  0.85  0.9   1.05  1.35  1.45  1.6   1.7   1.85  2.15  2.2
    #    2.25  2.35  2.5   2.6   2.7   2.85  2.95  3.1 ]
    #  [ 0.09  0.11  0.11  0.14  0.2   0.23  0.23  0.32  0.32  0.34  0.36  0.36
    #    0.38  0.49  0.56  0.59  0.59  0.67  0.69  0.79]
    # ]

    # 将输出数据改为 2 × 20 的矩阵;
    sampleout = np.mat([passengertraffic, freighttraffic]) 
    # print("sampleout: ", sampleout)
    # [
    #  [ 5126  6217  7730  9145 10460 11387 12353 15750 18304 19836 21024 19490
    #   20433 22598 25107 33442 36836 40548 42927 43462]
    #  [ 1237  1379  1385  1399  1663  1714  1834  4322  8132  8936 11099 11203
    #   10524 11115 13320 16762 18673 20724 20803 21804]
    # ]

    # 求得 vehicle 和 roadarea 的最小值和最大值:[最小值 最大值];
    sampleinminmax = np.array(
        [samplein.min(axis=1).T.tolist()[0],
        samplein.max(axis=1).T.tolist()[0]]
    ).transpose() 
    # print("sampleinminmax: ", sampleinminmax)
    # [[20.55 60.63]
    #  [ 0.6   3.1 ]
    #  [ 0.09  0.79]]

    # 求得 passengertraffic 和 freighttraffic 的最小值和最大值:[最小值 最大值];
    sampleoutminmax = np.array(
    [sampleout.min(axis=1).T.tolist()[0],
        sampleout.max(axis=1).T.tolist()[0]]
    ).transpose() 
    # print("sampleoutminmax: ", sampleoutminmax)
    # [[ 5126 43462]
    #  [ 1237 21804]]

    # 将数据进行标准化(归一化);

    # 将数据映射到 -1 到 1 的范围内;
    sampleinnorm = (
        2 * 
        (np.array(samplein.T) - sampleinminmax.transpose()[0]) /
        (sampleinminmax.transpose()[1] - sampleinminmax.transpose()[0]) 
        - 1
    ).transpose()
    # print("sampleinnorm: ", sampleinnorm)
    # [
    #  [-1.         -0.90568862 -0.75948104 -0.67165669 -0.55588822 -0.52345309
    #   -0.48053892 -0.3258483  -0.20808383 -0.1247505  -0.07285429 -0.02994012
    #    0.06686627  0.1996008   0.33483034  0.61377246  0.75548902  0.80688623
    #    0.92714571  1.        ]
    #  [-1.         -0.88       -0.8        -0.76       -0.64       -0.4
    #   -0.32       -0.2        -0.12        0.          0.24        0.28
    #    0.32        0.4         0.52        0.6         0.68        0.8
    #    0.88        1.        ]
    #  [-1.         -0.94285714 -0.94285714 -0.85714286 -0.68571429 -0.6
    #   -0.6        -0.34285714 -0.34285714 -0.28571429 -0.22857143 -0.22857143
    #   -0.17142857  0.14285714  0.34285714  0.42857143  0.42857143  0.65714286
    #    0.71428571  1.        ]
    # ]

    # 将数据映射到 -1 到 1 的范围内;
    sampleoutnorm = (
        2 *
        (np.array(sampleout.T).astype(float) - sampleoutminmax.transpose()[0]) /
        (sampleoutminmax.transpose()[1] - sampleoutminmax.transpose()[0]) 
        - 1
    ).transpose()
    # print("sampleoutnorm: ", sampleoutnorm)
    # [
    #  [-1.         -0.94308222 -0.86414858 -0.79032763 -0.72172371 -0.67336185
    #   -0.62296536 -0.4457429  -0.3125     -0.23257513 -0.17059683 -0.25062604
    #   -0.20142947 -0.0884808   0.04241444  0.47725376  0.6543197   0.84797579
    #    0.9720889   1.        ]
    #  [-1.         -0.98619147 -0.98560801 -0.98424661 -0.95857442 -0.95361501
    #   -0.94194584 -0.70000486 -0.32950844 -0.25132494 -0.04098799 -0.0308747
    #   -0.09690281 -0.0394321   0.17498906  0.5097      0.69553168  0.89497739
    #    0.9026596   1.        ]
    # ]

    # 返回标准化后的输入、输出数据、原始数据的最值;
    return sampleinnorm, sampleoutnorm

def sigmoid(x):
    return 1 / (1 + np.exp(-x))

def network(sampleinnorm, sampleoutnorm):
    '''神经网络;'''
    global sampleoutminmax, errhistory

    # 初始化神经网络的参数;
    
    # 训练的次数;
    maxepochs = 1000               
    # 学习率;
    learnrate = 0.035
    # 认为可以停止训练的理想误差,达到该误差值时停止训练;
    errorfinal = 0.5 * 10 ** (-3)
    # 样本数,下面反向求解计算时用于产生样本数个 1 参与运算,用于计数;
    samnum = 20
    # 输入神经网络的数据维度;
    indim = 3
    # 输出神经网络的数据维度;
    outdim = 2
    # 隐藏层的节点数;
    hiddenunitnum = 8

    # 使用 np.random.rand(3, 1) 创建一个数据范围在 [0, 1] 之间的 3 行 1 列的随机矩阵;
    # 通过 2 * np.random.rand(3, 1) - 1 的方法将数据范围映射到 [-1, 1] 之间;

    # 创建隐藏层的 w 权重和 b 偏置矩阵;
    w1 = 2 * np.random.rand(hiddenunitnum, indim) - 1
    b1 = 2 * np.random.rand(hiddenunitnum, 1) - 1

    # 创建输出层的 w 权重和 b 偏置矩阵;
    w2 = 2 * np.random.rand(outdim, hiddenunitnum) - 1
    b2 = 2 * np.random.rand(outdim, 1) - 1

    # 创建一个列表,用于存储每次训练产生的误差,训练结束后用于绘图,进行可视化,便于分析整个训练过程误差的变化情况;
    errhistory = []

    for _ in range(maxepochs):
        # 通过 h = sigmoid(w1 x + b1) 计算隐藏层的输出;
        hiddenout = sigmoid((np.dot(w1, sampleinnorm).transpose() + b1.transpose())).transpose()

        # 通过 y = w2 h + b2 计算输出层的输出;
        # 因为我们希望使用神经网络进行多输入参数的拟合,不是解决分类问题,所以输出层不能使用激活函数进行非线性化处理;
        networkout = (np.dot(w2, hiddenout).transpose() + b2.transpose()).transpose()

        # 计算神经网络输出和真实输出之间的误差;
        err = sampleoutnorm - networkout
        # print("err: ", err)
        # [
        #     [-1.15209228 -1.09856738 -1.03510767 -0.9593632  -0.87692973 -0.81286831
        #     -0.76319537 -0.55616018 -0.42568621 -0.33624089 -0.2584064  -0.3379644
        #     -0.28130657 -0.12589438  0.03087629  0.47121104  0.64813855  0.86237073
        #     0.9887427   1.03279493]
        #     [ 0.22413548  0.20693496  0.17399082  0.16539449  0.16536842  0.115607
        #     0.1019654   0.31584757  0.64734241  0.6869846   0.83598636  0.82889373
        #     0.73975088  0.77749909  0.95387178  1.21997793  1.35997221  1.54430285
        #     1.51774009  1.60832199]
        # ]

        # 对误差进行求和,得到总的误差;
        # 使用整个数据集的误差和作为误差,而不是单条数据段,目的是为了更快的去降低误差;
        sse = sum(sum(err ** 2))
        # print("sse: ", sse)
        # 31.580979073426597

        # 使用一个列表将误差存储下来;
        errhistory.append(sse)

        # 如果误差已经在期望的范围内,则停止训练;
        if sse < errorfinal:
            break

        # 开始进行反向传递;

        # 因为是使用神经网络进行拟合,所以最终的输出层不适用激活函数进行非线性化;
        # 所以最终额输出层的损失函数不需要对最终输出求偏导,直接赋值即可;
        delta2 = err

        # 隐藏层的损失函数对隐藏层的输出求偏导;
        # d_L_d_ypred × d_ypred_d_h 
        # 使用 x = (w2 × (ytrue - ypred))f'(h) = (w2 × (ytrue - ypred))(h)(1 - h) 进行反向求解;
        delta1 = np.dot(w2.transpose(), delta2) * hiddenout * (1 - hiddenout)

        # 计算输出层的权重 w2 和偏置 b2;
        # dw2 = (ytrue - ypred) × h
        dw2 = np.dot(delta2, hiddenout.transpose())
        # db2 = (ytrue - ypred) × 1
        db2 = np.dot(delta2, np.ones((samnum, 1)))

        # 计算隐藏层的权重 w1 和偏置 b1;
        # dw1 = d_L_d_ypred × d_ypred_d_h  × x
        dw1 = np.dot(delta1, sampleinnorm.transpose())
        # db1 = d_L_d_ypred × d_ypred_d_h × 1
        db1 = np.dot(delta1, np.ones((samnum, 1)))

        # 更新输出层的权重 w2 和偏置 b2;
        w2 += learnrate * dw2
        b2 += learnrate * db2

        # 更新隐藏层的权重 w1 和 b1;
        w1 += learnrate * dw1
        b1 += learnrate * db1
    return w1, b1, w2, b2

def predict(w1, b1, w2, b2, sampleinnorm):
    '''使用训练好的神经网络进行预测;'''
    # 使用新的权重计算隐藏层和输出层的输出;
    # h = f(w1 x + b1)
    hiddenout = sigmoid((np.dot(w1, sampleinnorm).transpose() + b1.transpose())).transpose()
    # o = w2 h + b2
    networkout = (np.dot(w2, hiddenout).transpose() + b2.transpose()).transpose()
    return networkout

def scalableData(normalizeData, minmax):
    # 由于进行了归一化,用最小值和最大值计算原始输出;
    # 计算样本原始数据的变化范围:最大值 - 最小值;
    diff = minmax[:, 1] - minmax[:, 0]

    # 将新权重计算的输出映射到 [0, 1] 范围;
    srcData = (normalizeData + 1) / 2
    # 将神经网络输出的第 1 个参数还原到原始的数据范围; 
    srcData[0] = srcData[0] * diff[0] + minmax[0][0]
    # 将神经网络输出的第 2 个参数还原到原始的数据范围; 
    srcData[1] = srcData[1] * diff[1] + minmax[1][0]
    return srcData

def plotGraph():
    '''结果可视化;'''
    global sampleout, errhistory, networkPredict

    # 输出转为 numpy;
    sampleout = np.array(sampleout)

    # 绘制第 1 张图;
    plt.figure(1)

    plt.plot(errhistory, label="err")

    plt.legend(loc='upper left')

    plt.show()

    # 绘制第 2 张图;
    plt.figure(2)

    plt.subplot(2, 1, 1)

    plt.plot(sampleout[0],
            color="blue",
            linewidth=1.5,
            linestyle="-",
            label=u"real passengertraffic")

    plt.plot(networkPredict[0],
            color="red",
            linewidth=1.5,
            linestyle="--",
            label=u"predict passengertraffic")

    plt.legend(loc='upper left')

    plt.subplot(2, 1, 2)

    plt.plot(sampleout[1],
            color="blue",
            linewidth=1.5,
            linestyle="-",
            label="real freighttraffic")

    plt.plot(networkPredict[1],
            color="red",
            linewidth=1.5,
            linestyle="--",
            label="predict freighttraffic")

    plt.legend(loc='upper left')

    # 展示第 2 张图;
    plt.show()

if __name__ == "__main__":
    global sampleoutminmax, networkPredict

    # 获取原始数据;
    population, vehicle, roadarea, passengertraffic, freighttraffic = getSrcData()
    # 将数据进行规划一处理;
    sampleinnorm, sampleoutnorm = normalizeData(population, vehicle, roadarea, passengertraffic, freighttraffic)
    # 将输入、输出、原始数据的最值传入神经网络,对神经网络进行训练;
    w1, b1, w2, b2 = network(sampleinnorm, sampleoutnorm)
    # 向训练好的神经网络内传入参数进行预测;
    networkPredict = predict(w1, b1, w2, b2, sampleinnorm)
    # 将预测好的结果缩放到正常的数据范围内;
    networkPredict = scalableData(networkPredict, sampleoutminmax)
    # 结论可视化;
    plotGraph()

整个训练过程损失值的变化如下。

loss

Logo

为开发者提供学习成长、分享交流、生态实践、资源工具等服务,帮助开发者快速成长。

更多推荐