0412
0412
4月前 · 21 人阅读

在前面两个神经网络的运用例子中,我们主要使用神经网络对输入数据预测出一个离散性结果,也就是预测的结果都是0,1,要不就是1到46中任意一个数,这些结果都是离散化,相互间不兼容。我们这节要用神经网络对输入数据预测出一个连续型结果,例如我们预测下个月房价的价格区间,明天的温度区间等等。

本节我们使用网络对房价进行预测,判断房价在未来一段时间内会处于怎样的价格区间,当模型构建好后,你把所关心地区历年来足够多的房价数据输入网络,你就可以得到它未来的价格变动走势,如此一来你便能抓住正确的买房出手时机了。

由于我们自己的数据匮乏,相关部分总是把数据当做“机密”来管控,所以我们本节使用的训练数据仍然来自于美国,美国政府公开了很多公共数据以便社会研究,在我们使用的Keras框架中,包含了美国1970年波士顿郊区的房地产相关信息,例如犯罪率,房地产税,不同户型的房价等等,这次我们要使用这些信息来预测房价的中位数。这次我们使用的数据有几个特点,一是数据量很小,总共才五百多条,其次是数据的格式不一样,有些数据以百分比来表示,有些数据处于范围1到12,而有些数据范围在0到100,因此数据的规格化处理也是本节的重点。

首先我们还是从框架中加载数据集:

from keras.datasets import boston_housing
(train_data, train_targets), (test_data, test_targets) = boston_housing.load_data()

上面代码运行后,会把相关数据下载到本地。我们运行下面代码看看数据集的大小:

print(train_data.shape)
print(test_data.shape)

这两句代码输出后,得到结果如下:
(404,13)
(102, 13)
也就是说训练数据集只有404条,测试数据集只有102条。而每条数据集都包含13个方面的特征,它包括以下几种数据信息:
1,城镇中非商业用地的比率
2,每块面积25000英尺的地皮上,住宅用地的比率。
3,城镇中非零售商业用地面积
4,是否沿河,1表示沿河,0表示不沿河
5,一氧化氮含量
6,住宅的平均单间数
7,1940年前自有产权房的数量
8,住宅与波士顿五个工作区的距离
9,接入高速公路的便利指数
10,每一万美金所要缴纳的全额房产税
11,城镇中的师生比
12,1000*(BK - 0.63)^2, BK表示黑人比率
13,底层人口的百分比

我们要预测的是自有产权房的中位价格,单位是千美元,我们看看训练数据集对应的结果:

屏幕快照 2018-07-03 上午10.57.38.png

从数据看,房价中位数在一万到一万五美金,记住这是1970年的房价。接下来我们需要把数据的格式统一起来,如果把不同格式,不同单位的数据杂糅一起输入网络会影响结果的准确度。数据规格化的做法是,计算每一个种类数据的均值,然后每个数据实例减去该均值,然后在除以数据的标准差,用Python很容易实现这种数值处理:

mean = train_data.mean(axis=0)
train_data -= mean
std = train_data.std(axis = 0)
train_data /= std

test_data -= mean
test_data /= std

经过规格化处理后,所以类型的数据都有了统一格式。这里要注意的是,我们对测试数据集进行规格化处理是,均值和方差都来自训练数据,切记不要随意对测试数据集本身做任何变换,因为这样会影响到预测结果的准确度。

接下来我们可以构建网络来处理数据了。由于数据量很小,因此我们的网络不能架构过大,基本上用两个中间层,每层64个节点即可。一个需要注意的问题在于,当训练数据越少时,过度拟合就会越严重,只有缩小网络的结构,我们才能抑制过度拟合的严重程度。以下是我们构建网络的代码:

from keras import models
from keras import layers

def build_model():
    '''
    由于后面我们需要反复构造同一种结构的网络,所以我们把网络的构造代码放在一个函数中,
    后面只要直接调用该函数就可以将网络迅速初始化
    '''
    model = models.Sequential()
    model.add(layers.Dense(64, activation='relu', input_shape=(train_data.shape[1],)))
    model.add(layers.Dense(64, activation='relu'))
    model.add(layers.Dense(1))
    model.compile(optimizer='rmsprop', loss='mse', metrics=['mae'])
    return model

我们构造的网络最后一层只有一个节点,因为我们只需要预测一个结果,同时该节点没有激活函数,当我们要预测的结果处于某个区间内时,我们就不用添加激活函数。如果使用激活函数的话,输出结果的取值范围就会收到影响,例如如果我们在最后一层的节点使用sigmoid函数,那么结果范围就会落入0到1之间,然而房价中位数可以落入任何非负区间,于是将其转换为0到1之间是不合理的。

还需要注意的是,这里用到的损失函数叫'mse',中文名叫均方误差,其公式如下:
MSE = 1/n * (Y - Y')^2,其中Y对应的是正确结果,Y'对应的是网络预测结果。当我们需要预测某个数值区间时,我们就使用MSE作用损失函数。在第三个参数中,我们使用mae,它表示平均绝对误差,它用来描述预测结果与正确结果之差的绝对值,例如MAE = 0.5, 那意味着我们的预测结果与正确结果相差500美元,记住我们的单位是千美元。

接下来我们可以把训练数据分成两组,一组用于训练网络,一组用于校验训练的结果。目前有一个问题是,数据量太小,这导致的结果是,我们对数据划分的不同方式都会对校验结果产生不同影响,如此一来我们就不能对网络的训练情况有确切的掌握。处理这种情况的有效办法叫k-fold交叉检验,k一般取4到5,选其中的k-1分数据来训练,剩下1份来校验。网络总共训练k次,每一份数据都有机会作为校验集,最后把k次校验的结果做一次平均。用代码或许能产生更好的解释效果:

import numpy as np
k = 4
num_val_samples = len(train_data) // k #整数除法
num_epochs = 10
all_scores = []
for i in range(k):
    print('processing fold #', i)
    #依次把k分数据中的每一份作为校验数据集
    val_data = train_data[i * num_val_samples : (i+1) * num_val_samples]
    val_targets = train_targets[i* num_val_samples : (i+1) * num_val_samples]
    
    #把剩下的k-1分数据作为训练数据,如果第i分数据作为校验数据,那么把前i-1份和第i份之后的数据连起来
    partial_train_data = np.concatenate([train_data[: i * num_val_samples], 
                                         train_data[(i+1) * num_val_samples:]], axis = 0)
    partial_train_targets = np.concatenate([train_targets[: i * num_val_samples], 
                                            train_targets[(i+1) * num_val_samples: ]],
                                          axis = 0)
    print("build model")
    model = build_model()
    #把分割好的训练数据和校验数据输入网络
    model.fit(partial_train_data, partial_train_targets, epochs = num_epochs, 
              batch_size = 1, verbose = 0)
    print("evaluate the model")
    val_mse, val_mae = model.evaluate(val_data, val_targets, verbose = 0)
    all_scores.append(val_mae)
    
print(all_scores)

上面代码运行后,输出结果如下:

屏幕快照 2018-07-03 下午6.12.10.png

上面的结果表明,我们的预测结果跟正确结果相比大概相差2.4*1000也就是2400美元左右,这个误差还是过大。为了缩小误差,我们提升训练的循环次数,并把每次训练中的相关信息,例如对校验数据的判断准确率记录下来,以便观察训练对网络的影响,相关代码如下:

import numpy as np
k = 4
num_val_samples = len(train_data) // k #整数除法
num_epochs = 200
all_mae_histories = []

for i in range(k):
    print('processing fold #', i)
    #依次把k分数据中的每一份作为校验数据集
    val_data = train_data[i * num_val_samples : (i+1) * num_val_samples]
    val_targets = train_targets[i* num_val_samples : (i+1) * num_val_samples]
    
    #把剩下的k-1分数据作为训练数据,如果第i分数据作为校验数据,那么把前i-1份和第i份之后的数据连起来
    partial_train_data = np.concatenate([train_data[: i * num_val_samples], 
                                         train_data[(i+1) * num_val_samples:]], axis = 0)
    partial_train_targets = np.concatenate([train_targets[: i * num_val_samples], 
                                            train_targets[(i+1) * num_val_samples: ]],
                                          axis = 0)
    print("build model")
    model = build_model()
    #把分割好的训练数据和校验数据输入网络
    history = model.fit(partial_train_data, partial_train_targets, 
              validation_data=(val_data, val_targets),
              epochs = num_epochs, 
              batch_size = 1, verbose = 0)
    mae_history = history.history['val_mean_absolute_error']
    all_mae_histories.append(mae_history) 

上面代码把每次训练的循环次数提高到200,并把每次训练后,对校验数据判断的误差记录下来,后面我们会按照惯例把数据绘制出来,通过图形的方式查看模型在训练过程中的变化。

我们最外层有4论循环,在内部训练网络时,又有200轮循环,也就是num_epochs的值,网络每进行一个epcho训练就会得到一个偏差结果,于是最外层第一轮循环得到200个偏差结果记作x1[0],x1[1]...x1[199],最外层第二轮同样有200个偏差,记作x2[0]x2[1]...x2[199],依次类推。我们计算4个大循环对应的误差平均值,也就是(x1[0]+x2[0]+x3[0]+x4[0])/2,(x1[1]+x2[1]+x3[1]+x4[1])/4,依次类推,如此计算下来我们得到200个平均值,这些平均值将用来衡量200个epoch训练中,模型精准度的变化,我们先看看上面的平均值如何用代码实现:

average_mae_history = [
    np.mean([x[i] for x in all_mae_histories]) for i in range(num_epochs)
]

接着我们把200次循环的误差绘制出来,代码如下:

import matplotlib.pyplot as plt
plt.plot(range(1, len(average_mae_history) + 1), average_mae_history)
plt.xlabel('Epochs')
plt.ylabel('Validation MAE')
plt.show()

上面代码运行后,结果如下:

屏幕快照 2018-07-05 下午5.51.07.png

从上图看到,前10个数据点误差值存有巨大的差异,因此统计时要忽略掉这些点,后面的数据段误差值变动很剧烈,但我们从图中很难看出不同点之间的差异究竟是多少。为了把第10个epoch后面的数据差异更明显的展现出来,我们对数据做一些变换:

def smooth_curve(points, factor=0.9):
    smoothed_points = []
    for point in points:
        if smoothed_points:
            previous = smoothed_points[-1]
            smoothed_points.append(previous * factor + point * (1 - factor))
        else:
            smoothed_points.append(point)
    return smoothed_points

smooth_mae_history = smooth_curve(average_mae_history[10:])

plt.plot(range(1, len(smooth_mae_history)+1), smooth_mae_history)
plt.xlabel('Epochs')
plt.ylabel('Validation MAE')
plt.show()

上面代码运行后结果如下:

屏幕快照 2018-07-05 下午5.59.56.png

上面代码用的方法叫指数滑动平均,它的计算公式为:

屏幕快照 2018-07-06 下午3.27.23.png

指数滑动平均具有把反复跳动的数据进行平滑的作用,使得我们能从反复变动的数据看出它潜在的变化趋势,例如上图,原来跳动很难看出数据趋势的数据点经过滑动平均后,变成了一条趋势明显的曲线,我们可以看到在epochs=30的时候,校验误差处于最低点,过了30后,误差不断升高,由此我们可以把循环训练的次数设定在30左右,这样得出的模型准确度最好。经过校验数据的检测后,我们可以调整各项参数,然后再把模型重新训练一遍:

model = build_model()
model.fit(train_data, train_targets, epochs = 30, batch_size = 16, verbose = 0)
test_mse_score, test_mae_score = model.evaluate(test_data, test_targets)
print(test_mae_score)

上面代码运行后,得到的结果为2.86,也就是说,我们模型对测试数据中位数的预测误差在2860作用,由于测试数据的中位值在5000多,因此这个误差还是比较大,主要原因在于数据量过小的原因。

从这个例子我们可以看到当我们预测的数据具有连续性,例如价格时,使用的损失函数是MSE,也叫均方误差。matric要设置成'mae',也就是平均绝对误差。当训练数据对应的特征有不同单位时,一定要把他们进行规格化处理,例如我们训练的数据中,有些数值对应房子的价格,有些数值对应房子的大小,两种数据性质不同,单位不一样,所以要分别进行规格化处理。

当可用于训练的数据量不大时,我们可以使用k-fold校验法,这样能有效提升模型的准确度,同时当训练数据量不足时,网络的中间层要尽量少,这样可以防止过度拟合。

这三节,我们用三个简单的实例介绍了神经网络在实际项目中的运用,从后面章节开始,我们将会慢慢的将网络运用到更复杂的运用实例,例如图像识别等应用中。

更详细的讲解和代码调试演示过程,请点击链接

欢迎关注公众号,让我们一起学习,交流,成长:


文章公众号.jpg
收藏 0
数据 train data val num model
评论 ( 3 )