机械学习——相对沿海位置的房价预测

一、选题的背景

  为了探索经纬度、靠海距离、房屋价值等属性对于一个地区房价的影响,选用了加利福尼亚住房数据集,该数据集包含了20640个街区的信息。每一行表示一个街区,共有10个属性,分别是经度、纬度、房屋年龄中位数、总房间数、总卧室数、人口数、家庭数、收入中位数、房屋价值中位数、离大海的距离。该项目可为房地产公司分析房价提供一些参考。

二、机器学习案例设计方案

1.本选题采用的机器学习案例(训练集与测试集)的来源描述

  该数据集可从LuísTorgo的页面(波尔图大学)获得。LuísTorgo从StatLib存储库(现已关闭)获取它。也可以从StatLib镜像下载数据集。

2 采用的机器学习框架描述

线性回归:

线性回归分为三步:

(1)假设目标值(因变量)与特征值(自变量)之间线性相关(即满足一个多元一次方程,如:f(x)=w1x1+…+wnxn+b.)。

(2)然后构建损失函数。

(3)最后通过令损失函数最小来确定参数。

决策树:

决策树在逻辑上以树的形式存在,包含根节点、内部结点和叶节点。

- 根节点:包含数据集中的所有数据的集合

- 内部节点:每个内部节点为一个判断条件,并且包含数据集中满足从根节点到该节点所有条件的数据的集合。根据内部结点的判断条件测试结果,内部节点对应的数据的集合别分到两个或多个子节点中。

- 叶节点:叶节点为最终的类别,被包含在该叶节点的数据属于该类别。

随机森林:

随机森林有4个步骤:

(1)一个样本容量为N的样本,有放回的抽取N次,每次抽取1个,最终形成了N个样本。这选择好了的N个样本用来训练一个决策树,作为决策树根节点处的样本。

(2)当每个样本有M个属性时,在决策树的每个节点需要分裂时,随机从这M个属性中选取出m个属性,满足条件m << M。然后从这m个属性中采用某种策略(比如说信息增益)来选择1个属性作为该节点的分裂属性。

(3)决策树形成过程中每个节点都要按照步骤2来分裂(很容易理解,如果下一次该节点选出来的那一个属性是刚刚其父节点分裂时用过的属性,则该节点已经达到了叶子节点,无须继续分裂了)。一直到不能够再分裂为止。注意整个决策树形成过程中没有进行剪枝。

(4)按照步骤1~3建立大量的决策树,这样就构成了随机森林了。

支持向量机:

  支持向量机是一种用来解决二分类问题的机器学习算法,它通过在样本空间中找到一个划分超平面,将不同类别的样本分开,同时使得两个点集到此平面的最小距离最大,两个点集中的边缘点到此平面的距离最大。在理想情况中,两个类别的样本之间存在着清晰的划分超平面,但我们在实际工作中处理的任务上并不一定都是这种清晰线性可分的,对于这种清晰线性可分的任务,我们学习一个线性分类器,也称为硬间隔支持向量机;当训练数据数据近似线性可分时,也学习一个线性的分类器,即软间隔支持向量机;当训练数据线性不可分时,我们可以通过使用核技巧及软间隔最大化,学习非线性支持向量机。

3.涉及到的技术难点与解决思路

  最开始的时候对数据的分析和处理不到位,忘记了对文本特征进行编码处理,导致直接使用sklearn库构建线性回归模型的时候报了错误,此时还没有注意到是因为特征编码的问题,通过在网上查找资料,学习机器学习数据的处理流程,才发现需要对文本特征进行特征编码处理。特征编码处理后,代码报错问题解决。

三、机器学习的实现步骤

1.导入数据

每一行表示一个街区,共有10个属性,分别是经度、纬度、房屋年龄中位数、总房间数、总卧室数、人口数、家庭数、收入中位数、房屋价值中位数、离大海的距离

2.查看数据集基本信息

 

总共有20640个街区的信息,其中缺少207个街区的total_bedrooms信息

3.查看数据描述

 

卧室总数total_bedrooms为20433,所以空值被忽略了,std是标准差,揭示数值的分散度,25%的街区房屋年龄中位数小于18,50%的小于19,75%的小于37,这些值通常称为第25个百分位数(第一个四分位数)、中位数、第75个百分位数(第三个四分位数),不同大小的分位数如果很接近,说明数据很集中,集中在某个段里。

4.数据清洗

 

首先对缺失的数据进行处理,然后对文本型数据进行编码。

 

最后对数据进行划分,训练集与测试集以8:2进行划分。

 

5.绘制数据分布图

 

① 收入中位不是美元,而是经过缩放调整的,过高收入的中位数会变为15,过低会变成0.5,也就是设置了阈值。

② 房屋年龄中位数housing_median_age和房屋价值中位数median_house_value也被设了上限,房屋价值中位数是目标属性,我们的机器学习算法学习到的价格不会超过该界限。有两种处理方法:

对于设了上限的标签,重新收集合适的标签

将这些街区从训练集中移除,也从测试集中移除,因为这会导致不准确

③ 这些属性有不同的量度,会在后续讨论特征缩放

④ 柱状图多为长尾的,是有偏的,对于某些机器学习算法,这会使得检测规律变得困难,后面我们将尝试变换处理这些属性,使其成为正态分布,比如log变换。

# 探索性数据分析
# 查看房价的分布情况
sns.distplot(housing_data['median_house_value'], bins=50)
plt.show()

# 查看房价与房间数的关系,双变量关系图
sns.jointplot(x='total_rooms', y='median_house_value', data=housing_data)
plt.show()

# 查看房价与人口数的关系
sns.jointplot(x='population', y='median_house_value', data=housing_data)
plt.show()

 

# 查看房价与收入的关系
sns.jointplot(x='median_income', y='median_house_value', data=housing_data)
plt.show()

6.特征相关性分析

 

房价与收入的相关性最高,其次是房间数。

7.线性回归

 

然后对训练的模型进行评估。

 

8.决策树模型

 

误差为0,应该是出现了严重的过拟合,所以使用交叉验证进行评估。

 

9.随机森林模型

 

10.支持向量机模型

 

四、总结

  在本次课程设计中,完成了对房价的预测,了解了完整的机器学习流程。

  首先理解实际问题,抽象为机器学习能处理的数学问题。机器学习的特征工程和模型训练通常都是一件非常耗时的过程,胡乱尝试时间成本是非常高的。深入理解要处理的问题,能避免走很多弯路。理解问题,包括明确可以获得什么样的数据,机器学习的目标是一个分类、回归还是聚类。如果都不是的话,要考虑将它们转变为机器学习问题。

  然后是获取数据。获取数据包括获取原始数据以及从原始数据中经过特征工程从中提取训练、测试数据。机器学习比赛中原始数据都是直接提供的,但是实际问题需要自己获得原始数据。“ 数据决定机器学习结果的上限,而算法只是尽可能的逼近这个上限”,可见数据在机器学习中的作用。总的来说数据要有具有“代表性”,否则必然会过拟合。对于分类问题,数据偏斜不能过于严重,不同类别的数据数量不要有数个数量级的差距。

  有了数据以后需要对数据进行特征工程。特征工程包括从原始数据中特征构建、特征提取、特征选择,非常有讲究。深入理解实际业务场景下的问题,丰富的机器学习经验能帮助我们更好的处理特征工程。特征工程做的好能发挥原始数据的最大效力,往往能够使得算法的效果和性能得到显著的提升,有时能使简单的模型的效果比复杂的模型效果好。数据预处理、数据清洗是很关键的步骤,往往能够使得算法的效果和性能得到显著提高。归一化、离散化、因子化、缺失值处理、去除共线性等,数据挖掘过程中很多时间就花在它们上面。

  然后是构建模型。使用skleran库可以很容易的构建模型,但是光构建出模型还不够,还需要对模型进行诊断,判断模型是否过拟合或者欠拟合等,常见的方法如:交叉验证,绘制学习曲线等。过拟合的基本调优思路是增加训练的数据量,降低模型复杂度。欠拟合的基本调优思路是提高特征数量和质量,增加模型复杂度。

  最后验证模型,进行误差分析。通过测试数据,验证模型的有效性,通过观察误差样本,分析误差产生的原因(是参数的问题还是算法选择的问题,是特征的问题还是数据本身的问题 …)。误差分析主要是分析出误差来源于 算法 、 特征 、数据。 。

以下附上完整代码:

# 导入第三方库
import pandas as pd
import numpy as np
import matplotlib as mpl
import matplotlib.pyplot as plt
import seaborn as sns

# 解决中文显示问题
mpl.rcParams['font.sans-serif'] = 'SimHei'
mpl.rcParams['axes.unicode_minus'] = False

# 数据导入
housing_data = pd.read_csv('房价数据集.csv')
housing_data.head(10)

# 查看数据集的基本信息
housing_data.info()

# 查看非数值的项——距离海洋的距离
housing_data["ocean_proximity"].value_counts()

# 查看数值型数据的摘要
housing_data.describe()

# 绘制柱状图
housing_data.hist(bins=50, figsize=(20, 15))
plt.show()

# 探索性数据分析
# 查看房价的分布情况
sns.distplot(housing_data['median_house_value'], bins=50)
plt.show()

# 查看房价与房间数的关系,双变量关系图
sns.jointplot(x='total_rooms', y='median_house_value', data=housing_data)
plt.show()

# 查看房价与人口数的关系
sns.jointplot(x='population', y='median_house_value', data=housing_data)
plt.show()

# 查看房价与收入的关系
sns.jointplot(x='median_income', y='median_house_value', data=housing_data)
plt.show()

# 查看房价与经度的关系
sns.jointplot(x='longitude', y='median_house_value', data=housing_data)
plt.show()

# 查看房价与纬度的关系
sns.jointplot(x='latitude', y='median_house_value', data=housing_data)
plt.show()

# 查看房价分布
housing_data['median_income'].plot.hist()

# 首先创建一个类别属性
housing_data['income_cat'] = np.ceil(housing_data['median_income']/1.5)
housing_data['income_cat'].value_counts(ascending=True).plot.bar()

# 将5以上的数据先归为一类
housing_data['income_cat'] = housing_data['income_cat'].where(housing_data['income_cat']<5 ,5 )

# 查看原始数据概率分布
housing_data['income_cat'].value_counts(normalize=True)

# 查看随机拆分数据后的概率分布
from sklearn.model_selection import train_test_split
train, test = train_test_split(housing_data, test_size = 0.2 , random_state=2020)
for data in (train, test):
    print('概率分布分别为',data['income_cat'].value_counts(normalize=True))

# 查看分层抽样概率分布,训练集和测试集几乎相同
from sklearn.model_selection import StratifiedShuffleSplit
split = StratifiedShuffleSplit(n_splits=1, test_size=0.2, random_state=2020)
for train_index, test_index in split.split(housing_data, housing_data['income_cat']):
    train = housing_data.loc[train_index]
    test = housing_data.loc[test_index]

for data in (train, test):
    print('概率分布分别为',data['income_cat'].value_counts(normalize=True))

for _ in (train, test, housing_data):
    _.drop('income_cat',axis=1,inplace=True)


# 地理相关数据进行可视化
# 对数据进行deep copy
housing = housing_data.copy()

# 地理信息可视化
housing.plot.scatter(x='longitude', y='latitude', alpha=0.1, s=housing['population']/100, 
                     label='population', c='median_house_value', cmap=plt.get_cmap('jet'), colorbar=True)
plt.legend()


# 特征相关分析,绝对值越大,相关性越强(可根据相关性强因素对数据进行分层采样)
corr = housing.corr()
corr['median_house_value'].sort_values(ascending=False)


# 绘制各属性关系图
# 这里绘制相关度大于0.1的
corrfea = list(corr['median_house_value'][(abs(corr['median_house_value'])>0.1)&(corr['median_house_value']!=-1)].index)
sns.pairplot(housing[corrfea], kind='scatter', diag_kind='kde')
plt.show()


# 对属性进行组合
housing["rooms_per_household"] = housing["total_rooms"]/housing["households"]
housing["bedrooms_per_room"] = housing["total_bedrooms"]/housing["total_rooms"]
housing["population_per_household"]=housing["population"]/housing["households"]

# 查看属性相关性
corr = housing.corr()
corr['median_house_value'].sort_values(ascending=False)

# 数据清洗
housing = train.drop('median_house_value', axis=1)
housing_label = train['median_house_value'].copy()

# 数值型数据缺失值处理
# 导入缺失值处理
from sklearn.impute import SimpleImputer
#Imputer只能算出数值属性
imputer = SimpleImputer(strategy='median')
housing_num = housing.drop('ocean_proximity', axis=1)
imputer.fit(housing_num)
SimpleImputer(add_indicator=False, copy=True, fill_value=None,
              missing_values='nan', strategy='median', verbose=0)

# imputer 对缺失值转换后为二维数组
X = imputer.transform(housing_num)

housing_tr = pd.DataFrame(X, columns=housing_num.columns)
housing_tr.head(2)

# 文本型数据缺失值处理
# LabelEncoder,只能用于单个特征列
from sklearn.preprocessing import OrdinalEncoder

housing_cat = housing[['ocean_proximity']]
encoder = OrdinalEncoder()
housing_cat_encoder = encoder.fit_transform(housing_cat)

housing_cat_encoder

#onehot讲数值转化为
from sklearn.preprocessing import OneHotEncoder

#默认sparse = True生成稀疏矩阵,False为原列表
encoder = OneHotEncoder()
housing_cat_encoder = encoder.fit_transform(housing_cat)

housing_cat_encoder


# 使用sparse=False或者 toarray()将稀疏矩阵转化为密集数组
housing_cat_encoder.toarray()


# 将BaseEstimator作为基类获取fit_transform方法,将TransformerMixin作为基类获取get_params()和set_params()方法
from sklearn.base import BaseEstimator, TransformerMixin

# 定义需组合特征位置
rooms_ix, bedrooms_ix, population_ix, household_ix = 3, 4, 5, 6

class CombinedAttributesAdder(BaseEstimator, TransformerMixin):
    def __init__(self, add_bedrooms_per_room = True):
        self.add_bedrooms_per_room = add_bedrooms_per_room
    
    def fit(self, X, y=None):
        return self
    
    def transform(self, X):
        rooms_per_household = X[:,rooms_ix]/X[:,household_ix]
        population_per_household = X[:,population_ix]/X[:,household_ix]
        if self.add_bedrooms_per_room:
            add_bedrooms_per_room = X[:,bedrooms_ix]/X[:,rooms_ix]
            return np.c_[X, rooms_per_household, population_per_household, add_bedrooms_per_room]
        else:
            return np.c_[X, rooms_per_household, population_per_household]
        
attr_adder = CombinedAttributesAdder(add_bedrooms_per_room=True)       
housing_extra_feature = attr_adder.fit_transform(housing.values)
housing_extra_attributes = pd.DataFrame(housing_extra_feature,
                                       columns=list(housing.columns)+['rooms_per_household', 'population_per_household', 'add_bedrooms_per_room'],
                                       index=housing.index)
housing_extra_attributes.head(3)

# 特征缩放
# 线性函数归一化(Min-Max scaling)
# 标准化(standardization)
from sklearn.pipeline import Pipeline
from sklearn.preprocessing import StandardScaler

num_pipeline = Pipeline([
    ('imputer', SimpleImputer(strategy='median')),
    ('combine', CombinedAttributesAdder(add_bedrooms_per_room=True)),
    ('std_scaler', StandardScaler())
])

housing_num_tr = num_pipeline.fit_transform(housing_num)


# 实现Feature union的功能
from sklearn.compose import ColumnTransformer

# 定义不同类型特征的名称
num_attribs = list(housing_num.columns)
cat_attribs = ['ocean_proximity']

full_pipeline = ColumnTransformer([
    ('num',num_pipeline, num_attribs),
    ('cat',OneHotEncoder(),cat_attribs)
])

housing_prepared = full_pipeline.fit_transform(housing)


# 导入基础包
from sklearn.linear_model import LinearRegression

linear = LinearRegression()
linear.fit(housing_prepared, housing_label)
LinearRegression(copy_X=True, fit_intercept=True, n_jobs=None, normalize=False)
some_data = housing.iloc[:5]
some_labels = housing_label[:5]
some_data_prepared = full_pipeline.transform(some_data)

print('predict_data',linear.predict(some_data_prepared))
print('true_label',some_labels)


# 使用EMSE进行模型评估,sklearn中未提供RMSE,但是提供了MSE
from sklearn.metrics import mean_squared_error

lin_mse = mean_squared_error(housing_label, linear.predict(housing_prepared))
lin_rmse = np.sqrt(lin_mse)
print(lin_rmse)


# 误差范围过大
print('房价的上四分位:',np.quantile(housing_label, 0.75))
print('房价的下四分位:',np.quantile(housing_label, 0.25))
print('房价的四分位距IQR:',np.quantile(housing_label, 0.75)-np.quantile(housing_label, 0.25))
print('误差的标准差:', lin_rmse)


## 决策树模型
from sklearn.tree import DecisionTreeRegressor

tree_reg = DecisionTreeRegressor(random_state=2020)
tree_reg.fit(housing_prepared, housing_label)
tree_mse = mean_squared_error(housing_label, tree_reg.predict(housing_prepared))
tree_rmse = np.sqrt(tree_mse)
print(tree_rmse)
# 严重过拟合


# 使用交叉验证进行评估
from sklearn.model_selection import cross_val_score

score = cross_val_score(tree_reg, housing_prepared, housing_label, 
                       scoring='neg_mean_squared_error', cv=10)
rmse = np.sqrt(-score)
print(rmse)


def display(score):
    print('score',score)
    print('mean', score.min())
    print('standard deviation',score.std())
    
display(rmse)


lin_scores = cross_val_score(linear, housing_prepared, housing_label,
                             scoring="neg_mean_squared_error", cv=10)
lin_rmse_scores = np.sqrt(-lin_scores)
display(lin_rmse_scores)


# 随机森林模型
from sklearn.ensemble import RandomForestRegressor

forest_reg = RandomForestRegressor(n_estimators=100,random_state=2020)
forest_reg.fit(housing_prepared, housing_label)
forest_score = cross_val_score(forest_reg,housing_prepared, housing_label,scoring='neg_mean_squared_error',
                       cv=10)
forest_rmse = np.sqrt(-forest_score)
display(forest_rmse)


# 支持向量机模型
from sklearn.svm import SVR

svr = SVR(kernel='linear')
svr.fit(housing_prepared, housing_label)
svr_scores = cross_val_score(svr, housing_prepared, housing_label,
                            scoring='neg_mean_squared_error', cv=10)
svr_rmse = np.sqrt(-svr_scores)
display(svr_rmse)

 

posted @ 2022-12-24 16:58  呵·呵  阅读(209)  评论(0编辑  收藏  举报