如何用Pytorch搭建一个房价预测模型

一、项目介绍

在此项目中,目的是预测爱荷华州Ames的房价,给定81个特征,描述了房子、面积、土地、基础设施、公共设施等。埃姆斯数据集具有分类和连续特征的良好组合,大小适中,也许最重要的是,它不像其他类似的数据集(如波士顿住房)那样存在潜在的红线或数据输入问题。在这里我将主要讨论PyTorch建模的相关方面,作为一点额外的内容,我还将演示PyTorch中开发的模型的神经元重要性。你可以在PyTorch中尝试不同的网络架构或模型类型。本项目中的重点是方法论,而不是详尽地寻找最佳解决方案。

二、准备工作

为了准备这个项目,我们首先需要下载数据,并通过以下步骤进行一些预处理。

  1. from sklearn.datasets import fetch_openml
  2. data = fetch_openml(data_id=42165, as_frame=True)

关于该数据集的完整描述,你可以去该网址查看:https://www.openml.org/d/42165。

查看数据特征

  1. import pandas as pd
  2. data_ames = pd.DataFrame(data.data, columns=data.feature_names)
  3. data_ames[‘SalePrice’] = data.target
  4. data_ames.info()

下面是DataFrame的信息

  1. <class ‘pandas.core.frame.DataFrame’>
  2. RangeIndex: 1460 entries, 0 to 1459
  3. Data columns (total 81 columns):
  4. Id 1460 non-null float64
  5. MSSubClass 1460 non-null float64
  6. MSZoning 1460 non-null object
  7. LotFrontage 1201 non-null float64
  8. LotArea 1460 non-null float64
  9. Street 1460 non-null object
  10. Alley 91 non-null object
  11. LotShape 1460 non-null object
  12. LandContour 1460 non-null object
  13. Utilities 1460 non-null object
  14. LotConfig 1460 non-null object
  15. LandSlope 1460 non-null object
  16. Neighborhood 1460 non-null object
  17. Condition1 1460 non-null object
  18. Condition2 1460 non-null object
  19. BldgType 1460 non-null object
  20. HouseStyle 1460 non-null object
  21. OverallQual 1460 non-null float64
  22. OverallCond 1460 non-null float64
  23. YearBuilt 1460 non-null float64
  24. YearRemodAdd 1460 non-null float64
  25. RoofStyle 1460 non-null object
  26. RoofMatl 1460 non-null object
  27. Exterior1st 1460 non-null object
  28. Exterior2nd 1460 non-null object
  29. MasVnrType 1452 non-null object
  30. MasVnrArea 1452 non-null float64
  31. ExterQual 1460 non-null object
  32. ExterCond 1460 non-null object
  33. Foundation 1460 non-null object
  34. BsmtQual 1423 non-null object
  35. BsmtCond 1423 non-null object
  36. BsmtExposure 1422 non-null object
  37. BsmtFinType1 1423 non-null object
  38. BsmtFinSF1 1460 non-null float64
  39. BsmtFinType2 1422 non-null object
  40. BsmtFinSF2 1460 non-null float64
  41. BsmtUnfSF 1460 non-null float64
  42. TotalBsmtSF 1460 non-null float64
  43. Heating 1460 non-null object
  44. HeatingQC 1460 non-null object
  45. CentralAir 1460 non-null object
  46. Electrical 1459 non-null object
  47. 1stFlrSF 1460 non-null float64
  48. 2ndFlrSF 1460 non-null float64
  49. LowQualFinSF 1460 non-null float64
  50. GrLivArea 1460 non-null float64
  51. BsmtFullBATh 1460 non-null float64
  52. BsmtHalfBath 1460 non-null float64
  53. FullBath 1460 non-null float64
  54. HalfBath 1460 non-null float64
  55. BedroomAbvGr 1460 non-null float64
  56. KitchenAbvGr 1460 non-null float64
  57. KitchenQual 1460 non-null object
  58. TotRmsAbvGrd 1460 non-null float64
  59. Functional 1460 non-null object
  60. Fireplaces 1460 non-null float64
  61. FireplaceQu 770 non-null object
  62. GarageType 1379 non-null object
  63. GarageYrBlt 1379 non-null float64
  64. GarageFinish 1379 non-null object
  65. GarageCars 1460 non-null float64
  66. GarageArea 1460 non-null float64
  67. GarageQual 1379 non-null object
  68. GarageCond 1379 non-null object
  69. PavedDrive 1460 non-null object
  70. WoodDeckSF 1460 non-null float64
  71. OpenPorchSF 1460 non-null float64
  72. EnclosedPorch 1460 non-null float64
  73. 3SsnPorch 1460 non-null float64
  74. ScreenPorch 1460 non-null float64
  75. PoolArea 1460 non-null float64
  76. PoolQC 7 non-null object
  77. Fence 281 non-null object
  78. MiscFeature 54 non-null object
  79. MiscVal 1460 non-null float64
  80. MoSold 1460 non-null float64
  81. YrSold 1460 non-null float64
  82. SaleType 1460 non-null object
  83. SaleCondition 1460 non-null object
  84. SalePrice 1460 non-null float64
  85. dtypes: float64(38), object(43)
  86. memory usage: 924.0+ KB

接下来,我们还将使用一个库,即 captum,它可以检查 PyTorch 模型的特征和神经元重要性。

  1. pip install captum

在做完这些准备工作后,我们来看看如何预测房价。

三、实验过程

3.1数据预处理

在这里,首先要进行数据缩放处理,因为所有的变量都有不同的尺度。分类变量需要转换为数值类型,以便将它们输入到我们的模型中。我们可以选择一热编码,即我们为每个分类因子创建哑变量,或者是序数编码,即我们对所有因子进行编号,并用这些数字替换字符串。我们可以像其他浮动变量一样将虚拟变量送入,而序数编码则需要使用嵌入,即线性神经网络投影,在多维空间中对类别进行重新排序。我们在这里采取嵌入的方式。

  1. import numpy as np
  2. from category_encoders.ordinal import OrdinalEncoder
  3. from sklearn.preprocessing import StandardScaler
  4. num_cols = list(data_ames.select_dtypes(include=‘float’))
  5. cat_cols = list(data_ames.select_dtypes(include=‘object’))
  6. ordinal_encoder = OrdinalEncoder().fit(
  7.      data_ames[cat_cols]
  8. )
  9. standard_scaler = StandardScaler().fit(
  10.      data_ames[num_cols]
  11. )
  12. = pd.DataFrame(
  13.      data=np.column_stack([
  14.          ordinal_encoder.transform(data_ames[cat_cols]),
  15.          standard_scaler.transform(data_ames[num_cols])
  16.      ]),
  17.      columns=cat_cols + num_cols
  18. )

3.2拆分数据集

在构建模型之前,我们需要将数据拆分为训练集和测试集。在这里,我们添加了一个数值变量的分层。这可以确保不同的部分(其中五个)在训练集和测试集中都以同等的数量包含。

  1. np.random.seed(12)
  2. from sklearn.model_selection import train_test_split
  3. bins = 5
  4. sale_price_bins = pd.qcut(
  5.      X[‘SalePrice’], q=bins, labels=list(range(bins))
  6. )
  7. X_train, X_test, y_train, y_test = train_test_split(
  8.      X.drop(columns=‘SalePrice’),
  9.      X[‘SalePrice’],
  10.      random_state=12,
  11.      stratify=sale_price_bins
  12. )

3.3构建PyTorch模型

接下来开始建立我们的PyTorch模型。我们将使用PyTorch实现一个具有批量输入的神经网络回归,具体将涉及以下步骤。

  • 1. 将数据转换为Torch tensors
  • 2. 定义模型结构
  • 3. 定义损失标准和优化器。
  • 4. 创建一个批次的数据加载器
  • 5. 跑步训练

3.3.1.数据转换

首先将数据转换为torch tensors

  1. from torch.autograd import Variable
  2. num_features = list(
  3.      set(num_cols)  set([‘SalePrice’, ‘Id’])
  4. )
  5. X_train_num_pt = Variable(
  6.      torch.cuda.FloatTensor(
  7.          X_train[num_features].values
  8.      )
  9. )
  10. X_train_cat_pt = Variable(
  11.      torch.cuda.LongTensor(
  12.          X_train[cat_cols].values
  13.      )
  14. )
  15. y_train_pt = Variable(
  16.      torch.cuda.FloatTensor(y_train.values)
  17. ).view(-1, 1)
  18. X_test_num_pt = Variable(
  19.      torch.cuda.FloatTensor(
  20.          X_test[num_features].values
  21.      )
  22. )
  23. X_test_cat_pt = Variable(
  24.      torch.cuda.LongTensor(
  25.          X_test[cat_cols].values
  26.      ).long()
  27. )
  28. y_test_pt = Variable(
  29.      torch.cuda.FloatTensor(y_test.values)
  30. ).view(-1, 1)

这可以确保我们将数字和分类数据加载到单独的变量中,类似于NumPy。如果你把数据类型混合在一个变量(数组/矩阵)中,它们就会变成对象。我们希望把数值变量弄成浮点数,把分类变量弄成长(或int),索引我们的类别。我们还将训练集和测试集分开。显然,一个ID变量在模型中不应该是重要的。在最坏的情况下,如果ID与目标有任何相关性,它可能会引入目标泄漏。我们已经把它从这一步的处理中删除了。

3.3.2定义模型架构

  1. class RegressionModel(torch.nn.Module):
  2.      def __init__(self, X, num_cols, cat_cols, device=torch.device(‘cuda’), embed_dim=2, hidden_layer_dim=2, p=0.5):
  3.          super(RegressionModel, self).__init__()
  4.          self.num_cols = num_cols
  5.          self.cat_cols = cat_cols
  6.          self.embed_dim = embed_dim
  7.          self.hidden_layer_dim = hidden_layer_dim
  8.          self.embeddings = [
  9.              torch.nn.Embedding(
  10.                  num_embeddings=len(X[col].unique()),
  11.                  embedding_dim=embed_dim
  12.              ).to(device)
  13.              for col in cat_cols
  14.          ]
  15.          hidden_dim = len(num_cols) + len(cat_cols) * embed_dim,
  16.          # hidden layer
  17.          self.hidden = torch.nn.Linear(torch.IntTensor(hidden_dim), hidden_layer_dim).to(device)
  18.          self.dropout_layer = torch.nn.Dropout(p=p).to(device)
  19.          self.hidden_act = torch.nn.ReLU().to(device)
  20.          # output layer
  21.          self.output = torch.nn.Linear(hidden_layer_dim, 1).to(device)
  22.      def forward(self, num_inputs, cat_inputs):
  23.          ”’Forward method with two input variables –
  24.          numeric and categorical.
  25.          ”’
  26.          cat_x = [
  27.              torch.squeeze(embed(cat_inputs[:, i]  1))
  28.              for i, embed in enumerate(self.embeddings)
  29.          ]
  30.          x = torch.cat(cat_x + [num_inputs], dim=1)
  31.          x = self.hidden(x)
  32.          x = self.dropout_layer(x)
  33.          x = self.hidden_act(x)
  34.          y_pred = self.output(x)
  35.          return y_pred
  36. house_model = RegressionModel(
  37.      data_ames, num_features, cat_cols
  38. )

我们在两个线性层(上的激活函数是整流线性单元激活(ReLU)函数。这里需要注意的是,我们不可能将相同的架构(很容易)封装成一个顺序模型,因为分类和数值类型上发生的操作不同。

3.3.3定义损失准则和优化器

接下来,定义损失准则和优化器。我们将均方误差(MSE)作为损失,随机梯度下降作为我们的优化算法。

  1. criterion = torch.nn.MSELoss().to(device)
  2. optimizer = torch.optim.SGD(house_model.parameters(), lr=0.001)

3.3.4创建数据加载器

现在,创建一个数据加载器,每次输入一批数据。

  1. data_batch = torch.utils.data.TensorDataset(
  2.      X_train_num_pt, X_train_cat_pt, y_train_pt
  3. )
  4. dataloader = torch.utils.data.DataLoader(
  5.      data_batch, batch_size=10, shuffle=True
  6. )

我们设置了10个批次的大小,接下来我们可以进行训练了。

3.3.5.训练模型

基本上,我们要在epoch上循环,在每个epoch内推理出性能,计算出误差,优化器根据误差进行调整。这是在没有训练的内循环的情况下,在epochs上的循环。

  1. from tqdm.notebook import trange
  2. train_losses, test_losses = [], []
  3. n_epochs = 30
  4. for epoch in trange(n_epochs):
  5.      train_loss, test_loss = 0, 0
  6.      # print the errors in training and test:
  7.      if epoch % 10 == 0 :
  8.          print(
  9.              ‘Epoch: {}/{}t’.format(epoch, 1000),
  10.              ‘Training Loss: {:.3f}t’.format(
  11.                  train_loss / len(dataloader)
  12.              ),
  13.              ‘Test Loss: {:.3f}’.format(
  14.                  test_loss / len(dataloader)
  15.              )
  16.          )

训练是在这个循环里面对所有批次的训练数据进行的。

  1. for (x_train_num_batch,x_train_cat_batch,y_train_batch) in dataloader:
  2.          (x_train_num_batch,x_train_cat_batch, y_train_batch) = (
  3.                  x_train_num_batch.to(device),
  4.                  x_train_cat_batch.to(device),
  5.                  y_train_batch.to(device))
  6.          pred_ytrain = house_model.forward(x_train_num_batch, x_train_cat_batch)
  7.          loss = torch.sqrt(criterion(pred_ytrain, y_train_batch))
  8.          optimizer.zero_grad()
  9.          loss.backward()
  10.          optimizer.step()
  11.          train_loss += loss.item()
  12.          with torch.no_grad():
  13.              house_model.eval()
  14.              pred_ytest = house_model.forward(X_test_num_pt, X_test_cat_pt)
  15.              test_loss += torch.sqrt(criterion(pred_ytest, y_test_pt))
  16.          train_losses.append(train_loss / len(dataloader))
  17.          test_losses.append(test_loss / len(dataloader))

训练结果如下:

-1

我们取 nn.MSELoss 的平方根,因为 PyTorch 中 nn.MSELoss 的定义如下:

  1. ((inputtarget)**2).mean()

绘制一下我们的模型在训练期间对训练和验证数据集的表现。

  1. plt.plot(
  2.      np.array(train_losses).reshape((n_epochs, 1)).mean(axis=1),
  3.      label=‘Training loss’
  4. )
  5. plt.plot(
  6.      np.array(test_losses).reshape((n_epochs, 1)).mean(axis=1),
  7.      label=‘Validation loss’
  8. )
  9. plt.legend(frameon=False)
  10. plt.xlabel(‘epochs’)
  11. plt.ylabel(‘MSE’)

-2

在我们的验证损失停止下降之前,我们及时停止了训练。我们还可以对目标变量进行排序和bin,并将预测结果与之对比绘制,以便了解模型在整个房价范围内的表现。这是为了避免回归中的情况,尤其是用MSE作为损失,即你只对一个中值范围的预测很好,接近平均值,但对其他任何东西都做得不好。

-3

我们可以看到,事实上,这个模型在整个房价范围内的预测非常接近。事实上,我们得到的Spearman秩相关度约为93%,具有非常高的显著性,这证实了这个模型的表现具有很高的准确性。

四、原理讲解

深度学习神经网络框架使用不同的优化算法。其中流行的有随机梯度下降(SGD)、均方根推进(RMSProp)和自适应矩估计(ADAM)。我们定义了随机梯度下降作为我们的优化算法。另外,我们还可以定义其他优化器。

  1. opt_SGD = torch.optim.SGD(net_SGD.parameters(), lr=LR)
  2. opt_Momentum = torch.optim.SGD(net_Momentum.parameters(), lr=LR, momentum=0.6)
  3. opt_RMSprop = torch.optim.RMSprop(net_RMSprop.parameters(), lr=LR, alpha=0.1)
  4. opt_Adam = torch.optim.Adam(net_Adam.parameters(), lr=LR, betas=(0.8, 0.98))

SGD的工作原理与梯度下降相同,只是它每次只在一个例子上工作。有趣的是,收敛性与梯度下降相似,并且更容易占用计算机内存。

RMSProp的工作原理是根据梯度符号来调整算法的学习率。最简单的变体是检查最后两个梯度符号,然后调整学习率,如果它们相同,则增加一个分数,如果它们不同,则减少一个分数。

ADAM是最流行的优化器之一。它是一种自适应学习算法,根据梯度的第一和第二时刻改变学习率。

Captum是一个工具,可以帮助我们了解在数据集上学习的神经网络模型的来龙去脉。它可以帮助我们学习以下内容。

  • 特征重要性
  • 层级重要性
  • 神经元的重要性

这在学习可解释的神经网络中是非常重要的。在这里,综合梯度已经被应用于理解特征重要性。之后,还用层传导法来证明神经元的重要性。

五、补充

既然我们已经定义并训练了我们的神经网络,那么让我们使用 captum 库找到重要的特征和神经元。

  1. from captum.attr import (
  2.      IntegratedGradients,
  3.      LayerConductance,
  4.      NeuronConductance
  5. )
  6. house_model.cpu()
  7. for embedding in house_model.embeddings:
  8.      embedding.cpu()
  9. house_model.cpu()
  10. ing_house = IntegratedGradients(forward_func=house_model.forward, )
  11. #X_test_cat_pt.requires_grad_()
  12. X_test_num_pt.requires_grad_()
  13. attr, delta = ing_house.attribute(
  14.      X_test_num_pt.cpu(),
  15.      target=None,
  16.      return_convergence_delta=True,
  17.      additional_forward_args=X_test_cat_pt.cpu()
  18. )
  19. attr = attr.detach().numpy()

现在,我们有了一个NumPy的特征重要性数组。层和神经元的重要性也可以用这个工具获得。让我们来看看我们第一层的神经元importances。我们可以传递house_model.act1,这是第一层线性层上面的ReLU激活函数。

  1. cond_layer1 = LayerConductance(house_model, house_model.act1)
  2. cond_vals = cond_layer1.attribute(X_test, target=None)
  3. cond_vals = cond_vals.detach().numpy()
  4. df_neuron = pd.DataFrame(data = np.mean(cond_vals, axis=0), columns=[‘Neuron Importance’])
  5. df_neuron[‘Neuron’] = range(10)

-4

这张图显示了神经元的重要性。显然,一个神经元就是不重要的。我们还可以通过对之前得到的NumPy数组进行排序,看到最重要的变量。

  1. df_feat = pd.DataFrame(np.mean(attr, axis=0), columns=[‘feature importance’] )
  2. df_feat[‘features’] = num_features
  3. df_feat.sort_values(
  4.      by=‘feature importance’, ascending=False
  5. ).head(10)

这里列出了10个最重要的变量

-1

通常情况下,特征导入可以帮助我们既理解模型,又修剪我们的模型,使其变得不那么复杂(希望减少过度拟合)。

到此这篇关于如何用Pytorch搭建一个房价预测模型的文章就介绍到这了,更多相关Pytorch房价预测内容请搜索我们以前的文章或继续浏览下面的相关文章希望大家以后多多支持我们!

标签

发表评论