数据

数据集来自:https://www.kaggle.com/harlfoxem/housesalesprediction

此数据集是King County地区2014年五月至2015年五月的房屋销售信息,适合于训练回归模型。

数据字段解读

  • id:唯一id
  • date:售出日期
  • price:售出价格(标签值)
  • bedrooms:卧室数量
  • bathrooms:浴室数量
  • sqft_living:居住面积
  • sqft_lot:停车场面积
  • floors:楼层数
  • waterfront:泳池
  • view:有多少次看房记录
  • condition:房屋状况
  • grade:评级
  • sqft_above:地面上的面积
  • sqft_basement:地下室的面积
  • yr_built:建筑年份
  • yr_renovated:翻修年份
  • zipcode:邮政编码
  • lat:维度
  • long:经度
  • sqft_living15:2015年翻修后的居住面积
  • sqft_lot15:2015年翻修后的停车场面积

一些考虑:

  • 唯一id在数据库中有用,在训练时并不是一个特征,所以要去掉
  • 售出日期,由于是在一年内的数据,所以也没有用
  • sqft_liging15的值,如果非0的话,应该替换掉sqft_living
  • sqft_lot15的值,如果非0的话,应该替换掉sqft_lot
  • 邮政编码对应的地理位置过于宽泛,只能引起噪音,应该去掉
  • 返修年份,笔者认为它如果是非0值的话,可以替换掉建筑年份
  • 看房记录次数多并不能代表该房子价格就高,而是因为地理位置、价格、配置等满足特定人群的要求,所以笔者认为它不是必须的特征值

所以最后只留下13个字段。

数据处理

原始数据只有一个数据集,所以需要我们自己把它分成训练集和测试集,比例大概为4:1。此数据集为csv文件格式,为了方便,我们把它转换成了两个扩展名为npz的numpy压缩形式:

  • house_Train.npz,训练数据集
  • house_Test.npz,测试数据集

加载数据

与上面第一个例子的代码相似,但是房屋数据属性繁杂,所以需要做归一化,房屋价格也是至少6位数,所以也需要做归一化。

这里有个需要注意的地方,即训练集和测试集的数据,需要合并在一起做归一化,然后再分开使用。为什么要先合并呢?假设训练集样本中的房屋面积的范围为150到220,而测试集中的房屋面积有可能是160到230,两者不一致。分别归一化的话,150变成0,160也变成0,这样预测就会产生误差。

最后还需要在训练集中用GenerateValidaionSet(k=10)分出一个1:9的验证集。

搭建模型

在不知道一个问题的实际复杂度之前,我们不妨把模型设计得复杂一些。如下图所示,这个模型包含了四组全连接层-Relu层的组合,最后是一个单输出做拟合。

回归任务 - 房价预测 - 图1

  1. def model():
  2. dr = LoadData()
  3. num_input = dr.num_feature
  4. num_hidden1 = 32
  5. num_hidden2 = 16
  6. num_hidden3 = 8
  7. num_hidden4 = 4
  8. num_output = 1
  9. max_epoch = 1000
  10. batch_size = 16
  11. learning_rate = 0.1
  12. params = HyperParameters_4_0(
  13. learning_rate, max_epoch, batch_size,
  14. net_type=NetType.Fitting,
  15. init_method=InitialMethod.Xavier,
  16. stopper=Stopper(StopCondition.StopDiff, 1e-7))
  17. net = NeuralNet_4_0(params, "HouseSingle")
  18. fc1 = FcLayer_1_0(num_input, num_hidden1, params)
  19. net.add_layer(fc1, "fc1")
  20. r1 = ActivationLayer(Relu())
  21. net.add_layer(r1, "r1")
  22. ......
  23. fc5 = FcLayer_1_0(num_hidden4, num_output, params)
  24. net.add_layer(fc5, "fc5")
  25. net.train(dr, checkpoint=10, need_test=True)
  26. output = net.inference(dr.XTest)
  27. real_output = dr.DeNormalizeY(output)
  28. mse = np.sum((dr.YTestRaw - real_output)**2)/dr.YTest.shape[0]/10000
  29. print("mse=", mse)
  30. net.ShowLossHistory()
  31. ShowResult(net, dr)

超参数说明:

  1. 学习率=0.1
  2. 最大epoch=1000
  3. 批大小=16
  4. 拟合网络
  5. 初始化方法Xavier
  6. 停止条件为相对误差1e-7

net.train()函数是一个阻塞函数,只有当训练完毕后才返回。

在train后面的部分,是用测试集来测试该模型的准确度,使用了数据城堡(Data Castle)的官方评测方法,用均方差除以10000,得到的数字越小越好。一般的模型大概是一个7位数的结果,稍微好一些的是6位数。

训练结果

回归任务 - 房价预测 - 图2

由于标签数据也做了归一化,变换为都是0至1间的小数,所以均方差的数值很小,需要观察小数点以后的第4位。从图14-6中可以看到,损失函数值很快就降到了0.0002以下,然后就很缓慢地下降。而精度值在不断的上升,相信更多的迭代次数会带来更高的精度。

再看下面的打印输出部分,用R2_Score法得到的值为0.841,而用数据城堡官方的评测标准,得到的MSE值为2384411,还比较大,说明模型精度还应该有上升的空间。

  1. ......
  2. epoch=999, total_iteration=972999
  3. loss_train=0.000079, accuracy_train=0.740406
  4. loss_valid=0.000193, accuracy_valid=0.857289
  5. time used: 193.5549156665802
  6. testing...
  7. 0.8412989144927305
  8. mse= 2384411.5840510926

代码位置

原代码位置:ch14, Level2

个人代码:HousePriceRegression**

keras实现

  1. from MiniFramework.DataReader_2_0 import *
  2. from keras.models import Sequential
  3. from keras.layers import Dense
  4. import matplotlib.pyplot as plt
  5. import os
  6. os.environ['KMP_DUPLICATE_LIB_OK']='True'
  7. def load_data():
  8. train_file = "../data/ch14.house.train.npz"
  9. test_file = "../data/ch14.house.test.npz"
  10. dataReader = DataReader_2_0(train_file, test_file)
  11. dataReader.ReadData()
  12. dataReader.NormalizeX()
  13. dataReader.NormalizeY(NetType.Fitting)
  14. dataReader.Shuffle()
  15. dataReader.GenerateValidationSet(k=10)
  16. return dataReader
  17. def gen_data(dataReader):
  18. x_train, y_train = dataReader.XTrain, dataReader.YTrain
  19. x_test, y_test = dataReader.XTest, dataReader.YTest
  20. x_val, y_val = dataReader.XDev, dataReader.YDev
  21. return x_train, y_train, x_test, y_test, x_val, y_val
  22. def showResult(net, dr):
  23. y_test_result = net.predict(dr.XTest[0:1000, :])
  24. y_test_real = dr.DeNormalizeY(y_test_result)
  25. plt.scatter(y_test_real, y_test_real - dr.YTestRaw[0:1000, :], marker='o', label='test data')
  26. plt.show()
  27. def build_model():
  28. model = Sequential()
  29. model.add(Dense(32, activation='relu', input_shape=(13, )))
  30. model.add(Dense(16, activation='relu'))
  31. model.add(Dense(8, activation='relu'))
  32. model.add(Dense(4, activation='relu'))
  33. model.add(Dense(1, activation='linear'))
  34. model.compile(optimizer='Adam',
  35. loss='mean_squared_error')
  36. return model
  37. #画出训练过程中训练和验证的精度与损失
  38. def draw_train_history(history):
  39. plt.plot(history.history['loss'])
  40. plt.plot(history.history['val_loss'])
  41. plt.title('model loss')
  42. plt.ylabel('loss')
  43. plt.xlabel('epoch')
  44. plt.legend(['train', 'validation'], loc='upper left')
  45. plt.show()
  46. if __name__ == '__main__':
  47. dataReader = load_data()
  48. x_train, y_train, x_test, y_test, x_val, y_val = gen_data(dataReader)
  49. model = build_model()
  50. history = model.fit(x_train, y_train, epochs=50, batch_size=16, validation_data=(x_val, y_val))
  51. draw_train_history(history)
  52. showResult(model, dataReader)
  53. loss = model.evaluate(x_test, y_test)
  54. print("test loss: {}".format(loss))
  55. weights = model.get_weights()
  56. print("weights: ", weights)

模型输出

  1. test loss: 0.0003824683979731346
  2. weights: [array([[-7.80718327e-02, -3.10383588e-01, 4.49726075e-01,
  3. ...
  4. [-0.21321435],
  5. [ 0.43942693],
  6. [-0.88441443]], dtype=float32), array([0.01937102], dtype=float32)]

模型损失曲线

回归任务 - 房价预测 - 图3