第12章 项目:波士顿住房价格回归

本章关于如何使用Keras和社交网络解决回归问题。本章将:

  • 导入CSV数据
  • 创建回归问题的神经网络模型
  • 使用scikit-learn对Keras的模型进行交叉验证
  • 预处理数据以增加效果
  • 微调网络参数

我们开始吧。

12.1 波士顿住房价格数据

本章我们研究波士顿住房价格数据集,即波士顿地区的住房信息。我们关心的是住房价格,单位是千美金:所以,这个问题是回归问题。数据有13个输入变量,代表房屋不同的属性:

  1. CRIM:人均犯罪率
  2. ZN:25,000平方英尺以上民用土地的比例
  3. INDUS:城镇非零售业商用土地比例
  4. CHAS:是否邻近查尔斯河,1是邻近,0是不邻近
  5. NOX:一氧化氮浓度(千万分之一)
  6. RM:住宅的平均房间数
  7. AGE:自住且建于1940年前的房屋比例
  8. DIS:到5个波士顿就业中心的加权距离
  9. RAD:到高速公路的便捷度指数
  10. TAX:每万元的房产税率
  11. PTRATIO:城镇学生教师比例
  12. B: 1000(Bk − 0.63)2 其中Bk是城镇中黑人比例
  13. LSTAT:低收入人群比例
  14. MEDV:自住房中位数价格,单位是千元

这个问题已经被深入研究过,所有的数据都是数字。数据的前5行是:

  1. 0.00632 18.00 2.310 0 0.5380 6.5750 65.20 4.0900 1 296.0 15.30 396.90 4.98 24.00
  2. 0.02731 0.00 7.070 0 0.4690 6.4210 78.90 4.9671 2 242.0 17.80 396.90 9.14 21.60
  3. 0.02729 0.00 7.070 0 0.4690 7.1850 61.10 4.9671 2 242.0 17.80 392.83 4.03 34.70
  4. 0.03237 0.00 2.180 0 0.4580 6.9980 45.80 6.0622 3 222.0 18.70 394.63 2.94 33.40
  5. 0.06905 0.00 2.180 0 0.4580 7.1470 54.20 6.0622 3 222.0 18.70 396.90 5.33 36.20

数据在本书的data目录下,也可以自行下载,重命名为housing.csv。普通模型的均方误差(MSE)大约是20,和方差(SSE)是$4,500美金。关于数据集详情,请到UCI机器学习网站查看。

12.2 简单的神经网络

先创建一个简单的回归神经网络。导入所有的库和函数:

  1. import numpy
  2. import pandas
  3. from keras.models import Sequential
  4. from keras.layers import Dense
  5. from keras.wrappers.scikit_learn import KerasRegressor
  6. from sklearn.cross_validation import cross_val_score
  7. from sklearn.cross_validation import KFold
  8. from sklearn.preprocessing import StandardScaler
  9. from sklearn.pipeline import Pipeline

源文件是CSV格式,分隔符是空格:可以用pandas导入,然后分成输入(X)和输出(Y)变量。

  1. # load dataset
  2. dataframe = pandas.read_csv("housing.csv", delim_whitespace=True, header=None)
  3. dataset = dataframe.values
  4. # split into input (X) and output (Y) variables
  5. X = dataset[:,0:13]
  6. Y = dataset[:,13]

Keras可以把模型封装好,交给scikit-learn使用,方便测试模型。我们写一个函数,创建神经网络。

代码如下。有一个全连接层,神经元数量和输入变量数一致(13),激活函数还是整流函数。输出层没有激活函数,因为在回归问题中我们希望直接取结果。

优化函数是Adam,损失函数是MSE,和我们要优化的函数一致:这样可以对模型的预测有直观的理解,因为MSE乘方就是千美元计的误差。

  1. # define base mode
  2. def baseline_model():
  3. # create model
  4. model = Sequential()
  5. model.add(Dense(13, input_dim=13, init='normal', activation='relu')) model.add(Dense(1, init='normal'))
  6. # Compile model
  7. model.compile(loss='mean_squared_error', optimizer='adam') return model

使用KerasRegressor封装这个模型,任何其他的变量都会传入fit()函数中,例如训练次数和批次大小,这里我们取默认值。老规矩,为了可以复现结果,指定一下随机数种子:

  1. # fix random seed for reproducibility
  2. seed = 7
  3. numpy.random.seed(seed)
  4. # evaluate model with standardized dataset
  5. estimator = KerasRegressor(build_fn=baseline_model, nb_epoch=100, batch_size=5, verbose=0)

可以测试一下基准模型的结果了:用10折交叉检验看看。

  1. kfold = KFold(n=len(X), n_folds=10, random_state=seed)
  2. results = cross_val_score(estimator, X, Y, cv=kfold)
  3. print("Results: %.2f (%.2f) MSE" % (results.mean(), results.std()))

结果是10次检验的误差均值和标准差。

  1. Results: 38.04 (28.15) MSE

12.3 预处理数据以增加性能

这个数据集的特点是变量的尺度不一致,所以标准化很有用。

scikit-learn的Pipeline可以直接进行均一化处理并交叉检验,这样模型不会预先知道新的数据。代码如下:

  1. # evaluate model with standardized dataset
  2. numpy.random.seed(seed)
  3. estimators = []
  4. estimators.append(('standardize', StandardScaler()))
  5. estimators.append(('mlp', KerasRegressor(build_fn=baseline_model, nb_epoch=50,
  6. batch_size=5, verbose=0)))
  7. pipeline = Pipeline(estimators)
  8. kfold = KFold(n=len(X), n_folds=10, random_state=seed)
  9. results = cross_val_score(pipeline, X, Y, cv=kfold)
  10. print("Standardized: %.2f (%.2f) MSE" % (results.mean(), results.std()))

效果直接好了一万刀:

  1. Standardized: 28.24 (26.25) MSE

也可以将数据标准化,在最后一层用S型函数作为激活函数,将比例拉到一样。

12.4 调整模型的拓扑

神经网络有很多可调的参数:最可玩的是网络的结构。这次我们用一个更深的和一个更宽的模型试试。

12.4.1 更深的模型

增加神经网络的层数可以提高效果,这样模型可以提取并组合更多的特征。我们试着加几层隐层:加几句话就行。代码从上面复制下来,在第一层后加一层隐层,神经元数量是上层的一半:

  1. def larger_model():
  2. # create model
  3. model = Sequential()
  4. model.add(Dense(13, input_dim=13, init='normal', activation='relu')) model.add(Dense(6, init='normal', activation='relu')) model.add(Dense(1, init='normal'))
  5. # Compile model
  6. model.compile(loss='mean_squared_error', optimizer='adam') return model

这样的结构是:

  1. 13 inputs -> [13 -> 6] -> 1 output

测试的方法一样,数据正则化一下:

  1. numpy.random.seed(seed)
  2. estimators = []
  3. estimators.append(('standardize', StandardScaler()))
  4. estimators.append(('mlp', KerasRegressor(build_fn=larger_model, nb_epoch=50, batch_size=5,
  5. verbose=0)))
  6. pipeline = Pipeline(estimators)
  7. kfold = KFold(n=len(X), n_folds=10, random_state=seed)
  8. results = cross_val_score(pipeline, X, Y, cv=kfold)
  9. print("Larger: %.2f (%.2f) MSE" % (results.mean(), results.std()))

效果好了一点,MSE从28变成24:

  1. Larger: 24.60 (25.65) MSE
12.4.1 更宽的模型

加宽模型可以增加网络容量。我们减去一层,把隐层的神经元数量加大,从13加到20:

  1. def wider_model():
  2. # create model
  3. model = Sequential()
  4. model.add(Dense(20, input_dim=13, init='normal', activation='relu')) model.add(Dense(1, init='normal'))
  5. # Compile model
  6. model.compile(loss='mean_squared_error', optimizer='adam') return model

网络的结构是:

  1. 13 inputs -> [20] -> 1 output

跑一下试试:

  1. numpy.random.seed(seed)
  2. estimators = []
  3. estimators.append(('standardize', StandardScaler()))
  4. estimators.append(('mlp', KerasRegressor(build_fn=wider_model, nb_epoch=100, batch_size=5,
  5. verbose=0)))
  6. pipeline = Pipeline(estimators)
  7. kfold = KFold(n=len(X), n_folds=10, random_state=seed)
  8. results = cross_val_score(pipeline, X, Y, cv=kfold)
  9. print("Wider: %.2f (%.2f) MSE" % (results.mean(), results.std()))

MSE下降到21,效果不错了。

  1. Wider: 21.64 (23.75) MSE

很难想到,加宽模型比加深模型效果更好:这就是欧皇的力量。

12.5 总结

本章关于使用Keras开发回归深度学习项目。总结一下:

  • 如何导入数据
  • 如何预处理数据提高性能
  • 如何调整网络结构提高性能
12.5.1 下一章

第三部分到此结束:你可以处理一般的机器学习问题了。下一章我们用一些奇技淫巧,使用一些Keras的高级API。