摘 要

在这次大作业中我完成了波士顿房价预测模型的实现, 波士顿房价预测是一个经典的回归模型。
在本次实验中, 首先对数据的分布情况以及特征信息, 相关性信息都进行了查看, 并分别对每个特征的相关性信息进行了分析, 并筛选掉无用的特征, 更好的对结果进行预测。
然后根据特征的信息与房价存在线性和非线性相关的关系, 这里依次选择了神经网络预测模型以及线性模型对房价的结果进行了预测, 并检验其效果。这里采用了 sklearn 中的库函数来进行训练集和测试集的划分,将 (30%) 的部分划分为测试集。
对于神经网络模型, 采用的是一个三层的全连接网络, 通过均方损失函数和 Adam 优化器对网络的参数进行更新, 最终使得网络可以更好的进行预测。对于线性模型, 采用了 sklearn 中的线性回归函数进行预测。
对于两者都进行了与实际值的对比, 并计算方差和相关系数, 从
而更好的对比了两者的效果差别。
关键词: 特征选择 神经网络 线性模型

目 录

1 背景介绍 1
1.1 问题描述 1
2 方法 1
3 代码实现 1
3.1 读取数据及预处理 1
3.2 神经网络预测模型实现 3
3.3 线性模型 6
4 试验结果 6
4.1 实验分析 6
4.2 对比 13
5 结论 16
6 总结 16
A 程序代码 17

1 背景介绍

1.1 问题描述

在本次的机器学习课程设计中需要选择一个项目, 应用机器学习算法到真实世界的任
务中去, 这里我选择了机器学习的经典案例: 波士顿房价预测任务。
波士顿房价数据说明: 此数据源于美国某经济学杂志上, 分析研究波士顿房价 (Boston HousePrice) 的数据集。数据集中的每一行数据都是对波士顿周边或城镇房价的情况描述, 下面对数据集变量说明, 数据集包含了 506 组数据, 一共 14 个属性, 为以下内容:
CRIM: 城镇人均犯罪率 ZN: 住宅用地所占比例
INDUS: 城镇中非住宅用地所占比例 CHAS: 虚拟变量, 用于回归分析 NOX: 环保指数
RM: 每栋住宅的房间数
AGE: 1940 年以前建成的自住单位的比例 DIS: 距离 5 个波士顿的就业中心的加权距离 RAD: 距离高速公路的便利指数 TAX: 每一万美元的不动产税率 PTRATIO: 城镇中的教师学生比例 B: 城镇中的黑人比例
LSTAT: 地区中有多少房东属于低收入人群 MEDV: 自住房屋房价中位数 (也就是均价)

2 方法

波士顿房价预测为一个回归模型, 回归模型的研究范围可以包括线性回归, 以及神经网络预测模型等多种, 这里实现了线性回归以及神经网络回归 [1]。

3 代码实现

3.1 读取数据及预处理

首先进行读取数据, 然后对数据预处理, 首先要清楚数据的分布信息, 这里使用了直方图以及箱图来观察, 然后依次查看数据的 13 个特征与房价之间的关系, 便于进行后续处理, 具体代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23

column_names = ['CRIM', 'ZN', 'INDUS', 'CHAS', 'NOX', 'RM', 'AGE', 'DIS',
'RAD', 'TAX', 'PTRATIO', 'B', 'LSTAT', 'PRICE']
all_data = pd.read_csv('./housing.csv', header=None,delimiter=r"\\&+",
names=column_names)
all_data.hist()
plt.show()

---
all_data.describe()
plt.figure(figsize=(20,10)) plt.boxplot(all_data) plt.show()
corr \( = \) all_data.corr()
plt.figure(figsize=(20, 10))
sns.heatmap(corr,annot=True, \({\text{cmap='twilight_r')}}^{{\prime}}\) data=all_data.iloc[:,:-1] label=all_data.iloc[:,-1]
data=np.array(data,dtype=float) label=np.array(label, dtype=float) for i in range(13):
plt.figure(figsize=(10,7)) plt.grid()
plt.scatter(data[:,i],label,s=5) \# 横纵坐标和点的大小 plt.title(column_names[i]) plt.show()
uns \(F = \lbrack\rbrack\#\) 次要特征下标
for i in range(data.shape[1]): if column_names [i] == 'CHAS': unsF.append(i)
data \( = \) np.delete(data,unsF,axis=1) \# 删除次要特征 uns \(T = \lbrack\rbrack\#\) 房价异常值下标
for i in range(data.shape[1]): if label[i] > 46: unsT.append(i)
data \( = \) np.delete(data,unsT,axis=0) \# 删除样本异常值数据
label \( = \) np.delete(label,unsT,axis=0) \# 删除异常房价

在查看完每个特征的信息后, 可以根据实际情况对数据无关部分以及异常的结果进行删改操作, 从而使得结果更加接近实际, 代码实现这里对于和结果相关性不大的特征进行了删除, 也去掉了部分异常高的房价。
在查看数据的分布信息后可以看到只有少部分数据的分布和房价是线性相关的, 大部分都不是明显的线性关系, 因此只采用线性模型进行预测可能会结果与实际差别较大, 因此这里对线性模型以及神经网络模型都进行了是实现, 并对比其结果的差别, 从而更好的进行结果分析。

3.2 神经网络预测模型实现

对于神经网络的模型, 这里使用的是简单的全连接方式, 用三层的网络来进行预测, 损失函数为均方损失, 优化器为 Adam, 使用 gpu 进行计算, 迭代次数为 1000 次, 训练过程的损失变化也记录下来, 然后将预测的房价与实际的房价进行对比, 即可得出预测的效果, 具体代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
data=torch.tensor(data, dtype=torch.float) label=torch.tensor(label, dtype=torch.float)
X_train, X_test, y_train, y_test = train_test_split(data,label, test_size
\( = 0.3\) ,random_state \( = 4\) )
def train(model, device, train_loader, optimizer, epoch, criterion): model.train() loss \( = 0.0\)
for i, (data, target) in enumerate(train_loader): data, target = data.to(device), target.to(device) optimizer.zero_grad() output \( = \) model (data)
loss = criterion(output, target.view_as(output)) loss.backward() optimizer.step() if i \% \(100 = = 0\) :
print('Train Epoch: {} Loss: {:.6f}'.format( epoch, loss.item()/len(train_loader)))
def test(model, device, test_loader, criterion):
model.eval()
test_loss \( = 0\)
with torch.no_grad():
for data, target in test_loader:
data, target = data.to(device), target.to(device) output \( = \) model (data)
test_loss += criterion(output, target.view_as(output)).item() \#
sum up batch loss
test_loss /= len(test_loader.dataset)
print('Test set: Average loss: {:.4f}\\n'.format( test_loss)) return test_loss class Net(nn.Module): def __init__(self):
super (Net, self).__init__() self.fc1 = nn.Linear \((12,128)\) self.fc2 = nn.Linear \((128,1)\) def forward(self, x): \(x = \operatorname{self}fc1(x)\) \(\mathrm{x} = \mathrm{F} {\cdot} \mathrm{relu}\left( \mathrm{x} \right)\) \(x = \operatorname{self}fc2(x)\) return \(x\)
device \( = \) torch.device("cuda" if torch.cuda.is_available() else "cpu") model=Net().to(device)
optimizer-torch.optim. Adam (params=model.parameters()) criterion=nn.MSELoss()
trainset=TensorDataset(X_train, y_train)
trainloader=DataLoader(trainset,batch_size=64,shuffle=True,num_workers=0) testset=TensorDataset(X_test, y_test)
testloader=DataLoader (testset,batch_size=64,shuffle=False,num_workers=0) epoch_list,loss_list \( = \lbrack\rbrack\) ,[] for epoch in range \((1,1000)\) :
---

train(model, device, trainloader, optimizer, epoch, criterion)
test_loss=test(model, device, testloader, criterion)
epoch_list.append(epoch)
loss_list.append(test_loss)
fig \( = \) plt.figure(figsize=(20,10))
plt.plot(epoch_list, loss_list)
plt.xlabel('epoch')
plt.ylabel('loss')
plt.title('error')
plt.show()
def read(test_loader):
model.eval()
output_list, target_list=[], []
with torch.no_grad():
for data, target in test_loader:
model.to('cpu')
output \( = \) model (data).detach().cpu().numpy()
output_list.extend(output)
target_list.extend(target.cpu().numpy())
p=pd.DataFrame(output_list, columns=['predict'])
p['real']=target_list
print(p.head())
return \(p\)

---

p=read(testloader)
error1 = mean_squared_error(p.iloc[:,1], p.iloc[:,0]).round(5) \# 平方差 score1 = r2_score(p.iloc[:,1],p.iloc[:,0]).round(5) \# 相关系数 plt.rcParams['font.family'] = "sans-serif" plt.rcParams ['font.sans-serif'] = "SimHei" plt.rcParams ['axes.unicode_minus'] = False fig1 = plt.figure(figsize=(20, 10))
plt.plot (range (p. shape[0]),p. iloc[:,1], color='red', linewidth=1, linestyle='-')
plt.plot (range (p. shape[0]), p. iloc[:,0], color='blue', linewidth=1,
linestyle='dashdot')
plt.legend(['真实值', '预测值'])
plt.title('神经网络预测值与准确率对比图')
error1 = "标准差d=" + str(error1)+"\\n"+"相关指数R^2="+str(score1) plt.xlabel(error1, size=18, color="green") plt.grid() plt.show()

3.3 线性模型

这里使用了传统的方法线性模型与神经网络的模型进行对比, 从而反映出效果, 这里的线性模型直接 sklearn 中的库函数来实现, 预测出房价后同样与实际进行对比, 并比较结果, 其代码实现较为简单, 代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17

1f = LinearRegression()
If.fit(X_train, y_train) \# 训练数据, 学习模型参数
y_predict = 1f.predict(X_test)
error \(2 = \) mean_squared_error (y_test.numpy(),y_predict).round(5) \#平方差
score \(2 = r2\) _score(y_test,y_predict).round(5)
fig2 = plt.figure(figsize=(20,10))
plt.plot (range(y_test.shape[0]), y_test, color='red', linewidth=1,
linestyle='-')
plt.plot (range (y_test.shape [0]), y_predict, color='blue', linewidth=1,
linestyle='dashdot')
plt.legend(['真实值', '预测值'])
plt.title('线性模型预测值与准确率对比图')
error \(2 = \) "标准差d=" + str(error2)+"\\n"+"相关指数R^2="+str(score2)
plt.xlabel(error2, size=18, color="green")
plt.grid()
plt.show()

4 试验结果

4.1 实验分析

实验过程中首先得到的直方图以及箱图, 以及相关系数矩阵的热力图如下图所示: 由不同特征与房价的直方图可以大致观察到不同特征的基本影响。
由盒图可以看到不同特征下, 数据的分布情况基本较好, 几乎没有异常值和离群点的
存在。
由热力图可以大致看出, 第四列和第四行, 即 CHAS 的颜色较深, 这说明其与其他特征的相关性较低, 这里将此特征作为重点排查对象, 此时并不能完全判断该特征是否合适,

图 1: 特征直方图

图 2: 盒图

图 3: 特征相关性热力图
还有看后续的信息。随后为了准确了解每个特征的信息, 这里分布对每个特征与房价的关系进行了可视化, 结果如下:
犯罪率: 高房价的房屋大都集中在低犯罪率地区, 有对预测结果有一定的参考价值

图 4: 犯罪率相关图

住宅用地比例: 与房价无明显的线性关系, 有一定相关性, 可以保留。

图 5: 住宅用地比例图

城镇中非商业用地的所占比例: 与房价无明显的线性关系, 只能说在某一区间内房价
呈现一定特征, 保留。
是否处于查尔斯河边 (1 表示在河边, 0 表示不在河边): 是否在查尔斯河边影响房价也
不明显, 因此考虑把此特征去除, 防止无关特征影响效果。
一氧化氮浓度: 一氧化氮浓度与房价的关系呈现极其微弱的线性关系, 一氧化氮低于

图 6: 城镇非商业用地比例图

图 7: 是否处于查尔斯河边图
0.5 的情况下, 房价绝大部分高于 15 。

图 8: 一氧化氮浓度相关图

每栋住宅的房间数: 与房价之间具有较强的线性关系, 保留。

图 9: 每栋住宅房间数图

1940 年以前建成的业主自住单位的占比: 对房价的影响较小, 但也可保留。
距离 5 个波士顿就业中心的平均距离: 平均距离较小的情况下, 房价对应也较低。距离高速公路的便利指数: 房价高于 30 的房产, 近乎都集中在距离高速公路的便利指
数低的地区, 有一定的相关性, 可以保留。
每一万美元的不动产税率: 与房价的线性相关度较小, 也可保留。城镇中学生教师比例: 对房价的影响较小, 呈微弱的线性关系。
黑人比例: 黑人比例对波士顿房价的影响尤其是往后的影响越趋于更小, 对结果预测

图 10: 1940 年前建成业主自住比例图

图 11: 距离 5 个波士顿就业中心的距离图

图 12: 距离高速公路的便利指数图

图 13: 每一万美元的不动产税率图

图 14: 城镇中学教师比例图

有一定的影响。

图 15: 黑人比例图

低收入阶层占比: 与房价具有较强的线性关系, 是影响房价的重要因素。
从以上对每个特征的分析, 我们可以得出, CHAS 特征与房价的关系非常小, 可以忽
略掉, 因此这里的特征仅去掉这一项。

4.2 对比

在数据处理完成后, 就是两个模型对于结果的预测。对于神经网络的模型, 其迭代过
程的损失变化如下图所示

图 16: 低收入阶层占比图

图 17: 迭代过程损失变化图
由图中可以看出, 损失在初期迅速下降, 随后缓慢下降, 最后达到了一个很低的水平,
说明模型在训练过程中性能越来越好。
随后是神经网络模型的预测值与实际值进行的对比图:

图 18: 神经网络预测值与准确率对比图

由图中可以看出, 绝大部分情况预测的准确度还是很高的, 在房价的峰值与谷值这里
出现了一定的误差。
线性模型的预测值和实际值对比图如下:

图 19: 线性模型预测值与准确率对比图

由图中可以看出, 预测效果也还不错, 但是预测的误差相对于神经网络还是高一些, 数据也反映出了这一点, 神经网络的标准差是 19.86254, 线性模型的标准差是 28.35625, 这说明了神经网络预测的的误差小,也更加稳定: 神经网络的相关系数 (R^{2}) 为 0.80978,线性模型的相关系数数 (R^{2}) 为 0.72844,神经网络的相关性要比线性模型的更好,与实际更加符合。

5 结论

线性模型和神经网络模型都能够较好的对房价进行预测, 神经网络预测的准确性相对
较好。

6 总结

由于期末时间问题, 课程复习相对较紧张, 而且还有很多事, 机器学习的大作业这里选取的是经典的题目来做, 并未进行创新性质的探索。但是在这次大作业中我也有很多收获, 代码能力得到了提升, 熟练度增加, 也提升了自己的综合能力。

参考文献

[1] 欧阳光. 正交回归最小二乘估计 [J]. 湘南学院学报,2021,42(2):1-5. DOI:10.3969/j.issn.1672-8173.2021.02.001.

A 程序代码

房价预测模型 - price_predict.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
#!/usr/bin/env python
# coding: utf-8


import sklearn
from sklearn import metrics
from sklearn.model_selection import train_test_split
import pandas as pd
import numpy as np
import torch
import torch.nn as nn
import torch.nn.functional as F
from torch.utils.data import DataLoader
from torch.utils.data import TensorDataset
import matplotlib.pyplot as plt
import seaborn as sns
from skimage.metrics import mean_squared_error
from sklearn.metrics import r2_score
from sklearn.linear_model import LinearRegression

column_names = ['CRIM', 'ZN', 'INDUS', 'CHAS', 'NOX', 'RM', 'AGE', 'DIS', 'RAD', 'TAX', 'PTRATIO', 'B', 'LSTAT',
'PRICE']
all_data = pd.read_csv('./housing.csv', header=None, delimiter=r"\s+", names=column_names)

all_data.hist()
plt.show()

all_data.describe()

plt.figure(figsize=(20, 10))
plt.boxplot(all_data)
plt.show()

corr = all_data.corr()

plt.figure(figsize=(20, 10))
sns.heatmap(corr, annot=True, cmap='twilight_r')

data = all_data.iloc[:, :-1]
label = all_data.iloc[:, -1]

data = np.array(data, dtype=float)
label = np.array(label, dtype=float)

for i in range(13):
plt.figure(figsize=(10, 7))
plt.grid()
plt.scatter(data[:, i], label, s=5) # 横纵坐标和点的大小
plt.title(column_names[i])
plt.show()

unsF = [] # 次要特征下标
for i in range(data.shape[1]):
if column_names[i] == 'CHAS':
unsF.append(i)
data = np.delete(data, unsF, axis=1) # 删除次要特征

unsT = [] # 房价异常值下标
for i in range(data.shape[1]):
if label[i] > 46:
unsT.append(i)
data = np.delete(data, unsT, axis=0) # 删除样本异常值数据
label = np.delete(label, unsT, axis=0) # 删除异常房价

data = torch.tensor(data, dtype=torch.float)
label = torch.tensor(label, dtype=torch.float)

X_train, X_test, y_train, y_test = train_test_split(data, label, test_size=0.3, random_state=4)


def train(model, device, train_loader, optimizer, epoch, criterion):
model.train()
loss = 0.0
for i, (data, target) in enumerate(train_loader):
data, target = data.to(device), target.to(device)
optimizer.zero_grad()
output = model(data)
loss = criterion(output, target.view_as(output))
loss.backward()
optimizer.step()
if i % 100 == 0:
print('Train Epoch: {} Loss: {:.6f}'.format(
epoch, loss.item() / len(train_loader)))


def test(model, device, test_loader, criterion):
model.eval()
test_loss = 0
with torch.no_grad():
for data, target in test_loader:
data, target = data.to(device), target.to(device)
output = model(data)

test_loss += criterion(output, target.view_as(output)).item() # sum up batch loss

test_loss /= len(test_loader.dataset)

print('Test set: Average loss: {:.4f}\n'.format(
test_loss))
return test_loss


class Net(nn.Module):
def __init__(self):
super(Net, self).__init__()
self.fc1 = nn.Linear(12, 128)
self.fc2 = nn.Linear(128, 1)

def forward(self, x):
x = self.fc1(x)
x = F.relu(x)
x = self.fc2(x)
return x


device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
model = Net().to(device)
optimizer = torch.optim.Adam(params=model.parameters())
criterion = nn.MSELoss()

trainset = TensorDataset(X_train, y_train)
trainloader = DataLoader(trainset, batch_size=64, shuffle=True, num_workers=0)
testset = TensorDataset(X_test, y_test)
testloader = DataLoader(testset, batch_size=64, shuffle=False, num_workers=0)

epoch_list, loss_list = [], []

for epoch in range(1, 1000):
train(model, device, trainloader, optimizer, epoch, criterion)
test_loss = test(model, device, testloader, criterion)
epoch_list.append(epoch)
loss_list.append(test_loss)

fig = plt.figure(figsize=(20, 10))
plt.plot(epoch_list, loss_list)
plt.xlabel('epoch')
plt.ylabel('loss')
plt.title('error')
plt.show()


def read(test_loader):
model.eval()
output_list, target_list = [], []
with torch.no_grad():
for data, target in test_loader:
model.to('cpu')
output = model(data).detach().cpu().numpy()
output_list.extend(output)
target_list.extend(target.cpu().numpy())
p = pd.DataFrame(output_list, columns=['predict'])
p['real'] = target_list
print(p.head())
return p


p = read(testloader)

error1 = mean_squared_error(p.iloc[:, 1], p.iloc[:, 0]).round(5) # 平方差
score1 = r2_score(p.iloc[:, 1], p.iloc[:, 0]).round(5) # 相关系数

plt.rcParams['font.family'] = "sans-serif"
plt.rcParams['font.sans-serif'] = "SimHei"
plt.rcParams['axes.unicode_minus'] = False
fig1 = plt.figure(figsize=(20, 10))
plt.plot(range(p.shape[0]), p.iloc[:, 1], color='red', linewidth=1, linestyle='-')
plt.plot(range(p.shape[0]), p.iloc[:, 0], color='blue', linewidth=1, linestyle='dashdot')
plt.legend(['真实值', '预测值'])
plt.title('神经网络预测值与准确率对比图')
error1 = "标准差d=" + str(error1) + "\n" + "相关指数R^2=" + str(score1)
plt.xlabel(error1, size=18, color="green")
plt.grid()
plt.show()

lf = LinearRegression()
lf.fit(X_train, y_train) # 训练数据,学习模型参数
y_predict = lf.predict(X_test)

error2 = mean_squared_error(y_test.numpy(), y_predict).round(5) # 平方差
score2 = r2_score(y_test, y_predict).round(5)

fig2 = plt.figure(figsize=(20, 10))
plt.plot(range(y_test.shape[0]), y_test, color='red', linewidth=1, linestyle='-')
plt.plot(range(y_test.shape[0]), y_predict, color='blue', linewidth=1, linestyle='dashdot')
plt.legend(['真实值', '预测值'])
plt.title('线性模型预测值与准确率对比图')
error2 = "标准差d=" + str(error2) + "\n" + "相关指数R^2=" + str(score2)
plt.xlabel(error2, size=18, color="green")
plt.grid()
plt.show()