基于sklearn岭回归的波士顿房价预测

0.写在前面

  波士顿房价预测案例提供5份文件,分别是:训练数据集 train.csv, 测试数据集 test.csv, 测试结果集 sample_submission.csv, 字段说明文档 data_description.txt, 本案例代码的预测结果 submission_df.xlsx。文件下载:

链接:https://pan.baidu.com/s/1-KZB5IQATI2h_EnQspiw6A
提取码:w2jp

  本案例主要通过pandas读取数据,通过sklearn进行超参数调优和岭回归训练,主要目的是通过该案例熟悉特征工程和sklearn,不注重预测结果的准确度。

1. 导入训练数据,使用pandas展示数据

import numpy as np
import pandas as pd

train_df = pd.read_csv(file, index_col=0)
print(train_df.head(10))

基于sklearn岭回归的波士顿房价预测
  观察数据可得,训练数据80列(pd.read_csv(file, index_col=0) 中的index_col=0已经将第1列的序号作为索引,因此不再属于训练数据),其中有79个可作为特征,最后1列是房价。

2 .观察房价数据的分布特征

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

train_df = pd.read_csv(file, index_col=0)
# 绘制房价的直方图
plt.hist(train_df['SalePrice'])
plt.show()

基于sklearn岭回归的波士顿房价预测
  可见,房价数据本身并不平滑,为了我们分类器的学习更加准确,我们会首先把房价数据变为标准的正态分布。通常使用ln(x+1)函数将数据转化为服从正态分布的数据,使用ln(x+1)同时能够避免复值问题,复值是指:一个自变量对应多个因变量。在python中使用np.log1p()实现ln(x+1)。如果我们这里把数据都给平滑化了,那么最后算结果的时候,得把预测到的平滑数据给变回去。按照“怎么来的怎么去”原则,log1p()就需要expm1()。

使用np.log1p()转换房价后的图像:

基于sklearn岭回归的波士顿房价预测

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt

train_df = pd.read_csv(file, index_col=0)
np.log1p(train_df['SalePrice'])})
plt.hist(np.log1p(train_df['SalePrice']))
plt.show()

3. 编写数据导入函数

  上面的第1步和第2步是对数据进行观察,不属于代码的正式内容,从第3步开始,属于代码的正式内容。首先要做的是读取训练数据和测试数据,并将训练数据的特征和房价分开存放,同时将分离出房价的训练数据和测试数据合并,并且把房价进行正态分布转化。需要注意的是必须将训练数据和测试数据合并后统一进行特征工程,如果分开处理会出现最终训练数据和测试数据的特征个数不一致的问题,这是因为有些值为分类数据(如A\B\C\D)不是数值,如果训练集中某一个分类字段只出现了A/B/C三个选项,而测试集中出现了A/B/C/D四个选项,若将训练集和测试集分开进行特征工程,那么训练集合比测试集少一个选项为D的特征,在测试时会出现特征个数不一样的问题导致程序无法进行下去。

# 导入数据
def import_data(train_file, test_file):
    # 读取训练数据文件
    train_df = pd.read_csv(train_file, index_col=0)

    # 从训练集中弹出房价并将房价转化为正态分布
    y_train = np.log1p(train_df.pop('SalePrice'))

    # 读取测试数据
    test_df = pd.read_csv(test_file, index_col=0)

    # 特征合并
    all_df = pd.concat((train_df, test_df), axis=0)

    return all_df, y_train, train_df, test_df

  返回值说明:all_df(合并后的特征集), y_train(训练数据的房价集), train_df(不含房价的训练集), test_df(测试集,不含房价)。

  代码中需要注意的是需要将原始的训练集(train_df)和测试集(test_df)返回,这是因为在后面我们还需要将合并后的数据集再分开,分开时需要用到这两个原始集合,通过这个两个原始集的index区分开。

4.特征转化

[1] 将所有代表分类的字段进行特征衍生

  数据集中会有一些分类字段,例如"HouseStyle"下对应多个属性,我们需要将这些有分类的特征拆解为多个特征,要被拆解的特征有几个值就要拆解成几个特征,拆解后的特征值为0或1.我们这里用一个与案例中的数据集无关的例子介绍一下如何拆解,例如我们有一份数据集,其中有一个“性别”特征,这个特征对应两个值“男性”或“女性”,因此我们需要将“性别”这个特征拆解为“是否为男性”和“是否为女性”两个特征,这两个新特征的值要么为0要么为1,0代表 “否”,1代表“是”。在python中使用“pd.get_dummies()”函数即可完成这个工作。

  拆解特征时需要注意,有些数据会有数字代表分类信息,例如表示性别时用代替“1”代表“男性”,用“0”代表“女性”,而这些数字本身是没有数学意义的,因此不能让它作为数字参与模型的训练,需要先将这些字段从数字转换为str,再进行分解。例如本案例中,“MSSubClass”字段表示住房类型时就用的数字表示,因此需要先将该字段转换为str,再进行拆解特征。python代码如下:

#将分类特征转化为多个特征
#1.将数字分类的转为str
 all_df['MSSubClass'] = all_df['MSSubClass'].astype(str)
#2.将数据集中的所有分类特征拆解
 all_df = pd.get_dummies(all_df)
print(all_df.head(2))

[2] 处理缺失数据

  先查看各特征缺失值的情况。

print(all_df.isnull().sum().sort_values(ascending=False).head(10))

基于sklearn岭回归的波士顿房价预测
  这里我们使用各个特征的平均值来填充这些缺失值。

all_df = all_df.fillna(mean_cols)

​ 【1】【2】两步整合后的代码为:

# 数据清洗
def data_cleaning(all_df):
    # 将分类特征转化为多个特征
    # 1.将数字分类的转为str
    all_df['MSSubClass'] = all_df['MSSubClass'].astype(str)
    # 2.将数据集中的所有分类特征拆解
    all_df = pd.get_dummies(all_df)
    # 处理空值
    # 1.计算每一个特征的均值
    mean_cols = all_df.mean()
    # 2.填充空值
    dummy_all_df = all_df.fillna(mean_cols)
    return dummy_all_df

[3] 特征缩放【归一化或标准化】

  将所有值为数字的特征进行特征缩放(除[1]中特征衍生出来的分类特征,因为分类特征的值要么是0要么是1,因此进行特征缩放没有意义),将这些特征的值转换为标准的正态分布数据。特征缩放可以采用归一化或标准化,两者有不同,此处采用标准化,关于归一化与标准化的不同可参考下一篇文章。标准化采用的公式是:

                    基于sklearn岭回归的波士顿房价预测
其中,u为对应特征的均值,delta为标准差。

# 数据标准化处理
def data_tandardization(dummy_all_df, train_df, test_df):
    # 获取所有值为数字的列,排除分类数据
    numeric_cols = dummy_all_df.columns[dummy_all_df.dtypes != 'uint8']
    # 提取所有的列并计算每一列的均值
    numeric_cols_mean = dummy_all_df.loc[:, numeric_cols].mean()
    # 提取所有的列并计算标准差
    numeric_cols_std = dummy_all_df.loc[:, numeric_cols].std()
    # 将所有值为数字的特征列替换为标准化后的值
    dummy_all_df.loc[:, numeric_cols] = (dummy_all_df.loc[:, numeric_cols] - numeric_cols_mean) / numeric_cols_std
    # 分回训练集测试集合
    dummy_train_df = dummy_all_df.loc[train_df.index]
    dummy_test_df = dummy_all_df.loc[test_df.index]

    return dummy_train_df, dummy_test_df

  代码中需要注意的是,在这一步时要将标准化后的数据在分成训练集和数据集。

5.超参数调优

  [1] 使用sklearn中的交叉验证对岭回归进行超参数调优,此处的代码基本固定。python代码如下:

# 岭回归交叉验证
def ridge_regression(dummy_train_df, y_train):
    X_train = dummy_train_df.values
    alphas = np.logspace(-3, 2, 50)
    test_scores = []
    for alpha in alphas:
        clf = Ridge(alpha)
        test_score = np.sqrt(-cross_val_score(clf, X_train, y_train, cv=10, scoring='neg_mean_squared_error'))
        test_scores.append(np.mean(test_score))
    plt.plot(alphas, test_scores)
    plt.title("Alpha vs CV Error")
    plt.show()

  岭回归对应的Alpha和错误率图像如下:通过图像可以看出,当Alpha大约在10-20之间时错误率最低。
基于sklearn岭回归的波士顿房价预测
  clf = Ridge(alpha):使用岭回归,设定一个alpha参数。
  cross_val_score(clf, X_train, y_train, cv=10, scoring=‘neg_mean_squared_error’):交叉验证,clf为选择的模型,X_train是特征,y_train是结果集,cv是交叉验证的迭代器,一般设置为10。
  scoring: 打分参数,在回归问题中,scoring一般选择为 'neg_mean_squared_error‘ 也就是均方差回归损失,这里要注意的是,上面的cross_val_score返回值都是负数,为什么均方误差会出现负数的情况呢?因为这里的neg_mean_squared_error是一种损失函数,优化的目标的使其最小化,而分类准确率是一种奖励函数,优化的目标是使其最大化,因此观察图像时需要添加负号,只需找图像中的最小值点即可。而对打分结果进行np.sqrt()处理是使打分结果之间的差距变大,使绘制出来的曲线更加明显,更容易观察出最小值。

6.传入alpha和测试集,输出预测数据。

  使用sklearn时,此处的代码基本通用,流程是:调用模型–使用训练集和结果集训练模型–使用测试集和模型得到预测结果。代码如下:

# 预测
def model_train(alpha, dummy_train_df, y_train, dummy_test_df):
    #调用岭回归模型
    clf = Ridge(alpha=alpha)
    #训练模型
    clf.fit(dummy_train_df, y_train)
    #预测值
    predicted = clf.predict(dummy_test_df)
    return np.expm1(predicted)

7.Main函数

if __name__ == '__main__':
    # 1.读取数据
    all_df, y_train, train_df, test_df = import_data("input/train.csv", "input/test.csv")

    # 2.数据清洗
    dummy_all_df = data_cleaning(all_df)
    dummy_all_df.to_excel("C:/Users/Administrator/Desktop/dummy_all_df.xlsx")

    # 3.标准化处理
    dummy_train_df, dummy_test_df = data_tandardization(dummy_all_df, train_df, test_df)
    dummy_train_df.to_excel("C:/Users/Administrator/Desktop/dummy_train_df.xlsx")

    # 4.调参数
    # ridge_regression(dummy_train_df, y_train)

    # 预测
    predicted = model_train(15, dummy_train_df, y_train, dummy_test_df)
    submission_df = pd.DataFrame(data={'Id': test_df.index, 'SalePrice': predicted})
    submission_df.to_excel("input/submission_df.xlsx")

8.完整代码

import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
from sklearn.linear_model import Ridge
from sklearn.model_selection import cross_val_score


# 导入数据
def import_data(train_file, test_file):
    # 读取训练数据文件
    train_df = pd.read_csv(train_file, index_col=0)

    # 从训练集中弹出房价并将房价转化为正态分布
    y_train = np.log1p(train_df.pop('SalePrice'))

    # 读取测试数据
    test_df = pd.read_csv(test_file, index_col=0)

    # 特征合并
    all_df = pd.concat((train_df, test_df), axis=0)

    return all_df, y_train, train_df, test_df


# 数据清洗
def data_cleaning(all_df):
    # 将分类特征转化为多个特征
    # 1.将数字分类的转为str
    all_df['MSSubClass'] = all_df['MSSubClass'].astype(str)
    # 2.将数据集中的所有分类特征拆解
    all_df = pd.get_dummies(all_df)
    # 处理空值
    # 1.计算每一个特征的均值
    mean_cols = all_df.mean()
    # 2.填充空值
    dummy_all_df = all_df.fillna(mean_cols)
    return dummy_all_df


# 数据标准化处理
def data_tandardization(dummy_all_df, train_df, test_df):
    # 获取所有值为数字的列,排除分类数据
    numeric_cols = dummy_all_df.columns[dummy_all_df.dtypes != 'uint8']
    # 提取所有的列并计算每一列的均值
    numeric_cols_mean = dummy_all_df.loc[:, numeric_cols].mean()
    # 提取所有的列并计算标准差
    numeric_cols_std = dummy_all_df.loc[:, numeric_cols].std()
    # 将所有值为数字的特征列替换为标准化后的值
    dummy_all_df.loc[:, numeric_cols] = (dummy_all_df.loc[:, numeric_cols] - numeric_cols_mean) / numeric_cols_std
    # 分回训练集测试集合
    dummy_train_df = dummy_all_df.loc[train_df.index]
    dummy_test_df = dummy_all_df.loc[test_df.index]

    return dummy_train_df, dummy_test_df


# 岭回归交叉验证
def ridge_regression(dummy_train_df, y_train):
    X_train = dummy_train_df.values
    alphas = np.logspace(-3, 2, 50)
    test_scores = []
    for alpha in alphas:
        clf = Ridge(alpha)
        test_score = np.sqrt(-cross_val_score(clf, X_train, y_train, cv=10, scoring='neg_mean_squared_error'))
        test_scores.append(np.mean(test_score))
    plt.plot(alphas, test_scores)
    plt.title("Alpha vs CV Error")
    plt.show()


# 预测
def model_train(alpha, dummy_train_df, y_train, dummy_test_df):
    #调用岭回归模型
    clf = Ridge(alpha=alpha)
    #训练模型
    clf.fit(dummy_train_df, y_train)
    #预测值
    predicted = clf.predict(dummy_test_df)
    return np.expm1(predicted)


if __name__ == '__main__':
    # 1.读取数据
    all_df, y_train, train_df, test_df = import_data("input/train.csv", "input/test.csv")

    # 2.数据清洗
    dummy_all_df = data_cleaning(all_df)
    # dummy_all_df.to_excel("C:/Users/Administrator/Desktop/dummy_all_df.xlsx")

    # 3.标准化处理
    dummy_train_df, dummy_test_df = data_tandardization(dummy_all_df, train_df, test_df)
    # dummy_train_df.to_excel("C:/Users/Administrator/Desktop/dummy_train_df.xlsx")

    # 4.调参数
    # ridge_regression(dummy_train_df, y_train)

    # 预测
    predicted = model_train(15, dummy_train_df, y_train, dummy_test_df)
    submission_df = pd.DataFrame(data={'Id': test_df.index, 'SalePrice': predicted})
    submission_df.to_excel("input/submission_df.xlsx)

  项目结构:
基于sklearn岭回归的波士顿房价预测