-1.使用的库
import numpy as np
import pandas as pd
from sklearn.model_selection import train_test_split
from sklearn.preprocessing import StandardScaler
from sklearn import linear_model
from sklearn.metrics import mean_squared_error
import tensorflow as tf
import copy
tf.random.set_seed(1234) # 设置好固定的随机数种子,以便重复实验
0.数据获取与预处理
# 下载数据并标准化
def download_data():
data_url = "http://lib.stat.cmu.edu/datasets/boston"
raw_df = pd.read_csv(data_url, sep="\s+", skiprows=22, header=None)
data_x = np.hstack([raw_df.values[::2, :], raw_df.values[1::2, :2]])
data_y = raw_df.values[1::2, 2]
data_x = StandardScaler().fit_transform(data_x)
# 数据集划分成训练集和测试集
train_x, test_x, train_y, test_y = train_test_split(data_x, data_y, test_size=0.1, random_state=1234)
# 从训练集中分出一点来作为验证集
train_x, verify_x, train_y, verify_y = train_test_split(train_x, train_y, test_size=1/9.0, random_state=1234)
return [train_x, verify_x, test_x, train_y, verify_y, test_y] # 训练集、验证集、测试集、训练集的y值、验证集的y值、测试集的y值
1.特征的选取
Boston房价数据有13个特征,只有500多个样例,这么多的特征和这么少的数据量,若不对特征加以筛选,可能难以训练出一个优秀的模型。
# 设置用于训练和预测的特征
price_factor = [True, # "CRIM":犯罪率
True, # "ZN": 超过25,000平方呎的住宅用地比例
True, # "INDUS":非零售商业面积的比例
True, # "CHAS" 查尔斯河道附近, 1是,0否
True, # "NOX":一氧化氮浓度(千万分之一)
True, # "RM": 每套住宅的平均房间数
True, # "AGE":1940年以前建造的自住单位的比例
True, # "DIS": 到波士顿五个就业中心的加权距离
True, # "RAD": 径向公路可达性指数
True, # "TAX":每1万美元的全额房产税税率
True, # "PTRATIO":城镇学生和教师的比例
True, # "B": 各城镇黑人的比例
True # "LSTAT": 人口的低出生率
]
# 先全选
# 按照price_factor选取特征
def select_factors(data):
new_data = []
for i in range(3):
new_data.append(copy.deepcopy(data[i][:, price_factor ])) # 对训练集、验证集、测试集都只保留price_factor中选取的特征
for i in range(3, len(data)):
new_data.append(copy.deepcopy(data[i]) ) # 填上训练集的y值、验证集的y值、测试集的y值
return new_data
# 使用sklearn库提供的岭回归训练模型
def linear_model_train(train_x, train_y):
my_model = linear_model.Ridge(alpha=0.06)
my_model.fit(train_x,train_y)
return my_model
if __name__ == "__main__":
data = download_data()
# find_best_feature(data)
train_x, verify_x, test_x ,train_y, verify_y, test_y = select_factors(data)
my_model = linear_model_train(train_x,train_y) # 训练
print(f"train_data_perform: MSE ")
print(mean_squared_error(train_y, my_model.predict(train_x) ))
print(f"verify_data_perform: MSE ")
print(mean_squared_error(verify_y, my_model.predict(verify_x) ))
直接将这13个特征都用来训练,结果如下train_data_perform: MSE
23.096444215174756
verify_data_perform: MSE
19.727919437983388
即训练集的均方差为23,验证集的均方差为19。作为一个初学者,想到的最简单粗暴的方法就是,对每种特征的组合都训练一个模型,并且比较它们验证集的均方差结果,则共会有2^13-1种组合,减1是因为不能一个特征都不选。这个可以用递归回溯法实现。
2. 模型的比较
这个模型的功能很简单,就是预测房价,最简单的就是直接使用sklearn包的一个线性回归单元完成,但我还想用一个小的神经网络进行比较一下效果,于是这里我使用了sklearn包里线性回归中的岭回归,并且使用tensorflow中的Sequential创建一个模型。岭回归的代码如上,神经网络的代码如下:
# 创建一个小神经网络
def creat_deep_learn_model():
# 创建模型时我直接指定了参数的初值,若不指定,则会随机产生,对于后续挑选表现最好的特征造成影响(即保持单一变量,只能变特征)
my_model = tf.keras.models.Sequential([
tf.keras.layers.Dense(units=5,activation="relu",kernel_initializer=tf.keras.initializers.Ones(),bias_initializer=tf.keras.initializers.Ones() ),
tf.keras.layers.Dense(units=1,activation="relu",kernel_initializer=tf.keras.initializers.Ones(),bias_initializer=tf.keras.initializers.Ones() )
])
my_model.compile(
loss="mse",
optimizer=tf.keras.optimizers.Adam(0.06)
)
return my_model
# 训练神经网络
def deep_learning_train(train_x, train_y):
my_model = creat_deep_learn_model()
my_model.fit(train_x, train_y, epochs=40, use_multiprocessing=True, workers=4, batch_size=128, verbose=0)
return my_model
这里调用fit函数时,将batch_size调整到128,以加快我们选取训练速度(后面挑选特征要训练八千多个模型)
创建一个函数来检验两种模型的表现
# 检查岭回归和神经网络在训练集和验证集的表现
def check_perform(model_list, train_x, train_y, verify_x, verify_y):
print(f"train_data_perform: MSE ")
for i in range(len(model_list) ):
print(f"{i} : {mean_squared_error(train_y, model_list[i].predict(train_x) )}")
print(f"verify_data_perform: MSE ")
for i in range(len(model_list) ):
pred_y = model_list[i].predict(verify_x)
print(f"{i} : {mean_squared_error(verify_y, pred_y)}")
main里改一改
if __name__ == "__main__":
data = download_data()
# find_best_feature(data)
train_x, verify_x, test_x ,train_y, verify_y, test_y = select_factors(data)
model_list = []
model_list.append(linear_model_train(train_x,train_y))
model_list.append(deep_learning_train(train_x,train_y))
check_perform(model_list, train_x, train_y, verify_x, verify_y)
两个模型表现如下:train_data_perform: MSE
0 : 23.096444215174756
13/13 [==============================] - 0s 2ms/step
1 : 30.51652489850927
verify_data_perform: MSE
0 : 19.727919437983388
2/2 [==============================] - 0s 1ms/step
1 : 17.252091274575182
初步观察,在训练集里,一个简单的线性回归要比神经网络表现要好,但在验证集里,又要差点,现在可能还难以分清谁好谁坏。我们开始选取我们的最佳特征
3.使用递归回溯算出2^13-1种特征组合里,最低的均方差
# 计算岭回归和神经网络的验证集的均方差
def get_mse(data):
train_x, verify_x, test_x ,train_y, verify_y, test_y = select_factors(data)
a = linear_model_train(train_x,train_y)
b = deep_learning_train(train_x,train_y)
return mean_squared_error(verify_y, a.predict(verify_x) ), mean_squared_error(verify_y, b(verify_x) )
min_linear_err = 10000000.0 # 岭回归最小均方差初始值
min_deep_learn_err = 10000000.0 # 神经网络最小均方差初始值
min_linear_features = [] # 岭回归最小均方差时的特征选取
min_deep_learn_features = [] # 神经网络最小均方差时的特征选取
all_zero = True # all_zero为true时表示一个特征也不选
# 回溯法找每一种特征选取的可能从全False到全True,共有2^13种组合
def find_func(idx, data):
global min_linear_err, min_deep_learn_err, min_linear_features, min_deep_learn_features, all_zero
length = len( price_factor )
if idx >= length: # 填充完整个price_factor后
if all_zero: # 首次进来肯定全False,要return掉,总不能一个特征都没有
all_zero = False # 后面就改成Fales
return
# 不是第一次进来就肯定非全False
a,b = get_mse(data) # 获取它们的线性回归和神经网络模型的均方误差
if a < min_linear_err:
min_linear_err = a
min_linear_features = copy.deepcopy(price_factor)
print(f"linear mse = {a}, {price_factor}")
if b < min_deep_learn_err:
min_deep_learn_err = b
min_deep_learn_features = copy.deepcopy(price_factor)
print(f"deep_learn mse = {b}, {price_factor}")
return
price_factor[idx] = False
find_func(idx+1,data)
price_factor[idx] = True
find_func(idx+1,data)
# 找最佳的特征选取
def find_best_feature(data):
for i in range(len(price_factor)):
price_factor[i] = False
find_func(0, data)
print(f"linear_min_mse = {min_linear_err}, features= {min_linear_features}")
print(f"deepLearn_min_mse = {min_deep_learn_err}, features= {min_deep_learn_features}")
main里面改成这样子
if __name__ == "__main__":
data = download_data()
find_best_feature(data)
最后跑了个把小时,输出的验证集的最小均方差为:linear_min_mse = 14.759951009440611, features= [True, False, False, True, False, True, True, True, True, True, True, False, False]
deepLearn_min_mse = 11.417357419353857, features= [True, False, True, False, True, True, True, True, True, True, True, True, False]
可见通过挑选出部分相关性更强特征组合在一起,最后的效果会更好,特别是数据少的情况下。我们再按神经网络模型最小均方差时的选出的特征,尝试修改一下模型参数进一步训练。
4. 调整参数重新训练神经网络
将编译模型时使用的学习率初值设低点,如0.04,将训练模型的的轮次增加到1000次,batch_size调到32
可得到,当特征选取为:True, False, True, False, True, True, True, True, True, True, True, True, False
神经网络训练集的均方差为:9.53064629681389,验证集的均方差为7.670203426797456
(每次训练可能得到的结果会浮动,但差距不会太大)
5.实验中存在的问题
而当特征选取为:True, False, False, True, False, True, True, True, True, True, True, False, False
岭回归的训练集均方差为:34.23614743088512,验证集均方差为:14.759951009440611
可见这个岭回归里训练集表现不好,验证集表现好,但因为这个选最优特征组合的,就是按验证集均方差最少的来选,故不太准确。但本次实验中,我们还是可以得到,经过调整后的神经网络还是要比单一的线性回归单元来预测房价,要更加准确的。
6.查看模型在测试集上的结果
在为岭回归选取最佳特征组合后,其测试集上的均方差为:test linear mse :14.14316351410805
,与岭回归验证集均方差差不多
在为神经网络选取最佳特征组合后,其在测试集上的均方差为:test deepLearn mse :10.750304093169447
,比神经网络的验证集的均方差略低
可见最后的结果使用一个小规模的神经网络还是要比线性回归单元要表现更优。