实验五 神经网络

参考链接

  • https://blog.csdn.net/tangyuanzong/article/details/78922874

  • https://blog.csdn.net/loveliuzz/article/details/78982928

  • https://blog.csdn.net/fanxin_i/article/details/80212906

  • 问题描述

公路运量主要包括公路客运量和公路货运量两个方面。据研究,某地区的公路运量主要与该地区的人口数量、机动车数量和公路面积有关。 下面数据表中给出了某地区公路运量相关数据。根据相关部门数据,该地区 2010 年和 2011 年的人口数量分别为 73.39 和75.55 万人,机动车数量分别为 3.9635 和 4.0975 万辆,公路面积将分别为 0.9880和 1.0268 万平方千米

  • 实验要求

(1) 请利用 BP 神经网络预测该地区 2010 年和 2011 年的公路客运量和公路货运量。
(2) 请利用其他方法预测该地区 2010 年和 2011 年的公路客运量和公路货运量,并比较神经网络和其他方法的优缺点

  • 数据集
年份人口数量机动车数量公路面积公路客运量公路货运量
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

实验过程

在本次实验中,我们小组实现了手工PB算法,对该地区 2010 年和 2011 年的公路客运量和公路货运量进行了预测。为比较不同算法之间的差异,丰富实验内容,我们实现了多元线性回归算法MLLR,后用使用sklearn实现SVM对结果进行预测。通过可视化实验结果,对不同算法进行了对比分析。

数据集处理

我们对将上表中的数据集保存为两种格式,一种为xls表格,另一种为csv文件,两种格式对应不同的读取方式,前者通过open_workbook打开并读取,后者通过readlines读取数据,最终返回xdata数据,和两组标签ydata1,ydata2即可,下面是对应实现。

  • 读取xls
# 数据读取
def read_xls_file(filename):                         #读取训练数据  
    data = xlrd.open_workbook(filename)                
    sheet1 = data.sheet_by_index(0)                    
    m = sheet1.nrows                                    
    n = sheet1.ncols                      
    # 人口数量 机动车数量 公路面积 公路客运量 公路货运量              
    pop,veh,roa,pas,fre=[],[],[],[],[] 
    for i in range(m):                                  
        row_data = sheet1.row_values(i)               
        if i > 0:
           pop.append(row_data[1])
           veh.append(row_data[2])
           roa.append(row_data[3])
           pas.append(row_data[4])
           fre.append(row_data[5])
    dataMat = np.mat([pop,veh,roa])
    labels = np.mat([pas,fre])
    dataMat_old = dataMat
    labels_old = labels
    # 数据集合,标签集合,保留数据集合,保留标签集合
    return dataMat,labels,dataMat_old,labels_old
  • 读取csv文件
def loadData():                                 # 文件读取函数
    f=open('./data/train.csv')                  # 打开文件    
    data = f.readlines()    
    print(data)
    l=len(data)                                 # mat为l*6的矩阵,元素都为0
    mat=zeros((l,6))                            
    index=0                                     
    xdata = ones((l,4))                         #xdata为l*4的矩阵,元素都为1
    ydata1,ydata2= [],[]                        #两列数据结果                      
    for line in data:
        line = line.strip()                     #去除多余字符
        linedata = line.split(',')              #对数据分割
        mat[index, :] = linedata[0:6]           #得到一行数据
        index +=1
    yearData   = mat[:,0]                       # 得到年份                  
    xdata[:,1] = mat[:,1]                       #得到第1列数据
    xdata[:,2] = mat[:,2]                       #得到第2列数据
    xdata[:,3] = mat[:,3]                       #得到第3列数据
    ydata1 = mat[:,4]                           #得到第4列数据
    ydata2 = mat[:,5]                           #得到第5列数据
    return yearData,xdata,ydata1,ydata2

数据集处理好之后,我们就要实现不同的算法以实现最终的预测了。

1.手工BP

(1) 基本原理

image-20201215193409601

back propagation

  • 算法思想:
    训练数据输入到神经网络中,神经网络计算真实输出与实际输出之间的差异,然后通过调节权值来减小这种差异
  • 两阶段
    训练数据输入,通过前向层层计算到输出层输出结果
    计算真实输出与实际输出之间的错误,通过反向从输出层-隐藏层-输入层的调节权值来减少错误

BP算法步骤:

  • 第一步,初始化

设定初始的权值w1,w2,…,wn和阈值θ为如下的一致分布中的随机数

image-20201215193607502

Fi是输入神经元数量总和

  • 第二步:计算激活函数值

根据输入x1§, x2§,…, xn§ 和权值w1,w2,…,wn计算输出y1§, y2§,…, yn§

(a)计算隐藏层神经元的输出

image-20201215193638926

n是第j个隐藏层神经元的输入数量,sigmoid是激活函数
(b)计算输出层神经元的输出

image-20201215193700741

m是第k个输出神经元的输入数量

  • 第三步:权值更新(从后往前)

(a)计算输出层的错误梯度
image-20201215193801413

image-20201215193809527
计算权值纠正值
image-20201215193820117
更新输出层的权值

(b)计算隐藏层的错误梯度

image-20201215193855095

​ 计算权值纠正值

image-20201215193900603

​ 更新隐藏层的权值

image-20201215193907290

  • 第四步:迭代循环

增加p值,不断重复步骤二和步骤三直到收敛

(2) 归一化处理

由于数据数量级相差太大,我们要先对数据进行归一化处理,常见的标准化方法有(0,1)标准化,Z-score标准化,Sigmoid标准化,这里我们采用(0,1)标准化:
x n o r m a l i z a t i o n = x − μ σ {x}_{normalization}=\frac{x-\mu }{\sigma } xnormalization=σxμ

def Norm(dataMat,labels):
    dataMat_minmax = np.array([dataMat.min(axis=1).T.tolist()[0],dataMat.max(axis=1).T.tolist()[0]]).transpose() 
    dataMat_Norm = ((np.array(dataMat.T)-dataMat_minmax.transpose()[0])/(dataMat_minmax.transpose()[1]-dataMat_minmax.transpose()[0])).transpose()
    labels_minmax  = np.array([labels.min(axis=1).T.tolist()[0],labels.max(axis=1).T.tolist()[0]]).transpose()
    labels_Norm = ((np.array(labels.T).astype(float)-labels_minmax.transpose()[0])/(labels_minmax.transpose()[1]-labels_minmax.transpose()[0])).transpose()
    return dataMat_Norm,labels_Norm,dataMat_minmax,labels_minmax

(3) 激活函数

激活函数我仍然选择的是Sigmoid函数,具有良好的阈值,在(0, 0.5)处中心对称,在(0, 0.5)附近有比较大的斜率,而当数据趋向于正无穷和负无穷的时候,映射出来的值就会无限趋向于1和0,是个人非常喜欢的“归一化方法”,之所以打引号是因为我觉得Sigmoid函数在阈值分割上也有很不错的表现,根据公式的改变,就可以改变分割阈值,这里作为归一化方法,我们只考虑(0, 0.5)作为分割阈值的点的情况:

img
  • 代码实现:
# 激活函数
def sigmod(x):
    return 1/(1+np.exp(-x))

(4) Back Propagation反向传播实现

设计神经网络的结构

# 超参数
maxepochs = 60000                                       # 最大迭代次数
learnrate = 0.030                                       # 学习率
errorfinal = 0.65*10**(-3)                              # 最终迭代误差
indim = 3                                               # 输入特征维度3
outdim = 2                                              # 输出特征唯独2
# 隐藏层默认为3个节点,1层
  • 黄金分割法
    算法的主要思想:首先在[a,b]内寻找理想的隐含层节点数,这样就充分保证了网络的逼近能力和泛化能力。为满足高精度逼近的要求,再按照黄金分割原理拓展搜索区间,即得到区间b,c,在区间[b,c]中搜索最优,则得到逼近能力更强的隐含层节点数,在实际应用根据要求,从中选取其一即可。
    在这里插入图片描述

我们确定了n=3, 输入层,l=2,输出层,m=(3+2)^1/2 = 2.449489743,由于alpha可以取1~10之间的数,所以我测试了以下几个层数2值,3, 4, 5,6,7,8最终对比训练结果,我选择了4个节点的中间层。
image-20210109091331102

for i in range(maxepochs):
    # 激活隐藏输出层
    hiddenout = sigmod((np.dot(w1,sampleinnorm).transpose()+b1.transpose())).transpose()
    # 计算输出层输出
    networkout = (np.dot(w2,hiddenout).transpose()+b2.transpose()).transpose()
    # 计算误差
    err = sampleoutnorm - networkout
    # 计算代价函数(cost function)sum对数组里面的所有数据求和,变为一个实数
    sse = sum(sum(err**2))/m                                
    errhistory.append(sse)
    if sse < errorfinal:                                    #迭代误差
        break
    # 计算delta
    delta2 = err
    delta1 = np.dot(w2.transpose(),delta2)*hiddenout*(1-hiddenout)
    # 计算偏置
    dw2 = np.dot(delta2,hiddenout.transpose())
    db2 = 1 / 20 * np.sum(delta2, axis=1, keepdims=True)

    dw1 = np.dot(delta1,sampleinnorm.transpose())
    db1 = 1/20*np.sum(delta1,axis=1,keepdims=True)

    # 更新权值
    w2 += learnrate*dw2
    b2 += learnrate*db2
    w1 += learnrate*dw1
    b1 += learnrate*db1

(5) 可视化训练情况

为了对训练情况有一个详细的了解,我将训练好的模型用训练集合进行预测,并且将真实 / 预测 公路客运量和真实 / 预测 公路货运量四条曲线绘制在一张图片上,除此之外,为了观察训练过程,我将训练的误差也绘制在图像中,下面是我可视化的代码核心部分:

def show(sampleinnorm,sampleoutminmax,sampleout,errhistory,maxepochs):      # 图形显示
    matplotlib.rcParams['font.sans-serif']=['SimHei']                       # 防止中文乱码                         
    networkout2[0] = networkout2[0]*diff[0]+sampleoutminmax[0][0]
    networkout2[1] = networkout2[1]*diff[1]+sampleoutminmax[1][0]
    sampleout = np.array(sampleout)

    fig,axes = plt.subplots(nrows=2,ncols=1,figsize=(12,10))
    line1, = axes[0].plot(networkout2[0],'k',markeredgecolor='b',marker = 'o',markersize=7)
    line2, = axes[0].plot(sampleout[0],'r',markeredgecolor='g',marker = u'$\star$',markersize=7)
    line3, = axes[0].plot(networkout2[1],'g',markeredgecolor='g',marker = 'o',markersize=7)
    line4, = axes[0].plot(sampleout[1],'y',markeredgecolor='b',marker = u'$\star$',markersize=7)
    axes[0].legend((line1,line2,line3,line4),(u'客运量预测输出',u'客运量真实输出',u'货运量预测输出',u'货运量真实输出'),loc = 'upper left')
    axes[0].set_ylabel(u'公路客运量及货运量')

    axes[1].set_xlabel(u'训练次数')
    axes[1].set_ylabel(u'误差')
    axes[1].set_title(u'误差曲线')
    plt.savefig("./fig/BP6.png")
    plt.show()
  • 可视化结果:

BP神经网络

image-20210109082023145

误差曲线

image-20210109082108291

可以看到整体上预测数据和实际数据比十分吻合,训练误差收敛很快。

(6) 预测结果

为了对结果进行预测,我们要先将测试数据归一化,然后再作为神经网络的输入,然后计算两层的输出结果,进行预测

def pre(dataMat,dataMat_minmax,diff,sampleoutminmax,w1,b1,w2,b2):          #数值预测
    # 归一化数据
    dataMat_test = ((np.array(dataMat.T)-dataMat_minmax.transpose()[0])/(dataMat_minmax.transpose()[1]-dataMat_minmax.transpose()[0])).transpose() 
    # 然后计算两层的输出结果
    # 隐藏层
    hiddenout = sigmod((np.dot(w1,dataMat_test).transpose()+b1.transpose())).transpose()
    # 输出层
    networkout1 = (np.dot(w2,hiddenout).transpose()+b2.transpose()).transpose()
    networkout = networkout1
    # 计算结果
    networkout[0] = networkout[0]*diff[0] + sampleoutminmax[0][0]
    networkout[1] = networkout[1]*diff[1] + sampleoutminmax[1][0]

    print("2010年预测的公路客运量为:", int(networkout[0][0]),"(万人)")
    print("2010年预测的公路货运量为:", int(networkout[1][0]),"(万吨)")
    print("2011年预测的公路客运量为:", int(networkout[0][1]),"(万人)")
    print("2011年预测的公路货运量为:", int(networkout[1][1]),"(万吨)")
  • 预测结果:
    image-20210109083558426

  • 结论:

2010年预测的公路客运量为: 54145 (万人),2010年预测的公路货运量为: 28907 (万吨),2011年预测的公路客运量为: 56138 (万人),2011年预测的公路货运量为: 30094 (万吨)。

2.线性回归

(1) 基本思路

在这里插入图片描述

这个代价函数是 x(i) 的估计值与真实值 y(i) 的差的平方和,前面乘上 1/2,是因为在求导的时候,这个系数就可以消去。

  • 梯度下降法的流程:

1)首先对 θ 赋值,这个值可以是随机的,也可以让 θ 是一个全零的向量。
2)改变 θ 的值,使得 J(θ) 的值按梯度下降的方向减小。

img参数 θ 与误差函数 J(θ) 的关系图

红色部分表示 J(θ) 有着比较高的取值,我们希望能够让 J(θ) 的值尽可能的低,也就是取到深蓝色的部分。θ0、θ1 表示 θ 向量的两个维度。上面提到梯度下降法的第一步,是给 θ 一个初值,假设随机的初值位于图上红色部分的十字点。然后我们将 θ 按梯度下降的方向进行调整,就会使 J(θ) 往更低的方向进行变化,如图所示,算法的结束将在 θ 下降到无法继续下降为止。

在这里插入图片描述

θi 表示更新前的值,减号后边的部分表示按梯度方向减少的量,α 表示步长,也就是每次按梯度减少的方向变化多少。

梯度是有方向的,对于一个向量 θ,每一维分量 θi 都可以求出一个梯度的方向,我们就可以找到一个整体的方向,在变化的时候,我们就朝着下降最多的方向进行变化,就可以达到一个最小点,不管它是局部的还是全局的。

算法步骤

  • 代价函数J(θ)的变量是θ,迭代过程中,使用:
    在这里插入图片描述

    对θ进行迭代,不断更新 θ的值,同时记录代价函数cost,预计cost将会在迭代过程中不断趋近一个稳定值。

  • 检查梯度下降:是打印出每一步代价函数J(θ)的值,看他是不是一直都在减小,并且最后收敛至一个稳定的值。

  • θ最后的结果会用来预测。

(2) 归一化数据

由于数据数量级相差太大,我们要先对数据进行归一化处理,常见的标准化方法有(0,1)标准化,Z-score标准化,Sigmoid标准化,这里我们采用(0,1)标准化:
x n o r m a l i z a t i o n = x − μ σ {x}_{normalization}=\frac{x-\mu }{\sigma } xnormalization=σxμ

# 归一化数据
def Min_Max(xdata):                             #归一化数据
    index = 1
    while index < len(xdata[0,:]):
          item = xdata[:,index].max()
          item1 = xdata[:,index].min()
          xdata[:,index] = (xdata[:,index] - item1)/(item-item1)   #归一化数据
          index = index+1
    return xdata

(3) 代价函数

计算公式:

image-20210109084509006

J(θ)为代价函数,h(θ)(x)为线性回归给出的结果,(表达式为2D线性线性回归模型),θi是迭代模型,用于做迭代处理,采用向量化的形式进行计算以优化计算速度,然后:根据上述的回归方程,定义代价函数:

# 代价函数
def cost(theta,xdata,ydata,l):                  #代价函数
    SUM = 0
    idex = 0
    ydata = mat(ydata)
    ydata = ydata.T
    for line in ydata:
          yp = model(theta,xdata[idex ,:])     
          yp = yp - ydata[idex ,:]
          yp = yp**2
          SUM = SUM + yp
          idex  =idex +1
    return SUM/2/l                             #返回代价

(4) 梯度下降求解

  • 代价函数J(θ)的变量是θ,迭代过程中,使用:

image-20201111151154805

对θ进行迭代,不断更新 θ的值,同时记录代价函数cost,预计cost将会在迭代过程中不断趋近一个稳定值。

  • 检查梯度下降:是打印出每一步代价函数J(θ)的值,看他是不是一直都在减小,并且最后收敛至一个稳定的值。

  • θ最后的结果会用来预测。

def OLS(xdata,ydata):                           #梯度下降求解函数
    theta = [0,0,0,0]
    iters = 0
    iters = int(iters)
    l = len(ydata)
    l = int(l)
    cost_record = []
    it = []
    sigmal = 0.1
    cost_val = cost(theta,xdata,ydata,l)           #计算代价
    cost_record.append(cost_val)
    it.append(iters)
    while iters <1500:
          theta = gradlient(theta,xdata,ydata,sigmal,l) #计算梯度
          cost_updata = cost(theta,xdata,ydata,l)       #计算代价
          iters = iters + 1
          cost_val = cost_updata
          cost_record.append(cost_val)
          it.append(iters)
    return mat(theta).T,cost_record,it,theta

(5) 可视化训练情况

为了对训练情况有一个详细的了解,我将训练好的模型用训练集合进行预测,并且将真实 / 预测 公路客运量和真实 / 预测 公路货运量四条曲线绘制在一张图片上,除此之外,为了观察训练过程,我将训练的误差也绘制在图像中,下面是我可视化的代码核心部分:

def show(pre1,pre2,ydata1,ydata2):                                          # 图形显示
    matplotlib.rcParams['font.sans-serif']=['SimHei']                       # 防止中文乱码                         
    fig,axes = plt.subplots(figsize=(12,10))
    line1, = axes.plot(pre1,'k',markeredgecolor='b',marker = 'o',markersize=9)
    line2, = axes.plot(ydata1,'r',markeredgecolor='g',marker = u'$\star$',markersize=9)
    line3, = axes.plot(pre2,'g',markeredgecolor='g',marker = 'o',markersize=9)
    line4, = axes.plot(ydata2,'y',markeredgecolor='b',marker = u'$\star$',markersize=9)
    
    axes.legend((line1,line2,line3,line4),(u'客运量预测输出',u'客运量真实输出',u'货运量预测输出',u'货运量真实输出'),loc = 'upper left')
    axes.set_ylabel(u'公路客运量及货运量')
    xticks = range(0,22,1)
    xtickslabel = range(1990,2012,1)
    axes.set_xticks(xticks)
    axes.set_xticklabels(xtickslabel)
    axes.set_xlabel(u'年份')
    axes.set_title(u'多元线性回归MLR')

    plt.savefig("./fig/MLR.png")
    plt.show()
    plt.close()
  • 下面是训练过程的预测:

image-20210109085321118

可以看到整体上呈现线性的趋势,但是在局部数据预测有较大的差异,误差较大。

(6) 预测结果

为了对结果进行预测,我们要先将测试数据归一化,然后再作为参数代入直线方程:

test1 = [1,73.39,3.9635,0.9880]
test2 = [1,75.55,4.0975,1.0268]

test1=Normal(test1,xcopy)
print("2010年预测的公路客运量为:", (test1*ws1)[0,0],"(万人)")
print("2010年预测的公路货运量为:", (test1*ws2)[0,0],"(万吨)")

test2=Normal(test2,xcopy)
print("2011年预测的公路客运量为:", (test2*ws1)[0,0],"(万人)")
print("2011年预测的公路货运量为:", (test2*ws2)[0,0],"(万吨)")
  • 预测结果:
    image-20210109090345409

  • 结论

2010年预测的公路客运量为: 54959.57130453731 (万人),2010年预测的公路货运量为: 29751.27273227173 (万吨),2011年预测的公路客运量为: 57130.95514474473 (万人),2011年预测的公路货运量为: 31066.13408367608 (万吨)和神经网络预测结果相近。

3.基于sklearn实现SVM支持向量积。

Scikit-learn(sklearn)是机器学习中常用的第三方模块,对常用的机器学习方法进行了封装,包括回归(Regression)、降维(Dimensionality Reduction)、分类(Classfication)、聚类(Clustering)等方法。

  • sklearn库的结构
这里写图片描述

对于这个实验中的公路运量预测问题,我们可以用sklearn实现SVM支持向量积

(1) 进行标准化标签

# 进行标准化标签
def Encodeing(X):
    label_encoder = []
    X_encoded = np.empty(X.shape)
    for i, item in enumerate(X[0]):
        if item.isdigit():
            X_encoded[:, i] = X[:, i]
        else:
            label_encoder.append(preprocessing.LabelEncoder())
            X_encoded[:, i] = label_encoder[-1].fit_transform(X[:, i])
    return X_encoded,label_encoder

(2) 预测

if __name__ == "__main__":
    dataMat,labels_1,labels_2,label_encoder= read_xls_file('./data/train.csv')

    dataMat_Norm = dataMat
    print(dataMat)
    print(labels_1)
    print(labels_2)
    
    params = {'kernel': 'rbf', 'C': 10.0, 'epsilon': 0.2}
    regressor = SVR(**params)

    regressor.fit(dataMat_Norm,labels_1)

    input_data = ['22.44','0.75','0.11']
    input_data_encoded = [-1] * len(input_data)
    count = 0
    for i, item in enumerate(input_data):
        if item.isdigit():
            input_data_encoded[i] = int(input_data[i])
        else:
            input_data_encoded[i] = int(label_encoder[count].transform([input_data[i]]))
            count = count + 1 

    input_data_encoded = np.array(input_data_encoded)

    print("Predicted traffic:", regressor.predict([input_data]))

(3) 结果分析

image-20210109100505628

4. 实验结果对比分析

  • 神经网络

image-20210109082023145

  • 梯度下降的线性回归

image-20210109085321118

BP
优点:既能求解非线性问题,又能算法实现比较简单, 需要训求解线性问题。训练的参数比较少。
缺点:
(1)容易形成局部极小值而得不到全局最优值。BP神经网络中极小值比较多,所以很容易陷入局部极小值。
(2) 训练次数多使得学习效率低,收敛速度慢。
(3)隐含层的选取缺乏理论的指导。
(4)训练时学习新祥本有遗忘旧祥本的趋势

线性回归
优点:
算法实现比较简单, 需要训练的参数比较少。
缺点:
对于某些非线性问题,线性回归求解出的结果,误差比比较大

联系:
神经网络的每一个神经元都是一个线性回归模型,并且在此基础上,每个线性回归还添加了激活函数,因此能够学习到更多有用信息。
Logo

旨在为数千万中国开发者提供一个无缝且高效的云端环境,以支持学习、使用和贡献开源项目。

更多推荐